tcl9.0.1/0000755000175000017500000000000014731057554011557 5ustar sergeisergeitcl9.0.1/unix/0000755000175000017500000000000014731057554012542 5ustar sergeisergeitcl9.0.1/unix/tclAppInit.c0000644000175000017500000001135714726623136014763 0ustar sergeisergei/* * tclAppInit.c -- * * Provides a default version of the main program and Tcl_AppInit * procedure for tclsh and other Tcl-based applications (without Tk). * * Copyright (c) 1993 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. * Copyright (c) 1998-1999 Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tcl.h" #if TCL_MAJOR_VERSION < 9 # if defined(USE_TCL_STUBS) # error "Don't build with USE_TCL_STUBS!" # endif # if TCL_MINOR_VERSION < 7 # define Tcl_LibraryInitProc Tcl_PackageInitProc # define Tcl_StaticLibrary Tcl_StaticPackage # endif #endif #ifdef TCL_TEST extern Tcl_LibraryInitProc Tcltest_Init; extern Tcl_LibraryInitProc Tcltest_SafeInit; #endif /* TCL_TEST */ #ifdef TCL_XT_TEST extern void XtToolkitInitialize(void); extern Tcl_LibraryInitProc Tclxttest_Init; #endif /* TCL_XT_TEST */ /* * The following #if block allows you to change the AppInit function by using * a #define of TCL_LOCAL_APPINIT instead of rewriting this entire file. The * #if checks for that #define and uses Tcl_AppInit if it does not exist. */ #ifndef TCL_LOCAL_APPINIT #define TCL_LOCAL_APPINIT Tcl_AppInit #endif #ifndef MODULE_SCOPE # define MODULE_SCOPE extern #endif MODULE_SCOPE int TCL_LOCAL_APPINIT(Tcl_Interp *); MODULE_SCOPE int main(int, char **); /* * The following #if block allows you to change how Tcl finds the startup * script, prime the library or encoding paths, fiddle with the argv, etc., * without needing to rewrite Tcl_Main() */ #ifdef TCL_LOCAL_MAIN_HOOK MODULE_SCOPE int TCL_LOCAL_MAIN_HOOK(int *argc, char ***argv); #endif /* *---------------------------------------------------------------------- * * main -- * * This is the main program for the application. * * Results: * None: Tcl_Main never returns here, so this procedure never returns * either. * * Side effects: * Just about anything, since from here we call arbitrary Tcl code. * *---------------------------------------------------------------------- */ int main( int argc, /* Number of command-line arguments. */ char *argv[]) /* Values of command-line arguments. */ { #ifdef TCL_XT_TEST XtToolkitInitialize(); #endif #ifdef TCL_LOCAL_MAIN_HOOK TCL_LOCAL_MAIN_HOOK(&argc, &argv); #elif (TCL_MAJOR_VERSION > 8 || TCL_MINOR_VERSION > 6) && (!defined(_WIN32) || defined(UNICODE)) /* New in Tcl 8.7. This doesn't work on Windows without UNICODE */ TclZipfs_AppHook(&argc, &argv); #endif Tcl_Main(argc, argv, TCL_LOCAL_APPINIT); return 0; /* Needed only to prevent compiler warning. */ } /* *---------------------------------------------------------------------- * * Tcl_AppInit -- * * This procedure performs application-specific initialization. Most * applications, especially those that incorporate additional packages, * will have their own version of this procedure. * * Results: * Returns a standard Tcl completion code, and leaves an error message in * the interp's result if an error occurs. * * Side effects: * Depends on the startup script. * *---------------------------------------------------------------------- */ int Tcl_AppInit( Tcl_Interp *interp) /* Interpreter for application. */ { if (Tcl_Init(interp) == TCL_ERROR) { return TCL_ERROR; } #ifdef TCL_XT_TEST if (Tclxttest_Init(interp) == TCL_ERROR) { return TCL_ERROR; } #endif #ifdef TCL_TEST if (Tcltest_Init(interp) == TCL_ERROR) { return TCL_ERROR; } Tcl_StaticLibrary(interp, "Tcltest", Tcltest_Init, Tcltest_SafeInit); #endif /* TCL_TEST */ /* * Call the init procedures for included packages. Each call should look * like this: * * if (Mod_Init(interp) == TCL_ERROR) { * return TCL_ERROR; * } * * where "Mod" is the name of the module. (Dynamically-loadable packages * should have the same entry-point name.) */ /* * Call Tcl_CreateObjCommand for application-specific commands, if they * weren't already created by the init procedures called above. */ /* * Specify a user-specific startup file to invoke if the application is * run interactively. Typically the startup file is "~/.apprc" where "app" * is the name of the application. If this line is deleted then no * user-specific startup file will be run under any conditions. */ #ifdef DJGPP #define INITFILENAME "tclshrc.tcl" #else #define INITFILENAME ".tclshrc" #endif (void) Tcl_EvalEx(interp, "set tcl_rcFileName [file tildeexpand ~/" INITFILENAME "]", -1, TCL_EVAL_GLOBAL); return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclEpollNotfy.c0000644000175000017500000005744114726623136015516 0ustar sergeisergei/* * tclEpollNotfy.c -- * * This file contains the implementation of the epoll()-based * Linux-specific notifier, which is the lowest-level part of the Tcl * event loop. This file works together with generic/tclNotify.c. * * Copyright © 1995-1997 Sun Microsystems, Inc. * Copyright © 2016 Lucio Andrés Illanes Albornoz * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is * in tclMacOSXNotify.c */ #if defined(NOTIFIER_EPOLL) && TCL_THREADS #ifndef _GNU_SOURCE # define _GNU_SOURCE /* For pipe2(2) */ #endif #include #include #include #ifdef HAVE_EVENTFD #include #endif /* HAVE_EVENTFD */ #include /* * This structure is used to keep track of the notifier info for a registered * file. */ struct PlatformEventData; typedef struct FileHandler { int fd; int mask; /* Mask of desired events: TCL_READABLE, * etc. */ int readyMask; /* Mask of events that have been seen since * the last time file handlers were invoked * for this file. */ Tcl_FileProc *proc; /* Function to call, in the style of * Tcl_CreateFileHandler. */ void *clientData; /* Argument to pass to proc. */ struct FileHandler *nextPtr;/* Next in list of all files we care about. */ LIST_ENTRY(FileHandler) readyNode; /* Next/previous in list of FileHandlers asso- * ciated with regular files (S_IFREG) that are * ready for I/O. */ struct PlatformEventData *pedPtr; /* Pointer to PlatformEventData associating this * FileHandler with epoll(7) events. */ } FileHandler; /* * The following structure associates a FileHandler and the thread that owns * it with the file descriptors of interest and their event masks passed to * epoll_ctl(2) and their corresponding event(s) returned by epoll_wait(2). */ struct ThreadSpecificData; struct PlatformEventData { FileHandler *filePtr; struct ThreadSpecificData *tsdPtr; }; /* * The following structure is what is added to the Tcl event queue when file * handlers are ready to fire. */ typedef struct { Tcl_Event header; /* Information that is standard for all * events. */ int fd; /* File descriptor that is ready. Used to find * the FileHandler structure for the file * (can't point directly to the FileHandler * structure because it could go away while * the event is queued). */ } FileHandlerEvent; /* * The following static structure contains the state information for the * epoll based implementation of the Tcl notifier. One of these structures is * created for each thread that is using the notifier. */ LIST_HEAD(PlatformReadyFileHandlerList, FileHandler); typedef struct ThreadSpecificData { FileHandler *triggerFilePtr; FileHandler *firstFileHandlerPtr; /* Pointer to head of file handler list. */ struct PlatformReadyFileHandlerList firstReadyFileHandlerPtr; /* Pointer to head of list of FileHandlers * associated with regular files (S_IFREG) * that are ready for I/O. */ pthread_mutex_t notifierMutex; /* Mutex protecting notifier termination in * TclpFinalizeNotifier. */ #ifdef HAVE_EVENTFD int triggerEventFd; /* eventfd(2) used by other threads to wake * up this thread for inter-thread IPC. */ #else int triggerPipe[2]; /* pipe(2) used by other threads to wake * up this thread for inter-thread IPC. */ #endif /* HAVE_EVENTFD */ int eventsFd; /* epoll(7) file descriptor used to wait for * fds */ struct epoll_event *readyEvents; /* Pointer to at most maxReadyEvents events * returned by epoll_wait(2). */ size_t maxReadyEvents; /* Count of epoll_events in readyEvents. */ int asyncPending; /* True when signal triggered thread. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * Forward declarations. */ static void PlatformEventsControl(FileHandler *filePtr, ThreadSpecificData *tsdPtr, int op, int isNew); static void PlatformEventsInit(void); static int PlatformEventsTranslate(struct epoll_event *event); static int PlatformEventsWait(struct epoll_event *events, size_t numEvents, struct timeval *timePtr); /* * Incorporate the base notifier implementation. */ #include "tclUnixNotfy.c" /* *---------------------------------------------------------------------- * * TclpInitNotifier -- * * Initializes the platform specific notifier state. * * Results: * Returns a handle to the notifier state for this thread. * * Side effects: * If no initNotifierProc notifier hook exists, PlatformEventsInit is * called. * *---------------------------------------------------------------------- */ void * TclpInitNotifier(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); PlatformEventsInit(); return tsdPtr; } /* *---------------------------------------------------------------------- * * PlatformEventsControl -- * * This function registers interest for the file descriptor and the mask * of TCL_* bits associated with filePtr on the epoll file descriptor * associated with tsdPtr. * * Future calls to epoll_wait will return filePtr and tsdPtr alongside * with the event registered here via the PlatformEventData struct. * * Results: * None. * * Side effects: * - If adding a new file descriptor, a PlatformEventData struct will be * allocated and associated with filePtr. * - fstat is called on the file descriptor; if it is associated with a * regular file (S_IFREG,) filePtr is considered to be ready for I/O * and added to or deleted from the corresponding list in tsdPtr. * - If it is not associated with a regular file, the file descriptor is * added, modified concerning its mask of events of interest, or * deleted from the epoll file descriptor of the calling thread. * *---------------------------------------------------------------------- */ static void PlatformEventsControl( FileHandler *filePtr, ThreadSpecificData *tsdPtr, int op, int isNew) { struct epoll_event newEvent; struct PlatformEventData *newPedPtr; Tcl_StatBuf fdStat; newEvent.events = 0; if (filePtr->mask & (TCL_READABLE | TCL_EXCEPTION)) { newEvent.events |= EPOLLIN; } if (filePtr->mask & TCL_WRITABLE) { newEvent.events |= EPOLLOUT; } if (isNew) { newPedPtr = (struct PlatformEventData *) Tcl_Alloc(sizeof(struct PlatformEventData)); newPedPtr->filePtr = filePtr; newPedPtr->tsdPtr = tsdPtr; filePtr->pedPtr = newPedPtr; } newEvent.data.ptr = filePtr->pedPtr; /* * N.B. As discussed in Tcl_WaitForEvent(), epoll(7) does not support * regular files (S_IFREG). Therefore, filePtr is in these cases simply * added or deleted from the list of FileHandlers associated with regular * files belonging to tsdPtr. */ if (TclOSfstat(filePtr->fd, &fdStat) == -1) { Tcl_Panic("fstat: %s", strerror(errno)); } if (epoll_ctl(tsdPtr->eventsFd, op, filePtr->fd, &newEvent) == -1) { switch (errno) { case EPERM: switch (op) { case EPOLL_CTL_ADD: if (isNew) { LIST_INSERT_HEAD(&tsdPtr->firstReadyFileHandlerPtr, filePtr, readyNode); } break; case EPOLL_CTL_DEL: LIST_REMOVE(filePtr, readyNode); break; } break; default: Tcl_Panic("epoll_ctl: %s", strerror(errno)); } } return; } /* *---------------------------------------------------------------------- * * TclpFinalizeNotifier -- * * This function closes the eventfd and the epoll file descriptor and * frees the epoll_event structs owned by the thread of the caller. The * above operations are protected by tsdPtr->notifierMutex, which is * destroyed thereafter. * * Results: * None. * * Side effects: * While tsdPtr->notifierMutex is held: * - The per-thread eventfd(2) is closed, if non-zero, and set to -1. * - The per-thread epoll(7) fd is closed, if non-zero, and set to 0. * - The per-thread epoll_event structs are freed, if any, and set to 0. * * tsdPtr->notifierMutex is destroyed. * *---------------------------------------------------------------------- */ void TclpFinalizeNotifier( TCL_UNUSED(void *)) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); pthread_mutex_lock(&tsdPtr->notifierMutex); #ifdef HAVE_EVENTFD if (tsdPtr->triggerEventFd) { close(tsdPtr->triggerEventFd); tsdPtr->triggerEventFd = -1; } #else /* !HAVE_EVENTFD */ if (tsdPtr->triggerPipe[0]) { close(tsdPtr->triggerPipe[0]); tsdPtr->triggerPipe[0] = -1; } if (tsdPtr->triggerPipe[1]) { close(tsdPtr->triggerPipe[1]); tsdPtr->triggerPipe[1] = -1; } #endif /* HAVE_EVENTFD */ Tcl_Free(tsdPtr->triggerFilePtr->pedPtr); Tcl_Free(tsdPtr->triggerFilePtr); if (tsdPtr->eventsFd > 0) { close(tsdPtr->eventsFd); tsdPtr->eventsFd = 0; } if (tsdPtr->readyEvents) { Tcl_Free(tsdPtr->readyEvents); tsdPtr->maxReadyEvents = 0; } pthread_mutex_unlock(&tsdPtr->notifierMutex); if ((errno = pthread_mutex_destroy(&tsdPtr->notifierMutex))) { Tcl_Panic("pthread_mutex_destroy: %s", strerror(errno)); } } /* *---------------------------------------------------------------------- * * PlatformEventsInit -- * * This function abstracts creating a kqueue fd via the epoll_create * system call and allocating memory for the epoll_event structs in * tsdPtr for the thread of the caller. * * Results: * None. * * Side effects: * The following per-thread entities are initialised: * - notifierMutex is initialised. * - The eventfd(2) is created w/ EFD_CLOEXEC and EFD_NONBLOCK. * - The epoll(7) fd is created w/ EPOLL_CLOEXEC. * - A FileHandler struct is allocated and initialised for the * eventfd(2), registering interest for TCL_READABLE on it via * PlatformEventsControl(). * - readyEvents and maxReadyEvents are initialised with 512 * epoll_events. * *---------------------------------------------------------------------- */ static void PlatformEventsInit(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); FileHandler *filePtr; errno = pthread_mutex_init(&tsdPtr->notifierMutex, NULL); if (errno) { Tcl_Panic("Tcl_InitNotifier: %s", "could not create mutex"); } filePtr = (FileHandler *) Tcl_Alloc(sizeof(FileHandler)); #ifdef HAVE_EVENTFD tsdPtr->triggerEventFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); if (tsdPtr->triggerEventFd <= 0) { Tcl_Panic("Tcl_InitNotifier: %s", "could not create trigger eventfd"); } filePtr->fd = tsdPtr->triggerEventFd; #else /* !HAVE_EVENTFD */ if (pipe2(tsdPtr->triggerPipe, O_CLOEXEC | O_NONBLOCK) != 0) { Tcl_Panic("Tcl_InitNotifier: %s", "could not create trigger pipe"); } filePtr->fd = tsdPtr->triggerPipe[0]; #endif /* HAVE_EVENTFD */ tsdPtr->triggerFilePtr = filePtr; if ((tsdPtr->eventsFd = epoll_create1(EPOLL_CLOEXEC)) == -1) { Tcl_Panic("epoll_create1: %s", strerror(errno)); } filePtr->mask = TCL_READABLE; PlatformEventsControl(filePtr, tsdPtr, EPOLL_CTL_ADD, 1); if (!tsdPtr->readyEvents) { tsdPtr->maxReadyEvents = 512; tsdPtr->readyEvents = (struct epoll_event *) Tcl_Alloc( tsdPtr->maxReadyEvents * sizeof(tsdPtr->readyEvents[0])); } LIST_INIT(&tsdPtr->firstReadyFileHandlerPtr); } /* *---------------------------------------------------------------------- * * PlatformEventsTranslate -- * * This function translates the platform-specific mask of returned events * in eventPtr to a mask of TCL_* bits. * * Results: * Returns the translated mask. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PlatformEventsTranslate( struct epoll_event *eventPtr) { int mask; mask = 0; if (eventPtr->events & (EPOLLIN | EPOLLHUP)) { mask |= TCL_READABLE; } if (eventPtr->events & EPOLLOUT) { mask |= TCL_WRITABLE; } if (eventPtr->events & EPOLLERR) { mask |= TCL_EXCEPTION; } return mask; } /* *---------------------------------------------------------------------- * * PlatformEventsWait -- * * This function abstracts waiting for I/O events via epoll_wait. * * Results: * Returns -1 if epoll_wait failed. Returns 0 if polling and if no events * became available whilst polling. Returns a pointer to and the count of * all returned events in all other cases. * * Side effects: * gettimeofday(2), epoll_wait(2), and gettimeofday(2) are called, in the * specified order. * If timePtr specifies a positive value, it is updated to reflect the * amount of time that has passed; if its value would {under, over}flow, * it is set to zero. * *---------------------------------------------------------------------- */ static int PlatformEventsWait( struct epoll_event *events, size_t numEvents, struct timeval *timePtr) { int numFound; struct timeval tv0, tv1, tv_delta; int timeout; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* * If timePtr is NULL, epoll_wait(2) will wait indefinitely. If it * specifies a timeout of {0,0}, epoll_wait(2) will poll. Otherwise, the * timeout will simply be converted to milliseconds. */ if (!timePtr) { timeout = -1; } else if (!timePtr->tv_sec && !timePtr->tv_usec) { timeout = 0; } else { timeout = (int) timePtr->tv_sec * 1000; if (timePtr->tv_usec) { timeout += (int) timePtr->tv_usec / 1000; } } /* * Call (and possibly block on) epoll_wait(2) and substract the delta of * gettimeofday(2) before and after the call from timePtr if the latter is * not NULL. Return the number of events returned by epoll_wait(2). */ gettimeofday(&tv0, NULL); numFound = epoll_wait(tsdPtr->eventsFd, events, (int) numEvents, timeout); gettimeofday(&tv1, NULL); if (timePtr && (timePtr->tv_sec && timePtr->tv_usec)) { timersub(&tv1, &tv0, &tv_delta); if (!timercmp(&tv_delta, timePtr, >)) { timersub(timePtr, &tv_delta, timePtr); } else { timePtr->tv_sec = 0; timePtr->tv_usec = 0; } } if (tsdPtr->asyncPending) { tsdPtr->asyncPending = 0; TclAsyncMarkFromNotifier(); } return numFound; } /* *---------------------------------------------------------------------- * * TclpCreateFileHandler -- * * This function registers a file handler with the epoll notifier of the * thread of the caller. * * Results: * None. * * Side effects: * Creates a new file handler structure. * PlatformEventsControl() is called for the new file handler structure. * *---------------------------------------------------------------------- */ void TclpCreateFileHandler( int fd, /* Handle of stream to watch. */ int mask, /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, and TCL_EXCEPTION: indicates * conditions under which proc should be * called. */ Tcl_FileProc *proc, /* Function to call for each selected * event. */ void *clientData) /* Arbitrary data to pass to proc. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); FileHandler *filePtr = LookUpFileHandler(tsdPtr, fd, NULL); int isNew = (filePtr == NULL); if (isNew) { filePtr = (FileHandler *) Tcl_Alloc(sizeof(FileHandler)); filePtr->fd = fd; filePtr->readyMask = 0; filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; tsdPtr->firstFileHandlerPtr = filePtr; } filePtr->proc = proc; filePtr->clientData = clientData; filePtr->mask = mask; PlatformEventsControl(filePtr, tsdPtr, isNew ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, isNew); } /* *---------------------------------------------------------------------- * * TclpDeleteFileHandler -- * * Cancel a previously-arranged callback arrangement for a file on the * epoll file descriptor of the thread of the caller. * * Results: * None. * * Side effects: * If a callback was previously registered on file, remove it. * PlatformEventsControl() is called for the file handler structure. * The PlatformEventData struct associated with the new file handler * structure is freed. * *---------------------------------------------------------------------- */ void TclpDeleteFileHandler( int fd) /* Stream id for which to remove callback * function. */ { FileHandler *filePtr, *prevPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* * Find the entry for the given file (and return if there isn't one). */ filePtr = LookUpFileHandler(tsdPtr, fd, &prevPtr); if (filePtr == NULL) { return; } /* * Update the check masks for this file. */ PlatformEventsControl(filePtr, tsdPtr, EPOLL_CTL_DEL, 0); if (filePtr->pedPtr) { Tcl_Free(filePtr->pedPtr); } /* * Clean up information in the callback record. */ if (prevPtr == NULL) { tsdPtr->firstFileHandlerPtr = filePtr->nextPtr; } else { prevPtr->nextPtr = filePtr->nextPtr; } Tcl_Free(filePtr); } /* *---------------------------------------------------------------------- * * TclpWaitForEvent -- * * This function is called by Tcl_DoOneEvent to wait for new events on * the message queue. If the block time is 0, then TclpWaitForEvent just * polls without blocking. * * The waiting logic is implemented in PlatformEventsWait. * * Results: * Returns -1 if PlatformEventsWait() would block forever, otherwise * returns 0. * * Side effects: * Queues file events that are detected by PlatformEventsWait(). * *---------------------------------------------------------------------- */ int TclpWaitForEvent( const Tcl_Time *timePtr) /* Maximum block time, or NULL. */ { FileHandler *filePtr; Tcl_Time vTime; struct timeval timeout, *timeoutPtr; /* Impl. notes: timeout & timeoutPtr are used * if, and only if threads are not enabled. * They are the arguments for the regular * epoll_wait() used when the core is not * thread-enabled. */ int mask, numFound, numEvent; struct PlatformEventData *pedPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); int numQueued; ssize_t i; /* * Set up the timeout structure. Note that if there are no events to check * for, we return with a negative result rather than blocking forever. */ if (timePtr != NULL) { /* * TIP #233 (Virtualized Time). Is virtual time in effect? And do we * actually have something to scale? If yes to both then we call the * handler to do this scaling. */ if (timePtr->sec != 0 || timePtr->usec != 0) { vTime = *timePtr; TclScaleTime(&vTime); timePtr = &vTime; } timeout.tv_sec = timePtr->sec; timeout.tv_usec = timePtr->usec; timeoutPtr = &timeout; } else { timeoutPtr = NULL; } /* * Walk the list of FileHandlers associated with regular files (S_IFREG) * belonging to tsdPtr, queue Tcl events for them, and update their mask * of events of interest. * * As epoll(7) does not support regular files, the behaviour of * {select,poll}(2) is simply simulated here: fds associated with regular * files are added to this list by PlatformEventsControl() and processed * here before calling (and possibly blocking) on PlatformEventsWait(). */ numQueued = 0; LIST_FOREACH(filePtr, &tsdPtr->firstReadyFileHandlerPtr, readyNode) { mask = 0; if (filePtr->mask & TCL_READABLE) { mask |= TCL_READABLE; } if (filePtr->mask & TCL_WRITABLE) { mask |= TCL_WRITABLE; } /* * Don't bother to queue an event if the mask was previously non-zero * since an event must still be on the queue. */ if (filePtr->readyMask == 0) { FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) Tcl_Alloc(sizeof(FileHandlerEvent)); fileEvPtr->header.proc = FileHandlerEventProc; fileEvPtr->fd = filePtr->fd; Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); numQueued++; } filePtr->readyMask = mask; } /* * If any events were queued in the above loop, force PlatformEventsWait() * to poll as there already are events that need to be processed at this * point. */ if (numQueued) { timeout.tv_sec = 0; timeout.tv_usec = 0; timeoutPtr = &timeout; } /* * Wait or poll for new events, queue Tcl events for the FileHandlers * corresponding to them, and update the FileHandlers' mask of events of * interest registered by the last call to Tcl_CreateFileHandler(). * * Events for the eventfd(2)/trigger pipe are processed here in order to * facilitate inter-thread IPC. If another thread intends to wake up this * thread whilst it's blocking on PlatformEventsWait(), it write(2)s to * the eventfd(2)/trigger pipe (see Tcl_AlertNotifier(),) which in turn * will cause PlatformEventsWait() to return immediately. */ numFound = PlatformEventsWait(tsdPtr->readyEvents, tsdPtr->maxReadyEvents, timeoutPtr); for (numEvent = 0; numEvent < numFound; numEvent++) { pedPtr = (struct PlatformEventData *) tsdPtr->readyEvents[numEvent].data.ptr; filePtr = pedPtr->filePtr; mask = PlatformEventsTranslate(&tsdPtr->readyEvents[numEvent]); #ifdef HAVE_EVENTFD if (filePtr->fd == tsdPtr->triggerEventFd) { uint64_t eventFdVal; i = read(tsdPtr->triggerEventFd, &eventFdVal, sizeof(eventFdVal)); if ((i != sizeof(eventFdVal)) && (errno != EAGAIN)) { Tcl_Panic("%s: read from %p->triggerEventFd: %s", "Tcl_WaitForEvent", tsdPtr, strerror(errno)); } continue; } #else /* !HAVE_EVENTFD */ if (filePtr->fd == tsdPtr->triggerPipe[0]) { char triggerPipeVal; i = read(tsdPtr->triggerPipe[0], &triggerPipeVal, sizeof(triggerPipeVal)); if ((i != sizeof(triggerPipeVal)) && (errno != EAGAIN)) { Tcl_Panic("%s: read from %p->triggerPipe[0]: %s", "Tcl_WaitForEvent", tsdPtr, strerror(errno)); } continue; } #endif /* HAVE_EVENTFD */ if (!mask) { continue; } /* * Don't bother to queue an event if the mask was previously non-zero * since an event must still be on the queue. */ if (filePtr->readyMask == 0) { FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) Tcl_Alloc(sizeof(FileHandlerEvent)); fileEvPtr->header.proc = FileHandlerEventProc; fileEvPtr->fd = filePtr->fd; Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); } filePtr->readyMask = mask; } return 0; } /* *---------------------------------------------------------------------- * * TclAsyncNotifier -- * * This procedure sets the async mark of an async handler to a * given value, if it is called from the target thread. * * Result: * True, when the handler will be marked, false otherwise. * * Side effects: * The signal may be resent to the target thread. * *---------------------------------------------------------------------- */ int TclAsyncNotifier( int sigNumber, /* Signal number. */ Tcl_ThreadId threadId, /* Target thread. */ void *clientData, /* Notifier data. */ int *flagPtr, /* Flag to mark. */ int value) /* Value of mark. */ { #if TCL_THREADS /* * WARNING: * This code most likely runs in a signal handler. Thus, * only few async-signal-safe system calls are allowed, * e.g. pthread_self(), sem_post(), write(). */ if (pthread_equal(pthread_self(), (pthread_t) threadId)) { ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData; *flagPtr = value; if (tsdPtr != NULL && !tsdPtr->asyncPending) { tsdPtr->asyncPending = 1; TclpAlertNotifier(tsdPtr); return 1; } return 0; } /* * Re-send the signal to the proper target thread. */ pthread_kill((pthread_t) threadId, sigNumber); #else (void)sigNumber; (void)threadId; (void)clientData; (void)flagPtr; (void)value; #endif return 0; } #endif /* NOTIFIER_EPOLL && TCL_THREADS */ #else TCL_MAC_EMPTY_FILE(unix_tclEpollNotfy_c) #endif /* !HAVE_COREFOUNDATION */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclKqueueNotfy.c0000644000175000017500000006036514726623136015701 0ustar sergeisergei/* * tclKqueueNotfy.c -- * * This file contains the implementation of the kqueue()-based * DragonFly/Free/Net/OpenBSD-specific notifier, which is the lowest- * level part of the Tcl event loop. This file works together with * generic/tclNotify.c. * * Copyright © 1995-1997 Sun Microsystems, Inc. * Copyright © 2016 Lucio Andrés Illanes Albornoz * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is * in tclMacOSXNotify.c */ #if defined(NOTIFIER_KQUEUE) && TCL_THREADS #include #include #include #include #include /* * This structure is used to keep track of the notifier info for a registered * file. */ struct PlatformEventData; typedef struct FileHandler { int fd; /* File descriptor that this is describing a * handler for. */ int mask; /* Mask of desired events: TCL_READABLE, * etc. */ int readyMask; /* Mask of events that have been seen since * the last time file handlers were invoked * for this file. */ Tcl_FileProc *proc; /* Function to call, in the style of * Tcl_CreateFileHandler. */ void *clientData; /* Argument to pass to proc. */ struct FileHandler *nextPtr;/* Next in list of all files we care about. */ LIST_ENTRY(FileHandler) readyNode; /* Next/previous in list of FileHandlers asso- * ciated with regular files (S_IFREG) that are * ready for I/O. */ struct PlatformEventData *pedPtr; /* Pointer to PlatformEventData associating this * FileHandler with kevent(2) events. */ } FileHandler; /* * The following structure associates a FileHandler and the thread that owns * it with the file descriptors of interest and their event masks passed to * kevent(2) and their corresponding event(s) returned by kevent(2). */ struct ThreadSpecificData; struct PlatformEventData { FileHandler *filePtr; struct ThreadSpecificData *tsdPtr; }; /* * The following structure is what is added to the Tcl event queue when file * handlers are ready to fire. */ typedef struct { Tcl_Event header; /* Information that is standard for all * events. */ int fd; /* File descriptor that is ready. Used to find * the FileHandler structure for the file * (can't point directly to the FileHandler * structure because it could go away while * the event is queued). */ } FileHandlerEvent; /* * The following static structure contains the state information for the * kqueue based implementation of the Tcl notifier. One of these structures is * created for each thread that is using the notifier. */ LIST_HEAD(PlatformReadyFileHandlerList, FileHandler); typedef struct ThreadSpecificData { FileHandler *firstFileHandlerPtr; /* Pointer to head of file handler list. */ struct PlatformReadyFileHandlerList firstReadyFileHandlerPtr; /* Pointer to head of list of FileHandlers * associated with regular files (S_IFREG) * that are ready for I/O. */ pthread_mutex_t notifierMutex; /* Mutex protecting notifier termination in * TclpFinalizeNotifier. */ int triggerPipe[2]; /* pipe(2) used by other threads to wake * up this thread for inter-thread IPC. */ int eventsFd; /* kqueue(2) file descriptor used to wait for * fds. */ struct kevent *readyEvents; /* Pointer to at most maxReadyEvents events * returned by kevent(2). */ size_t maxReadyEvents; /* Count of kevents in readyEvents. */ int asyncPending; /* True when signal triggered thread. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * Forward declarations of internal functions. */ static void PlatformEventsControl(FileHandler *filePtr, ThreadSpecificData *tsdPtr, int op, int isNew); static int PlatformEventsTranslate(struct kevent *eventPtr); static int PlatformEventsWait(struct kevent *events, size_t numEvents, struct timeval *timePtr); /* * Incorporate the base notifier implementation. */ #include "tclUnixNotfy.c" /* *---------------------------------------------------------------------- * * PlatformEventsControl -- * * This function registers interest for the file descriptor and the mask * of TCL_* bits associated with filePtr on the kqueue file descriptor * associated with tsdPtr. * * Future calls to kevent will return filePtr and tsdPtr alongside with * the event registered here via the PlatformEventData struct. * * Results: * None. * * Side effects: * - If adding a new file descriptor, a PlatformEventData struct will be * allocated and associated with filePtr. * - fstat is called on the file descriptor; if it is associated with * a regular file (S_IFREG,) filePtr is considered to be ready for I/O * and added to or deleted from the corresponding list in tsdPtr. * - If it is not associated with a regular file, the file descriptor is * added, modified concerning its mask of events of interest, or * deleted from the epoll file descriptor of the calling thread. * - If deleting a file descriptor, kevent(2) is called twice specifying * EVFILT_READ first and then EVFILT_WRITE (see note below.) * *---------------------------------------------------------------------- */ static void PlatformEventsControl( FileHandler *filePtr, ThreadSpecificData *tsdPtr, int op, int isNew) { int numChanges; struct kevent changeList[2]; struct PlatformEventData *newPedPtr; Tcl_StatBuf fdStat; if (isNew) { newPedPtr = (struct PlatformEventData *) Tcl_Alloc(sizeof(struct PlatformEventData)); newPedPtr->filePtr = filePtr; newPedPtr->tsdPtr = tsdPtr; filePtr->pedPtr = newPedPtr; } /* * N.B. As discussed in Tcl_WaitForEvent(), kqueue(2) does not reproduce * the `always ready' {select,poll}(2) behaviour for regular files * (S_IFREG) prior to FreeBSD 11.0-RELEASE. Therefore, filePtr is in these * cases simply added or deleted from the list of FileHandlers associated * with regular files belonging to tsdPtr. */ if (TclOSfstat(filePtr->fd, &fdStat) == -1) { Tcl_Panic("fstat: %s", strerror(errno)); } else if ((fdStat.st_mode & S_IFMT) == S_IFREG || (fdStat.st_mode & S_IFMT) == S_IFDIR || (fdStat.st_mode & S_IFMT) == S_IFLNK) { switch (op) { case EV_ADD: if (isNew) { LIST_INSERT_HEAD(&tsdPtr->firstReadyFileHandlerPtr, filePtr, readyNode); } break; case EV_DELETE: LIST_REMOVE(filePtr, readyNode); break; } return; } numChanges = 0; switch (op) { case EV_ADD: if (filePtr->mask & (TCL_READABLE | TCL_EXCEPTION)) { EV_SET(&changeList[numChanges], (uintptr_t) filePtr->fd, EVFILT_READ, op, 0, 0, filePtr->pedPtr); numChanges++; } if (filePtr->mask & TCL_WRITABLE) { EV_SET(&changeList[numChanges], (uintptr_t) filePtr->fd, EVFILT_WRITE, op, 0, 0, filePtr->pedPtr); numChanges++; } if (numChanges) { if (kevent(tsdPtr->eventsFd, changeList, numChanges, NULL, 0, NULL) == -1) { Tcl_Panic("kevent: %s", strerror(errno)); } } break; case EV_DELETE: /* * N.B. kqueue(2) has separate filters for readability and writability * fd events. We therefore need to ensure that fds are ompletely * removed from the kqueue(2) fd when deleting. This is exacerbated * by changes to filePtr->mask w/o calls to PlatforEventsControl() * after e.g. an exec(3) in a child process. * * As one of these calls can fail, two separate kevent(2) calls are * made for EVFILT_{READ,WRITE}. */ EV_SET(&changeList[0], (uintptr_t) filePtr->fd, EVFILT_READ, op, 0, 0, NULL); if ((kevent(tsdPtr->eventsFd, changeList, 1, NULL, 0, NULL) == -1) && (errno != ENOENT)) { Tcl_Panic("kevent: %s", strerror(errno)); } EV_SET(&changeList[0], (uintptr_t) filePtr->fd, EVFILT_WRITE, op, 0, 0, NULL); if ((kevent(tsdPtr->eventsFd, changeList, 1, NULL, 0, NULL) == -1) && (errno != ENOENT)) { Tcl_Panic("kevent: %s", strerror(errno)); } break; } } /* *---------------------------------------------------------------------- * * TclpFinalizeNotifier -- * * This function closes the pipe and the kqueue file descriptors and * frees the kevent structs owned by the thread of the caller. The above * operations are protected by tsdPtr->notifierMutex, which is destroyed * thereafter. * * Results: * None. * * Side effects: * While tsdPtr->notifierMutex is held: * The per-thread pipe(2) fds are closed, if non-zero, and set to -1. * The per-thread kqueue(2) fd is closed, if non-zero, and set to 0. * The per-thread kevent structs are freed, if any, and set to 0. * * tsdPtr->notifierMutex is destroyed. * *---------------------------------------------------------------------- */ void TclpFinalizeNotifier( TCL_UNUSED(void *)) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); pthread_mutex_lock(&tsdPtr->notifierMutex); if (tsdPtr->triggerPipe[0]) { close(tsdPtr->triggerPipe[0]); tsdPtr->triggerPipe[0] = -1; } if (tsdPtr->triggerPipe[1]) { close(tsdPtr->triggerPipe[1]); tsdPtr->triggerPipe[1] = -1; } if (tsdPtr->eventsFd > 0) { close(tsdPtr->eventsFd); tsdPtr->eventsFd = 0; } if (tsdPtr->readyEvents) { Tcl_Free(tsdPtr->readyEvents); tsdPtr->maxReadyEvents = 0; } pthread_mutex_unlock(&tsdPtr->notifierMutex); if ((errno = pthread_mutex_destroy(&tsdPtr->notifierMutex))) { Tcl_Panic("pthread_mutex_destroy: %s", strerror(errno)); } } /* *---------------------------------------------------------------------- * * TclpInitNotifier -- * * Initializes the platform specific notifier state. * * This function abstracts creating a kqueue fd via the kqueue system * call and allocating memory for the kevents structs in tsdPtr for the * thread of the caller. * * Results: * Returns a handle to the notifier state for this thread. * * Side effects: * The following per-thread entities are initialised: * - notifierMutex is initialised. * - The pipe(2) is created; fcntl(2) is called on both fds to set * FD_CLOEXEC and O_NONBLOCK. * - The kqueue(2) fd is created; fcntl(2) is called on it to set * FD_CLOEXEC. * - A FileHandler struct is allocated and initialised for the event- * fd(2), registering interest for TCL_READABLE on it via Platform- * EventsControl(). * - readyEvents and maxReadyEvents are initialised with 512 kevents. * *---------------------------------------------------------------------- */ void * TclpInitNotifier(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); int i, fdFl; FileHandler *filePtr; errno = pthread_mutex_init(&tsdPtr->notifierMutex, NULL); if (errno) { Tcl_Panic("Tcl_InitNotifier: %s", "could not create mutex"); } if (pipe(tsdPtr->triggerPipe) != 0) { Tcl_Panic("Tcl_InitNotifier: %s", "could not create trigger pipe"); } else for (i = 0; i < 2; i++) { if (fcntl(tsdPtr->triggerPipe[i], F_SETFD, FD_CLOEXEC) == -1) { Tcl_Panic("fcntl: %s", strerror(errno)); } else { fdFl = fcntl(tsdPtr->triggerPipe[i], F_GETFL); fdFl |= O_NONBLOCK; } if (fcntl(tsdPtr->triggerPipe[i], F_SETFL, fdFl) == -1) { Tcl_Panic("fcntl: %s", strerror(errno)); } } if ((tsdPtr->eventsFd = kqueue()) == -1) { Tcl_Panic("kqueue: %s", strerror(errno)); } else if (fcntl(tsdPtr->eventsFd, F_SETFD, FD_CLOEXEC) == -1) { Tcl_Panic("fcntl: %s", strerror(errno)); } filePtr = (FileHandler *) Tcl_Alloc(sizeof(FileHandler)); filePtr->fd = tsdPtr->triggerPipe[0]; filePtr->mask = TCL_READABLE; PlatformEventsControl(filePtr, tsdPtr, EV_ADD, 1); if (!tsdPtr->readyEvents) { tsdPtr->maxReadyEvents = 512; tsdPtr->readyEvents = (struct kevent *) Tcl_Alloc( tsdPtr->maxReadyEvents * sizeof(tsdPtr->readyEvents[0])); } LIST_INIT(&tsdPtr->firstReadyFileHandlerPtr); return tsdPtr; } /* *---------------------------------------------------------------------- * * PlatformEventsTranslate -- * * This function translates the platform-specific mask of returned * events in eventPtr to a mask of TCL_* bits. * * Results: * Returns the translated mask. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PlatformEventsTranslate( struct kevent *eventPtr) { int mask; mask = 0; if (eventPtr->filter == EVFILT_READ) { mask |= TCL_READABLE; if (eventPtr->flags & EV_ERROR) { mask |= TCL_EXCEPTION; } } if (eventPtr->filter == EVFILT_WRITE) { mask |= TCL_WRITABLE; if (eventPtr->flags & EV_ERROR) { mask |= TCL_EXCEPTION; } } return mask; } /* *---------------------------------------------------------------------- * * PlatformEventsWait -- * * This function abstracts waiting for I/O events via the kevent system * call. * * Results: * Returns -1 if kevent failed. Returns 0 if polling and if no events * became available whilst polling. Returns a pointer to and the count of * all returned events in all other cases. * * Side effects: * gettimeofday(2), kevent(2), and gettimeofday(2) are called, in the * specified order. * If timePtr specifies a positive value, it is updated to reflect the * amount of time that has passed; if its value would {under, over}flow, * it is set to zero. * *---------------------------------------------------------------------- */ static int PlatformEventsWait( struct kevent *events, size_t numEvents, struct timeval *timePtr) { int numFound; struct timeval tv0, tv1, tv_delta; struct timespec timeout, *timeoutPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* * If timePtr is NULL, kevent(2) will wait indefinitely. If it specifies a * timeout of {0,0}, kevent(2) will poll. Otherwise, the timeout will * simply be converted to a timespec. */ if (!timePtr) { timeoutPtr = NULL; } else if (!timePtr->tv_sec && !timePtr->tv_usec) { timeout.tv_sec = 0; timeout.tv_nsec = 0; timeoutPtr = &timeout; } else { timeout.tv_sec = timePtr->tv_sec; timeout.tv_nsec = timePtr->tv_usec * 1000; timeoutPtr = &timeout; } /* * Call (and possibly block on) kevent(2) and substract the delta of * gettimeofday(2) before and after the call from timePtr if the latter is * not NULL. Return the number of events returned by kevent(2). */ gettimeofday(&tv0, NULL); numFound = kevent(tsdPtr->eventsFd, NULL, 0, events, (int) numEvents, timeoutPtr); gettimeofday(&tv1, NULL); if (timePtr && (timePtr->tv_sec && timePtr->tv_usec)) { timersub(&tv1, &tv0, &tv_delta); if (!timercmp(&tv_delta, timePtr, >)) { timersub(timePtr, &tv_delta, timePtr); } else { timePtr->tv_sec = 0; timePtr->tv_usec = 0; } } if (tsdPtr->asyncPending) { tsdPtr->asyncPending = 0; TclAsyncMarkFromNotifier(); } return numFound; } /* *---------------------------------------------------------------------- * * TclpCreateFileHandler -- * * This function registers a file handler with the kqueue notifier * of the thread of the caller. * * Results: * None. * * Side effects: * Creates a new file handler structure. * PlatformEventsControl() is called for the new file handler structure. * *---------------------------------------------------------------------- */ void TclpCreateFileHandler( int fd, /* Handle of stream to watch. */ int mask, /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, and TCL_EXCEPTION: indicates * conditions under which proc should be * called. */ Tcl_FileProc *proc, /* Function to call for each selected * event. */ void *clientData) /* Arbitrary data to pass to proc. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); FileHandler *filePtr = LookUpFileHandler(tsdPtr, fd, NULL); int isNew = (filePtr == NULL); if (isNew) { filePtr = (FileHandler *) Tcl_Alloc(sizeof(FileHandler)); filePtr->fd = fd; filePtr->readyMask = 0; filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; tsdPtr->firstFileHandlerPtr = filePtr; } filePtr->proc = proc; filePtr->clientData = clientData; filePtr->mask = mask; PlatformEventsControl(filePtr, tsdPtr, EV_ADD, isNew); } /* *---------------------------------------------------------------------- * * TclpDeleteFileHandler -- * * Cancel a previously-arranged callback arrangement for a file on the * kqueue of the thread of the caller. * * Results: * None. * * Side effects: * If a callback was previously registered on file, remove it. * PlatformEventsControl() is called for the file handler structure. * The PlatformEventData struct associated with the new file handler * structure is freed. * *---------------------------------------------------------------------- */ void TclpDeleteFileHandler( int fd) /* Stream id for which to remove callback * function. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); FileHandler *filePtr, *prevPtr; /* * Find the entry for the given file (and return if there isn't one). */ filePtr = LookUpFileHandler(tsdPtr, fd, &prevPtr); if (filePtr == NULL) { return; } /* * Update the check masks for this file. */ PlatformEventsControl(filePtr, tsdPtr, EV_DELETE, 0); if (filePtr->pedPtr) { Tcl_Free(filePtr->pedPtr); } /* * Clean up information in the callback record. */ if (prevPtr == NULL) { tsdPtr->firstFileHandlerPtr = filePtr->nextPtr; } else { prevPtr->nextPtr = filePtr->nextPtr; } Tcl_Free(filePtr); } /* *---------------------------------------------------------------------- * * TclpWaitForEvent -- * * This function is called by Tcl_DoOneEvent to wait for new events on * the message queue. If the block time is 0, then TclpWaitForEvent just * polls without blocking. * * The waiting logic is implemented in PlatformEventsWait. * * Results: * Returns -1 if PlatformEventsWait() would block forever, otherwise * returns 0. * * Side effects: * Queues file events that are detected by PlatformEventsWait(). * *---------------------------------------------------------------------- */ int TclpWaitForEvent( const Tcl_Time *timePtr) /* Maximum block time, or NULL. */ { FileHandler *filePtr; int mask; Tcl_Time vTime; struct timeval timeout, *timeoutPtr; /* Impl. notes: timeout & timeoutPtr are used * if, and only if threads are not enabled. * They are the arguments for the regular * epoll_wait() used when the core is not * thread-enabled. */ int numFound, numEvent; struct PlatformEventData *pedPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); int numQueued; ssize_t i; char buf[1]; /* * Set up the timeout structure. Note that if there are no events to check * for, we return with a negative result rather than blocking forever. */ if (timePtr != NULL) { /* * TIP #233 (Virtualized Time). Is virtual time in effect? And do we * actually have something to scale? If yes to both then we call the * handler to do this scaling. */ if (timePtr->sec != 0 || timePtr->usec != 0) { vTime = *timePtr; TclScaleTime(&vTime); timePtr = &vTime; } timeout.tv_sec = timePtr->sec; timeout.tv_usec = timePtr->usec; timeoutPtr = &timeout; } else { timeoutPtr = NULL; } /* * Walk the list of FileHandlers associated with regular files (S_IFREG) * belonging to tsdPtr, queue Tcl events for them, and update their mask * of events of interest. * * kqueue(2), unlike epoll(7), does support regular files, but EVFILT_READ * only `[r]eturns when the file pointer is not at the end of file' as * opposed to unconditionally. While FreeBSD 11.0-RELEASE adds support for * this mode (NOTE_FILE_POLL,) this is not used for reasons of * compatibility. * * Therefore, the behaviour of {select,poll}(2) is simply simulated here: * fds associated with regular files are added to this list by * PlatformEventsControl() and processed here before calling (and possibly * blocking) on PlatformEventsWait(). */ numQueued = 0; LIST_FOREACH(filePtr, &tsdPtr->firstReadyFileHandlerPtr, readyNode) { mask = 0; if (filePtr->mask & TCL_READABLE) { mask |= TCL_READABLE; } if (filePtr->mask & TCL_WRITABLE) { mask |= TCL_WRITABLE; } /* * Don't bother to queue an event if the mask was previously non-zero * since an event must still be on the queue. */ if (filePtr->readyMask == 0) { FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) Tcl_Alloc(sizeof(FileHandlerEvent)); fileEvPtr->header.proc = FileHandlerEventProc; fileEvPtr->fd = filePtr->fd; Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); numQueued++; } filePtr->readyMask = mask; } /* * If any events were queued in the above loop, force PlatformEventsWait() * to poll as there already are events that need to be processed at this * point. */ if (numQueued) { timeout.tv_sec = 0; timeout.tv_usec = 0; timeoutPtr = &timeout; } /* * Wait or poll for new events, queue Tcl events for the FileHandlers * corresponding to them, and update the FileHandlers' mask of events of * interest registered by the last call to Tcl_CreateFileHandler(). * * Events for the trigger pipe are processed here in order to facilitate * inter-thread IPC. If another thread intends to wake up this thread * whilst it's blocking on PlatformEventsWait(), it write(2)s to the other * end of the pipe (see Tcl_AlertNotifier(),) which in turn will cause * PlatformEventsWait() to return immediately. */ numFound = PlatformEventsWait(tsdPtr->readyEvents, tsdPtr->maxReadyEvents, timeoutPtr); for (numEvent = 0; numEvent < numFound; numEvent++) { pedPtr = (struct PlatformEventData *) tsdPtr->readyEvents[numEvent].udata; filePtr = pedPtr->filePtr; mask = PlatformEventsTranslate(&tsdPtr->readyEvents[numEvent]); if (filePtr->fd == tsdPtr->triggerPipe[0]) { i = read(tsdPtr->triggerPipe[0], buf, 1); if ((i == -1) && (errno != EAGAIN)) { Tcl_Panic("Tcl_WaitForEvent: read from %p->triggerPipe: %s", tsdPtr, strerror(errno)); } continue; } if (!mask) { continue; } /* * Don't bother to queue an event if the mask was previously non-zero * since an event must still be on the queue. */ if (filePtr->readyMask == 0) { FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) Tcl_Alloc(sizeof(FileHandlerEvent)); fileEvPtr->header.proc = FileHandlerEventProc; fileEvPtr->fd = filePtr->fd; Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); } filePtr->readyMask |= mask; } return 0; } /* *---------------------------------------------------------------------- * * TclAsyncNotifier -- * * This procedure sets the async mark of an async handler to a * given value, if it is called from the target thread. * * Result: * True, when the handler will be marked, false otherwise. * * Side effects: * The signal may be resent to the target thread. * *---------------------------------------------------------------------- */ int TclAsyncNotifier( int sigNumber, /* Signal number. */ Tcl_ThreadId threadId, /* Target thread. */ void *clientData, /* Notifier data. */ int *flagPtr, /* Flag to mark. */ int value) /* Value of mark. */ { #if TCL_THREADS /* * WARNING: * This code most likely runs in a signal handler. Thus, * only few async-signal-safe system calls are allowed, * e.g. pthread_self(), sem_post(), write(). */ if (pthread_equal(pthread_self(), (pthread_t) threadId)) { ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData; *flagPtr = value; if (tsdPtr != NULL && !tsdPtr->asyncPending) { tsdPtr->asyncPending = 1; TclpAlertNotifier(tsdPtr); return 1; } return 0; } /* * Re-send the signal to the proper target thread. */ pthread_kill((pthread_t) threadId, sigNumber); #else (void)sigNumber; (void)threadId; (void)clientData; (void)flagPtr; (void)value; #endif return 0; } #endif /* NOTIFIER_KQUEUE && TCL_THREADS */ #else TCL_MAC_EMPTY_FILE(unix_tclKqueueNotfy_c) #endif /* !HAVE_COREFOUNDATION */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclLoadAix.c0000644000175000017500000003127114726623136014735 0ustar sergeisergei/* * tclLoadAix.c -- * * This file implements the dlopen and dlsym APIs under the AIX operating * system, to enable the Tcl "load" command to work. This code was * provided by Jens-Uwe Mager. * * This file is subject to the following copyright notice, which is * different from the notice used elsewhere in Tcl. The file has been * modified to incorporate the file dlfcn.h in-line. * * Copyright © 1992,1993,1995,1996, Jens-Uwe Mager, Helios Software GmbH * Not derived from licensed software. * * Permission is granted to freely use, copy, modify, and redistribute * this software, provided that the author is not construed to be liable * for any results of using the software, alterations are clearly marked * as such, and this notice is not modified. * * Note: this file has been altered from the original in a few ways in order * to work properly with Tcl. */ /* * @(#)dlfcn.c 1.7 revision of 95/08/14 19:08:38 * This is an unpublished work copyright © 1992 HELIOS Software GmbH * 30159 Hannover, Germany */ #include #include #include #include #include #include #include #include #include "../compat/dlfcn.h" /* * We simulate dlopen() et al. through a call to load. Because AIX has no call * to find an exported symbol we read the loader section of the loaded module * and build a list of exported symbols and their virtual address. */ typedef struct { char *name; /* The symbols's name. */ void *addr; /* Its relocated virtual address. */ } Export, *ExportPtr; /* * xlC uses the following structure to list its constructors and destructors. * This is gleaned from the output of munch. */ typedef struct { void (*init)(void); /* call static constructors */ void (*term)(void); /* call static destructors */ } Cdtor, *CdtorPtr; /* * The void * handle returned from dlopen is actually a ModulePtr. */ typedef struct Module { struct Module *next; char *name; /* module name for refcounting */ int refCnt; /* the number of references */ void *entry; /* entry point from load */ struct dl_info *info; /* optional init/terminate functions */ CdtorPtr cdtors; /* optional C++ constructors */ int nExports; /* the number of exports found */ ExportPtr exports; /* the array of exports */ } Module, *ModulePtr; /* * We keep a list of all loaded modules to be able to call the fini handlers * and destructors at atexit() time. */ static ModulePtr modList; /* * The last error from one of the dl* routines is kept in static variables * here. Each error is returned only once to the caller. */ static char errbuf[BUFSIZ]; static int errvalid; static void caterr(char *); static int readExports(ModulePtr); static void terminate(void); static void *findMain(void); void * dlopen( const char *path, int mode) { ModulePtr mp; static void *mainModule; /* * Upon the first call register a terminate handler that will close all * libraries. Also get a reference to the main module for use with * loadbind. */ if (!mainModule) { mainModule = findMain(); if (mainModule == NULL) { return NULL; } atexit(terminate); } /* * Scan the list of modules if we have the module already loaded. */ for (mp = modList; mp; mp = mp->next) { if (strcmp(mp->name, path) == 0) { mp->refCnt++; return (void *)mp; } } mp = (ModulePtr) calloc(1, sizeof(*mp)); if (mp == NULL) { errvalid++; strcpy(errbuf, "calloc: "); strcat(errbuf, strerror(errno)); return NULL; } mp->name = malloc(strlen(path) + 1); strcpy(mp->name, path); /* * load should be declared load(const char *...). Thus we cast the path to * a normal char *. Ugly. */ mp->entry = (void *)load((char *)path, L_NOAUTODEFER, NULL); if (mp->entry == NULL) { free(mp->name); free(mp); errvalid++; strcpy(errbuf, "dlopen: "); strcat(errbuf, path); strcat(errbuf, ": "); /* * If AIX says the file is not executable, the error can be further * described by querying the loader about the last error. */ if (errno == ENOEXEC) { char *tmp[BUFSIZ/sizeof(char *)], **p; if (loadquery(L_GETMESSAGES, tmp, sizeof(tmp)) == -1) { strcpy(errbuf, strerror(errno)); } else { for (p=tmp ; *p ; p++) { caterr(*p); } } } else { strcat(errbuf, strerror(errno)); } return NULL; } mp->refCnt = 1; mp->next = modList; modList = mp; if (loadbind(0, mainModule, mp->entry) == -1) { loadbindFailure: dlclose(mp); errvalid++; strcpy(errbuf, "loadbind: "); strcat(errbuf, strerror(errno)); return NULL; } /* * If the user wants global binding, loadbind against all other loaded * modules. */ if (mode & RTLD_GLOBAL) { ModulePtr mp1; for (mp1 = mp->next; mp1; mp1 = mp1->next) { if (loadbind(0, mp1->entry, mp->entry) == -1) { goto loadbindFailure; } } } if (readExports(mp) == -1) { dlclose(mp); return NULL; } /* * If there is a dl_info structure, call the init function. */ if (mp->info = (struct dl_info *)dlsym(mp, "dl_info")) { if (mp->info->init) { mp->info->init(); } } else { errvalid = 0; } /* * If the shared object was compiled using xlC we will need to call static * constructors (and later on dlclose destructors). */ if (mp->cdtors = (CdtorPtr) dlsym(mp, "__cdtors")) { while (mp->cdtors->init) { mp->cdtors->init(); mp->cdtors++; } } else { errvalid = 0; } return (void *)mp; } /* * Attempt to decipher an AIX loader error message and append it to our static * error message buffer. */ static void caterr( char *s) { char *p = s; while (*p >= '0' && *p <= '9') { p++; } switch (atoi(s)) { /* INTL: "C", UTF safe. */ case L_ERROR_TOOMANY: strcat(errbuf, "to many errors"); break; case L_ERROR_NOLIB: strcat(errbuf, "cannot load library"); strcat(errbuf, p); break; case L_ERROR_UNDEF: strcat(errbuf, "cannot find symbol"); strcat(errbuf, p); break; case L_ERROR_RLDBAD: strcat(errbuf, "bad RLD"); strcat(errbuf, p); break; case L_ERROR_FORMAT: strcat(errbuf, "bad exec format in"); strcat(errbuf, p); break; case L_ERROR_ERRNO: strcat(errbuf, strerror(atoi(++p))); /* INTL: "C", UTF safe. */ break; default: strcat(errbuf, s); break; } } void * dlsym( void *handle, const char *symbol) { ModulePtr mp = (ModulePtr)handle; ExportPtr ep; int i; /* * Could speed up the search, but I assume that one assigns the result to * function pointers anyways. */ for (ep = mp->exports, i = mp->nExports; i; i--, ep++) { if (strcmp(ep->name, symbol) == 0) { return ep->addr; } } errvalid++; strcpy(errbuf, "dlsym: undefined symbol "); strcat(errbuf, symbol); return NULL; } char * dlerror(void) { if (errvalid) { errvalid = 0; return errbuf; } return NULL; } int dlclose( void *handle) { ModulePtr mp = (ModulePtr)handle; int result; ModulePtr mp1; if (--mp->refCnt > 0) { return 0; } if (mp->info && mp->info->fini) { mp->info->fini(); } if (mp->cdtors) { while (mp->cdtors->term) { mp->cdtors->term(); mp->cdtors++; } } result = unload(mp->entry); if (result == -1) { errvalid++; strcpy(errbuf, strerror(errno)); } if (mp->exports) { ExportPtr ep; int i; for (ep = mp->exports, i = mp->nExports; i; i--, ep++) { if (ep->name) { free(ep->name); } } free(mp->exports); } if (mp == modList) { modList = mp->next; } else { for (mp1 = modList; mp1; mp1 = mp1->next) { if (mp1->next == mp) { mp1->next = mp->next; break; } } } free(mp->name); free(mp); return result; } static void terminate(void) { while (modList) { dlclose(modList); } } /* * Build the export table from the XCOFF .loader section. */ static int readExports( ModulePtr mp) { LDFILE *ldp = NULL; SCNHDR sh, shdata; LDHDR *lhp; char *ldbuf; LDSYM *ls; int i; ExportPtr ep; const char *errMsg; #define Error(msg) do{errMsg=(msg);goto error;}while(0) #define SysErr() Error(strerror(errno)) ldp = ldopen(mp->name, ldp); if (ldp == NULL) { struct ld_info *lp; char *buf; int size = 0; if (errno != ENOENT) { SysErr(); } /* * The module might be loaded due to the LIBPATH environment variable. * Search for the loaded module using L_GETINFO. */ while (1) { size += 4 * 1024; buf = malloc(size); if (buf == NULL) { SysErr(); } i = loadquery(L_GETINFO, buf, size); if (i != -1) { break; } free(buf); if (errno != ENOMEM) { SysErr(); } } /* * Traverse the list of loaded modules. The entry point returned by * load() does actually point to the data segment origin. */ lp = (struct ld_info *) buf; while (lp) { if (lp->ldinfo_dataorg == mp->entry) { ldp = ldopen(lp->ldinfo_filename, ldp); break; } if (lp->ldinfo_next == 0) { lp = NULL; } else { lp = (struct ld_info *)((char *)lp + lp->ldinfo_next); } } free(buf); if (!ldp) { SysErr(); } } if (TYPE(ldp) != U802TOCMAGIC) { Error("bad magic"); } /* * Get the padding for the data section. This is needed for AIX 4.1 * compilers. This is used when building the final function pointer to the * exported symbol. */ if (ldnshread(ldp, _DATA, &shdata) != SUCCESS) { Error("cannot read data section header"); } if (ldnshread(ldp, _LOADER, &sh) != SUCCESS) { Error("cannot read loader section header"); } /* * We read the complete loader section in one chunk, this makes finding * long symbol names residing in the string table easier. */ ldbuf = (char *) malloc(sh.s_size); if (ldbuf == NULL) { SysErr(); } if (FSEEK(ldp, sh.s_scnptr, BEGINNING) != OKFSEEK) { free(ldbuf); Error("cannot seek to loader section"); } if (FREAD(ldbuf, sh.s_size, 1, ldp) != 1) { free(ldbuf); Error("cannot read loader section"); } lhp = (LDHDR *) ldbuf; ls = (LDSYM *)(ldbuf + LDHDRSZ); /* * Count the number of exports to include in our export table. */ for (i = lhp->l_nsyms; i; i--, ls++) { if (!LDR_EXPORT(*ls)) { continue; } mp->nExports++; } mp->exports = (ExportPtr) calloc(mp->nExports, sizeof(*mp->exports)); if (mp->exports == NULL) { free(ldbuf); SysErr(); } /* * Fill in the export table. All entries are relative to the entry point * we got from load. */ ep = mp->exports; ls = (LDSYM *)(ldbuf + LDHDRSZ); for (i=lhp->l_nsyms ; i!=0 ; i--,ls++) { char *symname; char tmpsym[SYMNMLEN+1]; if (!LDR_EXPORT(*ls)) { continue; } if (ls->l_zeroes == 0) { symname = ls->l_offset + lhp->l_stoff + ldbuf; } else { /* * The l_name member is not zero terminated, we must copy the * first SYMNMLEN chars and make sure we have a zero byte at the * end. */ strncpy(tmpsym, ls->l_name, SYMNMLEN); tmpsym[SYMNMLEN] = '\0'; symname = tmpsym; } ep->name = malloc(strlen(symname) + 1); strcpy(ep->name, symname); ep->addr = (void *)((unsigned long) mp->entry + ls->l_value - shdata.s_vaddr); ep++; } free(ldbuf); while (ldclose(ldp) == FAILURE) { /* Empty body */ } return 0; /* * This is a factoring out of the error-handling code to make the rest of * the function much simpler to read. */ error: errvalid++; strcpy(errbuf, "readExports: "); strcat(errbuf, errMsg); if (ldp != NULL) { while (ldclose(ldp) == FAILURE) { /* Empty body */ } } return -1; } /* * Find the main modules entry point. This is used as export pointer for * loadbind() to be able to resolve references to the main part. */ static void * findMain(void) { struct ld_info *lp; char *buf; int size = 4*1024; int i; void *ret; buf = malloc(size); if (buf == NULL) { goto error; } while ((i = loadquery(L_GETINFO, buf, size)) == -1 && errno == ENOMEM) { free(buf); size += 4*1024; buf = malloc(size); if (buf == NULL) { goto error; } } if (i == -1) { free(buf); goto error; } /* * The first entry is the main module. The entry point returned by load() * does actually point to the data segment origin. */ lp = (struct ld_info *) buf; ret = lp->ldinfo_dataorg; free(buf); return ret; error: errvalid++; strcpy(errbuf, "findMain: "); strcat(errbuf, strerror(errno)); return NULL; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclLoadDl.c0000644000175000017500000001665214726623136014561 0ustar sergeisergei/* * tclLoadDl.c -- * * This procedure provides a version of the TclLoadFile that works with * the "dlopen" and "dlsym" library procedures for dynamic loading. * * Copyright © 1995-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #ifdef NO_DLFCN_H # include "../compat/dlfcn.h" #else # include #endif /* * In some systems, like SunOS 4.1.3, the RTLD_NOW flag isn't defined and this * argument to dlopen must always be 1. The RTLD_LOCAL flag doesn't exist on * some platforms; if it doesn't exist, set it to 0 so it has no effect. * See [Bug #3216070] */ #ifndef RTLD_NOW # define RTLD_NOW 1 #endif #ifndef RTLD_LOCAL # define RTLD_LOCAL 0 #endif /* * Static procedures defined within this file. */ static void * FindSymbol(Tcl_Interp *interp, Tcl_LoadHandle loadHandle, const char *symbol); static void UnloadFile(Tcl_LoadHandle loadHandle); /* *--------------------------------------------------------------------------- * * TclpDlopen -- * * Dynamically loads a binary code file into memory and returns a handle * to the new code. * * Results: * A standard Tcl completion code. If an error occurs, an error message * is left in the interp's result. * * Side effects: * New code suddenly appears in memory. * *--------------------------------------------------------------------------- */ int TclpDlopen( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Obj *pathPtr, /* Name of the file containing the desired * code (UTF-8). */ Tcl_LoadHandle *loadHandle, /* Filled with token for dynamically loaded * file which will be passed back to * (*unloadProcPtr)() to unload the file. */ Tcl_FSUnloadFileProc **unloadProcPtr, /* Filled with address of Tcl_FSUnloadFileProc * function which should be used for this * file. */ int flags) { void *handle; Tcl_LoadHandle newHandle; const char *native; int dlopenflags = 0; /* * First try the full path the user gave us. This is particularly * important if the cwd is inside a vfs, and we are trying to load using a * relative path. */ native = (const char *)Tcl_FSGetNativePath(pathPtr); /* * Use (RTLD_NOW|RTLD_LOCAL) as default, see [Bug #3216070] */ if (flags & TCL_LOAD_GLOBAL) { dlopenflags |= RTLD_GLOBAL; } else { dlopenflags |= RTLD_LOCAL; } if (flags & TCL_LOAD_LAZY) { dlopenflags |= RTLD_LAZY; } else { dlopenflags |= RTLD_NOW; } handle = dlopen(native, dlopenflags); if (handle == NULL) { /* * Let the OS loader examine the binary search path for whatever * string the user gave us which hopefully refers to a file on the * binary path. */ Tcl_DString ds; const char *fileName = TclGetString(pathPtr); if (Tcl_UtfToExternalDStringEx(interp, NULL, fileName, TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } native = Tcl_DStringValue(&ds); /* * Use (RTLD_NOW|RTLD_LOCAL) as default, see [Bug #3216070] */ handle = dlopen(native, dlopenflags); Tcl_DStringFree(&ds); } if (handle == NULL) { /* * Write the string to a variable first to work around a compiler bug * in the Sun Forte 6 compiler. [Bug 1503729] */ const char *errorStr = dlerror(); if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't load file \"%s\": %s", TclGetString(pathPtr), errorStr)); } return TCL_ERROR; } newHandle = (Tcl_LoadHandle)Tcl_Alloc(sizeof(*newHandle)); newHandle->clientData = handle; newHandle->findSymbolProcPtr = &FindSymbol; newHandle->unloadFileProcPtr = &UnloadFile; *unloadProcPtr = &UnloadFile; *loadHandle = newHandle; return TCL_OK; } /* *---------------------------------------------------------------------- * * FindSymbol -- * * Looks up a symbol, by name, through a handle associated with a * previously loaded piece of code (shared library). * * Results: * Returns a pointer to the function associated with 'symbol' if it is * found. Otherwise returns NULL and may leave an error message in the * interp's result. * *---------------------------------------------------------------------- */ static void * FindSymbol( Tcl_Interp *interp, /* Place to put error messages. */ Tcl_LoadHandle loadHandle, /* Value from TcpDlopen(). */ const char *symbol) /* Symbol to look up. */ { const char *native; /* Name of the library to be loaded, in * system encoding */ Tcl_DString newName, ds; /* Buffers for converting the name to * system encoding and prepending an * underscore*/ void *handle = loadHandle->clientData; /* Native handle to the loaded library */ void *proc; /* Address corresponding to the resolved * symbol */ /* * Some platforms still add an underscore to the beginning of symbol * names. If we can't find a name without an underscore, try again with * the underscore. */ if (Tcl_UtfToExternalDStringEx(interp, NULL, symbol, TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return NULL; } native = Tcl_DStringValue(&ds); proc = dlsym(handle, native); /* INTL: Native. */ if (proc == NULL) { Tcl_DStringInit(&newName); TclDStringAppendLiteral(&newName, "_"); native = Tcl_DStringAppend(&newName, native, TCL_INDEX_NONE); proc = dlsym(handle, native); /* INTL: Native. */ Tcl_DStringFree(&newName); } #ifdef __cplusplus if (proc == NULL) { char buf[32]; snprintf(buf, sizeof(buf), "%d", (int)Tcl_DStringLength(&ds)); Tcl_DStringInit(&newName); TclDStringAppendLiteral(&newName, "__Z"); Tcl_DStringAppend(&newName, buf, TCL_INDEX_NONE); Tcl_DStringAppend(&newName, Tcl_DStringValue(&ds), TCL_INDEX_NONE); TclDStringAppendLiteral(&newName, "P10Tcl_Interp"); native = Tcl_DStringValue(&newName); proc = dlsym(handle, native + 1); /* INTL: Native. */ if (proc == NULL) { proc = dlsym(handle, native); /* INTL: Native. */ } if (proc == NULL) { TclDStringAppendLiteral(&newName, "i"); native = Tcl_DStringValue(&newName); proc = dlsym(handle, native + 1); /* INTL: Native. */ } if (proc == NULL) { proc = dlsym(handle, native); /* INTL: Native. */ } Tcl_DStringFree(&newName); } #endif Tcl_DStringFree(&ds); if (proc == NULL) { const char *errorStr = dlerror(); if (interp) { if (!errorStr) { errorStr = "unknown"; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot find symbol \"%s\": %s", symbol, errorStr)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "LOAD_SYMBOL", symbol, (char *)NULL); } } return proc; } /* *---------------------------------------------------------------------- * * UnloadFile -- * * Unloads a dynamic shared object, after which all pointers to functions * in the formerly-loaded object are no longer valid. * * Results: * None. * * Side effects: * Memory for the loaded object is deallocated. * *---------------------------------------------------------------------- */ static void UnloadFile( Tcl_LoadHandle loadHandle) /* loadHandle returned by a previous call to * TclpDlopen(). The loadHandle is a token * that represents the loaded file. */ { void *handle = loadHandle->clientData; dlclose(handle); Tcl_Free(loadHandle); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclLoadDyld.c0000644000175000017500000003776714726623136015130 0ustar sergeisergei/* * tclLoadDyld.c -- * * This procedure provides a version of the TclLoadFile that works with * Apple's dyld dynamic loading. * Original version of his file (superseded long ago) provided by * Wilfredo Sanchez (wsanchez@apple.com). * * Copyright © 1995 Apple Computer, Inc. * Copyright © 2001-2007 Daniel A. Steffen * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #ifndef MODULE_SCOPE # define MODULE_SCOPE extern #endif /* * Use includes for the API we're using. */ #include #if defined(TCL_LOAD_FROM_MEMORY) #if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #include #include #include #include #include #include typedef struct Tcl_DyldModuleHandle { struct Tcl_DyldModuleHandle *nextPtr; NSModule module; } Tcl_DyldModuleHandle; #endif /* TCL_LOAD_FROM_MEMORY */ typedef struct { void *dlHandle; #if defined(TCL_LOAD_FROM_MEMORY) const struct mach_header *dyldLibHeader; Tcl_DyldModuleHandle *modulePtr; #endif } Tcl_DyldLoadHandle; /* * Static functions defined in this file. */ static void * FindSymbol(Tcl_Interp *interp, Tcl_LoadHandle loadHandle, const char *symbol); static void UnloadFile(Tcl_LoadHandle handle); /* *---------------------------------------------------------------------- * * DyldOFIErrorMsg -- * * Converts a numerical NSObjectFileImage error into an error message * string. * * Results: * Error message string. * * Side effects: * None. * *---------------------------------------------------------------------- */ #if defined(TCL_LOAD_FROM_MEMORY) static const char * DyldOFIErrorMsg( int err) { switch (err) { case NSObjectFileImageSuccess: return NULL; case NSObjectFileImageFailure: return "object file setup failure"; case NSObjectFileImageInappropriateFile: return "not a Mach-O MH_BUNDLE file"; case NSObjectFileImageArch: return "no object for this architecture"; case NSObjectFileImageFormat: return "bad object file format"; case NSObjectFileImageAccess: return "cannot read object file"; default: return "unknown error"; } } #endif /* TCL_LOAD_FROM_MEMORY */ /* *---------------------------------------------------------------------- * * TclpDlopen -- * * Dynamically loads a binary code file into memory and returns a handle * to the new code. * * Results: * A standard Tcl completion code. If an error occurs, an error message * is left in the interpreter's result. * * Side effects: * New code suddenly appears in memory. * *---------------------------------------------------------------------- */ MODULE_SCOPE int TclpDlopen( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Obj *pathPtr, /* Name of the file containing the desired * code (UTF-8). */ Tcl_LoadHandle *loadHandle, /* Filled with token for dynamically loaded * file which will be passed back to * (*unloadProcPtr)() to unload the file. */ Tcl_FSUnloadFileProc **unloadProcPtr, /* Filled with address of Tcl_FSUnloadFileProc * function which should be used for this * file. */ int flags) { Tcl_DyldLoadHandle *dyldLoadHandle; Tcl_LoadHandle newHandle; void *dlHandle = NULL; #if defined(TCL_LOAD_FROM_MEMORY) const struct mach_header *dyldLibHeader = NULL; Tcl_DyldModuleHandle *modulePtr = NULL; #endif const char *errMsg = NULL; int result; Tcl_DString ds; const char *nativePath, *nativeFileName = NULL; int dlopenflags = 0; /* * First try the full path the user gave us. This is particularly * important if the cwd is inside a vfs, and we are trying to load using a * relative path. */ nativePath = (const char *)Tcl_FSGetNativePath(pathPtr); if (Tcl_UtfToExternalDStringEx(interp, NULL, TclGetString(pathPtr), TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } nativeFileName = Tcl_DStringValue(&ds); /* * Use (RTLD_NOW|RTLD_LOCAL) as default, see [Bug #3216070] */ if (flags & TCL_LOAD_GLOBAL) { dlopenflags |= RTLD_GLOBAL; } else { dlopenflags |= RTLD_LOCAL; } if (flags & TCL_LOAD_LAZY) { dlopenflags |= RTLD_LAZY; } else { dlopenflags |= RTLD_NOW; } dlHandle = dlopen(nativePath, dlopenflags); if (!dlHandle) { /* * Let the OS loader examine the binary search path for whatever string * the user gave us which hopefully refers to a file on the binary * path. */ dlHandle = dlopen(nativeFileName, dlopenflags); if (!dlHandle) { errMsg = dlerror(); } } if (dlHandle) { dyldLoadHandle = (Tcl_DyldLoadHandle *)Tcl_Alloc(sizeof(Tcl_DyldLoadHandle)); dyldLoadHandle->dlHandle = dlHandle; #if defined(TCL_LOAD_FROM_MEMORY) dyldLoadHandle->dyldLibHeader = dyldLibHeader; dyldLoadHandle->modulePtr = modulePtr; #endif /* TCL_LOAD_FROM_MEMORY */ newHandle = (Tcl_LoadHandle)Tcl_Alloc(sizeof(*newHandle)); newHandle->clientData = dyldLoadHandle; newHandle->findSymbolProcPtr = &FindSymbol; newHandle->unloadFileProcPtr = &UnloadFile; *unloadProcPtr = &UnloadFile; *loadHandle = newHandle; result = TCL_OK; } else { Tcl_Obj *errObj; TclNewObj(errObj); if (errMsg != NULL) { Tcl_AppendToObj(errObj, errMsg, TCL_INDEX_NONE); } Tcl_SetObjResult(interp, errObj); result = TCL_ERROR; } Tcl_DStringFree(&ds); return result; } /* *---------------------------------------------------------------------- * * FindSymbol -- * * Looks up a symbol, by name, through a handle associated with a * previously loaded piece of code (shared library). * * Results: * Returns a pointer to the function associated with 'symbol' if it is * found. Otherwise returns NULL and may leave an error message in the * interp's result. * *---------------------------------------------------------------------- */ static void * FindSymbol( Tcl_Interp *interp, /* For error reporting. */ Tcl_LoadHandle loadHandle, /* Handle from TclpDlopen. */ const char *symbol) /* Symbol name to look up. */ { Tcl_DyldLoadHandle *dyldLoadHandle = (Tcl_DyldLoadHandle *)loadHandle->clientData; Tcl_LibraryInitProc *proc = NULL; const char *errMsg = NULL; Tcl_DString ds; const char *native; if (Tcl_UtfToExternalDStringEx(interp, NULL, symbol, TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return NULL; } native = Tcl_DStringValue(&ds); if (dyldLoadHandle->dlHandle) { proc = (Tcl_LibraryInitProc *)dlsym(dyldLoadHandle->dlHandle, native); if (!proc) { errMsg = dlerror(); } } else { #if defined(TCL_LOAD_FROM_MEMORY) NSSymbol nsSymbol = NULL; Tcl_DString newName; /* * dyld adds an underscore to the beginning of symbol names. */ Tcl_DStringInit(&newName); TclDStringAppendLiteral(&newName, "_"); native = Tcl_DStringAppend(&newName, native, TCL_INDEX_NONE); if (dyldLoadHandle->dyldLibHeader) { nsSymbol = NSLookupSymbolInImage(dyldLoadHandle->dyldLibHeader, native, NSLOOKUPSYMBOLINIMAGE_OPTION_BIND_NOW | NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR); if (nsSymbol) { /* * Until dyld supports unloading of MY_DYLIB binaries, the * following is not needed. */ #ifdef DYLD_SUPPORTS_DYLIB_UNLOADING NSModule module = NSModuleForSymbol(nsSymbol); Tcl_DyldModuleHandle *modulePtr = dyldLoadHandle->modulePtr; while (modulePtr != NULL) { if (module == modulePtr->module) { break; } modulePtr = modulePtr->nextPtr; } if (modulePtr == NULL) { modulePtr = (Tcl_DyldModuleHandle *)Tcl_Alloc(sizeof(Tcl_DyldModuleHandle)); modulePtr->module = module; modulePtr->nextPtr = dyldLoadHandle->modulePtr; dyldLoadHandle->modulePtr = modulePtr; } #endif /* DYLD_SUPPORTS_DYLIB_UNLOADING */ } else { NSLinkEditErrors editError; int errorNumber; const char *errorName; NSLinkEditError(&editError, &errorNumber, &errorName, &errMsg); } } else if (dyldLoadHandle->modulePtr) { nsSymbol = NSLookupSymbolInModule( dyldLoadHandle->modulePtr->module, native); } if (nsSymbol) { proc = (Tcl_LibraryInitProc *)NSAddressOfSymbol(nsSymbol); } Tcl_DStringFree(&newName); #endif /* TCL_LOAD_FROM_MEMORY */ } Tcl_DStringFree(&ds); if (errMsg && (interp != NULL)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot find symbol \"%s\": %s", symbol, errMsg)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "LOAD_SYMBOL", symbol, (char *)NULL); } return (void *)proc; } /* *---------------------------------------------------------------------- * * UnloadFile -- * * Unloads a dynamically loaded binary code file from memory. Code * pointers in the formerly loaded file are no longer valid after calling * this function. * * Results: * None. * * Side effects: * Code dissapears from memory. Note that dyld currently only supports * unloading of binaries of type MH_BUNDLE loaded with NSLinkModule() in * TclpDlopen() above. * *---------------------------------------------------------------------- */ static void UnloadFile( Tcl_LoadHandle loadHandle) /* loadHandle returned by a previous call to * TclpDlopen(). The loadHandle is a token * that represents the loaded file. */ { Tcl_DyldLoadHandle *dyldLoadHandle = (Tcl_DyldLoadHandle *)loadHandle->clientData; if (dyldLoadHandle->dlHandle) { (void) dlclose(dyldLoadHandle->dlHandle); } else { #if defined(TCL_LOAD_FROM_MEMORY) Tcl_DyldModuleHandle *modulePtr = dyldLoadHandle->modulePtr; while (modulePtr != NULL) { void *ptr = modulePtr; (void) NSUnLinkModule(modulePtr->module, NSUNLINKMODULE_OPTION_RESET_LAZY_REFERENCES); modulePtr = modulePtr->nextPtr; Tcl_Free(ptr); } #endif /* TCL_LOAD_FROM_MEMORY */ } Tcl_Free(dyldLoadHandle); Tcl_Free(loadHandle); } /* *---------------------------------------------------------------------- * * TclpLoadMemoryGetBuffer -- * * Allocate a buffer that can be used with TclpLoadMemory() below. * * Results: * Pointer to allocated buffer or NULL if an error occurs. * * Side effects: * Buffer is allocated. * *---------------------------------------------------------------------- */ #ifdef TCL_LOAD_FROM_MEMORY MODULE_SCOPE void * TclpLoadMemoryGetBuffer( size_t size) /* Size of desired buffer. */ { void *buffer = NULL; /* * We must allocate the buffer using vm_allocate, because * NSCreateObjectFileImageFromMemory will dispose of it using * vm_deallocate. */ if (vm_allocate(mach_task_self(), (vm_address_t *) &buffer, size, 1)) { buffer = NULL; } return buffer; } #endif /* TCL_LOAD_FROM_MEMORY */ /* *---------------------------------------------------------------------- * * TclpLoadMemory -- * * Dynamically loads binary code file from memory and returns a handle to * the new code. * * Results: * A standard Tcl completion code. If an error occurs, an error message * is left in the interpreter's result. * * Side effects: * New code is loaded from memory. * *---------------------------------------------------------------------- */ #ifdef TCL_LOAD_FROM_MEMORY MODULE_SCOPE int TclpLoadMemory( void *buffer, /* Buffer containing the desired code * (allocated with TclpLoadMemoryGetBuffer). */ size_t size, /* Allocation size of buffer. */ Tcl_Size codeSize, /* Size of code data read into buffer or -1 if * an error occurred and the buffer should * just be freed. */ const char *path, Tcl_LoadHandle *loadHandle, /* Filled with token for dynamically loaded * file which will be passed back to * (*unloadProcPtr)() to unload the file. */ Tcl_FSUnloadFileProc **unloadProcPtr, /* Filled with address of Tcl_FSUnloadFileProc * function which should be used for this * file. */ int flags) { Tcl_LoadHandle newHandle; Tcl_DyldLoadHandle *dyldLoadHandle; NSObjectFileImage dyldObjFileImage = NULL; Tcl_DyldModuleHandle *modulePtr; NSModule module; const char *objFileImageErrMsg = NULL; int nsflags = NSLINKMODULE_OPTION_RETURN_ON_ERROR; /* * Try to create an object file image that we can load from. */ if (codeSize >= 0) { NSObjectFileImageReturnCode err = NSObjectFileImageSuccess; const struct fat_header *fh = (const struct fat_header *)buffer; uint32_t ms = 0; #ifndef __LP64__ const struct mach_header *mh = NULL; # define mh_size sizeof(struct mach_header) # define mh_magic MH_MAGIC # define arch_abi 0 #else const struct mach_header_64 *mh = NULL; # define mh_size sizeof(struct mach_header_64) # define mh_magic MH_MAGIC_64 # define arch_abi CPU_ARCH_ABI64 #endif /* __LP64__ */ if ((size_t)codeSize >= sizeof(struct fat_header) && fh->magic == OSSwapHostToBigInt32(FAT_MAGIC)) { uint32_t fh_nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch); /* * Fat binary, try to find mach_header for our architecture */ if ((size_t)codeSize >= sizeof(struct fat_header) + fh_nfat_arch * sizeof(struct fat_arch)) { void *fatarchs = (char *)buffer + sizeof(struct fat_header); const NXArchInfo *arch = NXGetLocalArchInfo(); struct fat_arch *fa; if (fh->magic != FAT_MAGIC) { swap_fat_arch((struct fat_arch *)fatarchs, fh_nfat_arch, arch->byteorder); } fa = NXFindBestFatArch(arch->cputype | arch_abi, arch->cpusubtype, (struct fat_arch *)fatarchs, fh_nfat_arch); if (fa) { mh = (const struct mach_header_64 *)((char *) buffer + fa->offset); ms = fa->size; } else { err = NSObjectFileImageInappropriateFile; } if (fh->magic != FAT_MAGIC) { swap_fat_arch((struct fat_arch *)fatarchs, fh_nfat_arch, arch->byteorder); } } else { err = NSObjectFileImageInappropriateFile; } } else { /* * Thin binary */ mh = (const struct mach_header_64 *)buffer; ms = codeSize; } if (ms && !(ms >= mh_size && mh->magic == mh_magic && mh->filetype == MH_BUNDLE)) { err = NSObjectFileImageInappropriateFile; } if (err == NSObjectFileImageSuccess) { err = NSCreateObjectFileImageFromMemory(buffer, codeSize, &dyldObjFileImage); if (err != NSObjectFileImageSuccess) { objFileImageErrMsg = DyldOFIErrorMsg(err); } } else { objFileImageErrMsg = DyldOFIErrorMsg(err); } } /* * If it went wrong (or we were asked to just deallocate), get rid of the * memory block. */ if (dyldObjFileImage == NULL) { vm_deallocate(mach_task_self(), (vm_address_t) buffer, size); return TCL_ERROR; } /* * Extract the module we want from the image of the object file. */ if (!(flags & 1)) { nsflags |= NSLINKMODULE_OPTION_PRIVATE; } if (!(flags & 2)) { nsflags |= NSLINKMODULE_OPTION_BINDNOW; } module = NSLinkModule(dyldObjFileImage, (path ? path : "[Memory Based Bundle]"), nsflags); NSDestroyObjectFileImage(dyldObjFileImage); if (!module) { NSLinkEditErrors editError; int errorNumber; const char *errorName, *errMsg; NSLinkEditError(&editError, &errorNumber, &errorName, &errMsg); return TCL_ERROR; } /* * Stash the module reference within the load handle we create and return. */ modulePtr = (Tcl_DyldModuleHandle *)Tcl_Alloc(sizeof(Tcl_DyldModuleHandle)); modulePtr->module = module; modulePtr->nextPtr = NULL; dyldLoadHandle = (Tcl_DyldLoadHandle *)Tcl_Alloc(sizeof(Tcl_DyldLoadHandle)); dyldLoadHandle->dlHandle = NULL; dyldLoadHandle->dyldLibHeader = NULL; dyldLoadHandle->modulePtr = modulePtr; newHandle = (Tcl_LoadHandle)Tcl_Alloc(sizeof(*newHandle)); newHandle->clientData = dyldLoadHandle; newHandle->findSymbolProcPtr = &FindSymbol; newHandle->unloadFileProcPtr = &UnloadFile; *loadHandle = newHandle; *unloadProcPtr = &UnloadFile; return TCL_OK; } #endif /* TCL_LOAD_FROM_MEMORY */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 79 * End: */ tcl9.0.1/unix/tclLoadNext.c0000644000175000017500000001162514726623136015133 0ustar sergeisergei/* * tclLoadNext.c -- * * This procedure provides a version of the TclLoadFile that works with * NeXTs rld_* dynamic loading. This file provided by Pedja Bogdanovich. * * Copyright © 1995-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include #include /* * Static procedures defined within this file. */ static void * FindSymbol(Tcl_Interp *interp, Tcl_LoadHandle loadHandle, const char *symbol); static void UnloadFile(Tcl_LoadHandle loadHandle); /* *--------------------------------------------------------------------------- * * TclpDlopen -- * * Dynamically loads a binary code file into memory and returns a handle * to the new code. * * Results: * A standard Tcl completion code. If an error occurs, an error message * is left in the interp's result. * * Side effects: * New code suddenly appears in memory. * *--------------------------------------------------------------------------- */ int TclpDlopen( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Obj *pathPtr, /* Name of the file containing the desired * code (UTF-8). */ Tcl_LoadHandle *loadHandle, /* Filled with token for dynamically loaded * file which will be passed back to * (*unloadProcPtr)() to unload the file. */ Tcl_FSUnloadFileProc **unloadProcPtr, /* Filled with address of Tcl_FSUnloadFileProc * function which should be used for this * file. */ int flags) { Tcl_LoadHandle newHandle; struct mach_header *header; char *fileName; char *files[2]; const char *native; int result = 1; NXStream *errorStream = NXOpenMemory(0,0,NX_READWRITE); fileName = TclGetString(pathPtr); /* * First try the full path the user gave us. This is particularly * important if the cwd is inside a vfs, and we are trying to load using a * relative path. */ native = Tcl_FSGetNativePath(pathPtr); files = {native,NULL}; result = rld_load(errorStream, &header, files, NULL); if (!result) { /* * Let the OS loader examine the binary search path for whatever * string the user gave us which hopefully refers to a file on the * binary path. */ Tcl_DString ds; if (Tcl_UtfToExternalDStringEx(interp, NULL, fileName, TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } native = Tcl_DStringValue(&ds); files = {native,NULL}; result = rld_load(errorStream, &header, files, NULL); Tcl_DStringFree(&ds); } if (!result) { char *data; int len, maxlen; NXGetMemoryBuffer(errorStream, &data, &len, &maxlen); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't load file \"%s\": %s", fileName, data)); NXCloseMemory(errorStream, NX_FREEBUFFER); return TCL_ERROR; } NXCloseMemory(errorStream, NX_FREEBUFFER); newHandle = (Tcl_LoadHandle)Tcl_Alloc(sizeof(*newHandle)); newHandle->clientData = INT2PTR(1); newHandle->findSymbolProcPtr = &FindSymbol; newHandle->unloadFileProcPtr = &UnloadFile; *unloadProcPtr = &UnloadFile; *loadHandle = newHandle; return TCL_OK; } /* *---------------------------------------------------------------------- * * FindSymbol -- * * Looks up a symbol, by name, through a handle associated with a * previously loaded piece of code (shared library). * * Results: * Returns a pointer to the function associated with 'symbol' if it is * found. Otherwise returns NULL and may leave an error message in the * interp's result. * *---------------------------------------------------------------------- */ static void * FindSymbol( Tcl_Interp *interp, Tcl_LoadHandle loadHandle, const char *symbol) { Tcl_LibraryInitProc *proc = NULL; if (symbol) { char sym[strlen(symbol) + 2]; sym[0] = '_'; sym[1] = 0; strcat(sym, symbol); rld_lookup(NULL, sym, (unsigned long *) &proc); } if (proc == NULL && interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot find symbol \"%s\"", symbol)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "LOAD_SYMBOL", symbol, (char *)NULL); } return proc; } /* *---------------------------------------------------------------------- * * UnloadFile -- * * Unloads a dynamically loaded binary code file from memory. Code * pointers in the formerly loaded file are no longer valid after calling * this function. * * Results: * None. * * Side effects: * Does nothing. Can anything be done? * *---------------------------------------------------------------------- */ static void UnloadFile( Tcl_LoadHandle loadHandle) /* loadHandle returned by a previous call to * TclpDlopen(). The loadHandle is a token * that represents the loaded file. */ { Tcl_Free(loadHandle); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclLoadOSF.c0000644000175000017500000001314514726623136014643 0ustar sergeisergei/* * tclLoadOSF.c -- * * This function provides a version of the TclLoadFile that works under * OSF/1 1.0/1.1/1.2 and related systems, utilizing the old OSF/1 * /sbin/loader and /usr/include/loader.h. OSF/1 versions from 1.3 and on * use ELF, rtld, and dlopen()[/usr/include/ldfcn.h]. * * This is useful for: * OSF/1 1.0, 1.1, 1.2 (from OSF) * includes: MK4 and AD1 (from OSF RI) * OSF/1 1.3 (from OSF) using ROSE * HP OSF/1 1.0 ("Acorn") using COFF * * This is likely to be useful for: * Paragon OSF/1 (from Intel) * HI-OSF/1 (from Hitachi) * * This is NOT to be used on: * Digitial Alpha OSF/1 systems * OSF/1 1.3 or later (from OSF) using ELF * includes: MK6, MK7, AD2, AD3 (from OSF RI) * * This approach to things was utter @&^#; thankfully, OSF/1 eventually * supported dlopen(). * * John Robert LoVerso * * Copyright © 1995-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include #include /* * Static procedures defined within this file. */ static void * FindSymbol(Tcl_Interp *interp, Tcl_LoadHandle loadHandle, const char *symbol); static void UnloadFile(Tcl_LoadHandle loadHandle); /* *--------------------------------------------------------------------------- * * TclpDlopen -- * * Dynamically loads a binary code file into memory and returns a handle * to the new code. * * Results: * A standard Tcl completion code. If an error occurs, an error message * is left in the interp's result. * * Side effects: * New code suddenly appears in memory. * *--------------------------------------------------------------------------- */ int TclpDlopen( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Obj *pathPtr, /* Name of the file containing the desired * code (UTF-8). */ Tcl_LoadHandle *loadHandle, /* Filled with token for dynamically loaded * file which will be passed back to * (*unloadProcPtr)() to unload the file. */ Tcl_FSUnloadFileProc **unloadProcPtr, /* Filled with address of Tcl_FSUnloadFileProc * function which should be used for this * file. */ int flags) { Tcl_LoadHandle newHandle; ldr_module_t lm; char *pkg; char *fileName = TclGetString(pathPtr); const char *native; /* * First try the full path the user gave us. This is particularly * important if the cwd is inside a vfs, and we are trying to load using a * relative path. */ native = Tcl_FSGetNativePath(pathPtr); lm = (Tcl_LibraryInitProc *) load(native, LDR_NOFLAGS); if (lm == LDR_NULL_MODULE) { /* * Let the OS loader examine the binary search path for whatever * string the user gave us which hopefully refers to a file on the * binary path */ Tcl_DString ds; if (Tcl_UtfToExternalDStringEx(interp, NULL, fileName, TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } native = Tcl_DStringValue(&ds); lm = (Tcl_LibraryInitProc *) load(native, LDR_NOFLAGS); Tcl_DStringFree(&ds); } if (lm == LDR_NULL_MODULE) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't load file \"%s\": %s", fileName, Tcl_PosixError(interp))); return TCL_ERROR; } *clientDataPtr = NULL; /* * My convention is to use a [OSF loader] package name the same as shlib, * since the idiots never implemented ldr_lookup() and it is otherwise * impossible to get a package name given a module. * * I build loadable modules with a makefile rule like * ld ... -export $@: -o $@ $(OBJS) */ if ((pkg = strrchr(fileName, '/')) == NULL) { pkg = fileName; } else { pkg++; } newHandle = (Tcl_LoadHandle)Tcl_Alloc(sizeof(*newHandle)); newHandle->clientData = pkg; newHandle->findSymbolProcPtr = &FindSymbol; newHandle->unloadFileProcPtr = &UnloadFile; *unloadProcPtr = &UnloadFile; *loadHandle = newHandle; return TCL_OK; } /* *---------------------------------------------------------------------- * * FindSymbol -- * * Looks up a symbol, by name, through a handle associated with a * previously loaded piece of code (shared library). * * Results: * Returns a pointer to the function associated with 'symbol' if it is * found. Otherwise returns NULL and may leave an error message in the * interp's result. * *---------------------------------------------------------------------- */ static void * FindSymbol( Tcl_Interp *interp, Tcl_LoadHandle loadHandle, const char *symbol) { void *proc = ldr_lookup_package((char *) loadHandle, symbol); if (proc == NULL && interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot find symbol \"%s\"", symbol)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "LOAD_SYMBOL", symbol, (char *)NULL); } return proc; } /* *---------------------------------------------------------------------- * * UnloadFile -- * * Unloads a dynamically loaded binary code file from memory. Code * pointers in the formerly loaded file are no longer valid after calling * this function. * * Results: * None. * * Side effects: * Does nothing. Can anything be done? * *---------------------------------------------------------------------- */ static void UnloadFile( Tcl_LoadHandle loadHandle) /* loadHandle returned by a previous call to * TclpDlopen(). The loadHandle is a token * that represents the loaded file. */ { Tcl_Free(loadHandle); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclLoadShl.c0000644000175000017500000001275114726623136014744 0ustar sergeisergei/* * tclLoadShl.c -- * * This procedure provides a version of the TclLoadFile that works with * the "shl_load" and "shl_findsym" library procedures for dynamic * loading (e.g. for HP machines). * * Copyright © 1995-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include #include "tclInt.h" /* * Static functions defined within this file. */ static void * FindSymbol(Tcl_Interp *interp, Tcl_LoadHandle loadHandle, const char *symbol); static void UnloadFile(Tcl_LoadHandle handle); /* *---------------------------------------------------------------------- * * TclpDlopen -- * * Dynamically loads a binary code file into memory and returns a handle * to the new code. * * Results: * A standard Tcl completion code. If an error occurs, an error message * is left in the interp's result. * * Side effects: * New code suddenly appears in memory. * *--------------------------------------------------------------------------- */ int TclpDlopen( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Obj *pathPtr, /* Name of the file containing the desired * code (UTF-8). */ Tcl_LoadHandle *loadHandle, /* Filled with token for dynamically loaded * file which will be passed back to * (*unloadProcPtr)() to unload the file. */ Tcl_FSUnloadFileProc **unloadProcPtr, /* Filled with address of Tcl_FSUnloadFileProc * function which should be used for this * file. */ int flags) { shl_t handle; Tcl_LoadHandle newHandle; const char *native; char *fileName = TclGetString(pathPtr); /* * The flags below used to be BIND_IMMEDIATE; they were changed at the * suggestion of Wolfgang Kechel (wolfgang@prs.de): "This enables * verbosity for missing symbols when loading a shared lib and allows to * load libtk9.0.sl into tclsh9.0 without problems. In general, this * delays resolving symbols until they are actually needed. Shared libs * do no longer need all libraries linked in when they are build." */ /* * First try the full path the user gave us. This is particularly * important if the cwd is inside a vfs, and we are trying to load using a * relative path. */ native = Tcl_FSGetNativePath(pathPtr); handle = shl_load(native, BIND_DEFERRED|BIND_VERBOSE, 0L); if (handle == NULL) { /* * Let the OS loader examine the binary search path for whatever * string the user gave us which hopefully refers to a file on the * binary path. */ Tcl_DString ds; if (Tcl_UtfToExternalDStringEx(interp, NULL, fileName, TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } native = Tcl_DStringValue(&ds); handle = shl_load(native, BIND_DEFERRED|BIND_VERBOSE|DYNAMIC_PATH, 0L); Tcl_DStringFree(&ds); } if (handle == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't load file \"%s\": %s", fileName, Tcl_PosixError(interp))); return TCL_ERROR; } newHandle = (Tcl_LoadHandle)Tcl_Alloc(sizeof(*newHandle)); newHandle->clientData = handle; newHandle->findSymbolProcPtr = &FindSymbol; newHandle->unloadFileProcPtr = *unloadProcPtr = &UnloadFile; *loadHandle = newHandle; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_FindSymbol -- * * Looks up a symbol, by name, through a handle associated with a * previously loaded piece of code (shared library). * * Results: * Returns a pointer to the function associated with 'symbol' if it is * found. Otherwise returns NULL and may leave an error message in the * interp's result. * *---------------------------------------------------------------------- */ static void* FindSymbol( Tcl_Interp *interp, Tcl_LoadHandle loadHandle, const char *symbol) { Tcl_DString newName; Tcl_LibraryInitProc *proc = NULL; shl_t handle = (shl_t) loadHandle->clientData; /* * Some versions of the HP system software still use "_" at the beginning * of exported symbols while others don't; try both forms of each name. */ if (shl_findsym(&handle, symbol, (short) TYPE_PROCEDURE, (void *)&proc) != 0) { Tcl_DStringInit(&newName); TclDStringAppendLiteral(&newName, "_"); Tcl_DStringAppend(&newName, symbol, TCL_INDEX_NONE); if (shl_findsym(&handle, Tcl_DStringValue(&newName), (short) TYPE_PROCEDURE, (void *)&proc) != 0) { proc = NULL; } Tcl_DStringFree(&newName); } if (proc == NULL && interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot find symbol \"%s\": %s", symbol, Tcl_PosixError(interp))); } return proc; } /* *---------------------------------------------------------------------- * * UnloadFile -- * * Unloads a dynamically loaded binary code file from memory. Code * pointers in the formerly loaded file are no longer valid after calling * this function. * * Results: * None. * * Side effects: * Code removed from memory. * *---------------------------------------------------------------------- */ static void UnloadFile( Tcl_LoadHandle loadHandle) /* loadHandle returned by a previous call to * TclpDlopen(). The loadHandle is a token * that represents the loaded file. */ { shl_t handle = (shl_t) loadHandle->clientData; shl_unload(handle); Tcl_Free(loadHandle); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclSelectNotfy.c0000644000175000017500000010050114726623136015644 0ustar sergeisergei/* * tclSelectNotfy.c -- * * This file contains the implementation of the select()-based generic * Unix notifier, which is the lowest-level part of the Tcl event loop. * This file works together with generic/tclNotify.c. * * Copyright © 1995-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is * in tclMacOSXNotify.c */ #if (!defined(NOTIFIER_EPOLL) && !defined(NOTIFIER_KQUEUE)) || !TCL_THREADS #include /* * This structure is used to keep track of the notifier info for a registered * file. */ typedef struct FileHandler { int fd; int mask; /* Mask of desired events: TCL_READABLE, * etc. */ int readyMask; /* Mask of events that have been seen since * the last time file handlers were invoked * for this file. */ Tcl_FileProc *proc; /* Function to call, in the style of * Tcl_CreateFileHandler. */ void *clientData; /* Argument to pass to proc. */ struct FileHandler *nextPtr;/* Next in list of all files we care about. */ } FileHandler; /* * The following structure contains a set of select() masks to track readable, * writable, and exception conditions. */ typedef struct { fd_set readable; fd_set writable; fd_set exception; } SelectMasks; /* * The following structure is what is added to the Tcl event queue when file * handlers are ready to fire. */ typedef struct { Tcl_Event header; /* Information that is standard for all * events. */ int fd; /* File descriptor that is ready. Used to find * the FileHandler structure for the file * (can't point directly to the FileHandler * structure because it could go away while * the event is queued). */ } FileHandlerEvent; /* * The following static structure contains the state information for the * select based implementation of the Tcl notifier. One of these structures is * created for each thread that is using the notifier. */ typedef struct ThreadSpecificData { FileHandler *firstFileHandlerPtr; /* Pointer to head of file handler list. */ SelectMasks checkMasks; /* This structure is used to build up the * masks to be used in the next call to * select. Bits are set in response to calls * to Tcl_CreateFileHandler. */ SelectMasks readyMasks; /* This array reflects the readable/writable * conditions that were found to exist by the * last call to select. */ int numFdBits; /* Number of valid bits in checkMasks (one * more than highest fd for which * Tcl_WatchFile has been called). */ #if TCL_THREADS int onList; /* True if it is in this list */ unsigned int pollState; /* pollState is used to implement a polling * handshake between each thread and the * notifier thread. Bits defined below. */ struct ThreadSpecificData *nextPtr, *prevPtr; /* All threads that are currently waiting on * an event have their ThreadSpecificData * structure on a doubly-linked listed formed * from these pointers. You must hold the * notifierMutex lock before accessing these * fields. */ #ifdef __CYGWIN__ void *event; /* Any other thread alerts a notifier that an * event is ready to be processed by sending * this event. */ void *hwnd; /* Messaging window. */ #else /* !__CYGWIN__ */ pthread_cond_t waitCV; /* Any other thread alerts a notifier that an * event is ready to be processed by signaling * this condition variable. */ #endif /* __CYGWIN__ */ int waitCVinitialized; /* Variable to flag initialization of the * structure. */ int eventReady; /* True if an event is ready to be processed. * Used as condition flag together with waitCV * above. */ #endif /* TCL_THREADS */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; #if TCL_THREADS /* * The following static indicates the number of threads that have initialized * notifiers. * * You must hold the notifierMutex lock before accessing this variable. */ static int notifierCount = 0; /* * The following variable points to the head of a doubly-linked list of * ThreadSpecificData structures for all threads that are currently waiting on * an event. * * You must hold the notifierMutex lock before accessing this list. */ static ThreadSpecificData *waitingListPtr = NULL; /* * The notifier thread spends all its time in select() waiting for a file * descriptor associated with one of the threads on the waitingListPtr list to * do something interesting. But if the contents of the waitingListPtr list * ever changes, we need to wake up and restart the select() system call. You * can wake up the notifier thread by writing a single byte to the file * descriptor defined below. This file descriptor is the input-end of a pipe * and the notifier thread is listening for data on the output-end of the same * pipe. Hence writing to this file descriptor will cause the select() system * call to return and wake up the notifier thread. * * You must hold the notifierMutex lock before writing to the pipe. */ static int triggerPipe = -1; static int otherPipe = -1; /* * The notifierMutex locks access to all of the global notifier state. */ static pthread_mutex_t notifierInitMutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t notifierMutex = PTHREAD_MUTEX_INITIALIZER; /* * The following static indicates if the notifier thread is running. * * You must hold the notifierInitMutex before accessing this variable. */ static int notifierThreadRunning = 0; /* * The following static flag indicates that async handlers are pending. */ static int asyncPending = 0; /* * The notifier thread signals the notifierCV when it has finished * initializing the triggerPipe and right before the notifier thread * terminates. This condition is used to deal with the signal mask, too. */ static pthread_cond_t notifierCV = PTHREAD_COND_INITIALIZER; /* * The pollState bits: * * POLL_WANT is set by each thread before it waits on its condition variable. * It is checked by the notifier before it does select. * * POLL_DONE is set by the notifier if it goes into select after seeing * POLL_WANT. The idea is to ensure it tries a select with the same bits * the initial thread had set. */ #define POLL_WANT 0x1 #define POLL_DONE 0x2 /* * This is the thread ID of the notifier thread that does select. */ static Tcl_ThreadId notifierThread; /* * Signal mask information for notifier thread. */ static sigset_t notifierSigMask; #ifndef HAVE_PSELECT static sigset_t allSigMask; #endif /* HAVE_PSELECT */ #endif /* TCL_THREADS */ /* * Static routines defined in this file. */ #if TCL_THREADS static TCL_NORETURN void NotifierThreadProc(void *clientData); #if defined(HAVE_PTHREAD_ATFORK) static int atForkInit = 0; static void AtForkChild(void); #endif /* HAVE_PTHREAD_ATFORK */ #endif /* TCL_THREADS */ static int FileHandlerEventProc(Tcl_Event *evPtr, int flags); /* * Import of critical bits of Windows API when building threaded with Cygwin. */ #if defined(__CYGWIN__) #ifdef __cplusplus extern "C" { #endif typedef struct { void *hwnd; /* Messaging window. */ unsigned int *message; /* Message payload. */ size_t wParam; /* Event-specific "word" parameter. */ size_t lParam; /* Event-specific "long" parameter. */ int time; /* Event timestamp. */ int x; /* Event location (where meaningful). */ int y; int lPrivate; } MSG; typedef struct { unsigned int style; void *lpfnWndProc; int cbClsExtra; int cbWndExtra; void *hInstance; void *hIcon; void *hCursor; void *hbrBackground; const void *lpszMenuName; const void *lpszClassName; } WNDCLASSW; #ifdef __clang__ #pragma clang diagnostic ignored "-Wignored-attributes" #endif extern void __stdcall CloseHandle(void *); extern void *__stdcall CreateEventW(void *, unsigned char, unsigned char, void *); extern void *__stdcall CreateWindowExW(void *, const void *, const void *, unsigned int, int, int, int, int, void *, void *, void *, void *); extern unsigned int __stdcall DefWindowProcW(void *, int, void *, void *); extern unsigned char __stdcall DestroyWindow(void *); extern int __stdcall DispatchMessageW(const MSG *); extern unsigned char __stdcall GetMessageW(MSG *, void *, int, int); extern void __stdcall MsgWaitForMultipleObjects(unsigned int, void *, unsigned char, unsigned int, unsigned int); extern unsigned char __stdcall PeekMessageW(MSG *, void *, int, int, int); extern unsigned char __stdcall PostMessageW(void *, unsigned int, void *, void *); extern void __stdcall PostQuitMessage(int); extern void *__stdcall RegisterClassW(const WNDCLASSW *); extern unsigned char __stdcall ResetEvent(void *); extern unsigned char __stdcall TranslateMessage(const MSG *); /* * Threaded-cygwin specific constants and functions in this file: */ #if TCL_THREADS && defined(__CYGWIN__) static const wchar_t className[] = L"TclNotifier"; static unsigned int __stdcall NotifierProc(void *hwnd, unsigned int message, void *wParam, void *lParam); #endif /* TCL_THREADS && defined(__CYGWIN__) */ #ifdef __cplusplus } #endif #endif /* TCL_THREADS && __CYGWIN__ */ /* * Incorporate the base notifier implementation. */ #include "tclUnixNotfy.c" /* *---------------------------------------------------------------------- * * TclpInitNotifier -- * * Initializes the platform specific notifier state. * * Results: * Returns a handle to the notifier state for this thread. * * Side effects: * None. * *---------------------------------------------------------------------- */ void * TclpInitNotifier(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); #if TCL_THREADS tsdPtr->eventReady = 0; /* * Initialize thread specific condition variable for this thread. */ if (tsdPtr->waitCVinitialized == 0) { #ifdef __CYGWIN__ WNDCLASSW clazz; clazz.style = 0; clazz.cbClsExtra = 0; clazz.cbWndExtra = 0; clazz.hInstance = TclWinGetTclInstance(); clazz.hbrBackground = NULL; clazz.lpszMenuName = NULL; clazz.lpszClassName = className; clazz.lpfnWndProc = (void *)NotifierProc; clazz.hIcon = NULL; clazz.hCursor = NULL; RegisterClassW(&clazz); tsdPtr->hwnd = CreateWindowExW(NULL, clazz.lpszClassName, clazz.lpszClassName, 0, 0, 0, 0, 0, NULL, NULL, clazz.hInstance, NULL); tsdPtr->event = CreateEventW(NULL, 1 /* manual */, 0 /* !signaled */, NULL); #else /* !__CYGWIN__ */ pthread_cond_init(&tsdPtr->waitCV, NULL); #endif /* __CYGWIN__ */ tsdPtr->waitCVinitialized = 1; } pthread_mutex_lock(¬ifierInitMutex); #if defined(HAVE_PTHREAD_ATFORK) /* * Install pthread_atfork handlers to clean up the notifier in the child * of a fork. */ if (!atForkInit) { int result = pthread_atfork(NULL, NULL, AtForkChild); if (result) { Tcl_Panic("Tcl_InitNotifier: %s", "pthread_atfork failed"); } atForkInit = 1; } #endif /* HAVE_PTHREAD_ATFORK */ notifierCount++; pthread_mutex_unlock(¬ifierInitMutex); #endif /* TCL_THREADS */ return tsdPtr; } /* *---------------------------------------------------------------------- * * TclpFinalizeNotifier -- * * This function is called to cleanup the notifier state before a thread * is terminated. * * Results: * None. * * Side effects: * May terminate the background notifier thread if this is the last * notifier instance. * *---------------------------------------------------------------------- */ void TclpFinalizeNotifier( TCL_UNUSED(void *)) { #if TCL_THREADS ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); pthread_mutex_lock(¬ifierInitMutex); notifierCount--; /* * If this is the last thread to use the notifier, close the notifier pipe * and wait for the background thread to terminate. */ if (notifierCount == 0 && triggerPipe != -1) { if (write(triggerPipe, "q", 1) != 1) { Tcl_Panic("Tcl_FinalizeNotifier: %s", "unable to write 'q' to triggerPipe"); } close(triggerPipe); pthread_mutex_lock(¬ifierMutex); while(triggerPipe != -1) { pthread_cond_wait(¬ifierCV, ¬ifierMutex); } pthread_mutex_unlock(¬ifierMutex); if (notifierThreadRunning) { int result = pthread_join((pthread_t) notifierThread, NULL); if (result) { Tcl_Panic("Tcl_FinalizeNotifier: %s", "unable to join notifier thread"); } notifierThreadRunning = 0; /* * If async marks are outstanding, perform actions now. */ if (asyncPending) { asyncPending = 0; TclAsyncMarkFromNotifier(); } } } /* * Clean up any synchronization objects in the thread local storage. */ #ifdef __CYGWIN__ DestroyWindow(tsdPtr->hwnd); CloseHandle(tsdPtr->event); #else /* !__CYGWIN__ */ pthread_cond_destroy(&tsdPtr->waitCV); #endif /* __CYGWIN__ */ tsdPtr->waitCVinitialized = 0; pthread_mutex_unlock(¬ifierInitMutex); #endif /* TCL_THREADS */ } /* *---------------------------------------------------------------------- * * TclpCreateFileHandler -- * * This function registers a file handler with the select notifier. * * Results: * None. * * Side effects: * Creates a new file handler structure. * *---------------------------------------------------------------------- */ void TclpCreateFileHandler( int fd, /* Handle of stream to watch. */ int mask, /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, and TCL_EXCEPTION: indicates * conditions under which proc should be * called. */ Tcl_FileProc *proc, /* Function to call for each selected * event. */ void *clientData) /* Arbitrary data to pass to proc. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); FileHandler *filePtr = LookUpFileHandler(tsdPtr, fd, NULL); if (filePtr == NULL) { filePtr = (FileHandler *) Tcl_Alloc(sizeof(FileHandler)); filePtr->fd = fd; filePtr->readyMask = 0; filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; tsdPtr->firstFileHandlerPtr = filePtr; } filePtr->proc = proc; filePtr->clientData = clientData; filePtr->mask = mask; /* * Update the check masks for this file. */ if (mask & TCL_READABLE) { FD_SET(fd, &tsdPtr->checkMasks.readable); } else { FD_CLR(fd, &tsdPtr->checkMasks.readable); } if (mask & TCL_WRITABLE) { FD_SET(fd, &tsdPtr->checkMasks.writable); } else { FD_CLR(fd, &tsdPtr->checkMasks.writable); } if (mask & TCL_EXCEPTION) { FD_SET(fd, &tsdPtr->checkMasks.exception); } else { FD_CLR(fd, &tsdPtr->checkMasks.exception); } if (tsdPtr->numFdBits <= fd) { tsdPtr->numFdBits = fd + 1; } } /* *---------------------------------------------------------------------- * * TclpDeleteFileHandler -- * * Cancel a previously-arranged callback arrangement for a file. * * Results: * None. * * Side effects: * If a callback was previously registered on file, remove it. * *---------------------------------------------------------------------- */ void TclpDeleteFileHandler( int fd) /* Stream id for which to remove callback * function. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); FileHandler *filePtr, *prevPtr; int i; /* * Find the entry for the given file (and return if there isn't one). */ filePtr = LookUpFileHandler(tsdPtr, fd, &prevPtr); if (filePtr == NULL) { return; } /* * Update the check masks for this file. */ if (filePtr->mask & TCL_READABLE) { FD_CLR(fd, &tsdPtr->checkMasks.readable); } if (filePtr->mask & TCL_WRITABLE) { FD_CLR(fd, &tsdPtr->checkMasks.writable); } if (filePtr->mask & TCL_EXCEPTION) { FD_CLR(fd, &tsdPtr->checkMasks.exception); } /* * Find current max fd. */ if (fd + 1 == tsdPtr->numFdBits) { int numFdBits = 0; for (i = fd - 1; i >= 0; i--) { if (FD_ISSET(i, &tsdPtr->checkMasks.readable) || FD_ISSET(i, &tsdPtr->checkMasks.writable) || FD_ISSET(i, &tsdPtr->checkMasks.exception)) { numFdBits = i + 1; break; } } tsdPtr->numFdBits = numFdBits; } /* * Clean up information in the callback record. */ if (prevPtr == NULL) { tsdPtr->firstFileHandlerPtr = filePtr->nextPtr; } else { prevPtr->nextPtr = filePtr->nextPtr; } Tcl_Free(filePtr); } #if TCL_THREADS && defined(__CYGWIN__) static unsigned int __stdcall NotifierProc( void *hwnd, unsigned int message, void *wParam, void *lParam) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (message != 1024) { return DefWindowProcW(hwnd, message, wParam, lParam); } /* * Process all of the runnable events. */ tsdPtr->eventReady = 1; Tcl_ServiceAll(); return 0; } #endif /* TCL_THREADS && __CYGWIN__ */ /* *---------------------------------------------------------------------- * * TclpWaitForEvent -- * * This function is called by Tcl_DoOneEvent to wait for new events on * the message queue. If the block time is 0, then Tcl_WaitForEvent just * polls without blocking. * * Results: * Returns -1 if the select would block forever, otherwise returns 0. * * Side effects: * Queues file events that are detected by the select. * *---------------------------------------------------------------------- */ int TclpWaitForEvent( const Tcl_Time *timePtr) /* Maximum block time, or NULL. */ { FileHandler *filePtr; int mask; Tcl_Time vTime; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); #if TCL_THREADS int waitForFiles; # ifdef __CYGWIN__ MSG msg; # endif /* __CYGWIN__ */ #else /* !TCL_THREADS */ /* * Impl. notes: timeout & timeoutPtr are used if, and only if threads are * not enabled. They are the arguments for the regular select() used when * the core is not thread-enabled. */ struct timeval timeout, *timeoutPtr; int numFound; #endif /* TCL_THREADS */ /* * Set up the timeout structure. Note that if there are no events to check * for, we return with a negative result rather than blocking forever. */ if (timePtr != NULL) { /* * TIP #233 (Virtualized Time). Is virtual time in effect? And do we * actually have something to scale? If yes to both then we call the * handler to do this scaling. */ if (timePtr->sec != 0 || timePtr->usec != 0) { vTime = *timePtr; TclScaleTime(&vTime); timePtr = &vTime; } #if !TCL_THREADS timeout.tv_sec = timePtr->sec; timeout.tv_usec = timePtr->usec; timeoutPtr = &timeout; } else if (tsdPtr->numFdBits == 0) { /* * If there are no threads, no timeout, and no fds registered, then * there are no events possible and we must avoid deadlock. Note that * this is not entirely correct because there might be a signal that * could interrupt the select call, but we don't handle that case if * we aren't using threads. */ return -1; } else { timeoutPtr = NULL; #endif /* !TCL_THREADS */ } #if TCL_THREADS /* * Start notifier thread and place this thread on the list of interested * threads, signal the notifier thread, and wait for a response or a * timeout. */ StartNotifierThread("Tcl_WaitForEvent"); pthread_mutex_lock(¬ifierMutex); if (timePtr != NULL && timePtr->sec == 0 && (timePtr->usec == 0 #if defined(__APPLE__) && defined(__LP64__) /* * On 64-bit Darwin, pthread_cond_timedwait() appears to have a * bug that causes it to wait forever when passed an absolute time * which has already been exceeded by the system time; as a * workaround, when given a very brief timeout, just do a poll. * [Bug 1457797] */ || timePtr->usec < 10 #endif /* __APPLE__ && __LP64__ */ )) { /* * Cannot emulate a polling select with a polling condition variable. * Instead, pretend to wait for files and tell the notifier thread * what we are doing. The notifier thread makes sure it goes through * select with its select mask in the same state as ours currently is. * We block until that happens. */ waitForFiles = 1; tsdPtr->pollState = POLL_WANT; timePtr = NULL; } else { waitForFiles = (tsdPtr->numFdBits > 0); tsdPtr->pollState = 0; } if (waitForFiles) { /* * Add the ThreadSpecificData structure of this thread to the list of * ThreadSpecificData structures of all threads that are waiting on * file events. */ tsdPtr->nextPtr = waitingListPtr; if (waitingListPtr) { waitingListPtr->prevPtr = tsdPtr; } tsdPtr->prevPtr = 0; waitingListPtr = tsdPtr; tsdPtr->onList = 1; if ((write(triggerPipe, "", 1) == -1) && (errno != EAGAIN)) { Tcl_Panic("Tcl_WaitForEvent: %s", "unable to write to triggerPipe"); } } FD_ZERO(&tsdPtr->readyMasks.readable); FD_ZERO(&tsdPtr->readyMasks.writable); FD_ZERO(&tsdPtr->readyMasks.exception); if (!tsdPtr->eventReady) { #ifdef __CYGWIN__ if (!PeekMessageW(&msg, NULL, 0, 0, 0)) { unsigned int timeout; if (timePtr) { timeout = timePtr->sec * 1000 + timePtr->usec / 1000; } else { timeout = 0xFFFFFFFF; } pthread_mutex_unlock(¬ifierMutex); MsgWaitForMultipleObjects(1, &tsdPtr->event, 0, timeout, 1279); pthread_mutex_lock(¬ifierMutex); } #else /* !__CYGWIN__ */ if (timePtr != NULL) { Tcl_Time now; struct timespec ptime; Tcl_GetTime(&now); ptime.tv_sec = timePtr->sec + now.sec + (timePtr->usec + now.usec) / 1000000; ptime.tv_nsec = 1000 * ((timePtr->usec + now.usec) % 1000000); pthread_cond_timedwait(&tsdPtr->waitCV, ¬ifierMutex, &ptime); } else { pthread_cond_wait(&tsdPtr->waitCV, ¬ifierMutex); } #endif /* __CYGWIN__ */ } tsdPtr->eventReady = 0; #ifdef __CYGWIN__ while (PeekMessageW(&msg, NULL, 0, 0, 0)) { /* * Retrieve and dispatch the message. */ unsigned int result = GetMessageW(&msg, NULL, 0, 0); if (result == 0) { PostQuitMessage(msg.wParam); /* What to do here? */ } else if (result != (unsigned int) -1) { TranslateMessage(&msg); DispatchMessageW(&msg); } } ResetEvent(tsdPtr->event); #endif /* __CYGWIN__ */ if (waitForFiles && tsdPtr->onList) { /* * Remove the ThreadSpecificData structure of this thread from the * waiting list. Alert the notifier thread to recompute its select * masks - skipping this caused a hang when trying to close a pipe * which the notifier thread was still doing a select on. */ if (tsdPtr->prevPtr) { tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; } else { waitingListPtr = tsdPtr->nextPtr; } if (tsdPtr->nextPtr) { tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; } tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; tsdPtr->onList = 0; if ((write(triggerPipe, "", 1) == -1) && (errno != EAGAIN)) { Tcl_Panic("Tcl_WaitForEvent: %s", "unable to write to triggerPipe"); } } #else /* !TCL_THREADS */ tsdPtr->readyMasks = tsdPtr->checkMasks; numFound = select(tsdPtr->numFdBits, &tsdPtr->readyMasks.readable, &tsdPtr->readyMasks.writable, &tsdPtr->readyMasks.exception, timeoutPtr); /* * Some systems don't clear the masks after an error, so we have to do it * here. */ if (numFound == -1) { FD_ZERO(&tsdPtr->readyMasks.readable); FD_ZERO(&tsdPtr->readyMasks.writable); FD_ZERO(&tsdPtr->readyMasks.exception); } #endif /* TCL_THREADS */ /* * Queue all detected file events before returning. */ for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL); filePtr = filePtr->nextPtr) { mask = 0; if (FD_ISSET(filePtr->fd, &tsdPtr->readyMasks.readable)) { mask |= TCL_READABLE; } if (FD_ISSET(filePtr->fd, &tsdPtr->readyMasks.writable)) { mask |= TCL_WRITABLE; } if (FD_ISSET(filePtr->fd, &tsdPtr->readyMasks.exception)) { mask |= TCL_EXCEPTION; } if (!mask) { continue; } /* * Don't bother to queue an event if the mask was previously non-zero * since an event must still be on the queue. */ if (filePtr->readyMask == 0) { FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) Tcl_Alloc(sizeof(FileHandlerEvent)); fileEvPtr->header.proc = FileHandlerEventProc; fileEvPtr->fd = filePtr->fd; Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); } filePtr->readyMask = mask; } #if TCL_THREADS pthread_mutex_unlock(¬ifierMutex); #endif /* TCL_THREADS */ return 0; } /* *---------------------------------------------------------------------- * * TclAsyncNotifier -- * * This procedure sets the async mark of an async handler to a * given value, if it is called from the notifier thread. * * Result: * True, when the handler will be marked, false otherwise. * * Side effetcs: * The trigger pipe is written when called from the notifier * thread. * *---------------------------------------------------------------------- */ int TclAsyncNotifier( int sigNumber, /* Signal number. */ TCL_UNUSED(Tcl_ThreadId), /* Target thread. */ TCL_UNUSED(void *), /* Notifier data. */ int *flagPtr, /* Flag to mark. */ int value) /* Value of mark. */ { #if TCL_THREADS /* * WARNING: * This code most likely runs in a signal handler. Thus, * only few async-signal-safe system calls are allowed, * e.g. pthread_self(), sem_post(), write(). */ if (pthread_equal(pthread_self(), (pthread_t) notifierThread)) { if (notifierThreadRunning) { *flagPtr = value; if (!asyncPending) { asyncPending = 1; if (write(triggerPipe, "S", 1) != 1) { asyncPending = 0; return 0; }; } return 1; } return 0; } /* * Re-send the signal to the notifier thread. */ pthread_kill((pthread_t) notifierThread, sigNumber); #else (void)sigNumber; (void)flagPtr; (void)value; #endif return 0; } /* *---------------------------------------------------------------------- * * NotifierThreadProc -- * * This routine is the initial (and only) function executed by the * special notifier thread. Its job is to wait for file descriptors to * become readable or writable or to have an exception condition and then * to notify other threads who are interested in this information by * signalling a condition variable. Other threads can signal this * notifier thread of a change in their interests by writing a single * byte to a special pipe that the notifier thread is monitoring. * * Result: * None. Once started, this routine normally never exits and usually dies * with the overall process, but it can be shut down if the Tcl library * is finalized. * * Side effects: * The trigger pipe used to signal the notifier thread is created when * the notifier thread first starts. * *---------------------------------------------------------------------- */ #if TCL_THREADS static TCL_NORETURN void NotifierThreadProc( TCL_UNUSED(void *)) { ThreadSpecificData *tsdPtr; fd_set readableMask; fd_set writableMask; fd_set exceptionMask; int i, fds[2], receivePipe, ret; long found; struct timeval poll = {0, 0}, *timePtr; char buf[2]; int numFdBits = 0; if (pipe(fds) != 0) { Tcl_Panic("NotifierThreadProc: %s", "could not create trigger pipe"); } /* * Ticket [c6897e6e6a]. */ if (fds[0] >= FD_SETSIZE || fds[1] >= FD_SETSIZE) { Tcl_Panic("NotifierThreadProc: %s", "too many open files"); } receivePipe = fds[0]; if (TclUnixSetBlockingMode(receivePipe, TCL_MODE_NONBLOCKING) < 0) { Tcl_Panic("NotifierThreadProc: %s", "could not make receive pipe non blocking"); } if (TclUnixSetBlockingMode(fds[1], TCL_MODE_NONBLOCKING) < 0) { Tcl_Panic("NotifierThreadProc: %s", "could not make trigger pipe non blocking"); } if (fcntl(receivePipe, F_SETFD, FD_CLOEXEC) < 0) { Tcl_Panic("NotifierThreadProc: %s", "could not make receive pipe close-on-exec"); } if (fcntl(fds[1], F_SETFD, FD_CLOEXEC) < 0) { Tcl_Panic("NotifierThreadProc: %s", "could not make trigger pipe close-on-exec"); } /* * Install the write end of the pipe into the global variable. */ pthread_mutex_lock(¬ifierMutex); triggerPipe = fds[1]; otherPipe = fds[0]; /* * Signal any threads that are waiting. */ pthread_cond_broadcast(¬ifierCV); pthread_mutex_unlock(¬ifierMutex); /* * Look for file events and report them to interested threads. */ while (1) { FD_ZERO(&readableMask); FD_ZERO(&writableMask); FD_ZERO(&exceptionMask); /* * Compute the logical OR of the masks from all the waiting * notifiers. */ pthread_mutex_lock(¬ifierMutex); timePtr = NULL; for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { for (i = tsdPtr->numFdBits-1; i >= 0; --i) { if (FD_ISSET(i, &tsdPtr->checkMasks.readable)) { FD_SET(i, &readableMask); } if (FD_ISSET(i, &tsdPtr->checkMasks.writable)) { FD_SET(i, &writableMask); } if (FD_ISSET(i, &tsdPtr->checkMasks.exception)) { FD_SET(i, &exceptionMask); } } if (tsdPtr->numFdBits > numFdBits) { numFdBits = tsdPtr->numFdBits; } if (tsdPtr->pollState & POLL_WANT) { /* * Here we make sure we go through select() with the same mask * bits that were present when the thread tried to poll. */ tsdPtr->pollState |= POLL_DONE; timePtr = &poll; } } pthread_mutex_unlock(¬ifierMutex); /* * Set up the mask to include the receive pipe. */ if (receivePipe >= numFdBits) { numFdBits = receivePipe + 1; } FD_SET(receivePipe, &readableMask); /* * Signals are unblocked only during select(). */ #ifdef HAVE_PSELECT { struct timespec tspec, *tspecPtr; if (timePtr == NULL) { tspecPtr = NULL; } else { tspecPtr = &tspec; tspecPtr->tv_sec = timePtr->tv_sec; tspecPtr->tv_nsec = timePtr->tv_usec * 1000; } ret = pselect(numFdBits, &readableMask, &writableMask, &exceptionMask, tspecPtr, ¬ifierSigMask); } #else pthread_sigmask(SIG_SETMASK, ¬ifierSigMask, NULL); ret = select(numFdBits, &readableMask, &writableMask, &exceptionMask, timePtr); pthread_sigmask(SIG_BLOCK, &allSigMask, NULL); #endif if (ret == -1) { /* * In case a signal was caught during select(), * perform work on async handlers now. */ if (errno == EINTR && asyncPending) { asyncPending = 0; TclAsyncMarkFromNotifier(); } /* * Try again immediately on select() error. */ continue; } /* * Alert any threads that are waiting on a ready file descriptor. */ pthread_mutex_lock(¬ifierMutex); for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { found = 0; for (i = tsdPtr->numFdBits - 1; i >= 0; --i) { if (FD_ISSET(i, &tsdPtr->checkMasks.readable) && FD_ISSET(i, &readableMask)) { FD_SET(i, &tsdPtr->readyMasks.readable); found = 1; } if (FD_ISSET(i, &tsdPtr->checkMasks.writable) && FD_ISSET(i, &writableMask)) { FD_SET(i, &tsdPtr->readyMasks.writable); found = 1; } if (FD_ISSET(i, &tsdPtr->checkMasks.exception) && FD_ISSET(i, &exceptionMask)) { FD_SET(i, &tsdPtr->readyMasks.exception); found = 1; } } if (found || (tsdPtr->pollState & POLL_DONE)) { AlertSingleThread(tsdPtr); } } pthread_mutex_unlock(¬ifierMutex); /* * Consume the next byte from the notifier pipe if the pipe was * readable. Note that there may be multiple bytes pending, but to * avoid a race condition we only read one at a time. */ do { i = (int)read(receivePipe, buf, 1); if (i <= 0) { break; } else if ((i == 0) || ((i == 1) && (buf[0] == 'q'))) { /* * Someone closed the write end of the pipe or sent us a Quit * message [Bug: 4139] and then closed the write end of the * pipe so we need to shut down the notifier thread. */ break; } } while (1); if (asyncPending) { asyncPending = 0; TclAsyncMarkFromNotifier(); } if ((i == 0) || (buf[0] == 'q')) { break; } } /* * Clean up the read end of the pipe and signal any threads waiting on * termination of the notifier thread. */ close(receivePipe); pthread_mutex_lock(¬ifierMutex); triggerPipe = -1; otherPipe = -1; pthread_cond_broadcast(¬ifierCV); pthread_mutex_unlock(¬ifierMutex); TclpThreadExit(0); } #endif /* TCL_THREADS */ #endif /* (!NOTIFIER_EPOLL && !NOTIFIER_KQUEUE) || !TCL_THREADS */ #else TCL_MAC_EMPTY_FILE(unix_tclSelectNotfy_c) #endif /* !HAVE_COREFOUNDATION */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclUnixChan.c0000644000175000017500000016141214726623136015132 0ustar sergeisergei/* * tclUnixChan.c * * Common channel driver for Unix channels based on files, command pipes * and TCP sockets. * * Copyright © 1995-1997 Sun Microsystems, Inc. * Copyright © 1998-1999 Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* Internal definitions for Tcl. */ #include "tclFileSystem.h" #include "tclIO.h" /* To get Channel type declaration. */ #undef SUPPORTS_TTY #if defined(HAVE_TERMIOS_H) # define SUPPORTS_TTY 1 # include # ifdef HAVE_SYS_IOCTL_H # include # endif /* HAVE_SYS_IOCTL_H */ # ifdef HAVE_SYS_MODEM_H # include # endif /* HAVE_SYS_MODEM_H */ # ifdef FIONREAD # define GETREADQUEUE(fd, int) ioctl((fd), FIONREAD, &(int)) # elif defined(FIORDCHK) # define GETREADQUEUE(fd, int) int = ioctl((fd), FIORDCHK, NULL) # else # define GETREADQUEUE(fd, int) int = 0 # endif # ifdef TIOCOUTQ # define GETWRITEQUEUE(fd, int) ioctl((fd), TIOCOUTQ, &(int)) # else # define GETWRITEQUEUE(fd, int) int = 0 # endif # if !defined(CRTSCTS) && defined(CNEW_RTSCTS) # define CRTSCTS CNEW_RTSCTS # endif /* !CRTSCTS&CNEW_RTSCTS */ # if !defined(PAREXT) && defined(CMSPAR) # define PAREXT CMSPAR # endif /* !PAREXT&&CMSPAR */ #endif /* HAVE_TERMIOS_H */ /* * The bits supported for describing the closeMode field of TtyState. */ enum CloseModeBits { CLOSE_DEFAULT, CLOSE_DRAIN, CLOSE_DISCARD }; /* * Helper macros to make parts of this file clearer. The macros do exactly * what they say on the tin. :-) They also only ever refer to their arguments * once, and so can be used without regard to side effects. */ #define SET_BITS(var, bits) ((var) |= (bits)) #define CLEAR_BITS(var, bits) ((var) &= ~(bits)) /* * These structures describe per-instance state of file-based and serial-based * channels. */ typedef struct { Tcl_Channel channel; /* Channel associated with this file. */ int fd; /* File handle. */ int validMask; /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, or TCL_EXCEPTION: indicates * which operations are valid on the file. */ } FileState; typedef struct { FileState fileState; #ifdef SUPPORTS_TTY int closeMode; /* One of CLOSE_DEFAULT, CLOSE_DRAIN or * CLOSE_DISCARD. */ int doReset; /* Whether we should do a terminal reset on * close. */ struct termios initState; /* The state of the terminal when it was * opened. */ #endif /* SUPPORTS_TTY */ } TtyState; #ifdef SUPPORTS_TTY /* * The following structure is used to set or get the serial port attributes in * a platform-independent manner. */ typedef struct { int baud; int parity; int data; int stop; } TtyAttrs; #endif /* SUPPORTS_TTY */ #define UNSUPPORTED_OPTION(detail) \ if (interp) { \ Tcl_SetObjResult(interp, Tcl_ObjPrintf( \ "%s not supported for this platform", (detail))); \ Tcl_SetErrorCode(interp, "TCL", "UNSUPPORTED", (char *)NULL); \ } /* * Static routines for this file: */ static int FileBlockModeProc(void *instanceData, int mode); static int FileCloseProc(void *instanceData, Tcl_Interp *interp, int flags); static int FileGetHandleProc(void *instanceData, int direction, void **handlePtr); static int FileGetOptionProc(void *instanceData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr); static int FileInputProc(void *instanceData, char *buf, int toRead, int *errorCode); static int FileOutputProc(void *instanceData, const char *buf, int toWrite, int *errorCode); static int FileTruncateProc(void *instanceData, long long length); static long long FileWideSeekProc(void *instanceData, long long offset, int mode, int *errorCode); static void FileWatchProc(void *instanceData, int mask); #ifdef SUPPORTS_TTY static int TtyCloseProc(void *instanceData, Tcl_Interp *interp, int flags); static void TtyGetAttributes(int fd, TtyAttrs *ttyPtr); static int TtyGetOptionProc(void *instanceData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr); static int TtyGetBaud(speed_t speed); static speed_t TtyGetSpeed(int baud); static void TtyInit(int fd); static void TtyModemStatusStr(int status, Tcl_DString *dsPtr); static int TtyParseMode(Tcl_Interp *interp, const char *mode, TtyAttrs *ttyPtr); static void TtySetAttributes(int fd, TtyAttrs *ttyPtr); static int TtySetOptionProc(void *instanceData, Tcl_Interp *interp, const char *optionName, const char *value); #endif /* SUPPORTS_TTY */ /* * This structure describes the channel type structure for file based IO: */ static const Tcl_ChannelType fileChannelType = { "file", /* Type name. */ TCL_CHANNEL_VERSION_5, NULL, /* Deprecated. */ FileInputProc, FileOutputProc, NULL, /* Deprecated. */ NULL, /* Set option proc. */ FileGetOptionProc, FileWatchProc, FileGetHandleProc, FileCloseProc, FileBlockModeProc, NULL, /* Flush proc. */ NULL, /* Bubbled event handler proc. */ FileWideSeekProc, NULL, /* Thread action proc. */ FileTruncateProc }; #ifdef SUPPORTS_TTY /* * This structure describes the channel type structure for serial IO. * Note that this type is a subclass of the "file" type. */ static const Tcl_ChannelType ttyChannelType = { "tty", TCL_CHANNEL_VERSION_5, NULL, /* Deprecated. */ FileInputProc, FileOutputProc, NULL, /* Deprecated. */ TtySetOptionProc, TtyGetOptionProc, FileWatchProc, FileGetHandleProc, TtyCloseProc, FileBlockModeProc, NULL, /* Flush proc. */ NULL, /* Bubbled event handler proc. */ NULL, /* Seek proc. */ NULL, /* Thread action proc. */ NULL /* Truncate proc. */ }; #endif /* SUPPORTS_TTY */ /* *---------------------------------------------------------------------- * * FileBlockModeProc -- * * Helper function to set blocking and nonblocking modes on a file based * channel. Invoked by generic IO level code. * * Results: * 0 if successful, errno when failed. * * Side effects: * Sets the device into blocking or non-blocking mode. * *---------------------------------------------------------------------- */ static int FileBlockModeProc( void *instanceData, /* File state. */ int mode) /* The mode to set. Can be TCL_MODE_BLOCKING * or TCL_MODE_NONBLOCKING. */ { FileState *fsPtr = (FileState *)instanceData; if (TclUnixSetBlockingMode(fsPtr->fd, mode) < 0) { return errno; } return 0; } /* *---------------------------------------------------------------------- * * FileInputProc -- * * This function is invoked from the generic IO level to read input from * a file based channel. * * Results: * The number of bytes read is returned or -1 on error. An output * argument contains a POSIX error code if an error occurs, or zero. * * Side effects: * Reads input from the input device of the channel. * *---------------------------------------------------------------------- */ static int FileInputProc( void *instanceData, /* File state. */ char *buf, /* Where to store data read. */ int toRead, /* How much space is available in the * buffer? */ int *errorCodePtr) /* Where to store error code. */ { FileState *fsPtr = (FileState *)instanceData; int bytesRead; /* How many bytes were actually read from the * input device? */ *errorCodePtr = 0; /* * Assume there is always enough input available. This will block * appropriately, and read will unblock as soon as a short read is * possible, if the channel is in blocking mode. If the channel is * nonblocking, the read will never block. */ do { bytesRead = read(fsPtr->fd, buf, toRead); } while ((bytesRead < 0) && (errno == EINTR)); if (bytesRead < 0) { *errorCodePtr = errno; return -1; } return bytesRead; } /* *---------------------------------------------------------------------- * * FileOutputProc-- * * This function is invoked from the generic IO level to write output to * a file channel. * * Results: * The number of bytes written is returned or -1 on error. An output * argument contains a POSIX error code if an error occurred, or zero. * * Side effects: * Writes output on the output device of the channel. * *---------------------------------------------------------------------- */ static int FileOutputProc( void *instanceData, /* File state. */ const char *buf, /* The data buffer. */ int toWrite, /* How many bytes to write? */ int *errorCodePtr) /* Where to store error code. */ { FileState *fsPtr = (FileState *)instanceData; int written; *errorCodePtr = 0; if (toWrite == 0) { /* * SF Tcl Bug 465765. Do not try to write nothing into a file. STREAM * based implementations will considers this as EOF (if there is a * pipe behind the file). */ return 0; } written = write(fsPtr->fd, buf, toWrite); if (written >= 0) { return written; } *errorCodePtr = errno; return -1; } /* *---------------------------------------------------------------------- * * FileCloseProc, TtyCloseProc -- * * These functions are called from the generic IO level to perform * channel-type-specific cleanup when a file- or tty-based channel is * closed. * * Results: * 0 if successful, errno if failed. * * Side effects: * Closes the device of the channel. * *---------------------------------------------------------------------- */ static int FileCloseProc( void *instanceData, /* File state. */ TCL_UNUSED(Tcl_Interp *), int flags) { FileState *fsPtr = (FileState *)instanceData; int errorCode = 0; if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) { return EINVAL; } Tcl_DeleteFileHandler(fsPtr->fd); /* * Do not close standard channels while in thread-exit. */ if (!TclInThreadExit() || ((fsPtr->fd != 0) && (fsPtr->fd != 1) && (fsPtr->fd != 2))) { if (close(fsPtr->fd) < 0) { errorCode = errno; } } Tcl_Free(fsPtr); return errorCode; } #ifdef SUPPORTS_TTY static int TtyCloseProc( void *instanceData, Tcl_Interp *interp, int flags) { TtyState *ttyPtr = (TtyState*)instanceData; if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) { return EINVAL; } /* * If we've been asked by the user to drain or flush, do so now. */ switch (ttyPtr->closeMode) { case CLOSE_DRAIN: tcdrain(ttyPtr->fileState.fd); break; case CLOSE_DISCARD: tcflush(ttyPtr->fileState.fd, TCIOFLUSH); break; default: /* Do nothing */ break; } /* * If we've had our state changed from the default, reset now. */ if (ttyPtr->doReset) { tcsetattr(ttyPtr->fileState.fd, TCSANOW, &ttyPtr->initState); } /* * Delegate to close for files. */ return FileCloseProc(instanceData, interp, flags); } #endif /* SUPPORTS_TTY */ /* *---------------------------------------------------------------------- * * FileWideSeekProc -- * * This function is called by the generic IO level to move the access * point in a file based channel, with offsets expressed as wide * integers. * * Results: * -1 if failed, the new position if successful. An output argument * contains the POSIX error code if an error occurred, or zero. * * Side effects: * Moves the location at which the channel will be accessed in future * operations. * *---------------------------------------------------------------------- */ static long long FileWideSeekProc( void *instanceData, /* File state. */ long long offset, /* Offset to seek to. */ int mode, /* Relative to where should we seek? Can be * one of SEEK_START, SEEK_CUR or SEEK_END. */ int *errorCodePtr) /* To store error code. */ { FileState *fsPtr = (FileState *)instanceData; long long newLoc; newLoc = TclOSseek(fsPtr->fd, (Tcl_SeekOffset) offset, mode); *errorCodePtr = (newLoc == -1) ? errno : 0; return newLoc; } /* *---------------------------------------------------------------------- * * FileWatchProc -- * * Initialize the notifier to watch the fd from this channel. * * Results: * None. * * Side effects: * Sets up the notifier so that a future event on the channel will * be seen by Tcl. * *---------------------------------------------------------------------- */ /* * Bug ad5a57f2f271: Tcl_NotifyChannel is not a Tcl_FileProc, * so do not pass it to directly to Tcl_CreateFileHandler. * Instead, pass a wrapper which is a Tcl_FileProc. */ static void FileWatchNotifyChannelWrapper( void *clientData, int mask) { Tcl_Channel channel = (Tcl_Channel)clientData; Tcl_NotifyChannel(channel, mask); } static void FileWatchProc( void *instanceData, /* The file state. */ int mask) /* Events of interest; an OR-ed combination of * TCL_READABLE, TCL_WRITABLE and * TCL_EXCEPTION. */ { FileState *fsPtr = (FileState *)instanceData; /* * Make sure we only register for events that are valid on this file. */ mask &= fsPtr->validMask; if (mask) { Tcl_CreateFileHandler(fsPtr->fd, mask, FileWatchNotifyChannelWrapper, fsPtr->channel); } else { Tcl_DeleteFileHandler(fsPtr->fd); } } /* *---------------------------------------------------------------------- * * FileGetHandleProc -- * * Called from Tcl_GetChannelHandle to retrieve OS handles from a file * based channel. * * Results: * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no * handle for the specified direction. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FileGetHandleProc( void *instanceData, /* The file state. */ int direction, /* TCL_READABLE or TCL_WRITABLE */ void **handlePtr) /* Where to store the handle. */ { FileState *fsPtr = (FileState *)instanceData; if (direction & fsPtr->validMask) { *handlePtr = INT2PTR(fsPtr->fd); return TCL_OK; } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * FileGetOptionProc -- * * Gets an option associated with an open file. If the optionName arg is * non-NULL, retrieves the value of that option. If the optionName arg is * NULL, retrieves a list of alternating option names and values for the * given channel. * * Results: * A standard Tcl result. Also sets the supplied DString to the string * value of the option(s) returned. Sets error message if needed * (by calling Tcl_BadChannelOption). * *---------------------------------------------------------------------- */ static inline const char * GetTypeFromMode( int mode) { /* * TODO: deduplicate with tclCmdAH.c */ if (S_ISREG(mode)) { return "file"; } else if (S_ISDIR(mode)) { return "directory"; } else if (S_ISCHR(mode)) { return "characterSpecial"; } else if (S_ISBLK(mode)) { return "blockSpecial"; } else if (S_ISFIFO(mode)) { return "fifo"; #ifdef S_ISLNK } else if (S_ISLNK(mode)) { return "link"; #endif #ifdef S_ISSOCK } else if (S_ISSOCK(mode)) { return "socket"; #endif } return "unknown"; } static Tcl_Obj * StatOpenFile( FileState *fsPtr) { Tcl_StatBuf statBuf; /* Not allocated on heap; we're definitely * API-synchronized with how Tcl is built! */ Tcl_Obj *dictObj; unsigned short mode; if (TclOSfstat(fsPtr->fd, &statBuf) < 0) { return NULL; } /* * TODO: merge with TIP 594 implementation (it's silly to have a * duplicate!) */ TclNewObj(dictObj); #define STORE_ELEM(name, value) TclDictPut(NULL, dictObj, name, value) STORE_ELEM("dev", Tcl_NewWideIntObj((long) statBuf.st_dev)); STORE_ELEM("ino", Tcl_NewWideIntObj((Tcl_WideInt) statBuf.st_ino)); STORE_ELEM("nlink", Tcl_NewWideIntObj((long) statBuf.st_nlink)); STORE_ELEM("uid", Tcl_NewWideIntObj((long) statBuf.st_uid)); STORE_ELEM("gid", Tcl_NewWideIntObj((long) statBuf.st_gid)); STORE_ELEM("size", Tcl_NewWideIntObj((Tcl_WideInt) statBuf.st_size)); #ifdef HAVE_STRUCT_STAT_ST_BLOCKS STORE_ELEM("blocks", Tcl_NewWideIntObj((Tcl_WideInt) statBuf.st_blocks)); #endif #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE STORE_ELEM("blksize", Tcl_NewWideIntObj((long) statBuf.st_blksize)); #endif #ifdef HAVE_STRUCT_STAT_ST_RDEV if (S_ISCHR(statBuf.st_mode) || S_ISBLK(statBuf.st_mode)) { STORE_ELEM("rdev", Tcl_NewWideIntObj((long) statBuf.st_rdev)); } #endif STORE_ELEM("atime", Tcl_NewWideIntObj( Tcl_GetAccessTimeFromStat(&statBuf))); STORE_ELEM("mtime", Tcl_NewWideIntObj( Tcl_GetModificationTimeFromStat(&statBuf))); STORE_ELEM("ctime", Tcl_NewWideIntObj( Tcl_GetChangeTimeFromStat(&statBuf))); mode = (unsigned short) statBuf.st_mode; STORE_ELEM("mode", Tcl_NewWideIntObj(mode)); STORE_ELEM("type", Tcl_NewStringObj(GetTypeFromMode(mode), -1)); #undef STORE_ELEM return dictObj; } static int FileGetOptionProc( void *instanceData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr) { FileState *fsPtr = (FileState *)instanceData; int valid = 0; /* Flag if valid option parsed. */ int len; if (optionName == NULL) { len = 0; valid = 1; } else { len = strlen(optionName); } /* * Get option -stat * Option is readonly and returned by [fconfigure chan -stat] but not * returned by [fconfigure chan] without explicit option name. */ if ((len > 1) && (strncmp(optionName, "-stat", len) == 0)) { Tcl_Obj *dictObj = StatOpenFile(fsPtr); const char *dictContents; Tcl_Size dictLength; if (dictObj == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file channel status: %s", Tcl_PosixError(interp))); return TCL_ERROR; } /* * Transfer dictionary to the DString. Note that we don't do this as * an element as this is an option that can't be retrieved with a * general probe. */ dictContents = TclGetStringFromObj(dictObj, &dictLength); Tcl_DStringAppend(dsPtr, dictContents, dictLength); Tcl_DecrRefCount(dictObj); return TCL_OK; } if (valid) { return TCL_OK; } return Tcl_BadChannelOption(interp, optionName, "stat"); } #ifdef SUPPORTS_TTY /* *---------------------------------------------------------------------- * * TtyModemStatusStr -- * * Converts a RS232 modem status list of readable flags * *---------------------------------------------------------------------- */ static void TtyModemStatusStr( int status, /* RS232 modem status */ Tcl_DString *dsPtr) /* Where to store string */ { #ifdef TIOCM_CTS Tcl_DStringAppendElement(dsPtr, "CTS"); Tcl_DStringAppendElement(dsPtr, (status & TIOCM_CTS) ? "1" : "0"); #endif /* TIOCM_CTS */ #ifdef TIOCM_DSR Tcl_DStringAppendElement(dsPtr, "DSR"); Tcl_DStringAppendElement(dsPtr, (status & TIOCM_DSR) ? "1" : "0"); #endif /* TIOCM_DSR */ #ifdef TIOCM_RNG Tcl_DStringAppendElement(dsPtr, "RING"); Tcl_DStringAppendElement(dsPtr, (status & TIOCM_RNG) ? "1" : "0"); #endif /* TIOCM_RNG */ #ifdef TIOCM_CD Tcl_DStringAppendElement(dsPtr, "DCD"); Tcl_DStringAppendElement(dsPtr, (status & TIOCM_CD) ? "1" : "0"); #endif /* TIOCM_CD */ } /* *---------------------------------------------------------------------- * * TtySetOptionProc -- * * Sets an option on a channel. * * Results: * A standard Tcl result. Also sets the interp's result on error if * interp is not NULL. * * Side effects: * May modify an option on a device. Sets Error message if needed (by * calling Tcl_BadChannelOption). * *---------------------------------------------------------------------- */ static int TtySetOptionProc( void *instanceData, /* File state. */ Tcl_Interp *interp, /* For error reporting - can be NULL. */ const char *optionName, /* Which option to set? */ const char *value) /* New value for option. */ { TtyState *fsPtr = (TtyState *)instanceData; size_t len, vlen; TtyAttrs tty; Tcl_Size argc; const char **argv; struct termios iostate; len = strlen(optionName); vlen = strlen(value); /* * Option -mode baud,parity,databits,stopbits */ if ((len > 2) && (strncmp(optionName, "-mode", len) == 0)) { if (TtyParseMode(interp, value, &tty) != TCL_OK) { return TCL_ERROR; } /* * system calls results should be checked there. - dl */ TtySetAttributes(fsPtr->fileState.fd, &tty); return TCL_OK; } /* * Option -handshake none|xonxoff|rtscts|dtrdsr */ if ((len > 1) && (strncmp(optionName, "-handshake", len) == 0)) { /* * Reset all handshake options. DTR and RTS are ON by default. */ tcgetattr(fsPtr->fileState.fd, &iostate); CLEAR_BITS(iostate.c_iflag, IXON | IXOFF | IXANY); #ifdef CRTSCTS CLEAR_BITS(iostate.c_cflag, CRTSCTS); #endif /* CRTSCTS */ if (strncasecmp(value, "NONE", vlen) == 0) { /* * Leave all handshake options disabled. */ } else if (strncasecmp(value, "XONXOFF", vlen) == 0) { SET_BITS(iostate.c_iflag, IXON | IXOFF | IXANY); } else if (strncasecmp(value, "RTSCTS", vlen) == 0) { #ifdef CRTSCTS SET_BITS(iostate.c_cflag, CRTSCTS); #else /* !CRTSTS */ UNSUPPORTED_OPTION("-handshake RTSCTS"); return TCL_ERROR; #endif /* CRTSCTS */ } else if (strncasecmp(value, "DTRDSR", vlen) == 0) { UNSUPPORTED_OPTION("-handshake DTRDSR"); return TCL_ERROR; } else { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad value for -handshake: must be one of" " xonxoff, rtscts, dtrdsr or none", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE", "VALUE", (char *)NULL); } return TCL_ERROR; } tcsetattr(fsPtr->fileState.fd, TCSADRAIN, &iostate); return TCL_OK; } /* * Option -xchar {\x11 \x13} */ if ((len > 1) && (strncmp(optionName, "-xchar", len) == 0)) { if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) { return TCL_ERROR; } else if (argc != 2) { badXchar: if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad value for -xchar: should be a list of" " two elements with each a single 8-bit character", -1)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "XCHAR", (char *)NULL); } Tcl_Free(argv); return TCL_ERROR; } tcgetattr(fsPtr->fileState.fd, &iostate); iostate.c_cc[VSTART] = argv[0][0]; iostate.c_cc[VSTOP] = argv[1][0]; if (argv[0][0] & 0x80 || argv[1][0] & 0x80) { Tcl_UniChar character = 0; int charLen; charLen = TclUtfToUniChar(argv[0], &character); if ((character > 0xFF) || argv[0][charLen]) { goto badXchar; } iostate.c_cc[VSTART] = character; charLen = TclUtfToUniChar(argv[1], &character); if ((character > 0xFF) || argv[1][charLen]) { goto badXchar; } iostate.c_cc[VSTOP] = character; } Tcl_Free(argv); tcsetattr(fsPtr->fileState.fd, TCSADRAIN, &iostate); return TCL_OK; } /* * Option -timeout msec */ if ((len > 2) && (strncmp(optionName, "-timeout", len) == 0)) { int msec; tcgetattr(fsPtr->fileState.fd, &iostate); if (Tcl_GetInt(interp, value, &msec) != TCL_OK) { return TCL_ERROR; } iostate.c_cc[VMIN] = 0; iostate.c_cc[VTIME] = (msec==0) ? 0 : (msec<100) ? 1 : (msec+50)/100; tcsetattr(fsPtr->fileState.fd, TCSADRAIN, &iostate); return TCL_OK; } /* * Option -ttycontrol {DTR 1 RTS 0 BREAK 0} */ if ((len > 4) && (strncmp(optionName, "-ttycontrol", len) == 0)) { #if defined(TIOCMGET) && defined(TIOCMSET) int control, flag; Tcl_Size i; if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) { return TCL_ERROR; } if ((argc % 2) == 1) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad value for -ttycontrol: should be a list of" " signal,value pairs", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE", "VALUE", (char *)NULL); } Tcl_Free(argv); return TCL_ERROR; } ioctl(fsPtr->fileState.fd, TIOCMGET, &control); for (i = 0; i < argc-1; i += 2) { if (Tcl_GetBoolean(interp, argv[i+1], &flag) == TCL_ERROR) { Tcl_Free(argv); return TCL_ERROR; } if (strncasecmp(argv[i], "DTR", strlen(argv[i])) == 0) { if (flag) { SET_BITS(control, TIOCM_DTR); } else { CLEAR_BITS(control, TIOCM_DTR); } } else if (strncasecmp(argv[i], "RTS", strlen(argv[i])) == 0) { if (flag) { SET_BITS(control, TIOCM_RTS); } else { CLEAR_BITS(control, TIOCM_RTS); } } else if (strncasecmp(argv[i], "BREAK", strlen(argv[i])) == 0) { #if defined(TIOCSBRK) && defined(TIOCCBRK) if (flag) { ioctl(fsPtr->fileState.fd, TIOCSBRK, NULL); } else { ioctl(fsPtr->fileState.fd, TIOCCBRK, NULL); } #else /* TIOCSBRK & TIOCCBRK */ UNSUPPORTED_OPTION("-ttycontrol BREAK"); Tcl_Free(argv); return TCL_ERROR; #endif /* TIOCSBRK & TIOCCBRK */ } else { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad signal \"%s\" for -ttycontrol: must be" " DTR, RTS or BREAK", argv[i])); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE", "VALUE", (char *)NULL); } Tcl_Free(argv); return TCL_ERROR; } } /* -ttycontrol options loop */ ioctl(fsPtr->fileState.fd, TIOCMSET, &control); Tcl_Free(argv); return TCL_OK; #else /* TIOCMGET&TIOCMSET */ UNSUPPORTED_OPTION("-ttycontrol"); #endif /* TIOCMGET&TIOCMSET */ } /* * Option -closemode drain|discard */ if ((len > 2) && (strncmp(optionName, "-closemode", len) == 0)) { if (strncasecmp(value, "DEFAULT", vlen) == 0) { fsPtr->closeMode = CLOSE_DEFAULT; } else if (strncasecmp(value, "DRAIN", vlen) == 0) { fsPtr->closeMode = CLOSE_DRAIN; } else if (strncasecmp(value, "DISCARD", vlen) == 0) { fsPtr->closeMode = CLOSE_DISCARD; } else { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad mode \"%s\" for -closemode: must be" " default, discard, or drain", value)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE", "VALUE", (char *)NULL); } return TCL_ERROR; } return TCL_OK; } /* * Option -inputmode normal|password|raw */ if ((len > 2) && (strncmp(optionName, "-inputmode", len) == 0)) { if (tcgetattr(fsPtr->fileState.fd, &iostate) < 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read serial terminal control state: %s", Tcl_PosixError(interp))); } return TCL_ERROR; } if (strncasecmp(value, "NORMAL", vlen) == 0) { SET_BITS(iostate.c_iflag, BRKINT | IGNPAR | ISTRIP | ICRNL | IXON); SET_BITS(iostate.c_oflag, OPOST); SET_BITS(iostate.c_lflag, ECHO | ECHONL | ICANON | ISIG); } else if (strncasecmp(value, "PASSWORD", vlen) == 0) { SET_BITS(iostate.c_iflag, BRKINT | IGNPAR | ISTRIP | ICRNL | IXON); SET_BITS(iostate.c_oflag, OPOST); CLEAR_BITS(iostate.c_lflag, ECHO); /* * Note: password input turns out to be best if you echo the * newline that the user types. Theoretically we could get users * to do the processing of this in their scripts, but it always * feels highly unnatural to do so in practice. */ SET_BITS(iostate.c_lflag, ECHONL | ICANON | ISIG); } else if (strncasecmp(value, "RAW", vlen) == 0) { #ifdef HAVE_CFMAKERAW cfmakeraw(&iostate); #else /* !HAVE_CFMAKERAW */ CLEAR_BITS(iostate.c_iflag, IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); CLEAR_BITS(iostate.c_oflag, OPOST); CLEAR_BITS(iostate.c_lflag, ECHO | ECHONL | ICANON | ISIG | IEXTEN); CLEAR_BITS(iostate.c_cflag, CSIZE | PARENB); SET_BITS(iostate.c_cflag, CS8); #endif /* HAVE_CFMAKERAW */ } else if (strncasecmp(value, "RESET", vlen) == 0) { /* * Reset to the initial state, whatever that is. */ memcpy(&iostate, &fsPtr->initState, sizeof(struct termios)); } else { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad mode \"%s\" for -inputmode: must be" " normal, password, raw, or reset", value)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE", "VALUE", (char *)NULL); } return TCL_ERROR; } if (tcsetattr(fsPtr->fileState.fd, TCSADRAIN, &iostate) < 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't update serial terminal control state: %s", Tcl_PosixError(interp))); } return TCL_ERROR; } /* * If we've changed the state from default, schedule a reset later. * Note that this specifically does not detect changes made by calling * an external stty program; that is deliberate, as it maintains * compatibility with existing code! * * This mechanism in Tcl is not intended to be a full replacement for * what stty does; it just handles a few common cases and tries not to * leave things in a broken state. */ fsPtr->doReset = (memcmp(&iostate, &fsPtr->initState, sizeof(struct termios)) != 0); return TCL_OK; } return Tcl_BadChannelOption(interp, optionName, "closemode inputmode mode handshake timeout ttycontrol xchar"); } /* *---------------------------------------------------------------------- * * TtyGetOptionProc -- * * Gets a mode associated with an IO channel. If the optionName arg is * non-NULL, retrieves the value of that option. If the optionName arg is * NULL, retrieves a list of alternating option names and values for the * given channel. * * Results: * A standard Tcl result. Also sets the supplied DString to the string * value of the option(s) returned. Sets error message if needed * (by calling Tcl_BadChannelOption). * *---------------------------------------------------------------------- */ static int TtyGetOptionProc( void *instanceData, /* File state. */ Tcl_Interp *interp, /* For error reporting - can be NULL. */ const char *optionName, /* Option to get. */ Tcl_DString *dsPtr) /* Where to store value(s). */ { TtyState *fsPtr = (TtyState *)instanceData; size_t len; char buf[3*TCL_INTEGER_SPACE + 16]; int valid = 0; /* Flag if valid option parsed. */ struct termios iostate; if (optionName == NULL) { len = 0; } else { len = strlen(optionName); } /* * Get option -closemode */ if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-closemode"); } if (len==0 || (len>1 && strncmp(optionName, "-closemode", len)==0)) { switch (fsPtr->closeMode) { case CLOSE_DRAIN: Tcl_DStringAppendElement(dsPtr, "drain"); break; case CLOSE_DISCARD: Tcl_DStringAppendElement(dsPtr, "discard"); break; default: Tcl_DStringAppendElement(dsPtr, "default"); break; } } /* * Get option -inputmode * * This is a great simplification of the underlying reality, but actually * represents what almost all scripts really want to know. */ if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-inputmode"); } if (len==0 || (len>1 && strncmp(optionName, "-inputmode", len)==0)) { valid = 1; if (tcgetattr(fsPtr->fileState.fd, &iostate) < 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read serial terminal control state: %s", Tcl_PosixError(interp))); } return TCL_ERROR; } if (iostate.c_lflag & ICANON) { if (iostate.c_lflag & ECHO) { Tcl_DStringAppendElement(dsPtr, "normal"); } else { Tcl_DStringAppendElement(dsPtr, "password"); } } else { Tcl_DStringAppendElement(dsPtr, "raw"); } } /* * Get option -mode */ if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-mode"); } if (len==0 || (len>2 && strncmp(optionName, "-mode", len)==0)) { TtyAttrs tty; valid = 1; TtyGetAttributes(fsPtr->fileState.fd, &tty); snprintf(buf, sizeof(buf), "%d,%c,%d,%d", tty.baud, tty.parity, tty.data, tty.stop); Tcl_DStringAppendElement(dsPtr, buf); } /* * Get option -xchar */ if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-xchar"); Tcl_DStringStartSublist(dsPtr); } if (len==0 || (len>1 && strncmp(optionName, "-xchar", len)==0)) { Tcl_DString ds; valid = 1; tcgetattr(fsPtr->fileState.fd, &iostate); Tcl_DStringInit(&ds); Tcl_ExternalToUtfDStringEx(NULL, NULL, (char *) &iostate.c_cc[VSTART], 1, TCL_ENCODING_PROFILE_TCL8, &ds, NULL); Tcl_DStringAppendElement(dsPtr, Tcl_DStringValue(&ds)); TclDStringClear(&ds); Tcl_ExternalToUtfDStringEx(NULL, NULL, (char *) &iostate.c_cc[VSTOP], 1, TCL_ENCODING_PROFILE_TCL8, &ds, NULL); Tcl_DStringAppendElement(dsPtr, Tcl_DStringValue(&ds)); Tcl_DStringFree(&ds); } if (len == 0) { Tcl_DStringEndSublist(dsPtr); } /* * Get option -queue * Option is readonly and returned by [fconfigure chan -queue] but not * returned by unnamed [fconfigure chan]. */ if ((len > 1) && (strncmp(optionName, "-queue", len) == 0)) { int inQueue=0, outQueue=0, inBuffered, outBuffered; valid = 1; GETREADQUEUE(fsPtr->fileState.fd, inQueue); GETWRITEQUEUE(fsPtr->fileState.fd, outQueue); inBuffered = Tcl_InputBuffered(fsPtr->fileState.channel); outBuffered = Tcl_OutputBuffered(fsPtr->fileState.channel); snprintf(buf, sizeof(buf), "%d", inBuffered+inQueue); Tcl_DStringAppendElement(dsPtr, buf); snprintf(buf, sizeof(buf), "%d", outBuffered+outQueue); Tcl_DStringAppendElement(dsPtr, buf); } #if defined(TIOCMGET) /* * Get option -ttystatus * Option is readonly and returned by [fconfigure chan -ttystatus] but not * returned by unnamed [fconfigure chan]. */ if ((len > 4) && (strncmp(optionName, "-ttystatus", len) == 0)) { int status; valid = 1; ioctl(fsPtr->fileState.fd, TIOCMGET, &status); TtyModemStatusStr(status, dsPtr); } #endif /* TIOCMGET */ #if defined(TIOCGWINSZ) /* * Get option -winsize * Option is readonly and returned by [fconfigure chan -winsize] but not * returned by [fconfigure chan] without explicit option name. */ if ((len > 1) && (strncmp(optionName, "-winsize", len) == 0)) { struct winsize ws; valid = 1; if (ioctl(fsPtr->fileState.fd, TIOCGWINSZ, &ws) < 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read terminal size: %s", Tcl_PosixError(interp))); } return TCL_ERROR; } snprintf(buf, sizeof(buf), "%d", ws.ws_col); Tcl_DStringAppendElement(dsPtr, buf); snprintf(buf, sizeof(buf), "%d", ws.ws_row); Tcl_DStringAppendElement(dsPtr, buf); } #endif /* TIOCGWINSZ */ if (valid) { return TCL_OK; } return Tcl_BadChannelOption(interp, optionName, "closemode inputmode mode queue ttystatus winsize xchar"); } static const struct {int baud; speed_t speed;} speeds[] = { #ifdef B0 {0, B0}, #endif #ifdef B50 {50, B50}, #endif #ifdef B75 {75, B75}, #endif #ifdef B110 {110, B110}, #endif #ifdef B134 {134, B134}, #endif #ifdef B150 {150, B150}, #endif #ifdef B200 {200, B200}, #endif #ifdef B300 {300, B300}, #endif #ifdef B600 {600, B600}, #endif #ifdef B1200 {1200, B1200}, #endif #ifdef B1800 {1800, B1800}, #endif #ifdef B2400 {2400, B2400}, #endif #ifdef B4800 {4800, B4800}, #endif #ifdef B9600 {9600, B9600}, #endif #ifdef B14400 {14400, B14400}, #endif #ifdef B19200 {19200, B19200}, #endif #ifdef EXTA {19200, EXTA}, #endif #ifdef B28800 {28800, B28800}, #endif #ifdef B38400 {38400, B38400}, #endif #ifdef EXTB {38400, EXTB}, #endif #ifdef B57600 {57600, B57600}, #endif #ifdef _B57600 {57600, _B57600}, #endif #ifdef B76800 {76800, B76800}, #endif #ifdef B115200 {115200, B115200}, #endif #ifdef _B115200 {115200, _B115200}, #endif #ifdef B153600 {153600, B153600}, #endif #ifdef B230400 {230400, B230400}, #endif #ifdef B307200 {307200, B307200}, #endif #ifdef B460800 {460800, B460800}, #endif #ifdef B500000 {500000, B500000}, #endif #ifdef B576000 {576000, B576000}, #endif #ifdef B921600 {921600, B921600}, #endif #ifdef B1000000 {1000000, B1000000}, #endif #ifdef B1152000 {1152000, B1152000}, #endif #ifdef B1500000 {1500000,B1500000}, #endif #ifdef B2000000 {2000000, B2000000}, #endif #ifdef B2500000 {2500000,B2500000}, #endif #ifdef B3000000 {3000000,B3000000}, #endif #ifdef B3500000 {3500000,B3500000}, #endif #ifdef B4000000 {4000000,B4000000}, #endif {-1, 0} }; /* *--------------------------------------------------------------------------- * * TtyGetSpeed -- * * Given an integer baud rate, get the speed_t value that should be * used to select that baud rate. * * Results: * As above. * *--------------------------------------------------------------------------- */ static speed_t TtyGetSpeed( int baud) /* The baud rate to look up. */ { int bestIdx, bestDiff, i, diff; bestIdx = 0; bestDiff = 1000000; /* * If the baud rate does not correspond to one of the known mask values, * choose the mask value whose baud rate is closest to the specified baud * rate. */ for (i = 0; speeds[i].baud >= 0; i++) { diff = speeds[i].baud - baud; if (diff < 0) { diff = -diff; } if (diff < bestDiff) { bestIdx = i; bestDiff = diff; } } return speeds[bestIdx].speed; } /* *--------------------------------------------------------------------------- * * TtyGetBaud -- * * Return the integer baud rate corresponding to a given speed_t value. * * Results: * As above. If the mask value was not recognized, 0 is returned. * *--------------------------------------------------------------------------- */ static int TtyGetBaud( speed_t speed) /* Speed mask value to look up. */ { int i; for (i = 0; speeds[i].baud >= 0; i++) { if (speeds[i].speed == speed) { return speeds[i].baud; } } return 0; } /* *--------------------------------------------------------------------------- * * TtyGetAttributes -- * * Get the current attributes of the specified serial device. * * Results: * None. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static void TtyGetAttributes( int fd, /* Open file descriptor for serial port to be * queried. */ TtyAttrs *ttyPtr) /* Buffer filled with serial port * attributes. */ { struct termios iostate; int baud, parity, data, stop; tcgetattr(fd, &iostate); baud = TtyGetBaud(cfgetospeed(&iostate)); parity = 'n'; #ifdef PAREXT switch ((int) (iostate.c_cflag & (PARENB | PARODD | PAREXT))) { case PARENB : parity = 'e'; break; case PARENB | PARODD : parity = 'o'; break; case PARENB | PAREXT : parity = 's'; break; case PARENB | PARODD | PAREXT : parity = 'm'; break; } #else /* !PAREXT */ switch ((int) (iostate.c_cflag & (PARENB | PARODD))) { case PARENB : parity = 'e'; break; case PARENB | PARODD : parity = 'o'; break; } #endif /* PAREXT */ data = iostate.c_cflag & CSIZE; data = (data == CS5) ? 5 : (data == CS6) ? 6 : (data == CS7) ? 7 : 8; stop = (iostate.c_cflag & CSTOPB) ? 2 : 1; ttyPtr->baud = baud; ttyPtr->parity = parity; ttyPtr->data = data; ttyPtr->stop = stop; } /* *--------------------------------------------------------------------------- * * TtySetAttributes -- * * Set the current attributes of the specified serial device. * * Results: * None. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static void TtySetAttributes( int fd, /* Open file descriptor for serial port to be * modified. */ TtyAttrs *ttyPtr) /* Buffer containing new attributes for serial * port. */ { struct termios iostate; int parity, data, flag; tcgetattr(fd, &iostate); cfsetospeed(&iostate, TtyGetSpeed(ttyPtr->baud)); cfsetispeed(&iostate, TtyGetSpeed(ttyPtr->baud)); flag = 0; parity = ttyPtr->parity; if (parity != 'n') { SET_BITS(flag, PARENB); #ifdef PAREXT CLEAR_BITS(iostate.c_cflag, PAREXT); if ((parity == 'm') || (parity == 's')) { SET_BITS(flag, PAREXT); } #endif /* PAREXT */ if ((parity == 'm') || (parity == 'o')) { SET_BITS(flag, PARODD); } } data = ttyPtr->data; SET_BITS(flag, (data == 5) ? CS5 : (data == 6) ? CS6 : (data == 7) ? CS7 : CS8); if (ttyPtr->stop == 2) { SET_BITS(flag, CSTOPB); } CLEAR_BITS(iostate.c_cflag, PARENB | PARODD | CSIZE | CSTOPB); SET_BITS(iostate.c_cflag, flag); tcsetattr(fd, TCSADRAIN, &iostate); } /* *--------------------------------------------------------------------------- * * TtyParseMode -- * * Parse the "-mode" argument to the fconfigure command. The argument is * of the form baud,parity,data,stop. * * Results: * The return value is TCL_OK if the argument was successfully parsed, * TCL_ERROR otherwise. If TCL_ERROR is returned, an error message is * left in the interp's result (if interp is non-NULL). * *--------------------------------------------------------------------------- */ static int TtyParseMode( Tcl_Interp *interp, /* If non-NULL, interp for error return. */ const char *mode, /* Mode string to be parsed. */ TtyAttrs *ttyPtr) /* Filled with data from mode string */ { int i, end; char parity; const char *bad = "bad value for -mode"; i = sscanf(mode, "%d,%c,%d,%d%n", &ttyPtr->baud, &parity, &ttyPtr->data, &ttyPtr->stop, &end); if ((i != 4) || (mode[end] != '\0')) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s: should be baud,parity,data,stop", bad)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "SERIALMODE", (char *)NULL); } return TCL_ERROR; } /* * Only allow setting mark/space parity on platforms that support it Make * sure to allow for the case where strchr is a macro. [Bug: 5089] * * We cannot if/else/endif the strchr arguments, it has to be the whole * function. On AIX this function is apparently a macro, and macros do * not allow preprocessor directives in their arguments. */ #ifdef PAREXT #define PARITY_CHARS "noems" #define PARITY_MSG "n, o, e, m, or s" #else #define PARITY_CHARS "noe" #define PARITY_MSG "n, o, or e" #endif /* PAREXT */ if (strchr(PARITY_CHARS, parity) == NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s parity: should be %s", bad, PARITY_MSG)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "SERIALMODE", (char *)NULL); } return TCL_ERROR; } ttyPtr->parity = parity; if ((ttyPtr->data < 5) || (ttyPtr->data > 8)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s data: should be 5, 6, 7, or 8", bad)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "SERIALMODE", (char *)NULL); } return TCL_ERROR; } if ((ttyPtr->stop < 0) || (ttyPtr->stop > 2)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s stop: should be 1 or 2", bad)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "SERIALMODE", (char *)NULL); } return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * TtyInit -- * * Given file descriptor that refers to a serial port, initialize the * serial port to a set of sane values so that Tcl can talk to a device * located on the serial port. * * Side effects: * Serial device initialized to non-blocking raw mode, similar to sockets * All other modes can be simulated on top of this in Tcl. * *--------------------------------------------------------------------------- */ static void TtyInit( int fd) /* Open file descriptor for serial port to be * initialized. */ { struct termios iostate; tcgetattr(fd, &iostate); if (iostate.c_iflag != IGNBRK || iostate.c_oflag != 0 || iostate.c_lflag != 0 || iostate.c_cflag & CREAD || iostate.c_cc[VMIN] != 1 || iostate.c_cc[VTIME] != 0) { iostate.c_iflag = IGNBRK; iostate.c_oflag = 0; iostate.c_lflag = 0; iostate.c_cflag |= CREAD; iostate.c_cc[VMIN] = 1; iostate.c_cc[VTIME] = 0; tcsetattr(fd, TCSADRAIN, &iostate); } } #endif /* SUPPORTS_TTY */ /* *---------------------------------------------------------------------- * * TclpOpenFileChannel -- * * Open an file based channel on Unix systems. * * Results: * The new channel or NULL. If NULL, the output argument errorCodePtr is * set to a POSIX error and an error message is left in the interp's * result if interp is not NULL. * * Side effects: * May open the channel and may cause creation of a file on the file * system. * *---------------------------------------------------------------------- */ Tcl_Channel TclpOpenFileChannel( Tcl_Interp *interp, /* Interpreter for error reporting; can be * NULL. */ Tcl_Obj *pathPtr, /* Name of file to open. */ int mode, /* POSIX open mode. */ int permissions) /* If the open involves creating a file, with * what modes to create it? */ { int fd, channelPermissions; TtyState *fsPtr; const char *native, *translation; char channelName[16 + TCL_INTEGER_SPACE]; const Tcl_ChannelType *channelTypePtr; switch (mode & O_ACCMODE) { case O_RDONLY: channelPermissions = TCL_READABLE; break; case O_WRONLY: channelPermissions = TCL_WRITABLE; break; case O_RDWR: channelPermissions = (TCL_READABLE | TCL_WRITABLE); break; default: /* * This may occurr if modeString was "", for example. */ Tcl_Panic("TclpOpenFileChannel: invalid mode value"); return NULL; } native = (const char *)Tcl_FSGetNativePath(pathPtr); if (native == NULL) { if (interp != (Tcl_Interp *) NULL) { /* * We need this just to ensure we return the correct error messages under * some circumstances (relative paths only), so because the normalization * is very expensive, don't invoke it for native or absolute paths. * Note: since paths starting with ~ are absolute, it also considers tilde expansion, * (proper error message of tests *io-40.17 "tilde substitution in open") */ if ( ( ( !TclFSCwdIsNative() && (Tcl_FSGetPathType(pathPtr) != TCL_PATH_ABSOLUTE) ) || (*TclGetString(pathPtr) == '~') /* possible tilde expansion */ ) && Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL ) { return NULL; } Tcl_AppendResult(interp, "couldn't open \"", TclGetString(pathPtr), "\": filename is invalid on this platform", (char *)NULL); } return NULL; } #ifdef DJGPP SET_BITS(mode, O_BINARY); #endif fd = TclOSopen(native, mode, permissions); if (fd < 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't open \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); } return NULL; } /* * Set close-on-exec flag on the fd so that child processes will not * inherit this fd. */ fcntl(fd, F_SETFD, FD_CLOEXEC); #ifdef SUPPORTS_TTY if (strcmp(native, "/dev/tty") != 0 && isatty(fd)) { /* * Initialize the serial port to a set of sane parameters. Especially * important if the remote device is set to echo and the serial port * driver was also set to echo -- as soon as a char were sent to the * serial port, the remote device would echo it, then the serial * driver would echo it back to the device, etc. * * Note that we do not do this if we're dealing with /dev/tty itself, * as that tends to cause Bad Things To Happen when you're working * interactively. Strictly a better check would be to see if the FD * being set up is a device and has the same major/minor as the * initial std FDs (beware reopening!) but that's nearly as messy. */ translation = "auto crlf"; channelTypePtr = &ttyChannelType; TtyInit(fd); snprintf(channelName, sizeof(channelName), "serial%d", fd); } else #endif /* SUPPORTS_TTY */ { translation = NULL; channelTypePtr = &fileChannelType; snprintf(channelName, sizeof(channelName), "file%d", fd); } fsPtr = (TtyState *)Tcl_Alloc(sizeof(TtyState)); fsPtr->fileState.validMask = channelPermissions | TCL_EXCEPTION; fsPtr->fileState.fd = fd; #ifdef SUPPORTS_TTY if (channelTypePtr == &ttyChannelType) { fsPtr->closeMode = CLOSE_DEFAULT; fsPtr->doReset = 0; tcgetattr(fsPtr->fileState.fd, &fsPtr->initState); } #endif /* SUPPORTS_TTY */ fsPtr->fileState.channel = Tcl_CreateChannel(channelTypePtr, channelName, fsPtr, channelPermissions); if (translation != NULL) { /* * Gotcha. Most modems need a "\r" at the end of the command sequence. * If you just send "at\n", the modem will not respond with "OK" * because it never got a "\r" to actually invoke the command. So, by * default, newlines are translated to "\r\n" on output to avoid "bug" * reports that the serial port isn't working. */ if (Tcl_SetChannelOption(interp, fsPtr->fileState.channel, "-translation", translation) != TCL_OK) { Tcl_CloseEx(NULL, fsPtr->fileState.channel, 0); return NULL; } } return fsPtr->fileState.channel; } /* *---------------------------------------------------------------------- * * Tcl_MakeFileChannel -- * * Makes a Tcl_Channel from an existing OS level file handle. * * Results: * The Tcl_Channel created around the preexisting OS level file handle. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_MakeFileChannel( void *handle, /* OS level handle. */ int mode) /* OR'ed combination of TCL_READABLE and * TCL_WRITABLE to indicate file mode. */ { TtyState *fsPtr; char channelName[16 + TCL_INTEGER_SPACE]; int fd = PTR2INT(handle); const Tcl_ChannelType *channelTypePtr; Tcl_StatBuf buf; if (mode == 0) { return NULL; } #ifdef SUPPORTS_TTY if (isatty(fd)) { channelTypePtr = &ttyChannelType; snprintf(channelName, sizeof(channelName), "serial%d", fd); } else #endif /* SUPPORTS_TTY */ if (TclOSfstat(fd, &buf) == 0 && S_ISSOCK(buf.st_mode)) { struct sockaddr sockaddr; socklen_t sockaddrLen = sizeof(sockaddr); sockaddr.sa_family = AF_UNSPEC; if ((getsockname(fd, (struct sockaddr *)&sockaddr, &sockaddrLen) == 0) && (sockaddrLen > 0) && (sockaddr.sa_family == AF_INET || sockaddr.sa_family == AF_INET6)) { return (Tcl_Channel)TclpMakeTcpClientChannelMode(INT2PTR(fd), mode); } goto normalChannelAfterAll; } else { normalChannelAfterAll: channelTypePtr = &fileChannelType; snprintf(channelName, sizeof(channelName), "file%d", fd); } fsPtr = (TtyState *)Tcl_Alloc(sizeof(TtyState)); fsPtr->fileState.fd = fd; fsPtr->fileState.validMask = mode | TCL_EXCEPTION; fsPtr->fileState.channel = Tcl_CreateChannel(channelTypePtr, channelName, fsPtr, mode); #ifdef SUPPORTS_TTY if (channelTypePtr == &ttyChannelType) { fsPtr->closeMode = CLOSE_DEFAULT; fsPtr->doReset = 0; tcgetattr(fsPtr->fileState.fd, &fsPtr->initState); } #endif /* SUPPORTS_TTY */ return fsPtr->fileState.channel; } /* *---------------------------------------------------------------------- * * TclpGetDefaultStdChannel -- * * Creates channels for standard input, standard output or standard error * output if they do not already exist. * * Results: * Returns the specified default standard channel, or NULL. * * Side effects: * May cause the creation of a standard channel and the underlying file. * *---------------------------------------------------------------------- */ Tcl_Channel TclpGetDefaultStdChannel( int type) /* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR. */ { Tcl_Channel channel = NULL; int fd = 0; /* Initializations needed to prevent */ int mode = 0; /* compiler warning (used before set). */ const char *bufMode = NULL; /* * Some #def's to make the code a little clearer! */ #define ERROR_OFFSET ((Tcl_SeekOffset) -1) switch (type) { case TCL_STDIN: if ((TclOSseek(0, 0, SEEK_CUR) == ERROR_OFFSET) && (errno == EBADF)) { return NULL; } fd = 0; mode = TCL_READABLE; bufMode = "line"; break; case TCL_STDOUT: if ((TclOSseek(1, 0, SEEK_CUR) == ERROR_OFFSET) && (errno == EBADF)) { return NULL; } fd = 1; mode = TCL_WRITABLE; bufMode = "line"; break; case TCL_STDERR: if ((TclOSseek(2, 0, SEEK_CUR) == ERROR_OFFSET) && (errno == EBADF)) { return NULL; } fd = 2; mode = TCL_WRITABLE; bufMode = "none"; break; default: Tcl_Panic("TclGetDefaultStdChannel: Unexpected channel type"); break; } #undef ERROR_OFFSET channel = Tcl_MakeFileChannel(INT2PTR(fd), mode); if (channel == NULL) { return NULL; } /* * Set up the normal channel options for stdio handles. */ if (Tcl_GetChannelType(channel) == &fileChannelType) { Tcl_SetChannelOption(NULL, channel, "-translation", "auto"); } else { Tcl_SetChannelOption(NULL, channel, "-translation", "auto crlf"); } Tcl_SetChannelOption(NULL, channel, "-buffering", bufMode); return channel; } /* *---------------------------------------------------------------------- * * Tcl_GetOpenFile -- * * Given a name of a channel registered in the given interpreter, returns * a FILE * for it. * * Results: * A standard Tcl result. If the channel is registered in the given * interpreter and it is managed by the "file" channel driver, and it is * open for the requested mode, then the output parameter filePtr is set * to a FILE * for the underlying file. On error, the filePtr is not set, * TCL_ERROR is returned and an error message is left in the interp's * result. * * Side effects: * May invoke fdopen to create the FILE * for the requested file. * *---------------------------------------------------------------------- */ int Tcl_GetOpenFile( Tcl_Interp *interp, /* Interpreter in which to find file. */ const char *chanID, /* String that identifies file. */ int forWriting, /* 1 means the file is going to be used for * writing, 0 means for reading. */ TCL_UNUSED(int), /* Obsolete argument. * Ignored, we always check that * the channel is open for the requested * mode. */ void **filePtr) /* Store pointer to FILE structure here. */ { Tcl_Channel chan; int chanMode, fd; const Tcl_ChannelType *chanTypePtr; void *data; FILE *f; chan = Tcl_GetChannel(interp, chanID, &chanMode); if (chan == NULL) { return TCL_ERROR; } if (forWriting && !(chanMode & TCL_WRITABLE)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" wasn't opened for writing", chanID)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "CHANNEL", "NOT_WRITABLE", (char *)NULL); return TCL_ERROR; } else if (!forWriting && !(chanMode & TCL_READABLE)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" wasn't opened for reading", chanID)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "CHANNEL", "NOT_READABLE", (char *)NULL); return TCL_ERROR; } /* * We allow creating a FILE * out of file based, pipe based and socket * based channels. We currently do not allow any other channel types, * because it is likely that stdio will not know what to do with them. */ chanTypePtr = Tcl_GetChannelType(chan); if ((chanTypePtr == &fileChannelType) #ifdef SUPPORTS_TTY || (chanTypePtr == &ttyChannelType) #endif /* SUPPORTS_TTY */ || (strcmp(chanTypePtr->typeName, "tcp") == 0) || (strcmp(chanTypePtr->typeName, "pipe") == 0)) { if (Tcl_GetChannelHandle(chan, (forWriting ? TCL_WRITABLE : TCL_READABLE), &data) == TCL_OK) { fd = PTR2INT(data); /* * The call to fdopen below is probably dangerous, since it will * truncate an existing file if the file is being opened for * writing.... */ f = fdopen(fd, (forWriting ? "w" : "r")); if (f == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot get a FILE * for \"%s\"", chanID)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "CHANNEL", "FILE_FAILURE", (char *)NULL); return TCL_ERROR; } *filePtr = f; return TCL_OK; } } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" cannot be used to get a FILE *", chanID)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "CHANNEL", "NO_DESCRIPTOR", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * FileTruncateProc -- * * Truncates a file to a given length. * * Results: * 0 if the operation succeeded, and -1 if it failed (in which case * *errorCodePtr will be set to errno). * * Side effects: * The underlying file is potentially truncated. This can have a wide * variety of side effects, including moving file pointers that point at * places later in the file than the truncate point. * *---------------------------------------------------------------------- */ static int FileTruncateProc( void *instanceData, long long length) { FileState *fsPtr = (FileState *)instanceData; int result; #ifdef HAVE_TYPE_OFF64_T /* * We assume this goes with the type for now... */ result = ftruncate64(fsPtr->fd, (off64_t) length); #else result = ftruncate(fsPtr->fd, (off_t) length); #endif if (result) { return errno; } return 0; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclUnixCompat.c0000644000175000017500000005634614726623136015515 0ustar sergeisergei/* * tclUnixCompat.c * * Written by: Zoran Vasiljevic (vasiljevic@users.sourceforge.net). * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include #include /* * See also: SC_BLOCKING_STYLE in unix/tcl.m4 */ #ifdef USE_FIONBIO # ifdef HAVE_SYS_FILIO_H # include /* For FIONBIO. */ # endif # ifdef HAVE_SYS_IOCTL_H # include # endif #endif /* USE_FIONBIO */ /* * Used to pad structures at size'd boundaries * * This macro assumes that the pointer 'buffer' was created from an aligned * pointer by adding the 'length'. If this 'length' was not a multiple of the * 'size' the result is unaligned and PadBuffer corrects both the pointer, * _and_ the 'length'. The latter means that future increments of 'buffer' by * 'length' stay aligned. */ #define PadBuffer(buffer, length, size) \ if (((length) % (size))) { \ (buffer) += ((size) - ((length) % (size))); \ (length) += ((size) - ((length) % (size))); \ } /* * Per-thread private storage used to store values returned from MT-unsafe * library calls. */ #if TCL_THREADS typedef struct { struct passwd pwd; #if defined(HAVE_GETPWNAM_R_5) || defined(HAVE_GETPWUID_R_5) #define NEED_PW_CLEANER 1 char *pbuf; int pbuflen; #else char pbuf[2048]; #endif struct group grp; #if defined(HAVE_GETGRNAM_R_5) || defined(HAVE_GETGRGID_R_5) #define NEED_GR_CLEANER 1 char *gbuf; int gbuflen; #else char gbuf[2048]; #endif #if !defined(HAVE_MTSAFE_GETHOSTBYNAME) || !defined(HAVE_MTSAFE_GETHOSTBYADDR) struct hostent hent; char hbuf[2048]; #endif } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; #if ((!defined(HAVE_GETHOSTBYNAME_R) || !defined(HAVE_GETHOSTBYADDR_R)) && \ (!defined(HAVE_MTSAFE_GETHOSTBYNAME) || \ !defined(HAVE_MTSAFE_GETHOSTBYADDR))) || \ !defined(HAVE_GETPWNAM_R) || !defined(HAVE_GETPWUID_R) || \ !defined(HAVE_GETGRNAM_R) || !defined(HAVE_GETGRGID_R) /* * Mutex to lock access to MT-unsafe calls. This is just to protect our own * usage. It does not protect us from others calling the same functions * without (or using some different) lock. */ static Tcl_Mutex compatLock; /* * Helper function declarations. Note that these are only used if needed and * only defined if used (via the NEED_* macros). */ #undef NEED_COPYARRAY #undef NEED_COPYGRP #undef NEED_COPYHOSTENT #undef NEED_COPYPWD #undef NEED_COPYSTRING #if !defined(HAVE_GETGRNAM_R_5) && !defined(HAVE_GETGRNAM_R_4) #define NEED_COPYGRP 1 static int CopyGrp(struct group *tgtPtr, char *buf, int buflen); #endif #if !defined(HAVE_GETPWNAM_R_5) && !defined(HAVE_GETPWNAM_R_4) #define NEED_COPYPWD 1 static int CopyPwd(struct passwd *tgtPtr, char *buf, int buflen); #endif static int CopyArray(char **src, int elsize, char *buf, int buflen); static int CopyHostent(struct hostent *tgtPtr, char *buf, int buflen); static int CopyString(const char *src, char *buf, int buflen); #endif #ifdef NEED_PW_CLEANER static void FreePwBuf(void *dummy); #endif #ifdef NEED_GR_CLEANER static void FreeGrBuf(void *dummy); #endif #endif /* TCL_THREADS */ /* *--------------------------------------------------------------------------- * * TclUnixSetBlockingMode -- * * Set the blocking mode of a file descriptor. * * Results: * * 0 on success, -1 (with errno set) on error. * *--------------------------------------------------------------------------- */ int TclUnixSetBlockingMode( int fd, /* File descriptor */ int mode) /* Either TCL_MODE_BLOCKING or * TCL_MODE_NONBLOCKING. */ { #ifndef USE_FIONBIO int flags = fcntl(fd, F_GETFL); if (mode == TCL_MODE_BLOCKING) { flags &= ~O_NONBLOCK; } else { flags |= O_NONBLOCK; } return fcntl(fd, F_SETFL, flags); #else /* USE_FIONBIO */ int state = (mode == TCL_MODE_NONBLOCKING); return ioctl(fd, FIONBIO, &state); #endif /* !USE_FIONBIO */ } /* *--------------------------------------------------------------------------- * * TclpGetPwNam -- * * Thread-safe wrappers for getpwnam(). See "man getpwnam" for more * details. * * Results: * Pointer to struct passwd on success or NULL on error. * * Side effects: * None. * *--------------------------------------------------------------------------- */ struct passwd * TclpGetPwNam( const char *name) { #if !TCL_THREADS return getpwnam(name); #else ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); #if defined(HAVE_GETPWNAM_R_5) struct passwd *pwPtr = NULL; /* * How to allocate a buffer of the right initial size. If you want the * gory detail, see http://www.opengroup.org/austin/docs/austin_328.txt * and weep. */ if (tsdPtr->pbuf == NULL) { tsdPtr->pbuflen = (int) sysconf(_SC_GETPW_R_SIZE_MAX); if (tsdPtr->pbuflen < 1) { tsdPtr->pbuflen = 1024; } tsdPtr->pbuf = (char *)Tcl_Alloc(tsdPtr->pbuflen); Tcl_CreateThreadExitHandler(FreePwBuf, NULL); } while (1) { int e = getpwnam_r(name, &tsdPtr->pwd, tsdPtr->pbuf, tsdPtr->pbuflen, &pwPtr); if (e == 0) { break; } else if (e != ERANGE) { return NULL; } tsdPtr->pbuflen *= 2; tsdPtr->pbuf = (char *)Tcl_Realloc(tsdPtr->pbuf, tsdPtr->pbuflen); } return (pwPtr != NULL ? &tsdPtr->pwd : NULL); #elif defined(HAVE_GETPWNAM_R_4) return getpwnam_r(name, &tsdPtr->pwd, tsdPtr->pbuf, sizeof(tsdPtr->pbuf)); #else struct passwd *pwPtr; Tcl_MutexLock(&compatLock); pwPtr = getpwnam(name); if (pwPtr != NULL) { tsdPtr->pwd = *pwPtr; pwPtr = &tsdPtr->pwd; if (CopyPwd(&tsdPtr->pwd, tsdPtr->pbuf, sizeof(tsdPtr->pbuf)) == -1) { pwPtr = NULL; } } Tcl_MutexUnlock(&compatLock); return pwPtr; #endif return NULL; /* Not reached. */ #endif /* TCL_THREADS */ } /* *--------------------------------------------------------------------------- * * TclpGetPwUid -- * * Thread-safe wrappers for getpwuid(). See "man getpwuid" for more * details. * * Results: * Pointer to struct passwd on success or NULL on error. * * Side effects: * None. * *--------------------------------------------------------------------------- */ struct passwd * TclpGetPwUid( uid_t uid) { #if !TCL_THREADS return getpwuid(uid); #else ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); #if defined(HAVE_GETPWUID_R_5) struct passwd *pwPtr = NULL; /* * How to allocate a buffer of the right initial size. If you want the * gory detail, see http://www.opengroup.org/austin/docs/austin_328.txt * and weep. */ if (tsdPtr->pbuf == NULL) { tsdPtr->pbuflen = (int) sysconf(_SC_GETPW_R_SIZE_MAX); if (tsdPtr->pbuflen < 1) { tsdPtr->pbuflen = 1024; } tsdPtr->pbuf = (char *)Tcl_Alloc(tsdPtr->pbuflen); Tcl_CreateThreadExitHandler(FreePwBuf, NULL); } while (1) { int e = getpwuid_r(uid, &tsdPtr->pwd, tsdPtr->pbuf, tsdPtr->pbuflen, &pwPtr); if (e == 0) { break; } else if (e != ERANGE) { return NULL; } tsdPtr->pbuflen *= 2; tsdPtr->pbuf = (char *)Tcl_Realloc(tsdPtr->pbuf, tsdPtr->pbuflen); } return (pwPtr != NULL ? &tsdPtr->pwd : NULL); #elif defined(HAVE_GETPWUID_R_4) return getpwuid_r(uid, &tsdPtr->pwd, tsdPtr->pbuf, sizeof(tsdPtr->pbuf)); #else struct passwd *pwPtr; Tcl_MutexLock(&compatLock); pwPtr = getpwuid(uid); if (pwPtr != NULL) { tsdPtr->pwd = *pwPtr; pwPtr = &tsdPtr->pwd; if (CopyPwd(&tsdPtr->pwd, tsdPtr->pbuf, sizeof(tsdPtr->pbuf)) == -1) { pwPtr = NULL; } } Tcl_MutexUnlock(&compatLock); return pwPtr; #endif return NULL; /* Not reached. */ #endif /* TCL_THREADS */ } /* *--------------------------------------------------------------------------- * * FreePwBuf -- * * Helper that is used to dispose of space allocated and referenced from * the ThreadSpecificData for user entries. (Darn that baroque POSIX * reentrant interface.) * *--------------------------------------------------------------------------- */ #ifdef NEED_PW_CLEANER static void FreePwBuf( TCL_UNUSED(void *)) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Tcl_Free(tsdPtr->pbuf); } #endif /* NEED_PW_CLEANER */ /* *--------------------------------------------------------------------------- * * TclpGetGrNam -- * * Thread-safe wrappers for getgrnam(). See "man getgrnam" for more * details. * * Results: * Pointer to struct group on success or NULL on error. * * Side effects: * None. * *--------------------------------------------------------------------------- */ struct group * TclpGetGrNam( const char *name) { #if !TCL_THREADS return getgrnam(name); #else ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); #if defined(HAVE_GETGRNAM_R_5) struct group *grPtr = NULL; /* * How to allocate a buffer of the right initial size. If you want the * gory detail, see http://www.opengroup.org/austin/docs/austin_328.txt * and weep. */ if (tsdPtr->gbuf == NULL) { tsdPtr->gbuflen = (int) sysconf(_SC_GETGR_R_SIZE_MAX); if (tsdPtr->gbuflen < 1) { tsdPtr->gbuflen = 1024; } tsdPtr->gbuf = (char *)Tcl_Alloc(tsdPtr->gbuflen); Tcl_CreateThreadExitHandler(FreeGrBuf, NULL); } while (1) { int e = getgrnam_r(name, &tsdPtr->grp, tsdPtr->gbuf, tsdPtr->gbuflen, &grPtr); if (e == 0) { break; } else if (e != ERANGE) { return NULL; } tsdPtr->gbuflen *= 2; tsdPtr->gbuf = (char *)Tcl_Realloc(tsdPtr->gbuf, tsdPtr->gbuflen); } return (grPtr != NULL ? &tsdPtr->grp : NULL); #elif defined(HAVE_GETGRNAM_R_4) return getgrnam_r(name, &tsdPtr->grp, tsdPtr->gbuf, sizeof(tsdPtr->gbuf)); #else struct group *grPtr; Tcl_MutexLock(&compatLock); grPtr = getgrnam(name); if (grPtr != NULL) { tsdPtr->grp = *grPtr; grPtr = &tsdPtr->grp; if (CopyGrp(&tsdPtr->grp, tsdPtr->gbuf, sizeof(tsdPtr->gbuf)) == -1) { grPtr = NULL; } } Tcl_MutexUnlock(&compatLock); return grPtr; #endif return NULL; /* Not reached. */ #endif /* TCL_THREADS */ } /* *--------------------------------------------------------------------------- * * TclpGetGrGid -- * * Thread-safe wrappers for getgrgid(). See "man getgrgid" for more * details. * * Results: * Pointer to struct group on success or NULL on error. * * Side effects: * None. * *--------------------------------------------------------------------------- */ struct group * TclpGetGrGid( gid_t gid) { #if !TCL_THREADS return getgrgid(gid); #else ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); #if defined(HAVE_GETGRGID_R_5) struct group *grPtr = NULL; /* * How to allocate a buffer of the right initial size. If you want the * gory detail, see http://www.opengroup.org/austin/docs/austin_328.txt * and weep. */ if (tsdPtr->gbuf == NULL) { tsdPtr->gbuflen = (int) sysconf(_SC_GETGR_R_SIZE_MAX); if (tsdPtr->gbuflen < 1) { tsdPtr->gbuflen = 1024; } tsdPtr->gbuf = (char *)Tcl_Alloc(tsdPtr->gbuflen); Tcl_CreateThreadExitHandler(FreeGrBuf, NULL); } while (1) { int e = getgrgid_r(gid, &tsdPtr->grp, tsdPtr->gbuf, tsdPtr->gbuflen, &grPtr); if (e == 0) { break; } else if (e != ERANGE) { return NULL; } tsdPtr->gbuflen *= 2; tsdPtr->gbuf = (char *)Tcl_Realloc(tsdPtr->gbuf, tsdPtr->gbuflen); } return (grPtr != NULL ? &tsdPtr->grp : NULL); #elif defined(HAVE_GETGRGID_R_4) return getgrgid_r(gid, &tsdPtr->grp, tsdPtr->gbuf, sizeof(tsdPtr->gbuf)); #else struct group *grPtr; Tcl_MutexLock(&compatLock); grPtr = getgrgid(gid); if (grPtr != NULL) { tsdPtr->grp = *grPtr; grPtr = &tsdPtr->grp; if (CopyGrp(&tsdPtr->grp, tsdPtr->gbuf, sizeof(tsdPtr->gbuf)) == -1) { grPtr = NULL; } } Tcl_MutexUnlock(&compatLock); return grPtr; #endif return NULL; /* Not reached. */ #endif /* TCL_THREADS */ } /* *--------------------------------------------------------------------------- * * FreeGrBuf -- * * Helper that is used to dispose of space allocated and referenced from * the ThreadSpecificData for group entries. (Darn that baroque POSIX * reentrant interface.) * *--------------------------------------------------------------------------- */ #ifdef NEED_GR_CLEANER static void FreeGrBuf( TCL_UNUSED(void *)) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Tcl_Free(tsdPtr->gbuf); } #endif /* NEED_GR_CLEANER */ /* *--------------------------------------------------------------------------- * * TclpGetHostByName -- * * Thread-safe wrappers for gethostbyname(). See "man gethostbyname" for * more details. * * Results: * Pointer to struct hostent on success or NULL on error. * * Side effects: * None. * *--------------------------------------------------------------------------- */ struct hostent * TclpGetHostByName( const char *name) { #if !TCL_THREADS || defined(HAVE_MTSAFE_GETHOSTBYNAME) return gethostbyname(name); #else ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); #if defined(HAVE_GETHOSTBYNAME_R_5) int local_errno; return gethostbyname_r(name, &tsdPtr->hent, tsdPtr->hbuf, sizeof(tsdPtr->hbuf), &local_errno); #elif defined(HAVE_GETHOSTBYNAME_R_6) struct hostent *hePtr = NULL; int local_errno, result; result = gethostbyname_r(name, &tsdPtr->hent, tsdPtr->hbuf, sizeof(tsdPtr->hbuf), &hePtr, &local_errno); return (result == 0) ? hePtr : NULL; #elif defined(HAVE_GETHOSTBYNAME_R_3) struct hostent_data data; return (gethostbyname_r(name, &tsdPtr->hent, &data) == 0) ? &tsdPtr->hent : NULL; #else #define NEED_COPYHOSTENT 1 struct hostent *hePtr; Tcl_MutexLock(&compatLock); hePtr = gethostbyname(name); if (hePtr != NULL) { tsdPtr->hent = *hePtr; hePtr = &tsdPtr->hent; if (CopyHostent(&tsdPtr->hent, tsdPtr->hbuf, sizeof(tsdPtr->hbuf)) == -1) { hePtr = NULL; } } Tcl_MutexUnlock(&compatLock); return hePtr; #endif return NULL; /* Not reached. */ #endif /* TCL_THREADS */ } /* *--------------------------------------------------------------------------- * * TclpGetHostByAddr -- * * Thread-safe wrappers for gethostbyaddr(). See "man gethostbyaddr" for * more details. * * Results: * Pointer to struct hostent on success or NULL on error. * * Side effects: * None. * *--------------------------------------------------------------------------- */ struct hostent * TclpGetHostByAddr( const char *addr, int length, int type) { #if !TCL_THREADS || defined(HAVE_MTSAFE_GETHOSTBYADDR) return gethostbyaddr(addr, length, type); #else ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); #if defined(HAVE_GETHOSTBYADDR_R_7) int local_errno; return gethostbyaddr_r(addr, length, type, &tsdPtr->hent, tsdPtr->hbuf, sizeof(tsdPtr->hbuf), &local_errno); #elif defined(HAVE_GETHOSTBYADDR_R_8) struct hostent *hePtr; int local_errno; return (gethostbyaddr_r(addr, length, type, &tsdPtr->hent, tsdPtr->hbuf, sizeof(tsdPtr->hbuf), &hePtr, &local_errno) == 0) ? &tsdPtr->hent : NULL; #else #define NEED_COPYHOSTENT 1 struct hostent *hePtr; Tcl_MutexLock(&compatLock); hePtr = gethostbyaddr(addr, length, type); if (hePtr != NULL) { tsdPtr->hent = *hePtr; hePtr = &tsdPtr->hent; if (CopyHostent(&tsdPtr->hent, tsdPtr->hbuf, sizeof(tsdPtr->hbuf)) == -1) { hePtr = NULL; } } Tcl_MutexUnlock(&compatLock); return hePtr; #endif return NULL; /* Not reached. */ #endif /* TCL_THREADS */ } /* *--------------------------------------------------------------------------- * * CopyGrp -- * * Copies string fields of the group structure to the private buffer, * honouring the size of the buffer. * * Results: * 0 on success or -1 on error (errno = ERANGE). * * Side effects: * None. * *--------------------------------------------------------------------------- */ #ifdef NEED_COPYGRP #define NEED_COPYARRAY 1 #define NEED_COPYSTRING 1 static int CopyGrp( struct group *tgtPtr, char *buf, int buflen) { char *p = buf; int copied, len = 0; /* * Copy username. */ copied = CopyString(tgtPtr->gr_name, p, buflen - len); if (copied == -1) { goto range; } tgtPtr->gr_name = (copied > 0) ? p : NULL; len += copied; p = buf + len; /* * Copy password. */ copied = CopyString(tgtPtr->gr_passwd, p, buflen - len); if (copied == -1) { goto range; } tgtPtr->gr_passwd = (copied > 0) ? p : NULL; len += copied; p = buf + len; /* * Copy group members. */ PadBuffer(p, len, sizeof(char *)); copied = CopyArray((char **)tgtPtr->gr_mem, -1, p, buflen - len); if (copied == -1) { goto range; } tgtPtr->gr_mem = (copied > 0) ? (char **)p : NULL; return 0; range: errno = ERANGE; return -1; } #endif /* NEED_COPYGRP */ /* *--------------------------------------------------------------------------- * * CopyHostent -- * * Copies string fields of the hostent structure to the private buffer, * honouring the size of the buffer. * * Results: * Number of bytes copied on success or -1 on error (errno = ERANGE) * * Side effects: * None * *--------------------------------------------------------------------------- */ #ifdef NEED_COPYHOSTENT #define NEED_COPYSTRING 1 #define NEED_COPYARRAY 1 static int CopyHostent( struct hostent *tgtPtr, char *buf, int buflen) { char *p = buf; int copied, len = 0; copied = CopyString(tgtPtr->h_name, p, buflen - len); if (copied == -1) { goto range; } tgtPtr->h_name = (copied > 0) ? p : NULL; len += copied; p = buf + len; PadBuffer(p, len, sizeof(char *)); copied = CopyArray(tgtPtr->h_aliases, -1, p, buflen - len); if (copied == -1) { goto range; } tgtPtr->h_aliases = (copied > 0) ? (char **)p : NULL; len += copied; p += len; PadBuffer(p, len, sizeof(char *)); copied = CopyArray(tgtPtr->h_addr_list, tgtPtr->h_length, p, buflen-len); if (copied == -1) { goto range; } tgtPtr->h_addr_list = (copied > 0) ? (char **)p : NULL; return 0; range: errno = ERANGE; return -1; } #endif /* NEED_COPYHOSTENT */ /* *--------------------------------------------------------------------------- * * CopyPwd -- * * Copies string fields of the passwd structure to the private buffer, * honouring the size of the buffer. * * Results: * 0 on success or -1 on error (errno = ERANGE). * * Side effects: * We are not copying the gecos field as it may not be supported on all * platforms. * *--------------------------------------------------------------------------- */ #ifdef NEED_COPYPWD #define NEED_COPYSTRING 1 static int CopyPwd( struct passwd *tgtPtr, char *buf, int buflen) { char *p = buf; int copied, len = 0; copied = CopyString(tgtPtr->pw_name, p, buflen - len); if (copied == -1) { range: errno = ERANGE; return -1; } tgtPtr->pw_name = (copied > 0) ? p : NULL; len += copied; p = buf + len; copied = CopyString(tgtPtr->pw_passwd, p, buflen - len); if (copied == -1) { goto range; } tgtPtr->pw_passwd = (copied > 0) ? p : NULL; len += copied; p = buf + len; copied = CopyString(tgtPtr->pw_dir, p, buflen - len); if (copied == -1) { goto range; } tgtPtr->pw_dir = (copied > 0) ? p : NULL; len += copied; p = buf + len; copied = CopyString(tgtPtr->pw_shell, p, buflen - len); if (copied == -1) { goto range; } tgtPtr->pw_shell = (copied > 0) ? p : NULL; return 0; } #endif /* NEED_COPYPWD */ /* *--------------------------------------------------------------------------- * * CopyArray -- * * Copies array of NULL-terminated or fixed-length strings to the private * buffer, honouring the size of the buffer. * * Results: * Number of bytes copied on success or -1 on error (errno = ERANGE) * * Side effects: * None. * *--------------------------------------------------------------------------- */ #ifdef NEED_COPYARRAY static int CopyArray( char **src, /* Array of elements to copy. */ int elsize, /* Size of each element, or -1 to indicate * that they are C strings of dynamic * length. */ char *buf, /* Buffer to copy into. */ int buflen) /* Size of buffer. */ { int i, j, len = 0; char *p, **newBuffer; if (src == NULL) { return 0; } for (i = 0; src[i] != NULL; i++) { /* * Empty loop to count how many. */ } len = sizeof(char *) * (i + 1); /* Leave place for the array. */ if (len > buflen) { return -1; } newBuffer = (char **)buf; p = buf + len; for (j = 0; j < i; j++) { int sz = (elsize<0 ? (int) strlen(src[j]) + 1 : elsize); len += sz; if (len > buflen) { return -1; } memcpy(p, src[j], sz); newBuffer[j] = p; p = buf + len; } newBuffer[j] = NULL; return len; } #endif /* NEED_COPYARRAY */ /* *--------------------------------------------------------------------------- * * CopyString -- * * Copies a NULL-terminated string to the private buffer, honouring the * size of the buffer * * Results: * 0 success or -1 on error (errno = ERANGE) * * Side effects: * None * *--------------------------------------------------------------------------- */ #ifdef NEED_COPYSTRING static int CopyString( const char *src, /* String to copy. */ char *buf, /* Buffer to copy into. */ int buflen) /* Size of buffer. */ { int len = 0; if (src != NULL) { len = strlen(src) + 1; if (len > buflen) { return -1; } memcpy(buf, src, len); } return len; } #endif /* NEED_COPYSTRING */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ /* *------------------------------------------------------------------------ * * TclWinCPUID -- * * Get CPU ID information on an Intel box under UNIX (either Linux or Cygwin) * * Results: * Returns TCL_OK if successful, TCL_ERROR if CPUID is not supported. * * Side effects: * If successful, stores EAX, EBX, ECX and EDX registers after the CPUID * instruction in the four integers designated by 'regsPtr' * *---------------------------------------------------------------------- */ int TclWinCPUID( int index, /* Which CPUID value to retrieve. */ int *regsPtr) /* Registers after the CPUID. */ { int status = TCL_ERROR; /* See: */ #if defined(__x86_64__) || defined(_M_AMD64) || defined (_M_X64) __asm__ __volatile__("movq %%rbx, %%rsi \n\t" /* save %rbx */ "cpuid \n\t" "xchgq %%rsi, %%rbx \n\t" /* restore the old %rbx */ : "=a"(regsPtr[0]), "=S"(regsPtr[1]), "=c"(regsPtr[2]), "=d"(regsPtr[3]) : "a"(index)); status = TCL_OK; #elif defined(__i386__) || defined(_M_IX86) __asm__ __volatile__("mov %%ebx, %%esi \n\t" /* save %ebx */ "cpuid \n\t" "xchg %%esi, %%ebx \n\t" /* restore the old %ebx */ : "=a"(regsPtr[0]), "=S"(regsPtr[1]), "=c"(regsPtr[2]), "=d"(regsPtr[3]) : "a"(index)); status = TCL_OK; #else (void)index; (void)regsPtr; #endif return status; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclUnixEvent.c0000644000175000017500000000421314726623136015335 0ustar sergeisergei/* * tclUnixEvent.c -- * * This file implements Unix specific event related routines. * * Copyright © 1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is * in tclMacOSXNotify.c */ /* *---------------------------------------------------------------------- * * Tcl_Sleep -- * * Delay execution for the specified number of milliseconds. * * Results: * None. * * Side effects: * Time passes. * *---------------------------------------------------------------------- */ void Tcl_Sleep( int ms) /* Number of milliseconds to sleep. */ { struct timeval delay; Tcl_Time before, after, vdelay; /* * The only trick here is that select appears to return early under some * conditions, so we have to check to make sure that the right amount of * time really has elapsed. If it's too early, go back to sleep again. */ Tcl_GetTime(&before); after = before; after.sec += ms/1000; after.usec += (ms%1000)*1000; if (after.usec > 1000000) { after.usec -= 1000000; after.sec += 1; } while (1) { /* * TIP #233: Scale from virtual time to real-time for select. */ vdelay.sec = after.sec - before.sec; vdelay.usec = after.usec - before.usec; if (vdelay.usec < 0) { vdelay.usec += 1000000; vdelay.sec -= 1; } if ((vdelay.sec != 0) || (vdelay.usec != 0)) { TclScaleTime(&vdelay); } delay.tv_sec = vdelay.sec; delay.tv_usec = vdelay.usec; /* * Special note: must convert delay.tv_sec to int before comparing to * zero, since delay.tv_usec is unsigned on some platforms. */ if ((((int) delay.tv_sec) < 0) || ((delay.tv_usec == 0) && (delay.tv_sec == 0))) { break; } (void) select(0, (SELECT_MASK *) 0, (SELECT_MASK *) 0, (SELECT_MASK *) 0, &delay); Tcl_GetTime(&before); } } #else TCL_MAC_EMPTY_FILE(unix_tclUnixEvent_c) #endif /* HAVE_COREFOUNDATION */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclUnixFCmd.c0000644000175000017500000020656514726623136015103 0ustar sergeisergei/* * tclUnixFCmd.c * * This file implements the Unix specific portion of file manipulation * subcommands of the "file" command. All filename arguments should * already be translated to native format. * * Copyright © 1996-1998 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * * Portions of this code were derived from NetBSD source code which has the * following copyright notice: * * Copyright © 1988, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ #include "tclInt.h" #ifndef HAVE_STRUCT_STAT_ST_BLKSIZE #ifndef NO_FSTATFS #include #endif #endif /* !HAVE_STRUCT_STAT_ST_BLKSIZE */ #ifdef HAVE_FTS #include #endif /* * The following constants specify the type of callback when * TraverseUnixTree() calls the traverseProc() */ #define DOTREE_PRED 1 /* pre-order directory */ #define DOTREE_POSTD 2 /* post-order directory */ #define DOTREE_F 3 /* regular file */ /* * Fallback temporary file location the temporary file generation code. Can be * overridden at compile time for when it is known that temp files can't be * written to /tmp (hello, iOS!). */ #ifndef TCL_TEMPORARY_FILE_DIRECTORY #define TCL_TEMPORARY_FILE_DIRECTORY "/tmp" #endif /* * Callbacks for file attributes code. */ static int GetGroupAttribute(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr); static int GetOwnerAttribute(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr); static int GetPermissionsAttribute(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr); static int SetGroupAttribute(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj *attributePtr); static int SetOwnerAttribute(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj *attributePtr); static int SetPermissionsAttribute(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj *attributePtr); static int GetModeFromPermString(Tcl_Interp *interp, const char *modeStringPtr, mode_t *modePtr); #if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE) || defined(__CYGWIN__) static int GetUnixFileAttributes(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr); static int SetUnixFileAttributes(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj *attributePtr); #endif /* * Prototype for the TraverseUnixTree callback function. */ typedef int (TraversalProc)(Tcl_DString *srcPtr, Tcl_DString *dstPtr, const Tcl_StatBuf *statBufPtr, int type, Tcl_DString *errorPtr); /* * Constants and variables necessary for file attributes subcommand. * * IMPORTANT: The permissions attribute is assumed to be the third item (i.e. * to be indexed with '2' in arrays) in code in tclIOUtil.c and possibly * elsewhere in Tcl's core. */ #ifndef DJGPP enum { #if defined(__CYGWIN__) UNIX_ARCHIVE_ATTRIBUTE, #endif UNIX_GROUP_ATTRIBUTE, #if defined(__CYGWIN__) UNIX_HIDDEN_ATTRIBUTE, #endif UNIX_OWNER_ATTRIBUTE, UNIX_PERMISSIONS_ATTRIBUTE, #if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE) || defined(__CYGWIN__) UNIX_READONLY_ATTRIBUTE, #endif #if defined(__CYGWIN__) UNIX_SYSTEM_ATTRIBUTE, #endif #ifdef MAC_OSX_TCL MACOSX_CREATOR_ATTRIBUTE, MACOSX_TYPE_ATTRIBUTE, MACOSX_HIDDEN_ATTRIBUTE, MACOSX_RSRCLENGTH_ATTRIBUTE, #endif UNIX_INVALID_ATTRIBUTE }; const char *const tclpFileAttrStrings[] = { #if defined(__CYGWIN__) "-archive", #endif "-group", #if defined(__CYGWIN__) "-hidden", #endif "-owner", "-permissions", #if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE) || defined(__CYGWIN__) "-readonly", #endif #if defined(__CYGWIN__) "-system", #endif #ifdef MAC_OSX_TCL "-creator", "-type", "-hidden", "-rsrclength", #endif NULL }; const TclFileAttrProcs tclpFileAttrProcs[] = { #if defined(__CYGWIN__) {GetUnixFileAttributes, SetUnixFileAttributes}, #endif {GetGroupAttribute, SetGroupAttribute}, #if defined(__CYGWIN__) {GetUnixFileAttributes, SetUnixFileAttributes}, #endif {GetOwnerAttribute, SetOwnerAttribute}, {GetPermissionsAttribute, SetPermissionsAttribute}, #if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE) || defined(__CYGWIN__) {GetUnixFileAttributes, SetUnixFileAttributes}, #endif #if defined(__CYGWIN__) {GetUnixFileAttributes, SetUnixFileAttributes}, #endif #ifdef MAC_OSX_TCL {TclMacOSXGetFileAttribute, TclMacOSXSetFileAttribute}, {TclMacOSXGetFileAttribute, TclMacOSXSetFileAttribute}, {TclMacOSXGetFileAttribute, TclMacOSXSetFileAttribute}, {TclMacOSXGetFileAttribute, TclMacOSXSetFileAttribute}, #endif }; #endif /* DJGPP */ /* * This is the maximum number of consecutive readdir/unlink calls that can be * made (with no intervening rewinddir or closedir/opendir) before triggering * a bug that makes readdir return NULL even though some directory entries * have not been processed. The bug afflicts SunOS's readdir when applied to * ufs file systems and Darwin 6.5's (and OSX v.10.3.8's) HFS+. JH found the * Darwin readdir to reset at 147, so 130 is chosen to be conservative. We * can't do a general rewind on failure as NFS can create special files that * recreate themselves when you try and delete them. 8.4.8 added a solution * that was affected by a single such NFS file, this solution should not be * affected by less than THRESHOLD such files. [Bug 1034337] */ #define MAX_READDIR_UNLINK_THRESHOLD 130 /* * Declarations for local procedures defined in this file: */ static int CopyFileAtts(const char *src, const char *dst, const Tcl_StatBuf *statBufPtr); static const char * DefaultTempDir(void); static int DoCopyFile(const char *srcPtr, const char *dstPtr, const Tcl_StatBuf *statBufPtr); static int DoCreateDirectory(const char *pathPtr); static int DoRemoveDirectory(Tcl_DString *pathPtr, int recursive, Tcl_DString *errorPtr); static int DoRenameFile(const char *src, const char *dst); static int TraversalCopy(Tcl_DString *srcPtr, Tcl_DString *dstPtr, const Tcl_StatBuf *statBufPtr, int type, Tcl_DString *errorPtr); static int TraversalDelete(Tcl_DString *srcPtr, Tcl_DString *dstPtr, const Tcl_StatBuf *statBufPtr, int type, Tcl_DString *errorPtr); static int TraverseUnixTree(TraversalProc *traversalProc, Tcl_DString *sourcePtr, Tcl_DString *destPtr, Tcl_DString *errorPtr, int doRewind); #ifdef PURIFY /* * realpath and purify don't mix happily. It has been noted that realpath * should not be used with purify because of bogus warnings, but just * memset'ing the resolved path will squelch those. This assumes we are * passing the standard MAXPATHLEN size resolved arg. */ static char * Realpath(const char *path, char *resolved); char * Realpath( const char *path, char *resolved) { memset(resolved, 0, MAXPATHLEN); return realpath(path, resolved); } #else # define Realpath realpath #endif /* PURIFY */ #ifndef NO_REALPATH # define haveRealpath 1 #endif /* NO_REALPATH */ #ifdef HAVE_FTS #if defined(HAVE_STRUCT_STAT64) && !defined(__APPLE__) /* fts doesn't do stat64 */ # define noFtsStat 1 #else # define noFtsStat 0 #endif #endif /* HAVE_FTS */ /* *--------------------------------------------------------------------------- * * TclpObjRenameFile, DoRenameFile -- * * Changes the name of an existing file or directory, from src to dst. If * src and dst refer to the same file or directory, does nothing and * returns success. Otherwise if dst already exists, it will be deleted * and replaced by src subject to the following conditions: * If src is a directory, dst may be an empty directory. * If src is a file, dst may be a file. * In any other situation where dst already exists, the rename will fail. * * Results: * If the directory was successfully created, returns TCL_OK. Otherwise * the return value is TCL_ERROR and errno is set to indicate the error. * Some possible values for errno are: * * EACCES: src or dst parent directory can't be read and/or written. * EEXIST: dst is a non-empty directory. * EINVAL: src is a root directory or dst is a subdirectory of src. * EISDIR: dst is a directory, but src is not. * ENOENT: src doesn't exist, or src or dst is "". * ENOTDIR: src is a directory, but dst is not. * EXDEV: src and dst are on different filesystems. * * Side effects: * The implementation of rename may allow cross-filesystem renames, but * the caller should be prepared to emulate it with copy and delete if * errno is EXDEV. * *--------------------------------------------------------------------------- */ int TclpObjRenameFile( Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr) { return DoRenameFile((const char *)Tcl_FSGetNativePath(srcPathPtr), (const char *)Tcl_FSGetNativePath(destPathPtr)); } static int DoRenameFile( const char *src, /* Pathname of file or dir to be renamed * (native). */ const char *dst) /* New pathname of file or directory * (native). */ { if (rename(src, dst) == 0) { /* INTL: Native. */ return TCL_OK; } if (errno == ENOTEMPTY) { errno = EEXIST; } /* * IRIX returns EIO when you attempt to move a directory into itself. We * just map EIO to EINVAL get the right message on SGI. Most platforms * don't return EIO except in really strange cases. */ if (errno == EIO) { errno = EINVAL; } #ifndef NO_REALPATH /* * SunOS 4.1.4 reports overwriting a non-empty directory with a directory * as EINVAL instead of EEXIST (first rule out the correct EINVAL result * code for moving a directory into itself). Must be conditionally * compiled because realpath() not defined on all systems. */ if (errno == EINVAL && haveRealpath) { char srcPath[MAXPATHLEN], dstPath[MAXPATHLEN]; TclDIR *dirPtr; Tcl_DirEntry *dirEntPtr; if ((Realpath((char *) src, srcPath) != NULL) /* INTL: Native. */ && (Realpath((char *) dst, dstPath) != NULL) /* INTL: Native */ && (strncmp(srcPath, dstPath, strlen(srcPath)) != 0)) { dirPtr = TclOSopendir(dst); /* INTL: Native. */ if (dirPtr != NULL) { while (1) { dirEntPtr = TclOSreaddir(dirPtr); /* INTL: Native. */ if (dirEntPtr == NULL) { break; } if ((strcmp(dirEntPtr->d_name, ".") != 0) && (strcmp(dirEntPtr->d_name, "..") != 0)) { errno = EEXIST; TclOSclosedir(dirPtr); return TCL_ERROR; } } TclOSclosedir(dirPtr); } } errno = EINVAL; } #endif /* !NO_REALPATH */ if (strcmp(src, "/") == 0) { /* * Alpha reports renaming / as EBUSY and Linux reports it as EACCES, * instead of EINVAL. */ errno = EINVAL; } /* * DEC Alpha OSF1 V3.0 returns EACCES when attempting to move a file * across filesystems and the parent directory of that file is not * writable. Most other systems return EXDEV. Does nothing to correct this * behavior. */ return TCL_ERROR; } /* *--------------------------------------------------------------------------- * * TclpObjCopyFile, DoCopyFile -- * * Copy a single file (not a directory). If dst already exists and is not * a directory, it is removed. * * Results: * If the file was successfully copied, returns TCL_OK. Otherwise the * return value is TCL_ERROR and errno is set to indicate the error. Some * possible values for errno are: * * EACCES: src or dst parent directory can't be read and/or written. * EISDIR: src or dst is a directory. * ENOENT: src doesn't exist. src or dst is "". * * Side effects: * This procedure will also copy symbolic links, block, and character * devices, and fifos. For symbolic links, the links themselves will be * copied and not what they point to. For the other special file types, * the directory entry will be copied and not the contents of the device * that it refers to. * *--------------------------------------------------------------------------- */ int TclpObjCopyFile( Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr) { const char *src = (const char *)Tcl_FSGetNativePath(srcPathPtr); Tcl_StatBuf srcStatBuf; if (TclOSlstat(src, &srcStatBuf) != 0) { /* INTL: Native. */ return TCL_ERROR; } return DoCopyFile(src, (const char *)Tcl_FSGetNativePath(destPathPtr), &srcStatBuf); } static int DoCopyFile( const char *src, /* Pathname of file to be copied (native). */ const char *dst, /* Pathname of file to copy to (native). */ const Tcl_StatBuf *statBufPtr) /* Used to determine filetype. */ { Tcl_StatBuf dstStatBuf; if (S_ISDIR(statBufPtr->st_mode)) { errno = EISDIR; return TCL_ERROR; } /* * Symlink, and some of the other calls will fail if the target exists, so * we remove it first. */ if (TclOSlstat(dst, &dstStatBuf) == 0) { /* INTL: Native. */ if (S_ISDIR(dstStatBuf.st_mode)) { errno = EISDIR; return TCL_ERROR; } } if (unlink(dst) != 0) { /* INTL: Native. */ if (errno != ENOENT) { return TCL_ERROR; } } switch ((int) (statBufPtr->st_mode & S_IFMT)) { #ifndef DJGPP case S_IFLNK: { char linkBuf[MAXPATHLEN+1]; int length; length = readlink(src, linkBuf, MAXPATHLEN); /* INTL: Native. */ if (length == -1) { return TCL_ERROR; } linkBuf[length] = '\0'; if (symlink(linkBuf, dst) < 0) { /* INTL: Native. */ return TCL_ERROR; } #ifdef MAC_OSX_TCL TclMacOSXCopyFileAttributes(src, dst, statBufPtr); #endif break; } #endif /* !DJGPP */ case S_IFBLK: case S_IFCHR: if (mknod(dst, statBufPtr->st_mode, /* INTL: Native. */ statBufPtr->st_rdev) < 0) { return TCL_ERROR; } return CopyFileAtts(src, dst, statBufPtr); case S_IFIFO: if (mkfifo(dst, statBufPtr->st_mode) < 0) { /* INTL: Native. */ return TCL_ERROR; } return CopyFileAtts(src, dst, statBufPtr); default: return TclUnixCopyFile(src, dst, statBufPtr, 0); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclUnixCopyFile - * * Helper function for TclpCopyFile. Copies one regular file, using * read() and write(). * * Results: * A standard Tcl result. * * Side effects: * A file is copied. Dst will be overwritten if it exists. * *---------------------------------------------------------------------- */ int TclUnixCopyFile( const char *src, /* Pathname of file to copy (native). */ const char *dst, /* Pathname of file to create/overwrite * (native). */ const Tcl_StatBuf *statBufPtr, /* Used to determine mode and blocksize. */ int dontCopyAtts) /* If flag set, don't copy attributes. */ { int srcFd, dstFd; size_t blockSize; /* Optimal I/O blocksize for filesystem */ char *buffer; /* Data buffer for copy */ ssize_t nread; #ifdef DJGPP #define BINMODE |O_BINARY #else #define BINMODE #endif /* DJGPP */ #define DEFAULT_COPY_BLOCK_SIZE 4096 if ((srcFd = TclOSopen(src, O_RDONLY BINMODE, 0)) < 0) { /* INTL: Native */ return TCL_ERROR; } dstFd = TclOSopen(dst, O_CREAT|O_TRUNC|O_WRONLY BINMODE, /* INTL: Native */ statBufPtr->st_mode); if (dstFd < 0) { close(srcFd); return TCL_ERROR; } /* * Try to work out the best size of buffer to use for copying. If we * can't, it's no big deal as we can just use a (32-bit) page, since * that's likely to be fairly efficient anyway. */ #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE blockSize = statBufPtr->st_blksize; #elif !defined(NO_FSTATFS) { struct statfs fs; if (fstatfs(srcFd, &fs) == 0) { blockSize = fs.f_bsize; } else { blockSize = DEFAULT_COPY_BLOCK_SIZE; } } #else blockSize = DEFAULT_COPY_BLOCK_SIZE; #endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */ /* * [SF Tcl Bug 1586470] Even if we HAVE_STRUCT_STAT_ST_BLKSIZE, there are * filesystems which report a bogus value for the blocksize. An example * is the Andrew Filesystem (afs), reporting a blocksize of 0. When * detecting such a situation we now simply fall back to a hardwired * default size. */ if (blockSize <= 0) { blockSize = DEFAULT_COPY_BLOCK_SIZE; } buffer = (char *)Tcl_Alloc(blockSize); while (1) { nread = read(srcFd, buffer, blockSize); if ((nread == -1) || (nread == 0)) { break; } if (write(dstFd, buffer, nread) != nread) { nread = -1; break; } } Tcl_Free(buffer); close(srcFd); if ((close(dstFd) != 0) || (nread == -1)) { unlink(dst); /* INTL: Native. */ return TCL_ERROR; } if (!dontCopyAtts && CopyFileAtts(src, dst, statBufPtr) == TCL_ERROR) { /* * The copy succeeded, but setting the permissions failed, so be in a * consistent state, we remove the file that was created by the copy. */ unlink(dst); /* INTL: Native. */ return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * TclpObjDeleteFile, TclpDeleteFile -- * * Removes a single file (not a directory). * * Results: * If the file was successfully deleted, returns TCL_OK. Otherwise the * return value is TCL_ERROR and errno is set to indicate the error. Some * possible values for errno are: * * EACCES: a parent directory can't be read and/or written. * EISDIR: path is a directory. * ENOENT: path doesn't exist or is "". * * Side effects: * The file is deleted, even if it is read-only. * *--------------------------------------------------------------------------- */ int TclpObjDeleteFile( Tcl_Obj *pathPtr) { return TclpDeleteFile(Tcl_FSGetNativePath(pathPtr)); } int TclpDeleteFile( const void *path) /* Pathname of file to be removed (native). */ { if (unlink((const char *)path) != 0) { return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * TclpCreateDirectory, DoCreateDirectory -- * * Creates the specified directory. All parent directories of the * specified directory must already exist. The directory is automatically * created with permissions so that user can access the new directory and * create new files or subdirectories in it. * * Results: * If the directory was successfully created, returns TCL_OK. Otherwise * the return value is TCL_ERROR and errno is set to indicate the error. * Some possible values for errno are: * * EACCES: a parent directory can't be read and/or written. * EEXIST: path already exists. * ENOENT: a parent directory doesn't exist. * * Side effects: * A directory is created with the current umask, except that permission * for u+rwx will always be added. * *--------------------------------------------------------------------------- */ int TclpObjCreateDirectory( Tcl_Obj *pathPtr) { return DoCreateDirectory((const char *)Tcl_FSGetNativePath(pathPtr)); } static int DoCreateDirectory( const char *path) /* Pathname of directory to create (native). */ { mode_t mode; mode = umask(0); umask(mode); /* * umask return value is actually the inverse of the permissions. */ mode = (0777 & ~mode) | S_IRUSR | S_IWUSR | S_IXUSR; if (mkdir(path, mode) != 0) { /* INTL: Native. */ return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * TclpObjCopyDirectory -- * * Recursively copies a directory. The target directory dst must not * already exist. Note that this function does not merge two directory * hierarchies, even if the target directory is an empty directory. * * Results: * If the directory was successfully copied, returns TCL_OK. Otherwise * the return value is TCL_ERROR, errno is set to indicate the error, and * the pathname of the file that caused the error is stored in errorPtr. * See TclpObjCreateDirectory and TclpObjCopyFile for a description of * possible values for errno. * * Side effects: * An exact copy of the directory hierarchy src will be created with the * name dst. If an error occurs, the error will be returned immediately, * and remaining files will not be processed. * *--------------------------------------------------------------------------- */ int TclpObjCopyDirectory( Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr) { Tcl_DString ds; Tcl_DString srcString, dstString; int ret; Tcl_Obj *transPtr; transPtr = Tcl_FSGetTranslatedPath(NULL,srcPathPtr); ret = Tcl_UtfToExternalDStringEx(NULL, NULL, (transPtr != NULL ? TclGetString(transPtr) : NULL), -1, 0, &srcString, NULL); if (transPtr != NULL) { Tcl_DecrRefCount(transPtr); } if (ret != TCL_OK) { *errorPtr = srcPathPtr; } else { transPtr = Tcl_FSGetTranslatedPath(NULL,destPathPtr); ret = Tcl_UtfToExternalDStringEx(NULL, NULL, (transPtr != NULL ? TclGetString(transPtr) : NULL), -1, TCL_ENCODING_PROFILE_TCL8, &dstString, NULL); if (transPtr != NULL) { Tcl_DecrRefCount(transPtr); } if (ret != TCL_OK) { *errorPtr = destPathPtr; } else { ret = TraverseUnixTree(TraversalCopy, &srcString, &dstString, &ds, 0); /* Note above call only sets ds on error */ if (ret != TCL_OK) { *errorPtr = Tcl_DStringToObj(&ds); } Tcl_DStringFree(&dstString); } Tcl_DStringFree(&srcString); } if (ret != TCL_OK) { Tcl_IncrRefCount(*errorPtr); } return ret; } /* *--------------------------------------------------------------------------- * * TclpRemoveDirectory, DoRemoveDirectory -- * * Removes directory (and its contents, if the recursive flag is set). * * Results: * If the directory was successfully removed, returns TCL_OK. Otherwise * the return value is TCL_ERROR, errno is set to indicate the error, and * the pathname of the file that caused the error is stored in errorPtr. * Some possible values for errno are: * * EACCES: path directory can't be read and/or written. * EEXIST: path is a non-empty directory. * EINVAL: path is a root directory. * ENOENT: path doesn't exist or is "". * ENOTDIR: path is not a directory. * * Side effects: * Directory removed. If an error occurs, the error will be returned * immediately, and remaining files will not be deleted. * *--------------------------------------------------------------------------- */ int TclpObjRemoveDirectory( Tcl_Obj *pathPtr, int recursive, Tcl_Obj **errorPtr) { Tcl_DString ds; Tcl_DString pathString; int ret; Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr); ret = Tcl_UtfToExternalDStringEx(NULL, NULL, (transPtr != NULL ? TclGetString(transPtr) : NULL), -1, TCL_ENCODING_PROFILE_TCL8, &pathString, NULL); if (transPtr != NULL) { Tcl_DecrRefCount(transPtr); } if (ret != TCL_OK) { *errorPtr = pathPtr; } else { ret = DoRemoveDirectory(&pathString, recursive, &ds); Tcl_DStringFree(&pathString); /* Note above call only sets ds on error */ if (ret != TCL_OK) { *errorPtr = Tcl_DStringToObj(&ds); } } if (ret != TCL_OK) { Tcl_IncrRefCount(*errorPtr); } return ret; } static int DoRemoveDirectory( Tcl_DString *pathPtr, /* Pathname of directory to be removed * (native). */ int recursive, /* If non-zero, removes directories that are * nonempty. Otherwise, will only remove empty * directories. */ Tcl_DString *errorPtr) /* If non-NULL, uninitialized or free DString * filled with UTF-8 name of file causing * error. */ { const char *path; mode_t oldPerm = 0; int result; path = Tcl_DStringValue(pathPtr); if (recursive != 0) { /* * We should try to change permissions so this can be deleted. */ Tcl_StatBuf statBuf; int newPerm; if (TclOSstat(path, &statBuf) == 0) { oldPerm = (mode_t) (statBuf.st_mode & 0x00007FFF); } newPerm = oldPerm | (64+128+256); chmod(path, (mode_t) newPerm); } if (rmdir(path) == 0) { /* INTL: Native. */ return TCL_OK; } if (errno == ENOTEMPTY) { errno = EEXIST; } result = TCL_OK; if ((errno != EEXIST) || (recursive == 0)) { if (errorPtr != NULL) { Tcl_ExternalToUtfDStringEx(NULL, NULL, path, TCL_INDEX_NONE, 0, errorPtr, NULL); } result = TCL_ERROR; } /* * The directory is nonempty, but the recursive flag has been specified, * so we recursively remove all the files in the directory. */ if (result == TCL_OK) { result = TraverseUnixTree(TraversalDelete, pathPtr, NULL, errorPtr, 1); } if ((result != TCL_OK) && (recursive != 0)) { /* * Try to restore permissions. */ chmod(path, oldPerm); } return result; } /* *--------------------------------------------------------------------------- * * TraverseUnixTree -- * * Traverse directory tree specified by sourcePtr, calling the function * traverseProc for each file and directory encountered. If destPtr is * non-null, each of name in the sourcePtr directory is appended to the * directory specified by destPtr and passed as the second argument to * traverseProc(). * * Results: * Standard Tcl result. * * Side effects: * None caused by TraverseUnixTree, however the user specified * traverseProc() may change state. If an error occurs, the error will be * returned immediately, and remaining files will not be processed. * *--------------------------------------------------------------------------- */ static int TraverseUnixTree( TraversalProc *traverseProc,/* Function to call for every file and * directory in source hierarchy. */ Tcl_DString *sourcePtr, /* Pathname of source directory to be * traversed (native). */ Tcl_DString *targetPtr, /* Pathname of directory to traverse in * parallel with source directory (native). */ Tcl_DString *errorPtr, /* If non-NULL, uninitialized or free DString * filled with UTF-8 name of file causing * error. */ int doRewind) /* Flag indicating that to ensure complete * traversal of source hierarchy, the readdir * loop should be rewound whenever * traverseProc has returned TCL_OK; this is * required when traverseProc modifies the * source hierarchy, e.g. by deleting * files. */ { Tcl_StatBuf statBuf; const char *source, *errfile; int result; size_t targetLen, sourceLen; #ifndef HAVE_FTS int numProcessed = 0; Tcl_DirEntry *dirEntPtr; TclDIR *dirPtr; #else const char *paths[2] = {NULL, NULL}; FTS *fts = NULL; FTSENT *ent; #endif errfile = NULL; result = TCL_OK; targetLen = 0; source = Tcl_DStringValue(sourcePtr); if (TclOSlstat(source, &statBuf) != 0) { /* INTL: Native. */ errfile = source; goto end; } if (!S_ISDIR(statBuf.st_mode)) { /* * Process the regular file */ return traverseProc(sourcePtr, targetPtr, &statBuf, DOTREE_F, errorPtr); } #ifndef HAVE_FTS dirPtr = TclOSopendir(source); /* INTL: Native. */ if (dirPtr == NULL) { /* * Can't read directory */ errfile = source; goto end; } result = traverseProc(sourcePtr, targetPtr, &statBuf, DOTREE_PRED, errorPtr); if (result != TCL_OK) { TclOSclosedir(dirPtr); return result; } TclDStringAppendLiteral(sourcePtr, "/"); sourceLen = Tcl_DStringLength(sourcePtr); if (targetPtr != NULL) { TclDStringAppendLiteral(targetPtr, "/"); targetLen = Tcl_DStringLength(targetPtr); } while ((dirEntPtr = TclOSreaddir(dirPtr)) != NULL) { /* INTL: Native. */ if ((dirEntPtr->d_name[0] == '.') && ((dirEntPtr->d_name[1] == '\0') || (strcmp(dirEntPtr->d_name, "..") == 0))) { continue; } /* * Append name after slash, and recurse on the file. */ Tcl_DStringAppend(sourcePtr, dirEntPtr->d_name, TCL_INDEX_NONE); if (targetPtr != NULL) { Tcl_DStringAppend(targetPtr, dirEntPtr->d_name, TCL_INDEX_NONE); } result = TraverseUnixTree(traverseProc, sourcePtr, targetPtr, errorPtr, doRewind); if (result != TCL_OK) { break; } else { numProcessed++; } /* * Remove name after slash. */ Tcl_DStringSetLength(sourcePtr, sourceLen); if (targetPtr != NULL) { Tcl_DStringSetLength(targetPtr, targetLen); } if (doRewind && (numProcessed > MAX_READDIR_UNLINK_THRESHOLD)) { /* * Call rewinddir if we've called unlink or rmdir so many times * (since the opendir or the previous rewinddir), to avoid a * NULL-return that may a symptom of a buggy readdir. */ TclOSrewinddir(dirPtr); numProcessed = 0; } } TclOSclosedir(dirPtr); /* * Strip off the trailing slash we added */ Tcl_DStringSetLength(sourcePtr, sourceLen - 1); if (targetPtr != NULL) { Tcl_DStringSetLength(targetPtr, targetLen - 1); } if (result == TCL_OK) { /* * Call traverseProc() on a directory after visiting all the files in * that directory. */ result = traverseProc(sourcePtr, targetPtr, &statBuf, DOTREE_POSTD, errorPtr); } #else /* HAVE_FTS */ paths[0] = source; fts = fts_open((char **) paths, FTS_PHYSICAL | FTS_NOCHDIR | (noFtsStat || doRewind ? FTS_NOSTAT : 0), NULL); if (fts == NULL) { errfile = source; goto end; } sourceLen = Tcl_DStringLength(sourcePtr); if (targetPtr != NULL) { targetLen = Tcl_DStringLength(targetPtr); } while ((ent = fts_read(fts)) != NULL) { unsigned short info = ent->fts_info; char *path = ent->fts_path + sourceLen; unsigned short pathlen = ent->fts_pathlen - sourceLen; int type; Tcl_StatBuf *statBufPtr = NULL; if (info == FTS_DNR || info == FTS_ERR || info == FTS_NS) { errfile = ent->fts_path; break; } Tcl_DStringAppend(sourcePtr, path, pathlen); if (targetPtr != NULL) { Tcl_DStringAppend(targetPtr, path, pathlen); } switch (info) { case FTS_D: type = DOTREE_PRED; break; case FTS_DP: type = DOTREE_POSTD; break; default: type = DOTREE_F; break; } if (!doRewind) { /* no need to stat for delete */ if (noFtsStat) { statBufPtr = &statBuf; if (TclOSlstat(ent->fts_path, statBufPtr) != 0) { errfile = ent->fts_path; break; } } else { statBufPtr = (Tcl_StatBuf *) ent->fts_statp; } } result = traverseProc(sourcePtr, targetPtr, statBufPtr, type, errorPtr); if (result != TCL_OK) { break; } Tcl_DStringSetLength(sourcePtr, sourceLen); if (targetPtr != NULL) { Tcl_DStringSetLength(targetPtr, targetLen); } } #endif /* !HAVE_FTS */ end: if (errfile != NULL) { if (errorPtr != NULL) { Tcl_ExternalToUtfDStringEx(NULL, NULL, errfile, TCL_INDEX_NONE, 0, errorPtr, NULL); } result = TCL_ERROR; } #ifdef HAVE_FTS if (fts != NULL) { fts_close(fts); } #endif return result; } /* *---------------------------------------------------------------------- * * TraversalCopy * * Called from TraverseUnixTree in order to execute a recursive copy of a * directory. * * Results: * Standard Tcl result. * * Side effects: * The file or directory src may be copied to dst, depending on the value * of type. * *---------------------------------------------------------------------- */ static int TraversalCopy( Tcl_DString *srcPtr, /* Source pathname to copy (native). */ Tcl_DString *dstPtr, /* Destination pathname of copy (native). */ const Tcl_StatBuf *statBufPtr, /* Stat info for file specified by srcPtr. */ int type, /* Reason for call - see TraverseUnixTree(). */ Tcl_DString *errorPtr) /* If non-NULL, uninitialized or free DString * filled with UTF-8 name of file causing * error. */ { switch (type) { case DOTREE_F: if (DoCopyFile(Tcl_DStringValue(srcPtr), Tcl_DStringValue(dstPtr), statBufPtr) == TCL_OK) { return TCL_OK; } break; case DOTREE_PRED: if (DoCreateDirectory(Tcl_DStringValue(dstPtr)) == TCL_OK) { return TCL_OK; } break; case DOTREE_POSTD: if (CopyFileAtts(Tcl_DStringValue(srcPtr), Tcl_DStringValue(dstPtr), statBufPtr) == TCL_OK) { return TCL_OK; } break; } /* * There shouldn't be a problem with src, because we already checked it to * get here. */ if (errorPtr != NULL) { Tcl_ExternalToUtfDStringEx(NULL, NULL, Tcl_DStringValue(dstPtr), Tcl_DStringLength(dstPtr), 0, errorPtr, NULL); } return TCL_ERROR; } /* *--------------------------------------------------------------------------- * * TraversalDelete -- * * Called by procedure TraverseUnixTree for every file and directory that * it encounters in a directory hierarchy. This procedure unlinks files, * and removes directories after all the containing files have been * processed. * * Results: * Standard Tcl result. * * Side effects: * Files or directory specified by src will be deleted. * *---------------------------------------------------------------------- */ static int TraversalDelete( Tcl_DString *srcPtr, /* Source pathname (native). */ TCL_UNUSED(Tcl_DString *), TCL_UNUSED(const Tcl_StatBuf *), int type, /* Reason for call - see TraverseUnixTree(). */ Tcl_DString *errorPtr) /* If non-NULL, uninitialized or free DString * filled with UTF-8 name of file causing * error. */ { switch (type) { case DOTREE_F: if (TclpDeleteFile(Tcl_DStringValue(srcPtr)) == 0) { return TCL_OK; } break; case DOTREE_PRED: return TCL_OK; case DOTREE_POSTD: if (DoRemoveDirectory(srcPtr, 0, NULL) == 0) { return TCL_OK; } break; } if (errorPtr != NULL) { Tcl_ExternalToUtfDStringEx(NULL, NULL, Tcl_DStringValue(srcPtr), Tcl_DStringLength(srcPtr), 0, errorPtr, NULL); } return TCL_ERROR; } /* *--------------------------------------------------------------------------- * * CopyFileAtts -- * * Copy the file attributes such as owner, group, permissions, and * modification date from one file to another. * * Results: * Standard Tcl result. * * Side effects: * User id, group id, permission bits, last modification time, and last * access time are updated in the new file to reflect the old file. * *--------------------------------------------------------------------------- */ static int CopyFileAtts( #ifdef MAC_OSX_TCL const char *src, /* Path name of source file (native). */ #else TCL_UNUSED(const char *) /*src*/, #endif const char *dst, /* Path name of target file (native). */ const Tcl_StatBuf *statBufPtr) /* Stat info for source file */ { struct utimbuf tval; mode_t newMode; newMode = statBufPtr->st_mode & (S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO); /* * Note that if you copy a setuid file that is owned by someone else, and * you are not root, then the copy will be setuid to you. The most correct * implementation would probably be to have the copy not setuid to anyone * if the original file was owned by someone else, but this corner case * isn't currently handled. It would require another lstat(), or getuid(). */ if (chmod(dst, newMode)) { /* INTL: Native. */ newMode &= ~(S_ISUID | S_ISGID); if (chmod(dst, newMode)) { /* INTL: Native. */ return TCL_ERROR; } } tval.actime = Tcl_GetAccessTimeFromStat(statBufPtr); tval.modtime = Tcl_GetModificationTimeFromStat(statBufPtr); if (utime(dst, &tval)) { /* INTL: Native. */ return TCL_ERROR; } #ifdef MAC_OSX_TCL TclMacOSXCopyFileAttributes(src, dst, statBufPtr); #endif return TCL_OK; } /* *---------------------------------------------------------------------- * * GetGroupAttribute * * Gets the group attribute of a file. * * Results: * Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there * is no error. * * Side effects: * A new object is allocated. * *---------------------------------------------------------------------- */ static int GetGroupAttribute( Tcl_Interp *interp, /* The interp we are using for errors. */ TCL_UNUSED(int) /*objIndex*/, Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */ { Tcl_StatBuf statBuf; struct group *groupPtr; int result; result = TclpObjStat(fileName, &statBuf); if (result != 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); } return TCL_ERROR; } groupPtr = TclpGetGrGid(statBuf.st_gid); if (groupPtr == NULL) { TclNewIntObj(*attributePtrPtr, statBuf.st_gid); } else { Tcl_DString ds; const char *utf; utf = Tcl_ExternalToUtfDString(NULL, groupPtr->gr_name, TCL_INDEX_NONE, &ds); *attributePtrPtr = Tcl_NewStringObj(utf, TCL_INDEX_NONE); Tcl_DStringFree(&ds); } return TCL_OK; } /* *---------------------------------------------------------------------- * * GetOwnerAttribute * * Gets the owner attribute of a file. * * Results: * Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there * is no error. * * Side effects: * A new object is allocated. * *---------------------------------------------------------------------- */ static int GetOwnerAttribute( Tcl_Interp *interp, /* The interp we are using for errors. */ TCL_UNUSED(int) /*objIndex*/, Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */ { Tcl_StatBuf statBuf; struct passwd *pwPtr; int result; result = TclpObjStat(fileName, &statBuf); if (result != 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); } return TCL_ERROR; } pwPtr = TclpGetPwUid(statBuf.st_uid); if (pwPtr == NULL) { TclNewIntObj(*attributePtrPtr, statBuf.st_uid); } else { Tcl_DString ds; (void)Tcl_ExternalToUtfDString(NULL, pwPtr->pw_name, TCL_INDEX_NONE, &ds); *attributePtrPtr = Tcl_DStringToObj(&ds); } return TCL_OK; } /* *---------------------------------------------------------------------- * * GetPermissionsAttribute * * Gets the group attribute of a file. * * Results: * Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there * is no error. The object will have ref count 0. * * Side effects: * A new object is allocated. * *---------------------------------------------------------------------- */ static int GetPermissionsAttribute( Tcl_Interp *interp, /* The interp we are using for errors. */ TCL_UNUSED(int) /*objIndex*/, Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */ { Tcl_StatBuf statBuf; int result; result = TclpObjStat(fileName, &statBuf); if (result != 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); } return TCL_ERROR; } *attributePtrPtr = Tcl_ObjPrintf( "%0#5o", ((int)statBuf.st_mode & 0x7FFF)); return TCL_OK; } /* *--------------------------------------------------------------------------- * * SetGroupAttribute -- * * Sets the group of the file to the specified group. * * Results: * Standard TCL result. * * Side effects: * As above. * *--------------------------------------------------------------------------- */ static int SetGroupAttribute( Tcl_Interp *interp, /* The interp for error reporting. */ TCL_UNUSED(int) /*objIndex*/, Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj *attributePtr) /* New group for file. */ { Tcl_WideInt gid; int result; const char *native; if (TclGetWideIntFromObj(NULL, attributePtr, &gid) != TCL_OK) { Tcl_DString ds; struct group *groupPtr = NULL; const char *string; Tcl_Size length; string = TclGetStringFromObj(attributePtr, &length); if (Tcl_UtfToExternalDStringEx(interp, NULL, string, length, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } native = Tcl_DStringValue(&ds); groupPtr = TclpGetGrNam(native); /* INTL: Native. */ Tcl_DStringFree(&ds); if (groupPtr == NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not set group for file \"%s\":" " group \"%s\" does not exist", TclGetString(fileName), string)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "SETGRP", "NO_GROUP", (char *)NULL); } return TCL_ERROR; } gid = groupPtr->gr_gid; } native = (const char *)Tcl_FSGetNativePath(fileName); result = chown(native, (uid_t) -1, (gid_t) gid); /* INTL: Native. */ if (result != 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not set group for file \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); } return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * SetOwnerAttribute -- * * Sets the owner of the file to the specified owner. * * Results: * Standard TCL result. * * Side effects: * As above. * *--------------------------------------------------------------------------- */ static int SetOwnerAttribute( Tcl_Interp *interp, /* The interp for error reporting. */ TCL_UNUSED(int) /*objIndex*/, Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj *attributePtr) /* New owner for file. */ { Tcl_WideInt uid; int result; const char *native; if (TclGetWideIntFromObj(NULL, attributePtr, &uid) != TCL_OK) { Tcl_DString ds; struct passwd *pwPtr = NULL; const char *string; Tcl_Size length; string = TclGetStringFromObj(attributePtr, &length); if (Tcl_UtfToExternalDStringEx(interp, NULL, string, length, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } native = Tcl_DStringValue(&ds); pwPtr = TclpGetPwNam(native); /* INTL: Native. */ Tcl_DStringFree(&ds); if (pwPtr == NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not set owner for file \"%s\":" " user \"%s\" does not exist", TclGetString(fileName), string)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "SETOWN", "NO_USER", (char *)NULL); } return TCL_ERROR; } uid = pwPtr->pw_uid; } native = (const char *)Tcl_FSGetNativePath(fileName); result = chown(native, (uid_t) uid, (gid_t) -1); /* INTL: Native. */ if (result != 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not set owner for file \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); } return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * SetPermissionsAttribute * * Sets the file to the given permission. * * Results: * Standard TCL result. * * Side effects: * The permission of the file is changed. * *--------------------------------------------------------------------------- */ static int SetPermissionsAttribute( Tcl_Interp *interp, /* The interp we are using for errors. */ TCL_UNUSED(int) /*objIndex*/, Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj *attributePtr) /* The attribute to set. */ { Tcl_WideInt mode; mode_t newMode; int result = TCL_ERROR; const char *native; const char *modeStringPtr = TclGetString(attributePtr); int scanned = TclParseAllWhiteSpace(modeStringPtr, -1); /* * First supply support for octal number format */ if ((modeStringPtr[scanned] == '0') && (modeStringPtr[scanned+1] >= '0') && (modeStringPtr[scanned+1] <= '7')) { /* Leading zero - attempt octal interpretation */ Tcl_Obj *modeObj; TclNewLiteralStringObj(modeObj, "0o"); Tcl_AppendToObj(modeObj, modeStringPtr+scanned+1, TCL_INDEX_NONE); result = TclGetWideIntFromObj(NULL, modeObj, &mode); Tcl_DecrRefCount(modeObj); } if (result == TCL_OK || TclGetWideIntFromObj(NULL, attributePtr, &mode) == TCL_OK) { newMode = (mode_t) (mode & 0x00007FFF); } else { Tcl_StatBuf buf; /* * Try the forms "rwxrwxrwx" and "ugo=rwx" * * We get the current mode of the file, in order to allow for ug+-=rwx * style chmod strings. */ result = TclpObjStat(fileName, &buf); if (result != 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); } return TCL_ERROR; } newMode = (mode_t) (buf.st_mode & 0x00007FFF); if (GetModeFromPermString(NULL, modeStringPtr, &newMode) != TCL_OK) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown permission string format \"%s\"", modeStringPtr)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "PERMISSION", (char *)NULL); } return TCL_ERROR; } } native = (const char *)Tcl_FSGetNativePath(fileName); result = chmod(native, newMode); /* INTL: Native. */ if (result != 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not set permissions for file \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); } return TCL_ERROR; } return TCL_OK; } #ifndef DJGPP /* *--------------------------------------------------------------------------- * * TclpObjListVolumes -- * * Lists the currently mounted volumes, which on UNIX is just /. * * Results: * The list of volumes. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Obj * TclpObjListVolumes(void) { Tcl_Obj *resultPtr; TclNewLiteralStringObj(resultPtr, "/"); Tcl_IncrRefCount(resultPtr); return resultPtr; } #endif /* *---------------------------------------------------------------------- * * GetModeFromPermString -- * * This procedure is invoked to process the "file permissions" Tcl * command, to check for a "rwxrwxrwx" or "ugoa+-=rwxst" string. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int GetModeFromPermString( TCL_UNUSED(Tcl_Interp *), const char *modeStringPtr, /* Permissions string */ mode_t *modePtr) /* pointer to the mode value */ { mode_t newMode; mode_t oldMode; /* Storage for the value of the old mode (that * is passed in), to allow for the chmod style * manipulation. */ int i,n, who, op, what, op_found, who_found; /* * We start off checking for an "rwxrwxrwx" style permissions string */ if (strlen(modeStringPtr) != 9) { goto chmodStyleCheck; } newMode = 0; for (i = 0; i < 9; i++) { switch (modeStringPtr[i]) { case 'r': if ((i%3) != 0) { goto chmodStyleCheck; } newMode |= (1<<(8-i)); break; case 'w': if ((i%3) != 1) { goto chmodStyleCheck; } newMode |= (1<<(8-i)); break; case 'x': if ((i%3) != 2) { goto chmodStyleCheck; } newMode |= (1<<(8-i)); break; case 's': if (((i%3) != 2) || (i > 5)) { goto chmodStyleCheck; } newMode |= (1<<(8-i)); newMode |= (1<<(11-(i/3))); break; case 'S': if (((i%3) != 2) || (i > 5)) { goto chmodStyleCheck; } newMode |= (1<<(11-(i/3))); break; case 't': if (i != 8) { goto chmodStyleCheck; } newMode |= (1<<(8-i)); newMode |= (1<<9); break; case 'T': if (i != 8) { goto chmodStyleCheck; } newMode |= (1<<9); break; case '-': break; default: /* * Oops, not what we thought it was, so go on */ goto chmodStyleCheck; } } *modePtr = newMode; return TCL_OK; chmodStyleCheck: /* * We now check for an "ugoa+-=rwxst" style permissions string */ for (n = 0 ; modeStringPtr[n] != '\0' ; n += i) { oldMode = *modePtr; who = op = what = op_found = who_found = 0; for (i = 0 ; modeStringPtr[n + i] != '\0' ; i++ ) { if (!who_found) { /* who */ switch (modeStringPtr[n + i]) { case 'u': who |= 0x9C0; continue; case 'g': who |= 0x438; continue; case 'o': who |= 0x207; continue; case 'a': who |= 0xFFF; continue; } } who_found = 1; if (who == 0) { who = 0xFFF; } if (!op_found) { /* op */ switch (modeStringPtr[n + i]) { case '+': op = 1; op_found = 1; continue; case '-': op = 2; op_found = 1; continue; case '=': op = 3; op_found = 1; continue; default: return TCL_ERROR; } } /* what */ switch (modeStringPtr[n + i]) { case 'r': what |= 0x124; continue; case 'w': what |= 0x92; continue; case 'x': what |= 0x49; continue; case 's': what |= 0xC00; continue; case 't': what |= 0x200; continue; case ',': break; default: return TCL_ERROR; } if (modeStringPtr[n + i] == ',') { i++; break; } } switch (op) { case 1: *modePtr = oldMode | (who & what); continue; case 2: *modePtr = oldMode & ~(who & what); continue; case 3: *modePtr = (oldMode & ~who) | (who & what); continue; } } return TCL_OK; } /* *--------------------------------------------------------------------------- * * TclpObjNormalizePath -- * * Replaces each component except that last one in a pathname that is a * symbolic link with the fully resolved target of that link. * * Results: * Stores the resulting path in pathPtr and returns the offset of the last * byte processed to obtain the resulting path. * * Side effects: * *--------------------------------------------------------------------------- */ int TclpObjNormalizePath( Tcl_Interp *interp, Tcl_Obj *pathPtr, /* An unshared object containing the path to * normalize. */ int nextCheckpoint) /* offset to start at in pathPtr. Must either * be 0 or the offset of a directory separator * at the end of a path part that is already * normalized. I.e. this is not the index of * the byte just after the separator. */ { const char *currentPathEndPosition; char cur; Tcl_Size pathLen; const char *path = TclGetStringFromObj(pathPtr, &pathLen); Tcl_DString ds; const char *nativePath; #ifndef NO_REALPATH char normPath[MAXPATHLEN]; #endif currentPathEndPosition = path + nextCheckpoint; if (*currentPathEndPosition == '/') { currentPathEndPosition++; } #ifndef NO_REALPATH if (nextCheckpoint == 0 && haveRealpath) { /* * Try to get the entire path in one go */ const char *lastDir = strrchr(currentPathEndPosition, '/'); if (lastDir != NULL) { if (Tcl_UtfToExternalDStringEx(interp, NULL, path, lastDir-path, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return -1; } nativePath = Tcl_DStringValue(&ds); if (Realpath(nativePath, normPath) != NULL) { if (*nativePath != '/' && *normPath == '/') { /* * realpath transformed a relative path into an * absolute path. Fall back to the long way. */ /* * To do: This logic seems to be out of date. This whole * routine should be reviewed and cleaed up. */ } else { nextCheckpoint = lastDir - path; goto wholeStringOk; } } Tcl_DStringFree(&ds); } } /* * Else do it the slow way. */ #endif while (1) { cur = *currentPathEndPosition; if ((cur == '/') && (path != currentPathEndPosition)) { /* * Reached directory separator. */ int accessOk; if (Tcl_UtfToExternalDStringEx(interp, NULL, path, currentPathEndPosition - path, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return -1; } nativePath = Tcl_DStringValue(&ds); accessOk = access(nativePath, F_OK); Tcl_DStringFree(&ds); if (accessOk != 0) { /* * File doesn't exist. */ break; } /* * Assign the end of the current component to nextCheckpoint */ nextCheckpoint = currentPathEndPosition - path; } else if (cur == 0) { /* * The end of the string. */ break; } currentPathEndPosition++; } /* * Call 'realpath' to obtain a canonical path. */ #ifndef NO_REALPATH if (haveRealpath) { if (nextCheckpoint == 0) { /* * The path contains at most one component, e.g. '/foo' or '/', * so there is nothing to resolve. Also, on some platforms * 'Realpath' transforms an empty string into the normalized pwd, * which is the wrong answer. */ return 0; } if (Tcl_UtfToExternalDStringEx(interp, NULL, path,nextCheckpoint, 0, &ds, NULL)) { Tcl_DStringFree(&ds); return -1; } nativePath = Tcl_DStringValue(&ds); if (Realpath(nativePath, normPath) != NULL) { Tcl_Size newNormLen; wholeStringOk: newNormLen = strlen(normPath); if ((newNormLen == Tcl_DStringLength(&ds)) && (strcmp(normPath, nativePath) == 0)) { /* * The original path is unchanged. */ Tcl_DStringFree(&ds); /* * Uncommenting this would mean that this native filesystem * routine claims the path is normalized if the file exists, * which would permit the caller to avoid iterating through * other filesystems. Saving lots of calls is * probably worth the extra access() time, but in the common * case that no other filesystems are registered this is an * unnecessary expense. * if (0 == access(normPath, F_OK)) { return pathLen; } */ return nextCheckpoint; } /* * Free the original path and replace it with the normalized path. */ Tcl_DStringFree(&ds); Tcl_ExternalToUtfDStringEx(NULL, NULL, normPath, newNormLen, 0, &ds, NULL); if (path[nextCheckpoint] != '\0') { /* * Append the remaining path components. */ int normLen = Tcl_DStringLength(&ds); Tcl_DStringAppend(&ds, path + nextCheckpoint, pathLen - nextCheckpoint); /* * characters up to and including the directory separator have * been processed */ nextCheckpoint = normLen + 1; } else { /* * We recognise the whole string. */ nextCheckpoint = Tcl_DStringLength(&ds); } Tcl_SetStringObj(pathPtr, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds)); } Tcl_DStringFree(&ds); } #endif /* !NO_REALPATH */ return nextCheckpoint; } /* *---------------------------------------------------------------------- * * TclpOpenTemporaryFile, TclUnixOpenTemporaryFile -- * * Creates a temporary file, possibly based on the supplied bits and * pieces of template supplied in the first three arguments. If the * fourth argument is non-NULL, it contains a Tcl_Obj to store the name * of the temporary file in (and it is caller's responsibility to clean * up). If the fourth argument is NULL, try to arrange for the temporary * file to go away once it is no longer needed. * * Results: * A read-write Tcl Channel open on the file for TclpOpenTemporaryFile, * or a file descriptor (or -1 on failure) for TclUnixOpenTemporaryFile. * * Side effects: * Accesses the filesystem. Will set the contents of the Tcl_Obj fourth * argument (if that is non-NULL). * *---------------------------------------------------------------------- */ Tcl_Channel TclpOpenTemporaryFile( Tcl_Obj *dirObj, Tcl_Obj *basenameObj, Tcl_Obj *extensionObj, Tcl_Obj *resultingNameObj) { int fd = TclUnixOpenTemporaryFile(dirObj, basenameObj, extensionObj, resultingNameObj); if (fd == -1) { return NULL; } return Tcl_MakeFileChannel(INT2PTR(fd), TCL_READABLE|TCL_WRITABLE); } int TclUnixOpenTemporaryFile( Tcl_Obj *dirObj, Tcl_Obj *basenameObj, Tcl_Obj *extensionObj, Tcl_Obj *resultingNameObj) { Tcl_DString templ, tmp; const char *string; int fd; Tcl_Size length; /* * We should also check against making more than TMP_MAX of these. */ if (dirObj) { string = TclGetStringFromObj(dirObj, &length); if (Tcl_UtfToExternalDStringEx(NULL, NULL, string, length, 0, &templ, NULL) != TCL_OK) { return -1; } } else { Tcl_DStringInit(&templ); Tcl_DStringAppend(&templ, DefaultTempDir(), TCL_INDEX_NONE); /* INTL: native */ } TclDStringAppendLiteral(&templ, "/"); if (basenameObj) { string = TclGetStringFromObj(basenameObj, &length); if (Tcl_UtfToExternalDStringEx(NULL, NULL, string, length, 0, &tmp, NULL) != TCL_OK) { Tcl_DStringFree(&tmp); return -1; } TclDStringAppendDString(&templ, &tmp); Tcl_DStringFree(&tmp); } else { TclDStringAppendLiteral(&templ, "tcl"); } TclDStringAppendLiteral(&templ, "_XXXXXX"); #ifdef HAVE_MKSTEMPS if (extensionObj) { string = TclGetStringFromObj(extensionObj, &length); if (Tcl_UtfToExternalDStringEx(NULL, NULL, string, length, 0, &tmp, NULL) != TCL_OK) { Tcl_DStringFree(&templ); return -1; } TclDStringAppendDString(&templ, &tmp); fd = mkstemps(Tcl_DStringValue(&templ), Tcl_DStringLength(&tmp)); Tcl_DStringFree(&tmp); } else #endif { fd = mkstemp(Tcl_DStringValue(&templ)); } if (fd == -1) { Tcl_DStringFree(&templ); return -1; } if (resultingNameObj) { if (Tcl_ExternalToUtfDStringEx(NULL, NULL, Tcl_DStringValue(&templ), Tcl_DStringLength(&templ), 0, &tmp, NULL) != TCL_OK) { Tcl_DStringFree(&templ); return -1; } Tcl_SetStringObj(resultingNameObj, Tcl_DStringValue(&tmp), Tcl_DStringLength(&tmp)); Tcl_DStringFree(&tmp); } else { /* * Try to delete the file immediately since we're not reporting the * name to anyone. Note that we're *not* handling any errors from * this! */ unlink(Tcl_DStringValue(&templ)); errno = 0; } Tcl_DStringFree(&templ); return fd; } /* * Helper that does *part* of what tempnam() does. */ static const char * DefaultTempDir(void) { const char *dir; Tcl_StatBuf buf; dir = getenv("TMPDIR"); if (dir && dir[0] && TclOSstat(dir, &buf) == 0 && S_ISDIR(buf.st_mode) && access(dir, W_OK) == 0) { return dir; } #ifdef P_tmpdir dir = P_tmpdir; if (TclOSstat(dir, &buf)==0 && S_ISDIR(buf.st_mode) && access(dir, W_OK)==0) { return dir; } #endif /* * Assume that the default location ("/tmp" if not overridden) is always * an existing writable directory; we've no recovery mechanism if it * isn't. */ return TCL_TEMPORARY_FILE_DIRECTORY; } /* *---------------------------------------------------------------------- * * TclpCreateTemporaryDirectory -- * * Creates a temporary directory, possibly based on the supplied bits and * pieces of template supplied in the arguments. * * Results: * An object (refcount 0) containing the name of the newly-created * directory, or NULL on failure. * * Side effects: * Accesses the native filesystem. Makes a directory. * *---------------------------------------------------------------------- */ Tcl_Obj * TclpCreateTemporaryDirectory( Tcl_Obj *dirObj, Tcl_Obj *basenameObj) { Tcl_DString templ, tmp; const char *string; #define DEFAULT_TEMP_DIR_PREFIX "tcl" /* * Build the template in writable memory from the user-supplied pieces and * some defaults. */ if (dirObj) { string = TclGetString(dirObj); if (Tcl_UtfToExternalDStringEx(NULL, NULL, string, dirObj->length, 0, &templ, NULL) != TCL_OK) { return NULL; } } else { Tcl_DStringInit(&templ); Tcl_DStringAppend(&templ, DefaultTempDir(), TCL_INDEX_NONE); /* INTL: native */ } if (Tcl_DStringValue(&templ)[Tcl_DStringLength(&templ) - 1] != '/') { TclDStringAppendLiteral(&templ, "/"); } if (basenameObj) { string = TclGetString(basenameObj); if (basenameObj->length) { if (Tcl_UtfToExternalDStringEx(NULL, NULL, string, basenameObj->length, 0, &tmp, NULL) != TCL_OK) { Tcl_DStringFree(&templ); return NULL; } TclDStringAppendDString(&templ, &tmp); Tcl_DStringFree(&tmp); } else { TclDStringAppendLiteral(&templ, DEFAULT_TEMP_DIR_PREFIX); } } else { TclDStringAppendLiteral(&templ, DEFAULT_TEMP_DIR_PREFIX); } TclDStringAppendLiteral(&templ, "_XXXXXX"); /* * Make the temporary directory. */ if (mkdtemp(Tcl_DStringValue(&templ)) == NULL) { Tcl_DStringFree(&templ); return NULL; } /* * The template has been updated. Tell the caller what it was. */ if (Tcl_ExternalToUtfDStringEx(NULL, NULL, Tcl_DStringValue(&templ), Tcl_DStringLength(&templ), 0, &tmp, NULL) != TCL_OK) { Tcl_DStringFree(&templ); return NULL; } Tcl_DStringFree(&templ); return Tcl_DStringToObj(&tmp); } #if defined(__CYGWIN__) static void StatError( Tcl_Interp *interp, /* The interp that has the error */ Tcl_Obj *fileName) /* The name of the file which caused the * error. */ { Tcl_WinConvertError(GetLastError()); Tcl_SetObjResult(interp, Tcl_ObjPrintf("could not read \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); } static WCHAR * winPathFromObj( Tcl_Obj *fileName) { size_t size; const char *native = (const char *)Tcl_FSGetNativePath(fileName); WCHAR *winPath; size = cygwin_conv_path(1, native, NULL, 0); winPath = (WCHAR *)Tcl_Alloc(size); cygwin_conv_path(1, native, winPath, size); return winPath; } static const int attributeArray[] = { 0x20, 0, 2, 0, 0, 1, 4 }; /* *---------------------------------------------------------------------- * * GetUnixFileAttributes * * Gets an attribute of a file. * * Results: * A standard Tcl result. * * Side effects: * If there is no error assigns to *attributePtrPtr the address of a new * Tcl_Obj having a refCount of zero and containing the value of the * specified attribute. * * *---------------------------------------------------------------------- */ static int GetUnixFileAttributes( Tcl_Interp *interp, /* The interp to report errors to. */ int objIndex, /* The index of the attribute. */ Tcl_Obj *fileName, /* The pathname of the file (UTF-8). */ Tcl_Obj **attributePtrPtr) /* Where to store the result. */ { int fileAttributes; WCHAR *winPath = winPathFromObj(fileName); fileAttributes = GetFileAttributesW(winPath); Tcl_Free(winPath); if (fileAttributes == -1) { StatError(interp, fileName); return TCL_ERROR; } TclNewIntObj(*attributePtrPtr, (fileAttributes & attributeArray[objIndex]) != 0); return TCL_OK; } /* *--------------------------------------------------------------------------- * * SetUnixFileAttributes * * Sets the readonly attribute of a file. * * Results: * Standard TCL result. * * Side effects: * The readonly attribute of the file is changed. * *--------------------------------------------------------------------------- */ static int SetUnixFileAttributes( Tcl_Interp *interp, /* The interp we are using for errors. */ int objIndex, /* The index of the attribute. */ Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj *attributePtr) /* The attribute to set. */ { int yesNo, fileAttributes, old; WCHAR *winPath; if (Tcl_GetBooleanFromObj(interp, attributePtr, &yesNo) != TCL_OK) { return TCL_ERROR; } winPath = winPathFromObj(fileName); fileAttributes = old = GetFileAttributesW(winPath); if (fileAttributes == -1) { Tcl_Free(winPath); StatError(interp, fileName); return TCL_ERROR; } if (yesNo) { fileAttributes |= attributeArray[objIndex]; } else { fileAttributes &= ~attributeArray[objIndex]; } if ((fileAttributes != old) && !SetFileAttributesW(winPath, fileAttributes)) { Tcl_Free(winPath); StatError(interp, fileName); return TCL_ERROR; } Tcl_Free(winPath); return TCL_OK; } #elif defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE) /* *---------------------------------------------------------------------- * * GetUnixFileAttributes * * Gets the readonly attribute (user immutable flag) of a file. * * Results: * Standard TCL result. Returns a new Tcl_Obj in attributePtrPtr if there * is no error. The object will have ref count 0. * * Side effects: * A new object is allocated. * *---------------------------------------------------------------------- */ static int GetUnixFileAttributes( Tcl_Interp *interp, /* The interp we are using for errors. */ TCL_UNUSED(int) /*objIndex*/, Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */ { Tcl_StatBuf statBuf; int result; result = TclpObjStat(fileName, &statBuf); if (result != 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); } return TCL_ERROR; } TclNewIntObj(*attributePtrPtr, (statBuf.st_flags & UF_IMMUTABLE) != 0); return TCL_OK; } /* *--------------------------------------------------------------------------- * * SetUnixFileAttributes * * Sets the readonly attribute (user immutable flag) of a file. * * Results: * Standard TCL result. * * Side effects: * The readonly attribute of the file is changed. * *--------------------------------------------------------------------------- */ static int SetUnixFileAttributes( Tcl_Interp *interp, /* The interp we are using for errors. */ TCL_UNUSED(int) /*objIndex*/, Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj *attributePtr) /* The attribute to set. */ { Tcl_StatBuf statBuf; int result, readonly; const char *native; if (Tcl_GetBooleanFromObj(interp, attributePtr, &readonly) != TCL_OK) { return TCL_ERROR; } result = TclpObjStat(fileName, &statBuf); if (result != 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); } return TCL_ERROR; } if (readonly) { statBuf.st_flags |= UF_IMMUTABLE; } else { statBuf.st_flags &= ~UF_IMMUTABLE; } native = (const char *)Tcl_FSGetNativePath(fileName); result = chflags(native, statBuf.st_flags); /* INTL: Native. */ if (result != 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not set flags for file \"%s\": %s", TclGetString(fileName), Tcl_PosixError(interp))); } return TCL_ERROR; } return TCL_OK; } #endif /* defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE) */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclUnixFile.c0000644000175000017500000007703214726623136015144 0ustar sergeisergei/* * tclUnixFile.c -- * * This file contains wrappers around UNIX file handling functions. * These wrappers mask differences between Windows and UNIX. * * Copyright © 1995-1998 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclFileSystem.h" static int NativeMatchType(Tcl_Interp *interp, const char* nativeEntry, const char* nativeName, Tcl_GlobTypeData *types); /* *--------------------------------------------------------------------------- * * TclpFindExecutable -- * * This function computes the absolute path name of the current * application, given its argv[0] value. For Cygwin, argv[0] is * ignored and the path is determined the same as under win32. * * Results: * None. * * Side effects: * The computed path name is stored as a ProcessGlobalValue. * *--------------------------------------------------------------------------- */ #ifdef __CYGWIN__ void TclpFindExecutable( TCL_UNUSED(const char *) /*argv0*/) { size_t length; wchar_t buf[PATH_MAX] = L""; char name[PATH_MAX * 3 + 1]; GetModuleFileNameW(NULL, buf, PATH_MAX); cygwin_conv_path(3, buf, name, sizeof(name)); length = strlen(name); if ((length > 4) && !strcasecmp(name + length - 4, ".exe")) { /* Strip '.exe' part. */ length -= 4; } TclSetObjNameOfExecutable( Tcl_NewStringObj(name, length), NULL); } #else void TclpFindExecutable( const char *argv0) /* The value of the application's argv[0] * (native). */ { const char *name, *p; Tcl_StatBuf statBuf; Tcl_DString buffer, nameString, cwd, utfName; Tcl_Obj *obj; if (argv0 == NULL) { return; } Tcl_DStringInit(&buffer); name = argv0; for (p = name; *p != '\0'; p++) { if (*p == '/') { /* * The name contains a slash, so use the name directly without * doing a path search. */ goto gotName; } } p = getenv("PATH"); /* INTL: Native. */ if (p == NULL) { /* * There's no PATH environment variable; use the default that is used * by sh. */ p = ":/bin:/usr/bin"; } else if (*p == '\0') { /* * An empty path is equivalent to ".". */ p = "./"; } /* * Search through all the directories named in the PATH variable to see if * argv[0] is in one of them. If so, use that file name. */ while (1) { while (TclIsSpaceProcM(*p)) { p++; } name = p; while ((*p != ':') && (*p != 0)) { p++; } TclDStringClear(&buffer); if (p != name) { Tcl_DStringAppend(&buffer, name, p - name); if (p[-1] != '/') { TclDStringAppendLiteral(&buffer, "/"); } } name = Tcl_DStringAppend(&buffer, argv0, TCL_INDEX_NONE); /* * INTL: The following calls to access() and stat() should not be * converted to Tclp routines because they need to operate on native * strings directly. */ if ((access(name, X_OK) == 0) /* INTL: Native. */ && (TclOSstat(name, &statBuf) == 0) /* INTL: Native. */ && S_ISREG(statBuf.st_mode)) { goto gotName; } if (p[0] == '\0') { break; } else if (p[1] == 0) { p = "./"; } else { p++; } } TclNewObj(obj); TclSetObjNameOfExecutable(obj, NULL); goto done; /* * If the name starts with "/" then just store it */ gotName: #ifdef DJGPP if (name[1] == ':') #else if (name[0] == '/') #endif { Tcl_ExternalToUtfDStringEx(NULL, NULL, name, TCL_INDEX_NONE, TCL_ENCODING_PROFILE_TCL8, &utfName, NULL); TclSetObjNameOfExecutable(Tcl_DStringToObj(&utfName), NULL); goto done; } if (TclpGetCwd(NULL, &cwd) == NULL) { TclNewObj(obj); TclSetObjNameOfExecutable(obj, NULL); goto done; } /* * The name is relative to the current working directory. First strip off * a leading "./", if any, then add the full path name of the current * working directory. */ if ((name[0] == '.') && (name[1] == '/')) { name += 2; } Tcl_DStringInit(&nameString); Tcl_DStringAppend(&nameString, name, TCL_INDEX_NONE); Tcl_DStringFree(&buffer); Tcl_UtfToExternalDStringEx(NULL, NULL, Tcl_DStringValue(&cwd), Tcl_DStringLength(&cwd), TCL_ENCODING_PROFILE_TCL8, &buffer, NULL); if (Tcl_DStringValue(&cwd)[Tcl_DStringLength(&cwd) -1] != '/') { TclDStringAppendLiteral(&buffer, "/"); } Tcl_DStringFree(&cwd); TclDStringAppendDString(&buffer, &nameString); Tcl_DStringFree(&nameString); Tcl_ExternalToUtfDStringEx(NULL, NULL, Tcl_DStringValue(&buffer), TCL_INDEX_NONE, TCL_ENCODING_PROFILE_TCL8, &utfName, NULL); TclSetObjNameOfExecutable(Tcl_DStringToObj(&utfName), NULL); done: Tcl_DStringFree(&buffer); } #endif /* *---------------------------------------------------------------------- * * TclpMatchInDirectory -- * * This routine is used by the globbing code to search a directory for * all files which match a given pattern. * * Results: * The return value is a standard Tcl result indicating whether an error * occurred in globbing. Errors are left in interp, good results are * [lappend]ed to resultPtr (which must be a valid object). * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclpMatchInDirectory( Tcl_Interp *interp, /* Interpreter to receive errors. */ Tcl_Obj *resultPtr, /* List object to lappend results. */ Tcl_Obj *pathPtr, /* Contains path to directory to search. */ const char *pattern, /* Pattern to match against. */ Tcl_GlobTypeData *types) /* Object containing list of acceptable types. * May be NULL. In particular the directory * flag is very important. */ { const char *native; Tcl_Obj *fileNamePtr; int matchResult = 0; if (types != NULL && types->type == TCL_GLOB_TYPE_MOUNT) { /* * The native filesystem never adds mounts. */ return TCL_OK; } fileNamePtr = Tcl_FSGetTranslatedPath(interp, pathPtr); if (fileNamePtr == NULL) { return TCL_ERROR; } if (pattern == NULL || (*pattern == '\0')) { /* * Match a file directly. */ Tcl_Obj *tailPtr; const char *nativeTail; native = (const char *)Tcl_FSGetNativePath(pathPtr); tailPtr = TclPathPart(interp, pathPtr, TCL_PATH_TAIL); nativeTail = (const char *)Tcl_FSGetNativePath(tailPtr); matchResult = NativeMatchType(interp, native, nativeTail, types); if (matchResult == 1) { Tcl_ListObjAppendElement(interp, resultPtr, pathPtr); } Tcl_DecrRefCount(tailPtr); Tcl_DecrRefCount(fileNamePtr); } else { TclDIR *d; Tcl_DirEntry *entryPtr; const char *dirName; Tcl_Size dirLength, nativeDirLen; int matchHidden, matchHiddenPat; Tcl_StatBuf statBuf; Tcl_DString ds; /* native encoding of dir */ Tcl_DString dsOrig; /* utf-8 encoding of dir */ Tcl_DStringInit(&dsOrig); dirName = TclGetStringFromObj(fileNamePtr, &dirLength); Tcl_DStringAppend(&dsOrig, dirName, dirLength); /* * Make sure that the directory part of the name really is a * directory. If the directory name is "", use the name "." instead, * because some UNIX systems don't treat "" like "." automatically. * Keep the "" for use in generating file names, otherwise "glob * foo.c" would return "./foo.c". */ if (dirLength == 0) { dirName = "."; } else { dirName = Tcl_DStringValue(&dsOrig); /* * Make sure we have a trailing directory delimiter. */ if (dirName[dirLength-1] != '/') { dirName = TclDStringAppendLiteral(&dsOrig, "/"); dirLength++; } } /* * Now open the directory for reading and iterate over the contents. */ if (Tcl_UtfToExternalDStringEx(interp, NULL, dirName, TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&dsOrig); Tcl_DStringFree(&ds); Tcl_DecrRefCount(fileNamePtr); return TCL_ERROR; } native = Tcl_DStringValue(&ds); if ((TclOSstat(native, &statBuf) != 0) /* INTL: Native. */ || !S_ISDIR(statBuf.st_mode)) { Tcl_DStringFree(&dsOrig); Tcl_DStringFree(&ds); Tcl_DecrRefCount(fileNamePtr); return TCL_OK; } d = TclOSopendir(native); /* INTL: Native. */ if (d == NULL) { Tcl_DStringFree(&ds); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read directory \"%s\": %s", Tcl_DStringValue(&dsOrig), Tcl_PosixError(interp))); } Tcl_DStringFree(&dsOrig); Tcl_DecrRefCount(fileNamePtr); return TCL_ERROR; } nativeDirLen = Tcl_DStringLength(&ds); /* * Check to see if -type or the pattern requests hidden files. */ matchHiddenPat = (pattern[0] == '.') || ((pattern[0] == '\\') && (pattern[1] == '.')); matchHidden = matchHiddenPat || (types && (types->perm & TCL_GLOB_PERM_HIDDEN)); while ((entryPtr = TclOSreaddir(d)) != NULL) { /* INTL: Native. */ Tcl_DString utfDs; const char *utfname; /* * Skip this file if it doesn't agree with the hidden parameters * requested by the user (via -type or pattern). */ if (*entryPtr->d_name == '.') { if (!matchHidden) { continue; } } else { #ifdef MAC_OSX_TCL if (matchHiddenPat) { continue; } /* Also need to check HFS hidden flag in TclMacOSXMatchType. */ #else if (matchHidden) { continue; } #endif } /* * Now check to see if the file matches, according to both type * and pattern. If so, add the file to the result. */ if (Tcl_ExternalToUtfDStringEx(interp, NULL, entryPtr->d_name, TCL_INDEX_NONE, 0, &utfDs, NULL) != TCL_OK) { matchResult = -1; break; } utfname = Tcl_DStringValue(&utfDs); if (Tcl_StringCaseMatch(utfname, pattern, 0)) { int typeOk = 1; if (types != NULL) { Tcl_DStringSetLength(&ds, nativeDirLen); native = Tcl_DStringAppend(&ds, entryPtr->d_name, TCL_INDEX_NONE); matchResult = NativeMatchType(interp, native, entryPtr->d_name, types); typeOk = (matchResult == 1); } if (typeOk) { Tcl_ListObjAppendElement(interp, resultPtr, TclNewFSPathObj(pathPtr, utfname, Tcl_DStringLength(&utfDs))); } } Tcl_DStringFree(&utfDs); if (matchResult < 0) { break; } } TclOSclosedir(d); Tcl_DStringFree(&ds); Tcl_DStringFree(&dsOrig); Tcl_DecrRefCount(fileNamePtr); } if (matchResult < 0) { return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * NativeMatchType -- * * This routine is used by the globbing code to check if a file matches a * given type description. * * Results: * The return value is 1, 0 or -1 indicating whether the file matches the * given criteria, does not match them, or an error occurred (in which * case an error is left in interp). * * Side effects: * None. * *---------------------------------------------------------------------- */ static int NativeMatchType( Tcl_Interp *interp, /* Interpreter to receive errors. */ const char *nativeEntry, /* Native path to check. */ const char *nativeName, /* Native filename to check. */ Tcl_GlobTypeData *types) /* Type description to match against. */ { Tcl_StatBuf buf; if (types == NULL) { /* * Simply check for the file's existence, but do it with lstat, in * case it is a link to a file which doesn't exist (since that case * would not show up if we used 'access' or 'stat') */ if (TclOSlstat(nativeEntry, &buf) != 0) { return 0; } return 1; } if (types->perm != 0) { if (TclOSstat(nativeEntry, &buf) != 0) { /* * Either the file has disappeared between the 'readdir' call and * the 'stat' call, or the file is a link to a file which doesn't * exist (which we could ascertain with lstat), or there is some * other strange problem. In all these cases, we define this to * mean the file does not match any defined permission, and * therefore it is not added to the list of files to return. */ return 0; } /* * readonly means that there are NO write permissions (even for user), * but execute is OK for anybody OR that the user immutable flag is * set (where supported). */ if (((types->perm & TCL_GLOB_PERM_RONLY) && #if defined(HAVE_CHFLAGS) && defined(UF_IMMUTABLE) !(buf.st_flags & UF_IMMUTABLE) && #endif (buf.st_mode & (S_IWOTH|S_IWGRP|S_IWUSR))) || ((types->perm & TCL_GLOB_PERM_R) && (access(nativeEntry, R_OK) != 0)) || ((types->perm & TCL_GLOB_PERM_W) && (access(nativeEntry, W_OK) != 0)) || ((types->perm & TCL_GLOB_PERM_X) && (access(nativeEntry, X_OK) != 0)) #ifndef MAC_OSX_TCL || ((types->perm & TCL_GLOB_PERM_HIDDEN) && (*nativeName != '.')) #endif /* MAC_OSX_TCL */ ) { return 0; } } if (types->type != 0) { if (types->perm == 0) { /* * We haven't yet done a stat on the file. */ if (TclOSstat(nativeEntry, &buf) != 0) { /* * Posix error occurred. The only ok case is if this is a link * to a nonexistent file, and the user did 'glob -l'. So we * check that here: */ if ((types->type & TCL_GLOB_TYPE_LINK) && (TclOSlstat(nativeEntry, &buf) == 0) && S_ISLNK(buf.st_mode)) { return 1; } return 0; } } /* * In order bcdpsfl as in 'find -t' */ if ( ((types->type & TCL_GLOB_TYPE_BLOCK)&& S_ISBLK(buf.st_mode)) || ((types->type & TCL_GLOB_TYPE_CHAR) && S_ISCHR(buf.st_mode)) || ((types->type & TCL_GLOB_TYPE_DIR) && S_ISDIR(buf.st_mode)) || ((types->type & TCL_GLOB_TYPE_PIPE) && S_ISFIFO(buf.st_mode))|| #ifdef S_ISSOCK ((types->type & TCL_GLOB_TYPE_SOCK) && S_ISSOCK(buf.st_mode))|| #endif /* S_ISSOCK */ ((types->type & TCL_GLOB_TYPE_FILE) && S_ISREG(buf.st_mode))) { /* * Do nothing - this file is ok. */ } else { #ifdef S_ISLNK if ((types->type & TCL_GLOB_TYPE_LINK) && (TclOSlstat(nativeEntry, &buf) == 0) && S_ISLNK(buf.st_mode)) { goto filetypeOK; } #endif /* S_ISLNK */ return 0; } } filetypeOK: /* * If we're on OSX, we also have to worry about matching the file creator * code (if specified). Do that now. */ #ifdef MAC_OSX_TCL if (types->macType != NULL || types->macCreator != NULL || (types->perm & TCL_GLOB_PERM_HIDDEN)) { int matchResult; if (types->perm == 0 && types->type == 0) { /* * We haven't yet done a stat on the file. */ if (TclOSstat(nativeEntry, &buf) != 0) { return 0; } } matchResult = TclMacOSXMatchType(interp, nativeEntry, nativeName, &buf, types); if (matchResult != 1) { return matchResult; } } #else (void)interp; #endif /* MAC_OSX_TCL */ return 1; } /* *--------------------------------------------------------------------------- * * TclpGetUserHome -- * * This function takes the specified user name and finds their home * directory. * * Results: * The result is a pointer to a string specifying the user's home * directory, or NULL if the user's home directory could not be * determined. Storage for the result string is allocated in bufferPtr; * the caller must call Tcl_DStringFree() when the result is no longer * needed. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * TclpGetUserHome( const char *name, /* User name for desired home directory. */ Tcl_DString *bufferPtr) /* Uninitialized or free DString filled with * name of user's home directory. */ { struct passwd *pwPtr; Tcl_DString ds; const char *native; if (Tcl_UtfToExternalDStringEx(NULL, NULL, name, TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return NULL; } native = Tcl_DStringValue(&ds); pwPtr = TclpGetPwNam(native); /* INTL: Native. */ Tcl_DStringFree(&ds); if (pwPtr == NULL) { return NULL; } if (Tcl_ExternalToUtfDStringEx(NULL, NULL, pwPtr->pw_dir, TCL_INDEX_NONE, 0, bufferPtr, NULL) != TCL_OK) { return NULL; } else { return Tcl_DStringValue(bufferPtr); } } /* *--------------------------------------------------------------------------- * * TclpObjAccess -- * * This function replaces the library version of access(). * * Results: * See access() documentation. * * Side effects: * See access() documentation. * *--------------------------------------------------------------------------- */ int TclpObjAccess( Tcl_Obj *pathPtr, /* Path of file to access */ int mode) /* Permission setting. */ { const char *path = (const char *)Tcl_FSGetNativePath(pathPtr); if (path == NULL) { return -1; } return access(path, mode); } /* *--------------------------------------------------------------------------- * * TclpObjChdir -- * * This function replaces the library version of chdir(). * * Results: * See chdir() documentation. * * Side effects: * See chdir() documentation. * *--------------------------------------------------------------------------- */ int TclpObjChdir( Tcl_Obj *pathPtr) /* Path to new working directory */ { const char *path = (const char *)Tcl_FSGetNativePath(pathPtr); if (path == NULL) { return -1; } return chdir(path); } /* *---------------------------------------------------------------------- * * TclpObjLstat -- * * This function replaces the library version of lstat(). * * Results: * See lstat() documentation. * * Side effects: * See lstat() documentation. * *---------------------------------------------------------------------- */ int TclpObjLstat( Tcl_Obj *pathPtr, /* Path of file to stat */ Tcl_StatBuf *bufPtr) /* Filled with results of stat call. */ { return TclOSlstat((const char *)Tcl_FSGetNativePath(pathPtr), bufPtr); } /* *--------------------------------------------------------------------------- * * TclpGetNativeCwd -- * * This function replaces the library version of getcwd(). * * Results: * The input and output are filesystem paths in native form. The result * is either the given clientData, if the working directory hasn't * changed, or a new clientData (owned by our caller), giving the new * native path, or NULL if the current directory could not be determined. * If NULL is returned, the caller can examine the standard Posix error * codes to determine the cause of the problem. * * Side effects: * None. * *---------------------------------------------------------------------- */ void * TclpGetNativeCwd( void *clientData) { char buffer[MAXPATHLEN+1]; #ifdef USEGETWD if (getwd(buffer) == NULL) { /* INTL: Native. */ return NULL; } #else if (getcwd(buffer, MAXPATHLEN+1) == NULL) { /* INTL: Native. */ return NULL; } #endif /* USEGETWD */ if ((clientData == NULL) || strcmp(buffer, (const char *) clientData)) { char *newCd = (char *)Tcl_Alloc(strlen(buffer) + 1); strcpy(newCd, buffer); return newCd; } /* * No change to pwd. */ return clientData; } /* *--------------------------------------------------------------------------- * * TclpGetCwd -- * * This function replaces the library version of getcwd(). (Obsolete * function, only retained for old extensions which may call it * directly). * * Results: * The result is a pointer to a string specifying the current directory, * or NULL if the current directory could not be determined. If NULL is * returned, an error message is left in the interp's result. Storage for * the result string is allocated in bufferPtr; the caller must call * Tcl_DStringFree() when the result is no longer needed. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * TclpGetCwd( Tcl_Interp *interp, /* If non-NULL, used for error reporting. */ Tcl_DString *bufferPtr) /* Uninitialized or free DString filled with * name of current directory. */ { char buffer[MAXPATHLEN+1]; #ifdef USEGETWD if (getwd(buffer) == NULL) /* INTL: Native. */ #else if (getcwd(buffer, MAXPATHLEN+1) == NULL) /* INTL: Native. */ #endif /* USEGETWD */ { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error getting working directory name: %s", Tcl_PosixError(interp))); } return NULL; } if (Tcl_ExternalToUtfDStringEx(interp, NULL, buffer, TCL_INDEX_NONE, 0, bufferPtr, NULL) != TCL_OK) { return NULL; } return Tcl_DStringValue(bufferPtr); } /* *--------------------------------------------------------------------------- * * TclpReadlink -- * * This function replaces the library version of readlink(). * * Results: * The result is a pointer to a string specifying the contents of the * symbolic link given by 'path', or NULL if the symbolic link could not * be read. Storage for the result string is allocated in bufferPtr; the * caller must call Tcl_DStringFree() when the result is no longer * needed. * * Side effects: * See readlink() documentation. * *--------------------------------------------------------------------------- */ char * TclpReadlink( const char *path, /* Path of file to readlink (UTF-8). */ Tcl_DString *linkPtr) /* Uninitialized or free DString filled with * contents of link (UTF-8). */ { #ifndef DJGPP char link[MAXPATHLEN]; Tcl_Size length; const char *native; Tcl_DString ds; if (Tcl_UtfToExternalDStringEx(NULL, NULL, path, TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return NULL; } native = Tcl_DStringValue(&ds); length = readlink(native, link, sizeof(link)); /* INTL: Native. */ Tcl_DStringFree(&ds); if (length < 0) { return NULL; } if (Tcl_ExternalToUtfDStringEx(NULL, NULL, link, length, 0, linkPtr, NULL) == TCL_OK) { return Tcl_DStringValue(linkPtr); } #endif /* !DJGPP */ return NULL; } /* *---------------------------------------------------------------------- * * TclpObjStat -- * * This function replaces the library version of stat(). * * Results: * See stat() documentation. * * Side effects: * See stat() documentation. * *---------------------------------------------------------------------- */ int TclpObjStat( Tcl_Obj *pathPtr, /* Path of file to stat */ Tcl_StatBuf *bufPtr) /* Filled with results of stat call. */ { const char *path = (const char *)Tcl_FSGetNativePath(pathPtr); if (path == NULL) { return -1; } return TclOSstat(path, bufPtr); } #ifdef S_IFLNK Tcl_Obj * TclpObjLink( Tcl_Obj *pathPtr, Tcl_Obj *toPtr, int linkAction) { if (toPtr != NULL) { const char *src = (const char *)Tcl_FSGetNativePath(pathPtr); const char *target = NULL; if (src == NULL) { return NULL; } /* * If we're making a symbolic link and the path is relative, then we * must check whether it exists _relative_ to the directory in which * the src is found (not relative to the current cwd which is just not * relevant in this case). * * If we're making a hard link, then a relative path is just converted * to absolute relative to the cwd. */ if ((linkAction & TCL_CREATE_SYMBOLIC_LINK) && (Tcl_FSGetPathType(toPtr) == TCL_PATH_RELATIVE)) { Tcl_Obj *dirPtr, *absPtr; dirPtr = TclPathPart(NULL, pathPtr, TCL_PATH_DIRNAME); if (dirPtr == NULL) { return NULL; } absPtr = Tcl_FSJoinToPath(dirPtr, 1, &toPtr); Tcl_IncrRefCount(absPtr); if (Tcl_FSAccess(absPtr, F_OK) == -1) { Tcl_DecrRefCount(absPtr); Tcl_DecrRefCount(dirPtr); /* * Target doesn't exist. */ errno = ENOENT; return NULL; } /* * Target exists; we'll construct the relative path we want below. */ Tcl_DecrRefCount(absPtr); Tcl_DecrRefCount(dirPtr); } else { target = (const char*)Tcl_FSGetNativePath(toPtr); if (target == NULL) { return NULL; } if (access(target, F_OK) == -1) { /* * Target doesn't exist. */ errno = ENOENT; return NULL; } } if (access(src, F_OK) != -1) { /* * Src exists. */ errno = EEXIST; return NULL; } /* * Check symbolic link flag first, since we prefer to create these. */ if (linkAction & TCL_CREATE_SYMBOLIC_LINK) { Tcl_DString ds; Tcl_Obj *transPtr; Tcl_Size length; /* * Now we don't want to link to the absolute, normalized path. * Relative links are quite acceptable (but links to ~user are not * -- these must be expanded first). */ transPtr = Tcl_FSGetTranslatedPath(NULL, toPtr); if (transPtr == NULL) { return NULL; } target = TclGetStringFromObj(transPtr, &length); if (Tcl_UtfToExternalDStringEx(NULL, NULL, target, length, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return NULL; } target = Tcl_DStringValue(&ds); Tcl_DecrRefCount(transPtr); if (symlink(target, src) != 0) { toPtr = NULL; } Tcl_DStringFree(&ds); } else if (linkAction & TCL_CREATE_HARD_LINK) { if (link(target, src) != 0) { return NULL; } } else { errno = ENODEV; return NULL; } return toPtr; } else { Tcl_Obj *linkPtr = NULL; char link[MAXPATHLEN]; ssize_t length; Tcl_DString ds; Tcl_Obj *transPtr; transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr); if (transPtr == NULL) { return NULL; } Tcl_DecrRefCount(transPtr); length = readlink((const char *)Tcl_FSGetNativePath(pathPtr), link, sizeof(link)); if (length < 0) { return NULL; } if (Tcl_ExternalToUtfDStringEx(NULL, NULL, link, (size_t)length, 0, &ds, NULL) != TCL_OK) { return NULL; } linkPtr = Tcl_DStringToObj(&ds); Tcl_IncrRefCount(linkPtr); return linkPtr; } } #endif /* S_IFLNK */ /* *--------------------------------------------------------------------------- * * TclpFilesystemPathType -- * * This function is part of the native filesystem support, and returns * the path type of the given path. Right now it simply returns NULL. In * the future it could return specific path types, like 'nfs', 'samba', * 'FAT32', etc. * * Results: * NULL at present. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Obj * TclpFilesystemPathType( TCL_UNUSED(Tcl_Obj *)) { /* * All native paths are of the same type. */ return NULL; } /* *--------------------------------------------------------------------------- * * TclpNativeToNormalized -- * * Convert native format to a normalized path object, with refCount of * zero. * * Currently assumes all native paths are actually normalized already, so * if the path given is not normalized this will actually just convert to * a valid string path, but not necessarily a normalized one. * * Results: * A valid normalized path. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Obj * TclpNativeToNormalized( void *clientData) { Tcl_DString ds; Tcl_ExternalToUtfDStringEx(NULL, NULL, (const char *) clientData, TCL_INDEX_NONE, TCL_ENCODING_PROFILE_TCL8, &ds, NULL); return Tcl_DStringToObj(&ds); } /* *--------------------------------------------------------------------------- * * TclNativeCreateNativeRep -- * * Create a native representation for the given path. * * Results: * The nativePath representation. * * Side effects: * Memory will be allocated. The path may need to be normalized. * *--------------------------------------------------------------------------- */ void * TclNativeCreateNativeRep( Tcl_Obj *pathPtr) { char *nativePathPtr; const char *str; Tcl_DString ds; Tcl_Obj *validPathPtr; Tcl_Size len; if (TclFSCwdIsNative() || Tcl_FSGetPathType(pathPtr) == TCL_PATH_ABSOLUTE) { /* * The cwd is native (or path is absolute), use the translated path * without worrying about normalization (this will also usually be * shorter so the utf-to-external conversion will be somewhat faster). */ validPathPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr); if (validPathPtr == NULL) { return NULL; } } else { /* * Make sure the normalized path is set. */ validPathPtr = Tcl_FSGetNormalizedPath(NULL, pathPtr); if (validPathPtr == NULL) { return NULL; } Tcl_IncrRefCount(validPathPtr); } str = TclGetStringFromObj(validPathPtr, &len); if (Tcl_UtfToExternalDStringEx(NULL, NULL, str, len, 0, &ds, NULL) != TCL_OK) { Tcl_DecrRefCount(validPathPtr); Tcl_DStringFree(&ds); return NULL; } len = Tcl_DStringLength(&ds) + sizeof(char); if (strlen(Tcl_DStringValue(&ds)) < len - sizeof(char)) { /* See bug [3118489]: NUL in filenames */ Tcl_DecrRefCount(validPathPtr); Tcl_DStringFree(&ds); return NULL; } Tcl_DecrRefCount(validPathPtr); nativePathPtr = (char *)Tcl_Alloc(len); memcpy(nativePathPtr, Tcl_DStringValue(&ds), len); Tcl_DStringFree(&ds); return nativePathPtr; } /* *--------------------------------------------------------------------------- * * TclNativeDupInternalRep -- * * Duplicate the native representation. * * Results: * The copied native representation, or NULL if it is not possible to * copy the representation. * * Side effects: * Memory will be allocated for the copy. * *--------------------------------------------------------------------------- */ void * TclNativeDupInternalRep( void *clientData) { char *copy; size_t len; if (clientData == NULL) { return NULL; } /* * ASCII representation when running on Unix. */ len = (strlen((const char*) clientData) + 1) * sizeof(char); copy = (char *)Tcl_Alloc(len); memcpy(copy, clientData, len); return copy; } /* *--------------------------------------------------------------------------- * * TclpUtime -- * * Set the modification date for a file. * * Results: * 0 on success, -1 on error. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int TclpUtime( Tcl_Obj *pathPtr, /* File to modify */ struct utimbuf *tval) /* New modification date structure */ { return utime((const char *)Tcl_FSGetNativePath(pathPtr), tval); } #ifdef __CYGWIN__ int TclOSfstat( int fd, void *cygstat) { struct stat buf; Tcl_StatBuf *statBuf = (Tcl_StatBuf *)cygstat; int result = fstat(fd, &buf); statBuf->st_mode = buf.st_mode; statBuf->st_ino = buf.st_ino; statBuf->st_dev = buf.st_dev; statBuf->st_rdev = buf.st_rdev; statBuf->st_nlink = buf.st_nlink; statBuf->st_uid = buf.st_uid; statBuf->st_gid = buf.st_gid; statBuf->st_size = buf.st_size; statBuf->st_atime = buf.st_atime; statBuf->st_mtime = buf.st_mtime; statBuf->st_ctime = buf.st_ctime; return result; } int TclOSstat( const char *name, void *cygstat) { struct stat buf; Tcl_StatBuf *statBuf = (Tcl_StatBuf *)cygstat; int result = stat(name, &buf); statBuf->st_mode = buf.st_mode; statBuf->st_ino = buf.st_ino; statBuf->st_dev = buf.st_dev; statBuf->st_rdev = buf.st_rdev; statBuf->st_nlink = buf.st_nlink; statBuf->st_uid = buf.st_uid; statBuf->st_gid = buf.st_gid; statBuf->st_size = buf.st_size; statBuf->st_atime = buf.st_atime; statBuf->st_mtime = buf.st_mtime; statBuf->st_ctime = buf.st_ctime; return result; } int TclOSlstat( const char *name, void *cygstat) { struct stat buf; Tcl_StatBuf *statBuf = (Tcl_StatBuf *)cygstat; int result = lstat(name, &buf); statBuf->st_mode = buf.st_mode; statBuf->st_ino = buf.st_ino; statBuf->st_dev = buf.st_dev; statBuf->st_rdev = buf.st_rdev; statBuf->st_nlink = buf.st_nlink; statBuf->st_uid = buf.st_uid; statBuf->st_gid = buf.st_gid; statBuf->st_size = buf.st_size; statBuf->st_atime = buf.st_atime; statBuf->st_mtime = buf.st_mtime; statBuf->st_ctime = buf.st_ctime; return result; } #endif /* CYGWIN */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclUnixInit.c0000644000175000017500000007214314726623136015166 0ustar sergeisergei/* * tclUnixInit.c -- * * Contains the Unix-specific interpreter initialization functions. * * Copyright © 1995-1997 Sun Microsystems, Inc. * Copyright © 1999 Scriptics Corporation. * All rights reserved. */ #include "tclInt.h" #ifdef HAVE_LANGINFO # include #endif #include #if defined(__FreeBSD__) && defined(__GNUC__) # include #endif #if defined(__bsdi__) # include # if _BSDI_VERSION > 199501 # include # endif #endif #ifdef __CYGWIN__ #ifdef __cplusplus extern "C" { #endif #ifdef __clang__ #pragma clang diagnostic ignored "-Wignored-attributes" #endif DLLIMPORT extern __stdcall unsigned char GetVersionExW(void *); DLLIMPORT extern __stdcall void *GetModuleHandleW(const void *); DLLIMPORT extern __stdcall void FreeLibrary(void *); DLLIMPORT extern __stdcall void *GetProcAddress(void *, const char *); DLLIMPORT extern __stdcall void GetSystemInfo(void *); #ifdef __cplusplus } #endif #define NUMPROCESSORS 15 static const char *const processors[NUMPROCESSORS] = { "i686", "mips", "alpha", "ppc", "shx", "arm", "ia64", "alpha64", "msil", "x86_64", "ia32_on_win64", "neutral", "arm64", "arm32_on_win64", "ia32_on_arm64" }; typedef struct { union { unsigned int dwOemId; struct { int wProcessorArchitecture; int wReserved; }; }; unsigned int dwPageSize; void *lpMinimumApplicationAddress; void *lpMaximumApplicationAddress; void *dwActiveProcessorMask; unsigned int dwNumberOfProcessors; unsigned int dwProcessorType; unsigned int dwAllocationGranularity; int wProcessorLevel; int wProcessorRevision; } SYSTEM_INFO; typedef struct { unsigned int dwOSVersionInfoSize; unsigned int dwMajorVersion; unsigned int dwMinorVersion; unsigned int dwBuildNumber; unsigned int dwPlatformId; wchar_t szCSDVersion[128]; } OSVERSIONINFOW; #endif #ifdef HAVE_COREFOUNDATION #include #endif /* * Tcl tries to use standard and homebrew methods to guess the right encoding * on the platform. However, there is always a final fallback, and this value * is it. Make sure it is a real Tcl encoding. */ #ifndef TCL_DEFAULT_ENCODING #define TCL_DEFAULT_ENCODING "utf-8" #endif /* * Default directory in which to look for Tcl library scripts. The symbol is * defined by Makefile. */ static const char defaultLibraryDir[] = TCL_LIBRARY; /* * Directory in which to look for packages (each package is typically * installed as a subdirectory of this directory). The symbol is defined by * Makefile. */ static const char pkgPath[] = TCL_PACKAGE_PATH; /* * The following table is used to map from Unix locale strings to encoding * files. If HAVE_LANGINFO is defined, then this is a fallback table when the * result from nl_langinfo isn't a recognized encoding. Otherwise this is the * first list checked for a mapping from env encoding to Tcl encoding name. */ typedef struct { const char *lang; const char *encoding; } LocaleTable; /* * The table below is sorted for the sake of doing binary searches on it. The * indenting reflects different categories of data. The leftmost data * represent the encoding names directly implemented by data files in Tcl's * default encoding directory. Indented by one TAB are the encoding names that * are common alternative spellings. Indented by two TABs are the accumulated * "bug fixes" that have been added to deal with the wide variability seen * among existing platforms. */ static const LocaleTable localeTable[] = { {"", "iso8859-1"}, {"ansi-1251", "cp1251"}, {"ansi_x3.4-1968", "iso8859-1"}, {"ascii", "ascii"}, {"big5", "big5"}, {"cp1250", "cp1250"}, {"cp1251", "cp1251"}, {"cp1252", "cp1252"}, {"cp1253", "cp1253"}, {"cp1254", "cp1254"}, {"cp1255", "cp1255"}, {"cp1256", "cp1256"}, {"cp1257", "cp1257"}, {"cp1258", "cp1258"}, {"cp437", "cp437"}, {"cp737", "cp737"}, {"cp775", "cp775"}, {"cp850", "cp850"}, {"cp852", "cp852"}, {"cp855", "cp855"}, {"cp857", "cp857"}, {"cp860", "cp860"}, {"cp861", "cp861"}, {"cp862", "cp862"}, {"cp863", "cp863"}, {"cp864", "cp864"}, {"cp865", "cp865"}, {"cp866", "cp866"}, {"cp869", "cp869"}, {"cp874", "cp874"}, {"cp932", "cp932"}, {"cp936", "cp936"}, {"cp949", "cp949"}, {"cp950", "cp950"}, {"dingbats", "dingbats"}, {"ebcdic", "ebcdic"}, {"euc-cn", "euc-cn"}, {"euc-jp", "euc-jp"}, {"euc-kr", "euc-kr"}, {"eucjp", "euc-jp"}, {"euckr", "euc-kr"}, {"euctw", "euc-cn"}, {"gb12345", "gb12345"}, {"gb1988", "gb1988"}, {"gb2312", "gb2312"}, {"gb2312-1980", "gb2312"}, {"gb2312-raw", "gb2312-raw"}, {"greek8", "cp869"}, {"ibm1250", "cp1250"}, {"ibm1251", "cp1251"}, {"ibm1252", "cp1252"}, {"ibm1253", "cp1253"}, {"ibm1254", "cp1254"}, {"ibm1255", "cp1255"}, {"ibm1256", "cp1256"}, {"ibm1257", "cp1257"}, {"ibm1258", "cp1258"}, {"ibm437", "cp437"}, {"ibm737", "cp737"}, {"ibm775", "cp775"}, {"ibm850", "cp850"}, {"ibm852", "cp852"}, {"ibm855", "cp855"}, {"ibm857", "cp857"}, {"ibm860", "cp860"}, {"ibm861", "cp861"}, {"ibm862", "cp862"}, {"ibm863", "cp863"}, {"ibm864", "cp864"}, {"ibm865", "cp865"}, {"ibm866", "cp866"}, {"ibm869", "cp869"}, {"ibm874", "cp874"}, {"ibm932", "cp932"}, {"ibm936", "cp936"}, {"ibm949", "cp949"}, {"ibm950", "cp950"}, {"iso-2022", "iso2022"}, {"iso-2022-jp", "iso2022-jp"}, {"iso-2022-kr", "iso2022-kr"}, {"iso-8859-1", "iso8859-1"}, {"iso-8859-10", "iso8859-10"}, {"iso-8859-13", "iso8859-13"}, {"iso-8859-14", "iso8859-14"}, {"iso-8859-15", "iso8859-15"}, {"iso-8859-16", "iso8859-16"}, {"iso-8859-2", "iso8859-2"}, {"iso-8859-3", "iso8859-3"}, {"iso-8859-4", "iso8859-4"}, {"iso-8859-5", "iso8859-5"}, {"iso-8859-6", "iso8859-6"}, {"iso-8859-7", "iso8859-7"}, {"iso-8859-8", "iso8859-8"}, {"iso-8859-9", "iso8859-9"}, {"iso2022", "iso2022"}, {"iso2022-jp", "iso2022-jp"}, {"iso2022-kr", "iso2022-kr"}, {"iso8859-1", "iso8859-1"}, {"iso8859-10", "iso8859-10"}, {"iso8859-13", "iso8859-13"}, {"iso8859-14", "iso8859-14"}, {"iso8859-15", "iso8859-15"}, {"iso8859-16", "iso8859-16"}, {"iso8859-2", "iso8859-2"}, {"iso8859-3", "iso8859-3"}, {"iso8859-4", "iso8859-4"}, {"iso8859-5", "iso8859-5"}, {"iso8859-6", "iso8859-6"}, {"iso8859-7", "iso8859-7"}, {"iso8859-8", "iso8859-8"}, {"iso8859-9", "iso8859-9"}, {"iso88591", "iso8859-1"}, {"iso885915", "iso8859-15"}, {"iso88592", "iso8859-2"}, {"iso88595", "iso8859-5"}, {"iso88596", "iso8859-6"}, {"iso88597", "iso8859-7"}, {"iso88598", "iso8859-8"}, {"iso88599", "iso8859-9"}, #ifdef hpux {"ja", "shiftjis"}, #else {"ja", "euc-jp"}, #endif {"ja_jp", "euc-jp"}, {"ja_jp.euc", "euc-jp"}, {"ja_jp.eucjp", "euc-jp"}, {"ja_jp.jis", "iso2022-jp"}, {"ja_jp.mscode", "shiftjis"}, {"ja_jp.sjis", "shiftjis"}, {"ja_jp.ujis", "euc-jp"}, {"japan", "euc-jp"}, #ifdef hpux {"japanese", "shiftjis"}, #else {"japanese", "euc-jp"}, #endif {"japanese-sjis", "shiftjis"}, {"japanese-ujis", "euc-jp"}, {"japanese.euc", "euc-jp"}, {"japanese.sjis", "shiftjis"}, {"jis0201", "jis0201"}, {"jis0208", "jis0208"}, {"jis0212", "jis0212"}, {"jp_jp", "shiftjis"}, {"ko", "euc-kr"}, {"ko_kr", "euc-kr"}, {"ko_kr.euc", "euc-kr"}, {"ko_kw.euckw", "euc-kr"}, {"koi8-r", "koi8-r"}, {"koi8-u", "koi8-u"}, {"korean", "euc-kr"}, {"ksc5601", "ksc5601"}, {"maccenteuro", "macCentEuro"}, {"maccroatian", "macCroatian"}, {"maccyrillic", "macCyrillic"}, {"macdingbats", "macDingbats"}, {"macgreek", "macGreek"}, {"maciceland", "macIceland"}, {"macjapan", "macJapan"}, {"macroman", "macRoman"}, {"macromania", "macRomania"}, {"macthai", "macThai"}, {"macturkish", "macTurkish"}, {"macukraine", "macUkraine"}, {"roman8", "iso8859-1"}, {"ru", "iso8859-5"}, {"ru_ru", "iso8859-5"}, {"ru_su", "iso8859-5"}, {"shiftjis", "shiftjis"}, {"sjis", "shiftjis"}, {"symbol", "symbol"}, {"tis-620", "tis-620"}, {"tis620", "tis-620"}, {"turkish8", "cp857"}, {"utf8", "utf-8"}, {"zh", "cp936"}, {"zh_cn.gb2312", "euc-cn"}, {"zh_cn.gbk", "euc-cn"}, {"zh_cz.gb2312", "euc-cn"}, {"zh_tw", "euc-tw"}, {"zh_tw.big5", "big5"}, }; #ifdef HAVE_COREFOUNDATION static int MacOSXGetLibraryPath(Tcl_Interp *interp, int maxPathLen, char *tclLibPath); #endif /* HAVE_COREFOUNDATION */ /* *--------------------------------------------------------------------------- * * TclpInitPlatform -- * * Initialize all the platform-dependent things like signals and * floating-point error handling. * * Called at process initialization time. * * Results: * None. * * Side effects: * None. * *--------------------------------------------------------------------------- */ void TclpInitPlatform(void) { #ifdef DJGPP tclPlatform = TCL_PLATFORM_WINDOWS; #else tclPlatform = TCL_PLATFORM_UNIX; #endif /* * Make sure, that the standard FDs exist. [Bug 772288] */ if (TclOSseek(0, 0, SEEK_CUR) == -1 && errno == EBADF) { open("/dev/null", O_RDONLY); } if (TclOSseek(1, 0, SEEK_CUR) == -1 && errno == EBADF) { open("/dev/null", O_WRONLY); } if (TclOSseek(2, 0, SEEK_CUR) == -1 && errno == EBADF) { open("/dev/null", O_WRONLY); } /* * The code below causes SIGPIPE (broken pipe) errors to be ignored. This * is needed so that Tcl processes don't die if they create child * processes (e.g. using "exec" or "open") that terminate prematurely. * The signal handler is only set up when the first interpreter is * created; after this the application can override the handler with a * different one of its own, if it wants. */ #ifdef SIGPIPE (void) signal(SIGPIPE, SIG_IGN); #endif /* SIGPIPE */ #if defined(__FreeBSD__) && defined(__GNUC__) (void) fpsetmask(0L); #endif #if defined(__bsdi__) && (_BSDI_VERSION > 199501) /* * Find local symbols. Don't report an error if we fail. */ (void) dlopen(NULL, RTLD_NOW); /* INTL: Native. */ #endif /* * Initialize the C library's locale subsystem. This is required for input * methods to work properly on X11. We only do this for LC_CTYPE because * that's the necessary one, and we don't want to affect LC_TIME here. * The side effect of setting the default locale should be to load any * locale specific modules that are needed by X. [BUG: 5422 3345 4236 2522 * 2521]. */ setlocale(LC_CTYPE, ""); /* * In case the initial locale is not "C", ensure that the numeric * processing is done in "C" locale regardless. This is needed because Tcl * relies on routines like strtol/strtoul, but should not have locale dependent * behavior. */ setlocale(LC_NUMERIC, "C"); } /* *--------------------------------------------------------------------------- * * TclpInitLibraryPath -- * * This is the fallback routine that sets the library path if the * application has not set one by the first time it is needed. * * Results: * None. * * Side effects: * Sets the library path to an initial value. * *------------------------------------------------------------------------- */ void TclpInitLibraryPath( char **valuePtr, size_t *lengthPtr, Tcl_Encoding *encodingPtr) { #define LIBRARY_SIZE 32 Tcl_Obj *pathPtr, *objPtr; const char *str; Tcl_DString buffer; TclNewObj(pathPtr); /* * Look for the library relative to the TCL_LIBRARY env variable. If the * last dirname in the TCL_LIBRARY path does not match the last dirname in * the installLib variable, use the last dir name of installLib in * addition to the original TCL_LIBRARY path. */ str = getenv("TCL_LIBRARY"); /* INTL: Native. */ Tcl_ExternalToUtfDStringEx(NULL, NULL, str, TCL_INDEX_NONE, TCL_ENCODING_PROFILE_TCL8, &buffer, NULL); str = Tcl_DStringValue(&buffer); if ((str != NULL) && (str[0] != '\0')) { Tcl_DString ds; Tcl_Size pathc; const char **pathv; char installLib[LIBRARY_SIZE]; Tcl_DStringInit(&ds); /* * Initialize the substrings used when locating an executable. The * installLib variable computes the path as though the executable is * installed. */ snprintf(installLib, sizeof(installLib), "lib/tcl%s", TCL_VERSION); /* * If TCL_LIBRARY is set, search there. */ Tcl_ListObjAppendElement(NULL, pathPtr, Tcl_NewStringObj(str, TCL_INDEX_NONE)); Tcl_SplitPath(str, &pathc, &pathv); if ((pathc > 0) && (strcasecmp(installLib + 4, pathv[pathc-1]) != 0)) { /* * If TCL_LIBRARY is set but refers to a different tcl * installation than the current version, try fiddling with the * specified directory to make it refer to this installation by * removing the old "tclX.Y" and substituting the current version * string. */ pathv[pathc - 1] = installLib + 4; str = Tcl_JoinPath(pathc, pathv, &ds); Tcl_ListObjAppendElement(NULL, pathPtr, Tcl_DStringToObj(&ds)); } Tcl_Free(pathv); } /* * Finally, look for the library relative to the compiled-in path. This is * needed when users install Tcl with an exec-prefix that is different * from the prefix. */ { #ifdef HAVE_COREFOUNDATION char tclLibPath[MAXPATHLEN + 1]; if (MacOSXGetLibraryPath(NULL, MAXPATHLEN, tclLibPath) == TCL_OK) { str = tclLibPath; } else #endif /* HAVE_COREFOUNDATION */ { /* * TODO: Pull this value from the TIP 59 table. */ str = defaultLibraryDir; } if (str[0] != '\0') { objPtr = Tcl_NewStringObj(str, TCL_INDEX_NONE); Tcl_ListObjAppendElement(NULL, pathPtr, objPtr); } } Tcl_DStringFree(&buffer); *encodingPtr = Tcl_GetEncoding(NULL, NULL); /* * Note lengthPtr is (size_t *) which is unsigned so cannot * pass directly to Tcl_GetStringFromObj. * TODO - why is the type size_t anyways? */ Tcl_Size length; str = TclGetStringFromObj(pathPtr, &length); *lengthPtr = length; *valuePtr = (char *)Tcl_Alloc(length + 1); memcpy(*valuePtr, str, length + 1); Tcl_DecrRefCount(pathPtr); } /* *--------------------------------------------------------------------------- * * TclpSetInitialEncodings -- * * Based on the locale, determine the encoding of the operating system * and the default encoding for newly opened files. * * Called at process initialization time, and part way through startup, * we verify that the initial encodings were correctly setup. Depending * on Tcl's environment, there may not have been enough information first * time through (above). * * Results: * None. * * Side effects: * The Tcl library path is converted from native encoding to UTF-8, on * the first call, and the encodings may be changed on first or second * call. * *--------------------------------------------------------------------------- */ void TclpSetInitialEncodings(void) { Tcl_DString encodingName; Tcl_SetSystemEncoding(NULL, Tcl_GetEncodingNameFromEnvironment(&encodingName)); Tcl_DStringFree(&encodingName); } static const char * SearchKnownEncodings( const char *encoding) { int left = 0; int right = sizeof(localeTable)/sizeof(LocaleTable); /* Here, search for i in the interval left <= i < right. */ while (left < right) { int test = (left + right)/2; int code = strcmp(localeTable[test].lang, encoding); if (code == 0) { /* Found it at i == test. */ return localeTable[test].encoding; } if (code < 0) { /* Restrict the search to the interval test < i < right. */ left = test+1; } else { /* Restrict the search to the interval left <= i < test. */ right = test; } } return NULL; } const char * Tcl_GetEncodingNameFromEnvironment( Tcl_DString *bufPtr) { const char *encoding; const char *knownEncoding; Tcl_DStringInit(bufPtr); /* * Determine the current encoding from the LC_* or LANG environment * variables. We previously used setlocale() to determine the locale, but * this does not work on some systems (e.g. Linux/i386 RH 5.0). */ #ifdef HAVE_LANGINFO if ( #ifdef WEAK_IMPORT_NL_LANGINFO nl_langinfo != NULL && #endif setlocale(LC_CTYPE, "") != NULL) { Tcl_DString ds; /* * Use a DString so we can modify case. */ Tcl_DStringInit(&ds); encoding = Tcl_DStringAppend(&ds, nl_langinfo(CODESET), TCL_INDEX_NONE); Tcl_UtfToLower(Tcl_DStringValue(&ds)); knownEncoding = SearchKnownEncodings(encoding); if (knownEncoding != NULL) { Tcl_DStringAppend(bufPtr, knownEncoding, TCL_INDEX_NONE); } else if (NULL != Tcl_GetEncoding(NULL, encoding)) { Tcl_DStringAppend(bufPtr, encoding, TCL_INDEX_NONE); } Tcl_DStringFree(&ds); if (Tcl_DStringLength(bufPtr)) { return Tcl_DStringValue(bufPtr); } } #endif /* HAVE_LANGINFO */ /* * Classic fallback check. This tries a homebrew algorithm to determine * what encoding should be used based on env vars. */ encoding = getenv("LC_ALL"); if (encoding == NULL || encoding[0] == '\0') { encoding = getenv("LC_CTYPE"); } if (encoding == NULL || encoding[0] == '\0') { encoding = getenv("LANG"); } if (encoding == NULL || encoding[0] == '\0') { encoding = NULL; } if (encoding != NULL) { const char *p; Tcl_DString ds; Tcl_DStringInit(&ds); p = encoding; encoding = Tcl_DStringAppend(&ds, p, TCL_INDEX_NONE); Tcl_UtfToLower(Tcl_DStringValue(&ds)); knownEncoding = SearchKnownEncodings(encoding); if (knownEncoding != NULL) { Tcl_DStringAppend(bufPtr, knownEncoding, TCL_INDEX_NONE); } else if (NULL != Tcl_GetEncoding(NULL, encoding)) { Tcl_DStringAppend(bufPtr, encoding, TCL_INDEX_NONE); } if (Tcl_DStringLength(bufPtr)) { Tcl_DStringFree(&ds); return Tcl_DStringValue(bufPtr); } /* * We didn't recognize the full value as an encoding name. If there is * an encoding subfield, we can try to guess from that. */ for (p = encoding; *p != '\0'; p++) { if (*p == '.') { p++; break; } } if (*p != '\0') { knownEncoding = SearchKnownEncodings(p); if (knownEncoding != NULL) { Tcl_DStringAppend(bufPtr, knownEncoding, TCL_INDEX_NONE); } else if (NULL != Tcl_GetEncoding(NULL, p)) { Tcl_DStringAppend(bufPtr, p, TCL_INDEX_NONE); } } Tcl_DStringFree(&ds); if (Tcl_DStringLength(bufPtr)) { return Tcl_DStringValue(bufPtr); } } return Tcl_DStringAppend(bufPtr, TCL_DEFAULT_ENCODING, TCL_INDEX_NONE); } /* *--------------------------------------------------------------------------- * * TclpSetVariables -- * * Performs platform-specific interpreter initialization related to the * tcl_library and tcl_platform variables, and other platform-specific * things. * * Results: * None. * * Side effects: * Sets "tclDefaultLibrary", "tcl_pkgPath", and "tcl_platform" Tcl * variables. * *---------------------------------------------------------------------- */ #if defined(HAVE_COREFOUNDATION) /* * Helper because whether CFLocaleCopyCurrent and CFLocaleGetIdentifier are * strongly or weakly bound varies by version of OSX, triggering warnings. */ static inline void InitMacLocaleInfoVar( CFLocaleRef (*localeCopyCurrent)(void), CFStringRef (*localeGetIdentifier)(CFLocaleRef), Tcl_Interp *interp) { CFLocaleRef localeRef; CFStringRef locale; char loc[256]; if (localeCopyCurrent == NULL || localeGetIdentifier == NULL) { return; } localeRef = localeCopyCurrent(); if (!localeRef) { return; } locale = localeGetIdentifier(localeRef); if (locale && CFStringGetCString(locale, loc, 256, kCFStringEncodingUTF8)) { if (!Tcl_CreateNamespace(interp, "::tcl::mac", NULL, NULL)) { Tcl_ResetResult(interp); } Tcl_SetVar2(interp, "::tcl::mac::locale", NULL, loc, TCL_GLOBAL_ONLY); } CFRelease(localeRef); } #endif /*defined(HAVE_COREFOUNDATION)*/ void TclpSetVariables( Tcl_Interp *interp) { #ifdef __CYGWIN__ SYSTEM_INFO sysInfo; static OSVERSIONINFOW osInfo; static int osInfoInitialized = 0; char buffer[TCL_INTEGER_SPACE * 2]; #elif !defined(NO_UNAME) struct utsname name; #endif int unameOK; const char *p, *q; Tcl_Obj *pkgListObj = Tcl_NewObj(); #ifdef HAVE_COREFOUNDATION char tclLibPath[MAXPATHLEN + 1]; /* * Set msgcat fallback locale to current CFLocale identifier. */ InitMacLocaleInfoVar(CFLocaleCopyCurrent, CFLocaleGetIdentifier, interp); if (MacOSXGetLibraryPath(interp, MAXPATHLEN, tclLibPath) == TCL_OK) { const char *str; CFBundleRef bundleRef; Tcl_DString ds; Tcl_SetVar2(interp, "tclDefaultLibrary", NULL, tclLibPath, TCL_GLOBAL_ONLY); Tcl_ListObjAppendElement(NULL, pkgListObj, Tcl_NewStringObj(tclLibPath, -1)); str = TclGetEnv("DYLD_FRAMEWORK_PATH", &ds); if ((str != NULL) && (str[0] != '\0')) { p = Tcl_DStringValue(&ds); while ((q = strchr(p, ':')) != NULL) { Tcl_ListObjAppendElement(NULL, pkgListObj, Tcl_NewStringObj(p, q-p)); p = q+1; } if (*p) { Tcl_ListObjAppendElement(NULL, pkgListObj, Tcl_NewStringObj(p, -1)); } Tcl_DStringFree(&ds); } bundleRef = CFBundleGetMainBundle(); if (bundleRef) { CFURLRef frameworksURL; Tcl_StatBuf statBuf; frameworksURL = CFBundleCopyPrivateFrameworksURL(bundleRef); if (frameworksURL) { if (CFURLGetFileSystemRepresentation(frameworksURL, TRUE, (unsigned char*) tclLibPath, MAXPATHLEN) && ! TclOSstat(tclLibPath, &statBuf) && S_ISDIR(statBuf.st_mode)) { Tcl_ListObjAppendElement(NULL, pkgListObj, Tcl_NewStringObj(tclLibPath, -1)); } CFRelease(frameworksURL); } frameworksURL = CFBundleCopySharedFrameworksURL(bundleRef); if (frameworksURL) { if (CFURLGetFileSystemRepresentation(frameworksURL, TRUE, (unsigned char*) tclLibPath, MAXPATHLEN) && ! TclOSstat(tclLibPath, &statBuf) && S_ISDIR(statBuf.st_mode)) { Tcl_ListObjAppendElement(NULL, pkgListObj, Tcl_NewStringObj(tclLibPath, -1)); } CFRelease(frameworksURL); } } } #endif /* HAVE_COREFOUNDATION */ p = pkgPath; while ((q = strchr(p, ':')) != NULL) { Tcl_ListObjAppendElement(NULL, pkgListObj, Tcl_NewStringObj(p, q-p)); p = q+1; } if (*p) { Tcl_ListObjAppendElement(NULL, pkgListObj, Tcl_NewStringObj(p, -1)); } Tcl_ObjSetVar2(interp, Tcl_NewStringObj("tcl_pkgPath", -1), NULL, pkgListObj, TCL_GLOBAL_ONLY); { /* Some platforms build configure scripts expect ~ expansion so do that */ Tcl_Obj *origPaths; Tcl_Obj *resolvedPaths; origPaths = Tcl_GetVar2Ex(interp, "tcl_pkgPath", NULL, TCL_GLOBAL_ONLY); resolvedPaths = TclResolveTildePathList(origPaths); if (resolvedPaths != origPaths && resolvedPaths != NULL) { Tcl_SetVar2Ex(interp, "tcl_pkgPath", NULL, resolvedPaths, TCL_GLOBAL_ONLY); } } #ifdef DJGPP Tcl_SetVar2(interp, "tcl_platform", "platform", "dos", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "tcl_platform", "platform", "unix", TCL_GLOBAL_ONLY); #endif unameOK = 0; #ifdef __CYGWIN__ unameOK = 1; if (!osInfoInitialized) { void *handle = GetModuleHandleW(L"NTDLL"); int(__stdcall *getversion)(void *) = (int(__stdcall *)(void *))GetProcAddress(handle, "RtlGetVersion"); osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW); if (!getversion || getversion(&osInfo)) { GetVersionExW(&osInfo); } osInfoInitialized = 1; } GetSystemInfo(&sysInfo); if (osInfo.dwMajorVersion == 10 && osInfo.dwBuildNumber >= 22000) { osInfo.dwMajorVersion = 11; } Tcl_SetVar2(interp, "tcl_platform", "os", "Windows NT", TCL_GLOBAL_ONLY); snprintf(buffer, sizeof(buffer), "%d.%d", osInfo.dwMajorVersion, osInfo.dwMinorVersion); Tcl_SetVar2(interp, "tcl_platform", "osVersion", buffer, TCL_GLOBAL_ONLY); if (sysInfo.wProcessorArchitecture < NUMPROCESSORS) { Tcl_SetVar2(interp, "tcl_platform", "machine", processors[sysInfo.wProcessorArchitecture], TCL_GLOBAL_ONLY); } #elif !defined NO_UNAME if (uname(&name) >= 0) { const char *native; Tcl_DString ds; unameOK = 1; native = Tcl_ExternalToUtfDString(NULL, name.sysname, TCL_INDEX_NONE, &ds); Tcl_SetVar2(interp, "tcl_platform", "os", native, TCL_GLOBAL_ONLY); Tcl_DStringFree(&ds); /* * The following code is a special hack to handle differences in the * way version information is returned by uname. On most systems the * full version number is available in name.release. However, under * AIX the major version number is in name.version and the minor * version number is in name.release. */ if ((strchr(name.release, '.') != NULL) || !isdigit(UCHAR(name.version[0]))) { /* INTL: digit */ Tcl_SetVar2(interp, "tcl_platform", "osVersion", name.release, TCL_GLOBAL_ONLY); } else { #ifdef DJGPP /* * For some obscure reason DJGPP puts major version into * name.release and minor into name.version. As of DJGPP 2.04 this * is documented in djgpp libc.info file. */ Tcl_SetVar2(interp, "tcl_platform", "osVersion", name.release, TCL_GLOBAL_ONLY); Tcl_SetVar2(interp, "tcl_platform", "osVersion", ".", TCL_GLOBAL_ONLY|TCL_APPEND_VALUE); Tcl_SetVar2(interp, "tcl_platform", "osVersion", name.version, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE); #else Tcl_SetVar2(interp, "tcl_platform", "osVersion", name.version, TCL_GLOBAL_ONLY); Tcl_SetVar2(interp, "tcl_platform", "osVersion", ".", TCL_GLOBAL_ONLY|TCL_APPEND_VALUE); Tcl_SetVar2(interp, "tcl_platform", "osVersion", name.release, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE); #endif /* DJGPP */ } Tcl_SetVar2(interp, "tcl_platform", "machine", name.machine, TCL_GLOBAL_ONLY); } #endif /* !NO_UNAME */ if (!unameOK) { Tcl_SetVar2(interp, "tcl_platform", "os", "", TCL_GLOBAL_ONLY); Tcl_SetVar2(interp, "tcl_platform", "osVersion", "", TCL_GLOBAL_ONLY); Tcl_SetVar2(interp, "tcl_platform", "machine", "", TCL_GLOBAL_ONLY); } /* * Copy the username of the real user (according to getuid()) into * tcl_platform(user). */ { struct passwd *pwEnt = TclpGetPwUid(getuid()); const char *user; Tcl_DString ds; if (pwEnt == NULL) { user = ""; Tcl_DStringInit(&ds); /* ensure cleanliness */ } else { user = Tcl_ExternalToUtfDString(NULL, pwEnt->pw_name, TCL_INDEX_NONE, &ds); } Tcl_SetVar2(interp, "tcl_platform", "user", user, TCL_GLOBAL_ONLY); Tcl_DStringFree(&ds); } /* * Define what the platform PATH separator is. [TIP #315] */ Tcl_SetVar2(interp, "tcl_platform","pathSeparator", ":", TCL_GLOBAL_ONLY); } /* *---------------------------------------------------------------------- * * TclpFindVariable -- * * Locate the entry in environ for a given name. On Unix this routine is * case sensitive, on Windows this matches mixed case. * * Results: * The return value is the index in environ of an entry with the name * "name", or -1 if there is no such entry. The integer at *lengthPtr is * filled in with the length of name (if a matching entry is found) or * the length of the environ array (if no matching entry is found). * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size TclpFindVariable( const char *name, /* Name of desired environment variable * (native). */ Tcl_Size *lengthPtr) /* Used to return length of name (for * successful searches) or number of non-NULL * entries in environ (for unsuccessful * searches). */ { Tcl_Size i, result = -1; const char *env, *p1, *p2; Tcl_DString envString; Tcl_DStringInit(&envString); for (i = 0, env = environ[i]; env != NULL; i++, env = environ[i]) { p1 = Tcl_ExternalToUtfDString(NULL, env, TCL_INDEX_NONE, &envString); p2 = name; for (; *p2 == *p1; p1++, p2++) { /* NULL loop body. */ } if ((*p1 == '=') && (*p2 == '\0')) { *lengthPtr = p2 - name; result = i; goto done; } Tcl_DStringFree(&envString); } *lengthPtr = i; done: Tcl_DStringFree(&envString); return result; } /* *---------------------------------------------------------------------- * * MacOSXGetLibraryPath -- * * If we have a bundle structure for the Tcl installation, then check * there first to see if we can find the libraries there. * * Results: * TCL_OK if we have found the tcl library; TCL_ERROR otherwise. * * Side effects: * Same as for Tcl_MacOSXOpenVersionedBundleResources. * *---------------------------------------------------------------------- */ #ifdef HAVE_COREFOUNDATION #ifdef TCL_FRAMEWORK static int MacOSXGetLibraryPath( Tcl_Interp *interp, int maxPathLen, char *tclLibPath) { return Tcl_MacOSXOpenVersionedBundleResources(interp, "com.tcltk.tcllibrary", TCL_FRAMEWORK_VERSION, 0, maxPathLen, tclLibPath); } #else static int MacOSXGetLibraryPath( TCL_UNUSED(Tcl_Interp *), TCL_UNUSED(int), TCL_UNUSED(char *)) { return TCL_ERROR; } #endif #endif /* HAVE_COREFOUNDATION */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclUnixNotfy.c0000644000175000017500000004131314726623136015355 0ustar sergeisergei/* * tclUnixNotfy.c -- * * This file contains subroutines shared by all notifier backend * implementations on *nix platforms. It is *included* by the epoll, * kqueue and select notifier implementation files. * * Copyright © 1995-1997 Sun Microsystems, Inc. * Copyright © 2016 Lucio Andrés Illanes Albornoz * Copyright © 2021 Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include #include "tclInt.h" /* * Static routines defined in this file. */ static int FileHandlerEventProc(Tcl_Event *evPtr, int flags); #if !TCL_THREADS # undef NOTIFIER_EPOLL # undef NOTIFIER_KQUEUE # define NOTIFIER_SELECT #elif !defined(NOTIFIER_EPOLL) && !defined(NOTIFIER_KQUEUE) # define NOTIFIER_SELECT static TCL_NORETURN void NotifierThreadProc(void *clientData); # if defined(HAVE_PTHREAD_ATFORK) static void AtForkChild(void); # endif /* HAVE_PTHREAD_ATFORK */ /* *---------------------------------------------------------------------- * * StartNotifierThread -- * * Start a notifier thread and wait for the notifier pipe to be created. * * Results: * None. * * Side effects: * Running Thread. * *---------------------------------------------------------------------- */ static void StartNotifierThread( const char *proc) { if (!notifierThreadRunning) { pthread_mutex_lock(¬ifierInitMutex); if (!notifierThreadRunning) { if (TclpThreadCreate(¬ifierThread, NotifierThreadProc, NULL, TCL_THREAD_STACK_DEFAULT, TCL_THREAD_JOINABLE) != TCL_OK) { Tcl_Panic("%s: unable to start notifier thread", proc); } pthread_mutex_lock(¬ifierMutex); /* * Wait for the notifier pipe to be created. */ while (triggerPipe < 0) { pthread_cond_wait(¬ifierCV, ¬ifierMutex); } pthread_mutex_unlock(¬ifierMutex); notifierThreadRunning = 1; } pthread_mutex_unlock(¬ifierInitMutex); } } #endif /* NOTIFIER_SELECT */ /* *---------------------------------------------------------------------- * * TclpAlertNotifier -- * * Wake up the specified notifier from any thread. This routine is called * by the platform independent notifier code whenever the Tcl_ThreadAlert * routine is called. This routine is guaranteed not to be called on a * given notifier after Tcl_FinalizeNotifier is called for that notifier. * * Results: * None. * * Side effects: * select(2) notifier: * signals the notifier condition variable for the specified * notifier. * epoll(7) notifier: * write(2)s to the eventfd(2) of the specified thread. * kqueue(2) notifier: * write(2)s to the trigger pipe(2) of the specified thread. * *---------------------------------------------------------------------- */ void TclpAlertNotifier( void *clientData) { #ifdef NOTIFIER_SELECT #if TCL_THREADS ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData; pthread_mutex_lock(¬ifierMutex); tsdPtr->eventReady = 1; # ifdef __CYGWIN__ PostMessageW(tsdPtr->hwnd, 1024, 0, 0); # else pthread_cond_broadcast(&tsdPtr->waitCV); # endif /* __CYGWIN__ */ pthread_mutex_unlock(¬ifierMutex); #else (void)clientData; #endif /* TCL_THREADS */ #else /* !NOTIFIER_SELECT */ ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData; #if defined(NOTIFIER_EPOLL) && defined(HAVE_EVENTFD) uint64_t eventFdVal = 1; if (write(tsdPtr->triggerEventFd, &eventFdVal, sizeof(eventFdVal)) != sizeof(eventFdVal)) { Tcl_Panic("Tcl_AlertNotifier: unable to write to %p->triggerEventFd", tsdPtr); } #else if (write(tsdPtr->triggerPipe[1], "", 1) != 1) { Tcl_Panic("Tcl_AlertNotifier: unable to write to %p->triggerPipe", tsdPtr); } #endif /* NOTIFIER_EPOLL && HAVE_EVENTFD */ #endif /* NOTIFIER_SELECT */ } /* *---------------------------------------------------------------------- * * LookUpFileHandler -- * * Look up the file handler structure (and optionally the previous one in * the chain) associated with a file descriptor. * * Returns: * A pointer to the file handler, or NULL if it can't be found. * * Side effects: * If prevPtrPtr is non-NULL, it will be written to if the file handler * is found. * *---------------------------------------------------------------------- */ static inline FileHandler * LookUpFileHandler( ThreadSpecificData *tsdPtr, /* Where to look things up. */ int fd, /* What we are looking for. */ FileHandler **prevPtrPtr) /* If non-NULL, where to report the previous * pointer. */ { FileHandler *filePtr, *prevPtr; /* * Find the entry for the given file (and return if there isn't one). */ for (prevPtr = NULL, filePtr = tsdPtr->firstFileHandlerPtr; ; prevPtr = filePtr, filePtr = filePtr->nextPtr) { if (filePtr == NULL) { return NULL; } if (filePtr->fd == fd) { break; } } /* * Report what we've found to our caller. */ if (prevPtrPtr) { *prevPtrPtr = prevPtr; } return filePtr; } /* *---------------------------------------------------------------------- * * TclpSetTimer -- * * This function sets the current notifier timer value. This interface is * not implemented in this notifier because we are always running inside * of Tcl_DoOneEvent. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclpSetTimer( TCL_UNUSED(const Tcl_Time *)) /* Timeout value, may be NULL. */ { /* * The interval timer doesn't do anything in this implementation, because * the only event loop is via Tcl_DoOneEvent, which passes timeout values * to Tcl_WaitForEvent. */ } /* *---------------------------------------------------------------------- * * Tcl_ServiceModeHook -- * * This function is invoked whenever the service mode changes. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclpServiceModeHook( int mode) /* Either TCL_SERVICE_ALL, or * TCL_SERVICE_NONE. */ { if (mode == TCL_SERVICE_ALL) { #ifdef NOTIFIER_SELECT #if TCL_THREADS StartNotifierThread("Tcl_ServiceModeHook"); #endif #endif /* NOTIFIER_SELECT */ } } /* *---------------------------------------------------------------------- * * FileHandlerEventProc -- * * This function is called by Tcl_ServiceEvent when a file event reaches * the front of the event queue. This function is responsible for * actually handling the event by invoking the callback for the file * handler. * * Results: * Returns 1 if the event was handled, meaning it should be removed from * the queue. Returns 0 if the event was not handled, meaning it should * stay on the queue. The only time the event isn't handled is if the * TCL_FILE_EVENTS flag bit isn't set. * * Side effects: * Whatever the file handler's callback function does. * *---------------------------------------------------------------------- */ static int FileHandlerEventProc( Tcl_Event *evPtr, /* Event to service. */ int flags) /* Flags that indicate what events to handle, * such as TCL_FILE_EVENTS. */ { int mask; FileHandler *filePtr; FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr; ThreadSpecificData *tsdPtr; if (!(flags & TCL_FILE_EVENTS)) { return 0; } /* * Search through the file handlers to find the one whose handle matches * the event. We do this rather than keeping a pointer to the file handler * directly in the event, so that the handler can be deleted while the * event is queued without leaving a dangling pointer. */ tsdPtr = TCL_TSD_INIT(&dataKey); for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL; filePtr = filePtr->nextPtr) { if (filePtr->fd != fileEvPtr->fd) { continue; } /* * The code is tricky for two reasons: * 1. The file handler's desired events could have changed since the * time when the event was queued, so AND the ready mask with the * desired mask. * 2. The file could have been closed and re-opened since the time * when the event was queued. This is why the ready mask is stored * in the file handler rather than the queued event: it will be * zeroed when a new file handler is created for the newly opened * file. */ mask = filePtr->readyMask & filePtr->mask; filePtr->readyMask = 0; if (mask != 0) { filePtr->proc(filePtr->clientData, mask); } break; } return 1; } #ifdef NOTIFIER_SELECT #if TCL_THREADS /* *---------------------------------------------------------------------- * * AlertSingleThread -- * * Notify a single thread that is waiting on a file descriptor to become * readable or writable or to have an exception condition. * notifierMutex must be held. * * Result: * None. * * Side effects: * The condition variable associated with the thread is broadcasted. * *---------------------------------------------------------------------- */ static void AlertSingleThread( ThreadSpecificData *tsdPtr) { tsdPtr->eventReady = 1; if (tsdPtr->onList) { /* * Remove the ThreadSpecificData structure of this thread from the * waiting list. This prevents us from continuously spinning on * epoll_wait until the other threads runs and services the file * event. */ if (tsdPtr->prevPtr) { tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; } else { waitingListPtr = tsdPtr->nextPtr; } if (tsdPtr->nextPtr) { tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; } tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; tsdPtr->onList = 0; tsdPtr->pollState = 0; } #ifdef __CYGWIN__ PostMessageW(tsdPtr->hwnd, 1024, 0, 0); #else /* !__CYGWIN__ */ pthread_cond_broadcast(&tsdPtr->waitCV); #endif /* __CYGWIN__ */ } #if defined(HAVE_PTHREAD_ATFORK) /* *---------------------------------------------------------------------- * * AtForkChild -- * * Unlock and reinstall the notifier in the child after a fork. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void AtForkChild(void) { if (notifierThreadRunning == 1) { pthread_cond_destroy(¬ifierCV); } pthread_mutex_init(¬ifierInitMutex, NULL); pthread_mutex_init(¬ifierMutex, NULL); pthread_cond_init(¬ifierCV, NULL); #ifdef NOTIFIER_SELECT asyncPending = 0; #endif /* * notifierThreadRunning == 1: thread is running, (there might be data in * notifier lists) * atForkInit == 0: InitNotifier was never called * notifierCount != 0: unbalanced InitNotifier() / FinalizeNotifier calls * waitingListPtr != 0: there are threads currently waiting for events. */ if (atForkInit == 1) { notifierCount = 0; if (notifierThreadRunning == 1) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); notifierThreadRunning = 0; close(triggerPipe); triggerPipe = -1; #ifdef NOTIFIER_SELECT close(otherPipe); otherPipe = -1; #endif /* * The waitingListPtr might contain event info from multiple * threads, which are invalid here, so setting it to NULL is not * unreasonable. */ waitingListPtr = NULL; /* * The tsdPtr from before the fork is copied as well. But since we * are paranoiac, we don't trust its condvar and reset it. */ #ifdef __CYGWIN__ DestroyWindow(tsdPtr->hwnd); tsdPtr->hwnd = CreateWindowExW(NULL, className, className, 0, 0, 0, 0, 0, NULL, NULL, TclWinGetTclInstance(), NULL); ResetEvent(tsdPtr->event); #else /* !__CYGWIN__ */ pthread_cond_destroy(&tsdPtr->waitCV); pthread_cond_init(&tsdPtr->waitCV, NULL); #endif /* __CYGWIN__ */ /* * In case, we had multiple threads running before the fork, * make sure, we don't try to reach out to their thread local data. */ tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; /* * The list of registered event handlers at fork time is in * tsdPtr->firstFileHandlerPtr; */ } } Tcl_InitNotifier(); #ifdef NOTIFIER_SELECT /* * Restart the notifier thread for signal handling. */ StartNotifierThread("AtForkChild"); #endif } #endif /* HAVE_PTHREAD_ATFORK */ #endif /* TCL_THREADS */ #endif /* NOTIFIER_SELECT */ /* *---------------------------------------------------------------------- * * TclpNotifierData -- * * This function returns a void pointer to be associated * with a Tcl_AsyncHandler. * * Results: * For the epoll and kqueue notifiers, this function returns the * thread specific data. Otherwise NULL. * * Side effects: * None. * *---------------------------------------------------------------------- */ void * TclpNotifierData(void) { #if defined(NOTIFIER_EPOLL) || defined(NOTIFIER_KQUEUE) ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); return tsdPtr; #else return NULL; #endif } /* *---------------------------------------------------------------------- * * TclUnixWaitForFile -- * * This function waits synchronously for a file to become readable or * writable, with an optional timeout. * * Results: * The return value is an OR'ed combination of TCL_READABLE, * TCL_WRITABLE, and TCL_EXCEPTION, indicating the conditions that are * present on file at the time of the return. This function will not * return until either "timeout" milliseconds have elapsed or at least * one of the conditions given by mask has occurred for file (a return * value of 0 means that a timeout occurred). No normal events will be * serviced during the execution of this function. * * Side effects: * Time passes. * *---------------------------------------------------------------------- */ #ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is * in tclMacOSXNotify.c */ int TclUnixWaitForFile( int fd, /* Handle for file on which to wait. */ int mask, /* What to wait for: OR'ed combination of * TCL_READABLE, TCL_WRITABLE, and * TCL_EXCEPTION. */ int timeout) /* Maximum amount of time to wait for one of * the conditions in mask to occur, in * milliseconds. A value of 0 means don't wait * at all, and a value of -1 means wait * forever. */ { Tcl_Time abortTime = {0, 0}, now; /* silence gcc 4 warning */ struct timeval blockTime, *timeoutPtr; struct pollfd pollFds[1]; int numFound, result = 0, pollTimeout; /* * If there is a non-zero finite timeout, compute the time when we give * up. */ if (timeout > 0) { Tcl_GetTime(&now); abortTime.sec = now.sec + timeout / 1000; abortTime.usec = now.usec + (timeout % 1000) * 1000; if (abortTime.usec >= 1000000) { abortTime.usec -= 1000000; abortTime.sec += 1; } timeoutPtr = &blockTime; } else if (timeout == 0) { timeoutPtr = &blockTime; blockTime.tv_sec = 0; blockTime.tv_usec = 0; } else { timeoutPtr = NULL; } /* * Setup the pollfd structure for the fd. */ pollFds[0].fd = fd; pollFds[0].events = pollFds[0].revents = 0; if (mask & TCL_READABLE) { pollFds[0].events |= (POLLIN | POLLHUP); } if (mask & TCL_WRITABLE) { pollFds[0].events |= POLLOUT; } if (mask & TCL_EXCEPTION) { pollFds[0].events |= POLLERR; } /* * Loop in a mini-event loop of our own, waiting for either the file to * become ready or a timeout to occur. */ do { if (timeout > 0) { blockTime.tv_sec = abortTime.sec - now.sec; blockTime.tv_usec = abortTime.usec - now.usec; if (blockTime.tv_usec < 0) { blockTime.tv_sec -= 1; blockTime.tv_usec += 1000000; } if (blockTime.tv_sec < 0) { blockTime.tv_sec = 0; blockTime.tv_usec = 0; } } /* * Wait for the event or a timeout. */ if (!timeoutPtr) { pollTimeout = -1; } else if (!timeoutPtr->tv_sec && !timeoutPtr->tv_usec) { pollTimeout = 0; } else { pollTimeout = (int) timeoutPtr->tv_sec * 1000; if (timeoutPtr->tv_usec) { pollTimeout += (int) timeoutPtr->tv_usec / 1000; } } numFound = poll(pollFds, 1, pollTimeout); if (numFound == 1) { result = 0; if (pollFds[0].revents & (POLLIN | POLLHUP)) { result |= TCL_READABLE; } if (pollFds[0].revents & POLLOUT) { result |= TCL_WRITABLE; } if (pollFds[0].revents & POLLERR) { result |= TCL_EXCEPTION; } if (result) { break; } } if (timeout == 0) { break; } if (timeout < 0) { continue; } /* * The select returned early, so we need to recompute the timeout. */ Tcl_GetTime(&now); } while ((abortTime.sec > now.sec) || (abortTime.sec == now.sec && abortTime.usec > now.usec)); return result; } #endif /* !HAVE_COREFOUNDATION */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclUnixPipe.c0000644000175000017500000010600214726623136015150 0ustar sergeisergei/* * tclUnixPipe.c -- * * This file implements the UNIX-specific exec pipeline functions, the * "pipe" channel driver, and the "pid" Tcl command. * * Copyright © 1991-1994 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #ifdef HAVE_POSIX_SPAWNP # if defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDDUP2) \ && defined(HAVE_POSIX_SPAWNATTR_SETFLAGS) \ && !defined(HAVE_VFORK) # include # include # else # undef HAVE_POSIX_SPAWNP # endif #endif #ifdef HAVE_VFORK #define fork vfork #endif /* * The following macros convert between TclFile's and fd's. The conversion * simple involves shifting fd's up by one to ensure that no valid fd is ever * the same as NULL. */ #define MakeFile(fd) ((TclFile) INT2PTR(((int) (fd)) + 1)) #define GetFd(file) (PTR2INT(file) - 1) /* * This structure describes per-instance state of a pipe based channel. */ typedef struct { Tcl_Channel channel; /* Channel associated with this file. */ TclFile inFile; /* Output from pipe. */ TclFile outFile; /* Input to pipe. */ TclFile errorFile; /* Error output from pipe. */ size_t numPids; /* How many processes are attached to this * pipe? */ Tcl_Pid *pidPtr; /* The process IDs themselves. Allocated by * the creator of the pipe. */ int isNonBlocking; /* Nonzero when the pipe is in nonblocking * mode. Used to decide whether to wait for * the children at close time. */ } PipeState; /* * Declarations for local functions defined in this file: */ static int PipeBlockModeProc(void *instanceData, int mode); static int PipeClose2Proc(void *instanceData, Tcl_Interp *interp, int flags); static int PipeGetHandleProc(void *instanceData, int direction, void **handlePtr); static int PipeInputProc(void *instanceData, char *buf, int toRead, int *errorCode); static int PipeOutputProc(void *instanceData, const char *buf, int toWrite, int *errorCode); static void PipeWatchProc(void *instanceData, int mask); static void RestoreSignals(void); static int SetupStdFile(TclFile file, int type); /* * This structure describes the channel type structure for command pipe based * I/O: */ static const Tcl_ChannelType pipeChannelType = { "pipe", TCL_CHANNEL_VERSION_5, NULL, /* Deprecated. */ PipeInputProc, PipeOutputProc, NULL, /* Deprecated. */ NULL, /* Set option proc. */ NULL, /* Get option proc. */ PipeWatchProc, PipeGetHandleProc, PipeClose2Proc, PipeBlockModeProc, NULL, /* Flush proc. */ NULL, /* Bubbled event handler proc. */ NULL, /* Seek proc. */ NULL, /* Thread action proc. */ NULL /* Truncation proc. */ }; /* *---------------------------------------------------------------------- * * TclpMakeFile -- * * Make a TclFile from a channel. * * Results: * Returns a new TclFile or NULL on failure. * * Side effects: * None. * *---------------------------------------------------------------------- */ TclFile TclpMakeFile( Tcl_Channel channel, /* Channel to get file from. */ int direction) /* Either TCL_READABLE or TCL_WRITABLE. */ { void *data; if (Tcl_GetChannelHandle(channel, direction, &data) != TCL_OK) { return NULL; } return MakeFile(PTR2INT(data)); } /* *---------------------------------------------------------------------- * * TclpOpenFile -- * * Open a file for use in a pipeline. * * Results: * Returns a new TclFile handle or NULL on failure. * * Side effects: * May cause a file to be created on the file system. * *---------------------------------------------------------------------- */ TclFile TclpOpenFile( const char *fname, /* The name of the file to open. */ int mode) /* In what mode to open the file? */ { int fd; const char *native; Tcl_DString ds; if (Tcl_UtfToExternalDStringEx(NULL, NULL, fname, TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return NULL; } native = Tcl_DStringValue(&ds); fd = TclOSopen(native, mode, 0666); /* INTL: Native. */ Tcl_DStringFree(&ds); if (fd != -1) { fcntl(fd, F_SETFD, FD_CLOEXEC); /* * If the file is being opened for writing, seek to the end so we can * append to any data already in the file. */ if ((mode & O_WRONLY) && !(mode & O_APPEND)) { TclOSseek(fd, 0, SEEK_END); } /* * Increment the fd so it can't be 0, which would conflict with the * NULL return for errors. */ return MakeFile(fd); } return NULL; } /* *---------------------------------------------------------------------- * * TclpCreateTempFile -- * * This function creates a temporary file initialized with an optional * string, and returns a file handle with the file pointer at the * beginning of the file. * * Results: * A handle to a file. * * Side effects: * None. * *---------------------------------------------------------------------- */ TclFile TclpCreateTempFile( const char *contents) /* String to write into temp file, or NULL. */ { int fd = TclUnixOpenTemporaryFile(NULL, NULL, NULL, NULL); if (fd == -1) { return NULL; } fcntl(fd, F_SETFD, FD_CLOEXEC); if (contents != NULL) { Tcl_DString dstring; char *native; if (Tcl_UtfToExternalDStringEx(NULL, NULL, contents, TCL_INDEX_NONE, 0, &dstring, NULL) != TCL_OK) { close(fd); Tcl_DStringFree(&dstring); return NULL; } native = Tcl_DStringValue(&dstring); if (write(fd, native, Tcl_DStringLength(&dstring)) == -1) { close(fd); Tcl_DStringFree(&dstring); return NULL; } Tcl_DStringFree(&dstring); TclOSseek(fd, 0, SEEK_SET); } return MakeFile(fd); } /* *---------------------------------------------------------------------- * * TclpTempFileName -- * * This function returns unique filename. * * Results: * Returns a valid Tcl_Obj* with refCount 0, or NULL on failure. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj * TclpTempFileName(void) { Tcl_Obj *retVal, *nameObj; int fd; TclNewObj(nameObj); Tcl_IncrRefCount(nameObj); fd = TclUnixOpenTemporaryFile(NULL, NULL, NULL, nameObj); if (fd == -1) { Tcl_DecrRefCount(nameObj); return NULL; } fcntl(fd, F_SETFD, FD_CLOEXEC); TclpObjDeleteFile(nameObj); close(fd); retVal = Tcl_DuplicateObj(nameObj); Tcl_DecrRefCount(nameObj); return retVal; } /* *---------------------------------------------------------------------------- * * TclpTempFileNameForLibrary -- * * Constructs a file name in the native file system where a dynamically * loaded library may be placed. * * Results: * Returns the constructed file name. If an error occurs, returns NULL * and leaves an error message in the interpreter result. * * On Unix, it works to load a shared object from a file of any name, so this * function is merely a thin wrapper around TclpTempFileName(). * *---------------------------------------------------------------------------- */ Tcl_Obj * TclpTempFileNameForLibrary( Tcl_Interp *interp, /* Tcl interpreter. */ TCL_UNUSED(Tcl_Obj *) /*path*/) { Tcl_Obj *retval = TclpTempFileName(); if (retval == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't create temporary file: %s", Tcl_PosixError(interp))); } return retval; } /* *---------------------------------------------------------------------- * * TclpCreatePipe -- * * Creates a pipe - simply calls the pipe() function. * * Results: * Returns 1 on success, 0 on failure. * * Side effects: * Creates a pipe. * *---------------------------------------------------------------------- */ int TclpCreatePipe( TclFile *readPipe, /* Location to store file handle for read side * of pipe. */ TclFile *writePipe) /* Location to store file handle for write * side of pipe. */ { int pipeIds[2]; if (pipe(pipeIds) != 0) { return 0; } fcntl(pipeIds[0], F_SETFD, FD_CLOEXEC); fcntl(pipeIds[1], F_SETFD, FD_CLOEXEC); *readPipe = MakeFile(pipeIds[0]); *writePipe = MakeFile(pipeIds[1]); return 1; } /* *---------------------------------------------------------------------- * * TclpCloseFile -- * * Implements a mechanism to close a UNIX file. * * Results: * Returns 0 on success, or -1 on error, setting errno. * * Side effects: * The file is closed. * *---------------------------------------------------------------------- */ int TclpCloseFile( TclFile file) /* The file to close. */ { int fd = GetFd(file); /* * Refuse to close the fds for stdin, stdout and stderr. */ if ((fd == 0) || (fd == 1) || (fd == 2)) { return 0; } Tcl_DeleteFileHandler(fd); return close(fd); } /* *--------------------------------------------------------------------------- * * TclpCreateProcess -- * * Create a child process that has the specified files as its standard * input, output, and error. The child process runs asynchronously and * runs with the same environment variables as the creating process. * * The path is searched to find the specified executable. * * Results: * The return value is TCL_ERROR and an error message is left in the * interp's result if there was a problem creating the child process. * Otherwise, the return value is TCL_OK and *pidPtr is filled with the * process id of the child process. * * Side effects: * A process is created. * *--------------------------------------------------------------------------- */ int TclpCreateProcess( Tcl_Interp *interp, /* Interpreter in which to leave errors that * occurred when creating the child process. * Error messages from the child process * itself are sent to errorFile. */ size_t argc, /* Number of arguments in following array. */ const char **argv, /* Array of argument strings in UTF-8. * argv[0] contains the name of the executable * translated using Tcl_TranslateFileName * call). Additional arguments have not been * converted. */ TclFile inputFile, /* If non-NULL, gives the file to use as input * for the child process. If inputFile file is * not readable or is NULL, the child will * receive no standard input. */ TclFile outputFile, /* If non-NULL, gives the file that receives * output from the child process. If * outputFile file is not writable or is * NULL, output from the child will be * discarded. */ TclFile errorFile, /* If non-NULL, gives the file that receives * errors from the child process. If errorFile * file is not writable or is NULL, errors * from the child will be discarded. errorFile * may be the same as outputFile. */ Tcl_Pid *pidPtr) /* If this function is successful, pidPtr is * filled with the process id of the child * process. */ { TclFile errPipeIn, errPipeOut; int count, status, fd; char errSpace[200 + TCL_INTEGER_SPACE]; Tcl_DString *volatile dsArray; char **volatile newArgv; int pid; size_t i; #if defined(HAVE_POSIX_SPAWNP) int childErrno; static int use_spawn = -1; #endif errPipeIn = NULL; errPipeOut = NULL; pid = -1; /* * Create a pipe that the child can use to return error information if * anything goes wrong. */ if (TclpCreatePipe(&errPipeIn, &errPipeOut) == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't create pipe: %s", Tcl_PosixError(interp))); goto error; } /* * We need to allocate and convert this before the fork so it is properly * deallocated later */ dsArray = (Tcl_DString *)TclStackAlloc(interp, argc * sizeof(Tcl_DString)); newArgv = (char **)TclStackAlloc(interp, (argc+1) * sizeof(char *)); newArgv[argc] = NULL; for (i = 0; i < argc; i++) { if (Tcl_UtfToExternalDStringEx(interp, NULL, argv[i], TCL_INDEX_NONE, 0, &dsArray[i], NULL) != TCL_OK) { while (i-- > 0) { Tcl_DStringFree(&dsArray[i]); } TclStackFree(interp, newArgv); TclStackFree(interp, dsArray); goto error; } newArgv[i] = Tcl_DStringValue(&dsArray[i]); } #if defined(HAVE_VFORK) || defined(HAVE_POSIX_SPAWNP) /* * After vfork(), do not call code in the child that changes global state, * because it is using the parent's memory space at that point and writes * might corrupt the parent: so ensure standard channels are initialized * in the parent, otherwise SetupStdFile() might initialize them in the * child. */ if (!inputFile) { Tcl_GetStdChannel(TCL_STDIN); } if (!outputFile) { Tcl_GetStdChannel(TCL_STDOUT); } if (!errorFile) { Tcl_GetStdChannel(TCL_STDERR); } #endif #ifdef HAVE_POSIX_SPAWNP #ifdef _CS_GNU_LIBC_VERSION if (use_spawn < 0) { char conf[32], *p; int major = 0, minor = 0; use_spawn = 0; memset(conf, 0, sizeof(conf)); confstr(_CS_GNU_LIBC_VERSION, conf, sizeof(conf)); p = strchr(conf, ' '); /* skip "glibc" */ if (p != NULL) { ++p; if (sscanf(p, "%d.%d", &major, &minor) > 1) { if ((major > 2) || ((major == 2) && (minor >= 24))) { use_spawn = 1; } } } } #endif status = -1; if (use_spawn) { posix_spawn_file_actions_t actions; posix_spawnattr_t attr; sigset_t sigs; posix_spawn_file_actions_init(&actions); posix_spawnattr_init(&attr); sigfillset(&sigs); sigdelset(&sigs, SIGKILL); sigdelset(&sigs, SIGSTOP); posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF #ifdef POSIX_SPAWN_USEVFORK | POSIX_SPAWN_USEVFORK #endif ); posix_spawnattr_setsigdefault(&attr, &sigs); posix_spawn_file_actions_adddup2(&actions, GetFd(inputFile), 0); posix_spawn_file_actions_adddup2(&actions, GetFd(outputFile), 1); posix_spawn_file_actions_adddup2(&actions, GetFd(errorFile), 2); status = posix_spawnp(&pid, newArgv[0], &actions, &attr, newArgv, environ); childErrno = errno; posix_spawn_file_actions_destroy(&actions); posix_spawnattr_destroy(&attr); /* * Fork semantics: * - pid == 0: child process * - pid == -1: error * - pid > 0: parent process * * Mimic fork semantics to minimize changes below, * but retry with fork() as last ressort. */ } if (status != 0) { pid = fork(); childErrno = errno; } #else pid = fork(); #endif if (pid == 0) { size_t len; int joinThisError = errorFile && (errorFile == outputFile); fd = GetFd(errPipeOut); /* * Set up stdio file handles for the child process. */ if (!SetupStdFile(inputFile, TCL_STDIN) || !SetupStdFile(outputFile, TCL_STDOUT) || (!joinThisError && !SetupStdFile(errorFile, TCL_STDERR)) || (joinThisError && ((dup2(1,2) == -1) || (fcntl(2, F_SETFD, 0) != 0)))) { snprintf(errSpace, sizeof(errSpace), "%dforked process couldn't set up input/output", errno); len = strlen(errSpace); if (len != (size_t) write(fd, errSpace, len)) { Tcl_Panic("TclpCreateProcess: unable to write to errPipeOut"); } _exit(1); } /* * Close the input side of the error pipe. */ RestoreSignals(); execvp(newArgv[0], newArgv); /* INTL: Native. */ snprintf(errSpace, sizeof(errSpace), "%dcouldn't execute \"%.150s\"", errno, argv[0]); len = strlen(errSpace); if (len != (size_t) write(fd, errSpace, len)) { Tcl_Panic("TclpCreateProcess: unable to write to errPipeOut"); } _exit(1); } /* * Free the mem we used for the fork */ for (i = 0; i < argc; i++) { Tcl_DStringFree(&dsArray[i]); } TclStackFree(interp, newArgv); TclStackFree(interp, dsArray); if (pid == -1) { #ifdef HAVE_POSIX_SPAWNP errno = childErrno; #endif Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't fork child process: %s", Tcl_PosixError(interp))); goto error; } /* * Read back from the error pipe to see if the child started up OK. The * info in the pipe (if any) consists of a decimal errno value followed by * an error message. */ TclpCloseFile(errPipeOut); errPipeOut = NULL; fd = GetFd(errPipeIn); count = read(fd, errSpace, sizeof(errSpace) - 1); if (count > 0) { char *end; errSpace[count] = 0; errno = strtol(errSpace, &end, 10); Tcl_SetObjResult(interp, Tcl_ObjPrintf("%s: %s", end, Tcl_PosixError(interp))); goto error; } TclpCloseFile(errPipeIn); *pidPtr = (Tcl_Pid)INT2PTR(pid); return TCL_OK; error: if (pid != -1) { /* * Reap the child process now if an error occurred during its startup. * We don't call this with WNOHANG because that can lead to defunct * processes on an MP system. We shouldn't have to worry about hanging * here, since this is the error case. [Bug: 6148] */ Tcl_WaitPid((Tcl_Pid)INT2PTR(pid), &status, 0); } if (errPipeIn) { TclpCloseFile(errPipeIn); } if (errPipeOut) { TclpCloseFile(errPipeOut); } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * RestoreSignals -- * * This function is invoked in a forked child process just before * exec-ing a new program to restore all signals to their default * settings. * * Results: * None. * * Side effects: * Signal settings get changed. * *---------------------------------------------------------------------- */ static void RestoreSignals(void) { #ifdef SIGABRT signal(SIGABRT, SIG_DFL); #endif #ifdef SIGALRM signal(SIGALRM, SIG_DFL); #endif #ifdef SIGFPE signal(SIGFPE, SIG_DFL); #endif #ifdef SIGHUP signal(SIGHUP, SIG_DFL); #endif #ifdef SIGILL signal(SIGILL, SIG_DFL); #endif #ifdef SIGINT signal(SIGINT, SIG_DFL); #endif #ifdef SIGPIPE signal(SIGPIPE, SIG_DFL); #endif #ifdef SIGQUIT signal(SIGQUIT, SIG_DFL); #endif #ifdef SIGSEGV signal(SIGSEGV, SIG_DFL); #endif #ifdef SIGTERM signal(SIGTERM, SIG_DFL); #endif #ifdef SIGUSR1 signal(SIGUSR1, SIG_DFL); #endif #ifdef SIGUSR2 signal(SIGUSR2, SIG_DFL); #endif #ifdef SIGCHLD signal(SIGCHLD, SIG_DFL); #endif #ifdef SIGCONT signal(SIGCONT, SIG_DFL); #endif #ifdef SIGTSTP signal(SIGTSTP, SIG_DFL); #endif #ifdef SIGTTIN signal(SIGTTIN, SIG_DFL); #endif #ifdef SIGTTOU signal(SIGTTOU, SIG_DFL); #endif } /* *---------------------------------------------------------------------- * * SetupStdFile -- * * Set up stdio file handles for the child process, using the current * standard channels if no other files are specified. If no standard * channel is defined, or if no file is associated with the channel, then * the corresponding standard fd is closed. * * Results: * Returns 1 on success, or 0 on failure. * * Side effects: * Replaces stdio fds. * *---------------------------------------------------------------------- */ static int SetupStdFile( TclFile file, /* File to dup, or NULL. */ int type) /* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR */ { Tcl_Channel channel; int fd; int targetFd = 0; /* Initializations here needed only to */ int direction = 0; /* prevent warnings about using uninitialized * variables. */ switch (type) { case TCL_STDIN: targetFd = 0; direction = TCL_READABLE; break; case TCL_STDOUT: targetFd = 1; direction = TCL_WRITABLE; break; case TCL_STDERR: targetFd = 2; direction = TCL_WRITABLE; break; } if (!file) { channel = Tcl_GetStdChannel(type); if (channel) { file = TclpMakeFile(channel, direction); } } if (file) { fd = GetFd(file); if (fd != targetFd) { if (dup2(fd, targetFd) == -1) { return 0; } /* * Must clear the close-on-exec flag for the target FD, since some * systems (e.g. Ultrix) do not clear the CLOEXEC flag on the * target FD. */ fcntl(targetFd, F_SETFD, 0); } else { /* * Since we aren't dup'ing the file, we need to explicitly clear * the close-on-exec flag. */ fcntl(fd, F_SETFD, 0); } } else { close(targetFd); } return 1; } /* *---------------------------------------------------------------------- * * TclpCreateCommandChannel -- * * This function is called by the generic IO level to perform the * platform specific channel initialization for a command channel. * * Results: * Returns a new channel or NULL on failure. * * Side effects: * Allocates a new channel. * *---------------------------------------------------------------------- */ Tcl_Channel TclpCreateCommandChannel( TclFile readFile, /* If non-null, gives the file for reading. */ TclFile writeFile, /* If non-null, gives the file for writing. */ TclFile errorFile, /* If non-null, gives the file where errors * can be read. */ size_t numPids, /* The number of pids in the pid array. */ Tcl_Pid *pidPtr) /* An array of process identifiers. Allocated * by the caller, freed when the channel is * closed or the processes are detached (in a * background exec). */ { char channelName[16 + TCL_INTEGER_SPACE]; int fd; PipeState *statePtr = (PipeState *)Tcl_Alloc(sizeof(PipeState)); int mode; statePtr->inFile = readFile; statePtr->outFile = writeFile; statePtr->errorFile = errorFile; statePtr->numPids = numPids; statePtr->pidPtr = pidPtr; statePtr->isNonBlocking = 0; mode = 0; if (readFile) { mode |= TCL_READABLE; } if (writeFile) { mode |= TCL_WRITABLE; } /* * Use one of the fds associated with the channel as the channel id. */ if (readFile) { fd = GetFd(readFile); } else if (writeFile) { fd = GetFd(writeFile); } else if (errorFile) { fd = GetFd(errorFile); } else { fd = 0; } /* * For backward compatibility with previous versions of Tcl, we use * "file%d" as the base name for pipes even though it would be more * natural to use "pipe%d". */ snprintf(channelName, sizeof(channelName), "file%d", fd); statePtr->channel = Tcl_CreateChannel(&pipeChannelType, channelName, statePtr, mode); return statePtr->channel; } /* *---------------------------------------------------------------------- * * Tcl_CreatePipe -- * * System dependent interface to create a pipe for the [chan pipe] * command. Stolen from TclX. * * Results: * TCL_OK or TCL_ERROR. * * Side effects: * Registers two channels. * *---------------------------------------------------------------------- */ int Tcl_CreatePipe( Tcl_Interp *interp, /* Errors returned in result. */ Tcl_Channel *rchan, /* Returned read side. */ Tcl_Channel *wchan, /* Returned write side. */ TCL_UNUSED(int) /*flags*/) /* Reserved for future use. */ { int fileNums[2]; if (pipe(fileNums) < 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("pipe creation failed: %s", Tcl_PosixError(interp))); return TCL_ERROR; } fcntl(fileNums[0], F_SETFD, FD_CLOEXEC); fcntl(fileNums[1], F_SETFD, FD_CLOEXEC); *rchan = Tcl_MakeFileChannel(INT2PTR(fileNums[0]), TCL_READABLE); Tcl_RegisterChannel(interp, *rchan); *wchan = Tcl_MakeFileChannel(INT2PTR(fileNums[1]), TCL_WRITABLE); Tcl_RegisterChannel(interp, *wchan); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclGetAndDetachPids -- * * This function is invoked in the generic implementation of a * background "exec" (an exec when invoked with a terminating "&") to * store a list of the PIDs for processes in a command pipeline in the * interp's result and to detach the processes. * * Results: * None. * * Side effects: * Modifies the interp's result. Detaches processes. * *---------------------------------------------------------------------- */ void TclGetAndDetachPids( Tcl_Interp *interp, /* Interpreter to append the PIDs to. */ Tcl_Channel chan) /* Handle for the pipeline. */ { PipeState *pipePtr; const Tcl_ChannelType *chanTypePtr; Tcl_Obj *pidsObj; size_t i; /* * Punt if the channel is not a command channel. */ chanTypePtr = Tcl_GetChannelType(chan); if (chanTypePtr != &pipeChannelType) { return; } pipePtr = (PipeState *)Tcl_GetChannelInstanceData(chan); TclNewObj(pidsObj); for (i = 0; i < pipePtr->numPids; i++) { Tcl_ListObjAppendElement(NULL, pidsObj, Tcl_NewWideIntObj( PTR2INT(pipePtr->pidPtr[i]))); Tcl_DetachPids(1, &pipePtr->pidPtr[i]); } Tcl_SetObjResult(interp, pidsObj); if (pipePtr->numPids > 0) { Tcl_Free(pipePtr->pidPtr); pipePtr->numPids = 0; } } /* *---------------------------------------------------------------------- * * PipeBlockModeProc -- * * Helper function to set blocking and nonblocking modes on a pipe based * channel. Invoked by generic IO level code. * * Results: * 0 if successful, errno when failed. * * Side effects: * Sets the device into blocking or non-blocking mode. * *---------------------------------------------------------------------- */ static int PipeBlockModeProc( void *instanceData, /* Pipe state. */ int mode) /* The mode to set. Can be one of * TCL_MODE_BLOCKING or * TCL_MODE_NONBLOCKING. */ { PipeState *psPtr = (PipeState *)instanceData; if (psPtr->inFile && TclUnixSetBlockingMode(GetFd(psPtr->inFile), mode) < 0) { return errno; } if (psPtr->outFile && TclUnixSetBlockingMode(GetFd(psPtr->outFile), mode) < 0) { return errno; } psPtr->isNonBlocking = (mode == TCL_MODE_NONBLOCKING); return 0; } /* *---------------------------------------------------------------------- * * PipeClose2Proc * * This function is invoked by the generic IO level to perform * pipeline-type-specific half or full-close. * * Results: * 0 on success, errno otherwise. * * Side effects: * Closes the command pipeline channel. * *---------------------------------------------------------------------- */ static int PipeClose2Proc( void *instanceData, /* The pipe to close. */ Tcl_Interp *interp, /* For error reporting. */ int flags) /* Flags that indicate which side to close. */ { PipeState *pipePtr = (PipeState *)instanceData; Tcl_Channel errChan; int errorCode, result; errorCode = 0; result = 0; if (((!flags) || (flags & TCL_CLOSE_READ)) && (pipePtr->inFile != NULL)) { if (TclpCloseFile(pipePtr->inFile) < 0) { errorCode = errno; } else { pipePtr->inFile = NULL; } } if (((!flags) || (flags & TCL_CLOSE_WRITE)) && (pipePtr->outFile != NULL) && (errorCode == 0)) { if (TclpCloseFile(pipePtr->outFile) < 0) { errorCode = errno; } else { pipePtr->outFile = NULL; } } /* * If half-closing, stop here. */ if (flags) { return errorCode; } if (pipePtr->isNonBlocking || TclInExit()) { /* * If the channel is non-blocking or Tcl is being cleaned up, just * detach the children PIDs, reap them (important if we are in a * dynamic load module), and discard the errorFile. */ Tcl_DetachPids(pipePtr->numPids, pipePtr->pidPtr); Tcl_ReapDetachedProcs(); if (pipePtr->errorFile) { TclpCloseFile(pipePtr->errorFile); } } else { /* * Wrap the error file into a channel and give it to the cleanup * routine. */ if (pipePtr->errorFile) { errChan = Tcl_MakeFileChannel( INT2PTR(GetFd(pipePtr->errorFile)), TCL_READABLE); /* Error channels should not raise encoding errors */ Tcl_SetChannelOption(NULL, errChan, "-profile", "replace"); } else { errChan = NULL; } result = TclCleanupChildren(interp, pipePtr->numPids, pipePtr->pidPtr, errChan); } if (pipePtr->numPids != 0) { Tcl_Free(pipePtr->pidPtr); } Tcl_Free(pipePtr); if (errorCode == 0) { return result; } return errorCode; } /* *---------------------------------------------------------------------- * * PipeInputProc -- * * This function is invoked from the generic IO level to read input from * a command pipeline based channel. * * Results: * The number of bytes read is returned or -1 on error. An output * argument contains a POSIX error code if an error occurs, or zero. * * Side effects: * Reads input from the input device of the channel. * *---------------------------------------------------------------------- */ static int PipeInputProc( void *instanceData, /* Pipe state. */ char *buf, /* Where to store data read. */ int toRead, /* How much space is available in the * buffer? */ int *errorCodePtr) /* Where to store error code. */ { PipeState *psPtr = (PipeState *)instanceData; int bytesRead; /* How many bytes were actually read from the * input device? */ *errorCodePtr = 0; /* * Assume there is always enough input available. This will block * appropriately, and read will unblock as soon as a short read is * possible, if the channel is in blocking mode. If the channel is * nonblocking, the read will never block. Some OSes can throw an * interrupt error, for which we should immediately retry. [Bug #415131] */ do { bytesRead = read(GetFd(psPtr->inFile), buf, toRead); } while ((bytesRead < 0) && (errno == EINTR)); if (bytesRead < 0) { *errorCodePtr = errno; return -1; } return bytesRead; } /* *---------------------------------------------------------------------- * * PipeOutputProc-- * * This function is invoked from the generic IO level to write output to * a command pipeline based channel. * * Results: * The number of bytes written is returned or -1 on error. An output * argument contains a POSIX error code if an error occurred, or zero. * * Side effects: * Writes output on the output device of the channel. * *---------------------------------------------------------------------- */ static int PipeOutputProc( void *instanceData, /* Pipe state. */ const char *buf, /* The data buffer. */ int toWrite, /* How many bytes to write? */ int *errorCodePtr) /* Where to store error code. */ { PipeState *psPtr = (PipeState *)instanceData; int written; *errorCodePtr = 0; /* * Some OSes can throw an interrupt error, for which we should immediately * retry. [Bug #415131] */ do { written = write(GetFd(psPtr->outFile), buf, toWrite); } while ((written < 0) && (errno == EINTR)); if (written < 0) { *errorCodePtr = errno; return -1; } return written; } /* *---------------------------------------------------------------------- * * PipeWatchProc -- * * Initialize the notifier to watch the fds from this channel. * * Results: * None. * * Side effects: * Sets up the notifier so that a future event on the channel will be * seen by Tcl. * *---------------------------------------------------------------------- */ /* * Bug ad5a57f2f271: Tcl_NotifyChannel is not a Tcl_FileProc, * so do not pass it to directly to Tcl_CreateFileHandler. * Instead, pass a wrapper which is a Tcl_FileProc. */ static void PipeWatchNotifyChannelWrapper( void *clientData, int mask) { Tcl_Channel channel = (Tcl_Channel)clientData; Tcl_NotifyChannel(channel, mask); } static void PipeWatchProc( void *instanceData, /* The pipe state. */ int mask) /* Events of interest; an OR-ed combination of * TCL_READABLE, TCL_WRITABLE and * TCL_EXCEPTION. */ { PipeState *psPtr = (PipeState *)instanceData; int newmask; if (psPtr->inFile) { newmask = mask & (TCL_READABLE | TCL_EXCEPTION); if (newmask) { Tcl_CreateFileHandler(GetFd(psPtr->inFile), newmask, PipeWatchNotifyChannelWrapper, psPtr->channel); } else { Tcl_DeleteFileHandler(GetFd(psPtr->inFile)); } } if (psPtr->outFile) { newmask = mask & (TCL_WRITABLE | TCL_EXCEPTION); if (newmask) { Tcl_CreateFileHandler(GetFd(psPtr->outFile), newmask, PipeWatchNotifyChannelWrapper, psPtr->channel); } else { Tcl_DeleteFileHandler(GetFd(psPtr->outFile)); } } } /* *---------------------------------------------------------------------- * * PipeGetHandleProc -- * * Called from Tcl_GetChannelHandle to retrieve OS handles from inside a * command pipeline based channel. * * Results: * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no * handle for the specified direction. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PipeGetHandleProc( void *instanceData, /* The pipe state. */ int direction, /* TCL_READABLE or TCL_WRITABLE */ void **handlePtr) /* Where to store the handle. */ { PipeState *psPtr = (PipeState *)instanceData; if (direction == TCL_READABLE && psPtr->inFile) { *handlePtr = INT2PTR(GetFd(psPtr->inFile)); return TCL_OK; } if (direction == TCL_WRITABLE && psPtr->outFile) { *handlePtr = INT2PTR(GetFd(psPtr->outFile)); return TCL_OK; } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_WaitPid -- * * Implements the waitpid system call on Unix systems. * * Results: * Result of calling waitpid. * * Side effects: * Waits for a process to terminate. * *---------------------------------------------------------------------- */ Tcl_Pid Tcl_WaitPid( Tcl_Pid pid, int *statPtr, int options) { int result; pid_t real_pid = (pid_t) PTR2INT(pid); while (1) { result = (int) waitpid(real_pid, statPtr, options); if ((result != -1) || (errno != EINTR)) { return (Tcl_Pid)INT2PTR(result); } } } /* *---------------------------------------------------------------------- * * Tcl_PidObjCmd -- * * This function is invoked to process the "pid" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_PidObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Argument strings. */ { Tcl_Channel chan; PipeState *pipePtr; size_t i; Tcl_Obj *resultPtr; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?channel?"); return TCL_ERROR; } if (objc == 1) { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(getpid())); } else { /* * Get the channel and make sure that it refers to a pipe. */ chan = Tcl_GetChannel(interp, TclGetString(objv[1]), NULL); if (chan == NULL) { return TCL_ERROR; } if (Tcl_GetChannelType(chan) != &pipeChannelType) { return TCL_OK; } /* * Extract the process IDs from the pipe structure. */ pipePtr = (PipeState *)Tcl_GetChannelInstanceData(chan); TclNewObj(resultPtr); for (i = 0; i < pipePtr->numPids; i++) { Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewWideIntObj(TclpGetPid(pipePtr->pidPtr[i]))); } Tcl_SetObjResult(interp, resultPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclpFinalizePipes -- * * Cleans up the pipe subsystem from Tcl_FinalizeThread * * Results: * None. * * Notes: * This function carries out no operation on Unix. * *---------------------------------------------------------------------- */ void TclpFinalizePipes(void) { } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclUnixSock.c0000644000175000017500000014741214726623136015164 0ustar sergeisergei/* * tclUnixSock.c -- * * This file contains Unix-specific socket related code. * * Copyright © 1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include /* * Helper macros to make parts of this file clearer. The macros do exactly * what they say on the tin. :-) They also only ever refer to their arguments * once, and so can be used without regard to side effects. */ #define SET_BITS(var, bits) ((var) |= (bits)) #define CLEAR_BITS(var, bits) ((var) &= ~(bits)) #define GOT_BITS(var, bits) (((var) & (bits)) != 0) /* "sock" + a pointer in hex + \0 */ #define SOCK_CHAN_LENGTH (4 + sizeof(void *) * 2 + 1) #define SOCK_TEMPLATE "sock%" TCL_Z_MODIFIER "x" #undef SOCKET /* Possible conflict with win32 SOCKET */ /* * This is needed to comply with the strict aliasing rules of GCC, but it also * simplifies casting between the different sockaddr types. */ typedef union { struct sockaddr sa; struct sockaddr_in sa4; struct sockaddr_in6 sa6; struct sockaddr_storage sas; } address; /* * This structure describes per-instance state of a tcp-based channel. */ typedef struct TcpState TcpState; typedef struct TcpFdList { TcpState *statePtr; int fd; struct TcpFdList *next; } TcpFdList; struct TcpState { Tcl_Channel channel; /* Channel associated with this file. */ int flags; /* OR'ed combination of the bitfields defined * below. */ TcpFdList fds; /* The file descriptors of the sockets. */ int interest; /* Event types of interest */ /* * Only needed for server sockets */ Tcl_TcpAcceptProc *acceptProc; /* Proc to call on accept. */ void *acceptProcData; /* The data for the accept proc. */ /* * Only needed for client sockets */ struct addrinfo *addrlist; /* Addresses to connect to. */ struct addrinfo *addr; /* Iterator over addrlist. */ struct addrinfo *myaddrlist;/* Local address. */ struct addrinfo *myaddr; /* Iterator over myaddrlist. */ int filehandlers; /* Caches FileHandlers that get set up while * an async socket is not yet connected. */ int connectError; /* Cache SO_ERROR of async socket. */ int cachedBlocking; /* Cache blocking mode of async socket. */ }; /* * These bits may be OR'ed together into the "flags" field of a TcpState * structure. */ #define TCP_NONBLOCKING (1<<0) /* Socket with non-blocking I/O */ #define TCP_ASYNC_CONNECT (1<<1) /* Async connect in progress. */ #define TCP_ASYNC_PENDING (1<<4) /* TcpConnect was called to * process an async connect. This * flag indicates that reentry is * still pending */ #define TCP_ASYNC_FAILED (1<<5) /* An async connect finally failed */ #define TCP_ASYNC_TEST_MODE (1<<8) /* Async testing activated. Do not * automatically continue connection * process. */ /* * The following defines the maximum length of the listen queue. This is the * number of outstanding yet-to-be-serviced requests for a connection on a * server socket, more than this number of outstanding requests and the * connection request will fail. */ #ifndef SOMAXCONN # define SOMAXCONN 100 #elif (SOMAXCONN < 100) # undef SOMAXCONN # define SOMAXCONN 100 #endif /* SOMAXCONN < 100 */ /* * The following defines how much buffer space the kernel should maintain for * a socket. */ #define SOCKET_BUFSIZE 4096 /* * Static routines for this file: */ static void TcpAsyncCallback(void *clientData, int mask); static int TcpConnect(Tcl_Interp *interp, TcpState *state); static void TcpAccept(void *data, int mask); static int TcpBlockModeProc(void *data, int mode); static int TcpCloseProc(void *instanceData, Tcl_Interp *interp); static int TcpClose2Proc(void *instanceData, Tcl_Interp *interp, int flags); static int TcpGetHandleProc(void *instanceData, int direction, void **handlePtr); static int TcpGetOptionProc(void *instanceData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr); static int TcpInputProc(void *instanceData, char *buf, int toRead, int *errorCode); static int TcpOutputProc(void *instanceData, const char *buf, int toWrite, int *errorCode); static int TcpSetOptionProc(void *instanceData, Tcl_Interp *interp, const char *optionName, const char *value); static void TcpThreadActionProc(void *instanceData, int action); static void TcpWatchProc(void *instanceData, int mask); static int WaitForConnect(TcpState *statePtr, int *errorCodePtr); static Tcl_FileProc WrapNotify; /* * This structure describes the channel type structure for TCP socket * based IO: */ static const Tcl_ChannelType tcpChannelType = { "tcp", TCL_CHANNEL_VERSION_5, NULL, /* Deprecated. */ TcpInputProc, TcpOutputProc, NULL, /* Deprecated. */ TcpSetOptionProc, TcpGetOptionProc, TcpWatchProc, TcpGetHandleProc, TcpClose2Proc, TcpBlockModeProc, NULL, /* Flush proc. */ NULL, /* Bubbled event handler proc. */ NULL, /* Seek proc. */ TcpThreadActionProc, NULL /* Truncate proc. */ }; /* * The following variable holds the network name of this host. */ static TclInitProcessGlobalValueProc InitializeHostName; static ProcessGlobalValue hostName = {0, 0, NULL, NULL, InitializeHostName, NULL, NULL}; #if 0 /* printf debugging */ void printaddrinfo( struct addrinfo *addrlist, char *prefix) { char host[NI_MAXHOST], port[NI_MAXSERV]; struct addrinfo *ai; for (ai = addrlist; ai != NULL; ai = ai->ai_next) { getnameinfo(ai->ai_addr, ai->ai_addrlen, host, sizeof(host), port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV); fprintf(stderr,"%s: %s:%s\n", prefix, host, port); } } #endif /* * ---------------------------------------------------------------------- * * InitializeHostName -- * * This routine sets the process global value of the name of the local * host on which the process is running. * * Results: * None. * * ---------------------------------------------------------------------- */ static void InitializeHostName( char **valuePtr, size_t *lengthPtr, Tcl_Encoding *encodingPtr) { const char *native = NULL; #ifndef NO_UNAME struct utsname u; struct hostent *hp; memset(&u, (int) 0, sizeof(struct utsname)); if (uname(&u) >= 0) { /* INTL: Native. */ hp = TclpGetHostByName(u.nodename); /* INTL: Native. */ if (hp == NULL) { /* * Sometimes the nodename is fully qualified, but gets truncated * as it exceeds SYS_NMLN. See if we can just get the immediate * nodename and get a proper answer that way. */ char *dot = strchr(u.nodename, '.'); if (dot != NULL) { char *node = (char *)Tcl_Alloc(dot - u.nodename + 1); memcpy(node, u.nodename, dot - u.nodename); node[dot - u.nodename] = '\0'; hp = TclpGetHostByName(node); Tcl_Free(node); } } if (hp != NULL) { native = hp->h_name; } else { native = u.nodename; } } #else /* !NO_UNAME */ /* * Uname doesn't exist; try gethostname instead. * * There is no portable macro for the maximum length of host names * returned by gethostbyname(). We should only trust SYS_NMLN if it is at * least 255 + 1 bytes to comply with DNS host name limits. * * Note: SYS_NMLN is a restriction on "uname" not on gethostbyname! * * For example HP-UX 10.20 has SYS_NMLN == 9, while gethostbyname() can * return a fully qualified name from DNS of up to 255 bytes. * * Fix suggested by Viktor Dukhovni (viktor@esm.com) */ # if defined(SYS_NMLN) && (SYS_NMLEN >= 256) char buffer[SYS_NMLEN]; # else char buffer[256]; # endif if (gethostname(buffer, sizeof(buffer)) >= 0) { /* INTL: Native. */ native = buffer; } #endif /* NO_UNAME */ *encodingPtr = Tcl_GetEncoding(NULL, NULL); if (native) { *lengthPtr = strlen(native); *valuePtr = (char *)Tcl_Alloc(*lengthPtr + 1); memcpy(*valuePtr, native, *lengthPtr + 1); } else { *lengthPtr = 0; *valuePtr = (char *)Tcl_Alloc(1); *valuePtr[0] = '\0'; } } /* * ---------------------------------------------------------------------- * * Tcl_GetHostName -- * * Returns the name of the local host. * * Results: * A string containing the network name for this machine, or an empty * string if we can't figure out the name. The caller must not modify or * free this string. * * Side effects: * Caches the name to return for future calls. * * ---------------------------------------------------------------------- */ const char * Tcl_GetHostName(void) { Tcl_Obj *tclObj = TclGetProcessGlobalValue(&hostName); return TclGetString(tclObj); } /* * ---------------------------------------------------------------------- * * TclpFinalizeSockets -- * * Performs per-thread socket subsystem finalization. * * Results: * None. * * Side effects: * None. * * ---------------------------------------------------------------------- */ void TclpFinalizeSockets(void) { return; } /* * ---------------------------------------------------------------------- * * TcpBlockModeProc -- * * This function is invoked by the generic IO level to set blocking and * nonblocking mode on a TCP socket based channel. * * Results: * 0 if successful, errno when failed. * * Side effects: * Sets the device into blocking or nonblocking mode. * * ---------------------------------------------------------------------- */ static int TcpBlockModeProc( void *instanceData, /* Socket state. */ int mode) /* The mode to set. Can be one of * TCL_MODE_BLOCKING or * TCL_MODE_NONBLOCKING. */ { TcpState *statePtr = (TcpState *)instanceData; if (mode == TCL_MODE_BLOCKING) { CLEAR_BITS(statePtr->flags, TCP_NONBLOCKING); } else { SET_BITS(statePtr->flags, TCP_NONBLOCKING); } if (GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT)) { statePtr->cachedBlocking = mode; return 0; } if (TclUnixSetBlockingMode(statePtr->fds.fd, mode) < 0) { return errno; } return 0; } /* * ---------------------------------------------------------------------- * * WaitForConnect -- * * Check the state of an async connect process. If a connection attempt * terminated, process it, which may finalize it or may start the next * attempt. If a connect error occurs, it is saved in * statePtr->connectError to be reported by 'fconfigure -error'. * * There are two modes of operation, defined by errorCodePtr: * * non-NULL: Called by explicit read/write command. Blocks if the * socket is blocking. * May return two error codes: * * EWOULDBLOCK: if connect is still in progress * * ENOTCONN: if connect failed. This would be the error message * of a recv or sendto syscall so this is emulated here. * * NULL: Called by a background operation. Do not block and do not * return any error code. * * Results: * 0 if the connection has completed, -1 if still in progress or there is * an error. * * Side effects: * Processes socket events off the system queue. May process * asynchronous connects. * *---------------------------------------------------------------------- */ static int WaitForConnect( TcpState *statePtr, /* State of the socket. */ int *errorCodePtr) { int timeout; /* * Check if an async connect failed already and error reporting is * demanded, return the error ENOTCONN */ if (errorCodePtr != NULL && GOT_BITS(statePtr->flags, TCP_ASYNC_FAILED)) { *errorCodePtr = ENOTCONN; return -1; } /* * Check if an async connect is running. If not return ok. */ if (!GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING)) { return 0; } /* * In socket test mode do not continue with the connect. * Exceptions are: * - Call by recv/send and blocking socket * (errorCodePtr != NULL && !GOT_BITS(flags, TCP_NONBLOCKING)) */ if (GOT_BITS(statePtr->flags, TCP_ASYNC_TEST_MODE) && !(errorCodePtr != NULL && !GOT_BITS(statePtr->flags, TCP_NONBLOCKING))) { *errorCodePtr = EWOULDBLOCK; return -1; } if (errorCodePtr == NULL || GOT_BITS(statePtr->flags, TCP_NONBLOCKING)) { timeout = 0; } else { timeout = -1; } do { if (TclUnixWaitForFile(statePtr->fds.fd, TCL_WRITABLE | TCL_EXCEPTION, timeout) != 0) { TcpConnect(NULL, statePtr); } /* * Do this only once in the nonblocking case and repeat it until the * socket is final when blocking. */ } while (timeout == -1 && GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT)); if (errorCodePtr != NULL) { if (GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING)) { *errorCodePtr = EAGAIN; return -1; } else if (statePtr->connectError != 0) { *errorCodePtr = ENOTCONN; return -1; } } return 0; } /* *---------------------------------------------------------------------- * * TcpInputProc -- * * This function is invoked by the generic IO level to read input from a * TCP socket based channel. * * NOTE: We cannot share code with FilePipeInputProc because here we must * use recv to obtain the input from the channel, not read. * * Results: * The number of bytes read is returned or -1 on error. An output * argument contains the POSIX error code on error, or zero if no error * occurred. * * Side effects: * Reads input from the input device of the channel. * *---------------------------------------------------------------------- */ static int TcpInputProc( void *instanceData, /* Socket state. */ char *buf, /* Where to store data read. */ int bufSize, /* How much space is available in the * buffer? */ int *errorCodePtr) /* Where to store error code. */ { TcpState *statePtr = (TcpState *)instanceData; int bytesRead; *errorCodePtr = 0; if (WaitForConnect(statePtr, errorCodePtr) != 0) { return -1; } bytesRead = recv(statePtr->fds.fd, buf, bufSize, 0); if (bytesRead >= 0) { return bytesRead; } if (errno == ECONNRESET) { /* * Turn ECONNRESET into a soft EOF condition. */ return 0; } *errorCodePtr = errno; return -1; } /* *---------------------------------------------------------------------- * * TcpOutputProc -- * * This function is invoked by the generic IO level to write output to a * TCP socket based channel. * * NOTE: We cannot share code with FilePipeOutputProc because here we * must use send, not write, to get reliable error reporting. * * Results: * The number of bytes written is returned. An output argument is set to * a POSIX error code if an error occurred, or zero. * * Side effects: * Writes output on the output device of the channel. * *---------------------------------------------------------------------- */ static int TcpOutputProc( void *instanceData, /* Socket state. */ const char *buf, /* The data buffer. */ int toWrite, /* How many bytes to write? */ int *errorCodePtr) /* Where to store error code. */ { TcpState *statePtr = (TcpState *)instanceData; int written; *errorCodePtr = 0; if (WaitForConnect(statePtr, errorCodePtr) != 0) { return -1; } written = send(statePtr->fds.fd, buf, toWrite, 0); if (written >= 0) { return written; } *errorCodePtr = errno; return -1; } /* *---------------------------------------------------------------------- * * TcpCloseProc -- * * This function is invoked by the generic IO level to perform * channel-type-specific cleanup when a TCP socket based channel is * closed. * * Results: * 0 if successful, the value of errno if failed. * * Side effects: * Closes the socket of the channel. * *---------------------------------------------------------------------- */ static int TcpCloseProc( void *instanceData, /* The socket to close. */ TCL_UNUSED(Tcl_Interp *)) { TcpState *statePtr = (TcpState *)instanceData; int errorCode = 0; TcpFdList *fds; /* * Delete a file handler that may be active for this socket if this is a * server socket - the file handler was created automatically by Tcl as * part of the mechanism to accept new client connections. Channel * handlers are already deleted in the generic IO channel closing code * that called this function, so we do not have to delete them here. */ for (fds = &statePtr->fds; fds != NULL; fds = fds->next) { if (fds->fd < 0) { continue; } Tcl_DeleteFileHandler(fds->fd); if (close(fds->fd) < 0) { errorCode = errno; } } fds = statePtr->fds.next; while (fds != NULL) { TcpFdList *next = fds->next; Tcl_Free(fds); fds = next; } if (statePtr->addrlist != NULL) { freeaddrinfo(statePtr->addrlist); } if (statePtr->myaddrlist != NULL) { freeaddrinfo(statePtr->myaddrlist); } Tcl_Free(statePtr); return errorCode; } /* *---------------------------------------------------------------------- * * TcpClose2Proc -- * * This function is called by the generic IO level to perform the channel * type specific part of a half-close: namely, a shutdown() on a socket. * * Results: * 0 if successful, the value of errno if failed. * * Side effects: * Shuts down one side of the socket. * *---------------------------------------------------------------------- */ static int TcpClose2Proc( void *instanceData, /* The socket to close. */ TCL_UNUSED(Tcl_Interp *), int flags) /* Flags that indicate which side to close. */ { TcpState *statePtr = (TcpState *)instanceData; int readError = 0; int writeError = 0; /* * Shutdown the OS socket handle. */ if ((flags & (TCL_CLOSE_READ|TCL_CLOSE_WRITE)) == 0) { return TcpCloseProc(instanceData, NULL); } if ((flags & TCL_CLOSE_READ) && (shutdown(statePtr->fds.fd, SHUT_RD) < 0)) { readError = errno; } if ((flags & TCL_CLOSE_WRITE) && (shutdown(statePtr->fds.fd, SHUT_WR) < 0)) { writeError = errno; } return (readError != 0) ? readError : writeError; } /* *---------------------------------------------------------------------- * * TcpHostPortList -- * * This function is called by the -gethostname and -getpeername switches * of TcpGetOptionProc() to add three list elements with the textual * representation of the given address to the given DString. * * Results: * None. * * Side effects: * Adds three elements do dsPtr * *---------------------------------------------------------------------- */ #ifndef NEED_FAKE_RFC2553 #if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstrict-aliasing" #endif static inline int IPv6AddressNeedsNumericRendering( struct in6_addr addr) { if (IN6_ARE_ADDR_EQUAL(&addr, &in6addr_any)) { return 1; } /* * The IN6_IS_ADDR_V4MAPPED macro has a problem with aliasing warnings on * at least some versions of OSX. */ if (!IN6_IS_ADDR_V4MAPPED(&addr)) { return 0; } return (addr.s6_addr[12] == 0 && addr.s6_addr[13] == 0 && addr.s6_addr[14] == 0 && addr.s6_addr[15] == 0); } #if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) #pragma GCC diagnostic pop #endif #endif /* NEED_FAKE_RFC2553 */ static void TcpHostPortList( Tcl_Interp *interp, Tcl_DString *dsPtr, address addr, socklen_t salen) { #define SUPPRESS_RDNS_VAR "::tcl::unsupported::noReverseDNS" char host[NI_MAXHOST], nhost[NI_MAXHOST], nport[NI_MAXSERV]; int flags = 0; getnameinfo(&addr.sa, salen, nhost, sizeof(nhost), nport, sizeof(nport), NI_NUMERICHOST | NI_NUMERICSERV); Tcl_DStringAppendElement(dsPtr, nhost); /* * We don't want to resolve INADDR_ANY and sin6addr_any; they can * sometimes cause problems (and never have a name). */ if (addr.sa.sa_family == AF_INET) { if (addr.sa4.sin_addr.s_addr == INADDR_ANY) { flags |= NI_NUMERICHOST; } #ifndef NEED_FAKE_RFC2553 } else if (addr.sa.sa_family == AF_INET6) { if (IPv6AddressNeedsNumericRendering(addr.sa6.sin6_addr)) { flags |= NI_NUMERICHOST; } #endif /* NEED_FAKE_RFC2553 */ } /* * Check if reverse DNS has been switched off globally. */ if (interp != NULL && Tcl_GetVar2(interp, SUPPRESS_RDNS_VAR, NULL, 0) != NULL) { flags |= NI_NUMERICHOST; } if (getnameinfo(&addr.sa, salen, host, sizeof(host), NULL, 0, flags) == 0) { /* * Reverse mapping worked. */ Tcl_DStringAppendElement(dsPtr, host); } else { /* * Reverse mapping failed - use the numeric rep once more. */ Tcl_DStringAppendElement(dsPtr, nhost); } Tcl_DStringAppendElement(dsPtr, nport); } /* *---------------------------------------------------------------------- * * TcpSetOptionProc -- * * Sets TCP channel specific options. * * Results: * None, unless an error happens. * * Side effects: * Changes attributes of the socket at the system level. * *---------------------------------------------------------------------- */ static int TcpSetOptionProc( void *instanceData, /* Socket state. */ Tcl_Interp *interp, /* For error reporting - can be NULL. */ const char *optionName, /* Name of the option to set. */ const char *value) /* New value for option. */ { TcpState *statePtr = (TcpState *)instanceData; size_t len = 0; if (optionName != NULL) { len = strlen(optionName); } if ((len > 1) && (optionName[1] == 'k') && (strncmp(optionName, "-keepalive", len) == 0)) { int val = 0, ret; if (Tcl_GetBoolean(interp, value, &val) != TCL_OK) { return TCL_ERROR; } #if defined(SO_KEEPALIVE) ret = setsockopt(statePtr->fds.fd, SOL_SOCKET, SO_KEEPALIVE, (const char *) &val, sizeof(int)); #else ret = -1; Tcl_SetErrno(ENOTSUP); #endif if (ret < 0) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't set socket option: %s", Tcl_PosixError(interp))); } return TCL_ERROR; } return TCL_OK; } if ((len > 1) && (optionName[1] == 'n') && (strncmp(optionName, "-nodelay", len) == 0)) { int val = 0, ret; if (Tcl_GetBoolean(interp, value, &val) != TCL_OK) { return TCL_ERROR; } #if defined(SOL_TCP) && defined(TCP_NODELAY) ret = setsockopt(statePtr->fds.fd, SOL_TCP, TCP_NODELAY, (const char *) &val, sizeof(int)); #else ret = -1; Tcl_SetErrno(ENOTSUP); #endif if (ret < 0) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't set socket option: %s", Tcl_PosixError(interp))); } return TCL_ERROR; } return TCL_OK; } return Tcl_BadChannelOption(interp, optionName, "keepalive nodelay"); } /* *---------------------------------------------------------------------- * * TcpGetOptionProc -- * * Computes an option value for a TCP socket based channel, or a list of * all options and their values. * * Note: This code is based on code contributed by John Haxby. * * Results: * A standard Tcl result. The value of the specified option or a list of * all options and their values is returned in the supplied DString. Sets * Error message if needed. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TcpGetOptionProc( void *instanceData, /* Socket state. */ Tcl_Interp *interp, /* For error reporting - can be NULL. */ const char *optionName, /* Name of the option to retrieve the value * for, or NULL to get all options and their * values. */ Tcl_DString *dsPtr) /* Where to store the computed value; * initialized by caller. */ { TcpState *statePtr = (TcpState *)instanceData; size_t len = 0; if (optionName != NULL) { len = strlen(optionName); } if ((len > 1) && (optionName[1] == 'e') && (strncmp(optionName, "-error", len) == 0)) { socklen_t optlen = sizeof(int); WaitForConnect(statePtr, NULL); if (GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT)) { /* * Suppress errors as long as we are not done. */ errno = 0; } else if (statePtr->connectError != 0) { errno = statePtr->connectError; statePtr->connectError = 0; } else { int err; getsockopt(statePtr->fds.fd, SOL_SOCKET, SO_ERROR, (char *) &err, &optlen); errno = err; } if (errno != 0) { Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(errno), TCL_INDEX_NONE); } return TCL_OK; } if ((len > 1) && (optionName[1] == 'c') && (strncmp(optionName, "-connecting", len) == 0)) { WaitForConnect(statePtr, NULL); Tcl_DStringAppend(dsPtr, GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT) ? "1" : "0", TCL_INDEX_NONE); return TCL_OK; } if ((len == 0) || ((len > 1) && (optionName[1] == 'p') && (strncmp(optionName, "-peername", len) == 0))) { address peername; socklen_t size = sizeof(peername); WaitForConnect(statePtr, NULL); if (GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT)) { /* * In async connect output an empty string */ if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-peername"); Tcl_DStringAppendElement(dsPtr, ""); } else { return TCL_OK; } } else if (getpeername(statePtr->fds.fd, &peername.sa, &size) >= 0) { /* * Peername fetch succeeded - output list */ if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-peername"); Tcl_DStringStartSublist(dsPtr); } TcpHostPortList(interp, dsPtr, peername, size); if (len) { return TCL_OK; } Tcl_DStringEndSublist(dsPtr); } else { /* * getpeername failed - but if we were asked for all the options * (len==0), don't flag an error at that point because it could be * an fconfigure request on a server socket (which have no peer). * Same must be done on win&mac. */ if (len) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't get peername: %s", Tcl_PosixError(interp))); } return TCL_ERROR; } } } if ((len == 0) || ((len > 1) && (optionName[1] == 's') && (strncmp(optionName, "-sockname", len) == 0))) { TcpFdList *fds; address sockname; socklen_t size; int found = 0; WaitForConnect(statePtr, NULL); if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-sockname"); Tcl_DStringStartSublist(dsPtr); } if (GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT)) { /* * In async connect output an empty string */ found = 1; } else { for (fds = &statePtr->fds; fds != NULL; fds = fds->next) { size = sizeof(sockname); if (getsockname(fds->fd, &(sockname.sa), &size) >= 0) { found = 1; TcpHostPortList(interp, dsPtr, sockname, size); } } } if (found) { if (len) { return TCL_OK; } Tcl_DStringEndSublist(dsPtr); } else { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't get sockname: %s", Tcl_PosixError(interp))); } return TCL_ERROR; } } if ((len == 0) || ((len > 1) && (optionName[1] == 'k') && (strncmp(optionName, "-keepalive", len) == 0))) { int opt = 0; #if defined(SO_KEEPALIVE) socklen_t size = sizeof(opt); #endif if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-keepalive"); } #if defined(SO_KEEPALIVE) getsockopt(statePtr->fds.fd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, &size); #endif Tcl_DStringAppendElement(dsPtr, opt ? "1" : "0"); if (len > 0) { return TCL_OK; } } if ((len == 0) || ((len > 1) && (optionName[1] == 'n') && (strncmp(optionName, "-nodelay", len) == 0))) { int opt = 0; #if defined(SOL_TCP) && defined(TCP_NODELAY) socklen_t size = sizeof(opt); #endif if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-nodelay"); } #if defined(SOL_TCP) && defined(TCP_NODELAY) getsockopt(statePtr->fds.fd, SOL_TCP, TCP_NODELAY, (char *) &opt, &size); #endif Tcl_DStringAppendElement(dsPtr, opt ? "1" : "0"); if (len > 0) { return TCL_OK; } } if (len > 0) { return Tcl_BadChannelOption(interp, optionName, "connecting keepalive nodelay peername sockname"); } return TCL_OK; } /* * ---------------------------------------------------------------------- * * TcpThreadActionProc -- * * Handles detach/attach for asynchronously connecting socket. * * Reassigning the file handler associated with thread-related channel * notification, responsible for callbacks (signaling that asynchronous * connection attempt has succeeded or failed). * * Results: * None. * * ---------------------------------------------------------------------- */ static void TcpThreadActionProc( void *instanceData, int action) { TcpState *statePtr = (TcpState *)instanceData; if (GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT)) { /* * Async-connecting socket must get reassigned handler if it have been * transferred to another thread. Remove the handler if the socket is * not managed by this thread anymore and create new handler (TSD related) * so the callback will run in the correct thread, bug [f583715154]. */ switch (action) { case TCL_CHANNEL_THREAD_REMOVE: CLEAR_BITS(statePtr->flags, TCP_ASYNC_PENDING); Tcl_DeleteFileHandler(statePtr->fds.fd); break; case TCL_CHANNEL_THREAD_INSERT: Tcl_CreateFileHandler(statePtr->fds.fd, TCL_WRITABLE | TCL_EXCEPTION, TcpAsyncCallback, statePtr); SET_BITS(statePtr->flags, TCP_ASYNC_PENDING); break; } } } /* * ---------------------------------------------------------------------- * * TcpWatchProc -- * * Initialize the notifier to watch the fd from this channel. * * Results: * None. * * Side effects: * Sets up the notifier so that a future event on the channel will be * seen by Tcl. * * ---------------------------------------------------------------------- */ static void WrapNotify( void *clientData, int mask) { TcpState *statePtr = (TcpState *) clientData; int newmask = mask & statePtr->interest; if (newmask == 0) { /* * There was no overlap between the states the channel is interested * in notifications for, and the states that are reported present on * the file descriptor by select(). The only way that can happen is * when the channel is interested in a writable condition, and only a * readable state is reported present (see TcpWatchProc() below). In * that case, signal back to the caller the writable state, which is * really an error condition. As an extra check on that assumption, * check for a non-zero value of errno before reporting an artificial * writable state. */ if (errno == 0) { return; } newmask = TCL_WRITABLE; } Tcl_NotifyChannel(statePtr->channel, newmask); } static void TcpWatchProc( void *instanceData, /* The socket state. */ int mask) /* Events of interest; an OR-ed combination of * TCL_READABLE, TCL_WRITABLE and * TCL_EXCEPTION. */ { TcpState *statePtr = (TcpState *)instanceData; if (statePtr->acceptProc != NULL) { /* * Make sure we don't mess with server sockets since they will never * be readable or writable at the Tcl level. This keeps Tcl scripts * from interfering with the -accept behavior (bug #3394732). */ return; } if (GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING)) { /* * Async sockets use a FileHandler internally while connecting, so we * need to cache this request until the connection has succeeded. */ statePtr->filehandlers = mask; } else if (mask) { /* * Whether it is a bug or feature or otherwise, it is a fact of life * that on at least some Linux kernels select() fails to report that a * socket file descriptor is writable when the other end of the socket * is closed. This is in contrast to the guarantees Tcl makes that * its channels become writable and fire writable events on an error * condition. This has caused a leak of file descriptors in a state of * background flushing. See Tcl ticket 1758a0b603. * * As a workaround, when our caller indicates an interest in writable * notifications, we must tell the notifier built around select() that * we are interested in the readable state of the file descriptor as * well, as that is the only reliable means to get notified of error * conditions. Then it is the task of WrapNotify() above to untangle * the meaning of these channel states and report the chan events as * best it can. We save a copy of the mask passed in to assist with * that. */ statePtr->interest = mask; Tcl_CreateFileHandler(statePtr->fds.fd, mask|TCL_READABLE, WrapNotify, statePtr); } else { Tcl_DeleteFileHandler(statePtr->fds.fd); } } /* * ---------------------------------------------------------------------- * * TcpGetHandleProc -- * * Called from Tcl_GetChannelHandle to retrieve OS handles from inside a * TCP socket based channel. * * Results: * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no * handle for the specified direction. * * Side effects: * None. * * ---------------------------------------------------------------------- */ static int TcpGetHandleProc( void *instanceData, /* The socket state. */ TCL_UNUSED(int) /*direction*/, void **handlePtr) /* Where to store the handle. */ { TcpState *statePtr = (TcpState *)instanceData; *handlePtr = INT2PTR(statePtr->fds.fd); return TCL_OK; } /* * ---------------------------------------------------------------------- * * TcpAsyncCallback -- * * Called by the event handler that TcpConnect sets up internally for * [socket -async] to get notified when the asynchronous connection * attempt has succeeded or failed. * * ---------------------------------------------------------------------- */ static void TcpAsyncCallback( void *clientData, /* The socket state. */ TCL_UNUSED(int) /*mask*/) { TcpConnect(NULL, (TcpState *)clientData); } /* * ---------------------------------------------------------------------- * * TcpConnect -- * * This function opens a new socket in client mode. * * Results: * TCL_OK, if the socket was successfully connected or an asynchronous * connection is in progress. If an error occurs, TCL_ERROR is returned * and an error message is left in interp. * * Side effects: * Opens a socket. * * Remarks: * A single host name may resolve to more than one IP address, e.g. for * an IPv4/IPv6 dual stack host. For handling asynchronously connecting * sockets in the background for such hosts, this function can act as a * coroutine. On the first call, it sets up the control variables for the * two nested loops over the local and remote addresses. Once the first * connection attempt is in progress, it sets up itself as a writable * event handler for that socket, and returns. When the callback occurs, * control is transferred to the "reenter" label, right after the initial * return and the loops resume as if they had never been interrupted. * For synchronously connecting sockets, the loops work the usual way. * * ---------------------------------------------------------------------- */ static int TcpConnect( Tcl_Interp *interp, /* For error reporting; can be NULL. */ TcpState *statePtr) { socklen_t optlen; int async_callback = GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING); int ret = -1, error = EHOSTUNREACH; int async = GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT); static const int reuseaddr = 1; if (async_callback) { goto reenter; } for (statePtr->addr = statePtr->addrlist; statePtr->addr != NULL; statePtr->addr = statePtr->addr->ai_next) { for (statePtr->myaddr = statePtr->myaddrlist; statePtr->myaddr != NULL; statePtr->myaddr = statePtr->myaddr->ai_next) { /* * No need to try combinations of local and remote addresses of * different families. */ if (statePtr->myaddr->ai_family != statePtr->addr->ai_family) { continue; } /* * Close the socket if it is still open from the last unsuccessful * iteration. */ if (statePtr->fds.fd >= 0) { close(statePtr->fds.fd); statePtr->fds.fd = -1; errno = 0; } statePtr->fds.fd = socket(statePtr->addr->ai_family, SOCK_STREAM, 0); if (statePtr->fds.fd < 0) { continue; } /* * Set the close-on-exec flag so that the socket will not get * inherited by child processes. */ fcntl(statePtr->fds.fd, F_SETFD, FD_CLOEXEC); /* * Set kernel space buffering */ TclSockMinimumBuffers(INT2PTR(statePtr->fds.fd), SOCKET_BUFSIZE); if (async) { ret = TclUnixSetBlockingMode(statePtr->fds.fd, TCL_MODE_NONBLOCKING); if (ret < 0) { continue; } } /* * Must reset the error variable here, before we use it for the * first time in this iteration. */ error = 0; (void) setsockopt(statePtr->fds.fd, SOL_SOCKET, SO_REUSEADDR, (char *) &reuseaddr, sizeof(reuseaddr)); ret = bind(statePtr->fds.fd, statePtr->myaddr->ai_addr, statePtr->myaddr->ai_addrlen); if (ret < 0) { error = errno; continue; } /* * Attempt to connect. The connect may fail at present with an * EINPROGRESS but at a later time it will complete. The caller * will set up a file handler on the socket if she is interested * in being informed when the connect completes. */ ret = connect(statePtr->fds.fd, statePtr->addr->ai_addr, statePtr->addr->ai_addrlen); if (ret < 0) { error = errno; } if (ret < 0 && errno == EINPROGRESS) { Tcl_CreateFileHandler(statePtr->fds.fd, TCL_WRITABLE | TCL_EXCEPTION, TcpAsyncCallback, statePtr); errno = EWOULDBLOCK; SET_BITS(statePtr->flags, TCP_ASYNC_PENDING); return TCL_OK; reenter: CLEAR_BITS(statePtr->flags, TCP_ASYNC_PENDING); Tcl_DeleteFileHandler(statePtr->fds.fd); /* * Read the error state from the socket to see if the async * connection has succeeded or failed. As this clears the * error condition, we cache the status in the socket state * struct for later retrieval by [fconfigure -error]. */ optlen = sizeof(int); getsockopt(statePtr->fds.fd, SOL_SOCKET, SO_ERROR, (char *) &error, &optlen); errno = error; } if (error == 0) { goto out; } } } out: statePtr->connectError = error; CLEAR_BITS(statePtr->flags, TCP_ASYNC_CONNECT); if (async_callback) { /* * An asynchonous connection has finally succeeded or failed. */ TcpWatchProc(statePtr, statePtr->filehandlers); TclUnixSetBlockingMode(statePtr->fds.fd, statePtr->cachedBlocking); if (error != 0) { SET_BITS(statePtr->flags, TCP_ASYNC_FAILED); } /* * We need to forward the writable event that brought us here, because * upon reading of getsockopt(SO_ERROR), at least some OSes clear the * writable state from the socket, and so a subsequent select() on * behalf of a script level [fileevent] would not fire. It doesn't * hurt that this is also called in the successful case and will save * the event mechanism one roundtrip through select(). */ if (statePtr->cachedBlocking == TCL_MODE_NONBLOCKING) { Tcl_NotifyChannel(statePtr->channel, TCL_WRITABLE); } } if (error != 0) { /* * Failure for either a synchronous connection, or an async one that * failed before it could enter background mode, e.g. because an * invalid -myaddr was given. */ if (interp != NULL) { errno = error; Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't open socket: %s", Tcl_PosixError(interp))); } return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_OpenTcpClient -- * * Opens a TCP client socket and creates a channel around it. * * Results: * The channel or NULL if failed. An error message is returned in the * interpreter on failure. * * Side effects: * Opens a client socket and creates a new channel. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_OpenTcpClient( Tcl_Interp *interp, /* For error reporting; can be NULL. */ int port, /* Port number to open. */ const char *host, /* Host on which to open port. */ const char *myaddr, /* Client-side address */ int myport, /* Client-side port */ int async) /* If nonzero, attempt to do an asynchronous * connect. Otherwise we do a blocking * connect. */ { TcpState *statePtr; const char *errorMsg = NULL; struct addrinfo *addrlist = NULL, *myaddrlist = NULL; char channelName[SOCK_CHAN_LENGTH]; /* * Do the name lookups for the local and remote addresses. */ if (!TclCreateSocketAddress(interp, &addrlist, host, port, 0, &errorMsg) || !TclCreateSocketAddress(interp, &myaddrlist, myaddr, myport, 1, &errorMsg)) { if (addrlist != NULL) { freeaddrinfo(addrlist); } if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't open socket: %s", errorMsg)); } return NULL; } /* * Allocate a new TcpState for this socket. */ statePtr = (TcpState *)Tcl_Alloc(sizeof(TcpState)); memset(statePtr, 0, sizeof(TcpState)); statePtr->flags = async ? TCP_ASYNC_CONNECT : 0; statePtr->cachedBlocking = TCL_MODE_BLOCKING; statePtr->addrlist = addrlist; statePtr->myaddrlist = myaddrlist; statePtr->fds.fd = -1; /* * Create a new client socket and wrap it in a channel. */ if (TcpConnect(interp, statePtr) != TCL_OK) { TcpCloseProc(statePtr, NULL); return NULL; } snprintf(channelName, sizeof(channelName), SOCK_TEMPLATE, PTR2INT(statePtr)); statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, statePtr, TCL_READABLE | TCL_WRITABLE); if (Tcl_SetChannelOption(interp, statePtr->channel, "-translation", "auto crlf") == TCL_ERROR) { Tcl_CloseEx(NULL, statePtr->channel, 0); return NULL; } return statePtr->channel; } /* *---------------------------------------------------------------------- * * Tcl_MakeTcpClientChannel -- * * Creates a Tcl_Channel from an existing client TCP socket. * * Results: * The Tcl_Channel wrapped around the preexisting TCP socket. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_MakeTcpClientChannel( void *sock) /* The socket to wrap up into a channel. */ { return (Tcl_Channel) TclpMakeTcpClientChannelMode(sock, TCL_READABLE | TCL_WRITABLE); } /* *---------------------------------------------------------------------- * * TclpMakeTcpClientChannelMode -- * * Creates a Tcl_Channel from an existing client TCP socket * with given mode. * * Results: * The Tcl_Channel wrapped around the preexisting TCP socket. * * Side effects: * None. * *---------------------------------------------------------------------- */ void * TclpMakeTcpClientChannelMode( void *sock, /* The socket to wrap up into a channel. */ int mode) /* OR'ed combination of TCL_READABLE and * TCL_WRITABLE to indicate file mode. */ { TcpState *statePtr; char channelName[SOCK_CHAN_LENGTH]; statePtr = (TcpState *)Tcl_Alloc(sizeof(TcpState)); memset(statePtr, 0, sizeof(TcpState)); statePtr->fds.fd = PTR2INT(sock); statePtr->flags = 0; snprintf(channelName, sizeof(channelName), SOCK_TEMPLATE, PTR2INT(statePtr)); statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, statePtr, mode); if (Tcl_SetChannelOption(NULL, statePtr->channel, "-translation", "auto crlf") == TCL_ERROR) { Tcl_CloseEx(NULL, statePtr->channel, 0); return NULL; } return statePtr->channel; } /* *---------------------------------------------------------------------- * * Tcl_OpenTcpServerEx -- * * Opens a TCP server socket and creates a channel around it. * * Results: * The channel or NULL if failed. If an error occurred, an error message * is left in the interp's result if interp is not NULL. * * Side effects: * Opens a server socket and creates a new channel. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_OpenTcpServerEx( Tcl_Interp *interp, /* For error reporting - may be NULL. */ const char *service, /* Port number to open. */ const char *myHost, /* Name of local host. */ unsigned int flags, /* Flags. */ int backlog, /* Length of OS listen backlog queue. */ Tcl_TcpAcceptProc *acceptProc, /* Callback for accepting connections from new * clients. */ void *acceptProcData) /* Data for the callback. */ { int status = 0, sock = -1, optvalue, port, chosenport; struct addrinfo *addrlist = NULL, *addrPtr; /* socket address */ TcpState *statePtr = NULL; char channelName[SOCK_CHAN_LENGTH]; const char *errorMsg = NULL; TcpFdList *fds = NULL, *newfds; /* * Try to record and return the most meaningful error message, i.e. the * one from the first socket that went the farthest before it failed. */ enum { LOOKUP, SOCKET, BIND, LISTEN } howfar = LOOKUP; int my_errno = 0; /* * If we were called with port 0 to listen on a random port number, we * copy the port number from the first member of the addrinfo list to all * subsequent members, so that IPv4 and IPv6 listen on the same port. This * might fail to bind() with EADDRINUSE if a port is free on the first * address family in the list but already used on the other. In this case * we revert everything we've done so far and start from scratch hoping * that next time we'll find a port number that is usable on all address * families. We try this at most MAXRETRY times to avoid an endless loop * if all ports are taken. */ int retry = 0; #define MAXRETRY 10 repeat: if (retry > 0) { if (statePtr != NULL) { TcpCloseProc(statePtr, NULL); statePtr = NULL; } if (addrlist != NULL) { freeaddrinfo(addrlist); addrlist = NULL; } if (retry >= MAXRETRY) { goto error; } } retry++; chosenport = 0; if (TclSockGetPort(interp, service, "tcp", &port) != TCL_OK) { errorMsg = "invalid port number"; goto error; } if (!TclCreateSocketAddress(interp, &addrlist, myHost, port, 1, &errorMsg)) { my_errno = errno; goto error; } for (addrPtr = addrlist; addrPtr != NULL; addrPtr = addrPtr->ai_next) { sock = socket(addrPtr->ai_family, addrPtr->ai_socktype, addrPtr->ai_protocol); if (sock == -1) { if (howfar < SOCKET) { howfar = SOCKET; my_errno = errno; } continue; } /* * Set the close-on-exec flag so that the socket will not get * inherited by child processes. */ fcntl(sock, F_SETFD, FD_CLOEXEC); /* * Set kernel space buffering */ TclSockMinimumBuffers(INT2PTR(sock), SOCKET_BUFSIZE); /* * Set up to reuse server addresses and/or ports if requested. */ if (GOT_BITS(flags, TCL_TCPSERVER_REUSEADDR)) { optvalue = 1; (void) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &optvalue, sizeof(optvalue)); } if (GOT_BITS(flags, TCL_TCPSERVER_REUSEPORT)) { #ifndef SO_REUSEPORT /* * If the platform doesn't support the SO_REUSEPORT flag we can't * do much beside erroring out. */ errorMsg = "SO_REUSEPORT isn't supported by this platform"; goto error; #else optvalue = 1; (void) setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (char *) &optvalue, sizeof(optvalue)); #endif } /* * Make sure we use the same port number when opening two server * sockets for IPv4 and IPv6 on a random port. * * As sockaddr_in6 uses the same offset and size for the port member * as sockaddr_in, we can handle both through the IPv4 API. */ if (port == 0 && chosenport != 0) { ((struct sockaddr_in *) addrPtr->ai_addr)->sin_port = htons(chosenport); } #ifdef IPV6_V6ONLY /* * Missing on: Solaris 2.8 */ if (addrPtr->ai_family == AF_INET6) { int v6only = 1; (void) setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)); } #endif /* IPV6_V6ONLY */ status = bind(sock, addrPtr->ai_addr, addrPtr->ai_addrlen); if (status == -1) { if (howfar < BIND) { howfar = BIND; my_errno = errno; } close(sock); sock = -1; if (port == 0 && errno == EADDRINUSE) { goto repeat; } continue; } if (port == 0 && chosenport == 0) { address sockname; socklen_t namelen = sizeof(sockname); /* * Synchronize port numbers when binding to port 0 of multiple * addresses. */ if (getsockname(sock, &sockname.sa, &namelen) >= 0) { chosenport = ntohs(sockname.sa4.sin_port); } } if (backlog < 0) { backlog = SOMAXCONN; } status = listen(sock, backlog); if (status < 0) { if (howfar < LISTEN) { howfar = LISTEN; my_errno = errno; } close(sock); sock = -1; if (port == 0 && errno == EADDRINUSE) { goto repeat; } continue; } if (statePtr == NULL) { /* * Allocate a new TcpState for this socket. */ statePtr = (TcpState *)Tcl_Alloc(sizeof(TcpState)); memset(statePtr, 0, sizeof(TcpState)); statePtr->acceptProc = acceptProc; statePtr->acceptProcData = acceptProcData; snprintf(channelName, sizeof(channelName), SOCK_TEMPLATE, PTR2INT(statePtr)); newfds = &statePtr->fds; } else { newfds = (TcpFdList *)Tcl_Alloc(sizeof(TcpFdList)); memset(newfds, (int) 0, sizeof(TcpFdList)); fds->next = newfds; } newfds->fd = sock; newfds->statePtr = statePtr; fds = newfds; /* * Set up the callback mechanism for accepting connections from new * clients. */ Tcl_CreateFileHandler(sock, TCL_READABLE, TcpAccept, fds); } error: if (addrlist != NULL) { freeaddrinfo(addrlist); } if (statePtr != NULL) { statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, statePtr, 0); return statePtr->channel; } if (interp != NULL) { Tcl_Obj *errorObj = Tcl_NewStringObj("couldn't open socket: ", TCL_INDEX_NONE); if (errorMsg == NULL) { errno = my_errno; Tcl_AppendToObj(errorObj, Tcl_PosixError(interp), TCL_INDEX_NONE); } else { Tcl_AppendToObj(errorObj, errorMsg, TCL_INDEX_NONE); } Tcl_SetObjResult(interp, errorObj); } if (sock != -1) { close(sock); } return NULL; } /* *---------------------------------------------------------------------- * * TcpAccept -- * Accept a TCP socket connection. This is called by the event loop. * * Results: * None. * * Side effects: * Creates a new connection socket. Calls the registered callback for the * connection acceptance mechanism. * *---------------------------------------------------------------------- */ static void TcpAccept( void *data, /* Callback token. */ TCL_UNUSED(int) /*mask*/) { TcpFdList *fds = (TcpFdList *)data; /* Client data of server socket. */ int newsock; /* The new client socket */ TcpState *newSockState; /* State for new socket. */ address addr; /* The remote address */ socklen_t len; /* For accept interface */ char channelName[SOCK_CHAN_LENGTH]; char host[NI_MAXHOST], port[NI_MAXSERV]; len = sizeof(addr); newsock = accept(fds->fd, &addr.sa, &len); if (newsock < 0) { return; } /* * Set close-on-exec flag to prevent the newly accepted socket from being * inherited by child processes. */ (void) fcntl(newsock, F_SETFD, FD_CLOEXEC); newSockState = (TcpState *)Tcl_Alloc(sizeof(TcpState)); memset(newSockState, 0, sizeof(TcpState)); newSockState->flags = 0; newSockState->fds.fd = newsock; snprintf(channelName, sizeof(channelName), SOCK_TEMPLATE, PTR2INT(newSockState)); newSockState->channel = Tcl_CreateChannel(&tcpChannelType, channelName, newSockState, TCL_READABLE | TCL_WRITABLE); Tcl_SetChannelOption(NULL, newSockState->channel, "-translation", "auto crlf"); if (fds->statePtr->acceptProc != NULL) { getnameinfo(&addr.sa, len, host, sizeof(host), port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV); fds->statePtr->acceptProc(fds->statePtr->acceptProcData, newSockState->channel, host, atoi(port)); } } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * indent-tabs-mode: nil * End: */ tcl9.0.1/unix/tclUnixTest.c0000644000175000017500000004166414726623136015206 0ustar sergeisergei/* * tclUnixTest.c -- * * Contains platform specific test commands for the Unix platform. * * Copyright © 1996-1997 Sun Microsystems, Inc. * Copyright © 1998 Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #undef BUILD_tcl #undef STATIC_BUILD #ifndef USE_TCL_STUBS # define USE_TCL_STUBS #endif #include "tclInt.h" /* * The headers are needed for the testalarm command that verifies the use of * SA_RESTART in signal handlers. */ #include #include /* * The following macros convert between TclFile's and fd's. The conversion * simple involves shifting fd's up by one to ensure that no valid fd is ever * the same as NULL. Note that this code is duplicated from tclUnixPipe.c */ #define MakeFile(fd) ((TclFile)INT2PTR(((int)(fd))+1)) #define GetFd(file) (PTR2INT(file)-1) /* * The stuff below is used to keep track of file handlers created and * exercised by the "testfilehandler" command. */ typedef struct { TclFile readFile; /* File handle for reading from the pipe. NULL * means pipe doesn't exist yet. */ TclFile writeFile; /* File handle for writing from the pipe. */ int readCount; /* Number of times the file handler for this * file has triggered and the file was * readable. */ int writeCount; /* Number of times the file handler for this * file has triggered and the file was * writable. */ } Pipe; #define MAX_PIPES 10 static Pipe testPipes[MAX_PIPES]; /* * The stuff below is used by the testalarm and testgotsig ommands. */ static const char *gotsig = "0"; /* * Forward declarations of functions defined later in this file: */ static Tcl_ObjCmdProc TestalarmCmd; static Tcl_ObjCmdProc TestchmodCmd; static Tcl_ObjCmdProc TestfilehandlerCmd; static Tcl_ObjCmdProc TestfilewaitCmd; static Tcl_ObjCmdProc TestfindexecutableCmd; static Tcl_ObjCmdProc TestforkCmd; static Tcl_ObjCmdProc TestgotsigCmd; static Tcl_FileProc TestFileHandlerProc; static void AlarmHandler(int signum); /* *---------------------------------------------------------------------- * * TclplatformtestInit -- * * Defines commands that test platform specific functionality for Unix * platforms. * * Results: * A standard Tcl result. * * Side effects: * Defines new commands. * *---------------------------------------------------------------------- */ int TclplatformtestInit( Tcl_Interp *interp) /* Interpreter to add commands to. */ { Tcl_CreateObjCommand(interp, "testchmod", TestchmodCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testfilehandler", TestfilehandlerCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testfilewait", TestfilewaitCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testfindexecutable", TestfindexecutableCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testfork", TestforkCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testalarm", TestalarmCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testgotsig", TestgotsigCmd, NULL, NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestfilehandlerCmd -- * * This function implements the "testfilehandler" command. It is used to * test Tcl_CreateFileHandler, Tcl_DeleteFileHandler, and TclWaitForFile. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestfilehandlerCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Argument strings. */ { Pipe *pipePtr; int i, mask, timeout; static int initialized = 0; char buffer[4000]; TclFile file; /* * NOTE: When we make this code work on Windows also, the following * variable needs to be made Unix-only. */ if (!initialized) { for (i = 0; i < MAX_PIPES; i++) { testPipes[i].readFile = NULL; } initialized = 1; } if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ..."); return TCL_ERROR; } pipePtr = NULL; if (objc >= 3) { if (Tcl_GetIntFromObj(interp, objv[2], &i) != TCL_OK) { return TCL_ERROR; } if (i >= MAX_PIPES) { Tcl_AppendResult(interp, "bad index ", objv[2], (char *)NULL); return TCL_ERROR; } pipePtr = &testPipes[i]; } if (strcmp(Tcl_GetString(objv[1]), "close") == 0) { for (i = 0; i < MAX_PIPES; i++) { if (testPipes[i].readFile != NULL) { TclpCloseFile(testPipes[i].readFile); testPipes[i].readFile = NULL; TclpCloseFile(testPipes[i].writeFile); testPipes[i].writeFile = NULL; } } } else if (strcmp(Tcl_GetString(objv[1]), "clear") == 0) { if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "index"); return TCL_ERROR; } pipePtr->readCount = pipePtr->writeCount = 0; } else if (strcmp(Tcl_GetString(objv[1]), "counts") == 0) { char buf[TCL_INTEGER_SPACE * 2]; if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "index"); return TCL_ERROR; } snprintf(buf, sizeof(buf), "%d %d", pipePtr->readCount, pipePtr->writeCount); Tcl_AppendResult(interp, buf, (char *)NULL); } else if (strcmp(Tcl_GetString(objv[1]), "create") == 0) { if (objc != 5) { Tcl_WrongNumArgs(interp, 2, objv, "index readMode writeMode"); return TCL_ERROR; } if (pipePtr->readFile == NULL) { if (!TclpCreatePipe(&pipePtr->readFile, &pipePtr->writeFile)) { Tcl_AppendResult(interp, "couldn't open pipe: ", Tcl_PosixError(interp), (char *)NULL); return TCL_ERROR; } #ifdef O_NONBLOCK fcntl(GetFd(pipePtr->readFile), F_SETFL, O_NONBLOCK); fcntl(GetFd(pipePtr->writeFile), F_SETFL, O_NONBLOCK); #else Tcl_AppendResult(interp, "cannot make pipes non-blocking", (char *)NULL); return TCL_ERROR; #endif } pipePtr->readCount = 0; pipePtr->writeCount = 0; if (strcmp(Tcl_GetString(objv[3]), "readable") == 0) { Tcl_CreateFileHandler(GetFd(pipePtr->readFile), TCL_READABLE, TestFileHandlerProc, pipePtr); } else if (strcmp(Tcl_GetString(objv[3]), "off") == 0) { Tcl_DeleteFileHandler(GetFd(pipePtr->readFile)); } else if (strcmp(Tcl_GetString(objv[3]), "disabled") == 0) { Tcl_CreateFileHandler(GetFd(pipePtr->readFile), 0, TestFileHandlerProc, pipePtr); } else { Tcl_AppendResult(interp, "bad read mode \"", Tcl_GetString(objv[3]), "\"", (char *)NULL); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[4]), "writable") == 0) { Tcl_CreateFileHandler(GetFd(pipePtr->writeFile), TCL_WRITABLE, TestFileHandlerProc, pipePtr); } else if (strcmp(Tcl_GetString(objv[4]), "off") == 0) { Tcl_DeleteFileHandler(GetFd(pipePtr->writeFile)); } else if (strcmp(Tcl_GetString(objv[4]), "disabled") == 0) { Tcl_CreateFileHandler(GetFd(pipePtr->writeFile), 0, TestFileHandlerProc, pipePtr); } else { Tcl_AppendResult(interp, "bad read mode \"", Tcl_GetString(objv[4]), "\"", (char *)NULL); return TCL_ERROR; } } else if (strcmp(Tcl_GetString(objv[1]), "empty") == 0) { if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "index"); return TCL_ERROR; } while (read(GetFd(pipePtr->readFile), buffer, 4000) > 0) { /* Empty loop body. */ } } else if (strcmp(Tcl_GetString(objv[1]), "fill") == 0) { if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "index"); return TCL_ERROR; } memset(buffer, 'a', 4000); while (write(GetFd(pipePtr->writeFile), buffer, 4000) > 0) { /* Empty loop body. */ } } else if (strcmp(Tcl_GetString(objv[1]), "fillpartial") == 0) { char buf[TCL_INTEGER_SPACE]; if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "index"); return TCL_ERROR; } memset(buffer, 'b', 10); TclFormatInt(buf, write(GetFd(pipePtr->writeFile), buffer, 10)); Tcl_AppendResult(interp, buf, (char *)NULL); } else if (strcmp(Tcl_GetString(objv[1]), "oneevent") == 0) { Tcl_DoOneEvent(TCL_FILE_EVENTS|TCL_DONT_WAIT); } else if (strcmp(Tcl_GetString(objv[1]), "wait") == 0) { if (objc != 5) { Tcl_WrongNumArgs(interp, 2, objv, "index readable|writable timeout"); return TCL_ERROR; } if (pipePtr->readFile == NULL) { Tcl_AppendResult(interp, "pipe ", Tcl_GetString(objv[2]), " doesn't exist", (char *)NULL); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[3]), "readable") == 0) { mask = TCL_READABLE; file = pipePtr->readFile; } else { mask = TCL_WRITABLE; file = pipePtr->writeFile; } if (Tcl_GetIntFromObj(interp, objv[4], &timeout) != TCL_OK) { return TCL_ERROR; } i = TclUnixWaitForFile(GetFd(file), mask, timeout); if (i & TCL_READABLE) { Tcl_AppendElement(interp, "readable"); } if (i & TCL_WRITABLE) { Tcl_AppendElement(interp, "writable"); } } else if (strcmp(Tcl_GetString(objv[1]), "windowevent") == 0) { Tcl_DoOneEvent(TCL_WINDOW_EVENTS|TCL_DONT_WAIT); } else { Tcl_AppendResult(interp, "bad option \"", Tcl_GetString(objv[1]), "\": must be close, clear, counts, create, empty, fill, " "fillpartial, oneevent, wait, or windowevent", (char *)NULL); return TCL_ERROR; } return TCL_OK; } static void TestFileHandlerProc( void *clientData, /* Points to a Pipe structure. */ int mask) /* Indicates which events happened: * TCL_READABLE or TCL_WRITABLE. */ { Pipe *pipePtr = (Pipe *)clientData; if (mask & TCL_READABLE) { pipePtr->readCount++; } if (mask & TCL_WRITABLE) { pipePtr->writeCount++; } } /* *---------------------------------------------------------------------- * * TestfilewaitCmd -- * * This function implements the "testfilewait" command. It is used to * test TclUnixWaitForFile. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestfilewaitCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Argument strings. */ { int mask, result, timeout; Tcl_Channel channel; int fd; void *data; if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "file readable|writable|both timeout"); return TCL_ERROR; } channel = Tcl_GetChannel(interp, Tcl_GetString(objv[1]), NULL); if (channel == NULL) { return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[2]), "readable") == 0) { mask = TCL_READABLE; } else if (strcmp(Tcl_GetString(objv[2]), "writable") == 0){ mask = TCL_WRITABLE; } else if (strcmp(Tcl_GetString(objv[2]), "both") == 0){ mask = TCL_WRITABLE|TCL_READABLE; } else { Tcl_AppendResult(interp, "bad argument \"", Tcl_GetString(objv[2]), "\": must be readable, writable, or both", (char *)NULL); return TCL_ERROR; } if (Tcl_GetChannelHandle(channel, (mask & TCL_READABLE) ? TCL_READABLE : TCL_WRITABLE, (void **) &data) != TCL_OK) { Tcl_AppendResult(interp, "couldn't get channel file", (char *)NULL); return TCL_ERROR; } fd = PTR2INT(data); if (Tcl_GetIntFromObj(interp, objv[3], &timeout) != TCL_OK) { return TCL_ERROR; } result = TclUnixWaitForFile(fd, mask, timeout); if (result & TCL_READABLE) { Tcl_AppendElement(interp, "readable"); } if (result & TCL_WRITABLE) { Tcl_AppendElement(interp, "writable"); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TestfindexecutableCmd -- * * This function implements the "testfindexecutable" command. It is used * to test TclpFindExecutable. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestfindexecutableCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Argument strings. */ { Tcl_Obj *saveName; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "argv0"); return TCL_ERROR; } saveName = TclGetObjNameOfExecutable(); Tcl_IncrRefCount(saveName); TclpFindExecutable(Tcl_GetString(objv[1])); Tcl_SetObjResult(interp, TclGetObjNameOfExecutable()); TclSetObjNameOfExecutable(saveName, NULL); Tcl_DecrRefCount(saveName); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestforkCmd -- * * This function implements the "testfork" command. It is used to * fork the Tcl process for specific test cases. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestforkCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Argument strings. */ { pid_t pid; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } pid = fork(); if (pid == -1) { Tcl_AppendResult(interp, "Cannot fork", (char *)NULL); return TCL_ERROR; } /* Only needed when pthread_atfork is not present, * should not hurt otherwise. */ if (pid==0) { Tcl_InitNotifier(); } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(pid)); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestalarmCmd -- * * Test that EINTR is handled correctly by generating and handling a * signal. This requires using the SA_RESTART flag when registering the * signal handler. * * Results: * None. * * Side Effects: * Sets up an signal and async handlers. * *---------------------------------------------------------------------- */ static int TestalarmCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Argument strings. */ { #ifdef SA_RESTART unsigned int sec = 1; struct sigaction action; if (objc > 1) { Tcl_GetIntFromObj(interp, objv[1], (int *)&sec); } /* * Setup the signal handling that automatically retries any interrupted * I/O system calls. */ action.sa_handler = AlarmHandler; memset((void *)&action.sa_mask, 0, sizeof(sigset_t)); action.sa_flags = SA_RESTART; if (sigaction(SIGALRM, &action, NULL) < 0) { Tcl_AppendResult(interp, "sigaction: ", Tcl_PosixError(interp), (char *)NULL); return TCL_ERROR; } (void) alarm(sec); return TCL_OK; #else Tcl_AppendResult(interp, "warning: sigaction SA_RESTART not support on this platform", (char *)NULL); return TCL_ERROR; #endif } /* *---------------------------------------------------------------------- * * AlarmHandler -- * * Signal handler for the alarm command. * * Results: * None. * * Side effects: * Calls the Tcl Async handler. * *---------------------------------------------------------------------- */ static void AlarmHandler( TCL_UNUSED(int) /*signum*/) { gotsig = "1"; } /* *---------------------------------------------------------------------- * * TestgotsigCmd -- * * Verify the signal was handled after the testalarm command. * * Results: * None. * * Side Effects: * Resets the value of gotsig back to '0'. * *---------------------------------------------------------------------- */ static int TestgotsigCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *)) { Tcl_AppendResult(interp, gotsig, (char *)NULL); gotsig = "0"; return TCL_OK; } /* *--------------------------------------------------------------------------- * * TestchmodCmd -- * * Implements the "testchmod" cmd. Used when testing "file" command. * The only attribute used by the Windows platform is the user write * flag; if this is not set, the file is made read-only. Otherwise, the * file is made read-write. * * Results: * A standard Tcl result. * * Side effects: * Changes permissions of specified files. * *--------------------------------------------------------------------------- */ static int TestchmodCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Argument strings. */ { int i, mode; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "mode file ?file ...?"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[1], &mode) != TCL_OK) { return TCL_ERROR; } for (i = 2; i < objc; i++) { Tcl_DString buffer; const char *translated; translated = Tcl_TranslateFileName(interp, Tcl_GetString(objv[i]), &buffer); if (translated == NULL) { return TCL_ERROR; } if (chmod(translated, mode) != 0) { Tcl_AppendResult(interp, translated, ": ", Tcl_PosixError(interp), (char *)NULL); return TCL_ERROR; } Tcl_DStringFree(&buffer); } return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * End: */ tcl9.0.1/unix/tclUnixThrd.c0000644000175000017500000005042614726623136015164 0ustar sergeisergei/* * tclUnixThrd.c -- * * This file implements the UNIX-specific thread support. * * Copyright © 1991-1994 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * Copyright © 2008 George Peter Staplin * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #if TCL_THREADS /* * TIP #509. Ensures that Tcl's mutexes are reentrant. * *---------------------------------------------------------------------- * * PMutexInit -- * * Sets up the memory pointed to by its argument so that it contains the * implementation of a recursive lock. Caller supplies the space. * *---------------------------------------------------------------------- * * PMutexDestroy -- * * Tears down the implementation of a recursive lock (but does not * deallocate the space holding the lock). * *---------------------------------------------------------------------- * * PMutexLock -- * * Locks a recursive lock. (Similar to pthread_mutex_lock) * *---------------------------------------------------------------------- * * PMutexUnlock -- * * Unlocks a recursive lock. (Similar to pthread_mutex_unlock) * *---------------------------------------------------------------------- * * PCondWait -- * * Waits on a condition variable linked a recursive lock. (Similar to * pthread_cond_wait) * *---------------------------------------------------------------------- * * PCondTimedWait -- * * Waits for a limited amount of time on a condition variable linked to a * recursive lock. (Similar to pthread_cond_timedwait) * *---------------------------------------------------------------------- */ #ifndef HAVE_DECL_PTHREAD_MUTEX_RECURSIVE #define HAVE_DECL_PTHREAD_MUTEX_RECURSIVE 0 #endif #if HAVE_DECL_PTHREAD_MUTEX_RECURSIVE /* * Pthread has native reentrant (AKA recursive) mutexes. Use them for * Tcl_Mutex. */ typedef pthread_mutex_t PMutex; static void PMutexInit( PMutex *pmutexPtr) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(pmutexPtr, &attr); } #define PMutexDestroy pthread_mutex_destroy #define PMutexLock pthread_mutex_lock #define PMutexUnlock pthread_mutex_unlock #define PCondWait pthread_cond_wait #define PCondTimedWait pthread_cond_timedwait #else /* !HAVE_PTHREAD_MUTEX_RECURSIVE */ /* * No native support for reentrant mutexes. Emulate them with regular mutexes * and thread-local counters. */ typedef struct PMutex { pthread_mutex_t mutex; pthread_t thread; int counter; } PMutex; static void PMutexInit( PMutex *pmutexPtr) { pthread_mutex_init(&pmutexPtr->mutex, NULL); pmutexPtr->thread = 0; pmutexPtr->counter = 0; } static void PMutexDestroy( PMutex *pmutexPtr) { pthread_mutex_destroy(&pmutexPtr->mutex); } static void PMutexLock( PMutex *pmutexPtr) { if (pmutexPtr->thread != pthread_self() || pmutexPtr->counter == 0) { pthread_mutex_lock(&pmutexPtr->mutex); pmutexPtr->thread = pthread_self(); pmutexPtr->counter = 0; } pmutexPtr->counter++; } static void PMutexUnlock( PMutex *pmutexPtr) { pmutexPtr->counter--; if (pmutexPtr->counter == 0) { pmutexPtr->thread = 0; pthread_mutex_unlock(&pmutexPtr->mutex); } } static void PCondWait( pthread_cond_t *pcondPtr, PMutex *pmutexPtr) { pthread_cond_wait(pcondPtr, &pmutexPtr->mutex); } static void PCondTimedWait( pthread_cond_t *pcondPtr, PMutex *pmutexPtr, struct timespec *ptime) { pthread_cond_timedwait(pcondPtr, &pmutexPtr->mutex, ptime); } #endif /* HAVE_PTHREAD_MUTEX_RECURSIVE */ /* * globalLock is used to serialize creation of mutexes, condition variables, * and thread local storage. This is the only place that can count on the * ability to statically initialize the mutex. */ static pthread_mutex_t globalLock = PTHREAD_MUTEX_INITIALIZER; /* * initLock is used to serialize initialization and finalization of Tcl. It * cannot use any dynamically allocated storage. */ static pthread_mutex_t initLock = PTHREAD_MUTEX_INITIALIZER; /* * allocLock is used by Tcl's version of malloc for synchronization. For * obvious reasons, cannot use any dynamically allocated storage. */ static PMutex allocLock; static pthread_once_t allocLockInitOnce = PTHREAD_ONCE_INIT; static void allocLockInit(void) { PMutexInit(&allocLock); } static PMutex *allocLockPtr = &allocLock; #endif /* TCL_THREADS */ /* *---------------------------------------------------------------------- * * TclpThreadCreate -- * * This procedure creates a new thread. * * Results: * TCL_OK if the thread could be created. The thread ID is returned in a * parameter. * * Side effects: * A new thread is created. * *---------------------------------------------------------------------- */ int TclpThreadCreate( Tcl_ThreadId *idPtr, /* Return, the ID of the thread */ Tcl_ThreadCreateProc *proc, /* Main() function of the thread */ void *clientData, /* The one argument to Main() */ size_t stackSize, /* Size of stack for the new thread */ int flags) /* Flags controlling behaviour of the new * thread. */ { #if TCL_THREADS pthread_attr_t attr; pthread_t theThread; int result; pthread_attr_init(&attr); pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); #ifdef HAVE_PTHREAD_ATTR_SETSTACKSIZE if (stackSize != TCL_THREAD_STACK_DEFAULT) { pthread_attr_setstacksize(&attr, stackSize); #ifdef TCL_THREAD_STACK_MIN } else { /* * Certain systems define a thread stack size that by default is too * small for many operations. The user has the option of defining * TCL_THREAD_STACK_MIN to a value large enough to work for their * needs. This would look like (for 128K min stack): * make MEM_DEBUG_FLAGS=-DTCL_THREAD_STACK_MIN=131072L * * This solution is not optimal, as we should allow the user to * specify a size at runtime, but we don't want to slow this function * down, and that would still leave the main thread at the default. */ size_t size; result = pthread_attr_getstacksize(&attr, &size); if (!result && (size < TCL_THREAD_STACK_MIN)) { pthread_attr_setstacksize(&attr, (size_t)TCL_THREAD_STACK_MIN); } #endif /* TCL_THREAD_STACK_MIN */ } #endif /* HAVE_PTHREAD_ATTR_SETSTACKSIZE */ if (!(flags & TCL_THREAD_JOINABLE)) { pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); } if (pthread_create(&theThread, &attr, (void * (*)(void *))(void *)proc, (void *)clientData) && pthread_create(&theThread, NULL, (void * (*)(void *))(void *)proc, (void *)clientData)) { result = TCL_ERROR; } else { *idPtr = (Tcl_ThreadId)theThread; result = TCL_OK; } pthread_attr_destroy(&attr); return result; #else (void)idPtr; (void)proc; (void)clientData; (void)stackSize; (void)flags; return TCL_ERROR; #endif /* TCL_THREADS */ } /* *---------------------------------------------------------------------- * * Tcl_JoinThread -- * * This procedure waits upon the exit of the specified thread. * * Results: * TCL_OK if the wait was successful, TCL_ERROR else. * * Side effects: * The result area is set to the exit code of the thread we waited upon. * *---------------------------------------------------------------------- */ int Tcl_JoinThread( Tcl_ThreadId threadId, /* Id of the thread to wait upon. */ int *state) /* Reference to the storage the result of the * thread we wait upon will be written into. * May be NULL. */ { #if TCL_THREADS int result; unsigned long retcode, *retcodePtr = &retcode; result = pthread_join((pthread_t) threadId, (void**) retcodePtr); if (state) { *state = (int) retcode; } return (result == 0) ? TCL_OK : TCL_ERROR; #else (void)threadId; (void)state; return TCL_ERROR; #endif } /* *---------------------------------------------------------------------- * * TclpThreadExit -- * * This procedure terminates the current thread. * * Results: * None. * * Side effects: * This procedure terminates the current thread. * *---------------------------------------------------------------------- */ TCL_NORETURN void TclpThreadExit( int status) { #if TCL_THREADS pthread_exit(INT2PTR(status)); #else /* TCL_THREADS */ exit(status); #endif /* TCL_THREADS */ } /* *---------------------------------------------------------------------- * * Tcl_GetCurrentThread -- * * This procedure returns the ID of the currently running thread. * * Results: * A thread ID. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_ThreadId Tcl_GetCurrentThread(void) { #if TCL_THREADS return (Tcl_ThreadId) pthread_self(); #else return (Tcl_ThreadId) 0; #endif } /* *---------------------------------------------------------------------- * * TclpInitLock * * This procedure is used to grab a lock that serializes initialization * and finalization of Tcl. On some platforms this may also initialize * the mutex used to serialize creation of more mutexes and thread local * storage keys. * * Results: * None. * * Side effects: * Acquire the initialization mutex. * *---------------------------------------------------------------------- */ void TclpInitLock(void) { #if TCL_THREADS pthread_mutex_lock(&initLock); #endif } /* *---------------------------------------------------------------------- * * TclFinalizeLock * * This procedure is used to destroy all private resources used in this * file. * * Results: * None. * * Side effects: * Destroys everything private. TclpInitLock must be held entering this * function. * *---------------------------------------------------------------------- */ void TclFinalizeLock(void) { #if TCL_THREADS /* * You do not need to destroy mutexes that were created with the * PTHREAD_MUTEX_INITIALIZER macro. These mutexes do not need any * destruction: globalLock, allocLock, and initLock. */ pthread_mutex_unlock(&initLock); #endif } /* *---------------------------------------------------------------------- * * TclpInitUnlock * * This procedure is used to release a lock that serializes * initialization and finalization of Tcl. * * Results: * None. * * Side effects: * Release the initialization mutex. * *---------------------------------------------------------------------- */ void TclpInitUnlock(void) { #if TCL_THREADS pthread_mutex_unlock(&initLock); #endif } /* *---------------------------------------------------------------------- * * TclpGlobalLock * * This procedure is used to grab a lock that serializes creation and * finalization of serialization objects. This interface is only needed * in finalization; it is hidden during creation of the objects. * * This lock must be different than the initLock because the initLock is * held during creation of synchronization objects. * * Results: * None. * * Side effects: * Acquire the global mutex. * *---------------------------------------------------------------------- */ void TclpGlobalLock(void) { #if TCL_THREADS pthread_mutex_lock(&globalLock); #endif } /* *---------------------------------------------------------------------- * * TclpGlobalUnlock * * This procedure is used to release a lock that serializes creation and * finalization of synchronization objects. * * Results: * None. * * Side effects: * Release the global mutex. * *---------------------------------------------------------------------- */ void TclpGlobalUnlock(void) { #if TCL_THREADS pthread_mutex_unlock(&globalLock); #endif } /* *---------------------------------------------------------------------- * * Tcl_GetAllocMutex * * This procedure returns a pointer to a statically initialized mutex for * use by the memory allocator. The allocator must use this lock, because * all other locks are allocated... * * Results: * A pointer to a mutex that is suitable for passing to Tcl_MutexLock and * Tcl_MutexUnlock. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Mutex * Tcl_GetAllocMutex(void) { #if TCL_THREADS PMutex **allocLockPtrPtr = &allocLockPtr; pthread_once(&allocLockInitOnce, allocLockInit); return (Tcl_Mutex *) allocLockPtrPtr; #else return NULL; #endif } #if TCL_THREADS /* *---------------------------------------------------------------------- * * Tcl_MutexLock -- * * This procedure is invoked to lock a mutex. This procedure handles * initializing the mutex, if necessary. The caller can rely on the fact * that Tcl_Mutex is an opaque pointer. This routine will change that * pointer from NULL after first use. * * Results: * None. * * Side effects: * May block the current thread. The mutex is acquired when this returns. * Will allocate memory for a pthread_mutex_t and initialize this the * first time this Tcl_Mutex is used. * *---------------------------------------------------------------------- */ void Tcl_MutexLock( Tcl_Mutex *mutexPtr) /* Really (PMutex **) */ { PMutex *pmutexPtr; if (*mutexPtr == NULL) { pthread_mutex_lock(&globalLock); if (*mutexPtr == NULL) { /* * Double inside global lock check to avoid a race condition. */ pmutexPtr = (PMutex *)Tcl_Alloc(sizeof(PMutex)); PMutexInit(pmutexPtr); *mutexPtr = (Tcl_Mutex) pmutexPtr; TclRememberMutex(mutexPtr); } pthread_mutex_unlock(&globalLock); } pmutexPtr = *((PMutex **) mutexPtr); PMutexLock(pmutexPtr); } /* *---------------------------------------------------------------------- * * Tcl_MutexUnlock -- * * This procedure is invoked to unlock a mutex. The mutex must have been * locked by Tcl_MutexLock. * * Results: * None. * * Side effects: * The mutex is released when this returns. * *---------------------------------------------------------------------- */ void Tcl_MutexUnlock( Tcl_Mutex *mutexPtr) /* Really (PMutex **) */ { PMutex *pmutexPtr = *(PMutex **) mutexPtr; PMutexUnlock(pmutexPtr); } /* *---------------------------------------------------------------------- * * TclpFinalizeMutex -- * * This procedure is invoked to clean up one mutex. This is only safe to * call at the end of time. * * This assumes the Global Lock is held. * * Results: * None. * * Side effects: * The mutex list is deallocated. * *---------------------------------------------------------------------- */ void TclpFinalizeMutex( Tcl_Mutex *mutexPtr) { PMutex *pmutexPtr = *(PMutex **) mutexPtr; if (pmutexPtr != NULL) { PMutexDestroy(pmutexPtr); Tcl_Free(pmutexPtr); *mutexPtr = NULL; } } /* *---------------------------------------------------------------------- * * Tcl_ConditionWait -- * * This procedure is invoked to wait on a condition variable. The mutex * is automically released as part of the wait, and automatically grabbed * when the condition is signaled. * * The mutex must be held when this procedure is called. * * Results: * None. * * Side effects: * May block the current thread. The mutex is acquired when this returns. * Will allocate memory for a pthread_mutex_t and initialize this the * first time this Tcl_Mutex is used. * *---------------------------------------------------------------------- */ void Tcl_ConditionWait( Tcl_Condition *condPtr, /* Really (pthread_cond_t **) */ Tcl_Mutex *mutexPtr, /* Really (PMutex **) */ const Tcl_Time *timePtr) /* Timeout on waiting period */ { pthread_cond_t *pcondPtr; PMutex *pmutexPtr; struct timespec ptime; if (*condPtr == NULL) { pthread_mutex_lock(&globalLock); /* * Double check inside mutex to avoid race, then initialize condition * variable if necessary. */ if (*condPtr == NULL) { pcondPtr = (pthread_cond_t *)Tcl_Alloc(sizeof(pthread_cond_t)); pthread_cond_init(pcondPtr, NULL); *condPtr = (Tcl_Condition) pcondPtr; TclRememberCondition(condPtr); } pthread_mutex_unlock(&globalLock); } pmutexPtr = *((PMutex **)mutexPtr); pcondPtr = *((pthread_cond_t **)condPtr); if (timePtr == NULL) { PCondWait(pcondPtr, pmutexPtr); } else { Tcl_Time now; /* * Make sure to take into account the microsecond component of the * current time, including possible overflow situations. [Bug #411603] */ Tcl_GetTime(&now); ptime.tv_sec = timePtr->sec + now.sec + (timePtr->usec + now.usec) / 1000000; ptime.tv_nsec = 1000 * ((timePtr->usec + now.usec) % 1000000); PCondTimedWait(pcondPtr, pmutexPtr, &ptime); } } /* *---------------------------------------------------------------------- * * Tcl_ConditionNotify -- * * This procedure is invoked to signal a condition variable. * * The mutex must be held during this call to avoid races, but this * interface does not enforce that. * * Results: * None. * * Side effects: * May unblock another thread. * *---------------------------------------------------------------------- */ void Tcl_ConditionNotify( Tcl_Condition *condPtr) { pthread_cond_t *pcondPtr = *((pthread_cond_t **)condPtr); if (pcondPtr != NULL) { pthread_cond_broadcast(pcondPtr); } else { /* * No-one has used the condition variable, so there are no waiters. */ } } /* *---------------------------------------------------------------------- * * TclpFinalizeCondition -- * * This procedure is invoked to clean up a condition variable. This is * only safe to call at the end of time. * * This assumes the Global Lock is held. * * Results: * None. * * Side effects: * The condition variable is deallocated. * *---------------------------------------------------------------------- */ void TclpFinalizeCondition( Tcl_Condition *condPtr) { pthread_cond_t *pcondPtr = *(pthread_cond_t **)condPtr; if (pcondPtr != NULL) { pthread_cond_destroy(pcondPtr); Tcl_Free(pcondPtr); *condPtr = NULL; } } /* * Additions by AOL for specialized thread memory allocator. */ #ifdef USE_THREAD_ALLOC static pthread_key_t key; typedef struct { Tcl_Mutex tlock; PMutex plock; } AllocMutex; Tcl_Mutex * TclpNewAllocMutex(void) { AllocMutex *lockPtr; PMutex *plockPtr; lockPtr = (AllocMutex *)malloc(sizeof(AllocMutex)); if (lockPtr == NULL) { Tcl_Panic("could not allocate lock"); } plockPtr = &lockPtr->plock; lockPtr->tlock = (Tcl_Mutex) plockPtr; PMutexInit(&lockPtr->plock); return &lockPtr->tlock; } void TclpFreeAllocMutex( Tcl_Mutex *mutex) /* The alloc mutex to free. */ { AllocMutex *lockPtr = (AllocMutex *)mutex; if (!lockPtr) { return; } PMutexDestroy(&lockPtr->plock); free(lockPtr); } void TclpInitAllocCache(void) { pthread_key_create(&key, NULL); } void TclpFreeAllocCache( void *ptr) { if (ptr != NULL) { /* * Called by TclFinalizeThreadAllocThread() during the thread * finalization initiated from Tcl_FinalizeThread() */ TclFreeAllocCache(ptr); pthread_setspecific(key, NULL); } else { /* * Called by TclFinalizeThreadAlloc() during the process * finalization initiated from Tcl_Finalize() */ pthread_key_delete(key); } } void * TclpGetAllocCache(void) { return pthread_getspecific(key); } void TclpSetAllocCache( void *arg) { pthread_setspecific(key, arg); } #endif /* USE_THREAD_ALLOC */ void * TclpThreadCreateKey(void) { pthread_key_t *ptkeyPtr; ptkeyPtr = (pthread_key_t *)TclpSysAlloc(sizeof(pthread_key_t)); if (NULL == ptkeyPtr) { Tcl_Panic("unable to allocate thread key!"); } if (pthread_key_create(ptkeyPtr, NULL)) { Tcl_Panic("unable to create pthread key!"); } return ptkeyPtr; } void TclpThreadDeleteKey( void *keyPtr) { pthread_key_t *ptkeyPtr = (pthread_key_t *)keyPtr; if (pthread_key_delete(*ptkeyPtr)) { Tcl_Panic("unable to delete key!"); } TclpSysFree(keyPtr); } void TclpThreadSetGlobalTSD( void *tsdKeyPtr, void *ptr) { pthread_key_t *ptkeyPtr = (pthread_key_t *)tsdKeyPtr; if (pthread_setspecific(*ptkeyPtr, ptr)) { Tcl_Panic("unable to set global TSD value"); } } void * TclpThreadGetGlobalTSD( void *tsdKeyPtr) { pthread_key_t *ptkeyPtr = (pthread_key_t *)tsdKeyPtr; return pthread_getspecific(*ptkeyPtr); } #endif /* TCL_THREADS */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclUnixTime.c0000644000175000017500000002175714726623136015166 0ustar sergeisergei/* * tclUnixTime.c -- * * Contains Unix specific versions of Tcl functions that obtain time * values from the operating system. * * Copyright © 1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #if defined(TCL_WIDE_CLICKS) && defined(MAC_OSX_TCL) #include #endif /* * Static functions declared in this file. */ static void NativeScaleTime(Tcl_Time *timebuf, void *clientData); static void NativeGetTime(Tcl_Time *timebuf, void *clientData); /* * TIP #233 (Virtualized Time): Data for the time hooks, if any. */ Tcl_GetTimeProc *tclGetTimeProcPtr = NativeGetTime; Tcl_ScaleTimeProc *tclScaleTimeProcPtr = NativeScaleTime; void *tclTimeClientData = NULL; /* * Inlined version of Tcl_GetTime. */ static inline void GetTime( Tcl_Time *timePtr) { tclGetTimeProcPtr(timePtr, tclTimeClientData); } static inline int IsTimeNative(void) { return tclGetTimeProcPtr == NativeGetTime; } /* *---------------------------------------------------------------------- * * TclpGetSeconds -- * * This procedure returns the number of seconds from the epoch. On most * Unix systems the epoch is Midnight Jan 1, 1970 GMT. * * Results: * Number of seconds from the epoch. * * Side effects: * None. * *---------------------------------------------------------------------- */ unsigned long long TclpGetSeconds(void) { return (unsigned long long) time(NULL); } /* *---------------------------------------------------------------------- * * TclpGetMicroseconds -- * * This procedure returns the number of microseconds from the epoch. * On most Unix systems the epoch is Midnight Jan 1, 1970 GMT. * * Results: * Number of microseconds from the epoch. * * Side effects: * None. * *---------------------------------------------------------------------- */ long long TclpGetMicroseconds(void) { Tcl_Time time; GetTime(&time); return time.sec * 1000000 + time.usec; } /* *---------------------------------------------------------------------- * * TclpGetClicks -- * * This procedure returns a value that represents the highest resolution * clock available on the system. There are no guarantees on what the * resolution will be. In Tcl we will call this value a "click". The * start time is also system dependent. * * Results: * Number of clicks from some start time. * * Side effects: * None. * *---------------------------------------------------------------------- */ unsigned long long TclpGetClicks(void) { unsigned long long now; #ifdef NO_GETTOD if (!IsTimeNative()) { Tcl_Time time; GetTime(&time); now = ((unsigned long long)(time.sec)*1000000ULL) + (unsigned long long)(time.usec); } else { /* * A semi-NativeGetTime, specialized to clicks. */ struct tms dummy; now = (unsigned long long) times(&dummy); } #else /* !NO_GETTOD */ Tcl_Time time; GetTime(&time); now = ((unsigned long long)(time.sec)*1000000ULL) + (unsigned long long)(time.usec); #endif /* NO_GETTOD */ return now; } #ifdef TCL_WIDE_CLICKS /* *---------------------------------------------------------------------- * * TclpGetWideClicks -- * * This procedure returns a WideInt value that represents the highest * resolution clock available on the system. There are no guarantees on * what the resolution will be. In Tcl we will call this value a "click". * The start time is also system dependent. * * Results: * Number of WideInt clicks from some start time. * * Side effects: * None. * *---------------------------------------------------------------------- */ long long TclpGetWideClicks(void) { long long now; if (!IsTimeNative()) { Tcl_Time time; GetTime(&time); now = ((long long) time.sec)*1000000 + time.usec; } else { #ifdef MAC_OSX_TCL now = (long long) (mach_absolute_time() & INT64_MAX); #else #error Wide high-resolution clicks not implemented on this platform #endif /* MAC_OSX_TCL */ } return now; } /* *---------------------------------------------------------------------- * * TclpWideClicksToNanoseconds -- * * This procedure converts click values from the TclpGetWideClicks native * resolution to nanosecond resolution. * * Results: * Number of nanoseconds from some start time. * * Side effects: * None. * *---------------------------------------------------------------------- */ double TclpWideClicksToNanoseconds( long long clicks) { double nsec; if (!IsTimeNative()) { nsec = clicks * 1000; } else { #ifdef MAC_OSX_TCL static mach_timebase_info_data_t tb; static uint64_t maxClicksForUInt64; if (!tb.denom) { mach_timebase_info(&tb); maxClicksForUInt64 = UINT64_MAX / tb.numer; } if ((uint64_t) clicks < maxClicksForUInt64) { nsec = ((uint64_t) clicks) * tb.numer / tb.denom; } else { nsec = ((long double) (uint64_t) clicks) * tb.numer / tb.denom; } #else #error Wide high-resolution clicks not implemented on this platform #endif /* MAC_OSX_TCL */ } return nsec; } /* *---------------------------------------------------------------------- * * TclpWideClickInMicrosec -- * * This procedure return scale to convert click values from the * TclpGetWideClicks native resolution to microsecond resolution * and back. * * Results: * 1 click in microseconds as double. * * Side effects: * None. * *---------------------------------------------------------------------- */ double TclpWideClickInMicrosec(void) { if (!IsTimeNative()) { return 1.0; } else { #ifdef MAC_OSX_TCL static int initialized = 0; static double scale = 0.0; if (!initialized) { mach_timebase_info_data_t tb; mach_timebase_info(&tb); /* value of tb.numer / tb.denom = 1 click in nanoseconds */ scale = ((double) tb.numer) / tb.denom / 1000; initialized = 1; } return scale; #else #error Wide high-resolution clicks not implemented on this platform #endif /* MAC_OSX_TCL */ } } #endif /* TCL_WIDE_CLICKS */ /* *---------------------------------------------------------------------- * * Tcl_GetTime -- * * Gets the current system time in seconds and microseconds since the * beginning of the epoch: 00:00 UCT, January 1, 1970. * * This function is hooked, allowing users to specify their own virtual * system time. * * Results: * Returns the current time in timePtr. * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_GetTime( Tcl_Time *timePtr) /* Location to store time information. */ { GetTime(timePtr); } /* *---------------------------------------------------------------------- * * Tcl_SetTimeProc -- * * TIP #233 (Virtualized Time): Registers two handlers for the * virtualization of Tcl's access to time information. * * Results: * None. * * Side effects: * Remembers the handlers, alters core behaviour. * *---------------------------------------------------------------------- */ void Tcl_SetTimeProc( Tcl_GetTimeProc *getProc, Tcl_ScaleTimeProc *scaleProc, void *clientData) { tclGetTimeProcPtr = getProc; tclScaleTimeProcPtr = scaleProc; tclTimeClientData = clientData; } /* *---------------------------------------------------------------------- * * Tcl_QueryTimeProc -- * * TIP #233 (Virtualized Time): Query which time handlers are registered. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_QueryTimeProc( Tcl_GetTimeProc **getProc, Tcl_ScaleTimeProc **scaleProc, void **clientData) { if (getProc) { *getProc = tclGetTimeProcPtr; } if (scaleProc) { *scaleProc = tclScaleTimeProcPtr; } if (clientData) { *clientData = tclTimeClientData; } } /* *---------------------------------------------------------------------- * * NativeScaleTime -- * * TIP #233: Scale from virtual time to the real-time. For native scaling * the relationship is 1:1 and nothing has to be done. * * Results: * Scales the time in timePtr. * * Side effects: * See above. * *---------------------------------------------------------------------- */ static void NativeScaleTime( TCL_UNUSED(Tcl_Time *), TCL_UNUSED(void *)) { /* Native scale is 1:1. Nothing is done */ } /* *---------------------------------------------------------------------- * * NativeGetTime -- * * TIP #233: Gets the current system time in seconds and microseconds * since the beginning of the epoch: 00:00 UCT, January 1, 1970. * * Results: * Returns the current time in timePtr. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void NativeGetTime( Tcl_Time *timePtr, TCL_UNUSED(void *)) { struct timeval tv; (void) gettimeofday(&tv, NULL); timePtr->sec = tv.tv_sec; timePtr->usec = tv.tv_usec; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclXtNotify.c0000644000175000017500000003753614726623136015212 0ustar sergeisergei/* * tclXtNotify.c -- * * This file contains the notifier driver implementation for the Xt * intrinsics. * * Copyright © 1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef USE_TCL_STUBS # define USE_TCL_STUBS #endif #include #include "tclInt.h" /* * This structure is used to keep track of the notifier info for a * registered file. */ typedef struct FileHandler { int fd; int mask; /* Mask of desired events: TCL_READABLE, * etc. */ int readyMask; /* Events that have been seen since the last * time FileHandlerEventProc was called for * this file. */ XtInputId read; /* Xt read callback handle. */ XtInputId write; /* Xt write callback handle. */ XtInputId except; /* Xt exception callback handle. */ Tcl_FileProc *proc; /* Procedure to call, in the style of * Tcl_CreateFileHandler. */ void *clientData; /* Argument to pass to proc. */ struct FileHandler *nextPtr;/* Next in list of all files we care about. */ } FileHandler; /* * The following structure is what is added to the Tcl event queue when file * handlers are ready to fire. */ typedef struct { Tcl_Event header; /* Information that is standard for all * events. */ int fd; /* File descriptor that is ready. Used to find * the FileHandler structure for the file * (can't point directly to the FileHandler * structure because it could go away while * the event is queued). */ } FileHandlerEvent; /* * The following static structure contains the state information for the Xt * based implementation of the Tcl notifier. */ static struct NotifierState { XtAppContext appContext; /* The context used by the Xt notifier. Can be * set with TclSetAppContext. */ int appContextCreated; /* Was it created by us? */ XtIntervalId currentTimeout;/* Handle of current timer. */ FileHandler *firstFileHandlerPtr; /* Pointer to head of file handler list. */ } notifier; /* * The following static indicates whether this module has been initialized. */ static int initialized = 0; /* * Static routines defined in this file. */ static int FileHandlerEventProc(Tcl_Event *evPtr, int flags); static void FileProc(XtPointer clientData, int *source, XtInputId *id); static void NotifierExitHandler(void *clientData); static void TimerProc(XtPointer clientData, XtIntervalId *id); static void CreateFileHandler(int fd, int mask, Tcl_FileProc *proc, void *clientData); static void DeleteFileHandler(int fd); static void SetTimer(const Tcl_Time * timePtr); static int WaitForEvent(const Tcl_Time * timePtr); /* * Functions defined in this file for use by users of the Xt Notifier: */ MODULE_SCOPE void InitNotifier(void); MODULE_SCOPE XtAppContext TclSetAppContext(XtAppContext ctx); /* *---------------------------------------------------------------------- * * TclSetAppContext -- * * Set the notifier application context. * * Results: * None. * * Side effects: * Sets the application context used by the notifier. Panics if the * context is already set when called. * *---------------------------------------------------------------------- */ XtAppContext TclSetAppContext( XtAppContext appContext) { if (!initialized) { InitNotifier(); } /* * If we already have a context we check whether we were asked to set a * new context. If so, we panic because we try to prevent switching * contexts by mistake. Otherwise, we return the one we have. */ if (notifier.appContext != NULL) { if (appContext != NULL) { /* * We already have a context. We do not allow switching contexts * after initialization, so we panic. */ Tcl_Panic("TclSetAppContext: multiple application contexts"); } } else { /* * If we get here we have not yet gotten a context, so either create * one or use the one supplied by our caller. */ if (appContext == NULL) { /* * We must create a new context and tell our caller what it is, so * she can use it too. */ notifier.appContext = XtCreateApplicationContext(); notifier.appContextCreated = 1; } else { /* * Otherwise we remember the context that our caller gave us and * use it. */ notifier.appContextCreated = 0; notifier.appContext = appContext; } } return notifier.appContext; } /* *---------------------------------------------------------------------- * * InitNotifier -- * * Initializes the notifier state. * * Results: * None. * * Side effects: * Creates a new exit handler. * *---------------------------------------------------------------------- */ void InitNotifier(void) { static const Tcl_NotifierProcs np = SetTimer, WaitForEvent, CreateFileHandler, DeleteFileHandler, NULL, NULL, NULL, NULL }; /* * Only reinitialize if we are not in exit handling. The notifier can get * reinitialized after its own exit handler has run, because of exit * handlers for the I/O and timer sub-systems (order dependency). */ if (TclInExit()) { return; } Tcl_SetNotifier(&np); /* * DO NOT create the application context yet; doing so would prevent * external applications from setting it for us to their own ones. */ initialized = 1; Tcl_CreateExitHandler(NotifierExitHandler, NULL); } /* *---------------------------------------------------------------------- * * NotifierExitHandler -- * * This function is called to cleanup the notifier state before Tcl is * unloaded. * * Results: * None. * * Side effects: * Destroys the notifier window. * *---------------------------------------------------------------------- */ static void NotifierExitHandler( TCL_UNUSED(void *)) { if (notifier.currentTimeout != 0) { XtRemoveTimeOut(notifier.currentTimeout); } for (; notifier.firstFileHandlerPtr != NULL; ) { Tcl_DeleteFileHandler(notifier.firstFileHandlerPtr->fd); } if (notifier.appContextCreated) { XtDestroyApplicationContext(notifier.appContext); notifier.appContextCreated = 0; notifier.appContext = NULL; } initialized = 0; } /* *---------------------------------------------------------------------- * * SetTimer -- * * This procedure sets the current notifier timeout value. * * Results: * None. * * Side effects: * Replaces any previous timer. * *---------------------------------------------------------------------- */ static void SetTimer( const Tcl_Time *timePtr) /* Timeout value, may be NULL. */ { unsigned long timeout; if (!initialized) { InitNotifier(); } TclSetAppContext(NULL); if (notifier.currentTimeout != 0) { XtRemoveTimeOut(notifier.currentTimeout); } if (timePtr) { timeout = timePtr->sec * 1000 + timePtr->usec / 1000; notifier.currentTimeout = XtAppAddTimeOut(notifier.appContext, timeout, TimerProc, NULL); } else { notifier.currentTimeout = 0; } } /* *---------------------------------------------------------------------- * * TimerProc -- * * This procedure is the XtTimerCallbackProc used to handle timeouts. * * Results: * None. * * Side effects: * Processes all queued events. * *---------------------------------------------------------------------- */ static void TimerProc( TCL_UNUSED(XtPointer), XtIntervalId *id) { if (*id != notifier.currentTimeout) { return; } notifier.currentTimeout = 0; Tcl_ServiceAll(); } /* *---------------------------------------------------------------------- * * CreateFileHandler -- * * This procedure registers a file handler with the Xt notifier. * * Results: * None. * * Side effects: * Creates a new file handler structure and registers one or more input * procedures with Xt. * *---------------------------------------------------------------------- */ static void CreateFileHandler( int fd, /* Handle of stream to watch. */ int mask, /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, and TCL_EXCEPTION: indicates * conditions under which proc should be * called. */ Tcl_FileProc *proc, /* Procedure to call for each selected * event. */ void *clientData) /* Arbitrary data to pass to proc. */ { FileHandler *filePtr; if (!initialized) { InitNotifier(); } TclSetAppContext(NULL); for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL; filePtr = filePtr->nextPtr) { if (filePtr->fd == fd) { break; } } if (filePtr == NULL) { filePtr = (FileHandler *) Tcl_Alloc(sizeof(FileHandler)); filePtr->fd = fd; filePtr->read = 0; filePtr->write = 0; filePtr->except = 0; filePtr->readyMask = 0; filePtr->mask = 0; filePtr->nextPtr = notifier.firstFileHandlerPtr; notifier.firstFileHandlerPtr = filePtr; } filePtr->proc = proc; filePtr->clientData = clientData; /* * Register the file with the Xt notifier, if it hasn't been done yet. */ if (mask & TCL_READABLE) { if (!(filePtr->mask & TCL_READABLE)) { filePtr->read = XtAppAddInput(notifier.appContext, fd, INT2PTR(XtInputReadMask), FileProc, filePtr); } } else { if (filePtr->mask & TCL_READABLE) { XtRemoveInput(filePtr->read); } } if (mask & TCL_WRITABLE) { if (!(filePtr->mask & TCL_WRITABLE)) { filePtr->write = XtAppAddInput(notifier.appContext, fd, INT2PTR(XtInputWriteMask), FileProc, filePtr); } } else { if (filePtr->mask & TCL_WRITABLE) { XtRemoveInput(filePtr->write); } } if (mask & TCL_EXCEPTION) { if (!(filePtr->mask & TCL_EXCEPTION)) { filePtr->except = XtAppAddInput(notifier.appContext, fd, INT2PTR(XtInputExceptMask), FileProc, filePtr); } } else { if (filePtr->mask & TCL_EXCEPTION) { XtRemoveInput(filePtr->except); } } filePtr->mask = mask; } /* *---------------------------------------------------------------------- * * DeleteFileHandler -- * * Cancel a previously-arranged callback arrangement for a file. * * Results: * None. * * Side effects: * If a callback was previously registered on file, remove it. * *---------------------------------------------------------------------- */ static void DeleteFileHandler( int fd) /* Stream id for which to remove callback * procedure. */ { FileHandler *filePtr, *prevPtr; if (!initialized) { InitNotifier(); } TclSetAppContext(NULL); /* * Find the entry for the given file (and return if there isn't one). */ for (prevPtr = NULL, filePtr = notifier.firstFileHandlerPtr; ; prevPtr = filePtr, filePtr = filePtr->nextPtr) { if (filePtr == NULL) { return; } if (filePtr->fd == fd) { break; } } /* * Clean up information in the callback record. */ if (prevPtr == NULL) { notifier.firstFileHandlerPtr = filePtr->nextPtr; } else { prevPtr->nextPtr = filePtr->nextPtr; } if (filePtr->mask & TCL_READABLE) { XtRemoveInput(filePtr->read); } if (filePtr->mask & TCL_WRITABLE) { XtRemoveInput(filePtr->write); } if (filePtr->mask & TCL_EXCEPTION) { XtRemoveInput(filePtr->except); } Tcl_Free(filePtr); } /* *---------------------------------------------------------------------- * * FileProc -- * * These procedures are called by Xt when a file becomes readable, * writable, or has an exception. * * Results: * None. * * Side effects: * Makes an entry on the Tcl event queue if the event is interesting. * *---------------------------------------------------------------------- */ static void FileProc( XtPointer clientData, int *fd, XtInputId *id) { FileHandler *filePtr = (FileHandler *) clientData; FileHandlerEvent *fileEvPtr; int mask = 0; /* * Determine which event happened. */ if (*id == filePtr->read) { mask = TCL_READABLE; } else if (*id == filePtr->write) { mask = TCL_WRITABLE; } else if (*id == filePtr->except) { mask = TCL_EXCEPTION; } /* * Ignore unwanted or duplicate events. */ if (!(filePtr->mask & mask) || (filePtr->readyMask & mask)) { return; } /* * This is an interesting event, so put it onto the event queue. */ filePtr->readyMask |= mask; fileEvPtr = (FileHandlerEvent *) Tcl_Alloc(sizeof(FileHandlerEvent)); fileEvPtr->header.proc = FileHandlerEventProc; fileEvPtr->fd = filePtr->fd; Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); /* * Process events on the Tcl event queue before returning to Xt. */ Tcl_ServiceAll(); } /* *---------------------------------------------------------------------- * * FileHandlerEventProc -- * * This procedure is called by Tcl_ServiceEvent when a file event reaches * the front of the event queue. This procedure is responsible for * actually handling the event by invoking the callback for the file * handler. * * Results: * Returns 1 if the event was handled, meaning it should be removed from * the queue. Returns 0 if the event was not handled, meaning it should * stay on the queue. The only time the event isn't handled is if the * TCL_FILE_EVENTS flag bit isn't set. * * Side effects: * Whatever the file handler's callback procedure does. * *---------------------------------------------------------------------- */ static int FileHandlerEventProc( Tcl_Event *evPtr, /* Event to service. */ int flags) /* Flags that indicate what events to handle, * such as TCL_FILE_EVENTS. */ { FileHandler *filePtr; FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr; int mask; if (!(flags & TCL_FILE_EVENTS)) { return 0; } /* * Search through the file handlers to find the one whose handle matches * the event. We do this rather than keeping a pointer to the file handler * directly in the event, so that the handler can be deleted while the * event is queued without leaving a dangling pointer. */ for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL; filePtr = filePtr->nextPtr) { if (filePtr->fd != fileEvPtr->fd) { continue; } /* * The code is tricky for two reasons: * 1. The file handler's desired events could have changed since the * time when the event was queued, so AND the ready mask with the * desired mask. * 2. The file could have been closed and re-opened since the time * when the event was queued. This is why the ready mask is stored * in the file handler rather than the queued event: it will be * zeroed when a new file handler is created for the newly opened * file. */ mask = filePtr->readyMask & filePtr->mask; filePtr->readyMask = 0; if (mask != 0) { filePtr->proc(filePtr->clientData, mask); } break; } return 1; } /* *---------------------------------------------------------------------- * * WaitForEvent -- * * This function is called by Tcl_DoOneEvent to wait for new events on * the message queue. If the block time is 0, then Tcl_WaitForEvent just * polls without blocking. * * Results: * Returns 1 if an event was found, else 0. This ensures that * Tcl_DoOneEvent will return 1, even if the event is handled by non-Tcl * code. * * Side effects: * Queues file events that are detected by the select. * *---------------------------------------------------------------------- */ static int WaitForEvent( const Tcl_Time *timePtr) /* Maximum block time, or NULL. */ { int timeout; if (!initialized) { InitNotifier(); } TclSetAppContext(NULL); if (timePtr) { timeout = timePtr->sec * 1000 + timePtr->usec / 1000; if (timeout == 0) { if (XtAppPending(notifier.appContext)) { goto process; } else { return 0; } } else { Tcl_SetTimer(timePtr); } } process: XtAppProcessEvent(notifier.appContext, XtIMAll); return 1; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/tclXtTest.c0000644000175000017500000000620514726623136014646 0ustar sergeisergei/* * tclXtTest.c -- * * Contains commands for Xt notifier specific tests on Unix. * * Copyright © 1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef USE_TCL_STUBS # define USE_TCL_STUBS #endif #include #include "tcl.h" static Tcl_ObjCmdProc TesteventloopCmd; /* * Functions defined in tclXtNotify.c for use by users of the Xt Notifier: */ extern void InitNotifier(void); extern XtAppContext TclSetAppContext(XtAppContext ctx); /* *---------------------------------------------------------------------- * * Tclxttest_Init -- * * This procedure performs application-specific initialization. Most * applications, especially those that incorporate additional packages, * will have their own version of this procedure. * * Results: * Returns a standard Tcl completion code, and leaves an error message in * the interp's result if an error occurs. * * Side effects: * Depends on the startup script. * *---------------------------------------------------------------------- */ DLLEXPORT int Tclxttest_Init( Tcl_Interp *interp) /* Interpreter for application. */ { if (Tcl_InitStubs(interp, "8.5-", 0) == NULL) { return TCL_ERROR; } XtToolkitInitialize(); InitNotifier(); Tcl_CreateObjCommand(interp, "testeventloop", TesteventloopCmd, NULL, NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * TesteventloopCmd -- * * This procedure implements the "testeventloop" command. It is used to * test the Tcl notifier from an "external" event loop (i.e. not * Tcl_DoOneEvent()). * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TesteventloopCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static int *framePtr = NULL;/* Pointer to integer on stack frame of * innermost invocation of the "wait" * subcommand. */ if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ..."); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[1]), "done") == 0) { *framePtr = 1; } else if (strcmp(Tcl_GetString(objv[1]), "wait") == 0) { int *oldFramePtr; int done; int oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); /* * Save the old stack frame pointer and set up the current frame. */ oldFramePtr = framePtr; framePtr = &done; /* * Enter an Xt event loop until the flag changes. Note that we do not * explicitly call Tcl_ServiceEvent(). */ done = 0; while (!done) { XtAppProcessEvent(TclSetAppContext(NULL), XtIMAll); } (void) Tcl_SetServiceMode(oldMode); framePtr = oldFramePtr; } else { Tcl_AppendResult(interp, "bad option \"", Tcl_GetString(objv[1]), "\": must be done or wait", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * End: */ tcl9.0.1/unix/tclUnixPort.h0000644000175000017500000004473014726623136015215 0ustar sergeisergei/* * tclUnixPort.h -- * * This header file handles porting issues that occur because of * differences between systems. It reads in UNIX-related header files and * sets up UNIX-related macros for Tcl's UNIX core. It should be the only * file that contains #ifdefs to handle different flavors of UNIX. This * file sets up the union of all UNIX-related things needed by any of the * Tcl core files. This file depends on configuration #defines such as * HAVE_SYS_PARAM_H that are set up by the "configure" script. * * Much of the material in this file was originally contributed by Karl * Lehenbauer, Mark Diekhans and Peter da Silva. * * Copyright (c) 1991-1994 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef _TCLUNIXPORT #define _TCLUNIXPORT /* *--------------------------------------------------------------------------- * The following sets of #includes and #ifdefs are required to get Tcl to * compile under the various flavors of unix. *--------------------------------------------------------------------------- */ #include #include #ifdef HAVE_NET_ERRNO_H # include #endif #include #include #ifdef HAVE_SYS_PARAM_H # include #endif #include #include /* *--------------------------------------------------------------------------- * Parameterize for 64-bit filesystem support. *--------------------------------------------------------------------------- */ #ifdef HAVE_STRUCT_DIRENT64 typedef struct dirent64 Tcl_DirEntry; # define TclOSreaddir readdir64 #else typedef struct dirent Tcl_DirEntry; # define TclOSreaddir readdir #endif #ifdef HAVE_DIR64 typedef DIR64 TclDIR; # define TclOSopendir opendir64 # define TclOSrewinddir rewinddir64 # define TclOSclosedir closedir64 #else typedef DIR TclDIR; # define TclOSopendir opendir # define TclOSrewinddir rewinddir # define TclOSclosedir closedir #endif #ifdef HAVE_TYPE_OFF64_T typedef off64_t Tcl_SeekOffset; # define TclOSseek lseek64 # define TclOSopen open64 #else typedef off_t Tcl_SeekOffset; # define TclOSseek lseek # define TclOSopen open #endif #ifdef __CYGWIN__ #ifdef __cplusplus extern "C" { #endif /* Make some symbols available without including */ # define CP_UTF8 65001 # define GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS 0x00000004 # define HMODULE void * # define MAX_PATH 260 # define SOCKET unsigned int # define WSAEWOULDBLOCK 10035 typedef unsigned short WCHAR; #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wignored-attributes" #endif __declspec(dllimport) extern __stdcall int GetModuleHandleExW(unsigned int, const void *, void *); __declspec(dllimport) extern __stdcall int GetModuleFileNameW(void *, const void *, int); __declspec(dllimport) extern __stdcall int WideCharToMultiByte(int, int, const void *, int, char *, int, const char *, void *); __declspec(dllimport) extern __stdcall int MultiByteToWideChar(int, int, const char *, int, WCHAR *, int); __declspec(dllimport) extern __stdcall void OutputDebugStringW(const WCHAR *); __declspec(dllimport) extern __stdcall int IsDebuggerPresent(void); __declspec(dllimport) extern __stdcall int GetLastError(void); __declspec(dllimport) extern __stdcall int GetFileAttributesW(const WCHAR *); __declspec(dllimport) extern __stdcall int SetFileAttributesW(const WCHAR *, int); __declspec(dllimport) extern int cygwin_conv_path(int, const void *, void *, int); #ifdef __clang__ #pragma clang diagnostic pop #endif # define timezone _timezone extern int TclOSfstat(int fd, void *statBuf); extern int TclOSstat(const char *name, void *statBuf); extern int TclOSlstat(const char *name, void *statBuf); #ifdef __cplusplus } #endif #else # define TclOSfstat(fd, buf) fstat(fd, (struct stat *)buf) # define TclOSstat(name, buf) stat(name, (struct stat *)buf) # define TclOSlstat(name, buf) lstat(name, (struct stat *)buf) #endif /* *--------------------------------------------------------------------------- * Miscellaneous includes that might be missing. *--------------------------------------------------------------------------- */ #include #ifdef HAVE_SYS_SELECT_H # include #endif #include #ifdef HAVE_SYS_TIME_H # include #endif #include #ifndef NO_SYS_WAIT_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #include #include MODULE_SCOPE int TclUnixSetBlockingMode(int fd, int mode); #include /* *--------------------------------------------------------------------------- * Socket support stuff: This likely needs more work to parameterize for each * system. *--------------------------------------------------------------------------- */ #include /* struct sockaddr, SOCK_STREAM, ... */ #ifndef NO_UNAME # include /* uname system call. */ #endif #include /* struct in_addr, struct sockaddr_in */ #include /* inet_ntoa() */ #include /* getaddrinfo() */ #ifdef NEED_FAKE_RFC2553 # include "../compat/fake-rfc2553.h" #endif /* *--------------------------------------------------------------------------- * Some platforms (e.g. SunOS) don't define FLT_MAX and FLT_MIN, so we look * for an alternative definition. If no other alternative is available we use * a reasonable guess. *--------------------------------------------------------------------------- */ #include #ifndef FLT_MAX # ifdef MAXFLOAT # define FLT_MAX MAXFLOAT # else # define FLT_MAX 3.402823466E+38F # endif #endif #ifndef FLT_MIN # ifdef MINFLOAT # define FLT_MIN MINFLOAT # else # define FLT_MIN 1.175494351E-38F # endif #endif /* *--------------------------------------------------------------------------- * NeXT doesn't define O_NONBLOCK, so #define it here if necessary. *--------------------------------------------------------------------------- */ #ifndef O_NONBLOCK # define O_NONBLOCK 0x80 #endif /* *--------------------------------------------------------------------------- * The type of the status returned by wait varies from UNIX system to UNIX * system. The macro below defines it: *--------------------------------------------------------------------------- */ #ifdef _AIX # define WAIT_STATUS_TYPE pid_t #else #ifndef NO_UNION_WAIT # define WAIT_STATUS_TYPE union wait #else # define WAIT_STATUS_TYPE int #endif #endif /* *--------------------------------------------------------------------------- * Supply definitions for macros to query wait status, if not already defined * in header files above. *--------------------------------------------------------------------------- */ #ifndef WIFEXITED # define WIFEXITED(stat) (((*((int *) &(stat))) & 0xFF) == 0) #endif #ifndef WEXITSTATUS # define WEXITSTATUS(stat) (((*((int *) &(stat))) >> 8) & 0xFF) #endif #ifndef WIFSIGNALED # define WIFSIGNALED(stat) \ (((*((int *) &(stat)))) && ((*((int *) &(stat))) \ == ((*((int *) &(stat))) & 0x00FF))) #endif #ifndef WTERMSIG # define WTERMSIG(stat) ((*((int *) &(stat))) & 0x7F) #endif #ifndef WIFSTOPPED # define WIFSTOPPED(stat) (((*((int *) &(stat))) & 0xFF) == 0177) #endif #ifndef WSTOPSIG # define WSTOPSIG(stat) (((*((int *) &(stat))) >> 8) & 0xFF) #endif /* *--------------------------------------------------------------------------- * Define constants for waitpid() system call if they aren't defined by a * system header file. *--------------------------------------------------------------------------- */ #ifndef WNOHANG # define WNOHANG 1 #endif #ifndef WUNTRACED # define WUNTRACED 2 #endif /* *--------------------------------------------------------------------------- * Supply macros for seek offsets, if they're not already provided by an * include file. *--------------------------------------------------------------------------- */ #ifndef SEEK_SET # define SEEK_SET 0 #endif #ifndef SEEK_CUR # define SEEK_CUR 1 #endif #ifndef SEEK_END # define SEEK_END 2 #endif /* *--------------------------------------------------------------------------- * The stuff below is needed by the "time" command. If this system has no * gettimeofday call, then must use times() instead. *--------------------------------------------------------------------------- */ #ifdef NO_GETTOD # include #else # ifdef HAVE_BSDGETTIMEOFDAY # define gettimeofday BSDgettimeofday # endif #endif #ifdef GETTOD_NOT_DECLARED extern int gettimeofday(struct timeval *tp, struct timezone *tzp); #endif /* *--------------------------------------------------------------------------- * Define access mode constants if they aren't already defined. *--------------------------------------------------------------------------- */ #ifndef F_OK # define F_OK 00 #endif #ifndef X_OK # define X_OK 01 #endif #ifndef W_OK # define W_OK 02 #endif #ifndef R_OK # define R_OK 04 #endif /* *--------------------------------------------------------------------------- * Define FD_CLOEEXEC (the close-on-exec flag bit) if it isn't already * defined. *--------------------------------------------------------------------------- */ #ifndef FD_CLOEXEC # define FD_CLOEXEC 1 #endif /* *--------------------------------------------------------------------------- * On systems without symbolic links (i.e. S_IFLNK isn't defined) define * "lstat" to use "stat" instead. *--------------------------------------------------------------------------- */ #ifndef S_IFLNK # undef TclOSlstat # define lstat stat # define lstat64 stat64 # define TclOSlstat TclOSstat #endif /* *--------------------------------------------------------------------------- * Define macros to query file type bits, if they're not already defined. *--------------------------------------------------------------------------- */ #ifndef S_ISREG # ifdef S_IFREG # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) # else # define S_ISREG(m) 0 # endif #endif /* !S_ISREG */ #ifndef S_ISDIR # ifdef S_IFDIR # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) # else # define S_ISDIR(m) 0 # endif #endif /* !S_ISDIR */ #ifndef S_ISCHR # ifdef S_IFCHR # define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) # else # define S_ISCHR(m) 0 # endif #endif /* !S_ISCHR */ #ifndef S_ISBLK # ifdef S_IFBLK # define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) # else # define S_ISBLK(m) 0 # endif #endif /* !S_ISBLK */ #ifndef S_ISFIFO # ifdef S_IFIFO # define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) # else # define S_ISFIFO(m) 0 # endif #endif /* !S_ISFIFO */ #ifndef S_ISLNK # ifdef S_IFLNK # define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) # else # define S_ISLNK(m) 0 # endif #endif /* !S_ISLNK */ #ifndef S_ISSOCK # ifdef S_IFSOCK # define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) # else # define S_ISSOCK(m) 0 # endif #endif /* !S_ISSOCK */ /* *--------------------------------------------------------------------------- * Make sure that MAXPATHLEN and MAXNAMLEN are defined. *--------------------------------------------------------------------------- */ #ifndef MAXPATHLEN # ifdef PATH_MAX # define MAXPATHLEN PATH_MAX # else # define MAXPATHLEN 2048 # endif #endif #ifndef MAXNAMLEN # ifdef NAME_MAX # define MAXNAMLEN NAME_MAX # else # define MAXNAMLEN 255 # endif #endif /* *--------------------------------------------------------------------------- * The following macro defines the type of the mask arguments to select: *--------------------------------------------------------------------------- */ #ifndef NO_FD_SET # define SELECT_MASK fd_set #else /* NO_FD_SET */ # ifndef _AIX typedef long fd_mask; # endif /* !AIX */ # if defined(_IBMR2) # define SELECT_MASK void # else /* !defined(_IBMR2) */ # define SELECT_MASK int # endif /* defined(_IBMR2) */ #endif /* !NO_FD_SET */ /* *--------------------------------------------------------------------------- * Define "NBBY" (number of bits per byte) if it's not already defined. *--------------------------------------------------------------------------- */ #ifndef NBBY # define NBBY 8 #endif /* *--------------------------------------------------------------------------- * The following macro defines the number of fd_masks in an fd_set: *--------------------------------------------------------------------------- */ #ifndef FD_SETSIZE # ifdef OPEN_MAX # define FD_SETSIZE OPEN_MAX # else # define FD_SETSIZE 256 # endif #endif /* FD_SETSIZE */ #ifndef howmany # define howmany(x, y) (((x)+((y)-1))/(y)) #endif /* !defined(howmany) */ #ifndef NFDBITS # define NFDBITS NBBY*sizeof(fd_mask) #endif /* NFDBITS */ #define MASK_SIZE howmany(FD_SETSIZE, NFDBITS) /* *--------------------------------------------------------------------------- * Not all systems declare the errno variable in errno.h, so this file does it * explicitly. The list of system error messages also isn't generally declared * in a header file anywhere. *--------------------------------------------------------------------------- */ #ifdef NO_ERRNO extern int errno; #endif /* NO_ERRNO */ /* *--------------------------------------------------------------------------- * Not all systems declare all the errors that Tcl uses! Provide some * work-arounds... *--------------------------------------------------------------------------- */ #ifndef EOVERFLOW # ifdef EFBIG # define EOVERFLOW EFBIG # else /* !EFBIG */ # define EOVERFLOW EINVAL # endif /* EFBIG */ #endif /* EOVERFLOW */ /* *--------------------------------------------------------------------------- * Variables provided by the C library: *--------------------------------------------------------------------------- */ #if defined(__APPLE__) && defined(__DYNAMIC__) # include # define environ (*_NSGetEnviron()) # define USE_PUTENV 1 #else # if defined(_sgi) || defined(__sgi) # define environ _environ # endif extern char ** environ; #endif /* *--------------------------------------------------------------------------- * Darwin specifc configure overrides. *--------------------------------------------------------------------------- */ #ifdef __APPLE__ /* *--------------------------------------------------------------------------- * Support for fat compiles: configure runs only once for multiple architectures *--------------------------------------------------------------------------- */ # if defined(__LP64__) && defined (NO_COREFOUNDATION_64) # undef HAVE_COREFOUNDATION # endif /* __LP64__ && NO_COREFOUNDATION_64 */ # include # ifdef __DARWIN_UNIX03 # if __DARWIN_UNIX03 # undef HAVE_PUTENV_THAT_COPIES # else # define HAVE_PUTENV_THAT_COPIES 1 # endif # endif /* __DARWIN_UNIX03 */ /* *--------------------------------------------------------------------------- * Include AvailabilityMacros.h here (when available) to ensure any symbolic * MAC_OS_X_VERSION_* constants passed on the command line are translated. *--------------------------------------------------------------------------- */ # include /* *--------------------------------------------------------------------------- * Support for weak import. *--------------------------------------------------------------------------- */ # ifdef HAVE_WEAK_IMPORT # ifndef WEAK_IMPORT_ATTRIBUTE # define WEAK_IMPORT_ATTRIBUTE __attribute__((weak_import)) # endif # endif /* HAVE_WEAK_IMPORT */ /* * For now, test exec-17.1 fails (I/O setup after closing stdout) with * posix_spawnp(), but the classic implementation (based on fork()+execvp()) * works well under macOS. */ # undef HAVE_POSIX_SPAWNP # undef HAVE_VFORK #endif /* __APPLE__ */ /* *--------------------------------------------------------------------------- * The following macros and declarations represent the interface between * generic and unix-specific parts of Tcl. Some of the macros may override * functions declared in tclInt.h. *--------------------------------------------------------------------------- */ /* * The default platform eol translation on Unix is TCL_TRANSLATE_LF. */ #ifdef DJGPP #define TCL_PLATFORM_TRANSLATION TCL_TRANSLATE_CRLF typedef int socklen_t; #else #define TCL_PLATFORM_TRANSLATION TCL_TRANSLATE_LF #endif /* *--------------------------------------------------------------------------- * The following macros have trivial definitions, allowing generic code to * address platform-specific issues. *--------------------------------------------------------------------------- */ #define TclpReleaseFile(file) /* Nothing. */ /* *--------------------------------------------------------------------------- * The following defines wrap the system memory allocation routines. *--------------------------------------------------------------------------- */ #define TclpSysAlloc(size) malloc(size) #define TclpSysFree(ptr) free(ptr) #define TclpSysRealloc(ptr, size) realloc(ptr, size) /* *--------------------------------------------------------------------------- * The following macros and declaration wrap the C runtime library functions. *--------------------------------------------------------------------------- */ #if !defined(TCL_THREADS) || TCL_THREADS # include #endif /* TCL_THREADS */ /* FIXME - Hyper-enormous platform assumption! */ #ifndef AF_INET6 # define AF_INET6 10 #endif /* *--------------------------------------------------------------------------- * Set of MT-safe implementations of some known-to-be-MT-unsafe library calls. * Instead of returning pointers to the static storage, those return pointers * to the TSD data. *--------------------------------------------------------------------------- */ #include #include MODULE_SCOPE struct passwd * TclpGetPwNam(const char *name); MODULE_SCOPE struct group * TclpGetGrNam(const char *name); MODULE_SCOPE struct passwd * TclpGetPwUid(uid_t uid); MODULE_SCOPE struct group * TclpGetGrGid(gid_t gid); MODULE_SCOPE struct hostent * TclpGetHostByName(const char *name); MODULE_SCOPE struct hostent * TclpGetHostByAddr(const char *addr, int length, int type); MODULE_SCOPE void *TclpMakeTcpClientChannelMode( void *tcpSocket, int mode); #endif /* _TCLUNIXPORT */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/unix/Makefile.in0000644000175000017500000026323214726623136014616 0ustar sergeisergei# # This file is a Makefile for Tcl. If it has the name "Makefile.in" then it is # a template for a Makefile; to generate the actual Makefile, run # "./configure", which is a configuration script generated by the "autoconf" # program (constructs like "@foo@" will get replaced in the actual Makefile. VERSION = @TCL_VERSION@ MAJOR_VERSION = @TCL_MAJOR_VERSION@ MINOR_VERSION = @TCL_MINOR_VERSION@ PATCH_LEVEL = @TCL_PATCH_LEVEL@ #-------------------------------------------------------------------------- # Things you can change to personalize the Makefile for your own site (you can # make these changes in either Makefile.in or Makefile, but changes to # Makefile will get lost if you re-run the configuration script). #-------------------------------------------------------------------------- # Default top-level directories in which to install architecture-specific # files (exec_prefix) and machine-independent files such as scripts (prefix). # The values specified here may be overridden at configure-time with the # --exec-prefix and --prefix options to the "configure" script. The *dir vars # are standard configure substitutions that are based off prefix and # exec_prefix. prefix = @prefix@ exec_prefix = @exec_prefix@ bindir = @bindir@ libdir = @libdir@ includedir = @includedir@ datarootdir = @datarootdir@ runstatedir = @runstatedir@ mandir = @mandir@ # The following definition can be set to non-null for special systems like AFS # with replication. It allows the pathnames used for installation to be # different than those used for actually reference files at run-time. # INSTALL_ROOT is prepended to $prefix and $exec_prefix when installing files. INSTALL_ROOT = $(DESTDIR) # Path for the platform independent Tcl scripting libraries: TCL_LIBRARY = @TCL_LIBRARY@ # Path to use at runtime to refer to LIB_INSTALL_DIR: LIB_RUNTIME_DIR = $(libdir) # Directory in which to install the program tclsh: BIN_INSTALL_DIR = $(INSTALL_ROOT)$(bindir) # Directory in which to install libtcl.so or libtcl.a: LIB_INSTALL_DIR = $(INSTALL_ROOT)$(libdir) DLL_INSTALL_DIR = @DLL_INSTALL_DIR@ # Path name to use when installing library scripts. SCRIPT_INSTALL_DIR = $(INSTALL_ROOT)$(TCL_LIBRARY) # Path name to use when installing Tcl modules. MODULE_INSTALL_DIR = $(SCRIPT_INSTALL_DIR)/../tcl9 # Directory in which to install the include file tcl.h: INCLUDE_INSTALL_DIR = $(INSTALL_ROOT)$(includedir) # Path to the private tcl header dir: PRIVATE_INCLUDE_DIR = @PRIVATE_INCLUDE_DIR@ # Directory in which to (optionally) install the private tcl headers: PRIVATE_INCLUDE_INSTALL_DIR = $(INSTALL_ROOT)$(PRIVATE_INCLUDE_DIR) # Top-level directory in which to install manual entries: MAN_INSTALL_DIR = $(INSTALL_ROOT)$(mandir) # Directory in which to install manual entry for tclsh: MAN1_INSTALL_DIR = $(MAN_INSTALL_DIR)/man1 # Directory in which to install manual entries for Tcl's C library procedures: MAN3_INSTALL_DIR = $(MAN_INSTALL_DIR)/man3 # Directory in which to install manual entries for the built-in Tcl commands: MANN_INSTALL_DIR = $(MAN_INSTALL_DIR)/mann # Path to the html documentation dir: HTML_DIR = @HTML_DIR@ # Directory in which to install html documentation: HTML_INSTALL_DIR = $(INSTALL_ROOT)$(HTML_DIR) # Directory in which to install the configuration file tclConfig.sh CONFIG_INSTALL_DIR = $(INSTALL_ROOT)$(libdir) # Directory in which to install bundled packages: PACKAGE_DIR = @PACKAGE_DIR@ # Package search path. TCL_PACKAGE_PATH = @TCL_PACKAGE_PATH@ # Tcl Module default path roots (TIP189). TCL_MODULE_PATH = @TCL_MODULE_PATH@ # warning flags CFLAGS_WARNING = @CFLAGS_WARNING@ # The default switches for optimization or debugging CFLAGS_DEBUG = @CFLAGS_DEBUG@ CFLAGS_OPTIMIZE = @CFLAGS_OPTIMIZE@ # To change the compiler switches, for example to change from optimization to # debugging symbols, change the following line: #CFLAGS = $(CFLAGS_DEBUG) #CFLAGS = $(CFLAGS_OPTIMIZE) #CFLAGS = $(CFLAGS_DEBUG) $(CFLAGS_OPTIMIZE) CFLAGS = @CFLAGS_DEFAULT@ @CFLAGS@ # Flags to pass to the linker LDFLAGS_DEBUG = @LDFLAGS_DEBUG@ LDFLAGS_OPTIMIZE = @LDFLAGS_OPTIMIZE@ LDFLAGS = @LDFLAGS_DEFAULT@ @LDFLAGS@ # If you use the setenv, putenv, or unsetenv procedures to modify environment # variables in your application and you'd like those modifications to appear # in the "env" Tcl variable, switch the comments on the two lines below so # that Tcl provides these procedures instead of your standard C library. ENV_FLAGS = #ENV_FLAGS = -DTclSetEnv=setenv -DTcl_PutEnv=putenv -DTclUnsetEnv=unsetenv # To enable memory debugging, call configure with --enable-symbols=mem # Warning: if you enable memory debugging, you must do it *everywhere*, # including all the code that calls Tcl, and you must use Tcl_Alloc and Tcl_Free # everywhere instead of malloc and free. TCL_STUB_LIB_FILE = @TCL_STUB_LIB_FILE@ #TCL_STUB_LIB_FILE = libtclstub.a # Generic stub lib name used in rules that apply to tcl and tk STUB_LIB_FILE = ${TCL_STUB_LIB_FILE} TCL_STUB_LIB_FLAG = @TCL_STUB_LIB_FLAG@ #TCL_STUB_LIB_FLAG = -ltclstub # To compile without backward compatibility and deprecated code uncomment the # following NO_DEPRECATED_FLAGS = #NO_DEPRECATED_FLAGS = -DTCL_NO_DEPRECATED # Some versions of make, like SGI's, use the following variable to determine # which shell to use for executing commands: SHELL = @MAKEFILE_SHELL@ # Tcl used to let the configure script choose which program to use for # installing, but there are just too many different versions of "install" # around; better to use the install-sh script that comes with the # distribution, which is slower but guaranteed to work. INSTALL_STRIP_PROGRAM = strip INSTALL_STRIP_LIBRARY = strip -x INSTALL = $(SHELL) $(UNIX_DIR)/install-sh -c INSTALL_PROGRAM = ${INSTALL} INSTALL_LIBRARY = ${INSTALL} INSTALL_DATA = ${INSTALL} -m 644 INSTALL_DATA_DIR = ${INSTALL} -d -m 755 # NATIVE_TCLSH is the name of a tclsh executable that is available *BEFORE* # running make for the first time. Certain build targets (make genstubs) need # it to be available on the PATH. This executable should *NOT* be required # just to do a normal build although it can be required to run make dist. # Do not use SHELL_ENV for NATIVE_TCLSH unless it is the tclsh being built. EXE_SUFFIX = @EXEEXT@ TCL_EXE = tclsh${EXE_SUFFIX} TCLTEST_EXE = tcltest${EXE_SUFFIX} NATIVE_TCLSH = @TCLSH_PROG@ # The symbols below provide support for dynamic loading and shared libraries. # See configure.ac for a description of what the symbols mean. The values of # the symbols are normally set by the configure script. You shouldn't normally # need to modify any of these definitions by hand. STLIB_LD = @STLIB_LD@ SHLIB_LD = @SHLIB_LD@ SHLIB_CFLAGS = @SHLIB_CFLAGS@ SHLIB_LD_LIBS = @SHLIB_LD_LIBS@ SHLIB_LD_FLAGS = @SHLIB_LD_FLAGS@ TCL_SHLIB_LD_EXTRAS = @TCL_SHLIB_LD_EXTRAS@ SHLIB_SUFFIX = @SHLIB_SUFFIX@ DLTEST_TARGETS = dltest.marker # Additional search flags needed to find the various shared libraries at # run-time. The first symbol is for use when creating a binary with cc, and # the second is for use when running ld directly. CC_SEARCH_FLAGS = @CC_SEARCH_FLAGS@ LD_SEARCH_FLAGS = @LD_SEARCH_FLAGS@ # The following symbol is defined to "$(DLTEST_TARGETS)" if dynamic loading is # available; this causes everything in the "dltest" subdirectory to be built # when making "tcltest. If dynamic loading isn't available, configure defines # this symbol to an empty string, in which case the shared libraries aren't # built. BUILD_DLTEST = @BUILD_DLTEST@ #BUILD_DLTEST = TCL_LIB_FILE = @TCL_LIB_FILE@ #TCL_LIB_FILE = libtcl.a # Generic lib name used in rules that apply to tcl and tk LIB_FILE = ${TCL_LIB_FILE} TCL_LIB_FLAG = @TCL_LIB_FLAG@ #TCL_LIB_FLAG = -ltcl # support for embedded libraries on Darwin / Mac OS X DYLIB_INSTALL_DIR = $(libdir) #-------------------------------------------------------------------------- # The information below is modified by the configure script when Makefile is # generated from Makefile.in. You shouldn't normally modify any of this stuff # by hand. #-------------------------------------------------------------------------- COMPAT_OBJS = @LIBOBJS@ AC_FLAGS = @DEFS@ AR = @AR@ RANLIB = @RANLIB@ DTRACE = @DTRACE@ SRC_DIR = @srcdir@ TOP_DIR = @TCL_SRC_DIR@ BUILD_DIR = @builddir@ GENERIC_DIR = $(TOP_DIR)/generic COMPAT_DIR = $(TOP_DIR)/compat TOOL_DIR = $(TOP_DIR)/tools UNIX_DIR = $(TOP_DIR)/unix MAC_OSX_DIR = $(TOP_DIR)/macosx PKGS_DIR = $(TOP_DIR)/pkgs # Must be absolute because of the cd dltest $(DLTEST_DIR)/configure below. DLTEST_DIR = @TCL_SRC_DIR@/unix/dltest # Must be absolute to so the corresponding tcltest's tcl_library is absolute. TCL_BUILDTIME_LIBRARY = @TCL_BUILDTIME_LIBRARY@ ZLIB_DIR = ${COMPAT_DIR}/zlib ZLIB_INCLUDE = @ZLIB_INCLUDE@ TOMMATH_DIR = $(TOP_DIR)/libtommath TOMMATH_INCLUDE = @TOMMATH_INCLUDE@ CC = @CC@ OBJEXT = @OBJEXT@ #CC = purify -best-effort @CC@ -DPURIFY # Flags to be passed to installManPage to control how the manpages should be # installed (symlinks, compression, package name suffix). MAN_FLAGS = @MAN_FLAGS@ # If non-empty, install the timezone files that are included with Tcl, # otherwise use the ones that ship with the OS. INSTALL_TZDATA = @INSTALL_TZDATA@ #-------------------------------------------------------------------------- # The information below is usually usable as is. The configure script won't # modify it and it only exists to make working around selected rare system # configurations easier. #-------------------------------------------------------------------------- GDB = gdb LLDB = lldb TRACE = strace TRACE_OPTS = VALGRIND = valgrind VALGRINDARGS = --tool=memcheck --num-callers=24 \ --leak-resolution=high --leak-check=yes --show-reachable=yes -v \ --keep-debuginfo=yes \ --suppressions=$(TOOL_DIR)/valgrind_suppress #-------------------------------------------------------------------------- # The information below should be usable as is. The configure script won't # modify it and you shouldn't need to modify it either. #-------------------------------------------------------------------------- STUB_CC_SWITCHES = -I"${BUILD_DIR}" -I${UNIX_DIR} -I${GENERIC_DIR} -I${TOMMATH_DIR} \ ${CFLAGS} ${CFLAGS_WARNING} ${SHLIB_CFLAGS} \ ${AC_FLAGS} ${ENV_FLAGS} ${EXTRA_CFLAGS} @EXTRA_CC_SWITCHES@ \ ${NO_DEPRECATED_FLAGS} -DMP_FIXED_CUTOFFS CC_SWITCHES = $(STUB_CC_SWITCHES) -DBUILD_tcl APP_CC_SWITCHES = $(STUB_CC_SWITCHES) @EXTRA_APP_CC_SWITCHES@ LIBS = @TCL_LIBS@ DEPEND_SWITCHES = ${CFLAGS} -I${UNIX_DIR} -I${GENERIC_DIR} \ ${AC_FLAGS} ${EXTRA_CFLAGS} @EXTRA_CC_SWITCHES@ TCLSH_OBJS = tclAppInit.o TCLTEST_OBJS = tclTestInit.o tclTest.o tclTestObj.o tclTestProcBodyObj.o \ tclThreadTest.o tclUnixTest.o tclTestABSList.o XTTEST_OBJS = xtTestInit.o tclTest.o tclTestObj.o tclTestProcBodyObj.o \ tclThreadTest.o tclUnixTest.o tclXtNotify.o tclXtTest.o \ tclTestABSList.o GENERIC_OBJS = regcomp.o regexec.o regfree.o regerror.o tclAlloc.o \ tclArithSeries.o tclAssembly.o tclAsync.o tclBasic.o tclBinary.o \ tclCkalloc.o tclClock.o tclClockFmt.o tclCmdAH.o tclCmdIL.o tclCmdMZ.o \ tclCompCmds.o tclCompCmdsGR.o tclCompCmdsSZ.o tclCompExpr.o \ tclCompile.o tclConfig.o tclDate.o tclDictObj.o tclDisassemble.o \ tclEncoding.o tclEnsemble.o \ tclEnv.o tclEvent.o tclExecute.o tclFCmd.o tclFileName.o tclGet.o \ tclHash.o tclHistory.o \ tclIcu.o tclIndexObj.o tclInterp.o tclIO.o tclIOCmd.o \ tclIORChan.o tclIORTrans.o tclIOGT.o tclIOSock.o tclIOUtil.o \ tclLink.o tclListObj.o \ tclLiteral.o tclLoad.o tclMain.o tclNamesp.o tclNotify.o \ tclObj.o tclOptimize.o tclPanic.o tclParse.o tclPathObj.o tclPipe.o \ tclPkg.o tclPkgConfig.o tclPosixStr.o \ tclPreserve.o tclProc.o tclProcess.o tclRegexp.o \ tclResolve.o tclResult.o tclScan.o tclStringObj.o tclStrIdxTree.o \ tclStrToD.o tclThread.o \ tclThreadAlloc.o tclThreadJoin.o tclThreadStorage.o tclStubInit.o \ tclTimer.o tclTrace.o tclUtf.o tclUtil.o tclVar.o tclZlib.o \ tclTomMathInterface.o tclZipfs.o OO_OBJS = tclOO.o tclOOBasic.o tclOOCall.o tclOODefineCmds.o tclOOInfo.o \ tclOOMethod.o tclOOProp.o tclOOStubInit.o TOMMATH_OBJS = bn_s_mp_reverse.o bn_s_mp_mul_digs_fast.o \ bn_s_mp_sqr_fast.o bn_mp_add.o bn_mp_and.o \ bn_mp_add_d.o bn_mp_clamp.o bn_mp_clear.o bn_mp_clear_multi.o \ bn_mp_cmp.o bn_mp_cmp_d.o bn_mp_cmp_mag.o \ bn_mp_cnt_lsb.o bn_mp_copy.o \ bn_mp_count_bits.o bn_mp_div.o bn_mp_div_d.o bn_mp_div_2.o \ bn_mp_div_2d.o bn_s_mp_div_3.o bn_mp_exch.o bn_mp_expt_n.o \ bn_mp_get_mag_u64.o \ bn_mp_grow.o bn_mp_init.o \ bn_mp_init_copy.o bn_mp_init_multi.o bn_mp_init_set.o \ bn_mp_init_size.o bn_s_mp_karatsuba_mul.o \ bn_mp_init_i64.o bn_mp_init_u64.o \ bn_s_mp_karatsuba_sqr.o bn_s_mp_balance_mul.o \ bn_mp_lshd.o bn_mp_mod.o bn_mp_mod_2d.o bn_mp_mul.o bn_mp_mul_2.o \ bn_mp_mul_2d.o bn_mp_mul_d.o bn_mp_neg.o bn_mp_or.o bn_mp_pack.o \ bn_mp_pack_count.o bn_mp_radix_size.o bn_mp_radix_smap.o \ bn_mp_set_i64.o bn_mp_read_radix.o bn_mp_rshd.o \ bn_mp_set_u64.o bn_mp_shrink.o \ bn_mp_sqr.o bn_mp_sqrt.o bn_mp_sub.o bn_mp_sub_d.o \ bn_mp_signed_rsh.o \ bn_mp_to_ubin.o bn_mp_unpack.o \ bn_s_mp_toom_mul.o bn_s_mp_toom_sqr.o bn_mp_to_radix.o \ bn_mp_ubin_size.o bn_mp_xor.o bn_mp_zero.o bn_s_mp_add.o \ bn_s_mp_mul_digs.o bn_s_mp_sqr.o bn_s_mp_sub.o STUB_LIB_OBJS = tclStubLib.o \ tclStubCall.o \ tclStubLibTbl.o \ tclTomMathStubLib.o \ tclOOStubLib.o \ ${COMPAT_OBJS} UNIX_OBJS = tclUnixChan.o tclUnixEvent.o tclUnixFCmd.o \ tclUnixFile.o tclUnixPipe.o tclUnixSock.o \ tclUnixTime.o tclUnixInit.o tclUnixThrd.o \ tclUnixCompat.o NOTIFY_OBJS = tclEpollNotfy.o tclKqueueNotfy.o tclSelectNotfy.o MAC_OSX_OBJS = tclMacOSXBundle.o tclMacOSXFCmd.o tclMacOSXNotify.o CYGWIN_OBJS = tclWinError.o DTRACE_OBJ = tclDTrace.o ZLIB_OBJS = Zadler32.o Zcompress.o Zcrc32.o Zdeflate.o Zinfback.o \ Zinffast.o Zinflate.o Zinftrees.o Ztrees.o Zuncompr.o Zzutil.o TCL_OBJS = ${GENERIC_OBJS} ${UNIX_OBJS} ${NOTIFY_OBJS} ${COMPAT_OBJS} \ ${OO_OBJS} @DL_OBJS@ @PLAT_OBJS@ OBJS = ${TCL_OBJS} @DTRACE_OBJ@ @ZLIB_OBJS@ @TOMMATH_OBJS@ TCL_DECLS = \ $(GENERIC_DIR)/tcl.decls \ $(GENERIC_DIR)/tclInt.decls \ $(GENERIC_DIR)/tclOO.decls \ $(GENERIC_DIR)/tclTomMath.decls GENERIC_HDRS = \ $(GENERIC_DIR)/tcl.h \ $(GENERIC_DIR)/tclDecls.h \ $(GENERIC_DIR)/tclInt.h \ $(GENERIC_DIR)/tclIntDecls.h \ $(GENERIC_DIR)/tclIntPlatDecls.h \ $(GENERIC_DIR)/tclTomMath.h \ $(GENERIC_DIR)/tclTomMathDecls.h \ $(GENERIC_DIR)/tclOO.h \ $(GENERIC_DIR)/tclOODecls.h \ $(GENERIC_DIR)/tclOOInt.h \ $(GENERIC_DIR)/tclOOIntDecls.h \ $(GENERIC_DIR)/tclPatch.h \ $(GENERIC_DIR)/tclPlatDecls.h \ $(GENERIC_DIR)/tclPort.h \ $(GENERIC_DIR)/tclRegexp.h GENERIC_SRCS = \ $(GENERIC_DIR)/regcomp.c \ $(GENERIC_DIR)/regexec.c \ $(GENERIC_DIR)/regfree.c \ $(GENERIC_DIR)/regerror.c \ $(GENERIC_DIR)/tclAlloc.c \ $(GENERIC_DIR)/tclArithSeries.c \ $(GENERIC_DIR)/tclAssembly.c \ $(GENERIC_DIR)/tclAsync.c \ $(GENERIC_DIR)/tclBasic.c \ $(GENERIC_DIR)/tclBinary.c \ $(GENERIC_DIR)/tclCkalloc.c \ $(GENERIC_DIR)/tclClock.c \ $(GENERIC_DIR)/tclClockFmt.c \ $(GENERIC_DIR)/tclCmdAH.c \ $(GENERIC_DIR)/tclCmdIL.c \ $(GENERIC_DIR)/tclCmdMZ.c \ $(GENERIC_DIR)/tclCompCmds.c \ $(GENERIC_DIR)/tclCompCmdsGR.c \ $(GENERIC_DIR)/tclCompCmdsSZ.c \ $(GENERIC_DIR)/tclCompExpr.c \ $(GENERIC_DIR)/tclCompile.c \ $(GENERIC_DIR)/tclConfig.c \ $(GENERIC_DIR)/tclDate.c \ $(GENERIC_DIR)/tclDictObj.c \ $(GENERIC_DIR)/tclDisassemble.c \ $(GENERIC_DIR)/tclEncoding.c \ $(GENERIC_DIR)/tclEnsemble.c \ $(GENERIC_DIR)/tclEnv.c \ $(GENERIC_DIR)/tclEvent.c \ $(GENERIC_DIR)/tclExecute.c \ $(GENERIC_DIR)/tclFCmd.c \ $(GENERIC_DIR)/tclFileName.c \ $(GENERIC_DIR)/tclGet.c \ $(GENERIC_DIR)/tclHash.c \ $(GENERIC_DIR)/tclHistory.c \ $(GENERIC_DIR)/tclIcu.c \ $(GENERIC_DIR)/tclIndexObj.c \ $(GENERIC_DIR)/tclInterp.c \ $(GENERIC_DIR)/tclIO.c \ $(GENERIC_DIR)/tclIOCmd.c \ $(GENERIC_DIR)/tclIOGT.c \ $(GENERIC_DIR)/tclIOSock.c \ $(GENERIC_DIR)/tclIOUtil.c \ $(GENERIC_DIR)/tclIORChan.c \ $(GENERIC_DIR)/tclIORTrans.c \ $(GENERIC_DIR)/tclLink.c \ $(GENERIC_DIR)/tclListObj.c \ $(GENERIC_DIR)/tclLiteral.c \ $(GENERIC_DIR)/tclLoad.c \ $(GENERIC_DIR)/tclMain.c \ $(GENERIC_DIR)/tclNamesp.c \ $(GENERIC_DIR)/tclNotify.c \ $(GENERIC_DIR)/tclObj.c \ $(GENERIC_DIR)/tclOptimize.c \ $(GENERIC_DIR)/tclParse.c \ $(GENERIC_DIR)/tclPathObj.c \ $(GENERIC_DIR)/tclPipe.c \ $(GENERIC_DIR)/tclPkg.c \ $(GENERIC_DIR)/tclPkgConfig.c \ $(GENERIC_DIR)/tclPosixStr.c \ $(GENERIC_DIR)/tclPreserve.c \ $(GENERIC_DIR)/tclProc.c \ $(GENERIC_DIR)/tclProcess.c \ $(GENERIC_DIR)/tclRegexp.c \ $(GENERIC_DIR)/tclResolve.c \ $(GENERIC_DIR)/tclResult.c \ $(GENERIC_DIR)/tclScan.c \ $(GENERIC_DIR)/tclStubInit.c \ $(GENERIC_DIR)/tclStringObj.c \ $(GENERIC_DIR)/tclStrIdxTree.c \ $(GENERIC_DIR)/tclStrToD.c \ $(GENERIC_DIR)/tclTest.c \ $(GENERIC_DIR)/tclTestABSList.c \ $(GENERIC_DIR)/tclTestObj.c \ $(GENERIC_DIR)/tclTestProcBodyObj.c \ $(GENERIC_DIR)/tclThread.c \ $(GENERIC_DIR)/tclThreadAlloc.c \ $(GENERIC_DIR)/tclThreadJoin.c \ $(GENERIC_DIR)/tclThreadStorage.c \ $(GENERIC_DIR)/tclTimer.c \ $(GENERIC_DIR)/tclTrace.c \ $(GENERIC_DIR)/tclUtil.c \ $(GENERIC_DIR)/tclVar.c \ $(GENERIC_DIR)/tclAssembly.c \ $(GENERIC_DIR)/tclZlib.c \ $(GENERIC_DIR)/tclZipfs.c OO_SRCS = \ $(GENERIC_DIR)/tclOO.c \ $(GENERIC_DIR)/tclOOBasic.c \ $(GENERIC_DIR)/tclOOCall.c \ $(GENERIC_DIR)/tclOODefineCmds.c \ $(GENERIC_DIR)/tclOOInfo.c \ $(GENERIC_DIR)/tclOOMethod.c \ $(GENERIC_DIR)/tclOOProp.c \ $(GENERIC_DIR)/tclOOStubInit.c STUB_SRCS = \ $(GENERIC_DIR)/tclStubLib.c \ $(GENERIC_DIR)/tclStubCall.c \ $(GENERIC_DIR)/tclStubLibTbl.c \ $(GENERIC_DIR)/tclTomMathStubLib.c \ $(GENERIC_DIR)/tclOOStubLib.c TOMMATH_SRCS = \ $(TOMMATH_DIR)/bn_cutoffs.c \ $(TOMMATH_DIR)/bn_deprecated.c \ $(TOMMATH_DIR)/bn_mp_2expt.c \ $(TOMMATH_DIR)/bn_mp_abs.c \ $(TOMMATH_DIR)/bn_mp_add.c \ $(TOMMATH_DIR)/bn_mp_add_d.c \ $(TOMMATH_DIR)/bn_mp_addmod.c \ $(TOMMATH_DIR)/bn_mp_and.c \ $(TOMMATH_DIR)/bn_mp_clamp.c \ $(TOMMATH_DIR)/bn_mp_clear.c \ $(TOMMATH_DIR)/bn_mp_clear_multi.c \ $(TOMMATH_DIR)/bn_mp_cmp.c \ $(TOMMATH_DIR)/bn_mp_cmp_d.c \ $(TOMMATH_DIR)/bn_mp_cmp_mag.c \ $(TOMMATH_DIR)/bn_mp_cnt_lsb.c \ $(TOMMATH_DIR)/bn_mp_complement.c \ $(TOMMATH_DIR)/bn_mp_copy.c \ $(TOMMATH_DIR)/bn_mp_count_bits.c \ $(TOMMATH_DIR)/bn_mp_decr.c \ $(TOMMATH_DIR)/bn_mp_div.c \ $(TOMMATH_DIR)/bn_mp_div_2.c \ $(TOMMATH_DIR)/bn_mp_div_2d.c \ $(TOMMATH_DIR)/bn_s_mp_div_3.c \ $(TOMMATH_DIR)/bn_mp_div_d.c \ $(TOMMATH_DIR)/bn_mp_dr_is_modulus.c \ $(TOMMATH_DIR)/bn_mp_dr_reduce.c \ $(TOMMATH_DIR)/bn_mp_dr_setup.c \ $(TOMMATH_DIR)/bn_mp_error_to_string.c \ $(TOMMATH_DIR)/bn_mp_exch.c \ $(TOMMATH_DIR)/bn_mp_expt_n.c \ $(TOMMATH_DIR)/bn_mp_exptmod.c \ $(TOMMATH_DIR)/bn_mp_exteuclid.c \ $(TOMMATH_DIR)/bn_mp_fread.c \ $(TOMMATH_DIR)/bn_mp_from_sbin.c \ $(TOMMATH_DIR)/bn_mp_from_ubin.c \ $(TOMMATH_DIR)/bn_mp_fwrite.c \ $(TOMMATH_DIR)/bn_mp_gcd.c \ $(TOMMATH_DIR)/bn_mp_get_double.c \ $(TOMMATH_DIR)/bn_mp_get_i32.c \ $(TOMMATH_DIR)/bn_mp_get_i64.c \ $(TOMMATH_DIR)/bn_mp_get_l.c \ $(TOMMATH_DIR)/bn_mp_get_mag_u32.c \ $(TOMMATH_DIR)/bn_mp_get_mag_u64.c \ $(TOMMATH_DIR)/bn_mp_get_mag_ul.c \ $(TOMMATH_DIR)/bn_mp_grow.c \ $(TOMMATH_DIR)/bn_mp_incr.c \ $(TOMMATH_DIR)/bn_mp_init.c \ $(TOMMATH_DIR)/bn_mp_init_copy.c \ $(TOMMATH_DIR)/bn_mp_init_i32.c \ $(TOMMATH_DIR)/bn_mp_init_i64.c \ $(TOMMATH_DIR)/bn_mp_init_l.c \ $(TOMMATH_DIR)/bn_mp_init_multi.c \ $(TOMMATH_DIR)/bn_mp_init_set.c \ $(TOMMATH_DIR)/bn_mp_init_size.c \ $(TOMMATH_DIR)/bn_mp_init_u32.c \ $(TOMMATH_DIR)/bn_mp_init_u64.c \ $(TOMMATH_DIR)/bn_mp_init_ul.c \ $(TOMMATH_DIR)/bn_mp_invmod.c \ $(TOMMATH_DIR)/bn_mp_is_square.c \ $(TOMMATH_DIR)/bn_mp_iseven.c \ $(TOMMATH_DIR)/bn_mp_isodd.c \ $(TOMMATH_DIR)/bn_mp_kronecker.c \ $(TOMMATH_DIR)/bn_mp_lcm.c \ $(TOMMATH_DIR)/bn_mp_log_n.c \ $(TOMMATH_DIR)/bn_s_mp_log.c \ $(TOMMATH_DIR)/bn_s_mp_log_2expt.c \ $(TOMMATH_DIR)/bn_s_mp_log_d.c \ $(TOMMATH_DIR)/bn_mp_lshd.c \ $(TOMMATH_DIR)/bn_mp_mod.c \ $(TOMMATH_DIR)/bn_mp_mod_2d.c \ $(TOMMATH_DIR)/bn_mp_mod_d.c \ $(TOMMATH_DIR)/bn_mp_montgomery_calc_normalization.c \ $(TOMMATH_DIR)/bn_mp_montgomery_reduce.c \ $(TOMMATH_DIR)/bn_mp_montgomery_setup.c \ $(TOMMATH_DIR)/bn_mp_mul.c \ $(TOMMATH_DIR)/bn_mp_mul_2.c \ $(TOMMATH_DIR)/bn_mp_mul_2d.c \ $(TOMMATH_DIR)/bn_mp_mul_d.c \ $(TOMMATH_DIR)/bn_mp_mulmod.c \ $(TOMMATH_DIR)/bn_mp_neg.c \ $(TOMMATH_DIR)/bn_mp_or.c \ $(TOMMATH_DIR)/bn_mp_pack.c \ $(TOMMATH_DIR)/bn_mp_pack_count.c \ $(TOMMATH_DIR)/bn_mp_prime_fermat.c \ $(TOMMATH_DIR)/bn_mp_prime_frobenius_underwood.c \ $(TOMMATH_DIR)/bn_mp_prime_is_prime.c \ $(TOMMATH_DIR)/bn_mp_prime_miller_rabin.c \ $(TOMMATH_DIR)/bn_mp_prime_next_prime.c \ $(TOMMATH_DIR)/bn_mp_prime_rabin_miller_trials.c \ $(TOMMATH_DIR)/bn_mp_prime_rand.c \ $(TOMMATH_DIR)/bn_mp_prime_strong_lucas_selfridge.c \ $(TOMMATH_DIR)/bn_mp_radix_size.c \ $(TOMMATH_DIR)/bn_mp_radix_smap.c \ $(TOMMATH_DIR)/bn_mp_rand.c \ $(TOMMATH_DIR)/bn_mp_read_radix.c \ $(TOMMATH_DIR)/bn_mp_reduce.c \ $(TOMMATH_DIR)/bn_mp_reduce_2k.c \ $(TOMMATH_DIR)/bn_mp_reduce_2k_l.c \ $(TOMMATH_DIR)/bn_mp_reduce_2k_setup.c \ $(TOMMATH_DIR)/bn_mp_reduce_2k_setup_l.c \ $(TOMMATH_DIR)/bn_mp_reduce_is_2k.c \ $(TOMMATH_DIR)/bn_mp_reduce_is_2k_l.c \ $(TOMMATH_DIR)/bn_mp_reduce_setup.c \ $(TOMMATH_DIR)/bn_mp_root_n.c \ $(TOMMATH_DIR)/bn_mp_rshd.c \ $(TOMMATH_DIR)/bn_mp_sbin_size.c \ $(TOMMATH_DIR)/bn_mp_set.c \ $(TOMMATH_DIR)/bn_mp_set_double.c \ $(TOMMATH_DIR)/bn_mp_set_i32.c \ $(TOMMATH_DIR)/bn_mp_set_i64.c \ $(TOMMATH_DIR)/bn_mp_set_l.c \ $(TOMMATH_DIR)/bn_mp_set_u32.c \ $(TOMMATH_DIR)/bn_mp_set_u64.c \ $(TOMMATH_DIR)/bn_mp_set_ul.c \ $(TOMMATH_DIR)/bn_mp_shrink.c \ $(TOMMATH_DIR)/bn_mp_signed_rsh.c \ $(TOMMATH_DIR)/bn_mp_sqr.c \ $(TOMMATH_DIR)/bn_mp_sqrmod.c \ $(TOMMATH_DIR)/bn_mp_sqrt.c \ $(TOMMATH_DIR)/bn_mp_sqrtmod_prime.c \ $(TOMMATH_DIR)/bn_mp_sub.c \ $(TOMMATH_DIR)/bn_mp_sub_d.c \ $(TOMMATH_DIR)/bn_mp_submod.c \ $(TOMMATH_DIR)/bn_mp_to_radix.c \ $(TOMMATH_DIR)/bn_mp_to_sbin.c \ $(TOMMATH_DIR)/bn_mp_to_ubin.c \ $(TOMMATH_DIR)/bn_mp_ubin_size.c \ $(TOMMATH_DIR)/bn_mp_unpack.c \ $(TOMMATH_DIR)/bn_mp_xor.c \ $(TOMMATH_DIR)/bn_mp_zero.c \ $(TOMMATH_DIR)/bn_prime_tab.c \ $(TOMMATH_DIR)/bn_s_mp_add.c \ $(TOMMATH_DIR)/bn_s_mp_balance_mul.c \ $(TOMMATH_DIR)/bn_s_mp_exptmod.c \ $(TOMMATH_DIR)/bn_s_mp_exptmod_fast.c \ $(TOMMATH_DIR)/bn_s_mp_get_bit.c \ $(TOMMATH_DIR)/bn_s_mp_invmod_fast.c \ $(TOMMATH_DIR)/bn_s_mp_invmod_slow.c \ $(TOMMATH_DIR)/bn_s_mp_karatsuba_mul.c \ $(TOMMATH_DIR)/bn_s_mp_karatsuba_sqr.c \ $(TOMMATH_DIR)/bn_s_mp_montgomery_reduce_fast.c \ $(TOMMATH_DIR)/bn_s_mp_mul_digs.c \ $(TOMMATH_DIR)/bn_s_mp_mul_digs_fast.c \ $(TOMMATH_DIR)/bn_s_mp_mul_high_digs.c \ $(TOMMATH_DIR)/bn_s_mp_mul_high_digs_fast.c \ $(TOMMATH_DIR)/bn_s_mp_prime_is_divisible.c \ $(TOMMATH_DIR)/bn_s_mp_rand_jenkins.c \ $(TOMMATH_DIR)/bn_s_mp_rand_platform.c \ $(TOMMATH_DIR)/bn_s_mp_reverse.c \ $(TOMMATH_DIR)/bn_s_mp_sqr.c \ $(TOMMATH_DIR)/bn_s_mp_sqr_fast.c \ $(TOMMATH_DIR)/bn_s_mp_sub.c \ $(TOMMATH_DIR)/bn_s_mp_toom_mul.c \ $(TOMMATH_DIR)/bn_s_mp_toom_sqr.c UNIX_HDRS = \ $(UNIX_DIR)/tclUnixPort.h # $(UNIX_DIR)/tclConfig.h UNIX_SRCS = \ $(UNIX_DIR)/tclAppInit.c \ $(UNIX_DIR)/tclUnixChan.c \ $(UNIX_DIR)/tclUnixEvent.c \ $(UNIX_DIR)/tclUnixFCmd.c \ $(UNIX_DIR)/tclUnixFile.c \ $(UNIX_DIR)/tclUnixPipe.c \ $(UNIX_DIR)/tclUnixSock.c \ $(UNIX_DIR)/tclUnixTest.c \ $(UNIX_DIR)/tclUnixThrd.c \ $(UNIX_DIR)/tclUnixTime.c \ $(UNIX_DIR)/tclUnixInit.c \ $(UNIX_DIR)/tclUnixCompat.c NOTIFY_SRCS = \ $(UNIX_DIR)/tclEpollNotfy.c \ $(UNIX_DIR)/tclKqueueNotfy.c \ $(UNIX_DIR)/tclSelectNotfy.c \ $(UNIX_DIR)/tclUnixNotfy.c DL_SRCS = \ $(UNIX_DIR)/tclLoadAix.c \ $(UNIX_DIR)/tclLoadDl.c \ $(UNIX_DIR)/tclLoadDl2.c \ $(UNIX_DIR)/tclLoadDld.c \ $(UNIX_DIR)/tclLoadDyld.c \ $(GENERIC_DIR)/tclLoadNone.c \ $(UNIX_DIR)/tclLoadOSF.c \ $(UNIX_DIR)/tclLoadShl.c MAC_OSX_SRCS = \ $(MAC_OSX_DIR)/tclMacOSXBundle.c \ $(MAC_OSX_DIR)/tclMacOSXFCmd.c \ $(MAC_OSX_DIR)/tclMacOSXNotify.c CYGWIN_SRCS = \ $(TOP_DIR)/win/tclWinError.c DTRACE_HDR = tclDTrace.h DTRACE_SRC = $(GENERIC_DIR)/tclDTrace.d ZLIB_SRCS = \ $(ZLIB_DIR)/adler32.c \ $(ZLIB_DIR)/compress.c \ $(ZLIB_DIR)/crc32.c \ $(ZLIB_DIR)/deflate.c \ $(ZLIB_DIR)/infback.c \ $(ZLIB_DIR)/inffast.c \ $(ZLIB_DIR)/inflate.c \ $(ZLIB_DIR)/inftrees.c \ $(ZLIB_DIR)/trees.c \ $(ZLIB_DIR)/uncompr.c \ $(ZLIB_DIR)/zutil.c # Note: don't include DL_SRCS or MAC_OSX_SRCS in SRCS: most of those files # won't compile on the current machine, and they will cause problems for # things like "make depend". SRCS = $(GENERIC_SRCS) $(UNIX_SRCS) $(NOTIFY_SRCS) \ $(OO_SRCS) $(STUB_SRCS) @PLAT_SRCS@ @ZLIB_SRCS@ @TOMMATH_SRCS@ ### # Tip 430 - ZipFS Modifications ### TCL_ZIP_FILE = @TCL_ZIP_FILE@ TCL_VFS_ROOT = libtcl.vfs TCL_VFS_PATH = ${TCL_VFS_ROOT}/tcl_library HOST_CC = @CC_FOR_BUILD@ HOST_EXEEXT = @EXEEXT_FOR_BUILD@ HOST_OBJEXT = @OBJEXT_FOR_BUILD@ ZIPFS_BUILD = @ZIPFS_BUILD@ MACHER = @MACHER_PROG@ NATIVE_ZIP = @ZIP_PROG@ ZIP_PROG_OPTIONS = @ZIP_PROG_OPTIONS@ ZIP_PROG_VFSSEARCH = @ZIP_PROG_VFSSEARCH@ SHARED_BUILD = @SHARED_BUILD@ INSTALL_LIBRARIES = @INSTALL_LIBRARIES@ INSTALL_MSGS = @INSTALL_MSGS@ # Minizip MINIZIP_OBJS = \ adler32.$(HOST_OBJEXT) \ compress.$(HOST_OBJEXT) \ crc32.$(HOST_OBJEXT) \ deflate.$(HOST_OBJEXT) \ infback.$(HOST_OBJEXT) \ inffast.$(HOST_OBJEXT) \ inflate.$(HOST_OBJEXT) \ inftrees.$(HOST_OBJEXT) \ ioapi.$(HOST_OBJEXT) \ trees.$(HOST_OBJEXT) \ uncompr.$(HOST_OBJEXT) \ zip.$(HOST_OBJEXT) \ zutil.$(HOST_OBJEXT) \ minizip.$(HOST_OBJEXT) ZIP_INSTALL_OBJS = @ZIP_INSTALL_OBJS@ #-------------------------------------------------------------------------- # Start of rules #-------------------------------------------------------------------------- all: binaries libraries doc packages binaries: ${LIB_FILE} ${TCL_EXE} libraries: doc: tclzipfile: ${TCL_ZIP_FILE} ${TCL_ZIP_FILE}: ${ZIP_INSTALL_OBJS} @rm -rf ${TCL_VFS_ROOT} @mkdir -p ${TCL_VFS_PATH} @echo "creating ${TCL_VFS_PATH} (prepare compression)" @if \ ln -s $(TOP_DIR)/library/* ${TCL_VFS_PATH}/; \ then : ; else \ cp -a $(TOP_DIR)/library/* ${TCL_VFS_PATH}; \ fi mv ${TCL_VFS_PATH}/manifest.txt ${TCL_VFS_PATH}/pkgIndex.tcl rm -rf ${TCL_VFS_PATH}/dde ${TCL_VFS_PATH}/registry @find ${TCL_VFS_ROOT} -type d -empty -delete @echo "creating ${TCL_ZIP_FILE} from ${TCL_VFS_PATH}" @(zip=`(realpath '${NATIVE_ZIP}' || readlink -m '${NATIVE_ZIP}' || \ echo '${NATIVE_ZIP}' | sed "s?^\./?$$(pwd)/?") 2>/dev/null`; \ echo 'cd ${TCL_VFS_ROOT} &&' $$zip '${ZIP_PROG_OPTIONS} ../${TCL_ZIP_FILE} ${ZIP_PROG_VFSSEARCH}'; \ cd ${TCL_VFS_ROOT} && \ $$zip ${ZIP_PROG_OPTIONS} ../${TCL_ZIP_FILE} ${ZIP_PROG_VFSSEARCH} >/dev/null) # The following target is configured by autoconf to generate either a shared # library or non-shared library for Tcl. ${LIB_FILE}: ${STUB_LIB_FILE} ${OBJS} ${TCL_ZIP_FILE} rm -f $@ @MAKE_LIB@ @if test "${ZIPFS_BUILD}" = "1" ; then \ if test "x$(MACHER)" = "x" ; then \ cat ${TCL_ZIP_FILE} >> ${LIB_FILE}; \ else $(MACHER) append ${LIB_FILE} ${TCL_ZIP_FILE} /tmp/macher_output; \ mv /tmp/macher_output ${LIB_FILE}; chmod u+x ${LIB_FILE}; \ fi; \ fi ${STUB_LIB_FILE}: ${STUB_LIB_OBJS} @if [ "x${LIB_FILE}" = "xlibtcl${MAJOR_VERSION}.${MINOR_VERSION}.dll" ] ; then \ ( cd ${TOP_DIR}/win; ${MAKE} winextensions ); \ fi rm -f $@ @MAKE_STUB_LIB@ # Make target which outputs the list of the .o contained in the Tcl lib useful # to build a single big shared library containing Tcl and other extensions. # Used for the Tcl Plugin. -- dl # The dependency on OBJS is not there because we just want the list of objects # here, not actually building them tclLibObjs: @echo ${OBJS} # This targets actually build the objects needed for the lib in the above case objs: ${OBJS} ${TCL_EXE}: ${TCLSH_OBJS} ${TCL_LIB_FILE} ${TCL_STUB_LIB_FILE} ${TCL_ZIP_FILE} ${CC} ${CFLAGS} ${LDFLAGS} ${TCLSH_OBJS} \ @TCL_BUILD_LIB_SPEC@ ${TCL_STUB_LIB_FILE} ${LIBS} @EXTRA_TCLSH_LIBS@ \ ${CC_SEARCH_FLAGS} -o ${TCL_EXE} @if test "${ZIPFS_BUILD}" = "2" ; then \ if test "x$(MACHER)" = "x" ; then \ cat ${TCL_ZIP_FILE} >> ${TCL_EXE}; \ else $(MACHER) append ${TCL_EXE} ${TCL_ZIP_FILE} /tmp/macher_output; \ mv /tmp/macher_output ${TCL_EXE}; chmod u+x ${TCL_EXE}; \ fi; \ fi # Must be empty so it doesn't conflict with rule for ${TCL_EXE} above ${NATIVE_TCLSH}: Makefile: $(UNIX_DIR)/Makefile.in $(DLTEST_DIR)/Makefile.in $(SHELL) config.status #tclConfig.h: $(UNIX_DIR)/tclConfig.h.in # $(SHELL) config.status clean: clean-packages rm -rf *.a *.o libtcl* core errs *~ \#* TAGS *.E a.out \ errors ${TCL_EXE} ${TCLTEST_EXE} lib.exp Tcl @DTRACE_HDR@ \ minizip${HOST_EXEEXT} *.${HOST_OBJEXT} *.zip *.vfs (cd dltest ; $(MAKE) clean) distclean: distclean-packages clean rm -rf Makefile config.status config.cache config.log tclConfig.sh \ tclConfig.h *.plist Tcl.framework tcl.pc tclUuid.h (cd dltest ; $(MAKE) distclean) depend: makedepend -- $(DEPEND_SWITCHES) -- $(SRCS) #-------------------------------------------------------------------------- # The following target outputs the name of the top-level source directory for # Tcl (it is used by Tk's configure script, for example). The .NO_PARALLEL # line is needed to avoid problems under Sun's "pmake". Note: this target is # now obsolete (use the autoconf variable TCL_SRC_DIR from tclConfig.sh # instead). #-------------------------------------------------------------------------- .NO_PARALLEL: topDirName topDirName: @cd $(TOP_DIR); pwd #-------------------------------------------------------------------------- # Rules for testing #-------------------------------------------------------------------------- # Resetting the LIB_RUNTIME_DIR below is required so that the generated # tcltest executable gets the build directory burned into its ld search path. # This keeps tcltest from picking up an already installed version of the Tcl # library. SHELL_ENV = @LD_LIBRARY_PATH_VAR@=`pwd`:${@LD_LIBRARY_PATH_VAR@} \ TCLLIBPATH="@abs_builddir@/pkgs" \ TCL_LIBRARY="${TCL_BUILDTIME_LIBRARY}" ${TCLTEST_EXE}: ${TCLTEST_OBJS} ${TCL_LIB_FILE} ${TCL_STUB_LIB_FILE} ${BUILD_DLTEST} ${TCL_ZIP_FILE} $(MAKE) tcltest-real LIB_RUNTIME_DIR="`pwd`" tcltest-real: ${CC} ${CFLAGS} ${LDFLAGS} ${TCLTEST_OBJS} \ @TCL_BUILD_LIB_SPEC@ ${TCL_STUB_LIB_FILE} ${LIBS} @EXTRA_TCLSH_LIBS@ \ ${CC_SEARCH_FLAGS} -o ${TCLTEST_EXE} @if test "${ZIPFS_BUILD}" = "2" ; then \ if test "x$(MACHER)" = "x" ; then \ cat ${TCL_ZIP_FILE} >> ${TCLTEST_EXE}; \ else $(MACHER) append ${TCLTEST_EXE} ${TCL_ZIP_FILE} /tmp/macher_output; \ mv /tmp/macher_output ${TCLTEST_EXE}; chmod u+x ${TCLTEST_EXE}; \ fi; \ fi # Note, in the targets below TCL_LIBRARY needs to be set or else "make test" # won't work in the case where the compilation directory isn't the same as the # source directory. # # Specifying TESTFLAGS on the command line is the standard way to pass args to # tcltest, ie: # % make test TESTFLAGS="-verbose bps -file fileName.test" test: test-tcl test-packages test-tcl: ${TCLTEST_EXE} $(SHELL_ENV) ./${TCLTEST_EXE} $(TOP_DIR)/tests/all.tcl $(TESTFLAGS) gdb-test: ${TCLTEST_EXE} $(SHELL_ENV) $(GDB) --args ./${TCLTEST_EXE} $(TOP_DIR)/tests/all.tcl \ $(TESTFLAGS) -singleproc 1 lldb-test: ${TCLTEST_EXE} $(SHELL_ENV) $(LLDB) -- ./${TCLTEST_EXE} $(TOP_DIR)/tests/all.tcl \ $(TESTFLAGS) -singleproc 1 # Useful target to launch a built tcltest with the proper path,... runtest: ${TCLTEST_EXE} $(SHELL_ENV) ./${TCLTEST_EXE} # Useful target for running the test suite with an unwritable current # directory... ro-test: ${TCLTEST_EXE} echo 'exec chmod -w .;package require tcltest;tcltest::temporaryDirectory /tmp;source ../tests/all.tcl;exec chmod +w .' | $(SHELL_ENV) ./${TCLTEST_EXE} # The following target generates the shared libraries in dltest/ that are used # for testing; they are included as part of the "tcltest" target (via the # BUILD_DLTEST variable) if dynamic loading is supported on this platform. The # Makefile in the dltest subdirectory creates the dltest.marker file in this # directory after a successful build. dltest.marker: ${STUB_LIB_FILE} cd dltest ; $(MAKE) #-------------------------------------------------------------------------- # Rules for running a shell before installation #-------------------------------------------------------------------------- # This target can be used to run tclsh from the build directory # via `make shell SCRIPT=/tmp/foo.tcl` shell: ${TCL_EXE} $(SHELL_ENV) ./${TCL_EXE} $(SCRIPT) # This target can be used to run tclsh inside either gdb or insight gdb: ${TCL_EXE} $(SHELL_ENV) $(GDB) ./${TCL_EXE} lldb: ${TCL_EXE} $(SHELL_ENV) $(LLDB) ./${TCL_EXE} valgrind: ${TCL_EXE} ${TCLTEST_EXE} $(SHELL_ENV) $(VALGRIND) $(VALGRINDARGS) ./${TCLTEST_EXE} \ $(TOP_DIR)/tests/all.tcl -singleproc 1 -constraints valgrind \ $(TESTFLAGS) testresults/valgrind/%.result: ${TCL_EXE} ${TCLTEST_EXE} @mkdir -p testresults/valgrind $(SHELL_ENV) $(VALGRIND) $(VALGRINDARGS) ./${TCLTEST_EXE} \ $(TOP_DIR)/tests/all.tcl -singleproc 1 -constraints valgrind \ -file $(basename $(notdir $@)) > $@.tmp 2>&1 @mv $@.tmp $@ .PRECIOUS: testresults/valgrind/%.result testresults/valgrind/%.success: testresults/valgrind/%.result @printf '%s' valgrind >&2 @printf ' %s' $(basename $(notdir $@)) >&2 @printf '\n >&2' @status=$$(./${TCLTEST_EXE} $(TOP_DIR)/tools/valgrind_check_success \ file $(basename $@).result); \ if [ "$$status" -eq 1 ]; then touch $@; exit 0; else exit 1; fi valgrind_each: $(addprefix testresults/valgrind/,$(addsuffix .success,$(notdir\ $(wildcard $(TOP_DIR)/tests/*.test)))) valgrindshell: ${TCL_EXE} $(SHELL_ENV) $(VALGRIND) $(VALGRINDARGS) ./${TCL_EXE} $(SCRIPT) trace-shell: ${TCL_EXE} $(SHELL_ENV) ${TRACE} $(TRACE_OPTS) ./${TCL_EXE} $(SCRIPT) trace-test: ${TCLTEST_EXE} $(SHELL_ENV) ${TRACE} $(TRACE_OPTS) ./${TCLTEST_EXE} $(TOP_DIR)/tests/all.tcl -singleproc 1 $(TESTFLAGS) #-------------------------------------------------------------------------- # Installation rules #-------------------------------------------------------------------------- INSTALL_BASE_TARGETS = install-binaries $(INSTALL_LIBRARIES) $(INSTALL_MSGS) $(INSTALL_TZDATA) INSTALL_DOC_TARGETS = install-doc INSTALL_PACKAGE_TARGETS = install-packages INSTALL_DEV_TARGETS = install-headers INSTALL_EXTRA_TARGETS = @EXTRA_INSTALL@ INSTALL_TARGETS = $(INSTALL_BASE_TARGETS) $(INSTALL_DOC_TARGETS) $(INSTALL_DEV_TARGETS) \ $(INSTALL_PACKAGE_TARGETS) $(INSTALL_EXTRA_TARGETS) install: $(INSTALL_TARGETS) install-strip: $(MAKE) $(INSTALL_TARGETS) \ INSTALL_PROGRAM="STRIPPROG='${INSTALL_STRIP_PROGRAM}' $(INSTALL_PROGRAM) -s" \ INSTALL_LIBRARY="STRIPPROG='${INSTALL_STRIP_LIBRARY}' $(INSTALL_LIBRARY) -s" install-binaries: binaries @for i in "$(LIB_INSTALL_DIR)" "$(BIN_INSTALL_DIR)" \ "$(CONFIG_INSTALL_DIR)" ; do \ if [ ! -d "$$i" ] ; then \ echo "Making directory $$i"; \ $(INSTALL_DATA_DIR) "$$i"; \ fi; \ done @echo "Installing $(LIB_FILE) to $(DLL_INSTALL_DIR)/" @@INSTALL_LIB@ @chmod 555 "$(DLL_INSTALL_DIR)/$(LIB_FILE)" @echo "Installing ${TCL_EXE} as $(BIN_INSTALL_DIR)/tclsh$(VERSION)${EXE_SUFFIX}" @$(INSTALL_PROGRAM) ${TCL_EXE} "$(BIN_INSTALL_DIR)/tclsh$(VERSION)${EXE_SUFFIX}" @echo "Installing tclConfig.sh to $(CONFIG_INSTALL_DIR)/" @$(INSTALL_DATA) tclConfig.sh "$(CONFIG_INSTALL_DIR)/tclConfig.sh" @echo "Installing tclooConfig.sh to $(CONFIG_INSTALL_DIR)/" @$(INSTALL_DATA) $(UNIX_DIR)/tclooConfig.sh \ "$(CONFIG_INSTALL_DIR)/tclooConfig.sh" @if test "$(STUB_LIB_FILE)" != "" ; then \ echo "Installing $(STUB_LIB_FILE) to $(LIB_INSTALL_DIR)/"; \ @INSTALL_STUB_LIB@ ; \ fi @EXTRA_INSTALL_BINARIES@ @echo "Installing pkg-config file to $(LIB_INSTALL_DIR)/pkgconfig/" @$(INSTALL_DATA_DIR) "$(LIB_INSTALL_DIR)/pkgconfig" @$(INSTALL_DATA) tcl.pc "$(LIB_INSTALL_DIR)/pkgconfig/tcl.pc" install-libraries: libraries @for i in "$(SCRIPT_INSTALL_DIR)" "$(MODULE_INSTALL_DIR)"; \ do \ if [ ! -d "$$i" ] ; then \ echo "Making directory $$i"; \ $(INSTALL_DATA_DIR) "$$i"; \ fi; \ done; @for i in opt0.4 cookiejar0.2 encoding; \ do \ if [ ! -d "$(SCRIPT_INSTALL_DIR)/$$i" ] ; then \ echo "Making directory $(SCRIPT_INSTALL_DIR)/$$i"; \ $(INSTALL_DATA_DIR) "$(SCRIPT_INSTALL_DIR)/$$i"; \ else true; \ fi; \ done; @for i in 9.0 9.0/platform; \ do \ if [ ! -d "$(MODULE_INSTALL_DIR)/$$i" ] ; then \ echo "Making directory $(MODULE_INSTALL_DIR)/$$i"; \ $(INSTALL_DATA_DIR) "$(MODULE_INSTALL_DIR)/$$i"; \ fi; \ done; @echo "Installing library files to $(SCRIPT_INSTALL_DIR)/" @for i in $(TOP_DIR)/library/*.tcl $(TOP_DIR)/library/tclIndex \ $(UNIX_DIR)/tclAppInit.c @LDAIX_SRC@ @DTRACE_SRC@ ; do \ $(INSTALL_DATA) $$i "$(SCRIPT_INSTALL_DIR)"; \ done; @echo "Installing package cookiejar 0.2 files to $(SCRIPT_INSTALL_DIR)/cookiejar0.2/" @for i in $(TOP_DIR)/library/cookiejar/*.tcl \ $(TOP_DIR)/library/cookiejar/*.gz; \ do \ $(INSTALL_DATA) $$i "$(SCRIPT_INSTALL_DIR)/cookiejar0.2"; \ done @echo "Installing package http 2.10.0 as a Tcl Module" @$(INSTALL_DATA) $(TOP_DIR)/library/http/http.tcl \ "$(MODULE_INSTALL_DIR)/9.0/http-2.10.0.tm" @echo "Installing package opt0.4 files to $(SCRIPT_INSTALL_DIR)/opt0.4/" @for i in $(TOP_DIR)/library/opt/*.tcl; do \ $(INSTALL_DATA) $$i "$(SCRIPT_INSTALL_DIR)/opt0.4"; \ done @echo "Installing package msgcat 1.7.1 as a Tcl Module" @$(INSTALL_DATA) $(TOP_DIR)/library/msgcat/msgcat.tcl \ "$(MODULE_INSTALL_DIR)/9.0/msgcat-1.7.1.tm" @echo "Installing package tcltest 2.5.9 as a Tcl Module" @$(INSTALL_DATA) $(TOP_DIR)/library/tcltest/tcltest.tcl \ "$(MODULE_INSTALL_DIR)/9.0/tcltest-2.5.9.tm" @echo "Installing package platform 1.0.19 as a Tcl Module" @$(INSTALL_DATA) $(TOP_DIR)/library/platform/platform.tcl \ "$(MODULE_INSTALL_DIR)/9.0/platform-1.0.19.tm" @echo "Installing package platform::shell 1.1.4 as a Tcl Module" @$(INSTALL_DATA) $(TOP_DIR)/library/platform/shell.tcl \ "$(MODULE_INSTALL_DIR)/9.0/platform/shell-1.1.4.tm" @echo "Installing encoding files to $(SCRIPT_INSTALL_DIR)/encoding/" @for i in $(TOP_DIR)/library/encoding/*.enc; do \ $(INSTALL_DATA) $$i "$(SCRIPT_INSTALL_DIR)/encoding"; \ done @if [ -n "$(TCL_MODULE_PATH)" -a -f $(TOP_DIR)/library/tm.tcl ] ; then \ echo "Customizing tcl module path"; \ echo "if {![interp issafe]} { ::tcl::tm::roots [list $(TCL_MODULE_PATH)] }" >> \ "$(SCRIPT_INSTALL_DIR)/tm.tcl"; \ fi install-tzdata: @for i in tzdata; do \ if [ ! -d "$(SCRIPT_INSTALL_DIR)/$$i" ] ; then \ echo "Making directory $(SCRIPT_INSTALL_DIR)/$$i"; \ $(INSTALL_DATA_DIR) "$(SCRIPT_INSTALL_DIR)/$$i"; \ fi; \ done @echo "Installing time zone files to $(SCRIPT_INSTALL_DIR)/tzdata/" @for i in $(TOP_DIR)/library/tzdata/*; do \ if [ -d $$i ] ; then \ ii=`basename $$i`; \ if [ ! -d "$(SCRIPT_INSTALL_DIR)/tzdata/$$ii" ] ; then \ $(INSTALL_DATA_DIR) "$(SCRIPT_INSTALL_DIR)/tzdata/$$ii"; \ fi; \ for j in $$i/*; do \ if [ -d $$j ] ; then \ jj=`basename $$j`; \ if [ ! -d "$(SCRIPT_INSTALL_DIR)/tzdata/$$ii/$$jj" ] ; then \ $(INSTALL_DATA_DIR) "$(SCRIPT_INSTALL_DIR)/tzdata/$$ii/$$jj"; \ fi; \ for k in $$j/*; do \ $(INSTALL_DATA) $$k "$(SCRIPT_INSTALL_DIR)/tzdata/$$ii/$$jj"; \ done; \ else \ $(INSTALL_DATA) $$j "$(SCRIPT_INSTALL_DIR)/tzdata/$$ii"; \ fi; \ done; \ else \ $(INSTALL_DATA) $$i "$(SCRIPT_INSTALL_DIR)/tzdata"; \ fi; \ done install-msgs: @for i in msgs; do \ if [ ! -d "$(SCRIPT_INSTALL_DIR)/$$i" ] ; then \ echo "Making directory $(SCRIPT_INSTALL_DIR)/$$i"; \ $(INSTALL_DATA_DIR) "$(SCRIPT_INSTALL_DIR)/$$i"; \ fi; \ done @echo "Installing message catalog files to $(SCRIPT_INSTALL_DIR)/msgs/" @for i in $(TOP_DIR)/library/msgs/*.msg; do \ $(INSTALL_DATA) $$i "$(SCRIPT_INSTALL_DIR)/msgs"; \ done install-doc: doc @for i in "$(MAN_INSTALL_DIR)" "$(MAN1_INSTALL_DIR)" "$(MAN3_INSTALL_DIR)" "$(MANN_INSTALL_DIR)"; do \ if [ ! -d "$$i" ] ; then \ echo "Making directory $$i"; \ $(INSTALL_DATA_DIR) "$$i"; \ fi; \ done @echo "Installing and cross-linking top-level (.1) docs to $(MAN1_INSTALL_DIR)/" @for i in $(TOP_DIR)/doc/*.1; do \ $(SHELL) $(UNIX_DIR)/installManPage $(MAN_FLAGS) $$i "$(MAN1_INSTALL_DIR)"; \ done @echo "Installing and cross-linking C API (.3) docs to $(MAN3_INSTALL_DIR)/" @for i in $(TOP_DIR)/doc/*.3; do \ $(SHELL) $(UNIX_DIR)/installManPage $(MAN_FLAGS) $$i "$(MAN3_INSTALL_DIR)"; \ done @echo "Installing and cross-linking command (.n) docs to $(MANN_INSTALL_DIR)/"; @for i in $(TOP_DIR)/doc/*.n; do \ $(SHELL) $(UNIX_DIR)/installManPage $(MAN_FLAGS) $$i "$(MANN_INSTALL_DIR)"; \ done # Public headers that define Tcl's API TCL_PUBLIC_HEADERS = $(GENERIC_DIR)/tcl.h $(GENERIC_DIR)/tclDecls.h \ $(GENERIC_DIR)/tclOO.h $(GENERIC_DIR)/tclOODecls.h \ $(GENERIC_DIR)/tclPlatDecls.h $(GENERIC_DIR)/tclTomMath.h \ $(GENERIC_DIR)/tclTomMathDecls.h # Private headers that define Tcl's internal API TCL_PRIVATE_HEADERS = $(GENERIC_DIR)/tclInt.h $(GENERIC_DIR)/tclIntDecls.h \ $(GENERIC_DIR)/tclIntPlatDecls.h $(GENERIC_DIR)/tclPort.h \ $(GENERIC_DIR)/tclOOInt.h $(GENERIC_DIR)/tclOOIntDecls.h \ $(UNIX_DIR)/tclUnixPort.h # Any other headers you find in the Tcl sources are purely part of Tcl's # implementation, and aren't to be installed. install-headers: @for i in "$(INCLUDE_INSTALL_DIR)"; do \ if [ ! -d "$$i" ] ; then \ echo "Making directory $$i"; \ $(INSTALL_DATA_DIR) "$$i"; \ fi; \ done @echo "Installing header files to $(INCLUDE_INSTALL_DIR)/"; @for i in $(TCL_PUBLIC_HEADERS); do \ $(INSTALL_DATA) $$i "$(INCLUDE_INSTALL_DIR)"; \ done # Optional target to install private headers install-private-headers: @for i in "$(PRIVATE_INCLUDE_INSTALL_DIR)"; do \ if [ ! -d "$$i" ] ; then \ echo "Making directory $$i"; \ $(INSTALL_DATA_DIR) "$$i"; \ fi; \ done @echo "Installing private header files to $(PRIVATE_INCLUDE_INSTALL_DIR)/"; @for i in $(TCL_PRIVATE_HEADERS); do \ $(INSTALL_DATA) $$i "$(PRIVATE_INCLUDE_INSTALL_DIR)"; \ done @if test -f tclConfig.h; then\ $(INSTALL_DATA) tclConfig.h "$(PRIVATE_INCLUDE_INSTALL_DIR)"; \ fi #-------------------------------------------------------------------------- # Rules for how to compile C files #-------------------------------------------------------------------------- # Test binaries. The rules for tclTestInit.o and xtTestInit.o are complicated # because they are compiled from tclAppInit.c. Can't use the "-o" option # because this doesn't work on some strange compilers (e.g. UnixWare). # # To enable concurrent parallel make of tclsh and tcltest resp xttest, these # targets have to depend on tclsh, this ensures that linking of tclsh with # tclAppInit.o does not execute concurrently with the renaming and recompiling # of that same object file in the targets below. tclTestInit.o: $(UNIX_DIR)/tclAppInit.c ${TCL_EXE} @if test -f tclAppInit.o ; then \ rm -f tclAppInit.sav; \ mv tclAppInit.o tclAppInit.sav; \ fi $(CC) -c $(APP_CC_SWITCHES) \ -DTCL_BUILDTIME_LIBRARY="\"${TCL_BUILDTIME_LIBRARY}\"" \ -DTCL_TEST $(UNIX_DIR)/tclAppInit.c @rm -f tclTestInit.o mv tclAppInit.o tclTestInit.o @if test -f tclAppInit.sav ; then \ mv tclAppInit.sav tclAppInit.o; \ fi xtTestInit.o: $(UNIX_DIR)/tclAppInit.c ${TCL_EXE} @if test -f tclAppInit.o ; then \ rm -f tclAppInit.sav; \ mv tclAppInit.o tclAppInit.sav; \ fi $(CC) -c $(APP_CC_SWITCHES) \ -DTCL_BUILDTIME_LIBRARY="\"${TCL_BUILDTIME_LIBRARY}\"" \ -DTCL_TEST -DTCL_XT_TEST $(UNIX_DIR)/tclAppInit.c @rm -f xtTestInit.o mv tclAppInit.o xtTestInit.o @if test -f tclAppInit.sav ; then \ mv tclAppInit.sav tclAppInit.o; \ fi # Object files used on all Unix systems: REGHDRS = $(GENERIC_DIR)/regex.h $(GENERIC_DIR)/regguts.h \ $(GENERIC_DIR)/regcustom.h TCLREHDRS = $(GENERIC_DIR)/tclRegexp.h COMPILEHDR = $(GENERIC_DIR)/tclCompile.h FSHDR = $(GENERIC_DIR)/tclFileSystem.h IOHDR = $(GENERIC_DIR)/tclIO.h MATHHDRS = $(GENERIC_DIR)/tclTomMath.h $(GENERIC_DIR)/tclTomMathDecls.h PARSEHDR = $(GENERIC_DIR)/tclParse.h NREHDR = $(GENERIC_DIR)/tclInt.h TRIMHDR = $(GENERIC_DIR)/tclStringTrim.h TCL_LOCATIONS = -DTCL_LIBRARY="\"${TCL_LIBRARY}\"" \ -DTCL_PACKAGE_PATH="\"${TCL_PACKAGE_PATH}\"" TCLDATEHDR=$(GENERIC_DIR)/tclDate.h $(GENERIC_DIR)/tclStrIdxTree.h regcomp.o: $(REGHDRS) $(GENERIC_DIR)/regcomp.c $(GENERIC_DIR)/regc_lex.c \ $(GENERIC_DIR)/regc_color.c $(GENERIC_DIR)/regc_locale.c \ $(GENERIC_DIR)/regc_nfa.c $(GENERIC_DIR)/regc_cvec.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/regcomp.c regexec.o: $(REGHDRS) $(GENERIC_DIR)/regexec.c $(GENERIC_DIR)/rege_dfa.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/regexec.c regfree.o: $(REGHDRS) $(GENERIC_DIR)/regfree.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/regfree.c regerror.o: $(REGHDRS) $(GENERIC_DIR)/regerrs.h $(GENERIC_DIR)/regerror.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/regerror.c tclAppInit.o: $(UNIX_DIR)/tclAppInit.c $(CC) -c $(APP_CC_SWITCHES) $(UNIX_DIR)/tclAppInit.c tclAlloc.o: $(GENERIC_DIR)/tclAlloc.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclAlloc.c tclArithSeries.o: $(GENERIC_DIR)/tclArithSeries.c $(COMPILEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclArithSeries.c tclAssembly.o: $(GENERIC_DIR)/tclAssembly.c $(COMPILEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclAssembly.c tclAsync.o: $(GENERIC_DIR)/tclAsync.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclAsync.c tclBasic.o: $(GENERIC_DIR)/tclBasic.c $(COMPILEHDR) $(MATHHDRS) $(NREHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclBasic.c tclBinary.o: $(GENERIC_DIR)/tclBinary.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclBinary.c tclCkalloc.o: $(GENERIC_DIR)/tclCkalloc.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCkalloc.c tclClock.o: $(GENERIC_DIR)/tclClock.c $(TCLDATEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclClock.c tclClockFmt.o: $(GENERIC_DIR)/tclClockFmt.c $(TCLDATEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclClockFmt.c tclCmdAH.o: $(GENERIC_DIR)/tclCmdAH.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCmdAH.c tclCmdIL.o: $(GENERIC_DIR)/tclCmdIL.c $(TCLREHDRS) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCmdIL.c tclCmdMZ.o: $(GENERIC_DIR)/tclCmdMZ.c $(TCLREHDRS) $(TRIMHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCmdMZ.c tclDate.o: $(GENERIC_DIR)/tclDate.c $(TCLDATEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclDate.c tclCompCmds.o: $(GENERIC_DIR)/tclCompCmds.c $(COMPILEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCompCmds.c tclCompCmdsGR.o: $(GENERIC_DIR)/tclCompCmdsGR.c $(COMPILEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCompCmdsGR.c tclCompCmdsSZ.o: $(GENERIC_DIR)/tclCompCmdsSZ.c $(COMPILEHDR) $(TRIMHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCompCmdsSZ.c tclCompExpr.o: $(GENERIC_DIR)/tclCompExpr.c $(COMPILEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCompExpr.c tclCompile.o: $(GENERIC_DIR)/tclCompile.c $(COMPILEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclCompile.c tclConfig.o: $(GENERIC_DIR)/tclConfig.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclConfig.c tclDictObj.o: $(GENERIC_DIR)/tclDictObj.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclDictObj.c tclDisassemble.o: $(GENERIC_DIR)/tclDisassemble.c $(COMPILEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclDisassemble.c tclEncoding.o: $(GENERIC_DIR)/tclEncoding.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclEncoding.c tclEnsemble.o: $(GENERIC_DIR)/tclEnsemble.c $(COMPILEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclEnsemble.c tclEnv.o: $(GENERIC_DIR)/tclEnv.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclEnv.c tclEvent.o: $(GENERIC_DIR)/tclEvent.c tclUuid.h $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclEvent.c tclExecute.o: $(GENERIC_DIR)/tclExecute.c $(COMPILEHDR) $(MATHHDRS) $(NREHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclExecute.c tclFCmd.o: $(GENERIC_DIR)/tclFCmd.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclFCmd.c tclFileName.o: $(GENERIC_DIR)/tclFileName.c $(FSHDR) $(TCLREHDRS) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclFileName.c tclGet.o: $(GENERIC_DIR)/tclGet.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclGet.c tclHash.o: $(GENERIC_DIR)/tclHash.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclHash.c tclHistory.o: $(GENERIC_DIR)/tclHistory.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclHistory.c tclIcu.o: $(GENERIC_DIR)/tclIcu.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclIcu.c tclIndexObj.o: $(GENERIC_DIR)/tclIndexObj.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclIndexObj.c tclInterp.o: $(GENERIC_DIR)/tclInterp.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclInterp.c tclIO.o: $(GENERIC_DIR)/tclIO.c $(IOHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclIO.c tclIOCmd.o: $(GENERIC_DIR)/tclIOCmd.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclIOCmd.c tclIOGT.o: $(GENERIC_DIR)/tclIOGT.c $(IOHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclIOGT.c tclIOSock.o: $(GENERIC_DIR)/tclIOSock.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclIOSock.c tclIOUtil.o: $(GENERIC_DIR)/tclIOUtil.c $(FSHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclIOUtil.c tclIORChan.o: $(GENERIC_DIR)/tclIORChan.c $(IOHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclIORChan.c tclIORTrans.o: $(GENERIC_DIR)/tclIORTrans.c $(IOHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclIORTrans.c tclLink.o: $(GENERIC_DIR)/tclLink.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclLink.c tclListObj.o: $(GENERIC_DIR)/tclListObj.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclListObj.c tclLiteral.o: $(GENERIC_DIR)/tclLiteral.c $(COMPILEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclLiteral.c tclObj.o: $(GENERIC_DIR)/tclObj.c $(COMPILEHDR) $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclObj.c tclOptimize.o: $(GENERIC_DIR)/tclOptimize.c $(COMPILEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclOptimize.c tclLoad.o: $(GENERIC_DIR)/tclLoad.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclLoad.c tclLoadAix.o: $(UNIX_DIR)/tclLoadAix.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclLoadAix.c tclLoadDl.o: $(UNIX_DIR)/tclLoadDl.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclLoadDl.c tclLoadDl2.o: $(UNIX_DIR)/tclLoadDl2.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclLoadDl2.c tclLoadDld.o: $(UNIX_DIR)/tclLoadDld.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclLoadDld.c tclLoadDyld.o: $(UNIX_DIR)/tclLoadDyld.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclLoadDyld.c tclLoadNone.o: $(GENERIC_DIR)/tclLoadNone.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclLoadNone.c tclLoadOSF.o: $(UNIX_DIR)/tclLoadOSF.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclLoadOSF.c tclLoadShl.o: $(UNIX_DIR)/tclLoadShl.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclLoadShl.c tclMain.o: $(GENERIC_DIR)/tclMain.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclMain.c tclNamesp.o: $(GENERIC_DIR)/tclNamesp.c $(COMPILEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclNamesp.c tclNotify.o: $(GENERIC_DIR)/tclNotify.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclNotify.c tclOO.o: $(GENERIC_DIR)/tclOO.c $(GENERIC_DIR)/tclOOScript.h $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclOO.c tclOOBasic.o: $(GENERIC_DIR)/tclOOBasic.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclOOBasic.c tclOOCall.o: $(GENERIC_DIR)/tclOOCall.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclOOCall.c tclOODefineCmds.o: $(GENERIC_DIR)/tclOODefineCmds.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclOODefineCmds.c tclOOInfo.o: $(GENERIC_DIR)/tclOOInfo.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclOOInfo.c tclOOMethod.o: $(GENERIC_DIR)/tclOOMethod.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclOOMethod.c tclOOProp.o: $(GENERIC_DIR)/tclOOProp.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclOOProp.c tclOOStubInit.o: $(GENERIC_DIR)/tclOOStubInit.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclOOStubInit.c tclParse.o: $(GENERIC_DIR)/tclParse.c $(PARSEHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclParse.c tclPanic.o: $(GENERIC_DIR)/tclPanic.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclPanic.c tclPathObj.o: $(GENERIC_DIR)/tclPathObj.c $(FSHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclPathObj.c tclPipe.o: $(GENERIC_DIR)/tclPipe.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclPipe.c tclPkg.o: $(GENERIC_DIR)/tclPkg.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclPkg.c # TIP #59, embedding of configuration information into the binary library. # # Part of Tcl's configuration information are the paths where it was installed # and where it will look for its libraries (which can be different). We derive # this information from the variables which can be overridden by the user. As # every path can be configured separately we do not remember one general # prefix/exec_prefix but all the different paths individually. tclPkgConfig.o: $(GENERIC_DIR)/tclPkgConfig.c $(CC) -c $(CC_SWITCHES) \ -DCFG_INSTALL_LIBDIR="\"$(LIB_INSTALL_DIR)\"" \ -DCFG_INSTALL_BINDIR="\"$(BIN_INSTALL_DIR)\"" \ -DCFG_INSTALL_SCRDIR="\"$(SCRIPT_INSTALL_DIR)\"" \ -DCFG_INSTALL_INCDIR="\"$(INCLUDE_INSTALL_DIR)\"" \ -DCFG_INSTALL_DOCDIR="\"$(MAN_INSTALL_DIR)\"" \ -DCFG_RUNTIME_LIBDIR="\"$(libdir)\"" \ -DCFG_RUNTIME_BINDIR="\"$(bindir)\"" \ -DCFG_RUNTIME_SCRDIR="\"$(TCL_LIBRARY)\"" \ -DCFG_RUNTIME_INCDIR="\"$(includedir)\"" \ -DCFG_RUNTIME_DOCDIR="\"$(mandir)\"" \ -DCFG_RUNTIME_DLLFILE="\"$(TCL_LIB_FILE)\"" \ $(GENERIC_DIR)/tclPkgConfig.c tclPosixStr.o: $(GENERIC_DIR)/tclPosixStr.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclPosixStr.c tclPreserve.o: $(GENERIC_DIR)/tclPreserve.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclPreserve.c tclProc.o: $(GENERIC_DIR)/tclProc.c $(COMPILEHDR) $(NREHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclProc.c tclProcess.o: $(GENERIC_DIR)/tclProcess.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclProcess.c tclRegexp.o: $(GENERIC_DIR)/tclRegexp.c $(TCLREHDRS) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclRegexp.c tclResolve.o: $(GENERIC_DIR)/tclResolve.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclResolve.c tclResult.o: $(GENERIC_DIR)/tclResult.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclResult.c tclScan.o: $(GENERIC_DIR)/tclScan.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclScan.c tclStringObj.o: $(GENERIC_DIR)/tclStringObj.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclStringObj.c tclStrIdxTree.o: $(GENERIC_DIR)/tclStrIdxTree.c $(GENERIC_DIR)/tclStrIdxTree.h $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclStrIdxTree.c tclStrToD.o: $(GENERIC_DIR)/tclStrToD.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclStrToD.c tclStubInit.o: $(GENERIC_DIR)/tclStubInit.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclStubInit.c tclTrace.o: $(GENERIC_DIR)/tclTrace.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclTrace.c tclUtil.o: $(GENERIC_DIR)/tclUtil.c $(PARSEHDR) $(TRIMHDR) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclUtil.c tclUtf.o: $(GENERIC_DIR)/tclUtf.c $(GENERIC_DIR)/tclUniData.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclUtf.c tclVar.o: $(GENERIC_DIR)/tclVar.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclVar.c tclZlib.o: $(GENERIC_DIR)/tclZlib.c $(CC) -c $(CC_SWITCHES) $(ZLIB_INCLUDE) $(GENERIC_DIR)/tclZlib.c tclZipfs.o: $(GENERIC_DIR)/tclZipfs.c $(CC) -c $(CC_SWITCHES) -D_GNU_SOURCE \ -DCFG_RUNTIME_DLLFILE="\"$(TCL_LIB_FILE)\"" \ -DCFG_RUNTIME_LIBDIR="\"$(libdir)\"" \ -I$(ZLIB_DIR) -I$(ZLIB_DIR)/contrib/minizip \ $(GENERIC_DIR)/tclZipfs.c tclTest.o: $(GENERIC_DIR)/tclTest.c $(IOHDR) $(TCLREHDRS) tclUuid.h $(CC) -c $(APP_CC_SWITCHES) $(GENERIC_DIR)/tclTest.c tclTestABSList.o: $(GENERIC_DIR)/tclTestABSList.c $(MATHHDRS) $(CC) -c $(APP_CC_SWITCHES) $(GENERIC_DIR)/tclTestABSList.c tclTestObj.o: $(GENERIC_DIR)/tclTestObj.c $(MATHHDRS) $(CC) -c $(APP_CC_SWITCHES) $(GENERIC_DIR)/tclTestObj.c tclTestProcBodyObj.o: $(GENERIC_DIR)/tclTestProcBodyObj.c $(CC) -c $(APP_CC_SWITCHES) $(GENERIC_DIR)/tclTestProcBodyObj.c tclTimer.o: $(GENERIC_DIR)/tclTimer.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclTimer.c tclThread.o: $(GENERIC_DIR)/tclThread.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclThread.c tclThreadAlloc.o: $(GENERIC_DIR)/tclThreadAlloc.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclThreadAlloc.c tclThreadJoin.o: $(GENERIC_DIR)/tclThreadJoin.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclThreadJoin.c tclThreadStorage.o: $(GENERIC_DIR)/tclThreadStorage.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclThreadStorage.c tclThreadTest.o: $(GENERIC_DIR)/tclThreadTest.c $(CC) -c $(APP_CC_SWITCHES) $(GENERIC_DIR)/tclThreadTest.c tclTomMathInterface.o: $(GENERIC_DIR)/tclTomMathInterface.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclTomMathInterface.c bn_s_mp_reverse.o: $(TOMMATH_DIR)/bn_s_mp_reverse.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_s_mp_reverse.c bn_s_mp_mul_digs_fast.o: $(TOMMATH_DIR)/bn_s_mp_mul_digs_fast.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_s_mp_mul_digs_fast.c bn_s_mp_sqr_fast.o: $(TOMMATH_DIR)/bn_s_mp_sqr_fast.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_s_mp_sqr_fast.c bn_mp_add.o: $(TOMMATH_DIR)/bn_mp_add.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_add.c bn_mp_add_d.o: $(TOMMATH_DIR)/bn_mp_add_d.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_add_d.c bn_mp_and.o: $(TOMMATH_DIR)/bn_mp_and.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_and.c bn_mp_clamp.o: $(TOMMATH_DIR)/bn_mp_clamp.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_clamp.c bn_mp_clear.o: $(TOMMATH_DIR)/bn_mp_clear.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_clear.c bn_mp_clear_multi.o: $(TOMMATH_DIR)/bn_mp_clear_multi.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_clear_multi.c bn_mp_cmp.o: $(TOMMATH_DIR)/bn_mp_cmp.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_cmp.c bn_mp_cmp_d.o: $(TOMMATH_DIR)/bn_mp_cmp_d.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_cmp_d.c bn_mp_cmp_mag.o: $(TOMMATH_DIR)/bn_mp_cmp_mag.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_cmp_mag.c bn_mp_cnt_lsb.o: $(TOMMATH_DIR)/bn_mp_cnt_lsb.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_cnt_lsb.c bn_mp_copy.o: $(TOMMATH_DIR)/bn_mp_copy.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_copy.c bn_mp_count_bits.o: $(TOMMATH_DIR)/bn_mp_count_bits.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_count_bits.c bn_mp_div.o: $(TOMMATH_DIR)/bn_mp_div.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_div.c bn_mp_div_d.o: $(TOMMATH_DIR)/bn_mp_div_d.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_div_d.c bn_mp_div_2.o: $(TOMMATH_DIR)/bn_mp_div_2.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_div_2.c bn_mp_div_2d.o: $(TOMMATH_DIR)/bn_mp_div_2d.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_div_2d.c bn_s_mp_div_3.o: $(TOMMATH_DIR)/bn_s_mp_div_3.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_s_mp_div_3.c bn_mp_exch.o: $(TOMMATH_DIR)/bn_mp_exch.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_exch.c bn_mp_expt_n.o: $(TOMMATH_DIR)/bn_mp_expt_n.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_expt_n.c bn_mp_get_mag_u64.o: $(TOMMATH_DIR)/bn_mp_get_mag_u64.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_get_mag_u64.c bn_mp_grow.o: $(TOMMATH_DIR)/bn_mp_grow.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_grow.c bn_mp_init.o: $(TOMMATH_DIR)/bn_mp_init.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_init.c bn_mp_init_copy.o: $(TOMMATH_DIR)/bn_mp_init_copy.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_init_copy.c bn_mp_init_i64.o:$(TOMMATH_DIR)/bn_mp_init_i64.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_init_i64.c bn_mp_init_multi.o: $(TOMMATH_DIR)/bn_mp_init_multi.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_init_multi.c bn_mp_init_set.o: $(TOMMATH_DIR)/bn_mp_init_set.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_init_set.c bn_mp_init_size.o:$(TOMMATH_DIR)/bn_mp_init_size.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_init_size.c bn_mp_init_u64.o:$(TOMMATH_DIR)/bn_mp_init_u64.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_init_u64.c bn_s_mp_karatsuba_mul.o: $(TOMMATH_DIR)/bn_s_mp_karatsuba_mul.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_s_mp_karatsuba_mul.c bn_s_mp_karatsuba_sqr.o: $(TOMMATH_DIR)/bn_s_mp_karatsuba_sqr.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_s_mp_karatsuba_sqr.c bn_s_mp_balance_mul.o: $(TOMMATH_DIR)/bn_s_mp_balance_mul.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_s_mp_balance_mul.c bn_mp_lshd.o: $(TOMMATH_DIR)/bn_mp_lshd.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_lshd.c bn_mp_mod.o: $(TOMMATH_DIR)/bn_mp_mod.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_mod.c bn_mp_mod_2d.o: $(TOMMATH_DIR)/bn_mp_mod_2d.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_mod_2d.c bn_mp_mul.o: $(TOMMATH_DIR)/bn_mp_mul.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_mul.c bn_mp_mul_2.o: $(TOMMATH_DIR)/bn_mp_mul_2.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_mul_2.c bn_mp_mul_2d.o: $(TOMMATH_DIR)/bn_mp_mul_2d.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_mul_2d.c bn_mp_mul_d.o: $(TOMMATH_DIR)/bn_mp_mul_d.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_mul_d.c bn_mp_neg.o: $(TOMMATH_DIR)/bn_mp_neg.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_neg.c bn_mp_or.o: $(TOMMATH_DIR)/bn_mp_or.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_or.c bn_mp_pack.o: $(TOMMATH_DIR)/bn_mp_pack.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_pack.c bn_mp_pack_count.o: $(TOMMATH_DIR)/bn_mp_pack_count.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_pack_count.c bn_mp_radix_size.o: $(TOMMATH_DIR)/bn_mp_radix_size.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_radix_size.c bn_mp_radix_smap.o: $(TOMMATH_DIR)/bn_mp_radix_smap.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_radix_smap.c bn_mp_read_radix.o: $(TOMMATH_DIR)/bn_mp_read_radix.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_read_radix.c bn_mp_rshd.o: $(TOMMATH_DIR)/bn_mp_rshd.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_rshd.c bn_mp_set_i64.o: $(TOMMATH_DIR)/bn_mp_set_i64.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_set_i64.c bn_mp_set_u64.o: $(TOMMATH_DIR)/bn_mp_set_u64.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_set_u64.c bn_mp_shrink.o: $(TOMMATH_DIR)/bn_mp_shrink.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_shrink.c bn_mp_sqr.o: $(TOMMATH_DIR)/bn_mp_sqr.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_sqr.c bn_mp_sqrt.o: $(TOMMATH_DIR)/bn_mp_sqrt.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_sqrt.c bn_mp_sub.o: $(TOMMATH_DIR)/bn_mp_sub.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_sub.c bn_mp_sub_d.o: $(TOMMATH_DIR)/bn_mp_sub_d.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_sub_d.c bn_mp_signed_rsh.o: $(TOMMATH_DIR)/bn_mp_signed_rsh.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_signed_rsh.c bn_mp_to_ubin.o: $(TOMMATH_DIR)/bn_mp_to_ubin.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_to_ubin.c bn_s_mp_toom_mul.o: $(TOMMATH_DIR)/bn_s_mp_toom_mul.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_s_mp_toom_mul.c bn_s_mp_toom_sqr.o: $(TOMMATH_DIR)/bn_s_mp_toom_sqr.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_s_mp_toom_sqr.c bn_mp_to_radix.o: $(TOMMATH_DIR)/bn_mp_to_radix.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_to_radix.c bn_mp_ubin_size.o: $(TOMMATH_DIR)/bn_mp_ubin_size.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_ubin_size.c bn_mp_unpack.o: $(TOMMATH_DIR)/bn_mp_unpack.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_unpack.c bn_mp_xor.o: $(TOMMATH_DIR)/bn_mp_xor.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_xor.c bn_mp_zero.o: $(TOMMATH_DIR)/bn_mp_zero.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_mp_zero.c bn_s_mp_add.o: $(TOMMATH_DIR)/bn_s_mp_add.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_s_mp_add.c bn_s_mp_mul_digs.o: $(TOMMATH_DIR)/bn_s_mp_mul_digs.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_s_mp_mul_digs.c bn_s_mp_sqr.o: $(TOMMATH_DIR)/bn_s_mp_sqr.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_s_mp_sqr.c bn_s_mp_sub.o: $(TOMMATH_DIR)/bn_s_mp_sub.c $(MATHHDRS) $(CC) -c $(CC_SWITCHES) $(TOMMATH_DIR)/bn_s_mp_sub.c tclUnixChan.o: $(UNIX_DIR)/tclUnixChan.c $(IOHDR) $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclUnixChan.c tclUnixEvent.o: $(UNIX_DIR)/tclUnixEvent.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclUnixEvent.c tclUnixFCmd.o: $(UNIX_DIR)/tclUnixFCmd.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclUnixFCmd.c tclUnixFile.o: $(UNIX_DIR)/tclUnixFile.c $(FSHDR) $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclUnixFile.c tclEpollNotfy.o: $(UNIX_DIR)/tclEpollNotfy.c $(UNIX_DIR)/tclUnixNotfy.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclEpollNotfy.c tclKqueueNotfy.o: $(UNIX_DIR)/tclKqueueNotfy.c $(UNIX_DIR)/tclUnixNotfy.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclKqueueNotfy.c tclSelectNotfy.o: $(UNIX_DIR)/tclSelectNotfy.c $(UNIX_DIR)/tclUnixNotfy.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclSelectNotfy.c tclUnixPipe.o: $(UNIX_DIR)/tclUnixPipe.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclUnixPipe.c tclUnixSock.o: $(UNIX_DIR)/tclUnixSock.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclUnixSock.c tclUnixTest.o: $(UNIX_DIR)/tclUnixTest.c $(CC) -c $(APP_CC_SWITCHES) $(UNIX_DIR)/tclUnixTest.c tclUnixThrd.o: $(UNIX_DIR)/tclUnixThrd.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclUnixThrd.c tclUnixTime.o: $(UNIX_DIR)/tclUnixTime.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclUnixTime.c tclUnixInit.o: $(UNIX_DIR)/tclUnixInit.c tclConfig.sh $(CC) -c $(CC_SWITCHES) $(TCL_LOCATIONS) $(UNIX_DIR)/tclUnixInit.c tclUnixCompat.o: $(UNIX_DIR)/tclUnixCompat.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclUnixCompat.c # The following are Mac OS X only sources: tclMacOSXBundle.o: $(MAC_OSX_DIR)/tclMacOSXBundle.c $(CC) -c $(CC_SWITCHES) $(MAC_OSX_DIR)/tclMacOSXBundle.c tclMacOSXFCmd.o: $(MAC_OSX_DIR)/tclMacOSXFCmd.c $(CC) -c $(CC_SWITCHES) $(MAC_OSX_DIR)/tclMacOSXFCmd.c tclMacOSXNotify.o: $(MAC_OSX_DIR)/tclMacOSXNotify.c $(CC) -c $(CC_SWITCHES) $(MAC_OSX_DIR)/tclMacOSXNotify.c # The following is a CYGWIN only source: tclWinError.o: $(TOP_DIR)/win/tclWinError.c $(CC) -c $(CC_SWITCHES) $(TOP_DIR)/win/tclWinError.c # DTrace support $(TCL_OBJS) $(STUB_LIB_OBJS) $(TCLSH_OBJS) $(TCLTEST_OBJS) $(XTTEST_OBJS) $(TOMMATH_OBJS): @DTRACE_HDR@ $(DTRACE_HDR): $(DTRACE_SRC) $(DTRACE) -h $(DTRACE_SWITCHES) -o $@ -s $(DTRACE_SRC) $(DTRACE_OBJ): $(DTRACE_SRC) $(TCL_OBJS) $(DTRACE) -G $(DTRACE_SWITCHES) -o $@ -s $(DTRACE_SRC) $(TCL_OBJS) #-------------------------------------------------------------------------- # The following targets are not completely general. They are provide purely # for documentation purposes so people who are interested in the Xt based # notifier can modify them to suit their own installation. #-------------------------------------------------------------------------- xttest: ${XTTEST_OBJS} ${TCL_LIB_FILE} ${TCL_STUB_LIB_FILE} ${BUILD_DLTEST} ${CC} ${CFLAGS} ${LDFLAGS} ${XTTEST_OBJS} \ @TCL_BUILD_LIB_SPEC@ ${TCL_STUB_LIB_FILE} ${LIBS} @EXTRA_TCLSH_LIBS@ \ ${CC_SEARCH_FLAGS} -L/usr/openwin/lib -lXt -o xttest tclXtNotify.o: $(UNIX_DIR)/tclXtNotify.c $(CC) -c $(APP_CC_SWITCHES) -I/usr/openwin/include \ $(UNIX_DIR)/tclXtNotify.c tclXtTest.o: $(UNIX_DIR)/tclXtTest.c $(CC) -c $(APP_CC_SWITCHES) -I/usr/openwin/include \ $(UNIX_DIR)/tclXtTest.c #-------------------------------------------------------------------------- # Compat binaries, these must be compiled for use in a shared library even # though they may be placed in a static executable or library. Since they are # included in both the tcl library and the stub library, they need to be # relocatable. #-------------------------------------------------------------------------- mkstemp.o: $(COMPAT_DIR)/mkstemp.c $(CC) -c $(STUB_CC_SWITCHES) $(COMPAT_DIR)/mkstemp.c strncasecmp.o: $(COMPAT_DIR)/strncasecmp.c $(CC) -c $(STUB_CC_SWITCHES) $(COMPAT_DIR)/strncasecmp.c waitpid.o: $(COMPAT_DIR)/waitpid.c $(CC) -c $(STUB_CC_SWITCHES) $(COMPAT_DIR)/waitpid.c fake-rfc2553.o: $(COMPAT_DIR)/fake-rfc2553.c $(CC) -c $(STUB_CC_SWITCHES) $(COMPAT_DIR)/fake-rfc2553.c # For building zlib, only used in some build configurations Zadler32.o: $(ZLIB_DIR)/adler32.c $(CC) -c -o $@ $(CC_SWITCHES) -I$(ZLIB_DIR) $(ZLIB_DIR)/adler32.c Zcompress.o: $(ZLIB_DIR)/compress.c $(CC) -c -o $@ $(CC_SWITCHES) -I$(ZLIB_DIR) $(ZLIB_DIR)/compress.c Zcrc32.o: $(ZLIB_DIR)/crc32.c $(CC) -c -o $@ $(CC_SWITCHES) -I$(ZLIB_DIR) $(ZLIB_DIR)/crc32.c Zdeflate.o: $(ZLIB_DIR)/deflate.c $(CC) -c -o $@ $(CC_SWITCHES) -I$(ZLIB_DIR) $(ZLIB_DIR)/deflate.c Zinfback.o: $(ZLIB_DIR)/infback.c $(CC) -c -o $@ $(CC_SWITCHES) -I$(ZLIB_DIR) $(ZLIB_DIR)/infback.c Zinffast.o: $(ZLIB_DIR)/inffast.c $(CC) -c -o $@ $(CC_SWITCHES) -I$(ZLIB_DIR) $(ZLIB_DIR)/inffast.c Zinflate.o: $(ZLIB_DIR)/inflate.c $(CC) -c -o $@ $(CC_SWITCHES) -I$(ZLIB_DIR) $(ZLIB_DIR)/inflate.c Zinftrees.o: $(ZLIB_DIR)/inftrees.c $(CC) -c -o $@ $(CC_SWITCHES) -I$(ZLIB_DIR) $(ZLIB_DIR)/inftrees.c Ztrees.o: $(ZLIB_DIR)/trees.c $(CC) -c -o $@ $(CC_SWITCHES) -I$(ZLIB_DIR) $(ZLIB_DIR)/trees.c Zuncompr.o: $(ZLIB_DIR)/uncompr.c $(CC) -c -o $@ $(CC_SWITCHES) -I$(ZLIB_DIR) $(ZLIB_DIR)/uncompr.c Zzutil.o: $(ZLIB_DIR)/zutil.c $(CC) -c -o $@ $(CC_SWITCHES) -I$(ZLIB_DIR) $(ZLIB_DIR)/zutil.c #-------------------------------------------------------------------------- # Stub library binaries, these must be compiled for use in a shared library # even though they will be placed in a static archive #-------------------------------------------------------------------------- tclStubLib.o: $(GENERIC_DIR)/tclStubLib.c $(CC) -c $(STUB_CC_SWITCHES) -DSTATIC_BUILD @CFLAGS_NOLTO@ $(GENERIC_DIR)/tclStubLib.c tclStubCall.o: $(GENERIC_DIR)/tclStubCall.c $(CC) -c $(STUB_CC_SWITCHES) -DSTATIC_BUILD \ -DCFG_RUNTIME_DLLFILE="\"$(TCL_LIB_FILE)\"" \ -DCFG_RUNTIME_LIBDIR="\"$(libdir)\"" \ -DCFG_RUNTIME_BINDIR="\"$(bindir)\"" \ $(GENERIC_DIR)/tclStubCall.c tclStubLibTbl.o: $(GENERIC_DIR)/tclStubLibTbl.c $(CC) -c $(STUB_CC_SWITCHES) -DSTATIC_BUILD $(GENERIC_DIR)/tclStubLibTbl.c tclTomMathStubLib.o: $(GENERIC_DIR)/tclTomMathStubLib.c $(CC) -c $(STUB_CC_SWITCHES) @CFLAGS_NOLTO@ $(GENERIC_DIR)/tclTomMathStubLib.c tclOOStubLib.o: $(GENERIC_DIR)/tclOOStubLib.c $(CC) -c $(STUB_CC_SWITCHES) @CFLAGS_NOLTO@ $(GENERIC_DIR)/tclOOStubLib.c .c.o: $(CC) -c $(CC_SWITCHES) $< #-------------------------------------------------------------------------- # Minizip implementation #-------------------------------------------------------------------------- adler32.$(HOST_OBJEXT): $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/adler32.c compress.$(HOST_OBJEXT): $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/compress.c crc32.$(HOST_OBJEXT): $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/crc32.c deflate.$(HOST_OBJEXT): $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/deflate.c ioapi.$(HOST_OBJEXT): $(HOST_CC) -o $@ -DIOAPI_NO_64 -I$(ZLIB_DIR) -I$(ZLIB_DIR)/contrib/minizip -c \ $(ZLIB_DIR)/contrib/minizip/ioapi.c infback.$(HOST_OBJEXT): $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/infback.c inffast.$(HOST_OBJEXT): $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/inffast.c inflate.$(HOST_OBJEXT): $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/inflate.c inftrees.$(HOST_OBJEXT): $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/inftrees.c trees.$(HOST_OBJEXT): $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/trees.c uncompr.$(HOST_OBJEXT): $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/uncompr.c zip.$(HOST_OBJEXT): $(HOST_CC) -o $@ -I$(ZLIB_DIR) -I$(ZLIB_DIR)/contrib/minizip -c \ $(ZLIB_DIR)/contrib/minizip/zip.c zutil.$(HOST_OBJEXT): $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/zutil.c minizip.$(HOST_OBJEXT): $(HOST_CC) -o $@ -DIOAPI_NO_64 -I$(ZLIB_DIR) -I$(ZLIB_DIR)/contrib/minizip -c \ $(ZLIB_DIR)/contrib/minizip/minizip.c minizip${HOST_EXEEXT}: $(MINIZIP_OBJS) $(HOST_CC) -o $@ $(MINIZIP_OBJS) #-------------------------------------------------------------------------- # Bundled Package targets #-------------------------------------------------------------------------- # Propagate configure args like --enable-64bit to package configure PKG_CFG_ARGS = @PKG_CFG_ARGS@ # If PKG_DIR is changed to a different relative depth to the build dir, need # to adapt the ../.. relative paths below and at the top of configure.ac (we # cannot use absolute paths due to issues in nested configure when path to # build dir contains spaces). PKG_DIR = ./pkgs PKG8_DIR = ./pkgs8 configure-packages: @for i in $(PKGS_DIR)/*; do \ if [ -d $$i ] ; then \ if [ -x $$i/configure ] ; then \ pkg=`basename $$i`; \ echo "Configuring package '$$pkg'"; \ mkdir -p $(PKG8_DIR)/$$pkg; \ if [ ! -f $(PKG8_DIR)/$$pkg/Makefile ] ; then \ ( cd $(PKG8_DIR)/$$pkg; \ $$i/configure --with-tcl8 --with-tcl=../.. \ --with-tclinclude=$(GENERIC_DIR) \ $(PKG_CFG_ARGS) --libdir=$(PACKAGE_DIR) \ --enable-shared; ) || exit $$?; \ fi; \ mkdir -p $(PKG_DIR)/$$pkg; \ if [ ! -f $(PKG_DIR)/$$pkg/Makefile ] ; then \ ( cd $(PKG_DIR)/$$pkg; \ $$i/configure --with-tcl=../.. \ --with-tclinclude=$(GENERIC_DIR) \ $(PKG_CFG_ARGS) --libdir=$(PACKAGE_DIR) \ --enable-shared; ) || exit $$?; \ fi; \ fi; \ fi; \ done packages: configure-packages ${STUB_LIB_FILE} @for i in $(PKGS_DIR)/*; do \ if [ -d $$i ] ; then \ pkg=`basename $$i`; \ if [ -f $(PKG8_DIR)/$$pkg/Makefile ] ; then \ echo "Building package '$$pkg' for Tcl 8"; \ ( cd $(PKG8_DIR)/$$pkg; $(MAKE); ) || exit $$?; \ fi; \ if [ -f $(PKG_DIR)/$$pkg/Makefile ] ; then \ echo "Building package '$$pkg'"; \ ( cd $(PKG_DIR)/$$pkg; $(MAKE); ) || exit $$?; \ fi; \ fi; \ done install-packages: packages @for i in $(PKGS_DIR)/*; do \ if [ -d $$i ] ; then \ pkg=`basename $$i`; \ if [ -f $(PKG8_DIR)/$$pkg/Makefile ] ; then \ echo "Installing package '$$pkg' for Tcl 8"; \ ( cd $(PKG8_DIR)/$$pkg; $(MAKE) install \ "DESTDIR=$(INSTALL_ROOT)"; ) || exit $$?; \ fi; \ if [ -f $(PKG_DIR)/$$pkg/Makefile ] ; then \ echo "Installing package '$$pkg'"; \ ( cd $(PKG_DIR)/$$pkg; $(MAKE) install \ "DESTDIR=$(INSTALL_ROOT)"; ) || exit $$?; \ fi; \ fi; \ done test-packages: ${TCLTEST_EXE} packages @for i in $(PKGS_DIR)/*; do \ if [ -d $$i ] ; then \ pkg=`basename $$i`; \ if [ -f $(PKG_DIR)/$$pkg/Makefile ] ; then \ echo "Testing package '$$pkg'"; \ ( cd $(PKG_DIR)/$$pkg; $(MAKE) \ "@LD_LIBRARY_PATH_VAR@=../..:$${@LD_LIBRARY_PATH_VAR@}" \ "TCL_LIBRARY=${TCL_BUILDTIME_LIBRARY}" \ "TCLLIBPATH=../../pkgs" test \ "TCLSH_PROG=../../${TCLTEST_EXE}"; ) \ fi; \ fi; \ done clean-packages: @for i in $(PKGS_DIR)/*; do \ if [ -d $$i ] ; then \ pkg=`basename $$i`; \ if [ -f $(PKG8_DIR)/$$pkg/Makefile ] ; then \ ( cd $(PKG8_DIR)/$$pkg; $(MAKE) clean; ) \ fi; \ if [ -f $(PKG_DIR)/$$pkg/Makefile ] ; then \ ( cd $(PKG_DIR)/$$pkg; $(MAKE) clean; ) \ fi; \ fi; \ done distclean-packages: @for i in $(PKGS_DIR)/*; do \ if [ -d $$i ] ; then \ pkg=`basename $$i`; \ if [ -f $(PKG8_DIR)/$$pkg/Makefile ] ; then \ ( cd $(PKG8_DIR)/$$pkg; $(MAKE) distclean; ) \ fi; \ rm -rf $(PKG8_DIR)/$$pkg; \ if [ -f $(PKG_DIR)/$$pkg/Makefile ] ; then \ ( cd $(PKG_DIR)/$$pkg; $(MAKE) distclean; ) \ fi; \ rm -rf $(PKG_DIR)/$$pkg; \ fi; \ done; \ rm -rf $(PKG8_DIR) rm -rf $(PKG_DIR) dist-packages: configure-packages @rm -rf $(DISTROOT)/pkgs; \ mkdir -p $(DISTROOT)/pkgs; \ for i in $(PKGS_DIR)/*; do \ if [ -d $$i ] ; then \ pkg=`basename $$i`; \ if [ -f $(PKG_DIR)/$$pkg/Makefile ] ; then \ ( cd $(PKG_DIR)/$$pkg; $(MAKE) dist \ "DIST_ROOT=$(DISTROOT)/pkgs"; ) || exit $$?; \ fi; \ fi; \ done #-------------------------------------------------------------------------- # Maintainer-only targets #-------------------------------------------------------------------------- # The following target generates the file generic/tclDate.c from the yacc # grammar found in generic/tclGetDate.y. This is only run by hand as yacc is # not available in all environments. The name of the .c file is different than # the name of the .y file so that make doesn't try to automatically regenerate # the .c file. # # Remark: see [54a305cb88]. tclDate.c is manually edited, removing the unused "yynerrs" variable gendate: bison --output-file=$(GENERIC_DIR)/tclDate.c \ --no-lines \ --name-prefix=TclDate \ $(GENERIC_DIR)/tclGetDate.y # yacc -l $(GENERIC_DIR)/tclGetDate.y # sed -e 's/yy/TclDate/g' -e '/^#include /d' \ # -e 's?SCCSID?RCS: @(#) ?' \ # -e '/#ifdef __STDC__/,/#endif/d' -e '/TclDateerrlab:/d' \ # -e '/TclDatenewstate:/d' -e '/#pragma/d' \ # -e '/#include /d' \ # -e '/#define YYNEW/s/malloc/TclDateAlloc/g' \ # -e '/#define YYENLARGE/,/realloc/s/realloc/TclDateRealloc/g' \ # $(GENERIC_DIR)/tclDate.c # rm y.tab.c # # Target to regenerate header files and stub files from the *.decls tables. # $(GENERIC_DIR)/tclStubInit.c: $(GENERIC_DIR)/tcl.decls \ $(GENERIC_DIR)/tclInt.decls $(GENERIC_DIR)/tclTomMath.decls @echo "Warning: tclStubInit.c may be out of date." @echo "Developers may want to run \"make genstubs\" to regenerate." @echo "This warning can be safely ignored, do not report as a bug!" $(GENERIC_DIR)/tclOOStubInit.c: $(GENERIC_DIR)/tclOO.decls @echo "Warning: tclOOStubInit.c may be out of date." @echo "Developers may want to run \"make genstubs\" to regenerate." @echo "This warning can be safely ignored, do not report as a bug!" $(GENERIC_DIR)/tclOOScript.h: $(TOOL_DIR)/tclOOScript.tcl @echo "Warning: tclOOScript.h may be out of date." @echo "Developers may want to run \"make genscript\" to regenerate." @echo "This warning can be safely ignored, do not report as a bug!" genstubs: $(NATIVE_TCLSH) $(TOOL_DIR)/genStubs.tcl $(GENERIC_DIR) \ $(GENERIC_DIR)/tcl.decls $(GENERIC_DIR)/tclInt.decls \ $(GENERIC_DIR)/tclTomMath.decls $(NATIVE_TCLSH) $(TOOL_DIR)/genStubs.tcl $(GENERIC_DIR) \ $(GENERIC_DIR)/tclOO.decls genscript: $(NATIVE_TCLSH) $(TOOL_DIR)/makeHeader.tcl \ $(TOOL_DIR)/tclOOScript.tcl $(GENERIC_DIR)/tclOOScript.h # # Target to check that all exported functions have an entry in the stubs # tables. # checkstubs: $(TCL_LIB_FILE) -@for i in `nm -p $(TCL_LIB_FILE) \ | awk '$$2 ~ /^[TDBCS]$$/ { sub("^_", "", $$3); print $$3 }' \ | sort -n` ; do \ match=0; \ for j in $(TCL_DECLS); do \ if [ `grep -c "$$i *(" $$j` -gt 0 ] ; then \ match=1; \ fi; \ done; \ if [ $$match -eq 0 ] ; then \ echo $$i; \ fi; \ done # # Target to check that all public APIs which are not command implementations # have an entry in section three of the distributed manpages. # checkdoc: $(TCL_LIB_FILE) -@for i in `nm -p $(TCL_LIB_FILE) | awk '$$3 ~ /Tcl_/ { print $$3 }' \ | grep -Fv . | grep -v 'Cmd$$' | sort -n`; do \ match=0; \ i=`echo $$i | sed 's/^_//'`; \ for j in $(TOP_DIR)/doc/*.3; do \ if [ `grep '\-' $$j | grep -c $$i` -gt 0 ]; then \ match=1; \ fi; \ done; \ if [ $$match -eq 0 ]; then \ echo $$i; \ fi; \ done # # Target to check for proper usage of UCHAR macro. # checkuchar: -@egrep isalnum\|isalpha\|iscntrl\|isdigit\|islower\|isprint\|ispunct\|isspace\|isupper\|isxdigit\|toupper\|tolower $(SRCS) | grep -v UCHAR # # Target to make sure that only symbols with "Tcl" prefixes are exported. # checkexports: $(TCL_LIB_FILE) -@nm -p $(TCL_LIB_FILE) \ | awk '$$2 ~ /^[TDBCS]$$/ { sub("^_", "", $$3); print $$3 }' \ | sort -n | grep -E -v '^[Tt]cl' || true #-------------------------------------------------------------------------- # Distribution building rules #-------------------------------------------------------------------------- # # Target to create a Tcl RPM for Linux. Requires that you be on a Linux # system. # RPM_PLATFORMS = i386 rpm: all -@rm -f THIS.TCL.SPEC echo "%define _builddir `pwd`" > THIS.TCL.SPEC echo "%define _rpmdir `pwd`/RPMS" >> THIS.TCL.SPEC cat tcl.spec >> THIS.TCL.SPEC for platform in $(RPM_PLATFORMS); do \ mkdir -p RPMS/$$platform && \ rpmbuild -bb THIS.TCL.SPEC && \ mv RPMS/$$platform/*.rpm .; \ done -rm -rf RPMS THIS.TCL.SPEC # # Target to create a proper Tcl distribution from information in the # source directory. DISTDIR must be defined to indicate where to put # the distribution. DISTDIR must be an absolute path name. # DISTROOT = /tmp/dist DISTNAME = tcl${VERSION}${PATCH_LEVEL} ZIPNAME = tcl${MAJOR_VERSION}${MINOR_VERSION}${PATCH_LEVEL}-src.zip DISTDIR = $(DISTROOT)/$(DISTNAME) DIST_INSTALL_DATA = $(INSTALL) -p -m 644 DIST_INSTALL_SCRIPT = $(INSTALL) -p -m 755 BUILTIN_PACKAGE_LIST = cookiejar http opt msgcat registry dde tcltest platform $(UNIX_DIR)/configure: $(UNIX_DIR)/configure.ac $(UNIX_DIR)/tcl.m4 \ $(UNIX_DIR)/aclocal.m4 cd $(UNIX_DIR); autoconf $(MAC_OSX_DIR)/configure: $(MAC_OSX_DIR)/configure.ac $(UNIX_DIR)/configure cd $(MAC_OSX_DIR); autoconf $(UNIX_DIR)/tclConfig.h.in: $(MAC_OSX_DIR)/configure cd $(MAC_OSX_DIR); autoheader; touch $@ tclUuid.h: $(TOP_DIR)/manifest.uuid echo "#define TCL_VERSION_UUID \\" >$@ cat $(TOP_DIR)/manifest.uuid >>$@ echo "" >>$@ $(TOP_DIR)/manifest.uuid: printf "git-" >$(TOP_DIR)/manifest.uuid (cd $(TOP_DIR); git rev-parse HEAD >>$(TOP_DIR)/manifest.uuid || \ (printf "svn-r" >$(TOP_DIR)/manifest.uuid ; \ svn info --show-item last-changed-revision >>$(TOP_DIR)/manifest.uuid) || \ printf "unknown" >$(TOP_DIR)/manifest.uuid) dist: $(UNIX_DIR)/configure $(UNIX_DIR)/tclConfig.h.in $(UNIX_DIR)/tcl.pc.in genstubs \ $(MAC_OSX_DIR)/configure $(TOP_DIR)/manifest.uuid dist-packages ${NATIVE_TCLSH} rm -rf $(DISTDIR) $(INSTALL_DATA_DIR) $(DISTDIR)/unix $(DIST_INSTALL_DATA) $(TOP_DIR)/manifest.uuid $(DISTDIR) $(DIST_INSTALL_DATA) $(UNIX_DIR)/*.c $(UNIX_DIR)/tclUnixPort.h $(DISTDIR)/unix $(DIST_INSTALL_DATA) $(UNIX_DIR)/Makefile.in $(DISTDIR)/unix $(DIST_INSTALL_DATA) $(UNIX_DIR)/configure.ac \ $(UNIX_DIR)/tcl.m4 $(UNIX_DIR)/aclocal.m4 \ $(UNIX_DIR)/tclConfig.sh.in $(UNIX_DIR)/tclooConfig.sh \ $(UNIX_DIR)/install-sh \ $(UNIX_DIR)/README $(UNIX_DIR)/tcl.spec \ $(UNIX_DIR)/installManPage $(UNIX_DIR)/tclConfig.h.in \ $(UNIX_DIR)/tcl.pc.in $(DISTDIR)/unix $(DIST_INSTALL_SCRIPT) $(UNIX_DIR)/configure $(UNIX_DIR)/ldAix $(DISTDIR)/unix $(INSTALL_DATA_DIR) $(DISTDIR)/generic $(DIST_INSTALL_DATA) $(GENERIC_DIR)/*.[cdh] $(DISTDIR)/generic $(DIST_INSTALL_DATA) $(GENERIC_DIR)/*.decls $(DISTDIR)/generic $(DIST_INSTALL_DATA) $(GENERIC_DIR)/README $(DISTDIR)/generic $(DIST_INSTALL_DATA) $(GENERIC_DIR)/tclGetDate.y $(DISTDIR)/generic $(DIST_INSTALL_DATA) $(TOP_DIR)/changes.md $(TOP_DIR)/README.md \ $(TOP_DIR)/license.terms $(DISTDIR) $(INSTALL_DATA_DIR) $(DISTDIR)/library $(DIST_INSTALL_DATA) $(TOP_DIR)/license.terms $(TOP_DIR)/library/*.tcl \ $(TOP_DIR)/library/manifest.txt \ $(TOP_DIR)/library/tclIndex $(DISTDIR)/library @for i in $(BUILTIN_PACKAGE_LIST); do \ $(INSTALL_DATA_DIR) $(DISTDIR)/library/$$i;\ $(DIST_INSTALL_DATA) $(TOP_DIR)/library/$$i/*.tcl $(DISTDIR)/library/$$i; \ done $(DIST_INSTALL_DATA) $(TOP_DIR)/library/cookiejar/*.dat.gz $(DISTDIR)/library/cookiejar $(INSTALL_DATA_DIR) $(DISTDIR)/library/encoding $(DIST_INSTALL_DATA) $(TOP_DIR)/library/encoding/*.enc $(DISTDIR)/library/encoding $(INSTALL_DATA_DIR) $(DISTDIR)/library/msgs $(DIST_INSTALL_DATA) $(TOP_DIR)/library/msgs/*.msg $(DISTDIR)/library/msgs @echo cp -r $(TOP_DIR)/library/tzdata $(DISTDIR)/library/tzdata @( cd $(TOP_DIR); find library/tzdata -type f -print ) \ | ( cd $(TOP_DIR) ; xargs tar cf - ) \ | ( cd $(DISTDIR) ; tar xfp - ) $(INSTALL_DATA_DIR) $(DISTDIR)/doc $(DIST_INSTALL_DATA) $(TOP_DIR)/license.terms $(TOP_DIR)/doc/*.[13n] \ $(TOP_DIR)/doc/man.macros $(DISTDIR)/doc $(INSTALL_DATA_DIR) $(DISTDIR)/compat $(DIST_INSTALL_DATA) $(TOP_DIR)/license.terms $(COMPAT_DIR)/*.[ch] \ $(COMPAT_DIR)/README $(DISTDIR)/compat $(INSTALL_DATA_DIR) $(DISTDIR)/compat/zlib @echo cp -r $(COMPAT_DIR)/zlib $(DISTDIR)/compat/zlib @( cd $(COMPAT_DIR)/zlib; find . -type f -print ) \ | ( cd $(COMPAT_DIR)/zlib ; xargs tar cf - ) \ | ( cd $(DISTDIR)/compat/zlib ; tar xfp - ) $(INSTALL_DATA_DIR) $(DISTDIR)/libtommath @echo cp -r $(TOP_DIR)/libtommath $(DISTDIR)/libtommath @( cd $(TOP_DIR)/libtommath; find . -type f -print ) \ | ( cd $(TOP_DIR)/libtommath ; xargs tar cf - ) \ | ( cd $(DISTDIR)/libtommath ; tar xfp - ) $(INSTALL_DATA_DIR) $(DISTDIR)/tests $(DIST_INSTALL_DATA) $(TOP_DIR)/license.terms $(DISTDIR)/tests $(DIST_INSTALL_DATA) $(TOP_DIR)/tests/*.test $(TOP_DIR)/tests/README \ $(TOP_DIR)/tests/*.bench $(TOP_DIR)/tests/*.tar.gz \ $(TOP_DIR)/tests/httpd $(TOP_DIR)/tests/*.tcl \ $(TOP_DIR)/tests/*.zip $(DISTDIR)/tests @mkdir $(DISTDIR)/tests/auto0 for i in auto1 auto2 ; \ do \ $(INSTALL_DATA_DIR) $(DISTDIR)/tests/auto0/$$i ;\ $(DIST_INSTALL_DATA) $(TOP_DIR)/tests/auto0/$$i/tclIndex $(TOP_DIR)/tests/auto0/$$i/*.tcl \ $(DISTDIR)/tests/auto0/$$i; \ done; for i in modules modules/mod1 modules/mod2 ; \ do \ $(INSTALL_DATA_DIR) $(DISTDIR)/tests/auto0/$$i ;\ $(DIST_INSTALL_DATA) $(TOP_DIR)/tests/auto0/$$i/*.tm \ $(DISTDIR)/tests/auto0/$$i; \ done; @mkdir $(DISTDIR)/tests/zipfiles $(INSTALL_DATA_DIR) $(DISTDIR)/tests/zipfiles $(DIST_INSTALL_DATA) $(TOP_DIR)/tests/zipfiles/*.zip \ $(DISTDIR)/tests/zipfiles $(DIST_INSTALL_DATA) $(TOP_DIR)/tests/zipfiles/README \ $(DISTDIR)/tests/zipfiles $(DIST_INSTALL_DATA) $(TOP_DIR)/tests/zipfiles/LICENSE-libzip \ $(DISTDIR)/tests/zipfiles $(INSTALL_DATA_DIR) $(DISTDIR)/tests-perf $(DIST_INSTALL_DATA) $(TOP_DIR)/tests-perf/*.tcl $(DISTDIR)/tests-perf $(INSTALL_DATA_DIR) $(DISTDIR)/win $(DIST_INSTALL_DATA) $(TOP_DIR)/win/Makefile.in $(DISTDIR)/win $(DIST_INSTALL_DATA) $(TOP_DIR)/win/configure.ac \ $(TOP_DIR)/win/tclConfig.sh.in $(TOP_DIR)/win/tclooConfig.sh \ $(TOP_DIR)/win/tcl.m4 $(TOP_DIR)/win/aclocal.m4 \ $(TOP_DIR)/win/tclsh.exe.manifest.in $(TOP_DIR)/win/tclUuid.h.in \ $(TOP_DIR)/win/gitmanifest.in $(TOP_DIR)/win/svnmanifest.in \ $(TOP_DIR)/win/x86_64-w64-mingw32-nmakehlp.exe $(DISTDIR)/win chmod 775 $(DISTDIR)/win/x86_64-w64-mingw32-nmakehlp.exe $(DIST_INSTALL_SCRIPT) $(TOP_DIR)/win/configure $(DISTDIR)/win $(DIST_INSTALL_DATA) $(TOP_DIR)/win/*.c $(TOP_DIR)/win/*.ico $(TOP_DIR)/win/*.rc \ $(TOP_DIR)/win/tclWinInt.h $(TOP_DIR)/win/tclWinPort.h \ $(DISTDIR)/win $(DIST_INSTALL_DATA) $(TOP_DIR)/win/*.bat $(DISTDIR)/win $(DIST_INSTALL_DATA) $(TOP_DIR)/win/*.vc $(DISTDIR)/win $(DIST_INSTALL_DATA) $(TOP_DIR)/win/tcl.ds* $(DISTDIR)/win $(DIST_INSTALL_DATA) $(TOP_DIR)/win/README $(DISTDIR)/win $(DIST_INSTALL_DATA) $(TOP_DIR)/license.terms $(DISTDIR)/win $(INSTALL_DATA_DIR) $(DISTDIR)/macosx $(DIST_INSTALL_DATA) $(MAC_OSX_DIR)/GNUmakefile $(MAC_OSX_DIR)/README \ $(MAC_OSX_DIR)/*.c $(MAC_OSX_DIR)/*.in \ $(MAC_OSX_DIR)/*.ac \ $(DISTDIR)/macosx $(DIST_INSTALL_SCRIPT) $(MAC_OSX_DIR)/configure $(DISTDIR)/macosx $(DIST_INSTALL_DATA) $(TOP_DIR)/license.terms $(DISTDIR)/macosx $(INSTALL_DATA_DIR) $(DISTDIR)/unix/dltest $(DIST_INSTALL_DATA) $(UNIX_DIR)/dltest/*.c $(UNIX_DIR)/dltest/Makefile.in \ $(UNIX_DIR)/dltest/README $(DISTDIR)/unix/dltest $(INSTALL_DATA_DIR) $(DISTDIR)/tools $(DIST_INSTALL_DATA) $(TOOL_DIR)/README $(TOOL_DIR)/*.c $(TOOL_DIR)/*.svg \ $(TOOL_DIR)/*.tcl $(TOOL_DIR)/*.bmp $(TOOL_DIR)/valgrind_suppress \ $(TOOL_DIR)/valgrind_check_success $(DISTDIR)/tools chmod 755 $(DISTDIR)/tools/checkLibraryDoc.tcl \ $(DISTDIR)/tools/findBadExternals.tcl \ $(DISTDIR)/tools/loadICU.tcl $(DISTDIR)/tools/addVerToFile.tcl \ $(DISTDIR)/tools/makeTestCases.tcl $(DISTDIR)/tools/tclZIC.tcl \ $(DISTDIR)/tools/tcltk-man2html.tcl $(DISTDIR)/win/buildall.vc.bat \ $(DISTDIR)/unix/install-sh $(DISTDIR)/unix/installManPage $(INSTALL_DATA_DIR) $(DISTDIR)/pkgs $(DIST_INSTALL_DATA) $(TOP_DIR)/pkgs/README $(DISTDIR)/pkgs $(DIST_INSTALL_DATA) $(TOP_DIR)/pkgs/package.list.txt $(DISTDIR)/pkgs for i in `ls $(DISTROOT)/pkgs/*.tar.gz 2> /dev/null`; do \ tar -C $(DISTDIR)/pkgs -xzf "$$i"; \ done $(INSTALL_DATA_DIR) $(DISTDIR)/.github/workflows $(DIST_INSTALL_DATA) $(TOP_DIR)/.github/workflows/*.yml $(DISTDIR)/.github/workflows alldist: dist rm -f $(DISTROOT)/$(DISTNAME)-src.tar.gz $(DISTROOT)/$(ZIPNAME) ( cd $(DISTROOT); \ tar cf $(DISTNAME)-src.tar $(DISTNAME); \ gzip -9 $(DISTNAME)-src.tar; \ zip -qr8 $(ZIPNAME) $(DISTNAME) ) #-------------------------------------------------------------------------- # This target creates the HTML folder for Tcl & Tk and places it in # DISTDIR/html. It uses the tcltk-man2html.tcl tool from the Tcl group's tool # workspace. It depends on the Tcl & Tk being in directories called tcl9.* & # tk9.* up two directories from the TOOL_DIR. # # Note that for platforms where this is important, it is more common to use a # build of this HTML documentation that has already been placed online. As # such, this rule is not guaranteed to work well on all systems; it only needs # to function on those of the Tcl/Tk maintainers. # # Also note that the 8.6 tool build requires an installed 8.6 native Tcl # interpreter in order to be able to run. #-------------------------------------------------------------------------- html: ${NATIVE_TCLSH} $(BUILD_HTML) @EXTRA_BUILD_HTML@ html-tcl: ${NATIVE_TCLSH} $(BUILD_HTML) --tcl @EXTRA_BUILD_HTML@ html-tk: ${NATIVE_TCLSH} $(BUILD_HTML) --tk @EXTRA_BUILD_HTML@ BUILD_HTML = \ @${NATIVE_TCLSH} $(TOOL_DIR)/tcltk-man2html.tcl \ --useversion=$(MAJOR_VERSION).$(MINOR_VERSION) \ --htmldir="$(HTML_INSTALL_DIR)" \ --srcdir=$(TOP_DIR) $(BUILD_HTML_FLAGS) #-------------------------------------------------------------------------- # The list of all the targets that do not correspond to real files. This stops # 'make' from getting confused when someone makes an error in a rule. #-------------------------------------------------------------------------- .PHONY: all binaries libraries objs doc html html-tcl html-tk test runtest .PHONY: install install-strip install-binaries install-libraries .PHONY: install-headers install-private-headers install-doc .PHONY: clean distclean depend genstubs checkstubs checkexports checkuchar .PHONY: shell gdb valgrind valgrindshell dist alldist rpm .PHONY: tclLibObjs tcltest-real test-tcl gdb-test ro-test trace-test xttest .PHONY: topDirName gendate gentommath_h trace-shell checkdoc .PHONY: install-tzdata install-msgs .PHONY: packages configure-packages test-packages clean-packages .PHONY: dist-packages distclean-packages install-packages .PHONY: tclzipfile #-------------------------------------------------------------------------- # DO NOT DELETE THIS LINE -- make depend depends on it. tcl9.0.1/unix/configure.ac0000644000175000017500000010641214726623136015033 0ustar sergeisergei#! /bin/bash -norc dnl This file is an input file used by the GNU "autoconf" program to dnl generate the file "configure", which is run during Tcl installation dnl to configure the system for the local environment. AC_INIT([tcl],[9.0]) AC_PREREQ([2.69]) dnl This is only used when included from macosx/configure.ac m4_ifdef([SC_USE_CONFIG_HEADERS], [ AC_CONFIG_HEADERS([tclConfig.h:../unix/tclConfig.h.in]) AC_CONFIG_COMMANDS_PRE([DEFS="-DHAVE_TCL_CONFIG_H -imacros tclConfig.h"]) AH_TOP([ #ifndef _TCLCONFIG #define _TCLCONFIG]) AH_BOTTOM([ /* Undef unused package specific autoheader defines so that we can * include both tclConfig.h and tkConfig.h at the same time: */ /* override */ #undef PACKAGE_NAME /* override */ #undef PACKAGE_TARNAME /* override */ #undef PACKAGE_VERSION /* override */ #undef PACKAGE_STRING #endif /* _TCLCONFIG */]) ]) TCL_VERSION=9.0 TCL_MAJOR_VERSION=9 TCL_MINOR_VERSION=0 TCL_PATCH_LEVEL=".1" VERSION=${TCL_VERSION} EXTRA_INSTALL_BINARIES=${EXTRA_INSTALL_BINARIES:-"@:"} EXTRA_BUILD_HTML=${EXTRA_BUILD_HTML:-"@:"} #------------------------------------------------------------------------ # Setup configure arguments for bundled packages #------------------------------------------------------------------------ PKG_CFG_ARGS="$ac_configure_args ${PKG_CFG_ARGS}" if test -r "$cache_file" -a -f "$cache_file"; then case $cache_file in [[\\/]]* | ?:[[\\/]]* ) pkg_cache_file=$cache_file ;; *) pkg_cache_file=../../$cache_file ;; esac PKG_CFG_ARGS="${PKG_CFG_ARGS} --cache-file=$pkg_cache_file" fi #------------------------------------------------------------------------ # Empty slate for bundled packages, to avoid stale configuration #------------------------------------------------------------------------ #rm -Rf pkgs if test -f Makefile; then make distclean-packages fi #------------------------------------------------------------------------ # Handle the --prefix=... option #------------------------------------------------------------------------ if test "${prefix}" = "NONE"; then prefix=/usr/local fi if test "${exec_prefix}" = "NONE"; then exec_prefix=$prefix fi # Make sure srcdir is fully qualified! srcdir="`cd "$srcdir" ; pwd`" TCL_SRC_DIR="`cd "$srcdir"/..; pwd`" #------------------------------------------------------------------------ # Compress and/or soft link the manpages? #------------------------------------------------------------------------ SC_CONFIG_MANPAGES #------------------------------------------------------------------------ # Standard compiler checks #------------------------------------------------------------------------ # If the user did not set CFLAGS, set it now to keep # the AC_PROG_CC macro from adding "-g -O2". if test "${CFLAGS+set}" != "set" ; then CFLAGS="" fi AC_PROG_CC AC_C_INLINE #-------------------------------------------------------------------- # Supply substitutes for missing POSIX header files. Special notes: # - stdlib.h doesn't define strtol or strtoul in some versions # of SunOS # - some versions of string.h don't declare procedures such # as strstr # Do this early, otherwise an autoconf bug throws errors on configure #-------------------------------------------------------------------- SC_MISSING_POSIX_HEADERS #-------------------------------------------------------------------- # Determines the correct executable file extension (.exe) #-------------------------------------------------------------------- AC_EXEEXT #------------------------------------------------------------------------ # If we're using GCC, see if the compiler understands -pipe. If so, use it. # It makes compiling go faster. (This is only a performance feature.) #------------------------------------------------------------------------ if test -z "$no_pipe" && test -n "$GCC"; then AC_CACHE_CHECK([if the compiler understands -pipe], tcl_cv_cc_pipe, [ hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -pipe" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[]])],[tcl_cv_cc_pipe=yes],[tcl_cv_cc_pipe=no]) CFLAGS=$hold_cflags]) if test $tcl_cv_cc_pipe = yes; then CFLAGS="$CFLAGS -pipe" fi fi #------------------------------------------------------------------------ # Embedded configuration information, encoding to use for the values, TIP #59 #------------------------------------------------------------------------ SC_TCL_CFG_ENCODING #-------------------------------------------------------------------- # Look for libraries that we will need when compiling the Tcl shell #-------------------------------------------------------------------- SC_TCL_LINK_LIBS # Add the threads support libraries LIBS="$LIBS$THREADS_LIBS" SC_ENABLE_SHARED #-------------------------------------------------------------------- # Look for a native installed tclsh binary (if available) # If one cannot be found then use the binary we build (fails for # cross compiling). This is used for NATIVE_TCLSH in Makefile. #-------------------------------------------------------------------- SC_PROG_TCLSH if test "$TCLSH_PROG" = ""; then TCLSH_PROG='./${TCL_EXE}' fi #------------------------------------------------------------------------ # Add stuff for zlib #------------------------------------------------------------------------ zlib_ok=yes AC_CHECK_HEADER([zlib.h],[ AC_CHECK_TYPE([gz_header],[],[zlib_ok=no],[#include ])],[ zlib_ok=no]) AS_IF([test $zlib_ok = yes], [ AC_SEARCH_LIBS([deflateSetHeader],[z],[],[ zlib_ok=no ])]) AS_IF([test $zlib_ok = no], [ AC_SUBST(ZLIB_OBJS,[\${ZLIB_OBJS}]) AC_SUBST(ZLIB_SRCS,[\${ZLIB_SRCS}]) AC_SUBST(ZLIB_INCLUDE,[-I\${ZLIB_DIR}]) AC_DEFINE(TCL_WITH_INTERNAL_ZLIB, 1, [Tcl with internal zlib]) ]) #------------------------------------------------------------------------ # Add stuff for libtommath libtommath_ok=yes AC_ARG_WITH(system-libtommath, AS_HELP_STRING([--with-system-libtommath], [use external libtommath (default: true if available, false otherwise)]), [libtommath_ok=${withval}]) if test x"${libtommath_ok}" = x -o x"${libtommath_ok}" != xno; then AC_CHECK_HEADER([tommath.h],[ AC_CHECK_TYPE([mp_int],[],[libtommath_ok=no],[#include ])],[ libtommath_ok=no]) AS_IF([test $libtommath_ok = yes], [ AC_CHECK_LIB([tommath],[mp_log_u32],[MATH_LIBS="$MATH_LIBS -ltommath"],[ libtommath_ok=no])]) fi AS_IF([test $libtommath_ok = yes], [ AC_SUBST(TCL_PC_REQUIRES_PRIVATE, ['libtommath >= 1.2.0,']) AC_SUBST(TCL_PC_CFLAGS, ['-DTCL_WITH_EXTERNAL_TOMMATH']) AC_DEFINE(TCL_WITH_EXTERNAL_TOMMATH, 1, [Tcl with external libtommath]) ], [ AC_SUBST(TOMMATH_OBJS,[\${TOMMATH_OBJS}]) AC_SUBST(TOMMATH_SRCS,[\${TOMMATH_SRCS}]) AC_SUBST(TOMMATH_INCLUDE,[-I\${TOMMATH_DIR}]) ]) #-------------------------------------------------------------------- # The statements below define a collection of compile flags. This # macro depends on the value of SHARED_BUILD, and should be called # after SC_ENABLE_SHARED checks the configure switches. #-------------------------------------------------------------------- SC_CONFIG_CFLAGS SC_ENABLE_SYMBOLS(bccdebug) AC_DEFINE(MP_PREC, 4, [Default libtommath precision.]) #-------------------------------------------------------------------- # Detect what compiler flags to set for 64-bit support. #-------------------------------------------------------------------- SC_TCL_EARLY_FLAGS SC_TCL_64BIT_FLAGS #-------------------------------------------------------------------- # Check endianness because we can optimize comparisons of # Tcl_UniChar strings to memcmp on big-endian systems. #-------------------------------------------------------------------- AC_C_BIGENDIAN(,,,[#]) #-------------------------------------------------------------------- # Supply substitutes for missing POSIX library procedures, or # set flags so Tcl uses alternate procedures. #-------------------------------------------------------------------- # Check if Posix compliant getcwd exists, if not we'll use getwd. AC_CHECK_FUNCS(getcwd, , [AC_DEFINE(USEGETWD, 1, [Is getcwd Posix-compliant?])]) # Nb: if getcwd uses popen and pwd(1) (like SunOS 4) we should really # define USEGETWD even if the posix getcwd exists. Add a test ? AC_REPLACE_FUNCS(mkstemp waitpid) AC_CHECK_FUNC(strerror, , [AC_DEFINE(NO_STRERROR, 1, [Do we have strerror()])]) AC_CHECK_FUNC(getwd, , [AC_DEFINE(NO_GETWD, 1, [Do we have getwd()])]) AC_CHECK_FUNC(wait3, , [AC_DEFINE(NO_WAIT3, 1, [Do we have wait3()])]) AC_CHECK_FUNC(fork, , [AC_DEFINE(NO_FORK, 1, [Do we have fork()])]) AC_CHECK_FUNC(mknod, , [AC_DEFINE(NO_MKNOD, 1, [Do we have mknod()])]) AC_CHECK_FUNC(tcdrain, , [AC_DEFINE(NO_TCDRAIN, 1, [Do we have tcdrain()])]) AC_CHECK_FUNC(uname, , [AC_DEFINE(NO_UNAME, 1, [Do we have uname()])]) if test "`uname -s`" = "Darwin" && \ test "`uname -r | awk -F. '{print [$]1}'`" -lt 7; then # prior to Darwin 7, realpath is not threadsafe, so don't # use it when threads are enabled, c.f. bug # 711232 ac_cv_func_realpath=no fi AC_CHECK_FUNC(realpath, , [AC_DEFINE(NO_REALPATH, 1, [Do we have realpath()])]) SC_TCL_IPV6 #-------------------------------------------------------------------- # Look for thread-safe variants of some library functions. #-------------------------------------------------------------------- SC_TCL_GETPWUID_R SC_TCL_GETPWNAM_R SC_TCL_GETGRGID_R SC_TCL_GETGRNAM_R if test "`uname -s`" = "Darwin" && \ test "`uname -r | awk -F. '{print [$]1}'`" -gt 5; then # Starting with Darwin 6 (Mac OSX 10.2), gethostbyX # are actually MT-safe as they always return pointers # from TSD instead of static storage. AC_DEFINE(HAVE_MTSAFE_GETHOSTBYNAME, 1, [Do we have MT-safe gethostbyname() ?]) AC_DEFINE(HAVE_MTSAFE_GETHOSTBYADDR, 1, [Do we have MT-safe gethostbyaddr() ?]) elif test "`uname -s`" = "HP-UX" && \ test "`uname -r|sed -e 's|B\.||' -e 's|\..*$||'`" -gt 10; then # Starting with HPUX 11.00 (we believe), gethostbyX # are actually MT-safe as they always return pointers # from TSD instead of static storage. AC_DEFINE(HAVE_MTSAFE_GETHOSTBYNAME, 1, [Do we have MT-safe gethostbyname() ?]) AC_DEFINE(HAVE_MTSAFE_GETHOSTBYADDR, 1, [Do we have MT-safe gethostbyaddr() ?]) else SC_TCL_GETHOSTBYNAME_R SC_TCL_GETHOSTBYADDR_R fi #--------------------------------------------------------------------------- # Check for serial port interface. # # termios.h is present on all POSIX systems. # sys/ioctl.h is almost always present, though what it contains # is system-specific. # sys/modem.h is needed on HP-UX. #--------------------------------------------------------------------------- AC_CHECK_HEADERS(termios.h) AC_CHECK_HEADERS(sys/ioctl.h) AC_CHECK_HEADERS(sys/modem.h) #-------------------------------------------------------------------- # Include sys/select.h if it exists and if it supplies things # that appear to be useful and aren't already in sys/types.h. # This appears to be true only on the RS/6000 under AIX. Some # systems like OSF/1 have a sys/select.h that's of no use, and # other systems like SCO UNIX have a sys/select.h that's # pernicious. If "fd_set" isn't defined anywhere then set a # special flag. #-------------------------------------------------------------------- AC_CACHE_CHECK([for fd_set in sys/types], tcl_cv_type_fd_set, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[fd_set readMask, writeMask;]])], [tcl_cv_type_fd_set=yes],[tcl_cv_type_fd_set=no])]) tcl_ok=$tcl_cv_type_fd_set if test $tcl_ok = no; then AC_CACHE_CHECK([for fd_mask in sys/select], tcl_cv_grep_fd_mask, [ AC_EGREP_HEADER(fd_mask, sys/select.h, tcl_cv_grep_fd_mask=present, tcl_cv_grep_fd_mask=missing)]) if test $tcl_cv_grep_fd_mask = present; then AC_DEFINE(HAVE_SYS_SELECT_H, 1, [Should we include ?]) tcl_ok=yes fi fi if test $tcl_ok = no; then AC_DEFINE(NO_FD_SET, 1, [Do we have fd_set?]) fi AC_CACHE_CHECK([for pselect], tcl_cv_func_pselect, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[void *func = pselect;]])],[tcl_cv_func_pselect=yes],[tcl_cv_func_pselect=no])]) tcl_ok=$tcl_cv_func_pselect if test $tcl_ok = yes; then AC_DEFINE(HAVE_PSELECT, 1, [Should we use pselect()?]) fi #------------------------------------------------------------------------ # Options for the notifier. Checks for epoll(7) on Linux, and # kqueue(2) on {DragonFly,Free,Net,Open}BSD #------------------------------------------------------------------------ AC_MSG_CHECKING([for advanced notifier support]) case x`uname -s` in xLinux) AC_MSG_RESULT([epoll(7)]) AC_CHECK_HEADERS([sys/epoll.h], [AC_DEFINE(NOTIFIER_EPOLL, [1], [Is epoll(7) supported?])]) AC_CHECK_HEADERS([sys/eventfd.h], [AC_DEFINE(HAVE_EVENTFD, [1], [Is eventfd(2) supported?])]);; xDragonFlyBSD|xFreeBSD|xNetBSD|xOpenBSD) AC_MSG_RESULT([kqueue(2)]) # Messy because we want to check if *all* the headers are present, and not # just *any* tcl_kqueue_headers=x AC_CHECK_HEADERS([sys/types.h sys/event.h sys/time.h], [tcl_kqueue_headers=${tcl_kqueue_headers}y]) AS_IF([test $tcl_kqueue_headers = xyyy], [ AC_DEFINE(NOTIFIER_KQUEUE, [1], [Is kqueue(2) supported?])]);; xDarwin) # Assume that we've got CoreFoundation present (checked elsewhere because # of wider impact). AC_MSG_RESULT([OSX]);; *) AC_MSG_RESULT([none]);; esac #------------------------------------------------------------------------------ # Find out all about time handling differences. #------------------------------------------------------------------------------ SC_TIME_HANDLER #-------------------------------------------------------------------- # Some systems (e.g., IRIX 4.0.5) lack some fields in struct stat. But # we might be able to use fstatfs instead. Some systems (OpenBSD?) also # lack blkcnt_t. #-------------------------------------------------------------------- if test "$ac_cv_cygwin" != "yes"; then AC_CHECK_MEMBERS([struct stat.st_blocks, struct stat.st_blksize, struct stat.st_rdev]) fi AC_CHECK_TYPES([blkcnt_t]) AC_CHECK_FUNC(fstatfs, , [AC_DEFINE(NO_FSTATFS, 1, [Do we have fstatfs()?])]) #-------------------------------------------------------------------- # Check for various typedefs and provide substitutes if # they don't exist. #-------------------------------------------------------------------- AC_TYPE_MODE_T AC_TYPE_PID_T AC_TYPE_SIZE_T AC_TYPE_UID_T AC_CACHE_CHECK([for socklen_t], tcl_cv_type_socklen_t, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ socklen_t foo; ]])],[tcl_cv_type_socklen_t=yes],[tcl_cv_type_socklen_t=no])]) if test $tcl_cv_type_socklen_t = no; then AC_DEFINE(socklen_t, int, [Define as int if socklen_t is not available]) fi AC_CHECK_TYPES([intptr_t, uintptr_t],,,[[ #include ]]) #-------------------------------------------------------------------- # The check below checks whether defines the type # "union wait" correctly. It's needed because of weirdness in # HP-UX where "union wait" is defined in both the BSD and SYS-V # environments. Checking the usability of WIFEXITED seems to do # the trick. #-------------------------------------------------------------------- AC_CACHE_CHECK([union wait], tcl_cv_union_wait, [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include #include ]], [[ union wait x; WIFEXITED(x); /* Generates compiler error if WIFEXITED * uses an int. */ ]])],[tcl_cv_union_wait=yes],[tcl_cv_union_wait=no])]) if test $tcl_cv_union_wait = no; then AC_DEFINE(NO_UNION_WAIT, 1, [Do we have a usable 'union wait'?]) fi #-------------------------------------------------------------------- # Check whether there is an strncasecmp function on this system. # This is a bit tricky because under SCO it's in -lsocket and # under Sequent Dynix it's in -linet. #-------------------------------------------------------------------- AC_CHECK_FUNC(strncasecmp, tcl_ok=1, tcl_ok=0) if test "$tcl_ok" = 0; then AC_CHECK_LIB(socket, strncasecmp, tcl_ok=1, tcl_ok=0) fi if test "$tcl_ok" = 0; then AC_CHECK_LIB(inet, strncasecmp, tcl_ok=1, tcl_ok=0) fi if test "$tcl_ok" = 0; then AC_LIBOBJ([strncasecmp]) USE_COMPAT=1 fi #-------------------------------------------------------------------- # The code below deals with several issues related to gettimeofday: # 1. Some systems don't provide a gettimeofday function at all # (set NO_GETTOD if this is the case). # 2. See if gettimeofday is declared in the header file. # if not, set the GETTOD_NOT_DECLARED flag so that tclPort.h can # declare it. #-------------------------------------------------------------------- AC_CHECK_FUNC(gettimeofday,[],[ AC_DEFINE(NO_GETTOD, 1, [Do we have gettimeofday()?]) ]) AC_CACHE_CHECK([for gettimeofday declaration], tcl_cv_grep_gettimeofday, [ AC_EGREP_HEADER(gettimeofday, sys/time.h, tcl_cv_grep_gettimeofday=present, tcl_cv_grep_gettimeofday=missing)]) if test $tcl_cv_grep_gettimeofday = missing ; then AC_DEFINE(GETTOD_NOT_DECLARED, 1, [Is gettimeofday() actually declared in ?]) fi #-------------------------------------------------------------------- # The following code checks to see whether it is possible to get # signed chars on this platform. This is needed in order to # properly generate sign-extended ints from character values. #-------------------------------------------------------------------- AC_C_CHAR_UNSIGNED AC_CACHE_CHECK([signed char declarations], tcl_cv_char_signed, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[ signed char *p; p = 0; ]])],[tcl_cv_char_signed=yes],[tcl_cv_char_signed=no])]) if test $tcl_cv_char_signed = yes; then AC_DEFINE(HAVE_SIGNED_CHAR, 1, [Are characters signed?]) fi #-------------------------------------------------------------------- # Does putenv() copy or not? We need to know to avoid memory leaks. #-------------------------------------------------------------------- AC_CACHE_CHECK([for a putenv() that copies the buffer], tcl_cv_putenv_copy, [ AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include #include #define OURVAR "havecopy=yes" int main (int argc, char *argv[]) { char *foo, *bar; foo = (char *)strdup(OURVAR); putenv(foo); strcpy((char *)(strchr(foo, '=') + 1), "no"); bar = getenv("havecopy"); if (!strcmp(bar, "no")) { /* doesnt copy */ return 0; } else { /* does copy */ return 1; } } ]])], [tcl_cv_putenv_copy=no], [tcl_cv_putenv_copy=yes], [tcl_cv_putenv_copy=no])]) if test $tcl_cv_putenv_copy = yes; then AC_DEFINE(HAVE_PUTENV_THAT_COPIES, 1, [Does putenv() copy strings or incorporate them by reference?]) fi #-------------------------------------------------------------------- # Check for support of nl_langinfo function #-------------------------------------------------------------------- SC_ENABLE_LANGINFO #-------------------------------------------------------------------- # Check for support of cfmakeraw, chflags and mkstemps functions #-------------------------------------------------------------------- AC_CHECK_FUNCS(cfmakeraw chflags mkstemps) #-------------------------------------------------------------------- # Darwin specific API checks and defines #-------------------------------------------------------------------- if test "`uname -s`" = "Darwin" ; then AC_CHECK_FUNCS(getattrlist) AC_CHECK_HEADERS(copyfile.h) AC_CHECK_FUNCS(copyfile) if test $tcl_corefoundation = yes; then AC_CHECK_HEADERS(libkern/OSAtomic.h) AC_CHECK_FUNCS(OSSpinLockLock) fi AC_DEFINE(TCL_LOAD_FROM_MEMORY, 1, [Can this platform load code from memory?]) AC_DEFINE(TCL_WIDE_CLICKS, 1, [Does this platform have wide high-resolution clicks?]) AC_CACHE_CHECK([if weak import is available], tcl_cv_cc_weak_import, [ hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Werror" AC_LINK_IFELSE([AC_LANG_PROGRAM([[ int rand(void) __attribute__((weak_import)); ]], [[rand();]])], [tcl_cv_cc_weak_import=yes],[tcl_cv_cc_weak_import=no]) CFLAGS=$hold_cflags]) if test $tcl_cv_cc_weak_import = yes; then AC_DEFINE(HAVE_WEAK_IMPORT, 1, [Is weak import available?]) fi AC_CACHE_CHECK([if Darwin SUSv3 extensions are available], tcl_cv_cc_darwin_c_source, [ hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Werror" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #define _DARWIN_C_SOURCE 1 #include ]], [[]])],[tcl_cv_cc_darwin_c_source=yes],[tcl_cv_cc_darwin_c_source=no]) CFLAGS=$hold_cflags]) if test $tcl_cv_cc_darwin_c_source = yes; then AC_DEFINE(_DARWIN_C_SOURCE, 1, [Are Darwin SUSv3 extensions available?]) fi # Build .bundle dltest binaries in addition to .dylib DLTEST_LD='${CC} -bundle -Wl,-w ${CFLAGS} ${LDFLAGS}' DLTEST_SUFFIX=".bundle" else DLTEST_LD='${SHLIB_LD}' DLTEST_SUFFIX="" fi #-------------------------------------------------------------------- # Check for support of fts functions (readdir replacement) #-------------------------------------------------------------------- AC_CACHE_CHECK([for fts], tcl_cv_api_fts, [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include #include ]], [[ char*const p[2] = {"/", NULL}; FTS *f = fts_open(p, FTS_PHYSICAL|FTS_NOCHDIR|FTS_NOSTAT, NULL); FTSENT *e = fts_read(f); fts_close(f); ]])],[tcl_cv_api_fts=yes],[tcl_cv_api_fts=no])]) if test $tcl_cv_api_fts = yes; then AC_DEFINE(HAVE_FTS, 1, [Do we have fts functions?]) fi #-------------------------------------------------------------------- # The statements below check for systems where POSIX-style non-blocking # I/O (O_NONBLOCK) doesn't work or is unimplemented. On these systems # (mostly older ones), use the old BSD-style FIONBIO approach instead. #-------------------------------------------------------------------- SC_BLOCKING_STYLE #------------------------------------------------------------------------ AC_MSG_CHECKING([whether to use dll unloading]) AC_ARG_ENABLE(dll-unloading, AS_HELP_STRING([--enable-dll-unloading], [enable the 'unload' command (default: on)]), [tcl_ok=$enableval], [tcl_ok=yes]) if test $tcl_ok = yes; then AC_DEFINE(TCL_UNLOAD_DLLS, 1, [Do we allow unloading of shared libraries?]) fi AC_MSG_RESULT([$tcl_ok]) #------------------------------------------------------------------------ # Check whether the timezone data is supplied by the OS or has # to be installed by Tcl. The default is autodetection, but can # be overridden on the configure command line either way. #------------------------------------------------------------------------ AC_MSG_CHECKING([for timezone data]) AC_ARG_WITH(tzdata, AS_HELP_STRING([--with-tzdata], [install timezone data (default: autodetect)]), [tcl_ok=$withval], [tcl_ok=auto]) # # Any directories that get added here must also be added to the # search path in ::tcl::clock::Initialize (library/clock.tcl). # case $tcl_ok in no) AC_MSG_RESULT([supplied by OS vendor]) ;; yes) # nothing to do here ;; auto*) AC_CACHE_VAL([tcl_cv_dir_zoneinfo], [ for dir in /usr/share/zoneinfo \ /usr/share/lib/zoneinfo \ /usr/lib/zoneinfo do if test -f $dir/UTC -o -f $dir/GMT then tcl_cv_dir_zoneinfo="$dir" break fi done]) if test -n "$tcl_cv_dir_zoneinfo"; then tcl_ok=no AC_MSG_RESULT([$dir]) else tcl_ok=yes fi ;; *) AC_MSG_ERROR([invalid argument: $tcl_ok]) ;; esac if test $tcl_ok = yes then AC_MSG_RESULT([supplied by Tcl]) INSTALL_TZDATA=install-tzdata fi #-------------------------------------------------------------------- # DTrace support #-------------------------------------------------------------------- AC_ARG_ENABLE(dtrace, AS_HELP_STRING([--enable-dtrace], [build with DTrace support (default: off)]), [tcl_ok=$enableval], [tcl_ok=no]) if test $tcl_ok = yes; then AC_CHECK_HEADER(sys/sdt.h, [tcl_ok=yes], [tcl_ok=no]) fi if test $tcl_ok = yes; then AC_PATH_PROG(DTRACE, dtrace,, [$PATH:/usr/sbin]) test -z "$ac_cv_path_DTRACE" && tcl_ok=no fi AC_MSG_CHECKING([whether to enable DTrace support]) MAKEFILE_SHELL='/bin/sh' if test $tcl_ok = yes; then AC_DEFINE(USE_DTRACE, 1, [Are we building with DTrace support?]) DTRACE_SRC="\${DTRACE_SRC}" DTRACE_HDR="\${DTRACE_HDR}" if test "`uname -s`" != "Darwin" ; then DTRACE_OBJ="\${DTRACE_OBJ}" if test "`uname -s`" = "SunOS" -a "$SHARED_BUILD" = "0" ; then # Need to create an intermediate object file to ensure tclDTrace.o # gets included when linking against the static tcl library. STLIB_LD='stlib_ld () { /usr/ccs/bin/ld -r -o $${1%.a}.o "$${@:2}" && '"${STLIB_LD}"' $${1} $${1%.a}.o ; } && stlib_ld' MAKEFILE_SHELL='/bin/bash' # Force use of Sun ar and ranlib, the GNU versions choke on # tclDTrace.o and the combined object file above. AR='/usr/ccs/bin/ar' RANLIB='/usr/ccs/bin/ranlib' fi fi fi AC_MSG_RESULT([$tcl_ok]) #-------------------------------------------------------------------- # The check below checks whether the cpuid instruction is usable. #-------------------------------------------------------------------- AC_CACHE_CHECK([whether the cpuid instruction is usable], tcl_cv_cpuid, [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[ int index,regsPtr[4]; __asm__ __volatile__("mov %%ebx, %%edi \n\t" "cpuid \n\t" "mov %%ebx, %%esi \n\t" "mov %%edi, %%ebx \n\t" : "=a"(regsPtr[0]), "=S"(regsPtr[1]), "=c"(regsPtr[2]), "=d"(regsPtr[3]) : "a"(index) : "edi"); ]])],[tcl_cv_cpuid=yes],[tcl_cv_cpuid=no])]) if test $tcl_cv_cpuid = yes; then AC_DEFINE(HAVE_CPUID, 1, [Is the cpuid instruction usable?]) fi #-------------------------------------------------------------------- # The statements below define a collection of symbols related to # building libtcl as a shared library instead of a static library. #-------------------------------------------------------------------- TCL_UNSHARED_LIB_SUFFIX=${UNSHARED_LIB_SUFFIX} TCL_SHARED_LIB_SUFFIX=${SHARED_LIB_SUFFIX} eval "TCL_LIB_FILE=libtcl${LIB_SUFFIX}" # tclConfig.sh needs a version of the _LIB_SUFFIX that has been eval'ed # since on some platforms TCL_LIB_FILE contains shell escapes. # (See also: TCL_TRIM_DOTS). eval "TCL_LIB_FILE=${TCL_LIB_FILE}" test -z "$TCL_LIBRARY" && TCL_LIBRARY='$(prefix)/lib/tcl$(VERSION)' PRIVATE_INCLUDE_DIR='$(includedir)' HTML_DIR='$(DISTDIR)/html' # Note: in the following variable, it's important to use the absolute # path name of the Tcl directory rather than "..": this is because # AIX remembers this path and will attempt to use it at run-time to look # up the Tcl library. if test "`uname -s`" = "Darwin" ; then SC_ENABLE_FRAMEWORK TCL_SHLIB_LD_EXTRAS="-compatibility_version ${TCL_VERSION} -current_version ${TCL_VERSION}`echo ${TCL_PATCH_LEVEL} | awk ['{match($0, "\\\.[0-9]+"); print substr($0,RSTART,RLENGTH)}']`" TCL_SHLIB_LD_EXTRAS="${TCL_SHLIB_LD_EXTRAS}"' -install_name "${DYLIB_INSTALL_DIR}"/${TCL_LIB_FILE}' echo "$LDFLAGS " | grep -q -- '-prebind ' && TCL_SHLIB_LD_EXTRAS="${TCL_SHLIB_LD_EXTRAS}"' -seg1addr 0xA000000' TCL_SHLIB_LD_EXTRAS="${TCL_SHLIB_LD_EXTRAS}"' -sectcreate __TEXT __info_plist Tcl-Info.plist' EXTRA_TCLSH_LIBS='-sectcreate __TEXT __info_plist Tclsh-Info.plist' AC_CONFIG_FILES([Tcl-Info.plist:../macosx/Tcl-Info.plist.in Tclsh-Info.plist:../macosx/Tclsh-Info.plist.in]) TCL_YEAR="`date +%Y`" fi if test "$FRAMEWORK_BUILD" = "1" ; then AC_DEFINE(TCL_FRAMEWORK, 1, [Is Tcl built as a framework?]) # Construct a fake local framework structure to make linking with # '-framework Tcl' and running of tcltest work AC_CONFIG_COMMANDS([Tcl.framework], [n=Tcl && f=$n.framework && v=Versions/$VERSION && rm -rf $f && mkdir -p $f/$v/Resources && ln -s $v/$n $v/Resources $f && ln -s ../../../$n $f/$v && ln -s ../../../../$n-Info.plist $f/$v/Resources/Info.plist && unset n f v ], VERSION=${TCL_VERSION}) LD_LIBRARY_PATH_VAR="DYLD_FRAMEWORK_PATH" # default install directory for bundled packages if test "${libdir}" = '${exec_prefix}/lib' -o "`basename ${libdir}`" = 'Frameworks'; then PACKAGE_DIR="/Library/Tcl" else PACKAGE_DIR="$libdir" fi if test "${libdir}" = '${exec_prefix}/lib'; then # override libdir default libdir="/Library/Frameworks" fi TCL_LIB_FILE="Tcl" TCL_LIB_FLAG="-framework Tcl" TCL_BUILD_LIB_SPEC="-F`pwd | sed -e 's/ /\\\\ /g'` -framework Tcl" TCL_LIB_SPEC="-F${libdir} -framework Tcl" libdir="${libdir}/Tcl.framework/Versions/\${VERSION}" TCL_LIBRARY="${libdir}/Resources/Scripts" includedir="${libdir}/Headers" PRIVATE_INCLUDE_DIR="${libdir}/PrivateHeaders" HTML_DIR="${libdir}/Resources/Documentation/Reference/Tcl" EXTRA_INSTALL="install-private-headers html-tcl" EXTRA_BUILD_HTML='@ln -fs contents.htm "$(HTML_INSTALL_DIR)/TclTOC.html"' EXTRA_INSTALL_BINARIES='@echo "Installing Info.plist to $(LIB_INSTALL_DIR)/Resources/" && $(INSTALL_DATA_DIR) "$(LIB_INSTALL_DIR)/Resources" && $(INSTALL_DATA) Tcl-Info.plist "$(LIB_INSTALL_DIR)/Resources/Info.plist"' EXTRA_INSTALL_BINARIES="$EXTRA_INSTALL_BINARIES"' && echo "Installing license.terms to $(LIB_INSTALL_DIR)/Resources/" && $(INSTALL_DATA) "$(TOP_DIR)/license.terms" "$(LIB_INSTALL_DIR)/Resources"' EXTRA_INSTALL_BINARIES="$EXTRA_INSTALL_BINARIES"' && echo "Finalizing Tcl.framework" && rm -f "$(LIB_INSTALL_DIR)/../Current" && ln -s "$(VERSION)" "$(LIB_INSTALL_DIR)/../Current" && for f in "$(LIB_FILE)" tclConfig.sh Resources Headers PrivateHeaders; do rm -f "$(LIB_INSTALL_DIR)/../../$$f" && ln -s "Versions/Current/$$f" "$(LIB_INSTALL_DIR)/../.."; done && f="$(STUB_LIB_FILE)" && rm -f "$(LIB_INSTALL_DIR)/../../$$f" && ln -s "Versions/$(VERSION)/$$f" "$(LIB_INSTALL_DIR)/../.."' # Don't use AC_DEFINE for the following as the framework version define # needs to go into the Makefile even when using autoheader, so that we # can pick up a potential make override of VERSION. Also, don't put this # into CFLAGS as it should not go into tclConfig.sh EXTRA_CC_SWITCHES='-DTCL_FRAMEWORK_VERSION=\"$(VERSION)\"' else # libdir must be a fully qualified path and not ${exec_prefix}/lib eval libdir="$libdir" # default install directory for bundled packages PACKAGE_DIR="$libdir" if test "${TCL_LIB_VERSIONS_OK}" = "ok"; then TCL_LIB_FLAG="-ltcl${TCL_VERSION}" else TCL_LIB_FLAG="-ltcl`echo ${TCL_VERSION} | tr -d .`" fi TCL_BUILD_LIB_SPEC="-L`pwd | sed -e 's/ /\\\\ /g'` ${TCL_LIB_FLAG}" TCL_LIB_SPEC="-L${libdir} ${TCL_LIB_FLAG}" fi VERSION='${VERSION}' eval "CFG_TCL_SHARED_LIB_SUFFIX=${TCL_SHARED_LIB_SUFFIX}" eval "CFG_TCL_UNSHARED_LIB_SUFFIX=${TCL_UNSHARED_LIB_SUFFIX}" VERSION=${TCL_VERSION} #-------------------------------------------------------------------- # Zipfs support - Tip 430 #-------------------------------------------------------------------- AC_ARG_ENABLE(zipfs, AS_HELP_STRING([--enable-zipfs], [build with Zipfs support (default: on)]), [tcl_ok=$enableval], [tcl_ok=yes]) if test "$tcl_ok" = "yes" -a "x$enable_framework" != "xyes"; then # # Find a native compiler # AX_CC_FOR_BUILD # # Find a native zip implementation # SC_ZIPFS_SUPPORT ZIPFS_BUILD=1 TCL_ZIP_FILE=libtcl${TCL_MAJOR_VERSION}.${TCL_MINOR_VERSION}${TCL_PATCH_LEVEL}.zip else ZIPFS_BUILD=0 TCL_ZIP_FILE= fi # Do checking message here to not mess up interleaved configure output AC_MSG_CHECKING([for building with zipfs]) if test "${ZIPFS_BUILD}" = 1; then if test "${SHARED_BUILD}" = 0; then ZIPFS_BUILD=2; AC_DEFINE(ZIPFS_BUILD, 2, [Are we building with zipfs enabled?]) else AC_DEFINE(ZIPFS_BUILD, 1, [Are we building with zipfs enabled?])\ fi AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) INSTALL_LIBRARIES=install-libraries INSTALL_MSGS=install-msgs fi # Point to tcl script library if we are not embedding it. if test "${ZIPFS_BUILD}" = 0; then TCL_BUILDTIME_LIBRARY=${TCL_SRC_DIR}/library fi AC_SUBST(ZIPFS_BUILD) AC_SUBST(TCL_ZIP_FILE) AC_SUBST(INSTALL_LIBRARIES) AC_SUBST(INSTALL_MSGS) AC_SUBST(TCL_BUILDTIME_LIBRARY) #-------------------------------------------------------------------- # The statements below define the symbol TCL_PACKAGE_PATH, which # gives a list of directories that may contain packages. The list # consists of one directory for machine-dependent binaries and # another for platform-independent scripts. #-------------------------------------------------------------------- if test "$FRAMEWORK_BUILD" = "1" ; then test -z "$TCL_PACKAGE_PATH" && \ TCL_PACKAGE_PATH="~/Library/Tcl:/Library/Tcl:~/Library/Frameworks:/Library/Frameworks" # Allow tclsh to find Tk when multiple versions are installed. See Tk [1562e10c58]. TCL_PACKAGE_PATH="$TCL_PACKAGE_PATH:/Library/Frameworks/Tk.framework/Versions" test -z "$TCL_MODULE_PATH" && \ TCL_MODULE_PATH="~/Library/Tcl /Library/Tcl" elif test "$prefix/lib" != "$libdir"; then test -z "$TCL_PACKAGE_PATH" && TCL_PACKAGE_PATH="${libdir}:${prefix}/lib" else test -z "$TCL_PACKAGE_PATH" && TCL_PACKAGE_PATH="${prefix}/lib" fi #-------------------------------------------------------------------- # The statements below define various symbols relating to Tcl # stub support. #-------------------------------------------------------------------- # Replace ${VERSION} with contents of ${TCL_VERSION} # double-eval to account for TCL_TRIM_DOTS. # eval "TCL_STUB_LIB_FILE=libtclstub.a" eval "TCL_STUB_LIB_FILE=\"${TCL_STUB_LIB_FILE}\"" eval "TCL_STUB_LIB_DIR=\"${libdir}\"" TCL_STUB_LIB_FLAG="-ltclstub" TCL_BUILD_STUB_LIB_SPEC="-L`pwd | sed -e 's/ /\\\\ /g'` ${TCL_STUB_LIB_FLAG}" TCL_STUB_LIB_SPEC="-L${TCL_STUB_LIB_DIR} ${TCL_STUB_LIB_FLAG}" TCL_BUILD_STUB_LIB_PATH="`pwd`/${TCL_STUB_LIB_FILE}" TCL_STUB_LIB_PATH="${TCL_STUB_LIB_DIR}/${TCL_STUB_LIB_FILE}" # Install time header dir can be set via --includedir eval "TCL_INCLUDE_SPEC=\"-I${includedir}\"" #------------------------------------------------------------------------ # tclConfig.sh refers to this by a different name #------------------------------------------------------------------------ TCL_SHARED_BUILD=${SHARED_BUILD} AC_SUBST(TCL_VERSION) AC_SUBST(TCL_MAJOR_VERSION) AC_SUBST(TCL_MINOR_VERSION) AC_SUBST(TCL_PATCH_LEVEL) AC_SUBST(TCL_YEAR) AC_SUBST(PKG_CFG_ARGS) AC_SUBST(TCL_ZIP_FILE) AC_SUBST(TCL_LIB_FILE) AC_SUBST(TCL_LIB_FLAG) AC_SUBST(TCL_LIB_SPEC) AC_SUBST(TCL_STUB_LIB_FILE) AC_SUBST(TCL_STUB_LIB_FLAG) AC_SUBST(TCL_STUB_LIB_SPEC) AC_SUBST(TCL_STUB_LIB_PATH) AC_SUBST(TCL_INCLUDE_SPEC) AC_SUBST(TCL_BUILD_STUB_LIB_SPEC) AC_SUBST(TCL_BUILD_STUB_LIB_PATH) AC_SUBST(TCL_SRC_DIR) AC_SUBST(CFG_TCL_SHARED_LIB_SUFFIX) AC_SUBST(CFG_TCL_UNSHARED_LIB_SUFFIX) AC_SUBST(TCL_SHARED_BUILD) AC_SUBST(LD_LIBRARY_PATH_VAR) AC_SUBST(TCL_BUILD_LIB_SPEC) AC_SUBST(TCL_LIB_VERSIONS_OK) AC_SUBST(TCL_SHARED_LIB_SUFFIX) AC_SUBST(TCL_UNSHARED_LIB_SUFFIX) AC_SUBST(TCL_HAS_LONGLONG) AC_SUBST(INSTALL_TZDATA) AC_SUBST(DTRACE_SRC) AC_SUBST(DTRACE_HDR) AC_SUBST(DTRACE_OBJ) AC_SUBST(MAKEFILE_SHELL) AC_SUBST(BUILD_DLTEST) AC_SUBST(TCL_PACKAGE_PATH) AC_SUBST(TCL_MODULE_PATH) AC_SUBST(TCL_LIBRARY) AC_SUBST(PRIVATE_INCLUDE_DIR) AC_SUBST(HTML_DIR) AC_SUBST(PACKAGE_DIR) AC_SUBST(EXTRA_CC_SWITCHES) AC_SUBST(EXTRA_APP_CC_SWITCHES) AC_SUBST(EXTRA_INSTALL) AC_SUBST(EXTRA_INSTALL_BINARIES) AC_SUBST(EXTRA_BUILD_HTML) AC_SUBST(EXTRA_TCLSH_LIBS) AC_SUBST(DLTEST_LD) AC_SUBST(DLTEST_SUFFIX) dnl Disable the automake-friendly normalization of LIBOBJS dnl performed by autoconf 2.53 and later. It's not correct for us. define([_AC_LIBOBJS_NORMALIZE],[]) AC_CONFIG_FILES([ Makefile:../unix/Makefile.in dltest/Makefile:../unix/dltest/Makefile.in tclConfig.sh:../unix/tclConfig.sh.in tcl.pc:../unix/tcl.pc.in ]) AC_OUTPUT dnl Local Variables: dnl mode: autoconf dnl End: tcl9.0.1/unix/tcl.m40000644000175000017500000027611314726623136013577 0ustar sergeisergei#------------------------------------------------------------------------ # SC_PATH_TCLCONFIG -- # # Locate the tclConfig.sh file and perform a sanity check on # the Tcl compile flags # # Arguments: # none # # Results: # # Adds the following arguments to configure: # --with-tcl=... # # Defines the following vars: # TCL_BIN_DIR Full path to the directory containing # the tclConfig.sh file #------------------------------------------------------------------------ AC_DEFUN([SC_PATH_TCLCONFIG], [ # # Ok, lets find the tcl configuration # First, look for one uninstalled. # the alternative search directory is invoked by --with-tcl # if test x"${no_tcl}" = x ; then # we reset no_tcl in case something fails here no_tcl=true AC_ARG_WITH(tcl, AS_HELP_STRING([--with-tcl], [directory containing tcl configuration (tclConfig.sh)]), [with_tclconfig="${withval}"]) AC_MSG_CHECKING([for Tcl configuration]) AC_CACHE_VAL(ac_cv_c_tclconfig,[ # First check to see if --with-tcl was specified. if test x"${with_tclconfig}" != x ; then case "${with_tclconfig}" in */tclConfig.sh ) if test -f "${with_tclconfig}"; then AC_MSG_WARN([--with-tcl argument should refer to directory containing tclConfig.sh, not to tclConfig.sh itself]) with_tclconfig="`echo "${with_tclconfig}" | sed 's!/tclConfig\.sh$!!'`" fi ;; esac if test -f "${with_tclconfig}/tclConfig.sh" ; then ac_cv_c_tclconfig="`(cd "${with_tclconfig}"; pwd)`" else AC_MSG_ERROR([${with_tclconfig} directory doesn't contain tclConfig.sh]) fi fi # then check for a private Tcl installation if test x"${ac_cv_c_tclconfig}" = x ; then for i in \ ../tcl \ `ls -dr ../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ `ls -dr ../tcl[[8-9]].[[0-9]] 2>/dev/null` \ `ls -dr ../tcl[[8-9]].[[0-9]]* 2>/dev/null` \ ../../tcl \ `ls -dr ../../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ `ls -dr ../../tcl[[8-9]].[[0-9]] 2>/dev/null` \ `ls -dr ../../tcl[[8-9]].[[0-9]]* 2>/dev/null` \ ../../../tcl \ `ls -dr ../../../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ `ls -dr ../../../tcl[[8-9]].[[0-9]] 2>/dev/null` \ `ls -dr ../../../tcl[[8-9]].[[0-9]]* 2>/dev/null` ; do if test -f "$i/unix/tclConfig.sh" ; then ac_cv_c_tclconfig="`(cd $i/unix; pwd)`" break fi done fi # on Darwin, check in Framework installation locations if test "`uname -s`" = "Darwin" -a x"${ac_cv_c_tclconfig}" = x ; then for i in `ls -d ~/Library/Frameworks 2>/dev/null` \ `ls -d /Library/Frameworks 2>/dev/null` \ `ls -d /Network/Library/Frameworks 2>/dev/null` \ ; do if test -f "$i/Tcl.framework/tclConfig.sh" ; then ac_cv_c_tclconfig="`(cd $i/Tcl.framework; pwd)`" break fi done fi # check in a few common install locations if test x"${ac_cv_c_tclconfig}" = x ; then for i in `ls -d ${libdir} 2>/dev/null` \ `ls -d ${exec_prefix}/lib 2>/dev/null` \ `ls -d ${prefix}/lib 2>/dev/null` \ `ls -d /usr/local/lib 2>/dev/null` \ `ls -d /usr/contrib/lib 2>/dev/null` \ `ls -d /usr/pkg/lib 2>/dev/null` \ `ls -d /usr/lib/tcl9.0 2>/dev/null` \ `ls -d /usr/lib 2>/dev/null` \ `ls -d /usr/lib64 2>/dev/null` \ `ls -d /usr/local/lib/tcl9.0 2>/dev/null` \ `ls -d /usr/local/lib/tcl/tcl9.0 2>/dev/null` \ ; do if test -f "$i/tclConfig.sh" ; then ac_cv_c_tclconfig="`(cd $i; pwd)`" break fi done fi # check in a few other private locations if test x"${ac_cv_c_tclconfig}" = x ; then for i in \ ${srcdir}/../tcl \ `ls -dr ${srcdir}/../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ `ls -dr ${srcdir}/../tcl[[8-9]].[[0-9]] 2>/dev/null` \ `ls -dr ${srcdir}/../tcl[[8-9]].[[0-9]]* 2>/dev/null` ; do if test -f "$i/unix/tclConfig.sh" ; then ac_cv_c_tclconfig="`(cd $i/unix; pwd)`" break fi done fi ]) if test x"${ac_cv_c_tclconfig}" = x ; then TCL_BIN_DIR="# no Tcl configs found" AC_MSG_ERROR([Can't find Tcl configuration definitions. Use --with-tcl to specify a directory containing tclConfig.sh]) else no_tcl= TCL_BIN_DIR="${ac_cv_c_tclconfig}" AC_MSG_RESULT([found ${TCL_BIN_DIR}/tclConfig.sh]) fi fi ]) #------------------------------------------------------------------------ # SC_PATH_TKCONFIG -- # # Locate the tkConfig.sh file # # Arguments: # none # # Results: # # Adds the following arguments to configure: # --with-tk=... # # Defines the following vars: # TK_BIN_DIR Full path to the directory containing # the tkConfig.sh file #------------------------------------------------------------------------ AC_DEFUN([SC_PATH_TKCONFIG], [ # # Ok, lets find the tk configuration # First, look for one uninstalled. # the alternative search directory is invoked by --with-tk # if test x"${no_tk}" = x ; then # we reset no_tk in case something fails here no_tk=true AC_ARG_WITH(tk, AS_HELP_STRING([--with-tk], [directory containing tk configuration (tkConfig.sh)]), [with_tkconfig="${withval}"]) AC_MSG_CHECKING([for Tk configuration]) AC_CACHE_VAL(ac_cv_c_tkconfig,[ # First check to see if --with-tkconfig was specified. if test x"${with_tkconfig}" != x ; then case "${with_tkconfig}" in */tkConfig.sh ) if test -f "${with_tkconfig}"; then AC_MSG_WARN([--with-tk argument should refer to directory containing tkConfig.sh, not to tkConfig.sh itself]) with_tkconfig="`echo "${with_tkconfig}" | sed 's!/tkConfig\.sh$!!'`" fi ;; esac if test -f "${with_tkconfig}/tkConfig.sh" ; then ac_cv_c_tkconfig="`(cd "${with_tkconfig}"; pwd)`" else AC_MSG_ERROR([${with_tkconfig} directory doesn't contain tkConfig.sh]) fi fi # then check for a private Tk library if test x"${ac_cv_c_tkconfig}" = x ; then for i in \ ../tk \ `ls -dr ../tk[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ `ls -dr ../tk[[8-9]].[[0-9]] 2>/dev/null` \ `ls -dr ../tk[[8-9]].[[0-9]]* 2>/dev/null` \ ../../tk \ `ls -dr ../../tk[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ `ls -dr ../../tk[[8-9]].[[0-9]] 2>/dev/null` \ `ls -dr ../../tk[[8-9]].[[0-9]]* 2>/dev/null` \ ../../../tk \ `ls -dr ../../../tk[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ `ls -dr ../../../tk[[8-9]].[[0-9]] 2>/dev/null` \ `ls -dr ../../../tk[[8-9]].[[0-9]]* 2>/dev/null` ; do if test -f "$i/unix/tkConfig.sh" ; then ac_cv_c_tkconfig="`(cd $i/unix; pwd)`" break fi done fi # on Darwin, check in Framework installation locations if test "`uname -s`" = "Darwin" -a x"${ac_cv_c_tkconfig}" = x ; then for i in `ls -d ~/Library/Frameworks 2>/dev/null` \ `ls -d /Library/Frameworks 2>/dev/null` \ `ls -d /Network/Library/Frameworks 2>/dev/null` \ ; do if test -f "$i/Tk.framework/tkConfig.sh" ; then ac_cv_c_tkconfig="`(cd $i/Tk.framework; pwd)`" break fi done fi # check in a few common install locations if test x"${ac_cv_c_tkconfig}" = x ; then for i in `ls -d ${libdir} 2>/dev/null` \ `ls -d ${exec_prefix}/lib 2>/dev/null` \ `ls -d ${prefix}/lib 2>/dev/null` \ `ls -d /usr/local/lib 2>/dev/null` \ `ls -d /usr/contrib/lib 2>/dev/null` \ `ls -d /usr/pkg/lib 2>/dev/null` \ `ls -d /usr/lib/tk9.0 2>/dev/null` \ `ls -d /usr/lib 2>/dev/null` \ `ls -d /usr/lib64 2>/dev/null` \ `ls -d /usr/local/lib/tk9.0 2>/dev/null` \ `ls -d /usr/local/lib/tcl/tk9.0 2>/dev/null` \ ; do if test -f "$i/tkConfig.sh" ; then ac_cv_c_tkconfig="`(cd $i; pwd)`" break fi done fi # check in a few other private locations if test x"${ac_cv_c_tkconfig}" = x ; then for i in \ ${srcdir}/../tk \ `ls -dr ${srcdir}/../tk[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ `ls -dr ${srcdir}/../tk[[8-9]].[[0-9]] 2>/dev/null` \ `ls -dr ${srcdir}/../tk[[8-9]].[[0-9]]* 2>/dev/null` ; do if test -f "$i/unix/tkConfig.sh" ; then ac_cv_c_tkconfig="`(cd $i/unix; pwd)`" break fi done fi ]) if test x"${ac_cv_c_tkconfig}" = x ; then TK_BIN_DIR="# no Tk configs found" AC_MSG_ERROR([Can't find Tk configuration definitions. Use --with-tk to specify a directory containing tkConfig.sh]) else no_tk= TK_BIN_DIR="${ac_cv_c_tkconfig}" AC_MSG_RESULT([found ${TK_BIN_DIR}/tkConfig.sh]) fi fi ]) #------------------------------------------------------------------------ # SC_LOAD_TCLCONFIG -- # # Load the tclConfig.sh file # # Arguments: # # Requires the following vars to be set: # TCL_BIN_DIR # # Results: # # Substitutes the following vars: # TCL_BIN_DIR # TCL_SRC_DIR # TCL_LIB_FILE #------------------------------------------------------------------------ AC_DEFUN([SC_LOAD_TCLCONFIG], [ AC_MSG_CHECKING([for existence of ${TCL_BIN_DIR}/tclConfig.sh]) if test -f "${TCL_BIN_DIR}/tclConfig.sh" ; then AC_MSG_RESULT([loading]) . "${TCL_BIN_DIR}/tclConfig.sh" else AC_MSG_RESULT([could not find ${TCL_BIN_DIR}/tclConfig.sh]) fi # If the TCL_BIN_DIR is the build directory (not the install directory), # then set the common variable name to the value of the build variables. # For example, the variable TCL_LIB_SPEC will be set to the value # of TCL_BUILD_LIB_SPEC. An extension should make use of TCL_LIB_SPEC # instead of TCL_BUILD_LIB_SPEC since it will work with both an # installed and uninstalled version of Tcl. if test -f "${TCL_BIN_DIR}/Makefile" ; then TCL_LIB_SPEC="${TCL_BUILD_LIB_SPEC}" TCL_STUB_LIB_SPEC="${TCL_BUILD_STUB_LIB_SPEC}" TCL_STUB_LIB_PATH="${TCL_BUILD_STUB_LIB_PATH}" elif test "`uname -s`" = "Darwin"; then # If Tcl was built as a framework, attempt to use the libraries # from the framework at the given location so that linking works # against Tcl.framework installed in an arbitrary location. case ${TCL_DEFS} in *TCL_FRAMEWORK*) if test -f "${TCL_BIN_DIR}/${TCL_LIB_FILE}"; then for i in "`cd "${TCL_BIN_DIR}"; pwd`" \ "`cd "${TCL_BIN_DIR}"/../..; pwd`"; do if test "`basename "$i"`" = "${TCL_LIB_FILE}.framework"; then TCL_LIB_SPEC="-F`dirname "$i" | sed -e 's/ /\\\\ /g'` -framework ${TCL_LIB_FILE}" break fi done fi if test -f "${TCL_BIN_DIR}/${TCL_STUB_LIB_FILE}"; then TCL_STUB_LIB_SPEC="-L`echo "${TCL_BIN_DIR}" | sed -e 's/ /\\\\ /g'` ${TCL_STUB_LIB_FLAG}" TCL_STUB_LIB_PATH="${TCL_BIN_DIR}/${TCL_STUB_LIB_FILE}" fi ;; esac fi AC_SUBST(TCL_VERSION) AC_SUBST(TCL_PATCH_LEVEL) AC_SUBST(TCL_BIN_DIR) AC_SUBST(TCL_SRC_DIR) AC_SUBST(TCL_LIB_FILE) AC_SUBST(TCL_LIB_FLAG) AC_SUBST(TCL_LIB_SPEC) AC_SUBST(TCL_STUB_LIB_FILE) AC_SUBST(TCL_STUB_LIB_FLAG) AC_SUBST(TCL_STUB_LIB_SPEC) ]) #------------------------------------------------------------------------ # SC_LOAD_TKCONFIG -- # # Load the tkConfig.sh file # # Arguments: # # Requires the following vars to be set: # TK_BIN_DIR # # Results: # # Sets the following vars that should be in tkConfig.sh: # TK_BIN_DIR #------------------------------------------------------------------------ AC_DEFUN([SC_LOAD_TKCONFIG], [ AC_MSG_CHECKING([for existence of ${TK_BIN_DIR}/tkConfig.sh]) if test -f "${TK_BIN_DIR}/tkConfig.sh" ; then AC_MSG_RESULT([loading]) . "${TK_BIN_DIR}/tkConfig.sh" else AC_MSG_RESULT([could not find ${TK_BIN_DIR}/tkConfig.sh]) fi # If the TK_BIN_DIR is the build directory (not the install directory), # then set the common variable name to the value of the build variables. # For example, the variable TK_LIB_SPEC will be set to the value # of TK_BUILD_LIB_SPEC. An extension should make use of TK_LIB_SPEC # instead of TK_BUILD_LIB_SPEC since it will work with both an # installed and uninstalled version of Tcl. if test -f "${TK_BIN_DIR}/Makefile" ; then TK_LIB_SPEC="${TK_BUILD_LIB_SPEC}" TK_STUB_LIB_SPEC="${TK_BUILD_STUB_LIB_SPEC}" TK_STUB_LIB_PATH="${TK_BUILD_STUB_LIB_PATH}" elif test "`uname -s`" = "Darwin"; then # If Tk was built as a framework, attempt to use the libraries # from the framework at the given location so that linking works # against Tk.framework installed in an arbitrary location. case ${TK_DEFS} in *TK_FRAMEWORK*) if test -f "${TK_BIN_DIR}/${TK_LIB_FILE}"; then for i in "`cd "${TK_BIN_DIR}"; pwd`" \ "`cd "${TK_BIN_DIR}"/../..; pwd`"; do if test "`basename "$i"`" = "${TK_LIB_FILE}.framework"; then TK_LIB_SPEC="-F`dirname "$i" | sed -e 's/ /\\\\ /g'` -framework ${TK_LIB_FILE}" break fi done fi if test -f "${TK_BIN_DIR}/${TK_STUB_LIB_FILE}"; then TK_STUB_LIB_SPEC="-L` echo "${TK_BIN_DIR}" | sed -e 's/ /\\\\ /g'` ${TK_STUB_LIB_FLAG}" TK_STUB_LIB_PATH="${TK_BIN_DIR}/${TK_STUB_LIB_FILE}" fi ;; esac fi AC_SUBST(TK_VERSION) AC_SUBST(TK_BIN_DIR) AC_SUBST(TK_SRC_DIR) AC_SUBST(TK_LIB_FILE) AC_SUBST(TK_LIB_FLAG) AC_SUBST(TK_LIB_SPEC) AC_SUBST(TK_STUB_LIB_FILE) AC_SUBST(TK_STUB_LIB_FLAG) AC_SUBST(TK_STUB_LIB_SPEC) ]) #------------------------------------------------------------------------ # SC_PROG_TCLSH # Locate a tclsh shell installed on the system path. This macro # will only find a Tcl shell that already exists on the system. # It will not find a Tcl shell in the Tcl build directory or # a Tcl shell that has been installed from the Tcl build directory. # If a Tcl shell can't be located on the PATH, then TCLSH_PROG will # be set to "". Extensions should take care not to create Makefile # rules that are run by default and depend on TCLSH_PROG. An # extension can't assume that an executable Tcl shell exists at # build time. # # Arguments: # none # # Results: # Substitutes the following vars: # TCLSH_PROG #------------------------------------------------------------------------ AC_DEFUN([SC_PROG_TCLSH], [ AC_MSG_CHECKING([for tclsh]) AC_CACHE_VAL(ac_cv_path_tclsh, [ search_path=`echo ${PATH} | sed -e 's/:/ /g'` for dir in $search_path ; do for j in `ls -r $dir/tclsh[[8-9]]* 2> /dev/null` \ `ls -r $dir/tclsh* 2> /dev/null` ; do if test x"$ac_cv_path_tclsh" = x ; then if test -f "$j" ; then ac_cv_path_tclsh=$j break fi fi done done ]) if test -f "$ac_cv_path_tclsh" ; then TCLSH_PROG="$ac_cv_path_tclsh" AC_MSG_RESULT([$TCLSH_PROG]) else # It is not an error if an installed version of Tcl can't be located. TCLSH_PROG="" AC_MSG_RESULT([No tclsh found on PATH]) fi AC_SUBST(TCLSH_PROG) ]) #------------------------------------------------------------------------ # SC_BUILD_TCLSH # Determine the fully qualified path name of the tclsh executable # in the Tcl build directory. This macro will correctly determine # the name of the tclsh executable even if tclsh has not yet # been built in the build directory. The build tclsh must be used # when running tests from an extension build directory. It is not # correct to use the TCLSH_PROG in cases like this. # # Arguments: # none # # Results: # Substitutes the following values: # BUILD_TCLSH #------------------------------------------------------------------------ AC_DEFUN([SC_BUILD_TCLSH], [ AC_MSG_CHECKING([for tclsh in Tcl build directory]) BUILD_TCLSH="${TCL_BIN_DIR}"/tclsh AC_MSG_RESULT([$BUILD_TCLSH]) AC_SUBST(BUILD_TCLSH) ]) #------------------------------------------------------------------------ # SC_ENABLE_SHARED -- # # Allows the building of shared libraries # # Arguments: # none # # Results: # # Adds the following arguments to configure: # --enable-shared=yes|no # # Defines the following vars: # STATIC_BUILD Used for building import/export libraries # on Windows. # # Sets the following vars: # SHARED_BUILD Value of 1 or 0 #------------------------------------------------------------------------ AC_DEFUN([SC_ENABLE_SHARED], [ AC_MSG_CHECKING([how to build libraries]) AC_ARG_ENABLE(shared, AS_HELP_STRING([--enable-shared], [build and link with shared libraries (default: on)]), [tcl_ok=$enableval], [tcl_ok=yes]) if test "$tcl_ok" = "yes" ; then AC_MSG_RESULT([shared]) SHARED_BUILD=1 else AC_MSG_RESULT([static]) SHARED_BUILD=0 AC_DEFINE(STATIC_BUILD, 1, [Is this a static build?]) fi AC_SUBST(SHARED_BUILD) ]) #------------------------------------------------------------------------ # SC_ENABLE_FRAMEWORK -- # # Allows the building of shared libraries into frameworks # # Arguments: # none # # Results: # # Adds the following arguments to configure: # --enable-framework=yes|no # # Sets the following vars: # FRAMEWORK_BUILD Value of 1 or 0 #------------------------------------------------------------------------ AC_DEFUN([SC_ENABLE_FRAMEWORK], [ if test "`uname -s`" = "Darwin" ; then AC_MSG_CHECKING([how to package libraries]) AC_ARG_ENABLE(framework, AS_HELP_STRING([--enable-framework], [package shared libraries in MacOSX frameworks (default: off)]), [enable_framework=$enableval], [enable_framework=no]) if test $enable_framework = yes; then if test $SHARED_BUILD = 0; then AC_MSG_WARN([Frameworks can only be built if --enable-shared is yes]) enable_framework=no fi if test $tcl_corefoundation = no; then AC_MSG_WARN([Frameworks can only be used when CoreFoundation is available]) enable_framework=no fi fi if test $enable_framework = yes; then AC_MSG_RESULT([framework]) FRAMEWORK_BUILD=1 else if test $SHARED_BUILD = 1; then AC_MSG_RESULT([shared library]) else AC_MSG_RESULT([static library]) fi FRAMEWORK_BUILD=0 fi fi ]) #------------------------------------------------------------------------ # SC_ENABLE_SYMBOLS -- # # Specify if debugging symbols should be used. # Memory (TCL_MEM_DEBUG) and compile (TCL_COMPILE_DEBUG) debugging # can also be enabled. # # Arguments: # none # # Requires the following vars to be set in the Makefile: # CFLAGS_DEBUG # CFLAGS_OPTIMIZE # LDFLAGS_DEBUG # LDFLAGS_OPTIMIZE # # Results: # # Adds the following arguments to configure: # --enable-symbols # # Defines the following vars: # CFLAGS_DEFAULT Sets to $(CFLAGS_DEBUG) if true # Sets to $(CFLAGS_OPTIMIZE) if false # LDFLAGS_DEFAULT Sets to $(LDFLAGS_DEBUG) if true # Sets to $(LDFLAGS_OPTIMIZE) if false #------------------------------------------------------------------------ AC_DEFUN([SC_ENABLE_SYMBOLS], [ AC_MSG_CHECKING([for build with symbols]) AC_ARG_ENABLE(symbols, AS_HELP_STRING([--enable-symbols], [build with debugging symbols (default: off)]), [tcl_ok=$enableval], [tcl_ok=no]) # FIXME: Currently, LDFLAGS_DEFAULT is not used, it should work like CFLAGS_DEFAULT. if test "$tcl_ok" = "no"; then CFLAGS_DEFAULT='$(CFLAGS_OPTIMIZE)' LDFLAGS_DEFAULT='$(LDFLAGS_OPTIMIZE)' AC_DEFINE(NDEBUG, 1, [Is no debugging enabled?]) AC_MSG_RESULT([no]) AC_DEFINE(TCL_CFG_OPTIMIZED, 1, [Is this an optimized build?]) else CFLAGS_DEFAULT='$(CFLAGS_DEBUG)' LDFLAGS_DEFAULT='$(LDFLAGS_DEBUG)' if test "$tcl_ok" = "yes"; then AC_MSG_RESULT([yes (standard debugging)]) fi fi AC_SUBST(CFLAGS_DEFAULT) AC_SUBST(LDFLAGS_DEFAULT) if test "$tcl_ok" = "mem" -o "$tcl_ok" = "all"; then AC_DEFINE(TCL_MEM_DEBUG, 1, [Is memory debugging enabled?]) fi ifelse($1,bccdebug,dnl Only enable 'compile' for the Tcl core itself if test "$tcl_ok" = "compile" -o "$tcl_ok" = "all"; then AC_DEFINE(TCL_COMPILE_DEBUG, 1, [Is bytecode debugging enabled?]) AC_DEFINE(TCL_COMPILE_STATS, 1, [Are bytecode statistics enabled?]) fi) if test "$tcl_ok" != "yes" -a "$tcl_ok" != "no"; then if test "$tcl_ok" = "all"; then AC_MSG_RESULT([enabled symbols mem ]ifelse($1,bccdebug,[compile ])[debugging]) else AC_MSG_RESULT([enabled $tcl_ok debugging]) fi fi ]) #------------------------------------------------------------------------ # SC_ENABLE_LANGINFO -- # # Allows use of modern nl_langinfo check for better l10n. # This is only relevant for Unix. # # Arguments: # none # # Results: # # Adds the following arguments to configure: # --enable-langinfo=yes|no (default is yes) # # Defines the following vars: # HAVE_LANGINFO Triggers use of nl_langinfo if defined. #------------------------------------------------------------------------ AC_DEFUN([SC_ENABLE_LANGINFO], [ AC_ARG_ENABLE(langinfo, AS_HELP_STRING([--enable-langinfo], [use nl_langinfo if possible to determine encoding at startup, otherwise use old heuristic (default: on)]), [langinfo_ok=$enableval], [langinfo_ok=yes]) HAVE_LANGINFO=0 if test "$langinfo_ok" = "yes"; then AC_CHECK_HEADER(langinfo.h,[langinfo_ok=yes],[langinfo_ok=no]) fi AC_MSG_CHECKING([whether to use nl_langinfo]) if test "$langinfo_ok" = "yes"; then AC_CACHE_VAL(tcl_cv_langinfo_h, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[nl_langinfo(CODESET);]])], [tcl_cv_langinfo_h=yes], [tcl_cv_langinfo_h=no])]) AC_MSG_RESULT([$tcl_cv_langinfo_h]) if test $tcl_cv_langinfo_h = yes; then AC_DEFINE(HAVE_LANGINFO, 1, [Do we have nl_langinfo()?]) fi else AC_MSG_RESULT([$langinfo_ok]) fi ]) #-------------------------------------------------------------------- # SC_CONFIG_MANPAGES # # Decide whether to use symlinks for linking the manpages, # whether to compress the manpages after installation, and # whether to add a package name suffix to the installed # manpages to avoidfile name clashes. # If compression is enabled also find out what file name suffix # the given compression program is using. # # Arguments: # none # # Results: # # Adds the following arguments to configure: # --enable-man-symlinks # --enable-man-compression=PROG # --enable-man-suffix[=STRING] # # Defines the following variable: # # MAN_FLAGS - The apropriate flags for installManPage # according to the user's selection. # #-------------------------------------------------------------------- AC_DEFUN([SC_CONFIG_MANPAGES], [ AC_MSG_CHECKING([whether to use symlinks for manpages]) AC_ARG_ENABLE(man-symlinks, AS_HELP_STRING([--enable-man-symlinks], [use symlinks for the manpages (default: off)]), [test "$enableval" != "no" && MAN_FLAGS="$MAN_FLAGS --symlinks"], [enableval="no"]) AC_MSG_RESULT([$enableval]) AC_MSG_CHECKING([whether to compress the manpages]) AC_ARG_ENABLE(man-compression, AS_HELP_STRING([--enable-man-compression=PROG], [compress the manpages with PROG (default: off)]), [case $enableval in yes) AC_MSG_ERROR([missing argument to --enable-man-compression]);; no) ;; *) MAN_FLAGS="$MAN_FLAGS --compress $enableval";; esac], [enableval="no"]) AC_MSG_RESULT([$enableval]) if test "$enableval" != "no"; then AC_MSG_CHECKING([for compressed file suffix]) touch TeST $enableval TeST Z=`ls TeST* | sed 's/^....//'` rm -f TeST* MAN_FLAGS="$MAN_FLAGS --extension $Z" AC_MSG_RESULT([$Z]) fi AC_MSG_CHECKING([whether to add a package name suffix for the manpages]) AC_ARG_ENABLE(man-suffix, AS_HELP_STRING([--enable-man-suffix=STRING], [use STRING as a suffix to manpage file names (default: no, AC_PACKAGE_NAME if enabled without specifying STRING)]), [case $enableval in yes) enableval="AC_PACKAGE_NAME" MAN_FLAGS="$MAN_FLAGS --suffix $enableval";; no) ;; *) MAN_FLAGS="$MAN_FLAGS --suffix $enableval";; esac], [enableval="no"]) AC_MSG_RESULT([$enableval]) AC_SUBST(MAN_FLAGS) ]) #-------------------------------------------------------------------- # SC_CONFIG_SYSTEM # # Determine what the system is (some things cannot be easily checked # on a feature-driven basis, alas). This can usually be done via the # "uname" command. # # Arguments: # none # # Results: # Defines the following var: # # system - System/platform/version identification code. # #-------------------------------------------------------------------- AC_DEFUN([SC_CONFIG_SYSTEM], [ AC_CACHE_CHECK([system version], tcl_cv_sys_version, [ if test "${TEA_PLATFORM}" = "windows" ; then tcl_cv_sys_version=windows else tcl_cv_sys_version=`uname -s`-`uname -r` if test "$?" -ne 0 ; then AC_MSG_WARN([can't find uname command]) tcl_cv_sys_version=unknown else if test "`uname -s`" = "AIX" ; then tcl_cv_sys_version=AIX-`uname -v`.`uname -r` fi if test "`uname -s`" = "NetBSD" -a -f /etc/debian_version ; then tcl_cv_sys_version=NetBSD-Debian fi fi fi ]) system=$tcl_cv_sys_version ]) #-------------------------------------------------------------------- # SC_CONFIG_CFLAGS # # Try to determine the proper flags to pass to the compiler # for building shared libraries and other such nonsense. # # Arguments: # none # # Results: # # Defines and substitutes the following vars: # # DL_OBJS - Name of the object file that implements dynamic # loading for Tcl on this system. # DL_LIBS - Library file(s) to include in tclsh and other base # applications in order for the "load" command to work. # LDFLAGS - Flags to pass to the compiler when linking object # files into an executable application binary such # as tclsh. # LD_SEARCH_FLAGS-Flags to pass to ld, such as "-R /usr/local/tcl/lib", # that tell the run-time dynamic linker where to look # for shared libraries such as libtcl.so. Depends on # the variable LIB_RUNTIME_DIR in the Makefile. Could # be the same as CC_SEARCH_FLAGS if ${CC} is used to link. # CC_SEARCH_FLAGS-Flags to pass to ${CC}, such as "-Wl,-rpath,/usr/local/tcl/lib", # that tell the run-time dynamic linker where to look # for shared libraries such as libtcl.so. Depends on # the variable LIB_RUNTIME_DIR in the Makefile. # MAKE_LIB - Command to execute to build the a library; # differs when building shared or static. # MAKE_STUB_LIB - # Command to execute to build a stub library. # INSTALL_LIB - Command to execute to install a library; # differs when building shared or static. # INSTALL_STUB_LIB - # Command to execute to install a stub library. # STLIB_LD - Base command to use for combining object files # into a static library. # SHLIB_CFLAGS - Flags to pass to cc when compiling the components # of a shared library (may request position-independent # code, among other things). # SHLIB_LD - Base command to use for combining object files # into a shared library. # SHLIB_LD_LIBS - Dependent libraries for the linker to scan when # creating shared libraries. This symbol typically # goes at the end of the "ld" commands that build # shared libraries. The value of the symbol defaults to # "${LIBS}" if all of the dependent libraries should # be specified when creating a shared library. If # dependent libraries should not be specified (as on some # SunOS systems, where they cause the link to fail, or in # general if Tcl and Tk aren't themselves shared # libraries), then this symbol has an empty string # as its value. # SHLIB_SUFFIX - Suffix to use for the names of dynamically loadable # extensions. An empty string means we don't know how # to use shared libraries on this platform. # TCL_SHLIB_LD_EXTRAS - Additional element which are added to SHLIB_LD_LIBS # TK_SHLIB_LD_EXTRAS for the build of Tcl and Tk, but not recorded in the # tclConfig.sh, since they are only used for the build # of Tcl and Tk. # Examples: MacOS X records the library version and # compatibility version in the shared library. But # of course the Tcl version of this is only used for Tcl. # LIB_SUFFIX - Specifies everything that comes after the "libfoo" # in a static or shared library name, using the $VERSION variable # to put the version in the right place. This is used # by platforms that need non-standard library names. # Examples: ${VERSION}.so.1.1 on NetBSD, since it needs # to have a version after the .so, and ${VERSION}.a # on AIX, since a shared library needs to have # a .a extension whereas shared objects for loadable # extensions have a .so extension. Defaults to # ${VERSION}${SHLIB_SUFFIX}. # TCL_LIBS - # Libs to use when linking Tcl shell or some other # shell that includes Tcl libs. # CFLAGS_DEBUG - # Flags used when running the compiler in debug mode # CFLAGS_OPTIMIZE - # Flags used when running the compiler in optimize mode # CFLAGS - Additional CFLAGS added as necessary (usually 64-bit) # #-------------------------------------------------------------------- AC_DEFUN([SC_CONFIG_CFLAGS], [ # Step 0.a: Enable 64 bit support? AC_MSG_CHECKING([if 64bit support is requested]) AC_ARG_ENABLE(64bit, AS_HELP_STRING([--enable-64bit], [enable 64bit support (default: off)]), [do64bit=$enableval], [do64bit=no]) AC_MSG_RESULT([$do64bit]) # Step 0.b: Enable Solaris 64 bit VIS support? AC_MSG_CHECKING([if 64bit Sparc VIS support is requested]) AC_ARG_ENABLE(64bit-vis, AS_HELP_STRING([--enable-64bit-vis], [enable 64bit Sparc VIS support (default: off)]), [do64bitVIS=$enableval], [do64bitVIS=no]) AC_MSG_RESULT([$do64bitVIS]) # Force 64bit on with VIS AS_IF([test "$do64bitVIS" = "yes"], [do64bit=yes]) # Step 0.c: Check if visibility support is available. Do this here so # that platform specific alternatives can be used below if this fails. AC_CACHE_CHECK([if compiler supports visibility "hidden"], tcl_cv_cc_visibility_hidden, [ hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Werror" AC_LINK_IFELSE([AC_LANG_PROGRAM([[ extern __attribute__((__visibility__("hidden"))) void f(void); void f(void) {}]], [[f();]])], [tcl_cv_cc_visibility_hidden=yes], [tcl_cv_cc_visibility_hidden=no]) CFLAGS=$hold_cflags]) AS_IF([test $tcl_cv_cc_visibility_hidden = yes], [ AC_DEFINE(MODULE_SCOPE, [extern __attribute__((__visibility__("hidden")))], [Compiler support for module scope symbols]) AC_DEFINE(HAVE_HIDDEN, [1], [Compiler support for module scope symbols]) ]) # Step 0.d: Disable -rpath support? AC_MSG_CHECKING([if rpath support is requested]) AC_ARG_ENABLE(rpath, AS_HELP_STRING([--disable-rpath], [disable rpath support (default: on)]), [doRpath=$enableval], [doRpath=yes]) AC_MSG_RESULT([$doRpath]) # Step 1: set the variable "system" to hold the name and version number # for the system. SC_CONFIG_SYSTEM # Step 2: check for existence of -ldl library. This is needed because # Linux can use either -ldl or -ldld for dynamic loading. AC_CHECK_LIB(dl, dlopen, have_dl=yes, have_dl=no) # Require ranlib early so we can override it in special cases below. AC_REQUIRE([AC_PROG_RANLIB]) # Step 3: set configuration options based on system name and version. do64bit_ok=no # default to '{$LIBS}' and set to "" on per-platform necessary basis SHLIB_LD_LIBS='${LIBS}' LDFLAGS_ORIG="$LDFLAGS" # When ld needs options to work in 64-bit mode, put them in # LDFLAGS_ARCH so they eventually end up in LDFLAGS even if [load] # is disabled by the user. [Bug 1016796] LDFLAGS_ARCH="" UNSHARED_LIB_SUFFIX="" TCL_TRIM_DOTS='`echo ${VERSION} | tr -d .`' ECHO_VERSION='`echo ${VERSION}`' TCL_LIB_VERSIONS_OK=ok CFLAGS_DEBUG=-g AS_IF([test "$GCC" = yes], [ CFLAGS_OPTIMIZE=-O2 CFLAGS_WARNING="-Wall -Wextra -Wshadow -Wundef -Wwrite-strings -Wpointer-arith" case "${CC}" in *++|*++-*) ;; *) CFLAGS_WARNING="${CFLAGS_WARNING} -Wc++-compat -fextended-identifiers" ;; esac ], [ CFLAGS_OPTIMIZE=-O CFLAGS_WARNING="" ]) AC_CHECK_TOOL(AR, ar) STLIB_LD='${AR} cr' LD_LIBRARY_PATH_VAR="LD_LIBRARY_PATH" PLAT_OBJS="" PLAT_SRCS="" LDAIX_SRC="" AS_IF([test "x${SHLIB_VERSION}" = x], [SHLIB_VERSION="1.0"]) case $system in AIX-*) AS_IF([test "$GCC" != "yes"], [ # AIX requires the _r compiler when gcc isn't being used case "${CC}" in *_r|*_r\ *) # ok ... ;; *) # Make sure only first arg gets _r CC=`echo "$CC" | sed -e 's/^\([[^ ]]*\)/\1_r/'` ;; esac AC_MSG_RESULT([Using $CC for compiling with threads]) ]) LIBS="$LIBS -lc" SHLIB_CFLAGS="" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" LD_LIBRARY_PATH_VAR="LIBPATH" # ldAix No longer needed with use of -bexpall/-brtl # but some extensions may still reference it LDAIX_SRC='$(UNIX_DIR)/ldAix' # Check to enable 64-bit flags for compiler/linker AS_IF([test "$do64bit" = yes], [ AS_IF([test "$GCC" = yes], [ AC_MSG_WARN([64bit mode not supported with GCC on $system]) ], [ do64bit_ok=yes CFLAGS="$CFLAGS -q64" LDFLAGS_ARCH="-q64" RANLIB="${RANLIB} -X64" AR="${AR} -X64" SHLIB_LD_FLAGS="-b64" ]) ]) AS_IF([test "`uname -m`" = ia64], [ # AIX-5 uses ELF style dynamic libraries on IA-64, but not PPC SHLIB_LD="/usr/ccs/bin/ld -G -z text" # AIX-5 has dl* in libc.so DL_LIBS="" AS_IF([test "$GCC" = yes], [ CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' ], [ CC_SEARCH_FLAGS='-R${LIB_RUNTIME_DIR}' ]) LD_SEARCH_FLAGS='-R ${LIB_RUNTIME_DIR}' ], [ AS_IF([test "$GCC" = yes], [ SHLIB_LD='${CC} -shared -Wl,-bexpall' ], [ SHLIB_LD="/bin/ld -bhalt:4 -bM:SRE -bexpall -H512 -T512 -bnoentry" LDFLAGS="$LDFLAGS -brtl" ]) SHLIB_LD="${SHLIB_LD} ${SHLIB_LD_FLAGS}" DL_LIBS="-ldl" CC_SEARCH_FLAGS='-L${LIB_RUNTIME_DIR}' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} ]) ;; BeOS*) SHLIB_CFLAGS="-fPIC" SHLIB_LD='${CC} -nostart' SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" #----------------------------------------------------------- # Check for inet_ntoa in -lbind, for BeOS (which also needs # -lsocket, even if the network functions are in -lnet which # is always linked to, for compatibility. #----------------------------------------------------------- AC_CHECK_LIB(bind, inet_ntoa, [LIBS="$LIBS -lbind -lsocket"]) ;; BSD/OS-2.1*|BSD/OS-3*) SHLIB_CFLAGS="" SHLIB_LD="shlicc -r" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" ;; BSD/OS-4.*) SHLIB_CFLAGS="-export-dynamic -fPIC" SHLIB_LD='${CC} -shared' SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" LDFLAGS="$LDFLAGS -export-dynamic" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" ;; CYGWIN_*|MINGW32_*|MSYS_*) SHLIB_CFLAGS="-fno-common" SHLIB_LD='${CC} -shared' SHLIB_SUFFIX=".dll" DL_OBJS="tclLoadDl.o" PLAT_OBJS='${CYGWIN_OBJS}' PLAT_SRCS='${CYGWIN_SRCS}' DL_LIBS="-ldl" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" AC_CACHE_CHECK(for Cygwin version of gcc, ac_cv_cygwin, AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #ifdef __CYGWIN__ #error cygwin #endif ]], [[]])], [ac_cv_cygwin=no], [ac_cv_cygwin=yes]) ) if test "$ac_cv_cygwin" = "no"; then AC_MSG_ERROR([${CC} is not a cygwin compiler.]) fi do64bit_ok=yes if test "x${SHARED_BUILD}" = "x1"; then echo "running cd ../win; ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args --enable-64bit --host=x86_64-w64-mingw32" # The eval makes quoting arguments work. if cd ../win; eval ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args --enable-64bit --host=x86_64-w64-mingw32; cd ../unix then : else { echo "configure: error: configure failed for ../win" 1>&2; exit 1; } fi fi ;; dgux*) SHLIB_CFLAGS="-K PIC" SHLIB_LD='${CC} -G' SHLIB_LD_LIBS="" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" ;; Haiku*) LDFLAGS="$LDFLAGS -Wl,--export-dynamic" SHLIB_CFLAGS="-fPIC" SHLIB_SUFFIX=".so" SHLIB_LD='${CC} ${CFLAGS} ${LDFLAGS} -shared' DL_OBJS="tclLoadDl.o" DL_LIBS="-lroot" AC_CHECK_LIB(network, inet_ntoa, [LIBS="$LIBS -lnetwork"]) ;; HP-UX-*.11.*) # Use updated header definitions where possible AC_DEFINE(_XOPEN_SOURCE_EXTENDED, 1, [Do we want to use the XOPEN network library?]) AC_DEFINE(_XOPEN_SOURCE, 1, [Do we want to use the XOPEN network library?]) LIBS="$LIBS -lxnet" # Use the XOPEN network library AS_IF([test "`uname -m`" = ia64], [ SHLIB_SUFFIX=".so" ], [ SHLIB_SUFFIX=".sl" ]) AC_CHECK_LIB(dld, shl_load, tcl_ok=yes, tcl_ok=no) AS_IF([test "$tcl_ok" = yes], [ SHLIB_CFLAGS="+z" SHLIB_LD="ld -b" DL_OBJS="tclLoadShl.o" DL_LIBS="-ldld" LDFLAGS="$LDFLAGS -Wl,-E" CC_SEARCH_FLAGS='-Wl,+s,+b,${LIB_RUNTIME_DIR}:.' LD_SEARCH_FLAGS='+s +b ${LIB_RUNTIME_DIR}:.' LD_LIBRARY_PATH_VAR="SHLIB_PATH" ]) AS_IF([test "$GCC" = yes], [ SHLIB_LD='${CC} -shared' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} ], [ CFLAGS="$CFLAGS -z" ]) # Users may want PA-RISC 1.1/2.0 portable code - needs HP cc #CFLAGS="$CFLAGS +DAportable" # Check to enable 64-bit flags for compiler/linker AS_IF([test "$do64bit" = "yes"], [ AS_IF([test "$GCC" = yes], [ case `${CC} -dumpmachine` in hppa64*) # 64-bit gcc in use. Fix flags for GNU ld. do64bit_ok=yes SHLIB_LD='${CC} -shared' AS_IF([test $doRpath = yes], [ CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} ;; *) AC_MSG_WARN([64bit mode not supported with GCC on $system]) ;; esac ], [ do64bit_ok=yes CFLAGS="$CFLAGS +DD64" LDFLAGS_ARCH="+DD64" ]) ]) ;; HP-UX-*.08.*|HP-UX-*.09.*|HP-UX-*.10.*) SHLIB_SUFFIX=".sl" AC_CHECK_LIB(dld, shl_load, tcl_ok=yes, tcl_ok=no) AS_IF([test "$tcl_ok" = yes], [ SHLIB_CFLAGS="+z" SHLIB_LD="ld -b" SHLIB_LD_LIBS="" DL_OBJS="tclLoadShl.o" DL_LIBS="-ldld" LDFLAGS="$LDFLAGS -Wl,-E" CC_SEARCH_FLAGS='-Wl,+s,+b,${LIB_RUNTIME_DIR}:.' LD_SEARCH_FLAGS='+s +b ${LIB_RUNTIME_DIR}:.' LD_LIBRARY_PATH_VAR="SHLIB_PATH" ]) ;; IRIX-5.*) SHLIB_CFLAGS="" SHLIB_LD="ld -shared -rdata_shared" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" AC_LIBOBJ(mkstemp) AS_IF([test $doRpath = yes], [ CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}']) ;; IRIX-6.*) SHLIB_CFLAGS="" SHLIB_LD="ld -n32 -shared -rdata_shared" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" AC_LIBOBJ(mkstemp) AS_IF([test $doRpath = yes], [ CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}']) AS_IF([test "$GCC" = yes], [ CFLAGS="$CFLAGS -mabi=n32" LDFLAGS="$LDFLAGS -mabi=n32" ], [ case $system in IRIX-6.3) # Use to build 6.2 compatible binaries on 6.3. CFLAGS="$CFLAGS -n32 -D_OLD_TERMIOS" ;; *) CFLAGS="$CFLAGS -n32" ;; esac LDFLAGS="$LDFLAGS -n32" ]) ;; IRIX64-6.*) SHLIB_CFLAGS="" SHLIB_LD="ld -n32 -shared -rdata_shared" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" AC_LIBOBJ(mkstemp) AS_IF([test $doRpath = yes], [ CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}']) # Check to enable 64-bit flags for compiler/linker AS_IF([test "$do64bit" = yes], [ AS_IF([test "$GCC" = yes], [ AC_MSG_WARN([64bit mode not supported by gcc]) ], [ do64bit_ok=yes SHLIB_LD="ld -64 -shared -rdata_shared" CFLAGS="$CFLAGS -64" LDFLAGS_ARCH="-64" ]) ]) ;; Linux*|GNU*|NetBSD-Debian|DragonFly-*|FreeBSD-*) SHLIB_CFLAGS="-fPIC -fno-common" SHLIB_SUFFIX=".so" CFLAGS_OPTIMIZE="-O2" # egcs-2.91.66 on Redhat Linux 6.0 generates lots of warnings # when you inline the string and math operations. Turn this off to # get rid of the warnings. #CFLAGS_OPTIMIZE="${CFLAGS_OPTIMIZE} -D__NO_STRING_INLINES -D__NO_MATH_INLINES" SHLIB_LD='${CC} ${CFLAGS} ${LDFLAGS} -shared' DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" LDFLAGS="$LDFLAGS -Wl,--export-dynamic" case $system in DragonFly-*|FreeBSD-*) # The -pthread needs to go in the LDFLAGS, not LIBS LIBS=`echo $LIBS | sed s/-pthread//` CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LDFLAGS="$LDFLAGS $PTHREAD_LIBS" ;; esac AS_IF([test $doRpath = yes], [ CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} AS_IF([test "`uname -m`" = "alpha"], [CFLAGS="$CFLAGS -mieee"]) AS_IF([test $do64bit = yes], [ AC_CACHE_CHECK([if compiler accepts -m64 flag], tcl_cv_cc_m64, [ hold_cflags=$CFLAGS CFLAGS="$CFLAGS -m64" AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])], [tcl_cv_cc_m64=yes],[tcl_cv_cc_m64=no]) CFLAGS=$hold_cflags]) AS_IF([test $tcl_cv_cc_m64 = yes], [ CFLAGS="$CFLAGS -m64" do64bit_ok=yes ]) ]) # The combo of gcc + glibc has a bug related to inlining of # functions like strtol()/strtoul(). The -fno-builtin flag should address # this problem but it does not work. The -fno-inline flag is kind # of overkill but it works. Disable inlining only when one of the # files in compat/*.c is being linked in. AS_IF([test x"${USE_COMPAT}" != x],[CFLAGS="$CFLAGS -fno-inline"]) ;; Lynx*) SHLIB_CFLAGS="-fPIC" SHLIB_SUFFIX=".so" CFLAGS_OPTIMIZE=-02 SHLIB_LD='${CC} -shared' DL_OBJS="tclLoadDl.o" DL_LIBS="-mshared -ldl" LD_FLAGS="-Wl,--export-dynamic" AS_IF([test $doRpath = yes], [ CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) ;; OpenBSD-*) arch=`arch -s` case "$arch" in alpha|sparc64) SHLIB_CFLAGS="-fPIC" ;; *) SHLIB_CFLAGS="-fpic" ;; esac SHLIB_LD='${CC} ${SHLIB_CFLAGS} -shared' SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" AS_IF([test $doRpath = yes], [ CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} SHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.so.${SHLIB_VERSION}' LDFLAGS="-Wl,-export-dynamic" CFLAGS_OPTIMIZE="-O2" # On OpenBSD: Compile with -pthread # Don't link with -lpthread LIBS=`echo $LIBS | sed s/-lpthread//` CFLAGS="$CFLAGS -pthread" # OpenBSD doesn't do version numbers with dots. UNSHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.a' TCL_LIB_VERSIONS_OK=nodots ;; NetBSD-*) # NetBSD has ELF and can use 'cc -shared' to build shared libs SHLIB_CFLAGS="-fPIC" SHLIB_LD='${CC} ${SHLIB_CFLAGS} -shared' SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" LDFLAGS="$LDFLAGS -export-dynamic" AS_IF([test $doRpath = yes], [ CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"']) LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} # The -pthread needs to go in the CFLAGS, not LIBS LIBS=`echo $LIBS | sed s/-pthread//` CFLAGS="$CFLAGS -pthread" LDFLAGS="$LDFLAGS -pthread" ;; Darwin-*) CFLAGS_OPTIMIZE="-O2" SHLIB_CFLAGS="-fno-common" # To avoid discrepancies between what headers configure sees during # preprocessing tests and compiling tests, move any -isysroot and # -mmacosx-version-min flags from CFLAGS to CPPFLAGS: CPPFLAGS="${CPPFLAGS} `echo " ${CFLAGS}" | \ awk 'BEGIN {FS=" +-";ORS=" "}; {for (i=2;i<=NF;i++) \ if ([$]i~/^(isysroot|mmacosx-version-min)/) print "-"[$]i}'`" CFLAGS="`echo " ${CFLAGS}" | \ awk 'BEGIN {FS=" +-";ORS=" "}; {for (i=2;i<=NF;i++) \ if (!([$]i~/^(isysroot|mmacosx-version-min)/)) print "-"[$]i}'`" AS_IF([test $do64bit = yes], [ case `arch` in ppc) AC_CACHE_CHECK([if compiler accepts -arch ppc64 flag], tcl_cv_cc_arch_ppc64, [ hold_cflags=$CFLAGS CFLAGS="$CFLAGS -arch ppc64 -mpowerpc64 -mcpu=G5" AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])], [tcl_cv_cc_arch_ppc64=yes],[tcl_cv_cc_arch_ppc64=no]) CFLAGS=$hold_cflags]) AS_IF([test $tcl_cv_cc_arch_ppc64 = yes], [ CFLAGS="$CFLAGS -arch ppc64 -mpowerpc64 -mcpu=G5" do64bit_ok=yes ]);; i386|x86_64) AC_CACHE_CHECK([if compiler accepts -arch x86_64 flag], tcl_cv_cc_arch_x86_64, [ hold_cflags=$CFLAGS CFLAGS="$CFLAGS -arch x86_64" AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])], [tcl_cv_cc_arch_x86_64=yes],[tcl_cv_cc_arch_x86_64=no]) CFLAGS=$hold_cflags]) AS_IF([test $tcl_cv_cc_arch_x86_64 = yes], [ CFLAGS="$CFLAGS -arch x86_64" do64bit_ok=yes ]);; arm64) AC_CACHE_CHECK([if compiler accepts -arch arm64 flag], tcl_cv_cc_arch_arm64, [ hold_cflags=$CFLAGS CFLAGS="$CFLAGS -arch arm64" AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])], [tcl_cv_cc_arch_arm64=yes],[tcl_cv_cc_arch_arm64=no]) CFLAGS=$hold_cflags]) AS_IF([test $tcl_cv_cc_arch_arm64 = yes], [ CFLAGS="$CFLAGS -arch arm64" do64bit_ok=yes ]);; *) AC_MSG_WARN([Don't know how enable 64-bit on architecture `arch`]);; esac ], [ # Check for combined 32-bit and 64-bit fat build AS_IF([echo "$CFLAGS " |grep -E -q -- '-arch (ppc64|x86_64|arm64) ' \ && echo "$CFLAGS " |grep -E -q -- '-arch (ppc|i386) '], [ fat_32_64=yes]) ]) SHLIB_LD='${CC} -dynamiclib ${CFLAGS} ${LDFLAGS}' SHLIB_SUFFIX=".dylib" DL_OBJS="tclLoadDyld.o" DL_LIBS="" LDFLAGS="$LDFLAGS -headerpad_max_install_names" AC_CACHE_CHECK([if ld accepts -search_paths_first flag], tcl_cv_ld_search_paths_first, [ hold_ldflags=$LDFLAGS LDFLAGS="$LDFLAGS -Wl,-search_paths_first" AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[int i;]])], [tcl_cv_ld_search_paths_first=yes], [tcl_cv_ld_search_paths_first=no]) LDFLAGS=$hold_ldflags]) AS_IF([test $tcl_cv_ld_search_paths_first = yes], [ LDFLAGS="$LDFLAGS -Wl,-search_paths_first" ]) AS_IF([test "$tcl_cv_cc_visibility_hidden" != yes], [ AC_DEFINE(MODULE_SCOPE, [__private_extern__], [Compiler support for module scope symbols]) tcl_cv_cc_visibility_hidden=yes ]) CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" LD_LIBRARY_PATH_VAR="DYLD_FALLBACK_LIBRARY_PATH" AC_DEFINE(MAC_OSX_TCL, 1, [Is this a Mac I see before me?]) PLAT_OBJS='${MAC_OSX_OBJS}' PLAT_SRCS='${MAC_OSX_SRCS}' AC_MSG_CHECKING([whether to use CoreFoundation]) AC_ARG_ENABLE(corefoundation, AS_HELP_STRING([--enable-corefoundation], [use CoreFoundation API on MacOSX (default: on)]), [tcl_corefoundation=$enableval], [tcl_corefoundation=yes]) AC_MSG_RESULT([$tcl_corefoundation]) AS_IF([test $tcl_corefoundation = yes], [ AC_CACHE_CHECK([for CoreFoundation.framework], tcl_cv_lib_corefoundation, [ hold_libs=$LIBS AS_IF([test "$fat_32_64" = yes], [ for v in CFLAGS CPPFLAGS LDFLAGS; do # On Tiger there is no 64-bit CF, so remove 64-bit # archs from CFLAGS et al. while testing for # presence of CF. 64-bit CF is disabled in # tclUnixPort.h if necessary. eval 'hold_'$v'="$'$v'";'$v'="`echo "$'$v' "|sed -e "s/-arch ppc64 / /g" -e "s/-arch x86_64 / /g"`"' done]) LIBS="$LIBS -framework CoreFoundation" AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[CFBundleRef b = CFBundleGetMainBundle();]])], [tcl_cv_lib_corefoundation=yes], [tcl_cv_lib_corefoundation=no]) AS_IF([test "$fat_32_64" = yes], [ for v in CFLAGS CPPFLAGS LDFLAGS; do eval $v'="$hold_'$v'"' done]) LIBS=$hold_libs]) AS_IF([test $tcl_cv_lib_corefoundation = yes], [ LIBS="$LIBS -framework CoreFoundation" AC_DEFINE(HAVE_COREFOUNDATION, 1, [Do we have access to Darwin CoreFoundation.framework?]) ], [tcl_corefoundation=no]) AS_IF([test "$fat_32_64" = yes -a $tcl_corefoundation = yes],[ AC_CACHE_CHECK([for 64-bit CoreFoundation], tcl_cv_lib_corefoundation_64, [ for v in CFLAGS CPPFLAGS LDFLAGS; do eval 'hold_'$v'="$'$v'";'$v'="`echo "$'$v' "|sed -e "s/-arch ppc / /g" -e "s/-arch i386 / /g"`"' done AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[CFBundleRef b = CFBundleGetMainBundle();]])], [tcl_cv_lib_corefoundation_64=yes], [tcl_cv_lib_corefoundation_64=no]) for v in CFLAGS CPPFLAGS LDFLAGS; do eval $v'="$hold_'$v'"' done]) AS_IF([test $tcl_cv_lib_corefoundation_64 = no], [ AC_DEFINE(NO_COREFOUNDATION_64, 1, [Is Darwin CoreFoundation unavailable for 64-bit?]) LDFLAGS="$LDFLAGS -Wl,-no_arch_warnings" ]) ]) ]) ;; OS/390-*) SHLIB_LD_LIBS="" CFLAGS_OPTIMIZE="" # Optimizer is buggy AC_DEFINE(_OE_SOCKETS, 1, # needed in sys/socket.h [Should OS/390 do the right thing with sockets?]) ;; OSF1-V*) # Digital OSF/1 SHLIB_CFLAGS="" AS_IF([test "$SHARED_BUILD" = 1], [ SHLIB_LD='${CC} -shared' ], [ SHLIB_LD='${CC} -non_shared' ]) SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" AS_IF([test $doRpath = yes], [ CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}']) AS_IF([test "$GCC" = yes], [CFLAGS="$CFLAGS -mieee"], [ CFLAGS="$CFLAGS -DHAVE_TZSET -std1 -ieee"]) # see pthread_intro(3) for pthread support on osf1, k.furukawa CFLAGS="$CFLAGS -DHAVE_PTHREAD_ATTR_SETSTACKSIZE" CFLAGS="$CFLAGS -DTCL_THREAD_STACK_MIN=PTHREAD_STACK_MIN*64" LIBS=`echo $LIBS | sed s/-lpthreads//` AS_IF([test "$GCC" = yes], [ LIBS="$LIBS -lpthread -lmach -lexc" ], [ CFLAGS="$CFLAGS -pthread" LDFLAGS="$LDFLAGS -pthread" ]) ;; QNX-6*) # QNX RTP # This may work for all QNX, but it was only reported for v6. SHLIB_LD="ld -Bshareable -x" SHLIB_LD_LIBS="" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" # dlopen is in -lc on QNX DL_LIBS="" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" ;; SCO_SV-3.2*) # Note, dlopen is available only on SCO 3.2.5 and greater. However, # this test works, since "uname -s" was non-standard in 3.2.4 and # below. AS_IF([test "$GCC" = yes], [ SHLIB_CFLAGS="-fPIC -melf" LDFLAGS="$LDFLAGS -melf -Wl,-Bexport" ], [ SHLIB_CFLAGS="-Kpic -belf" LDFLAGS="$LDFLAGS -belf -Wl,-Bexport" ]) SHLIB_LD="ld -G" SHLIB_LD_LIBS="" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" ;; SunOS-5.[[0-6]]) # Careful to not let 5.10+ fall into this case # Note: If _REENTRANT isn't defined, then Solaris # won't define thread-safe library routines. AC_DEFINE(_REENTRANT, 1, [Do we want the reentrant OS API?]) AC_DEFINE(_POSIX_PTHREAD_SEMANTICS, 1, [Do we really want to follow the standard? Yes we do!]) SHLIB_CFLAGS="-KPIC" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" AS_IF([test "$GCC" = yes], [ SHLIB_LD='${CC} -shared' CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} ], [ SHLIB_LD="/usr/ccs/bin/ld -G -z text" CC_SEARCH_FLAGS='-R ${LIB_RUNTIME_DIR}' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} ]) ;; SunOS-5*) # Note: If _REENTRANT isn't defined, then Solaris # won't define thread-safe library routines. AC_DEFINE(_REENTRANT, 1, [Do we want the reentrant OS API?]) AC_DEFINE(_POSIX_PTHREAD_SEMANTICS, 1, [Do we really want to follow the standard? Yes we do!]) SHLIB_CFLAGS="-KPIC" # Check to enable 64-bit flags for compiler/linker AS_IF([test "$do64bit" = yes], [ arch=`isainfo` AS_IF([test "$arch" = "sparcv9 sparc"], [ AS_IF([test "$GCC" = yes], [ AS_IF([test "`${CC} -dumpversion | awk -F. '{print [$]1}'`" -lt 3], [ AC_MSG_WARN([64bit mode not supported with GCC < 3.2 on $system]) ], [ do64bit_ok=yes CFLAGS="$CFLAGS -m64 -mcpu=v9" LDFLAGS="$LDFLAGS -m64 -mcpu=v9" SHLIB_CFLAGS="-fPIC" ]) ], [ do64bit_ok=yes AS_IF([test "$do64bitVIS" = yes], [ CFLAGS="$CFLAGS -xarch=v9a" LDFLAGS_ARCH="-xarch=v9a" ], [ CFLAGS="$CFLAGS -xarch=v9" LDFLAGS_ARCH="-xarch=v9" ]) # Solaris 64 uses this as well #LD_LIBRARY_PATH_VAR="LD_LIBRARY_PATH_64" ]) ], [AS_IF([test "$arch" = "amd64 i386"], [ AS_IF([test "$GCC" = yes], [ case $system in SunOS-5.1[[1-9]]*|SunOS-5.[[2-9]][[0-9]]*) do64bit_ok=yes CFLAGS="$CFLAGS -m64" LDFLAGS="$LDFLAGS -m64";; *) AC_MSG_WARN([64bit mode not supported with GCC on $system]);; esac ], [ do64bit_ok=yes case $system in SunOS-5.1[[1-9]]*|SunOS-5.[[2-9]][[0-9]]*) CFLAGS="$CFLAGS -m64" LDFLAGS="$LDFLAGS -m64";; *) CFLAGS="$CFLAGS -xarch=amd64" LDFLAGS="$LDFLAGS -xarch=amd64";; esac ]) ], [AC_MSG_WARN([64bit mode not supported for $arch])])]) ]) #-------------------------------------------------------------------- # On Solaris 5.x i386 with the sunpro compiler we need to link # with sunmath to get floating point rounding control #-------------------------------------------------------------------- AS_IF([test "$GCC" = yes],[use_sunmath=no],[ arch=`isainfo` AC_MSG_CHECKING([whether to use -lsunmath for fp rounding control]) AS_IF([test "$arch" = "amd64 i386" -o "$arch" = "i386"], [ AC_MSG_RESULT([yes]) MATH_LIBS="-lsunmath $MATH_LIBS" AC_CHECK_HEADER(sunmath.h) use_sunmath=yes ], [ AC_MSG_RESULT([no]) use_sunmath=no ]) ]) SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" AS_IF([test "$GCC" = yes], [ SHLIB_LD='${CC} -shared' CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} AS_IF([test "$do64bit_ok" = yes], [ AS_IF([test "$arch" = "sparcv9 sparc"], [ # We need to specify -static-libgcc or we need to # add the path to the sparv9 libgcc. SHLIB_LD="$SHLIB_LD -m64 -mcpu=v9 -static-libgcc" # for finding sparcv9 libgcc, get the regular libgcc # path, remove so name and append 'sparcv9' #v9gcclibdir="`gcc -print-file-name=libgcc_s.so` | ..." #CC_SEARCH_FLAGS="${CC_SEARCH_FLAGS},-R,$v9gcclibdir" ], [AS_IF([test "$arch" = "amd64 i386"], [ SHLIB_LD="$SHLIB_LD -m64 -static-libgcc" ])]) ]) ], [ AS_IF([test "$use_sunmath" = yes], [textmode=textoff],[textmode=text]) case $system in SunOS-5.[[1-9]][[0-9]]*|SunOS-5.[[7-9]]) SHLIB_LD="\${CC} -G -z $textmode \${LDFLAGS}";; *) SHLIB_LD="/usr/ccs/bin/ld -G -z $textmode";; esac CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' LD_SEARCH_FLAGS='-R ${LIB_RUNTIME_DIR}' ]) ;; UNIX_SV* | UnixWare-5*) SHLIB_CFLAGS="-KPIC" SHLIB_LD='${CC} -G' SHLIB_LD_LIBS="" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" # Some UNIX_SV* systems (unixware 1.1.2 for example) have linkers # that don't grok the -Bexport option. Test that it does. AC_CACHE_CHECK([for ld accepts -Bexport flag], tcl_cv_ld_Bexport, [ hold_ldflags=$LDFLAGS LDFLAGS="$LDFLAGS -Wl,-Bexport" AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[int i;]])],[tcl_cv_ld_Bexport=yes],[tcl_cv_ld_Bexport=no]) LDFLAGS=$hold_ldflags]) AS_IF([test $tcl_cv_ld_Bexport = yes], [ LDFLAGS="$LDFLAGS -Wl,-Bexport" ]) CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" ;; esac AS_IF([test "$do64bit" = yes -a "$do64bit_ok" = no], [ AC_MSG_WARN([64bit support being disabled -- don't know magic for this platform]) ]) AS_IF([test "$do64bit" = yes -a "$do64bit_ok" = yes], [ AC_DEFINE(TCL_CFG_DO64BIT, 1, [Is this a 64-bit build?]) ]) dnl # Add any CPPFLAGS set in the environment to our CFLAGS, but delay doing so dnl # until the end of configure, as configure's compile and link tests use dnl # both CPPFLAGS and CFLAGS (unlike our compile and link) but configure's dnl # preprocessing tests use only CPPFLAGS. AC_CONFIG_COMMANDS_PRE([CFLAGS="${CFLAGS} ${CPPFLAGS}"; CPPFLAGS=""]) # Step 4: disable dynamic loading if requested via a command-line switch. AC_ARG_ENABLE(load, AS_HELP_STRING([--enable-load], [allow dynamic loading and "load" command (default: on)]), [tcl_ok=$enableval], [tcl_ok=yes]) AS_IF([test "$tcl_ok" = no], [DL_OBJS=""]) AS_IF([test "x$DL_OBJS" != x], [BUILD_DLTEST="\$(DLTEST_TARGETS)"], [ AC_MSG_WARN([Can't figure out how to do dynamic loading or shared libraries on this system.]) SHLIB_CFLAGS="" SHLIB_LD="" SHLIB_SUFFIX="" DL_OBJS="tclLoadNone.o" DL_LIBS="" LDFLAGS="$LDFLAGS_ORIG" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" BUILD_DLTEST="" ]) LDFLAGS="$LDFLAGS $LDFLAGS_ARCH" # If we're running gcc, then change the C flags for compiling shared # libraries to the right flags for gcc, instead of those for the # standard manufacturer compiler. AS_IF([test "$DL_OBJS" != "tclLoadNone.o" -a "$GCC" = yes], [ case $system in AIX-*) ;; BSD/OS*) ;; CYGWIN_*|MINGW32_*|MSYS_*) ;; HP-UX*) ;; Darwin-*) ;; IRIX*) ;; Linux*|GNU*) ;; NetBSD-*|OpenBSD-*) ;; OSF1-*) ;; SCO_SV-3.2*) ;; *) SHLIB_CFLAGS="-fPIC" ;; esac]) AS_IF([test "$tcl_cv_cc_visibility_hidden" != yes], [ AC_DEFINE(MODULE_SCOPE, [extern], [No Compiler support for module scope symbols]) ]) AS_IF([test "$SHARED_LIB_SUFFIX" = ""], [ SHARED_LIB_SUFFIX='${VERSION}${SHLIB_SUFFIX}']) AS_IF([test "$UNSHARED_LIB_SUFFIX" = ""], [ UNSHARED_LIB_SUFFIX='${VERSION}.a']) DLL_INSTALL_DIR="\$(LIB_INSTALL_DIR)" AS_IF([test "${SHARED_BUILD}" = 1 -a "${SHLIB_SUFFIX}" != ""], [ LIB_SUFFIX=${SHARED_LIB_SUFFIX} MAKE_LIB='${SHLIB_LD} -o [$]@ ${OBJS} ${LDFLAGS} ${SHLIB_LD_LIBS} ${TCL_SHLIB_LD_EXTRAS} ${TK_SHLIB_LD_EXTRAS} ${LD_SEARCH_FLAGS}' AS_IF([test "${SHLIB_SUFFIX}" = ".dll"], [ INSTALL_LIB='$(INSTALL_LIBRARY) $(LIB_FILE) "$(BIN_INSTALL_DIR)/$(LIB_FILE)"' DLL_INSTALL_DIR="\$(BIN_INSTALL_DIR)" ], [ INSTALL_LIB='$(INSTALL_LIBRARY) $(LIB_FILE) "$(LIB_INSTALL_DIR)/$(LIB_FILE)"' ]) ], [ LIB_SUFFIX=${UNSHARED_LIB_SUFFIX} AS_IF([test "$RANLIB" = ""], [ MAKE_LIB='$(STLIB_LD) [$]@ ${OBJS}' ], [ MAKE_LIB='${STLIB_LD} [$]@ ${OBJS} ; ${RANLIB} [$]@' ]) INSTALL_LIB='$(INSTALL_LIBRARY) $(LIB_FILE) "$(LIB_INSTALL_DIR)/$(LIB_FILE)"' ]) # Stub lib does not depend on shared/static configuration AS_IF([test "$RANLIB" = ""], [ MAKE_STUB_LIB='${STLIB_LD} [$]@ ${STUB_LIB_OBJS}' ], [ MAKE_STUB_LIB='${STLIB_LD} [$]@ ${STUB_LIB_OBJS} ; ${RANLIB} [$]@' ]) INSTALL_STUB_LIB='$(INSTALL_LIBRARY) $(STUB_LIB_FILE) "$(LIB_INSTALL_DIR)/$(STUB_LIB_FILE)"' # Define TCL_LIBS now that we know what DL_LIBS is. # The trick here is that we don't want to change the value of TCL_LIBS if # it is already set when tclConfig.sh had been loaded by Tk. AS_IF([test "x${TCL_LIBS}" = x], [ TCL_LIBS="${DL_LIBS} ${LIBS} ${MATH_LIBS}"]) AC_SUBST(TCL_LIBS) # See if the compiler supports casting to a union type. # This is used to stop gcc from printing a compiler # warning when initializing a union member. AC_CACHE_CHECK(for cast to union support, tcl_cv_cast_to_union, AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[ union foo { int i; double d; }; union foo f = (union foo) (int) 0; ]])], [tcl_cv_cast_to_union=yes], [tcl_cv_cast_to_union=no]) ) if test "$tcl_cv_cast_to_union" = "yes"; then AC_DEFINE(HAVE_CAST_TO_UNION, 1, [Defined when compiler supports casting to union type.]) fi hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -fno-lto" AC_CACHE_CHECK(for working -fno-lto, ac_cv_nolto, AC_COMPILE_IFELSE([AC_LANG_PROGRAM([])], [ac_cv_nolto=yes], [ac_cv_nolto=no]) ) CFLAGS=$hold_cflags if test "$ac_cv_nolto" = "yes" ; then CFLAGS_NOLTO="-fno-lto" else CFLAGS_NOLTO="" fi AC_CACHE_CHECK([if the compiler understands -finput-charset], tcl_cv_cc_input_charset, [ hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -finput-charset=UTF-8" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[]])],[tcl_cv_cc_input_charset=yes],[tcl_cv_cc_input_charset=no]) CFLAGS=$hold_cflags]) if test $tcl_cv_cc_input_charset = yes; then CFLAGS="$CFLAGS -finput-charset=UTF-8" fi AC_CHECK_HEADER(stdbool.h, [AC_DEFINE(HAVE_STDBOOL_H, 1, [Do we have ?])],) # Check for vfork, posix_spawnp() and friends unconditionally AC_CHECK_FUNCS(vfork posix_spawnp posix_spawn_file_actions_adddup2 posix_spawnattr_setflags) # FIXME: This subst was left in only because the TCL_DL_LIBS # entry in tclConfig.sh uses it. It is not clear why someone # would use TCL_DL_LIBS instead of TCL_LIBS. AC_SUBST(DL_LIBS) AC_SUBST(DL_OBJS) AC_SUBST(PLAT_OBJS) AC_SUBST(PLAT_SRCS) AC_SUBST(LDAIX_SRC) AC_SUBST(CFLAGS) AC_SUBST(CFLAGS_DEBUG) AC_SUBST(CFLAGS_OPTIMIZE) AC_SUBST(CFLAGS_WARNING) AC_SUBST(CFLAGS_NOLTO) AC_SUBST(LDFLAGS) AC_SUBST(LDFLAGS_DEBUG) AC_SUBST(LDFLAGS_OPTIMIZE) AC_SUBST(CC_SEARCH_FLAGS) AC_SUBST(LD_SEARCH_FLAGS) AC_SUBST(STLIB_LD) AC_SUBST(SHLIB_LD) AC_SUBST(TCL_SHLIB_LD_EXTRAS) AC_SUBST(TK_SHLIB_LD_EXTRAS) AC_SUBST(SHLIB_LD_LIBS) AC_SUBST(SHLIB_CFLAGS) AC_SUBST(SHLIB_SUFFIX) AC_DEFINE_UNQUOTED(TCL_SHLIB_EXT,"${SHLIB_SUFFIX}", [What is the default extension for shared libraries?]) AC_SUBST(MAKE_LIB) AC_SUBST(MAKE_STUB_LIB) AC_SUBST(INSTALL_LIB) AC_SUBST(DLL_INSTALL_DIR) AC_SUBST(INSTALL_STUB_LIB) AC_SUBST(RANLIB) ]) #-------------------------------------------------------------------- # SC_MISSING_POSIX_HEADERS # # Supply substitutes for missing POSIX header files. Special # notes: # - stdlib.h doesn't define strtol or strtoul in some # versions of SunOS # - some versions of string.h don't declare procedures such # as strstr # # Arguments: # none # # Results: # # Defines some of the following vars: # NO_SYS_WAIT_H # NO_DLFCN_H # HAVE_SYS_PARAM_H # HAVE_STRING_H ? # #-------------------------------------------------------------------- AC_DEFUN([SC_MISSING_POSIX_HEADERS], [ AC_CHECK_HEADER(string.h, tcl_ok=1, tcl_ok=0) AC_EGREP_HEADER(strstr, string.h, , tcl_ok=0) AC_EGREP_HEADER(strerror, string.h, , tcl_ok=0) AC_CHECK_HEADER(sys/wait.h, , [AC_DEFINE(NO_SYS_WAIT_H, 1, [Do we have ?])]) AC_CHECK_HEADER(dlfcn.h, , [AC_DEFINE(NO_DLFCN_H, 1, [Do we have ?])]) # OS/390 lacks sys/param.h (and doesn't need it, by chance). AC_CHECK_HEADERS([sys/param.h]) ]) #-------------------------------------------------------------------- # SC_PATH_X # # Locate the X11 header files and the X11 library archive. Try # the ac_path_x macro first, but if it doesn't find the X stuff # (e.g. because there's no xmkmf program) then check through # a list of possible directories. Under some conditions the # autoconf macro will return an include directory that contains # no include files, so double-check its result just to be safe. # # Arguments: # none # # Results: # # Sets the following vars: # XINCLUDES # XLIBSW # #-------------------------------------------------------------------- AC_DEFUN([SC_PATH_X], [ AC_PATH_X not_really_there="" if test "$no_x" = ""; then if test "$x_includes" = ""; then AC_PREPROC_IFELSE([AC_LANG_SOURCE([[#include ]])],[],[not_really_there="yes"]) else if test ! -r $x_includes/X11/Xlib.h; then not_really_there="yes" fi fi fi if test "$no_x" = "yes" -o "$not_really_there" = "yes"; then AC_MSG_CHECKING([for X11 header files]) found_xincludes="no" AC_PREPROC_IFELSE([AC_LANG_SOURCE([[#include ]])],[found_xincludes="yes"],[found_xincludes="no"]) if test "$found_xincludes" = "no"; then dirs="/usr/unsupported/include /usr/local/include /usr/X386/include /usr/X11R6/include /usr/X11R5/include /usr/include/X11R5 /usr/include/X11R4 /usr/openwin/include /usr/X11/include /usr/sww/include" for i in $dirs ; do if test -r $i/X11/Xlib.h; then AC_MSG_RESULT([$i]) XINCLUDES=" -I$i" found_xincludes="yes" break fi done fi else if test "$x_includes" != ""; then XINCLUDES="-I$x_includes" found_xincludes="yes" fi fi if test "$found_xincludes" = "no"; then AC_MSG_RESULT([couldn't find any!]) fi if test "$no_x" = yes; then AC_MSG_CHECKING([for X11 libraries]) XLIBSW=nope dirs="/usr/unsupported/lib /usr/local/lib /usr/X386/lib /usr/X11R6/lib /usr/X11R5/lib /usr/lib/X11R5 /usr/lib/X11R4 /usr/openwin/lib /usr/X11/lib /usr/sww/X11/lib" for i in $dirs ; do if test -r $i/libX11.a -o -r $i/libX11.so -o -r $i/libX11.sl -o -r $i/libX11.dylib; then AC_MSG_RESULT([$i]) XLIBSW="-L$i -lX11" x_libraries="$i" break fi done else if test "$x_libraries" = ""; then XLIBSW=-lX11 else XLIBSW="-L$x_libraries -lX11" fi fi if test "$XLIBSW" = nope ; then AC_CHECK_LIB(Xwindow, XCreateWindow, XLIBSW=-lXwindow) fi if test "$XLIBSW" = nope ; then AC_MSG_RESULT([could not find any! Using -lX11.]) XLIBSW=-lX11 fi ]) #-------------------------------------------------------------------- # SC_BLOCKING_STYLE # # The statements below check for systems where POSIX-style # non-blocking I/O (O_NONBLOCK) doesn't work or is unimplemented. # On these systems (mostly older ones), use the old BSD-style # FIONBIO approach instead. # # Arguments: # none # # Results: # # Defines some of the following vars: # HAVE_SYS_IOCTL_H # HAVE_SYS_FILIO_H # USE_FIONBIO # O_NONBLOCK # #-------------------------------------------------------------------- AC_DEFUN([SC_BLOCKING_STYLE], [ AC_CHECK_HEADERS(sys/ioctl.h) AC_CHECK_HEADERS(sys/filio.h) SC_CONFIG_SYSTEM AC_MSG_CHECKING([FIONBIO vs. O_NONBLOCK for nonblocking I/O]) case $system in OSF*) AC_DEFINE(USE_FIONBIO, 1, [Should we use FIONBIO?]) AC_MSG_RESULT([FIONBIO]) ;; *) AC_MSG_RESULT([O_NONBLOCK]) ;; esac ]) #-------------------------------------------------------------------- # SC_TIME_HANLDER # # Checks how the system deals with time.h, what time structures # are used on the system, and what fields the structures have. # # Arguments: # none # # Results: # # Defines some of the following vars: # USE_DELTA_FOR_TZ # HAVE_TM_GMTOFF # HAVE_TM_TZADJ # HAVE_TIMEZONE_VAR # #-------------------------------------------------------------------- AC_DEFUN([SC_TIME_HANDLER], [ AC_CHECK_HEADERS(sys/time.h) AC_CHECK_HEADERS_ONCE([sys/time.h]) AC_CHECK_FUNCS(gmtime_r localtime_r) AC_CACHE_CHECK([tm_tzadj in struct tm], tcl_cv_member_tm_tzadj, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[struct tm tm; (void)tm.tm_tzadj;]])], [tcl_cv_member_tm_tzadj=yes], [tcl_cv_member_tm_tzadj=no])]) if test $tcl_cv_member_tm_tzadj = yes ; then AC_DEFINE(HAVE_TM_TZADJ, 1, [Should we use the tm_tzadj field of struct tm?]) fi AC_CACHE_CHECK([tm_gmtoff in struct tm], tcl_cv_member_tm_gmtoff, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[struct tm tm; (void)tm.tm_gmtoff;]])], [tcl_cv_member_tm_gmtoff=yes], [tcl_cv_member_tm_gmtoff=no])]) if test $tcl_cv_member_tm_gmtoff = yes ; then AC_DEFINE(HAVE_TM_GMTOFF, 1, [Should we use the tm_gmtoff field of struct tm?]) fi # # Its important to include time.h in this check, as some systems # (like convex) have timezone functions, etc. # AC_CACHE_CHECK([long timezone variable], tcl_cv_timezone_long, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #include ]], [[extern long timezone; timezone += 1; exit (0);]])], [tcl_cv_timezone_long=yes], [tcl_cv_timezone_long=no])]) if test $tcl_cv_timezone_long = yes ; then AC_DEFINE(HAVE_TIMEZONE_VAR, 1, [Should we use the global timezone variable?]) else # # On some systems (eg IRIX 6.2), timezone is a time_t and not a long. # AC_CACHE_CHECK([time_t timezone variable], tcl_cv_timezone_time, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #include ]], [[extern time_t timezone; timezone += 1; exit (0);]])], [tcl_cv_timezone_time=yes], [tcl_cv_timezone_time=no])]) if test $tcl_cv_timezone_time = yes ; then AC_DEFINE(HAVE_TIMEZONE_VAR, 1, [Should we use the global timezone variable?]) fi fi ]) #-------------------------------------------------------------------- # SC_TCL_LINK_LIBS # # Search for the libraries needed to link the Tcl shell. # Things like the math library (-lm), socket stuff (-lsocket vs. # -lnsl), zlib (-lz) and libtommath (-ltommath) or thread library # (-lpthread) are dealt with here. # # Arguments: # None. # # Results: # # Sets the following vars: # THREADS_LIBS Thread library(s) # # Defines the following vars: # _REENTRANT # _THREAD_SAFE # # Might append to the following vars: # LIBS # MATH_LIBS # # Might define the following vars: # HAVE_NET_ERRNO_H # #-------------------------------------------------------------------- AC_DEFUN([SC_TCL_LINK_LIBS], [ #-------------------------------------------------------------------- # On a few very rare systems, all of the libm.a stuff is # already in libc.a. Set compiler flags accordingly. #-------------------------------------------------------------------- AC_CHECK_FUNC(sin, MATH_LIBS="", MATH_LIBS="-lm") #-------------------------------------------------------------------- # Interactive UNIX requires -linet instead of -lsocket, plus it # needs net/errno.h to define the socket-related error codes. #-------------------------------------------------------------------- AC_CHECK_LIB(inet, main, [LIBS="$LIBS -linet"]) AC_CHECK_HEADER(net/errno.h, [ AC_DEFINE(HAVE_NET_ERRNO_H, 1, [Do we have ?])]) #-------------------------------------------------------------------- # Check for the existence of the -lsocket and -lnsl libraries. # The order here is important, so that they end up in the right # order in the command line generated by make. Here are some # special considerations: # 1. Use "connect" and "accept" to check for -lsocket, and # "gethostbyname" to check for -lnsl. # 2. Use each function name only once: can't redo a check because # autoconf caches the results of the last check and won't redo it. # 3. Use -lnsl and -lsocket only if they supply procedures that # aren't already present in the normal libraries. This is because # IRIX 5.2 has libraries, but they aren't needed and they're # bogus: they goof up name resolution if used. # 4. On some SVR4 systems, can't use -lsocket without -lnsl too. # To get around this problem, check for both libraries together # if -lsocket doesn't work by itself. #-------------------------------------------------------------------- tcl_checkBoth=0 AC_CHECK_FUNC(connect, tcl_checkSocket=0, tcl_checkSocket=1) if test "$tcl_checkSocket" = 1; then AC_CHECK_FUNC(setsockopt, , [AC_CHECK_LIB(socket, setsockopt, LIBS="$LIBS -lsocket", tcl_checkBoth=1)]) fi if test "$tcl_checkBoth" = 1; then tk_oldLibs=$LIBS LIBS="$LIBS -lsocket -lnsl" AC_CHECK_FUNC(accept, tcl_checkNsl=0, [LIBS=$tk_oldLibs]) fi AC_CHECK_FUNC(gethostbyname, , [AC_CHECK_LIB(nsl, gethostbyname, [LIBS="$LIBS -lnsl"])]) AC_DEFINE(_REENTRANT, 1, [Do we want the reentrant OS API?]) AC_DEFINE(_THREAD_SAFE, 1, [Do we want the thread-safe OS API?]) AC_CHECK_LIB(pthread,pthread_mutex_init,tcl_ok=yes,tcl_ok=no) if test "$tcl_ok" = "no"; then # Check a little harder for __pthread_mutex_init in the same # library, as some systems hide it there until pthread.h is # defined. We could alternatively do an AC_TRY_COMPILE with # pthread.h, but that will work with libpthread really doesn't # exist, like AIX 4.2. [Bug: 4359] AC_CHECK_LIB(pthread, __pthread_mutex_init, tcl_ok=yes, tcl_ok=no) fi if test "$tcl_ok" = "yes"; then # The space is needed THREADS_LIBS=" -lpthread" else AC_CHECK_LIB(pthreads, pthread_mutex_init, _ok=yes, tcl_ok=no) if test "$tcl_ok" = "yes"; then # The space is needed THREADS_LIBS=" -lpthreads" else AC_CHECK_LIB(c, pthread_mutex_init, tcl_ok=yes, tcl_ok=no) if test "$tcl_ok" = "no"; then AC_CHECK_LIB(c_r, pthread_mutex_init, tcl_ok=yes, tcl_ok=no) if test "$tcl_ok" = "yes"; then # The space is needed THREADS_LIBS=" -pthread" else AC_MSG_WARN([Don't know how to find pthread lib on your system - you must edit the LIBS in the Makefile...]) fi fi fi fi # Does the pthread-implementation provide # 'pthread_attr_setstacksize' ? ac_saved_libs=$LIBS LIBS="$LIBS $THREADS_LIBS" AC_CHECK_FUNCS(pthread_attr_setstacksize pthread_atfork) LIBS=$ac_saved_libs # TIP #509 AC_CHECK_DECLS([PTHREAD_MUTEX_RECURSIVE],tcl_ok=yes,tcl_ok=no, [[#include ]]) ]) #-------------------------------------------------------------------- # SC_TCL_EARLY_FLAGS # # Check for what flags are needed to be passed so the correct OS # features are available. # # Arguments: # None # # Results: # # Might define the following vars: # _ISOC99_SOURCE # _FILE_OFFSET_BITS # _LARGEFILE64_SOURCE # #-------------------------------------------------------------------- AC_DEFUN([SC_TCL_EARLY_FLAG],[ AC_CACHE_VAL([tcl_cv_flag_]translit($1,[A-Z],[a-z]), AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[$2]], [[$3]])], [tcl_cv_flag_]translit($1,[A-Z],[a-z])=no,[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[[#define ]$1[ ]m4_default([$4],[1])[ ]$2]], [[$3]])], [tcl_cv_flag_]translit($1,[A-Z],[a-z])=yes, [tcl_cv_flag_]translit($1,[A-Z],[a-z])=no)])) if test ["x${tcl_cv_flag_]translit($1,[A-Z],[a-z])[}" = "xyes"] ; then AC_DEFINE($1, m4_default([$4],[1]), [Add the ]$1[ flag when building]) tcl_flags="$tcl_flags $1" fi ]) AC_DEFUN([SC_TCL_EARLY_FLAGS],[ AC_MSG_CHECKING([for required early compiler flags]) tcl_flags="" SC_TCL_EARLY_FLAG(_ISOC99_SOURCE,[#include ], [char *p = (char *)strtoll; char *q = (char *)strtoull;]) SC_TCL_EARLY_FLAG(_FILE_OFFSET_BITS,[#include ], [switch (0) { case 0: case (sizeof(off_t)==sizeof(long long)): ; }],64) SC_TCL_EARLY_FLAG(_LARGEFILE64_SOURCE,[#include ], [struct stat64 buf; int i = stat64("/", &buf);]) if test "x${tcl_flags}" = "x" ; then AC_MSG_RESULT([none]) else AC_MSG_RESULT([${tcl_flags}]) fi ]) #-------------------------------------------------------------------- # SC_TCL_64BIT_FLAGS # # Check for what is defined in the way of 64-bit features. # # Arguments: # None # # Results: # # Might define the following vars: # TCL_WIDE_INT_IS_LONG # HAVE_STRUCT_DIRENT64, HAVE_DIR64 # HAVE_TYPE_OFF64_T # _TIME_BITS # #-------------------------------------------------------------------- AC_DEFUN([SC_TCL_64BIT_FLAGS], [ AC_MSG_CHECKING([if 'long' and 'long long' have the same size (64-bit)?]) AC_CACHE_VAL(tcl_cv_type_64bit,[ tcl_cv_type_64bit=none # See if we could use long anyway Note that we substitute in the # type that is our current guess for a 64-bit type inside this check # program, so it should be modified only carefully... AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[switch (0) { case 1: case (sizeof(long long)==sizeof(long)): ; }]])],[tcl_cv_type_64bit="long long"],[])]) if test "${tcl_cv_type_64bit}" = none ; then AC_DEFINE(TCL_WIDE_INT_IS_LONG, 1, [Do 'long' and 'long long' have the same size (64-bit)?]) AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) # Now check for auxiliary declarations AC_CACHE_CHECK([for 64-bit time_t], tcl_cv_time_t_64,[ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[switch (0) {case 0: case (sizeof(time_t)==sizeof(long long)): ;}]])], [tcl_cv_time_t_64=yes],[tcl_cv_time_t_64=no])]) if test "x${tcl_cv_time_t_64}" = "xno" ; then # Note that _TIME_BITS=64 requires _FILE_OFFSET_BITS=64 # which SC_TCL_EARLY_FLAGS has defined if necessary. AC_CACHE_CHECK([if _TIME_BITS=64 enables 64-bit time_t], tcl_cv__time_bits,[ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#define _TIME_BITS 64 #include ]], [[switch (0) {case 0: case (sizeof(time_t)==sizeof(long long)): ;}]])], [tcl_cv__time_bits=yes],[tcl_cv__time_bits=no])]) if test "x${tcl_cv__time_bits}" = "xyes" ; then AC_DEFINE(_TIME_BITS, 64, [_TIME_BITS=64 enables 64-bit time_t.]) fi fi AC_CACHE_CHECK([for struct dirent64], tcl_cv_struct_dirent64,[ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #include ]], [[struct dirent64 p;]])], [tcl_cv_struct_dirent64=yes],[tcl_cv_struct_dirent64=no])]) if test "x${tcl_cv_struct_dirent64}" = "xyes" ; then AC_DEFINE(HAVE_STRUCT_DIRENT64, 1, [Is 'struct dirent64' in ?]) fi AC_CACHE_CHECK([for DIR64], tcl_cv_DIR64,[ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #include ]], [[struct dirent64 *p; DIR64 d = opendir64("."); p = readdir64(d); rewinddir64(d); closedir64(d);]])], [tcl_cv_DIR64=yes], [tcl_cv_DIR64=no])]) if test "x${tcl_cv_DIR64}" = "xyes" ; then AC_DEFINE(HAVE_DIR64, 1, [Is 'DIR64' in ?]) fi AC_CHECK_FUNCS(open64 lseek64) AC_MSG_CHECKING([for off64_t]) AC_CACHE_VAL(tcl_cv_type_off64_t,[ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[off64_t offset; ]])], [tcl_cv_type_off64_t=yes], [tcl_cv_type_off64_t=no])]) dnl Define HAVE_TYPE_OFF64_T only when the off64_t type and the dnl functions lseek64 and open64 are defined. if test "x${tcl_cv_type_off64_t}" = "xyes" && \ test "x${ac_cv_func_lseek64}" = "xyes" && \ test "x${ac_cv_func_open64}" = "xyes" ; then AC_DEFINE(HAVE_TYPE_OFF64_T, 1, [Is off64_t in ?]) AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) fi fi ]) #-------------------------------------------------------------------- # SC_TCL_CFG_ENCODING TIP #59 # # Declare the encoding to use for embedded configuration information. # # Arguments: # None. # # Results: # Might append to the following vars: # DEFS (implicit) # # Will define the following vars: # TCL_CFGVAL_ENCODING # #-------------------------------------------------------------------- AC_DEFUN([SC_TCL_CFG_ENCODING], [ AC_ARG_WITH(encoding, AS_HELP_STRING([--with-encoding], [encoding for configuration values (default: utf-8)]), [with_tcencoding=${withval}]) if test x"${with_tcencoding}" != x ; then AC_DEFINE_UNQUOTED(TCL_CFGVAL_ENCODING,"${with_tcencoding}", [What encoding should be used for embedded configuration info?]) else AC_DEFINE(TCL_CFGVAL_ENCODING,"utf-8", [What encoding should be used for embedded configuration info?]) fi ]) #-------------------------------------------------------------------- # SC_TCL_CHECK_BROKEN_FUNC # # Check for broken function. # # Arguments: # funcName - function to test for # advancedTest - the advanced test to run if the function is present # # Results: # Might cause compatibility versions of the function to be used. # Might affect the following vars: # USE_COMPAT (implicit) # #-------------------------------------------------------------------- AC_DEFUN([SC_TCL_CHECK_BROKEN_FUNC],[ AC_CHECK_FUNC($1, tcl_ok=1, tcl_ok=0) if test ["$tcl_ok"] = 1; then AC_CACHE_CHECK([proper ]$1[ implementation], [tcl_cv_]$1[_unbroken], AC_RUN_IFELSE([AC_LANG_SOURCE([[[ #include #include int main() {]$2[}]]])],[tcl_cv_$1_unbroken=ok], [tcl_cv_$1_unbroken=broken],[tcl_cv_$1_unbroken=unknown])) if test ["$tcl_cv_]$1[_unbroken"] = "ok"; then tcl_ok=1 else tcl_ok=0 fi fi if test ["$tcl_ok"] = 0; then AC_LIBOBJ($1) USE_COMPAT=1 fi ]) #-------------------------------------------------------------------- # SC_TCL_GETHOSTBYADDR_R # # Check if we have MT-safe variant of gethostbyaddr(). # # Arguments: # None # # Results: # # Might define the following vars: # HAVE_GETHOSTBYADDR_R # HAVE_GETHOSTBYADDR_R_7 # HAVE_GETHOSTBYADDR_R_8 # #-------------------------------------------------------------------- AC_DEFUN([SC_TCL_GETHOSTBYADDR_R], [ # Avoids picking hidden internal symbol from libc SC_TCL_GETHOSTBYADDR_R_DECL if test "$tcl_cv_api_gethostbyaddr_r" = yes; then SC_TCL_GETHOSTBYADDR_R_TYPE fi ]) AC_DEFUN([SC_TCL_GETHOSTBYADDR_R_DECL], [AC_CHECK_DECLS(gethostbyaddr_r, [ tcl_cv_api_gethostbyaddr_r=yes],[tcl_cv_api_gethostbyaddr_r=no],[#include ]) ]) AC_DEFUN([SC_TCL_GETHOSTBYADDR_R_TYPE], [AC_CHECK_FUNC(gethostbyaddr_r, [ AC_CACHE_CHECK([for gethostbyaddr_r with 7 args], tcl_cv_api_gethostbyaddr_r_7, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ char *addr; int length; int type; struct hostent *result; char buffer[2048]; int buflen = 2048; int h_errnop; (void) gethostbyaddr_r(addr, length, type, result, buffer, buflen, &h_errnop); ]])],[tcl_cv_api_gethostbyaddr_r_7=yes],[tcl_cv_api_gethostbyaddr_r_7=no])]) tcl_ok=$tcl_cv_api_gethostbyaddr_r_7 if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETHOSTBYADDR_R_7, 1, [Define to 1 if gethostbyaddr_r takes 7 args.]) else AC_CACHE_CHECK([for gethostbyaddr_r with 8 args], tcl_cv_api_gethostbyaddr_r_8, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ char *addr; int length; int type; struct hostent *result, *resultp; char buffer[2048]; int buflen = 2048; int h_errnop; (void) gethostbyaddr_r(addr, length, type, result, buffer, buflen, &resultp, &h_errnop); ]])],[tcl_cv_api_gethostbyaddr_r_8=yes],[tcl_cv_api_gethostbyaddr_r_8=no])]) tcl_ok=$tcl_cv_api_gethostbyaddr_r_8 if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETHOSTBYADDR_R_8, 1, [Define to 1 if gethostbyaddr_r takes 8 args.]) fi fi if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETHOSTBYADDR_R, 1, [Define to 1 if gethostbyaddr_r is available.]) fi ])]) #-------------------------------------------------------------------- # SC_TCL_GETHOSTBYNAME_R # # Check to see what variant of gethostbyname_r() we have. # Based on David Arnold's example from the comp.programming.threads # FAQ Q213 # # Arguments: # None # # Results: # # Might define the following vars: # HAVE_GETHOSTBYNAME_R # HAVE_GETHOSTBYNAME_R_3 # HAVE_GETHOSTBYNAME_R_5 # HAVE_GETHOSTBYNAME_R_6 # #-------------------------------------------------------------------- AC_DEFUN([SC_TCL_GETHOSTBYNAME_R], [ # Avoids picking hidden internal symbol from libc SC_TCL_GETHOSTBYNAME_R_DECL if test "$tcl_cv_api_gethostbyname_r" = yes; then SC_TCL_GETHOSTBYNAME_R_TYPE fi ]) AC_DEFUN([SC_TCL_GETHOSTBYNAME_R_DECL], [AC_CHECK_DECLS(gethostbyname_r, [ tcl_cv_api_gethostbyname_r=yes],[tcl_cv_api_gethostbyname_r=no],[#include ]) ]) AC_DEFUN([SC_TCL_GETHOSTBYNAME_R_TYPE], [AC_CHECK_FUNC(gethostbyname_r, [ AC_CACHE_CHECK([for gethostbyname_r with 6 args], tcl_cv_api_gethostbyname_r_6, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ char *name; struct hostent *he, *res; char buffer[2048]; int buflen = 2048; int h_errnop; (void) gethostbyname_r(name, he, buffer, buflen, &res, &h_errnop); ]])],[tcl_cv_api_gethostbyname_r_6=yes],[tcl_cv_api_gethostbyname_r_6=no])]) tcl_ok=$tcl_cv_api_gethostbyname_r_6 if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETHOSTBYNAME_R_6, 1, [Define to 1 if gethostbyname_r takes 6 args.]) else AC_CACHE_CHECK([for gethostbyname_r with 5 args], tcl_cv_api_gethostbyname_r_5, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ char *name; struct hostent *he; char buffer[2048]; int buflen = 2048; int h_errnop; (void) gethostbyname_r(name, he, buffer, buflen, &h_errnop); ]])],[tcl_cv_api_gethostbyname_r_5=yes],[tcl_cv_api_gethostbyname_r_5=no])]) tcl_ok=$tcl_cv_api_gethostbyname_r_5 if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETHOSTBYNAME_R_5, 1, [Define to 1 if gethostbyname_r takes 5 args.]) else AC_CACHE_CHECK([for gethostbyname_r with 3 args], tcl_cv_api_gethostbyname_r_3, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ char *name; struct hostent *he; struct hostent_data data; (void) gethostbyname_r(name, he, &data); ]])],[tcl_cv_api_gethostbyname_r_3=yes],[tcl_cv_api_gethostbyname_r_3=no])]) tcl_ok=$tcl_cv_api_gethostbyname_r_3 if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETHOSTBYNAME_R_3, 1, [Define to 1 if gethostbyname_r takes 3 args.]) fi fi fi if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETHOSTBYNAME_R, 1, [Define to 1 if gethostbyname_r is available.]) fi ])]) #-------------------------------------------------------------------- # SC_TCL_GETPWUID_R # # Check if we have MT-safe variant of getpwuid() and if yes, # which one exactly. # # Arguments: # None # # Results: # # Might define the following vars: # HAVE_GETPWUID_R # HAVE_GETPWUID_R_4 # HAVE_GETPWUID_R_5 # #-------------------------------------------------------------------- AC_DEFUN([SC_TCL_GETPWUID_R], [AC_CHECK_FUNC(getpwuid_r, [ AC_CACHE_CHECK([for getpwuid_r with 5 args], tcl_cv_api_getpwuid_r_5, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ uid_t uid; struct passwd pw, *pwp; char buf[512]; int buflen = 512; (void) getpwuid_r(uid, &pw, buf, buflen, &pwp); ]])],[tcl_cv_api_getpwuid_r_5=yes],[tcl_cv_api_getpwuid_r_5=no])]) tcl_ok=$tcl_cv_api_getpwuid_r_5 if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETPWUID_R_5, 1, [Define to 1 if getpwuid_r takes 5 args.]) else AC_CACHE_CHECK([for getpwuid_r with 4 args], tcl_cv_api_getpwuid_r_4, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ uid_t uid; struct passwd pw; char buf[512]; int buflen = 512; (void)getpwnam_r(uid, &pw, buf, buflen); ]])],[tcl_cv_api_getpwuid_r_4=yes],[tcl_cv_api_getpwuid_r_4=no])]) tcl_ok=$tcl_cv_api_getpwuid_r_4 if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETPWUID_R_4, 1, [Define to 1 if getpwuid_r takes 4 args.]) fi fi if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETPWUID_R, 1, [Define to 1 if getpwuid_r is available.]) fi ])]) #-------------------------------------------------------------------- # SC_TCL_GETPWNAM_R # # Check if we have MT-safe variant of getpwnam() and if yes, # which one exactly. # # Arguments: # None # # Results: # # Might define the following vars: # HAVE_GETPWNAM_R # HAVE_GETPWNAM_R_4 # HAVE_GETPWNAM_R_5 # #-------------------------------------------------------------------- AC_DEFUN([SC_TCL_GETPWNAM_R], [AC_CHECK_FUNC(getpwnam_r, [ AC_CACHE_CHECK([for getpwnam_r with 5 args], tcl_cv_api_getpwnam_r_5, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ char *name; struct passwd pw, *pwp; char buf[512]; int buflen = 512; (void) getpwnam_r(name, &pw, buf, buflen, &pwp); ]])],[tcl_cv_api_getpwnam_r_5=yes],[tcl_cv_api_getpwnam_r_5=no])]) tcl_ok=$tcl_cv_api_getpwnam_r_5 if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETPWNAM_R_5, 1, [Define to 1 if getpwnam_r takes 5 args.]) else AC_CACHE_CHECK([for getpwnam_r with 4 args], tcl_cv_api_getpwnam_r_4, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ char *name; struct passwd pw; char buf[512]; int buflen = 512; (void)getpwnam_r(name, &pw, buf, buflen); ]])],[tcl_cv_api_getpwnam_r_4=yes],[tcl_cv_api_getpwnam_r_4=no])]) tcl_ok=$tcl_cv_api_getpwnam_r_4 if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETPWNAM_R_4, 1, [Define to 1 if getpwnam_r takes 4 args.]) fi fi if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETPWNAM_R, 1, [Define to 1 if getpwnam_r is available.]) fi ])]) #-------------------------------------------------------------------- # SC_TCL_GETGRGID_R # # Check if we have MT-safe variant of getgrgid() and if yes, # which one exactly. # # Arguments: # None # # Results: # # Might define the following vars: # HAVE_GETGRGID_R # HAVE_GETGRGID_R_4 # HAVE_GETGRGID_R_5 # #-------------------------------------------------------------------- AC_DEFUN([SC_TCL_GETGRGID_R], [AC_CHECK_FUNC(getgrgid_r, [ AC_CACHE_CHECK([for getgrgid_r with 5 args], tcl_cv_api_getgrgid_r_5, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ gid_t gid; struct group gr, *grp; char buf[512]; int buflen = 512; (void) getgrgid_r(gid, &gr, buf, buflen, &grp); ]])],[tcl_cv_api_getgrgid_r_5=yes],[tcl_cv_api_getgrgid_r_5=no])]) tcl_ok=$tcl_cv_api_getgrgid_r_5 if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETGRGID_R_5, 1, [Define to 1 if getgrgid_r takes 5 args.]) else AC_CACHE_CHECK([for getgrgid_r with 4 args], tcl_cv_api_getgrgid_r_4, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ gid_t gid; struct group gr; char buf[512]; int buflen = 512; (void)getgrgid_r(gid, &gr, buf, buflen); ]])],[tcl_cv_api_getgrgid_r_4=yes],[tcl_cv_api_getgrgid_r_4=no])]) tcl_ok=$tcl_cv_api_getgrgid_r_4 if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETGRGID_R_4, 1, [Define to 1 if getgrgid_r takes 4 args.]) fi fi if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETGRGID_R, 1, [Define to 1 if getgrgid_r is available.]) fi ])]) #-------------------------------------------------------------------- # SC_TCL_GETGRNAM_R # # Check if we have MT-safe variant of getgrnam() and if yes, # which one exactly. # # Arguments: # None # # Results: # # Might define the following vars: # HAVE_GETGRNAM_R # HAVE_GETGRNAM_R_4 # HAVE_GETGRNAM_R_5 # #-------------------------------------------------------------------- AC_DEFUN([SC_TCL_GETGRNAM_R], [AC_CHECK_FUNC(getgrnam_r, [ AC_CACHE_CHECK([for getgrnam_r with 5 args], tcl_cv_api_getgrnam_r_5, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ char *name; struct group gr, *grp; char buf[512]; int buflen = 512; (void) getgrnam_r(name, &gr, buf, buflen, &grp); ]])],[tcl_cv_api_getgrnam_r_5=yes],[tcl_cv_api_getgrnam_r_5=no])]) tcl_ok=$tcl_cv_api_getgrnam_r_5 if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETGRNAM_R_5, 1, [Define to 1 if getgrnam_r takes 5 args.]) else AC_CACHE_CHECK([for getgrnam_r with 4 args], tcl_cv_api_getgrnam_r_4, [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ char *name; struct group gr; char buf[512]; int buflen = 512; (void)getgrnam_r(name, &gr, buf, buflen); ]])],[tcl_cv_api_getgrnam_r_4=yes],[tcl_cv_api_getgrnam_r_4=no])]) tcl_ok=$tcl_cv_api_getgrnam_r_4 if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETGRNAM_R_4, 1, [Define to 1 if getgrnam_r takes 4 args.]) fi fi if test "$tcl_ok" = yes; then AC_DEFINE(HAVE_GETGRNAM_R, 1, [Define to 1 if getgrnam_r is available.]) fi ])]) AC_DEFUN([SC_TCL_IPV6],[ NEED_FAKE_RFC2553=0 AC_CHECK_FUNCS(getnameinfo getaddrinfo freeaddrinfo gai_strerror,,[NEED_FAKE_RFC2553=1]) AC_CHECK_TYPES([ struct addrinfo, struct in6_addr, struct sockaddr_in6, struct sockaddr_storage],,[NEED_FAKE_RFC2553=1],[[ #include #include #include #include ]]) if test "x$NEED_FAKE_RFC2553" = "x1"; then AC_DEFINE([NEED_FAKE_RFC2553], 1, [Use compat implementation of getaddrinfo() and friends]) AC_LIBOBJ([fake-rfc2553]) AC_CHECK_FUNC(strlcpy) fi ]) #------------------------------------------------------------------------ # SC_CC_FOR_BUILD # For cross compiles, locate a C compiler that can generate native binaries. # # Arguments: # none # # Results: # Substitutes the following vars: # CC_FOR_BUILD # EXEEXT_FOR_BUILD #------------------------------------------------------------------------ dnl Get a default for CC_FOR_BUILD to put into Makefile. AC_DEFUN([AX_CC_FOR_BUILD],[# Put a plausible default for CC_FOR_BUILD in Makefile. if test -z "$CC_FOR_BUILD"; then if test "x$cross_compiling" = "xno"; then CC_FOR_BUILD='$(CC)' else AC_MSG_CHECKING([for gcc]) AC_CACHE_VAL(ac_cv_path_cc, [ search_path=`echo ${PATH} | sed -e 's/:/ /g'` for dir in $search_path ; do for j in `ls -r $dir/gcc 2> /dev/null` \ `ls -r $dir/gcc 2> /dev/null` ; do if test x"$ac_cv_path_cc" = x ; then if test -f "$j" ; then ac_cv_path_cc=$j break fi fi done done ]) fi fi AC_SUBST(CC_FOR_BUILD) # Also set EXEEXT_FOR_BUILD. if test "x$cross_compiling" = "xno"; then EXEEXT_FOR_BUILD='$(EXEEXT)' OBJEXT_FOR_BUILD='$(OBJEXT)' else OBJEXT_FOR_BUILD='.no' AC_CACHE_CHECK([for build system executable suffix], bfd_cv_build_exeext, [rm -f conftest* echo 'int main () { return 0; }' > conftest.c bfd_cv_build_exeext= ${CC_FOR_BUILD} -o conftest conftest.c 1>&5 2>&5 for file in conftest.*; do case $file in *.c | *.o | *.obj | *.ilk | *.pdb) ;; *) bfd_cv_build_exeext=`echo $file | sed -e s/conftest//` ;; esac done rm -f conftest* test x"${bfd_cv_build_exeext}" = x && bfd_cv_build_exeext=no]) EXEEXT_FOR_BUILD="" test x"${bfd_cv_build_exeext}" != xno && EXEEXT_FOR_BUILD=${bfd_cv_build_exeext} fi AC_SUBST(EXEEXT_FOR_BUILD)])dnl AC_SUBST(OBJEXT_FOR_BUILD)])dnl ]) #------------------------------------------------------------------------ # SC_ZIPFS_SUPPORT # Locate a zip encoder installed on the system path, or none. # # Arguments: # none # # Results: # Substitutes the following vars: # MACHER_PROG # ZIP_PROG # ZIP_PROG_OPTIONS # ZIP_PROG_VFSSEARCH # ZIP_INSTALL_OBJS #------------------------------------------------------------------------ AC_DEFUN([SC_ZIPFS_SUPPORT], [ MACHER_PROG="" ZIP_PROG="" ZIP_PROG_OPTIONS="" ZIP_PROG_VFSSEARCH="" ZIP_INSTALL_OBJS="" AC_MSG_CHECKING([for macher]) AC_CACHE_VAL(ac_cv_path_macher, [ search_path=`echo ${PATH} | sed -e 's/:/ /g'` for dir in $search_path ; do for j in `ls -r $dir/macher 2> /dev/null` \ `ls -r $dir/macher 2> /dev/null` ; do if test x"$ac_cv_path_macher" = x ; then if test -f "$j" ; then ac_cv_path_macher=$j break fi fi done done ]) if test -f "$ac_cv_path_macher" ; then MACHER_PROG="$ac_cv_path_macher" AC_MSG_RESULT([$MACHER_PROG]) AC_MSG_RESULT([Found macher in environment]) fi AC_MSG_CHECKING([for zip]) AC_CACHE_VAL(ac_cv_path_zip, [ search_path=`echo ${PATH} | sed -e 's/:/ /g'` for dir in $search_path ; do for j in `ls -r $dir/zip 2> /dev/null` \ `ls -r $dir/zip 2> /dev/null` ; do if test x"$ac_cv_path_zip" = x ; then if test -f "$j" ; then ac_cv_path_zip=$j break fi fi done done ]) if test -f "$ac_cv_path_zip" ; then ZIP_PROG="$ac_cv_path_zip" AC_MSG_RESULT([$ZIP_PROG]) ZIP_PROG_OPTIONS="-rq" ZIP_PROG_VFSSEARCH="*" AC_MSG_RESULT([Found INFO Zip in environment]) # Use standard arguments for zip else # It is not an error if an installed version of Zip can't be located. # We can use the locally distributed minizip instead ZIP_PROG="./minizip${EXEEXT_FOR_BUILD}" ZIP_PROG_OPTIONS="-o -r" ZIP_PROG_VFSSEARCH="*" ZIP_INSTALL_OBJS="minizip${EXEEXT_FOR_BUILD}" AC_MSG_RESULT([No zip found on PATH. Building minizip]) fi AC_SUBST(MACHER_PROG) AC_SUBST(ZIP_PROG) AC_SUBST(ZIP_PROG_OPTIONS) AC_SUBST(ZIP_PROG_VFSSEARCH) AC_SUBST(ZIP_INSTALL_OBJS) ]) # Local Variables: # mode: autoconf # End: tcl9.0.1/unix/aclocal.m40000644000175000017500000000004014726623136014373 0ustar sergeisergeibuiltin(include,../unix/tcl.m4) tcl9.0.1/unix/tclConfig.sh.in0000644000175000017500000001347414726623136015423 0ustar sergeisergei# tclConfig.sh -- # # This shell script (for sh) is generated automatically by Tcl's # configure script. It will create shell variables for most of # the configuration options discovered by the configure script. # This script is intended to be included by the configure scripts # for Tcl extensions so that they don't have to figure this all # out for themselves. # # The information in this file is specific to a single platform. # Tcl's version number. TCL_VERSION='@TCL_VERSION@' TCL_MAJOR_VERSION='@TCL_MAJOR_VERSION@' TCL_MINOR_VERSION='@TCL_MINOR_VERSION@' TCL_PATCH_LEVEL='@TCL_PATCH_LEVEL@' # C compiler to use for compilation. TCL_CC='@CC@' # -D flags for use with the C compiler. TCL_DEFS='@DEFS@' # Default flags used in an optimized and debuggable build, respectively. TCL_CFLAGS_DEBUG='@CFLAGS_DEBUG@' TCL_CFLAGS_OPTIMIZE='@CFLAGS_OPTIMIZE@' # Default linker flags used in an optimized and debuggable build, respectively. TCL_LDFLAGS_DEBUG='@LDFLAGS_DEBUG@' TCL_LDFLAGS_OPTIMIZE='@LDFLAGS_OPTIMIZE@' # Flag, 1: we built a shared lib, 0 we didn't TCL_SHARED_BUILD=@TCL_SHARED_BUILD@ # The name of the Tcl library (may be either a .a file or a shared library): TCL_LIB_FILE='@TCL_LIB_FILE@' # The name of a zip containing the /library and /encodings (may be either a .zip file or a shared library): TCL_ZIP_FILE='@TCL_ZIP_FILE@' # Additional libraries to use when linking Tcl. TCL_LIBS='@TCL_LIBS@' # Top-level directory in which Tcl's platform-independent files are # installed. TCL_PREFIX='@prefix@' # Top-level directory in which Tcl's platform-specific files (e.g. # executables) are installed. TCL_EXEC_PREFIX='@exec_prefix@' # Flags to pass to cc when compiling the components of a shared library: TCL_SHLIB_CFLAGS='@SHLIB_CFLAGS@' # Flags to pass to cc to get warning messages TCL_CFLAGS_WARNING='@CFLAGS_WARNING@' # Extra flags to pass to cc: TCL_EXTRA_CFLAGS='@CFLAGS@' # Base command to use for combining object files into a shared library: TCL_SHLIB_LD='@SHLIB_LD@' # Base command to use for combining object files into a static library: TCL_STLIB_LD='@STLIB_LD@' # Either '$LIBS' (if dependent libraries should be included when linking # shared libraries) or an empty string. See Tcl's configure.ac for more # explanation. TCL_SHLIB_LD_LIBS='@SHLIB_LD_LIBS@' # Suffix to use for the name of a shared library. TCL_SHLIB_SUFFIX='@SHLIB_SUFFIX@' # Library file(s) to include in tclsh and other base applications # in order to provide facilities needed by DLOBJ above. TCL_DL_LIBS='@DL_LIBS@' # Flags to pass to the compiler when linking object files into # an executable tclsh or tcltest binary. TCL_LD_FLAGS='@LDFLAGS@' # Flags to pass to cc/ld, such as "-R /usr/local/tcl/lib", that tell the # run-time dynamic linker where to look for shared libraries such as # libtcl.so. Used when linking applications. Only works if there # is a variable "LIB_RUNTIME_DIR" defined in the Makefile. TCL_CC_SEARCH_FLAGS='@CC_SEARCH_FLAGS@' TCL_LD_SEARCH_FLAGS='@LD_SEARCH_FLAGS@' # Additional object files linked with Tcl to provide compatibility # with standard facilities from ANSI C or POSIX. TCL_COMPAT_OBJS='@LIBOBJS@' # Name of the ranlib program to use. TCL_RANLIB='@RANLIB@' # -l flag to pass to the linker to pick up the Tcl library TCL_LIB_FLAG='@TCL_LIB_FLAG@' # String to pass to linker to pick up the Tcl library from its # build directory. TCL_BUILD_LIB_SPEC='@TCL_BUILD_LIB_SPEC@' # String to pass to linker to pick up the Tcl library from its # installed directory. TCL_LIB_SPEC='@TCL_LIB_SPEC@' # String to pass to the compiler so that an extension can # find installed Tcl headers. TCL_INCLUDE_SPEC='@TCL_INCLUDE_SPEC@' # Indicates whether a version numbers should be used in -l switches # ("ok" means it's safe to use switches like -ltcl7.5; "nodots" means # use switches like -ltcl75). SunOS and FreeBSD require "nodots", for # example. TCL_LIB_VERSIONS_OK='@TCL_LIB_VERSIONS_OK@' # String that can be evaluated to generate the part of a shared library # name that comes after the "libxxx" (includes version number, if any, # extension, and anything else needed). May depend on the variables # VERSION and SHLIB_SUFFIX. On most UNIX systems this is # ${VERSION}${SHLIB_SUFFIX}. TCL_SHARED_LIB_SUFFIX='@CFG_TCL_SHARED_LIB_SUFFIX@' # String that can be evaluated to generate the part of an unshared library # name that comes after the "libxxx" (includes version number, if any, # extension, and anything else needed). May depend on the variable # VERSION. On most UNIX systems this is ${VERSION}.a. TCL_UNSHARED_LIB_SUFFIX='@CFG_TCL_UNSHARED_LIB_SUFFIX@' # Location of the top-level source directory from which Tcl was built. # This is the directory that contains a README file as well as # subdirectories such as generic, unix, etc. If Tcl was compiled in a # different place than the directory containing the source files, this # points to the location of the sources, not the location where Tcl was # compiled. TCL_SRC_DIR='@TCL_SRC_DIR@' # List of standard directories in which to look for packages during # "package require" commands. Contains the "prefix" directory plus also # the "exec_prefix" directory, if it is different. TCL_PACKAGE_PATH='@TCL_PACKAGE_PATH@' # Tcl supports stub. TCL_SUPPORTS_STUBS=1 # The name of the Tcl stub library (.a): TCL_STUB_LIB_FILE='@TCL_STUB_LIB_FILE@' # -l flag to pass to the linker to pick up the Tcl stub library TCL_STUB_LIB_FLAG='@TCL_STUB_LIB_FLAG@' # String to pass to linker to pick up the Tcl stub library from its # build directory. TCL_BUILD_STUB_LIB_SPEC='@TCL_BUILD_STUB_LIB_SPEC@' # String to pass to linker to pick up the Tcl stub library from its # installed directory. TCL_STUB_LIB_SPEC='@TCL_STUB_LIB_SPEC@' # Path to the Tcl stub library in the build directory. TCL_BUILD_STUB_LIB_PATH='@TCL_BUILD_STUB_LIB_PATH@' # Path to the Tcl stub library in the install directory. TCL_STUB_LIB_PATH='@TCL_STUB_LIB_PATH@' tcl9.0.1/unix/tclooConfig.sh0000644000175000017500000000140314726623136015341 0ustar sergeisergei# tclooConfig.sh -- # # This shell script (for sh) is generated automatically by TclOO's configure # script, or would be except it has no values that we substitute. It will # create shell variables for most of the configuration options discovered by # the configure script. This script is intended to be included by TEA-based # configure scripts for TclOO extensions so that they don't have to figure # this all out for themselves. # # The information in this file is specific to a single platform. # These are mostly empty because no special steps are ever needed from Tcl 8.6 # onwards; all libraries and include files are just part of Tcl. TCLOO_LIB_SPEC="" TCLOO_STUB_LIB_SPEC="" TCLOO_INCLUDE_SPEC="" TCLOO_PRIVATE_INCLUDE_SPEC="" TCLOO_CFLAGS="" TCLOO_VERSION=1.3 tcl9.0.1/unix/install-sh0000755000175000017500000003577614726623136014567 0ustar sergeisergei#!/bin/sh # install - install a program, script, or datafile scriptversion=2020-11-14.01; # UTC # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the # following copyright and license. # # Copyright (C) 1994 X Consortium # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of the X Consortium shall not # be used in advertising or otherwise to promote the sale, use or other deal- # ings in this Software without prior written authorization from the X Consor- # tium. # # # FSF changes to this file are in the public domain. # # Calling this script install-sh is preferred over install.sh, to prevent # 'make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. tab=' ' nl=' ' IFS=" $tab$nl" # Set DOITPROG to "echo" to test this script. doit=${DOITPROG-} doit_exec=${doit:-exec} # Put in absolute file names if you don't have them in your path; # or use environment vars. chgrpprog=${CHGRPPROG-chgrp} chmodprog=${CHMODPROG-chmod} chownprog=${CHOWNPROG-chown} cmpprog=${CMPPROG-cmp} cpprog=${CPPROG-cp} mkdirprog=${MKDIRPROG-mkdir} mvprog=${MVPROG-mv} rmprog=${RMPROG-rm} stripprog=${STRIPPROG-strip} posix_mkdir= # Desired mode of installed file. mode=0755 # Create dirs (including intermediate dirs) using mode 755. # This is like GNU 'install' as of coreutils 8.32 (2020). mkdir_umask=22 backupsuffix= chgrpcmd= chmodcmd=$chmodprog chowncmd= mvcmd=$mvprog rmcmd="$rmprog -f" stripcmd= src= dst= dir_arg= dst_arg= copy_on_change=false is_target_a_directory=possibly usage="\ Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE or: $0 [OPTION]... SRCFILES... DIRECTORY or: $0 [OPTION]... -t DIRECTORY SRCFILES... or: $0 [OPTION]... -d DIRECTORIES... In the 1st form, copy SRCFILE to DSTFILE. In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. In the 4th, create DIRECTORIES. Options: --help display this help and exit. --version display version info and exit. -c (ignored) -C install only if different (preserve data modification time) -d create directories instead of installing files. -g GROUP $chgrpprog installed files to GROUP. -m MODE $chmodprog installed files to MODE. -o USER $chownprog installed files to USER. -p pass -p to $cpprog. -s $stripprog installed files. -S SUFFIX attempt to back up existing files, with suffix SUFFIX. -t DIRECTORY install into DIRECTORY. -T report an error if DSTFILE is a directory. Environment variables override the default commands: CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG By default, rm is invoked with -f; when overridden with RMPROG, it's up to you to specify -f if you want it. If -S is not specified, no backups are attempted. Email bug reports to bug-automake@gnu.org. Automake home page: https://www.gnu.org/software/automake/ " while test $# -ne 0; do case $1 in -c) ;; -C) copy_on_change=true;; -d) dir_arg=true;; -g) chgrpcmd="$chgrpprog $2" shift;; --help) echo "$usage"; exit $?;; -m) mode=$2 case $mode in *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) echo "$0: invalid mode: $mode" >&2 exit 1;; esac shift;; -o) chowncmd="$chownprog $2" shift;; -p) cpprog="$cpprog -p";; -s) stripcmd=$stripprog;; -S) backupsuffix="$2" shift;; -t) is_target_a_directory=always dst_arg=$2 # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac shift;; -T) is_target_a_directory=never;; --version) echo "$0 $scriptversion"; exit $?;; --) shift break;; -*) echo "$0: invalid option: $1" >&2 exit 1;; *) break;; esac shift done # We allow the use of options -d and -T together, by making -d # take the precedence; this is for compatibility with GNU install. if test -n "$dir_arg"; then if test -n "$dst_arg"; then echo "$0: target directory not allowed when installing a directory." >&2 exit 1 fi fi if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then # When -d is used, all remaining arguments are directories to create. # When -t is used, the destination is already specified. # Otherwise, the last argument is the destination. Remove it from $@. for arg do if test -n "$dst_arg"; then # $@ is not empty: it contains at least $arg. set fnord "$@" "$dst_arg" shift # fnord fi shift # arg dst_arg=$arg # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac done fi if test $# -eq 0; then if test -z "$dir_arg"; then echo "$0: no input file specified." >&2 exit 1 fi # It's OK to call 'install-sh -d' without argument. # This can happen when creating conditional directories. exit 0 fi if test -z "$dir_arg"; then if test $# -gt 1 || test "$is_target_a_directory" = always; then if test ! -d "$dst_arg"; then echo "$0: $dst_arg: Is not a directory." >&2 exit 1 fi fi fi if test -z "$dir_arg"; then do_exit='(exit $ret); exit $ret' trap "ret=129; $do_exit" 1 trap "ret=130; $do_exit" 2 trap "ret=141; $do_exit" 13 trap "ret=143; $do_exit" 15 # Set umask so as not to create temps with too-generous modes. # However, 'strip' requires both read and write access to temps. case $mode in # Optimize common cases. *644) cp_umask=133;; *755) cp_umask=22;; *[0-7]) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw='% 200' fi cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; *) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw=,u+rw fi cp_umask=$mode$u_plus_rw;; esac fi for src do # Protect names problematic for 'test' and other utilities. case $src in -* | [=\(\)!]) src=./$src;; esac if test -n "$dir_arg"; then dst=$src dstdir=$dst test -d "$dstdir" dstdir_status=$? # Don't chown directories that already exist. if test $dstdir_status = 0; then chowncmd="" fi else # Waiting for this to be detected by the "$cpprog $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if test ! -f "$src" && test ! -d "$src"; then echo "$0: $src does not exist." >&2 exit 1 fi if test -z "$dst_arg"; then echo "$0: no destination specified." >&2 exit 1 fi dst=$dst_arg # If destination is a directory, append the input filename. if test -d "$dst"; then if test "$is_target_a_directory" = never; then echo "$0: $dst_arg: Is a directory" >&2 exit 1 fi dstdir=$dst dstbase=`basename "$src"` case $dst in */) dst=$dst$dstbase;; *) dst=$dst/$dstbase;; esac dstdir_status=0 else dstdir=`dirname "$dst"` test -d "$dstdir" dstdir_status=$? fi fi case $dstdir in */) dstdirslash=$dstdir;; *) dstdirslash=$dstdir/;; esac obsolete_mkdir_used=false if test $dstdir_status != 0; then case $posix_mkdir in '') # With -d, create the new directory with the user-specified mode. # Otherwise, rely on $mkdir_umask. if test -n "$dir_arg"; then mkdir_mode=-m$mode else mkdir_mode= fi posix_mkdir=false # The $RANDOM variable is not portable (e.g., dash). Use it # here however when possible just to lower collision chance. tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ trap ' ret=$? rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null exit $ret ' 0 # Because "mkdir -p" follows existing symlinks and we likely work # directly in world-writeable /tmp, make sure that the '$tmpdir' # directory is successfully created first before we actually test # 'mkdir -p'. if (umask $mkdir_umask && $mkdirprog $mkdir_mode "$tmpdir" && exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 then if test -z "$dir_arg" || { # Check for POSIX incompatibilities with -m. # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or # other-writable bit of parent directory when it shouldn't. # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. test_tmpdir="$tmpdir/a" ls_ld_tmpdir=`ls -ld "$test_tmpdir"` case $ls_ld_tmpdir in d????-?r-*) different_mode=700;; d????-?--*) different_mode=755;; *) false;; esac && $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" } } then posix_mkdir=: fi rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" else # Remove any dirs left behind by ancient mkdir implementations. rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null fi trap '' 0;; esac if $posix_mkdir && ( umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" ) then : else # mkdir does not conform to POSIX, # or it failed possibly due to a race condition. Create the # directory the slow way, step by step, checking for races as we go. case $dstdir in /*) prefix='/';; [-=\(\)!]*) prefix='./';; *) prefix='';; esac oIFS=$IFS IFS=/ set -f set fnord $dstdir shift set +f IFS=$oIFS prefixes= for d do test X"$d" = X && continue prefix=$prefix$d if test -d "$prefix"; then prefixes= else if $posix_mkdir; then (umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break # Don't fail if two instances are running concurrently. test -d "$prefix" || exit 1 else case $prefix in *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; *) qprefix=$prefix;; esac prefixes="$prefixes '$qprefix'" fi fi prefix=$prefix/ done if test -n "$prefixes"; then # Don't fail if two instances are running concurrently. (umask $mkdir_umask && eval "\$doit_exec \$mkdirprog $prefixes") || test -d "$dstdir" || exit 1 obsolete_mkdir_used=true fi fi fi if test -n "$dir_arg"; then { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 else # Make a couple of temp file names in the proper directory. dsttmp=${dstdirslash}_inst.$$_ rmtmp=${dstdirslash}_rm.$$_ # Trap to clean up those temp files at exit. trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 # Copy the file name to the temp name. (umask $cp_umask && { test -z "$stripcmd" || { # Create $dsttmp read-write so that cp doesn't create it read-only, # which would cause strip to fail. if test -z "$doit"; then : >"$dsttmp" # No need to fork-exec 'touch'. else $doit touch "$dsttmp" fi } } && $doit_exec $cpprog "$src" "$dsttmp") && # and set any options; do chmod last to preserve setuid bits. # # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $cpprog $src $dsttmp" command. # { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && # If -C, don't bother to copy if it wouldn't change the file. if $copy_on_change && old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && set -f && set X $old && old=:$2:$4:$5:$6 && set X $new && new=:$2:$4:$5:$6 && set +f && test "$old" = "$new" && $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 then rm -f "$dsttmp" else # If $backupsuffix is set, and the file being installed # already exists, attempt a backup. Don't worry if it fails, # e.g., if mv doesn't support -f. if test -n "$backupsuffix" && test -f "$dst"; then $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null fi # Rename the file to the real destination. $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || # The rename failed, perhaps because mv can't rename something else # to itself, or perhaps because mv is so ancient that it does not # support -f. { # Now remove or move aside any old file at destination location. # We try this two ways since rm can't unlink itself on some # systems and the destination file might be busy for other # reasons. In this case, the final cleanup might fail but the new # file should still install successfully. { test ! -f "$dst" || $doit $rmcmd "$dst" 2>/dev/null || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && { $doit $rmcmd "$rmtmp" 2>/dev/null; :; } } || { echo "$0: cannot unlink or rename $dst" >&2 (exit 1); exit 1 } } && # Now rename the file to the real destination. $doit $mvcmd "$dsttmp" "$dst" } fi || exit 1 trap '' 0 fi done # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: tcl9.0.1/unix/README0000644000175000017500000002017014726623136013421 0ustar sergeisergeiTcl UNIX README --------------- This is the directory where you configure, compile, test, and install UNIX versions of Tcl. This directory also contains source files for Tcl that are specific to UNIX. Some of the files in this directory are used on the PC or MacOSX platform too, but they all depend on UNIX (POSIX/ANSI C) interfaces and some of them only make sense under UNIX. Updated forms of the information found in this file is available at: https://www.tcl-lang.org/doc/howto/compile.html#unix For information on platforms where Tcl is known to compile, along with any porting notes for getting it to work on those platforms, see: https://www.tcl-lang.org/software/tcltk/platforms.html The rest of this file contains instructions on how to do this. The release should compile and run either "out of the box" or with trivial changes on any UNIX-like system that approximates POSIX, BSD, or System V. We know that it runs on workstations from Sun, H-P, DEC, IBM, and SGI, as well as PCs running Linux, BSDI, and SCO UNIX. To compile for a PC running Windows, see the README file in the directory ../win. To compile for MacOSX, see the README file in the directory ../macosx. How To Compile And Install Tcl: ------------------------------- (a) If you have already compiled Tcl once in this directory and are now preparing to compile again in the same directory but for a different platform, or if you have applied patches, type "make distclean" to discard all the configuration information computed previously. (b) If you need to reconfigure because you changed any of the .in or .m4 files, you will need to run autoconf to create a new ./configure script. Most users will NOT need to do this since a configure script is already provided. (in the tcl/unix directory) autoconf (c) Type "./configure". This runs a configuration script created by GNU autoconf, which configures Tcl for your system and creates a Makefile. The configure script allows you to customize the Tcl configuration for your site; for details on how you can do this, type "./configure --help" or refer to the autoconf documentation (not included here). Tcl's "configure" supports the following special switches in addition to the standard ones: --disable-load If this switch is specified then Tcl will configure itself not to allow dynamic loading, even if your system appears to support it. Normally you can leave this switch out and Tcl will build itself for dynamic loading if your system supports it. --disable-dll-unloading Disables support for the [unload] command even on platforms that can support it. Meaningless when Tcl is compiled with --disable-load. --enable-shared If this switch is specified, Tcl will compile itself as a shared library if it can figure out how to do that on this platform. This is the default on platforms where we know how to build shared libraries. --disable-shared If this switch is specified, Tcl will compile itself as a static library. --enable-symbols Build with debugging symbols. By default standard debugging symbols are used. You can specify the value "mem" to include TCL_MEM_DEBUG memory debugging, "compile" to include TCL_COMPILE_DEBUG debugging, or "all" to enable all internal debugging. --disable-symbols Build without debugging symbols --enable-64bit Enable 64bit support (where applicable) --disable-64bit Disable 64bit support (where applicable) --enable-64bit-vis Enable 64bit Sparc VIS support --disable-64bit-vis Disable 64bit Sparc VIS support --enable-langinfo Allows use of modern nl_langinfo check for better localization support. This is on by default on platforms where nl_langinfo is found. --disable-langinfo Specifically disables use of nl_langinfo. --enable-man-symlinks Use symlinks for linking the manpages that should be reachable under several names. --enable-man-suffix[=STRING] Append STRING to the names of installed manual pages (prior to applying compression, if that is also enabled). If STRING is omitted, defaults to 'tcl'. --enable-man-compression=PROG Compress the manpages using PROG. --enable-dtrace Enable tcl DTrace provider (if DTrace is available on the platform), c.f. tclDTrace.d for descriptions of the probes made available, see https://wiki.tcl-lang.org/page/DTrace for more details --with-encoding=ENCODING Specifies the encoding for compile-time configuration values. Defaults to utf-8, which is also sufficient for ASCII. --with-tzdata=FLAG Specifies whether to install timezone data. By default, the configure script tries to detect whether a usable timezone database is present on the system already. Mac OS X only (i.e. completely unsupported on other platforms): --enable-framework Package Tcl as a framework. --disable-corefoundation Disable use of CoreFoundation API and revert to standard select based notifier, required when using naked fork (i.e. not followed by execve). Note: by default gcc will be used if it can be located on the PATH. If you want to use cc instead of gcc, set the CC environment variable to "cc" before running configure. It is not safe to edit the Makefile to use gcc after configure is run. Also note that you should use the same compiler when building extensions. Note: be sure to use only absolute path names (those starting with "/") in the --prefix and --exec-prefix options. (d) Type "make". This will create a library archive called "libtcl.a" or "libtcl.so" and an interpreter application called "tclsh" that allows you to type Tcl commands interactively or execute script files. It will also create a stub library archive "libtclstub.a" that developers may link against other C code to produce loadable extensions for Tcl. (e) If the make fails then you'll have to personalize the Makefile for your site or possibly modify the distribution in other ways. First check the porting Web page above to see if there are hints for compiling on your system. If you need to modify Makefile, there are comments at the beginning of it that describe the things you might want to change and how to change them. (f) Type "make install" to install Tcl binaries and script files in standard places. You'll need write permission on the installation directories to do this. The installation directories are determined by the "configure" script and may be specified with the standard --prefix and --exec-prefix options to "configure". See the Makefile for information on what directories were chosen; you can override these choices by modifying the "prefix" and "exec_prefix" variables in the Makefile. The installed binaries have embedded within them path values relative to the install directory. If you change your mind about where Tcl should be installed, start this procedure over again from step (a) so that the path embedded in the binaries agrees with the install location. (g) At this point you can play with Tcl by running the installed "tclsh" executable, or via the "make shell" target, and typing Tcl commands at the interactive prompt. If you have trouble compiling Tcl, see the URL noted above about working platforms. It contains information that people have provided about changes they had to make to compile Tcl in various environments. We're also interested in hearing how to change the configuration setup so that Tcl compiles on additional platforms "out of the box". Test suite ---------- There is a relatively complete test suite for all of the Tcl core in the subdirectory "tests". To use it just type "make test" in this directory. You should then see a printout of the test files processed. If any errors occur, you'll see a much more substantial printout for each error. See the README file in the "tests" directory for more information on the test suite. Note: don't run the tests as superuser: this will cause several of them to fail. If a test is failing consistently, please send us a bug report with as much detail as you can manage to our tracker: https://core.tcl-lang.org/tcl/reportlist tcl9.0.1/unix/tcl.spec0000644000175000017500000000251014726623136014175 0ustar sergeisergei# This file is the basis for a binary Tcl RPM for Linux. %{!?directory:%define directory /usr/local} Name: tcl Summary: Tcl scripting language development environment Version: 9.0.1 Release: 2 License: BSD Group: Development/Languages Source: http://prdownloads.sourceforge.net/tcl/tcl%{version}-src.tar.gz URL: https://www.tcl-lang.org/ Buildroot: /var/tmp/%{name}%{version} %description The Tcl (Tool Command Language) provides a powerful platform for creating integration applications that tie together diverse applications, protocols, devices, and frameworks. When paired with the Tk toolkit, Tcl provides the fastest and most powerful way to create GUI applications that run on PCs, Unix, and Mac OS X. Tcl can also be used for a variety of web-related tasks and for creating powerful command languages for applications. %prep %setup -q -n %{name}%{version} %build cd unix CFLAGS="%optflags" ./configure \ --prefix=%{directory} \ --exec-prefix=%{directory} \ --libdir=%{directory}/%{_lib} make %install cd unix make INSTALL_ROOT=%{buildroot} install %clean rm -rf %buildroot %files %defattr(-,root,root) %if %{_lib} != lib %{directory}/%{_lib} %endif %{directory}/lib %{directory}/bin %{directory}/include %{directory}/man/man1 %{directory}/man/man3 %{directory}/man/mann tcl9.0.1/unix/installManPage0000755000175000017500000000610114726623136015364 0ustar sergeisergei#!/bin/sh ######################################################################## ### Parse Options ### Gzip=: Sym="" Loc="" Gz="" Suffix="" while true; do case $1 in -s | --symlinks ) Sym="-s " ;; -z | --compress ) Gzip=$2; shift ;; -e | --extension ) Gz=$2; shift ;; -x | --suffix ) Suffix=$2; shift ;; -*) cat < file dir" exit 1 fi ######################################################################## ### Parse Required Arguments ### ManPage=$1 Dir=$2 if test -f $ManPage ; then : ; else echo "source manual page file must exist" exit 1 fi if test -d "$Dir" ; then : ; else echo "target directory must exist" exit 1 fi test -z "$Sym" && Loc="$Dir/" ######################################################################## ### Extract Target Names from Manual Page ### # A sed script to parse the alternative names out of a man page. # # Backslashes are trippled in the sed script, because it is in # backticks which doesn't pass backslashes literally. # Names=`sed -n ' # Look for a line that starts with .SH NAME /^\.SH NAME/,/^\./{ /^\./!{ # Remove all commas... s/,//g # ... and backslash-escaped spaces. s/\\\ //g /\\\-.*/{ # Delete from \- to the end of line s/ \\\-.*// h s/.*/./ x } # Convert all non-space non-alphanum sequences # to single underscores. s/[^ A-Za-z0-9][^ A-Za-z0-9]*/_/g p g /^\./{ q } } }' $ManPage` if test -z "$Names" ; then echo "warning: no target names found in $ManPage" fi ######################################################################## ### Remaining Set Up ### case $ManPage in *.1) Section=1 ;; *.3) Section=3 ;; *.n) Section=n ;; *) echo "unknown section for $ManPage" exit 2 ;; esac Name=`basename $ManPage .$Section` SrcDir=`dirname $ManPage` ######################################################################## ### Process Page to Create Target Pages ### Specials="DString Thread Notifier RegExp library packagens pkgMkIndex safesock FindPhoto FontId MeasureChar" for n in $Specials; do if [ "$Name" = "$n" ] ; then Names="$n $Names" fi done First="" for Target in $Names; do Target=$Target.$Section$Suffix rm -f "$Dir/$Target" "$Dir/$Target.*" if test -z "$First" ; then First=$Target sed -e "/man\.macros/r $SrcDir/man.macros" -e "/man\.macros/d" \ $ManPage > "$Dir/$First" chmod 644 "$Dir/$First" $Gzip "$Dir/$First" else ln $Sym"$Loc$First$Gz" "$Dir/$Target$Gz" fi done ######################################################################## exit 0 tcl9.0.1/unix/tclConfig.h.in0000644000175000017500000003217514731057471015236 0ustar sergeisergei/* ../unix/tclConfig.h.in. Generated from configure.ac by autoheader. */ #ifndef _TCLCONFIG #define _TCLCONFIG /* Is gettimeofday() actually declared in ? */ #undef GETTOD_NOT_DECLARED /* Define to 1 if the system has the type 'blkcnt_t'. */ #undef HAVE_BLKCNT_T /* Defined when compiler supports casting to union type. */ #undef HAVE_CAST_TO_UNION /* Define to 1 if you have the 'cfmakeraw' function. */ #undef HAVE_CFMAKERAW /* Define to 1 if you have the 'chflags' function. */ #undef HAVE_CHFLAGS /* Define to 1 if you have the 'copyfile' function. */ #undef HAVE_COPYFILE /* Define to 1 if you have the header file. */ #undef HAVE_COPYFILE_H /* Do we have access to Darwin CoreFoundation.framework? */ #undef HAVE_COREFOUNDATION /* Is the cpuid instruction usable? */ #undef HAVE_CPUID /* Define to 1 if you have the declaration of 'gethostbyaddr_r', and to 0 if you don't. */ #undef HAVE_DECL_GETHOSTBYADDR_R /* Define to 1 if you have the declaration of 'gethostbyname_r', and to 0 if you don't. */ #undef HAVE_DECL_GETHOSTBYNAME_R /* Define to 1 if you have the declaration of 'PTHREAD_MUTEX_RECURSIVE', and to 0 if you don't. */ #undef HAVE_DECL_PTHREAD_MUTEX_RECURSIVE /* Is 'DIR64' in ? */ #undef HAVE_DIR64 /* Is eventfd(2) supported? */ #undef HAVE_EVENTFD /* Define to 1 if you have the 'freeaddrinfo' function. */ #undef HAVE_FREEADDRINFO /* Do we have fts functions? */ #undef HAVE_FTS /* Define to 1 if you have the 'gai_strerror' function. */ #undef HAVE_GAI_STRERROR /* Define to 1 if you have the 'getaddrinfo' function. */ #undef HAVE_GETADDRINFO /* Define to 1 if you have the 'getattrlist' function. */ #undef HAVE_GETATTRLIST /* Define to 1 if you have the 'getcwd' function. */ #undef HAVE_GETCWD /* Define to 1 if getgrgid_r is available. */ #undef HAVE_GETGRGID_R /* Define to 1 if getgrgid_r takes 4 args. */ #undef HAVE_GETGRGID_R_4 /* Define to 1 if getgrgid_r takes 5 args. */ #undef HAVE_GETGRGID_R_5 /* Define to 1 if getgrnam_r is available. */ #undef HAVE_GETGRNAM_R /* Define to 1 if getgrnam_r takes 4 args. */ #undef HAVE_GETGRNAM_R_4 /* Define to 1 if getgrnam_r takes 5 args. */ #undef HAVE_GETGRNAM_R_5 /* Define to 1 if gethostbyaddr_r is available. */ #undef HAVE_GETHOSTBYADDR_R /* Define to 1 if gethostbyaddr_r takes 7 args. */ #undef HAVE_GETHOSTBYADDR_R_7 /* Define to 1 if gethostbyaddr_r takes 8 args. */ #undef HAVE_GETHOSTBYADDR_R_8 /* Define to 1 if gethostbyname_r is available. */ #undef HAVE_GETHOSTBYNAME_R /* Define to 1 if gethostbyname_r takes 3 args. */ #undef HAVE_GETHOSTBYNAME_R_3 /* Define to 1 if gethostbyname_r takes 5 args. */ #undef HAVE_GETHOSTBYNAME_R_5 /* Define to 1 if gethostbyname_r takes 6 args. */ #undef HAVE_GETHOSTBYNAME_R_6 /* Define to 1 if you have the 'getnameinfo' function. */ #undef HAVE_GETNAMEINFO /* Define to 1 if getpwnam_r is available. */ #undef HAVE_GETPWNAM_R /* Define to 1 if getpwnam_r takes 4 args. */ #undef HAVE_GETPWNAM_R_4 /* Define to 1 if getpwnam_r takes 5 args. */ #undef HAVE_GETPWNAM_R_5 /* Define to 1 if getpwuid_r is available. */ #undef HAVE_GETPWUID_R /* Define to 1 if getpwuid_r takes 4 args. */ #undef HAVE_GETPWUID_R_4 /* Define to 1 if getpwuid_r takes 5 args. */ #undef HAVE_GETPWUID_R_5 /* Define to 1 if you have the 'gmtime_r' function. */ #undef HAVE_GMTIME_R /* Compiler support for module scope symbols */ #undef HAVE_HIDDEN /* Define to 1 if the system has the type 'intptr_t'. */ #undef HAVE_INTPTR_T /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H /* Do we have nl_langinfo()? */ #undef HAVE_LANGINFO /* Define to 1 if you have the header file. */ #undef HAVE_LIBKERN_OSATOMIC_H /* Define to 1 if you have the 'localtime_r' function. */ #undef HAVE_LOCALTIME_R /* Define to 1 if you have the 'lseek64' function. */ #undef HAVE_LSEEK64 /* Define to 1 if you have the 'mkstemp' function. */ #undef HAVE_MKSTEMP /* Define to 1 if you have the 'mkstemps' function. */ #undef HAVE_MKSTEMPS /* Do we have MT-safe gethostbyaddr() ? */ #undef HAVE_MTSAFE_GETHOSTBYADDR /* Do we have MT-safe gethostbyname() ? */ #undef HAVE_MTSAFE_GETHOSTBYNAME /* Do we have ? */ #undef HAVE_NET_ERRNO_H /* Define to 1 if you have the 'open64' function. */ #undef HAVE_OPEN64 /* Define to 1 if you have the 'OSSpinLockLock' function. */ #undef HAVE_OSSPINLOCKLOCK /* Define to 1 if you have the 'posix_spawnattr_setflags' function. */ #undef HAVE_POSIX_SPAWNATTR_SETFLAGS /* Define to 1 if you have the 'posix_spawnp' function. */ #undef HAVE_POSIX_SPAWNP /* Define to 1 if you have the 'posix_spawn_file_actions_adddup2' function. */ #undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDDUP2 /* Should we use pselect()? */ #undef HAVE_PSELECT /* Define to 1 if you have the 'pthread_atfork' function. */ #undef HAVE_PTHREAD_ATFORK /* Define to 1 if you have the 'pthread_attr_setstacksize' function. */ #undef HAVE_PTHREAD_ATTR_SETSTACKSIZE /* Does putenv() copy strings or incorporate them by reference? */ #undef HAVE_PUTENV_THAT_COPIES /* Are characters signed? */ #undef HAVE_SIGNED_CHAR /* Do we have ? */ #undef HAVE_STDBOOL_H /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H /* Define to 1 if you have the header file. */ #undef HAVE_STDIO_H /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H /* Define to 1 if you have the header file. */ #undef HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H /* Define to 1 if the system has the type 'struct addrinfo'. */ #undef HAVE_STRUCT_ADDRINFO /* Is 'struct dirent64' in ? */ #undef HAVE_STRUCT_DIRENT64 /* Define to 1 if the system has the type 'struct in6_addr'. */ #undef HAVE_STRUCT_IN6_ADDR /* Define to 1 if the system has the type 'struct sockaddr_in6'. */ #undef HAVE_STRUCT_SOCKADDR_IN6 /* Define to 1 if the system has the type 'struct sockaddr_storage'. */ #undef HAVE_STRUCT_SOCKADDR_STORAGE /* Define to 1 if 'st_blksize' is a member of 'struct stat'. */ #undef HAVE_STRUCT_STAT_ST_BLKSIZE /* Define to 1 if 'st_blocks' is a member of 'struct stat'. */ #undef HAVE_STRUCT_STAT_ST_BLOCKS /* Define to 1 if 'st_rdev' is a member of 'struct stat'. */ #undef HAVE_STRUCT_STAT_ST_RDEV /* Define to 1 if you have the header file. */ #undef HAVE_SYS_EPOLL_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_EVENTFD_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_EVENT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_FILIO_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_IOCTL_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_MODEM_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_PARAM_H /* Should we include ? */ #undef HAVE_SYS_SELECT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TIME_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H /* Define to 1 if you have the header file. */ #undef HAVE_TERMIOS_H /* Should we use the global timezone variable? */ #undef HAVE_TIMEZONE_VAR /* Should we use the tm_gmtoff field of struct tm? */ #undef HAVE_TM_GMTOFF /* Should we use the tm_tzadj field of struct tm? */ #undef HAVE_TM_TZADJ /* Is off64_t in ? */ #undef HAVE_TYPE_OFF64_T /* Define to 1 if the system has the type 'uintptr_t'. */ #undef HAVE_UINTPTR_T /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H /* Define to 1 if you have the 'vfork' function. */ #undef HAVE_VFORK /* Define to 1 if you have the 'waitpid' function. */ #undef HAVE_WAITPID /* Is weak import available? */ #undef HAVE_WEAK_IMPORT /* Is this a Mac I see before me? */ #undef MAC_OSX_TCL /* No Compiler support for module scope symbols */ #undef MODULE_SCOPE /* Default libtommath precision. */ #undef MP_PREC /* Is no debugging enabled? */ #undef NDEBUG /* Use compat implementation of getaddrinfo() and friends */ #undef NEED_FAKE_RFC2553 /* Is epoll(7) supported? */ #undef NOTIFIER_EPOLL /* Is kqueue(2) supported? */ #undef NOTIFIER_KQUEUE /* Is Darwin CoreFoundation unavailable for 64-bit? */ #undef NO_COREFOUNDATION_64 /* Do we have ? */ #undef NO_DLFCN_H /* Do we have fd_set? */ #undef NO_FD_SET /* Do we have fork() */ #undef NO_FORK /* Do we have fstatfs()? */ #undef NO_FSTATFS /* Do we have gettimeofday()? */ #undef NO_GETTOD /* Do we have getwd() */ #undef NO_GETWD /* Do we have mknod() */ #undef NO_MKNOD /* Do we have realpath() */ #undef NO_REALPATH /* Do we have strerror() */ #undef NO_STRERROR /* Do we have ? */ #undef NO_SYS_WAIT_H /* Do we have tcdrain() */ #undef NO_TCDRAIN /* Do we have uname() */ #undef NO_UNAME /* Do we have a usable 'union wait'? */ #undef NO_UNION_WAIT /* Do we have wait3() */ #undef NO_WAIT3 /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT /* Define to the full name of this package. */ #undef PACKAGE_NAME /* Define to the full name and version of this package. */ #undef PACKAGE_STRING /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME /* Define to the home page for this package. */ #undef PACKAGE_URL /* Define to the version of this package. */ #undef PACKAGE_VERSION /* Is this a static build? */ #undef STATIC_BUILD /* Define to 1 if all of the C89 standard headers exist (not just the ones required in a freestanding environment). This macro is provided for backward compatibility; new code need not use it. */ #undef STDC_HEADERS /* What encoding should be used for embedded configuration info? */ #undef TCL_CFGVAL_ENCODING /* Is this a 64-bit build? */ #undef TCL_CFG_DO64BIT /* Is this an optimized build? */ #undef TCL_CFG_OPTIMIZED /* Is bytecode debugging enabled? */ #undef TCL_COMPILE_DEBUG /* Are bytecode statistics enabled? */ #undef TCL_COMPILE_STATS /* Is Tcl built as a framework? */ #undef TCL_FRAMEWORK /* Can this platform load code from memory? */ #undef TCL_LOAD_FROM_MEMORY /* Is memory debugging enabled? */ #undef TCL_MEM_DEBUG /* What is the default extension for shared libraries? */ #undef TCL_SHLIB_EXT /* Do we allow unloading of shared libraries? */ #undef TCL_UNLOAD_DLLS /* Does this platform have wide high-resolution clicks? */ #undef TCL_WIDE_CLICKS /* Do 'long' and 'long long' have the same size (64-bit)? */ #undef TCL_WIDE_INT_IS_LONG /* Tcl with external libtommath */ #undef TCL_WITH_EXTERNAL_TOMMATH /* Tcl with internal zlib */ #undef TCL_WITH_INTERNAL_ZLIB /* Is getcwd Posix-compliant? */ #undef USEGETWD /* Are we building with DTrace support? */ #undef USE_DTRACE /* Should we use FIONBIO? */ #undef USE_FIONBIO /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel). */ #if defined AC_APPLE_UNIVERSAL_BUILD # if defined __BIG_ENDIAN__ # define WORDS_BIGENDIAN 1 # endif #else # ifndef WORDS_BIGENDIAN # undef WORDS_BIGENDIAN # endif #endif /* Are we building with zipfs enabled? */ #undef ZIPFS_BUILD /* Are Darwin SUSv3 extensions available? */ #undef _DARWIN_C_SOURCE /* Add the _FILE_OFFSET_BITS flag when building */ #undef _FILE_OFFSET_BITS /* Add the _ISOC99_SOURCE flag when building */ #undef _ISOC99_SOURCE /* Add the _LARGEFILE64_SOURCE flag when building */ #undef _LARGEFILE64_SOURCE /* # needed in sys/socket.h Should OS/390 do the right thing with sockets? */ #undef _OE_SOCKETS /* Do we really want to follow the standard? Yes we do! */ #undef _POSIX_PTHREAD_SEMANTICS /* Do we want the reentrant OS API? */ #undef _REENTRANT /* Do we want the thread-safe OS API? */ #undef _THREAD_SAFE /* _TIME_BITS=64 enables 64-bit time_t. */ #undef _TIME_BITS /* Do we want to use the XOPEN network library? */ #undef _XOPEN_SOURCE /* Do we want to use the XOPEN network library? */ #undef _XOPEN_SOURCE_EXTENDED /* Define to 1 if type 'char' is unsigned and your compiler does not predefine this macro. */ #ifndef __CHAR_UNSIGNED__ # undef __CHAR_UNSIGNED__ #endif /* Define as 'int' if doesn't define. */ #undef gid_t /* Define to '__inline__' or '__inline' if that's what the C compiler calls it, or to nothing if 'inline' is not supported under any name. */ #ifndef __cplusplus #undef inline #endif /* Define to 'int' if does not define. */ #undef mode_t /* Define as a signed integer type capable of holding a process identifier. */ #undef pid_t /* Define as 'unsigned int' if doesn't define. */ #undef size_t /* Define as int if socklen_t is not available */ #undef socklen_t /* Define as 'int' if doesn't define. */ #undef uid_t /* Undef unused package specific autoheader defines so that we can * include both tclConfig.h and tkConfig.h at the same time: */ /* override */ #undef PACKAGE_NAME /* override */ #undef PACKAGE_TARNAME /* override */ #undef PACKAGE_VERSION /* override */ #undef PACKAGE_STRING #endif /* _TCLCONFIG */ tcl9.0.1/unix/tcl.pc.in0000644000175000017500000000101214726623136014246 0ustar sergeisergei# tcl pkg-config source file prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ libfile=@TCL_LIB_FILE@ Name: Tool Command Language Description: Tcl is a powerful, easy-to-learn dynamic programming language, suitable for a wide range of uses. URL: https://www.tcl-lang.org/ Version: @TCL_VERSION@@TCL_PATCH_LEVEL@ Requires.private: @TCL_PC_REQUIRES_PRIVATE@ zlib >= 1.2.3 Libs: -L${libdir} @TCL_LIB_FLAG@ @TCL_STUB_LIB_FLAG@ Libs.private: @TCL_LIBS@ Cflags: -I${includedir} @TCL_PC_CFLAGS@ tcl9.0.1/unix/configure0000755000175000017500000121523114726623136014455 0ustar sergeisergei#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.72 for tcl 9.0. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation, # Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case e in #( e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed 'exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case e in #( e) case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ) then : else case e in #( e) exitcode=1; echo positional parameters were not saved. ;; esac fi test x\$exitcode = x0 || exit 1 blah=\$(echo \$(echo blah)) test x\"\$blah\" = xblah || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 test \$(( 1 + 1 )) = 2 || exit 1" if (eval "$as_required") 2>/dev/null then : as_have_required=yes else case e in #( e) as_have_required=no ;; esac fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null then : else case e in #( e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$as_shell as_have_required=yes if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null then : break 2 fi fi done;; esac as_found=false done IFS=$as_save_IFS if $as_found then : else case e in #( e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$SHELL as_have_required=yes fi ;; esac fi if test "x$CONFIG_SHELL" != x then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed 'exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno then : printf "%s\n" "$0: This script requires a shell more modern than all" printf "%s\n" "$0: the shells that I found on your system." if test ${ZSH_VERSION+y} ; then printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should" printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." else printf "%s\n" "$0: Please tell bug-autoconf@gnu.org about your system, $0: including any error possibly output before this $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 fi ;; esac fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else case e in #( e) as_fn_append () { eval $1=\$$1\$2 } ;; esac fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else case e in #( e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } ;; esac fi # as_fn_arith # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' t clear :clear s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac # For backward compatibility with old third-party macros, we provide # the shell variables $as_echo and $as_echo_n. New code should use # AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. as_echo='printf %s\n' as_echo_n='printf %s' rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" as_tr_sh="eval sed '$as_sed_sh'" # deprecated test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='tcl' PACKAGE_TARNAME='tcl' PACKAGE_VERSION='9.0' PACKAGE_STRING='tcl 9.0' PACKAGE_BUGREPORT='' PACKAGE_URL='' # Factoring default headers for most tests. ac_includes_default="\ #include #ifdef HAVE_STDIO_H # include #endif #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif" ac_header_c_list= ac_subst_vars='DLTEST_SUFFIX DLTEST_LD EXTRA_TCLSH_LIBS EXTRA_BUILD_HTML EXTRA_INSTALL_BINARIES EXTRA_INSTALL EXTRA_APP_CC_SWITCHES EXTRA_CC_SWITCHES PACKAGE_DIR HTML_DIR PRIVATE_INCLUDE_DIR TCL_LIBRARY TCL_MODULE_PATH TCL_PACKAGE_PATH BUILD_DLTEST MAKEFILE_SHELL DTRACE_OBJ DTRACE_HDR DTRACE_SRC INSTALL_TZDATA TCL_HAS_LONGLONG TCL_UNSHARED_LIB_SUFFIX TCL_SHARED_LIB_SUFFIX TCL_LIB_VERSIONS_OK TCL_BUILD_LIB_SPEC LD_LIBRARY_PATH_VAR TCL_SHARED_BUILD CFG_TCL_UNSHARED_LIB_SUFFIX CFG_TCL_SHARED_LIB_SUFFIX TCL_SRC_DIR TCL_BUILD_STUB_LIB_PATH TCL_BUILD_STUB_LIB_SPEC TCL_INCLUDE_SPEC TCL_STUB_LIB_PATH TCL_STUB_LIB_SPEC TCL_STUB_LIB_FLAG TCL_STUB_LIB_FILE TCL_LIB_SPEC TCL_LIB_FLAG TCL_LIB_FILE PKG_CFG_ARGS TCL_YEAR TCL_PATCH_LEVEL TCL_MINOR_VERSION TCL_MAJOR_VERSION TCL_VERSION TCL_BUILDTIME_LIBRARY INSTALL_MSGS INSTALL_LIBRARIES TCL_ZIP_FILE ZIPFS_BUILD ZIP_INSTALL_OBJS ZIP_PROG_VFSSEARCH ZIP_PROG_OPTIONS ZIP_PROG MACHER_PROG EXEEXT_FOR_BUILD CC_FOR_BUILD DTRACE LDFLAGS_DEFAULT CFLAGS_DEFAULT INSTALL_STUB_LIB DLL_INSTALL_DIR INSTALL_LIB MAKE_STUB_LIB MAKE_LIB SHLIB_SUFFIX SHLIB_CFLAGS SHLIB_LD_LIBS TK_SHLIB_LD_EXTRAS TCL_SHLIB_LD_EXTRAS SHLIB_LD STLIB_LD LD_SEARCH_FLAGS CC_SEARCH_FLAGS LDFLAGS_OPTIMIZE LDFLAGS_DEBUG CFLAGS_NOLTO CFLAGS_WARNING CFLAGS_OPTIMIZE CFLAGS_DEBUG LDAIX_SRC PLAT_SRCS PLAT_OBJS DL_OBJS DL_LIBS TCL_LIBS LIBOBJS AR RANLIB TOMMATH_INCLUDE TOMMATH_SRCS TOMMATH_OBJS TCL_PC_CFLAGS TCL_PC_REQUIRES_PRIVATE ZLIB_INCLUDE ZLIB_SRCS ZLIB_OBJS TCLSH_PROG SHARED_BUILD CPP OBJEXT EXEEXT ac_ct_CC CPPFLAGS LDFLAGS CFLAGS CC MAN_FLAGS target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir runstatedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL OBJEXT_FOR_BUILD' ac_subst_files='' ac_user_opts=' enable_option_checking enable_man_symlinks enable_man_compression enable_man_suffix with_encoding enable_shared with_system_libtommath enable_64bit enable_64bit_vis enable_rpath enable_corefoundation enable_load enable_symbols enable_langinfo enable_dll_unloading with_tzdata enable_dtrace enable_framework enable_zipfs ' ac_precious_vars='build_alias host_alias target_alias CC CFLAGS LDFLAGS LIBS CPPFLAGS CPP' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -runstatedir | --runstatedir | --runstatedi | --runstated \ | --runstate | --runstat | --runsta | --runst | --runs \ | --run | --ru | --r) ac_prev=runstatedir ;; -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ | --run=* | --ru=* | --r=*) runstatedir=$ac_optarg ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: '$ac_option' Try '$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: '$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: '$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF 'configure' configures tcl 9.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print 'checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for '--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or '..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, 'make install' will install all the files in '$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify an installation prefix other than '$ac_default_prefix' using '--prefix', for instance '--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/tcl] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of tcl 9.0:";; esac cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-man-symlinks use symlinks for the manpages (default: off) --enable-man-compression=PROG compress the manpages with PROG (default: off) --enable-man-suffix=STRING use STRING as a suffix to manpage file names (default: no, tcl if enabled without specifying STRING) --enable-shared build and link with shared libraries (default: on) --enable-64bit enable 64bit support (default: off) --enable-64bit-vis enable 64bit Sparc VIS support (default: off) --disable-rpath disable rpath support (default: on) --enable-corefoundation use CoreFoundation API on MacOSX (default: on) --enable-load allow dynamic loading and "load" command (default: on) --enable-symbols build with debugging symbols (default: off) --enable-langinfo use nl_langinfo if possible to determine encoding at startup, otherwise use old heuristic (default: on) --enable-dll-unloading enable the 'unload' command (default: on) --enable-dtrace build with DTrace support (default: off) --enable-framework package shared libraries in MacOSX frameworks (default: off) --enable-zipfs build with Zipfs support (default: on) Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-encoding encoding for configuration values (default: utf-8) --with-system-libtommath use external libtommath (default: true if available, false otherwise) --with-tzdata install timezone data (default: autodetect) Some influential environment variables: CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory CPP C preprocessor Use these variables to override the choices made by 'configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to the package provider. _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for configure.gnu first; this name is used for a wrapper for # Metaconfig's "Configure" on case-insensitive file systems. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF tcl configure 9.0 generated by GNU Autoconf 2.72 Copyright (C) 2023 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## # ac_fn_c_try_compile LINENO # -------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_compile # ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists and can be compiled using the include files in # INCLUDES, setting the cache variable VAR accordingly. ac_fn_c_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" else case e in #( e) eval "$3=no" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_compile # ac_fn_c_try_cpp LINENO # ---------------------- # Try to preprocess conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_cpp () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } > conftest.i && { test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || test ! -s conftest.err } then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_cpp # ac_fn_c_try_link LINENO # ----------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext } then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_link # ac_fn_c_check_func LINENO FUNC VAR # ---------------------------------- # Tests whether FUNC exists, setting the cache variable VAR accordingly ac_fn_c_check_func () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, which can conflict with char $2 (void); below. */ #include #undef $2 /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char $2 (void); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined __stub_$2 || defined __stub___$2 choke me #endif int main (void) { return $2 (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : eval "$3=yes" else case e in #( e) eval "$3=no" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ;; esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_func # ac_fn_check_decl LINENO SYMBOL VAR INCLUDES EXTRA-OPTIONS FLAG-VAR # ------------------------------------------------------------------ # Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR # accordingly. Pass EXTRA-OPTIONS to the compiler, using FLAG-VAR. ac_fn_check_decl () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack as_decl_name=`echo $2|sed 's/ *(.*//'` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5 printf %s "checking whether $as_decl_name is declared... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'` eval ac_save_FLAGS=\$$6 as_fn_append $6 " $5" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { #ifndef $as_decl_name #ifdef __cplusplus (void) $as_decl_use; #else (void) $as_decl_name; #endif #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" else case e in #( e) eval "$3=no" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext eval $6=\$ac_save_FLAGS ;; esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_check_decl # ac_fn_c_check_type LINENO TYPE VAR INCLUDES # ------------------------------------------- # Tests whether TYPE exists after having included INCLUDES, setting cache # variable VAR accordingly. ac_fn_c_check_type () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) eval "$3=no" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { if (sizeof ($2)) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { if (sizeof (($2))) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) eval "$3=yes" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_type # ac_fn_c_try_run LINENO # ---------------------- # Try to run conftest.$ac_ext, and return whether this succeeded. Assumes that # executables *can* be run. ac_fn_c_try_run () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; } then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: program exited with status $ac_status" >&5 printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=$ac_status ;; esac fi rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_run # ac_fn_c_check_member LINENO AGGR MEMBER VAR INCLUDES # ---------------------------------------------------- # Tries to find if the field MEMBER exists in type AGGR, after including # INCLUDES, setting cache variable VAR accordingly. ac_fn_c_check_member () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2.$3" >&5 printf %s "checking for $2.$3... " >&6; } if eval test \${$4+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $5 int main (void) { static $2 ac_aggr; if (ac_aggr.$3) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$4=yes" else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $5 int main (void) { static $2 ac_aggr; if (sizeof ac_aggr.$3) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$4=yes" else case e in #( e) eval "$4=no" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi eval ac_res=\$$4 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_member ac_configure_args_raw= for ac_arg do case $ac_arg in *\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append ac_configure_args_raw " '$ac_arg'" done case $ac_configure_args_raw in *$as_nl*) ac_safe_unquote= ;; *) ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. ac_unsafe_a="$ac_unsafe_z#~" ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; esac cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by tcl $as_me 9.0, which was generated by GNU Autoconf 2.72. Invocation command line was $ $0$ac_configure_args_raw _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac printf "%s\n" "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Sanitize IFS. IFS=" "" $as_nl" # Save into config.log some information that might help in debugging. { echo printf "%s\n" "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo printf "%s\n" "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then printf "%s\n" "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then printf "%s\n" "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && printf "%s\n" "$as_me: caught signal $ac_signal" printf "%s\n" "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h printf "%s\n" "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. if test -n "$CONFIG_SITE"; then ac_site_files="$CONFIG_SITE" elif test "x$prefix" != xNONE; then ac_site_files="$prefix/share/config.site $prefix/etc/config.site" else ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" fi for ac_site_file in $ac_site_files do case $ac_site_file in #( */*) : ;; #( *) : ac_site_file=./$ac_site_file ;; esac if test -f "$ac_site_file" && test -r "$ac_site_file"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See 'config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 printf "%s\n" "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 printf "%s\n" "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Test code for whether the C compiler supports C89 (global declarations) ac_c_conftest_c89_globals=' /* Does the compiler advertise C89 conformance? Do not test the value of __STDC__, because some compilers set it to 0 while being otherwise adequately conformant. */ #if !defined __STDC__ # error "Compiler does not advertise C89 conformance" #endif #include #include struct stat; /* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ struct buf { int x; }; struct buf * (*rcsopen) (struct buf *, struct stat *, int); static char *e (char **p, int i) { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } /* C89 style stringification. */ #define noexpand_stringify(a) #a const char *stringified = noexpand_stringify(arbitrary+token=sequence); /* C89 style token pasting. Exercises some of the corner cases that e.g. old MSVC gets wrong, but not very hard. */ #define noexpand_concat(a,b) a##b #define expand_concat(a,b) noexpand_concat(a,b) extern int vA; extern int vbee; #define aye A #define bee B int *pvA = &expand_concat(v,aye); int *pvbee = &noexpand_concat(v,bee); /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not \xHH hex character constants. These do not provoke an error unfortunately, instead are silently treated as an "x". The following induces an error, until -std is added to get proper ANSI mode. Curiously \x00 != x always comes out true, for an array size at least. It is necessary to write \x00 == 0 to get something that is true only with -std. */ int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1]; /* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters inside strings and character constants. */ #define FOO(x) '\''x'\'' int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1]; int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int), int, int);' # Test code for whether the C compiler supports C89 (body of main). ac_c_conftest_c89_main=' ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); ' # Test code for whether the C compiler supports C99 (global declarations) ac_c_conftest_c99_globals=' /* Does the compiler advertise C99 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L # error "Compiler does not advertise C99 conformance" #endif // See if C++-style comments work. #include extern int puts (const char *); extern int printf (const char *, ...); extern int dprintf (int, const char *, ...); extern void *malloc (size_t); extern void free (void *); // Check varargs macros. These examples are taken from C99 6.10.3.5. // dprintf is used instead of fprintf to avoid needing to declare // FILE and stderr. #define debug(...) dprintf (2, __VA_ARGS__) #define showlist(...) puts (#__VA_ARGS__) #define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) static void test_varargs_macros (void) { int x = 1234; int y = 5678; debug ("Flag"); debug ("X = %d\n", x); showlist (The first, second, and third items.); report (x>y, "x is %d but y is %d", x, y); } // Check long long types. #define BIG64 18446744073709551615ull #define BIG32 4294967295ul #define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) #if !BIG_OK #error "your preprocessor is broken" #endif #if BIG_OK #else #error "your preprocessor is broken" #endif static long long int bignum = -9223372036854775807LL; static unsigned long long int ubignum = BIG64; struct incomplete_array { int datasize; double data[]; }; struct named_init { int number; const wchar_t *name; double average; }; typedef const char *ccp; static inline int test_restrict (ccp restrict text) { // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) continue; return 0; } // Check varargs and va_copy. static bool test_varargs (const char *format, ...) { va_list args; va_start (args, format); va_list args_copy; va_copy (args_copy, args); const char *str = ""; int number = 0; float fnumber = 0; while (*format) { switch (*format++) { case '\''s'\'': // string str = va_arg (args_copy, const char *); break; case '\''d'\'': // int number = va_arg (args_copy, int); break; case '\''f'\'': // float fnumber = va_arg (args_copy, double); break; default: break; } } va_end (args_copy); va_end (args); return *str && number && fnumber; } ' # Test code for whether the C compiler supports C99 (body of main). ac_c_conftest_c99_main=' // Check bool. _Bool success = false; success |= (argc != 0); // Check restrict. if (test_restrict ("String literal") == 0) success = true; char *restrict newvar = "Another string"; // Check varargs. success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234); test_varargs_macros (); // Check flexible array members. struct incomplete_array *ia = malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10)); ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234; // Work around memory leak warnings. free (ia); // Check named initializers. struct named_init ni = { .number = 34, .name = L"Test wide string", .average = 543.34343, }; ni.number = 58; int dynamic_array[ni.number]; dynamic_array[0] = argv[0][0]; dynamic_array[ni.number - 1] = 543; // work around unused variable warnings ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\'' || dynamic_array[ni.number - 1] != 543); ' # Test code for whether the C compiler supports C11 (global declarations) ac_c_conftest_c11_globals=' /* Does the compiler advertise C11 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L # error "Compiler does not advertise C11 conformance" #endif // Check _Alignas. char _Alignas (double) aligned_as_double; char _Alignas (0) no_special_alignment; extern char aligned_as_int; char _Alignas (0) _Alignas (int) aligned_as_int; // Check _Alignof. enum { int_alignment = _Alignof (int), int_array_alignment = _Alignof (int[100]), char_alignment = _Alignof (char) }; _Static_assert (0 < -_Alignof (int), "_Alignof is signed"); // Check _Noreturn. int _Noreturn does_not_return (void) { for (;;) continue; } // Check _Static_assert. struct test_static_assert { int x; _Static_assert (sizeof (int) <= sizeof (long int), "_Static_assert does not work in struct"); long int y; }; // Check UTF-8 literals. #define u8 syntax error! char const utf8_literal[] = u8"happens to be ASCII" "another string"; // Check duplicate typedefs. typedef long *long_ptr; typedef long int *long_ptr; typedef long_ptr long_ptr; // Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1. struct anonymous { union { struct { int i; int j; }; struct { int k; long int l; } w; }; int m; } v1; ' # Test code for whether the C compiler supports C11 (body of main). ac_c_conftest_c11_main=' _Static_assert ((offsetof (struct anonymous, i) == offsetof (struct anonymous, w.k)), "Anonymous union alignment botch"); v1.i = 2; v1.w.k = 5; ok |= v1.i != 5; ' # Test code for whether the C compiler supports C11 (complete). ac_c_conftest_c11_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} ${ac_c_conftest_c11_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} ${ac_c_conftest_c11_main} return ok; } " # Test code for whether the C compiler supports C99 (complete). ac_c_conftest_c99_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} return ok; } " # Test code for whether the C compiler supports C89 (complete). ac_c_conftest_c89_program="${ac_c_conftest_c89_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} return ok; } " as_fn_append ac_header_c_list " stdio.h stdio_h HAVE_STDIO_H" as_fn_append ac_header_c_list " stdlib.h stdlib_h HAVE_STDLIB_H" as_fn_append ac_header_c_list " string.h string_h HAVE_STRING_H" as_fn_append ac_header_c_list " inttypes.h inttypes_h HAVE_INTTYPES_H" as_fn_append ac_header_c_list " stdint.h stdint_h HAVE_STDINT_H" as_fn_append ac_header_c_list " strings.h strings_h HAVE_STRINGS_H" as_fn_append ac_header_c_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H" as_fn_append ac_header_c_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H" as_fn_append ac_header_c_list " unistd.h unistd_h HAVE_UNISTD_H" as_fn_append ac_header_c_list " sys/time.h sys_time_h HAVE_SYS_TIME_H" # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 printf "%s\n" "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 printf "%s\n" "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 printf "%s\n" "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 printf "%s\n" "$as_me: former value: '$ac_old_val'" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu TCL_VERSION=9.0 TCL_MAJOR_VERSION=9 TCL_MINOR_VERSION=0 TCL_PATCH_LEVEL=".1" VERSION=${TCL_VERSION} EXTRA_INSTALL_BINARIES=${EXTRA_INSTALL_BINARIES:-"@:"} EXTRA_BUILD_HTML=${EXTRA_BUILD_HTML:-"@:"} #------------------------------------------------------------------------ # Setup configure arguments for bundled packages #------------------------------------------------------------------------ PKG_CFG_ARGS="$ac_configure_args ${PKG_CFG_ARGS}" if test -r "$cache_file" -a -f "$cache_file"; then case $cache_file in [\\/]* | ?:[\\/]* ) pkg_cache_file=$cache_file ;; *) pkg_cache_file=../../$cache_file ;; esac PKG_CFG_ARGS="${PKG_CFG_ARGS} --cache-file=$pkg_cache_file" fi #------------------------------------------------------------------------ # Empty slate for bundled packages, to avoid stale configuration #------------------------------------------------------------------------ #rm -Rf pkgs if test -f Makefile; then make distclean-packages fi #------------------------------------------------------------------------ # Handle the --prefix=... option #------------------------------------------------------------------------ if test "${prefix}" = "NONE"; then prefix=/usr/local fi if test "${exec_prefix}" = "NONE"; then exec_prefix=$prefix fi # Make sure srcdir is fully qualified! srcdir="`cd "$srcdir" ; pwd`" TCL_SRC_DIR="`cd "$srcdir"/..; pwd`" #------------------------------------------------------------------------ # Compress and/or soft link the manpages? #------------------------------------------------------------------------ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to use symlinks for manpages" >&5 printf %s "checking whether to use symlinks for manpages... " >&6; } # Check whether --enable-man-symlinks was given. if test ${enable_man_symlinks+y} then : enableval=$enable_man_symlinks; test "$enableval" != "no" && MAN_FLAGS="$MAN_FLAGS --symlinks" else case e in #( e) enableval="no" ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enableval" >&5 printf "%s\n" "$enableval" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to compress the manpages" >&5 printf %s "checking whether to compress the manpages... " >&6; } # Check whether --enable-man-compression was given. if test ${enable_man_compression+y} then : enableval=$enable_man_compression; case $enableval in yes) as_fn_error $? "missing argument to --enable-man-compression" "$LINENO" 5;; no) ;; *) MAN_FLAGS="$MAN_FLAGS --compress $enableval";; esac else case e in #( e) enableval="no" ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enableval" >&5 printf "%s\n" "$enableval" >&6; } if test "$enableval" != "no"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for compressed file suffix" >&5 printf %s "checking for compressed file suffix... " >&6; } touch TeST $enableval TeST Z=`ls TeST* | sed 's/^....//'` rm -f TeST* MAN_FLAGS="$MAN_FLAGS --extension $Z" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $Z" >&5 printf "%s\n" "$Z" >&6; } fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to add a package name suffix for the manpages" >&5 printf %s "checking whether to add a package name suffix for the manpages... " >&6; } # Check whether --enable-man-suffix was given. if test ${enable_man_suffix+y} then : enableval=$enable_man_suffix; case $enableval in yes) enableval="tcl" MAN_FLAGS="$MAN_FLAGS --suffix $enableval";; no) ;; *) MAN_FLAGS="$MAN_FLAGS --suffix $enableval";; esac else case e in #( e) enableval="no" ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enableval" >&5 printf "%s\n" "$enableval" >&6; } #------------------------------------------------------------------------ # Standard compiler checks #------------------------------------------------------------------------ # If the user did not set CFLAGS, set it now to keep # the AC_PROG_CC macro from adding "-g -O2". if test "${CFLAGS+set}" != "set" ; then CFLAGS="" fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" fi fi fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl.exe do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl.exe do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args. set dummy ${ac_tool_prefix}clang; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}clang" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "clang", so it can be a program name with args. set dummy clang; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="clang" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi fi test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See 'config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion -version; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 printf %s "checking whether the C compiler works... " >&6; } ac_link_default=`printf "%s\n" "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { { ac_try="$ac_link_default" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. # So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an '-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else case e in #( e) ac_file='' ;; esac fi if test -z "$ac_file" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables See 'config.log' for more details" "$LINENO" 5; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 printf %s "checking for C compiler default output file name... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 printf "%s\n" "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 printf %s "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) # catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will # work properly (i.e., refer to 'conftest.exe'), while it won't with # 'rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else case e in #( e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link See 'config.log' for more details" "$LINENO" 5; } ;; esac fi rm -f conftest conftest$ac_cv_exeext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 printf "%s\n" "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { FILE *f = fopen ("conftest.out", "w"); if (!f) return 1; return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 printf %s "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot run C compiled programs. If you meant to cross compile, use '--host'. See 'config.log' for more details" "$LINENO" 5; } fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 printf "%s\n" "$cross_compiling" >&6; } rm -f conftest.$ac_ext conftest$ac_cv_exeext \ conftest.o conftest.obj conftest.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 printf %s "checking for suffix of object files... " >&6; } if test ${ac_cv_objext+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile See 'config.log' for more details" "$LINENO" 5; } ;; esac fi rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 printf "%s\n" "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 printf %s "checking whether the compiler supports GNU C... " >&6; } if test ${ac_cv_c_compiler_gnu+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_compiler_gnu=yes else case e in #( e) ac_compiler_gnu=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; } ac_compiler_gnu=$ac_cv_c_compiler_gnu if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi ac_test_CFLAGS=${CFLAGS+y} ac_save_CFLAGS=$CFLAGS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 printf %s "checking whether $CC accepts -g... " >&6; } if test ${ac_cv_prog_cc_g+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes else case e in #( e) CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 printf "%s\n" "$ac_cv_prog_cc_g" >&6; } if test $ac_test_CFLAGS; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi ac_prog_cc_stdc=no if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 printf %s "checking for $CC option to enable C11 features... " >&6; } if test ${ac_cv_prog_cc_c11+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c11=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c11_program _ACEOF for ac_arg in '' -std=gnu11 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c11=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c11" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c11" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c11" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 printf "%s\n" "$ac_cv_prog_cc_c11" >&6; } CC="$CC $ac_cv_prog_cc_c11" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 ac_prog_cc_stdc=c11 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 printf %s "checking for $CC option to enable C99 features... " >&6; } if test ${ac_cv_prog_cc_c99+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c99_program _ACEOF for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99= do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c99=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c99" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c99" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 printf "%s\n" "$ac_cv_prog_cc_c99" >&6; } CC="$CC $ac_cv_prog_cc_c99" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 ac_prog_cc_stdc=c99 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 printf %s "checking for $CC option to enable C89 features... " >&6; } if test ${ac_cv_prog_cc_c89+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c89_program _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c89" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c89" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 printf "%s\n" "$ac_cv_prog_cc_c89" >&6; } CC="$CC $ac_cv_prog_cc_c89" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 ac_prog_cc_stdc=c89 ;; esac fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for inline" >&5 printf %s "checking for inline... " >&6; } if test ${ac_cv_c_inline+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_c_inline=no for ac_kw in inline __inline__ __inline; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifndef __cplusplus typedef int foo_t; static $ac_kw foo_t static_foo (void) {return 0; } $ac_kw foo_t foo (void) {return 0; } #endif _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_c_inline=$ac_kw fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext test "$ac_cv_c_inline" != no && break done ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_inline" >&5 printf "%s\n" "$ac_cv_c_inline" >&6; } case $ac_cv_c_inline in inline | yes) ;; *) case $ac_cv_c_inline in no) ac_val=;; *) ac_val=$ac_cv_c_inline;; esac cat >>confdefs.h <<_ACEOF #ifndef __cplusplus #define inline $ac_val #endif _ACEOF ;; esac #-------------------------------------------------------------------- # Supply substitutes for missing POSIX header files. Special notes: # - stdlib.h doesn't define strtol or strtoul in some versions # of SunOS # - some versions of string.h don't declare procedures such # as strstr # Do this early, otherwise an autoconf bug throws errors on configure #-------------------------------------------------------------------- ac_header= ac_cache= for ac_item in $ac_header_c_list do if test $ac_cache; then ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then printf "%s\n" "#define $ac_item 1" >> confdefs.h fi ac_header= ac_cache= elif test $ac_header; then ac_cache=$ac_item else ac_header=$ac_item fi done if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes then : printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 printf %s "checking how to run the C preprocessor... " >&6; } # On Suns, sometimes $CPP names a directory. if test -n "$CPP" && test -d "$CPP"; then CPP= fi if test -z "$CPP"; then if test ${ac_cv_prog_CPP+y} then : printf %s "(cached) " >&6 else case e in #( e) # Double quotes because $CC needs to be expanded for CPP in "$CC -E" "$CC -E -traditional-cpp" cpp /lib/cpp do ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO" then : else case e in #( e) # Broken: fails on valid input. continue ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO" then : # Broken: success on invalid input. continue else case e in #( e) # Passes both tests. ac_preproc_ok=: break ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok then : break fi done ac_cv_prog_CPP=$CPP ;; esac fi CPP=$ac_cv_prog_CPP else ac_cv_prog_CPP=$CPP fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 printf "%s\n" "$CPP" >&6; } ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO" then : else case e in #( e) # Broken: fails on valid input. continue ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO" then : # Broken: success on invalid input. continue else case e in #( e) # Passes both tests. ac_preproc_ok=: break ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok then : else case e in #( e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "C preprocessor \"$CPP\" fails sanity check See 'config.log' for more details" "$LINENO" 5; } ;; esac fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep -e" >&5 printf %s "checking for egrep -e... " >&6; } if test ${ac_cv_path_EGREP_TRADITIONAL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -z "$EGREP_TRADITIONAL"; then ac_path_EGREP_TRADITIONAL_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in grep ggrep do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_EGREP_TRADITIONAL="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_EGREP_TRADITIONAL" || continue # Check for GNU ac_path_EGREP_TRADITIONAL and select it if it is found. # Check for GNU $ac_path_EGREP_TRADITIONAL case `"$ac_path_EGREP_TRADITIONAL" --version 2>&1` in #( *GNU*) ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_found=:;; #( *) ac_count=0 printf %s 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" printf "%s\n" 'EGREP_TRADITIONAL' >> "conftest.nl" "$ac_path_EGREP_TRADITIONAL" -E 'EGR(EP|AC)_TRADITIONAL$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_EGREP_TRADITIONAL_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_EGREP_TRADITIONAL_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_EGREP_TRADITIONAL"; then : fi else ac_cv_path_EGREP_TRADITIONAL=$EGREP_TRADITIONAL fi if test "$ac_cv_path_EGREP_TRADITIONAL" then : ac_cv_path_EGREP_TRADITIONAL="$ac_cv_path_EGREP_TRADITIONAL -E" else case e in #( e) if test -z "$EGREP_TRADITIONAL"; then ac_path_EGREP_TRADITIONAL_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in egrep do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_EGREP_TRADITIONAL="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_EGREP_TRADITIONAL" || continue # Check for GNU ac_path_EGREP_TRADITIONAL and select it if it is found. # Check for GNU $ac_path_EGREP_TRADITIONAL case `"$ac_path_EGREP_TRADITIONAL" --version 2>&1` in #( *GNU*) ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_found=:;; #( *) ac_count=0 printf %s 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" printf "%s\n" 'EGREP_TRADITIONAL' >> "conftest.nl" "$ac_path_EGREP_TRADITIONAL" 'EGR(EP|AC)_TRADITIONAL$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_EGREP_TRADITIONAL_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_EGREP_TRADITIONAL_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_EGREP_TRADITIONAL"; then as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_EGREP_TRADITIONAL=$EGREP_TRADITIONAL fi ;; esac fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP_TRADITIONAL" >&5 printf "%s\n" "$ac_cv_path_EGREP_TRADITIONAL" >&6; } EGREP_TRADITIONAL=$ac_cv_path_EGREP_TRADITIONAL ac_fn_c_check_header_compile "$LINENO" "string.h" "ac_cv_header_string_h" "$ac_includes_default" if test "x$ac_cv_header_string_h" = xyes then : tcl_ok=1 else case e in #( e) tcl_ok=0 ;; esac fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP_TRADITIONAL "strstr" >/dev/null 2>&1 then : else case e in #( e) tcl_ok=0 ;; esac fi rm -rf conftest* cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP_TRADITIONAL "strerror" >/dev/null 2>&1 then : else case e in #( e) tcl_ok=0 ;; esac fi rm -rf conftest* ac_fn_c_check_header_compile "$LINENO" "sys/wait.h" "ac_cv_header_sys_wait_h" "$ac_includes_default" if test "x$ac_cv_header_sys_wait_h" = xyes then : else case e in #( e) printf "%s\n" "#define NO_SYS_WAIT_H 1" >>confdefs.h ;; esac fi ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default" if test "x$ac_cv_header_dlfcn_h" = xyes then : else case e in #( e) printf "%s\n" "#define NO_DLFCN_H 1" >>confdefs.h ;; esac fi # OS/390 lacks sys/param.h (and doesn't need it, by chance). ac_fn_c_check_header_compile "$LINENO" "sys/param.h" "ac_cv_header_sys_param_h" "$ac_includes_default" if test "x$ac_cv_header_sys_param_h" = xyes then : printf "%s\n" "#define HAVE_SYS_PARAM_H 1" >>confdefs.h fi #-------------------------------------------------------------------- # Determines the correct executable file extension (.exe) #-------------------------------------------------------------------- #------------------------------------------------------------------------ # If we're using GCC, see if the compiler understands -pipe. If so, use it. # It makes compiling go faster. (This is only a performance feature.) #------------------------------------------------------------------------ if test -z "$no_pipe" && test -n "$GCC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if the compiler understands -pipe" >&5 printf %s "checking if the compiler understands -pipe... " >&6; } if test ${tcl_cv_cc_pipe+y} then : printf %s "(cached) " >&6 else case e in #( e) hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -pipe" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_cc_pipe=yes else case e in #( e) tcl_cv_cc_pipe=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext CFLAGS=$hold_cflags ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_pipe" >&5 printf "%s\n" "$tcl_cv_cc_pipe" >&6; } if test $tcl_cv_cc_pipe = yes; then CFLAGS="$CFLAGS -pipe" fi fi #------------------------------------------------------------------------ # Embedded configuration information, encoding to use for the values, TIP #59 #------------------------------------------------------------------------ # Check whether --with-encoding was given. if test ${with_encoding+y} then : withval=$with_encoding; with_tcencoding=${withval} fi if test x"${with_tcencoding}" != x ; then printf "%s\n" "#define TCL_CFGVAL_ENCODING \"${with_tcencoding}\"" >>confdefs.h else printf "%s\n" "#define TCL_CFGVAL_ENCODING \"utf-8\"" >>confdefs.h fi #-------------------------------------------------------------------- # Look for libraries that we will need when compiling the Tcl shell #-------------------------------------------------------------------- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC options needed to detect all undeclared functions" >&5 printf %s "checking for $CC options needed to detect all undeclared functions... " >&6; } if test ${ac_cv_c_undeclared_builtin_options+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_save_CFLAGS=$CFLAGS ac_cv_c_undeclared_builtin_options='cannot detect' for ac_arg in '' -fno-builtin; do CFLAGS="$ac_save_CFLAGS $ac_arg" # This test program should *not* compile successfully. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { (void) strchr; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) # This test program should compile successfully. # No library function is consistently available on # freestanding implementations, so test against a dummy # declaration. Include always-available headers on the # off chance that they somehow elicit warnings. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include extern void ac_decl (int, char *); int main (void) { (void) ac_decl (0, (char *) 0); (void) ac_decl; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : if test x"$ac_arg" = x then : ac_cv_c_undeclared_builtin_options='none needed' else case e in #( e) ac_cv_c_undeclared_builtin_options=$ac_arg ;; esac fi break fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext done CFLAGS=$ac_save_CFLAGS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_undeclared_builtin_options" >&5 printf "%s\n" "$ac_cv_c_undeclared_builtin_options" >&6; } case $ac_cv_c_undeclared_builtin_options in #( 'cannot detect') : { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot make $CC report undeclared builtins See 'config.log' for more details" "$LINENO" 5; } ;; #( 'none needed') : ac_c_undeclared_builtin_options='' ;; #( *) : ac_c_undeclared_builtin_options=$ac_cv_c_undeclared_builtin_options ;; esac #-------------------------------------------------------------------- # On a few very rare systems, all of the libm.a stuff is # already in libc.a. Set compiler flags accordingly. #-------------------------------------------------------------------- ac_fn_c_check_func "$LINENO" "sin" "ac_cv_func_sin" if test "x$ac_cv_func_sin" = xyes then : MATH_LIBS="" else case e in #( e) MATH_LIBS="-lm" ;; esac fi #-------------------------------------------------------------------- # Interactive UNIX requires -linet instead of -lsocket, plus it # needs net/errno.h to define the socket-related error codes. #-------------------------------------------------------------------- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for main in -linet" >&5 printf %s "checking for main in -linet... " >&6; } if test ${ac_cv_lib_inet_main+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-linet $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return main (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_inet_main=yes else case e in #( e) ac_cv_lib_inet_main=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_inet_main" >&5 printf "%s\n" "$ac_cv_lib_inet_main" >&6; } if test "x$ac_cv_lib_inet_main" = xyes then : LIBS="$LIBS -linet" fi ac_fn_c_check_header_compile "$LINENO" "net/errno.h" "ac_cv_header_net_errno_h" "$ac_includes_default" if test "x$ac_cv_header_net_errno_h" = xyes then : printf "%s\n" "#define HAVE_NET_ERRNO_H 1" >>confdefs.h fi #-------------------------------------------------------------------- # Check for the existence of the -lsocket and -lnsl libraries. # The order here is important, so that they end up in the right # order in the command line generated by make. Here are some # special considerations: # 1. Use "connect" and "accept" to check for -lsocket, and # "gethostbyname" to check for -lnsl. # 2. Use each function name only once: can't redo a check because # autoconf caches the results of the last check and won't redo it. # 3. Use -lnsl and -lsocket only if they supply procedures that # aren't already present in the normal libraries. This is because # IRIX 5.2 has libraries, but they aren't needed and they're # bogus: they goof up name resolution if used. # 4. On some SVR4 systems, can't use -lsocket without -lnsl too. # To get around this problem, check for both libraries together # if -lsocket doesn't work by itself. #-------------------------------------------------------------------- tcl_checkBoth=0 ac_fn_c_check_func "$LINENO" "connect" "ac_cv_func_connect" if test "x$ac_cv_func_connect" = xyes then : tcl_checkSocket=0 else case e in #( e) tcl_checkSocket=1 ;; esac fi if test "$tcl_checkSocket" = 1; then ac_fn_c_check_func "$LINENO" "setsockopt" "ac_cv_func_setsockopt" if test "x$ac_cv_func_setsockopt" = xyes then : else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for setsockopt in -lsocket" >&5 printf %s "checking for setsockopt in -lsocket... " >&6; } if test ${ac_cv_lib_socket_setsockopt+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lsocket $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char setsockopt (void); int main (void) { return setsockopt (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_socket_setsockopt=yes else case e in #( e) ac_cv_lib_socket_setsockopt=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_setsockopt" >&5 printf "%s\n" "$ac_cv_lib_socket_setsockopt" >&6; } if test "x$ac_cv_lib_socket_setsockopt" = xyes then : LIBS="$LIBS -lsocket" else case e in #( e) tcl_checkBoth=1 ;; esac fi ;; esac fi fi if test "$tcl_checkBoth" = 1; then tk_oldLibs=$LIBS LIBS="$LIBS -lsocket -lnsl" ac_fn_c_check_func "$LINENO" "accept" "ac_cv_func_accept" if test "x$ac_cv_func_accept" = xyes then : tcl_checkNsl=0 else case e in #( e) LIBS=$tk_oldLibs ;; esac fi fi ac_fn_c_check_func "$LINENO" "gethostbyname" "ac_cv_func_gethostbyname" if test "x$ac_cv_func_gethostbyname" = xyes then : else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for gethostbyname in -lnsl" >&5 printf %s "checking for gethostbyname in -lnsl... " >&6; } if test ${ac_cv_lib_nsl_gethostbyname+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lnsl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char gethostbyname (void); int main (void) { return gethostbyname (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_nsl_gethostbyname=yes else case e in #( e) ac_cv_lib_nsl_gethostbyname=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nsl_gethostbyname" >&5 printf "%s\n" "$ac_cv_lib_nsl_gethostbyname" >&6; } if test "x$ac_cv_lib_nsl_gethostbyname" = xyes then : LIBS="$LIBS -lnsl" fi ;; esac fi printf "%s\n" "#define _REENTRANT 1" >>confdefs.h printf "%s\n" "#define _THREAD_SAFE 1" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pthread_mutex_init in -lpthread" >&5 printf %s "checking for pthread_mutex_init in -lpthread... " >&6; } if test ${ac_cv_lib_pthread_pthread_mutex_init+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lpthread $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char pthread_mutex_init (void); int main (void) { return pthread_mutex_init (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_pthread_pthread_mutex_init=yes else case e in #( e) ac_cv_lib_pthread_pthread_mutex_init=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_mutex_init" >&5 printf "%s\n" "$ac_cv_lib_pthread_pthread_mutex_init" >&6; } if test "x$ac_cv_lib_pthread_pthread_mutex_init" = xyes then : tcl_ok=yes else case e in #( e) tcl_ok=no ;; esac fi if test "$tcl_ok" = "no"; then # Check a little harder for __pthread_mutex_init in the same # library, as some systems hide it there until pthread.h is # defined. We could alternatively do an AC_TRY_COMPILE with # pthread.h, but that will work with libpthread really doesn't # exist, like AIX 4.2. [Bug: 4359] { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for __pthread_mutex_init in -lpthread" >&5 printf %s "checking for __pthread_mutex_init in -lpthread... " >&6; } if test ${ac_cv_lib_pthread___pthread_mutex_init+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lpthread $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char __pthread_mutex_init (void); int main (void) { return __pthread_mutex_init (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_pthread___pthread_mutex_init=yes else case e in #( e) ac_cv_lib_pthread___pthread_mutex_init=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread___pthread_mutex_init" >&5 printf "%s\n" "$ac_cv_lib_pthread___pthread_mutex_init" >&6; } if test "x$ac_cv_lib_pthread___pthread_mutex_init" = xyes then : tcl_ok=yes else case e in #( e) tcl_ok=no ;; esac fi fi if test "$tcl_ok" = "yes"; then # The space is needed THREADS_LIBS=" -lpthread" else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pthread_mutex_init in -lpthreads" >&5 printf %s "checking for pthread_mutex_init in -lpthreads... " >&6; } if test ${ac_cv_lib_pthreads_pthread_mutex_init+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lpthreads $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char pthread_mutex_init (void); int main (void) { return pthread_mutex_init (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_pthreads_pthread_mutex_init=yes else case e in #( e) ac_cv_lib_pthreads_pthread_mutex_init=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthreads_pthread_mutex_init" >&5 printf "%s\n" "$ac_cv_lib_pthreads_pthread_mutex_init" >&6; } if test "x$ac_cv_lib_pthreads_pthread_mutex_init" = xyes then : _ok=yes else case e in #( e) tcl_ok=no ;; esac fi if test "$tcl_ok" = "yes"; then # The space is needed THREADS_LIBS=" -lpthreads" else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pthread_mutex_init in -lc" >&5 printf %s "checking for pthread_mutex_init in -lc... " >&6; } if test ${ac_cv_lib_c_pthread_mutex_init+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lc $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char pthread_mutex_init (void); int main (void) { return pthread_mutex_init (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_c_pthread_mutex_init=yes else case e in #( e) ac_cv_lib_c_pthread_mutex_init=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_pthread_mutex_init" >&5 printf "%s\n" "$ac_cv_lib_c_pthread_mutex_init" >&6; } if test "x$ac_cv_lib_c_pthread_mutex_init" = xyes then : tcl_ok=yes else case e in #( e) tcl_ok=no ;; esac fi if test "$tcl_ok" = "no"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pthread_mutex_init in -lc_r" >&5 printf %s "checking for pthread_mutex_init in -lc_r... " >&6; } if test ${ac_cv_lib_c_r_pthread_mutex_init+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lc_r $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char pthread_mutex_init (void); int main (void) { return pthread_mutex_init (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_c_r_pthread_mutex_init=yes else case e in #( e) ac_cv_lib_c_r_pthread_mutex_init=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_r_pthread_mutex_init" >&5 printf "%s\n" "$ac_cv_lib_c_r_pthread_mutex_init" >&6; } if test "x$ac_cv_lib_c_r_pthread_mutex_init" = xyes then : tcl_ok=yes else case e in #( e) tcl_ok=no ;; esac fi if test "$tcl_ok" = "yes"; then # The space is needed THREADS_LIBS=" -pthread" else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Don't know how to find pthread lib on your system - you must edit the LIBS in the Makefile..." >&5 printf "%s\n" "$as_me: WARNING: Don't know how to find pthread lib on your system - you must edit the LIBS in the Makefile..." >&2;} fi fi fi fi # Does the pthread-implementation provide # 'pthread_attr_setstacksize' ? ac_saved_libs=$LIBS LIBS="$LIBS $THREADS_LIBS" ac_fn_c_check_func "$LINENO" "pthread_attr_setstacksize" "ac_cv_func_pthread_attr_setstacksize" if test "x$ac_cv_func_pthread_attr_setstacksize" = xyes then : printf "%s\n" "#define HAVE_PTHREAD_ATTR_SETSTACKSIZE 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "pthread_atfork" "ac_cv_func_pthread_atfork" if test "x$ac_cv_func_pthread_atfork" = xyes then : printf "%s\n" "#define HAVE_PTHREAD_ATFORK 1" >>confdefs.h fi LIBS=$ac_saved_libs # TIP #509 ac_fn_check_decl "$LINENO" "PTHREAD_MUTEX_RECURSIVE" "ac_cv_have_decl_PTHREAD_MUTEX_RECURSIVE" "#include " "$ac_c_undeclared_builtin_options" "CFLAGS" if test "x$ac_cv_have_decl_PTHREAD_MUTEX_RECURSIVE" = xyes then : ac_have_decl=1 else case e in #( e) ac_have_decl=0 ;; esac fi printf "%s\n" "#define HAVE_DECL_PTHREAD_MUTEX_RECURSIVE $ac_have_decl" >>confdefs.h if test $ac_have_decl = 1 then : tcl_ok=yes else case e in #( e) tcl_ok=no ;; esac fi # Add the threads support libraries LIBS="$LIBS$THREADS_LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to build libraries" >&5 printf %s "checking how to build libraries... " >&6; } # Check whether --enable-shared was given. if test ${enable_shared+y} then : enableval=$enable_shared; tcl_ok=$enableval else case e in #( e) tcl_ok=yes ;; esac fi if test "$tcl_ok" = "yes" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: shared" >&5 printf "%s\n" "shared" >&6; } SHARED_BUILD=1 else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: static" >&5 printf "%s\n" "static" >&6; } SHARED_BUILD=0 printf "%s\n" "#define STATIC_BUILD 1" >>confdefs.h fi #-------------------------------------------------------------------- # Look for a native installed tclsh binary (if available) # If one cannot be found then use the binary we build (fails for # cross compiling). This is used for NATIVE_TCLSH in Makefile. #-------------------------------------------------------------------- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for tclsh" >&5 printf %s "checking for tclsh... " >&6; } if test ${ac_cv_path_tclsh+y} then : printf %s "(cached) " >&6 else case e in #( e) search_path=`echo ${PATH} | sed -e 's/:/ /g'` for dir in $search_path ; do for j in `ls -r $dir/tclsh[8-9]* 2> /dev/null` \ `ls -r $dir/tclsh* 2> /dev/null` ; do if test x"$ac_cv_path_tclsh" = x ; then if test -f "$j" ; then ac_cv_path_tclsh=$j break fi fi done done ;; esac fi if test -f "$ac_cv_path_tclsh" ; then TCLSH_PROG="$ac_cv_path_tclsh" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $TCLSH_PROG" >&5 printf "%s\n" "$TCLSH_PROG" >&6; } else # It is not an error if an installed version of Tcl can't be located. TCLSH_PROG="" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: No tclsh found on PATH" >&5 printf "%s\n" "No tclsh found on PATH" >&6; } fi if test "$TCLSH_PROG" = ""; then TCLSH_PROG='./${TCL_EXE}' fi #------------------------------------------------------------------------ # Add stuff for zlib #------------------------------------------------------------------------ zlib_ok=yes ac_fn_c_check_header_compile "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default" if test "x$ac_cv_header_zlib_h" = xyes then : ac_fn_c_check_type "$LINENO" "gz_header" "ac_cv_type_gz_header" "#include " if test "x$ac_cv_type_gz_header" = xyes then : else case e in #( e) zlib_ok=no ;; esac fi else case e in #( e) zlib_ok=no ;; esac fi if test $zlib_ok = yes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing deflateSetHeader" >&5 printf %s "checking for library containing deflateSetHeader... " >&6; } if test ${ac_cv_search_deflateSetHeader+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char deflateSetHeader (void); int main (void) { return deflateSetHeader (); ; return 0; } _ACEOF for ac_lib in '' z do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO" then : ac_cv_search_deflateSetHeader=$ac_res fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext if test ${ac_cv_search_deflateSetHeader+y} then : break fi done if test ${ac_cv_search_deflateSetHeader+y} then : else case e in #( e) ac_cv_search_deflateSetHeader=no ;; esac fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_deflateSetHeader" >&5 printf "%s\n" "$ac_cv_search_deflateSetHeader" >&6; } ac_res=$ac_cv_search_deflateSetHeader if test "$ac_res" != no then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" else case e in #( e) zlib_ok=no ;; esac fi fi if test $zlib_ok = no then : ZLIB_OBJS=\${ZLIB_OBJS} ZLIB_SRCS=\${ZLIB_SRCS} ZLIB_INCLUDE=-I\${ZLIB_DIR} printf "%s\n" "#define TCL_WITH_INTERNAL_ZLIB 1" >>confdefs.h fi #------------------------------------------------------------------------ # Add stuff for libtommath libtommath_ok=yes # Check whether --with-system-libtommath was given. if test ${with_system_libtommath+y} then : withval=$with_system_libtommath; libtommath_ok=${withval} fi if test x"${libtommath_ok}" = x -o x"${libtommath_ok}" != xno; then ac_fn_c_check_header_compile "$LINENO" "tommath.h" "ac_cv_header_tommath_h" "$ac_includes_default" if test "x$ac_cv_header_tommath_h" = xyes then : ac_fn_c_check_type "$LINENO" "mp_int" "ac_cv_type_mp_int" "#include " if test "x$ac_cv_type_mp_int" = xyes then : else case e in #( e) libtommath_ok=no ;; esac fi else case e in #( e) libtommath_ok=no ;; esac fi if test $libtommath_ok = yes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for mp_log_u32 in -ltommath" >&5 printf %s "checking for mp_log_u32 in -ltommath... " >&6; } if test ${ac_cv_lib_tommath_mp_log_u32+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ltommath $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char mp_log_u32 (void); int main (void) { return mp_log_u32 (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_tommath_mp_log_u32=yes else case e in #( e) ac_cv_lib_tommath_mp_log_u32=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_tommath_mp_log_u32" >&5 printf "%s\n" "$ac_cv_lib_tommath_mp_log_u32" >&6; } if test "x$ac_cv_lib_tommath_mp_log_u32" = xyes then : MATH_LIBS="$MATH_LIBS -ltommath" else case e in #( e) libtommath_ok=no ;; esac fi fi fi if test $libtommath_ok = yes then : TCL_PC_REQUIRES_PRIVATE='libtommath >= 1.2.0,' TCL_PC_CFLAGS='-DTCL_WITH_EXTERNAL_TOMMATH' printf "%s\n" "#define TCL_WITH_EXTERNAL_TOMMATH 1" >>confdefs.h else case e in #( e) TOMMATH_OBJS=\${TOMMATH_OBJS} TOMMATH_SRCS=\${TOMMATH_SRCS} TOMMATH_INCLUDE=-I\${TOMMATH_DIR} ;; esac fi #-------------------------------------------------------------------- # The statements below define a collection of compile flags. This # macro depends on the value of SHARED_BUILD, and should be called # after SC_ENABLE_SHARED checks the configure switches. #-------------------------------------------------------------------- if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. set dummy ${ac_tool_prefix}ranlib; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_RANLIB+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$RANLIB"; then ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi RANLIB=$ac_cv_prog_RANLIB if test -n "$RANLIB"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 printf "%s\n" "$RANLIB" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_RANLIB"; then ac_ct_RANLIB=$RANLIB # Extract the first word of "ranlib", so it can be a program name with args. set dummy ranlib; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_RANLIB+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_RANLIB"; then ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_RANLIB="ranlib" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB if test -n "$ac_ct_RANLIB"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 printf "%s\n" "$ac_ct_RANLIB" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_RANLIB" = x; then RANLIB=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac RANLIB=$ac_ct_RANLIB fi else RANLIB="$ac_cv_prog_RANLIB" fi # Step 0.a: Enable 64 bit support? { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if 64bit support is requested" >&5 printf %s "checking if 64bit support is requested... " >&6; } # Check whether --enable-64bit was given. if test ${enable_64bit+y} then : enableval=$enable_64bit; do64bit=$enableval else case e in #( e) do64bit=no ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $do64bit" >&5 printf "%s\n" "$do64bit" >&6; } # Step 0.b: Enable Solaris 64 bit VIS support? { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if 64bit Sparc VIS support is requested" >&5 printf %s "checking if 64bit Sparc VIS support is requested... " >&6; } # Check whether --enable-64bit-vis was given. if test ${enable_64bit_vis+y} then : enableval=$enable_64bit_vis; do64bitVIS=$enableval else case e in #( e) do64bitVIS=no ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $do64bitVIS" >&5 printf "%s\n" "$do64bitVIS" >&6; } # Force 64bit on with VIS if test "$do64bitVIS" = "yes" then : do64bit=yes fi # Step 0.c: Check if visibility support is available. Do this here so # that platform specific alternatives can be used below if this fails. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler supports visibility \"hidden\"" >&5 printf %s "checking if compiler supports visibility \"hidden\"... " >&6; } if test ${tcl_cv_cc_visibility_hidden+y} then : printf %s "(cached) " >&6 else case e in #( e) hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ extern __attribute__((__visibility__("hidden"))) void f(void); void f(void) {} int main (void) { f(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : tcl_cv_cc_visibility_hidden=yes else case e in #( e) tcl_cv_cc_visibility_hidden=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext CFLAGS=$hold_cflags ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_visibility_hidden" >&5 printf "%s\n" "$tcl_cv_cc_visibility_hidden" >&6; } if test $tcl_cv_cc_visibility_hidden = yes then : printf "%s\n" "#define MODULE_SCOPE extern __attribute__((__visibility__(\"hidden\")))" >>confdefs.h printf "%s\n" "#define HAVE_HIDDEN 1" >>confdefs.h fi # Step 0.d: Disable -rpath support? { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if rpath support is requested" >&5 printf %s "checking if rpath support is requested... " >&6; } # Check whether --enable-rpath was given. if test ${enable_rpath+y} then : enableval=$enable_rpath; doRpath=$enableval else case e in #( e) doRpath=yes ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $doRpath" >&5 printf "%s\n" "$doRpath" >&6; } # Step 1: set the variable "system" to hold the name and version number # for the system. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking system version" >&5 printf %s "checking system version... " >&6; } if test ${tcl_cv_sys_version+y} then : printf %s "(cached) " >&6 else case e in #( e) if test "${TEA_PLATFORM}" = "windows" ; then tcl_cv_sys_version=windows else tcl_cv_sys_version=`uname -s`-`uname -r` if test "$?" -ne 0 ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: can't find uname command" >&5 printf "%s\n" "$as_me: WARNING: can't find uname command" >&2;} tcl_cv_sys_version=unknown else if test "`uname -s`" = "AIX" ; then tcl_cv_sys_version=AIX-`uname -v`.`uname -r` fi if test "`uname -s`" = "NetBSD" -a -f /etc/debian_version ; then tcl_cv_sys_version=NetBSD-Debian fi fi fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_sys_version" >&5 printf "%s\n" "$tcl_cv_sys_version" >&6; } system=$tcl_cv_sys_version # Step 2: check for existence of -ldl library. This is needed because # Linux can use either -ldl or -ldld for dynamic loading. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 printf %s "checking for dlopen in -ldl... " >&6; } if test ${ac_cv_lib_dl_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dl_dlopen=yes else case e in #( e) ac_cv_lib_dl_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 printf "%s\n" "$ac_cv_lib_dl_dlopen" >&6; } if test "x$ac_cv_lib_dl_dlopen" = xyes then : have_dl=yes else case e in #( e) have_dl=no ;; esac fi # Require ranlib early so we can override it in special cases below. # Step 3: set configuration options based on system name and version. do64bit_ok=no # default to '{$LIBS}' and set to "" on per-platform necessary basis SHLIB_LD_LIBS='${LIBS}' LDFLAGS_ORIG="$LDFLAGS" # When ld needs options to work in 64-bit mode, put them in # LDFLAGS_ARCH so they eventually end up in LDFLAGS even if [load] # is disabled by the user. [Bug 1016796] LDFLAGS_ARCH="" UNSHARED_LIB_SUFFIX="" TCL_TRIM_DOTS='`echo ${VERSION} | tr -d .`' ECHO_VERSION='`echo ${VERSION}`' TCL_LIB_VERSIONS_OK=ok CFLAGS_DEBUG=-g if test "$GCC" = yes then : CFLAGS_OPTIMIZE=-O2 CFLAGS_WARNING="-Wall -Wextra -Wshadow -Wundef -Wwrite-strings -Wpointer-arith" case "${CC}" in *++|*++-*) ;; *) CFLAGS_WARNING="${CFLAGS_WARNING} -Wc++-compat -fextended-identifiers" ;; esac else case e in #( e) CFLAGS_OPTIMIZE=-O CFLAGS_WARNING="" ;; esac fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}ar", so it can be a program name with args. set dummy ${ac_tool_prefix}ar; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_AR+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$AR"; then ac_cv_prog_AR="$AR" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_AR="${ac_tool_prefix}ar" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi AR=$ac_cv_prog_AR if test -n "$AR"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 printf "%s\n" "$AR" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_AR"; then ac_ct_AR=$AR # Extract the first word of "ar", so it can be a program name with args. set dummy ar; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_AR+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_AR"; then ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_AR="ar" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_AR=$ac_cv_prog_ac_ct_AR if test -n "$ac_ct_AR"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 printf "%s\n" "$ac_ct_AR" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_AR" = x; then AR="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac AR=$ac_ct_AR fi else AR="$ac_cv_prog_AR" fi STLIB_LD='${AR} cr' LD_LIBRARY_PATH_VAR="LD_LIBRARY_PATH" PLAT_OBJS="" PLAT_SRCS="" LDAIX_SRC="" if test "x${SHLIB_VERSION}" = x then : SHLIB_VERSION="1.0" fi case $system in AIX-*) if test "$GCC" != "yes" then : # AIX requires the _r compiler when gcc isn't being used case "${CC}" in *_r|*_r\ *) # ok ... ;; *) # Make sure only first arg gets _r CC=`echo "$CC" | sed -e 's/^\([^ ]*\)/\1_r/'` ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Using $CC for compiling with threads" >&5 printf "%s\n" "Using $CC for compiling with threads" >&6; } fi LIBS="$LIBS -lc" SHLIB_CFLAGS="" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" LD_LIBRARY_PATH_VAR="LIBPATH" # ldAix No longer needed with use of -bexpall/-brtl # but some extensions may still reference it LDAIX_SRC='$(UNIX_DIR)/ldAix' # Check to enable 64-bit flags for compiler/linker if test "$do64bit" = yes then : if test "$GCC" = yes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported with GCC on $system" >&5 printf "%s\n" "$as_me: WARNING: 64bit mode not supported with GCC on $system" >&2;} else case e in #( e) do64bit_ok=yes CFLAGS="$CFLAGS -q64" LDFLAGS_ARCH="-q64" RANLIB="${RANLIB} -X64" AR="${AR} -X64" SHLIB_LD_FLAGS="-b64" ;; esac fi fi if test "`uname -m`" = ia64 then : # AIX-5 uses ELF style dynamic libraries on IA-64, but not PPC SHLIB_LD="/usr/ccs/bin/ld -G -z text" # AIX-5 has dl* in libc.so DL_LIBS="" if test "$GCC" = yes then : CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' else case e in #( e) CC_SEARCH_FLAGS='-R${LIB_RUNTIME_DIR}' ;; esac fi LD_SEARCH_FLAGS='-R ${LIB_RUNTIME_DIR}' else case e in #( e) if test "$GCC" = yes then : SHLIB_LD='${CC} -shared -Wl,-bexpall' else case e in #( e) SHLIB_LD="/bin/ld -bhalt:4 -bM:SRE -bexpall -H512 -T512 -bnoentry" LDFLAGS="$LDFLAGS -brtl" ;; esac fi SHLIB_LD="${SHLIB_LD} ${SHLIB_LD_FLAGS}" DL_LIBS="-ldl" CC_SEARCH_FLAGS='-L${LIB_RUNTIME_DIR}' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} ;; esac fi ;; BeOS*) SHLIB_CFLAGS="-fPIC" SHLIB_LD='${CC} -nostart' SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" #----------------------------------------------------------- # Check for inet_ntoa in -lbind, for BeOS (which also needs # -lsocket, even if the network functions are in -lnet which # is always linked to, for compatibility. #----------------------------------------------------------- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for inet_ntoa in -lbind" >&5 printf %s "checking for inet_ntoa in -lbind... " >&6; } if test ${ac_cv_lib_bind_inet_ntoa+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lbind $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char inet_ntoa (void); int main (void) { return inet_ntoa (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_bind_inet_ntoa=yes else case e in #( e) ac_cv_lib_bind_inet_ntoa=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bind_inet_ntoa" >&5 printf "%s\n" "$ac_cv_lib_bind_inet_ntoa" >&6; } if test "x$ac_cv_lib_bind_inet_ntoa" = xyes then : LIBS="$LIBS -lbind -lsocket" fi ;; BSD/OS-2.1*|BSD/OS-3*) SHLIB_CFLAGS="" SHLIB_LD="shlicc -r" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" ;; BSD/OS-4.*) SHLIB_CFLAGS="-export-dynamic -fPIC" SHLIB_LD='${CC} -shared' SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" LDFLAGS="$LDFLAGS -export-dynamic" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" ;; CYGWIN_*|MINGW32_*|MSYS_*) SHLIB_CFLAGS="-fno-common" SHLIB_LD='${CC} -shared' SHLIB_SUFFIX=".dll" DL_OBJS="tclLoadDl.o" PLAT_OBJS='${CYGWIN_OBJS}' PLAT_SRCS='${CYGWIN_SRCS}' DL_LIBS="-ldl" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Cygwin version of gcc" >&5 printf %s "checking for Cygwin version of gcc... " >&6; } if test ${ac_cv_cygwin+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef __CYGWIN__ #error cygwin #endif int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_cygwin=no else case e in #( e) ac_cv_cygwin=yes ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cygwin" >&5 printf "%s\n" "$ac_cv_cygwin" >&6; } if test "$ac_cv_cygwin" = "no"; then as_fn_error $? "${CC} is not a cygwin compiler." "$LINENO" 5 fi do64bit_ok=yes if test "x${SHARED_BUILD}" = "x1"; then echo "running cd ../win; ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args --enable-64bit --host=x86_64-w64-mingw32" # The eval makes quoting arguments work. if cd ../win; eval ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args --enable-64bit --host=x86_64-w64-mingw32; cd ../unix then : else { echo "configure: error: configure failed for ../win" 1>&2; exit 1; } fi fi ;; dgux*) SHLIB_CFLAGS="-K PIC" SHLIB_LD='${CC} -G' SHLIB_LD_LIBS="" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" ;; Haiku*) LDFLAGS="$LDFLAGS -Wl,--export-dynamic" SHLIB_CFLAGS="-fPIC" SHLIB_SUFFIX=".so" SHLIB_LD='${CC} ${CFLAGS} ${LDFLAGS} -shared' DL_OBJS="tclLoadDl.o" DL_LIBS="-lroot" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for inet_ntoa in -lnetwork" >&5 printf %s "checking for inet_ntoa in -lnetwork... " >&6; } if test ${ac_cv_lib_network_inet_ntoa+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lnetwork $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char inet_ntoa (void); int main (void) { return inet_ntoa (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_network_inet_ntoa=yes else case e in #( e) ac_cv_lib_network_inet_ntoa=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_network_inet_ntoa" >&5 printf "%s\n" "$ac_cv_lib_network_inet_ntoa" >&6; } if test "x$ac_cv_lib_network_inet_ntoa" = xyes then : LIBS="$LIBS -lnetwork" fi ;; HP-UX-*.11.*) # Use updated header definitions where possible printf "%s\n" "#define _XOPEN_SOURCE_EXTENDED 1" >>confdefs.h printf "%s\n" "#define _XOPEN_SOURCE 1" >>confdefs.h LIBS="$LIBS -lxnet" # Use the XOPEN network library if test "`uname -m`" = ia64 then : SHLIB_SUFFIX=".so" else case e in #( e) SHLIB_SUFFIX=".sl" ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 printf %s "checking for shl_load in -ldld... " >&6; } if test ${ac_cv_lib_dld_shl_load+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldld $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char shl_load (void); int main (void) { return shl_load (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dld_shl_load=yes else case e in #( e) ac_cv_lib_dld_shl_load=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 printf "%s\n" "$ac_cv_lib_dld_shl_load" >&6; } if test "x$ac_cv_lib_dld_shl_load" = xyes then : tcl_ok=yes else case e in #( e) tcl_ok=no ;; esac fi if test "$tcl_ok" = yes then : SHLIB_CFLAGS="+z" SHLIB_LD="ld -b" DL_OBJS="tclLoadShl.o" DL_LIBS="-ldld" LDFLAGS="$LDFLAGS -Wl,-E" CC_SEARCH_FLAGS='-Wl,+s,+b,${LIB_RUNTIME_DIR}:.' LD_SEARCH_FLAGS='+s +b ${LIB_RUNTIME_DIR}:.' LD_LIBRARY_PATH_VAR="SHLIB_PATH" fi if test "$GCC" = yes then : SHLIB_LD='${CC} -shared' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} else case e in #( e) CFLAGS="$CFLAGS -z" ;; esac fi # Users may want PA-RISC 1.1/2.0 portable code - needs HP cc #CFLAGS="$CFLAGS +DAportable" # Check to enable 64-bit flags for compiler/linker if test "$do64bit" = "yes" then : if test "$GCC" = yes then : case `${CC} -dumpmachine` in hppa64*) # 64-bit gcc in use. Fix flags for GNU ld. do64bit_ok=yes SHLIB_LD='${CC} -shared' if test $doRpath = yes then : CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' fi LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} ;; *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported with GCC on $system" >&5 printf "%s\n" "$as_me: WARNING: 64bit mode not supported with GCC on $system" >&2;} ;; esac else case e in #( e) do64bit_ok=yes CFLAGS="$CFLAGS +DD64" LDFLAGS_ARCH="+DD64" ;; esac fi fi ;; HP-UX-*.08.*|HP-UX-*.09.*|HP-UX-*.10.*) SHLIB_SUFFIX=".sl" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 printf %s "checking for shl_load in -ldld... " >&6; } if test ${ac_cv_lib_dld_shl_load+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldld $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char shl_load (void); int main (void) { return shl_load (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dld_shl_load=yes else case e in #( e) ac_cv_lib_dld_shl_load=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 printf "%s\n" "$ac_cv_lib_dld_shl_load" >&6; } if test "x$ac_cv_lib_dld_shl_load" = xyes then : tcl_ok=yes else case e in #( e) tcl_ok=no ;; esac fi if test "$tcl_ok" = yes then : SHLIB_CFLAGS="+z" SHLIB_LD="ld -b" SHLIB_LD_LIBS="" DL_OBJS="tclLoadShl.o" DL_LIBS="-ldld" LDFLAGS="$LDFLAGS -Wl,-E" CC_SEARCH_FLAGS='-Wl,+s,+b,${LIB_RUNTIME_DIR}:.' LD_SEARCH_FLAGS='+s +b ${LIB_RUNTIME_DIR}:.' LD_LIBRARY_PATH_VAR="SHLIB_PATH" fi ;; IRIX-5.*) SHLIB_CFLAGS="" SHLIB_LD="ld -shared -rdata_shared" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" case " $LIBOBJS " in *" mkstemp.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS mkstemp.$ac_objext" ;; esac if test $doRpath = yes then : CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}' fi ;; IRIX-6.*) SHLIB_CFLAGS="" SHLIB_LD="ld -n32 -shared -rdata_shared" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" case " $LIBOBJS " in *" mkstemp.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS mkstemp.$ac_objext" ;; esac if test $doRpath = yes then : CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}' fi if test "$GCC" = yes then : CFLAGS="$CFLAGS -mabi=n32" LDFLAGS="$LDFLAGS -mabi=n32" else case e in #( e) case $system in IRIX-6.3) # Use to build 6.2 compatible binaries on 6.3. CFLAGS="$CFLAGS -n32 -D_OLD_TERMIOS" ;; *) CFLAGS="$CFLAGS -n32" ;; esac LDFLAGS="$LDFLAGS -n32" ;; esac fi ;; IRIX64-6.*) SHLIB_CFLAGS="" SHLIB_LD="ld -n32 -shared -rdata_shared" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" case " $LIBOBJS " in *" mkstemp.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS mkstemp.$ac_objext" ;; esac if test $doRpath = yes then : CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}' fi # Check to enable 64-bit flags for compiler/linker if test "$do64bit" = yes then : if test "$GCC" = yes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported by gcc" >&5 printf "%s\n" "$as_me: WARNING: 64bit mode not supported by gcc" >&2;} else case e in #( e) do64bit_ok=yes SHLIB_LD="ld -64 -shared -rdata_shared" CFLAGS="$CFLAGS -64" LDFLAGS_ARCH="-64" ;; esac fi fi ;; Linux*|GNU*|NetBSD-Debian|DragonFly-*|FreeBSD-*) SHLIB_CFLAGS="-fPIC -fno-common" SHLIB_SUFFIX=".so" CFLAGS_OPTIMIZE="-O2" # egcs-2.91.66 on Redhat Linux 6.0 generates lots of warnings # when you inline the string and math operations. Turn this off to # get rid of the warnings. #CFLAGS_OPTIMIZE="${CFLAGS_OPTIMIZE} -D__NO_STRING_INLINES -D__NO_MATH_INLINES" SHLIB_LD='${CC} ${CFLAGS} ${LDFLAGS} -shared' DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" LDFLAGS="$LDFLAGS -Wl,--export-dynamic" case $system in DragonFly-*|FreeBSD-*) # The -pthread needs to go in the LDFLAGS, not LIBS LIBS=`echo $LIBS | sed s/-pthread//` CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LDFLAGS="$LDFLAGS $PTHREAD_LIBS" ;; esac if test $doRpath = yes then : CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' fi LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} if test "`uname -m`" = "alpha" then : CFLAGS="$CFLAGS -mieee" fi if test $do64bit = yes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler accepts -m64 flag" >&5 printf %s "checking if compiler accepts -m64 flag... " >&6; } if test ${tcl_cv_cc_m64+y} then : printf %s "(cached) " >&6 else case e in #( e) hold_cflags=$CFLAGS CFLAGS="$CFLAGS -m64" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : tcl_cv_cc_m64=yes else case e in #( e) tcl_cv_cc_m64=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext CFLAGS=$hold_cflags ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_m64" >&5 printf "%s\n" "$tcl_cv_cc_m64" >&6; } if test $tcl_cv_cc_m64 = yes then : CFLAGS="$CFLAGS -m64" do64bit_ok=yes fi fi # The combo of gcc + glibc has a bug related to inlining of # functions like strtol()/strtoul(). The -fno-builtin flag should address # this problem but it does not work. The -fno-inline flag is kind # of overkill but it works. Disable inlining only when one of the # files in compat/*.c is being linked in. if test x"${USE_COMPAT}" != x then : CFLAGS="$CFLAGS -fno-inline" fi ;; Lynx*) SHLIB_CFLAGS="-fPIC" SHLIB_SUFFIX=".so" CFLAGS_OPTIMIZE=-02 SHLIB_LD='${CC} -shared' DL_OBJS="tclLoadDl.o" DL_LIBS="-mshared -ldl" LD_FLAGS="-Wl,--export-dynamic" if test $doRpath = yes then : CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' fi ;; OpenBSD-*) arch=`arch -s` case "$arch" in alpha|sparc64) SHLIB_CFLAGS="-fPIC" ;; *) SHLIB_CFLAGS="-fpic" ;; esac SHLIB_LD='${CC} ${SHLIB_CFLAGS} -shared' SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" if test $doRpath = yes then : CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' fi LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} SHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.so.${SHLIB_VERSION}' LDFLAGS="-Wl,-export-dynamic" CFLAGS_OPTIMIZE="-O2" # On OpenBSD: Compile with -pthread # Don't link with -lpthread LIBS=`echo $LIBS | sed s/-lpthread//` CFLAGS="$CFLAGS -pthread" # OpenBSD doesn't do version numbers with dots. UNSHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.a' TCL_LIB_VERSIONS_OK=nodots ;; NetBSD-*) # NetBSD has ELF and can use 'cc -shared' to build shared libs SHLIB_CFLAGS="-fPIC" SHLIB_LD='${CC} ${SHLIB_CFLAGS} -shared' SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" LDFLAGS="$LDFLAGS -export-dynamic" if test $doRpath = yes then : CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' fi LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} # The -pthread needs to go in the CFLAGS, not LIBS LIBS=`echo $LIBS | sed s/-pthread//` CFLAGS="$CFLAGS -pthread" LDFLAGS="$LDFLAGS -pthread" ;; Darwin-*) CFLAGS_OPTIMIZE="-O2" SHLIB_CFLAGS="-fno-common" # To avoid discrepancies between what headers configure sees during # preprocessing tests and compiling tests, move any -isysroot and # -mmacosx-version-min flags from CFLAGS to CPPFLAGS: CPPFLAGS="${CPPFLAGS} `echo " ${CFLAGS}" | \ awk 'BEGIN {FS=" +-";ORS=" "}; {for (i=2;i<=NF;i++) \ if ($i~/^(isysroot|mmacosx-version-min)/) print "-"$i}'`" CFLAGS="`echo " ${CFLAGS}" | \ awk 'BEGIN {FS=" +-";ORS=" "}; {for (i=2;i<=NF;i++) \ if (!($i~/^(isysroot|mmacosx-version-min)/)) print "-"$i}'`" if test $do64bit = yes then : case `arch` in ppc) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler accepts -arch ppc64 flag" >&5 printf %s "checking if compiler accepts -arch ppc64 flag... " >&6; } if test ${tcl_cv_cc_arch_ppc64+y} then : printf %s "(cached) " >&6 else case e in #( e) hold_cflags=$CFLAGS CFLAGS="$CFLAGS -arch ppc64 -mpowerpc64 -mcpu=G5" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : tcl_cv_cc_arch_ppc64=yes else case e in #( e) tcl_cv_cc_arch_ppc64=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext CFLAGS=$hold_cflags ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_arch_ppc64" >&5 printf "%s\n" "$tcl_cv_cc_arch_ppc64" >&6; } if test $tcl_cv_cc_arch_ppc64 = yes then : CFLAGS="$CFLAGS -arch ppc64 -mpowerpc64 -mcpu=G5" do64bit_ok=yes fi;; i386|x86_64) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler accepts -arch x86_64 flag" >&5 printf %s "checking if compiler accepts -arch x86_64 flag... " >&6; } if test ${tcl_cv_cc_arch_x86_64+y} then : printf %s "(cached) " >&6 else case e in #( e) hold_cflags=$CFLAGS CFLAGS="$CFLAGS -arch x86_64" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : tcl_cv_cc_arch_x86_64=yes else case e in #( e) tcl_cv_cc_arch_x86_64=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext CFLAGS=$hold_cflags ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_arch_x86_64" >&5 printf "%s\n" "$tcl_cv_cc_arch_x86_64" >&6; } if test $tcl_cv_cc_arch_x86_64 = yes then : CFLAGS="$CFLAGS -arch x86_64" do64bit_ok=yes fi;; arm64) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler accepts -arch arm64 flag" >&5 printf %s "checking if compiler accepts -arch arm64 flag... " >&6; } if test ${tcl_cv_cc_arch_arm64+y} then : printf %s "(cached) " >&6 else case e in #( e) hold_cflags=$CFLAGS CFLAGS="$CFLAGS -arch arm64" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : tcl_cv_cc_arch_arm64=yes else case e in #( e) tcl_cv_cc_arch_arm64=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext CFLAGS=$hold_cflags ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_arch_arm64" >&5 printf "%s\n" "$tcl_cv_cc_arch_arm64" >&6; } if test $tcl_cv_cc_arch_arm64 = yes then : CFLAGS="$CFLAGS -arch arm64" do64bit_ok=yes fi;; *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Don't know how enable 64-bit on architecture \`arch\`" >&5 printf "%s\n" "$as_me: WARNING: Don't know how enable 64-bit on architecture \`arch\`" >&2;};; esac else case e in #( e) # Check for combined 32-bit and 64-bit fat build if echo "$CFLAGS " |grep -E -q -- '-arch (ppc64|x86_64|arm64) ' \ && echo "$CFLAGS " |grep -E -q -- '-arch (ppc|i386) ' then : fat_32_64=yes fi ;; esac fi SHLIB_LD='${CC} -dynamiclib ${CFLAGS} ${LDFLAGS}' SHLIB_SUFFIX=".dylib" DL_OBJS="tclLoadDyld.o" DL_LIBS="" LDFLAGS="$LDFLAGS -headerpad_max_install_names" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if ld accepts -search_paths_first flag" >&5 printf %s "checking if ld accepts -search_paths_first flag... " >&6; } if test ${tcl_cv_ld_search_paths_first+y} then : printf %s "(cached) " >&6 else case e in #( e) hold_ldflags=$LDFLAGS LDFLAGS="$LDFLAGS -Wl,-search_paths_first" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { int i; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : tcl_cv_ld_search_paths_first=yes else case e in #( e) tcl_cv_ld_search_paths_first=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$hold_ldflags ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_ld_search_paths_first" >&5 printf "%s\n" "$tcl_cv_ld_search_paths_first" >&6; } if test $tcl_cv_ld_search_paths_first = yes then : LDFLAGS="$LDFLAGS -Wl,-search_paths_first" fi if test "$tcl_cv_cc_visibility_hidden" != yes then : printf "%s\n" "#define MODULE_SCOPE __private_extern__" >>confdefs.h tcl_cv_cc_visibility_hidden=yes fi CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" LD_LIBRARY_PATH_VAR="DYLD_FALLBACK_LIBRARY_PATH" printf "%s\n" "#define MAC_OSX_TCL 1" >>confdefs.h PLAT_OBJS='${MAC_OSX_OBJS}' PLAT_SRCS='${MAC_OSX_SRCS}' { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to use CoreFoundation" >&5 printf %s "checking whether to use CoreFoundation... " >&6; } # Check whether --enable-corefoundation was given. if test ${enable_corefoundation+y} then : enableval=$enable_corefoundation; tcl_corefoundation=$enableval else case e in #( e) tcl_corefoundation=yes ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_corefoundation" >&5 printf "%s\n" "$tcl_corefoundation" >&6; } if test $tcl_corefoundation = yes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for CoreFoundation.framework" >&5 printf %s "checking for CoreFoundation.framework... " >&6; } if test ${tcl_cv_lib_corefoundation+y} then : printf %s "(cached) " >&6 else case e in #( e) hold_libs=$LIBS if test "$fat_32_64" = yes then : for v in CFLAGS CPPFLAGS LDFLAGS; do # On Tiger there is no 64-bit CF, so remove 64-bit # archs from CFLAGS et al. while testing for # presence of CF. 64-bit CF is disabled in # tclUnixPort.h if necessary. eval 'hold_'$v'="$'$v'";'$v'="`echo "$'$v' "|sed -e "s/-arch ppc64 / /g" -e "s/-arch x86_64 / /g"`"' done fi LIBS="$LIBS -framework CoreFoundation" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { CFBundleRef b = CFBundleGetMainBundle(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : tcl_cv_lib_corefoundation=yes else case e in #( e) tcl_cv_lib_corefoundation=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext if test "$fat_32_64" = yes then : for v in CFLAGS CPPFLAGS LDFLAGS; do eval $v'="$hold_'$v'"' done fi LIBS=$hold_libs ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_lib_corefoundation" >&5 printf "%s\n" "$tcl_cv_lib_corefoundation" >&6; } if test $tcl_cv_lib_corefoundation = yes then : LIBS="$LIBS -framework CoreFoundation" printf "%s\n" "#define HAVE_COREFOUNDATION 1" >>confdefs.h else case e in #( e) tcl_corefoundation=no ;; esac fi if test "$fat_32_64" = yes -a $tcl_corefoundation = yes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for 64-bit CoreFoundation" >&5 printf %s "checking for 64-bit CoreFoundation... " >&6; } if test ${tcl_cv_lib_corefoundation_64+y} then : printf %s "(cached) " >&6 else case e in #( e) for v in CFLAGS CPPFLAGS LDFLAGS; do eval 'hold_'$v'="$'$v'";'$v'="`echo "$'$v' "|sed -e "s/-arch ppc / /g" -e "s/-arch i386 / /g"`"' done cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { CFBundleRef b = CFBundleGetMainBundle(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : tcl_cv_lib_corefoundation_64=yes else case e in #( e) tcl_cv_lib_corefoundation_64=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext for v in CFLAGS CPPFLAGS LDFLAGS; do eval $v'="$hold_'$v'"' done ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_lib_corefoundation_64" >&5 printf "%s\n" "$tcl_cv_lib_corefoundation_64" >&6; } if test $tcl_cv_lib_corefoundation_64 = no then : printf "%s\n" "#define NO_COREFOUNDATION_64 1" >>confdefs.h LDFLAGS="$LDFLAGS -Wl,-no_arch_warnings" fi fi fi ;; OS/390-*) SHLIB_LD_LIBS="" CFLAGS_OPTIMIZE="" # Optimizer is buggy printf "%s\n" "#define _OE_SOCKETS 1" >>confdefs.h ;; OSF1-V*) # Digital OSF/1 SHLIB_CFLAGS="" if test "$SHARED_BUILD" = 1 then : SHLIB_LD='${CC} -shared' else case e in #( e) SHLIB_LD='${CC} -non_shared' ;; esac fi SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" if test $doRpath = yes then : CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}' fi if test "$GCC" = yes then : CFLAGS="$CFLAGS -mieee" else case e in #( e) CFLAGS="$CFLAGS -DHAVE_TZSET -std1 -ieee" ;; esac fi # see pthread_intro(3) for pthread support on osf1, k.furukawa CFLAGS="$CFLAGS -DHAVE_PTHREAD_ATTR_SETSTACKSIZE" CFLAGS="$CFLAGS -DTCL_THREAD_STACK_MIN=PTHREAD_STACK_MIN*64" LIBS=`echo $LIBS | sed s/-lpthreads//` if test "$GCC" = yes then : LIBS="$LIBS -lpthread -lmach -lexc" else case e in #( e) CFLAGS="$CFLAGS -pthread" LDFLAGS="$LDFLAGS -pthread" ;; esac fi ;; QNX-6*) # QNX RTP # This may work for all QNX, but it was only reported for v6. SHLIB_LD="ld -Bshareable -x" SHLIB_LD_LIBS="" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" # dlopen is in -lc on QNX DL_LIBS="" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" ;; SCO_SV-3.2*) # Note, dlopen is available only on SCO 3.2.5 and greater. However, # this test works, since "uname -s" was non-standard in 3.2.4 and # below. if test "$GCC" = yes then : SHLIB_CFLAGS="-fPIC -melf" LDFLAGS="$LDFLAGS -melf -Wl,-Bexport" else case e in #( e) SHLIB_CFLAGS="-Kpic -belf" LDFLAGS="$LDFLAGS -belf -Wl,-Bexport" ;; esac fi SHLIB_LD="ld -G" SHLIB_LD_LIBS="" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" ;; SunOS-5.[0-6]) # Careful to not let 5.10+ fall into this case # Note: If _REENTRANT isn't defined, then Solaris # won't define thread-safe library routines. printf "%s\n" "#define _REENTRANT 1" >>confdefs.h printf "%s\n" "#define _POSIX_PTHREAD_SEMANTICS 1" >>confdefs.h SHLIB_CFLAGS="-KPIC" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" if test "$GCC" = yes then : SHLIB_LD='${CC} -shared' CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} else case e in #( e) SHLIB_LD="/usr/ccs/bin/ld -G -z text" CC_SEARCH_FLAGS='-R ${LIB_RUNTIME_DIR}' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} ;; esac fi ;; SunOS-5*) # Note: If _REENTRANT isn't defined, then Solaris # won't define thread-safe library routines. printf "%s\n" "#define _REENTRANT 1" >>confdefs.h printf "%s\n" "#define _POSIX_PTHREAD_SEMANTICS 1" >>confdefs.h SHLIB_CFLAGS="-KPIC" # Check to enable 64-bit flags for compiler/linker if test "$do64bit" = yes then : arch=`isainfo` if test "$arch" = "sparcv9 sparc" then : if test "$GCC" = yes then : if test "`${CC} -dumpversion | awk -F. '{print $1}'`" -lt 3 then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported with GCC < 3.2 on $system" >&5 printf "%s\n" "$as_me: WARNING: 64bit mode not supported with GCC < 3.2 on $system" >&2;} else case e in #( e) do64bit_ok=yes CFLAGS="$CFLAGS -m64 -mcpu=v9" LDFLAGS="$LDFLAGS -m64 -mcpu=v9" SHLIB_CFLAGS="-fPIC" ;; esac fi else case e in #( e) do64bit_ok=yes if test "$do64bitVIS" = yes then : CFLAGS="$CFLAGS -xarch=v9a" LDFLAGS_ARCH="-xarch=v9a" else case e in #( e) CFLAGS="$CFLAGS -xarch=v9" LDFLAGS_ARCH="-xarch=v9" ;; esac fi # Solaris 64 uses this as well #LD_LIBRARY_PATH_VAR="LD_LIBRARY_PATH_64" ;; esac fi else case e in #( e) if test "$arch" = "amd64 i386" then : if test "$GCC" = yes then : case $system in SunOS-5.1[1-9]*|SunOS-5.[2-9][0-9]*) do64bit_ok=yes CFLAGS="$CFLAGS -m64" LDFLAGS="$LDFLAGS -m64";; *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported with GCC on $system" >&5 printf "%s\n" "$as_me: WARNING: 64bit mode not supported with GCC on $system" >&2;};; esac else case e in #( e) do64bit_ok=yes case $system in SunOS-5.1[1-9]*|SunOS-5.[2-9][0-9]*) CFLAGS="$CFLAGS -m64" LDFLAGS="$LDFLAGS -m64";; *) CFLAGS="$CFLAGS -xarch=amd64" LDFLAGS="$LDFLAGS -xarch=amd64";; esac ;; esac fi else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported for $arch" >&5 printf "%s\n" "$as_me: WARNING: 64bit mode not supported for $arch" >&2;} ;; esac fi ;; esac fi fi #-------------------------------------------------------------------- # On Solaris 5.x i386 with the sunpro compiler we need to link # with sunmath to get floating point rounding control #-------------------------------------------------------------------- if test "$GCC" = yes then : use_sunmath=no else case e in #( e) arch=`isainfo` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to use -lsunmath for fp rounding control" >&5 printf %s "checking whether to use -lsunmath for fp rounding control... " >&6; } if test "$arch" = "amd64 i386" -o "$arch" = "i386" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } MATH_LIBS="-lsunmath $MATH_LIBS" ac_fn_c_check_header_compile "$LINENO" "sunmath.h" "ac_cv_header_sunmath_h" "$ac_includes_default" if test "x$ac_cv_header_sunmath_h" = xyes then : fi use_sunmath=yes else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } use_sunmath=no ;; esac fi ;; esac fi SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" if test "$GCC" = yes then : SHLIB_LD='${CC} -shared' CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} if test "$do64bit_ok" = yes then : if test "$arch" = "sparcv9 sparc" then : # We need to specify -static-libgcc or we need to # add the path to the sparv9 libgcc. SHLIB_LD="$SHLIB_LD -m64 -mcpu=v9 -static-libgcc" # for finding sparcv9 libgcc, get the regular libgcc # path, remove so name and append 'sparcv9' #v9gcclibdir="`gcc -print-file-name=libgcc_s.so` | ..." #CC_SEARCH_FLAGS="${CC_SEARCH_FLAGS},-R,$v9gcclibdir" else case e in #( e) if test "$arch" = "amd64 i386" then : SHLIB_LD="$SHLIB_LD -m64 -static-libgcc" fi ;; esac fi fi else case e in #( e) if test "$use_sunmath" = yes then : textmode=textoff else case e in #( e) textmode=text ;; esac fi case $system in SunOS-5.[1-9][0-9]*|SunOS-5.[7-9]) SHLIB_LD="\${CC} -G -z $textmode \${LDFLAGS}";; *) SHLIB_LD="/usr/ccs/bin/ld -G -z $textmode";; esac CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' LD_SEARCH_FLAGS='-R ${LIB_RUNTIME_DIR}' ;; esac fi ;; UNIX_SV* | UnixWare-5*) SHLIB_CFLAGS="-KPIC" SHLIB_LD='${CC} -G' SHLIB_LD_LIBS="" SHLIB_SUFFIX=".so" DL_OBJS="tclLoadDl.o" DL_LIBS="-ldl" # Some UNIX_SV* systems (unixware 1.1.2 for example) have linkers # that don't grok the -Bexport option. Test that it does. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ld accepts -Bexport flag" >&5 printf %s "checking for ld accepts -Bexport flag... " >&6; } if test ${tcl_cv_ld_Bexport+y} then : printf %s "(cached) " >&6 else case e in #( e) hold_ldflags=$LDFLAGS LDFLAGS="$LDFLAGS -Wl,-Bexport" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { int i; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : tcl_cv_ld_Bexport=yes else case e in #( e) tcl_cv_ld_Bexport=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$hold_ldflags ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_ld_Bexport" >&5 printf "%s\n" "$tcl_cv_ld_Bexport" >&6; } if test $tcl_cv_ld_Bexport = yes then : LDFLAGS="$LDFLAGS -Wl,-Bexport" fi CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" ;; esac if test "$do64bit" = yes -a "$do64bit_ok" = no then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: 64bit support being disabled -- don't know magic for this platform" >&5 printf "%s\n" "$as_me: WARNING: 64bit support being disabled -- don't know magic for this platform" >&2;} fi if test "$do64bit" = yes -a "$do64bit_ok" = yes then : printf "%s\n" "#define TCL_CFG_DO64BIT 1" >>confdefs.h fi # Step 4: disable dynamic loading if requested via a command-line switch. # Check whether --enable-load was given. if test ${enable_load+y} then : enableval=$enable_load; tcl_ok=$enableval else case e in #( e) tcl_ok=yes ;; esac fi if test "$tcl_ok" = no then : DL_OBJS="" fi if test "x$DL_OBJS" != x then : BUILD_DLTEST="\$(DLTEST_TARGETS)" else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Can't figure out how to do dynamic loading or shared libraries on this system." >&5 printf "%s\n" "$as_me: WARNING: Can't figure out how to do dynamic loading or shared libraries on this system." >&2;} SHLIB_CFLAGS="" SHLIB_LD="" SHLIB_SUFFIX="" DL_OBJS="tclLoadNone.o" DL_LIBS="" LDFLAGS="$LDFLAGS_ORIG" CC_SEARCH_FLAGS="" LD_SEARCH_FLAGS="" BUILD_DLTEST="" ;; esac fi LDFLAGS="$LDFLAGS $LDFLAGS_ARCH" # If we're running gcc, then change the C flags for compiling shared # libraries to the right flags for gcc, instead of those for the # standard manufacturer compiler. if test "$DL_OBJS" != "tclLoadNone.o" -a "$GCC" = yes then : case $system in AIX-*) ;; BSD/OS*) ;; CYGWIN_*|MINGW32_*|MSYS_*) ;; HP-UX*) ;; Darwin-*) ;; IRIX*) ;; Linux*|GNU*) ;; NetBSD-*|OpenBSD-*) ;; OSF1-*) ;; SCO_SV-3.2*) ;; *) SHLIB_CFLAGS="-fPIC" ;; esac fi if test "$tcl_cv_cc_visibility_hidden" != yes then : printf "%s\n" "#define MODULE_SCOPE extern" >>confdefs.h fi if test "$SHARED_LIB_SUFFIX" = "" then : SHARED_LIB_SUFFIX='${VERSION}${SHLIB_SUFFIX}' fi if test "$UNSHARED_LIB_SUFFIX" = "" then : UNSHARED_LIB_SUFFIX='${VERSION}.a' fi DLL_INSTALL_DIR="\$(LIB_INSTALL_DIR)" if test "${SHARED_BUILD}" = 1 -a "${SHLIB_SUFFIX}" != "" then : LIB_SUFFIX=${SHARED_LIB_SUFFIX} MAKE_LIB='${SHLIB_LD} -o $@ ${OBJS} ${LDFLAGS} ${SHLIB_LD_LIBS} ${TCL_SHLIB_LD_EXTRAS} ${TK_SHLIB_LD_EXTRAS} ${LD_SEARCH_FLAGS}' if test "${SHLIB_SUFFIX}" = ".dll" then : INSTALL_LIB='$(INSTALL_LIBRARY) $(LIB_FILE) "$(BIN_INSTALL_DIR)/$(LIB_FILE)"' DLL_INSTALL_DIR="\$(BIN_INSTALL_DIR)" else case e in #( e) INSTALL_LIB='$(INSTALL_LIBRARY) $(LIB_FILE) "$(LIB_INSTALL_DIR)/$(LIB_FILE)"' ;; esac fi else case e in #( e) LIB_SUFFIX=${UNSHARED_LIB_SUFFIX} if test "$RANLIB" = "" then : MAKE_LIB='$(STLIB_LD) $@ ${OBJS}' else case e in #( e) MAKE_LIB='${STLIB_LD} $@ ${OBJS} ; ${RANLIB} $@' ;; esac fi INSTALL_LIB='$(INSTALL_LIBRARY) $(LIB_FILE) "$(LIB_INSTALL_DIR)/$(LIB_FILE)"' ;; esac fi # Stub lib does not depend on shared/static configuration if test "$RANLIB" = "" then : MAKE_STUB_LIB='${STLIB_LD} $@ ${STUB_LIB_OBJS}' else case e in #( e) MAKE_STUB_LIB='${STLIB_LD} $@ ${STUB_LIB_OBJS} ; ${RANLIB} $@' ;; esac fi INSTALL_STUB_LIB='$(INSTALL_LIBRARY) $(STUB_LIB_FILE) "$(LIB_INSTALL_DIR)/$(STUB_LIB_FILE)"' # Define TCL_LIBS now that we know what DL_LIBS is. # The trick here is that we don't want to change the value of TCL_LIBS if # it is already set when tclConfig.sh had been loaded by Tk. if test "x${TCL_LIBS}" = x then : TCL_LIBS="${DL_LIBS} ${LIBS} ${MATH_LIBS}" fi # See if the compiler supports casting to a union type. # This is used to stop gcc from printing a compiler # warning when initializing a union member. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for cast to union support" >&5 printf %s "checking for cast to union support... " >&6; } if test ${tcl_cv_cast_to_union+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { union foo { int i; double d; }; union foo f = (union foo) (int) 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_cast_to_union=yes else case e in #( e) tcl_cv_cast_to_union=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cast_to_union" >&5 printf "%s\n" "$tcl_cv_cast_to_union" >&6; } if test "$tcl_cv_cast_to_union" = "yes"; then printf "%s\n" "#define HAVE_CAST_TO_UNION 1" >>confdefs.h fi hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -fno-lto" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for working -fno-lto" >&5 printf %s "checking for working -fno-lto... " >&6; } if test ${ac_cv_nolto+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_nolto=yes else case e in #( e) ac_cv_nolto=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_nolto" >&5 printf "%s\n" "$ac_cv_nolto" >&6; } CFLAGS=$hold_cflags if test "$ac_cv_nolto" = "yes" ; then CFLAGS_NOLTO="-fno-lto" else CFLAGS_NOLTO="" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if the compiler understands -finput-charset" >&5 printf %s "checking if the compiler understands -finput-charset... " >&6; } if test ${tcl_cv_cc_input_charset+y} then : printf %s "(cached) " >&6 else case e in #( e) hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -finput-charset=UTF-8" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_cc_input_charset=yes else case e in #( e) tcl_cv_cc_input_charset=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext CFLAGS=$hold_cflags ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_input_charset" >&5 printf "%s\n" "$tcl_cv_cc_input_charset" >&6; } if test $tcl_cv_cc_input_charset = yes; then CFLAGS="$CFLAGS -finput-charset=UTF-8" fi ac_fn_c_check_header_compile "$LINENO" "stdbool.h" "ac_cv_header_stdbool_h" "$ac_includes_default" if test "x$ac_cv_header_stdbool_h" = xyes then : printf "%s\n" "#define HAVE_STDBOOL_H 1" >>confdefs.h fi # Check for vfork, posix_spawnp() and friends unconditionally ac_fn_c_check_func "$LINENO" "vfork" "ac_cv_func_vfork" if test "x$ac_cv_func_vfork" = xyes then : printf "%s\n" "#define HAVE_VFORK 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "posix_spawnp" "ac_cv_func_posix_spawnp" if test "x$ac_cv_func_posix_spawnp" = xyes then : printf "%s\n" "#define HAVE_POSIX_SPAWNP 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_adddup2" "ac_cv_func_posix_spawn_file_actions_adddup2" if test "x$ac_cv_func_posix_spawn_file_actions_adddup2" = xyes then : printf "%s\n" "#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDDUP2 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "posix_spawnattr_setflags" "ac_cv_func_posix_spawnattr_setflags" if test "x$ac_cv_func_posix_spawnattr_setflags" = xyes then : printf "%s\n" "#define HAVE_POSIX_SPAWNATTR_SETFLAGS 1" >>confdefs.h fi # FIXME: This subst was left in only because the TCL_DL_LIBS # entry in tclConfig.sh uses it. It is not clear why someone # would use TCL_DL_LIBS instead of TCL_LIBS. printf "%s\n" "#define TCL_SHLIB_EXT \"${SHLIB_SUFFIX}\"" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build with symbols" >&5 printf %s "checking for build with symbols... " >&6; } # Check whether --enable-symbols was given. if test ${enable_symbols+y} then : enableval=$enable_symbols; tcl_ok=$enableval else case e in #( e) tcl_ok=no ;; esac fi # FIXME: Currently, LDFLAGS_DEFAULT is not used, it should work like CFLAGS_DEFAULT. if test "$tcl_ok" = "no"; then CFLAGS_DEFAULT='$(CFLAGS_OPTIMIZE)' LDFLAGS_DEFAULT='$(LDFLAGS_OPTIMIZE)' printf "%s\n" "#define NDEBUG 1" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } printf "%s\n" "#define TCL_CFG_OPTIMIZED 1" >>confdefs.h else CFLAGS_DEFAULT='$(CFLAGS_DEBUG)' LDFLAGS_DEFAULT='$(LDFLAGS_DEBUG)' if test "$tcl_ok" = "yes"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes (standard debugging)" >&5 printf "%s\n" "yes (standard debugging)" >&6; } fi fi if test "$tcl_ok" = "mem" -o "$tcl_ok" = "all"; then printf "%s\n" "#define TCL_MEM_DEBUG 1" >>confdefs.h fi if test "$tcl_ok" = "compile" -o "$tcl_ok" = "all"; then printf "%s\n" "#define TCL_COMPILE_DEBUG 1" >>confdefs.h printf "%s\n" "#define TCL_COMPILE_STATS 1" >>confdefs.h fi if test "$tcl_ok" != "yes" -a "$tcl_ok" != "no"; then if test "$tcl_ok" = "all"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: enabled symbols mem compile debugging" >&5 printf "%s\n" "enabled symbols mem compile debugging" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: enabled $tcl_ok debugging" >&5 printf "%s\n" "enabled $tcl_ok debugging" >&6; } fi fi printf "%s\n" "#define MP_PREC 4" >>confdefs.h #-------------------------------------------------------------------- # Detect what compiler flags to set for 64-bit support. #-------------------------------------------------------------------- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for required early compiler flags" >&5 printf %s "checking for required early compiler flags... " >&6; } tcl_flags="" if test ${tcl_cv_flag__isoc99_source+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { char *p = (char *)strtoll; char *q = (char *)strtoull; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_flag__isoc99_source=no else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #define _ISOC99_SOURCE 1 #include int main (void) { char *p = (char *)strtoll; char *q = (char *)strtoull; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_flag__isoc99_source=yes else case e in #( e) tcl_cv_flag__isoc99_source=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi if test "x${tcl_cv_flag__isoc99_source}" = "xyes" ; then printf "%s\n" "#define _ISOC99_SOURCE 1" >>confdefs.h tcl_flags="$tcl_flags _ISOC99_SOURCE" fi if test ${tcl_cv_flag__file_offset_bits+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { switch (0) { case 0: case (sizeof(off_t)==sizeof(long long)): ; } ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_flag__file_offset_bits=no else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #define _FILE_OFFSET_BITS 64 #include int main (void) { switch (0) { case 0: case (sizeof(off_t)==sizeof(long long)): ; } ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_flag__file_offset_bits=yes else case e in #( e) tcl_cv_flag__file_offset_bits=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi if test "x${tcl_cv_flag__file_offset_bits}" = "xyes" ; then printf "%s\n" "#define _FILE_OFFSET_BITS 64" >>confdefs.h tcl_flags="$tcl_flags _FILE_OFFSET_BITS" fi if test ${tcl_cv_flag__largefile64_source+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { struct stat64 buf; int i = stat64("/", &buf); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_flag__largefile64_source=no else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #define _LARGEFILE64_SOURCE 1 #include int main (void) { struct stat64 buf; int i = stat64("/", &buf); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_flag__largefile64_source=yes else case e in #( e) tcl_cv_flag__largefile64_source=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi if test "x${tcl_cv_flag__largefile64_source}" = "xyes" ; then printf "%s\n" "#define _LARGEFILE64_SOURCE 1" >>confdefs.h tcl_flags="$tcl_flags _LARGEFILE64_SOURCE" fi if test "x${tcl_flags}" = "x" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none" >&5 printf "%s\n" "none" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${tcl_flags}" >&5 printf "%s\n" "${tcl_flags}" >&6; } fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if 'long' and 'long long' have the same size (64-bit)?" >&5 printf %s "checking if 'long' and 'long long' have the same size (64-bit)?... " >&6; } if test ${tcl_cv_type_64bit+y} then : printf %s "(cached) " >&6 else case e in #( e) tcl_cv_type_64bit=none # See if we could use long anyway Note that we substitute in the # type that is our current guess for a 64-bit type inside this check # program, so it should be modified only carefully... cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { switch (0) { case 1: case (sizeof(long long)==sizeof(long)): ; } ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_type_64bit="long long" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi if test "${tcl_cv_type_64bit}" = none ; then printf "%s\n" "#define TCL_WIDE_INT_IS_LONG 1" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } # Now check for auxiliary declarations { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for 64-bit time_t" >&5 printf %s "checking for 64-bit time_t... " >&6; } if test ${tcl_cv_time_t_64+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { switch (0) {case 0: case (sizeof(time_t)==sizeof(long long)): ;} ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_time_t_64=yes else case e in #( e) tcl_cv_time_t_64=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_time_t_64" >&5 printf "%s\n" "$tcl_cv_time_t_64" >&6; } if test "x${tcl_cv_time_t_64}" = "xno" ; then # Note that _TIME_BITS=64 requires _FILE_OFFSET_BITS=64 # which SC_TCL_EARLY_FLAGS has defined if necessary. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if _TIME_BITS=64 enables 64-bit time_t" >&5 printf %s "checking if _TIME_BITS=64 enables 64-bit time_t... " >&6; } if test ${tcl_cv__time_bits+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #define _TIME_BITS 64 #include int main (void) { switch (0) {case 0: case (sizeof(time_t)==sizeof(long long)): ;} ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv__time_bits=yes else case e in #( e) tcl_cv__time_bits=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv__time_bits" >&5 printf "%s\n" "$tcl_cv__time_bits" >&6; } if test "x${tcl_cv__time_bits}" = "xyes" ; then printf "%s\n" "#define _TIME_BITS 64" >>confdefs.h fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for struct dirent64" >&5 printf %s "checking for struct dirent64... " >&6; } if test ${tcl_cv_struct_dirent64+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { struct dirent64 p; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_struct_dirent64=yes else case e in #( e) tcl_cv_struct_dirent64=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_struct_dirent64" >&5 printf "%s\n" "$tcl_cv_struct_dirent64" >&6; } if test "x${tcl_cv_struct_dirent64}" = "xyes" ; then printf "%s\n" "#define HAVE_STRUCT_DIRENT64 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for DIR64" >&5 printf %s "checking for DIR64... " >&6; } if test ${tcl_cv_DIR64+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { struct dirent64 *p; DIR64 d = opendir64("."); p = readdir64(d); rewinddir64(d); closedir64(d); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_DIR64=yes else case e in #( e) tcl_cv_DIR64=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_DIR64" >&5 printf "%s\n" "$tcl_cv_DIR64" >&6; } if test "x${tcl_cv_DIR64}" = "xyes" ; then printf "%s\n" "#define HAVE_DIR64 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "open64" "ac_cv_func_open64" if test "x$ac_cv_func_open64" = xyes then : printf "%s\n" "#define HAVE_OPEN64 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "lseek64" "ac_cv_func_lseek64" if test "x$ac_cv_func_lseek64" = xyes then : printf "%s\n" "#define HAVE_LSEEK64 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for off64_t" >&5 printf %s "checking for off64_t... " >&6; } if test ${tcl_cv_type_off64_t+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { off64_t offset; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_type_off64_t=yes else case e in #( e) tcl_cv_type_off64_t=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi if test "x${tcl_cv_type_off64_t}" = "xyes" && \ test "x${ac_cv_func_lseek64}" = "xyes" && \ test "x${ac_cv_func_open64}" = "xyes" ; then printf "%s\n" "#define HAVE_TYPE_OFF64_T 1" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi #-------------------------------------------------------------------- # Check endianness because we can optimize comparisons of # Tcl_UniChar strings to memcmp on big-endian systems. #-------------------------------------------------------------------- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether byte ordering is bigendian" >&5 printf %s "checking whether byte ordering is bigendian... " >&6; } if test ${ac_cv_c_bigendian+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_c_bigendian=unknown # See if we're dealing with a universal compiler. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifndef __APPLE_CC__ not a universal capable compiler #endif typedef int dummy; _ACEOF if ac_fn_c_try_compile "$LINENO" then : # Check for potential -arch flags. It is not universal unless # there are at least two -arch flags with different values. ac_arch= ac_prev= for ac_word in $CC $CFLAGS $CPPFLAGS $LDFLAGS; do if test -n "$ac_prev"; then case $ac_word in i?86 | x86_64 | ppc | ppc64) if test -z "$ac_arch" || test "$ac_arch" = "$ac_word"; then ac_arch=$ac_word else ac_cv_c_bigendian=universal break fi ;; esac ac_prev= elif test "x$ac_word" = "x-arch"; then ac_prev=arch fi done fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test $ac_cv_c_bigendian = unknown; then # See if sys/param.h defines the BYTE_ORDER macro. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { #if ! (defined BYTE_ORDER && defined BIG_ENDIAN \\ && defined LITTLE_ENDIAN && BYTE_ORDER && BIG_ENDIAN \\ && LITTLE_ENDIAN) bogus endian macros #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : # It does; now see whether it defined to BIG_ENDIAN or not. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { #if BYTE_ORDER != BIG_ENDIAN not big endian #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_c_bigendian=yes else case e in #( e) ac_cv_c_bigendian=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi if test $ac_cv_c_bigendian = unknown; then # See if defines _LITTLE_ENDIAN or _BIG_ENDIAN (e.g., Solaris). cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { #if ! (defined _LITTLE_ENDIAN || defined _BIG_ENDIAN) bogus endian macros #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : # It does; now see whether it defined to _BIG_ENDIAN or not. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { #ifndef _BIG_ENDIAN not big endian #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_c_bigendian=yes else case e in #( e) ac_cv_c_bigendian=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi if test $ac_cv_c_bigendian = unknown; then # Compile a test program. if test "$cross_compiling" = yes then : # Try to guess by grepping values from an object file. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ unsigned short int ascii_mm[] = { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 }; unsigned short int ascii_ii[] = { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 }; int use_ascii (int i) { return ascii_mm[i] + ascii_ii[i]; } unsigned short int ebcdic_ii[] = { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 }; unsigned short int ebcdic_mm[] = { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 }; int use_ebcdic (int i) { return ebcdic_mm[i] + ebcdic_ii[i]; } int main (int argc, char **argv) { /* Intimidate the compiler so that it does not optimize the arrays away. */ char *p = argv[0]; ascii_mm[1] = *p++; ebcdic_mm[1] = *p++; ascii_ii[1] = *p++; ebcdic_ii[1] = *p++; return use_ascii (argc) == use_ebcdic (*p); } _ACEOF if ac_fn_c_try_link "$LINENO" then : if grep BIGenDianSyS conftest$ac_exeext >/dev/null; then ac_cv_c_bigendian=yes fi if grep LiTTleEnDian conftest$ac_exeext >/dev/null ; then if test "$ac_cv_c_bigendian" = unknown; then ac_cv_c_bigendian=no else # finding both strings is unlikely to happen, but who knows? ac_cv_c_bigendian=unknown fi fi fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_includes_default int main (void) { /* Are we little or big endian? From Harbison&Steele. */ union { long int l; char c[sizeof (long int)]; } u; u.l = 1; return u.c[sizeof (long int) - 1] == 1; ; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO" then : ac_cv_c_bigendian=no else case e in #( e) ac_cv_c_bigendian=yes ;; esac fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_bigendian" >&5 printf "%s\n" "$ac_cv_c_bigendian" >&6; } case $ac_cv_c_bigendian in #( yes) printf "%s\n" "#define WORDS_BIGENDIAN 1" >>confdefs.h ;; #( no) ;; #( universal) # ;; #( *) as_fn_error $? "unknown endianness presetting ac_cv_c_bigendian=no (or yes) will help" "$LINENO" 5 ;; esac #-------------------------------------------------------------------- # Supply substitutes for missing POSIX library procedures, or # set flags so Tcl uses alternate procedures. #-------------------------------------------------------------------- # Check if Posix compliant getcwd exists, if not we'll use getwd. for ac_func in getcwd do : ac_fn_c_check_func "$LINENO" "getcwd" "ac_cv_func_getcwd" if test "x$ac_cv_func_getcwd" = xyes then : printf "%s\n" "#define HAVE_GETCWD 1" >>confdefs.h else case e in #( e) printf "%s\n" "#define USEGETWD 1" >>confdefs.h ;; esac fi done # Nb: if getcwd uses popen and pwd(1) (like SunOS 4) we should really # define USEGETWD even if the posix getcwd exists. Add a test ? ac_fn_c_check_func "$LINENO" "mkstemp" "ac_cv_func_mkstemp" if test "x$ac_cv_func_mkstemp" = xyes then : printf "%s\n" "#define HAVE_MKSTEMP 1" >>confdefs.h else case e in #( e) case " $LIBOBJS " in *" mkstemp.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS mkstemp.$ac_objext" ;; esac ;; esac fi ac_fn_c_check_func "$LINENO" "waitpid" "ac_cv_func_waitpid" if test "x$ac_cv_func_waitpid" = xyes then : printf "%s\n" "#define HAVE_WAITPID 1" >>confdefs.h else case e in #( e) case " $LIBOBJS " in *" waitpid.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS waitpid.$ac_objext" ;; esac ;; esac fi ac_fn_c_check_func "$LINENO" "strerror" "ac_cv_func_strerror" if test "x$ac_cv_func_strerror" = xyes then : else case e in #( e) printf "%s\n" "#define NO_STRERROR 1" >>confdefs.h ;; esac fi ac_fn_c_check_func "$LINENO" "getwd" "ac_cv_func_getwd" if test "x$ac_cv_func_getwd" = xyes then : else case e in #( e) printf "%s\n" "#define NO_GETWD 1" >>confdefs.h ;; esac fi ac_fn_c_check_func "$LINENO" "wait3" "ac_cv_func_wait3" if test "x$ac_cv_func_wait3" = xyes then : else case e in #( e) printf "%s\n" "#define NO_WAIT3 1" >>confdefs.h ;; esac fi ac_fn_c_check_func "$LINENO" "fork" "ac_cv_func_fork" if test "x$ac_cv_func_fork" = xyes then : else case e in #( e) printf "%s\n" "#define NO_FORK 1" >>confdefs.h ;; esac fi ac_fn_c_check_func "$LINENO" "mknod" "ac_cv_func_mknod" if test "x$ac_cv_func_mknod" = xyes then : else case e in #( e) printf "%s\n" "#define NO_MKNOD 1" >>confdefs.h ;; esac fi ac_fn_c_check_func "$LINENO" "tcdrain" "ac_cv_func_tcdrain" if test "x$ac_cv_func_tcdrain" = xyes then : else case e in #( e) printf "%s\n" "#define NO_TCDRAIN 1" >>confdefs.h ;; esac fi ac_fn_c_check_func "$LINENO" "uname" "ac_cv_func_uname" if test "x$ac_cv_func_uname" = xyes then : else case e in #( e) printf "%s\n" "#define NO_UNAME 1" >>confdefs.h ;; esac fi if test "`uname -s`" = "Darwin" && \ test "`uname -r | awk -F. '{print $1}'`" -lt 7; then # prior to Darwin 7, realpath is not threadsafe, so don't # use it when threads are enabled, c.f. bug # 711232 ac_cv_func_realpath=no fi ac_fn_c_check_func "$LINENO" "realpath" "ac_cv_func_realpath" if test "x$ac_cv_func_realpath" = xyes then : else case e in #( e) printf "%s\n" "#define NO_REALPATH 1" >>confdefs.h ;; esac fi NEED_FAKE_RFC2553=0 for ac_func in getnameinfo getaddrinfo freeaddrinfo gai_strerror do : as_ac_var=`printf "%s\n" "ac_cv_func_$ac_func" | sed "$as_sed_sh"` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" if eval test \"x\$"$as_ac_var"\" = x"yes" then : cat >>confdefs.h <<_ACEOF #define `printf "%s\n" "HAVE_$ac_func" | sed "$as_sed_cpp"` 1 _ACEOF else case e in #( e) NEED_FAKE_RFC2553=1 ;; esac fi done ac_fn_c_check_type "$LINENO" "struct addrinfo" "ac_cv_type_struct_addrinfo" " #include #include #include #include " if test "x$ac_cv_type_struct_addrinfo" = xyes then : printf "%s\n" "#define HAVE_STRUCT_ADDRINFO 1" >>confdefs.h else case e in #( e) NEED_FAKE_RFC2553=1 ;; esac fi ac_fn_c_check_type "$LINENO" "struct in6_addr" "ac_cv_type_struct_in6_addr" " #include #include #include #include " if test "x$ac_cv_type_struct_in6_addr" = xyes then : printf "%s\n" "#define HAVE_STRUCT_IN6_ADDR 1" >>confdefs.h else case e in #( e) NEED_FAKE_RFC2553=1 ;; esac fi ac_fn_c_check_type "$LINENO" "struct sockaddr_in6" "ac_cv_type_struct_sockaddr_in6" " #include #include #include #include " if test "x$ac_cv_type_struct_sockaddr_in6" = xyes then : printf "%s\n" "#define HAVE_STRUCT_SOCKADDR_IN6 1" >>confdefs.h else case e in #( e) NEED_FAKE_RFC2553=1 ;; esac fi ac_fn_c_check_type "$LINENO" "struct sockaddr_storage" "ac_cv_type_struct_sockaddr_storage" " #include #include #include #include " if test "x$ac_cv_type_struct_sockaddr_storage" = xyes then : printf "%s\n" "#define HAVE_STRUCT_SOCKADDR_STORAGE 1" >>confdefs.h else case e in #( e) NEED_FAKE_RFC2553=1 ;; esac fi if test "x$NEED_FAKE_RFC2553" = "x1"; then printf "%s\n" "#define NEED_FAKE_RFC2553 1" >>confdefs.h case " $LIBOBJS " in *" fake-rfc2553.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS fake-rfc2553.$ac_objext" ;; esac ac_fn_c_check_func "$LINENO" "strlcpy" "ac_cv_func_strlcpy" if test "x$ac_cv_func_strlcpy" = xyes then : fi fi #-------------------------------------------------------------------- # Look for thread-safe variants of some library functions. #-------------------------------------------------------------------- ac_fn_c_check_func "$LINENO" "getpwuid_r" "ac_cv_func_getpwuid_r" if test "x$ac_cv_func_getpwuid_r" = xyes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for getpwuid_r with 5 args" >&5 printf %s "checking for getpwuid_r with 5 args... " >&6; } if test ${tcl_cv_api_getpwuid_r_5+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { uid_t uid; struct passwd pw, *pwp; char buf[512]; int buflen = 512; (void) getpwuid_r(uid, &pw, buf, buflen, &pwp); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_api_getpwuid_r_5=yes else case e in #( e) tcl_cv_api_getpwuid_r_5=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getpwuid_r_5" >&5 printf "%s\n" "$tcl_cv_api_getpwuid_r_5" >&6; } tcl_ok=$tcl_cv_api_getpwuid_r_5 if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETPWUID_R_5 1" >>confdefs.h else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for getpwuid_r with 4 args" >&5 printf %s "checking for getpwuid_r with 4 args... " >&6; } if test ${tcl_cv_api_getpwuid_r_4+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { uid_t uid; struct passwd pw; char buf[512]; int buflen = 512; (void)getpwnam_r(uid, &pw, buf, buflen); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_api_getpwuid_r_4=yes else case e in #( e) tcl_cv_api_getpwuid_r_4=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getpwuid_r_4" >&5 printf "%s\n" "$tcl_cv_api_getpwuid_r_4" >&6; } tcl_ok=$tcl_cv_api_getpwuid_r_4 if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETPWUID_R_4 1" >>confdefs.h fi fi if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETPWUID_R 1" >>confdefs.h fi fi ac_fn_c_check_func "$LINENO" "getpwnam_r" "ac_cv_func_getpwnam_r" if test "x$ac_cv_func_getpwnam_r" = xyes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for getpwnam_r with 5 args" >&5 printf %s "checking for getpwnam_r with 5 args... " >&6; } if test ${tcl_cv_api_getpwnam_r_5+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { char *name; struct passwd pw, *pwp; char buf[512]; int buflen = 512; (void) getpwnam_r(name, &pw, buf, buflen, &pwp); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_api_getpwnam_r_5=yes else case e in #( e) tcl_cv_api_getpwnam_r_5=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getpwnam_r_5" >&5 printf "%s\n" "$tcl_cv_api_getpwnam_r_5" >&6; } tcl_ok=$tcl_cv_api_getpwnam_r_5 if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETPWNAM_R_5 1" >>confdefs.h else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for getpwnam_r with 4 args" >&5 printf %s "checking for getpwnam_r with 4 args... " >&6; } if test ${tcl_cv_api_getpwnam_r_4+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { char *name; struct passwd pw; char buf[512]; int buflen = 512; (void)getpwnam_r(name, &pw, buf, buflen); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_api_getpwnam_r_4=yes else case e in #( e) tcl_cv_api_getpwnam_r_4=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getpwnam_r_4" >&5 printf "%s\n" "$tcl_cv_api_getpwnam_r_4" >&6; } tcl_ok=$tcl_cv_api_getpwnam_r_4 if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETPWNAM_R_4 1" >>confdefs.h fi fi if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETPWNAM_R 1" >>confdefs.h fi fi ac_fn_c_check_func "$LINENO" "getgrgid_r" "ac_cv_func_getgrgid_r" if test "x$ac_cv_func_getgrgid_r" = xyes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for getgrgid_r with 5 args" >&5 printf %s "checking for getgrgid_r with 5 args... " >&6; } if test ${tcl_cv_api_getgrgid_r_5+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { gid_t gid; struct group gr, *grp; char buf[512]; int buflen = 512; (void) getgrgid_r(gid, &gr, buf, buflen, &grp); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_api_getgrgid_r_5=yes else case e in #( e) tcl_cv_api_getgrgid_r_5=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getgrgid_r_5" >&5 printf "%s\n" "$tcl_cv_api_getgrgid_r_5" >&6; } tcl_ok=$tcl_cv_api_getgrgid_r_5 if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETGRGID_R_5 1" >>confdefs.h else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for getgrgid_r with 4 args" >&5 printf %s "checking for getgrgid_r with 4 args... " >&6; } if test ${tcl_cv_api_getgrgid_r_4+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { gid_t gid; struct group gr; char buf[512]; int buflen = 512; (void)getgrgid_r(gid, &gr, buf, buflen); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_api_getgrgid_r_4=yes else case e in #( e) tcl_cv_api_getgrgid_r_4=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getgrgid_r_4" >&5 printf "%s\n" "$tcl_cv_api_getgrgid_r_4" >&6; } tcl_ok=$tcl_cv_api_getgrgid_r_4 if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETGRGID_R_4 1" >>confdefs.h fi fi if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETGRGID_R 1" >>confdefs.h fi fi ac_fn_c_check_func "$LINENO" "getgrnam_r" "ac_cv_func_getgrnam_r" if test "x$ac_cv_func_getgrnam_r" = xyes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for getgrnam_r with 5 args" >&5 printf %s "checking for getgrnam_r with 5 args... " >&6; } if test ${tcl_cv_api_getgrnam_r_5+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { char *name; struct group gr, *grp; char buf[512]; int buflen = 512; (void) getgrnam_r(name, &gr, buf, buflen, &grp); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_api_getgrnam_r_5=yes else case e in #( e) tcl_cv_api_getgrnam_r_5=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getgrnam_r_5" >&5 printf "%s\n" "$tcl_cv_api_getgrnam_r_5" >&6; } tcl_ok=$tcl_cv_api_getgrnam_r_5 if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETGRNAM_R_5 1" >>confdefs.h else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for getgrnam_r with 4 args" >&5 printf %s "checking for getgrnam_r with 4 args... " >&6; } if test ${tcl_cv_api_getgrnam_r_4+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { char *name; struct group gr; char buf[512]; int buflen = 512; (void)getgrnam_r(name, &gr, buf, buflen); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_api_getgrnam_r_4=yes else case e in #( e) tcl_cv_api_getgrnam_r_4=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getgrnam_r_4" >&5 printf "%s\n" "$tcl_cv_api_getgrnam_r_4" >&6; } tcl_ok=$tcl_cv_api_getgrnam_r_4 if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETGRNAM_R_4 1" >>confdefs.h fi fi if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETGRNAM_R 1" >>confdefs.h fi fi if test "`uname -s`" = "Darwin" && \ test "`uname -r | awk -F. '{print $1}'`" -gt 5; then # Starting with Darwin 6 (Mac OSX 10.2), gethostbyX # are actually MT-safe as they always return pointers # from TSD instead of static storage. printf "%s\n" "#define HAVE_MTSAFE_GETHOSTBYNAME 1" >>confdefs.h printf "%s\n" "#define HAVE_MTSAFE_GETHOSTBYADDR 1" >>confdefs.h elif test "`uname -s`" = "HP-UX" && \ test "`uname -r|sed -e 's|B\.||' -e 's|\..*$||'`" -gt 10; then # Starting with HPUX 11.00 (we believe), gethostbyX # are actually MT-safe as they always return pointers # from TSD instead of static storage. printf "%s\n" "#define HAVE_MTSAFE_GETHOSTBYNAME 1" >>confdefs.h printf "%s\n" "#define HAVE_MTSAFE_GETHOSTBYADDR 1" >>confdefs.h else # Avoids picking hidden internal symbol from libc ac_fn_check_decl "$LINENO" "gethostbyname_r" "ac_cv_have_decl_gethostbyname_r" "#include " "$ac_c_undeclared_builtin_options" "CFLAGS" if test "x$ac_cv_have_decl_gethostbyname_r" = xyes then : ac_have_decl=1 else case e in #( e) ac_have_decl=0 ;; esac fi printf "%s\n" "#define HAVE_DECL_GETHOSTBYNAME_R $ac_have_decl" >>confdefs.h if test $ac_have_decl = 1 then : tcl_cv_api_gethostbyname_r=yes else case e in #( e) tcl_cv_api_gethostbyname_r=no ;; esac fi if test "$tcl_cv_api_gethostbyname_r" = yes; then ac_fn_c_check_func "$LINENO" "gethostbyname_r" "ac_cv_func_gethostbyname_r" if test "x$ac_cv_func_gethostbyname_r" = xyes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for gethostbyname_r with 6 args" >&5 printf %s "checking for gethostbyname_r with 6 args... " >&6; } if test ${tcl_cv_api_gethostbyname_r_6+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { char *name; struct hostent *he, *res; char buffer[2048]; int buflen = 2048; int h_errnop; (void) gethostbyname_r(name, he, buffer, buflen, &res, &h_errnop); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_api_gethostbyname_r_6=yes else case e in #( e) tcl_cv_api_gethostbyname_r_6=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_gethostbyname_r_6" >&5 printf "%s\n" "$tcl_cv_api_gethostbyname_r_6" >&6; } tcl_ok=$tcl_cv_api_gethostbyname_r_6 if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETHOSTBYNAME_R_6 1" >>confdefs.h else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for gethostbyname_r with 5 args" >&5 printf %s "checking for gethostbyname_r with 5 args... " >&6; } if test ${tcl_cv_api_gethostbyname_r_5+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { char *name; struct hostent *he; char buffer[2048]; int buflen = 2048; int h_errnop; (void) gethostbyname_r(name, he, buffer, buflen, &h_errnop); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_api_gethostbyname_r_5=yes else case e in #( e) tcl_cv_api_gethostbyname_r_5=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_gethostbyname_r_5" >&5 printf "%s\n" "$tcl_cv_api_gethostbyname_r_5" >&6; } tcl_ok=$tcl_cv_api_gethostbyname_r_5 if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETHOSTBYNAME_R_5 1" >>confdefs.h else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for gethostbyname_r with 3 args" >&5 printf %s "checking for gethostbyname_r with 3 args... " >&6; } if test ${tcl_cv_api_gethostbyname_r_3+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { char *name; struct hostent *he; struct hostent_data data; (void) gethostbyname_r(name, he, &data); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_api_gethostbyname_r_3=yes else case e in #( e) tcl_cv_api_gethostbyname_r_3=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_gethostbyname_r_3" >&5 printf "%s\n" "$tcl_cv_api_gethostbyname_r_3" >&6; } tcl_ok=$tcl_cv_api_gethostbyname_r_3 if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETHOSTBYNAME_R_3 1" >>confdefs.h fi fi fi if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETHOSTBYNAME_R 1" >>confdefs.h fi fi fi # Avoids picking hidden internal symbol from libc ac_fn_check_decl "$LINENO" "gethostbyaddr_r" "ac_cv_have_decl_gethostbyaddr_r" "#include " "$ac_c_undeclared_builtin_options" "CFLAGS" if test "x$ac_cv_have_decl_gethostbyaddr_r" = xyes then : ac_have_decl=1 else case e in #( e) ac_have_decl=0 ;; esac fi printf "%s\n" "#define HAVE_DECL_GETHOSTBYADDR_R $ac_have_decl" >>confdefs.h if test $ac_have_decl = 1 then : tcl_cv_api_gethostbyaddr_r=yes else case e in #( e) tcl_cv_api_gethostbyaddr_r=no ;; esac fi if test "$tcl_cv_api_gethostbyaddr_r" = yes; then ac_fn_c_check_func "$LINENO" "gethostbyaddr_r" "ac_cv_func_gethostbyaddr_r" if test "x$ac_cv_func_gethostbyaddr_r" = xyes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for gethostbyaddr_r with 7 args" >&5 printf %s "checking for gethostbyaddr_r with 7 args... " >&6; } if test ${tcl_cv_api_gethostbyaddr_r_7+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { char *addr; int length; int type; struct hostent *result; char buffer[2048]; int buflen = 2048; int h_errnop; (void) gethostbyaddr_r(addr, length, type, result, buffer, buflen, &h_errnop); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_api_gethostbyaddr_r_7=yes else case e in #( e) tcl_cv_api_gethostbyaddr_r_7=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_gethostbyaddr_r_7" >&5 printf "%s\n" "$tcl_cv_api_gethostbyaddr_r_7" >&6; } tcl_ok=$tcl_cv_api_gethostbyaddr_r_7 if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETHOSTBYADDR_R_7 1" >>confdefs.h else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for gethostbyaddr_r with 8 args" >&5 printf %s "checking for gethostbyaddr_r with 8 args... " >&6; } if test ${tcl_cv_api_gethostbyaddr_r_8+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { char *addr; int length; int type; struct hostent *result, *resultp; char buffer[2048]; int buflen = 2048; int h_errnop; (void) gethostbyaddr_r(addr, length, type, result, buffer, buflen, &resultp, &h_errnop); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_api_gethostbyaddr_r_8=yes else case e in #( e) tcl_cv_api_gethostbyaddr_r_8=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_gethostbyaddr_r_8" >&5 printf "%s\n" "$tcl_cv_api_gethostbyaddr_r_8" >&6; } tcl_ok=$tcl_cv_api_gethostbyaddr_r_8 if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETHOSTBYADDR_R_8 1" >>confdefs.h fi fi if test "$tcl_ok" = yes; then printf "%s\n" "#define HAVE_GETHOSTBYADDR_R 1" >>confdefs.h fi fi fi fi #--------------------------------------------------------------------------- # Check for serial port interface. # # termios.h is present on all POSIX systems. # sys/ioctl.h is almost always present, though what it contains # is system-specific. # sys/modem.h is needed on HP-UX. #--------------------------------------------------------------------------- ac_fn_c_check_header_compile "$LINENO" "termios.h" "ac_cv_header_termios_h" "$ac_includes_default" if test "x$ac_cv_header_termios_h" = xyes then : printf "%s\n" "#define HAVE_TERMIOS_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "sys/ioctl.h" "ac_cv_header_sys_ioctl_h" "$ac_includes_default" if test "x$ac_cv_header_sys_ioctl_h" = xyes then : printf "%s\n" "#define HAVE_SYS_IOCTL_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "sys/modem.h" "ac_cv_header_sys_modem_h" "$ac_includes_default" if test "x$ac_cv_header_sys_modem_h" = xyes then : printf "%s\n" "#define HAVE_SYS_MODEM_H 1" >>confdefs.h fi #-------------------------------------------------------------------- # Include sys/select.h if it exists and if it supplies things # that appear to be useful and aren't already in sys/types.h. # This appears to be true only on the RS/6000 under AIX. Some # systems like OSF/1 have a sys/select.h that's of no use, and # other systems like SCO UNIX have a sys/select.h that's # pernicious. If "fd_set" isn't defined anywhere then set a # special flag. #-------------------------------------------------------------------- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fd_set in sys/types" >&5 printf %s "checking for fd_set in sys/types... " >&6; } if test ${tcl_cv_type_fd_set+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { fd_set readMask, writeMask; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_type_fd_set=yes else case e in #( e) tcl_cv_type_fd_set=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_type_fd_set" >&5 printf "%s\n" "$tcl_cv_type_fd_set" >&6; } tcl_ok=$tcl_cv_type_fd_set if test $tcl_ok = no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fd_mask in sys/select" >&5 printf %s "checking for fd_mask in sys/select... " >&6; } if test ${tcl_cv_grep_fd_mask+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP_TRADITIONAL "fd_mask" >/dev/null 2>&1 then : tcl_cv_grep_fd_mask=present else case e in #( e) tcl_cv_grep_fd_mask=missing ;; esac fi rm -rf conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_grep_fd_mask" >&5 printf "%s\n" "$tcl_cv_grep_fd_mask" >&6; } if test $tcl_cv_grep_fd_mask = present; then printf "%s\n" "#define HAVE_SYS_SELECT_H 1" >>confdefs.h tcl_ok=yes fi fi if test $tcl_ok = no; then printf "%s\n" "#define NO_FD_SET 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pselect" >&5 printf %s "checking for pselect... " >&6; } if test ${tcl_cv_func_pselect+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { void *func = pselect; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_func_pselect=yes else case e in #( e) tcl_cv_func_pselect=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_func_pselect" >&5 printf "%s\n" "$tcl_cv_func_pselect" >&6; } tcl_ok=$tcl_cv_func_pselect if test $tcl_ok = yes; then printf "%s\n" "#define HAVE_PSELECT 1" >>confdefs.h fi #------------------------------------------------------------------------ # Options for the notifier. Checks for epoll(7) on Linux, and # kqueue(2) on {DragonFly,Free,Net,Open}BSD #------------------------------------------------------------------------ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for advanced notifier support" >&5 printf %s "checking for advanced notifier support... " >&6; } case x`uname -s` in xLinux) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: epoll(7)" >&5 printf "%s\n" "epoll(7)" >&6; } for ac_header in sys/epoll.h do : ac_fn_c_check_header_compile "$LINENO" "sys/epoll.h" "ac_cv_header_sys_epoll_h" "$ac_includes_default" if test "x$ac_cv_header_sys_epoll_h" = xyes then : printf "%s\n" "#define HAVE_SYS_EPOLL_H 1" >>confdefs.h printf "%s\n" "#define NOTIFIER_EPOLL 1" >>confdefs.h fi done for ac_header in sys/eventfd.h do : ac_fn_c_check_header_compile "$LINENO" "sys/eventfd.h" "ac_cv_header_sys_eventfd_h" "$ac_includes_default" if test "x$ac_cv_header_sys_eventfd_h" = xyes then : printf "%s\n" "#define HAVE_SYS_EVENTFD_H 1" >>confdefs.h printf "%s\n" "#define HAVE_EVENTFD 1" >>confdefs.h fi done;; xDragonFlyBSD|xFreeBSD|xNetBSD|xOpenBSD) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: kqueue(2)" >&5 printf "%s\n" "kqueue(2)" >&6; } # Messy because we want to check if *all* the headers are present, and not # just *any* tcl_kqueue_headers=x for ac_header in sys/types.h sys/event.h sys/time.h do : as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | sed "$as_sed_sh"` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" if eval test \"x\$"$as_ac_Header"\" = x"yes" then : cat >>confdefs.h <<_ACEOF #define `printf "%s\n" "HAVE_$ac_header" | sed "$as_sed_cpp"` 1 _ACEOF tcl_kqueue_headers=${tcl_kqueue_headers}y fi done if test $tcl_kqueue_headers = xyyy then : printf "%s\n" "#define NOTIFIER_KQUEUE 1" >>confdefs.h fi;; xDarwin) # Assume that we've got CoreFoundation present (checked elsewhere because # of wider impact). { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: OSX" >&5 printf "%s\n" "OSX" >&6; };; *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none" >&5 printf "%s\n" "none" >&6; };; esac #------------------------------------------------------------------------------ # Find out all about time handling differences. #------------------------------------------------------------------------------ ac_fn_c_check_header_compile "$LINENO" "sys/time.h" "ac_cv_header_sys_time_h" "$ac_includes_default" if test "x$ac_cv_header_sys_time_h" = xyes then : printf "%s\n" "#define HAVE_SYS_TIME_H 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "gmtime_r" "ac_cv_func_gmtime_r" if test "x$ac_cv_func_gmtime_r" = xyes then : printf "%s\n" "#define HAVE_GMTIME_R 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "localtime_r" "ac_cv_func_localtime_r" if test "x$ac_cv_func_localtime_r" = xyes then : printf "%s\n" "#define HAVE_LOCALTIME_R 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking tm_tzadj in struct tm" >&5 printf %s "checking tm_tzadj in struct tm... " >&6; } if test ${tcl_cv_member_tm_tzadj+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { struct tm tm; (void)tm.tm_tzadj; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_member_tm_tzadj=yes else case e in #( e) tcl_cv_member_tm_tzadj=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_member_tm_tzadj" >&5 printf "%s\n" "$tcl_cv_member_tm_tzadj" >&6; } if test $tcl_cv_member_tm_tzadj = yes ; then printf "%s\n" "#define HAVE_TM_TZADJ 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking tm_gmtoff in struct tm" >&5 printf %s "checking tm_gmtoff in struct tm... " >&6; } if test ${tcl_cv_member_tm_gmtoff+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { struct tm tm; (void)tm.tm_gmtoff; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_member_tm_gmtoff=yes else case e in #( e) tcl_cv_member_tm_gmtoff=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_member_tm_gmtoff" >&5 printf "%s\n" "$tcl_cv_member_tm_gmtoff" >&6; } if test $tcl_cv_member_tm_gmtoff = yes ; then printf "%s\n" "#define HAVE_TM_GMTOFF 1" >>confdefs.h fi # # Its important to include time.h in this check, as some systems # (like convex) have timezone functions, etc. # { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking long timezone variable" >&5 printf %s "checking long timezone variable... " >&6; } if test ${tcl_cv_timezone_long+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { extern long timezone; timezone += 1; exit (0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_timezone_long=yes else case e in #( e) tcl_cv_timezone_long=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_timezone_long" >&5 printf "%s\n" "$tcl_cv_timezone_long" >&6; } if test $tcl_cv_timezone_long = yes ; then printf "%s\n" "#define HAVE_TIMEZONE_VAR 1" >>confdefs.h else # # On some systems (eg IRIX 6.2), timezone is a time_t and not a long. # { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking time_t timezone variable" >&5 printf %s "checking time_t timezone variable... " >&6; } if test ${tcl_cv_timezone_time+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { extern time_t timezone; timezone += 1; exit (0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_timezone_time=yes else case e in #( e) tcl_cv_timezone_time=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_timezone_time" >&5 printf "%s\n" "$tcl_cv_timezone_time" >&6; } if test $tcl_cv_timezone_time = yes ; then printf "%s\n" "#define HAVE_TIMEZONE_VAR 1" >>confdefs.h fi fi #-------------------------------------------------------------------- # Some systems (e.g., IRIX 4.0.5) lack some fields in struct stat. But # we might be able to use fstatfs instead. Some systems (OpenBSD?) also # lack blkcnt_t. #-------------------------------------------------------------------- if test "$ac_cv_cygwin" != "yes"; then ac_fn_c_check_member "$LINENO" "struct stat" "st_blocks" "ac_cv_member_struct_stat_st_blocks" "$ac_includes_default" if test "x$ac_cv_member_struct_stat_st_blocks" = xyes then : printf "%s\n" "#define HAVE_STRUCT_STAT_ST_BLOCKS 1" >>confdefs.h fi ac_fn_c_check_member "$LINENO" "struct stat" "st_blksize" "ac_cv_member_struct_stat_st_blksize" "$ac_includes_default" if test "x$ac_cv_member_struct_stat_st_blksize" = xyes then : printf "%s\n" "#define HAVE_STRUCT_STAT_ST_BLKSIZE 1" >>confdefs.h fi ac_fn_c_check_member "$LINENO" "struct stat" "st_rdev" "ac_cv_member_struct_stat_st_rdev" "$ac_includes_default" if test "x$ac_cv_member_struct_stat_st_rdev" = xyes then : printf "%s\n" "#define HAVE_STRUCT_STAT_ST_RDEV 1" >>confdefs.h fi fi ac_fn_c_check_type "$LINENO" "blkcnt_t" "ac_cv_type_blkcnt_t" "$ac_includes_default" if test "x$ac_cv_type_blkcnt_t" = xyes then : printf "%s\n" "#define HAVE_BLKCNT_T 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "fstatfs" "ac_cv_func_fstatfs" if test "x$ac_cv_func_fstatfs" = xyes then : else case e in #( e) printf "%s\n" "#define NO_FSTATFS 1" >>confdefs.h ;; esac fi #-------------------------------------------------------------------- # Check for various typedefs and provide substitutes if # they don't exist. #-------------------------------------------------------------------- ac_fn_c_check_type "$LINENO" "mode_t" "ac_cv_type_mode_t" "$ac_includes_default" if test "x$ac_cv_type_mode_t" = xyes then : else case e in #( e) printf "%s\n" "#define mode_t int" >>confdefs.h ;; esac fi ac_fn_c_check_type "$LINENO" "pid_t" "ac_cv_type_pid_t" "$ac_includes_default " if test "x$ac_cv_type_pid_t" = xyes then : else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #if defined _WIN64 && !defined __CYGWIN__ LLP64 #endif int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_pid_type='int' else case e in #( e) ac_pid_type='__int64' ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext printf "%s\n" "#define pid_t $ac_pid_type" >>confdefs.h ;; esac fi ac_fn_c_check_type "$LINENO" "size_t" "ac_cv_type_size_t" "$ac_includes_default" if test "x$ac_cv_type_size_t" = xyes then : else case e in #( e) printf "%s\n" "#define size_t unsigned int" >>confdefs.h ;; esac fi ac_fn_c_check_type "$LINENO" "uid_t" "ac_cv_type_uid_t" "$ac_includes_default" if test "x$ac_cv_type_uid_t" = xyes then : else case e in #( e) printf "%s\n" "#define uid_t int" >>confdefs.h ;; esac fi ac_fn_c_check_type "$LINENO" "gid_t" "ac_cv_type_gid_t" "$ac_includes_default" if test "x$ac_cv_type_gid_t" = xyes then : else case e in #( e) printf "%s\n" "#define gid_t int" >>confdefs.h ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for socklen_t" >&5 printf %s "checking for socklen_t... " >&6; } if test ${tcl_cv_type_socklen_t+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { socklen_t foo; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_type_socklen_t=yes else case e in #( e) tcl_cv_type_socklen_t=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_type_socklen_t" >&5 printf "%s\n" "$tcl_cv_type_socklen_t" >&6; } if test $tcl_cv_type_socklen_t = no; then printf "%s\n" "#define socklen_t int" >>confdefs.h fi ac_fn_c_check_type "$LINENO" "intptr_t" "ac_cv_type_intptr_t" " #include " if test "x$ac_cv_type_intptr_t" = xyes then : printf "%s\n" "#define HAVE_INTPTR_T 1" >>confdefs.h fi ac_fn_c_check_type "$LINENO" "uintptr_t" "ac_cv_type_uintptr_t" " #include " if test "x$ac_cv_type_uintptr_t" = xyes then : printf "%s\n" "#define HAVE_UINTPTR_T 1" >>confdefs.h fi #-------------------------------------------------------------------- # The check below checks whether defines the type # "union wait" correctly. It's needed because of weirdness in # HP-UX where "union wait" is defined in both the BSD and SYS-V # environments. Checking the usability of WIFEXITED seems to do # the trick. #-------------------------------------------------------------------- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking union wait" >&5 printf %s "checking union wait... " >&6; } if test ${tcl_cv_union_wait+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { union wait x; WIFEXITED(x); /* Generates compiler error if WIFEXITED * uses an int. */ ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : tcl_cv_union_wait=yes else case e in #( e) tcl_cv_union_wait=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_union_wait" >&5 printf "%s\n" "$tcl_cv_union_wait" >&6; } if test $tcl_cv_union_wait = no; then printf "%s\n" "#define NO_UNION_WAIT 1" >>confdefs.h fi #-------------------------------------------------------------------- # Check whether there is an strncasecmp function on this system. # This is a bit tricky because under SCO it's in -lsocket and # under Sequent Dynix it's in -linet. #-------------------------------------------------------------------- ac_fn_c_check_func "$LINENO" "strncasecmp" "ac_cv_func_strncasecmp" if test "x$ac_cv_func_strncasecmp" = xyes then : tcl_ok=1 else case e in #( e) tcl_ok=0 ;; esac fi if test "$tcl_ok" = 0; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for strncasecmp in -lsocket" >&5 printf %s "checking for strncasecmp in -lsocket... " >&6; } if test ${ac_cv_lib_socket_strncasecmp+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lsocket $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char strncasecmp (void); int main (void) { return strncasecmp (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_socket_strncasecmp=yes else case e in #( e) ac_cv_lib_socket_strncasecmp=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_strncasecmp" >&5 printf "%s\n" "$ac_cv_lib_socket_strncasecmp" >&6; } if test "x$ac_cv_lib_socket_strncasecmp" = xyes then : tcl_ok=1 else case e in #( e) tcl_ok=0 ;; esac fi fi if test "$tcl_ok" = 0; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for strncasecmp in -linet" >&5 printf %s "checking for strncasecmp in -linet... " >&6; } if test ${ac_cv_lib_inet_strncasecmp+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-linet $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char strncasecmp (void); int main (void) { return strncasecmp (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_inet_strncasecmp=yes else case e in #( e) ac_cv_lib_inet_strncasecmp=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_inet_strncasecmp" >&5 printf "%s\n" "$ac_cv_lib_inet_strncasecmp" >&6; } if test "x$ac_cv_lib_inet_strncasecmp" = xyes then : tcl_ok=1 else case e in #( e) tcl_ok=0 ;; esac fi fi if test "$tcl_ok" = 0; then case " $LIBOBJS " in *" strncasecmp.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS strncasecmp.$ac_objext" ;; esac USE_COMPAT=1 fi #-------------------------------------------------------------------- # The code below deals with several issues related to gettimeofday: # 1. Some systems don't provide a gettimeofday function at all # (set NO_GETTOD if this is the case). # 2. See if gettimeofday is declared in the header file. # if not, set the GETTOD_NOT_DECLARED flag so that tclPort.h can # declare it. #-------------------------------------------------------------------- ac_fn_c_check_func "$LINENO" "gettimeofday" "ac_cv_func_gettimeofday" if test "x$ac_cv_func_gettimeofday" = xyes then : else case e in #( e) printf "%s\n" "#define NO_GETTOD 1" >>confdefs.h ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for gettimeofday declaration" >&5 printf %s "checking for gettimeofday declaration... " >&6; } if test ${tcl_cv_grep_gettimeofday+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP_TRADITIONAL "gettimeofday" >/dev/null 2>&1 then : tcl_cv_grep_gettimeofday=present else case e in #( e) tcl_cv_grep_gettimeofday=missing ;; esac fi rm -rf conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_grep_gettimeofday" >&5 printf "%s\n" "$tcl_cv_grep_gettimeofday" >&6; } if test $tcl_cv_grep_gettimeofday = missing ; then printf "%s\n" "#define GETTOD_NOT_DECLARED 1" >>confdefs.h fi #-------------------------------------------------------------------- # The following code checks to see whether it is possible to get # signed chars on this platform. This is needed in order to # properly generate sign-extended ints from character values. #-------------------------------------------------------------------- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether char is unsigned" >&5 printf %s "checking whether char is unsigned... " >&6; } if test ${ac_cv_c_char_unsigned+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_includes_default int main (void) { static int test_array [1 - 2 * !(((char) -1) < 0)]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_c_char_unsigned=no else case e in #( e) ac_cv_c_char_unsigned=yes ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_char_unsigned" >&5 printf "%s\n" "$ac_cv_c_char_unsigned" >&6; } if test $ac_cv_c_char_unsigned = yes; then printf "%s\n" "#define __CHAR_UNSIGNED__ 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking signed char declarations" >&5 printf %s "checking signed char declarations... " >&6; } if test ${tcl_cv_char_signed+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { signed char *p; p = 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_char_signed=yes else case e in #( e) tcl_cv_char_signed=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_char_signed" >&5 printf "%s\n" "$tcl_cv_char_signed" >&6; } if test $tcl_cv_char_signed = yes; then printf "%s\n" "#define HAVE_SIGNED_CHAR 1" >>confdefs.h fi #-------------------------------------------------------------------- # Does putenv() copy or not? We need to know to avoid memory leaks. #-------------------------------------------------------------------- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a putenv() that copies the buffer" >&5 printf %s "checking for a putenv() that copies the buffer... " >&6; } if test ${tcl_cv_putenv_copy+y} then : printf %s "(cached) " >&6 else case e in #( e) if test "$cross_compiling" = yes then : tcl_cv_putenv_copy=no else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #define OURVAR "havecopy=yes" int main (int argc, char *argv[]) { char *foo, *bar; foo = (char *)strdup(OURVAR); putenv(foo); strcpy((char *)(strchr(foo, '=') + 1), "no"); bar = getenv("havecopy"); if (!strcmp(bar, "no")) { /* doesnt copy */ return 0; } else { /* does copy */ return 1; } } _ACEOF if ac_fn_c_try_run "$LINENO" then : tcl_cv_putenv_copy=no else case e in #( e) tcl_cv_putenv_copy=yes ;; esac fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_putenv_copy" >&5 printf "%s\n" "$tcl_cv_putenv_copy" >&6; } if test $tcl_cv_putenv_copy = yes; then printf "%s\n" "#define HAVE_PUTENV_THAT_COPIES 1" >>confdefs.h fi #-------------------------------------------------------------------- # Check for support of nl_langinfo function #-------------------------------------------------------------------- # Check whether --enable-langinfo was given. if test ${enable_langinfo+y} then : enableval=$enable_langinfo; langinfo_ok=$enableval else case e in #( e) langinfo_ok=yes ;; esac fi HAVE_LANGINFO=0 if test "$langinfo_ok" = "yes"; then ac_fn_c_check_header_compile "$LINENO" "langinfo.h" "ac_cv_header_langinfo_h" "$ac_includes_default" if test "x$ac_cv_header_langinfo_h" = xyes then : langinfo_ok=yes else case e in #( e) langinfo_ok=no ;; esac fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to use nl_langinfo" >&5 printf %s "checking whether to use nl_langinfo... " >&6; } if test "$langinfo_ok" = "yes"; then if test ${tcl_cv_langinfo_h+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { nl_langinfo(CODESET); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_langinfo_h=yes else case e in #( e) tcl_cv_langinfo_h=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_langinfo_h" >&5 printf "%s\n" "$tcl_cv_langinfo_h" >&6; } if test $tcl_cv_langinfo_h = yes; then printf "%s\n" "#define HAVE_LANGINFO 1" >>confdefs.h fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $langinfo_ok" >&5 printf "%s\n" "$langinfo_ok" >&6; } fi #-------------------------------------------------------------------- # Check for support of cfmakeraw, chflags and mkstemps functions #-------------------------------------------------------------------- ac_fn_c_check_func "$LINENO" "cfmakeraw" "ac_cv_func_cfmakeraw" if test "x$ac_cv_func_cfmakeraw" = xyes then : printf "%s\n" "#define HAVE_CFMAKERAW 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "chflags" "ac_cv_func_chflags" if test "x$ac_cv_func_chflags" = xyes then : printf "%s\n" "#define HAVE_CHFLAGS 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "mkstemps" "ac_cv_func_mkstemps" if test "x$ac_cv_func_mkstemps" = xyes then : printf "%s\n" "#define HAVE_MKSTEMPS 1" >>confdefs.h fi #-------------------------------------------------------------------- # Darwin specific API checks and defines #-------------------------------------------------------------------- if test "`uname -s`" = "Darwin" ; then ac_fn_c_check_func "$LINENO" "getattrlist" "ac_cv_func_getattrlist" if test "x$ac_cv_func_getattrlist" = xyes then : printf "%s\n" "#define HAVE_GETATTRLIST 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "copyfile.h" "ac_cv_header_copyfile_h" "$ac_includes_default" if test "x$ac_cv_header_copyfile_h" = xyes then : printf "%s\n" "#define HAVE_COPYFILE_H 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "copyfile" "ac_cv_func_copyfile" if test "x$ac_cv_func_copyfile" = xyes then : printf "%s\n" "#define HAVE_COPYFILE 1" >>confdefs.h fi if test $tcl_corefoundation = yes; then ac_fn_c_check_header_compile "$LINENO" "libkern/OSAtomic.h" "ac_cv_header_libkern_OSAtomic_h" "$ac_includes_default" if test "x$ac_cv_header_libkern_OSAtomic_h" = xyes then : printf "%s\n" "#define HAVE_LIBKERN_OSATOMIC_H 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "OSSpinLockLock" "ac_cv_func_OSSpinLockLock" if test "x$ac_cv_func_OSSpinLockLock" = xyes then : printf "%s\n" "#define HAVE_OSSPINLOCKLOCK 1" >>confdefs.h fi fi printf "%s\n" "#define TCL_LOAD_FROM_MEMORY 1" >>confdefs.h printf "%s\n" "#define TCL_WIDE_CLICKS 1" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if weak import is available" >&5 printf %s "checking if weak import is available... " >&6; } if test ${tcl_cv_cc_weak_import+y} then : printf %s "(cached) " >&6 else case e in #( e) hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int rand(void) __attribute__((weak_import)); int main (void) { rand(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : tcl_cv_cc_weak_import=yes else case e in #( e) tcl_cv_cc_weak_import=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext CFLAGS=$hold_cflags ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_weak_import" >&5 printf "%s\n" "$tcl_cv_cc_weak_import" >&6; } if test $tcl_cv_cc_weak_import = yes; then printf "%s\n" "#define HAVE_WEAK_IMPORT 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if Darwin SUSv3 extensions are available" >&5 printf %s "checking if Darwin SUSv3 extensions are available... " >&6; } if test ${tcl_cv_cc_darwin_c_source+y} then : printf %s "(cached) " >&6 else case e in #( e) hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #define _DARWIN_C_SOURCE 1 #include int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : tcl_cv_cc_darwin_c_source=yes else case e in #( e) tcl_cv_cc_darwin_c_source=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext CFLAGS=$hold_cflags ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_darwin_c_source" >&5 printf "%s\n" "$tcl_cv_cc_darwin_c_source" >&6; } if test $tcl_cv_cc_darwin_c_source = yes; then printf "%s\n" "#define _DARWIN_C_SOURCE 1" >>confdefs.h fi # Build .bundle dltest binaries in addition to .dylib DLTEST_LD='${CC} -bundle -Wl,-w ${CFLAGS} ${LDFLAGS}' DLTEST_SUFFIX=".bundle" else DLTEST_LD='${SHLIB_LD}' DLTEST_SUFFIX="" fi #-------------------------------------------------------------------- # Check for support of fts functions (readdir replacement) #-------------------------------------------------------------------- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fts" >&5 printf %s "checking for fts... " >&6; } if test ${tcl_cv_api_fts+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main (void) { char*const p[2] = {"/", NULL}; FTS *f = fts_open(p, FTS_PHYSICAL|FTS_NOCHDIR|FTS_NOSTAT, NULL); FTSENT *e = fts_read(f); fts_close(f); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : tcl_cv_api_fts=yes else case e in #( e) tcl_cv_api_fts=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_fts" >&5 printf "%s\n" "$tcl_cv_api_fts" >&6; } if test $tcl_cv_api_fts = yes; then printf "%s\n" "#define HAVE_FTS 1" >>confdefs.h fi #-------------------------------------------------------------------- # The statements below check for systems where POSIX-style non-blocking # I/O (O_NONBLOCK) doesn't work or is unimplemented. On these systems # (mostly older ones), use the old BSD-style FIONBIO approach instead. #-------------------------------------------------------------------- ac_fn_c_check_header_compile "$LINENO" "sys/ioctl.h" "ac_cv_header_sys_ioctl_h" "$ac_includes_default" if test "x$ac_cv_header_sys_ioctl_h" = xyes then : printf "%s\n" "#define HAVE_SYS_IOCTL_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "sys/filio.h" "ac_cv_header_sys_filio_h" "$ac_includes_default" if test "x$ac_cv_header_sys_filio_h" = xyes then : printf "%s\n" "#define HAVE_SYS_FILIO_H 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking system version" >&5 printf %s "checking system version... " >&6; } if test ${tcl_cv_sys_version+y} then : printf %s "(cached) " >&6 else case e in #( e) if test "${TEA_PLATFORM}" = "windows" ; then tcl_cv_sys_version=windows else tcl_cv_sys_version=`uname -s`-`uname -r` if test "$?" -ne 0 ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: can't find uname command" >&5 printf "%s\n" "$as_me: WARNING: can't find uname command" >&2;} tcl_cv_sys_version=unknown else if test "`uname -s`" = "AIX" ; then tcl_cv_sys_version=AIX-`uname -v`.`uname -r` fi if test "`uname -s`" = "NetBSD" -a -f /etc/debian_version ; then tcl_cv_sys_version=NetBSD-Debian fi fi fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_sys_version" >&5 printf "%s\n" "$tcl_cv_sys_version" >&6; } system=$tcl_cv_sys_version { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking FIONBIO vs. O_NONBLOCK for nonblocking I/O" >&5 printf %s "checking FIONBIO vs. O_NONBLOCK for nonblocking I/O... " >&6; } case $system in OSF*) printf "%s\n" "#define USE_FIONBIO 1" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: FIONBIO" >&5 printf "%s\n" "FIONBIO" >&6; } ;; *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: O_NONBLOCK" >&5 printf "%s\n" "O_NONBLOCK" >&6; } ;; esac #------------------------------------------------------------------------ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to use dll unloading" >&5 printf %s "checking whether to use dll unloading... " >&6; } # Check whether --enable-dll-unloading was given. if test ${enable_dll_unloading+y} then : enableval=$enable_dll_unloading; tcl_ok=$enableval else case e in #( e) tcl_ok=yes ;; esac fi if test $tcl_ok = yes; then printf "%s\n" "#define TCL_UNLOAD_DLLS 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_ok" >&5 printf "%s\n" "$tcl_ok" >&6; } #------------------------------------------------------------------------ # Check whether the timezone data is supplied by the OS or has # to be installed by Tcl. The default is autodetection, but can # be overridden on the configure command line either way. #------------------------------------------------------------------------ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for timezone data" >&5 printf %s "checking for timezone data... " >&6; } # Check whether --with-tzdata was given. if test ${with_tzdata+y} then : withval=$with_tzdata; tcl_ok=$withval else case e in #( e) tcl_ok=auto ;; esac fi # # Any directories that get added here must also be added to the # search path in ::tcl::clock::Initialize (library/clock.tcl). # case $tcl_ok in no) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: supplied by OS vendor" >&5 printf "%s\n" "supplied by OS vendor" >&6; } ;; yes) # nothing to do here ;; auto*) if test ${tcl_cv_dir_zoneinfo+y} then : printf %s "(cached) " >&6 else case e in #( e) for dir in /usr/share/zoneinfo \ /usr/share/lib/zoneinfo \ /usr/lib/zoneinfo do if test -f $dir/UTC -o -f $dir/GMT then tcl_cv_dir_zoneinfo="$dir" break fi done ;; esac fi if test -n "$tcl_cv_dir_zoneinfo"; then tcl_ok=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $dir" >&5 printf "%s\n" "$dir" >&6; } else tcl_ok=yes fi ;; *) as_fn_error $? "invalid argument: $tcl_ok" "$LINENO" 5 ;; esac if test $tcl_ok = yes then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: supplied by Tcl" >&5 printf "%s\n" "supplied by Tcl" >&6; } INSTALL_TZDATA=install-tzdata fi #-------------------------------------------------------------------- # DTrace support #-------------------------------------------------------------------- # Check whether --enable-dtrace was given. if test ${enable_dtrace+y} then : enableval=$enable_dtrace; tcl_ok=$enableval else case e in #( e) tcl_ok=no ;; esac fi if test $tcl_ok = yes; then ac_fn_c_check_header_compile "$LINENO" "sys/sdt.h" "ac_cv_header_sys_sdt_h" "$ac_includes_default" if test "x$ac_cv_header_sys_sdt_h" = xyes then : tcl_ok=yes else case e in #( e) tcl_ok=no ;; esac fi fi if test $tcl_ok = yes; then # Extract the first word of "dtrace", so it can be a program name with args. set dummy dtrace; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_DTRACE+y} then : printf %s "(cached) " >&6 else case e in #( e) case $DTRACE in [\\/]* | ?:[\\/]*) ac_cv_path_DTRACE="$DTRACE" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_dummy="$PATH:/usr/sbin" for as_dir in $as_dummy do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_DTRACE="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac ;; esac fi DTRACE=$ac_cv_path_DTRACE if test -n "$DTRACE"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $DTRACE" >&5 printf "%s\n" "$DTRACE" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -z "$ac_cv_path_DTRACE" && tcl_ok=no fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to enable DTrace support" >&5 printf %s "checking whether to enable DTrace support... " >&6; } MAKEFILE_SHELL='/bin/sh' if test $tcl_ok = yes; then printf "%s\n" "#define USE_DTRACE 1" >>confdefs.h DTRACE_SRC="\${DTRACE_SRC}" DTRACE_HDR="\${DTRACE_HDR}" if test "`uname -s`" != "Darwin" ; then DTRACE_OBJ="\${DTRACE_OBJ}" if test "`uname -s`" = "SunOS" -a "$SHARED_BUILD" = "0" ; then # Need to create an intermediate object file to ensure tclDTrace.o # gets included when linking against the static tcl library. STLIB_LD='stlib_ld () { /usr/ccs/bin/ld -r -o $${1%.a}.o "$${@:2}" && '"${STLIB_LD}"' $${1} $${1%.a}.o ; } && stlib_ld' MAKEFILE_SHELL='/bin/bash' # Force use of Sun ar and ranlib, the GNU versions choke on # tclDTrace.o and the combined object file above. AR='/usr/ccs/bin/ar' RANLIB='/usr/ccs/bin/ranlib' fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_ok" >&5 printf "%s\n" "$tcl_ok" >&6; } #-------------------------------------------------------------------- # The check below checks whether the cpuid instruction is usable. #-------------------------------------------------------------------- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the cpuid instruction is usable" >&5 printf %s "checking whether the cpuid instruction is usable... " >&6; } if test ${tcl_cv_cpuid+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { int index,regsPtr[4]; __asm__ __volatile__("mov %%ebx, %%edi \n\t" "cpuid \n\t" "mov %%ebx, %%esi \n\t" "mov %%edi, %%ebx \n\t" : "=a"(regsPtr[0]), "=S"(regsPtr[1]), "=c"(regsPtr[2]), "=d"(regsPtr[3]) : "a"(index) : "edi"); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : tcl_cv_cpuid=yes else case e in #( e) tcl_cv_cpuid=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cpuid" >&5 printf "%s\n" "$tcl_cv_cpuid" >&6; } if test $tcl_cv_cpuid = yes; then printf "%s\n" "#define HAVE_CPUID 1" >>confdefs.h fi #-------------------------------------------------------------------- # The statements below define a collection of symbols related to # building libtcl as a shared library instead of a static library. #-------------------------------------------------------------------- TCL_UNSHARED_LIB_SUFFIX=${UNSHARED_LIB_SUFFIX} TCL_SHARED_LIB_SUFFIX=${SHARED_LIB_SUFFIX} eval "TCL_LIB_FILE=libtcl${LIB_SUFFIX}" # tclConfig.sh needs a version of the _LIB_SUFFIX that has been eval'ed # since on some platforms TCL_LIB_FILE contains shell escapes. # (See also: TCL_TRIM_DOTS). eval "TCL_LIB_FILE=${TCL_LIB_FILE}" test -z "$TCL_LIBRARY" && TCL_LIBRARY='$(prefix)/lib/tcl$(VERSION)' PRIVATE_INCLUDE_DIR='$(includedir)' HTML_DIR='$(DISTDIR)/html' # Note: in the following variable, it's important to use the absolute # path name of the Tcl directory rather than "..": this is because # AIX remembers this path and will attempt to use it at run-time to look # up the Tcl library. if test "`uname -s`" = "Darwin" ; then if test "`uname -s`" = "Darwin" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to package libraries" >&5 printf %s "checking how to package libraries... " >&6; } # Check whether --enable-framework was given. if test ${enable_framework+y} then : enableval=$enable_framework; enable_framework=$enableval else case e in #( e) enable_framework=no ;; esac fi if test $enable_framework = yes; then if test $SHARED_BUILD = 0; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Frameworks can only be built if --enable-shared is yes" >&5 printf "%s\n" "$as_me: WARNING: Frameworks can only be built if --enable-shared is yes" >&2;} enable_framework=no fi if test $tcl_corefoundation = no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Frameworks can only be used when CoreFoundation is available" >&5 printf "%s\n" "$as_me: WARNING: Frameworks can only be used when CoreFoundation is available" >&2;} enable_framework=no fi fi if test $enable_framework = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: framework" >&5 printf "%s\n" "framework" >&6; } FRAMEWORK_BUILD=1 else if test $SHARED_BUILD = 1; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: shared library" >&5 printf "%s\n" "shared library" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: static library" >&5 printf "%s\n" "static library" >&6; } fi FRAMEWORK_BUILD=0 fi fi TCL_SHLIB_LD_EXTRAS="-compatibility_version ${TCL_VERSION} -current_version ${TCL_VERSION}`echo ${TCL_PATCH_LEVEL} | awk '{match($0, "\\\.[0-9]+"); print substr($0,RSTART,RLENGTH)}'`" TCL_SHLIB_LD_EXTRAS="${TCL_SHLIB_LD_EXTRAS}"' -install_name "${DYLIB_INSTALL_DIR}"/${TCL_LIB_FILE}' echo "$LDFLAGS " | grep -q -- '-prebind ' && TCL_SHLIB_LD_EXTRAS="${TCL_SHLIB_LD_EXTRAS}"' -seg1addr 0xA000000' TCL_SHLIB_LD_EXTRAS="${TCL_SHLIB_LD_EXTRAS}"' -sectcreate __TEXT __info_plist Tcl-Info.plist' EXTRA_TCLSH_LIBS='-sectcreate __TEXT __info_plist Tclsh-Info.plist' ac_config_files="$ac_config_files Tcl-Info.plist:../macosx/Tcl-Info.plist.in Tclsh-Info.plist:../macosx/Tclsh-Info.plist.in" TCL_YEAR="`date +%Y`" fi if test "$FRAMEWORK_BUILD" = "1" ; then printf "%s\n" "#define TCL_FRAMEWORK 1" >>confdefs.h # Construct a fake local framework structure to make linking with # '-framework Tcl' and running of tcltest work ac_config_commands="$ac_config_commands Tcl.framework" LD_LIBRARY_PATH_VAR="DYLD_FRAMEWORK_PATH" # default install directory for bundled packages if test "${libdir}" = '${exec_prefix}/lib' -o "`basename ${libdir}`" = 'Frameworks'; then PACKAGE_DIR="/Library/Tcl" else PACKAGE_DIR="$libdir" fi if test "${libdir}" = '${exec_prefix}/lib'; then # override libdir default libdir="/Library/Frameworks" fi TCL_LIB_FILE="Tcl" TCL_LIB_FLAG="-framework Tcl" TCL_BUILD_LIB_SPEC="-F`pwd | sed -e 's/ /\\\\ /g'` -framework Tcl" TCL_LIB_SPEC="-F${libdir} -framework Tcl" libdir="${libdir}/Tcl.framework/Versions/\${VERSION}" TCL_LIBRARY="${libdir}/Resources/Scripts" includedir="${libdir}/Headers" PRIVATE_INCLUDE_DIR="${libdir}/PrivateHeaders" HTML_DIR="${libdir}/Resources/Documentation/Reference/Tcl" EXTRA_INSTALL="install-private-headers html-tcl" EXTRA_BUILD_HTML='@ln -fs contents.htm "$(HTML_INSTALL_DIR)/TclTOC.html"' EXTRA_INSTALL_BINARIES='@echo "Installing Info.plist to $(LIB_INSTALL_DIR)/Resources/" && $(INSTALL_DATA_DIR) "$(LIB_INSTALL_DIR)/Resources" && $(INSTALL_DATA) Tcl-Info.plist "$(LIB_INSTALL_DIR)/Resources/Info.plist"' EXTRA_INSTALL_BINARIES="$EXTRA_INSTALL_BINARIES"' && echo "Installing license.terms to $(LIB_INSTALL_DIR)/Resources/" && $(INSTALL_DATA) "$(TOP_DIR)/license.terms" "$(LIB_INSTALL_DIR)/Resources"' EXTRA_INSTALL_BINARIES="$EXTRA_INSTALL_BINARIES"' && echo "Finalizing Tcl.framework" && rm -f "$(LIB_INSTALL_DIR)/../Current" && ln -s "$(VERSION)" "$(LIB_INSTALL_DIR)/../Current" && for f in "$(LIB_FILE)" tclConfig.sh Resources Headers PrivateHeaders; do rm -f "$(LIB_INSTALL_DIR)/../../$$f" && ln -s "Versions/Current/$$f" "$(LIB_INSTALL_DIR)/../.."; done && f="$(STUB_LIB_FILE)" && rm -f "$(LIB_INSTALL_DIR)/../../$$f" && ln -s "Versions/$(VERSION)/$$f" "$(LIB_INSTALL_DIR)/../.."' # Don't use AC_DEFINE for the following as the framework version define # needs to go into the Makefile even when using autoheader, so that we # can pick up a potential make override of VERSION. Also, don't put this # into CFLAGS as it should not go into tclConfig.sh EXTRA_CC_SWITCHES='-DTCL_FRAMEWORK_VERSION=\"$(VERSION)\"' else # libdir must be a fully qualified path and not ${exec_prefix}/lib eval libdir="$libdir" # default install directory for bundled packages PACKAGE_DIR="$libdir" if test "${TCL_LIB_VERSIONS_OK}" = "ok"; then TCL_LIB_FLAG="-ltcl${TCL_VERSION}" else TCL_LIB_FLAG="-ltcl`echo ${TCL_VERSION} | tr -d .`" fi TCL_BUILD_LIB_SPEC="-L`pwd | sed -e 's/ /\\\\ /g'` ${TCL_LIB_FLAG}" TCL_LIB_SPEC="-L${libdir} ${TCL_LIB_FLAG}" fi VERSION='${VERSION}' eval "CFG_TCL_SHARED_LIB_SUFFIX=${TCL_SHARED_LIB_SUFFIX}" eval "CFG_TCL_UNSHARED_LIB_SUFFIX=${TCL_UNSHARED_LIB_SUFFIX}" VERSION=${TCL_VERSION} #-------------------------------------------------------------------- # Zipfs support - Tip 430 #-------------------------------------------------------------------- # Check whether --enable-zipfs was given. if test ${enable_zipfs+y} then : enableval=$enable_zipfs; tcl_ok=$enableval else case e in #( e) tcl_ok=yes ;; esac fi if test "$tcl_ok" = "yes" -a "x$enable_framework" != "xyes"; then # # Find a native compiler # # Put a plausible default for CC_FOR_BUILD in Makefile. if test -z "$CC_FOR_BUILD"; then if test "x$cross_compiling" = "xno"; then CC_FOR_BUILD='$(CC)' else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for gcc" >&5 printf %s "checking for gcc... " >&6; } if test ${ac_cv_path_cc+y} then : printf %s "(cached) " >&6 else case e in #( e) search_path=`echo ${PATH} | sed -e 's/:/ /g'` for dir in $search_path ; do for j in `ls -r $dir/gcc 2> /dev/null` \ `ls -r $dir/gcc 2> /dev/null` ; do if test x"$ac_cv_path_cc" = x ; then if test -f "$j" ; then ac_cv_path_cc=$j break fi fi done done ;; esac fi fi fi # Also set EXEEXT_FOR_BUILD. if test "x$cross_compiling" = "xno"; then EXEEXT_FOR_BUILD='$(EXEEXT)' OBJEXT_FOR_BUILD='$(OBJEXT)' else OBJEXT_FOR_BUILD='.no' { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build system executable suffix" >&5 printf %s "checking for build system executable suffix... " >&6; } if test ${bfd_cv_build_exeext+y} then : printf %s "(cached) " >&6 else case e in #( e) rm -f conftest* echo 'int main () { return 0; }' > conftest.c bfd_cv_build_exeext= ${CC_FOR_BUILD} -o conftest conftest.c 1>&5 2>&5 for file in conftest.*; do case $file in *.c | *.o | *.obj | *.ilk | *.pdb) ;; *) bfd_cv_build_exeext=`echo $file | sed -e s/conftest//` ;; esac done rm -f conftest* test x"${bfd_cv_build_exeext}" = x && bfd_cv_build_exeext=no ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $bfd_cv_build_exeext" >&5 printf "%s\n" "$bfd_cv_build_exeext" >&6; } EXEEXT_FOR_BUILD="" test x"${bfd_cv_build_exeext}" != xno && EXEEXT_FOR_BUILD=${bfd_cv_build_exeext} fi # # Find a native zip implementation # MACHER_PROG="" ZIP_PROG="" ZIP_PROG_OPTIONS="" ZIP_PROG_VFSSEARCH="" ZIP_INSTALL_OBJS="" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for macher" >&5 printf %s "checking for macher... " >&6; } if test ${ac_cv_path_macher+y} then : printf %s "(cached) " >&6 else case e in #( e) search_path=`echo ${PATH} | sed -e 's/:/ /g'` for dir in $search_path ; do for j in `ls -r $dir/macher 2> /dev/null` \ `ls -r $dir/macher 2> /dev/null` ; do if test x"$ac_cv_path_macher" = x ; then if test -f "$j" ; then ac_cv_path_macher=$j break fi fi done done ;; esac fi if test -f "$ac_cv_path_macher" ; then MACHER_PROG="$ac_cv_path_macher" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MACHER_PROG" >&5 printf "%s\n" "$MACHER_PROG" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Found macher in environment" >&5 printf "%s\n" "Found macher in environment" >&6; } fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for zip" >&5 printf %s "checking for zip... " >&6; } if test ${ac_cv_path_zip+y} then : printf %s "(cached) " >&6 else case e in #( e) search_path=`echo ${PATH} | sed -e 's/:/ /g'` for dir in $search_path ; do for j in `ls -r $dir/zip 2> /dev/null` \ `ls -r $dir/zip 2> /dev/null` ; do if test x"$ac_cv_path_zip" = x ; then if test -f "$j" ; then ac_cv_path_zip=$j break fi fi done done ;; esac fi if test -f "$ac_cv_path_zip" ; then ZIP_PROG="$ac_cv_path_zip" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ZIP_PROG" >&5 printf "%s\n" "$ZIP_PROG" >&6; } ZIP_PROG_OPTIONS="-rq" ZIP_PROG_VFSSEARCH="*" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Found INFO Zip in environment" >&5 printf "%s\n" "Found INFO Zip in environment" >&6; } # Use standard arguments for zip else # It is not an error if an installed version of Zip can't be located. # We can use the locally distributed minizip instead ZIP_PROG="./minizip${EXEEXT_FOR_BUILD}" ZIP_PROG_OPTIONS="-o -r" ZIP_PROG_VFSSEARCH="*" ZIP_INSTALL_OBJS="minizip${EXEEXT_FOR_BUILD}" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: No zip found on PATH. Building minizip" >&5 printf "%s\n" "No zip found on PATH. Building minizip" >&6; } fi ZIPFS_BUILD=1 TCL_ZIP_FILE=libtcl${TCL_MAJOR_VERSION}.${TCL_MINOR_VERSION}${TCL_PATCH_LEVEL}.zip else ZIPFS_BUILD=0 TCL_ZIP_FILE= fi # Do checking message here to not mess up interleaved configure output { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for building with zipfs" >&5 printf %s "checking for building with zipfs... " >&6; } if test "${ZIPFS_BUILD}" = 1; then if test "${SHARED_BUILD}" = 0; then ZIPFS_BUILD=2; printf "%s\n" "#define ZIPFS_BUILD 2" >>confdefs.h else printf "%s\n" "#define ZIPFS_BUILD 1" >>confdefs.h \ fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } INSTALL_LIBRARIES=install-libraries INSTALL_MSGS=install-msgs fi # Point to tcl script library if we are not embedding it. if test "${ZIPFS_BUILD}" = 0; then TCL_BUILDTIME_LIBRARY=${TCL_SRC_DIR}/library fi #-------------------------------------------------------------------- # The statements below define the symbol TCL_PACKAGE_PATH, which # gives a list of directories that may contain packages. The list # consists of one directory for machine-dependent binaries and # another for platform-independent scripts. #-------------------------------------------------------------------- if test "$FRAMEWORK_BUILD" = "1" ; then test -z "$TCL_PACKAGE_PATH" && \ TCL_PACKAGE_PATH="~/Library/Tcl:/Library/Tcl:~/Library/Frameworks:/Library/Frameworks" # Allow tclsh to find Tk when multiple versions are installed. See Tk [1562e10c58]. TCL_PACKAGE_PATH="$TCL_PACKAGE_PATH:/Library/Frameworks/Tk.framework/Versions" test -z "$TCL_MODULE_PATH" && \ TCL_MODULE_PATH="~/Library/Tcl /Library/Tcl" elif test "$prefix/lib" != "$libdir"; then test -z "$TCL_PACKAGE_PATH" && TCL_PACKAGE_PATH="${libdir}:${prefix}/lib" else test -z "$TCL_PACKAGE_PATH" && TCL_PACKAGE_PATH="${prefix}/lib" fi #-------------------------------------------------------------------- # The statements below define various symbols relating to Tcl # stub support. #-------------------------------------------------------------------- # Replace ${VERSION} with contents of ${TCL_VERSION} # double-eval to account for TCL_TRIM_DOTS. # eval "TCL_STUB_LIB_FILE=libtclstub.a" eval "TCL_STUB_LIB_FILE=\"${TCL_STUB_LIB_FILE}\"" eval "TCL_STUB_LIB_DIR=\"${libdir}\"" TCL_STUB_LIB_FLAG="-ltclstub" TCL_BUILD_STUB_LIB_SPEC="-L`pwd | sed -e 's/ /\\\\ /g'` ${TCL_STUB_LIB_FLAG}" TCL_STUB_LIB_SPEC="-L${TCL_STUB_LIB_DIR} ${TCL_STUB_LIB_FLAG}" TCL_BUILD_STUB_LIB_PATH="`pwd`/${TCL_STUB_LIB_FILE}" TCL_STUB_LIB_PATH="${TCL_STUB_LIB_DIR}/${TCL_STUB_LIB_FILE}" # Install time header dir can be set via --includedir eval "TCL_INCLUDE_SPEC=\"-I${includedir}\"" #------------------------------------------------------------------------ # tclConfig.sh refers to this by a different name #------------------------------------------------------------------------ TCL_SHARED_BUILD=${SHARED_BUILD} ac_config_files="$ac_config_files Makefile:../unix/Makefile.in dltest/Makefile:../unix/dltest/Makefile.in tclConfig.sh:../unix/tclConfig.sh.in tcl.pc:../unix/tcl.pc.in" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # 'ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* 'ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # 'set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # 'set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 printf "%s\n" "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' # Transform confdefs.h into DEFS. # Protect against shell expansion while executing Makefile rules. # Protect against Makefile macro expansion. # # If the first sed substitution is executed (which looks for macros that # take arguments), then branch to the quote section. Otherwise, # look for a macro that doesn't take arguments. ac_script=' :mline /\\$/{ N s,\\\n,, b mline } t clear :clear s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g t quote s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g t quote b any :quote s/[][ `~#$^&*(){}\\|;'\''"<>?]/\\&/g s/\$/$$/g H :any ${ g s/^\n// s/\n/ /g p } ' DEFS=`sed -n "$ac_script" confdefs.h` CFLAGS="${CFLAGS} ${CPPFLAGS}"; CPPFLAGS="" : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case e in #( e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else case e in #( e) as_fn_append () { eval $1=\$$1\$2 } ;; esac fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else case e in #( e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } ;; esac fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac # For backward compatibility with old third-party macros, we provide # the shell variables $as_echo and $as_echo_n. New code should use # AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. as_echo='printf %s\n' as_echo_n='printf %s' rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" as_tr_sh="eval sed '$as_sed_sh'" # deprecated exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by tcl $as_me 9.0, which was generated by GNU Autoconf 2.72. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" config_commands="$ac_config_commands" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ '$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE Configuration files: $config_files Configuration commands: $config_commands Report bugs to the package provider." _ACEOF ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ tcl config.status 9.0 configured by $0, generated by GNU Autoconf 2.72, with options \\"\$ac_cs_config\\" Copyright (C) 2023 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) printf "%s\n" "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) printf "%s\n" "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --he | --h | --help | --hel | -h ) printf "%s\n" "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: '$1' Try '$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX printf "%s\n" "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # # INIT-COMMANDS # VERSION=${TCL_VERSION} _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "Tcl-Info.plist") CONFIG_FILES="$CONFIG_FILES Tcl-Info.plist:../macosx/Tcl-Info.plist.in" ;; "Tclsh-Info.plist") CONFIG_FILES="$CONFIG_FILES Tclsh-Info.plist:../macosx/Tclsh-Info.plist.in" ;; "Tcl.framework") CONFIG_COMMANDS="$CONFIG_COMMANDS Tcl.framework" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile:../unix/Makefile.in" ;; "dltest/Makefile") CONFIG_FILES="$CONFIG_FILES dltest/Makefile:../unix/dltest/Makefile.in" ;; "tclConfig.sh") CONFIG_FILES="$CONFIG_FILES tclConfig.sh:../unix/tclConfig.sh.in" ;; "tcl.pc") CONFIG_FILES="$CONFIG_FILES tcl.pc:../unix/tcl.pc.in" ;; *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files test ${CONFIG_COMMANDS+y} || CONFIG_COMMANDS=$config_commands fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to '$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with './config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" eval set X " :F $CONFIG_FILES :C $CONFIG_COMMANDS" shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain ':'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is 'configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 printf "%s\n" "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`printf "%s\n" "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 printf "%s\n" "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when '$srcdir' = '.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&5 printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; :C) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 printf "%s\n" "$as_me: executing $ac_file commands" >&6;} ;; esac case $ac_file$ac_mode in "Tcl.framework":C) n=Tcl && f=$n.framework && v=Versions/$VERSION && rm -rf $f && mkdir -p $f/$v/Resources && ln -s $v/$n $v/Resources $f && ln -s ../../../$n $f/$v && ln -s ../../../../$n-Info.plist $f/$v/Resources/Info.plist && unset n f v ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi tcl9.0.1/unix/ldAix0000755000175000017500000000373514726623136013540 0ustar sergeisergei#!/bin/sh # # ldAix ldCmd ldArg ldArg ... # # This shell script provides a wrapper for ld under AIX in order to # create the .exp file required for linking. Its arguments consist # of the name and arguments that would normally be provided to the # ld command. This script extracts the names of the object files # from the argument list, creates a .exp file describing all of the # symbols exported by those files, and then invokes "ldCmd" to # perform the real link. # Extract from the arguments the names of all of the object files. args=$* ofiles="" for i do x=`echo $i | grep '[^.].o$'` if test "$x" != ""; then ofiles="$ofiles $i" fi done # Extract the name of the object file that we're linking. outputFile=`echo $args | sed -e 's/.*-o \([^ ]*\).*/\1/'` # Create the export file from all of the object files, using nm followed # by sed editing. Here are some tricky aspects of this: # # - Use the -X32_64 switch to nm to handle 32 or 64bit compiles. # - Eliminate lines that end in ":": these are the names of object files # - Eliminate entries with the "U" key letter; these are undefined symbols # - If a line starts with ".", delete the leading ".", since this will just # cause confusion later # - Eliminate everything after the first field in a line, so that we're # left with just the symbol name nmopts="-g -C -h -X32_64" rm -f lib.exp echo "#! $outputFile" >lib.exp /usr/ccs/bin/nm $nmopts $ofiles | sed -e '/:$/d' -e '/ U /d' -e 's/^\.//' -e 's/[ |].*//' | sort | uniq >>lib.exp # If we're linking a .a file, then link all the objects together into a # single file "shr.o" and then put that into the archive. Otherwise link # the object files directly into the .a file. noDotA=`echo $outputFile | sed -e '/\.a$/d'` echo "noDotA=\"$noDotA\"" if test "$noDotA" = "" ; then linkArgs=`echo $args | sed -e 's/-o .*\.a /-o shr.o /'` echo $linkArgs eval $linkArgs echo ar cr $outputFile shr.o ar cr $outputFile shr.o rm -f shr.o else eval $args fi tcl9.0.1/unix/dltest/0000755000175000017500000000000014731057554014041 5ustar sergeisergeitcl9.0.1/unix/dltest/embtest.c0000644000175000017500000000200314726623136015642 0ustar sergeisergei#include "tcl.h" #include MODULE_SCOPE const TclStubs *tclStubsPtr; int main(int argc, char **argv) { const char *version; int exitcode = 0; (void)argc; if (tclStubsPtr != NULL) { printf("ERROR: stub table is already initialized"); exitcode = 1; } tclStubsPtr = NULL; version = Tcl_SetPanicProc(Tcl_ConsolePanic); if (tclStubsPtr == NULL) { printf("ERROR: Tcl_SetPanicProc does not initialize the stub table\n"); exitcode = 1; } tclStubsPtr = NULL; version = Tcl_InitSubsystems(); if (tclStubsPtr == NULL) { printf("ERROR: Tcl_InitSubsystems does not initialize the stub table\n"); exitcode = 1; } tclStubsPtr = NULL; version = Tcl_FindExecutable(argv[0]); if (version != NULL) { printf("Tcl_FindExecutable gives version %s\n", version); } if (tclStubsPtr == NULL) { printf("ERROR: Tcl_FindExecutable does not initialize the stub table\n"); exitcode = 1; } if (!exitcode) { printf("All OK!\n"); } return exitcode; } tcl9.0.1/unix/dltest/pkga.c0000644000175000017500000000617014726623136015132 0ustar sergeisergei/* * pkga.c -- * * This file contains a simple Tcl package "pkga" that is intended for * testing the Tcl dynamic loading facilities. * * Copyright © 1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #undef STATIC_BUILD #include "tcl.h" /* *---------------------------------------------------------------------- * * Pkga_EqObjCmd -- * * This procedure is invoked to process the "pkga_eq" Tcl command. It * expects two arguments and returns 1 if they are the same, 0 if they * are different. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int Pkga_EqObjCmd( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int result; const char *str1, *str2; Tcl_Size len1, len2; (void)dummy; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "string1 string2"); return TCL_ERROR; } str1 = Tcl_GetStringFromObj(objv[1], &len1); str2 = Tcl_GetStringFromObj(objv[2], &len2); len1 = Tcl_NumUtfChars(str1, len1); len2 = Tcl_NumUtfChars(str2, len2); if (len1 == len2) { result = (Tcl_UtfNcmp(str1, str2, (size_t)len1) == 0); } else { result = 0; } Tcl_SetObjResult(interp, Tcl_NewIntObj(result)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkga_QuoteObjCmd -- * * This procedure is invoked to process the "pkga_quote" Tcl command. It * expects one argument, which it returns as result. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int Pkga_QuoteObjCmd( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument strings. */ { (void)dummy; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "value"); return TCL_ERROR; } Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkga_Init -- * * This is a package initialization procedure, which is called by Tcl * when this package is to be added to an interpreter. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ DLLEXPORT int Pkga_Init( Tcl_Interp *interp) /* Interpreter in which the package is to be * made available. */ { int code; if (Tcl_InitStubs(interp, "8.5-", 0) == NULL) { return TCL_ERROR; } code = Tcl_PkgProvide(interp, "pkga", "1.0"); if (code != TCL_OK) { return code; } Tcl_CreateObjCommand(interp, "pkga_eq", Pkga_EqObjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "pkga_quote", Pkga_QuoteObjCmd, NULL, NULL); return TCL_OK; } tcl9.0.1/unix/dltest/pkgb.c0000644000175000017500000001114514726623136015131 0ustar sergeisergei/* * pkgb.c -- * * This file contains a simple Tcl package "pkgb" that is intended for * testing the Tcl dynamic loading facilities. It can be used in both * safe and unsafe interpreters. * * Copyright © 1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #undef STATIC_BUILD #include "tcl.h" #if defined(_WIN32) && defined(_MSC_VER) # define snprintf _snprintf #endif /* *---------------------------------------------------------------------- * * Pkgb_SubObjCmd -- * * This procedure is invoked to process the "pkgb_sub" Tcl command. It * expects two arguments and returns their difference. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int Pkgb_SubObjCmd( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int first, second; (void)dummy; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "num num"); return TCL_ERROR; } if ((Tcl_GetIntFromObj(interp, objv[1], &first) != TCL_OK) || (Tcl_GetIntFromObj(interp, objv[2], &second) != TCL_OK)) { char buf[TCL_INTEGER_SPACE]; snprintf(buf, sizeof(buf), "%d", Tcl_GetErrorLine(interp)); Tcl_AppendResult(interp, " in line: ", buf, (char *)NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewIntObj(first - second)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgb_UnsafeObjCmd -- * * This procedure is invoked to process the "pkgb_unsafe" Tcl command. It * just returns a constant string. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int Pkgb_UnsafeObjCmd( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { (void)dummy; (void)objc; (void)objv; return Tcl_EvalEx(interp, "list unsafe command invoked", TCL_INDEX_NONE, TCL_EVAL_GLOBAL); } static int Pkgb_DemoObjCmd( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_WideInt numChars; int result; (void)dummy; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "arg1 arg2 num"); return TCL_ERROR; } if (Tcl_GetWideIntFromObj(interp, objv[3], &numChars) != TCL_OK) { return TCL_ERROR; } result = Tcl_UtfNcmp(Tcl_GetString(objv[1]), Tcl_GetString(objv[2]), (size_t)numChars); Tcl_SetObjResult(interp, Tcl_NewIntObj(result)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgb_Init -- * * This is a package initialization procedure, which is called by Tcl * when this package is to be added to an interpreter. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ DLLEXPORT int Pkgb_Init( Tcl_Interp *interp) /* Interpreter in which the package is to be * made available. */ { int code; if (Tcl_InitStubs(interp, "8.5-", 0) == NULL) { return TCL_ERROR; } code = Tcl_PkgProvide(interp, "pkgb", "2.3"); if (code != TCL_OK) { return code; } Tcl_CreateObjCommand(interp, "pkgb_sub", Pkgb_SubObjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "pkgb_unsafe", Pkgb_UnsafeObjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "pkgb_demo", Pkgb_DemoObjCmd, NULL, NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgb_SafeInit -- * * This is a package initialization procedure, which is called by Tcl * when this package is to be added to a safe interpreter. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ DLLEXPORT int Pkgb_SafeInit( Tcl_Interp *interp) /* Interpreter in which the package is to be * made available. */ { int code; if (Tcl_InitStubs(interp, "8.5-", 0) == NULL) { return TCL_ERROR; } code = Tcl_PkgProvide(interp, "pkgb", "2.3"); if (code != TCL_OK) { return code; } Tcl_CreateObjCommand(interp, "pkgb_sub", Pkgb_SubObjCmd, NULL, NULL); return TCL_OK; } tcl9.0.1/unix/dltest/pkgc.c0000644000175000017500000000732014726623136015132 0ustar sergeisergei/* * pkgc.c -- * * This file contains a simple Tcl package "pkgc" that is intended for * testing the Tcl dynamic loading facilities. It can be used in both * safe and unsafe interpreters. * * Copyright © 1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #undef STATIC_BUILD #include "tcl.h" /* *---------------------------------------------------------------------- * * Pkgc_SubObjCmd -- * * This procedure is invoked to process the "pkgc_sub" Tcl command. It * expects two arguments and returns their difference. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int Pkgc_SubObjCmd( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int first, second; (void)dummy; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "num num"); return TCL_ERROR; } if ((Tcl_GetIntFromObj(interp, objv[1], &first) != TCL_OK) || (Tcl_GetIntFromObj(interp, objv[2], &second) != TCL_OK)) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewIntObj(first - second)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgc_UnsafeCmd -- * * This procedure is invoked to process the "pkgc_unsafe" Tcl command. It * just returns a constant string. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int Pkgc_UnsafeObjCmd( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { (void)dummy; (void)objc; (void)objv; Tcl_SetObjResult(interp, Tcl_NewStringObj("unsafe command invoked", TCL_INDEX_NONE)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgc_Init -- * * This is a package initialization procedure, which is called by Tcl * when this package is to be added to an interpreter. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ DLLEXPORT int Pkgc_Init( Tcl_Interp *interp) /* Interpreter in which the package is to be * made available. */ { int code; if (Tcl_InitStubs(interp, "8.5-", 0) == NULL) { return TCL_ERROR; } code = Tcl_PkgProvide(interp, "pkgc", "1.7.2"); if (code != TCL_OK) { return code; } Tcl_CreateObjCommand(interp, "pkgc_sub", Pkgc_SubObjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "pkgc_unsafe", Pkgc_UnsafeObjCmd, NULL, NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgc_SafeInit -- * * This is a package initialization procedure, which is called by Tcl * when this package is to be added to a safe interpreter. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ DLLEXPORT int Pkgc_SafeInit( Tcl_Interp *interp) /* Interpreter in which the package is to be * made available. */ { int code; if (Tcl_InitStubs(interp, "8.5-", 0) == NULL) { return TCL_ERROR; } code = Tcl_PkgProvide(interp, "pkgc", "1.7.2"); if (code != TCL_OK) { return code; } Tcl_CreateObjCommand(interp, "pkgc_sub", Pkgc_SubObjCmd, NULL, NULL); return TCL_OK; } tcl9.0.1/unix/dltest/pkgd.c0000644000175000017500000000731414726623136015136 0ustar sergeisergei/* * pkgd.c -- * * This file contains a simple Tcl package "pkgd" that is intended for * testing the Tcl dynamic loading facilities. It can be used in both * safe and unsafe interpreters. * * Copyright © 1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #undef STATIC_BUILD #include "tcl.h" /* *---------------------------------------------------------------------- * * Pkgd_SubObjCmd -- * * This procedure is invoked to process the "pkgd_sub" Tcl command. It * expects two arguments and returns their difference. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int Pkgd_SubObjCmd( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int first, second; (void)dummy; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "num num"); return TCL_ERROR; } if ((Tcl_GetIntFromObj(interp, objv[1], &first) != TCL_OK) || (Tcl_GetIntFromObj(interp, objv[2], &second) != TCL_OK)) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewIntObj(first - second)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgd_UnsafeCmd -- * * This procedure is invoked to process the "pkgd_unsafe" Tcl command. It * just returns a constant string. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int Pkgd_UnsafeObjCmd( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { (void)dummy; (void)objc; (void)objv; Tcl_SetObjResult(interp, Tcl_NewStringObj("unsafe command invoked", TCL_INDEX_NONE)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgd_Init -- * * This is a package initialization procedure, which is called by Tcl * when this package is to be added to an interpreter. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ DLLEXPORT int Pkgd_Init( Tcl_Interp *interp) /* Interpreter in which the package is to be * made available. */ { int code; if (Tcl_InitStubs(interp, "8.5-", 0) == NULL) { return TCL_ERROR; } code = Tcl_PkgProvide(interp, "pkgd", "7.3"); if (code != TCL_OK) { return code; } Tcl_CreateObjCommand(interp, "pkgd_sub", Pkgd_SubObjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "pkgd_unsafe", Pkgd_UnsafeObjCmd, NULL, NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgd_SafeInit -- * * This is a package initialization procedure, which is called by Tcl * when this package is to be added to a safe interpreter. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ DLLEXPORT int Pkgd_SafeInit( Tcl_Interp *interp) /* Interpreter in which the package is to be * made available. */ { int code; if (Tcl_InitStubs(interp, "8.5-", 0) == NULL) { return TCL_ERROR; } code = Tcl_PkgProvide(interp, "pkgd", "7.3"); if (code != TCL_OK) { return code; } Tcl_CreateObjCommand(interp, "pkgd_sub", Pkgd_SubObjCmd, NULL, NULL); return TCL_OK; } tcl9.0.1/unix/dltest/pkge.c0000644000175000017500000000223414726623136015133 0ustar sergeisergei/* * pkge.c -- * * This file contains a simple Tcl package "pkge" that is intended for * testing the Tcl dynamic loading facilities. Its Init procedure returns * an error in order to test how this is handled. * * Copyright © 1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #undef STATIC_BUILD #include "tcl.h" /* *---------------------------------------------------------------------- * * Pkge_Init -- * * This is a package initialization procedure, which is called by Tcl * when this package is to be added to an interpreter. * * Results: * Returns TCL_ERROR and leaves an error message in interp->result. * * Side effects: * None. * *---------------------------------------------------------------------- */ DLLEXPORT int Pkge_Init( Tcl_Interp *interp) /* Interpreter in which the package is to be * made available. */ { static const char script[] = "if 44 {open non_existent}"; if (Tcl_InitStubs(interp, "8.5-", 0) == NULL) { return TCL_ERROR; } return Tcl_EvalEx(interp, script, TCL_INDEX_NONE, 0); } tcl9.0.1/unix/dltest/pkgooa.c0000644000175000017500000001043414726623136015466 0ustar sergeisergei/* * pkgooa.c -- * * This file contains a simple Tcl package "pkgooa" that is intended for * testing the Tcl dynamic loading facilities. * * Copyright © 1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #undef STATIC_BUILD #include "tclOO.h" #include /* *---------------------------------------------------------------------- * * Pkgooa_StubsOKObjCmd -- * * This procedure is invoked to process the "pkgooa_stubsok" Tcl command. * It gives 1 if stubs are used correctly, 0 if stubs are not OK. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int Pkgooa_StubsOKObjCmd( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { (void)dummy; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewIntObj( Tcl_CopyObjectInstance == tclOOStubsPtr->tcl_CopyObjectInstance)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgooa_Init -- * * This is a package initialization procedure, which is called by Tcl * when this package is to be added to an interpreter. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ extern void *tclOOIntStubsPtr; static TclOOStubs stubsCopy = { TCL_STUB_MAGIC, NULL, /* It doesn't really matter what implementation of * Tcl_CopyObjectInstance is put in the "pseudo" * stub table, since the test-case never actually * calls this function. All that matters is that it's * a function with a different memory address than * the real Tcl_CopyObjectInstance function in Tcl. */ (Tcl_Object (*) (Tcl_Interp *, Tcl_Object, const char *, const char *t))(void *)Pkgooa_StubsOKObjCmd, /* More entries could be here, but those are not used * for this test-case. So, being NULL is OK. */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; DLLEXPORT int Pkgooa_Init( Tcl_Interp *interp) /* Interpreter in which the package is to be * made available. */ { int code; /* Any TclOO extension which uses stubs, calls * both Tcl_InitStubs and Tcl_OOInitStubs() and * does not use any Tcl 8.6 features should be * loadable in Tcl 8.5 as well, provided the * TclOO extension (for Tcl 8.5) is installed. * This worked in Tcl 8.6.0, and is expected * to keep working in all future Tcl 8.x releases. */ if (Tcl_InitStubs(interp, "8.5-", 0) == NULL) { return TCL_ERROR; } if (tclStubsPtr == NULL) { Tcl_AppendResult(interp, "Tcl stubs are not initialized, " "did you compile using -DUSE_TCL_STUBS? ", (char *)NULL); return TCL_ERROR; } if (Tcl_OOInitStubs(interp) == NULL) { return TCL_ERROR; } if (tclOOStubsPtr == NULL) { Tcl_AppendResult(interp, "TclOO stubs are not initialized", (char *)NULL); return TCL_ERROR; } if (tclOOIntStubsPtr == NULL) { Tcl_AppendResult(interp, "TclOO internal stubs are not initialized", (char *)NULL); return TCL_ERROR; } /* Test case for Bug [f51efe99a7]. * * Let tclOOStubsPtr point to an alternate stub table * (with only a single function, that's enough for * this test). This way, the function "pkgooa_stubsok" * can check whether the TclOO function calls really * use the stub table, or only pretend to. * * On platforms without backlinking (Windows, Cygwin, * AIX), this code doesn't even compile without using * stubs, but on UNIX ELF systems, the problem is * less visible. */ tclOOStubsPtr = &stubsCopy; code = Tcl_PkgProvide(interp, "pkgooa", "1.0"); if (code != TCL_OK) { return code; } Tcl_CreateObjCommand(interp, "pkgooa_stubsok", Pkgooa_StubsOKObjCmd, NULL, NULL); return TCL_OK; } tcl9.0.1/unix/dltest/pkgt.c0000644000175000017500000000530714726623136015156 0ustar sergeisergei/* * pkgt.c -- * * This file contains a simple Tcl package "pkgt" that is intended for * testing the Tcl dynamic loading facilities. * * Copyright © 1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #undef STATIC_BUILD #include "tcl.h" static int TraceProc2 ( void *clientData, Tcl_Interp *interp, Tcl_Size level, const char *command, Tcl_Command commandInfo, Tcl_Size objc, struct Tcl_Obj *const *objv) { (void)clientData; (void)interp; (void)level; (void)command; (void)commandInfo; (void)objc; (void)objv; return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgt_EqObjCmd2 -- * * This procedure is invoked to process the "pkgt_eq" Tcl command. It * expects two arguments and returns 1 if they are the same, 0 if they * are different. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int Pkgt_EqObjCmd2( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_WideInt result; const char *str1, *str2; Tcl_Size len1, len2; (void)dummy; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "string1 string2"); return TCL_ERROR; } str1 = Tcl_GetStringFromObj(objv[1], &len1); str2 = Tcl_GetStringFromObj(objv[2], &len2); len1 = Tcl_NumUtfChars(str1, len1); len2 = Tcl_NumUtfChars(str2, len2); if (len1 == len2) { result = (Tcl_UtfNcmp(str1, str2, (size_t)len1) == 0); } else { result = 0; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(result)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgt_Init -- * * This is a package initialization procedure, which is called by Tcl * when this package is to be added to an interpreter. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ DLLEXPORT int Pkgt_Init( Tcl_Interp *interp) /* Interpreter in which the package is to be * made available. */ { int code; if (Tcl_InitStubs(interp, "8.7-", 0) == NULL) { return TCL_ERROR; } code = Tcl_PkgProvide(interp, "pkgt", "1.0"); if (code != TCL_OK) { return code; } Tcl_CreateObjCommand2(interp, "pkgt_eq", Pkgt_EqObjCmd2, NULL, NULL); Tcl_CreateObjTrace2(interp, 0, 0, TraceProc2, NULL, NULL); return TCL_OK; } tcl9.0.1/unix/dltest/pkgua.c0000644000175000017500000002104414726623136015314 0ustar sergeisergei/* * pkgua.c -- * * This file contains a simple Tcl package "pkgua" that is intended for * testing the Tcl dynamic unloading facilities. * * Copyright © 1995 Sun Microsystems, Inc. * Copyright © 2004 Georgios Petasis * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tcl.h" /* * In the following hash table we are going to store a struct that holds all * the command tokens created by Tcl_CreateObjCommand in an interpreter, * indexed by the interpreter. In this way, we can find which command tokens * we have registered in a specific interpreter, in order to unload them. We * need to keep the various command tokens we have registered, as they are the * only safe way to unregister our registered commands, even if they have been * renamed. */ typedef struct ThreadSpecificData { int interpTokenMapInitialised; Tcl_HashTable interpTokenMap; } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; #define MAX_REGISTERED_COMMANDS 2 static void CommandDeleted(void *clientData) { Tcl_Command *cmdToken = (Tcl_Command *)clientData; *cmdToken = NULL; } static void PkguaInitTokensHashTable(void) { ThreadSpecificData *tsdPtr = (ThreadSpecificData *)Tcl_GetThreadData((&dataKey), sizeof(ThreadSpecificData)); if (tsdPtr->interpTokenMapInitialised) { return; } Tcl_InitHashTable(&tsdPtr->interpTokenMap, TCL_ONE_WORD_KEYS); tsdPtr->interpTokenMapInitialised = 1; } static void PkguaFreeTokensHashTable(void) { Tcl_HashSearch search; Tcl_HashEntry *entryPtr; ThreadSpecificData *tsdPtr = (ThreadSpecificData *)Tcl_GetThreadData((&dataKey), sizeof(ThreadSpecificData)); for (entryPtr = Tcl_FirstHashEntry(&tsdPtr->interpTokenMap, &search); entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { Tcl_Free((char *) Tcl_GetHashValue(entryPtr)); } tsdPtr->interpTokenMapInitialised = 0; } static Tcl_Command * PkguaInterpToTokens( Tcl_Interp *interp) { int newEntry; Tcl_Command *cmdTokens; ThreadSpecificData *tsdPtr = (ThreadSpecificData *)Tcl_GetThreadData((&dataKey), sizeof(ThreadSpecificData)); Tcl_HashEntry *entryPtr = Tcl_CreateHashEntry(&tsdPtr->interpTokenMap, (char *) interp, &newEntry); if (newEntry) { cmdTokens = (Tcl_Command *) Tcl_Alloc(sizeof(Tcl_Command) * (MAX_REGISTERED_COMMANDS)); for (newEntry=0 ; newEntryinterpTokenMap, interp); if (entryPtr) { Tcl_Free((char *) Tcl_GetHashValue(entryPtr)); Tcl_DeleteHashEntry(entryPtr); } } /* *---------------------------------------------------------------------- * * PkguaEqObjCmd -- * * This procedure is invoked to process the "pkgua_eq" Tcl command. It * expects two arguments and returns 1 if they are the same, 0 if they * are different. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int PkguaEqObjCmd( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int result; const char *str1, *str2; Tcl_Size len1, len2; (void)dummy; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "string1 string2"); return TCL_ERROR; } str1 = Tcl_GetStringFromObj(objv[1], &len1); str2 = Tcl_GetStringFromObj(objv[2], &len2); len1 = Tcl_NumUtfChars(str1, len1); len2 = Tcl_NumUtfChars(str2, len2); if (len1 == len2) { result = (Tcl_UtfNcmp(str1, str2, (size_t)len1) == 0); } else { result = 0; } Tcl_SetObjResult(interp, Tcl_NewIntObj(result)); return TCL_OK; } /* *---------------------------------------------------------------------- * * PkguaQuoteObjCmd -- * * This procedure is invoked to process the "pkgua_quote" Tcl command. It * expects one argument, which it returns as result. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int PkguaQuoteObjCmd( void *dummy, /* Not used. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument strings. */ { (void)dummy; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "value"); return TCL_ERROR; } Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgua_Init -- * * This is a package initialization procedure, which is called by Tcl * when this package is to be added to an interpreter. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ DLLEXPORT int Pkgua_Init( Tcl_Interp *interp) /* Interpreter in which the package is to be * made available. */ { int code; Tcl_Command *cmdTokens; if (Tcl_InitStubs(interp, "8.5-", 0) == NULL) { return TCL_ERROR; } /* * Initialize our Hash table, where we store the registered command tokens * for each interpreter. */ PkguaInitTokensHashTable(); code = Tcl_PkgProvide(interp, "pkgua", "1.0"); if (code != TCL_OK) { return code; } Tcl_SetVar2(interp, "::pkgua_loaded", NULL, ".", TCL_APPEND_VALUE); cmdTokens = PkguaInterpToTokens(interp); cmdTokens[0] = Tcl_CreateObjCommand(interp, "pkgua_eq", PkguaEqObjCmd, &cmdTokens[0], CommandDeleted); cmdTokens[1] = Tcl_CreateObjCommand(interp, "pkgua_quote", PkguaQuoteObjCmd, &cmdTokens[1], CommandDeleted); return TCL_OK; } /* *---------------------------------------------------------------------- * * Pkgua_SafeInit -- * * This is a package initialization procedure, which is called by Tcl * when this package is to be added to a safe interpreter. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ DLLEXPORT int Pkgua_SafeInit( Tcl_Interp *interp) /* Interpreter in which the package is to be * made available. */ { return Pkgua_Init(interp); } /* *---------------------------------------------------------------------- * * Pkgua_Unload -- * * This is a package unloading initialization procedure, which is called * by Tcl when this package is to be unloaded from an interpreter. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ DLLEXPORT int Pkgua_Unload( Tcl_Interp *interp, /* Interpreter from which the package is to be * unloaded. */ int flags) /* Flags passed by the unloading mechanism */ { int code, cmdIndex; Tcl_Command *cmdTokens = PkguaInterpToTokens(interp); for (cmdIndex=0 ; cmdIndexv) #define CERR(e) VERR(cm->v, (e)) /* - initcm - set up new colormap ^ static void initcm(struct vars *, struct colormap *); */ static void initcm( struct vars *v, struct colormap *cm) { int i; int j; union tree *t; union tree *nextt; struct colordesc *cd; cm->magic = CMMAGIC; cm->v = v; cm->ncds = NINLINECDS; cm->cd = cm->cdspace; cm->max = 0; cm->free = 0; cd = cm->cd; /* cm->cd[WHITE] */ cd->sub = NOSUB; cd->arcs = NULL; cd->flags = 0; cd->nchrs = CHR_MAX - CHR_MIN + 1; /* * Upper levels of tree. */ for (t=&cm->tree[0], j=NBYTS-1 ; j>0 ; t=nextt, j--) { nextt = t + 1; for (i=BYTTAB-1 ; i>=0 ; i--) { t->tptr[i] = nextt; } } /* * Bottom level is solid white. */ t = &cm->tree[NBYTS-1]; for (i=BYTTAB-1 ; i>=0 ; i--) { t->tcolor[i] = WHITE; } cd->block = t; } /* - freecm - free dynamically-allocated things in a colormap ^ static void freecm(struct colormap *); */ static void freecm( struct colormap *cm) { size_t i; union tree *cb; cm->magic = 0; if (NBYTS > 1) { cmtreefree(cm, cm->tree, 0); } for (i=1 ; i<=cm->max ; i++) { /* skip WHITE */ if (!UNUSEDCOLOR(&cm->cd[i])) { cb = cm->cd[i].block; if (cb != NULL) { FREE(cb); } } } if (cm->cd != cm->cdspace) { FREE(cm->cd); } } /* - cmtreefree - free a non-terminal part of a colormap tree ^ static void cmtreefree(struct colormap *, union tree *, int); */ static void cmtreefree( struct colormap *cm, union tree *tree, int level) /* level number (top == 0) of this block */ { int i; union tree *t; union tree *fillt = &cm->tree[level+1]; union tree *cb; assert(level < NBYTS-1); /* this level has pointers */ for (i=BYTTAB-1 ; i>=0 ; i--) { t = tree->tptr[i]; assert(t != NULL); if (t != fillt) { if (level < NBYTS-2) { /* more pointer blocks below */ cmtreefree(cm, t, level+1); FREE(t); } else { /* color block below */ cb = cm->cd[t->tcolor[0]].block; if (t != cb) { /* not a solid block */ FREE(t); } } } } } /* - setcolor - set the color of a character in a colormap ^ static color setcolor(struct colormap *, pchr, pcolor); */ static color /* previous color */ setcolor( struct colormap *cm, pchr c, pcolor co) { uchr uc = c; int shift; int level; int b; int bottom; union tree *t; union tree *newt; union tree *fillt; union tree *lastt; union tree *cb; color prev; assert(cm->magic == CMMAGIC); if (CISERR() || co == COLORLESS) { return COLORLESS; } t = cm->tree; for (level=0, shift=BYTBITS*(NBYTS-1) ; shift>0; level++, shift-=BYTBITS){ b = (uc >> shift) & BYTMASK; lastt = t; t = lastt->tptr[b]; assert(t != NULL); fillt = &cm->tree[level+1]; bottom = (shift <= BYTBITS) ? 1 : 0; cb = (bottom) ? cm->cd[t->tcolor[0]].block : fillt; if (t == fillt || t == cb) { /* must allocate a new block */ newt = (union tree *) MALLOC((bottom) ? sizeof(struct colors) : sizeof(struct ptrs)); if (newt == NULL) { CERR(REG_ESPACE); return COLORLESS; } if (bottom) { memcpy(newt->tcolor, t->tcolor, BYTTAB*sizeof(color)); } else { memcpy(newt->tptr, t->tptr, BYTTAB*sizeof(union tree *)); } t = newt; lastt->tptr[b] = t; } } b = uc & BYTMASK; prev = t->tcolor[b]; t->tcolor[b] = (color) co; return prev; } /* - maxcolor - report largest color number in use ^ static color maxcolor(struct colormap *); */ static color maxcolor( struct colormap *cm) { if (CISERR()) { return COLORLESS; } return (color) cm->max; } /* - newcolor - find a new color (must be subject of setcolor at once) * Beware: may relocate the colordescs. ^ static color newcolor(struct colormap *); */ static color /* COLORLESS for error */ newcolor( struct colormap *cm) { struct colordesc *cd; size_t n; if (CISERR()) { return COLORLESS; } if (cm->free != 0) { assert(cm->free > 0); assert((size_t) cm->free < cm->ncds); cd = &cm->cd[cm->free]; assert(UNUSEDCOLOR(cd)); assert(cd->arcs == NULL); cm->free = cd->sub; } else if (cm->max < cm->ncds - 1) { cm->max++; cd = &cm->cd[cm->max]; } else { struct colordesc *newCd; /* * Oops, must allocate more. */ if (cm->max == MAX_COLOR) { CERR(REG_ECOLORS); return COLORLESS; /* too many colors */ } n = cm->ncds * 2; if (n > MAX_COLOR + 1) { n = MAX_COLOR + 1; } if (cm->cd == cm->cdspace) { newCd = (struct colordesc *) MALLOC(n * sizeof(struct colordesc)); if (newCd != NULL) { memcpy(newCd, cm->cdspace, cm->ncds * sizeof(struct colordesc)); } } else { newCd = (struct colordesc *) REALLOC(cm->cd, n * sizeof(struct colordesc)); } if (newCd == NULL) { CERR(REG_ESPACE); return COLORLESS; } cm->cd = newCd; cm->ncds = n; assert(cm->max < cm->ncds - 1); cm->max++; cd = &cm->cd[cm->max]; } cd->nchrs = 0; cd->sub = NOSUB; cd->arcs = NULL; cd->flags = 0; cd->block = NULL; return (color) (cd - cm->cd); } /* - freecolor - free a color (must have no arcs or subcolor) ^ static void freecolor(struct colormap *, pcolor); */ static void freecolor( struct colormap *cm, pcolor co) { struct colordesc *cd = &cm->cd[co]; color pco, nco; /* for freelist scan */ assert(co >= 0); if (co == WHITE) { return; } assert(cd->arcs == NULL); assert(cd->sub == NOSUB); assert(cd->nchrs == 0); cd->flags = FREECOL; if (cd->block != NULL) { FREE(cd->block); cd->block = NULL; /* just paranoia */ } if ((size_t) co == cm->max) { while (cm->max > WHITE && UNUSEDCOLOR(&cm->cd[cm->max])) { cm->max--; } assert(cm->free >= 0); while ((size_t) cm->free > cm->max) { cm->free = cm->cd[cm->free].sub; } if (cm->free > 0) { assert((size_t)cm->free < cm->max); pco = cm->free; nco = cm->cd[pco].sub; while (nco > 0) { if ((size_t) nco > cm->max) { /* * Take this one out of freelist. */ nco = cm->cd[nco].sub; cm->cd[pco].sub = nco; } else { assert((size_t)nco < cm->max); pco = nco; nco = cm->cd[pco].sub; } } } } else { cd->sub = cm->free; cm->free = (color) (cd - cm->cd); } } /* - pseudocolor - allocate a false color, to be managed by other means ^ static color pseudocolor(struct colormap *); */ static color pseudocolor( struct colormap *cm) { color co; co = newcolor(cm); if (CISERR()) { return COLORLESS; } cm->cd[co].nchrs = 1; cm->cd[co].flags = PSEUDO; return co; } /* - subcolor - allocate a new subcolor (if necessary) to this chr ^ static color subcolor(struct colormap *, pchr c); */ static color subcolor( struct colormap *cm, pchr c) { color co; /* current color of c */ color sco; /* new subcolor */ co = GETCOLOR(cm, c); sco = newsub(cm, co); if (CISERR()) { return COLORLESS; } assert(sco != COLORLESS); if (co == sco) { /* already in an open subcolor */ return co; /* rest is redundant */ } cm->cd[co].nchrs--; cm->cd[sco].nchrs++; setcolor(cm, c, sco); return sco; } /* - newsub - allocate a new subcolor (if necessary) for a color ^ static color newsub(struct colormap *, pcolor); */ static color newsub( struct colormap *cm, pcolor co) { color sco; /* new subcolor */ sco = cm->cd[co].sub; if (sco == NOSUB) { /* color has no open subcolor */ if (cm->cd[co].nchrs == 1) { /* optimization */ return co; } sco = newcolor(cm); /* must create subcolor */ if (sco == COLORLESS) { assert(CISERR()); return COLORLESS; } cm->cd[co].sub = sco; cm->cd[sco].sub = sco; /* open subcolor points to self */ } assert(sco != NOSUB); return sco; } /* - subrange - allocate new subcolors to this range of chrs, fill in arcs ^ static void subrange(struct vars *, pchr, pchr, struct state *, ^ struct state *); */ static void subrange( struct vars *v, pchr from, pchr to, struct state *lp, struct state *rp) { uchr uf; int i; assert(from <= to); /* * First, align "from" on a tree-block boundary */ uf = (uchr) from; i = (int) (((uf + BYTTAB - 1) & (uchr) ~BYTMASK) - uf); for (; from<=to && i>0; i--, from++) { newarc(v->nfa, PLAIN, subcolor(v->cm, from), lp, rp); } if (from > to) { /* didn't reach a boundary */ return; } /* * Deal with whole blocks. */ for (; to-from>=BYTTAB ; from+=BYTTAB) { subblock(v, from, lp, rp); } /* * Clean up any remaining partial table. */ for (; from<=to ; from++) { newarc(v->nfa, PLAIN, subcolor(v->cm, from), lp, rp); } } /* - subblock - allocate new subcolors for one tree block of chrs, fill in arcs ^ static void subblock(struct vars *, pchr, struct state *, struct state *); */ static void subblock( struct vars *v, pchr start, /* first of BYTTAB chrs */ struct state *lp, struct state *rp) { uchr uc = start; struct colormap *cm = v->cm; int shift; int level; int i; int b; union tree *t; union tree *cb; union tree *fillt; union tree *lastt; int previ; int ndone; color co; color sco; assert((uc % BYTTAB) == 0); /* * Find its color block, making new pointer blocks as needed. */ t = cm->tree; fillt = NULL; for (level=0, shift=BYTBITS*(NBYTS-1); shift>0; level++, shift-=BYTBITS) { b = (uc >> shift) & BYTMASK; lastt = t; t = lastt->tptr[b]; assert(t != NULL); fillt = &cm->tree[level+1]; if (t == fillt && shift > BYTBITS) { /* need new ptr block */ t = (union tree *) MALLOC(sizeof(struct ptrs)); if (t == NULL) { CERR(REG_ESPACE); return; } memcpy(t->tptr, fillt->tptr, BYTTAB*sizeof(union tree *)); lastt->tptr[b] = t; } } /* * Special cases: fill block or solid block. */ co = t->tcolor[0]; cb = cm->cd[co].block; if (t == fillt || t == cb) { /* * Either way, we want a subcolor solid block. */ sco = newsub(cm, co); t = cm->cd[sco].block; if (t == NULL) { /* must set it up */ t = (union tree *) MALLOC(sizeof(struct colors)); if (t == NULL) { CERR(REG_ESPACE); return; } for (i=0 ; itcolor[i] = sco; } cm->cd[sco].block = t; } /* * Find loop must have run at least once. */ lastt->tptr[b] = t; newarc(v->nfa, PLAIN, sco, lp, rp); cm->cd[co].nchrs -= BYTTAB; cm->cd[sco].nchrs += BYTTAB; return; } /* * General case, a mixed block to be altered. */ i = 0; while (i < BYTTAB) { co = t->tcolor[i]; sco = newsub(cm, co); newarc(v->nfa, PLAIN, sco, lp, rp); previ = i; do { t->tcolor[i++] = sco; } while (i < BYTTAB && t->tcolor[i] == co); ndone = i - previ; cm->cd[co].nchrs -= ndone; cm->cd[sco].nchrs += ndone; } } /* - okcolors - promote subcolors to full colors ^ static void okcolors(struct nfa *, struct colormap *); */ static void okcolors( struct nfa *nfa, struct colormap *cm) { struct colordesc *cd; struct colordesc *end = CDEND(cm); struct colordesc *scd; struct arc *a; color co; color sco; for (cd=cm->cd, co=0 ; cdsub; if (UNUSEDCOLOR(cd) || sco == NOSUB) { /* * Has no subcolor, no further action. */ } else if (sco == co) { /* * Is subcolor, let parent deal with it. */ } else if (cd->nchrs == 0) { /* * Parent empty, its arcs change color to subcolor. */ cd->sub = NOSUB; scd = &cm->cd[sco]; assert(scd->nchrs > 0); assert(scd->sub == sco); scd->sub = NOSUB; while ((a = cd->arcs) != NULL) { assert(a->co == co); uncolorchain(cm, a); a->co = sco; colorchain(cm, a); } freecolor(cm, co); } else { /* * Parent's arcs must gain parallel subcolor arcs. */ cd->sub = NOSUB; scd = &cm->cd[sco]; assert(scd->nchrs > 0); assert(scd->sub == sco); scd->sub = NOSUB; for (a=cd->arcs ; a!=NULL ; a=a->colorchain) { assert(a->co == co); newarc(nfa, a->type, sco, a->from, a->to); } } } } /* - colorchain - add this arc to the color chain of its color ^ static void colorchain(struct colormap *, struct arc *); */ static void colorchain( struct colormap *cm, struct arc *a) { struct colordesc *cd = &cm->cd[a->co]; if (cd->arcs != NULL) { cd->arcs->colorchainRev = a; } a->colorchain = cd->arcs; a->colorchainRev = NULL; cd->arcs = a; } /* - uncolorchain - delete this arc from the color chain of its color ^ static void uncolorchain(struct colormap *, struct arc *); */ static void uncolorchain( struct colormap *cm, struct arc *a) { struct colordesc *cd = &cm->cd[a->co]; struct arc *aa = a->colorchainRev; if (aa == NULL) { assert(cd->arcs == a); cd->arcs = a->colorchain; } else { assert(aa->colorchain == a); aa->colorchain = a->colorchain; } if (a->colorchain != NULL) { a->colorchain->colorchainRev = aa; } a->colorchain = NULL; /* paranoia */ a->colorchainRev = NULL; } /* - rainbow - add arcs of all full colors (but one) between specified states ^ static void rainbow(struct nfa *, struct colormap *, int, pcolor, ^ struct state *, struct state *); */ static void rainbow( struct nfa *nfa, struct colormap *cm, int type, pcolor but, /* COLORLESS if no exceptions */ struct state *from, struct state *to) { struct colordesc *cd; struct colordesc *end = CDEND(cm); color co; for (cd=cm->cd, co=0 ; cdsub != co) && (co != but) && !(cd->flags&PSEUDO)) { newarc(nfa, type, co, from, to); } } } /* - colorcomplement - add arcs of complementary colors * The calling sequence ought to be reconciled with cloneouts(). ^ static void colorcomplement(struct nfa *, struct colormap *, int, ^ struct state *, struct state *, struct state *); */ static void colorcomplement( struct nfa *nfa, struct colormap *cm, int type, struct state *of, /* complements of this guy's PLAIN outarcs */ struct state *from, struct state *to) { struct colordesc *cd; struct colordesc *end = CDEND(cm); color co; assert(of != from); for (cd=cm->cd, co=0 ; cdflags&PSEUDO)) { if (findarc(of, PLAIN, co) == NULL) { newarc(nfa, type, co, from, to); } } } } #ifdef REG_DEBUG /* ^ #ifdef REG_DEBUG */ /* - dumpcolors - debugging output ^ static void dumpcolors(struct colormap *, FILE *); */ static void dumpcolors( struct colormap *cm, FILE *f) { struct colordesc *cd; struct colordesc *end; color co; chr c; const char *has; fprintf(f, "max %" TCL_Z_MODIFIER "u\n", cm->max); if (NBYTS > 1) { fillcheck(cm, cm->tree, 0, f); } end = CDEND(cm); for (cd=cm->cd+1, co=1 ; cdnchrs > 0); has = (cd->block != NULL) ? "#" : ""; if (cd->flags&PSEUDO) { fprintf(f, "#%2ld%s(ps): ", (long) co, has); } else { fprintf(f, "#%2ld%s(%2d): ", (long) co, has, cd->nchrs); } /* * Unfortunately, it's hard to do this next bit more efficiently. * * Spencer's original coding has the loop iterating from CHR_MIN * to CHR_MAX, but that's utterly unusable for 32-bit chr, or * even 16-bit. For debugging purposes it seems fine to print * only chr codes up to 1000 or so. */ for (c=CHR_MIN ; c<1000 ; c++) { if (GETCOLOR(cm, c) == co) { dumpchr(c, f); } } fprintf(f, "\n"); } } } /* - fillcheck - check proper filling of a tree ^ static void fillcheck(struct colormap *, union tree *, int, FILE *); */ static void fillcheck( struct colormap *cm, union tree *tree, int level, /* level number (top == 0) of this block */ FILE *f) { int i; union tree *t; union tree *fillt = &cm->tree[level+1]; assert(level < NBYTS-1); /* this level has pointers */ for (i=BYTTAB-1 ; i>=0 ; i--) { t = tree->tptr[i]; if (t == NULL) { fprintf(f, "NULL found in filled tree!\n"); } else if (t == fillt) { /* empty body */ } else if (level < NBYTS-2) { /* more pointer blocks below */ fillcheck(cm, t, level+1, f); } } } /* - dumpchr - print a chr * Kind of char-centric but works well enough for debug use. ^ static void dumpchr(pchr, FILE *); */ static void dumpchr( pchr c, FILE *f) { if (c == '\\') { fprintf(f, "\\\\"); } else if (c > ' ' && c <= '~') { putc((char) c, f); } else { fprintf(f, "\\u%04lx", (long) c); } } /* ^ #endif */ #endif /* ifdef REG_DEBUG */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/regc_cvec.c0000644000175000017500000000765414726623136015272 0ustar sergeisergei/* * Utility functions for handling cvecs * This file is #included by regcomp.c. * * Copyright © 1998, 1999 Henry Spencer. All rights reserved. * * Development of this software was funded, in part, by Cray Research Inc., * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics * Corporation, none of whom are responsible for the results. The author * thanks all of them. * * Redistribution and use in source and binary forms -- with or without * modification -- are permitted for any purpose, provided that * redistributions in source form retain this entire copyright notice and * indicate the origin and nature of any modifications. * * I'd appreciate being given credit for this package in the documentation of * software which uses it, but that is not a requirement. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Notes: * Only (selected) functions in _this_ file should treat chr* as non-constant. */ /* - newcvec - allocate a new cvec ^ static struct cvec *newcvec(size_t, size_t); */ static struct cvec * newcvec( size_t nchrs, /* to hold this many chrs... */ size_t nranges) /* ... and this many ranges... */ { size_t nc = nchrs + nranges*2; size_t n = sizeof(struct cvec) + nc*sizeof(chr); struct cvec *cv = (struct cvec *) MALLOC(n); if (cv == NULL) { return NULL; } cv->chrspace = nchrs; cv->chrs = (chr *)(((char *)cv)+sizeof(struct cvec)); cv->ranges = cv->chrs + nchrs; cv->rangespace = nranges; return clearcvec(cv); } /* - clearcvec - clear a possibly-new cvec * Returns pointer as convenience. ^ static struct cvec *clearcvec(struct cvec *); */ static struct cvec * clearcvec( struct cvec *cv) /* character vector */ { assert(cv != NULL); cv->nchrs = 0; cv->nranges = 0; return cv; } /* - addchr - add a chr to a cvec ^ static void addchr(struct cvec *, pchr); */ static void addchr( struct cvec *cv, /* character vector */ pchr c) /* character to add */ { assert(cv->nchrs < cv->chrspace); cv->chrs[cv->nchrs++] = (chr)c; } /* - addrange - add a range to a cvec ^ static void addrange(struct cvec *, pchr, pchr); */ static void addrange( struct cvec *cv, /* character vector */ pchr from, /* first character of range */ pchr to) /* last character of range */ { assert(cv->nranges < cv->rangespace); cv->ranges[cv->nranges*2] = (chr)from; cv->ranges[cv->nranges*2 + 1] = (chr)to; cv->nranges++; } /* - getcvec - get a cvec, remembering it as v->cv ^ static struct cvec *getcvec(struct vars *, int, int); */ static struct cvec * getcvec( struct vars *v, /* context */ size_t nchrs, /* to hold this many chrs... */ size_t nranges) /* ... and this many ranges... */ { if ((v->cv != NULL) && (nchrs <= v->cv->chrspace) && (nranges <= v->cv->rangespace)) { return clearcvec(v->cv); } if (v->cv != NULL) { freecvec(v->cv); } v->cv = newcvec(nchrs, nranges); if (v->cv == NULL) { ERR(REG_ESPACE); } return v->cv; } /* - freecvec - free a cvec ^ static void freecvec(struct cvec *); */ static void freecvec( struct cvec *cv) /* character vector */ { FREE(cv); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/regc_lex.c0000644000175000017500000006240214726623136015132 0ustar sergeisergei/* * lexical analyzer * This file is #included by regcomp.c. * * Copyright © 1998, 1999 Henry Spencer. All rights reserved. * * Development of this software was funded, in part, by Cray Research Inc., * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics * Corporation, none of whom are responsible for the results. The author * thanks all of them. * * Redistribution and use in source and binary forms -- with or without * modification -- are permitted for any purpose, provided that * redistributions in source form retain this entire copyright notice and * indicate the origin and nature of any modifications. * * I'd appreciate being given credit for this package in the documentation of * software which uses it, but that is not a requirement. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* scanning macros (know about v) */ #define ATEOS() (v->now >= v->stop) #define HAVE(n) (v->stop - v->now >= (n)) #define NEXT1(c) (!ATEOS() && *v->now == CHR(c)) #define NEXT2(a,b) (HAVE(2) && *v->now == CHR(a) && *(v->now+1) == CHR(b)) #define NEXT3(a,b,c) \ (HAVE(3) && *v->now == CHR(a) && \ *(v->now+1) == CHR(b) && \ *(v->now+2) == CHR(c)) #define SET(c) (v->nexttype = (c)) #define SETV(c, n) (v->nexttype = (c), v->nextvalue = (n)) #define RET(c) return (SET(c), 1) #define RETV(c, n) return (SETV(c, n), 1) #define FAILW(e) return (ERR(e), 0) /* ERR does SET(EOS) */ #define LASTTYPE(t) (v->lasttype == (t)) /* lexical contexts */ #define L_ERE 1 /* mainline ERE/ARE */ #define L_BRE 2 /* mainline BRE */ #define L_Q 3 /* REG_QUOTE */ #define L_EBND 4 /* ERE/ARE bound */ #define L_BBND 5 /* BRE bound */ #define L_BRACK 6 /* brackets */ #define L_CEL 7 /* collating element */ #define L_ECL 8 /* equivalence class */ #define L_CCL 9 /* character class */ #define INTOCON(c) (v->lexcon = (c)) #define INCON(con) (v->lexcon == (con)) /* construct pointer past end of chr array */ #define ENDOF(array) ((array) + sizeof(array)/sizeof(chr)) /* - lexstart - set up lexical stuff, scan leading options ^ static void lexstart(struct vars *); */ static void lexstart( struct vars *v) { prefixes(v); /* may turn on new type bits etc. */ NOERR(); if (v->cflags®_QUOTE) { assert(!(v->cflags&(REG_ADVANCED|REG_EXPANDED|REG_NEWLINE))); INTOCON(L_Q); } else if (v->cflags®_EXTENDED) { assert(!(v->cflags®_QUOTE)); INTOCON(L_ERE); } else { assert(!(v->cflags&(REG_QUOTE|REG_ADVF))); INTOCON(L_BRE); } v->nexttype = EMPTY; /* remember we were at the start */ next(v); /* set up the first token */ } /* - prefixes - implement various special prefixes ^ static void prefixes(struct vars *); */ static void prefixes( struct vars *v) { /* * Literal string doesn't get any of this stuff. */ if (v->cflags®_QUOTE) { return; } /* * Initial "***" gets special things. */ if (HAVE(4) && NEXT3('*', '*', '*')) { switch (*(v->now + 3)) { case CHR('?'): /* "***?" error, msg shows version */ ERR(REG_BADPAT); return; /* proceed no further */ break; case CHR('='): /* "***=" shifts to literal string */ NOTE(REG_UNONPOSIX); v->cflags |= REG_QUOTE; v->cflags &= ~(REG_ADVANCED|REG_EXPANDED|REG_NEWLINE); v->now += 4; return; /* and there can be no more prefixes */ break; case CHR(':'): /* "***:" shifts to AREs */ NOTE(REG_UNONPOSIX); v->cflags |= REG_ADVANCED; v->now += 4; break; default: /* otherwise *** is just an error */ ERR(REG_BADRPT); return; break; } } /* * BREs and EREs don't get embedded options. */ if ((v->cflags®_ADVANCED) != REG_ADVANCED) { return; } /* * Embedded options (AREs only). */ if (HAVE(3) && NEXT2('(', '?') && iscalpha(*(v->now + 2))) { NOTE(REG_UNONPOSIX); v->now += 2; for (; !ATEOS() && iscalpha(*v->now); v->now++) { switch (*v->now) { case CHR('b'): /* BREs (but why???) */ v->cflags &= ~(REG_ADVANCED|REG_QUOTE); break; case CHR('c'): /* case sensitive */ v->cflags &= ~REG_ICASE; break; case CHR('e'): /* plain EREs */ v->cflags |= REG_EXTENDED; v->cflags &= ~(REG_ADVF|REG_QUOTE); break; case CHR('i'): /* case insensitive */ v->cflags |= REG_ICASE; break; case CHR('m'): /* Perloid synonym for n */ case CHR('n'): /* \n affects ^ $ . [^ */ v->cflags |= REG_NEWLINE; break; case CHR('p'): /* ~Perl, \n affects . [^ */ v->cflags |= REG_NLSTOP; v->cflags &= ~REG_NLANCH; break; case CHR('q'): /* literal string */ v->cflags |= REG_QUOTE; v->cflags &= ~REG_ADVANCED; break; case CHR('s'): /* single line, \n ordinary */ v->cflags &= ~REG_NEWLINE; break; case CHR('t'): /* tight syntax */ v->cflags &= ~REG_EXPANDED; break; case CHR('w'): /* weird, \n affects ^ $ only */ v->cflags &= ~REG_NLSTOP; v->cflags |= REG_NLANCH; break; case CHR('x'): /* expanded syntax */ v->cflags |= REG_EXPANDED; break; default: ERR(REG_BADOPT); return; } } if (!NEXT1(')')) { ERR(REG_BADOPT); return; } v->now++; if (v->cflags®_QUOTE) { v->cflags &= ~(REG_EXPANDED|REG_NEWLINE); } } } /* - lexnest - "call a subroutine", interpolating string at the lexical level * Note, this is not a very general facility. There are a number of * implicit assumptions about what sorts of strings can be subroutines. ^ static void lexnest(struct vars *, const chr *, const chr *); */ static void lexnest( struct vars *v, const chr *beginp, /* start of interpolation */ const chr *endp) /* one past end of interpolation */ { assert(v->savenow == NULL); /* only one level of nesting */ v->savenow = v->now; v->savestop = v->stop; v->now = beginp; v->stop = endp; } /* * string constants to interpolate as expansions of things like \d */ static const chr backd[] = { /* \d */ CHR('['), CHR('['), CHR(':'), CHR('d'), CHR('i'), CHR('g'), CHR('i'), CHR('t'), CHR(':'), CHR(']'), CHR(']') }; static const chr backD[] = { /* \D */ CHR('['), CHR('^'), CHR('['), CHR(':'), CHR('d'), CHR('i'), CHR('g'), CHR('i'), CHR('t'), CHR(':'), CHR(']'), CHR(']') }; static const chr brbackd[] = { /* \d within brackets */ CHR('['), CHR(':'), CHR('d'), CHR('i'), CHR('g'), CHR('i'), CHR('t'), CHR(':'), CHR(']') }; static const chr backs[] = { /* \s */ CHR('['), CHR('['), CHR(':'), CHR('s'), CHR('p'), CHR('a'), CHR('c'), CHR('e'), CHR(':'), CHR(']'), CHR(']') }; static const chr backS[] = { /* \S */ CHR('['), CHR('^'), CHR('['), CHR(':'), CHR('s'), CHR('p'), CHR('a'), CHR('c'), CHR('e'), CHR(':'), CHR(']'), CHR(']') }; static const chr brbacks[] = { /* \s within brackets */ CHR('['), CHR(':'), CHR('s'), CHR('p'), CHR('a'), CHR('c'), CHR('e'), CHR(':'), CHR(']') }; #define PUNCT_CONN \ CHR('_'), \ 0x203F /* UNDERTIE */, \ 0x2040 /* CHARACTER TIE */,\ 0x2054 /* INVERTED UNDERTIE */,\ 0xFE33 /* PRESENTATION FORM FOR VERTICAL LOW LINE */, \ 0xFE34 /* PRESENTATION FORM FOR VERTICAL WAVY LOW LINE */, \ 0xFE4D /* DASHED LOW LINE */, \ 0xFE4E /* CENTRELINE LOW LINE */, \ 0xFE4F /* WAVY LOW LINE */, \ 0xFF3F /* FULLWIDTH LOW LINE */ static const chr backw[] = { /* \w */ CHR('['), CHR('['), CHR(':'), CHR('a'), CHR('l'), CHR('n'), CHR('u'), CHR('m'), CHR(':'), CHR(']'), PUNCT_CONN, CHR(']') }; static const chr backW[] = { /* \W */ CHR('['), CHR('^'), CHR('['), CHR(':'), CHR('a'), CHR('l'), CHR('n'), CHR('u'), CHR('m'), CHR(':'), CHR(']'), PUNCT_CONN, CHR(']') }; static const chr brbackw[] = { /* \w within brackets */ CHR('['), CHR(':'), CHR('a'), CHR('l'), CHR('n'), CHR('u'), CHR('m'), CHR(':'), CHR(']'), PUNCT_CONN }; /* - lexword - interpolate a bracket expression for word characters * Possibly ought to inquire whether there is a "word" character class. ^ static void lexword(struct vars *); */ static void lexword( struct vars *v) { lexnest(v, backw, ENDOF(backw)); } /* - next - get next token ^ static int next(struct vars *); */ static int /* 1 normal, 0 failure */ next( struct vars *v) { chr c; /* * Errors yield an infinite sequence of failures. */ if (ISERR()) { return 0; /* the error has set nexttype to EOS */ } /* * Remember flavor of last token. */ v->lasttype = v->nexttype; /* * REG_BOSONLY */ if (v->nexttype == EMPTY && (v->cflags®_BOSONLY)) { /* at start of a REG_BOSONLY RE */ RETV(SBEGIN, 0); /* same as \A */ } /* * If we're nested and we've hit end, return to outer level. */ if (v->savenow != NULL && ATEOS()) { v->now = v->savenow; v->stop = v->savestop; v->savenow = v->savestop = NULL; } /* * Skip white space etc. if appropriate (not in literal or []) */ if (v->cflags®_EXPANDED) { switch (v->lexcon) { case L_ERE: case L_BRE: case L_EBND: case L_BBND: skip(v); break; } } /* * Handle EOS, depending on context. */ if (ATEOS()) { switch (v->lexcon) { case L_ERE: case L_BRE: case L_Q: RET(EOS); break; case L_EBND: case L_BBND: FAILW(REG_EBRACE); break; case L_BRACK: case L_CEL: case L_ECL: case L_CCL: FAILW(REG_EBRACK); break; } assert(NOTREACHED); } /* * Okay, time to actually get a character. */ c = *v->now++; /* * Deal with the easy contexts, punt EREs to code below. */ switch (v->lexcon) { case L_BRE: /* punt BREs to separate function */ return brenext(v, c); break; case L_ERE: /* see below */ break; case L_Q: /* literal strings are easy */ RETV(PLAIN, c); break; case L_BBND: /* bounds are fairly simple */ case L_EBND: switch (c) { case CHR('0'): case CHR('1'): case CHR('2'): case CHR('3'): case CHR('4'): case CHR('5'): case CHR('6'): case CHR('7'): case CHR('8'): case CHR('9'): RETV(DIGIT, (chr)DIGITVAL(c)); break; case CHR(','): RET(','); break; case CHR('}'): /* ERE bound ends with } */ if (INCON(L_EBND)) { INTOCON(L_ERE); if ((v->cflags®_ADVF) && NEXT1('?')) { v->now++; NOTE(REG_UNONPOSIX); RETV('}', 0); } RETV('}', 1); } else { FAILW(REG_BADBR); } break; case CHR('\\'): /* BRE bound ends with \} */ if (INCON(L_BBND) && NEXT1('}')) { v->now++; INTOCON(L_BRE); RETV('}', 1); } else { FAILW(REG_BADBR); } break; default: FAILW(REG_BADBR); break; } assert(NOTREACHED); break; case L_BRACK: /* brackets are not too hard */ switch (c) { case CHR(']'): if (LASTTYPE('[')) { RETV(PLAIN, c); } else { INTOCON((v->cflags®_EXTENDED) ? L_ERE : L_BRE); RET(']'); } break; case CHR('\\'): NOTE(REG_UBBS); if (!(v->cflags®_ADVF)) { RETV(PLAIN, c); } NOTE(REG_UNONPOSIX); if (ATEOS()) { FAILW(REG_EESCAPE); } (void)lexescape(v); switch (v->nexttype) { /* not all escapes okay here */ case PLAIN: return 1; break; case CCLASS: switch (v->nextvalue) { case 'd': lexnest(v, brbackd, ENDOF(brbackd)); break; case 's': lexnest(v, brbacks, ENDOF(brbacks)); break; case 'w': lexnest(v, brbackw, ENDOF(brbackw)); break; default: FAILW(REG_EESCAPE); break; } /* * lexnest() done, back up and try again. */ v->nexttype = v->lasttype; return next(v); break; } /* * Not one of the acceptable escapes. */ FAILW(REG_EESCAPE); break; case CHR('-'): if (LASTTYPE('[') || NEXT1(']')) { RETV(PLAIN, c); } else { RETV(RANGE, c); } break; case CHR('['): if (ATEOS()) { FAILW(REG_EBRACK); } switch (*v->now++) { case CHR('.'): INTOCON(L_CEL); /* * Might or might not be locale-specific. */ RET(COLLEL); break; case CHR('='): INTOCON(L_ECL); NOTE(REG_ULOCALE); RET(ECLASS); break; case CHR(':'): INTOCON(L_CCL); NOTE(REG_ULOCALE); RET(CCLASS); break; default: /* oops */ v->now--; RETV(PLAIN, c); break; } assert(NOTREACHED); break; default: RETV(PLAIN, c); break; } assert(NOTREACHED); break; case L_CEL: /* collating elements are easy */ if (c == CHR('.') && NEXT1(']')) { v->now++; INTOCON(L_BRACK); RETV(END, '.'); } else { RETV(PLAIN, c); } break; case L_ECL: /* ditto equivalence classes */ if (c == CHR('=') && NEXT1(']')) { v->now++; INTOCON(L_BRACK); RETV(END, '='); } else { RETV(PLAIN, c); } break; case L_CCL: /* ditto character classes */ if (c == CHR(':') && NEXT1(']')) { v->now++; INTOCON(L_BRACK); RETV(END, ':'); } else { RETV(PLAIN, c); } break; default: assert(NOTREACHED); break; } /* * That got rid of everything except EREs and AREs. */ assert(INCON(L_ERE)); /* * Deal with EREs and AREs, except for backslashes. */ switch (c) { case CHR('|'): RET('|'); break; case CHR('*'): if ((v->cflags®_ADVF) && NEXT1('?')) { v->now++; NOTE(REG_UNONPOSIX); RETV('*', 0); } RETV('*', 1); break; case CHR('+'): if ((v->cflags®_ADVF) && NEXT1('?')) { v->now++; NOTE(REG_UNONPOSIX); RETV('+', 0); } RETV('+', 1); break; case CHR('?'): if ((v->cflags®_ADVF) && NEXT1('?')) { v->now++; NOTE(REG_UNONPOSIX); RETV('?', 0); } RETV('?', 1); break; case CHR('{'): /* bounds start or plain character */ if (v->cflags®_EXPANDED) { skip(v); } if (ATEOS() || !iscdigit(*v->now)) { NOTE(REG_UBRACES); NOTE(REG_UUNSPEC); RETV(PLAIN, c); } else { NOTE(REG_UBOUNDS); INTOCON(L_EBND); RET('{'); } assert(NOTREACHED); break; case CHR('('): /* parenthesis, or advanced extension */ if ((v->cflags®_ADVF) && NEXT1('?')) { NOTE(REG_UNONPOSIX); v->now++; switch (*v->now++) { case CHR(':'): /* non-capturing paren */ RETV('(', 0); break; case CHR('#'): /* comment */ while (!ATEOS() && *v->now != CHR(')')) { v->now++; } if (!ATEOS()) { v->now++; } assert(v->nexttype == v->lasttype); return next(v); break; case CHR('='): /* positive lookahead */ NOTE(REG_ULOOKAHEAD); RETV(LACON, 1); break; case CHR('!'): /* negative lookahead */ NOTE(REG_ULOOKAHEAD); RETV(LACON, 0); break; default: FAILW(REG_BADRPT); break; } assert(NOTREACHED); } if (v->cflags®_NOSUB) { RETV('(', 0); /* all parens non-capturing */ } else { RETV('(', 1); } break; case CHR(')'): if (LASTTYPE('(')) { NOTE(REG_UUNSPEC); } RETV(')', c); break; case CHR('['): /* easy except for [[:<:]] and [[:>:]] */ if (HAVE(6) && *(v->now+0) == CHR('[') && *(v->now+1) == CHR(':') && (*(v->now+2) == CHR('<') || *(v->now+2) == CHR('>')) && *(v->now+3) == CHR(':') && *(v->now+4) == CHR(']') && *(v->now+5) == CHR(']')) { c = *(v->now+2); v->now += 6; NOTE(REG_UNONPOSIX); RET((c == CHR('<')) ? '<' : '>'); } INTOCON(L_BRACK); if (NEXT1('^')) { v->now++; RETV('[', 0); } RETV('[', 1); break; case CHR('.'): RET('.'); break; case CHR('^'): RET('^'); break; case CHR('$'): RET('$'); break; case CHR('\\'): /* mostly punt backslashes to code below */ if (ATEOS()) { FAILW(REG_EESCAPE); } break; default: /* ordinary character */ RETV(PLAIN, c); break; } /* * ERE/ARE backslash handling; backslash already eaten. */ assert(!ATEOS()); if (!(v->cflags®_ADVF)) {/* only AREs have non-trivial escapes */ if (iscalnum(*v->now)) { NOTE(REG_UBSALNUM); NOTE(REG_UUNSPEC); } RETV(PLAIN, *v->now++); } (void)lexescape(v); if (ISERR()) { FAILW(REG_EESCAPE); } if (v->nexttype == CCLASS) {/* fudge at lexical level */ switch (v->nextvalue) { case 'd': lexnest(v, backd, ENDOF(backd)); break; case 'D': lexnest(v, backD, ENDOF(backD)); break; case 's': lexnest(v, backs, ENDOF(backs)); break; case 'S': lexnest(v, backS, ENDOF(backS)); break; case 'w': lexnest(v, backw, ENDOF(backw)); break; case 'W': lexnest(v, backW, ENDOF(backW)); break; default: assert(NOTREACHED); FAILW(REG_ASSERT); break; } /* lexnest done, back up and try again */ v->nexttype = v->lasttype; return next(v); } /* * Otherwise, lexescape has already done the work. */ return !ISERR(); } /* - lexescape - parse an ARE backslash escape (backslash already eaten) * Note slightly nonstandard use of the CCLASS type code. ^ static int lexescape(struct vars *); */ static int /* not actually used, but convenient for RETV */ lexescape( struct vars *v) { chr c; int i; static const chr alert[] = { CHR('a'), CHR('l'), CHR('e'), CHR('r'), CHR('t') }; static const chr esc[] = { CHR('E'), CHR('S'), CHR('C') }; const chr *save; assert(v->cflags®_ADVF); assert(!ATEOS()); c = *v->now++; if (!iscalnum(c)) { RETV(PLAIN, c); } NOTE(REG_UNONPOSIX); switch (c) { case CHR('a'): RETV(PLAIN, chrnamed(v, alert, ENDOF(alert), CHR('\x07'))); break; case CHR('A'): RETV(SBEGIN, 0); break; case CHR('b'): RETV(PLAIN, CHR('\b')); break; case CHR('B'): RETV(PLAIN, CHR('\\')); break; case CHR('c'): NOTE(REG_UUNPORT); if (ATEOS()) { FAILW(REG_EESCAPE); } RETV(PLAIN, (chr)(*v->now++ & 037)); break; case CHR('d'): NOTE(REG_ULOCALE); RETV(CCLASS, 'd'); break; case CHR('D'): NOTE(REG_ULOCALE); RETV(CCLASS, 'D'); break; case CHR('e'): NOTE(REG_UUNPORT); RETV(PLAIN, chrnamed(v, esc, ENDOF(esc), CHR('\x1B'))); break; case CHR('f'): RETV(PLAIN, CHR('\f')); break; case CHR('m'): RET('<'); break; case CHR('M'): RET('>'); break; case CHR('n'): RETV(PLAIN, CHR('\n')); break; case CHR('r'): RETV(PLAIN, CHR('\r')); break; case CHR('s'): NOTE(REG_ULOCALE); RETV(CCLASS, 's'); break; case CHR('S'): NOTE(REG_ULOCALE); RETV(CCLASS, 'S'); break; case CHR('t'): RETV(PLAIN, CHR('\t')); break; case CHR('u'): c = (uchr) lexdigits(v, 16, 1, 4); if (ISERR()) { FAILW(REG_EESCAPE); } RETV(PLAIN, c); break; case CHR('U'): i = lexdigits(v, 16, 1, 8); if (ISERR()) { FAILW(REG_EESCAPE); } RETV(PLAIN, (uchr) i); break; case CHR('v'): RETV(PLAIN, CHR('\v')); break; case CHR('w'): NOTE(REG_ULOCALE); RETV(CCLASS, 'w'); break; case CHR('W'): NOTE(REG_ULOCALE); RETV(CCLASS, 'W'); break; case CHR('x'): NOTE(REG_UUNPORT); c = (uchr) lexdigits(v, 16, 1, 2); if (ISERR()) { FAILW(REG_EESCAPE); } RETV(PLAIN, c); break; case CHR('y'): NOTE(REG_ULOCALE); RETV(WBDRY, 0); break; case CHR('Y'): NOTE(REG_ULOCALE); RETV(NWBDRY, 0); break; case CHR('Z'): RETV(SEND, 0); break; case CHR('1'): case CHR('2'): case CHR('3'): case CHR('4'): case CHR('5'): case CHR('6'): case CHR('7'): case CHR('8'): case CHR('9'): save = v->now; v->now--; /* put first digit back */ c = (uchr) lexdigits(v, 10, 1, 255); /* REs >255 long outside spec */ if (ISERR()) { FAILW(REG_EESCAPE); } /* * Ugly heuristic (first test is "exactly 1 digit?") */ if (v->now - save == 0 || ((int) c > 0 && (size_t)c <= v->nsubexp)) { NOTE(REG_UBACKREF); RETV(BACKREF, (chr)c); } /* * Oops, doesn't look like it's a backref after all... */ v->now = save; /* FALLTHRU */ case CHR('0'): NOTE(REG_UUNPORT); v->now--; /* put first digit back */ c = (uchr) lexdigits(v, 8, 1, 3); if (ISERR()) { FAILW(REG_EESCAPE); } if (c > 0xFF) { /* out of range, so we handled one digit too much */ v->now--; c >>= 3; } RETV(PLAIN, c); break; default: assert(iscalpha(c)); FAILW(REG_EESCAPE); /* unknown alphabetic escape */ break; } assert(NOTREACHED); } /* - lexdigits - slurp up digits and return chr value ^ static int lexdigits(struct vars *, int, int, int); */ static int /* chr value; errors signalled via ERR */ lexdigits( struct vars *v, int base, int minlen, int maxlen) { int n; int len; chr c; int d; const uchr ub = (uchr) base; n = 0; for (len = 0; len < maxlen && !ATEOS(); len++) { if (n > 0x10FFF) { /* Stop when continuing would otherwise overflow */ break; } c = *v->now++; switch (c) { case CHR('0'): case CHR('1'): case CHR('2'): case CHR('3'): case CHR('4'): case CHR('5'): case CHR('6'): case CHR('7'): case CHR('8'): case CHR('9'): d = DIGITVAL(c); break; case CHR('a'): case CHR('A'): d = 10; break; case CHR('b'): case CHR('B'): d = 11; break; case CHR('c'): case CHR('C'): d = 12; break; case CHR('d'): case CHR('D'): d = 13; break; case CHR('e'): case CHR('E'): d = 14; break; case CHR('f'): case CHR('F'): d = 15; break; default: v->now--; /* oops, not a digit at all */ d = -1; break; } if (d >= base) { /* not a plausible digit */ v->now--; d = -1; } if (d < 0) { break; /* NOTE BREAK OUT */ } n = n*ub + (uchr)d; } if (len < minlen) { ERR(REG_EESCAPE); } return n; } /* - brenext - get next BRE token * This is much like EREs except for all the stupid backslashes and the * context-dependency of some things. ^ static int brenext(struct vars *, pchr); */ static int /* 1 normal, 0 failure */ brenext( struct vars *v, pchr pc) { chr c = (chr)pc; switch (c) { case CHR('*'): if (LASTTYPE(EMPTY) || LASTTYPE('(') || LASTTYPE('^')) { RETV(PLAIN, c); } RETV('*', 1); break; case CHR('['): if (HAVE(6) && *(v->now+0) == CHR('[') && *(v->now+1) == CHR(':') && (*(v->now+2) == CHR('<') || *(v->now+2) == CHR('>')) && *(v->now+3) == CHR(':') && *(v->now+4) == CHR(']') && *(v->now+5) == CHR(']')) { c = *(v->now+2); v->now += 6; NOTE(REG_UNONPOSIX); RET((c == CHR('<')) ? '<' : '>'); } INTOCON(L_BRACK); if (NEXT1('^')) { v->now++; RETV('[', 0); } RETV('[', 1); break; case CHR('.'): RET('.'); break; case CHR('^'): if (LASTTYPE(EMPTY)) { RET('^'); } if (LASTTYPE('(')) { NOTE(REG_UUNSPEC); RET('^'); } RETV(PLAIN, c); break; case CHR('$'): if (v->cflags®_EXPANDED) { skip(v); } if (ATEOS()) { RET('$'); } if (NEXT2('\\', ')')) { NOTE(REG_UUNSPEC); RET('$'); } RETV(PLAIN, c); break; case CHR('\\'): break; /* see below */ default: RETV(PLAIN, c); break; } assert(c == CHR('\\')); if (ATEOS()) { FAILW(REG_EESCAPE); } c = *v->now++; switch (c) { case CHR('{'): INTOCON(L_BBND); NOTE(REG_UBOUNDS); RET('{'); break; case CHR('('): RETV('(', 1); break; case CHR(')'): RETV(')', c); break; case CHR('<'): NOTE(REG_UNONPOSIX); RET('<'); break; case CHR('>'): NOTE(REG_UNONPOSIX); RET('>'); break; case CHR('1'): case CHR('2'): case CHR('3'): case CHR('4'): case CHR('5'): case CHR('6'): case CHR('7'): case CHR('8'): case CHR('9'): NOTE(REG_UBACKREF); RETV(BACKREF, (chr)DIGITVAL(c)); break; default: if (iscalnum(c)) { NOTE(REG_UBSALNUM); NOTE(REG_UUNSPEC); } RETV(PLAIN, c); break; } assert(NOTREACHED); } /* - skip - skip white space and comments in expanded form ^ static void skip(struct vars *); */ static void skip( struct vars *v) { const chr *start = v->now; assert(v->cflags®_EXPANDED); for (;;) { while (!ATEOS() && iscspace(*v->now)) { v->now++; } if (ATEOS() || *v->now != CHR('#')) { break; /* NOTE BREAK OUT */ } assert(NEXT1('#')); while (!ATEOS() && *v->now != CHR('\n')) { v->now++; } /* * Leave the newline to be picked up by the iscspace loop. */ } if (v->now != start) { NOTE(REG_UNONPOSIX); } } /* - newline - return the chr for a newline * This helps confine use of CHR to this source file. ^ static chr newline(void); */ static chr newline(void) { return CHR('\n'); } /* - chrnamed - return the chr known by a given (chr string) name * The code is a bit clumsy, but this routine gets only such specialized * use that it hardly matters. ^ static chr chrnamed(struct vars *, const chr *, const chr *, pchr); */ static chr chrnamed( struct vars *v, const chr *startp, /* start of name */ const chr *endp, /* just past end of name */ pchr lastresort) /* what to return if name lookup fails */ { celt c; int errsave; int e; struct cvec *cv; errsave = v->err; v->err = 0; c = element(v, startp, endp); e = v->err; v->err = errsave; if (e != 0) { return (chr)lastresort; } cv = range(v, c, c, 0); if (cv->nchrs == 0) { return (chr)lastresort; } return cv->chrs[0]; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/regc_locale.c0000644000175000017500000016341514726623136015607 0ustar sergeisergei/* * regc_locale.c -- * * This file contains the Unicode locale specific regexp routines. * This file is #included by regcomp.c. * * Copyright © 1998 Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ /* ASCII character-name table */ static const struct cname { const char *name; const char code; } cnames[] = { {"NUL", '\x00'}, {"SOH", '\x01'}, {"STX", '\x02'}, {"ETX", '\x03'}, {"EOT", '\x04'}, {"ENQ", '\x05'}, {"ACK", '\x06'}, {"BEL", '\x07'}, {"alert", '\x07'}, {"BS", '\x08'}, {"backspace", '\x08'}, {"HT", '\x09'}, {"tab", '\x09'}, {"LF", '\x0A'}, {"newline", '\x0A'}, {"VT", '\x0B'}, {"vertical-tab", '\x0B'}, {"FF", '\x0C'}, {"form-feed", '\x0C'}, {"CR", '\x0D'}, {"carriage-return", '\x0D'}, {"SO", '\x0E'}, {"SI", '\x0F'}, {"DLE", '\x10'}, {"DC1", '\x11'}, {"DC2", '\x12'}, {"DC3", '\x13'}, {"DC4", '\x14'}, {"NAK", '\x15'}, {"SYN", '\x16'}, {"ETB", '\x17'}, {"CAN", '\x18'}, {"EM", '\x19'}, {"SUB", '\x1A'}, {"ESC", '\x1B'}, {"IS4", '\x1C'}, {"FS", '\x1C'}, {"IS3", '\x1D'}, {"GS", '\x1D'}, {"IS2", '\x1E'}, {"RS", '\x1E'}, {"IS1", '\x1F'}, {"US", '\x1F'}, {"space", ' '}, {"exclamation-mark",'!'}, {"quotation-mark", '"'}, {"number-sign", '#'}, {"dollar-sign", '$'}, {"percent-sign", '%'}, {"ampersand", '&'}, {"apostrophe", '\''}, {"left-parenthesis",'('}, {"right-parenthesis", ')'}, {"asterisk", '*'}, {"plus-sign", '+'}, {"comma", ','}, {"hyphen", '-'}, {"hyphen-minus", '-'}, {"period", '.'}, {"full-stop", '.'}, {"slash", '/'}, {"solidus", '/'}, {"zero", '0'}, {"one", '1'}, {"two", '2'}, {"three", '3'}, {"four", '4'}, {"five", '5'}, {"six", '6'}, {"seven", '7'}, {"eight", '8'}, {"nine", '9'}, {"colon", ':'}, {"semicolon", ';'}, {"less-than-sign", '<'}, {"equals-sign", '='}, {"greater-than-sign", '>'}, {"question-mark", '?'}, {"commercial-at", '@'}, {"left-square-bracket", '['}, {"backslash", '\\'}, {"reverse-solidus", '\\'}, {"right-square-bracket", ']'}, {"circumflex", '^'}, {"circumflex-accent", '^'}, {"underscore", '_'}, {"low-line", '_'}, {"grave-accent", '`'}, {"left-brace", '{'}, {"left-curly-bracket", '{'}, {"vertical-line", '|'}, {"right-brace", '}'}, {"right-curly-bracket", '}'}, {"tilde", '~'}, {"DEL", '\x7F'}, {NULL, 0} }; /* * Unicode character-class tables. */ typedef struct { chr start; chr end; } crange; /* * Declarations of Unicode character ranges. This code * is automatically generated by the tools/uniClass.tcl script * and used in generic/regc_locale.c. Do not modify by hand. */ /* * Unicode: alphabetic characters. */ static const crange alphaRangeTable[] = { {0x41, 0x5A}, {0x61, 0x7A}, {0xC0, 0xD6}, {0xD8, 0xF6}, {0xF8, 0x2C1}, {0x2C6, 0x2D1}, {0x2E0, 0x2E4}, {0x370, 0x374}, {0x37A, 0x37D}, {0x388, 0x38A}, {0x38E, 0x3A1}, {0x3A3, 0x3F5}, {0x3F7, 0x481}, {0x48A, 0x52F}, {0x531, 0x556}, {0x560, 0x588}, {0x5D0, 0x5EA}, {0x5EF, 0x5F2}, {0x620, 0x64A}, {0x671, 0x6D3}, {0x6FA, 0x6FC}, {0x712, 0x72F}, {0x74D, 0x7A5}, {0x7CA, 0x7EA}, {0x800, 0x815}, {0x840, 0x858}, {0x860, 0x86A}, {0x870, 0x887}, {0x889, 0x88E}, {0x8A0, 0x8C9}, {0x904, 0x939}, {0x958, 0x961}, {0x971, 0x980}, {0x985, 0x98C}, {0x993, 0x9A8}, {0x9AA, 0x9B0}, {0x9B6, 0x9B9}, {0x9DF, 0x9E1}, {0xA05, 0xA0A}, {0xA13, 0xA28}, {0xA2A, 0xA30}, {0xA59, 0xA5C}, {0xA72, 0xA74}, {0xA85, 0xA8D}, {0xA8F, 0xA91}, {0xA93, 0xAA8}, {0xAAA, 0xAB0}, {0xAB5, 0xAB9}, {0xB05, 0xB0C}, {0xB13, 0xB28}, {0xB2A, 0xB30}, {0xB35, 0xB39}, {0xB5F, 0xB61}, {0xB85, 0xB8A}, {0xB8E, 0xB90}, {0xB92, 0xB95}, {0xBA8, 0xBAA}, {0xBAE, 0xBB9}, {0xC05, 0xC0C}, {0xC0E, 0xC10}, {0xC12, 0xC28}, {0xC2A, 0xC39}, {0xC58, 0xC5A}, {0xC85, 0xC8C}, {0xC8E, 0xC90}, {0xC92, 0xCA8}, {0xCAA, 0xCB3}, {0xCB5, 0xCB9}, {0xD04, 0xD0C}, {0xD0E, 0xD10}, {0xD12, 0xD3A}, {0xD54, 0xD56}, {0xD5F, 0xD61}, {0xD7A, 0xD7F}, {0xD85, 0xD96}, {0xD9A, 0xDB1}, {0xDB3, 0xDBB}, {0xDC0, 0xDC6}, {0xE01, 0xE30}, {0xE40, 0xE46}, {0xE86, 0xE8A}, {0xE8C, 0xEA3}, {0xEA7, 0xEB0}, {0xEC0, 0xEC4}, {0xEDC, 0xEDF}, {0xF40, 0xF47}, {0xF49, 0xF6C}, {0xF88, 0xF8C}, {0x1000, 0x102A}, {0x1050, 0x1055}, {0x105A, 0x105D}, {0x106E, 0x1070}, {0x1075, 0x1081}, {0x10A0, 0x10C5}, {0x10D0, 0x10FA}, {0x10FC, 0x1248}, {0x124A, 0x124D}, {0x1250, 0x1256}, {0x125A, 0x125D}, {0x1260, 0x1288}, {0x128A, 0x128D}, {0x1290, 0x12B0}, {0x12B2, 0x12B5}, {0x12B8, 0x12BE}, {0x12C2, 0x12C5}, {0x12C8, 0x12D6}, {0x12D8, 0x1310}, {0x1312, 0x1315}, {0x1318, 0x135A}, {0x1380, 0x138F}, {0x13A0, 0x13F5}, {0x13F8, 0x13FD}, {0x1401, 0x166C}, {0x166F, 0x167F}, {0x1681, 0x169A}, {0x16A0, 0x16EA}, {0x16F1, 0x16F8}, {0x1700, 0x1711}, {0x171F, 0x1731}, {0x1740, 0x1751}, {0x1760, 0x176C}, {0x176E, 0x1770}, {0x1780, 0x17B3}, {0x1820, 0x1878}, {0x1880, 0x1884}, {0x1887, 0x18A8}, {0x18B0, 0x18F5}, {0x1900, 0x191E}, {0x1950, 0x196D}, {0x1970, 0x1974}, {0x1980, 0x19AB}, {0x19B0, 0x19C9}, {0x1A00, 0x1A16}, {0x1A20, 0x1A54}, {0x1B05, 0x1B33}, {0x1B45, 0x1B4C}, {0x1B83, 0x1BA0}, {0x1BBA, 0x1BE5}, {0x1C00, 0x1C23}, {0x1C4D, 0x1C4F}, {0x1C5A, 0x1C7D}, {0x1C80, 0x1C8A}, {0x1C90, 0x1CBA}, {0x1CBD, 0x1CBF}, {0x1CE9, 0x1CEC}, {0x1CEE, 0x1CF3}, {0x1D00, 0x1DBF}, {0x1E00, 0x1F15}, {0x1F18, 0x1F1D}, {0x1F20, 0x1F45}, {0x1F48, 0x1F4D}, {0x1F50, 0x1F57}, {0x1F5F, 0x1F7D}, {0x1F80, 0x1FB4}, {0x1FB6, 0x1FBC}, {0x1FC2, 0x1FC4}, {0x1FC6, 0x1FCC}, {0x1FD0, 0x1FD3}, {0x1FD6, 0x1FDB}, {0x1FE0, 0x1FEC}, {0x1FF2, 0x1FF4}, {0x1FF6, 0x1FFC}, {0x2090, 0x209C}, {0x210A, 0x2113}, {0x2119, 0x211D}, {0x212A, 0x212D}, {0x212F, 0x2139}, {0x213C, 0x213F}, {0x2145, 0x2149}, {0x2C00, 0x2CE4}, {0x2CEB, 0x2CEE}, {0x2D00, 0x2D25}, {0x2D30, 0x2D67}, {0x2D80, 0x2D96}, {0x2DA0, 0x2DA6}, {0x2DA8, 0x2DAE}, {0x2DB0, 0x2DB6}, {0x2DB8, 0x2DBE}, {0x2DC0, 0x2DC6}, {0x2DC8, 0x2DCE}, {0x2DD0, 0x2DD6}, {0x2DD8, 0x2DDE}, {0x3031, 0x3035}, {0x3041, 0x3096}, {0x309D, 0x309F}, {0x30A1, 0x30FA}, {0x30FC, 0x30FF}, {0x3105, 0x312F}, {0x3131, 0x318E}, {0x31A0, 0x31BF}, {0x31F0, 0x31FF}, {0x3400, 0x4DBF}, {0x4E00, 0xA48C}, {0xA4D0, 0xA4FD}, {0xA500, 0xA60C}, {0xA610, 0xA61F}, {0xA640, 0xA66E}, {0xA67F, 0xA69D}, {0xA6A0, 0xA6E5}, {0xA717, 0xA71F}, {0xA722, 0xA788}, {0xA78B, 0xA7CD}, {0xA7D5, 0xA7DC}, {0xA7F2, 0xA801}, {0xA803, 0xA805}, {0xA807, 0xA80A}, {0xA80C, 0xA822}, {0xA840, 0xA873}, {0xA882, 0xA8B3}, {0xA8F2, 0xA8F7}, {0xA90A, 0xA925}, {0xA930, 0xA946}, {0xA960, 0xA97C}, {0xA984, 0xA9B2}, {0xA9E0, 0xA9E4}, {0xA9E6, 0xA9EF}, {0xA9FA, 0xA9FE}, {0xAA00, 0xAA28}, {0xAA40, 0xAA42}, {0xAA44, 0xAA4B}, {0xAA60, 0xAA76}, {0xAA7E, 0xAAAF}, {0xAAB9, 0xAABD}, {0xAADB, 0xAADD}, {0xAAE0, 0xAAEA}, {0xAAF2, 0xAAF4}, {0xAB01, 0xAB06}, {0xAB09, 0xAB0E}, {0xAB11, 0xAB16}, {0xAB20, 0xAB26}, {0xAB28, 0xAB2E}, {0xAB30, 0xAB5A}, {0xAB5C, 0xAB69}, {0xAB70, 0xABE2}, {0xAC00, 0xD7A3}, {0xD7B0, 0xD7C6}, {0xD7CB, 0xD7FB}, {0xF900, 0xFA6D}, {0xFA70, 0xFAD9}, {0xFB00, 0xFB06}, {0xFB13, 0xFB17}, {0xFB1F, 0xFB28}, {0xFB2A, 0xFB36}, {0xFB38, 0xFB3C}, {0xFB46, 0xFBB1}, {0xFBD3, 0xFD3D}, {0xFD50, 0xFD8F}, {0xFD92, 0xFDC7}, {0xFDF0, 0xFDFB}, {0xFE70, 0xFE74}, {0xFE76, 0xFEFC}, {0xFF21, 0xFF3A}, {0xFF41, 0xFF5A}, {0xFF66, 0xFFBE}, {0xFFC2, 0xFFC7}, {0xFFCA, 0xFFCF}, {0xFFD2, 0xFFD7}, {0xFFDA, 0xFFDC} #if CHRBITS > 16 ,{0x10000, 0x1000B}, {0x1000D, 0x10026}, {0x10028, 0x1003A}, {0x1003F, 0x1004D}, {0x10050, 0x1005D}, {0x10080, 0x100FA}, {0x10280, 0x1029C}, {0x102A0, 0x102D0}, {0x10300, 0x1031F}, {0x1032D, 0x10340}, {0x10342, 0x10349}, {0x10350, 0x10375}, {0x10380, 0x1039D}, {0x103A0, 0x103C3}, {0x103C8, 0x103CF}, {0x10400, 0x1049D}, {0x104B0, 0x104D3}, {0x104D8, 0x104FB}, {0x10500, 0x10527}, {0x10530, 0x10563}, {0x10570, 0x1057A}, {0x1057C, 0x1058A}, {0x1058C, 0x10592}, {0x10597, 0x105A1}, {0x105A3, 0x105B1}, {0x105B3, 0x105B9}, {0x105C0, 0x105F3}, {0x10600, 0x10736}, {0x10740, 0x10755}, {0x10760, 0x10767}, {0x10780, 0x10785}, {0x10787, 0x107B0}, {0x107B2, 0x107BA}, {0x10800, 0x10805}, {0x1080A, 0x10835}, {0x1083F, 0x10855}, {0x10860, 0x10876}, {0x10880, 0x1089E}, {0x108E0, 0x108F2}, {0x10900, 0x10915}, {0x10920, 0x10939}, {0x10980, 0x109B7}, {0x10A10, 0x10A13}, {0x10A15, 0x10A17}, {0x10A19, 0x10A35}, {0x10A60, 0x10A7C}, {0x10A80, 0x10A9C}, {0x10AC0, 0x10AC7}, {0x10AC9, 0x10AE4}, {0x10B00, 0x10B35}, {0x10B40, 0x10B55}, {0x10B60, 0x10B72}, {0x10B80, 0x10B91}, {0x10C00, 0x10C48}, {0x10C80, 0x10CB2}, {0x10CC0, 0x10CF2}, {0x10D00, 0x10D23}, {0x10D4A, 0x10D65}, {0x10D6F, 0x10D85}, {0x10E80, 0x10EA9}, {0x10EC2, 0x10EC4}, {0x10F00, 0x10F1C}, {0x10F30, 0x10F45}, {0x10F70, 0x10F81}, {0x10FB0, 0x10FC4}, {0x10FE0, 0x10FF6}, {0x11003, 0x11037}, {0x11083, 0x110AF}, {0x110D0, 0x110E8}, {0x11103, 0x11126}, {0x11150, 0x11172}, {0x11183, 0x111B2}, {0x111C1, 0x111C4}, {0x11200, 0x11211}, {0x11213, 0x1122B}, {0x11280, 0x11286}, {0x1128A, 0x1128D}, {0x1128F, 0x1129D}, {0x1129F, 0x112A8}, {0x112B0, 0x112DE}, {0x11305, 0x1130C}, {0x11313, 0x11328}, {0x1132A, 0x11330}, {0x11335, 0x11339}, {0x1135D, 0x11361}, {0x11380, 0x11389}, {0x11390, 0x113B5}, {0x11400, 0x11434}, {0x11447, 0x1144A}, {0x1145F, 0x11461}, {0x11480, 0x114AF}, {0x11580, 0x115AE}, {0x115D8, 0x115DB}, {0x11600, 0x1162F}, {0x11680, 0x116AA}, {0x11700, 0x1171A}, {0x11740, 0x11746}, {0x11800, 0x1182B}, {0x118A0, 0x118DF}, {0x118FF, 0x11906}, {0x1190C, 0x11913}, {0x11918, 0x1192F}, {0x119A0, 0x119A7}, {0x119AA, 0x119D0}, {0x11A0B, 0x11A32}, {0x11A5C, 0x11A89}, {0x11AB0, 0x11AF8}, {0x11BC0, 0x11BE0}, {0x11C00, 0x11C08}, {0x11C0A, 0x11C2E}, {0x11C72, 0x11C8F}, {0x11D00, 0x11D06}, {0x11D0B, 0x11D30}, {0x11D60, 0x11D65}, {0x11D6A, 0x11D89}, {0x11EE0, 0x11EF2}, {0x11F04, 0x11F10}, {0x11F12, 0x11F33}, {0x12000, 0x12399}, {0x12480, 0x12543}, {0x12F90, 0x12FF0}, {0x13000, 0x1342F}, {0x13441, 0x13446}, {0x13460, 0x143FA}, {0x14400, 0x14646}, {0x16100, 0x1611D}, {0x16800, 0x16A38}, {0x16A40, 0x16A5E}, {0x16A70, 0x16ABE}, {0x16AD0, 0x16AED}, {0x16B00, 0x16B2F}, {0x16B40, 0x16B43}, {0x16B63, 0x16B77}, {0x16B7D, 0x16B8F}, {0x16D40, 0x16D6C}, {0x16E40, 0x16E7F}, {0x16F00, 0x16F4A}, {0x16F93, 0x16F9F}, {0x17000, 0x187F7}, {0x18800, 0x18CD5}, {0x18CFF, 0x18D08}, {0x1AFF0, 0x1AFF3}, {0x1AFF5, 0x1AFFB}, {0x1B000, 0x1B122}, {0x1B150, 0x1B152}, {0x1B164, 0x1B167}, {0x1B170, 0x1B2FB}, {0x1BC00, 0x1BC6A}, {0x1BC70, 0x1BC7C}, {0x1BC80, 0x1BC88}, {0x1BC90, 0x1BC99}, {0x1D400, 0x1D454}, {0x1D456, 0x1D49C}, {0x1D4A9, 0x1D4AC}, {0x1D4AE, 0x1D4B9}, {0x1D4BD, 0x1D4C3}, {0x1D4C5, 0x1D505}, {0x1D507, 0x1D50A}, {0x1D50D, 0x1D514}, {0x1D516, 0x1D51C}, {0x1D51E, 0x1D539}, {0x1D53B, 0x1D53E}, {0x1D540, 0x1D544}, {0x1D54A, 0x1D550}, {0x1D552, 0x1D6A5}, {0x1D6A8, 0x1D6C0}, {0x1D6C2, 0x1D6DA}, {0x1D6DC, 0x1D6FA}, {0x1D6FC, 0x1D714}, {0x1D716, 0x1D734}, {0x1D736, 0x1D74E}, {0x1D750, 0x1D76E}, {0x1D770, 0x1D788}, {0x1D78A, 0x1D7A8}, {0x1D7AA, 0x1D7C2}, {0x1D7C4, 0x1D7CB}, {0x1DF00, 0x1DF1E}, {0x1DF25, 0x1DF2A}, {0x1E030, 0x1E06D}, {0x1E100, 0x1E12C}, {0x1E137, 0x1E13D}, {0x1E290, 0x1E2AD}, {0x1E2C0, 0x1E2EB}, {0x1E4D0, 0x1E4EB}, {0x1E5D0, 0x1E5ED}, {0x1E7E0, 0x1E7E6}, {0x1E7E8, 0x1E7EB}, {0x1E7F0, 0x1E7FE}, {0x1E800, 0x1E8C4}, {0x1E900, 0x1E943}, {0x1EE00, 0x1EE03}, {0x1EE05, 0x1EE1F}, {0x1EE29, 0x1EE32}, {0x1EE34, 0x1EE37}, {0x1EE4D, 0x1EE4F}, {0x1EE67, 0x1EE6A}, {0x1EE6C, 0x1EE72}, {0x1EE74, 0x1EE77}, {0x1EE79, 0x1EE7C}, {0x1EE80, 0x1EE89}, {0x1EE8B, 0x1EE9B}, {0x1EEA1, 0x1EEA3}, {0x1EEA5, 0x1EEA9}, {0x1EEAB, 0x1EEBB}, {0x20000, 0x2A6DF}, {0x2A700, 0x2B739}, {0x2B740, 0x2B81D}, {0x2B820, 0x2CEA1}, {0x2CEB0, 0x2EBE0}, {0x2EBF0, 0x2EE5D}, {0x2F800, 0x2FA1D}, {0x30000, 0x3134A}, {0x31350, 0x323AF} #endif }; #define NUM_ALPHA_RANGE ((int)(sizeof(alphaRangeTable)/sizeof(crange))) static const chr alphaCharTable[] = { 0xAA, 0xB5, 0xBA, 0x2EC, 0x2EE, 0x376, 0x377, 0x37F, 0x386, 0x38C, 0x559, 0x66E, 0x66F, 0x6D5, 0x6E5, 0x6E6, 0x6EE, 0x6EF, 0x6FF, 0x710, 0x7B1, 0x7F4, 0x7F5, 0x7FA, 0x81A, 0x824, 0x828, 0x93D, 0x950, 0x98F, 0x990, 0x9B2, 0x9BD, 0x9CE, 0x9DC, 0x9DD, 0x9F0, 0x9F1, 0x9FC, 0xA0F, 0xA10, 0xA32, 0xA33, 0xA35, 0xA36, 0xA38, 0xA39, 0xA5E, 0xAB2, 0xAB3, 0xABD, 0xAD0, 0xAE0, 0xAE1, 0xAF9, 0xB0F, 0xB10, 0xB32, 0xB33, 0xB3D, 0xB5C, 0xB5D, 0xB71, 0xB83, 0xB99, 0xB9A, 0xB9C, 0xB9E, 0xB9F, 0xBA3, 0xBA4, 0xBD0, 0xC3D, 0xC5D, 0xC60, 0xC61, 0xC80, 0xCBD, 0xCDD, 0xCDE, 0xCE0, 0xCE1, 0xCF1, 0xCF2, 0xD3D, 0xD4E, 0xDBD, 0xE32, 0xE33, 0xE81, 0xE82, 0xE84, 0xEA5, 0xEB2, 0xEB3, 0xEBD, 0xEC6, 0xF00, 0x103F, 0x1061, 0x1065, 0x1066, 0x108E, 0x10C7, 0x10CD, 0x1258, 0x12C0, 0x17D7, 0x17DC, 0x18AA, 0x1AA7, 0x1BAE, 0x1BAF, 0x1CF5, 0x1CF6, 0x1CFA, 0x1F59, 0x1F5B, 0x1F5D, 0x1FBE, 0x2071, 0x207F, 0x2102, 0x2107, 0x2115, 0x2124, 0x2126, 0x2128, 0x214E, 0x2183, 0x2184, 0x2CF2, 0x2CF3, 0x2D27, 0x2D2D, 0x2D6F, 0x2E2F, 0x3005, 0x3006, 0x303B, 0x303C, 0xA62A, 0xA62B, 0xA7D0, 0xA7D1, 0xA7D3, 0xA8FB, 0xA8FD, 0xA8FE, 0xA9CF, 0xAA7A, 0xAAB1, 0xAAB5, 0xAAB6, 0xAAC0, 0xAAC2, 0xFB1D, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44 #if CHRBITS > 16 ,0x1003C, 0x1003D, 0x10594, 0x10595, 0x105BB, 0x105BC, 0x10808, 0x10837, 0x10838, 0x1083C, 0x108F4, 0x108F5, 0x109BE, 0x109BF, 0x10A00, 0x10EB0, 0x10EB1, 0x10F27, 0x11071, 0x11072, 0x11075, 0x11144, 0x11147, 0x11176, 0x111DA, 0x111DC, 0x1123F, 0x11240, 0x11288, 0x1130F, 0x11310, 0x11332, 0x11333, 0x1133D, 0x11350, 0x1138B, 0x1138E, 0x113B7, 0x113D1, 0x113D3, 0x114C4, 0x114C5, 0x114C7, 0x11644, 0x116B8, 0x11909, 0x11915, 0x11916, 0x1193F, 0x11941, 0x119E1, 0x119E3, 0x11A00, 0x11A3A, 0x11A50, 0x11A9D, 0x11C40, 0x11D08, 0x11D09, 0x11D46, 0x11D67, 0x11D68, 0x11D98, 0x11F02, 0x11FB0, 0x16F50, 0x16FE0, 0x16FE1, 0x16FE3, 0x1AFFD, 0x1AFFE, 0x1B132, 0x1B155, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D4BB, 0x1D546, 0x1E14E, 0x1E5F0, 0x1E7ED, 0x1E7EE, 0x1E94B, 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE27, 0x1EE39, 0x1EE3B, 0x1EE42, 0x1EE47, 0x1EE49, 0x1EE4B, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE57, 0x1EE59, 0x1EE5B, 0x1EE5D, 0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE7E #endif }; #define NUM_ALPHA_CHAR ((int)(sizeof(alphaCharTable)/sizeof(chr))) /* * Unicode: control characters. */ static const crange controlRangeTable[] = { {0x0, 0x1F}, {0x7F, 0x9F}, {0x600, 0x605}, {0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2064}, {0x2066, 0x206F}, {0xFFF9, 0xFFFB} #if CHRBITS > 16 ,{0x13430, 0x1343F}, {0x1BCA0, 0x1BCA3}, {0x1D173, 0x1D17A}, {0xE0020, 0xE007F} #endif }; #define NUM_CONTROL_RANGE ((int)(sizeof(controlRangeTable)/sizeof(crange))) static const chr controlCharTable[] = { 0xAD, 0x61C, 0x6DD, 0x70F, 0x890, 0x891, 0x8E2, 0x180E, 0xFEFF #if CHRBITS > 16 ,0x110BD, 0x110CD, 0xE0001 #endif }; #define NUM_CONTROL_CHAR ((int)(sizeof(controlCharTable)/sizeof(chr))) /* * Unicode: decimal digit characters. */ static const crange digitRangeTable[] = { {0x30, 0x39}, {0x660, 0x669}, {0x6F0, 0x6F9}, {0x7C0, 0x7C9}, {0x966, 0x96F}, {0x9E6, 0x9EF}, {0xA66, 0xA6F}, {0xAE6, 0xAEF}, {0xB66, 0xB6F}, {0xBE6, 0xBEF}, {0xC66, 0xC6F}, {0xCE6, 0xCEF}, {0xD66, 0xD6F}, {0xDE6, 0xDEF}, {0xE50, 0xE59}, {0xED0, 0xED9}, {0xF20, 0xF29}, {0x1040, 0x1049}, {0x1090, 0x1099}, {0x17E0, 0x17E9}, {0x1810, 0x1819}, {0x1946, 0x194F}, {0x19D0, 0x19D9}, {0x1A80, 0x1A89}, {0x1A90, 0x1A99}, {0x1B50, 0x1B59}, {0x1BB0, 0x1BB9}, {0x1C40, 0x1C49}, {0x1C50, 0x1C59}, {0xA620, 0xA629}, {0xA8D0, 0xA8D9}, {0xA900, 0xA909}, {0xA9D0, 0xA9D9}, {0xA9F0, 0xA9F9}, {0xAA50, 0xAA59}, {0xABF0, 0xABF9}, {0xFF10, 0xFF19} #if CHRBITS > 16 ,{0x104A0, 0x104A9}, {0x10D30, 0x10D39}, {0x10D40, 0x10D49}, {0x11066, 0x1106F}, {0x110F0, 0x110F9}, {0x11136, 0x1113F}, {0x111D0, 0x111D9}, {0x112F0, 0x112F9}, {0x11450, 0x11459}, {0x114D0, 0x114D9}, {0x11650, 0x11659}, {0x116C0, 0x116C9}, {0x116D0, 0x116E3}, {0x11730, 0x11739}, {0x118E0, 0x118E9}, {0x11950, 0x11959}, {0x11BF0, 0x11BF9}, {0x11C50, 0x11C59}, {0x11D50, 0x11D59}, {0x11DA0, 0x11DA9}, {0x11F50, 0x11F59}, {0x16130, 0x16139}, {0x16A60, 0x16A69}, {0x16AC0, 0x16AC9}, {0x16B50, 0x16B59}, {0x16D70, 0x16D79}, {0x1CCF0, 0x1CCF9}, {0x1D7CE, 0x1D7FF}, {0x1E140, 0x1E149}, {0x1E2F0, 0x1E2F9}, {0x1E4F0, 0x1E4F9}, {0x1E5F1, 0x1E5FA}, {0x1E950, 0x1E959}, {0x1FBF0, 0x1FBF9} #endif }; #define NUM_DIGIT_RANGE ((int)(sizeof(digitRangeTable)/sizeof(crange))) /* * no singletons of digit characters. */ /* * Unicode: punctuation characters. */ static const crange punctRangeTable[] = { {0x21, 0x23}, {0x25, 0x2A}, {0x2C, 0x2F}, {0x5B, 0x5D}, {0x55A, 0x55F}, {0x61D, 0x61F}, {0x66A, 0x66D}, {0x700, 0x70D}, {0x7F7, 0x7F9}, {0x830, 0x83E}, {0xF04, 0xF12}, {0xF3A, 0xF3D}, {0xFD0, 0xFD4}, {0x104A, 0x104F}, {0x1360, 0x1368}, {0x16EB, 0x16ED}, {0x17D4, 0x17D6}, {0x17D8, 0x17DA}, {0x1800, 0x180A}, {0x1AA0, 0x1AA6}, {0x1AA8, 0x1AAD}, {0x1B5A, 0x1B60}, {0x1B7D, 0x1B7F}, {0x1BFC, 0x1BFF}, {0x1C3B, 0x1C3F}, {0x1CC0, 0x1CC7}, {0x2010, 0x2027}, {0x2030, 0x2043}, {0x2045, 0x2051}, {0x2053, 0x205E}, {0x2308, 0x230B}, {0x2768, 0x2775}, {0x27E6, 0x27EF}, {0x2983, 0x2998}, {0x29D8, 0x29DB}, {0x2CF9, 0x2CFC}, {0x2E00, 0x2E2E}, {0x2E30, 0x2E4F}, {0x2E52, 0x2E5D}, {0x3001, 0x3003}, {0x3008, 0x3011}, {0x3014, 0x301F}, {0xA60D, 0xA60F}, {0xA6F2, 0xA6F7}, {0xA874, 0xA877}, {0xA8F8, 0xA8FA}, {0xA9C1, 0xA9CD}, {0xAA5C, 0xAA5F}, {0xFE10, 0xFE19}, {0xFE30, 0xFE52}, {0xFE54, 0xFE61}, {0xFF01, 0xFF03}, {0xFF05, 0xFF0A}, {0xFF0C, 0xFF0F}, {0xFF3B, 0xFF3D}, {0xFF5F, 0xFF65} #if CHRBITS > 16 ,{0x10100, 0x10102}, {0x10A50, 0x10A58}, {0x10AF0, 0x10AF6}, {0x10B39, 0x10B3F}, {0x10B99, 0x10B9C}, {0x10F55, 0x10F59}, {0x10F86, 0x10F89}, {0x11047, 0x1104D}, {0x110BE, 0x110C1}, {0x11140, 0x11143}, {0x111C5, 0x111C8}, {0x111DD, 0x111DF}, {0x11238, 0x1123D}, {0x1144B, 0x1144F}, {0x115C1, 0x115D7}, {0x11641, 0x11643}, {0x11660, 0x1166C}, {0x1173C, 0x1173E}, {0x11944, 0x11946}, {0x11A3F, 0x11A46}, {0x11A9A, 0x11A9C}, {0x11A9E, 0x11AA2}, {0x11B00, 0x11B09}, {0x11C41, 0x11C45}, {0x11F43, 0x11F4F}, {0x12470, 0x12474}, {0x16B37, 0x16B3B}, {0x16D6D, 0x16D6F}, {0x16E97, 0x16E9A}, {0x1DA87, 0x1DA8B} #endif }; #define NUM_PUNCT_RANGE ((int)(sizeof(punctRangeTable)/sizeof(crange))) static const chr punctCharTable[] = { 0x3A, 0x3B, 0x3F, 0x40, 0x5F, 0x7B, 0x7D, 0xA1, 0xA7, 0xAB, 0xB6, 0xB7, 0xBB, 0xBF, 0x37E, 0x387, 0x589, 0x58A, 0x5BE, 0x5C0, 0x5C3, 0x5C6, 0x5F3, 0x5F4, 0x609, 0x60A, 0x60C, 0x60D, 0x61B, 0x6D4, 0x85E, 0x964, 0x965, 0x970, 0x9FD, 0xA76, 0xAF0, 0xC77, 0xC84, 0xDF4, 0xE4F, 0xE5A, 0xE5B, 0xF14, 0xF85, 0xFD9, 0xFDA, 0x10FB, 0x1400, 0x166E, 0x169B, 0x169C, 0x1735, 0x1736, 0x1944, 0x1945, 0x1A1E, 0x1A1F, 0x1B4E, 0x1B4F, 0x1C7E, 0x1C7F, 0x1CD3, 0x207D, 0x207E, 0x208D, 0x208E, 0x2329, 0x232A, 0x27C5, 0x27C6, 0x29FC, 0x29FD, 0x2CFE, 0x2CFF, 0x2D70, 0x3030, 0x303D, 0x30A0, 0x30FB, 0xA4FE, 0xA4FF, 0xA673, 0xA67E, 0xA8CE, 0xA8CF, 0xA8FC, 0xA92E, 0xA92F, 0xA95F, 0xA9DE, 0xA9DF, 0xAADE, 0xAADF, 0xAAF0, 0xAAF1, 0xABEB, 0xFD3E, 0xFD3F, 0xFE63, 0xFE68, 0xFE6A, 0xFE6B, 0xFF1A, 0xFF1B, 0xFF1F, 0xFF20, 0xFF3F, 0xFF5B, 0xFF5D #if CHRBITS > 16 ,0x1039F, 0x103D0, 0x1056F, 0x10857, 0x1091F, 0x1093F, 0x10A7F, 0x10D6E, 0x10EAD, 0x110BB, 0x110BC, 0x11174, 0x11175, 0x111CD, 0x111DB, 0x112A9, 0x113D4, 0x113D5, 0x113D7, 0x113D8, 0x1145A, 0x1145B, 0x1145D, 0x114C6, 0x116B9, 0x1183B, 0x119E2, 0x11BE1, 0x11C70, 0x11C71, 0x11EF7, 0x11EF8, 0x11FFF, 0x12FF1, 0x12FF2, 0x16A6E, 0x16A6F, 0x16AF5, 0x16B44, 0x16FE2, 0x1BC9F, 0x1E5FF, 0x1E95E, 0x1E95F #endif }; #define NUM_PUNCT_CHAR ((int)(sizeof(punctCharTable)/sizeof(chr))) /* * Unicode: white space characters. */ static const crange spaceRangeTable[] = { {0x9, 0xD}, {0x2000, 0x200B} }; #define NUM_SPACE_RANGE ((int)(sizeof(spaceRangeTable)/sizeof(crange))) static const chr spaceCharTable[] = { 0x20, 0x85, 0xA0, 0x1680, 0x180E, 0x2028, 0x2029, 0x202F, 0x205F, 0x2060, 0x3000, 0xFEFF }; #define NUM_SPACE_CHAR ((int)(sizeof(spaceCharTable)/sizeof(chr))) /* * Unicode: lowercase characters. */ static const crange lowerRangeTable[] = { {0x61, 0x7A}, {0xDF, 0xF6}, {0xF8, 0xFF}, {0x17E, 0x180}, {0x199, 0x19B}, {0x1BD, 0x1BF}, {0x233, 0x239}, {0x24F, 0x293}, {0x295, 0x2AF}, {0x37B, 0x37D}, {0x3AC, 0x3CE}, {0x3D5, 0x3D7}, {0x3EF, 0x3F3}, {0x430, 0x45F}, {0x560, 0x588}, {0x10D0, 0x10FA}, {0x10FD, 0x10FF}, {0x13F8, 0x13FD}, {0x1C80, 0x1C88}, {0x1D00, 0x1D2B}, {0x1D6B, 0x1D77}, {0x1D79, 0x1D9A}, {0x1E95, 0x1E9D}, {0x1EFF, 0x1F07}, {0x1F10, 0x1F15}, {0x1F20, 0x1F27}, {0x1F30, 0x1F37}, {0x1F40, 0x1F45}, {0x1F50, 0x1F57}, {0x1F60, 0x1F67}, {0x1F70, 0x1F7D}, {0x1F80, 0x1F87}, {0x1F90, 0x1F97}, {0x1FA0, 0x1FA7}, {0x1FB0, 0x1FB4}, {0x1FC2, 0x1FC4}, {0x1FD0, 0x1FD3}, {0x1FE0, 0x1FE7}, {0x1FF2, 0x1FF4}, {0x2146, 0x2149}, {0x2C30, 0x2C5F}, {0x2C76, 0x2C7B}, {0x2D00, 0x2D25}, {0xA72F, 0xA731}, {0xA771, 0xA778}, {0xA793, 0xA795}, {0xAB30, 0xAB5A}, {0xAB60, 0xAB68}, {0xAB70, 0xABBF}, {0xFB00, 0xFB06}, {0xFB13, 0xFB17}, {0xFF41, 0xFF5A} #if CHRBITS > 16 ,{0x10428, 0x1044F}, {0x104D8, 0x104FB}, {0x10597, 0x105A1}, {0x105A3, 0x105B1}, {0x105B3, 0x105B9}, {0x10CC0, 0x10CF2}, {0x10D70, 0x10D85}, {0x118C0, 0x118DF}, {0x16E60, 0x16E7F}, {0x1D41A, 0x1D433}, {0x1D44E, 0x1D454}, {0x1D456, 0x1D467}, {0x1D482, 0x1D49B}, {0x1D4B6, 0x1D4B9}, {0x1D4BD, 0x1D4C3}, {0x1D4C5, 0x1D4CF}, {0x1D4EA, 0x1D503}, {0x1D51E, 0x1D537}, {0x1D552, 0x1D56B}, {0x1D586, 0x1D59F}, {0x1D5BA, 0x1D5D3}, {0x1D5EE, 0x1D607}, {0x1D622, 0x1D63B}, {0x1D656, 0x1D66F}, {0x1D68A, 0x1D6A5}, {0x1D6C2, 0x1D6DA}, {0x1D6DC, 0x1D6E1}, {0x1D6FC, 0x1D714}, {0x1D716, 0x1D71B}, {0x1D736, 0x1D74E}, {0x1D750, 0x1D755}, {0x1D770, 0x1D788}, {0x1D78A, 0x1D78F}, {0x1D7AA, 0x1D7C2}, {0x1D7C4, 0x1D7C9}, {0x1DF00, 0x1DF09}, {0x1DF0B, 0x1DF1E}, {0x1DF25, 0x1DF2A}, {0x1E922, 0x1E943} #endif }; #define NUM_LOWER_RANGE ((int)(sizeof(lowerRangeTable)/sizeof(crange))) static const chr lowerCharTable[] = { 0xB5, 0x101, 0x103, 0x105, 0x107, 0x109, 0x10B, 0x10D, 0x10F, 0x111, 0x113, 0x115, 0x117, 0x119, 0x11B, 0x11D, 0x11F, 0x121, 0x123, 0x125, 0x127, 0x129, 0x12B, 0x12D, 0x12F, 0x131, 0x133, 0x135, 0x137, 0x138, 0x13A, 0x13C, 0x13E, 0x140, 0x142, 0x144, 0x146, 0x148, 0x149, 0x14B, 0x14D, 0x14F, 0x151, 0x153, 0x155, 0x157, 0x159, 0x15B, 0x15D, 0x15F, 0x161, 0x163, 0x165, 0x167, 0x169, 0x16B, 0x16D, 0x16F, 0x171, 0x173, 0x175, 0x177, 0x17A, 0x17C, 0x183, 0x185, 0x188, 0x18C, 0x18D, 0x192, 0x195, 0x19E, 0x1A1, 0x1A3, 0x1A5, 0x1A8, 0x1AA, 0x1AB, 0x1AD, 0x1B0, 0x1B4, 0x1B6, 0x1B9, 0x1BA, 0x1C6, 0x1C9, 0x1CC, 0x1CE, 0x1D0, 0x1D2, 0x1D4, 0x1D6, 0x1D8, 0x1DA, 0x1DC, 0x1DD, 0x1DF, 0x1E1, 0x1E3, 0x1E5, 0x1E7, 0x1E9, 0x1EB, 0x1ED, 0x1EF, 0x1F0, 0x1F3, 0x1F5, 0x1F9, 0x1FB, 0x1FD, 0x1FF, 0x201, 0x203, 0x205, 0x207, 0x209, 0x20B, 0x20D, 0x20F, 0x211, 0x213, 0x215, 0x217, 0x219, 0x21B, 0x21D, 0x21F, 0x221, 0x223, 0x225, 0x227, 0x229, 0x22B, 0x22D, 0x22F, 0x231, 0x23C, 0x23F, 0x240, 0x242, 0x247, 0x249, 0x24B, 0x24D, 0x371, 0x373, 0x377, 0x390, 0x3D0, 0x3D1, 0x3D9, 0x3DB, 0x3DD, 0x3DF, 0x3E1, 0x3E3, 0x3E5, 0x3E7, 0x3E9, 0x3EB, 0x3ED, 0x3F5, 0x3F8, 0x3FB, 0x3FC, 0x461, 0x463, 0x465, 0x467, 0x469, 0x46B, 0x46D, 0x46F, 0x471, 0x473, 0x475, 0x477, 0x479, 0x47B, 0x47D, 0x47F, 0x481, 0x48B, 0x48D, 0x48F, 0x491, 0x493, 0x495, 0x497, 0x499, 0x49B, 0x49D, 0x49F, 0x4A1, 0x4A3, 0x4A5, 0x4A7, 0x4A9, 0x4AB, 0x4AD, 0x4AF, 0x4B1, 0x4B3, 0x4B5, 0x4B7, 0x4B9, 0x4BB, 0x4BD, 0x4BF, 0x4C2, 0x4C4, 0x4C6, 0x4C8, 0x4CA, 0x4CC, 0x4CE, 0x4CF, 0x4D1, 0x4D3, 0x4D5, 0x4D7, 0x4D9, 0x4DB, 0x4DD, 0x4DF, 0x4E1, 0x4E3, 0x4E5, 0x4E7, 0x4E9, 0x4EB, 0x4ED, 0x4EF, 0x4F1, 0x4F3, 0x4F5, 0x4F7, 0x4F9, 0x4FB, 0x4FD, 0x4FF, 0x501, 0x503, 0x505, 0x507, 0x509, 0x50B, 0x50D, 0x50F, 0x511, 0x513, 0x515, 0x517, 0x519, 0x51B, 0x51D, 0x51F, 0x521, 0x523, 0x525, 0x527, 0x529, 0x52B, 0x52D, 0x52F, 0x1C8A, 0x1E01, 0x1E03, 0x1E05, 0x1E07, 0x1E09, 0x1E0B, 0x1E0D, 0x1E0F, 0x1E11, 0x1E13, 0x1E15, 0x1E17, 0x1E19, 0x1E1B, 0x1E1D, 0x1E1F, 0x1E21, 0x1E23, 0x1E25, 0x1E27, 0x1E29, 0x1E2B, 0x1E2D, 0x1E2F, 0x1E31, 0x1E33, 0x1E35, 0x1E37, 0x1E39, 0x1E3B, 0x1E3D, 0x1E3F, 0x1E41, 0x1E43, 0x1E45, 0x1E47, 0x1E49, 0x1E4B, 0x1E4D, 0x1E4F, 0x1E51, 0x1E53, 0x1E55, 0x1E57, 0x1E59, 0x1E5B, 0x1E5D, 0x1E5F, 0x1E61, 0x1E63, 0x1E65, 0x1E67, 0x1E69, 0x1E6B, 0x1E6D, 0x1E6F, 0x1E71, 0x1E73, 0x1E75, 0x1E77, 0x1E79, 0x1E7B, 0x1E7D, 0x1E7F, 0x1E81, 0x1E83, 0x1E85, 0x1E87, 0x1E89, 0x1E8B, 0x1E8D, 0x1E8F, 0x1E91, 0x1E93, 0x1E9F, 0x1EA1, 0x1EA3, 0x1EA5, 0x1EA7, 0x1EA9, 0x1EAB, 0x1EAD, 0x1EAF, 0x1EB1, 0x1EB3, 0x1EB5, 0x1EB7, 0x1EB9, 0x1EBB, 0x1EBD, 0x1EBF, 0x1EC1, 0x1EC3, 0x1EC5, 0x1EC7, 0x1EC9, 0x1ECB, 0x1ECD, 0x1ECF, 0x1ED1, 0x1ED3, 0x1ED5, 0x1ED7, 0x1ED9, 0x1EDB, 0x1EDD, 0x1EDF, 0x1EE1, 0x1EE3, 0x1EE5, 0x1EE7, 0x1EE9, 0x1EEB, 0x1EED, 0x1EEF, 0x1EF1, 0x1EF3, 0x1EF5, 0x1EF7, 0x1EF9, 0x1EFB, 0x1EFD, 0x1FB6, 0x1FB7, 0x1FBE, 0x1FC6, 0x1FC7, 0x1FD6, 0x1FD7, 0x1FF6, 0x1FF7, 0x210A, 0x210E, 0x210F, 0x2113, 0x212F, 0x2134, 0x2139, 0x213C, 0x213D, 0x214E, 0x2184, 0x2C61, 0x2C65, 0x2C66, 0x2C68, 0x2C6A, 0x2C6C, 0x2C71, 0x2C73, 0x2C74, 0x2C81, 0x2C83, 0x2C85, 0x2C87, 0x2C89, 0x2C8B, 0x2C8D, 0x2C8F, 0x2C91, 0x2C93, 0x2C95, 0x2C97, 0x2C99, 0x2C9B, 0x2C9D, 0x2C9F, 0x2CA1, 0x2CA3, 0x2CA5, 0x2CA7, 0x2CA9, 0x2CAB, 0x2CAD, 0x2CAF, 0x2CB1, 0x2CB3, 0x2CB5, 0x2CB7, 0x2CB9, 0x2CBB, 0x2CBD, 0x2CBF, 0x2CC1, 0x2CC3, 0x2CC5, 0x2CC7, 0x2CC9, 0x2CCB, 0x2CCD, 0x2CCF, 0x2CD1, 0x2CD3, 0x2CD5, 0x2CD7, 0x2CD9, 0x2CDB, 0x2CDD, 0x2CDF, 0x2CE1, 0x2CE3, 0x2CE4, 0x2CEC, 0x2CEE, 0x2CF3, 0x2D27, 0x2D2D, 0xA641, 0xA643, 0xA645, 0xA647, 0xA649, 0xA64B, 0xA64D, 0xA64F, 0xA651, 0xA653, 0xA655, 0xA657, 0xA659, 0xA65B, 0xA65D, 0xA65F, 0xA661, 0xA663, 0xA665, 0xA667, 0xA669, 0xA66B, 0xA66D, 0xA681, 0xA683, 0xA685, 0xA687, 0xA689, 0xA68B, 0xA68D, 0xA68F, 0xA691, 0xA693, 0xA695, 0xA697, 0xA699, 0xA69B, 0xA723, 0xA725, 0xA727, 0xA729, 0xA72B, 0xA72D, 0xA733, 0xA735, 0xA737, 0xA739, 0xA73B, 0xA73D, 0xA73F, 0xA741, 0xA743, 0xA745, 0xA747, 0xA749, 0xA74B, 0xA74D, 0xA74F, 0xA751, 0xA753, 0xA755, 0xA757, 0xA759, 0xA75B, 0xA75D, 0xA75F, 0xA761, 0xA763, 0xA765, 0xA767, 0xA769, 0xA76B, 0xA76D, 0xA76F, 0xA77A, 0xA77C, 0xA77F, 0xA781, 0xA783, 0xA785, 0xA787, 0xA78C, 0xA78E, 0xA791, 0xA797, 0xA799, 0xA79B, 0xA79D, 0xA79F, 0xA7A1, 0xA7A3, 0xA7A5, 0xA7A7, 0xA7A9, 0xA7AF, 0xA7B5, 0xA7B7, 0xA7B9, 0xA7BB, 0xA7BD, 0xA7BF, 0xA7C1, 0xA7C3, 0xA7C8, 0xA7CA, 0xA7CD, 0xA7D1, 0xA7D3, 0xA7D5, 0xA7D7, 0xA7D9, 0xA7DB, 0xA7F6, 0xA7FA #if CHRBITS > 16 ,0x105BB, 0x105BC, 0x1D4BB, 0x1D7CB #endif }; #define NUM_LOWER_CHAR ((int)(sizeof(lowerCharTable)/sizeof(chr))) /* * Unicode: uppercase characters. */ static const crange upperRangeTable[] = { {0x41, 0x5A}, {0xC0, 0xD6}, {0xD8, 0xDE}, {0x189, 0x18B}, {0x18E, 0x191}, {0x196, 0x198}, {0x1B1, 0x1B3}, {0x1F6, 0x1F8}, {0x243, 0x246}, {0x388, 0x38A}, {0x391, 0x3A1}, {0x3A3, 0x3AB}, {0x3D2, 0x3D4}, {0x3FD, 0x42F}, {0x531, 0x556}, {0x10A0, 0x10C5}, {0x13A0, 0x13F5}, {0x1C90, 0x1CBA}, {0x1CBD, 0x1CBF}, {0x1F08, 0x1F0F}, {0x1F18, 0x1F1D}, {0x1F28, 0x1F2F}, {0x1F38, 0x1F3F}, {0x1F48, 0x1F4D}, {0x1F68, 0x1F6F}, {0x1FB8, 0x1FBB}, {0x1FC8, 0x1FCB}, {0x1FD8, 0x1FDB}, {0x1FE8, 0x1FEC}, {0x1FF8, 0x1FFB}, {0x210B, 0x210D}, {0x2110, 0x2112}, {0x2119, 0x211D}, {0x212A, 0x212D}, {0x2130, 0x2133}, {0x2C00, 0x2C2F}, {0x2C62, 0x2C64}, {0x2C6D, 0x2C70}, {0x2C7E, 0x2C80}, {0xA7AA, 0xA7AE}, {0xA7B0, 0xA7B4}, {0xA7C4, 0xA7C7}, {0xFF21, 0xFF3A} #if CHRBITS > 16 ,{0x10400, 0x10427}, {0x104B0, 0x104D3}, {0x10570, 0x1057A}, {0x1057C, 0x1058A}, {0x1058C, 0x10592}, {0x10C80, 0x10CB2}, {0x10D50, 0x10D65}, {0x118A0, 0x118BF}, {0x16E40, 0x16E5F}, {0x1D400, 0x1D419}, {0x1D434, 0x1D44D}, {0x1D468, 0x1D481}, {0x1D4A9, 0x1D4AC}, {0x1D4AE, 0x1D4B5}, {0x1D4D0, 0x1D4E9}, {0x1D507, 0x1D50A}, {0x1D50D, 0x1D514}, {0x1D516, 0x1D51C}, {0x1D53B, 0x1D53E}, {0x1D540, 0x1D544}, {0x1D54A, 0x1D550}, {0x1D56C, 0x1D585}, {0x1D5A0, 0x1D5B9}, {0x1D5D4, 0x1D5ED}, {0x1D608, 0x1D621}, {0x1D63C, 0x1D655}, {0x1D670, 0x1D689}, {0x1D6A8, 0x1D6C0}, {0x1D6E2, 0x1D6FA}, {0x1D71C, 0x1D734}, {0x1D756, 0x1D76E}, {0x1D790, 0x1D7A8}, {0x1E900, 0x1E921} #endif }; #define NUM_UPPER_RANGE ((int)(sizeof(upperRangeTable)/sizeof(crange))) static const chr upperCharTable[] = { 0x100, 0x102, 0x104, 0x106, 0x108, 0x10A, 0x10C, 0x10E, 0x110, 0x112, 0x114, 0x116, 0x118, 0x11A, 0x11C, 0x11E, 0x120, 0x122, 0x124, 0x126, 0x128, 0x12A, 0x12C, 0x12E, 0x130, 0x132, 0x134, 0x136, 0x139, 0x13B, 0x13D, 0x13F, 0x141, 0x143, 0x145, 0x147, 0x14A, 0x14C, 0x14E, 0x150, 0x152, 0x154, 0x156, 0x158, 0x15A, 0x15C, 0x15E, 0x160, 0x162, 0x164, 0x166, 0x168, 0x16A, 0x16C, 0x16E, 0x170, 0x172, 0x174, 0x176, 0x178, 0x179, 0x17B, 0x17D, 0x181, 0x182, 0x184, 0x186, 0x187, 0x193, 0x194, 0x19C, 0x19D, 0x19F, 0x1A0, 0x1A2, 0x1A4, 0x1A6, 0x1A7, 0x1A9, 0x1AC, 0x1AE, 0x1AF, 0x1B5, 0x1B7, 0x1B8, 0x1BC, 0x1C4, 0x1C7, 0x1CA, 0x1CD, 0x1CF, 0x1D1, 0x1D3, 0x1D5, 0x1D7, 0x1D9, 0x1DB, 0x1DE, 0x1E0, 0x1E2, 0x1E4, 0x1E6, 0x1E8, 0x1EA, 0x1EC, 0x1EE, 0x1F1, 0x1F4, 0x1FA, 0x1FC, 0x1FE, 0x200, 0x202, 0x204, 0x206, 0x208, 0x20A, 0x20C, 0x20E, 0x210, 0x212, 0x214, 0x216, 0x218, 0x21A, 0x21C, 0x21E, 0x220, 0x222, 0x224, 0x226, 0x228, 0x22A, 0x22C, 0x22E, 0x230, 0x232, 0x23A, 0x23B, 0x23D, 0x23E, 0x241, 0x248, 0x24A, 0x24C, 0x24E, 0x370, 0x372, 0x376, 0x37F, 0x386, 0x38C, 0x38E, 0x38F, 0x3CF, 0x3D8, 0x3DA, 0x3DC, 0x3DE, 0x3E0, 0x3E2, 0x3E4, 0x3E6, 0x3E8, 0x3EA, 0x3EC, 0x3EE, 0x3F4, 0x3F7, 0x3F9, 0x3FA, 0x460, 0x462, 0x464, 0x466, 0x468, 0x46A, 0x46C, 0x46E, 0x470, 0x472, 0x474, 0x476, 0x478, 0x47A, 0x47C, 0x47E, 0x480, 0x48A, 0x48C, 0x48E, 0x490, 0x492, 0x494, 0x496, 0x498, 0x49A, 0x49C, 0x49E, 0x4A0, 0x4A2, 0x4A4, 0x4A6, 0x4A8, 0x4AA, 0x4AC, 0x4AE, 0x4B0, 0x4B2, 0x4B4, 0x4B6, 0x4B8, 0x4BA, 0x4BC, 0x4BE, 0x4C0, 0x4C1, 0x4C3, 0x4C5, 0x4C7, 0x4C9, 0x4CB, 0x4CD, 0x4D0, 0x4D2, 0x4D4, 0x4D6, 0x4D8, 0x4DA, 0x4DC, 0x4DE, 0x4E0, 0x4E2, 0x4E4, 0x4E6, 0x4E8, 0x4EA, 0x4EC, 0x4EE, 0x4F0, 0x4F2, 0x4F4, 0x4F6, 0x4F8, 0x4FA, 0x4FC, 0x4FE, 0x500, 0x502, 0x504, 0x506, 0x508, 0x50A, 0x50C, 0x50E, 0x510, 0x512, 0x514, 0x516, 0x518, 0x51A, 0x51C, 0x51E, 0x520, 0x522, 0x524, 0x526, 0x528, 0x52A, 0x52C, 0x52E, 0x10C7, 0x10CD, 0x1C89, 0x1E00, 0x1E02, 0x1E04, 0x1E06, 0x1E08, 0x1E0A, 0x1E0C, 0x1E0E, 0x1E10, 0x1E12, 0x1E14, 0x1E16, 0x1E18, 0x1E1A, 0x1E1C, 0x1E1E, 0x1E20, 0x1E22, 0x1E24, 0x1E26, 0x1E28, 0x1E2A, 0x1E2C, 0x1E2E, 0x1E30, 0x1E32, 0x1E34, 0x1E36, 0x1E38, 0x1E3A, 0x1E3C, 0x1E3E, 0x1E40, 0x1E42, 0x1E44, 0x1E46, 0x1E48, 0x1E4A, 0x1E4C, 0x1E4E, 0x1E50, 0x1E52, 0x1E54, 0x1E56, 0x1E58, 0x1E5A, 0x1E5C, 0x1E5E, 0x1E60, 0x1E62, 0x1E64, 0x1E66, 0x1E68, 0x1E6A, 0x1E6C, 0x1E6E, 0x1E70, 0x1E72, 0x1E74, 0x1E76, 0x1E78, 0x1E7A, 0x1E7C, 0x1E7E, 0x1E80, 0x1E82, 0x1E84, 0x1E86, 0x1E88, 0x1E8A, 0x1E8C, 0x1E8E, 0x1E90, 0x1E92, 0x1E94, 0x1E9E, 0x1EA0, 0x1EA2, 0x1EA4, 0x1EA6, 0x1EA8, 0x1EAA, 0x1EAC, 0x1EAE, 0x1EB0, 0x1EB2, 0x1EB4, 0x1EB6, 0x1EB8, 0x1EBA, 0x1EBC, 0x1EBE, 0x1EC0, 0x1EC2, 0x1EC4, 0x1EC6, 0x1EC8, 0x1ECA, 0x1ECC, 0x1ECE, 0x1ED0, 0x1ED2, 0x1ED4, 0x1ED6, 0x1ED8, 0x1EDA, 0x1EDC, 0x1EDE, 0x1EE0, 0x1EE2, 0x1EE4, 0x1EE6, 0x1EE8, 0x1EEA, 0x1EEC, 0x1EEE, 0x1EF0, 0x1EF2, 0x1EF4, 0x1EF6, 0x1EF8, 0x1EFA, 0x1EFC, 0x1EFE, 0x1F59, 0x1F5B, 0x1F5D, 0x1F5F, 0x2102, 0x2107, 0x2115, 0x2124, 0x2126, 0x2128, 0x213E, 0x213F, 0x2145, 0x2183, 0x2C60, 0x2C67, 0x2C69, 0x2C6B, 0x2C72, 0x2C75, 0x2C82, 0x2C84, 0x2C86, 0x2C88, 0x2C8A, 0x2C8C, 0x2C8E, 0x2C90, 0x2C92, 0x2C94, 0x2C96, 0x2C98, 0x2C9A, 0x2C9C, 0x2C9E, 0x2CA0, 0x2CA2, 0x2CA4, 0x2CA6, 0x2CA8, 0x2CAA, 0x2CAC, 0x2CAE, 0x2CB0, 0x2CB2, 0x2CB4, 0x2CB6, 0x2CB8, 0x2CBA, 0x2CBC, 0x2CBE, 0x2CC0, 0x2CC2, 0x2CC4, 0x2CC6, 0x2CC8, 0x2CCA, 0x2CCC, 0x2CCE, 0x2CD0, 0x2CD2, 0x2CD4, 0x2CD6, 0x2CD8, 0x2CDA, 0x2CDC, 0x2CDE, 0x2CE0, 0x2CE2, 0x2CEB, 0x2CED, 0x2CF2, 0xA640, 0xA642, 0xA644, 0xA646, 0xA648, 0xA64A, 0xA64C, 0xA64E, 0xA650, 0xA652, 0xA654, 0xA656, 0xA658, 0xA65A, 0xA65C, 0xA65E, 0xA660, 0xA662, 0xA664, 0xA666, 0xA668, 0xA66A, 0xA66C, 0xA680, 0xA682, 0xA684, 0xA686, 0xA688, 0xA68A, 0xA68C, 0xA68E, 0xA690, 0xA692, 0xA694, 0xA696, 0xA698, 0xA69A, 0xA722, 0xA724, 0xA726, 0xA728, 0xA72A, 0xA72C, 0xA72E, 0xA732, 0xA734, 0xA736, 0xA738, 0xA73A, 0xA73C, 0xA73E, 0xA740, 0xA742, 0xA744, 0xA746, 0xA748, 0xA74A, 0xA74C, 0xA74E, 0xA750, 0xA752, 0xA754, 0xA756, 0xA758, 0xA75A, 0xA75C, 0xA75E, 0xA760, 0xA762, 0xA764, 0xA766, 0xA768, 0xA76A, 0xA76C, 0xA76E, 0xA779, 0xA77B, 0xA77D, 0xA77E, 0xA780, 0xA782, 0xA784, 0xA786, 0xA78B, 0xA78D, 0xA790, 0xA792, 0xA796, 0xA798, 0xA79A, 0xA79C, 0xA79E, 0xA7A0, 0xA7A2, 0xA7A4, 0xA7A6, 0xA7A8, 0xA7B6, 0xA7B8, 0xA7BA, 0xA7BC, 0xA7BE, 0xA7C0, 0xA7C2, 0xA7C9, 0xA7CB, 0xA7CC, 0xA7D0, 0xA7D6, 0xA7D8, 0xA7DA, 0xA7DC, 0xA7F5 #if CHRBITS > 16 ,0x10594, 0x10595, 0x1D49C, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D504, 0x1D505, 0x1D538, 0x1D539, 0x1D546, 0x1D7CA #endif }; #define NUM_UPPER_CHAR ((int)(sizeof(upperCharTable)/sizeof(chr))) /* * Unicode: unicode print characters excluding space. */ static const crange graphRangeTable[] = { {0x21, 0x7E}, {0xA1, 0xAC}, {0xAE, 0x377}, {0x37A, 0x37F}, {0x384, 0x38A}, {0x38E, 0x3A1}, {0x3A3, 0x52F}, {0x531, 0x556}, {0x559, 0x58A}, {0x58D, 0x58F}, {0x591, 0x5C7}, {0x5D0, 0x5EA}, {0x5EF, 0x5F4}, {0x606, 0x61B}, {0x61D, 0x6DC}, {0x6DE, 0x70D}, {0x710, 0x74A}, {0x74D, 0x7B1}, {0x7C0, 0x7FA}, {0x7FD, 0x82D}, {0x830, 0x83E}, {0x840, 0x85B}, {0x860, 0x86A}, {0x870, 0x88E}, {0x897, 0x8E1}, {0x8E3, 0x983}, {0x985, 0x98C}, {0x993, 0x9A8}, {0x9AA, 0x9B0}, {0x9B6, 0x9B9}, {0x9BC, 0x9C4}, {0x9CB, 0x9CE}, {0x9DF, 0x9E3}, {0x9E6, 0x9FE}, {0xA01, 0xA03}, {0xA05, 0xA0A}, {0xA13, 0xA28}, {0xA2A, 0xA30}, {0xA3E, 0xA42}, {0xA4B, 0xA4D}, {0xA59, 0xA5C}, {0xA66, 0xA76}, {0xA81, 0xA83}, {0xA85, 0xA8D}, {0xA8F, 0xA91}, {0xA93, 0xAA8}, {0xAAA, 0xAB0}, {0xAB5, 0xAB9}, {0xABC, 0xAC5}, {0xAC7, 0xAC9}, {0xACB, 0xACD}, {0xAE0, 0xAE3}, {0xAE6, 0xAF1}, {0xAF9, 0xAFF}, {0xB01, 0xB03}, {0xB05, 0xB0C}, {0xB13, 0xB28}, {0xB2A, 0xB30}, {0xB35, 0xB39}, {0xB3C, 0xB44}, {0xB4B, 0xB4D}, {0xB55, 0xB57}, {0xB5F, 0xB63}, {0xB66, 0xB77}, {0xB85, 0xB8A}, {0xB8E, 0xB90}, {0xB92, 0xB95}, {0xBA8, 0xBAA}, {0xBAE, 0xBB9}, {0xBBE, 0xBC2}, {0xBC6, 0xBC8}, {0xBCA, 0xBCD}, {0xBE6, 0xBFA}, {0xC00, 0xC0C}, {0xC0E, 0xC10}, {0xC12, 0xC28}, {0xC2A, 0xC39}, {0xC3C, 0xC44}, {0xC46, 0xC48}, {0xC4A, 0xC4D}, {0xC58, 0xC5A}, {0xC60, 0xC63}, {0xC66, 0xC6F}, {0xC77, 0xC8C}, {0xC8E, 0xC90}, {0xC92, 0xCA8}, {0xCAA, 0xCB3}, {0xCB5, 0xCB9}, {0xCBC, 0xCC4}, {0xCC6, 0xCC8}, {0xCCA, 0xCCD}, {0xCE0, 0xCE3}, {0xCE6, 0xCEF}, {0xCF1, 0xCF3}, {0xD00, 0xD0C}, {0xD0E, 0xD10}, {0xD12, 0xD44}, {0xD46, 0xD48}, {0xD4A, 0xD4F}, {0xD54, 0xD63}, {0xD66, 0xD7F}, {0xD81, 0xD83}, {0xD85, 0xD96}, {0xD9A, 0xDB1}, {0xDB3, 0xDBB}, {0xDC0, 0xDC6}, {0xDCF, 0xDD4}, {0xDD8, 0xDDF}, {0xDE6, 0xDEF}, {0xDF2, 0xDF4}, {0xE01, 0xE3A}, {0xE3F, 0xE5B}, {0xE86, 0xE8A}, {0xE8C, 0xEA3}, {0xEA7, 0xEBD}, {0xEC0, 0xEC4}, {0xEC8, 0xECE}, {0xED0, 0xED9}, {0xEDC, 0xEDF}, {0xF00, 0xF47}, {0xF49, 0xF6C}, {0xF71, 0xF97}, {0xF99, 0xFBC}, {0xFBE, 0xFCC}, {0xFCE, 0xFDA}, {0x1000, 0x10C5}, {0x10D0, 0x1248}, {0x124A, 0x124D}, {0x1250, 0x1256}, {0x125A, 0x125D}, {0x1260, 0x1288}, {0x128A, 0x128D}, {0x1290, 0x12B0}, {0x12B2, 0x12B5}, {0x12B8, 0x12BE}, {0x12C2, 0x12C5}, {0x12C8, 0x12D6}, {0x12D8, 0x1310}, {0x1312, 0x1315}, {0x1318, 0x135A}, {0x135D, 0x137C}, {0x1380, 0x1399}, {0x13A0, 0x13F5}, {0x13F8, 0x13FD}, {0x1400, 0x167F}, {0x1681, 0x169C}, {0x16A0, 0x16F8}, {0x1700, 0x1715}, {0x171F, 0x1736}, {0x1740, 0x1753}, {0x1760, 0x176C}, {0x176E, 0x1770}, {0x1780, 0x17DD}, {0x17E0, 0x17E9}, {0x17F0, 0x17F9}, {0x1800, 0x180D}, {0x180F, 0x1819}, {0x1820, 0x1878}, {0x1880, 0x18AA}, {0x18B0, 0x18F5}, {0x1900, 0x191E}, {0x1920, 0x192B}, {0x1930, 0x193B}, {0x1944, 0x196D}, {0x1970, 0x1974}, {0x1980, 0x19AB}, {0x19B0, 0x19C9}, {0x19D0, 0x19DA}, {0x19DE, 0x1A1B}, {0x1A1E, 0x1A5E}, {0x1A60, 0x1A7C}, {0x1A7F, 0x1A89}, {0x1A90, 0x1A99}, {0x1AA0, 0x1AAD}, {0x1AB0, 0x1ACE}, {0x1B00, 0x1B4C}, {0x1B4E, 0x1BF3}, {0x1BFC, 0x1C37}, {0x1C3B, 0x1C49}, {0x1C4D, 0x1C8A}, {0x1C90, 0x1CBA}, {0x1CBD, 0x1CC7}, {0x1CD0, 0x1CFA}, {0x1D00, 0x1F15}, {0x1F18, 0x1F1D}, {0x1F20, 0x1F45}, {0x1F48, 0x1F4D}, {0x1F50, 0x1F57}, {0x1F5F, 0x1F7D}, {0x1F80, 0x1FB4}, {0x1FB6, 0x1FC4}, {0x1FC6, 0x1FD3}, {0x1FD6, 0x1FDB}, {0x1FDD, 0x1FEF}, {0x1FF2, 0x1FF4}, {0x1FF6, 0x1FFE}, {0x2010, 0x2027}, {0x2030, 0x205E}, {0x2074, 0x208E}, {0x2090, 0x209C}, {0x20A0, 0x20C0}, {0x20D0, 0x20F0}, {0x2100, 0x218B}, {0x2190, 0x2429}, {0x2440, 0x244A}, {0x2460, 0x2B73}, {0x2B76, 0x2B95}, {0x2B97, 0x2CF3}, {0x2CF9, 0x2D25}, {0x2D30, 0x2D67}, {0x2D7F, 0x2D96}, {0x2DA0, 0x2DA6}, {0x2DA8, 0x2DAE}, {0x2DB0, 0x2DB6}, {0x2DB8, 0x2DBE}, {0x2DC0, 0x2DC6}, {0x2DC8, 0x2DCE}, {0x2DD0, 0x2DD6}, {0x2DD8, 0x2DDE}, {0x2DE0, 0x2E5D}, {0x2E80, 0x2E99}, {0x2E9B, 0x2EF3}, {0x2F00, 0x2FD5}, {0x2FF0, 0x2FFF}, {0x3001, 0x303F}, {0x3041, 0x3096}, {0x3099, 0x30FF}, {0x3105, 0x312F}, {0x3131, 0x318E}, {0x3190, 0x31E5}, {0x31EF, 0x321E}, {0x3220, 0xA48C}, {0xA490, 0xA4C6}, {0xA4D0, 0xA62B}, {0xA640, 0xA6F7}, {0xA700, 0xA7CD}, {0xA7D5, 0xA7DC}, {0xA7F2, 0xA82C}, {0xA830, 0xA839}, {0xA840, 0xA877}, {0xA880, 0xA8C5}, {0xA8CE, 0xA8D9}, {0xA8E0, 0xA953}, {0xA95F, 0xA97C}, {0xA980, 0xA9CD}, {0xA9CF, 0xA9D9}, {0xA9DE, 0xA9FE}, {0xAA00, 0xAA36}, {0xAA40, 0xAA4D}, {0xAA50, 0xAA59}, {0xAA5C, 0xAAC2}, {0xAADB, 0xAAF6}, {0xAB01, 0xAB06}, {0xAB09, 0xAB0E}, {0xAB11, 0xAB16}, {0xAB20, 0xAB26}, {0xAB28, 0xAB2E}, {0xAB30, 0xAB6B}, {0xAB70, 0xABED}, {0xABF0, 0xABF9}, {0xAC00, 0xD7A3}, {0xD7B0, 0xD7C6}, {0xD7CB, 0xD7FB}, {0xF900, 0xFA6D}, {0xFA70, 0xFAD9}, {0xFB00, 0xFB06}, {0xFB13, 0xFB17}, {0xFB1D, 0xFB36}, {0xFB38, 0xFB3C}, {0xFB46, 0xFBC2}, {0xFBD3, 0xFD8F}, {0xFD92, 0xFDC7}, {0xFDF0, 0xFE19}, {0xFE20, 0xFE52}, {0xFE54, 0xFE66}, {0xFE68, 0xFE6B}, {0xFE70, 0xFE74}, {0xFE76, 0xFEFC}, {0xFF01, 0xFFBE}, {0xFFC2, 0xFFC7}, {0xFFCA, 0xFFCF}, {0xFFD2, 0xFFD7}, {0xFFDA, 0xFFDC}, {0xFFE0, 0xFFE6}, {0xFFE8, 0xFFEE} #if CHRBITS > 16 ,{0x10000, 0x1000B}, {0x1000D, 0x10026}, {0x10028, 0x1003A}, {0x1003F, 0x1004D}, {0x10050, 0x1005D}, {0x10080, 0x100FA}, {0x10100, 0x10102}, {0x10107, 0x10133}, {0x10137, 0x1018E}, {0x10190, 0x1019C}, {0x101D0, 0x101FD}, {0x10280, 0x1029C}, {0x102A0, 0x102D0}, {0x102E0, 0x102FB}, {0x10300, 0x10323}, {0x1032D, 0x1034A}, {0x10350, 0x1037A}, {0x10380, 0x1039D}, {0x1039F, 0x103C3}, {0x103C8, 0x103D5}, {0x10400, 0x1049D}, {0x104A0, 0x104A9}, {0x104B0, 0x104D3}, {0x104D8, 0x104FB}, {0x10500, 0x10527}, {0x10530, 0x10563}, {0x1056F, 0x1057A}, {0x1057C, 0x1058A}, {0x1058C, 0x10592}, {0x10597, 0x105A1}, {0x105A3, 0x105B1}, {0x105B3, 0x105B9}, {0x105C0, 0x105F3}, {0x10600, 0x10736}, {0x10740, 0x10755}, {0x10760, 0x10767}, {0x10780, 0x10785}, {0x10787, 0x107B0}, {0x107B2, 0x107BA}, {0x10800, 0x10805}, {0x1080A, 0x10835}, {0x1083F, 0x10855}, {0x10857, 0x1089E}, {0x108A7, 0x108AF}, {0x108E0, 0x108F2}, {0x108FB, 0x1091B}, {0x1091F, 0x10939}, {0x10980, 0x109B7}, {0x109BC, 0x109CF}, {0x109D2, 0x10A03}, {0x10A0C, 0x10A13}, {0x10A15, 0x10A17}, {0x10A19, 0x10A35}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A48}, {0x10A50, 0x10A58}, {0x10A60, 0x10A9F}, {0x10AC0, 0x10AE6}, {0x10AEB, 0x10AF6}, {0x10B00, 0x10B35}, {0x10B39, 0x10B55}, {0x10B58, 0x10B72}, {0x10B78, 0x10B91}, {0x10B99, 0x10B9C}, {0x10BA9, 0x10BAF}, {0x10C00, 0x10C48}, {0x10C80, 0x10CB2}, {0x10CC0, 0x10CF2}, {0x10CFA, 0x10D27}, {0x10D30, 0x10D39}, {0x10D40, 0x10D65}, {0x10D69, 0x10D85}, {0x10E60, 0x10E7E}, {0x10E80, 0x10EA9}, {0x10EAB, 0x10EAD}, {0x10EC2, 0x10EC4}, {0x10EFC, 0x10F27}, {0x10F30, 0x10F59}, {0x10F70, 0x10F89}, {0x10FB0, 0x10FCB}, {0x10FE0, 0x10FF6}, {0x11000, 0x1104D}, {0x11052, 0x11075}, {0x1107F, 0x110BC}, {0x110BE, 0x110C2}, {0x110D0, 0x110E8}, {0x110F0, 0x110F9}, {0x11100, 0x11134}, {0x11136, 0x11147}, {0x11150, 0x11176}, {0x11180, 0x111DF}, {0x111E1, 0x111F4}, {0x11200, 0x11211}, {0x11213, 0x11241}, {0x11280, 0x11286}, {0x1128A, 0x1128D}, {0x1128F, 0x1129D}, {0x1129F, 0x112A9}, {0x112B0, 0x112EA}, {0x112F0, 0x112F9}, {0x11300, 0x11303}, {0x11305, 0x1130C}, {0x11313, 0x11328}, {0x1132A, 0x11330}, {0x11335, 0x11339}, {0x1133B, 0x11344}, {0x1134B, 0x1134D}, {0x1135D, 0x11363}, {0x11366, 0x1136C}, {0x11370, 0x11374}, {0x11380, 0x11389}, {0x11390, 0x113B5}, {0x113B7, 0x113C0}, {0x113C7, 0x113CA}, {0x113CC, 0x113D5}, {0x11400, 0x1145B}, {0x1145D, 0x11461}, {0x11480, 0x114C7}, {0x114D0, 0x114D9}, {0x11580, 0x115B5}, {0x115B8, 0x115DD}, {0x11600, 0x11644}, {0x11650, 0x11659}, {0x11660, 0x1166C}, {0x11680, 0x116B9}, {0x116C0, 0x116C9}, {0x116D0, 0x116E3}, {0x11700, 0x1171A}, {0x1171D, 0x1172B}, {0x11730, 0x11746}, {0x11800, 0x1183B}, {0x118A0, 0x118F2}, {0x118FF, 0x11906}, {0x1190C, 0x11913}, {0x11918, 0x11935}, {0x1193B, 0x11946}, {0x11950, 0x11959}, {0x119A0, 0x119A7}, {0x119AA, 0x119D7}, {0x119DA, 0x119E4}, {0x11A00, 0x11A47}, {0x11A50, 0x11AA2}, {0x11AB0, 0x11AF8}, {0x11B00, 0x11B09}, {0x11BC0, 0x11BE1}, {0x11BF0, 0x11BF9}, {0x11C00, 0x11C08}, {0x11C0A, 0x11C36}, {0x11C38, 0x11C45}, {0x11C50, 0x11C6C}, {0x11C70, 0x11C8F}, {0x11C92, 0x11CA7}, {0x11CA9, 0x11CB6}, {0x11D00, 0x11D06}, {0x11D0B, 0x11D36}, {0x11D3F, 0x11D47}, {0x11D50, 0x11D59}, {0x11D60, 0x11D65}, {0x11D6A, 0x11D8E}, {0x11D93, 0x11D98}, {0x11DA0, 0x11DA9}, {0x11EE0, 0x11EF8}, {0x11F00, 0x11F10}, {0x11F12, 0x11F3A}, {0x11F3E, 0x11F5A}, {0x11FC0, 0x11FF1}, {0x11FFF, 0x12399}, {0x12400, 0x1246E}, {0x12470, 0x12474}, {0x12480, 0x12543}, {0x12F90, 0x12FF2}, {0x13000, 0x1342F}, {0x13440, 0x13455}, {0x13460, 0x143FA}, {0x14400, 0x14646}, {0x16100, 0x16139}, {0x16800, 0x16A38}, {0x16A40, 0x16A5E}, {0x16A60, 0x16A69}, {0x16A6E, 0x16ABE}, {0x16AC0, 0x16AC9}, {0x16AD0, 0x16AED}, {0x16AF0, 0x16AF5}, {0x16B00, 0x16B45}, {0x16B50, 0x16B59}, {0x16B5B, 0x16B61}, {0x16B63, 0x16B77}, {0x16B7D, 0x16B8F}, {0x16D40, 0x16D79}, {0x16E40, 0x16E9A}, {0x16F00, 0x16F4A}, {0x16F4F, 0x16F87}, {0x16F8F, 0x16F9F}, {0x16FE0, 0x16FE4}, {0x17000, 0x187F7}, {0x18800, 0x18CD5}, {0x18CFF, 0x18D08}, {0x1AFF0, 0x1AFF3}, {0x1AFF5, 0x1AFFB}, {0x1B000, 0x1B122}, {0x1B150, 0x1B152}, {0x1B164, 0x1B167}, {0x1B170, 0x1B2FB}, {0x1BC00, 0x1BC6A}, {0x1BC70, 0x1BC7C}, {0x1BC80, 0x1BC88}, {0x1BC90, 0x1BC99}, {0x1BC9C, 0x1BC9F}, {0x1CC00, 0x1CCF9}, {0x1CD00, 0x1CEB3}, {0x1CF00, 0x1CF2D}, {0x1CF30, 0x1CF46}, {0x1CF50, 0x1CFC3}, {0x1D000, 0x1D0F5}, {0x1D100, 0x1D126}, {0x1D129, 0x1D172}, {0x1D17B, 0x1D1EA}, {0x1D200, 0x1D245}, {0x1D2C0, 0x1D2D3}, {0x1D2E0, 0x1D2F3}, {0x1D300, 0x1D356}, {0x1D360, 0x1D378}, {0x1D400, 0x1D454}, {0x1D456, 0x1D49C}, {0x1D4A9, 0x1D4AC}, {0x1D4AE, 0x1D4B9}, {0x1D4BD, 0x1D4C3}, {0x1D4C5, 0x1D505}, {0x1D507, 0x1D50A}, {0x1D50D, 0x1D514}, {0x1D516, 0x1D51C}, {0x1D51E, 0x1D539}, {0x1D53B, 0x1D53E}, {0x1D540, 0x1D544}, {0x1D54A, 0x1D550}, {0x1D552, 0x1D6A5}, {0x1D6A8, 0x1D7CB}, {0x1D7CE, 0x1DA8B}, {0x1DA9B, 0x1DA9F}, {0x1DAA1, 0x1DAAF}, {0x1DF00, 0x1DF1E}, {0x1DF25, 0x1DF2A}, {0x1E000, 0x1E006}, {0x1E008, 0x1E018}, {0x1E01B, 0x1E021}, {0x1E026, 0x1E02A}, {0x1E030, 0x1E06D}, {0x1E100, 0x1E12C}, {0x1E130, 0x1E13D}, {0x1E140, 0x1E149}, {0x1E290, 0x1E2AE}, {0x1E2C0, 0x1E2F9}, {0x1E4D0, 0x1E4F9}, {0x1E5D0, 0x1E5FA}, {0x1E7E0, 0x1E7E6}, {0x1E7E8, 0x1E7EB}, {0x1E7F0, 0x1E7FE}, {0x1E800, 0x1E8C4}, {0x1E8C7, 0x1E8D6}, {0x1E900, 0x1E94B}, {0x1E950, 0x1E959}, {0x1EC71, 0x1ECB4}, {0x1ED01, 0x1ED3D}, {0x1EE00, 0x1EE03}, {0x1EE05, 0x1EE1F}, {0x1EE29, 0x1EE32}, {0x1EE34, 0x1EE37}, {0x1EE4D, 0x1EE4F}, {0x1EE67, 0x1EE6A}, {0x1EE6C, 0x1EE72}, {0x1EE74, 0x1EE77}, {0x1EE79, 0x1EE7C}, {0x1EE80, 0x1EE89}, {0x1EE8B, 0x1EE9B}, {0x1EEA1, 0x1EEA3}, {0x1EEA5, 0x1EEA9}, {0x1EEAB, 0x1EEBB}, {0x1F000, 0x1F02B}, {0x1F030, 0x1F093}, {0x1F0A0, 0x1F0AE}, {0x1F0B1, 0x1F0BF}, {0x1F0C1, 0x1F0CF}, {0x1F0D1, 0x1F0F5}, {0x1F100, 0x1F1AD}, {0x1F1E6, 0x1F202}, {0x1F210, 0x1F23B}, {0x1F240, 0x1F248}, {0x1F260, 0x1F265}, {0x1F300, 0x1F6D7}, {0x1F6DC, 0x1F6EC}, {0x1F6F0, 0x1F6FC}, {0x1F700, 0x1F776}, {0x1F77B, 0x1F7D9}, {0x1F7E0, 0x1F7EB}, {0x1F800, 0x1F80B}, {0x1F810, 0x1F847}, {0x1F850, 0x1F859}, {0x1F860, 0x1F887}, {0x1F890, 0x1F8AD}, {0x1F8B0, 0x1F8BB}, {0x1F900, 0x1FA53}, {0x1FA60, 0x1FA6D}, {0x1FA70, 0x1FA7C}, {0x1FA80, 0x1FA89}, {0x1FA8F, 0x1FAC6}, {0x1FACE, 0x1FADC}, {0x1FADF, 0x1FAE9}, {0x1FAF0, 0x1FAF8}, {0x1FB00, 0x1FB92}, {0x1FB94, 0x1FBF9}, {0x20000, 0x2A6DF}, {0x2A700, 0x2B739}, {0x2B740, 0x2B81D}, {0x2B820, 0x2CEA1}, {0x2CEB0, 0x2EBE0}, {0x2EBF0, 0x2EE5D}, {0x2F800, 0x2FA1D}, {0x30000, 0x3134A}, {0x31350, 0x323AF}, {0xE0100, 0xE01EF} #endif }; #define NUM_GRAPH_RANGE ((int)(sizeof(graphRangeTable)/sizeof(crange))) static const chr graphCharTable[] = { 0x38C, 0x85E, 0x98F, 0x990, 0x9B2, 0x9C7, 0x9C8, 0x9D7, 0x9DC, 0x9DD, 0xA0F, 0xA10, 0xA32, 0xA33, 0xA35, 0xA36, 0xA38, 0xA39, 0xA3C, 0xA47, 0xA48, 0xA51, 0xA5E, 0xAB2, 0xAB3, 0xAD0, 0xB0F, 0xB10, 0xB32, 0xB33, 0xB47, 0xB48, 0xB5C, 0xB5D, 0xB82, 0xB83, 0xB99, 0xB9A, 0xB9C, 0xB9E, 0xB9F, 0xBA3, 0xBA4, 0xBD0, 0xBD7, 0xC55, 0xC56, 0xC5D, 0xCD5, 0xCD6, 0xCDD, 0xCDE, 0xDBD, 0xDCA, 0xDD6, 0xE81, 0xE82, 0xE84, 0xEA5, 0xEC6, 0x10C7, 0x10CD, 0x1258, 0x12C0, 0x1772, 0x1773, 0x1940, 0x1F59, 0x1F5B, 0x1F5D, 0x2070, 0x2071, 0x2D27, 0x2D2D, 0x2D6F, 0x2D70, 0xA7D0, 0xA7D1, 0xA7D3, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44, 0xFDCF, 0xFFFC, 0xFFFD #if CHRBITS > 16 ,0x1003C, 0x1003D, 0x101A0, 0x10594, 0x10595, 0x105BB, 0x105BC, 0x10808, 0x10837, 0x10838, 0x1083C, 0x108F4, 0x108F5, 0x1093F, 0x10A05, 0x10A06, 0x10D8E, 0x10D8F, 0x10EB0, 0x10EB1, 0x11288, 0x1130F, 0x11310, 0x11332, 0x11333, 0x11347, 0x11348, 0x11350, 0x11357, 0x1138B, 0x1138E, 0x113C2, 0x113C5, 0x113D7, 0x113D8, 0x113E1, 0x113E2, 0x11909, 0x11915, 0x11916, 0x11937, 0x11938, 0x11D08, 0x11D09, 0x11D3A, 0x11D3C, 0x11D3D, 0x11D67, 0x11D68, 0x11D90, 0x11D91, 0x11FB0, 0x16FF0, 0x16FF1, 0x1AFFD, 0x1AFFE, 0x1B132, 0x1B155, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D4BB, 0x1D546, 0x1E023, 0x1E024, 0x1E08F, 0x1E14E, 0x1E14F, 0x1E2FF, 0x1E5FF, 0x1E7ED, 0x1E7EE, 0x1E95E, 0x1E95F, 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE27, 0x1EE39, 0x1EE3B, 0x1EE42, 0x1EE47, 0x1EE49, 0x1EE4B, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE57, 0x1EE59, 0x1EE5B, 0x1EE5D, 0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE7E, 0x1EEF0, 0x1EEF1, 0x1F250, 0x1F251, 0x1F7F0, 0x1F8C0, 0x1F8C1 #endif }; #define NUM_GRAPH_CHAR ((int)(sizeof(graphCharTable)/sizeof(chr))) /* * End of auto-generated Unicode character ranges declarations. */ /* - element - map collating-element name to celt ^ static celt element(struct vars *, const chr *, const chr *); */ static celt element( struct vars *v, /* context */ const chr *startp, /* points to start of name */ const chr *endp) /* points just past end of name */ { const struct cname *cn; size_t len; Tcl_DString ds; const char *np; /* * Generic: one-chr names stand for themselves. */ assert(startp < endp); len = endp - startp; if (len == 1) { return *startp; } NOTE(REG_ULOCALE); /* * Search table. */ Tcl_DStringInit(&ds); np = Tcl_UniCharToUtfDString(startp, len, &ds); for (cn=cnames; cn->name!=NULL; cn++) { if (strlen(cn->name)==len && strncmp(cn->name, np, len)==0) { break; /* NOTE BREAK OUT */ } } Tcl_DStringFree(&ds); if (cn->name != NULL) { return CHR(cn->code); } /* * Couldn't find it. */ ERR(REG_ECOLLATE); return 0; } /* - range - supply cvec for a range, including legality check ^ static struct cvec *range(struct vars *, celt, celt, int); */ static struct cvec * range( struct vars *v, /* context */ celt a, /* range start */ celt b, /* range end, might equal a */ int cases) /* case-independent? */ { int nchrs; struct cvec *cv; celt c, lc, uc, tc; if (a != b && !before(a, b)) { ERR(REG_ERANGE); return NULL; } if (!cases) { /* easy version */ cv = getcvec(v, 0, 1); NOERRN(); addrange(cv, a, b); return cv; } /* * When case-independent, it's hard to decide when cvec ranges are usable, * so for now at least, we won't try. We allocate enough space for two * case variants plus a little extra for the two title case variants. */ nchrs = (b - a + 1)*2 + 4; cv = getcvec(v, nchrs, 0); NOERRN(); for (c=a; c<=b; c++) { addchr(cv, c); lc = Tcl_UniCharToLower(c); uc = Tcl_UniCharToUpper(c); tc = Tcl_UniCharToTitle(c); if (c != lc) { addchr(cv, lc); } if (c != uc) { addchr(cv, uc); } if (c != tc && tc != uc) { addchr(cv, tc); } } return cv; } /* - before - is celt x before celt y, for purposes of range legality? ^ static int before(celt, celt); */ static int /* predicate */ before( celt x, celt y) /* collating elements */ { if (x < y) { return 1; } return 0; } /* - eclass - supply cvec for an equivalence class * Must include case counterparts on request. ^ static struct cvec *eclass(struct vars *, celt, int); */ static struct cvec * eclass( struct vars *v, /* context */ celt c, /* Collating element representing the * equivalence class. */ int cases) /* all cases? */ { struct cvec *cv; /* * Crude fake equivalence class for testing. */ if ((v->cflags®_FAKE) && c == 'x') { cv = getcvec(v, 4, 0); addchr(cv, 'x'); addchr(cv, 'y'); if (cases) { addchr(cv, 'X'); addchr(cv, 'Y'); } return cv; } /* * Otherwise, none. */ if (cases) { return allcases(v, c); } cv = getcvec(v, 1, 0); assert(cv != NULL); addchr(cv, c); return cv; } /* - cclass - supply cvec for a character class * Must include case counterparts on request. ^ static struct cvec *cclass(struct vars *, const chr *, const chr *, int); */ static struct cvec * cclass( struct vars *v, /* context */ const chr *startp, /* where the name starts */ const chr *endp, /* just past the end of the name */ int cases) /* case-independent? */ { size_t len; struct cvec *cv = NULL; Tcl_DString ds; const char *np; const char *const *namePtr; int i; /* * The following arrays define the valid character class names. */ static const char *const classNames[] = { "alnum", "alpha", "ascii", "blank", "cntrl", "digit", "graph", "lower", "print", "punct", "space", "upper", "xdigit", NULL }; enum classes { CC_NULL = -1, CC_ALNUM, CC_ALPHA, CC_ASCII, CC_BLANK, CC_CNTRL, CC_DIGIT, CC_GRAPH, CC_LOWER, CC_PRINT, CC_PUNCT, CC_SPACE, CC_UPPER, CC_XDIGIT } index; /* * Extract the class name */ len = endp - startp; Tcl_DStringInit(&ds); np = Tcl_UniCharToUtfDString(startp, len, &ds); /* * Map the name to the corresponding enumerated value. */ index = CC_NULL; for (namePtr=classNames,i=0 ; *namePtr!=NULL ; namePtr++,i++) { if ((strlen(*namePtr) == len) && (strncmp(*namePtr, np, len) == 0)) { index = (enum classes)i; break; } } Tcl_DStringFree(&ds); /* * Remap lower and upper to alpha if the match is case insensitive. */ if (cases && ((index == CC_LOWER) || (index == CC_UPPER))) { index = CC_ALNUM; } /* * Now compute the character class contents. */ switch (index) { case CC_NULL: ERR(REG_ECTYPE); return NULL; case CC_ALNUM: cv = getcvec(v, NUM_ALPHA_CHAR, NUM_DIGIT_RANGE + NUM_ALPHA_RANGE); if (cv) { for (i=0 ; i 0; len--, x++, y++) { if ((*x!=*y) && (Tcl_UniCharToLower(*x) != Tcl_UniCharToLower(*y))) { return 1; } } return 0; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/regc_nfa.c0000644000175000017500000024511514726623136015112 0ustar sergeisergei/* * NFA utilities. * This file is #included by regcomp.c. * * Copyright © 1998, 1999 Henry Spencer. All rights reserved. * * Development of this software was funded, in part, by Cray Research Inc., * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics * Corporation, none of whom are responsible for the results. The author * thanks all of them. * * Redistribution and use in source and binary forms -- with or without * modification -- are permitted for any purpose, provided that * redistributions in source form retain this entire copyright notice and * indicate the origin and nature of any modifications. * * I'd appreciate being given credit for this package in the documentation of * software which uses it, but that is not a requirement. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * One or two things that technically ought to be in here are actually in * color.c, thanks to some incestuous relationships in the color chains. */ #define NISERR() VISERR(nfa->v) #define NERR(e) VERR(nfa->v, (e)) #define STACK_TOO_DEEP(x) (0) #define CANCEL_REQUESTED(x) (0) #define REG_CANCEL 777 /* - newnfa - set up an NFA ^ static struct nfa *newnfa(struct vars *, struct colormap *, struct nfa *); */ static struct nfa * /* the NFA, or NULL */ newnfa( struct vars *v, struct colormap *cm, struct nfa *parent) /* NULL if primary NFA */ { struct nfa *nfa; nfa = (struct nfa *) MALLOC(sizeof(struct nfa)); if (nfa == NULL) { ERR(REG_ESPACE); return NULL; } nfa->states = NULL; nfa->slast = NULL; nfa->free = NULL; nfa->nstates = 0; nfa->cm = cm; nfa->v = v; nfa->bos[0] = nfa->bos[1] = COLORLESS; nfa->eos[0] = nfa->eos[1] = COLORLESS; nfa->parent = parent; /* Precedes newfstate so parent is valid. */ nfa->post = newfstate(nfa, '@'); /* number 0 */ nfa->pre = newfstate(nfa, '>'); /* number 1 */ nfa->init = newstate(nfa); /* May become invalid later. */ nfa->final = newstate(nfa); if (ISERR()) { freenfa(nfa); return NULL; } rainbow(nfa, nfa->cm, PLAIN, COLORLESS, nfa->pre, nfa->init); newarc(nfa, '^', 1, nfa->pre, nfa->init); newarc(nfa, '^', 0, nfa->pre, nfa->init); rainbow(nfa, nfa->cm, PLAIN, COLORLESS, nfa->final, nfa->post); newarc(nfa, '$', 1, nfa->final, nfa->post); newarc(nfa, '$', 0, nfa->final, nfa->post); if (ISERR()) { freenfa(nfa); return NULL; } return nfa; } /* - freenfa - free an entire NFA ^ static void freenfa(struct nfa *); */ static void freenfa( struct nfa *nfa) { struct state *s; while ((s = nfa->states) != NULL) { s->nins = s->nouts = 0; /* don't worry about arcs */ freestate(nfa, s); } while ((s = nfa->free) != NULL) { nfa->free = s->next; destroystate(nfa, s); } nfa->slast = NULL; nfa->nstates = FREESTATE; nfa->pre = NULL; nfa->post = NULL; FREE(nfa); } /* - newstate - allocate an NFA state, with zero flag value ^ static struct state *newstate(struct nfa *); */ static struct state * /* NULL on error */ newstate( struct nfa *nfa) { struct state *s; if (nfa->free != NULL) { s = nfa->free; nfa->free = s->next; } else { if (nfa->v->spaceused >= REG_MAX_COMPILE_SPACE) { NERR(REG_ETOOBIG); return NULL; } s = (struct state *) MALLOC(sizeof(struct state)); if (s == NULL) { NERR(REG_ESPACE); return NULL; } nfa->v->spaceused += sizeof(struct state); s->oas.next = NULL; s->free = NULL; s->noas = 0; } assert(nfa->nstates != FREESTATE); s->no = nfa->nstates++; s->flag = 0; if (nfa->states == NULL) { nfa->states = s; } s->nins = 0; s->ins = NULL; s->nouts = 0; s->outs = NULL; s->tmp = NULL; s->next = NULL; if (nfa->slast != NULL) { assert(nfa->slast->next == NULL); nfa->slast->next = s; } s->prev = nfa->slast; nfa->slast = s; return s; } /* - newfstate - allocate an NFA state with a specified flag value ^ static struct state *newfstate(struct nfa *, int flag); */ static struct state * /* NULL on error */ newfstate( struct nfa *nfa, int flag) { struct state *s; s = newstate(nfa); if (s != NULL) { s->flag = (char) flag; } return s; } /* - dropstate - delete a state's inarcs and outarcs and free it ^ static void dropstate(struct nfa *, struct state *); */ static void dropstate( struct nfa *nfa, struct state *s) { struct arc *a; while ((a = s->ins) != NULL) { freearc(nfa, a); } while ((a = s->outs) != NULL) { freearc(nfa, a); } freestate(nfa, s); } /* - freestate - free a state, which has no in-arcs or out-arcs ^ static void freestate(struct nfa *, struct state *); */ static void freestate( struct nfa *nfa, struct state *s) { assert(s != NULL); assert(s->nins == 0 && s->nouts == 0); s->no = FREESTATE; s->flag = 0; if (s->next != NULL) { s->next->prev = s->prev; } else { assert(s == nfa->slast); nfa->slast = s->prev; } if (s->prev != NULL) { s->prev->next = s->next; } else { assert(s == nfa->states); nfa->states = s->next; } s->prev = NULL; s->next = nfa->free; /* don't delete it, put it on the free list */ nfa->free = s; } /* - destroystate - really get rid of an already-freed state ^ static void destroystate(struct nfa *, struct state *); */ static void destroystate( struct nfa *nfa, struct state *s) { struct arcbatch *ab; struct arcbatch *abnext; assert(s->no == FREESTATE); for (ab=s->oas.next ; ab!=NULL ; ab=abnext) { abnext = ab->next; FREE(ab); nfa->v->spaceused -= sizeof(struct arcbatch); } s->ins = NULL; s->outs = NULL; s->next = NULL; FREE(s); nfa->v->spaceused -= sizeof(struct state); } /* - newarc - set up a new arc within an NFA ^ static void newarc(struct nfa *, int, pcolor, struct state *, ^ struct state *); */ /* * This function checks to make sure that no duplicate arcs are created. * In general we never want duplicates. */ static void newarc( struct nfa *nfa, int t, pcolor co, struct state *from, struct state *to) { struct arc *a; assert(from != NULL && to != NULL); /* check for duplicate arc, using whichever chain is shorter */ if (from->nouts <= to->nins) { for (a = from->outs; a != NULL; a = a->outchain) { if (a->to == to && a->co == co && a->type == t) { return; } } } else { for (a = to->ins; a != NULL; a = a->inchain) { if (a->from == from && a->co == co && a->type == t) { return; } } } /* no dup, so create the arc */ createarc(nfa, t, co, from, to); } /* * createarc - create a new arc within an NFA * * This function must *only* be used after verifying that there is no existing * identical arc (same type/color/from/to). */ static void createarc( struct nfa * nfa, int t, pcolor co, struct state * from, struct state * to) { struct arc *a; /* the arc is physically allocated within its from-state */ a = allocarc(nfa, from); if (NISERR()) { return; } assert(a != NULL); a->type = t; a->co = (color) co; a->to = to; a->from = from; /* * Put the new arc on the beginning, not the end, of the chains; it's * simpler here, and freearc() is the same cost either way. See also the * logic in moveins() and its cohorts, as well as fixempties(). */ a->inchain = to->ins; a->inchainRev = NULL; if (to->ins) { to->ins->inchainRev = a; } to->ins = a; a->outchain = from->outs; a->outchainRev = NULL; if (from->outs) { from->outs->outchainRev = a; } from->outs = a; from->nouts++; to->nins++; if (COLORED(a) && nfa->parent == NULL) { colorchain(nfa->cm, a); } } /* - allocarc - allocate a new out-arc within a state ^ static struct arc *allocarc(struct nfa *, struct state *); */ static struct arc * /* NULL for failure */ allocarc( struct nfa *nfa, struct state *s) { struct arc *a; /* * Shortcut */ if (s->free == NULL && s->noas < ABSIZE) { a = &s->oas.a[s->noas]; s->noas++; return a; } /* * if none at hand, get more */ if (s->free == NULL) { struct arcbatch *newAb; int i; if (nfa->v->spaceused >= REG_MAX_COMPILE_SPACE) { NERR(REG_ETOOBIG); return NULL; } newAb = (struct arcbatch *) MALLOC(sizeof(struct arcbatch)); if (newAb == NULL) { NERR(REG_ESPACE); return NULL; } nfa->v->spaceused += sizeof(struct arcbatch); newAb->next = s->oas.next; s->oas.next = newAb; for (i=0 ; ia[i].type = 0; newAb->a[i].freechain = &newAb->a[i+1]; } newAb->a[ABSIZE-1].freechain = NULL; s->free = &newAb->a[0]; } assert(s->free != NULL); a = s->free; s->free = a->freechain; return a; } /* - freearc - free an arc ^ static void freearc(struct nfa *, struct arc *); */ static void freearc( struct nfa *nfa, struct arc *victim) { struct state *from = victim->from; struct state *to = victim->to; struct arc *predecessor; assert(victim->type != 0); /* * Take it off color chain if necessary. */ if (COLORED(victim) && nfa->parent == NULL) { uncolorchain(nfa->cm, victim); } /* * Take it off source's out-chain. */ assert(from != NULL); predecessor = victim->outchainRev; if (predecessor == NULL) { assert(from->outs == victim); from->outs = victim->outchain; } else { assert(predecessor->outchain == victim); predecessor->outchain = victim->outchain; } if (victim->outchain != NULL) { assert(victim->outchain->outchainRev == victim); victim->outchain->outchainRev = predecessor; } from->nouts--; /* * Take it off target's in-chain. */ assert(to != NULL); predecessor = victim->inchainRev; if (predecessor == NULL) { assert(to->ins == victim); to->ins = victim->inchain; } else { assert(predecessor->inchain == victim); predecessor->inchain = victim->inchain; } if (victim->inchain != NULL) { assert(victim->inchain->inchainRev == victim); victim->inchain->inchainRev = predecessor; } to->nins--; /* * Clean up and place on from-state's free list. */ victim->type = 0; victim->from = NULL; /* precautions... */ victim->to = NULL; victim->inchain = NULL; victim->inchainRev = NULL; victim->outchain = NULL; victim->outchainRev = NULL; victim->freechain = from->free; from->free = victim; } /* * changearctarget - flip an arc to have a different to state * * Caller must have verified that there is no preexisting duplicate arc. * * Note that because we store arcs in their from state, we can't easily have * a similar changearcsource function. */ static void changearctarget(struct arc * a, struct state * newto) { struct state *oldto = a->to; struct arc *predecessor; assert(oldto != newto); /* take it off old target's in-chain */ assert(oldto != NULL); predecessor = a->inchainRev; if (predecessor == NULL) { assert(oldto->ins == a); oldto->ins = a->inchain; } else { assert(predecessor->inchain == a); predecessor->inchain = a->inchain; } if (a->inchain != NULL) { assert(a->inchain->inchainRev == a); a->inchain->inchainRev = predecessor; } oldto->nins--; a->to = newto; /* prepend it to new target's in-chain */ a->inchain = newto->ins; a->inchainRev = NULL; if (newto->ins) { newto->ins->inchainRev = a; } newto->ins = a; newto->nins++; } /* - hasnonemptyout - Does state have a non-EMPTY out arc? ^ static int hasnonemptyout(struct state *); */ static int hasnonemptyout( struct state *s) { struct arc *a; for (a = s->outs; a != NULL; a = a->outchain) { if (a->type != EMPTY) { return 1; } } return 0; } /* - findarc - find arc, if any, from given source with given type and color * If there is more than one such arc, the result is random. ^ static struct arc *findarc(struct state *, int, pcolor); */ static struct arc * findarc( struct state *s, int type, pcolor co) { struct arc *a; for (a=s->outs ; a!=NULL ; a=a->outchain) { if (a->type == type && a->co == co) { return a; } } return NULL; } /* - cparc - allocate a new arc within an NFA, copying details from old one ^ static void cparc(struct nfa *, struct arc *, struct state *, ^ struct state *); */ static void cparc( struct nfa *nfa, struct arc *oa, struct state *from, struct state *to) { newarc(nfa, oa->type, oa->co, from, to); } /* * sortins - sort the in arcs of a state by from/color/type */ static void sortins( struct nfa * nfa, struct state * s) { struct arc **sortarray; struct arc *a; int n = s->nins; int i; if (n <= 1) { return; /* nothing to do */ } /* make an array of arc pointers ... */ sortarray = (struct arc **) MALLOC(n * sizeof(struct arc *)); if (sortarray == NULL) { NERR(REG_ESPACE); return; } i = 0; for (a = s->ins; a != NULL; a = a->inchain) { sortarray[i++] = a; } assert(i == n); /* ... sort the array */ qsort(sortarray, n, sizeof(struct arc *), sortins_cmp); /* ... and rebuild arc list in order */ /* it seems worth special-casing first and last items to simplify loop */ a = sortarray[0]; s->ins = a; a->inchain = sortarray[1]; a->inchainRev = NULL; for (i = 1; i < n - 1; i++) { a = sortarray[i]; a->inchain = sortarray[i + 1]; a->inchainRev = sortarray[i - 1]; } a = sortarray[i]; a->inchain = NULL; a->inchainRev = sortarray[i - 1]; FREE(sortarray); } static int sortins_cmp( const void *a, const void *b) { const struct arc *aa = *((const struct arc * const *) a); const struct arc *bb = *((const struct arc * const *) b); /* we check the fields in the order they are most likely to be different */ if (aa->from->no < bb->from->no) { return -1; } if (aa->from->no > bb->from->no) { return 1; } if (aa->co < bb->co) { return -1; } if (aa->co > bb->co) { return 1; } if (aa->type < bb->type) { return -1; } if (aa->type > bb->type) { return 1; } return 0; } /* * sortouts - sort the out arcs of a state by to/color/type */ static void sortouts( struct nfa * nfa, struct state * s) { struct arc **sortarray; struct arc *a; int n = s->nouts; int i; if (n <= 1) { return; /* nothing to do */ } /* make an array of arc pointers ... */ sortarray = (struct arc **) MALLOC(n * sizeof(struct arc *)); if (sortarray == NULL) { NERR(REG_ESPACE); return; } i = 0; for (a = s->outs; a != NULL; a = a->outchain) { sortarray[i++] = a; } assert(i == n); /* ... sort the array */ qsort(sortarray, n, sizeof(struct arc *), sortouts_cmp); /* ... and rebuild arc list in order */ /* it seems worth special-casing first and last items to simplify loop */ a = sortarray[0]; s->outs = a; a->outchain = sortarray[1]; a->outchainRev = NULL; for (i = 1; i < n - 1; i++) { a = sortarray[i]; a->outchain = sortarray[i + 1]; a->outchainRev = sortarray[i - 1]; } a = sortarray[i]; a->outchain = NULL; a->outchainRev = sortarray[i - 1]; FREE(sortarray); } static int sortouts_cmp( const void *a, const void *b) { const struct arc *aa = *((const struct arc * const *) a); const struct arc *bb = *((const struct arc * const *) b); /* we check the fields in the order they are most likely to be different */ if (aa->to->no < bb->to->no) { return -1; } if (aa->to->no > bb->to->no) { return 1; } if (aa->co < bb->co) { return -1; } if (aa->co > bb->co) { return 1; } if (aa->type < bb->type) { return -1; } if (aa->type > bb->type) { return 1; } return 0; } /* * Common decision logic about whether to use arc-by-arc operations or * sort/merge. If there's just a few source arcs we cannot recoup the * cost of sorting the destination arc list, no matter how large it is. * Otherwise, limit the number of arc-by-arc comparisons to about 1000 * (a somewhat arbitrary choice, but the breakeven point would probably * be machine dependent anyway). */ #define BULK_ARC_OP_USE_SORT(nsrcarcs, ndestarcs) \ ((nsrcarcs) < 4 ? 0 : ((nsrcarcs) > 32 || (ndestarcs) > 32)) /* - moveins - move all in arcs of a state to another state * You might think this could be done better by just updating the * existing arcs, and you would be right if it weren't for the need * for duplicate suppression, which makes it easier to just make new * ones to exploit the suppression built into newarc. * * However, if we have a whole lot of arcs to deal with, retail duplicate * checks become too slow. In that case we proceed by sorting and merging * the arc lists, and then we can indeed just update the arcs in-place. * ^ static void moveins(struct nfa *, struct state *, struct state *); */ static void moveins( struct nfa *nfa, struct state *oldState, struct state *newState) { assert(oldState != newState); if (!BULK_ARC_OP_USE_SORT(oldState->nins, newState->nins)) { /* With not too many arcs, just do them one at a time */ struct arc *a; while ((a = oldState->ins) != NULL) { cparc(nfa, a, a->from, newState); freearc(nfa, a); } } else { /* * With many arcs, use a sort-merge approach. Note changearctarget() * will put the arc onto the front of newState's chain, so it does not * break our walk through the sorted part of the chain. */ struct arc *oa; struct arc *na; /* * Because we bypass newarc() in this code path, we'd better include a * cancel check. */ if (CANCEL_REQUESTED(nfa->v->re)) { NERR(REG_CANCEL); return; } sortins(nfa, oldState); sortins(nfa, newState); if (NISERR()) { return; /* might have failed to sort */ } oa = oldState->ins; na = newState->ins; while (oa != NULL && na != NULL) { struct arc *a = oa; switch (sortins_cmp(&oa, &na)) { case -1: /* newState does not have anything matching oa */ oa = oa->inchain; /* * Rather than doing createarc+freearc, we can just unlink * and relink the existing arc struct. */ changearctarget(a, newState); break; case 0: /* match, advance in both lists */ oa = oa->inchain; na = na->inchain; /* ... and drop duplicate arc from oldState */ freearc(nfa, a); break; case +1: /* advance only na; oa might have a match later */ na = na->inchain; break; default: assert(NOTREACHED); } } while (oa != NULL) { /* newState does not have anything matching oa */ struct arc *a = oa; oa = oa->inchain; changearctarget(a, newState); } } assert(oldState->nins == 0); assert(oldState->ins == NULL); } /* - copyins - copy in arcs of a state to another state ^ static void copyins(struct nfa *, struct state *, struct state *, int); */ static void copyins( struct nfa *nfa, struct state *oldState, struct state *newState) { assert(oldState != newState); if (!BULK_ARC_OP_USE_SORT(oldState->nins, newState->nins)) { /* With not too many arcs, just do them one at a time */ struct arc *a; for (a = oldState->ins; a != NULL; a = a->inchain) { cparc(nfa, a, a->from, newState); } } else { /* * With many arcs, use a sort-merge approach. Note that createarc() * will put new arcs onto the front of newState's chain, so it does * not break our walk through the sorted part of the chain. */ struct arc *oa; struct arc *na; /* * Because we bypass newarc() in this code path, we'd better include a * cancel check. */ if (CANCEL_REQUESTED(nfa->v->re)) { NERR(REG_CANCEL); return; } sortins(nfa, oldState); sortins(nfa, newState); if (NISERR()) { return; /* might have failed to sort */ } oa = oldState->ins; na = newState->ins; while (oa != NULL && na != NULL) { struct arc *a = oa; switch (sortins_cmp(&oa, &na)) { case -1: /* newState does not have anything matching oa */ oa = oa->inchain; createarc(nfa, a->type, a->co, a->from, newState); break; case 0: /* match, advance in both lists */ oa = oa->inchain; na = na->inchain; break; case +1: /* advance only na; oa might have a match later */ na = na->inchain; break; default: assert(NOTREACHED); } } while (oa != NULL) { /* newState does not have anything matching oa */ struct arc *a = oa; oa = oa->inchain; createarc(nfa, a->type, a->co, a->from, newState); } } } /* * mergeins - merge a list of inarcs into a state * * This is much like copyins, but the source arcs are listed in an array, * and are not guaranteed unique. It's okay to clobber the array contents. */ static void mergeins( struct nfa * nfa, struct state * s, struct arc ** arcarray, int arccount) { struct arc *na; int i; int j; if (arccount <= 0) { return; } /* * Because we bypass newarc() in this code path, we'd better include a * cancel check. */ if (CANCEL_REQUESTED(nfa->v->re)) { NERR(REG_CANCEL); return; } /* Sort existing inarcs as well as proposed new ones */ sortins(nfa, s); if (NISERR()) { return; /* might have failed to sort */ } qsort(arcarray, arccount, sizeof(struct arc *), sortins_cmp); /* * arcarray very likely includes dups, so we must eliminate them. (This * could be folded into the next loop, but it's not worth the trouble.) */ j = 0; for (i = 1; i < arccount; i++) { switch (sortins_cmp(&arcarray[j], &arcarray[i])) { case -1: /* non-dup */ arcarray[++j] = arcarray[i]; break; case 0: /* dup */ break; default: /* trouble */ assert(NOTREACHED); } } arccount = j + 1; /* * Now merge into s' inchain. Note that createarc() will put new arcs * onto the front of s's chain, so it does not break our walk through the * sorted part of the chain. */ i = 0; na = s->ins; while (i < arccount && na != NULL) { struct arc *a = arcarray[i]; switch (sortins_cmp(&a, &na)) { case -1: /* s does not have anything matching a */ createarc(nfa, a->type, a->co, a->from, s); i++; break; case 0: /* match, advance in both lists */ i++; na = na->inchain; break; case +1: /* advance only na; array might have a match later */ na = na->inchain; break; default: assert(NOTREACHED); } } while (i < arccount) { /* s does not have anything matching a */ struct arc *a = arcarray[i]; createarc(nfa, a->type, a->co, a->from, s); i++; } } /* - moveouts - move all out arcs of a state to another state ^ static void moveouts(struct nfa *, struct state *, struct state *); */ static void moveouts( struct nfa *nfa, struct state *oldState, struct state *newState) { assert(oldState != newState); if (!BULK_ARC_OP_USE_SORT(oldState->nouts, newState->nouts)) { /* With not too many arcs, just do them one at a time */ struct arc *a; while ((a = oldState->outs) != NULL) { cparc(nfa, a, newState, a->to); freearc(nfa, a); } } else { /* * With many arcs, use a sort-merge approach. Note that createarc() * will put new arcs onto the front of newState's chain, so it does * not break our walk through the sorted part of the chain. */ struct arc *oa; struct arc *na; /* * Because we bypass newarc() in this code path, we'd better include a * cancel check. */ if (CANCEL_REQUESTED(nfa->v->re)) { NERR(REG_CANCEL); return; } sortouts(nfa, oldState); sortouts(nfa, newState); if (NISERR()) { return; /* might have failed to sort */ } oa = oldState->outs; na = newState->outs; while (oa != NULL && na != NULL) { struct arc *a = oa; switch (sortouts_cmp(&oa, &na)) { case -1: /* newState does not have anything matching oa */ oa = oa->outchain; createarc(nfa, a->type, a->co, newState, a->to); freearc(nfa, a); break; case 0: /* match, advance in both lists */ oa = oa->outchain; na = na->outchain; /* ... and drop duplicate arc from oldState */ freearc(nfa, a); break; case +1: /* advance only na; oa might have a match later */ na = na->outchain; break; default: assert(NOTREACHED); } } while (oa != NULL) { /* newState does not have anything matching oa */ struct arc *a = oa; oa = oa->outchain; createarc(nfa, a->type, a->co, newState, a->to); freearc(nfa, a); } } assert(oldState->nouts == 0); assert(oldState->outs == NULL); } /* - copyouts - copy out arcs of a state to another state ^ static void copyouts(struct nfa *, struct state *, struct state *, int); */ static void copyouts( struct nfa *nfa, struct state *oldState, struct state *newState) { assert(oldState != newState); if (!BULK_ARC_OP_USE_SORT(oldState->nouts, newState->nouts)) { /* With not too many arcs, just do them one at a time */ struct arc *a; for (a = oldState->outs; a != NULL; a = a->outchain) { cparc(nfa, a, newState, a->to); } } else { /* * With many arcs, use a sort-merge approach. Note that createarc() * will put new arcs onto the front of newState's chain, so it does * not break our walk through the sorted part of the chain. */ struct arc *oa; struct arc *na; /* * Because we bypass newarc() in this code path, we'd better include a * cancel check. */ if (CANCEL_REQUESTED(nfa->v->re)) { NERR(REG_CANCEL); return; } sortouts(nfa, oldState); sortouts(nfa, newState); if (NISERR()) { return; /* might have failed to sort */ } oa = oldState->outs; na = newState->outs; while (oa != NULL && na != NULL) { struct arc *a = oa; switch (sortouts_cmp(&oa, &na)) { case -1: /* newState does not have anything matching oa */ oa = oa->outchain; createarc(nfa, a->type, a->co, newState, a->to); break; case 0: /* match, advance in both lists */ oa = oa->outchain; na = na->outchain; break; case +1: /* advance only na; oa might have a match later */ na = na->outchain; break; default: assert(NOTREACHED); } } while (oa != NULL) { /* newState does not have anything matching oa */ struct arc *a = oa; oa = oa->outchain; createarc(nfa, a->type, a->co, newState, a->to); } } } /* - cloneouts - copy out arcs of a state to another state pair, modifying type ^ static void cloneouts(struct nfa *, struct state *, struct state *, ^ struct state *, int); */ static void cloneouts( struct nfa *nfa, struct state *old, struct state *from, struct state *to, int type) { struct arc *a; assert(old != from); for (a=old->outs ; a!=NULL ; a=a->outchain) { newarc(nfa, type, a->co, from, to); } } /* - delsub - delete a sub-NFA, updating subre pointers if necessary * This uses a recursive traversal of the sub-NFA, marking already-seen * states using their tmp pointer. ^ static void delsub(struct nfa *, struct state *, struct state *); */ static void delsub( struct nfa *nfa, struct state *lp, /* the sub-NFA goes from here... */ struct state *rp) /* ...to here, *not* inclusive */ { assert(lp != rp); rp->tmp = rp; /* mark end */ deltraverse(nfa, lp, lp); assert(lp->nouts == 0 && rp->nins == 0); /* did the job */ assert(lp->no != FREESTATE && rp->no != FREESTATE); /* no more */ rp->tmp = NULL; /* unmark end */ lp->tmp = NULL; /* and begin, marked by deltraverse */ } /* - deltraverse - the recursive heart of delsub * This routine's basic job is to destroy all out-arcs of the state. ^ static void deltraverse(struct nfa *, struct state *, struct state *); */ static void deltraverse( struct nfa *nfa, struct state *leftend, struct state *s) { struct arc *a; struct state *to; if (s->nouts == 0) { return; /* nothing to do */ } if (s->tmp != NULL) { return; /* already in progress */ } s->tmp = s; /* mark as in progress */ while ((a = s->outs) != NULL) { to = a->to; deltraverse(nfa, leftend, to); assert(to->nouts == 0 || to->tmp != NULL); freearc(nfa, a); if (to->nins == 0 && to->tmp == NULL) { assert(to->nouts == 0); freestate(nfa, to); } } assert(s->no != FREESTATE); /* we're still here */ assert(s == leftend || s->nins != 0); /* and still reachable */ assert(s->nouts == 0); /* but have no outarcs */ s->tmp = NULL; /* we're done here */ } /* - dupnfa - duplicate sub-NFA * Another recursive traversal, this time using tmp to point to duplicates as * well as mark already-seen states. (You knew there was a reason why it's a * state pointer, didn't you? :-)) ^ static void dupnfa(struct nfa *, struct state *, struct state *, ^ struct state *, struct state *); */ static void dupnfa( struct nfa *nfa, struct state *start, /* duplicate of subNFA starting here */ struct state *stop, /* and stopping here */ struct state *from, /* stringing duplicate from here */ struct state *to) /* to here */ { if (start == stop) { newarc(nfa, EMPTY, 0, from, to); return; } stop->tmp = to; duptraverse(nfa, start, from, 0); /* done, except for clearing out the tmp pointers */ stop->tmp = NULL; cleartraverse(nfa, start); } /* - duptraverse - recursive heart of dupnfa ^ static void duptraverse(struct nfa *, struct state *, struct state *); */ static void duptraverse( struct nfa *nfa, struct state *s, struct state *stmp, /* s's duplicate, or NULL */ int depth) { struct arc *a; if (s->tmp != NULL) { return; /* already done */ } s->tmp = (stmp == NULL) ? newstate(nfa) : stmp; if (s->tmp == NULL) { assert(NISERR()); return; } /* * Arbitrary depth limit. Needs tuning, but this value is sufficient to * make all normal tests (not reg-33.14) pass. */ #ifndef DUPTRAVERSE_MAX_DEPTH #define DUPTRAVERSE_MAX_DEPTH 15000 #endif if (depth++ > DUPTRAVERSE_MAX_DEPTH) { NERR(REG_ESPACE); } for (a=s->outs ; a!=NULL && !NISERR() ; a=a->outchain) { duptraverse(nfa, a->to, NULL, depth); if (NISERR()) { break; } assert(a->to->tmp != NULL); cparc(nfa, a, s->tmp, a->to->tmp); } } /* - cleartraverse - recursive cleanup for algorithms that leave tmp ptrs set ^ static void cleartraverse(struct nfa *, struct state *); */ static void cleartraverse( struct nfa *nfa, struct state *s) { struct arc *a; if (s->tmp == NULL) { return; } s->tmp = NULL; for (a=s->outs ; a!=NULL ; a=a->outchain) { cleartraverse(nfa, a->to); } } /* - specialcolors - fill in special colors for an NFA ^ static void specialcolors(struct nfa *); */ static void specialcolors( struct nfa *nfa) { /* * False colors for BOS, BOL, EOS, EOL */ if (nfa->parent == NULL) { nfa->bos[0] = pseudocolor(nfa->cm); nfa->bos[1] = pseudocolor(nfa->cm); nfa->eos[0] = pseudocolor(nfa->cm); nfa->eos[1] = pseudocolor(nfa->cm); } else { assert(nfa->parent->bos[0] != COLORLESS); nfa->bos[0] = nfa->parent->bos[0]; assert(nfa->parent->bos[1] != COLORLESS); nfa->bos[1] = nfa->parent->bos[1]; assert(nfa->parent->eos[0] != COLORLESS); nfa->eos[0] = nfa->parent->eos[0]; assert(nfa->parent->eos[1] != COLORLESS); nfa->eos[1] = nfa->parent->eos[1]; } } /* - optimize - optimize an NFA ^ static long optimize(struct nfa *, FILE *); */ /* * The main goal of this function is not so much "optimization" (though it * does try to get rid of useless NFA states) as reducing the NFA to a form * the regex executor can handle. The executor, and indeed the cNFA format * that is its input, can only handle PLAIN and LACON arcs. The output of * the regex parser also includes EMPTY (do-nothing) arcs, as well as * ^, $, AHEAD, and BEHIND constraint arcs, which we must get rid of here. * We first get rid of EMPTY arcs and then deal with the constraint arcs. * The hardest part of either job is to get rid of circular loops of the * target arc type. We would have to do that in any case, though, as such a * loop would otherwise allow the executor to cycle through the loop endlessly * without making any progress in the input string. */ static long /* re_info bits */ optimize( struct nfa *nfa, FILE *f) /* for debug output; NULL none */ { int verbose = (f != NULL) ? 1 : 0; if (verbose) { fprintf(f, "\ninitial cleanup:\n"); } cleanup(nfa); /* may simplify situation */ if (verbose) { dumpnfa(nfa, f); } if (verbose) { fprintf(f, "\nempties:\n"); } fixempties(nfa, f); /* get rid of EMPTY arcs */ if (verbose) { fprintf(f, "\nconstraints:\n"); } fixconstraintloops(nfa, f); /* get rid of constraint loops */ pullback(nfa, f); /* pull back constraints backward */ pushfwd(nfa, f); /* push fwd constraints forward */ if (verbose) { fprintf(f, "\nfinal cleanup:\n"); } cleanup(nfa); /* final tidying */ #ifdef REG_DEBUG if (verbose) { dumpnfa(nfa, f); } #endif return analyze(nfa); /* and analysis */ } /* - pullback - pull back constraints backward to eliminate them ^ static void pullback(struct nfa *, FILE *); */ static void pullback( struct nfa *nfa, FILE *f) /* for debug output; NULL none */ { struct state *s; struct state *nexts; struct arc *a; struct arc *nexta; struct state *intermediates; int progress; /* * Find and pull until there are no more. */ do { progress = 0; for (s=nfa->states ; s!=NULL && !NISERR() ; s=nexts) { nexts = s->next; intermediates = NULL; for (a=s->outs ; a!=NULL && !NISERR() ; a=nexta) { nexta = a->outchain; if (a->type == '^' || a->type == BEHIND) { if (pull(nfa, a, &intermediates)) { progress = 1; } } assert(nexta == NULL || s->no != FREESTATE); } /* clear tmp fields of intermediate states created here */ while (intermediates != NULL) { struct state *ns = intermediates->tmp; intermediates->tmp = NULL; intermediates = ns; } /* if s is now useless, get rid of it */ if ((s->nins == 0 || s->nouts == 0) && !s->flag) { dropstate(nfa, s); } } if (progress && f != NULL) { dumpnfa(nfa, f); } } while (progress && !NISERR()); if (NISERR()) { return; } /* * Any ^ constraints we were able to pull to the start state can now be * replaced by PLAIN arcs referencing the BOS or BOL colors. There should * be no other ^ or BEHIND arcs left in the NFA, though we do not check * that here (compact() will fail if so). */ for (a=nfa->pre->outs ; a!=NULL ; a=nexta) { nexta = a->outchain; if (a->type == '^') { assert(a->co == 0 || a->co == 1); newarc(nfa, PLAIN, nfa->bos[a->co], a->from, a->to); freearc(nfa, a); } } } /* - pull - pull a back constraint backward past its source state * * Returns 1 if successful (which it always is unless the source is the * start state or we have an internal error), 0 if nothing happened. * * A significant property of this function is that it deletes no preexisting * states, and no outarcs of the constraint's from state other than the given * constraint arc. This makes the loops in pullback() safe, at the cost that * we may leave useless states behind. Therefore, we leave it to pullback() * to delete such states. * * If the from state has multiple back-constraint outarcs, and/or multiple * compatible constraint inarcs, we only need to create one new intermediate * state per combination of predecessor and successor states. *intermediates * points to a list of such intermediate states for this from state (chained * through their tmp fields). ^ static int pull(struct nfa *, struct arc *); */ static int pull( struct nfa *nfa, struct arc *con, struct state **intermediates) { struct state *from = con->from; struct state *to = con->to; struct arc *a; struct arc *nexta; struct state *s; assert(from != to); /* should have gotten rid of this earlier */ if (from->flag) { /* can't pull back beyond start */ return 0; } if (from->nins == 0) { /* unreachable */ freearc(nfa, con); return 1; } /* * First, clone from state if necessary to avoid other outarcs. This may * seem wasteful, but it simplifies the logic, and we'll get rid of the * clone state again at the bottom. */ if (from->nouts > 1) { s = newstate(nfa); if (NISERR()) { return 0; } copyins(nfa, from, s); /* duplicate inarcs */ cparc(nfa, con, s, to); /* move constraint arc */ freearc(nfa, con); if (NISERR()) { return 0; } from = s; con = from->outs; } assert(from->nouts == 1); /* * Propagate the constraint into the from state's inarcs. */ for (a=from->ins ; a!=NULL && !NISERR(); a=nexta) { nexta = a->inchain; switch (combine(con, a)) { case INCOMPATIBLE: /* destroy the arc */ freearc(nfa, a); break; case SATISFIED: /* no action needed */ break; case COMPATIBLE: /* swap the two arcs, more or less */ /* need an intermediate state, but might have one already */ for (s = *intermediates; s != NULL; s = s->tmp) { assert(s->nins > 0 && s->nouts > 0); if (s->ins->from == a->from && s->outs->to == to) { break; } } if (s == NULL) { s = newstate(nfa); if (NISERR()) { return 0; } s->tmp = *intermediates; *intermediates = s; } cparc(nfa, con, a->from, s); cparc(nfa, a, s, to); freearc(nfa, a); break; default: assert(NOTREACHED); break; } } /* * Remaining inarcs, if any, incorporate the constraint. */ moveins(nfa, from, to); freearc(nfa, con); /* from state is now useless, but we leave it to pullback() to clean up */ return 1; } /* - pushfwd - push forward constraints forward to eliminate them ^ static void pushfwd(struct nfa *, FILE *); */ static void pushfwd( struct nfa *nfa, FILE *f) /* for debug output; NULL none */ { struct state *s; struct state *nexts; struct arc *a; struct arc *nexta; struct state *intermediates; int progress; /* * Find and push until there are no more. */ do { progress = 0; for (s=nfa->states ; s!=NULL && !NISERR() ; s=nexts) { nexts = s->next; intermediates = NULL; for (a = s->ins; a != NULL && !NISERR(); a = nexta) { nexta = a->inchain; if (a->type == '$' || a->type == AHEAD) { if (push(nfa, a, &intermediates)) { progress = 1; } } } /* clear tmp fields of intermediate states created here */ while (intermediates != NULL) { struct state *ns = intermediates->tmp; intermediates->tmp = NULL; intermediates = ns; } /* if s is now useless, get rid of it */ if ((s->nins == 0 || s->nouts == 0) && !s->flag) { dropstate(nfa, s); } } if (progress && f != NULL) { dumpnfa(nfa, f); } } while (progress && !NISERR()); if (NISERR()) { return; } /* * Any $ constraints we were able to push to the post state can now be * replaced by PLAIN arcs referencing the EOS or EOL colors. There should * be no other $ or AHEAD arcs left in the NFA, though we do not check * that here (compact() will fail if so). */ for (a = nfa->post->ins; a != NULL; a = nexta) { nexta = a->inchain; if (a->type == '$') { assert(a->co == 0 || a->co == 1); newarc(nfa, PLAIN, nfa->eos[a->co], a->from, a->to); freearc(nfa, a); } } } /* - push - push a forward constraint forward past its destination state * * Returns 1 if successful (which it always is unless the destination is the * post state or we have an internal error), 0 if nothing happened. * * A significant property of this function is that it deletes no preexisting * states, and no inarcs of the constraint's to state other than the given * constraint arc. This makes the loops in pushfwd() safe, at the cost that * we may leave useless states behind. Therefore, we leave it to pushfwd() * to delete such states. * * If the to state has multiple forward-constraint inarcs, and/or multiple * compatible constraint outarcs, we only need to create one new intermediate * state per combination of predecessor and successor states. *intermediates * points to a list of such intermediate states for this to state (chained * through their tmp fields). ^ static int push(struct nfa *, struct arc *); */ static int push( struct nfa *nfa, struct arc *con, struct state **intermediates) { struct state *from = con->from; struct state *to = con->to; struct arc *a; struct arc *nexta; struct state *s; assert(to != from); /* should have gotten rid of this earlier */ if (to->flag) { /* can't push forward beyond end */ return 0; } if (to->nouts == 0) { /* dead end */ freearc(nfa, con); return 1; } /* * First, clone to state if necessary to avoid other inarcs. This may * seem wasteful, but it simplifies the logic, and we'll get rid of the * clone state again at the bottom. */ if (to->nins > 1) { s = newstate(nfa); if (NISERR()) { return 0; } copyouts(nfa, to, s); /* duplicate outarcs */ cparc(nfa, con, from, s); /* move constraint arc */ freearc(nfa, con); if (NISERR()) { return 0; } to = s; con = to->ins; } assert(to->nins == 1); /* * Propagate the constraint into the to state's outarcs. */ for (a = to->outs; a != NULL && !NISERR(); a = nexta) { nexta = a->outchain; switch (combine(con, a)) { case INCOMPATIBLE: /* destroy the arc */ freearc(nfa, a); break; case SATISFIED: /* no action needed */ break; case COMPATIBLE: /* swap the two arcs, more or less */ /* need an intermediate state, but might have one already */ for (s = *intermediates; s != NULL; s = s->tmp) { assert(s->nins > 0 && s->nouts > 0); if (s->ins->from == from && s->outs->to == a->to) { break; } } if (s == NULL) { s = newstate(nfa); if (NISERR()) { return 0; } s->tmp = *intermediates; *intermediates = s; } cparc(nfa, con, s, a->to); cparc(nfa, a, from, s); freearc(nfa, a); break; default: assert(NOTREACHED); break; } } /* * Remaining outarcs, if any, incorporate the constraint. */ moveouts(nfa, to, from); freearc(nfa, con); /* to state is now useless, but we leave it to pushfwd() to clean up */ return 1; } /* - combine - constraint lands on an arc, what happens? ^ #def INCOMPATIBLE 1 // destroys arc ^ #def SATISFIED 2 // constraint satisfied ^ #def COMPATIBLE 3 // compatible but not satisfied yet ^ static int combine(struct arc *, struct arc *); */ static int combine( struct arc *con, struct arc *a) { #define CA(ct,at) (((ct)<type, a->type)) { case CA('^', PLAIN): /* newlines are handled separately */ case CA('$', PLAIN): return INCOMPATIBLE; break; case CA(AHEAD, PLAIN): /* color constraints meet colors */ case CA(BEHIND, PLAIN): if (con->co == a->co) { return SATISFIED; } return INCOMPATIBLE; break; case CA('^', '^'): /* collision, similar constraints */ case CA('$', '$'): case CA(AHEAD, AHEAD): case CA(BEHIND, BEHIND): if (con->co == a->co) { /* true duplication */ return SATISFIED; } return INCOMPATIBLE; break; case CA('^', BEHIND): /* collision, dissimilar constraints */ case CA(BEHIND, '^'): case CA('$', AHEAD): case CA(AHEAD, '$'): return INCOMPATIBLE; break; case CA('^', '$'): /* constraints passing each other */ case CA('^', AHEAD): case CA(BEHIND, '$'): case CA(BEHIND, AHEAD): case CA('$', '^'): case CA('$', BEHIND): case CA(AHEAD, '^'): case CA(AHEAD, BEHIND): case CA('^', LACON): case CA(BEHIND, LACON): case CA('$', LACON): case CA(AHEAD, LACON): return COMPATIBLE; break; } assert(NOTREACHED); return INCOMPATIBLE; /* for benefit of blind compilers */ } /* - fixempties - get rid of EMPTY arcs ^ static void fixempties(struct nfa *, FILE *); */ static void fixempties( struct nfa *nfa, FILE *f) /* for debug output; NULL none */ { struct state *s; struct state *s2; struct state *nexts; struct arc *a; struct arc *nexta; int totalinarcs; struct arc **inarcsorig; struct arc **arcarray; int arccount; int prevnins; int nskip; /* * First, get rid of any states whose sole out-arc is an EMPTY, * since they're basically just aliases for their successor. The * parsing algorithm creates enough of these that it's worth * special-casing this. */ for (s = nfa->states; s != NULL && !NISERR(); s = nexts) { nexts = s->next; if (s->flag || s->nouts != 1) { continue; } a = s->outs; assert(a != NULL && a->outchain == NULL); if (a->type != EMPTY) { continue; } if (s != a->to) { moveins(nfa, s, a->to); } dropstate(nfa, s); } /* * Similarly, get rid of any state with a single EMPTY in-arc, by * folding it into its predecessor. */ for (s = nfa->states; s != NULL && !NISERR(); s = nexts) { nexts = s->next; /* Ensure tmp fields are clear for next step */ assert(s->tmp == NULL); if (s->flag || s->nins != 1) { continue; } a = s->ins; assert(a != NULL && a->inchain == NULL); if (a->type != EMPTY) { continue; } if (s != a->from) { moveouts(nfa, s, a->from); } dropstate(nfa, s); } if (NISERR()) { return; } /* * For each remaining NFA state, find all other states from which it is * reachable by a chain of one or more EMPTY arcs. Then generate new arcs * that eliminate the need for each such chain. * * We could replace a chain of EMPTY arcs that leads from a "from" state * to a "to" state either by pushing non-EMPTY arcs forward (linking * directly from "from"'s predecessors to "to") or by pulling them back * (linking directly from "from" to "to"'s successors). We choose to * always do the former; this choice is somewhat arbitrary, but the * approach below requires that we uniformly do one or the other. * * Suppose we have a chain of N successive EMPTY arcs (where N can easily * approach the size of the NFA). All of the intermediate states must * have additional inarcs and outarcs, else they'd have been removed by * the steps above. Assuming their inarcs are mostly not empties, we will * add O(N^2) arcs to the NFA, since a non-EMPTY inarc leading to any one * state in the chain must be duplicated to lead to all its successor * states as well. So there is no hope of doing less than O(N^2) work; * however, we should endeavor to keep the big-O cost from being even * worse than that, which it can easily become without care. In * particular, suppose we were to copy all S1's inarcs forward to S2, and * then also to S3, and then later we consider pushing S2's inarcs forward * to S3. If we include the arcs already copied from S1 in that, we'd be * doing O(N^3) work. (The duplicate-arc elimination built into newarc() * and its cohorts would get rid of the extra arcs, but not without cost.) * * We can avoid this cost by treating only arcs that existed at the start * of this phase as candidates to be pushed forward. To identify those, * we remember the first inarc each state had to start with. We rely on * the fact that newarc() and friends put new arcs on the front of their * to-states' inchains, and that this phase never deletes arcs, so that * the original arcs must be the last arcs in their to-states' inchains. * * So the process here is that, for each state in the NFA, we gather up * all non-EMPTY inarcs of states that can reach the target state via * EMPTY arcs. We then sort, de-duplicate, and merge these arcs into the * target state's inchain. (We can safely use sort-merge for this as long * as we update each state's original-arcs pointer after we add arcs to * it; the sort step of mergeins probably changed the order of the old * arcs.) * * Another refinement worth making is that, because we only add non-EMPTY * arcs during this phase, and all added arcs have the same from-state as * the non-EMPTY arc they were cloned from, we know ahead of time that any * states having only EMPTY outarcs will be useless for lack of outarcs * after we drop the EMPTY arcs. (They cannot gain non-EMPTY outarcs if * they had none to start with.) So we need not bother to update the * inchains of such states at all. */ /* Remember the states' first original inarcs */ /* ... and while at it, count how many old inarcs there are altogether */ inarcsorig = (struct arc **) MALLOC(nfa->nstates * sizeof(struct arc *)); if (inarcsorig == NULL) { NERR(REG_ESPACE); return; } totalinarcs = 0; for (s = nfa->states; s != NULL; s = s->next) { inarcsorig[s->no] = s->ins; totalinarcs += s->nins; } /* * Create a workspace for accumulating the inarcs to be added to the * current target state. totalinarcs is probably a considerable * overestimate of the space needed, but the NFA is unlikely to be large * enough at this point to make it worth being smarter. */ arcarray = (struct arc **) MALLOC(totalinarcs * sizeof(struct arc *)); if (arcarray == NULL) { NERR(REG_ESPACE); FREE(inarcsorig); return; } /* And iterate over the target states */ for (s = nfa->states; s != NULL && !NISERR(); s = s->next) { /* Ignore target states without non-EMPTY outarcs, per note above */ if (!s->flag && !hasnonemptyout(s)) { continue; } /* Find predecessor states and accumulate their original inarcs */ arccount = 0; for (s2 = emptyreachable(nfa, s, s, inarcsorig); s2 != s; s2 = nexts) { /* Add s2's original inarcs to arcarray[], but ignore empties */ for (a = inarcsorig[s2->no]; a != NULL; a = a->inchain) { if (a->type != EMPTY) { arcarray[arccount++] = a; } } /* Reset the tmp fields as we walk back */ nexts = s2->tmp; s2->tmp = NULL; } s->tmp = NULL; assert(arccount <= totalinarcs); /* Remember how many original inarcs this state has */ prevnins = s->nins; /* Add non-duplicate inarcs to target state */ mergeins(nfa, s, arcarray, arccount); /* Now we must update the state's inarcsorig pointer */ nskip = s->nins - prevnins; a = s->ins; while (nskip-- > 0) { a = a->inchain; } inarcsorig[s->no] = a; } FREE(arcarray); FREE(inarcsorig); if (NISERR()) { return; } /* * Remove all the EMPTY arcs, since we don't need them anymore. */ for (s = nfa->states; s != NULL; s = s->next) { for (a = s->outs; a != NULL; a = nexta) { nexta = a->outchain; if (a->type == EMPTY) { freearc(nfa, a); } } } /* * And remove any states that have become useless. (This cleanup is * not very thorough, and would be even less so if we tried to * combine it with the previous step; but cleanup() will take care * of anything we miss.) */ for (s = nfa->states; s != NULL; s = nexts) { nexts = s->next; if ((s->nins == 0 || s->nouts == 0) && !s->flag) { dropstate(nfa, s); } } if (f != NULL) { dumpnfa(nfa, f); } } /* - emptyreachable - recursively find all states that can reach s by EMPTY arcs * The return value is the last such state found. Its tmp field links back * to the next-to-last such state, and so on back to s, so that all these * states can be located without searching the whole NFA. * * Since this is only used in fixempties(), we pass in the inarcsorig[] array * maintained by that function. This lets us skip over all new inarcs, which * are certainly not EMPTY arcs. * * The maximum recursion depth here is equal to the length of the longest * loop-free chain of EMPTY arcs, which is surely no more than the size of * the NFA, and in practice will be less than that. ^ static struct state *emptyreachable(struct state *, struct state *); */ static struct state * emptyreachable( struct nfa *nfa, struct state *s, struct state *lastfound, struct arc **inarcsorig) { struct arc *a; s->tmp = lastfound; lastfound = s; for (a = inarcsorig[s->no]; a != NULL; a = a->inchain) { if (a->type == EMPTY && a->from->tmp == NULL) { lastfound = emptyreachable(nfa, a->from, lastfound, inarcsorig); } } return lastfound; } /* * isconstraintarc - detect whether an arc is of a constraint type */ static inline int isconstraintarc(struct arc * a) { switch (a->type) { case '^': case '$': case BEHIND: case AHEAD: case LACON: return 1; } return 0; } /* * hasconstraintout - does state have a constraint out arc? */ static int hasconstraintout(struct state * s) { struct arc *a; for (a = s->outs; a != NULL; a = a->outchain) { if (isconstraintarc(a)) { return 1; } } return 0; } /* * fixconstraintloops - get rid of loops containing only constraint arcs * * A loop of states that contains only constraint arcs is useless, since * passing around the loop represents no forward progress. Moreover, it * would cause infinite looping in pullback/pushfwd, so we need to get rid * of such loops before doing that. */ static void fixconstraintloops( struct nfa * nfa, FILE *f) /* for debug output; NULL none */ { struct state *s; struct state *nexts; struct arc *a; struct arc *nexta; int hasconstraints; /* * In the trivial case of a state that loops to itself, we can just drop * the constraint arc altogether. This is worth special-casing because * such loops are far more common than loops containing multiple states. * While we're at it, note whether any constraint arcs survive. */ hasconstraints = 0; for (s = nfa->states; s != NULL && !NISERR(); s = nexts) { nexts = s->next; /* while we're at it, ensure tmp fields are clear for next step */ assert(s->tmp == NULL); for (a = s->outs; a != NULL && !NISERR(); a = nexta) { nexta = a->outchain; if (isconstraintarc(a)) { if (a->to == s) { freearc(nfa, a); } else { hasconstraints = 1; } } } /* If we removed all the outarcs, the state is useless. */ if (s->nouts == 0 && !s->flag) { dropstate(nfa, s); } } /* Nothing to do if no remaining constraint arcs */ if (NISERR() || !hasconstraints) { return; } /* * Starting from each remaining NFA state, search outwards for a * constraint loop. If we find a loop, break the loop, then start the * search over. (We could possibly retain some state from the first scan, * but it would complicate things greatly, and multi-state constraint * loops are rare enough that it's not worth optimizing the case.) */ restart: for (s = nfa->states; s != NULL && !NISERR(); s = s->next) { if (findconstraintloop(nfa, s)) { goto restart; } } if (NISERR()) { return; } /* * Now remove any states that have become useless. (This cleanup is not * very thorough, and would be even less so if we tried to combine it with * the previous step; but cleanup() will take care of anything we miss.) * * Because findconstraintloop intentionally doesn't reset all tmp fields, * we have to clear them after it's done. This is a convenient place to * do that, too. */ for (s = nfa->states; s != NULL; s = nexts) { nexts = s->next; s->tmp = NULL; if ((s->nins == 0 || s->nouts == 0) && !s->flag) { dropstate(nfa, s); } } if (f != NULL) { dumpnfa(nfa, f); } } /* * findconstraintloop - recursively find a loop of constraint arcs * * If we find a loop, break it by calling breakconstraintloop(), then * return 1; otherwise return 0. * * State tmp fields are guaranteed all NULL on a success return, because * breakconstraintloop does that. After a failure return, any state that * is known not to be part of a loop is marked with s->tmp == s; this allows * us not to have to re-prove that fact on later calls. (This convention is * workable because we already eliminated single-state loops.) * * Note that the found loop doesn't necessarily include the first state we * are called on. Any loop reachable from that state will do. * * The maximum recursion depth here is one more than the length of the longest * loop-free chain of constraint arcs, which is surely no more than the size * of the NFA, and in practice will be a lot less than that. */ static int findconstraintloop(struct nfa * nfa, struct state * s) { struct arc *a; /* Since this is recursive, it could be driven to stack overflow */ if (STACK_TOO_DEEP(nfa->v->re)) { NERR(REG_ETOOBIG); return 1; /* to exit as quickly as possible */ } if (s->tmp != NULL) { /* Already proven uninteresting? */ if (s->tmp == s) { return 0; } /* Found a loop involving s */ breakconstraintloop(nfa, s); /* The tmp fields have been cleaned up by breakconstraintloop */ return 1; } for (a = s->outs; a != NULL; a = a->outchain) { if (isconstraintarc(a)) { struct state *sto = a->to; assert(sto != s); s->tmp = sto; if (findconstraintloop(nfa, sto)) { return 1; } } } /* * If we get here, no constraint loop exists leading out from s. Mark it * with s->tmp == s so we need not rediscover that fact again later. */ s->tmp = s; return 0; } /* * breakconstraintloop - break a loop of constraint arcs * * sinitial is any one member state of the loop. Each loop member's tmp * field links to its successor within the loop. (Note that this function * will reset all the tmp fields to NULL.) * * We can break the loop by, for any one state S1 in the loop, cloning its * loop successor state S2 (and possibly following states), and then moving * all S1->S2 constraint arcs to point to the cloned S2. The cloned S2 should * copy any non-constraint outarcs of S2. Constraint outarcs should be * dropped if they point back to S1, else they need to be copied as arcs to * similarly cloned states S3, S4, etc. In general, each cloned state copies * non-constraint outarcs, drops constraint outarcs that would lead to itself * or any earlier cloned state, and sends other constraint outarcs to newly * cloned states. No cloned state will have any inarcs that aren't constraint * arcs or do not lead from S1 or earlier-cloned states. It's okay to drop * constraint back-arcs since they would not take us to any state we've not * already been in; therefore, no new constraint loop is created. In this way * we generate a modified NFA that can still represent every useful state * sequence, but not sequences that represent state loops with no consumption * of input data. Note that the set of cloned states will certainly include * all of the loop member states other than S1, and it may also include * non-loop states that are reachable from S2 via constraint arcs. This is * important because there is no guarantee that findconstraintloop found a * maximal loop (and searching for one would be NP-hard, so don't try). * Frequently the "non-loop states" are actually part of a larger loop that * we didn't notice, and indeed there may be several overlapping loops. * This technique ensures convergence in such cases, while considering only * the originally-found loop does not. * * If there is only one S1->S2 constraint arc, then that constraint is * certainly satisfied when we enter any of the clone states. This means that * in the common case where many of the constraint arcs are identically * labeled, we can merge together clone states linked by a similarly-labeled * constraint: if we can get to the first one we can certainly get to the * second, so there's no need to distinguish. This greatly reduces the number * of new states needed, so we preferentially break the given loop at a state * pair where this is true. * * Furthermore, it's fairly common to find that a cloned successor state has * no outarcs, especially if we're a bit aggressive about removing unnecessary * outarcs. If that happens, then there is simply not any interesting state * that can be reached through the predecessor's loop arcs, which means we can * break the loop just by removing those loop arcs, with no new states added. */ static void breakconstraintloop(struct nfa * nfa, struct state * sinitial) { struct state *s; struct state *shead; struct state *stail; struct state *sclone; struct state *nexts; struct arc *refarc; struct arc *a; struct arc *nexta; /* * Start by identifying which loop step we want to break at. * Preferentially this is one with only one constraint arc. (XXX are * there any other secondary heuristics we want to use here?) Set refarc * to point to the selected lone constraint arc, if there is one. */ refarc = NULL; s = sinitial; do { nexts = s->tmp; assert(nexts != s); /* should not see any one-element loops */ if (refarc == NULL) { int narcs = 0; for (a = s->outs; a != NULL; a = a->outchain) { if (a->to == nexts && isconstraintarc(a)) { refarc = a; narcs++; } } assert(narcs > 0); if (narcs > 1) { refarc = NULL; /* multiple constraint arcs here, no good */ } } s = nexts; } while (s != sinitial); if (refarc) { /* break at the refarc */ shead = refarc->from; stail = refarc->to; assert(stail == shead->tmp); } else { /* for lack of a better idea, break after sinitial */ shead = sinitial; stail = sinitial->tmp; } /* * Reset the tmp fields so that we can use them for local storage in * clonesuccessorstates. (findconstraintloop won't mind, since it's just * going to abandon its search anyway.) */ for (s = nfa->states; s != NULL; s = s->next) { s->tmp = NULL; } /* * Recursively build clone state(s) as needed. */ sclone = newstate(nfa); if (sclone == NULL) { assert(NISERR()); return; } clonesuccessorstates(nfa, stail, sclone, shead, refarc, NULL, NULL, nfa->nstates); if (NISERR()) { return; } /* * It's possible that sclone has no outarcs at all, in which case it's * useless. (We don't try extremely hard to get rid of useless states * here, but this is an easy and fairly common case.) */ if (sclone->nouts == 0) { freestate(nfa, sclone); sclone = NULL; } /* * Move shead's constraint-loop arcs to point to sclone, or just drop them * if we discovered we don't need sclone. */ for (a = shead->outs; a != NULL; a = nexta) { nexta = a->outchain; if (a->to == stail && isconstraintarc(a)) { if (sclone) { cparc(nfa, a, shead, sclone); } freearc(nfa, a); if (NISERR()) { break; } } } } /* * clonesuccessorstates - create a tree of constraint-arc successor states * * ssource is the state to be cloned, and sclone is the state to copy its * outarcs into. sclone's inarcs, if any, should already be set up. * * spredecessor is the original predecessor state that we are trying to build * successors for (it may not be the immediate predecessor of ssource). * refarc, if not NULL, is the original constraint arc that is known to have * been traversed out of spredecessor to reach the successor(s). * * For each cloned successor state, we transiently create a "donemap" that is * a boolean array showing which source states we've already visited for this * clone state. This prevents infinite recursion as well as useless repeat * visits to the same state subtree (which can add up fast, since typical NFAs * have multiple redundant arc pathways). Each donemap is a char array * indexed by state number. The donemaps are all of the same size "nstates", * which is nfa->nstates as of the start of the recursion. This is enough to * have entries for all preexisting states, but *not* entries for clone * states created during the recursion. That's okay since we have no need to * mark those. * * curdonemap is NULL when recursing to a new sclone state, or sclone's * donemap when we are recursing without having created a new state (which we * do when we decide we can merge a successor state into the current clone * state). outerdonemap is NULL at the top level and otherwise the parent * clone state's donemap. * * The successor states we create and fill here form a strict tree structure, * with each state having exactly one predecessor, except that the toplevel * state has no inarcs as yet (breakconstraintloop will add its inarcs from * spredecessor after we're done). Thus, we can examine sclone's inarcs back * to the root, plus refarc if any, to identify the set of constraints already * known valid at the current point. This allows us to avoid generating extra * successor states. */ static void clonesuccessorstates( struct nfa * nfa, struct state * ssource, struct state * sclone, struct state * spredecessor, struct arc * refarc, char *curdonemap, char *outerdonemap, size_t nstates) { char *donemap; struct arc *a; /* Since this is recursive, it could be driven to stack overflow */ if (STACK_TOO_DEEP(nfa->v->re)) { NERR(REG_ETOOBIG); return; } /* If this state hasn't already got a donemap, create one */ donemap = curdonemap; if (donemap == NULL) { donemap = (char *) MALLOC(nstates * sizeof(char)); if (donemap == NULL) { NERR(REG_ESPACE); return; } if (outerdonemap != NULL) { /* * Not at outermost recursion level, so copy the outer level's * donemap; this ensures that we see states in process of being * visited at outer levels, or already merged into predecessor * states, as ones we shouldn't traverse back to. */ memcpy(donemap, outerdonemap, nstates * sizeof(char)); } else { /* At outermost level, only spredecessor is off-limits */ memset(donemap, 0, nstates * sizeof(char)); assert(spredecessor->no < nstates); donemap[spredecessor->no] = 1; } } /* Mark ssource as visited in the donemap */ assert(ssource->no < nstates); assert(donemap[ssource->no] == 0); donemap[ssource->no] = 1; /* * We proceed by first cloning all of ssource's outarcs, creating new * clone states as needed but not doing more with them than that. Then in * a second pass, recurse to process the child clone states. This allows * us to have only one child clone state per reachable source state, even * when there are multiple outarcs leading to the same state. Also, when * we do visit a child state, its set of inarcs is known exactly, which * makes it safe to apply the constraint-is-already-checked optimization. * Also, this ensures that we've merged all the states we can into the * current clone before we recurse to any children, thus possibly saving * them from making extra images of those states. * * While this function runs, child clone states of the current state are * marked by setting their tmp fields to point to the original state they * were cloned from. This makes it possible to detect multiple outarcs * leading to the same state, and also makes it easy to distinguish clone * states from original states (which will have tmp == NULL). */ for (a = ssource->outs; a != NULL && !NISERR(); a = a->outchain) { struct state *sto = a->to; /* * We do not consider cloning successor states that have no constraint * outarcs; just link to them as-is. They cannot be part of a * constraint loop so there is no need to make copies. In particular, * this rule keeps us from trying to clone the post state, which would * be a bad idea. */ if (isconstraintarc(a) && hasconstraintout(sto)) { struct state *prevclone; int canmerge; struct arc *a2; /* * Back-link constraint arcs must not be followed. Nor is there a * need to revisit states previously merged into this clone. */ assert(sto->no < nstates); if (donemap[sto->no] != 0) { continue; } /* * Check whether we already have a child clone state for this * source state. */ prevclone = NULL; for (a2 = sclone->outs; a2 != NULL; a2 = a2->outchain) { if (a2->to->tmp == sto) { prevclone = a2->to; break; } } /* * If this arc is labeled the same as refarc, or the same as any * arc we must have traversed to get to sclone, then no additional * constraints need to be met to get to sto, so we should just * merge its outarcs into sclone. */ if (refarc && a->type == refarc->type && a->co == refarc->co) { canmerge = 1; } else { struct state *s; canmerge = 0; for (s = sclone; s->ins; s = s->ins->from) { if (s->nins == 1 && a->type == s->ins->type && a->co == s->ins->co) { canmerge = 1; break; } } } if (canmerge) { /* * We can merge into sclone. If we previously made a child * clone state, drop it; there's no need to visit it. (This * can happen if ssource has multiple pathways to sto, and we * only just now found one that is provably a no-op.) */ if (prevclone) { dropstate(nfa, prevclone); /* kills our outarc, too */ } /* Recurse to merge sto's outarcs into sclone */ clonesuccessorstates(nfa, sto, sclone, spredecessor, refarc, donemap, outerdonemap, nstates); /* sto should now be marked as previously visited */ assert(NISERR() || donemap[sto->no] == 1); } else if (prevclone) { /* * We already have a clone state for this successor, so just * make another arc to it. */ cparc(nfa, a, sclone, prevclone); } else { /* * We need to create a new successor clone state. */ struct state *stoclone; stoclone = newstate(nfa); if (stoclone == NULL) { assert(NISERR()); break; } /* Mark it as to what it's a clone of */ stoclone->tmp = sto; /* ... and add the outarc leading to it */ cparc(nfa, a, sclone, stoclone); } } else { /* * Non-constraint outarcs just get copied to sclone, as do outarcs * leading to states with no constraint outarc. */ cparc(nfa, a, sclone, sto); } } /* * If we are at outer level for this clone state, recurse to all its child * clone states, clearing their tmp fields as we go. (If we're not * outermost for sclone, leave this to be done by the outer call level.) * Note that if we have multiple outarcs leading to the same clone state, * it will only be recursed-to once. */ if (curdonemap == NULL) { for (a = sclone->outs; a != NULL && !NISERR(); a = a->outchain) { struct state *stoclone = a->to; struct state *sto = stoclone->tmp; if (sto != NULL) { stoclone->tmp = NULL; clonesuccessorstates(nfa, sto, stoclone, spredecessor, refarc, NULL, donemap, nstates); } } /* Don't forget to free sclone's donemap when done with it */ FREE(donemap); } } /* - cleanup - clean up NFA after optimizations ^ static void cleanup(struct nfa *); */ static void cleanup( struct nfa *nfa) { struct state *s; struct state *nexts; size_t n; /* * Clear out unreachable or dead-end states. Use pre to mark reachable, * then post to mark can-reach-post. */ markreachable(nfa, nfa->pre, NULL, nfa->pre); markcanreach(nfa, nfa->post, nfa->pre, nfa->post); for (s = nfa->states; s != NULL; s = nexts) { nexts = s->next; if (s->tmp != nfa->post && !s->flag) { dropstate(nfa, s); } } assert(nfa->post->nins == 0 || nfa->post->tmp == nfa->post); cleartraverse(nfa, nfa->pre); assert(nfa->post->nins == 0 || nfa->post->tmp == NULL); /* the nins==0 (final unreachable) case will be caught later */ /* * Renumber surviving states. */ n = 0; for (s = nfa->states; s != NULL; s = s->next) { s->no = n++; } nfa->nstates = n; } /* - markreachable - recursive marking of reachable states ^ static void markreachable(struct nfa *, struct state *, struct state *, ^ struct state *); */ static void markreachable( struct nfa *nfa, struct state *s, struct state *okay, /* consider only states with this mark */ struct state *mark) /* the value to mark with */ { struct arc *a; if (s->tmp != okay) { return; } s->tmp = mark; for (a = s->outs; a != NULL; a = a->outchain) { markreachable(nfa, a->to, okay, mark); } } /* - markcanreach - recursive marking of states which can reach here ^ static void markcanreach(struct nfa *, struct state *, struct state *, ^ struct state *); */ static void markcanreach( struct nfa *nfa, struct state *s, struct state *okay, /* consider only states with this mark */ struct state *mark) /* the value to mark with */ { struct arc *a; if (s->tmp != okay) { return; } s->tmp = mark; for (a = s->ins; a != NULL; a = a->inchain) { markcanreach(nfa, a->from, okay, mark); } } /* - analyze - ascertain potentially-useful facts about an optimized NFA ^ static long analyze(struct nfa *); */ static long /* re_info bits to be OR'ed in */ analyze( struct nfa *nfa) { struct arc *a; struct arc *aa; if (nfa->pre->outs == NULL) { return REG_UIMPOSSIBLE; } for (a = nfa->pre->outs; a != NULL; a = a->outchain) { for (aa = a->to->outs; aa != NULL; aa = aa->outchain) { if (aa->to == nfa->post) { return REG_UEMPTYMATCH; } } } return 0; } /* - compact - construct the compact representation of an NFA ^ static void compact(struct nfa *, struct cnfa *); */ static void compact( struct nfa *nfa, struct cnfa *cnfa) { struct state *s; struct arc *a; size_t nstates; size_t narcs; struct carc *ca; struct carc *first; assert(!NISERR()); nstates = 0; narcs = 0; for (s = nfa->states; s != NULL; s = s->next) { nstates++; narcs += s->nouts + 1; /* need one extra for endmarker */ } cnfa->stflags = (char *) MALLOC(nstates * sizeof(char)); cnfa->states = (struct carc **) MALLOC(nstates * sizeof(struct carc *)); cnfa->arcs = (struct carc *) MALLOC(narcs * sizeof(struct carc)); if (cnfa->stflags == NULL || cnfa->states == NULL || cnfa->arcs == NULL) { if (cnfa->stflags != NULL) { FREE(cnfa->stflags); } if (cnfa->states != NULL) { FREE(cnfa->states); } if (cnfa->arcs != NULL) { FREE(cnfa->arcs); } NERR(REG_ESPACE); return; } cnfa->nstates = nstates; cnfa->pre = nfa->pre->no; cnfa->post = nfa->post->no; cnfa->bos[0] = nfa->bos[0]; cnfa->bos[1] = nfa->bos[1]; cnfa->eos[0] = nfa->eos[0]; cnfa->eos[1] = nfa->eos[1]; cnfa->ncolors = maxcolor(nfa->cm) + 1; cnfa->flags = 0; ca = cnfa->arcs; for (s = nfa->states; s != NULL; s = s->next) { assert(s->no < nstates); cnfa->stflags[s->no] = 0; cnfa->states[s->no] = ca; first = ca; for (a = s->outs; a != NULL; a = a->outchain) { switch (a->type) { case PLAIN: ca->co = a->co; ca->to = a->to->no; ca++; break; case LACON: assert(s->no != cnfa->pre); ca->co = (color) (cnfa->ncolors + a->co); ca->to = a->to->no; ca++; cnfa->flags |= HASLACONS; break; default: NERR(REG_ASSERT); break; } } carcsort(first, ca - first); ca->co = COLORLESS; ca->to = 0; ca++; } assert(ca == &cnfa->arcs[narcs]); assert(cnfa->nstates != 0); /* * Mark no-progress states. */ for (a = nfa->pre->outs; a != NULL; a = a->outchain) { cnfa->stflags[a->to->no] = CNFA_NOPROGRESS; } cnfa->stflags[nfa->pre->no] = CNFA_NOPROGRESS; } /* - carcsort - sort compacted-NFA arcs by color ^ static void carcsort(struct carc *, struct carc *); */ static void carcsort( struct carc *first, size_t n) { if (n > 1) { qsort(first, n, sizeof(struct carc), carc_cmp); } } static int carc_cmp( const void *a, const void *b) { const struct carc *aa = (const struct carc *) a; const struct carc *bb = (const struct carc *) b; if (aa->co < bb->co) { return -1; } if (aa->co > bb->co) { return +1; } if (aa->to < bb->to) { return -1; } if (aa->to > bb->to) { return +1; } return 0; } /* - freecnfa - free a compacted NFA ^ static void freecnfa(struct cnfa *); */ static void freecnfa( struct cnfa *cnfa) { assert(cnfa->nstates != 0); /* not empty already */ cnfa->nstates = 0; FREE(cnfa->stflags); FREE(cnfa->states); FREE(cnfa->arcs); } /* - dumpnfa - dump an NFA in human-readable form ^ static void dumpnfa(struct nfa *, FILE *); */ static void dumpnfa( struct nfa *nfa, FILE *f) { #ifdef REG_DEBUG struct state *s; size_t nstates = 0; size_t narcs = 0; fprintf(f, "pre %" TCL_Z_MODIFIER "u, post %" TCL_Z_MODIFIER "u", nfa->pre->no, nfa->post->no); if (nfa->bos[0] != COLORLESS) { fprintf(f, ", bos [%ld]", (long) nfa->bos[0]); } if (nfa->bos[1] != COLORLESS) { fprintf(f, ", bol [%ld]", (long) nfa->bos[1]); } if (nfa->eos[0] != COLORLESS) { fprintf(f, ", eos [%ld]", (long) nfa->eos[0]); } if (nfa->eos[1] != COLORLESS) { fprintf(f, ", eol [%ld]", (long) nfa->eos[1]); } fprintf(f, "\n"); for (s = nfa->states; s != NULL; s = s->next) { dumpstate(s, f); nstates++; narcs += s->nouts; } fprintf(f, "total of %" TCL_Z_MODIFIER "u states, %" TCL_Z_MODIFIER "u arcs\n", nstates, narcs); if (nfa->parent == NULL) { dumpcolors(nfa->cm, f); } fflush(f); #else (void)nfa; (void)f; #endif } #ifdef REG_DEBUG /* subordinates of dumpnfa */ /* ^ #ifdef REG_DEBUG */ /* - dumpstate - dump an NFA state in human-readable form ^ static void dumpstate(struct state *, FILE *); */ static void dumpstate( struct state *s, FILE *f) { struct arc *a; fprintf(f, "%" TCL_Z_MODIFIER "u%s%c", s->no, (s->tmp != NULL) ? "T" : "", (s->flag) ? s->flag : '.'); if (s->prev != NULL && s->prev->next != s) { fprintf(f, "\tstate chain bad\n"); } if (s->nouts == 0) { fprintf(f, "\tno out arcs\n"); } else { dumparcs(s, f); } fflush(f); for (a = s->ins; a != NULL; a = a->inchain) { if (a->to != s) { fprintf(f, "\tlink from %" TCL_Z_MODIFIER "u to %" TCL_Z_MODIFIER "u on %" TCL_Z_MODIFIER "u's in-chain\n", a->from->no, a->to->no, s->no); } } } /* - dumparcs - dump out-arcs in human-readable form ^ static void dumparcs(struct state *, FILE *); */ static void dumparcs( struct state *s, FILE *f) { int pos; struct arc *a; /* printing oldest arcs first is usually clearer */ a = s->outs; assert(a != NULL); while (a->outchain != NULL) { a = a->outchain; } pos = 1; do { dumparc(a, s, f); if (pos == 5) { fprintf(f, "\n"); pos = 1; } else { pos++; } a = a->outchainRev; } while (a != NULL); if (pos != 1) { fprintf(f, "\n"); } } /* - dumparc - dump one outarc in readable form, including prefixing tab ^ static void dumparc(struct arc *, struct state *, FILE *); */ static void dumparc( struct arc *a, struct state *s, FILE *f) { struct arc *aa; struct arcbatch *ab; fprintf(f, "\t"); switch (a->type) { case PLAIN: fprintf(f, "[%ld]", (long) a->co); break; case AHEAD: fprintf(f, ">%ld>", (long) a->co); break; case BEHIND: fprintf(f, "<%ld<", (long) a->co); break; case LACON: fprintf(f, ":%ld:", (long) a->co); break; case '^': case '$': fprintf(f, "%c%d", a->type, (int) a->co); break; case EMPTY: break; default: fprintf(f, "0x%x/0%lo", a->type, (long) a->co); break; } if (a->from != s) { fprintf(f, "?%" TCL_Z_MODIFIER "u?", a->from->no); } for (ab = &a->from->oas; ab != NULL; ab = ab->next) { for (aa = &ab->a[0]; aa < &ab->a[ABSIZE]; aa++) { if (aa == a) { break; /* NOTE BREAK OUT */ } } if (aa < &ab->a[ABSIZE]) { /* propagate break */ break; /* NOTE BREAK OUT */ } } if (ab == NULL) { fprintf(f, "?!?"); /* not in allocated space */ } fprintf(f, "->"); if (a->to == NULL) { fprintf(f, "NULL"); return; } fprintf(f, "%" TCL_Z_MODIFIER "u", a->to->no); for (aa = a->to->ins; aa != NULL; aa = aa->inchain) { if (aa == a) { break; /* NOTE BREAK OUT */ } } if (aa == NULL) { fprintf(f, "?!?"); /* missing from in-chain */ } } /* ^ #endif */ #endif /* ifdef REG_DEBUG */ /* - dumpcnfa - dump a compacted NFA in human-readable form ^ static void dumpcnfa(struct cnfa *, FILE *); */ static void dumpcnfa( struct cnfa *cnfa, FILE *f) { #ifdef REG_DEBUG size_t st; fprintf(f, "pre %" TCL_Z_MODIFIER "u, post %" TCL_Z_MODIFIER "u", cnfa->pre, cnfa->post); if (cnfa->bos[0] != COLORLESS) { fprintf(f, ", bos [%ld]", (long) cnfa->bos[0]); } if (cnfa->bos[1] != COLORLESS) { fprintf(f, ", bol [%ld]", (long) cnfa->bos[1]); } if (cnfa->eos[0] != COLORLESS) { fprintf(f, ", eos [%ld]", (long) cnfa->eos[0]); } if (cnfa->eos[1] != COLORLESS) { fprintf(f, ", eol [%ld]", (long) cnfa->eos[1]); } if (cnfa->flags&HASLACONS) { fprintf(f, ", haslacons"); } fprintf(f, "\n"); for (st = 0; st < cnfa->nstates; st++) { dumpcstate(st, cnfa, f); } fflush(f); #else (void)cnfa; (void)f; #endif } #ifdef REG_DEBUG /* subordinates of dumpcnfa */ /* ^ #ifdef REG_DEBUG */ /* - dumpcstate - dump a compacted-NFA state in human-readable form ^ static void dumpcstate(int, struct cnfa *, FILE *); */ static void dumpcstate( int st, struct cnfa *cnfa, FILE *f) { struct carc *ca; size_t pos; fprintf(f, "%d%s", st, (cnfa->stflags[st] & CNFA_NOPROGRESS) ? ":" : "."); pos = 1; for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++) { if (ca->co < cnfa->ncolors) { fprintf(f, "\t[%d]->%" TCL_Z_MODIFIER "u", ca->co, ca->to); } else { fprintf(f, "\t:%d:->%" TCL_Z_MODIFIER "u", ca->co - cnfa->ncolors, ca->to); } if (pos == 5) { fprintf(f, "\n"); pos = 1; } else { pos++; } } if (ca == cnfa->states[st] || pos != 1) { fprintf(f, "\n"); } fflush(f); } /* ^ #endif */ #endif /* ifdef REG_DEBUG */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/regcomp.c0000644000175000017500000015722314726623136015004 0ustar sergeisergei/* * re_*comp and friends - compile REs * This file #includes several others (see the bottom). * * Copyright © 1998, 1999 Henry Spencer. All rights reserved. * * Development of this software was funded, in part, by Cray Research Inc., * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics * Corporation, none of whom are responsible for the results. The author * thanks all of them. * * Redistribution and use in source and binary forms -- with or without * modification -- are permitted for any purpose, provided that * redistributions in source form retain this entire copyright notice and * indicate the origin and nature of any modifications. * * I'd appreciate being given credit for this package in the documentation of * software which uses it, but that is not a requirement. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "regguts.h" /* * forward declarations, up here so forward datatypes etc. are defined early */ /* =====^!^===== begin forwards =====^!^===== */ /* automatically gathered by fwd; do not hand-edit */ /* === regcomp.c === */ int compile(regex_t *, const chr *, size_t, int); static void moresubs(struct vars *, size_t); static int freev(struct vars *, int); static void makesearch(struct vars *, struct nfa *); static struct subre *parse(struct vars *, int, int, struct state *, struct state *); static struct subre *parsebranch(struct vars *, int, int, struct state *, struct state *, int); static void parseqatom(struct vars *, int, int, struct state *, struct state *, struct subre *); static void nonword(struct vars *, int, struct state *, struct state *); static void word(struct vars *, int, struct state *, struct state *); static int scannum(struct vars *); static void repeat(struct vars *, struct state *, struct state *, int, int); static void bracket(struct vars *, struct state *, struct state *); static void cbracket(struct vars *, struct state *, struct state *); static void brackpart(struct vars *, struct state *, struct state *); static const chr *scanplain(struct vars *); static void onechr(struct vars *, pchr, struct state *, struct state *); static void dovec(struct vars *, struct cvec *, struct state *, struct state *); static void wordchrs(struct vars *); static struct subre *sub_re(struct vars *, int, int, struct state *, struct state *); static void freesubre(struct vars *, struct subre *); static void freesrnode(struct vars *, struct subre *); static int numst(struct subre *, int); static void markst(struct subre *); static void cleanst(struct vars *); static long nfatree(struct vars *, struct subre *, FILE *); static long nfanode(struct vars *, struct subre *, FILE *); static int newlacon(struct vars *, struct state *, struct state *, int); static void freelacons(struct subre *, int); static void rfree(regex_t *); static void dump(regex_t *, FILE *); static void dumpst(struct subre *, FILE *, int); static void stdump(struct subre *, FILE *, int); static const char *stid(struct subre *, char *, size_t); /* === regc_lex.c === */ static void lexstart(struct vars *); static void prefixes(struct vars *); static void lexnest(struct vars *, const chr *, const chr *); static void lexword(struct vars *); static int next(struct vars *); static int lexescape(struct vars *); static int lexdigits(struct vars *, int, int, int); static int brenext(struct vars *, pchr); static void skip(struct vars *); static chr newline(void); static chr chrnamed(struct vars *, const chr *, const chr *, pchr); /* === regc_color.c === */ static void initcm(struct vars *, struct colormap *); static void freecm(struct colormap *); static void cmtreefree(struct colormap *, union tree *, int); static color setcolor(struct colormap *, pchr, pcolor); static color maxcolor(struct colormap *); static color newcolor(struct colormap *); static void freecolor(struct colormap *, pcolor); static color pseudocolor(struct colormap *); static color subcolor(struct colormap *, pchr c); static color newsub(struct colormap *, pcolor); static void subrange(struct vars *, pchr, pchr, struct state *, struct state *); static void subblock(struct vars *, pchr, struct state *, struct state *); static void okcolors(struct nfa *, struct colormap *); static void colorchain(struct colormap *, struct arc *); static void uncolorchain(struct colormap *, struct arc *); static void rainbow(struct nfa *, struct colormap *, int, pcolor, struct state *, struct state *); static void colorcomplement(struct nfa *, struct colormap *, int, struct state *, struct state *, struct state *); #ifdef REG_DEBUG static void dumpcolors(struct colormap *, FILE *); static void fillcheck(struct colormap *, union tree *, int, FILE *); static void dumpchr(pchr, FILE *); #endif /* === regc_nfa.c === */ static struct nfa *newnfa(struct vars *, struct colormap *, struct nfa *); static void freenfa(struct nfa *); static struct state *newstate(struct nfa *); static struct state *newfstate(struct nfa *, int flag); static void dropstate(struct nfa *, struct state *); static void freestate(struct nfa *, struct state *); static void destroystate(struct nfa *, struct state *); static void newarc(struct nfa *, int, pcolor, struct state *, struct state *); static void createarc(struct nfa *, int, pcolor, struct state *, struct state *); static struct arc *allocarc(struct nfa *, struct state *); static void freearc(struct nfa *, struct arc *); static void changearctarget(struct arc *, struct state *); static int hasnonemptyout(struct state *); static struct arc *findarc(struct state *, int, pcolor); static void cparc(struct nfa *, struct arc *, struct state *, struct state *); static void sortins(struct nfa *, struct state *); static int sortins_cmp(const void *, const void *); static void sortouts(struct nfa *, struct state *); static int sortouts_cmp(const void *, const void *); static void moveins(struct nfa *, struct state *, struct state *); static void copyins(struct nfa *, struct state *, struct state *); static void mergeins(struct nfa *, struct state *, struct arc **, int); static void moveouts(struct nfa *, struct state *, struct state *); static void copyouts(struct nfa *, struct state *, struct state *); static void cloneouts(struct nfa *, struct state *, struct state *, struct state *, int); static void delsub(struct nfa *, struct state *, struct state *); static void deltraverse(struct nfa *, struct state *, struct state *); static void dupnfa(struct nfa *, struct state *, struct state *, struct state *, struct state *); static void duptraverse(struct nfa *, struct state *, struct state *, int); static void cleartraverse(struct nfa *, struct state *); static void specialcolors(struct nfa *); static long optimize(struct nfa *, FILE *); static void pullback(struct nfa *, FILE *); static int pull(struct nfa *, struct arc *, struct state **); static void pushfwd(struct nfa *, FILE *); static int push(struct nfa *, struct arc *, struct state **); #define INCOMPATIBLE 1 /* destroys arc */ #define SATISFIED 2 /* constraint satisfied */ #define COMPATIBLE 3 /* compatible but not satisfied yet */ static int combine(struct arc *, struct arc *); static void fixempties(struct nfa *, FILE *); static struct state *emptyreachable(struct nfa *, struct state *, struct state *, struct arc **); static int isconstraintarc(struct arc *); static int hasconstraintout(struct state *); static void fixconstraintloops(struct nfa *, FILE *); static int findconstraintloop(struct nfa *, struct state *); static void breakconstraintloop(struct nfa *, struct state *); static void clonesuccessorstates(struct nfa *, struct state *, struct state *, struct state *, struct arc *, char *, char *, size_t); static void cleanup(struct nfa *); static void markreachable(struct nfa *, struct state *, struct state *, struct state *); static void markcanreach(struct nfa *, struct state *, struct state *, struct state *); static long analyze(struct nfa *); static void compact(struct nfa *, struct cnfa *); static void carcsort(struct carc *, size_t); static int carc_cmp(const void *, const void *); static void freecnfa(struct cnfa *); static void dumpnfa(struct nfa *, FILE *); #ifdef REG_DEBUG static void dumpstate(struct state *, FILE *); static void dumparcs(struct state *, FILE *); static void dumparc(struct arc *, struct state *, FILE *); #endif static void dumpcnfa(struct cnfa *, FILE *); #ifdef REG_DEBUG static void dumpcstate(int, struct cnfa *, FILE *); #endif /* === regc_cvec.c === */ static struct cvec *clearcvec(struct cvec *); static void addchr(struct cvec *, pchr); static void addrange(struct cvec *, pchr, pchr); static struct cvec *newcvec(size_t, size_t); static struct cvec *getcvec(struct vars *, size_t, size_t); static void freecvec(struct cvec *); /* === regc_locale.c === */ static celt element(struct vars *, const chr *, const chr *); static struct cvec *range(struct vars *, celt, celt, int); static int before(celt, celt); static struct cvec *eclass(struct vars *, celt, int); static struct cvec *cclass(struct vars *, const chr *, const chr *, int); static struct cvec *allcases(struct vars *, pchr); static int cmp(const chr *, const chr *, size_t); static int casecmp(const chr *, const chr *, size_t); /* automatically gathered by fwd; do not hand-edit */ /* =====^!^===== end forwards =====^!^===== */ /* internal variables, bundled for easy passing around */ struct vars { regex_t *re; const chr *now; /* scan pointer into string */ const chr *stop; /* end of string */ const chr *savenow; /* saved now and stop for "subroutine call" */ const chr *savestop; int err; /* error code (0 if none) */ int cflags; /* copy of compile flags */ int lasttype; /* type of previous token */ int nexttype; /* type of next token */ size_t nextvalue; /* value (if any) of next token */ int lexcon; /* lexical context type (see lex.c) */ size_t nsubexp; /* subexpression count */ struct subre **subs; /* subRE pointer vector */ size_t nsubs; /* length of vector */ struct subre *sub10[10]; /* initial vector, enough for most */ struct nfa *nfa; /* the NFA */ struct colormap *cm; /* character color map */ color nlcolor; /* color of newline */ struct state *wordchrs; /* state in nfa holding word-char outarcs */ struct subre *tree; /* subexpression tree */ struct subre *treechain; /* all tree nodes allocated */ struct subre *treefree; /* any free tree nodes */ int ntree; /* number of tree nodes, plus one */ struct cvec *cv; /* interface cvec */ struct cvec *cv2; /* utility cvec */ struct subre *lacons; /* lookahead-constraint vector */ size_t nlacons; /* size of lacons */ size_t spaceused; /* approx. space used for compilation */ }; /* parsing macros; most know that `v' is the struct vars pointer */ #define NEXT() (next(v)) /* advance by one token */ #define SEE(t) (v->nexttype == (t)) /* is next token this? */ #define EAT(t) (SEE(t) && next(v)) /* if next is this, swallow it */ #define VISERR(vv) ((vv)->err != 0)/* have we seen an error yet? */ #define ISERR() VISERR(v) #define VERR(vv,e) ((vv)->nexttype = EOS, \ (vv)->err = ((vv)->err ? (vv)->err : (e))) #define ERR(e) VERR(v, e) /* record an error */ #define NOERR() {if (ISERR()) return;} /* if error seen, return */ #define NOERRN() {if (ISERR()) return NULL;} /* NOERR with retval */ #define NOERRZ() {if (ISERR()) return 0;} /* NOERR with retval */ #define INSIST(c, e) do { if (!(c)) ERR(e); } while (0) /* error if c false */ #define NOTE(b) (v->re->re_info |= (b)) /* note visible condition */ #define EMPTYARC(x, y) newarc(v->nfa, EMPTY, 0, x, y) /* token type codes, some also used as NFA arc types */ #undef DIGIT /* prevent conflict with libtommath */ #define EMPTY 'n' /* no token present */ #define EOS 'e' /* end of string */ #define PLAIN 'p' /* ordinary character */ #define DIGIT 'd' /* digit (in bound) */ #define BACKREF 'b' /* back reference */ #define COLLEL 'I' /* start of [. */ #define ECLASS 'E' /* start of [= */ #define CCLASS 'C' /* start of [: */ #define END 'X' /* end of [. [= [: */ #define RANGE 'R' /* - within [] which might be range delim. */ #define LACON 'L' /* lookahead constraint subRE */ #define AHEAD 'a' /* color-lookahead arc */ #define BEHIND 'r' /* color-lookbehind arc */ #define WBDRY 'w' /* word boundary constraint */ #define NWBDRY 'W' /* non-word-boundary constraint */ #define SBEGIN 'A' /* beginning of string (even if not BOL) */ #define SEND 'Z' /* end of string (even if not EOL) */ #define PREFER 'P' /* length preference */ /* is an arc colored, and hence on a color chain? */ #define COLORED(a) \ ((a)->type == PLAIN || (a)->type == AHEAD || (a)->type == BEHIND) /* static function list */ static const struct fns functions = { rfree, /* regfree insides */ }; /* - compile - compile regular expression * Note: on failure, no resources remain allocated, so regfree() * need not be applied to re. ^ int compile(regex_t *, const chr *, size_t, int); */ int compile( regex_t *re, const chr *string, size_t len, int flags) { AllocVars(v); struct guts *g; size_t i, j; FILE *debug = (flags®_PROGRESS) ? stdout : NULL; #define CNOERR() { if (ISERR()) return freev(v, v->err); } /* * Sanity checks. */ if (re == NULL || string == NULL) { FreeVars(v); return REG_INVARG; } if ((flags®_QUOTE) && (flags&(REG_ADVANCED|REG_EXPANDED|REG_NEWLINE))) { FreeVars(v); return REG_INVARG; } if (!(flags®_EXTENDED) && (flags®_ADVF)) { FreeVars(v); return REG_INVARG; } /* * Initial setup (after which freev() is callable). */ v->re = re; v->now = string; v->stop = v->now + len; v->savenow = v->savestop = NULL; v->err = 0; v->cflags = flags; v->nsubexp = 0; v->subs = v->sub10; v->nsubs = 10; for (j = 0; j < v->nsubs; j++) { v->subs[j] = NULL; } v->nfa = NULL; v->cm = NULL; v->nlcolor = COLORLESS; v->wordchrs = NULL; v->tree = NULL; v->treechain = NULL; v->treefree = NULL; v->cv = NULL; v->cv2 = NULL; v->lacons = NULL; v->nlacons = 0; v->spaceused = 0; re->re_magic = REMAGIC; re->re_info = 0; /* bits get set during parse */ re->re_guts = NULL; re->re_fns = (void*)(&functions); /* * More complex setup, malloced things. */ re->re_guts = (void*)(MALLOC(sizeof(struct guts))); if (re->re_guts == NULL) { return freev(v, REG_ESPACE); } g = (struct guts *) re->re_guts; g->tree = NULL; initcm(v, &g->cmap); v->cm = &g->cmap; g->lacons = NULL; g->nlacons = 0; ZAPCNFA(g->search); v->nfa = newnfa(v, v->cm, NULL); CNOERR(); v->cv = newcvec(100, 20); if (v->cv == NULL) { return freev(v, REG_ESPACE); } /* * Parsing. */ lexstart(v); /* also handles prefixes */ if ((v->cflags®_NLSTOP) || (v->cflags®_NLANCH)) { /* * Assign newline a unique color. */ v->nlcolor = subcolor(v->cm, newline()); okcolors(v->nfa, v->cm); } CNOERR(); v->tree = parse(v, EOS, PLAIN, v->nfa->init, v->nfa->final); assert(SEE(EOS)); /* even if error; ISERR() => SEE(EOS) */ CNOERR(); assert(v->tree != NULL); /* * Finish setup of nfa and its subre tree. */ specialcolors(v->nfa); CNOERR(); if (debug != NULL) { fprintf(debug, "\n\n\n========= RAW ==========\n"); dumpnfa(v->nfa, debug); dumpst(v->tree, debug, 1); } v->ntree = numst(v->tree, 1); markst(v->tree); cleanst(v); if (debug != NULL) { fprintf(debug, "\n\n\n========= TREE FIXED ==========\n"); dumpst(v->tree, debug, 1); } /* * Build compacted NFAs for tree and lacons. */ re->re_info |= nfatree(v, v->tree, debug); CNOERR(); assert(v->nlacons == 0 || v->lacons != NULL); for (i = 1; i < v->nlacons; i++) { if (debug != NULL) { fprintf(debug, "\n\n\n========= LA%" TCL_Z_MODIFIER "u ==========\n", i); } nfanode(v, &v->lacons[i], debug); } CNOERR(); if (v->tree->flags&SHORTER) { NOTE(REG_USHORTEST); } /* * Build compacted NFAs for tree, lacons, fast search. */ if (debug != NULL) { fprintf(debug, "\n\n\n========= SEARCH ==========\n"); } /* * Can sacrifice main NFA now, so use it as work area. */ (void) optimize(v->nfa, debug); CNOERR(); makesearch(v, v->nfa); CNOERR(); compact(v->nfa, &g->search); CNOERR(); /* * Looks okay, package it up. */ re->re_nsub = v->nsubexp; v->re = NULL; /* freev no longer frees re */ g->magic = GUTSMAGIC; g->cflags = v->cflags; g->info = re->re_info; g->nsub = re->re_nsub; g->tree = v->tree; v->tree = NULL; g->ntree = v->ntree; g->compare = (v->cflags®_ICASE) ? casecmp : cmp; g->lacons = v->lacons; v->lacons = NULL; g->nlacons = v->nlacons; if (flags®_DUMP) { dump(re, stdout); } assert(v->err == 0); return freev(v, 0); } /* - moresubs - enlarge subRE vector ^ static void moresubs(struct vars *, size_t); */ static void moresubs( struct vars *v, size_t wanted) /* want enough room for this one */ { struct subre **p; size_t n; assert(wanted > 0 && wanted >= v->nsubs); n = wanted * 3 / 2 + 1; if (v->subs == v->sub10) { p = (struct subre **) MALLOC(n * sizeof(struct subre *)); if (p != NULL) { memcpy(p, v->subs, v->nsubs * sizeof(struct subre *)); } } else { p = (struct subre **) REALLOC(v->subs, n*sizeof(struct subre *)); } if (p == NULL) { ERR(REG_ESPACE); return; } v->subs = p; for (p = &v->subs[v->nsubs]; v->nsubs < n; p++, v->nsubs++) { *p = NULL; } assert(v->nsubs == n); assert(wanted < v->nsubs); } /* - freev - free vars struct's substructures where necessary * Optionally does error-number setting, and always returns error code (if * any), to make error-handling code terser. ^ static int freev(struct vars *, int); */ static int freev( struct vars *v, int err) { int ret; if (v->re != NULL) { rfree(v->re); } if (v->subs != v->sub10) { FREE(v->subs); } if (v->nfa != NULL) { freenfa(v->nfa); } if (v->tree != NULL) { freesubre(v, v->tree); } if (v->treechain != NULL) { cleanst(v); } if (v->cv != NULL) { freecvec(v->cv); } if (v->cv2 != NULL) { freecvec(v->cv2); } if (v->lacons != NULL) { freelacons(v->lacons, v->nlacons); } ERR(err); /* nop if err==0 */ ret = v->err; FreeVars(v); return ret; } /* - makesearch - turn an NFA into a search NFA (implicit prepend of .*?) * NFA must have been optimize()d already. ^ static void makesearch(struct vars *, struct nfa *); */ static void makesearch( struct vars *v, struct nfa *nfa) { struct arc *a, *b; struct state *pre = nfa->pre; struct state *s, *s2, *slist; /* * No loops are needed if it's anchored. */ for (a = pre->outs; a != NULL; a = a->outchain) { assert(a->type == PLAIN); if (a->co != nfa->bos[0] && a->co != nfa->bos[1]) { break; } } if (a != NULL) { /* * Add implicit .* in front. */ rainbow(nfa, v->cm, PLAIN, COLORLESS, pre, pre); /* * And ^* and \A* too -- not always necessary, but harmless. */ newarc(nfa, PLAIN, nfa->bos[0], pre, pre); newarc(nfa, PLAIN, nfa->bos[1], pre, pre); } /* * Now here's the subtle part. Because many REs have no lookback * constraints, often knowing when you were in the pre state tells you * little; it's the next state(s) that are informative. But some of them * may have other inarcs, i.e. it may be possible to make actual progress * and then return to one of them. We must de-optimize such cases, * splitting each such state into progress and no-progress states. */ /* * First, make a list of the states. */ slist = NULL; for (a=pre->outs ; a!=NULL ; a=a->outchain) { s = a->to; for (b=s->ins ; b!=NULL ; b=b->inchain) { if (b->from != pre) { break; } } /* * We want to mark states as being in the list already by having non * NULL tmp fields, but we can't just store the old slist value in tmp * because that doesn't work for the first such state. Instead, the * first list entry gets its own address in tmp. */ if (b != NULL && s->tmp == NULL) { s->tmp = (slist != NULL) ? slist : s; slist = s; } } /* * Do the splits. */ for (s=slist ; s!=NULL ; s=s2) { s2 = newstate(nfa); NOERR(); copyouts(nfa, s, s2); NOERR(); for (a=s->ins ; a!=NULL ; a=b) { b = a->inchain; if (a->from != pre) { cparc(nfa, a, a->from, s2); freearc(nfa, a); } } s2 = (s->tmp != s) ? s->tmp : NULL; s->tmp = NULL; /* clean up while we're at it */ } } /* - parse - parse an RE * This is actually just the top level, which parses a bunch of branches tied * together with '|'. They appear in the tree as the left children of a chain * of '|' subres. ^ static struct subre *parse(struct vars *, int, int, struct state *, ^ struct state *); */ static struct subre * parse( struct vars *v, int stopper, /* EOS or ')' */ int type, /* LACON (lookahead subRE) or PLAIN */ struct state *init, /* initial state */ struct state *final) /* final state */ { struct state *left, *right; /* scaffolding for branch */ struct subre *branches; /* top level */ struct subre *branch; /* current branch */ struct subre *t; /* temporary */ int firstbranch; /* is this the first branch? */ assert(stopper == ')' || stopper == EOS); branches = sub_re(v, '|', LONGER, init, final); NOERRN(); branch = branches; firstbranch = 1; do { /* a branch */ if (!firstbranch) { /* * Need a place to hang the branch. */ branch->right = sub_re(v, '|', LONGER, init, final); NOERRN(); branch = branch->right; } firstbranch = 0; left = newstate(v->nfa); right = newstate(v->nfa); NOERRN(); EMPTYARC(init, left); EMPTYARC(right, final); NOERRN(); branch->left = parsebranch(v, stopper, type, left, right, 0); NOERRN(); branch->flags |= UP(branch->flags | branch->left->flags); if ((branch->flags &~ branches->flags) != 0) { /* new flags */ for (t = branches; t != branch; t = t->right) { t->flags |= branch->flags; } } } while (EAT('|')); assert(SEE(stopper) || SEE(EOS)); if (!SEE(stopper)) { assert(stopper == ')' && SEE(EOS)); ERR(REG_EPAREN); } /* * Optimize out simple cases. */ if (branch == branches) { /* only one branch */ assert(branch->right == NULL); t = branch->left; branch->left = NULL; freesubre(v, branches); branches = t; } else if (!MESSY(branches->flags)) { /* no interesting innards */ freesubre(v, branches->left); branches->left = NULL; freesubre(v, branches->right); branches->right = NULL; branches->op = '='; } return branches; } /* - parsebranch - parse one branch of an RE * This mostly manages concatenation, working closely with parseqatom(). * Concatenated things are bundled up as much as possible, with separate * ',' nodes introduced only when necessary due to substructure. ^ static struct subre *parsebranch(struct vars *, int, int, struct state *, ^ struct state *, int); */ static struct subre * parsebranch( struct vars *v, int stopper, /* EOS or ')' */ int type, /* LACON (lookahead subRE) or PLAIN */ struct state *left, /* leftmost state */ struct state *right, /* rightmost state */ int partial) /* is this only part of a branch? */ { struct state *lp; /* left end of current construct */ int seencontent; /* is there anything in this branch yet? */ struct subre *t; lp = left; seencontent = 0; t = sub_re(v, '=', 0, left, right); /* op '=' is tentative */ NOERRN(); while (!SEE('|') && !SEE(stopper) && !SEE(EOS)) { if (seencontent) { /* implicit concat operator */ lp = newstate(v->nfa); NOERRN(); moveins(v->nfa, right, lp); } seencontent = 1; /* NB, recursion in parseqatom() may swallow rest of branch */ parseqatom(v, stopper, type, lp, right, t); NOERRN(); } if (!seencontent) { /* empty branch */ if (!partial) { NOTE(REG_UUNSPEC); } assert(lp == left); EMPTYARC(left, right); } return t; } /* - parseqatom - parse one quantified atom or constraint of an RE * The bookkeeping near the end cooperates very closely with parsebranch(); in * particular, it contains a recursion that can involve parsing the rest of * the branch, making this function's name somewhat inaccurate. ^ static void parseqatom(struct vars *, int, int, struct state *, ^ struct state *, struct subre *); */ static void parseqatom( struct vars *v, int stopper, /* EOS or ')' */ int type, /* LACON (lookahead subRE) or PLAIN */ struct state *lp, /* left state to hang it on */ struct state *rp, /* right state to hang it on */ struct subre *top) /* subtree top */ { struct state *s; /* temporaries for new states */ struct state *s2; #define ARCV(t, val) newarc(v->nfa, t, val, lp, rp) int m, n; struct subre *atom; /* atom's subtree */ struct subre *t; int cap; /* capturing parens? */ int pos; /* positive lookahead? */ size_t subno; /* capturing-parens or backref number */ int atomtype; int qprefer; /* quantifier short/long preference */ int f; struct subre **atomp; /* where the pointer to atom is */ /* * Initial bookkeeping. */ atom = NULL; assert(lp->nouts == 0); /* must string new code */ assert(rp->nins == 0); /* between lp and rp */ subno = 0; /* * An atom or constraint... */ atomtype = v->nexttype; switch (atomtype) { /* first, constraints, which end by returning */ case '^': ARCV('^', 1); if (v->cflags®_NLANCH) { ARCV(BEHIND, v->nlcolor); } NEXT(); return; case '$': ARCV('$', 1); if (v->cflags®_NLANCH) { ARCV(AHEAD, v->nlcolor); } NEXT(); return; case SBEGIN: ARCV('^', 1); /* BOL */ ARCV('^', 0); /* or BOS */ NEXT(); return; case SEND: ARCV('$', 1); /* EOL */ ARCV('$', 0); /* or EOS */ NEXT(); return; case '<': wordchrs(v); /* does NEXT() */ s = newstate(v->nfa); NOERR(); nonword(v, BEHIND, lp, s); word(v, AHEAD, s, rp); return; case '>': wordchrs(v); /* does NEXT() */ s = newstate(v->nfa); NOERR(); word(v, BEHIND, lp, s); nonword(v, AHEAD, s, rp); return; case WBDRY: wordchrs(v); /* does NEXT() */ s = newstate(v->nfa); NOERR(); nonword(v, BEHIND, lp, s); word(v, AHEAD, s, rp); s = newstate(v->nfa); NOERR(); word(v, BEHIND, lp, s); nonword(v, AHEAD, s, rp); return; case NWBDRY: wordchrs(v); /* does NEXT() */ s = newstate(v->nfa); NOERR(); word(v, BEHIND, lp, s); word(v, AHEAD, s, rp); s = newstate(v->nfa); NOERR(); nonword(v, BEHIND, lp, s); nonword(v, AHEAD, s, rp); return; case LACON: /* lookahead constraint */ pos = v->nextvalue; NEXT(); s = newstate(v->nfa); s2 = newstate(v->nfa); NOERR(); t = parse(v, ')', LACON, s, s2); freesubre(v, t); /* internal structure irrelevant */ assert(SEE(')') || ISERR()); NEXT(); n = newlacon(v, s, s2, pos); NOERR(); ARCV(LACON, n); return; /* * Then errors, to get them out of the way. */ case '*': case '+': case '?': case '{': ERR(REG_BADRPT); return; default: ERR(REG_ASSERT); return; /* * Then plain characters, and minor variants on that theme. */ case ')': /* unbalanced paren */ if ((v->cflags®_ADVANCED) != REG_EXTENDED) { ERR(REG_EPAREN); return; } /* * Legal in EREs due to specification botch. */ NOTE(REG_UPBOTCH); /* FALLTHRU */ case PLAIN: onechr(v, v->nextvalue, lp, rp); okcolors(v->nfa, v->cm); NOERR(); NEXT(); break; case '[': if (v->nextvalue == 1) { bracket(v, lp, rp); } else { cbracket(v, lp, rp); } assert(SEE(']') || ISERR()); NEXT(); break; case '.': rainbow(v->nfa, v->cm, PLAIN, (v->cflags®_NLSTOP) ? v->nlcolor : COLORLESS, lp, rp); NEXT(); break; /* * And finally the ugly stuff. */ case '(': /* value flags as capturing or non */ cap = (type == LACON) ? 0 : v->nextvalue; if (cap) { v->nsubexp++; subno = v->nsubexp; if (subno >= v->nsubs) { moresubs(v, subno); } assert(subno < v->nsubs); } else { atomtype = PLAIN; /* something that's not '(' */ } NEXT(); /* * Need new endpoints because tree will contain pointers. */ s = newstate(v->nfa); s2 = newstate(v->nfa); NOERR(); EMPTYARC(lp, s); EMPTYARC(s2, rp); NOERR(); atom = parse(v, ')', PLAIN, s, s2); assert(SEE(')') || ISERR()); NEXT(); NOERR(); if (cap) { v->subs[subno] = atom; t = sub_re(v, '(', atom->flags|CAP, lp, rp); NOERR(); t->subno = subno; t->left = atom; atom = t; } /* * Postpone everything else pending possible {0}. */ break; case BACKREF: /* the Feature From The Black Lagoon */ INSIST(type != LACON, REG_ESUBREG); INSIST(v->nextvalue < v->nsubs, REG_ESUBREG); INSIST(v->subs[v->nextvalue] != NULL, REG_ESUBREG); NOERR(); assert(v->nextvalue > 0); atom = sub_re(v, 'b', BACKR, lp, rp); NOERR(); subno = v->nextvalue; atom->subno = subno; EMPTYARC(lp, rp); /* temporarily, so there's something */ NEXT(); break; } /* * ...and an atom may be followed by a quantifier. */ switch (v->nexttype) { case '*': m = 0; n = DUPINF; qprefer = (v->nextvalue) ? LONGER : SHORTER; NEXT(); break; case '+': m = 1; n = DUPINF; qprefer = (v->nextvalue) ? LONGER : SHORTER; NEXT(); break; case '?': m = 0; n = 1; qprefer = (v->nextvalue) ? LONGER : SHORTER; NEXT(); break; case '{': NEXT(); m = scannum(v); if (EAT(',')) { if (SEE(DIGIT)) { n = scannum(v); } else { n = DUPINF; } if (m > n) { ERR(REG_BADBR); return; } /* * {m,n} exercises preference, even if it's {m,m} */ qprefer = (v->nextvalue) ? LONGER : SHORTER; } else { n = m; /* * {m} passes operand's preference through. */ qprefer = 0; } if (!SEE('}')) { /* catches errors too */ ERR(REG_BADBR); return; } NEXT(); break; default: /* no quantifier */ m = n = 1; qprefer = 0; break; } /* * Annoying special case: {0} or {0,0} cancels everything. */ if (m == 0 && n == 0) { if (atom != NULL) { freesubre(v, atom); } if (atomtype == '(') { v->subs[subno] = NULL; } delsub(v->nfa, lp, rp); EMPTYARC(lp, rp); return; } /* * If not a messy case, avoid hard part. */ assert(!MESSY(top->flags)); f = top->flags | qprefer | ((atom != NULL) ? atom->flags : 0); if (atomtype != '(' && atomtype != BACKREF && !MESSY(UP(f))) { if (!(m == 1 && n == 1)) { repeat(v, lp, rp, m, n); } if (atom != NULL) { freesubre(v, atom); } top->flags = f; return; } /* * hard part: something messy * That is, capturing parens, back reference, short/long clash, or an atom * with substructure containing one of those. */ /* * Now we'll need a subre for the contents even if they're boring. */ if (atom == NULL) { atom = sub_re(v, '=', 0, lp, rp); NOERR(); } /* * Prepare a general-purpose state skeleton. * * In the no-backrefs case, we want this: * * [lp] ---> [s] ---prefix---> [begin] ---atom---> [end] ---rest---> [rp] * * where prefix is some repetitions of atom. In the general case we need * * [lp] ---> [s] ---iterator---> [s2] ---rest---> [rp] * * where the iterator wraps around [begin] ---atom---> [end] * * We make the s state here for both cases; s2 is made below if needed */ s = newstate(v->nfa); /* first, new endpoints for the atom */ s2 = newstate(v->nfa); NOERR(); moveouts(v->nfa, lp, s); moveins(v->nfa, rp, s2); NOERR(); atom->begin = s; atom->end = s2; s = newstate(v->nfa); /* set up starting state */ NOERR(); EMPTYARC(lp, s); NOERR(); /* * Break remaining subRE into x{...} and what follows. */ t = sub_re(v, '.', COMBINE(qprefer, atom->flags), lp, rp); NOERR(); t->left = atom; atomp = &t->left; /* * Here we should recurse... but we must postpone that to the end. */ /* * Split top into prefix and remaining. */ assert(top->op == '=' && top->left == NULL && top->right == NULL); top->left = sub_re(v, '=', top->flags, top->begin, lp); NOERR(); top->op = '.'; top->right = t; /* * If it's a backref, now is the time to replicate the subNFA. */ if (atomtype == BACKREF) { assert(atom->begin->nouts == 1); /* just the EMPTY */ delsub(v->nfa, atom->begin, atom->end); assert(v->subs[subno] != NULL); /* * And here's why the recursion got postponed: it must wait until the * skeleton is filled in, because it may hit a backref that wants to * copy the filled-in skeleton. */ dupnfa(v->nfa, v->subs[subno]->begin, v->subs[subno]->end, atom->begin, atom->end); NOERR(); } /* * It's quantifier time. If the atom is just a backref, we'll let it deal * with quantifiers internally. */ if (atomtype == BACKREF) { /* * Special case: backrefs have internal quantifiers. */ EMPTYARC(s, atom->begin); /* empty prefix */ /* * Just stuff everything into atom. */ repeat(v, atom->begin, atom->end, m, n); atom->min = (short) m; atom->max = (short) n; atom->flags |= COMBINE(qprefer, atom->flags); /* rest of branch can be strung starting from atom->end */ s2 = atom->end; } else if (m == 1 && n == 1) { /* * No/vacuous quantifier: done. */ EMPTYARC(s, atom->begin); /* empty prefix */ /* rest of branch can be strung starting from atom->end */ s2 = atom->end; } else if (m > 0 && !(atom->flags & BACKR)) { /* * If there's no backrefs involved, we can turn x{m,n} into * x{m-1,n-1}x, with capturing parens in only the second x. This * is valid because we only care about capturing matches from the * final iteration of the quantifier. It's a win because we can * implement the backref-free left side as a plain DFA node, since * we don't really care where its submatches are. */ dupnfa(v->nfa, atom->begin, atom->end, s, atom->begin); assert(m >= 1 && m != DUPINF && n >= 1); repeat(v, s, atom->begin, m-1, (n == DUPINF) ? n : n-1); f = COMBINE(qprefer, atom->flags); t = sub_re(v, '.', f, s, atom->end); /* prefix and atom */ NOERR(); t->left = sub_re(v, '=', PREF(f), s, atom->begin); NOERR(); t->right = atom; *atomp = t; /* rest of branch can be strung starting from atom->end */ s2 = atom->end; } else { /* general case: need an iteration node */ s2 = newstate(v->nfa); NOERR(); moveouts(v->nfa, atom->end, s2); NOERR(); dupnfa(v->nfa, atom->begin, atom->end, s, s2); repeat(v, s, s2, m, n); f = COMBINE(qprefer, atom->flags); t = sub_re(v, '*', f, s, s2); NOERR(); t->min = (short) m; t->max = (short) n; t->left = atom; *atomp = t; /* rest of branch is to be strung from iteration's end state */ } /* * And finally, look after that postponed recursion. */ t = top->right; if (!(SEE('|') || SEE(stopper) || SEE(EOS))) { t->right = parsebranch(v, stopper, type, s2, rp, 1); } else { EMPTYARC(s2, rp); t->right = sub_re(v, '=', 0, s2, rp); } NOERR(); assert(SEE('|') || SEE(stopper) || SEE(EOS)); t->flags |= COMBINE(t->flags, t->right->flags); top->flags |= COMBINE(top->flags, t->flags); } /* - nonword - generate arcs for non-word-character ahead or behind ^ static void nonword(struct vars *, int, struct state *, struct state *); */ static void nonword( struct vars *v, int dir, /* AHEAD or BEHIND */ struct state *lp, struct state *rp) { int anchor = (dir == AHEAD) ? '$' : '^'; assert(dir == AHEAD || dir == BEHIND); newarc(v->nfa, anchor, 1, lp, rp); newarc(v->nfa, anchor, 0, lp, rp); colorcomplement(v->nfa, v->cm, dir, v->wordchrs, lp, rp); /* (no need for special attention to \n) */ } /* - word - generate arcs for word character ahead or behind ^ static void word(struct vars *, int, struct state *, struct state *); */ static void word( struct vars *v, int dir, /* AHEAD or BEHIND */ struct state *lp, struct state *rp) { assert(dir == AHEAD || dir == BEHIND); cloneouts(v->nfa, v->wordchrs, lp, rp, dir); /* (no need for special attention to \n) */ } /* - scannum - scan a number ^ static int scannum(struct vars *); */ static int /* value, <= DUPMAX */ scannum( struct vars *v) { int n = 0; while (SEE(DIGIT) && n < DUPMAX) { n = n*10 + v->nextvalue; NEXT(); } if (SEE(DIGIT) || n > DUPMAX) { ERR(REG_BADBR); return 0; } return n; } /* - repeat - replicate subNFA for quantifiers * The sub-NFA strung from lp to rp is modified to represent m to n * repetitions of its initial contents. * The duplication sequences used here are chosen carefully so that any * pointers starting out pointing into the subexpression end up pointing into * the last occurrence. (Note that it may not be strung between the same left * and right end states, however!) This used to be important for the subRE * tree, although the important bits are now handled by the in-line code in * parse(), and when this is called, it doesn't matter any more. ^ static void repeat(struct vars *, struct state *, struct state *, int, int); */ static void repeat( struct vars *v, struct state *lp, struct state *rp, int m, int n) { #define SOME 2 #define INF 3 #define PAIR(x, y) ((x)*4 + (y)) #define REDUCE(x) ( ((x) == DUPINF) ? INF : (((x) > 1) ? SOME : (x)) ) const int rm = REDUCE(m); const int rn = REDUCE(n); struct state *s, *s2; switch (PAIR(rm, rn)) { case PAIR(0, 0): /* empty string */ delsub(v->nfa, lp, rp); EMPTYARC(lp, rp); break; case PAIR(0, 1): /* do as x| */ EMPTYARC(lp, rp); break; case PAIR(0, SOME): /* do as x{1,n}| */ repeat(v, lp, rp, 1, n); NOERR(); EMPTYARC(lp, rp); break; case PAIR(0, INF): /* loop x around */ s = newstate(v->nfa); NOERR(); moveouts(v->nfa, lp, s); moveins(v->nfa, rp, s); EMPTYARC(lp, s); EMPTYARC(s, rp); break; case PAIR(1, 1): /* no action required */ break; case PAIR(1, SOME): /* do as x{0,n-1}x = (x{1,n-1}|)x */ s = newstate(v->nfa); NOERR(); moveouts(v->nfa, lp, s); dupnfa(v->nfa, s, rp, lp, s); NOERR(); repeat(v, lp, s, 1, n-1); NOERR(); EMPTYARC(lp, s); break; case PAIR(1, INF): /* add loopback arc */ s = newstate(v->nfa); s2 = newstate(v->nfa); NOERR(); moveouts(v->nfa, lp, s); moveins(v->nfa, rp, s2); EMPTYARC(lp, s); EMPTYARC(s2, rp); EMPTYARC(s2, s); break; case PAIR(SOME, SOME): /* do as x{m-1,n-1}x */ s = newstate(v->nfa); NOERR(); moveouts(v->nfa, lp, s); dupnfa(v->nfa, s, rp, lp, s); NOERR(); repeat(v, lp, s, m-1, n-1); break; case PAIR(SOME, INF): /* do as x{m-1,}x */ s = newstate(v->nfa); NOERR(); moveouts(v->nfa, lp, s); dupnfa(v->nfa, s, rp, lp, s); NOERR(); repeat(v, lp, s, m-1, n); break; default: ERR(REG_ASSERT); break; } } /* - bracket - handle non-complemented bracket expression * Also called from cbracket for complemented bracket expressions. ^ static void bracket(struct vars *, struct state *, struct state *); */ static void bracket( struct vars *v, struct state *lp, struct state *rp) { assert(SEE('[')); NEXT(); while (!SEE(']') && !SEE(EOS)) { brackpart(v, lp, rp); } assert(SEE(']') || ISERR()); okcolors(v->nfa, v->cm); } /* - cbracket - handle complemented bracket expression * We do it by calling bracket() with dummy endpoints, and then complementing * the result. The alternative would be to invoke rainbow(), and then delete * arcs as the b.e. is seen... but that gets messy. ^ static void cbracket(struct vars *, struct state *, struct state *); */ static void cbracket( struct vars *v, struct state *lp, struct state *rp) { struct state *left = newstate(v->nfa); struct state *right = newstate(v->nfa); NOERR(); bracket(v, left, right); if (v->cflags®_NLSTOP) { newarc(v->nfa, PLAIN, v->nlcolor, left, right); } NOERR(); assert(lp->nouts == 0); /* all outarcs will be ours */ /* * Easy part of complementing, and all there is to do since the MCCE code * was removed. */ colorcomplement(v->nfa, v->cm, PLAIN, left, lp, rp); NOERR(); dropstate(v->nfa, left); assert(right->nins == 0); freestate(v->nfa, right); return; } /* - brackpart - handle one item (or range) within a bracket expression ^ static void brackpart(struct vars *, struct state *, struct state *); */ static void brackpart( struct vars *v, struct state *lp, struct state *rp) { celt startc, endc; struct cvec *cv; const chr *startp, *endp; chr c; /* * Parse something, get rid of special cases, take shortcuts. */ switch (v->nexttype) { case RANGE: /* a-b-c or other botch */ ERR(REG_ERANGE); return; break; case PLAIN: c = v->nextvalue; NEXT(); /* * Shortcut for ordinary chr (not range). */ if (!SEE(RANGE)) { onechr(v, c, lp, rp); return; } startc = element(v, &c, &c+1); NOERR(); break; case COLLEL: startp = v->now; endp = scanplain(v); INSIST(startp < endp, REG_ECOLLATE); NOERR(); startc = element(v, startp, endp); NOERR(); break; case ECLASS: startp = v->now; endp = scanplain(v); INSIST(startp < endp, REG_ECOLLATE); NOERR(); startc = element(v, startp, endp); NOERR(); cv = eclass(v, startc, (v->cflags®_ICASE)); NOERR(); dovec(v, cv, lp, rp); return; break; case CCLASS: startp = v->now; endp = scanplain(v); INSIST(startp < endp, REG_ECTYPE); NOERR(); cv = cclass(v, startp, endp, (v->cflags®_ICASE)); NOERR(); dovec(v, cv, lp, rp); return; break; default: ERR(REG_ASSERT); return; break; } if (SEE(RANGE)) { NEXT(); switch (v->nexttype) { case PLAIN: case RANGE: c = v->nextvalue; NEXT(); endc = element(v, &c, &c+1); NOERR(); break; case COLLEL: startp = v->now; endp = scanplain(v); INSIST(startp < endp, REG_ECOLLATE); NOERR(); endc = element(v, startp, endp); NOERR(); break; default: ERR(REG_ERANGE); return; break; } } else { endc = startc; } /* * Ranges are unportable. Actually, standard C does guarantee that digits * are contiguous, but making that an exception is just too complicated. */ if (startc != endc) { NOTE(REG_UUNPORT); } cv = range(v, startc, endc, (v->cflags®_ICASE)); NOERR(); dovec(v, cv, lp, rp); } /* - scanplain - scan PLAIN contents of [. etc. * Certain bits of trickery in lex.c know that this code does not try to look * past the final bracket of the [. etc. ^ static const chr *scanplain(struct vars *); */ static const chr * /* just after end of sequence */ scanplain( struct vars *v) { const chr *endp; assert(SEE(COLLEL) || SEE(ECLASS) || SEE(CCLASS)); NEXT(); endp = v->now; while (SEE(PLAIN)) { endp = v->now; NEXT(); } assert(SEE(END) || ISERR()); NEXT(); return endp; } /* - onechr - fill in arcs for a plain character, and possible case complements * This is mostly a shortcut for efficient handling of the common case. ^ static void onechr(struct vars *, pchr, struct state *, struct state *); */ static void onechr( struct vars *v, pchr c, struct state *lp, struct state *rp) { if (!(v->cflags®_ICASE)) { newarc(v->nfa, PLAIN, subcolor(v->cm, c), lp, rp); return; } /* * Rats, need general case anyway... */ dovec(v, allcases(v, c), lp, rp); } /* - dovec - fill in arcs for each element of a cvec ^ static void dovec(struct vars *, struct cvec *, struct state *, ^ struct state *); */ static void dovec( struct vars *v, struct cvec *cv, struct state *lp, struct state *rp) { chr ch, from, to; const chr *p; int i; for (p = cv->chrs, i = cv->nchrs; i > 0; p++, i--) { ch = *p; newarc(v->nfa, PLAIN, subcolor(v->cm, ch), lp, rp); } for (p = cv->ranges, i = cv->nranges; i > 0; p += 2, i--) { from = *p; to = *(p+1); if (from <= to) { subrange(v, from, to, lp, rp); } } } /* - wordchrs - set up word-chr list for word-boundary stuff, if needed * The list is kept as a bunch of arcs between two dummy states; it's disposed * of by the unreachable-states sweep in NFA optimization. Does NEXT(). Must * not be called from any unusual lexical context. This should be reconciled * with the \w etc. handling in lex.c, and should be cleaned up to reduce * dependencies on input scanning. ^ static void wordchrs(struct vars *); */ static void wordchrs( struct vars *v) { struct state *left, *right; if (v->wordchrs != NULL) { NEXT(); /* for consistency */ return; } left = newstate(v->nfa); right = newstate(v->nfa); NOERR(); /* * Fine point: implemented with [::], and lexer will set REG_ULOCALE. */ lexword(v); NEXT(); assert(v->savenow != NULL && SEE('[')); bracket(v, left, right); assert((v->savenow != NULL && SEE(']')) || ISERR()); NEXT(); NOERR(); v->wordchrs = left; } /* - sub_re - allocate a subre ^ static struct subre *sub_re(struct vars *, int, int, struct state *, ^ struct state *); */ static struct subre * sub_re( struct vars *v, int op, int flags, struct state *begin, struct state *end) { struct subre *ret = v->treefree; if (ret != NULL) { v->treefree = ret->left; } else { ret = (struct subre *) MALLOC(sizeof(struct subre)); if (ret == NULL) { ERR(REG_ESPACE); return NULL; } ret->chain = v->treechain; v->treechain = ret; } assert(strchr("=b|.*(", op) != NULL); ret->op = op; ret->flags = flags; ret->id = 0; /* will be assigned later */ ret->subno = 0; ret->min = ret->max = 1; ret->left = NULL; ret->right = NULL; ret->begin = begin; ret->end = end; ZAPCNFA(ret->cnfa); return ret; } /* - freesubre - free a subRE subtree ^ static void freesubre(struct vars *, struct subre *); */ static void freesubre( struct vars *v, /* might be NULL */ struct subre *sr) { if (sr == NULL) { return; } if (sr->left != NULL) { freesubre(v, sr->left); } if (sr->right != NULL) { freesubre(v, sr->right); } freesrnode(v, sr); } /* - freesrnode - free one node in a subRE subtree ^ static void freesrnode(struct vars *, struct subre *); */ static void freesrnode( struct vars *v, /* might be NULL */ struct subre *sr) { if (sr == NULL) { return; } if (!NULLCNFA(sr->cnfa)) { freecnfa(&sr->cnfa); } sr->flags = 0; if (v != NULL && v->treechain != NULL) { /* we're still parsing, maybe we can reuse the subre */ sr->left = v->treefree; v->treefree = sr; } else { FREE(sr); } } /* - numst - number tree nodes (assigning "id" indexes) ^ static int numst(struct subre *, int); */ static int /* next number */ numst( struct subre *t, int start) /* starting point for subtree numbers */ { int i; assert(t != NULL); i = start; t->id = (short) i++; if (t->left != NULL) { i = numst(t->left, i); } if (t->right != NULL) { i = numst(t->right, i); } return i; } /* - markst - mark tree nodes as INUSE * Note: this is a great deal more subtle than it looks. During initial * parsing of a regex, all subres are linked into the treechain list; * discarded ones are also linked into the treefree list for possible reuse. * After we are done creating all subres required for a regex, we run markst() * then cleanst(), which results in discarding all subres not reachable from * v->tree. We then clear v->treechain, indicating that subres must be found * by descending from v->tree. This changes the behavior of freesubre(): it * will henceforth FREE() unwanted subres rather than sticking them into the * treefree list. (Doing that any earlier would result in dangling links in * the treechain list.) This all means that freev() will clean up correctly * if invoked before or after markst()+cleanst(); but it would not work if * called partway through this state conversion, so we mustn't error out * in or between these two functions. ^ static void markst(struct subre *); */ static void markst( struct subre *t) { assert(t != NULL); t->flags |= INUSE; if (t->left != NULL) { markst(t->left); } if (t->right != NULL) { markst(t->right); } } /* - cleanst - free any tree nodes not marked INUSE ^ static void cleanst(struct vars *); */ static void cleanst( struct vars *v) { struct subre *t; struct subre *next; for (t = v->treechain; t != NULL; t = next) { next = t->chain; if (!(t->flags&INUSE)) { FREE(t); } } v->treechain = NULL; v->treefree = NULL; /* just on general principles */ } /* - nfatree - turn a subRE subtree into a tree of compacted NFAs ^ static long nfatree(struct vars *, struct subre *, FILE *); */ static long /* optimize results from top node */ nfatree( struct vars *v, struct subre *t, FILE *f) /* for debug output */ { assert(t != NULL && t->begin != NULL); if (t->left != NULL) { (void) nfatree(v, t->left, f); } if (t->right != NULL) { (void) nfatree(v, t->right, f); } return nfanode(v, t, f); } /* - nfanode - do one NFA for nfatree ^ static long nfanode(struct vars *, struct subre *, FILE *); */ static long /* optimize results */ nfanode( struct vars *v, struct subre *t, FILE *f) /* for debug output */ { struct nfa *nfa; long ret = 0; char idbuf[50]; assert(t->begin != NULL); if (f != NULL) { fprintf(f, "\n\n\n========= TREE NODE %s ==========\n", stid(t, idbuf, sizeof(idbuf))); } nfa = newnfa(v, v->cm, v->nfa); NOERRZ(); dupnfa(nfa, t->begin, t->end, nfa->init, nfa->final); if (!ISERR()) { specialcolors(nfa); ret = optimize(nfa, f); } if (!ISERR()) { compact(nfa, &t->cnfa); } freenfa(nfa); return ret; } /* - newlacon - allocate a lookahead-constraint subRE ^ static int newlacon(struct vars *, struct state *, struct state *, int); */ static int /* lacon number */ newlacon( struct vars *v, struct state *begin, struct state *end, int pos) { int n; struct subre *newlacons; struct subre *sub; if (v->nlacons == 0) { n = 1; /* skip 0th */ newlacons = (struct subre *) MALLOC(2 * sizeof(struct subre)); } else { n = v->nlacons; newlacons = (struct subre *) REALLOC(v->lacons, (n + 1) * sizeof(struct subre)); } if (newlacons == NULL) { ERR(REG_ESPACE); return 0; } v->lacons = newlacons; v->nlacons = n + 1; sub = &v->lacons[n]; sub->begin = begin; sub->end = end; sub->subno = pos; ZAPCNFA(sub->cnfa); return n; } /* - freelacons - free lookahead-constraint subRE vector ^ static void freelacons(struct subre *, int); */ static void freelacons( struct subre *subs, int n) { struct subre *sub; int i; assert(n > 0); for (sub=subs+1, i=n-1; i>0; sub++, i--) { /* no 0th */ if (!NULLCNFA(sub->cnfa)) { freecnfa(&sub->cnfa); } } FREE(subs); } /* - rfree - free a whole RE (insides of regfree) ^ static void rfree(regex_t *); */ static void rfree( regex_t *re) { struct guts *g; if (re == NULL || re->re_magic != REMAGIC) { return; } re->re_magic = 0; /* invalidate RE */ g = (struct guts *) re->re_guts; re->re_guts = NULL; re->re_fns = NULL; if (g != NULL) { g->magic = 0; freecm(&g->cmap); if (g->tree != NULL) { freesubre(NULL, g->tree); } if (g->lacons != NULL) { freelacons(g->lacons, g->nlacons); } if (!NULLCNFA(g->search)) { freecnfa(&g->search); } FREE(g); } } /* - dump - dump an RE in human-readable form ^ static void dump(regex_t *, FILE *); */ static void dump( regex_t *re, FILE *f) { #ifdef REG_DEBUG struct guts *g; size_t i; if (re->re_magic != REMAGIC) { fprintf(f, "bad magic number (0x%x not 0x%x)\n", re->re_magic, REMAGIC); } if (re->re_guts == NULL) { fprintf(f, "NULL guts!!!\n"); return; } g = (struct guts *) re->re_guts; if (g->magic != GUTSMAGIC) { fprintf(f, "bad guts magic number (0x%x not 0x%x)\n", g->magic, GUTSMAGIC); } fprintf(f, "\n\n\n========= DUMP ==========\n"); fprintf(f, "nsub %" TCL_Z_MODIFIER "u, info 0%lo, ntree %" TCL_Z_MODIFIER "u\n", re->re_nsub, re->re_info, g->ntree); dumpcolors(&g->cmap, f); if (!NULLCNFA(g->search)) { fprintf(f, "\nsearch:\n"); dumpcnfa(&g->search, f); } for (i = 1; i < g->nlacons; i++) { fprintf(f, "\nla%" TCL_Z_MODIFIER "u (%s):\n", i, (g->lacons[i].subno) ? "positive" : "negative"); dumpcnfa(&g->lacons[i].cnfa, f); } fprintf(f, "\n"); dumpst(g->tree, f, 0); #else (void)re; (void)f; #endif } /* - dumpst - dump a subRE tree ^ static void dumpst(struct subre *, FILE *, int); */ static void dumpst( struct subre *t, FILE *f, int nfapresent) /* is the original NFA still around? */ { if (t == NULL) { fprintf(f, "null tree\n"); } else { stdump(t, f, nfapresent); } fflush(f); } /* - stdump - recursive guts of dumpst ^ static void stdump(struct subre *, FILE *, int); */ static void stdump( struct subre *t, FILE *f, int nfapresent) /* is the original NFA still around? */ { char idbuf[50]; fprintf(f, "%s. `%c'", stid(t, idbuf, sizeof(idbuf)), t->op); if (t->flags&LONGER) { fprintf(f, " longest"); } if (t->flags&SHORTER) { fprintf(f, " shortest"); } if (t->flags&MIXED) { fprintf(f, " hasmixed"); } if (t->flags&CAP) { fprintf(f, " hascapture"); } if (t->flags&BACKR) { fprintf(f, " hasbackref"); } if (!(t->flags&INUSE)) { fprintf(f, " UNUSED"); } if (t->subno != 0) { fprintf(f, " (#%d)", t->subno); } if (t->min != 1 || t->max != 1) { fprintf(f, " {%d,", t->min); if (t->max != DUPINF) { fprintf(f, "%d", t->max); } fprintf(f, "}"); } if (nfapresent) { fprintf(f, " %" TCL_Z_MODIFIER "u-%" TCL_Z_MODIFIER "u", t->begin->no, t->end->no); } if (t->left != NULL) { fprintf(f, " L:%s", stid(t->left, idbuf, sizeof(idbuf))); } if (t->right != NULL) { fprintf(f, " R:%s", stid(t->right, idbuf, sizeof(idbuf))); } if (!NULLCNFA(t->cnfa)) { fprintf(f, "\n"); dumpcnfa(&t->cnfa, f); } fprintf(f, "\n"); if (t->left != NULL) { stdump(t->left, f, nfapresent); } if (t->right != NULL) { stdump(t->right, f, nfapresent); } } /* - stid - identify a subtree node for dumping ^ static const char *stid(struct subre *, char *, size_t); */ static const char * /* points to buf or constant string */ stid( struct subre *t, char *buf, size_t bufsize) { /* * Big enough for hex int or decimal t->id? */ if (bufsize < sizeof(void*)*2 + 3 || bufsize < sizeof(t->id)*3 + 1) { return "unable"; } if (t->id != 0) { snprintf(buf, bufsize, "%d", t->id); } else { snprintf(buf, bufsize, "%p", t); } return buf; } #include "regc_lex.c" #include "regc_color.c" #include "regc_nfa.c" #include "regc_cvec.c" #include "regc_locale.c" /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/regcustom.h0000644000175000017500000001052114726623136015352 0ustar sergeisergei/* * Copyright (c) 1998, 1999 Henry Spencer. All rights reserved. * * Development of this software was funded, in part, by Cray Research Inc., * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics * Corporation, none of whom are responsible for the results. The author * thanks all of them. * * Redistribution and use in source and binary forms - with or without * modification - are permitted for any purpose, provided that redistributions * in source form retain this entire copyright notice and indicate the origin * and nature of any modifications. * * I'd appreciate being given credit for this package in the documentation of * software which uses it, but that is not a requirement. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Headers if any. */ #include "regex.h" /* * Overrides for regguts.h definitions, if any. */ #define MALLOC(n) Tcl_AttemptAlloc(n) #define FREE(p) Tcl_Free(p) #define REALLOC(p,n) Tcl_AttemptRealloc(p,n) /* * Do not insert extras between the "begin" and "end" lines - this chunk is * automatically extracted to be fitted into regex.h. */ /* --- begin --- */ /* Ensure certain things don't sneak in from system headers. */ #ifdef __REG_WIDE_T #undef __REG_WIDE_T #endif #ifdef __REG_WIDE_COMPILE #undef __REG_WIDE_COMPILE #endif #ifdef __REG_WIDE_EXEC #undef __REG_WIDE_EXEC #endif #ifdef __REG_NOFRONT #undef __REG_NOFRONT #endif #ifdef __REG_NOCHAR #undef __REG_NOCHAR #endif /* Interface types */ #define __REG_WIDE_T Tcl_UniChar /* Names and declarations */ #define __REG_WIDE_COMPILE TclReComp #define __REG_WIDE_EXEC TclReExec #define __REG_NOFRONT /* Don't want regcomp() and regexec() */ #define __REG_NOCHAR /* Or the char versions */ #define regfree TclReFree #define regerror TclReError /* --- end --- */ /* * Internal character type and related. */ typedef Tcl_UniChar chr; /* The type itself. */ typedef int pchr; /* What it promotes to. */ typedef unsigned uchr; /* Unsigned type that will hold a chr. */ typedef int celt; /* Type to hold chr, or NOCELT */ #define NOCELT (-1) /* Celt value which is not valid chr */ #define CHR(c) (UCHAR(c)) /* Turn char literal into chr literal */ #define DIGITVAL(c) ((c)-'0') /* Turn chr digit into its value */ #define CHRBITS 32 /* Bits in a chr; must not use sizeof */ #define CHR_MIN 0x00000000 /* Smallest and largest chr; the value */ #define CHR_MAX 0x10FFFF /* CHR_MAX-CHR_MIN+1 should fit in uchr */ /* * Functions operating on chr. */ #define iscalnum(x) Tcl_UniCharIsAlnum(x) #define iscalpha(x) Tcl_UniCharIsAlpha(x) #define iscdigit(x) Tcl_UniCharIsDigit(x) #define iscspace(x) Tcl_UniCharIsSpace(x) /* * Name the external functions. */ #define compile TclReComp #define exec TclReExec /* & Enable/disable debugging code (by whether REG_DEBUG is defined or not). */ #if 0 /* No debug unless requested by makefile. */ #define REG_DEBUG /* */ #endif /* * Method of allocating a local workspace. We used a thread-specific data * space to store this because the regular expression engine is never * reentered from the same thread; it doesn't make any callbacks. */ #if 1 #define AllocVars(vPtr) \ static Tcl_ThreadDataKey varsKey; \ struct vars *vPtr = (struct vars *) \ Tcl_GetThreadData(&varsKey, sizeof(struct vars)) #else /* * This strategy for allocating workspace is "more proper" in some sense, but * quite a bit slower. Using TSD (as above) leads to code that is quite a bit * faster in practice (measured!) */ #define AllocVars(vPtr) \ struct vars *vPtr = (struct vars *) MALLOC(sizeof(struct vars)) #define FreeVars(vPtr) \ FREE(vPtr) #endif /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/rege_dfa.c0000644000175000017500000004640214726623136015100 0ustar sergeisergei/* * DFA routines * This file is #included by regexec.c. * * Copyright © 1998, 1999 Henry Spencer. All rights reserved. * * Development of this software was funded, in part, by Cray Research Inc., * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics * Corporation, none of whom are responsible for the results. The author * thanks all of them. * * Redistribution and use in source and binary forms -- with or without * modification -- are permitted for any purpose, provided that * redistributions in source form retain this entire copyright notice and * indicate the origin and nature of any modifications. * * I'd appreciate being given credit for this package in the documentation * of software which uses it, but that is not a requirement. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ /* - longest - longest-preferred matching engine ^ static chr *longest(struct vars *, struct dfa *, chr *, chr *, int *); */ static chr * /* endpoint, or NULL */ longest( struct vars *const v, /* used only for debug and exec flags */ struct dfa *const d, chr *const start, /* where the match should start */ chr *const stop, /* match must end at or before here */ int *const hitstopp) /* record whether hit v->stop, if non-NULL */ { chr *cp; chr *realstop = (stop == v->stop) ? stop : stop + 1; color co; struct sset *css, *ss; chr *post; size_t i; struct colormap *cm = d->cm; /* * Initialize. */ css = initialize(v, d, start); cp = start; if (hitstopp != NULL) { *hitstopp = 0; } /* * Startup. */ FDEBUG(("+++ startup +++\n")); if (cp == v->start) { co = d->cnfa->bos[(v->eflags®_NOTBOL) ? 0 : 1]; FDEBUG(("color %ld\n", (long)co)); } else { co = GETCOLOR(cm, *(cp - 1)); FDEBUG(("char %c, color %ld\n", (char)*(cp-1), (long)co)); } css = miss(v, d, css, co, cp, start); if (css == NULL) { return NULL; } css->lastseen = cp; /* * Main loop. */ if (v->eflags®_FTRACE) { while (cp < realstop) { FDEBUG(("+++ at c%d +++\n", (int) (css - d->ssets))); co = GETCOLOR(cm, *cp); FDEBUG(("char %c, color %ld\n", (char)*cp, (long)co)); ss = css->outs[co]; if (ss == NULL) { ss = miss(v, d, css, co, cp+1, start); if (ss == NULL) { break; /* NOTE BREAK OUT */ } } cp++; ss->lastseen = cp; css = ss; } } else { while (cp < realstop) { co = GETCOLOR(cm, *cp); ss = css->outs[co]; if (ss == NULL) { ss = miss(v, d, css, co, cp+1, start); if (ss == NULL) { break; /* NOTE BREAK OUT */ } } cp++; ss->lastseen = cp; css = ss; } } /* * Shutdown. */ FDEBUG(("+++ shutdown at c%d +++\n", (int) (css - d->ssets))); if (cp == v->stop && stop == v->stop) { if (hitstopp != NULL) { *hitstopp = 1; } co = d->cnfa->eos[(v->eflags®_NOTEOL) ? 0 : 1]; FDEBUG(("color %ld\n", (long)co)); ss = miss(v, d, css, co, cp, start); /* * Special case: match ended at eol? */ if (ss != NULL && (ss->flags&POSTSTATE)) { return cp; } else if (ss != NULL) { ss->lastseen = cp; /* to be tidy */ } } /* * Find last match, if any. */ post = d->lastpost; for (ss = d->ssets, i = d->nssused; i > 0; ss++, i--) { if ((ss->flags&POSTSTATE) && (post != ss->lastseen) && (post == NULL || post < ss->lastseen)) { post = ss->lastseen; } } if (post != NULL) { /* found one */ return post - 1; } return NULL; } /* - shortest - shortest-preferred matching engine ^ static chr *shortest(struct vars *, struct dfa *, chr *, chr *, chr *, ^ chr **, int *); */ static chr * /* endpoint, or NULL */ shortest( struct vars *const v, struct dfa *const d, chr *const start, /* where the match should start */ chr *const min, /* match must end at or after here */ chr *const max, /* match must end at or before here */ chr **const coldp, /* store coldstart pointer here, if nonNULL */ int *const hitstopp) /* record whether hit v->stop, if non-NULL */ { chr *cp; chr *realmin = (min == v->stop) ? min : min + 1; chr *realmax = (max == v->stop) ? max : max + 1; color co; struct sset *css, *ss; struct colormap *cm = d->cm; /* * Initialize. */ css = initialize(v, d, start); cp = start; if (hitstopp != NULL) { *hitstopp = 0; } /* * Startup. */ FDEBUG(("--- startup ---\n")); if (cp == v->start) { co = d->cnfa->bos[(v->eflags®_NOTBOL) ? 0 : 1]; FDEBUG(("color %ld\n", (long)co)); } else { co = GETCOLOR(cm, *(cp - 1)); FDEBUG(("char %c, color %ld\n", (char)*(cp-1), (long)co)); } css = miss(v, d, css, co, cp, start); if (css == NULL) { return NULL; } css->lastseen = cp; ss = css; /* * Main loop. */ if (v->eflags®_FTRACE) { while (cp < realmax) { FDEBUG(("--- at c%d ---\n", (int) (css - d->ssets))); co = GETCOLOR(cm, *cp); FDEBUG(("char %c, color %ld\n", (char)*cp, (long)co)); ss = css->outs[co]; if (ss == NULL) { ss = miss(v, d, css, co, cp+1, start); if (ss == NULL) { break; /* NOTE BREAK OUT */ } } cp++; ss->lastseen = cp; css = ss; if ((ss->flags&POSTSTATE) && cp >= realmin) { break; /* NOTE BREAK OUT */ } } } else { while (cp < realmax) { co = GETCOLOR(cm, *cp); ss = css->outs[co]; if (ss == NULL) { ss = miss(v, d, css, co, cp+1, start); if (ss == NULL) { break; /* NOTE BREAK OUT */ } } cp++; ss->lastseen = cp; css = ss; if ((ss->flags&POSTSTATE) && cp >= realmin) { break; /* NOTE BREAK OUT */ } } } if (ss == NULL) { return NULL; } if (coldp != NULL) { /* report last no-progress state set, if any */ *coldp = lastCold(v, d); } if ((ss->flags&POSTSTATE) && cp > min) { assert(cp >= realmin); cp--; } else if (cp == v->stop && max == v->stop) { co = d->cnfa->eos[(v->eflags®_NOTEOL) ? 0 : 1]; FDEBUG(("color %ld\n", (long)co)); ss = miss(v, d, css, co, cp, start); /* * Match might have ended at eol. */ if ((ss == NULL || !(ss->flags&POSTSTATE)) && hitstopp != NULL) { *hitstopp = 1; } } if (ss == NULL || !(ss->flags&POSTSTATE)) { return NULL; } return cp; } /* - lastCold - determine last point at which no progress had been made ^ static chr *lastCold(struct vars *, struct dfa *); */ static chr * /* endpoint, or NULL */ lastCold( struct vars *const v, struct dfa *const d) { struct sset *ss; chr *nopr = d->lastnopr; size_t i; if (nopr == NULL) { nopr = v->start; } for (ss = d->ssets, i = d->nssused; i > 0; ss++, i--) { if ((ss->flags&NOPROGRESS) && nopr < ss->lastseen) { nopr = ss->lastseen; } } return nopr; } /* - newDFA - set up a fresh DFA ^ static struct dfa *newDFA(struct vars *, struct cnfa *, ^ struct colormap *, struct smalldfa *); */ static struct dfa * newDFA( struct vars *const v, struct cnfa *const cnfa, struct colormap *const cm, struct smalldfa *sml) /* preallocated space, may be NULL */ { struct dfa *d; size_t nss = cnfa->nstates * 2; size_t wordsper = (cnfa->nstates + UBITS - 1) / UBITS; struct smalldfa *smallwas = sml; assert(cnfa != NULL && cnfa->nstates != 0); if (nss <= FEWSTATES && cnfa->ncolors <= FEWCOLORS) { assert(wordsper == 1); if (sml == NULL) { sml = (struct smalldfa *) MALLOC(sizeof(struct smalldfa)); if (sml == NULL) { ERR(REG_ESPACE); return NULL; } } d = &sml->dfa; d->ssets = sml->ssets; d->statesarea = sml->statesarea; d->work = &d->statesarea[nss]; d->outsarea = sml->outsarea; d->incarea = sml->incarea; d->cptsmalloced = 0; d->mallocarea = (smallwas == NULL) ? (char *)sml : NULL; } else { d = (struct dfa *) MALLOC(sizeof(struct dfa)); if (d == NULL) { ERR(REG_ESPACE); return NULL; } d->ssets = (struct sset *) MALLOC(nss * sizeof(struct sset)); d->statesarea = (unsigned *) MALLOC((nss+WORK) * wordsper * sizeof(unsigned)); d->work = &d->statesarea[nss * wordsper]; d->outsarea = (struct sset **) MALLOC(nss * cnfa->ncolors * sizeof(struct sset *)); d->incarea = (struct arcp *) MALLOC(nss * cnfa->ncolors * sizeof(struct arcp)); d->cptsmalloced = 1; d->mallocarea = (char *)d; if (d->ssets == NULL || d->statesarea == NULL || d->outsarea == NULL || d->incarea == NULL) { freeDFA(d); ERR(REG_ESPACE); return NULL; } } d->nssets = (v->eflags®_SMALL) ? 7 : nss; d->nssused = 0; d->nstates = cnfa->nstates; d->ncolors = cnfa->ncolors; d->wordsper = wordsper; d->cnfa = cnfa; d->cm = cm; d->lastpost = NULL; d->lastnopr = NULL; d->search = d->ssets; /* * Initialization of sset fields is done as needed. */ return d; } /* - freeDFA - free a DFA ^ static void freeDFA(struct dfa *); */ static void freeDFA( struct dfa *const d) { if (d->cptsmalloced) { if (d->ssets != NULL) { FREE(d->ssets); } if (d->statesarea != NULL) { FREE(d->statesarea); } if (d->outsarea != NULL) { FREE(d->outsarea); } if (d->incarea != NULL) { FREE(d->incarea); } } if (d->mallocarea != NULL) { FREE(d->mallocarea); } } /* - hash - construct a hash code for a bitvector * There are probably better ways, but they're more expensive. ^ static unsigned hash(unsigned *, int); */ static unsigned hash( unsigned *const uv, int n) { int i; unsigned h; h = 0; for (i = 0; i < n; i++) { h ^= uv[i]; } return h; } /* - initialize - hand-craft a cache entry for startup, otherwise get ready ^ static struct sset *initialize(struct vars *, struct dfa *, chr *); */ static struct sset * initialize( struct vars *const v, /* used only for debug flags */ struct dfa *const d, chr *const start) { struct sset *ss; size_t i; /* * Is previous one still there? */ if (d->nssused > 0 && (d->ssets[0].flags&STARTER)) { ss = &d->ssets[0]; } else { /* no, must (re)build it */ ss = getVacantSS(v, d, start, start); for (i = 0; i < d->wordsper; i++) { ss->states[i] = 0; } BSET(ss->states, d->cnfa->pre); ss->hash = HASH(ss->states, d->wordsper); assert(d->cnfa->pre != d->cnfa->post); ss->flags = STARTER|LOCKED|NOPROGRESS; /* * lastseen dealt with below */ } for (i = 0; i < d->nssused; i++) { d->ssets[i].lastseen = NULL; } ss->lastseen = start; /* maybe untrue, but harmless */ d->lastpost = NULL; d->lastnopr = NULL; return ss; } /* - miss - handle a cache miss ^ static struct sset *miss(struct vars *, struct dfa *, struct sset *, ^ pcolor, chr *, chr *); */ static struct sset * /* NULL if goes to empty set */ miss( struct vars *const v, /* used only for debug flags */ struct dfa *const d, struct sset *const css, const pcolor co, chr *const cp, /* next chr */ chr *const start) /* where the attempt got started */ { struct cnfa *cnfa = d->cnfa; unsigned h; struct carc *ca; struct sset *p; size_t i; int isPost, noProgress, gotState, doLAConstraints, sawLAConstraints; /* * For convenience, we can be called even if it might not be a miss. */ if (css->outs[co] != NULL) { FDEBUG(("hit\n")); return css->outs[co]; } FDEBUG(("miss\n")); /* * First, what set of states would we end up in? */ for (i = 0; i < d->wordsper; i++) { d->work[i] = 0; } isPost = 0; noProgress = 1; gotState = 0; for (i = 0; i < d->nstates; i++) { if (ISBSET(css->states, i)) { for (ca = cnfa->states[i]; ca->co != COLORLESS; ca++) { if (ca->co == co) { BSET(d->work, ca->to); gotState = 1; if (ca->to == cnfa->post) { isPost = 1; } if (!(cnfa->stflags[ca->to] & CNFA_NOPROGRESS)) { noProgress = 0; } FDEBUG(("%" TCL_Z_MODIFIER "u -> %" TCL_Z_MODIFIER "u\n", i, ca->to)); } } } } doLAConstraints = (gotState ? (cnfa->flags&HASLACONS) : 0); sawLAConstraints = 0; while (doLAConstraints) { /* transitive closure */ doLAConstraints = 0; for (i = 0; i < d->nstates; i++) { if (ISBSET(d->work, i)) { for (ca = cnfa->states[i]; ca->co != COLORLESS; ca++) { if (ca->co < cnfa->ncolors) { continue; /* NOTE CONTINUE */ } sawLAConstraints = 1; if (ISBSET(d->work, ca->to)) { continue; /* NOTE CONTINUE */ } if (!checkLAConstraint(v, cnfa, cp, ca->co)) { continue; /* NOTE CONTINUE */ } BSET(d->work, ca->to); doLAConstraints = 1; if (ca->to == cnfa->post) { isPost = 1; } if (!(cnfa->stflags[ca->to] & CNFA_NOPROGRESS)) { noProgress = 0; } FDEBUG(("%" TCL_Z_MODIFIER "u :> %" TCL_Z_MODIFIER"u\n", i, ca->to)); } } } } if (!gotState) { return NULL; } h = HASH(d->work, d->wordsper); /* * Next, is that in the cache? */ for (p = d->ssets, i = d->nssused; i > 0; p++, i--) { if (HIT(h, d->work, p, d->wordsper)) { FDEBUG(("cached c%d\n", (int) (p - d->ssets))); break; /* NOTE BREAK OUT */ } } if (i == 0) { /* nope, need a new cache entry */ p = getVacantSS(v, d, cp, start); assert(p != css); for (i = 0; i < d->wordsper; i++) { p->states[i] = d->work[i]; } p->hash = h; p->flags = (isPost ? POSTSTATE : 0); if (noProgress) { p->flags |= NOPROGRESS; } /* * lastseen to be dealt with by caller */ } if (!sawLAConstraints) { /* lookahead conds. always cache miss */ FDEBUG(("c%d[%d]->c%d\n", (int) (css - d->ssets), co, (int) (p - d->ssets))); css->outs[co] = p; css->inchain[co] = p->ins; p->ins.ss = css; p->ins.co = (color) co; } return p; } /* - checkLAConstraint - lookahead-constraint checker for miss() ^ static int checkLAConstraint(struct vars *, struct cnfa *, chr *, pcolor); */ static int /* predicate: constraint satisfied? */ checkLAConstraint( struct vars *const v, struct cnfa *const pcnfa, /* parent cnfa */ chr *const cp, const pcolor co) /* "color" of the lookahead constraint */ { size_t n; struct subre *sub; struct dfa *d; struct smalldfa sd; chr *end; n = co - pcnfa->ncolors; assert(n < v->g->nlacons && v->g->lacons != NULL); FDEBUG(("=== testing lacon %" TCL_Z_MODIFIER "u\n", n)); sub = &v->g->lacons[n]; d = newDFA(v, &sub->cnfa, &v->g->cmap, &sd); if (d == NULL) { ERR(REG_ESPACE); return 0; } end = longest(v, d, cp, v->stop, NULL); freeDFA(d); FDEBUG(("=== lacon %" TCL_Z_MODIFIER "u match %d\n", n, (end != NULL))); return (sub->subno) ? (end != NULL) : (end == NULL); } /* - getVacantSS - get a vacant state set * This routine clears out the inarcs and outarcs, but does not otherwise * clear the innards of the state set -- that's up to the caller. ^ static struct sset *getVacantSS(struct vars *, struct dfa *, chr *, chr *); */ static struct sset * getVacantSS( struct vars *const v, /* used only for debug flags */ struct dfa *const d, chr *const cp, chr *const start) { int i; struct sset *ss, *p; struct arcp ap, lastap = {NULL, 0}; /* silence gcc 4 warning */ color co; ss = pickNextSS(v, d, cp, start); assert(!(ss->flags&LOCKED)); /* * Clear out its inarcs, including self-referential ones. */ ap = ss->ins; while ((p = ap.ss) != NULL) { co = ap.co; FDEBUG(("zapping c%d's %ld outarc\n", (int) (p - d->ssets), (long)co)); p->outs[co] = NULL; ap = p->inchain[co]; p->inchain[co].ss = NULL; /* paranoia */ } ss->ins.ss = NULL; /* * Take it off the inarc chains of the ssets reached by its outarcs. */ for (i = 0; i < d->ncolors; i++) { p = ss->outs[i]; assert(p != ss); /* not self-referential */ if (p == NULL) { continue; /* NOTE CONTINUE */ } FDEBUG(("del outarc %d from c%d's in chn\n", i, (int) (p - d->ssets))); if (p->ins.ss == ss && p->ins.co == i) { p->ins = ss->inchain[i]; } else { assert(p->ins.ss != NULL); for (ap = p->ins; ap.ss != NULL && !(ap.ss == ss && ap.co == i); ap = ap.ss->inchain[ap.co]) { lastap = ap; } assert(ap.ss != NULL); lastap.ss->inchain[lastap.co] = ss->inchain[i]; } ss->outs[i] = NULL; ss->inchain[i].ss = NULL; } /* * If ss was a success state, may need to remember location. */ if ((ss->flags&POSTSTATE) && ss->lastseen != d->lastpost && (d->lastpost == NULL || d->lastpost < ss->lastseen)) { d->lastpost = ss->lastseen; } /* * Likewise for a no-progress state. */ if ((ss->flags&NOPROGRESS) && ss->lastseen != d->lastnopr && (d->lastnopr == NULL || d->lastnopr < ss->lastseen)) { d->lastnopr = ss->lastseen; } return ss; } /* - pickNextSS - pick the next stateset to be used ^ static struct sset *pickNextSS(struct vars *, struct dfa *, chr *, chr *); */ static struct sset * pickNextSS( struct vars *const v, /* used only for debug flags */ struct dfa *const d, chr *const cp, chr *const start) { int i; struct sset *ss, *end; chr *ancient; /* * Shortcut for cases where cache isn't full. */ if (d->nssused < d->nssets) { size_t j = d->nssused; d->nssused++; ss = &d->ssets[j]; FDEBUG(("new c%" TCL_Z_MODIFIER "u\n", j)); /* * Set up innards. */ ss->states = &d->statesarea[j * d->wordsper]; ss->flags = 0; ss->ins.ss = NULL; ss->ins.co = WHITE; /* give it some value */ ss->outs = &d->outsarea[j * d->ncolors]; ss->inchain = &d->incarea[j * d->ncolors]; for (i = 0; i < d->ncolors; i++) { ss->outs[i] = NULL; ss->inchain[i].ss = NULL; } return ss; } /* * Look for oldest, or old enough anyway. */ if ((size_t)(cp - start) > d->nssets*2/3) { /* oldest 33% are expendable */ ancient = cp - d->nssets*2/3; } else { ancient = start; } for (ss = d->search, end = &d->ssets[d->nssets]; ss < end; ss++) { if ((ss->lastseen == NULL || ss->lastseen < ancient) && !(ss->flags&LOCKED)) { d->search = ss + 1; FDEBUG(("replacing c%" TCL_Z_MODIFIER "u\n", (size_t)(ss - d->ssets))); return ss; } } for (ss = d->ssets, end = d->search; ss < end; ss++) { if ((ss->lastseen == NULL || ss->lastseen < ancient) && !(ss->flags&LOCKED)) { d->search = ss + 1; FDEBUG(("replacing c%" TCL_Z_MODIFIER "u\n", (size_t)(ss - d->ssets))); return ss; } } /* * Nobody's old enough?!? -- something's really wrong. */ FDEBUG(("cannot find victim to replace!\n")); assert(NOTREACHED); ERR(REG_ASSERT); return d->ssets; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/regerror.c0000644000175000017500000000714014726623136015167 0ustar sergeisergei/* * regerror - error-code expansion * * Copyright © 1998, 1999 Henry Spencer. All rights reserved. * * Development of this software was funded, in part, by Cray Research Inc., * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics * Corporation, none of whom are responsible for the results. The author * thanks all of them. * * Redistribution and use in source and binary forms -- with or without * modification -- are permitted for any purpose, provided that * redistributions in source form retain this entire copyright notice and * indicate the origin and nature of any modifications. * * I'd appreciate being given credit for this package in the documentation of * software which uses it, but that is not a requirement. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "regguts.h" /* * Unknown-error explanation. */ static const char unk[] = "*** unknown regex error code 0x%x ***"; /* * Struct to map among codes, code names, and explanations. */ static const struct rerr { int code; const char *name; const char *explain; } rerrs[] = { /* The actual table is built from regex.h */ #include "regerrs.h" { -1, "", "oops" }, /* explanation special-cased in code */ }; /* - regerror - the interface to error numbers */ size_t /* Actual space needed (including NUL) */ regerror( int code, /* Error code, or REG_ATOI or REG_ITOA */ char *errbuf, /* Result buffer (unless errbuf_size==0) */ size_t errbuf_size) /* Available space in errbuf, can be 0 */ { const struct rerr *r; const char *msg; char convbuf[sizeof(unk)+50]; /* 50 = plenty for int */ size_t len; int icode; switch (code) { case REG_ATOI: /* Convert name to number */ for (r = rerrs; r->code >= 0; r++) { if (strcmp(r->name, errbuf) == 0) { break; } } snprintf(convbuf, sizeof(convbuf), "%d", r->code); /* -1 for unknown */ msg = convbuf; break; case REG_ITOA: /* Convert number to name */ icode = atoi(errbuf); /* Not our problem if this fails */ for (r = rerrs; r->code >= 0; r++) { if (r->code == icode) { break; } } if (r->code >= 0) { msg = r->name; } else { /* Unknown; tell him the number */ snprintf(convbuf, sizeof(convbuf), "REG_%u", icode); msg = convbuf; } break; default: /* A real, normal error code */ for (r = rerrs; r->code >= 0; r++) { if (r->code == code) { break; } } if (r->code >= 0) { msg = r->explain; } else { /* Unknown; say so */ snprintf(convbuf, sizeof(convbuf), unk, code); msg = convbuf; } break; } len = strlen(msg) + 1; /* Space needed, including NUL */ if (errbuf_size > 0) { if (errbuf_size > len) { strcpy(errbuf, msg); } else { /* Truncate to fit */ strncpy(errbuf, msg, errbuf_size-1); errbuf[errbuf_size-1] = '\0'; } } return len; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/regerrs.h0000644000175000017500000000225714726623136015022 0ustar sergeisergei{ REG_OKAY, "REG_OKAY", "no errors detected" }, { REG_NOMATCH, "REG_NOMATCH", "failed to match" }, { REG_BADPAT, "REG_BADPAT", "invalid regexp (reg version 0.8)" }, { REG_ECOLLATE, "REG_ECOLLATE", "invalid collating element" }, { REG_ECTYPE, "REG_ECTYPE", "invalid character class" }, { REG_EESCAPE, "REG_EESCAPE", "invalid escape \\ sequence" }, { REG_ESUBREG, "REG_ESUBREG", "invalid backreference number" }, { REG_EBRACK, "REG_EBRACK", "brackets [] not balanced" }, { REG_EPAREN, "REG_EPAREN", "parentheses () not balanced" }, { REG_EBRACE, "REG_EBRACE", "braces {} not balanced" }, { REG_BADBR, "REG_BADBR", "invalid repetition count(s)" }, { REG_ERANGE, "REG_ERANGE", "invalid character range" }, { REG_ESPACE, "REG_ESPACE", "out of memory" }, { REG_BADRPT, "REG_BADRPT", "invalid quantifier operand" }, { REG_ASSERT, "REG_ASSERT", "\"cannot happen\" -- you found a bug" }, { REG_INVARG, "REG_INVARG", "invalid argument to regex function" }, { REG_MIXED, "REG_MIXED", "character widths of regex and string differ" }, { REG_BADOPT, "REG_BADOPT", "invalid embedded option" }, { REG_ETOOBIG, "REG_ETOOBIG", "regular expression is too complex" }, { REG_ECOLORS, "REG_ECOLORS", "too many colors" }, tcl9.0.1/generic/regex.h0000644000175000017500000002364214726623136014464 0ustar sergeisergei#ifndef _REGEX_H_ #define _REGEX_H_ /* never again */ #include "tclInt.h" /* * regular expressions * * Copyright (c) 1998, 1999 Henry Spencer. All rights reserved. * * Development of this software was funded, in part, by Cray Research Inc., * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics * Corporation, none of whom are responsible for the results. The author * thanks all of them. * * Redistribution and use in source and binary forms -- with or without * modification -- are permitted for any purpose, provided that * redistributions in source form retain this entire copyright notice and * indicate the origin and nature of any modifications. * * I'd appreciate being given credit for this package in the documentation of * software which uses it, but that is not a requirement. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * Prototypes etc. marked with "^" within comments get gathered up (and * possibly edited) by the regfwd program and inserted near the bottom of this * file. * * We offer the option of declaring one wide-character version of the RE * functions as well as the char versions. To do that, define __REG_WIDE_T to * the type of wide characters (unfortunately, there is no consensus that * wchar_t is suitable) and __REG_WIDE_COMPILE and __REG_WIDE_EXEC to the * names to be used for the compile and execute functions (suggestion: * re_Xcomp and re_Xexec, where X is a letter suggestive of the wide type, * e.g. re_ucomp and re_uexec for Unicode). For cranky old compilers, it may * be necessary to do something like: * #define __REG_WIDE_COMPILE(a,b,c,d) re_Xcomp(a,b,c,d) * #define __REG_WIDE_EXEC(a,b,c,d,e,f,g) re_Xexec(a,b,c,d,e,f,g) * rather than just #defining the names as parameterless macros. * * For some specialized purposes, it may be desirable to suppress the * declarations of the "front end" functions, regcomp() and regexec(), or of * the char versions of the compile and execute functions. To suppress the * front-end functions, define __REG_NOFRONT. To suppress the char versions, * define __REG_NOCHAR. * * The right place to do those defines (and some others you may want, see * below) would be . If you don't have control of that file, the * right place to add your own defines to this file is marked below. This is * normally done automatically, by the makefile and regmkhdr, based on the * contents of regcustom.h. */ /* * voodoo for C++ */ #ifdef __cplusplus extern "C" { #endif /* * Add your own defines, if needed, here. */ /* * Location where a chunk of regcustom.h is automatically spliced into this * file (working from its prototype, regproto.h). */ /* --- begin --- */ /* ensure certain things don't sneak in from system headers */ #ifdef __REG_WIDE_T #undef __REG_WIDE_T #endif #ifdef __REG_WIDE_COMPILE #undef __REG_WIDE_COMPILE #endif #ifdef __REG_WIDE_EXEC #undef __REG_WIDE_EXEC #endif #ifdef __REG_NOFRONT #undef __REG_NOFRONT #endif #ifdef __REG_NOCHAR #undef __REG_NOCHAR #endif /* interface types */ #define __REG_WIDE_T Tcl_UniChar /* names and declarations */ #define __REG_WIDE_COMPILE TclReComp #define __REG_WIDE_EXEC TclReExec #define __REG_NOFRONT /* don't want regcomp() and regexec() */ #define __REG_NOCHAR /* or the char versions */ #define regfree TclReFree #define regerror TclReError /* --- end --- */ /* * interface types etc. */ /* * other interface types */ /* the biggie, a compiled RE (or rather, a front end to same) */ typedef struct { int re_magic; /* magic number */ long re_info; /* information about RE */ size_t re_nsub; /* number of subexpressions */ #define REG_UBACKREF 000001 #define REG_ULOOKAHEAD 000002 #define REG_UBOUNDS 000004 #define REG_UBRACES 000010 #define REG_UBSALNUM 000020 #define REG_UPBOTCH 000040 #define REG_UBBS 000100 #define REG_UNONPOSIX 000200 #define REG_UUNSPEC 000400 #define REG_UUNPORT 001000 #define REG_ULOCALE 002000 #define REG_UEMPTYMATCH 004000 #define REG_UIMPOSSIBLE 010000 #define REG_USHORTEST 020000 char *re_endp; /* backward compatibility kludge */ /* the rest is opaque pointers to hidden innards */ void *re_guts; void *re_fns; } regex_t; /* result reporting (may acquire more fields later) */ typedef struct { size_t rm_so; /* start of substring */ size_t rm_eo; /* end of substring */ } regmatch_t; /* supplementary control and reporting */ typedef struct { regmatch_t rm_extend; /* see REG_EXPECT */ } rm_detail_t; /* * compilation ^ #ifndef __REG_NOCHAR ^ int re_comp(regex_t *, const char *, size_t, int); ^ #endif ^ #ifndef __REG_NOFRONT ^ int regcomp(regex_t *, const char *, int); ^ #endif ^ #ifdef __REG_WIDE_T ^ int __REG_WIDE_COMPILE(regex_t *, const __REG_WIDE_T *, size_t, int); ^ #endif */ #define REG_BASIC 000000 /* BREs (convenience) */ #define REG_EXTENDED 000001 /* EREs */ #define REG_ADVF 000002 /* advanced features in EREs */ #define REG_ADVANCED 000003 /* AREs (which are also EREs) */ #define REG_QUOTE 000004 /* no special characters, none */ #define REG_NOSPEC REG_QUOTE /* historical synonym */ #define REG_ICASE 000010 /* ignore case */ #define REG_NOSUB 000020 /* don't care about subexpressions */ #define REG_EXPANDED 000040 /* expanded format, white space & comments */ #define REG_NLSTOP 000100 /* \n doesn't match . or [^ ] */ #define REG_NLANCH 000200 /* ^ matches after \n, $ before */ #define REG_NEWLINE 000300 /* newlines are line terminators */ #define REG_PEND 000400 /* ugh -- backward-compatibility hack */ #define REG_EXPECT 001000 /* report details on partial/limited matches */ #define REG_BOSONLY 002000 /* temporary kludge for BOS-only matches */ #define REG_DUMP 004000 /* none of your business :-) */ #define REG_FAKE 010000 /* none of your business :-) */ #define REG_PROGRESS 020000 /* none of your business :-) */ /* * execution ^ #ifndef __REG_NOCHAR ^ int re_exec(regex_t *, const char *, size_t, ^ rm_detail_t *, size_t, regmatch_t [], int); ^ #endif ^ #ifndef __REG_NOFRONT ^ int regexec(regex_t *, const char *, size_t, regmatch_t [], int); ^ #endif ^ #ifdef __REG_WIDE_T ^ int __REG_WIDE_EXEC(regex_t *, const __REG_WIDE_T *, size_t, ^ rm_detail_t *, size_t, regmatch_t [], int); ^ #endif */ #define REG_NOTBOL 0001 /* BOS is not BOL */ #define REG_NOTEOL 0002 /* EOS is not EOL */ #define REG_STARTEND 0004 /* backward compatibility kludge */ #define REG_FTRACE 0010 /* none of your business */ #define REG_MTRACE 0020 /* none of your business */ #define REG_SMALL 0040 /* none of your business */ /* * misc generics (may be more functions here eventually) ^ void regfree(regex_t *); */ /* * error reporting * Be careful if modifying the list of error codes -- the table used by * regerror() is generated automatically from this file! * * Note that there is no wide-char variant of regerror at this time; what kind * of character is used for error reports is independent of what kind is used * in matching. * ^ extern size_t regerror(int, char *, size_t); */ #define REG_OKAY 0 /* no errors detected */ #define REG_NOMATCH 1 /* failed to match */ #define REG_BADPAT 2 /* invalid regexp */ #define REG_ECOLLATE 3 /* invalid collating element */ #define REG_ECTYPE 4 /* invalid character class */ #define REG_EESCAPE 5 /* invalid escape \ sequence */ #define REG_ESUBREG 6 /* invalid backreference number */ #define REG_EBRACK 7 /* brackets [] not balanced */ #define REG_EPAREN 8 /* parentheses () not balanced */ #define REG_EBRACE 9 /* braces {} not balanced */ #define REG_BADBR 10 /* invalid repetition count(s) */ #define REG_ERANGE 11 /* invalid character range */ #define REG_ESPACE 12 /* out of memory */ #define REG_BADRPT 13 /* quantifier operand invalid */ #define REG_ASSERT 15 /* "can't happen" -- you found a bug */ #define REG_INVARG 16 /* invalid argument to regex function */ #define REG_MIXED 17 /* character widths of regex and string differ */ #define REG_BADOPT 18 /* invalid embedded option */ #define REG_ETOOBIG 19 /* regular expression is too complex */ #define REG_ECOLORS 20 /* too many colors */ /* two specials for debugging and testing */ #define REG_ATOI 101 /* convert error-code name to number */ #define REG_ITOA 102 /* convert error-code number to name */ /* * the prototypes, as possibly munched by regfwd */ /* =====^!^===== begin forwards =====^!^===== */ /* automatically gathered by fwd; do not hand-edit */ /* === regproto.h === */ #ifndef __REG_NOCHAR int re_comp(regex_t *, const char *, size_t, int); #endif #ifndef __REG_NOFRONT int regcomp(regex_t *, const char *, int); #endif #ifdef __REG_WIDE_T MODULE_SCOPE int __REG_WIDE_COMPILE(regex_t *, const __REG_WIDE_T *, size_t, int); #endif #ifndef __REG_NOCHAR int re_exec(regex_t *, const char *, size_t, rm_detail_t *, size_t, regmatch_t [], int); #endif #ifndef __REG_NOFRONT int regexec(regex_t *, const char *, size_t, regmatch_t [], int); #endif #ifdef __REG_WIDE_T MODULE_SCOPE int __REG_WIDE_EXEC(regex_t *, const __REG_WIDE_T *, size_t, rm_detail_t *, size_t, regmatch_t [], int); #endif MODULE_SCOPE void regfree(regex_t *); MODULE_SCOPE size_t regerror(int, char *, size_t); /* automatically gathered by fwd; do not hand-edit */ /* =====^!^===== end forwards =====^!^===== */ /* * more C++ voodoo */ #ifdef __cplusplus } #endif #endif /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/regexec.c0000644000175000017500000010645514726623136014773 0ustar sergeisergei/* * re_*exec and friends - match REs * * Copyright © 1998, 1999 Henry Spencer. All rights reserved. * * Development of this software was funded, in part, by Cray Research Inc., * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics * Corporation, none of whom are responsible for the results. The author * thanks all of them. * * Redistribution and use in source and binary forms -- with or without * modification -- are permitted for any purpose, provided that * redistributions in source form retain this entire copyright notice and * indicate the origin and nature of any modifications. * * I'd appreciate being given credit for this package in the documentation of * software which uses it, but that is not a requirement. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "regguts.h" /* * Lazy-DFA representation. */ struct arcp { /* "pointer" to an outarc */ struct sset *ss; color co; }; struct sset { /* state set */ unsigned *states; /* pointer to bitvector */ unsigned hash; /* hash of bitvector */ #define HASH(bv, nw) (((nw) == 1) ? *(bv) : hash(bv, nw)) #define HIT(h,bv,ss,nw) ((ss)->hash == (h) && ((nw) == 1 || \ memcmp((void*)(bv), (void*)((ss)->states), (nw)*sizeof(unsigned)) == 0)) int flags; #define STARTER 01 /* the initial state set */ #define POSTSTATE 02 /* includes the goal state */ #define LOCKED 04 /* locked in cache */ #define NOPROGRESS 010 /* zero-progress state set */ struct arcp ins; /* chain of inarcs pointing here */ chr *lastseen; /* last entered on arrival here */ struct sset **outs; /* outarc vector indexed by color */ struct arcp *inchain; /* chain-pointer vector for outarcs */ }; struct dfa { size_t nssets; /* size of cache */ size_t nssused; /* how many entries occupied yet */ size_t nstates; /* number of states */ size_t wordsper; /* length of state-set bitvectors */ int ncolors; /* length of outarc and inchain vectors */ int cptsmalloced; /* were the areas individually malloced? */ struct sset *ssets; /* state-set cache */ unsigned *statesarea; /* bitvector storage */ unsigned *work; /* pointer to work area within statesarea */ struct sset **outsarea; /* outarc-vector storage */ struct arcp *incarea; /* inchain storage */ struct cnfa *cnfa; struct colormap *cm; chr *lastpost; /* location of last cache-flushed success */ chr *lastnopr; /* location of last cache-flushed NOPROGRESS */ struct sset *search; /* replacement-search-pointer memory */ char *mallocarea; /* self, or malloced area, or NULL */ }; #define WORK 1 /* number of work bitvectors needed */ /* * Setup for non-malloc allocation for small cases. */ #define FEWSTATES 20 /* must be less than UBITS */ #define FEWCOLORS 15 struct smalldfa { struct dfa dfa; struct sset ssets[FEWSTATES*2]; unsigned statesarea[FEWSTATES*2 + WORK]; struct sset *outsarea[FEWSTATES*2 * FEWCOLORS]; struct arcp incarea[FEWSTATES*2 * FEWCOLORS]; }; /* * Internal variables, bundled for easy passing around. */ struct vars { regex_t *re; struct guts *g; int eflags; /* copies of arguments */ size_t nmatch; regmatch_t *pmatch; rm_detail_t *details; chr *start; /* start of string */ chr *stop; /* just past end of string */ int err; /* error code if any (0 none) */ struct dfa **subdfas; /* per-subre DFAs */ struct smalldfa dfa1; struct smalldfa dfa2; }; #define VISERR(vv) ((vv)->err != 0) /* have we seen an error yet? */ #define ISERR() VISERR(v) #define VERR(vv,e) ((vv)->err = ((vv)->err ? (vv)->err : (e))) #define ERR(e) VERR(v, e) /* record an error */ #define NOERR() {if (ISERR()) return v->err;} /* if error seen, return it */ #define OFF(p) ((p) - v->start) #define LOFF(p) ((size_t)OFF(p)) /* * forward declarations */ /* =====^!^===== begin forwards =====^!^===== */ /* automatically gathered by fwd; do not hand-edit */ /* === regexec.c === */ int exec(regex_t *, const chr *, size_t, rm_detail_t *, size_t, regmatch_t [], int); static struct dfa *getsubdfa(struct vars *, struct subre *); static int simpleFind(struct vars *const, struct cnfa *const, struct colormap *const); static int complicatedFind(struct vars *const, struct cnfa *const, struct colormap *const); static int complicatedFindLoop(struct vars *const, struct dfa *const, struct dfa *const, chr **const); static void zapallsubs(regmatch_t *const, const size_t); static void zaptreesubs(struct vars *const, struct subre *const); static void subset(struct vars *const, struct subre *const, chr *const, chr *const); static int cdissect(struct vars *, struct subre *, chr *, chr *); static int ccondissect(struct vars *, struct subre *, chr *, chr *); static int crevcondissect(struct vars *, struct subre *, chr *, chr *); static int cbrdissect(struct vars *, struct subre *, chr *, chr *); static int caltdissect(struct vars *, struct subre *, chr *, chr *); static int citerdissect(struct vars *, struct subre *, chr *, chr *); static int creviterdissect(struct vars *, struct subre *, chr *, chr *); /* === rege_dfa.c === */ static chr *longest(struct vars *const, struct dfa *const, chr *const, chr *const, int *const); static chr *shortest(struct vars *const, struct dfa *const, chr *const, chr *const, chr *const, chr **const, int *const); static chr *lastCold(struct vars *const, struct dfa *const); static struct dfa *newDFA(struct vars *const, struct cnfa *const, struct colormap *const, struct smalldfa *); static void freeDFA(struct dfa *const); static unsigned hash(unsigned *const, int); static struct sset *initialize(struct vars *const, struct dfa *const, chr *const); static struct sset *miss(struct vars *const, struct dfa *const, struct sset *const, const pcolor, chr *const, chr *const); static int checkLAConstraint(struct vars *const, struct cnfa *const, chr *const, const pcolor); static struct sset *getVacantSS(struct vars *const, struct dfa *const, chr *const, chr *const); static struct sset *pickNextSS(struct vars *const, struct dfa *const, chr *const, chr *const); /* automatically gathered by fwd; do not hand-edit */ /* =====^!^===== end forwards =====^!^===== */ /* - exec - match regular expression ^ int exec(regex_t *, const chr *, size_t, rm_detail_t *, ^ size_t, regmatch_t [], int); */ int exec( regex_t *re, const chr *string, size_t len, rm_detail_t *details, size_t nmatch, regmatch_t pmatch[], int flags) { AllocVars(v); int st, backref; int n; int i; #define LOCALMAT 20 regmatch_t mat[LOCALMAT]; #define LOCALDFAS 40 struct dfa *subdfas[LOCALDFAS]; /* * Sanity checks. */ if (re == NULL || string == NULL || re->re_magic != REMAGIC) { FreeVars(v); return REG_INVARG; } /* * Setup. */ v->re = re; v->g = (struct guts *)re->re_guts; if ((v->g->cflags®_EXPECT) && details == NULL) { FreeVars(v); return REG_INVARG; } if (v->g->info®_UIMPOSSIBLE) { FreeVars(v); return REG_NOMATCH; } backref = (v->g->info®_UBACKREF) ? 1 : 0; v->eflags = flags; if (v->g->cflags®_NOSUB) { nmatch = 0; /* override client */ } v->nmatch = nmatch; if (backref) { /* * Need work area. */ if (v->g->nsub + 1 <= LOCALMAT) { v->pmatch = mat; } else { v->pmatch = (regmatch_t *) MALLOC((v->g->nsub + 1) * sizeof(regmatch_t)); } if (v->pmatch == NULL) { FreeVars(v); return REG_ESPACE; } v->nmatch = v->g->nsub + 1; } else { v->pmatch = pmatch; } v->details = details; v->start = (chr *)string; v->stop = (chr *)string + len; v->err = 0; assert(v->g->ntree >= 0); n = v->g->ntree; if (n <= LOCALDFAS) { v->subdfas = subdfas; } else { v->subdfas = (struct dfa **) MALLOC(n * sizeof(struct dfa *)); } if (v->subdfas == NULL) { if (v->pmatch != pmatch && v->pmatch != mat) { FREE(v->pmatch); } FreeVars(v); return REG_ESPACE; } for (i = 0; i < n; i++) v->subdfas[i] = NULL; /* * Do it. */ assert(v->g->tree != NULL); if (backref) { st = complicatedFind(v, &v->g->tree->cnfa, &v->g->cmap); } else { st = simpleFind(v, &v->g->tree->cnfa, &v->g->cmap); } /* * Copy (portion of) match vector over if necessary. */ if (st == REG_OKAY && v->pmatch != pmatch && nmatch > 0) { zapallsubs(pmatch, nmatch); n = (nmatch < v->nmatch) ? nmatch : v->nmatch; memcpy((void*)(pmatch), (void*)(v->pmatch), n*sizeof(regmatch_t)); } /* * Clean up. */ if (v->pmatch != pmatch && v->pmatch != mat) { FREE(v->pmatch); } n = v->g->ntree; for (i = 0; i < n; i++) { if (v->subdfas[i] != NULL) { freeDFA(v->subdfas[i]); } } if (v->subdfas != subdfas) { FREE(v->subdfas); } FreeVars(v); return st; } /* - getsubdfa - create or re-fetch the DFA for a subre node * We only need to create the DFA once per overall regex execution. * The DFA will be freed by the cleanup step in exec(). */ static struct dfa * getsubdfa(struct vars * v, struct subre * t) { if (v->subdfas[t->id] == NULL) { v->subdfas[t->id] = newDFA(v, &t->cnfa, &v->g->cmap, NULL); if (ISERR()) { return NULL; } } return v->subdfas[t->id]; } /* - simpleFind - find a match for the main NFA (no-complications case) ^ static int simpleFind(struct vars *, struct cnfa *, struct colormap *); */ static int simpleFind( struct vars *const v, struct cnfa *const cnfa, struct colormap *const cm) { struct dfa *s, *d; chr *begin, *end = NULL; chr *cold; chr *open, *close; /* Open and close of range of possible * starts */ int hitend; int shorter = (v->g->tree->flags&SHORTER) ? 1 : 0; /* * First, a shot with the search RE. */ s = newDFA(v, &v->g->search, cm, &v->dfa1); assert(!(ISERR() && s != NULL)); NOERR(); MDEBUG(("\nsearch at %" TCL_Z_MODIFIER "u\n", LOFF(v->start))); cold = NULL; close = shortest(v, s, v->start, v->start, v->stop, &cold, NULL); freeDFA(s); NOERR(); if (v->g->cflags®_EXPECT) { assert(v->details != NULL); if (cold != NULL) { v->details->rm_extend.rm_so = OFF(cold); } else { v->details->rm_extend.rm_so = OFF(v->stop); } v->details->rm_extend.rm_eo = OFF(v->stop); /* unknown */ } if (close == NULL) { /* not found */ return REG_NOMATCH; } if (v->nmatch == 0) { /* found, don't need exact location */ return REG_OKAY; } /* * Find starting point and match. */ assert(cold != NULL); open = cold; cold = NULL; MDEBUG(("between %" TCL_Z_MODIFIER "u and %" TCL_Z_MODIFIER "u\n", LOFF(open), LOFF(close))); d = newDFA(v, cnfa, cm, &v->dfa1); assert(!(ISERR() && d != NULL)); NOERR(); for (begin = open; begin <= close; begin++) { MDEBUG(("\nfind trying at %" TCL_Z_MODIFIER "u\n", LOFF(begin))); if (shorter) { end = shortest(v, d, begin, begin, v->stop, NULL, &hitend); } else { end = longest(v, d, begin, v->stop, &hitend); } if (ISERR()) { freeDFA(d); return v->err; } if (hitend && cold == NULL) { cold = begin; } if (end != NULL) { break; /* NOTE BREAK OUT */ } } assert(end != NULL); /* search RE succeeded so loop should */ freeDFA(d); /* * And pin down details. */ assert(v->nmatch > 0); v->pmatch[0].rm_so = OFF(begin); v->pmatch[0].rm_eo = OFF(end); if (v->g->cflags®_EXPECT) { if (cold != NULL) { v->details->rm_extend.rm_so = OFF(cold); } else { v->details->rm_extend.rm_so = OFF(v->stop); } v->details->rm_extend.rm_eo = OFF(v->stop); /* unknown */ } if (v->nmatch == 1) { /* no need for submatches */ return REG_OKAY; } /* * Find submatches. */ zapallsubs(v->pmatch, v->nmatch); return cdissect(v, v->g->tree, begin, end); } /* - complicatedFind - find a match for the main NFA (with complications) ^ static int complicatedFind(struct vars *, struct cnfa *, struct colormap *); */ static int complicatedFind( struct vars *const v, struct cnfa *const cnfa, struct colormap *const cm) { struct dfa *s, *d; chr *cold = NULL; /* silence gcc 4 warning */ int ret; s = newDFA(v, &v->g->search, cm, &v->dfa1); NOERR(); d = newDFA(v, cnfa, cm, &v->dfa2); if (ISERR()) { assert(d == NULL); freeDFA(s); return v->err; } ret = complicatedFindLoop(v, d, s, &cold); freeDFA(d); freeDFA(s); NOERR(); if (v->g->cflags®_EXPECT) { assert(v->details != NULL); if (cold != NULL) { v->details->rm_extend.rm_so = OFF(cold); } else { v->details->rm_extend.rm_so = OFF(v->stop); } v->details->rm_extend.rm_eo = OFF(v->stop); /* unknown */ } return ret; } /* - complicatedFindLoop - the heart of complicatedFind ^ static int complicatedFindLoop(struct vars *, ^ struct dfa *, struct dfa *, chr **); */ static int complicatedFindLoop( struct vars *const v, struct dfa *const d, struct dfa *const s, chr **const coldp) /* where to put coldstart pointer */ { chr *begin, *end; chr *cold; chr *open, *close; /* Open and close of range of possible * starts */ chr *estart, *estop; int er, hitend; int shorter = v->g->tree->flags&SHORTER; assert(d != NULL && s != NULL); cold = NULL; close = v->start; do { MDEBUG(("\ncsearch at %" TCL_Z_MODIFIER "u\n", LOFF(close))); close = shortest(v, s, close, close, v->stop, &cold, NULL); if (close == NULL) { break; /* NOTE BREAK */ } assert(cold != NULL); open = cold; cold = NULL; MDEBUG(("cbetween %" TCL_Z_MODIFIER "u and %" TCL_Z_MODIFIER "u\n", LOFF(open), LOFF(close))); for (begin = open; begin <= close; begin++) { MDEBUG(("\ncomplicatedFind trying at %" TCL_Z_MODIFIER "u\n", LOFF(begin))); estart = begin; estop = v->stop; for (;;) { if (shorter) { end = shortest(v, d, begin, estart, estop, NULL, &hitend); } else { end = longest(v, d, begin, estop, &hitend); } if (hitend && cold == NULL) { cold = begin; } if (end == NULL) { break; /* NOTE BREAK OUT */ } MDEBUG(("tentative end %" TCL_Z_MODIFIER "u\n", LOFF(end))); zapallsubs(v->pmatch, v->nmatch); er = cdissect(v, v->g->tree, begin, end); if (er == REG_OKAY) { if (v->nmatch > 0) { v->pmatch[0].rm_so = OFF(begin); v->pmatch[0].rm_eo = OFF(end); } *coldp = cold; return REG_OKAY; } if (er != REG_NOMATCH) { ERR(er); *coldp = cold; return er; } if ((shorter) ? end == estop : end == begin) { break; } /* * Go around and try again */ if (shorter) { estart = end + 1; } else { estop = end - 1; } } } } while (close < v->stop); *coldp = cold; return REG_NOMATCH; } /* - zapallsubs - initialize all subexpression matches to "no match" ^ static void zapallsubs(regmatch_t *, size_t); */ static void zapallsubs( regmatch_t *const p, const size_t n) { size_t i; for (i = n-1; i > 0; i--) { p[i].rm_so = FREESTATE; p[i].rm_eo = FREESTATE; } } /* - zaptreesubs - initialize subexpressions within subtree to "no match" ^ static void zaptreesubs(struct vars *, struct subre *); */ static void zaptreesubs( struct vars *const v, struct subre *const t) { if (t->op == '(') { size_t n = t->subno; assert(n > 0); if (n < v->nmatch) { v->pmatch[n].rm_so = FREESTATE; v->pmatch[n].rm_eo = FREESTATE; } } if (t->left != NULL) { zaptreesubs(v, t->left); } if (t->right != NULL) { zaptreesubs(v, t->right); } } /* - subset - set subexpression match data for a successful subre ^ static void subset(struct vars *, struct subre *, chr *, chr *); */ static void subset( struct vars *const v, struct subre *const sub, chr *const begin, chr *const end) { int n = sub->subno; assert(n > 0); if ((size_t)n >= v->nmatch) { return; } MDEBUG(("setting %d\n", n)); v->pmatch[n].rm_so = OFF(begin); v->pmatch[n].rm_eo = OFF(end); } /* - cdissect - check backrefs and determine subexpression matches * cdissect recursively processes a subre tree to check matching of backrefs * and/or identify submatch boundaries for capture nodes. The proposed match * runs from "begin" to "end" (not including "end"), and we are basically * "dissecting" it to see where the submatches are. * Before calling any level of cdissect, the caller must have run the node's * DFA and found that the proposed substring satisfies the DFA. (We make * the caller do that because in concatenation and iteration nodes, it's * much faster to check all the substrings against the child DFAs before we * recurse.) Also, caller must have cleared subexpression match data via * zaptreesubs (or zapallsubs at the top level). ^ static int cdissect(struct vars *, struct subre *, chr *, chr *); */ static int /* regexec return code */ cdissect( struct vars *v, struct subre *t, chr *begin, /* beginning of relevant substring */ chr *end) /* end of same */ { int er; assert(t != NULL); MDEBUG(("cdissect %" TCL_Z_MODIFIER "u-%" TCL_Z_MODIFIER "u %c\n", LOFF(begin), LOFF(end), t->op)); switch (t->op) { case '=': /* terminal node */ assert(t->left == NULL && t->right == NULL); er = REG_OKAY; /* no action, parent did the work */ break; case 'b': /* back reference */ assert(t->left == NULL && t->right == NULL); er = cbrdissect(v, t, begin, end); break; case '.': /* concatenation */ assert(t->left != NULL && t->right != NULL); if (t->left->flags & SHORTER) {/* reverse scan */ er = crevcondissect(v, t, begin, end); } else { er = ccondissect(v, t, begin, end); } break; case '|': /* alternation */ assert(t->left != NULL); er = caltdissect(v, t, begin, end); break; case '*': /* iteration */ assert(t->left != NULL); if (t->left->flags & SHORTER) {/* reverse scan */ er = creviterdissect(v, t, begin, end); } else { er = citerdissect(v, t, begin, end); } break; case '(': /* capturing */ assert(t->left != NULL && t->right == NULL); assert(t->subno > 0); er = cdissect(v, t->left, begin, end); if (er == REG_OKAY) { subset(v, t, begin, end); } break; default: er = REG_ASSERT; break; } /* * We should never have a match failure unless backrefs lurk below; * otherwise, either caller failed to check the DFA, or there's some * inconsistency between the DFA and the node's innards. */ assert(er != REG_NOMATCH || (t->flags & BACKR)); return er; } /* - ccondissect - dissect match for concatenation node ^ static int ccondissect(struct vars *, struct subre *, chr *, chr *); */ static int /* regexec return code */ ccondissect( struct vars *v, struct subre *t, chr *begin, /* beginning of relevant substring */ chr *end) /* end of same */ { struct dfa *d, *d2; chr *mid; assert(t->op == '.'); assert(t->left != NULL && t->left->cnfa.nstates > 0); assert(t->right != NULL && t->right->cnfa.nstates > 0); assert(!(t->left->flags & SHORTER)); d = getsubdfa(v, t->left); NOERR(); d2 = getsubdfa(v, t->right); NOERR(); MDEBUG(("cConcat %d\n", t->id)); /* * Pick a tentative midpoint. */ mid = longest(v, d, begin, end, (int *) NULL); if (mid == NULL) { return REG_NOMATCH; } MDEBUG(("tentative midpoint %" TCL_Z_MODIFIER "u\n", LOFF(mid))); /* * Iterate until satisfaction or failure. */ for (;;) { /* * Try this midpoint on for size. */ if (longest(v, d2, mid, end, NULL) == end) { int er = cdissect(v, t->left, begin, mid); if (er == REG_OKAY) { er = cdissect(v, t->right, mid, end); if (er == REG_OKAY) { /* * Satisfaction. */ MDEBUG(("successful\n")); return REG_OKAY; } } if (er != REG_NOMATCH) { return er; } } /* * That midpoint didn't work, find a new one. */ if (mid == begin) { /* * All possibilities exhausted. */ MDEBUG(("%d no midpoint\n", t->id)); return REG_NOMATCH; } mid = longest(v, d, begin, mid-1, NULL); if (mid == NULL) { /* * Failed to find a new one. */ MDEBUG(("%d failed midpoint\n", t->id)); return REG_NOMATCH; } MDEBUG(("%d: new midpoint %" TCL_Z_MODIFIER "u\n", t->id, LOFF(mid))); zaptreesubs(v, t->left); zaptreesubs(v, t->right); } } /* - crevcondissect - dissect match for concatenation node, shortest-first ^ static int crevcondissect(struct vars *, struct subre *, chr *, chr *); */ static int /* regexec return code */ crevcondissect( struct vars *v, struct subre *t, chr *begin, /* beginning of relevant substring */ chr *end) /* end of same */ { struct dfa *d, *d2; chr *mid; assert(t->op == '.'); assert(t->left != NULL && t->left->cnfa.nstates > 0); assert(t->right != NULL && t->right->cnfa.nstates > 0); assert(t->left->flags&SHORTER); d = getsubdfa(v, t->left); NOERR(); d2 = getsubdfa(v, t->right); NOERR(); MDEBUG(("crevcon %d\n", t->id)); /* * Pick a tentative midpoint. */ mid = shortest(v, d, begin, begin, end, (chr **) NULL, (int *) NULL); if (mid == NULL) { return REG_NOMATCH; } MDEBUG(("tentative midpoint %" TCL_Z_MODIFIER "u\n", LOFF(mid))); /* * Iterate until satisfaction or failure. */ for (;;) { /* * Try this midpoint on for size. */ if (longest(v, d2, mid, end, NULL) == end) { int er = cdissect(v, t->left, begin, mid); if (er == REG_OKAY) { er = cdissect(v, t->right, mid, end); if (er == REG_OKAY) { /* * Satisfaction. */ MDEBUG(("successful\n")); return REG_OKAY; } } if (er != REG_NOMATCH) { return er; } } /* * That midpoint didn't work, find a new one. */ if (mid == end) { /* * All possibilities exhausted. */ MDEBUG(("%d no midpoint\n", t->id)); return REG_NOMATCH; } mid = shortest(v, d, begin, mid+1, end, NULL, NULL); if (mid == NULL) { /* * Failed to find a new one. */ MDEBUG(("%d failed midpoint\n", t->id)); return REG_NOMATCH; } MDEBUG(("%d: new midpoint %" TCL_Z_MODIFIER "u\n", t->id, LOFF(mid))); zaptreesubs(v, t->left); zaptreesubs(v, t->right); } } /* - cbrdissect - dissect match for backref node ^ static int cbrdissect(struct vars *, struct subre *, chr *, chr *); */ static int /* regexec return code */ cbrdissect( struct vars *v, struct subre *t, chr *begin, /* beginning of relevant substring */ chr *end) /* end of same */ { int n = t->subno, min = t->min, max = t->max; size_t numreps; size_t tlen; size_t brlen; chr *brstring; chr *p; assert(t != NULL); assert(t->op == 'b'); assert(n >= 0); assert((size_t)n < v->nmatch); MDEBUG(("cbackref n%d %d{%d-%d}\n", t->id, n, min, max)); /* get the backreferenced string */ if (v->pmatch[n].rm_so == FREESTATE) { return REG_NOMATCH; } brstring = v->start + v->pmatch[n].rm_so; brlen = v->pmatch[n].rm_eo - v->pmatch[n].rm_so; /* special cases for zero-length strings */ if (brlen == 0) { /* * matches only if target is zero length, but any number of * repetitions can be considered to be present */ if (begin == end && min <= max) { MDEBUG(("cbackref matched trivially\n")); return REG_OKAY; } return REG_NOMATCH; } if (begin == end) { /* matches only if zero repetitions are okay */ if (min == 0) { MDEBUG(("cbackref matched trivially\n")); return REG_OKAY; } return REG_NOMATCH; } /* * check target length to see if it could possibly be an allowed number of * repetitions of brstring */ assert(end > begin); tlen = end - begin; if (tlen % brlen != 0) { return REG_NOMATCH; } numreps = tlen / brlen; if (numreps < (size_t)min || (numreps > (size_t)max && max != DUPINF)) { return REG_NOMATCH; } /* okay, compare the actual string contents */ p = begin; while (numreps-- > 0) { if ((*v->g->compare) (brstring, p, brlen) != 0) { return REG_NOMATCH; } p += brlen; } MDEBUG(("cbackref matched\n")); return REG_OKAY; } /* - caltdissect - dissect match for alternation node ^ static int caltdissect(struct vars *, struct subre *, chr *, chr *); */ static int /* regexec return code */ caltdissect( struct vars *v, struct subre *t, chr *begin, /* beginning of relevant substring */ chr *end) /* end of same */ { struct dfa *d; int er; /* We loop, rather than tail-recurse, to handle a chain of alternatives */ while (t != NULL) { assert(t->op == '|'); assert(t->left != NULL && t->left->cnfa.nstates > 0); MDEBUG(("calt n%d\n", t->id)); d = getsubdfa(v, t->left); NOERR(); if (longest(v, d, begin, end, (int *) NULL) == end) { MDEBUG(("calt matched\n")); er = cdissect(v, t->left, begin, end); if (er != REG_NOMATCH) { return er; } } t = t->right; } return REG_NOMATCH; } /* - citerdissect - dissect match for iteration node ^ static int citerdissect(struct vars *, struct subre *, chr *, chr *); */ static int /* regexec return code */ citerdissect(struct vars * v, struct subre * t, chr *begin, /* beginning of relevant substring */ chr *end) /* end of same */ { struct dfa *d; chr **endpts; chr *limit; int min_matches; size_t max_matches; int nverified; int k; int i; int er; assert(t->op == '*'); assert(t->left != NULL && t->left->cnfa.nstates > 0); assert(!(t->left->flags & SHORTER)); assert(begin <= end); /* * If zero matches are allowed, and target string is empty, just declare * victory. OTOH, if target string isn't empty, zero matches can't work * so we pretend the min is 1. */ min_matches = t->min; if (min_matches <= 0) { if (begin == end) { return REG_OKAY; } min_matches = 1; } /* * We need workspace to track the endpoints of each sub-match. Normally * we consider only nonzero-length sub-matches, so there can be at most * end-begin of them. However, if min is larger than that, we will also * consider zero-length sub-matches in order to find enough matches. * * For convenience, endpts[0] contains the "begin" pointer and we store * sub-match endpoints in endpts[1..max_matches]. */ max_matches = end - begin; if (max_matches > (size_t)t->max && t->max != DUPINF) { max_matches = t->max; } if (max_matches < (size_t)min_matches) max_matches = min_matches; endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *)); if (endpts == NULL) return REG_ESPACE; endpts[0] = begin; d = getsubdfa(v, t->left); if (ISERR()) { FREE(endpts); return v->err; } MDEBUG(("citer %d\n", t->id)); /* * Our strategy is to first find a set of sub-match endpoints that are * valid according to the child node's DFA, and then recursively dissect * each sub-match to confirm validity. If any validity check fails, * backtrack the last sub-match and try again. And, when we next try for * a validity check, we need not recheck any successfully verified * sub-matches that we didn't move the endpoints of. nverified remembers * how many sub-matches are currently known okay. */ /* initialize to consider first sub-match */ nverified = 0; k = 1; limit = end; /* iterate until satisfaction or failure */ while (k > 0) { /* try to find an endpoint for the k'th sub-match */ endpts[k] = longest(v, d, endpts[k - 1], limit, (int *) NULL); if (endpts[k] == NULL) { /* no match possible, so see if we can shorten previous one */ k--; goto backtrack; } MDEBUG(("%d: working endpoint %d: %" TCL_Z_MODIFIER "u\n", t->id, k, LOFF(endpts[k]))); /* k'th sub-match can no longer be considered verified */ if (nverified >= k) { nverified = k - 1; } if (endpts[k] != end) { /* haven't reached end yet, try another iteration if allowed */ if ((size_t)k >= max_matches) { /* must try to shorten some previous match */ k--; goto backtrack; } /* reject zero-length match unless necessary to achieve min */ if (endpts[k] == endpts[k - 1] && (k >= min_matches || min_matches - k < end - endpts[k])) goto backtrack; k++; limit = end; continue; } /* * We've identified a way to divide the string into k sub-matches * that works so far as the child DFA can tell. If k is an allowed * number of matches, start the slow part: recurse to verify each * sub-match. We always have k <= max_matches, needn't check that. */ if (k < min_matches) { goto backtrack; } MDEBUG(("%d: verifying %d..%d\n", t->id, nverified + 1, k)); for (i = nverified + 1; i <= k; i++) { zaptreesubs(v, t->left); er = cdissect(v, t->left, endpts[i - 1], endpts[i]); if (er == REG_OKAY) { nverified = i; continue; } if (er == REG_NOMATCH) { break; } /* oops, something failed */ FREE(endpts); return er; } if (i > k) { /* satisfaction */ MDEBUG(("%d successful\n", t->id)); FREE(endpts); return REG_OKAY; } /* match failed to verify, so backtrack */ backtrack: /* * Must consider shorter versions of the current sub-match. However, * we'll only ask for a zero-length match if necessary. */ while (k > 0) { chr *prev_end = endpts[k - 1]; if (endpts[k] > prev_end) { limit = endpts[k] - 1; if (limit > prev_end || (k < min_matches && min_matches - k >= end - prev_end)) { /* break out of backtrack loop, continue the outer one */ break; } } /* can't shorten k'th sub-match any more, consider previous one */ k--; } } /* all possibilities exhausted */ MDEBUG(("%d failed\n", t->id)); FREE(endpts); return REG_NOMATCH; } /* - creviterdissect - dissect match for iteration node, shortest-first ^ static int creviterdissect(struct vars *, struct subre *, chr *, chr *); */ static int /* regexec return code */ creviterdissect(struct vars * v, struct subre * t, chr *begin, /* beginning of relevant substring */ chr *end) /* end of same */ { struct dfa *d; chr **endpts; chr *limit; int min_matches; size_t max_matches; int nverified; int k; int i; int er; assert(t->op == '*'); assert(t->left != NULL && t->left->cnfa.nstates > 0); assert(t->left->flags & SHORTER); assert(begin <= end); /* * If zero matches are allowed, and target string is empty, just declare * victory. OTOH, if target string isn't empty, zero matches can't work * so we pretend the min is 1. */ min_matches = t->min; if (min_matches <= 0) { if (begin == end) { return REG_OKAY; } min_matches = 1; } /* * We need workspace to track the endpoints of each sub-match. Normally * we consider only nonzero-length sub-matches, so there can be at most * end-begin of them. However, if min is larger than that, we will also * consider zero-length sub-matches in order to find enough matches. * * For convenience, endpts[0] contains the "begin" pointer and we store * sub-match endpoints in endpts[1..max_matches]. */ max_matches = end - begin; if (max_matches > (size_t)t->max && t->max != DUPINF) max_matches = t->max; if (max_matches < (size_t)min_matches) max_matches = min_matches; endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *)); if (endpts == NULL) return REG_ESPACE; endpts[0] = begin; d = getsubdfa(v, t->left); if (ISERR()) { FREE(endpts); return v->err; } MDEBUG(("creviter %d\n", t->id)); /* * Our strategy is to first find a set of sub-match endpoints that are * valid according to the child node's DFA, and then recursively dissect * each sub-match to confirm validity. If any validity check fails, * backtrack the last sub-match and try again. And, when we next try for * a validity check, we need not recheck any successfully verified * sub-matches that we didn't move the endpoints of. nverified remembers * how many sub-matches are currently known okay. */ /* initialize to consider first sub-match */ nverified = 0; k = 1; limit = begin; /* iterate until satisfaction or failure */ while (k > 0) { /* disallow zero-length match unless necessary to achieve min */ if (limit == endpts[k - 1] && limit != end && (k >= min_matches || min_matches - k < end - limit)) limit++; /* if this is the last allowed sub-match, it must reach to the end */ if ((size_t)k >= max_matches) { limit = end; } /* try to find an endpoint for the k'th sub-match */ endpts[k] = shortest(v, d, endpts[k - 1], limit, end, (chr **) NULL, (int *) NULL); if (endpts[k] == NULL) { /* no match possible, so see if we can lengthen previous one */ k--; goto backtrack; } MDEBUG(("%d: working endpoint %d: %" TCL_Z_MODIFIER "u\n", t->id, k, LOFF(endpts[k]))); /* k'th sub-match can no longer be considered verified */ if (nverified >= k) { nverified = k - 1; } if (endpts[k] != end) { /* haven't reached end yet, try another iteration if allowed */ if ((size_t)k >= max_matches) { /* must try to lengthen some previous match */ k--; goto backtrack; } k++; limit = endpts[k - 1]; continue; } /* * We've identified a way to divide the string into k sub-matches * that works so far as the child DFA can tell. If k is an allowed * number of matches, start the slow part: recurse to verify each * sub-match. We always have k <= max_matches, needn't check that. */ if (k < min_matches) { goto backtrack; } MDEBUG(("%d: verifying %d..%d\n", t->id, nverified + 1, k)); for (i = nverified + 1; i <= k; i++) { zaptreesubs(v, t->left); er = cdissect(v, t->left, endpts[i - 1], endpts[i]); if (er == REG_OKAY) { nverified = i; continue; } if (er == REG_NOMATCH) { break; } /* oops, something failed */ FREE(endpts); return er; } if (i > k) { /* satisfaction */ MDEBUG(("%d successful\n", t->id)); FREE(endpts); return REG_OKAY; } /* match failed to verify, so backtrack */ backtrack: /* * Must consider longer versions of the current sub-match. */ while (k > 0) { if (endpts[k] < end) { limit = endpts[k] + 1; /* break out of backtrack loop, continue the outer one */ break; } /* can't lengthen k'th sub-match any more, consider previous one */ k--; } } /* all possibilities exhausted */ MDEBUG(("%d failed\n", t->id)); FREE(endpts); return REG_NOMATCH; } #include "rege_dfa.c" /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/regfree.c0000644000175000017500000000420014726623136014751 0ustar sergeisergei/* * regfree - free an RE * * Copyright © 1998, 1999 Henry Spencer. All rights reserved. * * Development of this software was funded, in part, by Cray Research Inc., * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics * Corporation, none of whom are responsible for the results. The author * thanks all of them. * * Redistribution and use in source and binary forms -- with or without * modification -- are permitted for any purpose, provided that * redistributions in source form retain this entire copyright notice and * indicate the origin and nature of any modifications. * * I'd appreciate being given credit for this package in the documentation of * software which uses it, but that is not a requirement. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * You might think that this could be incorporated into regcomp.c, and that * would be a reasonable idea... except that this is a generic function (with * a generic name), applicable to all compiled REs regardless of the size of * their characters, whereas the stuff in regcomp.c gets compiled once per * character size. */ #include "regguts.h" /* - regfree - free an RE (generic function, punts to RE-specific function) * * Ignoring invocation with NULL is a convenience. */ void regfree( regex_t *re) { if (re == NULL) { return; } (*((struct fns *)re->re_fns)->free)(re); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/regfronts.c0000644000175000017500000000470214726623136015352 0ustar sergeisergei/* * regcomp and regexec - front ends to re_ routines * * Mostly for implementation of backward-compatibility kludges. Note that * these routines exist ONLY in char versions. * * Copyright © 1998, 1999 Henry Spencer. All rights reserved. * * Development of this software was funded, in part, by Cray Research Inc., * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics * Corporation, none of whom are responsible for the results. The author * thanks all of them. * * Redistribution and use in source and binary forms -- with or without * modification -- are permitted for any purpose, provided that * redistributions in source form retain this entire copyright notice and * indicate the origin and nature of any modifications. * * I'd appreciate being given credit for this package in the documentation of * software which uses it, but that is not a requirement. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "regguts.h" /* - regcomp - compile regular expression */ int regcomp( regex_t *re, const char *str, int flags) { size_t len; int f = flags; if (f®_PEND) { len = re->re_endp - str; f &= ~REG_PEND; } else { len = strlen(str); } return re_comp(re, str, len, f); } /* - regexec - execute regular expression */ int regexec( regex_t *re, const char *str, size_t nmatch, regmatch_t pmatch[], int flags) { const char *start; size_t len; int f = flags; if (f & REG_STARTEND) { start = str + pmatch[0].rm_so; len = pmatch[0].rm_eo - pmatch[0].rm_so; f &= ~REG_STARTEND; } else { start = str; len = strlen(str); } return re_exec(re, start, len, nmatch, pmatch, f); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/regguts.h0000644000175000017500000003472414726623136015035 0ustar sergeisergei/* * Internal interface definitions, etc., for the reg package * * Copyright (c) 1998, 1999 Henry Spencer. All rights reserved. * * Development of this software was funded, in part, by Cray Research Inc., * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics * Corporation, none of whom are responsible for the results. The author * thanks all of them. * * Redistribution and use in source and binary forms -- with or without * modification -- are permitted for any purpose, provided that * redistributions in source form retain this entire copyright notice and * indicate the origin and nature of any modifications. * * I'd appreciate being given credit for this package in the documentation of * software which uses it, but that is not a requirement. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Environmental customization. It should not (I hope) be necessary to alter * the file you are now reading -- regcustom.h should handle it all, given * care here and elsewhere. */ #include "regcustom.h" /* * Things that regcustom.h might override. */ /* assertions */ #ifndef assert #ifndef REG_DEBUG #ifndef NDEBUG #define NDEBUG /* no assertions */ #endif #endif /* !REG_DEBUG */ #include #endif /* memory allocation */ #ifndef MALLOC #define MALLOC(n) malloc(n) #endif #ifndef REALLOC #define REALLOC(p, n) realloc(p, n) #endif #ifndef FREE #define FREE(p) free(p) #endif /* want size of a char in bits, and max value in bounded quantifiers */ #ifndef _POSIX2_RE_DUP_MAX #define _POSIX2_RE_DUP_MAX 255 /* normally from */ #endif /* * misc */ #define NOTREACHED 0 #define DUPMAX _POSIX2_RE_DUP_MAX #define DUPINF (DUPMAX+1) #define REMAGIC 0xFED7 /* magic number for main struct */ /* * debugging facilities */ #ifdef REG_DEBUG /* FDEBUG does finite-state tracing */ #define FDEBUG(arglist) { if (v->eflags®_FTRACE) printf arglist; } /* MDEBUG does higher-level tracing */ #define MDEBUG(arglist) { if (v->eflags®_MTRACE) printf arglist; } #else #define FDEBUG(arglist) {} #define MDEBUG(arglist) {} #endif /* * bitmap manipulation */ #define UBITS (CHAR_BIT * sizeof(unsigned)) #define BSET(uv, sn) ((uv)[(sn)/UBITS] |= (unsigned)1 << ((sn)%UBITS)) #define ISBSET(uv, sn) ((uv)[(sn)/UBITS] & ((unsigned)1 << ((sn)%UBITS))) /* * We dissect a chr into byts for colormap table indexing. Here we define a * byt, which will be the same as a byte on most machines... The exact size of * a byt is not critical, but about 8 bits is good, and extraction of 8-bit * chunks is sometimes especially fast. */ #ifndef BYTBITS #define BYTBITS 8 /* bits in a byt */ #endif #define BYTTAB (1<flags&FREECOL) union tree *block; /* block of solid color, if any */ }; /* * The color map itself * * Much of the data in the colormap struct is only used at compile time. * However, the bulk of the space usage is in the "tree" structure, so it's * not clear that there's much point in converting the rest to a more compact * form when compilation is finished. */ struct colormap { int magic; #define CMMAGIC 0x876 struct vars *v; /* for compile error reporting */ size_t ncds; /* number of colordescs */ size_t max; /* highest in use */ color free; /* beginning of free chain (if non-0) */ struct colordesc *cd; #define CDEND(cm) (&(cm)->cd[(cm)->max + 1]) #define NINLINECDS ((size_t)10) struct colordesc cdspace[NINLINECDS]; union tree tree[NBYTS]; /* tree top, plus fill blocks */ }; /* optimization magic to do fast chr->color mapping */ #define B0(c) ((c) & BYTMASK) #define B1(c) (((c)>>BYTBITS) & BYTMASK) #define B2(c) (((c)>>(2*BYTBITS)) & BYTMASK) #define B3(c) (((c)>>(3*BYTBITS)) & BYTMASK) #if NBYTS == 1 #define GETCOLOR(cm, c) ((cm)->tree->tcolor[B0(c)]) #endif /* beware, for NBYTS>1, GETCOLOR() is unsafe -- 2nd arg used repeatedly */ #if NBYTS == 2 #define GETCOLOR(cm, c) ((cm)->tree->tptr[B1(c)]->tcolor[B0(c)]) #endif #if NBYTS == 4 #define GETCOLOR(cm, c) ((cm)->tree->tptr[B3(c)]->tptr[B2(c)]->tptr[B1(c)]->tcolor[B0(c)]) #endif /* * Interface definitions for locale-interface functions in locale.c. */ /* Representation of a set of characters. */ struct cvec { size_t nchrs; /* number of chrs */ size_t chrspace; /* number of chrs possible */ chr *chrs; /* pointer to vector of chrs */ size_t nranges; /* number of ranges (chr pairs) */ size_t rangespace; /* number of chrs possible */ chr *ranges; /* pointer to vector of chr pairs */ }; /* * definitions for non-deterministic finite autmaton (NFA) internal * representation * * Having a "from" pointer within each arc may seem redundant, but it saves a * lot of hassle. */ struct state; struct arc { int type; /* 0 if free, else an NFA arc type code */ color co; struct state *from; /* where it's from (and contained within) */ struct state *to; /* where it's to */ struct arc *outchain; /* link in *from's outs chain or free chain */ struct arc *outchainRev; /* back-link in *from's outs chain */ #define freechain outchain /* we do not maintain "freechainRev" */ struct arc *inchain; /* *to's ins chain */ struct arc *inchainRev; /* back-link in *to's ins chain */ struct arc *colorchain; /* color's arc chain */ struct arc *colorchainRev; /* back-link in color's arc chain */ }; struct arcbatch { /* for bulk allocation of arcs */ struct arcbatch *next; #define ABSIZE 10 struct arc a[ABSIZE]; }; struct state { size_t no; #define FREESTATE ((size_t)-1) char flag; /* marks special states */ size_t nins; /* number of inarcs */ struct arc *ins; /* chain of inarcs */ size_t nouts; /* number of outarcs */ struct arc *outs; /* chain of outarcs */ struct arc *free; /* chain of free arcs */ struct state *tmp; /* temporary for traversal algorithms */ struct state *next; /* chain for traversing all */ struct state *prev; /* back chain */ struct arcbatch oas; /* first arcbatch, avoid malloc in easy case */ size_t noas; /* number of arcs used in first arcbatch */ }; struct nfa { struct state *pre; /* preinitial state */ struct state *init; /* initial state */ struct state *final; /* final state */ struct state *post; /* postfinal state */ size_t nstates; /* for numbering states */ struct state *states; /* state-chain header */ struct state *slast; /* tail of the chain */ struct state *free; /* free list */ struct colormap *cm; /* the color map */ color bos[2]; /* colors, if any, assigned to BOS and BOL */ color eos[2]; /* colors, if any, assigned to EOS and EOL */ struct vars *v; /* simplifies compile error reporting */ struct nfa *parent; /* parent NFA, if any */ }; /* * definitions for compacted NFA * * The main space savings in a compacted NFA is from making the arcs as small * as possible. We store only the transition color and next-state number for * each arc. The list of out arcs for each state is an array beginning at * cnfa.states[statenumber], and terminated by a dummy carc struct with * co == COLORLESS. * * The non-dummy carc structs are of two types: plain arcs and LACON arcs. * Plain arcs just store the transition color number as "co". LACON arcs * store the lookahead constraint number plus cnfa.ncolors as "co". LACON * arcs can be distinguished from plain by testing for co >= cnfa.ncolors. */ struct carc { color co; /* COLORLESS is list terminator */ size_t to; /* next-state number */ }; struct cnfa { size_t nstates; /* number of states */ int ncolors; /* number of colors */ int flags; #define HASLACONS 01 /* uses lookahead constraints */ size_t pre; /* setup state number */ size_t post; /* teardown state number */ color bos[2]; /* colors, if any, assigned to BOS and BOL */ color eos[2]; /* colors, if any, assigned to EOS and EOL */ char *stflags; /* vector of per-state flags bytes */ #define CNFA_NOPROGRESS 01 /* flag bit for a no-progress state */ struct carc **states; /* vector of pointers to outarc lists */ /* states[n] are pointers into a single malloc'd array of arcs */ struct carc *arcs; /* the area for the lists */ }; #define ZAPCNFA(cnfa) ((cnfa).nstates = 0) #define NULLCNFA(cnfa) ((cnfa).nstates == 0) /* * This symbol limits the transient heap space used by the regex compiler, * and thereby also the maximum complexity of NFAs that we'll deal with. * Currently we only count NFA states and arcs against this; the other * transient data is generally not large enough to notice compared to those. * Note that we do not charge anything for the final output data structures * (the compacted NFA and the colormap). */ #ifndef REG_MAX_COMPILE_SPACE #define REG_MAX_COMPILE_SPACE \ (100000 * sizeof(struct state) + 100000 * sizeof(struct arcbatch)) #endif /* * subexpression tree * * "op" is one of: * '=' plain regex without interesting substructure (implemented as DFA) * 'b' back-reference (has no substructure either) * '(' capture node: captures the match of its single child * '.' concatenation: matches a match for left, then a match for right * '|' alternation: matches a match for left or a match for right * '*' iteration: matches some number of matches of its single child * * Note: the right child of an alternation must be another alternation or * NULL; hence, an N-way branch requires N alternation nodes, not N-1 as you * might expect. This could stand to be changed. Actually I'd rather see * a single alternation node with N children, but that will take revising * the representation of struct subre. * * Note: when a backref is directly quantified, we stick the min/max counts * into the backref rather than plastering an iteration node on top. This is * for efficiency: there is no need to search for possible division points. */ struct subre { char op; /* see type codes above */ char flags; #define LONGER 01 /* prefers longer match */ #define SHORTER 02 /* prefers shorter match */ #define MIXED 04 /* mixed preference below */ #define CAP 010 /* capturing parens below */ #define BACKR 020 /* back reference below */ #define INUSE 0100 /* in use in final tree */ #define NOPROP 03 /* bits which may not propagate up */ #define LMIX(f) ((f)<<2) /* LONGER -> MIXED */ #define SMIX(f) ((f)<<1) /* SHORTER -> MIXED */ #define UP(f) (((f)&~NOPROP) | (LMIX(f) & SMIX(f) & MIXED)) #define MESSY(f) ((f)&(MIXED|CAP|BACKR)) #define PREF(f) ((f)&NOPROP) #define PREF2(f1, f2) ((PREF(f1) != 0) ? PREF(f1) : PREF(f2)) #define COMBINE(f1, f2) (UP((f1)|(f2)) | PREF2(f1, f2)) short id; /* ID of subre (1..ntree-1) */ int subno; /* subexpression number (for 'b' and '(') */ short min; /* min repetitions for iteration or backref */ short max; /* max repetitions for iteration or backref */ struct subre *left; /* left child, if any (also freelist chain) */ struct subre *right; /* right child, if any */ struct state *begin; /* outarcs from here... */ struct state *end; /* ...ending in inarcs here */ struct cnfa cnfa; /* compacted NFA, if any */ struct subre *chain; /* for bookkeeping and error cleanup */ }; /* * table of function pointers for generic manipulation functions. A regex_t's * re_fns points to one of these. */ struct fns { void (*free) (regex_t *); }; /* * the insides of a regex_t, hidden behind a void * */ struct guts { int magic; #define GUTSMAGIC 0xFED9 int cflags; /* copy of compile flags */ long info; /* copy of re_info */ size_t nsub; /* copy of re_nsub */ struct subre *tree; struct cnfa search; /* for fast preliminary search */ size_t ntree; /* number of subre's, plus one */ struct colormap cmap; int (*compare) (const chr *, const chr *, size_t); struct subre *lacons; /* lookahead-constraint vector */ size_t nlacons; /* size of lacons */ }; /* * Magic for allocating a variable workspace. This default version is * stack-hungry. */ #ifndef AllocVars #define AllocVars(vPtr) \ struct vars var; \ struct vars *vPtr = &var #endif #ifndef FreeVars #define FreeVars(vPtr) ((void) 0) #endif /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tcl.h0000644000175000017500000027036614726623136014143 0ustar sergeisergei/* * tcl.h -- * * This header file describes the externally-visible facilities of the * Tcl interpreter. * * Copyright (c) 1987-1994 The Regents of the University of California. * Copyright (c) 1993-1996 Lucent Technologies. * Copyright (c) 1994-1998 Sun Microsystems, Inc. * Copyright (c) 1998-2000 by Scriptics Corporation. * Copyright (c) 2002 by Kevin B. Kenny. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef _TCL #define _TCL /* * For C++ compilers, use extern "C" */ #ifdef __cplusplus extern "C" { #endif /* * The following defines are used to indicate the various release levels. */ #define TCL_ALPHA_RELEASE 0 #define TCL_BETA_RELEASE 1 #define TCL_FINAL_RELEASE 2 /* * When version numbers change here, must also go into the following files and * update the version numbers: * * library/init.tcl (1 LOC patch) * unix/configure.ac (2 LOC Major, 2 LOC minor, 1 LOC patch) * win/configure.ac (as above) * win/tcl.m4 (not patchlevel) * README.md (sections 0 and 2, with and without separator) * win/README (not patchlevel) (sections 0 and 2) * unix/tcl.spec (1 LOC patch) */ #if !defined(TCL_MAJOR_VERSION) # define TCL_MAJOR_VERSION 9 #endif #if TCL_MAJOR_VERSION == 9 # define TCL_MINOR_VERSION 0 # define TCL_RELEASE_LEVEL TCL_FINAL_RELEASE # define TCL_RELEASE_SERIAL 1 # define TCL_VERSION "9.0" # define TCL_PATCH_LEVEL "9.0.1" #endif /* TCL_MAJOR_VERSION */ #if defined(RC_INVOKED) /* * Utility macros: STRINGIFY takes an argument and wraps it in "" (double * quotation marks), JOIN joins two arguments. */ #ifndef STRINGIFY # define STRINGIFY(x) STRINGIFY1(x) # define STRINGIFY1(x) #x #endif #ifndef JOIN # define JOIN(a,b) JOIN1(a,b) # define JOIN1(a,b) a##b #endif #endif /* RC_INVOKED */ /* * A special definition used to allow this header file to be included from * windows resource files so that they can obtain version information. * RC_INVOKED is defined by default by the windows RC tool. * * Resource compilers don't like all the C stuff, like typedefs and function * declarations, that occur below, so block them out. */ #ifndef RC_INVOKED /* * Special macro to define mutexes. */ #define TCL_DECLARE_MUTEX(name) \ static Tcl_Mutex name; /* * Tcl's public routine Tcl_FSSeek() uses the values SEEK_SET, SEEK_CUR, and * SEEK_END, all #define'd by stdio.h . * * Also, many extensions need stdio.h, and they've grown accustomed to tcl.h * providing it for them rather than #include-ing it themselves as they * should, so also for their sake, we keep the #include to be consistent with * prior Tcl releases. */ #include #include #if defined(__GNUC__) && (__GNUC__ > 2) # if defined(_WIN32) && defined(__USE_MINGW_ANSI_STDIO) && __USE_MINGW_ANSI_STDIO # define TCL_FORMAT_PRINTF(a,b) __attribute__ ((__format__ (__MINGW_PRINTF_FORMAT, a, b))) # else # define TCL_FORMAT_PRINTF(a,b) __attribute__ ((__format__ (__printf__, a, b))) # endif # define TCL_NORETURN __attribute__ ((noreturn)) # define TCL_NOINLINE __attribute__ ((noinline)) # define TCL_NORETURN1 __attribute__ ((noreturn)) #else # define TCL_FORMAT_PRINTF(a,b) # if defined(_MSC_VER) # define TCL_NORETURN __declspec(noreturn) # define TCL_NOINLINE __declspec(noinline) # else # define TCL_NORETURN /* nothing */ # define TCL_NOINLINE /* nothing */ # endif # define TCL_NORETURN1 /* nothing */ #endif /* * Allow a part of Tcl's API to be explicitly marked as deprecated. * * Used to make TIP 330/336 generate moans even if people use the * compatibility macros. Change your code, guys! We won't support you forever. */ #if defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))) # if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 5)) # define TCL_DEPRECATED_API(msg) __attribute__ ((__deprecated__ (msg))) # else # define TCL_DEPRECATED_API(msg) __attribute__ ((__deprecated__)) # endif #else # define TCL_DEPRECATED_API(msg) /* nothing portable */ #endif /* *---------------------------------------------------------------------------- * Macros used to declare a function to be exported by a DLL. Used by Windows, * maps to no-op declarations on non-Windows systems. The default build on * windows is for a DLL, which causes the DLLIMPORT and DLLEXPORT macros to be * nonempty. To build a static library, the macro STATIC_BUILD should be * defined. * * Note: when building static but linking dynamically to MSVCRT we must still * correctly decorate the C library imported function. Use CRTIMPORT * for this purpose. _DLL is defined by the compiler when linking to * MSVCRT. */ #ifdef _WIN32 # ifdef STATIC_BUILD # define DLLIMPORT # define DLLEXPORT # ifdef _DLL # define CRTIMPORT __declspec(dllimport) # else # define CRTIMPORT # endif # else # define DLLIMPORT __declspec(dllimport) # define DLLEXPORT __declspec(dllexport) # define CRTIMPORT __declspec(dllimport) # endif #else # define DLLIMPORT # if defined(__GNUC__) && __GNUC__ > 3 # define DLLEXPORT __attribute__ ((visibility("default"))) # else # define DLLEXPORT # endif # define CRTIMPORT #endif /* * These macros are used to control whether functions are being declared for * import or export. If a function is being declared while it is being built * to be included in a shared library, then it should have the DLLEXPORT * storage class. If is being declared for use by a module that is going to * link against the shared library, then it should have the DLLIMPORT storage * class. If the symbol is being declared for a static build or for use from a * stub library, then the storage class should be empty. * * The convention is that a macro called BUILD_xxxx, where xxxx is the name of * a library we are building, is set on the compile line for sources that are * to be placed in the library. When this macro is set, the storage class will * be set to DLLEXPORT. At the end of the header file, the storage class will * be reset to DLLIMPORT. */ #undef TCL_STORAGE_CLASS #ifdef BUILD_tcl # define TCL_STORAGE_CLASS DLLEXPORT #else # ifdef USE_TCL_STUBS # define TCL_STORAGE_CLASS # else # define TCL_STORAGE_CLASS DLLIMPORT # endif #endif #if !defined(CONST86) && !defined(TCL_NO_DEPRECATED) # define CONST86 const #endif /* * Make sure EXTERN isn't defined elsewhere. */ #ifdef EXTERN # undef EXTERN #endif /* EXTERN */ #ifdef __cplusplus # define EXTERN extern "C" TCL_STORAGE_CLASS #else # define EXTERN extern TCL_STORAGE_CLASS #endif /* * Miscellaneous declarations. */ typedef void *ClientData; /* * Darwin specific configure overrides (to support fat compiles, where * configure runs only once for multiple architectures): */ #ifdef __APPLE__ # ifdef __LP64__ # define TCL_WIDE_INT_IS_LONG 1 # define TCL_CFG_DO64BIT 1 # else /* !__LP64__ */ # undef TCL_WIDE_INT_IS_LONG # undef TCL_CFG_DO64BIT # endif /* __LP64__ */ # undef HAVE_STRUCT_STAT64 #endif /* __APPLE__ */ /* Cross-compiling 32-bit on a 64-bit platform? Then our * configure script does the wrong thing. Correct that here. */ #if defined(__GNUC__) && !defined(_WIN32) && !defined(__LP64__) # undef TCL_WIDE_INT_IS_LONG #endif /* * Define Tcl_WideInt to be a type that is (at least) 64-bits wide, and define * Tcl_WideUInt to be the unsigned variant of that type (assuming that where * we have one, we can have the other.) * * Also defines the following macros: * TCL_WIDE_INT_IS_LONG - if wide ints are really longs (i.e. we're on a * LP64 system such as modern Solaris or Linux ... not including Win64) * Tcl_WideAsLong - forgetful converter from wideInt to long. * Tcl_LongAsWide - sign-extending converter from long to wideInt. * Tcl_WideAsDouble - converter from wideInt to double. * Tcl_DoubleAsWide - converter from double to wideInt. * * The following invariant should hold for any long value 'longVal': * longVal == Tcl_WideAsLong(Tcl_LongAsWide(longVal)) */ #if !defined(TCL_WIDE_INT_TYPE) && !defined(TCL_WIDE_INT_IS_LONG) && !defined(_WIN32) && !defined(__GNUC__) /* * Don't know what platform it is and configure hasn't discovered what is * going on for us. Try to guess... */ # include # if defined(LLONG_MAX) && (LLONG_MAX == LONG_MAX) # define TCL_WIDE_INT_IS_LONG 1 # endif #endif #ifndef TCL_WIDE_INT_TYPE # define TCL_WIDE_INT_TYPE long long #endif /* !TCL_WIDE_INT_TYPE */ typedef TCL_WIDE_INT_TYPE Tcl_WideInt; typedef unsigned TCL_WIDE_INT_TYPE Tcl_WideUInt; #ifndef TCL_LL_MODIFIER # if defined(_WIN32) && (!defined(__USE_MINGW_ANSI_STDIO) || !__USE_MINGW_ANSI_STDIO) # define TCL_LL_MODIFIER "I64" # else # define TCL_LL_MODIFIER "ll" # endif #endif /* !TCL_LL_MODIFIER */ #ifndef TCL_Z_MODIFIER # if defined(__GNUC__) && !defined(_WIN32) # define TCL_Z_MODIFIER "z" # elif defined(_WIN64) # define TCL_Z_MODIFIER TCL_LL_MODIFIER # else # define TCL_Z_MODIFIER "" # endif #endif /* !TCL_Z_MODIFIER */ #ifndef TCL_T_MODIFIER # if defined(__GNUC__) && !defined(_WIN32) # define TCL_T_MODIFIER "t" # elif defined(_WIN64) # define TCL_T_MODIFIER TCL_LL_MODIFIER # else # define TCL_T_MODIFIER TCL_Z_MODIFIER # endif #endif /* !TCL_T_MODIFIER */ #define Tcl_WideAsLong(val) ((long)((Tcl_WideInt)(val))) #define Tcl_LongAsWide(val) ((Tcl_WideInt)((long)(val))) #define Tcl_WideAsDouble(val) ((double)((Tcl_WideInt)(val))) #define Tcl_DoubleAsWide(val) ((Tcl_WideInt)((double)(val))) #if TCL_MAJOR_VERSION < 9 typedef int Tcl_Size; # define TCL_SIZE_MAX ((int)(((unsigned int)-1)>>1)) # define TCL_SIZE_MODIFIER "" #else typedef ptrdiff_t Tcl_Size; # define TCL_SIZE_MAX ((Tcl_Size)(((size_t)-1)>>1)) # define TCL_SIZE_MODIFIER TCL_T_MODIFIER #endif /* TCL_MAJOR_VERSION */ #ifdef _WIN32 # if TCL_MAJOR_VERSION > 8 || defined(_WIN64) || defined(_USE_64BIT_TIME_T) typedef struct __stat64 Tcl_StatBuf; # elif defined(_USE_32BIT_TIME_T) typedef struct _stati64 Tcl_StatBuf; # else typedef struct _stat32i64 Tcl_StatBuf; # endif #elif defined(__CYGWIN__) typedef struct { unsigned st_dev; unsigned short st_ino; unsigned short st_mode; short st_nlink; short st_uid; short st_gid; /* Here is a 2-byte gap */ unsigned st_rdev; /* Here is a 4-byte gap */ long long st_size; struct {long tv_sec;} st_atim; struct {long tv_sec;} st_mtim; struct {long tv_sec;} st_ctim; } Tcl_StatBuf; #else typedef struct stat Tcl_StatBuf; #endif /* *---------------------------------------------------------------------------- * Data structures defined opaquely in this module. The definitions below just * provide dummy types. */ typedef struct Tcl_AsyncHandler_ *Tcl_AsyncHandler; typedef struct Tcl_Channel_ *Tcl_Channel; typedef struct Tcl_ChannelTypeVersion_ *Tcl_ChannelTypeVersion; typedef struct Tcl_Command_ *Tcl_Command; typedef struct Tcl_Condition_ *Tcl_Condition; typedef struct Tcl_Dict_ *Tcl_Dict; typedef struct Tcl_EncodingState_ *Tcl_EncodingState; typedef struct Tcl_Encoding_ *Tcl_Encoding; typedef struct Tcl_Event Tcl_Event; typedef struct Tcl_Interp Tcl_Interp; typedef struct Tcl_InterpState_ *Tcl_InterpState; typedef struct Tcl_LoadHandle_ *Tcl_LoadHandle; typedef struct Tcl_Mutex_ *Tcl_Mutex; typedef struct Tcl_Pid_ *Tcl_Pid; typedef struct Tcl_RegExp_ *Tcl_RegExp; typedef struct Tcl_ThreadDataKey_ *Tcl_ThreadDataKey; typedef struct Tcl_ThreadId_ *Tcl_ThreadId; typedef struct Tcl_TimerToken_ *Tcl_TimerToken; typedef struct Tcl_Trace_ *Tcl_Trace; typedef struct Tcl_Var_ *Tcl_Var; typedef struct Tcl_ZLibStream_ *Tcl_ZlibStream; /* *---------------------------------------------------------------------------- * Definition of the interface to functions implementing threads. A function * following this definition is given to each call of 'Tcl_CreateThread' and * will be called as the main fuction of the new thread created by that call. */ #if defined _WIN32 typedef unsigned (__stdcall Tcl_ThreadCreateProc) (void *clientData); #else typedef void (Tcl_ThreadCreateProc) (void *clientData); #endif /* * Threading function return types used for abstracting away platform * differences when writing a Tcl_ThreadCreateProc. See the NewThread function * in generic/tclThreadTest.c for it's usage. */ #if defined _WIN32 # define Tcl_ThreadCreateType unsigned __stdcall # define TCL_THREAD_CREATE_RETURN return 0 #else # define Tcl_ThreadCreateType void # define TCL_THREAD_CREATE_RETURN #endif /* * Definition of values for default stacksize and the possible flags to be * given to Tcl_CreateThread. */ #define TCL_THREAD_STACK_DEFAULT (0) /* Use default size for stack. */ #define TCL_THREAD_NOFLAGS (0000) /* Standard flags, default * behaviour. */ #define TCL_THREAD_JOINABLE (0001) /* Mark the thread as joinable. */ /* * Flag values passed to Tcl_StringCaseMatch. */ #define TCL_MATCH_NOCASE (1<<0) /* * Flag values passed to Tcl_GetRegExpFromObj. */ #define TCL_REG_BASIC 000000 /* BREs (convenience). */ #define TCL_REG_EXTENDED 000001 /* EREs. */ #define TCL_REG_ADVF 000002 /* Advanced features in EREs. */ #define TCL_REG_ADVANCED 000003 /* AREs (which are also EREs). */ #define TCL_REG_QUOTE 000004 /* No special characters, none. */ #define TCL_REG_NOCASE 000010 /* Ignore case. */ #define TCL_REG_NOSUB 000020 /* Don't care about subexpressions. */ #define TCL_REG_EXPANDED 000040 /* Expanded format, white space & * comments. */ #define TCL_REG_NLSTOP 000100 /* \n doesn't match . or [^ ] */ #define TCL_REG_NLANCH 000200 /* ^ matches after \n, $ before. */ #define TCL_REG_NEWLINE 000300 /* Newlines are line terminators. */ #define TCL_REG_CANMATCH 001000 /* Report details on partial/limited * matches. */ /* * Flags values passed to Tcl_RegExpExecObj. */ #define TCL_REG_NOTBOL 0001 /* Beginning of string does not match ^. */ #define TCL_REG_NOTEOL 0002 /* End of string does not match $. */ /* * Structures filled in by Tcl_RegExpInfo. Note that all offset values are * relative to the start of the match string, not the beginning of the entire * string. */ typedef struct Tcl_RegExpIndices { #if TCL_MAJOR_VERSION > 8 Tcl_Size start; /* Character offset of first character in * match. */ Tcl_Size end; /* Character offset of first character after * the match. */ #else long start; long end; #endif } Tcl_RegExpIndices; typedef struct Tcl_RegExpInfo { Tcl_Size nsubs; /* Number of subexpressions in the compiled * expression. */ Tcl_RegExpIndices *matches; /* Array of nsubs match offset pairs. */ #if TCL_MAJOR_VERSION > 8 Tcl_Size extendStart; /* The offset at which a subsequent match * might begin. */ #else long extendStart; long reserved; /* Reserved for later use. */ #endif } Tcl_RegExpInfo; /* * Picky compilers complain if this typdef doesn't appear before the struct's * reference in tclDecls.h. */ typedef Tcl_StatBuf *Tcl_Stat_; typedef struct stat *Tcl_OldStat_; /* *---------------------------------------------------------------------------- * When a TCL command returns, the interpreter contains a result from the * command. Programmers are strongly encouraged to use one of the functions * Tcl_GetObjResult() or Tcl_GetStringResult() to read the interpreter's * result. See the SetResult man page for details. Besides this result, the * command function returns an integer code, which is one of the following: * * TCL_OK Command completed normally; the interpreter's result * contains the command's result. * TCL_ERROR The command couldn't be completed successfully; the * interpreter's result describes what went wrong. * TCL_RETURN The command requests that the current function return; * the interpreter's result contains the function's * return value. * TCL_BREAK The command requests that the innermost loop be * exited; the interpreter's result is meaningless. * TCL_CONTINUE Go on to the next iteration of the current loop; the * interpreter's result is meaningless. * Integer return codes in the range TCL_CODE_USER_MIN to TCL_CODE_USER_MAX are * reserved for the use of packages. */ #define TCL_OK 0 #define TCL_ERROR 1 #define TCL_RETURN 2 #define TCL_BREAK 3 #define TCL_CONTINUE 4 #define TCL_CODE_USER_MIN 5 #define TCL_CODE_USER_MAX 0x3fffffff /* 1073741823 */ /* *---------------------------------------------------------------------------- * Flags to control what substitutions are performed by Tcl_SubstObj(): */ #define TCL_SUBST_COMMANDS 001 #define TCL_SUBST_VARIABLES 002 #define TCL_SUBST_BACKSLASHES 004 #define TCL_SUBST_ALL 007 /* * Forward declaration of Tcl_Obj to prevent an error when the forward * reference to Tcl_Obj is encountered in the function types declared below. */ struct Tcl_Obj; /* *---------------------------------------------------------------------------- * Function types defined by Tcl: */ typedef int (Tcl_AppInitProc) (Tcl_Interp *interp); typedef int (Tcl_AsyncProc) (void *clientData, Tcl_Interp *interp, int code); typedef void (Tcl_ChannelProc) (void *clientData, int mask); typedef void (Tcl_CloseProc) (void *data); typedef void (Tcl_CmdDeleteProc) (void *clientData); typedef int (Tcl_CmdProc) (void *clientData, Tcl_Interp *interp, int argc, const char *argv[]); typedef void (Tcl_CmdTraceProc) (void *clientData, Tcl_Interp *interp, int level, char *command, Tcl_CmdProc *proc, void *cmdClientData, int argc, const char *argv[]); typedef int (Tcl_CmdObjTraceProc) (void *clientData, Tcl_Interp *interp, int level, const char *command, Tcl_Command commandInfo, int objc, struct Tcl_Obj *const *objv); typedef void (Tcl_CmdObjTraceDeleteProc) (void *clientData); typedef void (Tcl_DupInternalRepProc) (struct Tcl_Obj *srcPtr, struct Tcl_Obj *dupPtr); typedef int (Tcl_EncodingConvertProc) (void *clientData, const char *src, int srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, int dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr); typedef void (Tcl_EncodingFreeProc) (void *clientData); typedef int (Tcl_EventProc) (Tcl_Event *evPtr, int flags); typedef void (Tcl_EventCheckProc) (void *clientData, int flags); typedef int (Tcl_EventDeleteProc) (Tcl_Event *evPtr, void *clientData); typedef void (Tcl_EventSetupProc) (void *clientData, int flags); typedef void (Tcl_ExitProc) (void *clientData); typedef void (Tcl_FileProc) (void *clientData, int mask); typedef void (Tcl_FileFreeProc) (void *clientData); typedef void (Tcl_FreeInternalRepProc) (struct Tcl_Obj *objPtr); typedef void (Tcl_IdleProc) (void *clientData); typedef void (Tcl_InterpDeleteProc) (void *clientData, Tcl_Interp *interp); typedef void (Tcl_NamespaceDeleteProc) (void *clientData); typedef int (Tcl_ObjCmdProc) (void *clientData, Tcl_Interp *interp, int objc, struct Tcl_Obj *const *objv); #if TCL_MAJOR_VERSION > 8 typedef int (Tcl_ObjCmdProc2) (void *clientData, Tcl_Interp *interp, Tcl_Size objc, struct Tcl_Obj *const *objv); typedef int (Tcl_CmdObjTraceProc2) (void *clientData, Tcl_Interp *interp, Tcl_Size level, const char *command, Tcl_Command commandInfo, Tcl_Size objc, struct Tcl_Obj *const *objv); typedef void (Tcl_FreeProc) (void *blockPtr); #define Tcl_ExitProc Tcl_FreeProc #define Tcl_FileFreeProc Tcl_FreeProc #define Tcl_FileFreeProc Tcl_FreeProc #define Tcl_EncodingFreeProc Tcl_FreeProc #else #define Tcl_ObjCmdProc2 Tcl_ObjCmdProc #define Tcl_CmdObjTraceProc2 Tcl_CmdObjTraceProc typedef void (Tcl_FreeProc) (char *blockPtr); #endif typedef int (Tcl_LibraryInitProc) (Tcl_Interp *interp); typedef int (Tcl_LibraryUnloadProc) (Tcl_Interp *interp, int flags); typedef void (Tcl_PanicProc) (const char *format, ...); typedef void (Tcl_TcpAcceptProc) (void *callbackData, Tcl_Channel chan, char *address, int port); typedef void (Tcl_TimerProc) (void *clientData); typedef int (Tcl_SetFromAnyProc) (Tcl_Interp *interp, struct Tcl_Obj *objPtr); typedef void (Tcl_UpdateStringProc) (struct Tcl_Obj *objPtr); typedef char * (Tcl_VarTraceProc) (void *clientData, Tcl_Interp *interp, const char *part1, const char *part2, int flags); typedef void (Tcl_CommandTraceProc) (void *clientData, Tcl_Interp *interp, const char *oldName, const char *newName, int flags); typedef void (Tcl_CreateFileHandlerProc) (int fd, int mask, Tcl_FileProc *proc, void *clientData); typedef void (Tcl_DeleteFileHandlerProc) (int fd); typedef void (Tcl_AlertNotifierProc) (void *clientData); typedef void (Tcl_ServiceModeHookProc) (int mode); typedef void *(Tcl_InitNotifierProc) (void); typedef void (Tcl_FinalizeNotifierProc) (void *clientData); typedef void (Tcl_MainLoopProc) (void); /* Abstract List functions */ typedef Tcl_Size (Tcl_ObjTypeLengthProc) (struct Tcl_Obj *listPtr); typedef int (Tcl_ObjTypeIndexProc) (Tcl_Interp *interp, struct Tcl_Obj *listPtr, Tcl_Size index, struct Tcl_Obj** elemObj); typedef int (Tcl_ObjTypeSliceProc) (Tcl_Interp *interp, struct Tcl_Obj *listPtr, Tcl_Size fromIdx, Tcl_Size toIdx, struct Tcl_Obj **newObjPtr); typedef int (Tcl_ObjTypeReverseProc) (Tcl_Interp *interp, struct Tcl_Obj *listPtr, struct Tcl_Obj **newObjPtr); typedef int (Tcl_ObjTypeGetElements) (Tcl_Interp *interp, struct Tcl_Obj *listPtr, Tcl_Size *objcptr, struct Tcl_Obj ***objvptr); typedef struct Tcl_Obj *(Tcl_ObjTypeSetElement) (Tcl_Interp *interp, struct Tcl_Obj *listPtr, Tcl_Size indexCount, struct Tcl_Obj *const indexArray[], struct Tcl_Obj *valueObj); typedef int (Tcl_ObjTypeReplaceProc) (Tcl_Interp *interp, struct Tcl_Obj *listObj, Tcl_Size first, Tcl_Size numToDelete, Tcl_Size numToInsert, struct Tcl_Obj *const insertObjs[]); typedef int (Tcl_ObjTypeInOperatorProc) (Tcl_Interp *interp, struct Tcl_Obj *valueObj, struct Tcl_Obj *listObj, int *boolResult); #ifndef TCL_NO_DEPRECATED # define Tcl_PackageInitProc Tcl_LibraryInitProc # define Tcl_PackageUnloadProc Tcl_LibraryUnloadProc #endif /* *---------------------------------------------------------------------------- * The following structure represents a type of object, which is a particular * internal representation for an object plus a set of functions that provide * standard operations on objects of that type. */ typedef struct Tcl_ObjType { const char *name; /* Name of the type, e.g. "int". */ Tcl_FreeInternalRepProc *freeIntRepProc; /* Called to free any storage for the type's * internal rep. NULL if the internal rep does * not need freeing. */ Tcl_DupInternalRepProc *dupIntRepProc; /* Called to create a new object as a copy of * an existing object. */ Tcl_UpdateStringProc *updateStringProc; /* Called to update the string rep from the * type's internal representation. */ Tcl_SetFromAnyProc *setFromAnyProc; /* Called to convert the object's internal rep * to this type. Frees the internal rep of the * old type. Returns TCL_ERROR on failure. */ #if TCL_MAJOR_VERSION > 8 size_t version; /* Version field for future-proofing. */ /* List emulation functions - ObjType Version 1 */ Tcl_ObjTypeLengthProc *lengthProc; /* Return the [llength] of the AbstractList */ Tcl_ObjTypeIndexProc *indexProc; /* Return a value (Tcl_Obj) at a given index */ Tcl_ObjTypeSliceProc *sliceProc; /* Return an AbstractList for * [lrange $al $start $end] */ Tcl_ObjTypeReverseProc *reverseProc; /* Return an AbstractList for [lreverse $al] */ Tcl_ObjTypeGetElements *getElementsProc; /* Return an objv[] of all elements in the list */ Tcl_ObjTypeSetElement *setElementProc; /* Replace the element at the indicies with the * given valueObj. */ Tcl_ObjTypeReplaceProc *replaceProc; /* Replace sublist with another sublist */ Tcl_ObjTypeInOperatorProc *inOperProc; /* "in" and "ni" expr list operation. * Determine if the given string value matches * an element in the list. */ #endif } Tcl_ObjType; #if TCL_MAJOR_VERSION > 8 # define TCL_OBJTYPE_V0 0, \ 0,0,0,0,0,0,0,0 /* Pre-Tcl 9 */ # define TCL_OBJTYPE_V1(a) offsetof(Tcl_ObjType, indexProc), \ a,0,0,0,0,0,0,0 /* Tcl 9 Version 1 */ # define TCL_OBJTYPE_V2(a,b,c,d,e,f,g,h) sizeof(Tcl_ObjType), \ a,b,c,d,e,f,g,h /* Tcl 9 - AbstractLists */ #else # define TCL_OBJTYPE_V0 /* just empty */ # define TCL_OBJTYPE_V1(a) /* just empty */ # define TCL_OBJTYPE_V2(a,b,c,d,e,f,g,h) /* just empty */ #endif /* * The following structure stores an internal representation (internalrep) for * a Tcl value. An internalrep is associated with an Tcl_ObjType when both * are stored in the same Tcl_Obj. The routines of the Tcl_ObjType govern * the handling of the internalrep. */ typedef union Tcl_ObjInternalRep { /* The internal representation: */ long longValue; /* - an long integer value. */ double doubleValue; /* - a double-precision floating value. */ void *otherValuePtr; /* - another, type-specific value, */ /* not used internally any more. */ Tcl_WideInt wideValue; /* - an integer value >= 64bits */ struct { /* - internal rep as two pointers. */ void *ptr1; void *ptr2; } twoPtrValue; struct { /* - internal rep as a pointer and a long, */ void *ptr; /* not used internally any more. */ unsigned long value; } ptrAndLongRep; struct { /* - use for pointer and length reps */ void *ptr; Tcl_Size size; } ptrAndSize; } Tcl_ObjInternalRep; /* * One of the following structures exists for each object in the Tcl system. * An object stores a value as either a string, some internal representation, * or both. */ typedef struct Tcl_Obj { Tcl_Size refCount; /* When 0 the object will be freed. */ char *bytes; /* This points to the first byte of the * object's string representation. The array * must be followed by a null byte (i.e., at * offset length) but may also contain * embedded null characters. The array's * storage is allocated by Tcl_Alloc. NULL means * the string rep is invalid and must be * regenerated from the internal rep. Clients * should use Tcl_GetStringFromObj or * Tcl_GetString to get a pointer to the byte * array as a readonly value. */ Tcl_Size length; /* The number of bytes at *bytes, not * including the terminating null. */ const Tcl_ObjType *typePtr; /* Denotes the object's type. Always * corresponds to the type of the object's * internal rep. NULL indicates the object has * no internal rep (has no type). */ Tcl_ObjInternalRep internalRep; /* The internal representation: */ } Tcl_Obj; /* *---------------------------------------------------------------------------- * The following definitions support Tcl's namespace facility. Note: the first * five fields must match exactly the fields in a Namespace structure (see * tclInt.h). */ typedef struct Tcl_Namespace { char *name; /* The namespace's name within its parent * namespace. This contains no ::'s. The name * of the global namespace is "" although "::" * is an synonym. */ char *fullName; /* The namespace's fully qualified name. This * starts with ::. */ void *clientData; /* Arbitrary value associated with this * namespace. */ Tcl_NamespaceDeleteProc *deleteProc; /* Function invoked when deleting the * namespace to, e.g., free clientData. */ struct Tcl_Namespace *parentPtr; /* Points to the namespace that contains this * one. NULL if this is the global * namespace. */ } Tcl_Namespace; /* *---------------------------------------------------------------------------- * The following structure represents a call frame, or activation record. A * call frame defines a naming context for a procedure call: its local scope * (for local variables) and its namespace scope (used for non-local * variables; often the global :: namespace). A call frame can also define the * naming context for a namespace eval or namespace inscope command: the * namespace in which the command's code should execute. The Tcl_CallFrame * structures exist only while procedures or namespace eval/inscope's are * being executed, and provide a Tcl call stack. * * A call frame is initialized and pushed using Tcl_PushCallFrame and popped * using Tcl_PopCallFrame. Storage for a Tcl_CallFrame must be provided by the * Tcl_PushCallFrame caller, and callers typically allocate them on the C call * stack for efficiency. For this reason, Tcl_CallFrame is defined as a * structure and not as an opaque token. However, most Tcl_CallFrame fields * are hidden since applications should not access them directly; others are * declared as "dummyX". * * WARNING!! The structure definition must be kept consistent with the * CallFrame structure in tclInt.h. If you change one, change the other. */ typedef struct Tcl_CallFrame { Tcl_Namespace *nsPtr; /* Current namespace for the call frame. */ int dummy1; Tcl_Size dummy2; void *dummy3; void *dummy4; void *dummy5; Tcl_Size dummy6; void *dummy7; void *dummy8; Tcl_Size dummy9; void *dummy10; void *dummy11; void *dummy12; void *dummy13; } Tcl_CallFrame; /* *---------------------------------------------------------------------------- * Information about commands that is returned by Tcl_GetCommandInfo and * passed to Tcl_SetCommandInfo. objProc is an objc/objv object-based command * function while proc is a traditional Tcl argc/argv string-based function. * Tcl_CreateObjCommand and Tcl_CreateCommand ensure that both objProc and * proc are non-NULL and can be called to execute the command. However, it may * be faster to call one instead of the other. The member isNativeObjectProc * is set to 1 if an object-based function was registered by * Tcl_CreateObjCommand, and to 0 if a string-based function was registered by * Tcl_CreateCommand. The other function is typically set to a compatibility * wrapper that does string-to-object or object-to-string argument conversions * then calls the other function. */ typedef struct { int isNativeObjectProc; /* 1 if objProc was registered by a call to * Tcl_CreateObjCommand; 2 if objProc was registered by * a call to Tcl_CreateObjCommand2; 0 otherwise. * Tcl_SetCmdInfo does not modify this field. */ Tcl_ObjCmdProc *objProc; /* Command's object-based function. */ void *objClientData; /* ClientData for object proc. */ Tcl_CmdProc *proc; /* Command's string-based function. */ void *clientData; /* ClientData for string proc. */ Tcl_CmdDeleteProc *deleteProc; /* Function to call when command is * deleted. */ void *deleteData; /* Value to pass to deleteProc (usually the * same as clientData). */ Tcl_Namespace *namespacePtr;/* Points to the namespace that contains this * command. Note that Tcl_SetCmdInfo will not * change a command's namespace; use * TclRenameCommand or Tcl_Eval (of 'rename') * to do that. */ Tcl_ObjCmdProc2 *objProc2; /* Command's object2-based function. */ void *objClientData2; /* ClientData for object2 proc. */ } Tcl_CmdInfo; /* *---------------------------------------------------------------------------- * The structure defined below is used to hold dynamic strings. The only * fields that clients should use are string and length, accessible via the * macros Tcl_DStringValue and Tcl_DStringLength. */ #define TCL_DSTRING_STATIC_SIZE 200 typedef struct Tcl_DString { char *string; /* Points to beginning of string: either * staticSpace below or a malloced array. */ Tcl_Size length; /* Number of bytes in string excluding * terminating nul */ Tcl_Size spaceAvl; /* Total number of bytes available for the * string and its terminating NULL char. */ char staticSpace[TCL_DSTRING_STATIC_SIZE]; /* Space to use in common case where string is * small. */ } Tcl_DString; #define Tcl_DStringLength(dsPtr) ((dsPtr)->length) #define Tcl_DStringValue(dsPtr) ((dsPtr)->string) /* * Definitions for the maximum number of digits of precision that may be * produced by Tcl_PrintDouble, and the number of bytes of buffer space * required by Tcl_PrintDouble. */ #define TCL_MAX_PREC 17 #define TCL_DOUBLE_SPACE (TCL_MAX_PREC+10) /* * Definition for a number of bytes of buffer space sufficient to hold the * string representation of an integer in base 10 (assuming the existence of * 64-bit integers). */ #define TCL_INTEGER_SPACE (3*(int)sizeof(Tcl_WideInt)) /* *---------------------------------------------------------------------------- * Type values returned by Tcl_GetNumberFromObj * TCL_NUMBER_INT Representation is a Tcl_WideInt * TCL_NUMBER_BIG Representation is an mp_int * TCL_NUMBER_DOUBLE Representation is a double * TCL_NUMBER_NAN Value is NaN. */ #define TCL_NUMBER_INT 2 #define TCL_NUMBER_BIG 3 #define TCL_NUMBER_DOUBLE 4 #define TCL_NUMBER_NAN 5 /* * Flag values passed to Tcl_ConvertElement. * TCL_DONT_USE_BRACES forces it not to enclose the element in braces, but to * use backslash quoting instead. * TCL_DONT_QUOTE_HASH disables the default quoting of the '#' character. It * is safe to leave the hash unquoted when the element is not the first * element of a list, and this flag can be used by the caller to indicate * that condition. */ #define TCL_DONT_USE_BRACES 1 #define TCL_DONT_QUOTE_HASH 8 /* * Flags that may be passed to Tcl_GetIndexFromObj. * TCL_EXACT disallows abbreviated strings. * TCL_NULL_OK allows the empty string or NULL to return TCL_OK. * The returned value will be -1; * TCL_INDEX_TEMP_TABLE disallows caching of lookups. A possible use case is * a table that will not live long enough to make it worthwhile. */ #define TCL_EXACT 1 #define TCL_NULL_OK 32 #define TCL_INDEX_TEMP_TABLE 64 /* * Flags that may be passed to Tcl_UniCharToUtf. * TCL_COMBINE Combine surrogates */ #if TCL_MAJOR_VERSION > 8 # define TCL_COMBINE 0x1000000 #else # define TCL_COMBINE 0 #endif /* *---------------------------------------------------------------------------- * Flag values passed to Tcl_RecordAndEval, Tcl_EvalObj, Tcl_EvalObjv. * WARNING: these bit choices must not conflict with the bit choices for * evalFlag bits in tclInt.h! * * Meanings: * TCL_NO_EVAL: Just record this command * TCL_EVAL_GLOBAL: Execute script in global namespace * TCL_EVAL_DIRECT: Do not compile this script * TCL_EVAL_INVOKE: Magical Tcl_EvalObjv mode for aliases/ensembles * o Run in iPtr->lookupNsPtr or global namespace * o Cut out of error traces * o Don't reset the flags controlling ensemble * error message rewriting. * TCL_CANCEL_UNWIND: Magical Tcl_CancelEval mode that causes the * stack for the script in progress to be * completely unwound. * TCL_EVAL_NOERR: Do no exception reporting at all, just return * as the caller will report. */ #define TCL_NO_EVAL 0x010000 #define TCL_EVAL_GLOBAL 0x020000 #define TCL_EVAL_DIRECT 0x040000 #define TCL_EVAL_INVOKE 0x080000 #define TCL_CANCEL_UNWIND 0x100000 #define TCL_EVAL_NOERR 0x200000 /* * Special freeProc values that may be passed to Tcl_SetResult (see the man * page for details): */ #define TCL_VOLATILE ((Tcl_FreeProc *) 1) #define TCL_STATIC ((Tcl_FreeProc *) 0) #define TCL_DYNAMIC ((Tcl_FreeProc *) 3) /* * Flag values passed to variable-related functions. * WARNING: these bit choices must not conflict with the bit choice for * TCL_CANCEL_UNWIND, above. */ #define TCL_GLOBAL_ONLY 1 #define TCL_NAMESPACE_ONLY 2 #define TCL_APPEND_VALUE 4 #define TCL_LIST_ELEMENT 8 #define TCL_TRACE_READS 0x10 #define TCL_TRACE_WRITES 0x20 #define TCL_TRACE_UNSETS 0x40 #define TCL_TRACE_DESTROYED 0x80 #define TCL_LEAVE_ERR_MSG 0x200 #define TCL_TRACE_ARRAY 0x800 /* Indicate the semantics of the result of a trace. */ #define TCL_TRACE_RESULT_DYNAMIC 0x8000 #define TCL_TRACE_RESULT_OBJECT 0x10000 /* * Flag values for ensemble commands. */ #define TCL_ENSEMBLE_PREFIX 0x02/* Flag value to say whether to allow * unambiguous prefixes of commands or to * require exact matches for command names. */ /* * Flag values passed to command-related functions. */ #define TCL_TRACE_RENAME 0x2000 #define TCL_TRACE_DELETE 0x4000 #define TCL_ALLOW_INLINE_COMPILATION 0x20000 /* * Types for linked variables: */ #define TCL_LINK_INT 1 #define TCL_LINK_DOUBLE 2 #define TCL_LINK_BOOLEAN 3 #define TCL_LINK_STRING 4 #define TCL_LINK_WIDE_INT 5 #define TCL_LINK_CHAR 6 #define TCL_LINK_UCHAR 7 #define TCL_LINK_SHORT 8 #define TCL_LINK_USHORT 9 #define TCL_LINK_UINT 10 #define TCL_LINK_LONG ((sizeof(long) != sizeof(int)) ? TCL_LINK_WIDE_INT : TCL_LINK_INT) #define TCL_LINK_ULONG ((sizeof(long) != sizeof(int)) ? TCL_LINK_WIDE_UINT : TCL_LINK_UINT) #define TCL_LINK_FLOAT 13 #define TCL_LINK_WIDE_UINT 14 #define TCL_LINK_CHARS 15 #define TCL_LINK_BINARY 16 #define TCL_LINK_READ_ONLY 0x80 /* *---------------------------------------------------------------------------- * Forward declarations of Tcl_HashTable and related types. */ #ifndef TCL_HASH_TYPE #if TCL_MAJOR_VERSION > 8 # define TCL_HASH_TYPE size_t #else # define TCL_HASH_TYPE unsigned #endif #endif typedef struct Tcl_HashKeyType Tcl_HashKeyType; typedef struct Tcl_HashTable Tcl_HashTable; typedef struct Tcl_HashEntry Tcl_HashEntry; typedef TCL_HASH_TYPE (Tcl_HashKeyProc) (Tcl_HashTable *tablePtr, void *keyPtr); typedef int (Tcl_CompareHashKeysProc) (void *keyPtr, Tcl_HashEntry *hPtr); typedef Tcl_HashEntry * (Tcl_AllocHashEntryProc) (Tcl_HashTable *tablePtr, void *keyPtr); typedef void (Tcl_FreeHashEntryProc) (Tcl_HashEntry *hPtr); /* * Structure definition for an entry in a hash table. No-one outside Tcl * should access any of these fields directly; use the macros defined below. */ struct Tcl_HashEntry { Tcl_HashEntry *nextPtr; /* Pointer to next entry in this hash bucket, * or NULL for end of chain. */ Tcl_HashTable *tablePtr; /* Pointer to table containing entry. */ size_t hash; /* Hash value. */ void *clientData; /* Application stores something here with * Tcl_SetHashValue. */ union { /* Key has one of these forms: */ char *oneWordValue; /* One-word value for key. */ Tcl_Obj *objPtr; /* Tcl_Obj * key value. */ int words[1]; /* Multiple integer words for key. The actual * size will be as large as necessary for this * table's keys. */ char string[1]; /* String for key. The actual size will be as * large as needed to hold the key. */ } key; /* MUST BE LAST FIELD IN RECORD!! */ }; /* * Flags used in Tcl_HashKeyType. * * TCL_HASH_KEY_RANDOMIZE_HASH - * There are some things, pointers for example * which don't hash well because they do not use * the lower bits. If this flag is set then the * hash table will attempt to rectify this by * randomising the bits and then using the upper * N bits as the index into the table. * TCL_HASH_KEY_SYSTEM_HASH - If this flag is set then all memory internally * allocated for the hash table that is not for an * entry will use the system heap. * TCL_HASH_KEY_DIRECT_COMPARE - * Allows fast comparison for hash keys directly * by compare of their key.oneWordValue values, * before call of compareKeysProc (much slower * than a direct compare, so it is speed-up only * flag). Don't use it if keys contain values rather * than pointers. */ #define TCL_HASH_KEY_RANDOMIZE_HASH 0x1 #define TCL_HASH_KEY_SYSTEM_HASH 0x2 #define TCL_HASH_KEY_DIRECT_COMPARE 0x4 /* * Structure definition for the methods associated with a hash table key type. */ #define TCL_HASH_KEY_TYPE_VERSION 1 struct Tcl_HashKeyType { int version; /* Version of the table. If this structure is * extended in future then the version can be * used to distinguish between different * structures. */ int flags; /* Flags, see above for details. */ Tcl_HashKeyProc *hashKeyProc; /* Calculates a hash value for the key. If * this is NULL then the pointer itself is * used as a hash value. */ Tcl_CompareHashKeysProc *compareKeysProc; /* Compares two keys and returns zero if they * do not match, and non-zero if they do. If * this is NULL then the pointers are * compared. */ Tcl_AllocHashEntryProc *allocEntryProc; /* Called to allocate memory for a new entry, * i.e. if the key is a string then this could * allocate a single block which contains * enough space for both the entry and the * string. Only the key field of the allocated * Tcl_HashEntry structure needs to be filled * in. If something else needs to be done to * the key, i.e. incrementing a reference * count then that should be done by this * function. If this is NULL then Tcl_Alloc is * used to allocate enough space for a * Tcl_HashEntry and the key pointer is * assigned to key.oneWordValue. */ Tcl_FreeHashEntryProc *freeEntryProc; /* Called to free memory associated with an * entry. If something else needs to be done * to the key, i.e. decrementing a reference * count then that should be done by this * function. If this is NULL then Tcl_Free is * used to free the Tcl_HashEntry. */ }; /* * Structure definition for a hash table. Must be in tcl.h so clients can * allocate space for these structures, but clients should never access any * fields in this structure. */ #define TCL_SMALL_HASH_TABLE 4 struct Tcl_HashTable { Tcl_HashEntry **buckets; /* Pointer to bucket array. Each element * points to first entry in bucket's hash * chain, or NULL. */ Tcl_HashEntry *staticBuckets[TCL_SMALL_HASH_TABLE]; /* Bucket array used for small tables (to * avoid mallocs and frees). */ Tcl_Size numBuckets; /* Total number of buckets allocated at * **bucketPtr. */ Tcl_Size numEntries; /* Total number of entries present in * table. */ Tcl_Size rebuildSize; /* Enlarge table when numEntries gets to be * this large. */ #if TCL_MAJOR_VERSION > 8 size_t mask; /* Mask value used in hashing function. */ #endif int downShift; /* Shift count used in hashing function. * Designed to use high-order bits of * randomized keys. */ #if TCL_MAJOR_VERSION < 9 int mask; /* Mask value used in hashing function. */ #endif int keyType; /* Type of keys used in this table. It's * either TCL_CUSTOM_KEYS, TCL_STRING_KEYS, * TCL_ONE_WORD_KEYS, or an integer giving the * number of ints that is the size of the * key. */ Tcl_HashEntry *(*findProc) (Tcl_HashTable *tablePtr, const char *key); Tcl_HashEntry *(*createProc) (Tcl_HashTable *tablePtr, const char *key, int *newPtr); const Tcl_HashKeyType *typePtr; /* Type of the keys used in the * Tcl_HashTable. */ }; /* * Structure definition for information used to keep track of searches through * hash tables: */ typedef struct Tcl_HashSearch { Tcl_HashTable *tablePtr; /* Table being searched. */ Tcl_Size nextIndex; /* Index of next bucket to be enumerated after * present one. */ Tcl_HashEntry *nextEntryPtr;/* Next entry to be enumerated in the current * bucket. */ } Tcl_HashSearch; /* * Acceptable key types for hash tables: * * TCL_STRING_KEYS: The keys are strings, they are copied into the * entry. * TCL_ONE_WORD_KEYS: The keys are pointers, the pointer is stored * in the entry. * TCL_CUSTOM_TYPE_KEYS: The keys are arbitrary types which are copied * into the entry. * TCL_CUSTOM_PTR_KEYS: The keys are pointers to arbitrary types, the * pointer is stored in the entry. * * While maintaining binary compatibility the above have to be distinct values * as they are used to differentiate between old versions of the hash table * which don't have a typePtr and new ones which do. Once binary compatibility * is discarded in favour of making more wide spread changes TCL_STRING_KEYS * can be the same as TCL_CUSTOM_TYPE_KEYS, and TCL_ONE_WORD_KEYS can be the * same as TCL_CUSTOM_PTR_KEYS because they simply determine how the key is * accessed from the entry and not the behaviour. */ #define TCL_STRING_KEYS (0) #define TCL_ONE_WORD_KEYS (1) #define TCL_CUSTOM_TYPE_KEYS (-2) #define TCL_CUSTOM_PTR_KEYS (-1) /* * Structure definition for information used to keep track of searches through * dictionaries. These fields should not be accessed by code outside * tclDictObj.c */ typedef struct { void *next; /* Search position for underlying hash * table. */ TCL_HASH_TYPE epoch; /* Epoch marker for dictionary being searched, * or 0 if search has terminated. */ Tcl_Dict dictionaryPtr; /* Reference to dictionary being searched. */ } Tcl_DictSearch; /* *---------------------------------------------------------------------------- * Flag values to pass to Tcl_DoOneEvent to disable searches for some kinds of * events: */ #define TCL_DONT_WAIT (1<<1) #define TCL_WINDOW_EVENTS (1<<2) #define TCL_FILE_EVENTS (1<<3) #define TCL_TIMER_EVENTS (1<<4) #define TCL_IDLE_EVENTS (1<<5) /* WAS 0x10 ???? */ #define TCL_ALL_EVENTS (~TCL_DONT_WAIT) /* * The following structure defines a generic event for the Tcl event system. * These are the things that are queued in calls to Tcl_QueueEvent and * serviced later by Tcl_DoOneEvent. There can be many different kinds of * events with different fields, corresponding to window events, timer events, * etc. The structure for a particular event consists of a Tcl_Event header * followed by additional information specific to that event. */ struct Tcl_Event { Tcl_EventProc *proc; /* Function to call to service this event. */ struct Tcl_Event *nextPtr; /* Next in list of pending events, or NULL. */ }; /* * Positions to pass to Tcl_QueueEvent/Tcl_ThreadQueueEvent: */ typedef enum { TCL_QUEUE_TAIL, TCL_QUEUE_HEAD, TCL_QUEUE_MARK, TCL_QUEUE_ALERT_IF_EMPTY=4 } Tcl_QueuePosition; /* * Values to pass to Tcl_SetServiceMode to specify the behavior of notifier * event routines. */ #define TCL_SERVICE_NONE 0 #define TCL_SERVICE_ALL 1 /* * The following structure keeps is used to hold a time value, either as an * absolute time (the number of seconds from the epoch) or as an elapsed time. * On Unix systems the epoch is Midnight Jan 1, 1970 GMT. */ typedef struct Tcl_Time { #if TCL_MAJOR_VERSION > 8 long long sec; /* Seconds. */ #else long sec; /* Seconds. */ #endif #if defined(_CYGWIN_) && TCL_MAJOR_VERSION > 8 int usec; /* Microseconds. */ #else long usec; /* Microseconds. */ #endif } Tcl_Time; typedef void (Tcl_SetTimerProc) (const Tcl_Time *timePtr); typedef int (Tcl_WaitForEventProc) (const Tcl_Time *timePtr); /* * TIP #233 (Virtualized Time) */ typedef void (Tcl_GetTimeProc) (Tcl_Time *timebuf, void *clientData); typedef void (Tcl_ScaleTimeProc) (Tcl_Time *timebuf, void *clientData); /* *---------------------------------------------------------------------------- * Bits to pass to Tcl_CreateFileHandler and Tcl_CreateChannelHandler to * indicate what sorts of events are of interest: */ #define TCL_READABLE (1<<1) #define TCL_WRITABLE (1<<2) #define TCL_EXCEPTION (1<<3) /* * Flag values to pass to Tcl_OpenCommandChannel to indicate the disposition * of the stdio handles. TCL_STDIN, TCL_STDOUT, TCL_STDERR, are also used in * Tcl_GetStdChannel. */ #define TCL_STDIN (1<<1) #define TCL_STDOUT (1<<2) #define TCL_STDERR (1<<3) #define TCL_ENFORCE_MODE (1<<4) /* * Bits passed to Tcl_DriverClose2Proc to indicate which side of a channel * should be closed. */ #define TCL_CLOSE_READ (1<<1) #define TCL_CLOSE_WRITE (1<<2) /* * Value to use as the closeProc for a channel that supports the close2Proc * interface. */ #if TCL_MAJOR_VERSION > 8 # define TCL_CLOSE2PROC NULL #else # define TCL_CLOSE2PROC ((void *) 1) #endif /* * Channel version tag. This was introduced in 8.3.2/8.4. */ #define TCL_CHANNEL_VERSION_5 ((Tcl_ChannelTypeVersion) 0x5) /* * TIP #218: Channel Actions, Ids for Tcl_DriverThreadActionProc. */ #define TCL_CHANNEL_THREAD_INSERT (0) #define TCL_CHANNEL_THREAD_REMOVE (1) /* * Typedefs for the various operations in a channel type: */ typedef int (Tcl_DriverBlockModeProc) (void *instanceData, int mode); typedef void Tcl_DriverCloseProc; typedef int (Tcl_DriverClose2Proc) (void *instanceData, Tcl_Interp *interp, int flags); typedef int (Tcl_DriverInputProc) (void *instanceData, char *buf, int toRead, int *errorCodePtr); typedef int (Tcl_DriverOutputProc) (void *instanceData, const char *buf, int toWrite, int *errorCodePtr); typedef void Tcl_DriverSeekProc; typedef int (Tcl_DriverSetOptionProc) (void *instanceData, Tcl_Interp *interp, const char *optionName, const char *value); typedef int (Tcl_DriverGetOptionProc) (void *instanceData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr); typedef void (Tcl_DriverWatchProc) (void *instanceData, int mask); typedef int (Tcl_DriverGetHandleProc) (void *instanceData, int direction, void **handlePtr); typedef int (Tcl_DriverFlushProc) (void *instanceData); typedef int (Tcl_DriverHandlerProc) (void *instanceData, int interestMask); typedef long long (Tcl_DriverWideSeekProc) (void *instanceData, long long offset, int mode, int *errorCodePtr); /* * TIP #218, Channel Thread Actions */ typedef void (Tcl_DriverThreadActionProc) (void *instanceData, int action); /* * TIP #208, File Truncation (etc.) */ typedef int (Tcl_DriverTruncateProc) (void *instanceData, long long length); /* * struct Tcl_ChannelType: * * One such structure exists for each type (kind) of channel. It collects * together in one place all the functions that are part of the specific * channel type. * * It is recommend that the Tcl_Channel* functions are used to access elements * of this structure, instead of direct accessing. */ typedef struct Tcl_ChannelType { const char *typeName; /* The name of the channel type in Tcl * commands. This storage is owned by channel * type. */ Tcl_ChannelTypeVersion version; /* Version of the channel type. */ void *closeProc; /* Not used any more. */ Tcl_DriverInputProc *inputProc; /* Function to call for input on channel. */ Tcl_DriverOutputProc *outputProc; /* Function to call for output on channel. */ void *seekProc; /* Not used any more. */ Tcl_DriverSetOptionProc *setOptionProc; /* Set an option on a channel. */ Tcl_DriverGetOptionProc *getOptionProc; /* Get an option from a channel. */ Tcl_DriverWatchProc *watchProc; /* Set up the notifier to watch for events on * this channel. */ Tcl_DriverGetHandleProc *getHandleProc; /* Get an OS handle from the channel or NULL * if not supported. */ Tcl_DriverClose2Proc *close2Proc; /* Function to call to close the channel if * the device supports closing the read & * write sides independently. */ Tcl_DriverBlockModeProc *blockModeProc; /* Set blocking mode for the raw channel. May * be NULL. */ Tcl_DriverFlushProc *flushProc; /* Function to call to flush a channel. May be * NULL. */ Tcl_DriverHandlerProc *handlerProc; /* Function to call to handle a channel event. * This will be passed up the stacked channel * chain. */ Tcl_DriverWideSeekProc *wideSeekProc; /* Function to call to seek on the channel * which can handle 64-bit offsets. May be * NULL, and must be NULL if seekProc is * NULL. */ Tcl_DriverThreadActionProc *threadActionProc; /* Function to call to notify the driver of * thread specific activity for a channel. May * be NULL. */ Tcl_DriverTruncateProc *truncateProc; /* Function to call to truncate the underlying * file to a particular length. May be NULL if * the channel does not support truncation. */ } Tcl_ChannelType; /* * The following flags determine whether the blockModeProc above should set * the channel into blocking or nonblocking mode. They are passed as arguments * to the blockModeProc function in the above structure. */ #define TCL_MODE_BLOCKING 0 /* Put channel into blocking mode. */ #define TCL_MODE_NONBLOCKING 1 /* Put channel into nonblocking * mode. */ /* *---------------------------------------------------------------------------- * Enum for different types of file paths. */ typedef enum Tcl_PathType { TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, TCL_PATH_VOLUME_RELATIVE } Tcl_PathType; /* * The following structure is used to pass glob type data amongst the various * glob routines and Tcl_FSMatchInDirectory. */ typedef struct Tcl_GlobTypeData { int type; /* Corresponds to bcdpfls as in 'find -t'. */ int perm; /* Corresponds to file permissions. */ Tcl_Obj *macType; /* Acceptable Mac type. */ Tcl_Obj *macCreator; /* Acceptable Mac creator. */ } Tcl_GlobTypeData; /* * Type and permission definitions for glob command. */ #define TCL_GLOB_TYPE_BLOCK (1<<0) #define TCL_GLOB_TYPE_CHAR (1<<1) #define TCL_GLOB_TYPE_DIR (1<<2) #define TCL_GLOB_TYPE_PIPE (1<<3) #define TCL_GLOB_TYPE_FILE (1<<4) #define TCL_GLOB_TYPE_LINK (1<<5) #define TCL_GLOB_TYPE_SOCK (1<<6) #define TCL_GLOB_TYPE_MOUNT (1<<7) #define TCL_GLOB_PERM_RONLY (1<<0) #define TCL_GLOB_PERM_HIDDEN (1<<1) #define TCL_GLOB_PERM_R (1<<2) #define TCL_GLOB_PERM_W (1<<3) #define TCL_GLOB_PERM_X (1<<4) /* * Flags for the unload callback function. */ #define TCL_UNLOAD_DETACH_FROM_INTERPRETER (1<<0) #define TCL_UNLOAD_DETACH_FROM_PROCESS (1<<1) /* * Typedefs for the various filesystem operations: */ typedef int (Tcl_FSStatProc) (Tcl_Obj *pathPtr, Tcl_StatBuf *buf); typedef int (Tcl_FSAccessProc) (Tcl_Obj *pathPtr, int mode); typedef Tcl_Channel (Tcl_FSOpenFileChannelProc) (Tcl_Interp *interp, Tcl_Obj *pathPtr, int mode, int permissions); typedef int (Tcl_FSMatchInDirectoryProc) (Tcl_Interp *interp, Tcl_Obj *result, Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types); typedef Tcl_Obj * (Tcl_FSGetCwdProc) (Tcl_Interp *interp); typedef int (Tcl_FSChdirProc) (Tcl_Obj *pathPtr); typedef int (Tcl_FSLstatProc) (Tcl_Obj *pathPtr, Tcl_StatBuf *buf); typedef int (Tcl_FSCreateDirectoryProc) (Tcl_Obj *pathPtr); typedef int (Tcl_FSDeleteFileProc) (Tcl_Obj *pathPtr); typedef int (Tcl_FSCopyDirectoryProc) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr); typedef int (Tcl_FSCopyFileProc) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); typedef int (Tcl_FSRemoveDirectoryProc) (Tcl_Obj *pathPtr, int recursive, Tcl_Obj **errorPtr); typedef int (Tcl_FSRenameFileProc) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); typedef void (Tcl_FSUnloadFileProc) (Tcl_LoadHandle loadHandle); typedef Tcl_Obj * (Tcl_FSListVolumesProc) (void); /* We have to declare the utime structure here. */ struct utimbuf; typedef int (Tcl_FSUtimeProc) (Tcl_Obj *pathPtr, struct utimbuf *tval); typedef int (Tcl_FSNormalizePathProc) (Tcl_Interp *interp, Tcl_Obj *pathPtr, int nextCheckpoint); typedef int (Tcl_FSFileAttrsGetProc) (Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); typedef const char *const * (Tcl_FSFileAttrStringsProc) (Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); typedef int (Tcl_FSFileAttrsSetProc) (Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj *objPtr); typedef Tcl_Obj * (Tcl_FSLinkProc) (Tcl_Obj *pathPtr, Tcl_Obj *toPtr, int linkType); typedef int (Tcl_FSLoadFileProc) (Tcl_Interp *interp, Tcl_Obj *pathPtr, Tcl_LoadHandle *handlePtr, Tcl_FSUnloadFileProc **unloadProcPtr); typedef int (Tcl_FSPathInFilesystemProc) (Tcl_Obj *pathPtr, void **clientDataPtr); typedef Tcl_Obj * (Tcl_FSFilesystemPathTypeProc) (Tcl_Obj *pathPtr); typedef Tcl_Obj * (Tcl_FSFilesystemSeparatorProc) (Tcl_Obj *pathPtr); #define Tcl_FSFreeInternalRepProc Tcl_FreeProc typedef void *(Tcl_FSDupInternalRepProc) (void *clientData); typedef Tcl_Obj * (Tcl_FSInternalToNormalizedProc) (void *clientData); typedef void *(Tcl_FSCreateInternalRepProc) (Tcl_Obj *pathPtr); typedef struct Tcl_FSVersion_ *Tcl_FSVersion; /* *---------------------------------------------------------------------------- * Data structures related to hooking into the filesystem */ /* * Filesystem version tag. This was introduced in 8.4. */ #define TCL_FILESYSTEM_VERSION_1 ((Tcl_FSVersion) 0x1) /* * struct Tcl_Filesystem: * * One such structure exists for each type (kind) of filesystem. It collects * together the functions that form the interface for a particulr the * filesystem. Tcl always accesses the filesystem through one of these * structures. * * Not all entries need be non-NULL; any which are NULL are simply ignored. * However, a complete filesystem should provide all of these functions. The * explanations in the structure show the importance of each function. */ typedef struct Tcl_Filesystem { const char *typeName; /* The name of the filesystem. */ Tcl_Size structureLength; /* Length of this structure, so future binary * compatibility can be assured. */ Tcl_FSVersion version; /* Version of the filesystem type. */ Tcl_FSPathInFilesystemProc *pathInFilesystemProc; /* Determines whether the pathname is in this * filesystem. This is the most important * filesystem function. */ Tcl_FSDupInternalRepProc *dupInternalRepProc; /* Duplicates the internal handle of the node. * If it is NULL, the filesystem is less * performant. */ Tcl_FSFreeInternalRepProc *freeInternalRepProc; /* Frees the internal handle of the node. NULL * only if there is no need to free resources * used for the internal handle. */ Tcl_FSInternalToNormalizedProc *internalToNormalizedProc; /* Converts the internal handle to a normalized * path. NULL if the filesystem creates nodes * having no pathname. */ Tcl_FSCreateInternalRepProc *createInternalRepProc; /* Creates an internal handle for a pathname. * May be NULL if pathnames have no internal * handle or if pathInFilesystemProc always * immediately creates an internal * representation for pathnames in the * filesystem. */ Tcl_FSNormalizePathProc *normalizePathProc; /* Normalizes a path. Should be implemented if * the filesystems supports multiple paths to * the same node. */ Tcl_FSFilesystemPathTypeProc *filesystemPathTypeProc; /* Determines the type of a path in this * filesystem. May be NULL. */ Tcl_FSFilesystemSeparatorProc *filesystemSeparatorProc; /* Produces the separator character(s) for this * filesystem. Must not be NULL. */ Tcl_FSStatProc *statProc; /* Called by 'Tcl_FSStat()'. Provided by any * reasonable filesystem. */ Tcl_FSAccessProc *accessProc; /* Called by 'Tcl_FSAccess()'. Implemented by * any reasonable filesystem. */ Tcl_FSOpenFileChannelProc *openFileChannelProc; /* Called by 'Tcl_FSOpenFileChannel()'. * Provided by any reasonable filesystem. */ Tcl_FSMatchInDirectoryProc *matchInDirectoryProc; /* Called by 'Tcl_FSMatchInDirectory()'. NULL * if the filesystem does not support glob or * recursive copy. */ Tcl_FSUtimeProc *utimeProc; /* Called by 'Tcl_FSUtime()', by 'file * mtime' to set (not read) times, 'file * atime', and the open-r/open-w/fcopy variant * of 'file copy'. */ Tcl_FSLinkProc *linkProc; /* Called by 'Tcl_FSLink()'. NULL if reading or * creating links is not supported. */ Tcl_FSListVolumesProc *listVolumesProc; /* Lists filesystem volumes added by this * filesystem. NULL if the filesystem does not * use volumes. */ Tcl_FSFileAttrStringsProc *fileAttrStringsProc; /* List all valid attributes strings. NULL if * the filesystem does not support the 'file * attributes' command. Can be used to attach * arbitrary additional data to files in a * filesystem. */ Tcl_FSFileAttrsGetProc *fileAttrsGetProc; /* Called by 'Tcl_FSFileAttrsGet()' and by * 'file attributes'. */ Tcl_FSFileAttrsSetProc *fileAttrsSetProc; /* Called by 'Tcl_FSFileAttrsSet()' and by * 'file attributes'. */ Tcl_FSCreateDirectoryProc *createDirectoryProc; /* Called by 'Tcl_FSCreateDirectory()'. May be * NULL if the filesystem is read-only. */ Tcl_FSRemoveDirectoryProc *removeDirectoryProc; /* Called by 'Tcl_FSRemoveDirectory()'. May be * NULL if the filesystem is read-only. */ Tcl_FSDeleteFileProc *deleteFileProc; /* Called by 'Tcl_FSDeleteFile()' May be NULL * if the filesystem is is read-only. */ Tcl_FSCopyFileProc *copyFileProc; /* Called by 'Tcl_FSCopyFile()'. If NULL, for * a copy operation at the script level (not * C) Tcl uses open-r, open-w and fcopy. */ Tcl_FSRenameFileProc *renameFileProc; /* Called by 'Tcl_FSRenameFile()'. If NULL, for * a rename operation at the script level (not * C) Tcl performs a copy operation followed * by a delete operation. */ Tcl_FSCopyDirectoryProc *copyDirectoryProc; /* Called by 'Tcl_FSCopyDirectory()'. If NULL, * for a copy operation at the script level * (not C) Tcl recursively creates directories * and copies files. */ Tcl_FSLstatProc *lstatProc; /* Called by 'Tcl_FSLstat()'. If NULL, Tcl * attempts to use 'statProc' instead. */ Tcl_FSLoadFileProc *loadFileProc; /* Called by 'Tcl_FSLoadFile()'. If NULL, Tcl * performs a copy to a temporary file in the * native filesystem and then calls * Tcl_FSLoadFile() on that temporary copy. */ Tcl_FSGetCwdProc *getCwdProc; /* Called by 'Tcl_FSGetCwd()'. Normally NULL. * Usually only called once: If 'getcwd' is * called before 'chdir' is ever called. */ Tcl_FSChdirProc *chdirProc; /* Called by 'Tcl_FSChdir()'. For a virtual * filesystem, chdirProc just returns zero * (success) if the pathname is a valid * directory, and some other value otherwise. * For A real filesystem, chdirProc performs * the correct action, e.g. calls the system * 'chdir' function. If not implemented, then * 'cd' and 'pwd' fail for a pathname in this * filesystem. On success Tcl stores the * pathname for use by GetCwd. If NULL, Tcl * performs records the pathname as the new * current directory if it passes a series of * directory access checks. */ } Tcl_Filesystem; /* * The following definitions are used as values for the 'linkAction' flag to * Tcl_FSLink, or the linkProc of any filesystem. Any combination of flags can * be given. For link creation, the linkProc should create a link which * matches any of the types given. * * TCL_CREATE_SYMBOLIC_LINK - Create a symbolic or soft link. * TCL_CREATE_HARD_LINK - Create a hard link. */ #define TCL_CREATE_SYMBOLIC_LINK 0x01 #define TCL_CREATE_HARD_LINK 0x02 /* *---------------------------------------------------------------------------- * The following structure represents the Notifier functions that you can * override with the Tcl_SetNotifier call. */ typedef struct Tcl_NotifierProcs { Tcl_SetTimerProc *setTimerProc; Tcl_WaitForEventProc *waitForEventProc; Tcl_CreateFileHandlerProc *createFileHandlerProc; Tcl_DeleteFileHandlerProc *deleteFileHandlerProc; Tcl_InitNotifierProc *initNotifierProc; Tcl_FinalizeNotifierProc *finalizeNotifierProc; Tcl_AlertNotifierProc *alertNotifierProc; Tcl_ServiceModeHookProc *serviceModeHookProc; } Tcl_NotifierProcs; /* *---------------------------------------------------------------------------- * The following data structures and declarations are for the new Tcl parser. * * For each word of a command, and for each piece of a word such as a variable * reference, one of the following structures is created to describe the * token. */ typedef struct Tcl_Token { int type; /* Type of token, such as TCL_TOKEN_WORD; see * below for valid types. */ const char *start; /* First character in token. */ Tcl_Size size; /* Number of bytes in token. */ Tcl_Size numComponents; /* If this token is composed of other tokens, * this field tells how many of them there are * (including components of components, etc.). * The component tokens immediately follow * this one. */ } Tcl_Token; /* * Type values defined for Tcl_Token structures. These values are defined as * mask bits so that it's easy to check for collections of types. * * TCL_TOKEN_WORD - The token describes one word of a command, * from the first non-blank character of the word * (which may be " or {) up to but not including * the space, semicolon, or bracket that * terminates the word. NumComponents counts the * total number of sub-tokens that make up the * word. This includes, for example, sub-tokens * of TCL_TOKEN_VARIABLE tokens. * TCL_TOKEN_SIMPLE_WORD - This token is just like TCL_TOKEN_WORD except * that the word is guaranteed to consist of a * single TCL_TOKEN_TEXT sub-token. * TCL_TOKEN_TEXT - The token describes a range of literal text * that is part of a word. NumComponents is * always 0. * TCL_TOKEN_BS - The token describes a backslash sequence that * must be collapsed. NumComponents is always 0. * TCL_TOKEN_COMMAND - The token describes a command whose result * must be substituted into the word. The token * includes the enclosing brackets. NumComponents * is always 0. * TCL_TOKEN_VARIABLE - The token describes a variable substitution, * including the dollar sign, variable name, and * array index (if there is one) up through the * right parentheses. NumComponents tells how * many additional tokens follow to represent the * variable name. The first token will be a * TCL_TOKEN_TEXT token that describes the * variable name. If the variable is an array * reference then there will be one or more * additional tokens, of type TCL_TOKEN_TEXT, * TCL_TOKEN_BS, TCL_TOKEN_COMMAND, and * TCL_TOKEN_VARIABLE, that describe the array * index; numComponents counts the total number * of nested tokens that make up the variable * reference, including sub-tokens of * TCL_TOKEN_VARIABLE tokens. * TCL_TOKEN_SUB_EXPR - The token describes one subexpression of an * expression, from the first non-blank character * of the subexpression up to but not including * the space, brace, or bracket that terminates * the subexpression. NumComponents counts the * total number of following subtokens that make * up the subexpression; this includes all * subtokens for any nested TCL_TOKEN_SUB_EXPR * tokens. For example, a numeric value used as a * primitive operand is described by a * TCL_TOKEN_SUB_EXPR token followed by a * TCL_TOKEN_TEXT token. A binary subexpression * is described by a TCL_TOKEN_SUB_EXPR token * followed by the TCL_TOKEN_OPERATOR token for * the operator, then TCL_TOKEN_SUB_EXPR tokens * for the left then the right operands. * TCL_TOKEN_OPERATOR - The token describes one expression operator. * An operator might be the name of a math * function such as "abs". A TCL_TOKEN_OPERATOR * token is always preceded by one * TCL_TOKEN_SUB_EXPR token for the operator's * subexpression, and is followed by zero or more * TCL_TOKEN_SUB_EXPR tokens for the operator's * operands. NumComponents is always 0. * TCL_TOKEN_EXPAND_WORD - This token is just like TCL_TOKEN_WORD except * that it marks a word that began with the * literal character prefix "{*}". This word is * marked to be expanded - that is, broken into * words after substitution is complete. */ #define TCL_TOKEN_WORD 1 #define TCL_TOKEN_SIMPLE_WORD 2 #define TCL_TOKEN_TEXT 4 #define TCL_TOKEN_BS 8 #define TCL_TOKEN_COMMAND 16 #define TCL_TOKEN_VARIABLE 32 #define TCL_TOKEN_SUB_EXPR 64 #define TCL_TOKEN_OPERATOR 128 #define TCL_TOKEN_EXPAND_WORD 256 /* * Parsing error types. On any parsing error, one of these values will be * stored in the error field of the Tcl_Parse structure defined below. */ #define TCL_PARSE_SUCCESS 0 #define TCL_PARSE_QUOTE_EXTRA 1 #define TCL_PARSE_BRACE_EXTRA 2 #define TCL_PARSE_MISSING_BRACE 3 #define TCL_PARSE_MISSING_BRACKET 4 #define TCL_PARSE_MISSING_PAREN 5 #define TCL_PARSE_MISSING_QUOTE 6 #define TCL_PARSE_MISSING_VAR_BRACE 7 #define TCL_PARSE_SYNTAX 8 #define TCL_PARSE_BAD_NUMBER 9 /* * A structure of the following type is filled in by Tcl_ParseCommand. It * describes a single command parsed from an input string. */ #define NUM_STATIC_TOKENS 20 typedef struct Tcl_Parse { const char *commentStart; /* Pointer to # that begins the first of one * or more comments preceding the command. */ Tcl_Size commentSize; /* Number of bytes in comments (up through * newline character that terminates the last * comment). If there were no comments, this * field is 0. */ const char *commandStart; /* First character in first word of * command. */ Tcl_Size commandSize; /* Number of bytes in command, including first * character of first word, up through the * terminating newline, close bracket, or * semicolon. */ Tcl_Size numWords; /* Total number of words in command. May be * 0. */ Tcl_Token *tokenPtr; /* Pointer to first token representing the * words of the command. Initially points to * staticTokens, but may change to point to * malloc-ed space if command exceeds space in * staticTokens. */ Tcl_Size numTokens; /* Total number of tokens in command. */ Tcl_Size tokensAvailable; /* Total number of tokens available at * *tokenPtr. */ int errorType; /* One of the parsing error types defined * above. */ #if TCL_MAJOR_VERSION > 8 int incomplete; /* This field is set to 1 by Tcl_ParseCommand * if the command appears to be incomplete. * This information is used by * Tcl_CommandComplete. */ #endif /* * The fields below are intended only for the private use of the parser. * They should not be used by functions that invoke Tcl_ParseCommand. */ const char *string; /* The original command string passed to * Tcl_ParseCommand. */ const char *end; /* Points to the character just after the last * one in the command string. */ Tcl_Interp *interp; /* Interpreter to use for error reporting, or * NULL. */ const char *term; /* Points to character in string that * terminated most recent token. Filled in by * ParseTokens. If an error occurs, points to * beginning of region where the error * occurred (e.g. the open brace if the close * brace is missing). */ #if TCL_MAJOR_VERSION < 9 int incomplete; #endif Tcl_Token staticTokens[NUM_STATIC_TOKENS]; /* Initial space for tokens for command. This * space should be large enough to accommodate * most commands; dynamic space is allocated * for very large commands that don't fit * here. */ } Tcl_Parse; /* *---------------------------------------------------------------------------- * The following structure represents a user-defined encoding. It collects * together all the functions that are used by the specific encoding. */ typedef struct Tcl_EncodingType { const char *encodingName; /* The name of the encoding, e.g. "euc-jp". * This name is the unique key for this * encoding type. */ Tcl_EncodingConvertProc *toUtfProc; /* Function to convert from external encoding * into UTF-8. */ Tcl_EncodingConvertProc *fromUtfProc; /* Function to convert from UTF-8 into * external encoding. */ Tcl_FreeProc *freeProc; /* If non-NULL, function to call when this * encoding is deleted. */ void *clientData; /* Arbitrary value associated with encoding * type. Passed to conversion functions. */ Tcl_Size nullSize; /* Number of zero bytes that signify * end-of-string in this encoding. This number * is used to determine the source string * length when the srcLen argument is * negative. Must be 1, 2, or 4. */ } Tcl_EncodingType; /* * The following definitions are used as values for the conversion control * flags argument when converting text from one character set to another: * * TCL_ENCODING_START - Signifies that the source buffer is the first * block in a (potentially multi-block) input * stream. Tells the conversion function to reset * to an initial state and perform any * initialization that needs to occur before the * first byte is converted. If the source buffer * contains the entire input stream to be * converted, this flag should be set. * TCL_ENCODING_END - Signifies that the source buffer is the last * block in a (potentially multi-block) input * stream. Tells the conversion routine to * perform any finalization that needs to occur * after the last byte is converted and then to * reset to an initial state. If the source * buffer contains the entire input stream to be * converted, this flag should be set. * TCL_ENCODING_STOPONERROR - Not used any more. * TCL_ENCODING_NO_TERMINATE - If set, Tcl_ExternalToUtf does not append a * terminating NUL byte. Since it does not need * an extra byte for a terminating NUL, it fills * all dstLen bytes with encoded UTF-8 content if * needed. If clear, a byte is reserved in the * dst space for NUL termination, and a * terminating NUL is appended. * TCL_ENCODING_CHAR_LIMIT - If set and dstCharsPtr is not NULL, then * Tcl_ExternalToUtf takes the initial value of * *dstCharsPtr as a limit of the maximum number * of chars to produce in the encoded UTF-8 * content. Otherwise, the number of chars * produced is controlled only by other limiting * factors. * TCL_ENCODING_PROFILE_* - Mutually exclusive encoding profile ids. Note * these are bit masks. * * NOTE: THESE BIT DEFINITIONS SHOULD NOT OVERLAP WITH INTERNAL USE BITS * DEFINED IN tclEncoding.c (ENCODING_INPUT et al). Be cognizant of this * when adding bits. */ #define TCL_ENCODING_START 0x01 #define TCL_ENCODING_END 0x02 #if TCL_MAJOR_VERSION > 8 # define TCL_ENCODING_STOPONERROR 0x0 /* Not used any more */ #else # define TCL_ENCODING_STOPONERROR 0x04 #endif #define TCL_ENCODING_NO_TERMINATE 0x08 #define TCL_ENCODING_CHAR_LIMIT 0x10 /* Internal use bits, do not define bits in this space. See above comment */ #define TCL_ENCODING_INTERNAL_USE_MASK 0xFF00 /* * Reserve top byte for profile values (disjoint, not a mask). In case of * changes, ensure ENCODING_PROFILE_* macros in tclInt.h are modified if * necessary. */ #define TCL_ENCODING_PROFILE_STRICT TCL_ENCODING_STOPONERROR #define TCL_ENCODING_PROFILE_TCL8 0x01000000 #define TCL_ENCODING_PROFILE_REPLACE 0x02000000 /* * The following definitions are the error codes returned by the conversion * routines: * * TCL_OK - All characters were converted. * TCL_CONVERT_NOSPACE - The output buffer would not have been large * enough for all of the converted data; as many * characters as could fit were converted though. * TCL_CONVERT_MULTIBYTE - The last few bytes in the source string were * the beginning of a multibyte sequence, but * more bytes were needed to complete this * sequence. A subsequent call to the conversion * routine should pass the beginning of this * unconverted sequence plus additional bytes * from the source stream to properly convert the * formerly split-up multibyte sequence. * TCL_CONVERT_SYNTAX - The source stream contained an invalid * character sequence. This may occur if the * input stream has been damaged or if the input * encoding method was misidentified. * TCL_CONVERT_UNKNOWN - The source string contained a character that * could not be represented in the target * encoding. */ #define TCL_CONVERT_MULTIBYTE (-1) #define TCL_CONVERT_SYNTAX (-2) #define TCL_CONVERT_UNKNOWN (-3) #define TCL_CONVERT_NOSPACE (-4) /* * The maximum number of bytes that are necessary to represent a single * Unicode character in UTF-8. The valid values are 3 and 4. If > 3, * then Tcl_UniChar must be 4-bytes in size (UCS-4) (the default). If == 3, * then Tcl_UniChar must be 2-bytes in size (UTF-16). Since Tcl 9.0, UCS-4 * mode is the default and recommended mode. */ #ifndef TCL_UTF_MAX # if TCL_MAJOR_VERSION > 8 # define TCL_UTF_MAX 4 # else # define TCL_UTF_MAX 3 # endif #endif /* * This represents a Unicode character. Any changes to this should also be * reflected in regcustom.h. */ #if TCL_UTF_MAX == 4 /* * int isn't 100% accurate as it should be a strict 4-byte value * (perhaps int32_t). ILP64/SILP64 systems may have troubles. The * size of this value must be reflected correctly in regcustom.h. */ typedef int Tcl_UniChar; #elif TCL_UTF_MAX == 3 && !defined(BUILD_tcl) typedef unsigned short Tcl_UniChar; #else # error "This TCL_UTF_MAX value is not supported" #endif /* *---------------------------------------------------------------------------- * TIP #59: The following structure is used in calls 'Tcl_RegisterConfig' to * provide the system with the embedded configuration data. */ typedef struct Tcl_Config { const char *key; /* Configuration key to register. ASCII * encoded, thus UTF-8. */ const char *value; /* The value associated with the key. System * encoding. */ } Tcl_Config; /* *---------------------------------------------------------------------------- * Flags for TIP#143 limits, detailing which limits are active in an * interpreter. Used for Tcl_{Add,Remove}LimitHandler type argument. */ #define TCL_LIMIT_COMMANDS 0x01 #define TCL_LIMIT_TIME 0x02 /* * Structure containing information about a limit handler to be called when a * command- or time-limit is exceeded by an interpreter. */ typedef void (Tcl_LimitHandlerProc) (void *clientData, Tcl_Interp *interp); #if TCL_MAJOR_VERSION > 8 #define Tcl_LimitHandlerDeleteProc Tcl_FreeProc #else typedef void (Tcl_LimitHandlerDeleteProc) (void *clientData); #endif #if 0 /* *---------------------------------------------------------------------------- * We would like to provide an anonymous structure "mp_int" here, which is * compatible with libtommath's "mp_int", but without duplicating anything * from or including here. But the libtommath project * didn't honor our request. See: * * That's why this part is commented out, and we are using (void *) in * various API's in stead of the more correct (mp_int *). */ #ifndef MP_INT_DECLARED #define MP_INT_DECLARED typedef struct mp_int mp_int; #endif #endif /* *---------------------------------------------------------------------------- * Definitions needed for Tcl_ParseArgvObj routines. * Based on tkArgv.c. * Modifications from the original are copyright (c) Sam Bromley 2006 */ typedef struct { int type; /* Indicates the option type; see below. */ const char *keyStr; /* The key string that flags the option in the * argv array. */ void *srcPtr; /* Value to be used in setting dst; usage * depends on type.*/ void *dstPtr; /* Address of value to be modified; usage * depends on type.*/ const char *helpStr; /* Documentation message describing this * option. */ void *clientData; /* Word to pass to function callbacks. */ } Tcl_ArgvInfo; /* * Legal values for the type field of a Tcl_ArgInfo: see the user * documentation for details. */ #define TCL_ARGV_CONSTANT 15 #define TCL_ARGV_INT 16 #define TCL_ARGV_STRING 17 #define TCL_ARGV_REST 18 #define TCL_ARGV_FLOAT 19 #define TCL_ARGV_FUNC 20 #define TCL_ARGV_GENFUNC 21 #define TCL_ARGV_HELP 22 #define TCL_ARGV_END 23 /* * Types of callback functions for the TCL_ARGV_FUNC and TCL_ARGV_GENFUNC * argument types: */ typedef int (Tcl_ArgvFuncProc)(void *clientData, Tcl_Obj *objPtr, void *dstPtr); typedef Tcl_Size (Tcl_ArgvGenFuncProc)(void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv, void *dstPtr); /* * Shorthand for commonly used argTable entries. */ #define TCL_ARGV_AUTO_HELP \ {TCL_ARGV_HELP, "-help", NULL, NULL, \ "Print summary of command-line options and abort", NULL} #define TCL_ARGV_AUTO_REST \ {TCL_ARGV_REST, "--", NULL, NULL, \ "Marks the end of the options", NULL} #define TCL_ARGV_TABLE_END \ {TCL_ARGV_END, NULL, NULL, NULL, NULL, NULL} /* *---------------------------------------------------------------------------- * Definitions needed for Tcl_Zlib routines. [TIP #234] * * Constants for the format flags describing what sort of data format is * desired/expected for the Tcl_ZlibDeflate, Tcl_ZlibInflate and * Tcl_ZlibStreamInit functions. */ #define TCL_ZLIB_FORMAT_RAW 1 #define TCL_ZLIB_FORMAT_ZLIB 2 #define TCL_ZLIB_FORMAT_GZIP 4 #define TCL_ZLIB_FORMAT_AUTO 8 /* * Constants that describe whether the stream is to operate in compressing or * decompressing mode. */ #define TCL_ZLIB_STREAM_DEFLATE 16 #define TCL_ZLIB_STREAM_INFLATE 32 /* * Constants giving compression levels. Use of TCL_ZLIB_COMPRESS_DEFAULT is * recommended. */ #define TCL_ZLIB_COMPRESS_NONE 0 #define TCL_ZLIB_COMPRESS_FAST 1 #define TCL_ZLIB_COMPRESS_BEST 9 #define TCL_ZLIB_COMPRESS_DEFAULT (-1) /* * Constants for types of flushing, used with Tcl_ZlibFlush. */ #define TCL_ZLIB_NO_FLUSH 0 #define TCL_ZLIB_FLUSH 2 #define TCL_ZLIB_FULLFLUSH 3 #define TCL_ZLIB_FINALIZE 4 /* *---------------------------------------------------------------------------- * Definitions needed for the Tcl_LoadFile function. [TIP #416] */ #define TCL_LOAD_GLOBAL 1 #define TCL_LOAD_LAZY 2 /* *---------------------------------------------------------------------------- * Definitions needed for the Tcl_OpenTcpServerEx function. [TIP #456] */ #define TCL_TCPSERVER_REUSEADDR (1<<0) #define TCL_TCPSERVER_REUSEPORT (1<<1) /* * Constants for special Tcl_Size-typed values, see TIP #494 */ #define TCL_IO_FAILURE ((Tcl_Size)-1) #define TCL_AUTO_LENGTH ((Tcl_Size)-1) #define TCL_INDEX_NONE ((Tcl_Size)-1) /* *---------------------------------------------------------------------------- * Single public declaration for NRE. */ typedef int (Tcl_NRPostProc) (void *data[], Tcl_Interp *interp, int result); /* *---------------------------------------------------------------------------- * The following constant is used to test for older versions of Tcl in the * stubs tables. */ #if TCL_MAJOR_VERSION > 8 # define TCL_STUB_MAGIC ((int) 0xFCA3BACB + (int) sizeof(void *)) #else # define TCL_STUB_MAGIC ((int) 0xFCA3BACF) #endif /* * The following function is required to be defined in all stubs aware * extensions. The function is actually implemented in the stub library, not * the main Tcl library, although there is a trivial implementation in the * main library in case an extension is statically linked into an application. */ const char * Tcl_InitStubs(Tcl_Interp *interp, const char *version, int exact, int magic); const char * TclTomMathInitializeStubs(Tcl_Interp *interp, const char *version, int epoch, int revision); const char * TclInitStubTable(const char *version); void * TclStubCall(void *arg); #if defined(_WIN32) TCL_NORETURN void Tcl_ConsolePanic(const char *format, ...); #else # define Tcl_ConsolePanic ((Tcl_PanicProc *)NULL) #endif #ifdef USE_TCL_STUBS #if TCL_MAJOR_VERSION < 9 # if TCL_UTF_MAX < 4 # define Tcl_InitStubs(interp, version, exact) \ (Tcl_InitStubs)(interp, version, \ (exact)|(TCL_MAJOR_VERSION<<8)|(0xFF<<16), \ TCL_STUB_MAGIC) # else # define Tcl_InitStubs(interp, version, exact) \ (Tcl_InitStubs)(interp, "8.7b1", \ (exact)|(TCL_MAJOR_VERSION<<8)|(0xFF<<16), \ TCL_STUB_MAGIC) # endif #elif TCL_RELEASE_LEVEL == TCL_FINAL_RELEASE # define Tcl_InitStubs(interp, version, exact) \ (Tcl_InitStubs)(interp, version, \ (exact)|(TCL_MAJOR_VERSION<<8)|(TCL_MINOR_VERSION<<16), \ TCL_STUB_MAGIC) #else # define Tcl_InitStubs(interp, version, exact) \ (Tcl_InitStubs)(interp, (((exact)&1) ? (version) : "9.0.0"), \ (exact)|(TCL_MAJOR_VERSION<<8)|(TCL_MINOR_VERSION<<16), \ TCL_STUB_MAGIC) #endif #else #if TCL_MAJOR_VERSION < 9 # define Tcl_InitStubs(interp, version, exact) \ Tcl_Panic(((void)interp, (void)version, \ (void)exact, "Please define -DUSE_TCL_STUBS")) #elif TCL_RELEASE_LEVEL == TCL_FINAL_RELEASE # define Tcl_InitStubs(interp, version, exact) \ Tcl_PkgInitStubsCheck(interp, version, \ (exact)|(TCL_MAJOR_VERSION<<8)|(TCL_MINOR_VERSION<<16)) #else # define Tcl_InitStubs(interp, version, exact) \ Tcl_PkgInitStubsCheck(interp, TCL_PATCH_LEVEL, \ 1|(TCL_MAJOR_VERSION<<8)|(TCL_MINOR_VERSION<<16)) #endif #endif /* * Public functions that are not accessible via the stubs table. * Tcl_GetMemoryInfo is needed for AOLserver. [Bug 1868171] */ #define Tcl_Main(argc, argv, proc) \ Tcl_MainEx(argc, argv, proc, \ ((Tcl_SetPanicProc(Tcl_ConsolePanic), Tcl_CreateInterp()))) EXTERN TCL_NORETURN void Tcl_MainEx(Tcl_Size argc, char **argv, Tcl_AppInitProc *appInitProc, Tcl_Interp *interp); EXTERN const char * Tcl_PkgInitStubsCheck(Tcl_Interp *interp, const char *version, int exact); EXTERN const char * Tcl_InitSubsystems(void); EXTERN void Tcl_GetMemoryInfo(Tcl_DString *dsPtr); EXTERN const char * Tcl_FindExecutable(const char *argv0); EXTERN const char * Tcl_SetPreInitScript(const char *string); EXTERN const char * Tcl_SetPanicProc( Tcl_PanicProc *panicProc); EXTERN void Tcl_StaticLibrary(Tcl_Interp *interp, const char *prefix, Tcl_LibraryInitProc *initProc, Tcl_LibraryInitProc *safeInitProc); #ifndef TCL_NO_DEPRECATED # define Tcl_StaticPackage Tcl_StaticLibrary #endif EXTERN Tcl_ExitProc * Tcl_SetExitProc(Tcl_ExitProc *proc); #ifdef _WIN32 EXTERN const char * TclZipfs_AppHook(int *argc, wchar_t ***argv); #else EXTERN const char *TclZipfs_AppHook(int *argc, char ***argv); #endif #if defined(_WIN32) && defined(UNICODE) #ifndef USE_TCL_STUBS # define Tcl_FindExecutable(arg) ((Tcl_FindExecutable)((const char *)(arg))) #endif # define Tcl_MainEx Tcl_MainExW EXTERN TCL_NORETURN void Tcl_MainExW(Tcl_Size argc, wchar_t **argv, Tcl_AppInitProc *appInitProc, Tcl_Interp *interp); #endif #if defined(USE_TCL_STUBS) && (TCL_MAJOR_VERSION > 8) #define Tcl_SetPanicProc(panicProc) \ TclInitStubTable(((const char *(*)(Tcl_PanicProc *))TclStubCall((void *)panicProc))(panicProc)) #define Tcl_InitSubsystems() \ TclInitStubTable(((const char *(*)(void))TclStubCall((void *)1))()) #define Tcl_FindExecutable(argv0) \ TclInitStubTable(((const char *(*)(const char *))TclStubCall((void *)2))(argv0)) #define TclZipfs_AppHook(argcp, argvp) \ TclInitStubTable(((const char *(*)(int *, void *))TclStubCall((void *)3))(argcp, argvp)) #define Tcl_MainExW(argc, argv, appInitProc, interp) \ (void)((const char *(*)(Tcl_Size, const void *, Tcl_AppInitProc *, Tcl_Interp *)) \ TclStubCall((void *)4))(argc, argv, appInitProc, interp) #if !defined(_WIN32) || !defined(UNICODE) #define Tcl_MainEx(argc, argv, appInitProc, interp) \ (void)((const char *(*)(Tcl_Size, const void *, Tcl_AppInitProc *, Tcl_Interp *)) \ TclStubCall((void *)5))(argc, argv, appInitProc, interp) #endif #define Tcl_StaticLibrary(interp, pkgName, initProc, safeInitProc) \ (void)((const char *(*)(Tcl_Interp *, const char *, Tcl_LibraryInitProc *, Tcl_LibraryInitProc *)) \ TclStubCall((void *)6))(interp, pkgName, initProc, safeInitProc) #define Tcl_SetExitProc(proc) \ ((Tcl_ExitProc *(*)(Tcl_ExitProc *))TclStubCall((void *)7))(proc) #define Tcl_GetMemoryInfo(dsPtr) \ (void)((const char *(*)(Tcl_DString *))TclStubCall((void *)8))(dsPtr) #define Tcl_SetPreInitScript(string) \ ((const char *(*)(const char *))TclStubCall((void *)9))(string) #endif /* *---------------------------------------------------------------------------- * Include the public function declarations that are accessible via the stubs * table. */ #include "tclDecls.h" /* * Include platform specific public function declarations that are accessible * via the stubs table. Make all TclOO symbols MODULE_SCOPE (which only * has effect on building it as a shared library). See ticket [3010352]. */ #if defined(BUILD_tcl) # undef TCLAPI # define TCLAPI MODULE_SCOPE #endif #include "tclPlatDecls.h" /* *---------------------------------------------------------------------------- * The following declarations map ckalloc and ckfree to Tcl_Alloc and * Tcl_Free for use in Tcl-8.x-compatible extensions. */ #ifndef BUILD_tcl # define ckalloc Tcl_Alloc # define attemptckalloc Tcl_AttemptAlloc # ifdef _MSC_VER /* Silence invalid C4090 warnings */ # define ckfree(a) Tcl_Free((void *)(a)) # define ckrealloc(a,b) Tcl_Realloc((void *)(a),(b)) # define attemptckrealloc(a,b) Tcl_AttemptRealloc((void *)(a),(b)) # else # define ckfree Tcl_Free # define ckrealloc Tcl_Realloc # define attemptckrealloc Tcl_AttemptRealloc # endif #endif #ifndef TCL_MEM_DEBUG /* * If we are not using the debugging allocator, we should call the Tcl_Alloc, * et al. routines in order to guarantee that every module is using the same * memory allocator both inside and outside of the Tcl library. */ # undef Tcl_InitMemory # define Tcl_InitMemory(x) # undef Tcl_DumpActiveMemory # define Tcl_DumpActiveMemory(x) # undef Tcl_ValidateAllMemory # define Tcl_ValidateAllMemory(x,y) #endif /* !TCL_MEM_DEBUG */ #ifdef TCL_MEM_DEBUG # undef Tcl_IncrRefCount # define Tcl_IncrRefCount(objPtr) \ Tcl_DbIncrRefCount(objPtr, __FILE__, __LINE__) # undef Tcl_DecrRefCount # define Tcl_DecrRefCount(objPtr) \ Tcl_DbDecrRefCount(objPtr, __FILE__, __LINE__) # undef Tcl_IsShared # define Tcl_IsShared(objPtr) \ Tcl_DbIsShared(objPtr, __FILE__, __LINE__) /* * Free the Obj by effectively doing: * * Tcl_IncrRefCount(objPtr); * Tcl_DecrRefCount(objPtr); * * This will free the obj if there are no references to the obj. */ # define Tcl_BounceRefCount(objPtr) \ TclBounceRefCount(objPtr, __FILE__, __LINE__) static inline void TclBounceRefCount( Tcl_Obj* objPtr, const char* fn, int line) { if (objPtr) { if ((objPtr)->refCount == 0) { Tcl_DbDecrRefCount(objPtr, fn, line); } } } #else # undef Tcl_IncrRefCount # define Tcl_IncrRefCount(objPtr) \ ((void)++(objPtr)->refCount) /* * Use do/while0 idiom for optimum correctness without compiler warnings. * https://wiki.c2.com/?TrivialDoWhileLoop */ # undef Tcl_DecrRefCount # define Tcl_DecrRefCount(objPtr) \ do { \ Tcl_Obj *_objPtr = (objPtr); \ if (_objPtr->refCount-- <= 1) { \ TclFreeObj(_objPtr); \ } \ } while(0) # undef Tcl_IsShared # define Tcl_IsShared(objPtr) \ ((objPtr)->refCount > 1) /* * Declare that obj will no longer be used or referenced. * This will free the obj if there are no references to the obj. */ # define Tcl_BounceRefCount(objPtr) \ TclBounceRefCount(objPtr); static inline void TclBounceRefCount( Tcl_Obj* objPtr) { if (objPtr) { if ((objPtr)->refCount == 0) { Tcl_DecrRefCount(objPtr); } } } #endif /* * Macros and definitions that help to debug the use of Tcl objects. When * TCL_MEM_DEBUG is defined, the Tcl_New declarations are overridden to call * debugging versions of the object creation functions. */ #ifdef TCL_MEM_DEBUG # undef Tcl_NewBignumObj # define Tcl_NewBignumObj(val) \ Tcl_DbNewBignumObj(val, __FILE__, __LINE__) # undef Tcl_NewBooleanObj # define Tcl_NewBooleanObj(val) \ Tcl_DbNewWideIntObj((val)!=0, __FILE__, __LINE__) # undef Tcl_NewByteArrayObj # define Tcl_NewByteArrayObj(bytes, len) \ Tcl_DbNewByteArrayObj(bytes, len, __FILE__, __LINE__) # undef Tcl_NewDoubleObj # define Tcl_NewDoubleObj(val) \ Tcl_DbNewDoubleObj(val, __FILE__, __LINE__) # undef Tcl_NewListObj # define Tcl_NewListObj(objc, objv) \ Tcl_DbNewListObj(objc, objv, __FILE__, __LINE__) # undef Tcl_NewObj # define Tcl_NewObj() \ Tcl_DbNewObj(__FILE__, __LINE__) # undef Tcl_NewStringObj # define Tcl_NewStringObj(bytes, len) \ Tcl_DbNewStringObj(bytes, len, __FILE__, __LINE__) # undef Tcl_NewWideIntObj # define Tcl_NewWideIntObj(val) \ Tcl_DbNewWideIntObj(val, __FILE__, __LINE__) #endif /* TCL_MEM_DEBUG */ /* *---------------------------------------------------------------------------- * Macros for clients to use to access fields of hash entries: */ #define Tcl_GetHashValue(h) ((h)->clientData) #define Tcl_SetHashValue(h, value) ((h)->clientData = (void *)(value)) #define Tcl_GetHashKey(tablePtr, h) \ ((void *) (((tablePtr)->keyType == TCL_ONE_WORD_KEYS || \ (tablePtr)->keyType == TCL_CUSTOM_PTR_KEYS) \ ? (h)->key.oneWordValue \ : (h)->key.string)) /* * Macros to use for clients to use to invoke find and create functions for * hash tables: */ #undef Tcl_FindHashEntry #define Tcl_FindHashEntry(tablePtr, key) \ (*((tablePtr)->findProc))(tablePtr, (const char *)(key)) #undef Tcl_CreateHashEntry #define Tcl_CreateHashEntry(tablePtr, key, newPtr) \ (*((tablePtr)->createProc))(tablePtr, (const char *)(key), newPtr) #endif /* RC_INVOKED */ /* * end block for C++ */ #ifdef __cplusplus } #endif #endif /* _TCL */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclAlloc.c0000644000175000017500000004262414726623136015103 0ustar sergeisergei/* * tclAlloc.c -- * * This is a very fast storage allocator. It allocates blocks of a small * number of different sizes, and keeps free lists of each size. Blocks * that don't exactly fit are passed up to the next larger size. Blocks * over a certain size are directly allocated from the system. * * Copyright © 1983 Regents of the University of California. * Copyright © 1996-1997 Sun Microsystems, Inc. * Copyright © 1998-1999 Scriptics Corporation. * * Portions contributed by Chris Kingsley, Jack Jansen and Ray Johnson. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ /* * Windows and Unix use an alternative allocator when building with threads * that has significantly reduced lock contention. */ #include "tclInt.h" #if !TCL_THREADS || !defined(USE_THREAD_ALLOC) #if defined(USE_TCLALLOC) && USE_TCLALLOC /* * We should really make use of AC_CHECK_TYPE(caddr_t) here, but it can wait * until Tcl uses config.h properly. */ #if defined(_MSC_VER) || defined(__MSVCRT__) typedef size_t caddr_t; #endif /* * The overhead on a block is at least 8 bytes. When free, this space contains * a pointer to the next free block, and the bottom two bits must be zero. * When in use, the first byte is set to MAGIC, and the second byte is the * size index. The remaining bytes are for alignment. If range checking is * enabled then a second word holds the size of the requested block, less 1, * rounded up to a multiple of sizeof(RMAGIC). The order of elements is * critical: ov.magic must overlay the low order bits of ov.next, and ov.magic * can not be a valid ov.next bit pattern. */ union overhead { union overhead *next; /* when free */ unsigned char padding[TCL_ALLOCALIGN]; /* align struct to TCL_ALLOCALIGN bytes */ struct { unsigned char magic0; /* magic number */ unsigned char index; /* bucket # */ unsigned char unused; /* unused */ unsigned char magic1; /* other magic number */ #ifndef NDEBUG unsigned short rmagic; /* range magic number */ size_t size; /* actual block size */ unsigned short unused2; /* padding to 8-byte align */ #endif } ovu; #define overMagic0 ovu.magic0 #define overMagic1 ovu.magic1 #define bucketIndex ovu.index #define rangeCheckMagic ovu.rmagic #define realBlockSize ovu.size }; #define MAGIC 0xEF /* magic # on accounting info */ #define RMAGIC 0x5555 /* magic # on range info */ #ifndef NDEBUG #define RSLOP sizeof(unsigned short) #else #define RSLOP 0 #endif #define OVERHEAD (sizeof(union overhead) + RSLOP) /* * Macro to make it easier to refer to the end-of-block guard magic. */ #define BLOCK_END(overPtr) \ (*(unsigned short *)((caddr_t)((overPtr) + 1) + (overPtr)->realBlockSize)) /* * nextf[i] is the pointer to the next free block of size 2^(i+3). The * smallest allocatable block is MINBLOCK bytes. The overhead information * precedes the data area returned to the user. */ #define MINBLOCK \ ((sizeof(union overhead) + (TCL_ALLOCALIGN-1)) & ~(TCL_ALLOCALIGN-1)) #define NBUCKETS (13 - (MINBLOCK >> 4)) #define MAXMALLOC ((size_t)1 << (NBUCKETS+2)) static union overhead *nextf[NBUCKETS]; /* * The following structure is used to keep track of all system memory * currently owned by Tcl. When finalizing, all this memory will be returned * to the system. */ struct block { struct block *nextPtr; /* Linked list. */ struct block *prevPtr; /* Linked list for big blocks, ensures 8-byte * alignment for suballocated blocks. */ }; static struct block *blockList; /* Tracks the suballocated blocks. */ static struct block bigBlocks={ /* Big blocks aren't suballocated. */ &bigBlocks, &bigBlocks }; /* * The allocator is protected by a special mutex that must be explicitly * initialized. Furthermore, because Tcl_Alloc may be used before anything else * in Tcl, we make this module self-initializing after all with the allocInit * variable. */ #if TCL_THREADS static Tcl_Mutex *allocMutexPtr; #endif static int allocInit = 0; #ifdef MSTATS /* * numMallocs[i] is the difference between the number of mallocs and frees for * a given block size. */ static size_t numMallocs[NBUCKETS+1]; #endif #if !defined(NDEBUG) #define ASSERT(p) if (!(p)) Tcl_Panic(# p) #define RANGE_ASSERT(p) if (!(p)) Tcl_Panic(# p) #else #define ASSERT(p) #define RANGE_ASSERT(p) #endif /* * Prototypes for functions used only in this file. */ static void MoreCore(size_t bucket); /* *------------------------------------------------------------------------- * * TclInitAlloc -- * * Initialize the memory system. * * Results: * None. * * Side effects: * Initialize the mutex used to serialize allocations. * *------------------------------------------------------------------------- */ void TclInitAlloc(void) { if (!allocInit) { allocInit = 1; #if TCL_THREADS allocMutexPtr = Tcl_GetAllocMutex(); #endif } } /* *------------------------------------------------------------------------- * * TclFinalizeAllocSubsystem -- * * Release all resources being used by this subsystem, including * aggressively freeing all memory allocated by TclpAlloc() that has not * yet been released with TclpFree(). * * After this function is called, all memory allocated with TclpAlloc() * should be considered unusable. * * Results: * None. * * Side effects: * This subsystem is self-initializing, since memory can be allocated * before Tcl is formally initialized. After this call, this subsystem * has been reset to its initial state and is usable again. * *------------------------------------------------------------------------- */ void TclFinalizeAllocSubsystem(void) { unsigned int i; struct block *blockPtr, *nextPtr; Tcl_MutexLock(allocMutexPtr); for (blockPtr = blockList; blockPtr != NULL; blockPtr = nextPtr) { nextPtr = blockPtr->nextPtr; TclpSysFree(blockPtr); } blockList = NULL; for (blockPtr = bigBlocks.nextPtr; blockPtr != &bigBlocks; ) { nextPtr = blockPtr->nextPtr; TclpSysFree(blockPtr); blockPtr = nextPtr; } bigBlocks.nextPtr = &bigBlocks; bigBlocks.prevPtr = &bigBlocks; for (i=0 ; i= MAXMALLOC - OVERHEAD) { if (numBytes <= UINT_MAX - OVERHEAD -sizeof(struct block)) { bigBlockPtr = TclpSysAlloc( sizeof(struct block) + OVERHEAD + numBytes); } if (bigBlockPtr == NULL) { Tcl_MutexUnlock(allocMutexPtr); return NULL; } bigBlockPtr->nextPtr = bigBlocks.nextPtr; bigBlocks.nextPtr = bigBlockPtr; bigBlockPtr->prevPtr = &bigBlocks; bigBlockPtr->nextPtr->prevPtr = bigBlockPtr; overPtr = (union overhead *) (bigBlockPtr + 1); overPtr->overMagic0 = overPtr->overMagic1 = MAGIC; overPtr->bucketIndex = 0xFF; #ifdef MSTATS numMallocs[NBUCKETS]++; #endif #ifndef NDEBUG /* * Record allocated size of block and bound space with magic numbers. */ overPtr->realBlockSize = (numBytes + RSLOP - 1) & ~(RSLOP - 1); overPtr->rangeCheckMagic = RMAGIC; BLOCK_END(overPtr) = RMAGIC; #endif Tcl_MutexUnlock(allocMutexPtr); return (void *)(overPtr+1); } /* * Convert amount of memory requested into closest block size stored in * hash buckets which satisfies request. Account for space used per block * for accounting. */ amount = MINBLOCK; /* size of first bucket */ bucket = MINBLOCK >> 4; while (numBytes + OVERHEAD > amount) { amount <<= 1; if (amount == 0) { Tcl_MutexUnlock(allocMutexPtr); return NULL; } bucket++; } ASSERT(bucket < NBUCKETS); /* * If nothing in hash bucket right now, request more memory from the * system. */ if ((overPtr = nextf[bucket]) == NULL) { MoreCore(bucket); if ((overPtr = nextf[bucket]) == NULL) { Tcl_MutexUnlock(allocMutexPtr); return NULL; } } /* * Remove from linked list */ nextf[bucket] = overPtr->next; overPtr->overMagic0 = overPtr->overMagic1 = MAGIC; overPtr->bucketIndex = UCHAR(bucket); #ifdef MSTATS numMallocs[bucket]++; #endif #ifndef NDEBUG /* * Record allocated size of block and bound space with magic numbers. */ overPtr->realBlockSize = (numBytes + RSLOP - 1) & ~(RSLOP - 1); overPtr->rangeCheckMagic = RMAGIC; BLOCK_END(overPtr) = RMAGIC; #endif Tcl_MutexUnlock(allocMutexPtr); return ((char *)(overPtr + 1)); } /* *---------------------------------------------------------------------- * * MoreCore -- * * Allocate more memory to the indicated bucket. * * Assumes Mutex is already held. * * Results: * None. * * Side effects: * Attempts to get more memory from the system. * *---------------------------------------------------------------------- */ static void MoreCore( size_t bucket) /* What bucket to allocate to. */ { union overhead *overPtr; size_t size; /* size of desired block */ size_t amount; /* amount to allocate */ size_t numBlocks; /* how many blocks we get */ struct block *blockPtr; /* * sbrk_size <= 0 only for big, FLUFFY, requests (about 2^30 bytes on a * VAX, I think) or for a negative arg. */ size = ((size_t)1) << (bucket + 3); ASSERT(size > 0); amount = MAXMALLOC; numBlocks = amount / size; ASSERT(numBlocks*size == amount); blockPtr = TclpSysAlloc(sizeof(struct block) + amount); /* no more room! */ if (blockPtr == NULL) { return; } blockPtr->nextPtr = blockList; blockList = blockPtr; overPtr = (union overhead *) (blockPtr + 1); /* * Add new memory allocated to that on free list for this hash bucket. */ nextf[bucket] = overPtr; while (--numBlocks > 0) { overPtr->next = (union overhead *)((caddr_t)overPtr + size); overPtr = (union overhead *)((caddr_t)overPtr + size); } overPtr->next = NULL; } /* *---------------------------------------------------------------------- * * TclpFree -- * * Free memory. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclpFree( void *oldPtr) /* Pointer to memory to free. */ { size_t size; union overhead *overPtr; struct block *bigBlockPtr; if (oldPtr == NULL) { return; } Tcl_MutexLock(allocMutexPtr); overPtr = (union overhead *)((caddr_t)oldPtr - sizeof(union overhead)); ASSERT(overPtr->overMagic0 == MAGIC); /* make sure it was in use */ ASSERT(overPtr->overMagic1 == MAGIC); if (overPtr->overMagic0 != MAGIC || overPtr->overMagic1 != MAGIC) { Tcl_MutexUnlock(allocMutexPtr); return; } RANGE_ASSERT(overPtr->rangeCheckMagic == RMAGIC); RANGE_ASSERT(BLOCK_END(overPtr) == RMAGIC); size = overPtr->bucketIndex; if (size == 0xFF) { #ifdef MSTATS numMallocs[NBUCKETS]--; #endif bigBlockPtr = (struct block *) overPtr - 1; bigBlockPtr->prevPtr->nextPtr = bigBlockPtr->nextPtr; bigBlockPtr->nextPtr->prevPtr = bigBlockPtr->prevPtr; TclpSysFree(bigBlockPtr); Tcl_MutexUnlock(allocMutexPtr); return; } ASSERT(size < NBUCKETS); overPtr->next = nextf[size]; /* also clobbers overMagic */ nextf[size] = overPtr; #ifdef MSTATS numMallocs[size]--; #endif Tcl_MutexUnlock(allocMutexPtr); } /* *---------------------------------------------------------------------- * * TclpRealloc -- * * Reallocate memory. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void * TclpRealloc( void *oldPtr, /* Pointer to alloc'ed block. */ size_t numBytes) /* New size of memory. */ { int i; union overhead *overPtr; struct block *bigBlockPtr; int expensive; size_t maxSize; if (oldPtr == NULL) { return TclpAlloc(numBytes); } Tcl_MutexLock(allocMutexPtr); overPtr = (union overhead *)((caddr_t)oldPtr - sizeof(union overhead)); ASSERT(overPtr->overMagic0 == MAGIC); /* make sure it was in use */ ASSERT(overPtr->overMagic1 == MAGIC); if (overPtr->overMagic0 != MAGIC || overPtr->overMagic1 != MAGIC) { Tcl_MutexUnlock(allocMutexPtr); return NULL; } RANGE_ASSERT(overPtr->rangeCheckMagic == RMAGIC); RANGE_ASSERT(BLOCK_END(overPtr) == RMAGIC); i = overPtr->bucketIndex; /* * If the block isn't in a bin, just realloc it. */ if (i == 0xFF) { struct block *prevPtr, *nextPtr; bigBlockPtr = (struct block *) overPtr - 1; prevPtr = bigBlockPtr->prevPtr; nextPtr = bigBlockPtr->nextPtr; bigBlockPtr = (struct block *) TclpSysRealloc(bigBlockPtr, sizeof(struct block) + OVERHEAD + numBytes); if (bigBlockPtr == NULL) { Tcl_MutexUnlock(allocMutexPtr); return NULL; } if (prevPtr->nextPtr != bigBlockPtr) { /* * If the block has moved, splice the new block into the list * where the old block used to be. */ prevPtr->nextPtr = bigBlockPtr; nextPtr->prevPtr = bigBlockPtr; } overPtr = (union overhead *) (bigBlockPtr + 1); #ifdef MSTATS numMallocs[NBUCKETS]++; #endif #ifndef NDEBUG /* * Record allocated size of block and update magic number bounds. */ overPtr->realBlockSize = (numBytes + RSLOP - 1) & ~(RSLOP - 1); BLOCK_END(overPtr) = RMAGIC; #endif Tcl_MutexUnlock(allocMutexPtr); return (void *)(overPtr+1); } maxSize = (size_t)1 << (i+3); expensive = 0; if (numBytes+OVERHEAD > maxSize) { expensive = 1; } else if (i>0 && numBytes+OVERHEAD < maxSize/2) { expensive = 1; } if (expensive) { void *newPtr; Tcl_MutexUnlock(allocMutexPtr); newPtr = TclpAlloc(numBytes); if (newPtr == NULL) { return NULL; } maxSize -= OVERHEAD; if (maxSize < numBytes) { numBytes = maxSize; } memcpy(newPtr, oldPtr, numBytes); TclpFree(oldPtr); return newPtr; } /* * No need to copy. It fits as-is. */ #ifndef NDEBUG overPtr->realBlockSize = (numBytes + RSLOP - 1) & ~(RSLOP - 1); BLOCK_END(overPtr) = RMAGIC; #endif Tcl_MutexUnlock(allocMutexPtr); return(oldPtr); } /* *---------------------------------------------------------------------- * * mstats -- * * Prints two lines of numbers, one showing the length of the free list * for each size category, the second showing the number of mallocs - * frees for each size category. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ #ifdef MSTATS void mstats( char *s) /* Where to write info. */ { unsigned int i, j; union overhead *overPtr; size_t totalFree = 0, totalUsed = 0; Tcl_MutexLock(allocMutexPtr); fprintf(stderr, "Memory allocation statistics %s\nTclpFree:\t", s); for (i = 0; i < NBUCKETS; i++) { for (j=0, overPtr=nextf[i]; overPtr; overPtr=overPtr->next, j++) { fprintf(stderr, " %u", j); } totalFree += ((size_t)j) * ((size_t)1 << (i + 3)); } fprintf(stderr, "\nused:\t"); for (i = 0; i < NBUCKETS; i++) { fprintf(stderr, " %" TCL_Z_MODIFIER "u", numMallocs[i]); totalUsed += numMallocs[i] * ((size_t)1 << (i + 3)); } fprintf(stderr, "\n\tTotal small in use: %" TCL_Z_MODIFIER "u, total free: %" TCL_Z_MODIFIER "u\n", totalUsed, totalFree); fprintf(stderr, "\n\tNumber of big (>%" TCL_Z_MODIFIER "u) blocks in use: %" TCL_Z_MODIFIER "u\n", MAXMALLOC, numMallocs[NBUCKETS]); Tcl_MutexUnlock(allocMutexPtr); } #endif #else /* !USE_TCLALLOC */ /* *---------------------------------------------------------------------- * * TclpAlloc -- * * Allocate more memory. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ #undef TclpAlloc void * TclpAlloc( size_t numBytes) /* Number of bytes to allocate. */ { return malloc(numBytes); } /* *---------------------------------------------------------------------- * * TclpFree -- * * Free memory. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ #undef TclpFree void TclpFree( void *oldPtr) /* Pointer to memory to free. */ { free(oldPtr); return; } /* *---------------------------------------------------------------------- * * TclpRealloc -- * * Reallocate memory. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void * TclpRealloc( void *oldPtr, /* Pointer to alloced block. */ size_t numBytes) /* New size of memory. */ { return realloc(oldPtr, numBytes); } #endif /* !USE_TCLALLOC */ #else TCL_MAC_EMPTY_FILE(generic_tclAlloc_c) #endif /* !TCL_THREADS */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclArithSeries.c0000644000175000017500000010300614726623136016263 0ustar sergeisergei/* * tclArithSeries.c -- * * This file contains the ArithSeries concrete abstract list * implementation. It implements the inner workings of the lseq command. * * Copyright © 2022 Brian S. Griffin. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include #include /* * The structure below defines the arithmetic series Tcl object type by * means of procedures that can be invoked by generic object code. * * The arithmetic series object is a special case of Tcl list representing * an interval of an arithmetic series in constant space. * * The arithmetic series is internally represented with three integers, * *start*, *end*, and *step*, Where the length is calculated with * the following algorithm: * * if RANGE == 0 THEN * ERROR * if RANGE > 0 * LEN is (((END-START)-1)/STEP) + 1 * else if RANGE < 0 * LEN is (((END-START)-1)/STEP) - 1 * * And where the equivalent's list I-th element is calculated * as: * * LIST[i] = START + (STEP * i) * * Zero elements ranges, like in the case of START=10 END=10 STEP=1 * are valid and will be equivalent to the empty list. */ /* * The structure used for the ArithSeries internal representation. * Note that the len can in theory be always computed by start,end,step * but it's faster to cache it inside the internal representation. */ typedef struct { Tcl_Size len; Tcl_Obj **elements; int isDouble; } ArithSeries; typedef struct { ArithSeries base; Tcl_WideInt start; Tcl_WideInt end; Tcl_WideInt step; } ArithSeriesInt; typedef struct { ArithSeries base; double start; double end; double step; unsigned precision; /* Number of decimal places to render. */ } ArithSeriesDbl; /* Forward declarations. */ static int TclArithSeriesObjIndex(TCL_UNUSED(Tcl_Interp *), Tcl_Obj *arithSeriesObj, Tcl_Size index, Tcl_Obj **elemObj); static Tcl_Size ArithSeriesObjLength(Tcl_Obj *arithSeriesObj); static int TclArithSeriesObjRange(Tcl_Interp *interp, Tcl_Obj *arithSeriesObj, Tcl_Size fromIdx, Tcl_Size toIdx, Tcl_Obj **newObjPtr); static int TclArithSeriesObjReverse(Tcl_Interp *interp, Tcl_Obj *arithSeriesObj, Tcl_Obj **newObjPtr); static int TclArithSeriesGetElements(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size *objcPtr, Tcl_Obj ***objvPtr); static void DupArithSeriesInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr); static void FreeArithSeriesInternalRep(Tcl_Obj *arithSeriesObjPtr); static void UpdateStringOfArithSeries(Tcl_Obj *arithSeriesObjPtr); static int SetArithSeriesFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); static int ArithSeriesInOperation(Tcl_Interp *interp, Tcl_Obj *valueObj, Tcl_Obj *arithSeriesObj, int *boolResult); static int TclArithSeriesObjStep(Tcl_Obj *arithSeriesObj, Tcl_Obj **stepObj); /* ------------------------ ArithSeries object type -------------------------- */ static const Tcl_ObjType arithSeriesType = { "arithseries", /* name */ FreeArithSeriesInternalRep, /* freeIntRepProc */ DupArithSeriesInternalRep, /* dupIntRepProc */ UpdateStringOfArithSeries, /* updateStringProc */ SetArithSeriesFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V2( ArithSeriesObjLength, TclArithSeriesObjIndex, TclArithSeriesObjRange, TclArithSeriesObjReverse, TclArithSeriesGetElements, NULL, // SetElement NULL, // Replace ArithSeriesInOperation) // "in" operator }; /* * Helper functions * * - power10 -- Fast version of pow(10, (int) n) for common cases. * - ArithRound -- Round doubles to the number of significant fractional * digits * - ArithSeriesIndexDbl -- base list indexing operation for doubles * - ArithSeriesIndexInt -- " " " " " integers * - ArithSeriesGetInternalRep -- Return the internal rep from a Tcl_Obj * - Precision -- determine the number of factional digits for the given * double value * - maxPrecision -- Using the values provide, determine the longest percision * in the arithSeries */ static inline double power10( unsigned n) { static const double powers[] = { 1, 10, 100, 1000, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20 }; if (n < sizeof(powers) / sizeof(*powers)) { return powers[n]; } else { // Not an expected case. Doesn't need to be so fast return pow(10, n); } } static inline double ArithRound( double d, unsigned n) { double scalefactor = power10(n); return round(d * scalefactor) / scalefactor; } static inline double ArithSeriesIndexDbl( ArithSeries *arithSeriesRepPtr, Tcl_WideInt index) { if (arithSeriesRepPtr->isDouble) { ArithSeriesDbl *dblRepPtr = (ArithSeriesDbl *) arithSeriesRepPtr; double d = dblRepPtr->start + (index * dblRepPtr->step); return ArithRound(d, dblRepPtr->precision); } else { ArithSeriesInt *intRepPtr = (ArithSeriesInt *) arithSeriesRepPtr; return (double)(intRepPtr->start + (index * intRepPtr->step)); } } static inline Tcl_WideInt ArithSeriesIndexInt( ArithSeries *arithSeriesRepPtr, Tcl_WideInt index) { if (arithSeriesRepPtr->isDouble) { ArithSeriesDbl *dblRepPtr = (ArithSeriesDbl *) arithSeriesRepPtr; return (Tcl_WideInt) (dblRepPtr->start + (index * dblRepPtr->step)); } else { ArithSeriesInt *intRepPtr = (ArithSeriesInt *) arithSeriesRepPtr; return intRepPtr->start + (index * intRepPtr->step); } } static inline ArithSeries * ArithSeriesGetInternalRep( Tcl_Obj *objPtr) { const Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(objPtr, &arithSeriesType); return irPtr ? (ArithSeries *) irPtr->twoPtrValue.ptr1 : NULL; } /* * Compute number of significant fractional digits */ static inline unsigned Precision( double d) { char tmp[TCL_DOUBLE_SPACE + 2], *off; tmp[0] = 0; Tcl_PrintDouble(NULL, d, tmp); off = strchr(tmp, '.'); return (off ? strlen(off + 1) : 0); } /* * Find longest number of digits after the decimal point. */ static inline unsigned maxPrecision( double start, double end, double step) { unsigned dp = Precision(step); unsigned i = Precision(start); dp = i>dp ? i : dp; i = Precision(end); dp = i>dp ? i : dp; return dp; } /* *---------------------------------------------------------------------- * * ArithSeriesLen -- * * Compute the length of the equivalent list where * every element is generated starting from *start*, * and adding *step* to generate every successive element * that's < *end* for positive steps, or > *end* for negative * steps. * * Results: * The length of the list generated by the given range, * that may be zero. * The function returns -1 if the list is of length infinite. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_WideInt ArithSeriesLenInt( Tcl_WideInt start, Tcl_WideInt end, Tcl_WideInt step) { Tcl_WideInt len; if (step == 0) { return 0; } len = 1 + ((end - start) / step); return (len < 0) ? -1 : len; } static Tcl_WideInt ArithSeriesLenDbl( double start, double end, double step, unsigned precision) { double istart, iend, istep, ilen; if (step == 0) { return 0; } istart = start * power10(precision); iend = end * power10(precision); istep = step * power10(precision); ilen = (iend - istart + istep) / istep; return floor(ilen); } /* *---------------------------------------------------------------------- * * DupArithSeriesInternalRep -- * * Initialize the internal representation of a arithseries Tcl_Obj to a * copy of the internal representation of an existing arithseries object. * The copy does not share the cache of the elements. * * Results: * None. * * Side effects: * We set "copyPtr"s internal rep to a pointer to a * newly allocated ArithSeries structure. * *---------------------------------------------------------------------- */ static void DupArithSeriesInternalRep( Tcl_Obj *srcPtr, /* Object with internal rep to copy. */ Tcl_Obj *copyPtr) /* Object with internal rep to set. */ { ArithSeries *srcRepPtr = (ArithSeries *) srcPtr->internalRep.twoPtrValue.ptr1; if (srcRepPtr->isDouble) { ArithSeriesDbl *srcDblPtr = (ArithSeriesDbl *) srcRepPtr; ArithSeriesDbl *copyDblPtr = (ArithSeriesDbl *) Tcl_Alloc(sizeof(ArithSeriesDbl)); *copyDblPtr = *srcDblPtr; copyDblPtr->base.elements = NULL; copyPtr->internalRep.twoPtrValue.ptr1 = copyDblPtr; } else { ArithSeriesInt *srcIntPtr = (ArithSeriesInt *) srcRepPtr; ArithSeriesInt *copyIntPtr = (ArithSeriesInt *) Tcl_Alloc(sizeof(ArithSeriesInt)); *copyIntPtr = *srcIntPtr; copyIntPtr->base.elements = NULL; copyPtr->internalRep.twoPtrValue.ptr1 = copyIntPtr; } copyPtr->internalRep.twoPtrValue.ptr2 = NULL; copyPtr->typePtr = &arithSeriesType; } /* *---------------------------------------------------------------------- * * FreeArithSeriesInternalRep -- * * Free any allocated memory in the ArithSeries Rep * * Results: * None. * * Side effects: * *---------------------------------------------------------------------- */ static inline void FreeElements( ArithSeries *arithSeriesRepPtr) { if (arithSeriesRepPtr->elements) { Tcl_WideInt i, len = arithSeriesRepPtr->len; for (i=0; ielements[i]); } Tcl_Free((void *)arithSeriesRepPtr->elements); arithSeriesRepPtr->elements = NULL; } } static void FreeArithSeriesInternalRep( Tcl_Obj *arithSeriesObjPtr) { ArithSeries *arithSeriesRepPtr = (ArithSeries *) arithSeriesObjPtr->internalRep.twoPtrValue.ptr1; if (arithSeriesRepPtr) { FreeElements(arithSeriesRepPtr); Tcl_Free((void *)arithSeriesRepPtr); } } /* *---------------------------------------------------------------------- * * NewArithSeriesInt -- * * Creates a new ArithSeries object. The returned object has * refcount = 0. * * Results: * A Tcl_Obj pointer to the created ArithSeries object. * A NULL pointer of the range is invalid. * * Side Effects: * None. * *---------------------------------------------------------------------- */ static Tcl_Obj * NewArithSeriesInt( Tcl_WideInt start, Tcl_WideInt end, Tcl_WideInt step, Tcl_WideInt len) { Tcl_WideInt length; Tcl_Obj *arithSeriesObj; ArithSeriesInt *arithSeriesRepPtr; length = len>=0 ? len : -1; if (length < 0) { length = -1; } TclNewObj(arithSeriesObj); if (length <= 0) { return arithSeriesObj; } arithSeriesRepPtr = (ArithSeriesInt *) Tcl_Alloc(sizeof(ArithSeriesInt)); arithSeriesRepPtr->base.len = length; arithSeriesRepPtr->base.elements = NULL; arithSeriesRepPtr->base.isDouble = 0; arithSeriesRepPtr->start = start; arithSeriesRepPtr->end = end; arithSeriesRepPtr->step = step; arithSeriesObj->internalRep.twoPtrValue.ptr1 = arithSeriesRepPtr; arithSeriesObj->internalRep.twoPtrValue.ptr2 = NULL; arithSeriesObj->typePtr = &arithSeriesType; if (length > 0) { Tcl_InvalidateStringRep(arithSeriesObj); } return arithSeriesObj; } /* *---------------------------------------------------------------------- * * NewArithSeriesDbl -- * * Creates a new ArithSeries object with doubles. The returned object has * refcount = 0. * * Results: * A Tcl_Obj pointer to the created ArithSeries object. * A NULL pointer of the range is invalid. * * Side Effects: * None. *---------------------------------------------------------------------- */ static Tcl_Obj * NewArithSeriesDbl( double start, double end, double step, Tcl_WideInt len) { Tcl_WideInt length; Tcl_Obj *arithSeriesObj; ArithSeriesDbl *arithSeriesRepPtr; length = len>=0 ? len : -1; if (length < 0) { length = -1; } TclNewObj(arithSeriesObj); if (length <= 0) { return arithSeriesObj; } arithSeriesRepPtr = (ArithSeriesDbl *) Tcl_Alloc(sizeof(ArithSeriesDbl)); arithSeriesRepPtr->base.len = length; arithSeriesRepPtr->base.elements = NULL; arithSeriesRepPtr->base.isDouble = 1; arithSeriesRepPtr->start = start; arithSeriesRepPtr->end = end; arithSeriesRepPtr->step = step; arithSeriesRepPtr->precision = maxPrecision(start, end, step); arithSeriesObj->internalRep.twoPtrValue.ptr1 = arithSeriesRepPtr; arithSeriesObj->internalRep.twoPtrValue.ptr2 = NULL; arithSeriesObj->typePtr = &arithSeriesType; if (length > 0) { Tcl_InvalidateStringRep(arithSeriesObj); } return arithSeriesObj; } /* *---------------------------------------------------------------------- * * assignNumber -- * * Create the appropriate Tcl_Obj value for the given numeric values. * Used locally only for decoding [lseq] numeric arguments. * refcount = 0. * * Results: * A Tcl_Obj pointer. No assignment on error. * * Side Effects: * None. *---------------------------------------------------------------------- */ static int assignNumber( Tcl_Interp *interp, int useDoubles, Tcl_WideInt *intNumberPtr, double *dblNumberPtr, Tcl_Obj *numberObj) { void *clientData; int tcl_number_type; if (Tcl_GetNumberFromObj(interp, numberObj, &clientData, &tcl_number_type) != TCL_OK) { return TCL_ERROR; } if (tcl_number_type == TCL_NUMBER_BIG) { /* bignum is not supported yet. */ Tcl_WideInt w; (void)Tcl_GetWideIntFromObj(interp, numberObj, &w); return TCL_ERROR; } if (useDoubles) { if (tcl_number_type != TCL_NUMBER_INT) { *dblNumberPtr = *(double *)clientData; } else { *dblNumberPtr = (double)*(Tcl_WideInt *)clientData; } } else { if (tcl_number_type == TCL_NUMBER_INT) { *intNumberPtr = *(Tcl_WideInt *)clientData; } else { *intNumberPtr = (Tcl_WideInt)*(double *)clientData; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclNewArithSeriesObj -- * * Creates a new ArithSeries object. Some arguments may be NULL and will * be computed based on the other given arguments. * refcount = 0. * * Results: * A Tcl_Obj pointer to the created ArithSeries object. * NULL if the range is invalid. * * Side Effects: * None. *---------------------------------------------------------------------- */ Tcl_Obj * TclNewArithSeriesObj( Tcl_Interp *interp, /* For error reporting */ int useDoubles, /* Flag indicates values start, ** end, step, are treated as doubles */ Tcl_Obj *startObj, /* Starting value */ Tcl_Obj *endObj, /* Ending limit */ Tcl_Obj *stepObj, /* increment value */ Tcl_Obj *lenObj) /* Number of elements */ { double dstart, dend, dstep; Tcl_WideInt start, end, step; Tcl_WideInt len = -1; Tcl_Obj *objPtr; if (startObj) { if (assignNumber(interp, useDoubles, &start, &dstart, startObj) != TCL_OK) { return NULL; } } else { start = 0; dstart = start; } if (stepObj) { if (assignNumber(interp, useDoubles, &step, &dstep, stepObj) != TCL_OK) { return NULL; } if (useDoubles) { step = dstep; } else { dstep = step; } if (dstep == 0) { TclNewObj(objPtr); return objPtr; } } if (endObj) { if (assignNumber(interp, useDoubles, &end, &dend, endObj) != TCL_OK) { return NULL; } } if (lenObj) { if (TCL_OK != Tcl_GetWideIntFromObj(interp, lenObj, &len)) { return NULL; } } if (startObj && endObj) { if (!stepObj) { if (useDoubles) { dstep = (dstart < dend) ? 1.0 : -1.0; step = dstep; } else { step = (start < end) ? 1 : -1; dstep = step; } } assert(dstep!=0); if (!lenObj) { if (useDoubles) { unsigned precision = maxPrecision(dstart, dend, dstep); len = ArithSeriesLenDbl(dstart, dend, dstep, precision); } else { len = ArithSeriesLenInt(start, end, step); } } } if (!endObj) { if (useDoubles) { // Compute precision based on given command argument values unsigned precision = maxPrecision(dstart, len, dstep); dend = dstart + (dstep * (len-1)); // Make computed end value match argument(s) precision dend = ArithRound(dend, precision); end = dend; } else { end = start + (step * (len - 1)); dend = end; } } if (len > TCL_SIZE_MAX) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "max length of a Tcl list exceeded", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); return NULL; } objPtr = (useDoubles) ? NewArithSeriesDbl(dstart, dend, dstep, len) : NewArithSeriesInt(start, end, step, len); return objPtr; } /* *---------------------------------------------------------------------- * * TclArithSeriesObjIndex -- * * Returns the element with the specified index in the list * represented by the specified Arithmetic Sequence object. * If the index is out of range, TCL_ERROR is returned, * otherwise TCL_OK is returned and the integer value of the * element is stored in *element. * * Results: * TCL_OK on success. * * Side Effects: * On success, the integer pointed by *element is modified. * An empty string ("") is assigned if index is out-of-bounds. * *---------------------------------------------------------------------- */ int TclArithSeriesObjIndex( TCL_UNUSED(Tcl_Interp *), Tcl_Obj *arithSeriesObj, /* List obj */ Tcl_Size index, /* index to element of interest */ Tcl_Obj **elemObj) /* Return value */ { ArithSeries *arithSeriesRepPtr = ArithSeriesGetInternalRep(arithSeriesObj); if (index < 0 || arithSeriesRepPtr->len <= index) { *elemObj = NULL; } else { /* List[i] = Start + (Step * index) */ if (arithSeriesRepPtr->isDouble) { *elemObj = Tcl_NewDoubleObj(ArithSeriesIndexDbl(arithSeriesRepPtr, index)); } else { *elemObj = Tcl_NewWideIntObj(ArithSeriesIndexInt(arithSeriesRepPtr, index)); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * ArithSeriesObjLength * * Returns the length of the arithmetic series. * * Results: * The length of the series as Tcl_WideInt. * * Side Effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size ArithSeriesObjLength( Tcl_Obj *arithSeriesObj) { ArithSeries *arithSeriesRepPtr = (ArithSeries *) arithSeriesObj->internalRep.twoPtrValue.ptr1; return arithSeriesRepPtr->len; } /* *---------------------------------------------------------------------- * * TclArithSeriesObjStep -- * * Return a Tcl_Obj with the step value from the give ArithSeries Obj. * refcount = 0. * * Results: * A Tcl_Obj pointer to the created ArithSeries object. * A NULL pointer of the range is invalid. * * Side Effects: * None. *---------------------------------------------------------------------- */ int TclArithSeriesObjStep( Tcl_Obj *arithSeriesObj, Tcl_Obj **stepObj) { ArithSeries *arithSeriesRepPtr = ArithSeriesGetInternalRep(arithSeriesObj); if (arithSeriesRepPtr->isDouble) { *stepObj = Tcl_NewDoubleObj(((ArithSeriesDbl *) arithSeriesRepPtr)->step); } else { *stepObj = Tcl_NewWideIntObj(((ArithSeriesInt *) arithSeriesRepPtr)->step); } return TCL_OK; } /* *---------------------------------------------------------------------- * * SetArithSeriesFromAny -- * * The Arithmetic Series object is just an way to optimize * Lists space complexity, so no one should try to convert * a string to an Arithmetic Series object. * * This function is here just to populate the Type structure. * * Results: * The result is always TCL_ERROR. But see Side Effects. * * Side effects: * Tcl Panic if called. * *---------------------------------------------------------------------- */ static int SetArithSeriesFromAny( TCL_UNUSED(Tcl_Interp *), /* Used for error reporting if not NULL. */ TCL_UNUSED(Tcl_Obj *)) /* The object to convert. */ { Tcl_Panic("SetArithSeriesFromAny: should never be called"); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TclArithSeriesObjRange -- * * Makes a slice of an ArithSeries value. * *arithSeriesObj must be known to be a valid list. * * Results: * Returns a pointer to the sliced series. * This may be a new object or the same object if not shared. * * Side effects: * ?The possible conversion of the object referenced by listPtr? * ?to a list object.? * *---------------------------------------------------------------------- */ int TclArithSeriesObjRange( Tcl_Interp *interp, /* For error message(s) */ Tcl_Obj *arithSeriesObj, /* List object to take a range from. */ Tcl_Size fromIdx, /* Index of first element to include. */ Tcl_Size toIdx, /* Index of last element to include. */ Tcl_Obj **newObjPtr) /* return value */ { ArithSeries *arithSeriesRepPtr; Tcl_Obj *startObj, *endObj, *stepObj; (void)interp; /* silence compiler */ arithSeriesRepPtr = ArithSeriesGetInternalRep(arithSeriesObj); if (fromIdx == TCL_INDEX_NONE) { fromIdx = 0; } if (toIdx >= arithSeriesRepPtr->len) { toIdx = arithSeriesRepPtr->len-1; } if (fromIdx > toIdx || fromIdx >= arithSeriesRepPtr->len) { TclNewObj(*newObjPtr); return TCL_OK; } if (fromIdx < 0) { fromIdx = 0; } if (toIdx < 0) { toIdx = 0; } if (toIdx > arithSeriesRepPtr->len - 1) { toIdx = arithSeriesRepPtr->len - 1; } TclArithSeriesObjIndex(interp, arithSeriesObj, fromIdx, &startObj); Tcl_IncrRefCount(startObj); TclArithSeriesObjIndex(interp, arithSeriesObj, toIdx, &endObj); Tcl_IncrRefCount(endObj); TclArithSeriesObjStep(arithSeriesObj, &stepObj); Tcl_IncrRefCount(stepObj); if (Tcl_IsShared(arithSeriesObj) || ((arithSeriesObj->refCount > 1))) { Tcl_Obj *newSlicePtr = TclNewArithSeriesObj(interp, arithSeriesRepPtr->isDouble, startObj, endObj, stepObj, NULL); *newObjPtr = newSlicePtr; Tcl_DecrRefCount(startObj); Tcl_DecrRefCount(endObj); Tcl_DecrRefCount(stepObj); return newSlicePtr ? TCL_OK : TCL_ERROR; } /* * In-place is possible. */ /* * Even if nothing below causes any changes, we still want the * string-canonizing effect of [lrange 0 end]. */ TclInvalidateStringRep(arithSeriesObj); if (arithSeriesRepPtr->isDouble) { ArithSeriesDbl *dblRepPtr = (ArithSeriesDbl *) arithSeriesRepPtr; double start, end, step; Tcl_GetDoubleFromObj(NULL, startObj, &start); Tcl_GetDoubleFromObj(NULL, endObj, &end); Tcl_GetDoubleFromObj(NULL, stepObj, &step); dblRepPtr->start = start; dblRepPtr->end = end; dblRepPtr->step = step; dblRepPtr->precision = maxPrecision(start, end, step); FreeElements(arithSeriesRepPtr); dblRepPtr->base.len = ArithSeriesLenDbl(start, end, step, dblRepPtr->precision); } else { ArithSeriesInt *intRepPtr = (ArithSeriesInt *) arithSeriesRepPtr; Tcl_WideInt start, end, step; Tcl_GetWideIntFromObj(NULL, startObj, &start); Tcl_GetWideIntFromObj(NULL, endObj, &end); Tcl_GetWideIntFromObj(NULL, stepObj, &step); intRepPtr->start = start; intRepPtr->end = end; intRepPtr->step = step; FreeElements(arithSeriesRepPtr); intRepPtr->base.len = ArithSeriesLenInt(start, end, step); } Tcl_DecrRefCount(startObj); Tcl_DecrRefCount(endObj); Tcl_DecrRefCount(stepObj); *newObjPtr = arithSeriesObj; return TCL_OK; } /* *---------------------------------------------------------------------- * * TclArithSeriesGetElements -- * * This function returns an (objc,objv) array of the elements in a list * object. * * Results: * The return value is normally TCL_OK; in this case *objcPtr is set to * the count of list elements and *objvPtr is set to a pointer to an * array of (*objcPtr) pointers to each list element. If listPtr does not * refer to an Abstract List object and the object can not be converted * to one, TCL_ERROR is returned and an error message will be left in the * interpreter's result if interp is not NULL. * * The objects referenced by the returned array should be treated as * readonly and their ref counts are _not_ incremented; the caller must * do that if it holds on to a reference. Furthermore, the pointer and * length returned by this function may change as soon as any function is * called on the list object; be careful about retaining the pointer in a * local data structure. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclArithSeriesGetElements( Tcl_Interp *interp, /* Used to report errors if not NULL. */ Tcl_Obj *objPtr, /* ArithSeries object for which an element * array is to be returned. */ Tcl_Size *objcPtr, /* Where to store the count of objects * referenced by objv. */ Tcl_Obj ***objvPtr) /* Where to store the pointer to an array of * pointers to the list's objects. */ { if (TclHasInternalRep(objPtr, &arithSeriesType)) { ArithSeries *arithSeriesRepPtr = ArithSeriesGetInternalRep(objPtr); Tcl_Obj **objv; Tcl_Size objc = arithSeriesRepPtr->len; if (objc > 0) { if (arithSeriesRepPtr->elements) { /* If this exists, it has already been populated */ objv = arithSeriesRepPtr->elements; } else { /* Construct the elements array */ objv = (Tcl_Obj **) Tcl_Alloc(sizeof(Tcl_Obj*) * objc); if (objv == NULL) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "max length of a Tcl list exceeded", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); } return TCL_ERROR; } arithSeriesRepPtr->elements = objv; Tcl_Size i; for (i = 0; i < objc; i++) { int status = TclArithSeriesObjIndex(interp, objPtr, i, &objv[i]); if (status) { return TCL_ERROR; } Tcl_IncrRefCount(objv[i]); } } } else { objv = NULL; } *objvPtr = objv; *objcPtr = objc; } else { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "value is not an arithseries", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "UNKNOWN", (char *)NULL); } return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclArithSeriesObjReverse -- * * Reverse the order of the ArithSeries value. The arithSeriesObj is * assumed to be a valid ArithSeries. The new Obj has the Start and End * values appropriately swapped and the Step value sign is changed. * * Results: * The result will be an ArithSeries in the reverse order. * * Side effects: * The ogiginal obj will be modified and returned if it is not Shared. * *---------------------------------------------------------------------- */ int TclArithSeriesObjReverse( Tcl_Interp *interp, /* For error message(s) */ Tcl_Obj *arithSeriesObj, /* List object to reverse. */ Tcl_Obj **newObjPtr) { ArithSeries *arithSeriesRepPtr; Tcl_Obj *startObj, *endObj, *stepObj; Tcl_Obj *resultObj; Tcl_WideInt start, end, step, len; double dstart, dend, dstep; int isDouble; (void)interp; if (newObjPtr == NULL) { return TCL_ERROR; } arithSeriesRepPtr = ArithSeriesGetInternalRep(arithSeriesObj); isDouble = arithSeriesRepPtr->isDouble; len = arithSeriesRepPtr->len; TclArithSeriesObjIndex(NULL, arithSeriesObj, len - 1, &startObj); Tcl_IncrRefCount(startObj); TclArithSeriesObjIndex(NULL, arithSeriesObj, 0, &endObj); Tcl_IncrRefCount(endObj); TclArithSeriesObjStep(arithSeriesObj, &stepObj); Tcl_IncrRefCount(stepObj); if (isDouble) { Tcl_GetDoubleFromObj(NULL, startObj, &dstart); Tcl_GetDoubleFromObj(NULL, endObj, &dend); Tcl_GetDoubleFromObj(NULL, stepObj, &dstep); dstep = -dstep; TclSetDoubleObj(stepObj, dstep); } else { Tcl_GetWideIntFromObj(NULL, startObj, &start); Tcl_GetWideIntFromObj(NULL, endObj, &end); Tcl_GetWideIntFromObj(NULL, stepObj, &step); step = -step; TclSetIntObj(stepObj, step); } if (Tcl_IsShared(arithSeriesObj) || (arithSeriesObj->refCount > 1)) { Tcl_Obj *lenObj; TclNewIntObj(lenObj, len); resultObj = TclNewArithSeriesObj(interp, isDouble, startObj, endObj, stepObj, lenObj); Tcl_DecrRefCount(lenObj); } else { /* * In-place is possible. */ TclInvalidateStringRep(arithSeriesObj); if (isDouble) { ArithSeriesDbl *dblRepPtr = (ArithSeriesDbl *) arithSeriesRepPtr; dblRepPtr->start = dstart; dblRepPtr->end = dend; dblRepPtr->step = dstep; } else { ArithSeriesInt *intRepPtr = (ArithSeriesInt *) arithSeriesRepPtr; intRepPtr->start = start; intRepPtr->end = end; intRepPtr->step = step; } FreeElements(arithSeriesRepPtr); resultObj = arithSeriesObj; } Tcl_DecrRefCount(startObj); Tcl_DecrRefCount(endObj); Tcl_DecrRefCount(stepObj); *newObjPtr = resultObj; return resultObj ? TCL_OK : TCL_ERROR; } /* *---------------------------------------------------------------------- * * UpdateStringOfArithSeries -- * * Update the string representation for an arithseries object. * Note: This procedure does not invalidate an existing old string rep * so storage will be lost if this has not already been done. * * Results: * None. * * Side effects: * The object's string is set to a valid string that results from * the list-to-string conversion. This string will be empty if the * list has no elements. The list internal representation * should not be NULL and we assume it is not NULL. * * Notes: * At the cost of overallocation it's possible to estimate * the length of the string representation and make this procedure * much faster. Because the programmer shouldn't expect the * string conversion of a big arithmetic sequence to be fast * this version takes more care of space than time. * *---------------------------------------------------------------------- */ static void UpdateStringOfArithSeries( Tcl_Obj *arithSeriesObjPtr) { ArithSeries *arithSeriesRepPtr = (ArithSeries *) arithSeriesObjPtr->internalRep.twoPtrValue.ptr1; char *p; Tcl_Obj *eleObj; Tcl_Size i, bytlen = 0; /* * Pass 1: estimate space. */ if (!arithSeriesRepPtr->isDouble) { for (i = 0; i < arithSeriesRepPtr->len; i++) { double d = ArithSeriesIndexInt(arithSeriesRepPtr, i); size_t slen = d>0 ? log10(d)+1 : d<0 ? log10(-d)+2 : 1; bytlen += slen; } } else { for (i = 0; i < arithSeriesRepPtr->len; i++) { double d = ArithSeriesIndexDbl(arithSeriesRepPtr, i); char tmp[TCL_DOUBLE_SPACE + 2]; tmp[0] = 0; Tcl_PrintDouble(NULL,d,tmp); if ((bytlen + strlen(tmp)) > TCL_SIZE_MAX) { break; // overflow } bytlen += strlen(tmp); } } bytlen += arithSeriesRepPtr->len; // Space for each separator /* * Pass 2: generate the string repr. */ p = Tcl_InitStringRep(arithSeriesObjPtr, NULL, bytlen); for (i = 0; i < arithSeriesRepPtr->len; i++) { if (TclArithSeriesObjIndex(NULL, arithSeriesObjPtr, i, &eleObj) == TCL_OK) { Tcl_Size slen; char *str = TclGetStringFromObj(eleObj, &slen); strcpy(p, str); p[slen] = ' '; p += slen + 1; Tcl_DecrRefCount(eleObj); } // else TODO: report error here? } if (bytlen > 0) { arithSeriesObjPtr->bytes[bytlen - 1] = '\0'; } arithSeriesObjPtr->length = bytlen - 1; } /* *---------------------------------------------------------------------- * * ArithSeriesInOperator -- * * Evaluate the "in" operation for expr * * This can be done more efficiently in the Arith Series relative to * doing a linear search as implemented in expr. * * Results: * Boolean true or false (1/0) * * Side effects: * None * *---------------------------------------------------------------------- */ static int ArithSeriesInOperation( Tcl_Interp *interp, Tcl_Obj *valueObj, Tcl_Obj *arithSeriesObjPtr, int *boolResult) { ArithSeries *repPtr = (ArithSeries *) arithSeriesObjPtr->internalRep.twoPtrValue.ptr1; int status; Tcl_Size index, incr, elen, vlen; if (repPtr->isDouble) { ArithSeriesDbl *dblRepPtr = (ArithSeriesDbl *) repPtr; double y; int test = 0; incr = 0; // Check index+incr where incr is 0 and 1 status = Tcl_GetDoubleFromObj(interp, valueObj, &y); if (status != TCL_OK) { test = 0; } else { const char *vstr = TclGetStringFromObj(valueObj, &vlen); index = (y - dblRepPtr->start) / dblRepPtr->step; while (incr<2) { Tcl_Obj *elemObj; elen = 0; TclArithSeriesObjIndex(interp, arithSeriesObjPtr, (index+incr), &elemObj); const char *estr = elemObj ? TclGetStringFromObj(elemObj, &elen) : ""; /* "in" operation defined as a string compare */ test = (elen == vlen) ? (memcmp(estr, vstr, elen) == 0) : 0; Tcl_BounceRefCount(elemObj); /* Stop if we have a match */ if (test) { break; } incr++; } } if (boolResult) { *boolResult = test; } } else { ArithSeriesInt *intRepPtr = (ArithSeriesInt *) repPtr; Tcl_WideInt y; status = Tcl_GetWideIntFromObj(NULL, valueObj, &y); if (status != TCL_OK) { if (boolResult) { *boolResult = 0; } } else { Tcl_Obj *elemObj; elen = 0; index = (y - intRepPtr->start) / intRepPtr->step; TclArithSeriesObjIndex(interp, arithSeriesObjPtr, index, &elemObj); char const *vstr = TclGetStringFromObj(valueObj, &vlen); char const *estr = elemObj ? TclGetStringFromObj(elemObj, &elen) : ""; if (boolResult) { *boolResult = (elen == vlen) ? (memcmp(estr, vstr, elen) == 0) : 0; } Tcl_BounceRefCount(elemObj); } } return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclAssembly.c0000644000175000017500000041022214726623136015621 0ustar sergeisergei/* * tclAssembly.c -- * * Assembler for Tcl bytecodes. * * This file contains the procedures that convert Tcl Assembly Language (TAL) * to a sequence of bytecode instructions for the Tcl execution engine. * * Copyright © 2010 Ozgur Dogan Ugurlu. * Copyright © 2010 Kevin B. Kenny. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ /*- *- THINGS TO DO: *- More instructions: *- done - alternate exit point (affects stack and exception range checking) *- break and continue - if exception ranges can be sorted out. *- foreach_start4, foreach_step4 *- returnImm, returnStk *- expandStart, expandStkTop, invokeExpanded, expandDrop *- dictFirst, dictNext, dictDone *- dictUpdateStart, dictUpdateEnd *- jumpTable testing *- syntax (?) *- returnCodeBranch *- tclooNext, tclooNextClass */ #include "tclInt.h" #include "tclCompile.h" #include "tclOOInt.h" #include /* * Structure that represents a range of instructions in the bytecode. */ typedef struct CodeRange { int startOffset; /* Start offset in the bytecode array */ int endOffset; /* End offset in the bytecode array */ } CodeRange; /* * State identified for a basic block's catch context. */ typedef enum BasicBlockCatchState { BBCS_UNKNOWN = 0, /* Catch context has not yet been identified */ BBCS_NONE, /* Block is outside of any catch */ BBCS_INCATCH, /* Block is within a catch context */ BBCS_CAUGHT /* Block is within a catch context and * may be executed after an exception fires */ } BasicBlockCatchState; /* * Structure that defines a basic block - a linear sequence of bytecode * instructions with no jumps in or out (including not changing the * state of any exception range). */ typedef struct BasicBlock { int originalStartOffset; /* Instruction offset before JUMP1s were * substituted with JUMP4's */ int startOffset; /* Instruction offset of the start of the * block */ int startLine; /* Line number in the input script of the * instruction at the start of the block */ int jumpOffset; /* Bytecode offset of the 'jump' instruction * that ends the block, or -1 if there is no * jump. */ int jumpLine; /* Line number in the input script of the * 'jump' instruction that ends the block, or * -1 if there is no jump */ struct BasicBlock* prevPtr; /* Immediate predecessor of this block */ struct BasicBlock* predecessor; /* Predecessor of this block in the spanning * tree */ struct BasicBlock* successor1; /* BasicBlock structure of the following * block: NULL at the end of the bytecode * sequence. */ Tcl_Obj* jumpTarget; /* Jump target label if the jump target is * unresolved */ int initialStackDepth; /* Absolute stack depth on entry */ int minStackDepth; /* Low-water relative stack depth */ int maxStackDepth; /* High-water relative stack depth */ int finalStackDepth; /* Relative stack depth on exit */ enum BasicBlockCatchState catchState; /* State of the block for 'catch' analysis */ int catchDepth; /* Number of nested catches in which the basic * block appears */ struct BasicBlock* enclosingCatch; /* BasicBlock structure of the last startCatch * executed on a path to this block, or NULL * if there is no enclosing catch */ int foreignExceptionBase; /* Base index of foreign exceptions */ int foreignExceptionCount; /* Count of foreign exceptions */ ExceptionRange* foreignExceptions; /* ExceptionRange structures for exception * ranges belonging to embedded scripts and * expressions in this block */ JumptableInfo* jtPtr; /* Jump table at the end of this basic block */ int flags; /* Boolean flags */ } BasicBlock; /* * Flags that pertain to a basic block. */ enum BasicBlockFlags { BB_VISITED = (1 << 0), /* Block has been visited in the current * traversal */ BB_FALLTHRU = (1 << 1), /* Control may pass from this block to a * successor */ BB_JUMP1 = (1 << 2), /* Basic block ends with a 1-byte-offset jump * and may need expansion */ BB_JUMPTABLE = (1 << 3), /* Basic block ends with a jump table */ BB_BEGINCATCH = (1 << 4), /* Block ends with a 'beginCatch' instruction, * marking it as the start of a 'catch' * sequence. The 'jumpTarget' is the exception * exit from the catch block. */ BB_ENDCATCH = (1 << 5) /* Block ends with an 'endCatch' instruction, * unwinding the catch from the exception * stack. */ }; /* * Source instruction type recognized by the assembler. */ typedef enum { ASSEM_1BYTE, /* Fixed arity, 1-byte instruction */ ASSEM_BEGIN_CATCH, /* Begin catch: one 4-byte jump offset to be * converted to appropriate exception * ranges */ ASSEM_BOOL, /* One Boolean operand */ ASSEM_BOOL_LVT4, /* One Boolean, one 4-byte LVT ref. */ ASSEM_CLOCK_READ, /* 1-byte unsigned-integer case number, in the * range 0-3 */ ASSEM_CONCAT1, /* 1-byte unsigned-integer operand count, must * be strictly positive, consumes N, produces * 1 */ ASSEM_DICT_GET, /* 'dict get' and related - consumes N+1 * operands, produces 1, N > 0 */ ASSEM_DICT_SET, /* specifies key count and LVT index, consumes * N+1 operands, produces 1, N > 0 */ ASSEM_DICT_UNSET, /* specifies key count and LVT index, consumes * N operands, produces 1, N > 0 */ ASSEM_END_CATCH, /* End catch. No args. Exception range popped * from stack and stack pointer restored. */ ASSEM_EVAL, /* 'eval' - evaluate a constant script (by * compiling it in line with the assembly * code! I love Tcl!) */ ASSEM_INDEX, /* 4 byte operand, integer or end-integer */ ASSEM_INVOKE, /* 1- or 4-byte operand count, must be * strictly positive, consumes N, produces * 1. */ ASSEM_JUMP, /* Jump instructions */ ASSEM_JUMP4, /* Jump instructions forcing a 4-byte offset */ ASSEM_JUMPTABLE, /* Jumptable (switch -exact) */ ASSEM_LABEL, /* The assembly directive that defines a * label */ ASSEM_LINDEX_MULTI, /* 4-byte operand count, must be strictly * positive, consumes N, produces 1 */ ASSEM_LIST, /* 4-byte operand count, must be nonnegative, * consumses N, produces 1 */ ASSEM_LSET_FLAT, /* 4-byte operand count, must be >= 3, * consumes N, produces 1 */ ASSEM_LVT, /* One operand that references a local * variable */ ASSEM_LVT1, /* One 1-byte operand that references a local * variable */ ASSEM_LVT1_SINT1, /* One 1-byte operand that references a local * variable, one signed-integer 1-byte * operand */ ASSEM_LVT4, /* One 4-byte operand that references a local * variable */ ASSEM_OVER, /* OVER: 4-byte operand count, consumes N+1, * produces N+2 */ ASSEM_PUSH, /* one literal operand */ ASSEM_REGEXP, /* One Boolean operand, but weird mapping to * call flags */ ASSEM_REVERSE, /* REVERSE: 4-byte operand count, consumes N, * produces N */ ASSEM_SINT1, /* One 1-byte signed-integer operand * (INCR_STK_IMM) */ ASSEM_SINT4_LVT4, /* Signed 4-byte integer operand followed by * LVT entry. Fixed arity */ ASSEM_DICT_GET_DEF /* 'dict getwithdefault' - consumes N+2 * operands, produces 1, N > 0 */ } TalInstType; /* * Description of an instruction recognized by the assembler. */ typedef struct TalInstDesc { const char *name; /* Name of instruction. */ TalInstType instType; /* The type of instruction */ int tclInstCode; /* Instruction code. For instructions having * 1- and 4-byte variables, tclInstCode is * ((1byte)<<8) || (4byte) */ int operandsConsumed; /* Number of operands consumed by the * operation, or INT_MIN if the operation is * variadic */ int operandsProduced; /* Number of operands produced by the * operation. If negative, the operation has a * net stack effect of -1-operandsProduced */ } TalInstDesc; /* * Structure that holds the state of the assembler while generating code. */ typedef struct AssemblyEnv { CompileEnv* envPtr; /* Compilation environment being used for code * generation */ Tcl_Parse* parsePtr; /* Parse of the current line of source */ Tcl_HashTable labelHash; /* Hash table whose keys are labels and whose * values are 'label' objects storing the code * offsets of the labels. */ Tcl_Size cmdLine; /* Current line number within the assembly * code */ Tcl_Size* clNext; /* Invisible continuation line for * [info frame] */ BasicBlock* head_bb; /* First basic block in the code */ BasicBlock* curr_bb; /* Current basic block */ int maxDepth; /* Maximum stack depth encountered */ int curCatchDepth; /* Current depth of catches */ int maxCatchDepth; /* Maximum depth of catches encountered */ int flags; /* Compilation flags (TCL_EVAL_DIRECT) */ } AssemblyEnv; /* * Static functions defined in this file. */ static void AddBasicBlockRangeToErrorInfo(AssemblyEnv*, BasicBlock*); static BasicBlock * AllocBB(AssemblyEnv*); static int AssembleOneLine(AssemblyEnv* envPtr); static void BBAdjustStackDepth(BasicBlock* bbPtr, int consumed, int produced); static void BBUpdateStackReqs(BasicBlock* bbPtr, int tblIdx, int count); static void BBEmitInstInt1(AssemblyEnv* assemEnvPtr, int tblIdx, int opnd, int count); static void BBEmitInstInt4(AssemblyEnv* assemEnvPtr, int tblIdx, int opnd, int count); static void BBEmitInst1or4(AssemblyEnv* assemEnvPtr, int tblIdx, int param, int count); static void BBEmitOpcode(AssemblyEnv* assemEnvPtr, int tblIdx, int count); static int BuildExceptionRanges(AssemblyEnv* assemEnvPtr); static int CalculateJumpRelocations(AssemblyEnv*, int*); static int CheckForUnclosedCatches(AssemblyEnv*); static int CheckForThrowInWrongContext(AssemblyEnv*); static int CheckNonThrowingBlock(AssemblyEnv*, BasicBlock*); static int BytecodeMightThrow(unsigned char); static int CheckJumpTableLabels(AssemblyEnv*, BasicBlock*); static int CheckNamespaceQualifiers(Tcl_Interp*, const char*, int); static int CheckNonNegative(Tcl_Interp*, int); static int CheckOneByte(Tcl_Interp*, int); static int CheckSignedOneByte(Tcl_Interp*, int); static int CheckStack(AssemblyEnv*); static int CheckStrictlyPositive(Tcl_Interp*, int); static ByteCode * CompileAssembleObj(Tcl_Interp *interp, Tcl_Obj *objPtr); static void CompileEmbeddedScript(AssemblyEnv*, Tcl_Token*, const TalInstDesc*); static int DefineLabel(AssemblyEnv* envPtr, const char* label); static void DeleteMirrorJumpTable(JumptableInfo* jtPtr); static void FillInJumpOffsets(AssemblyEnv*); static int CreateMirrorJumpTable(AssemblyEnv* assemEnvPtr, Tcl_Obj* jumpTable); static size_t FindLocalVar(AssemblyEnv* envPtr, Tcl_Token** tokenPtrPtr); static int FinishAssembly(AssemblyEnv*); static void FreeAssemblyEnv(AssemblyEnv*); static int GetBooleanOperand(AssemblyEnv*, Tcl_Token**, int*); static int GetListIndexOperand(AssemblyEnv*, Tcl_Token**, int*); static int GetIntegerOperand(AssemblyEnv*, Tcl_Token**, int*); static int GetNextOperand(AssemblyEnv*, Tcl_Token**, Tcl_Obj**); static void LookForFreshCatches(BasicBlock*, BasicBlock**); static void MoveCodeForJumps(AssemblyEnv*, int); static void MoveExceptionRangesToBasicBlock(AssemblyEnv*, int); static AssemblyEnv* NewAssemblyEnv(CompileEnv*, int); static int ProcessCatches(AssemblyEnv*); static int ProcessCatchesInBasicBlock(AssemblyEnv*, BasicBlock*, BasicBlock*, enum BasicBlockCatchState, int); static void ResetVisitedBasicBlocks(AssemblyEnv*); static void ResolveJumpTableTargets(AssemblyEnv*, BasicBlock*); static void ReportUndefinedLabel(AssemblyEnv*, BasicBlock*, Tcl_Obj*); static void RestoreEmbeddedExceptionRanges(AssemblyEnv*); static int StackCheckBasicBlock(AssemblyEnv*, BasicBlock *, BasicBlock *, int); static BasicBlock* StartBasicBlock(AssemblyEnv*, int fallthrough, Tcl_Obj* jumpLabel); /* static int AdvanceIp(const unsigned char *pc); */ static int StackCheckBasicBlock(AssemblyEnv*, BasicBlock *, BasicBlock *, int); static int StackCheckExit(AssemblyEnv*); static void StackFreshCatches(AssemblyEnv*, BasicBlock*, int, BasicBlock**, int*); static void SyncStackDepth(AssemblyEnv*); static int TclAssembleCode(CompileEnv* envPtr, const char* code, int codeLen, int flags); static void UnstackExpiredCatches(CompileEnv*, BasicBlock*, int, BasicBlock**, int*); /* * Tcl_ObjType that describes bytecode emitted by the assembler. */ static Tcl_FreeInternalRepProc FreeAssembleCodeInternalRep; static Tcl_DupInternalRepProc DupAssembleCodeInternalRep; static const Tcl_ObjType assembleCodeType = { "assemblecode", FreeAssembleCodeInternalRep, /* freeIntRepProc */ DupAssembleCodeInternalRep, /* dupIntRepProc */ NULL, /* updateStringProc */ NULL, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; /* * Source instructions recognized in the Tcl Assembly Language (TAL) */ static const TalInstDesc TalInstructionTable[] = { /* PUSH must be first, see the code near the end of TclAssembleCode */ {"push", ASSEM_PUSH, (INST_PUSH1<<8 | INST_PUSH4), 0, 1}, {"add", ASSEM_1BYTE, INST_ADD, 2, 1}, {"append", ASSEM_LVT, (INST_APPEND_SCALAR1<<8 | INST_APPEND_SCALAR4),1, 1}, {"appendArray", ASSEM_LVT, (INST_APPEND_ARRAY1<<8 | INST_APPEND_ARRAY4), 2, 1}, {"appendArrayStk", ASSEM_1BYTE, INST_APPEND_ARRAY_STK, 3, 1}, {"appendStk", ASSEM_1BYTE, INST_APPEND_STK, 2, 1}, {"arrayExistsImm", ASSEM_LVT4, INST_ARRAY_EXISTS_IMM, 0, 1}, {"arrayExistsStk", ASSEM_1BYTE, INST_ARRAY_EXISTS_STK, 1, 1}, {"arrayMakeImm", ASSEM_LVT4, INST_ARRAY_MAKE_IMM, 0, 0}, {"arrayMakeStk", ASSEM_1BYTE, INST_ARRAY_MAKE_STK, 1, 0}, {"beginCatch", ASSEM_BEGIN_CATCH, INST_BEGIN_CATCH4, 0, 0}, {"bitand", ASSEM_1BYTE, INST_BITAND, 2, 1}, {"bitnot", ASSEM_1BYTE, INST_BITNOT, 1, 1}, {"bitor", ASSEM_1BYTE, INST_BITOR, 2, 1}, {"bitxor", ASSEM_1BYTE, INST_BITXOR, 2, 1}, {"clockRead", ASSEM_CLOCK_READ, INST_CLOCK_READ, 0, 1}, {"concat", ASSEM_CONCAT1, INST_STR_CONCAT1, INT_MIN,1}, {"concatStk", ASSEM_LIST, INST_CONCAT_STK, INT_MIN,1}, {"coroName", ASSEM_1BYTE, INST_COROUTINE_NAME, 0, 1}, {"currentNamespace",ASSEM_1BYTE, INST_NS_CURRENT, 0, 1}, {"dictAppend", ASSEM_LVT4, INST_DICT_APPEND, 2, 1}, {"dictExists", ASSEM_DICT_GET, INST_DICT_EXISTS, INT_MIN,1}, {"dictExpand", ASSEM_1BYTE, INST_DICT_EXPAND, 3, 1}, {"dictGet", ASSEM_DICT_GET, INST_DICT_GET, INT_MIN,1}, {"dictGetDef", ASSEM_DICT_GET_DEF, INST_DICT_GET_DEF, INT_MIN,1}, {"dictIncrImm", ASSEM_SINT4_LVT4, INST_DICT_INCR_IMM, 1, 1}, {"dictLappend", ASSEM_LVT4, INST_DICT_LAPPEND, 2, 1}, {"dictRecombineStk",ASSEM_1BYTE, INST_DICT_RECOMBINE_STK,3, 0}, {"dictRecombineImm",ASSEM_LVT4, INST_DICT_RECOMBINE_IMM,2, 0}, {"dictSet", ASSEM_DICT_SET, INST_DICT_SET, INT_MIN,1}, {"dictUnset", ASSEM_DICT_UNSET, INST_DICT_UNSET, INT_MIN,1}, {"div", ASSEM_1BYTE, INST_DIV, 2, 1}, {"dup", ASSEM_1BYTE, INST_DUP, 1, 2}, {"endCatch", ASSEM_END_CATCH,INST_END_CATCH, 0, 0}, {"eq", ASSEM_1BYTE, INST_EQ, 2, 1}, {"eval", ASSEM_EVAL, INST_EVAL_STK, 1, 1}, {"evalStk", ASSEM_1BYTE, INST_EVAL_STK, 1, 1}, {"exist", ASSEM_LVT4, INST_EXIST_SCALAR, 0, 1}, {"existArray", ASSEM_LVT4, INST_EXIST_ARRAY, 1, 1}, {"existArrayStk", ASSEM_1BYTE, INST_EXIST_ARRAY_STK, 2, 1}, {"existStk", ASSEM_1BYTE, INST_EXIST_STK, 1, 1}, {"expon", ASSEM_1BYTE, INST_EXPON, 2, 1}, {"expr", ASSEM_EVAL, INST_EXPR_STK, 1, 1}, {"exprStk", ASSEM_1BYTE, INST_EXPR_STK, 1, 1}, {"ge", ASSEM_1BYTE, INST_GE, 2, 1}, {"gt", ASSEM_1BYTE, INST_GT, 2, 1}, {"incr", ASSEM_LVT1, INST_INCR_SCALAR1, 1, 1}, {"incrArray", ASSEM_LVT1, INST_INCR_ARRAY1, 2, 1}, {"incrArrayImm", ASSEM_LVT1_SINT1, INST_INCR_ARRAY1_IMM, 1, 1}, {"incrArrayStk", ASSEM_1BYTE, INST_INCR_ARRAY_STK, 3, 1}, {"incrArrayStkImm", ASSEM_SINT1, INST_INCR_ARRAY_STK_IMM,2, 1}, {"incrImm", ASSEM_LVT1_SINT1, INST_INCR_SCALAR1_IMM, 0, 1}, {"incrStk", ASSEM_1BYTE, INST_INCR_STK, 2, 1}, {"incrStkImm", ASSEM_SINT1, INST_INCR_STK_IMM, 1, 1}, {"infoLevelArgs", ASSEM_1BYTE, INST_INFO_LEVEL_ARGS, 1, 1}, {"infoLevelNumber", ASSEM_1BYTE, INST_INFO_LEVEL_NUM, 0, 1}, {"invokeStk", ASSEM_INVOKE, (INST_INVOKE_STK1 << 8 | INST_INVOKE_STK4), INT_MIN,1}, {"jump", ASSEM_JUMP, INST_JUMP1, 0, 0}, {"jump4", ASSEM_JUMP4, INST_JUMP4, 0, 0}, {"jumpFalse", ASSEM_JUMP, INST_JUMP_FALSE1, 1, 0}, {"jumpFalse4", ASSEM_JUMP4, INST_JUMP_FALSE4, 1, 0}, {"jumpTable", ASSEM_JUMPTABLE,INST_JUMP_TABLE, 1, 0}, {"jumpTrue", ASSEM_JUMP, INST_JUMP_TRUE1, 1, 0}, {"jumpTrue4", ASSEM_JUMP4, INST_JUMP_TRUE4, 1, 0}, {"label", ASSEM_LABEL, 0, 0, 0}, {"lappend", ASSEM_LVT, (INST_LAPPEND_SCALAR1<<8 | INST_LAPPEND_SCALAR4), 1, 1}, {"lappendArray", ASSEM_LVT, (INST_LAPPEND_ARRAY1<<8 | INST_LAPPEND_ARRAY4),2, 1}, {"lappendArrayStk", ASSEM_1BYTE, INST_LAPPEND_ARRAY_STK, 3, 1}, {"lappendList", ASSEM_LVT4, INST_LAPPEND_LIST, 1, 1}, {"lappendListArray",ASSEM_LVT4, INST_LAPPEND_LIST_ARRAY,2, 1}, {"lappendListArrayStk", ASSEM_1BYTE,INST_LAPPEND_LIST_ARRAY_STK, 3, 1}, {"lappendListStk", ASSEM_1BYTE, INST_LAPPEND_LIST_STK, 2, 1}, {"lappendStk", ASSEM_1BYTE, INST_LAPPEND_STK, 2, 1}, {"le", ASSEM_1BYTE, INST_LE, 2, 1}, {"lindexMulti", ASSEM_LINDEX_MULTI, INST_LIST_INDEX_MULTI, INT_MIN,1}, {"list", ASSEM_LIST, INST_LIST, INT_MIN,1}, {"listConcat", ASSEM_1BYTE, INST_LIST_CONCAT, 2, 1}, {"listIn", ASSEM_1BYTE, INST_LIST_IN, 2, 1}, {"listIndex", ASSEM_1BYTE, INST_LIST_INDEX, 2, 1}, {"listIndexImm", ASSEM_INDEX, INST_LIST_INDEX_IMM, 1, 1}, {"listLength", ASSEM_1BYTE, INST_LIST_LENGTH, 1, 1}, {"listNotIn", ASSEM_1BYTE, INST_LIST_NOT_IN, 2, 1}, {"load", ASSEM_LVT, (INST_LOAD_SCALAR1 << 8 | INST_LOAD_SCALAR4), 0, 1}, {"loadArray", ASSEM_LVT, (INST_LOAD_ARRAY1<<8 | INST_LOAD_ARRAY4), 1, 1}, {"loadArrayStk", ASSEM_1BYTE, INST_LOAD_ARRAY_STK, 2, 1}, {"loadStk", ASSEM_1BYTE, INST_LOAD_STK, 1, 1}, {"lsetFlat", ASSEM_LSET_FLAT,INST_LSET_FLAT, INT_MIN,1}, {"lsetList", ASSEM_1BYTE, INST_LSET_LIST, 3, 1}, {"lshift", ASSEM_1BYTE, INST_LSHIFT, 2, 1}, {"lt", ASSEM_1BYTE, INST_LT, 2, 1}, {"mod", ASSEM_1BYTE, INST_MOD, 2, 1}, {"mult", ASSEM_1BYTE, INST_MULT, 2, 1}, {"neq", ASSEM_1BYTE, INST_NEQ, 2, 1}, {"nop", ASSEM_1BYTE, INST_NOP, 0, 0}, {"not", ASSEM_1BYTE, INST_LNOT, 1, 1}, {"nsupvar", ASSEM_LVT4, INST_NSUPVAR, 2, 1}, {"numericType", ASSEM_1BYTE, INST_NUM_TYPE, 1, 1}, {"originCmd", ASSEM_1BYTE, INST_ORIGIN_COMMAND, 1, 1}, {"over", ASSEM_OVER, INST_OVER, INT_MIN,-1-1}, {"pop", ASSEM_1BYTE, INST_POP, 1, 0}, {"pushReturnCode", ASSEM_1BYTE, INST_PUSH_RETURN_CODE, 0, 1}, {"pushReturnOpts", ASSEM_1BYTE, INST_PUSH_RETURN_OPTIONS, 0, 1}, {"pushResult", ASSEM_1BYTE, INST_PUSH_RESULT, 0, 1}, {"regexp", ASSEM_REGEXP, INST_REGEXP, 2, 1}, {"resolveCmd", ASSEM_1BYTE, INST_RESOLVE_COMMAND, 1, 1}, {"reverse", ASSEM_REVERSE, INST_REVERSE, INT_MIN,-1-0}, {"rshift", ASSEM_1BYTE, INST_RSHIFT, 2, 1}, {"store", ASSEM_LVT, (INST_STORE_SCALAR1<<8 | INST_STORE_SCALAR4), 1, 1}, {"storeArray", ASSEM_LVT, (INST_STORE_ARRAY1<<8 | INST_STORE_ARRAY4), 2, 1}, {"storeArrayStk", ASSEM_1BYTE, INST_STORE_ARRAY_STK, 3, 1}, {"storeStk", ASSEM_1BYTE, INST_STORE_STK, 2, 1}, {"strcaseLower", ASSEM_1BYTE, INST_STR_LOWER, 1, 1}, {"strcaseTitle", ASSEM_1BYTE, INST_STR_TITLE, 1, 1}, {"strcaseUpper", ASSEM_1BYTE, INST_STR_UPPER, 1, 1}, {"strcmp", ASSEM_1BYTE, INST_STR_CMP, 2, 1}, {"strcat", ASSEM_CONCAT1, INST_STR_CONCAT1, INT_MIN,1}, {"streq", ASSEM_1BYTE, INST_STR_EQ, 2, 1}, {"strfind", ASSEM_1BYTE, INST_STR_FIND, 2, 1}, {"strge", ASSEM_1BYTE, INST_STR_GE, 2, 1}, {"strgt", ASSEM_1BYTE, INST_STR_GT, 2, 1}, {"strindex", ASSEM_1BYTE, INST_STR_INDEX, 2, 1}, {"strle", ASSEM_1BYTE, INST_STR_LE, 2, 1}, {"strlen", ASSEM_1BYTE, INST_STR_LEN, 1, 1}, {"strlt", ASSEM_1BYTE, INST_STR_LT, 2, 1}, {"strmap", ASSEM_1BYTE, INST_STR_MAP, 3, 1}, {"strmatch", ASSEM_BOOL, INST_STR_MATCH, 2, 1}, {"strneq", ASSEM_1BYTE, INST_STR_NEQ, 2, 1}, {"strrange", ASSEM_1BYTE, INST_STR_RANGE, 3, 1}, {"strreplace", ASSEM_1BYTE, INST_STR_REPLACE, 4, 1}, {"strrfind", ASSEM_1BYTE, INST_STR_FIND_LAST, 2, 1}, {"strtrim", ASSEM_1BYTE, INST_STR_TRIM, 2, 1}, {"strtrimLeft", ASSEM_1BYTE, INST_STR_TRIM_LEFT, 2, 1}, {"strtrimRight", ASSEM_1BYTE, INST_STR_TRIM_RIGHT, 2, 1}, {"sub", ASSEM_1BYTE, INST_SUB, 2, 1}, {"tclooClass", ASSEM_1BYTE, INST_TCLOO_CLASS, 1, 1}, {"tclooIsObject", ASSEM_1BYTE, INST_TCLOO_IS_OBJECT, 1, 1}, {"tclooNamespace", ASSEM_1BYTE, INST_TCLOO_NS, 1, 1}, {"tclooSelf", ASSEM_1BYTE, INST_TCLOO_SELF, 0, 1}, {"tryCvtToBoolean", ASSEM_1BYTE, INST_TRY_CVT_TO_BOOLEAN,1, 2}, {"tryCvtToNumeric", ASSEM_1BYTE, INST_TRY_CVT_TO_NUMERIC,1, 1}, {"uminus", ASSEM_1BYTE, INST_UMINUS, 1, 1}, {"unset", ASSEM_BOOL_LVT4,INST_UNSET_SCALAR, 0, 0}, {"unsetArray", ASSEM_BOOL_LVT4,INST_UNSET_ARRAY, 1, 0}, {"unsetArrayStk", ASSEM_BOOL, INST_UNSET_ARRAY_STK, 2, 0}, {"unsetStk", ASSEM_BOOL, INST_UNSET_STK, 1, 0}, {"uplus", ASSEM_1BYTE, INST_UPLUS, 1, 1}, {"upvar", ASSEM_LVT4, INST_UPVAR, 2, 1}, {"variable", ASSEM_LVT4, INST_VARIABLE, 1, 0}, {"verifyDict", ASSEM_1BYTE, INST_DICT_VERIFY, 1, 0}, {"yield", ASSEM_1BYTE, INST_YIELD, 1, 1}, {NULL, ASSEM_1BYTE, 0, 0, 0} }; /* * List of instructions that cannot throw an exception under any * circumstances. These instructions are the ones that are permissible after * an exception is caught but before the corresponding exception range is * popped from the stack. * The instructions must be in ascending order by numeric operation code. */ static const unsigned char NonThrowingByteCodes[] = { INST_PUSH1, INST_PUSH4, INST_POP, INST_DUP, /* 1-4 */ INST_JUMP1, INST_JUMP4, /* 34-35 */ INST_END_CATCH, INST_PUSH_RESULT, INST_PUSH_RETURN_CODE, /* 70-72 */ INST_STR_EQ, INST_STR_NEQ, INST_STR_CMP, INST_STR_LEN, /* 73-76 */ INST_LIST, /* 79 */ INST_OVER, /* 95 */ INST_PUSH_RETURN_OPTIONS, /* 108 */ INST_REVERSE, /* 126 */ INST_NOP, /* 132 */ INST_STR_MAP, /* 143 */ INST_STR_FIND, /* 144 */ INST_COROUTINE_NAME, /* 149 */ INST_NS_CURRENT, /* 151 */ INST_INFO_LEVEL_NUM, /* 152 */ INST_RESOLVE_COMMAND, /* 154 */ INST_STR_TRIM, INST_STR_TRIM_LEFT, INST_STR_TRIM_RIGHT, /* 166-168 */ INST_CONCAT_STK, /* 169 */ INST_STR_UPPER, INST_STR_LOWER, INST_STR_TITLE, /* 170-172 */ INST_NUM_TYPE, /* 180 */ INST_STR_LT, INST_STR_GT, INST_STR_LE, INST_STR_GE /* 191-194 */ }; /* * Helper macros. */ #if defined(TCL_DEBUG_ASSEMBLY) && defined(__GNUC__) && __GNUC__ > 2 #define DEBUG_PRINT(...) fprintf(stderr, ##__VA_ARGS__);fflush(stderr) #elif defined(__GNUC__) && __GNUC__ > 2 #define DEBUG_PRINT(...) /* nothing */ #else #define DEBUG_PRINT /* nothing */ #endif /* *----------------------------------------------------------------------------- * * BBAdjustStackDepth -- * * When an opcode is emitted, adjusts the stack information in the basic * block to reflect the number of operands produced and consumed. * * Results: * None. * * Side effects: * Updates minimum, maximum and final stack requirements in the basic * block. * *----------------------------------------------------------------------------- */ static void BBAdjustStackDepth( BasicBlock *bbPtr, /* Structure describing the basic block */ int consumed, /* Count of operands consumed by the * operation */ int produced) /* Count of operands produced by the * operation */ { int depth = bbPtr->finalStackDepth; depth -= consumed; if (depth < bbPtr->minStackDepth) { bbPtr->minStackDepth = depth; } depth += produced; if (depth > bbPtr->maxStackDepth) { bbPtr->maxStackDepth = depth; } bbPtr->finalStackDepth = depth; } /* *----------------------------------------------------------------------------- * * BBUpdateStackReqs -- * * Updates the stack requirements of a basic block, given the opcode * being emitted and an operand count. * * Results: * None. * * Side effects: * Updates min, max and final stack requirements in the basic block. * * Notes: * This function must not be called for instructions such as REVERSE and * OVER that are variadic but do not consume all their operands. Instead, * BBAdjustStackDepth should be called directly. * * count should be provided only for variadic operations. For operations * with known arity, count should be 0. * *----------------------------------------------------------------------------- */ static void BBUpdateStackReqs( BasicBlock* bbPtr, /* Structure describing the basic block */ int tblIdx, /* Index in TalInstructionTable of the * operation being assembled */ int count) /* Count of operands for variadic insts */ { int consumed = TalInstructionTable[tblIdx].operandsConsumed; int produced = TalInstructionTable[tblIdx].operandsProduced; if (consumed == INT_MIN) { /* * The instruction is variadic; it consumes 'count' operands, or * 'count+1' for ASSEM_DICT_GET_DEF. */ consumed = count; if (TalInstructionTable[tblIdx].instType == ASSEM_DICT_GET_DEF) { consumed++; } } if (produced < 0) { /* * The instruction leaves some of its variadic operands on the stack, * with net stack effect of '-1-produced' */ produced = consumed - produced - 1; } BBAdjustStackDepth(bbPtr, consumed, produced); } /* *----------------------------------------------------------------------------- * * BBEmitOpcode, BBEmitInstInt1, BBEmitInstInt4 -- * * Emit the opcode part of an instruction, or the entirety of an * instruction with a 1- or 4-byte operand, and adjust stack * requirements. * * Results: * None. * * Side effects: * Stores instruction and operand in the operand stream, and adjusts the * stack. * *----------------------------------------------------------------------------- */ static void BBEmitOpcode( AssemblyEnv* assemEnvPtr, /* Assembly environment */ int tblIdx, /* Table index in TalInstructionTable of op */ int count) /* Operand count for variadic ops */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ BasicBlock* bbPtr = assemEnvPtr->curr_bb; /* Current basic block */ int op = TalInstructionTable[tblIdx].tclInstCode & 0xFF; /* * If this is the first instruction in a basic block, record its line * number. */ if (bbPtr->startOffset == envPtr->codeNext - envPtr->codeStart) { bbPtr->startLine = assemEnvPtr->cmdLine; } TclEmitInt1(op, envPtr); TclUpdateAtCmdStart(op, envPtr); BBUpdateStackReqs(bbPtr, tblIdx, count); } static void BBEmitInstInt1( AssemblyEnv* assemEnvPtr, /* Assembly environment */ int tblIdx, /* Index in TalInstructionTable of op */ int opnd, /* 1-byte operand */ int count) /* Operand count for variadic ops */ { BBEmitOpcode(assemEnvPtr, tblIdx, count); TclEmitInt1(opnd, assemEnvPtr->envPtr); } static void BBEmitInstInt4( AssemblyEnv* assemEnvPtr, /* Assembly environment */ int tblIdx, /* Index in TalInstructionTable of op */ int opnd, /* 4-byte operand */ int count) /* Operand count for variadic ops */ { BBEmitOpcode(assemEnvPtr, tblIdx, count); TclEmitInt4(opnd, assemEnvPtr->envPtr); } /* *----------------------------------------------------------------------------- * * BBEmitInst1or4 -- * * Emits a 1- or 4-byte operation according to the magnitude of the * operand. * *----------------------------------------------------------------------------- */ static void BBEmitInst1or4( AssemblyEnv* assemEnvPtr, /* Assembly environment */ int tblIdx, /* Index in TalInstructionTable of op */ int param, /* Variable-length parameter */ int count) /* Arity if variadic */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ BasicBlock* bbPtr = assemEnvPtr->curr_bb; /* Current basic block */ int op = TalInstructionTable[tblIdx].tclInstCode; if (param <= 0xFF) { op >>= 8; } else { op &= 0xFF; } TclEmitInt1(op, envPtr); if (param <= 0xFF) { TclEmitInt1(param, envPtr); } else { TclEmitInt4(param, envPtr); } TclUpdateAtCmdStart(op, envPtr); BBUpdateStackReqs(bbPtr, tblIdx, count); } /* *----------------------------------------------------------------------------- * * Tcl_AssembleObjCmd, TclNRAssembleObjCmd -- * * Direct evaluation path for tcl::unsupported::assemble * * Results: * Returns a standard Tcl result. * * Side effects: * Assembles the code in objv[1], and executes it, so side effects * include whatever the code does. * *----------------------------------------------------------------------------- */ int Tcl_AssembleObjCmd( void *clientData, /* clientData */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { /* * Boilerplate - make sure that there is an NRE trampoline on the C stack * because there needs to be one in place to execute bytecode. */ return Tcl_NRCallObjProc(interp, TclNRAssembleObjCmd, clientData, objc, objv); } int TclNRAssembleObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { ByteCode *codePtr; /* Pointer to the bytecode to execute */ Tcl_Obj* backtrace; /* Object where extra error information is * constructed. */ if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "bytecodeList"); return TCL_ERROR; } /* * Assemble the source to bytecode. */ codePtr = CompileAssembleObj(interp, objv[1]); /* * On failure, report error line. */ if (codePtr == NULL) { Tcl_AddErrorInfo(interp, "\n (\""); Tcl_AppendObjToErrorInfo(interp, objv[0]); Tcl_AddErrorInfo(interp, "\" body, line "); TclNewIntObj(backtrace, Tcl_GetErrorLine(interp)); Tcl_AppendObjToErrorInfo(interp, backtrace); Tcl_AddErrorInfo(interp, ")"); return TCL_ERROR; } /* * Use NRE to evaluate the bytecode from the trampoline. */ return TclNRExecuteByteCode(interp, codePtr); } /* *----------------------------------------------------------------------------- * * CompileAssembleObj -- * * Sets up and assembles Tcl bytecode for the direct-execution path in * the Tcl bytecode assembler. * * Results: * Returns a pointer to the assembled code. Returns NULL if the assembly * fails for any reason, with an appropriate error message in the * interpreter. * *----------------------------------------------------------------------------- */ static ByteCode * CompileAssembleObj( Tcl_Interp *interp, /* Tcl interpreter */ Tcl_Obj *objPtr) /* Source code to assemble */ { Interp *iPtr = (Interp *) interp; /* Internals of the interpreter */ CompileEnv compEnv; /* Compilation environment structure */ ByteCode *codePtr = NULL; /* Bytecode resulting from the assembly */ Namespace* namespacePtr; /* Namespace in which variable and command * names in the bytecode resolve */ int status; /* Status return from Tcl_AssembleCode */ const char* source; /* String representation of the source code */ Tcl_Size sourceLen; /* Length of the source code in bytes */ /* * Get the expression ByteCode from the object. If it exists, make sure it * is valid in the current context. */ ByteCodeGetInternalRep(objPtr, &assembleCodeType, codePtr); if (codePtr) { namespacePtr = iPtr->varFramePtr->nsPtr; if (((Interp *) *codePtr->interpHandle == iPtr) && (codePtr->compileEpoch == iPtr->compileEpoch) && (codePtr->nsPtr == namespacePtr) && (codePtr->nsEpoch == namespacePtr->resolverEpoch) && (codePtr->localCachePtr == iPtr->varFramePtr->localCachePtr)) { return codePtr; } /* * Not valid, so free it and regenerate. */ Tcl_StoreInternalRep(objPtr, &assembleCodeType, NULL); } /* * Set up the compilation environment, and assemble the code. */ source = TclGetStringFromObj(objPtr, &sourceLen); TclInitCompileEnv(interp, &compEnv, source, sourceLen, NULL, 0); status = TclAssembleCode(&compEnv, source, sourceLen, TCL_EVAL_DIRECT); if (status != TCL_OK) { /* * Assembly failed. Clean up and report the error. */ TclFreeCompileEnv(&compEnv); return NULL; } /* * Add a "done" instruction as the last instruction and change the object * into a ByteCode object. Ownership of the literal objects and aux data * items is given to the ByteCode object. */ TclEmitOpcode(INST_DONE, &compEnv); codePtr = TclInitByteCodeObj(objPtr, &assembleCodeType, &compEnv); TclFreeCompileEnv(&compEnv); /* * Record the local variable context to which the bytecode pertains */ if (iPtr->varFramePtr->localCachePtr) { codePtr->localCachePtr = iPtr->varFramePtr->localCachePtr; codePtr->localCachePtr->refCount++; } /* * Report on what the assembler did. */ TclDebugPrintByteCodeObj(objPtr); return codePtr; } /* *----------------------------------------------------------------------------- * * TclCompileAssembleCmd -- * * Compilation procedure for the '::tcl::unsupported::assemble' command. * * Results: * Returns a standard Tcl result. * * Side effects: * Puts the result of assembling the code into the bytecode stream in * 'compileEnv'. * * This procedure makes sure that the command has a single arg, which is * constant. If that condition is met, the procedure calls TclAssembleCode to * produce bytecode for the given assembly code, and returns any error * resulting from the assembly. * *----------------------------------------------------------------------------- */ int TclCompileAssembleCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { Tcl_Token *tokenPtr; /* Token in the input script */ size_t numCommands = envPtr->numCommands; int offset = envPtr->codeNext - envPtr->codeStart; size_t depth = envPtr->currStackDepth; /* * Make sure that the command has a single arg that is a simple word. */ if (parsePtr->numWords != 2) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { return TCL_ERROR; } /* * Compile the code and convert any error from the compilation into * bytecode reporting the error; */ if (TCL_ERROR == TclAssembleCode(envPtr, tokenPtr[1].start, tokenPtr[1].size, TCL_EVAL_DIRECT)) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (\"%.*s\" body, line %d)", (int)parsePtr->tokenPtr->size, parsePtr->tokenPtr->start, Tcl_GetErrorLine(interp))); envPtr->numCommands = numCommands; envPtr->codeNext = envPtr->codeStart + offset; envPtr->currStackDepth = depth; TclCompileSyntaxError(interp, envPtr); } return TCL_OK; } /* *----------------------------------------------------------------------------- * * TclAssembleCode -- * * Take a list of instructions in a Tcl_Obj, and assemble them to Tcl * bytecodes * * Results: * Returns TCL_OK on success, TCL_ERROR on failure. If 'flags' includes * TCL_EVAL_DIRECT, places an error message in the interpreter result. * * Side effects: * Adds byte codes to the compile environment, and updates the * environment's stack depth. * *----------------------------------------------------------------------------- */ static int TclAssembleCode( CompileEnv *envPtr, /* Compilation environment that is to receive * the generated bytecode */ const char* codePtr, /* Assembly-language code to be processed */ int codeLen, /* Length of the code */ int flags) /* OR'ed combination of flags */ { Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ /* * Walk through the assembly script using the Tcl parser. Each 'command' * will be an instruction or assembly directive. */ const char* instPtr = codePtr; /* Where to start looking for a line of code */ const char* nextPtr; /* Pointer to the end of the line of code */ int bytesLeft = codeLen; /* Number of bytes of source code remaining to * be parsed */ int status; /* Tcl status return */ AssemblyEnv* assemEnvPtr = NewAssemblyEnv(envPtr, flags); Tcl_Parse* parsePtr = assemEnvPtr->parsePtr; do { /* * Parse out one command line from the assembly script. */ status = Tcl_ParseCommand(interp, instPtr, bytesLeft, 0, parsePtr); /* * Report errors in the parse. */ if (status != TCL_OK) { if (flags & TCL_EVAL_DIRECT) { Tcl_LogCommandInfo(interp, codePtr, parsePtr->commandStart, parsePtr->term + 1 - parsePtr->commandStart); } FreeAssemblyEnv(assemEnvPtr); return TCL_ERROR; } /* * Advance the pointers around any leading commentary. */ TclAdvanceLines(&assemEnvPtr->cmdLine, instPtr, parsePtr->commandStart); TclAdvanceContinuations(&assemEnvPtr->cmdLine, &assemEnvPtr->clNext, parsePtr->commandStart - envPtr->source); /* * Process the line of code. */ if (parsePtr->numWords > 0) { size_t instLen = parsePtr->commandSize; /* Length in bytes of the current command */ if (parsePtr->term == parsePtr->commandStart + instLen - 1) { --instLen; } /* * If tracing, show each line assembled as it happens. */ #ifdef TCL_COMPILE_DEBUG if ((tclTraceCompile >= 2) && (envPtr->procPtr == NULL)) { printf(" %4" TCL_Z_MODIFIER "d Assembling: ", envPtr->codeNext - envPtr->codeStart); TclPrintSource(stdout, parsePtr->commandStart, TclMin(instLen, 55)); printf("\n"); } #endif if (AssembleOneLine(assemEnvPtr) != TCL_OK) { if (flags & TCL_EVAL_DIRECT) { Tcl_LogCommandInfo(interp, codePtr, parsePtr->commandStart, instLen); } Tcl_FreeParse(parsePtr); FreeAssemblyEnv(assemEnvPtr); return TCL_ERROR; } } /* * Advance to the next line of code. */ nextPtr = parsePtr->commandStart + parsePtr->commandSize; bytesLeft -= (nextPtr - instPtr); instPtr = nextPtr; TclAdvanceLines(&assemEnvPtr->cmdLine, parsePtr->commandStart, instPtr); TclAdvanceContinuations(&assemEnvPtr->cmdLine, &assemEnvPtr->clNext, instPtr - envPtr->source); Tcl_FreeParse(parsePtr); } while (bytesLeft > 0); /* * Done with parsing the code. */ status = FinishAssembly(assemEnvPtr); FreeAssemblyEnv(assemEnvPtr); return status; } /* *----------------------------------------------------------------------------- * * NewAssemblyEnv -- * * Creates an environment for the assembler to run in. * * Results: * Allocates, initialises and returns an assembler environment * *----------------------------------------------------------------------------- */ static AssemblyEnv* NewAssemblyEnv( CompileEnv* envPtr, /* Compilation environment being used for code * generation*/ int flags) /* Compilation flags (TCL_EVAL_DIRECT) */ { Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ AssemblyEnv* assemEnvPtr = (AssemblyEnv*)TclStackAlloc(interp, sizeof(AssemblyEnv)); /* Assembler environment under construction */ Tcl_Parse* parsePtr = (Tcl_Parse*)TclStackAlloc(interp, sizeof(Tcl_Parse)); /* Parse of one line of assembly code */ assemEnvPtr->envPtr = envPtr; assemEnvPtr->parsePtr = parsePtr; assemEnvPtr->cmdLine = 1; assemEnvPtr->clNext = envPtr->clNext; /* * Make the hashtables that store symbol resolution. */ Tcl_InitHashTable(&assemEnvPtr->labelHash, TCL_STRING_KEYS); /* * Start the first basic block. */ assemEnvPtr->curr_bb = NULL; assemEnvPtr->head_bb = AllocBB(assemEnvPtr); assemEnvPtr->curr_bb = assemEnvPtr->head_bb; assemEnvPtr->head_bb->startLine = 1; /* * Stash compilation flags. */ assemEnvPtr->flags = flags; return assemEnvPtr; } /* *----------------------------------------------------------------------------- * * FreeAssemblyEnv -- * * Cleans up the assembler environment when assembly is complete. * *----------------------------------------------------------------------------- */ static void FreeAssemblyEnv( AssemblyEnv* assemEnvPtr) /* Environment to free */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment being used for code * generation */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ BasicBlock* thisBB; /* Pointer to a basic block being deleted */ BasicBlock* nextBB; /* Pointer to a deleted basic block's * successor */ /* * Free all the basic block structures. */ for (thisBB = assemEnvPtr->head_bb; thisBB != NULL; thisBB = nextBB) { if (thisBB->jumpTarget != NULL) { Tcl_DecrRefCount(thisBB->jumpTarget); } if (thisBB->foreignExceptions != NULL) { Tcl_Free(thisBB->foreignExceptions); } nextBB = thisBB->successor1; if (thisBB->jtPtr != NULL) { DeleteMirrorJumpTable(thisBB->jtPtr); thisBB->jtPtr = NULL; } Tcl_Free(thisBB); } /* * Dispose what's left. */ Tcl_DeleteHashTable(&assemEnvPtr->labelHash); TclStackFree(interp, assemEnvPtr->parsePtr); TclStackFree(interp, assemEnvPtr); } /* *----------------------------------------------------------------------------- * * AssembleOneLine -- * * Assembles a single command from an assembly language source. * * Results: * Returns TCL_ERROR with an appropriate error message if the assembly * fails. Returns TCL_OK if the assembly succeeds. Updates the assembly * environment with the state of the assembly. * *----------------------------------------------------------------------------- */ static int AssembleOneLine( AssemblyEnv* assemEnvPtr) /* State of the assembly */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment being used for code * gen */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ Tcl_Parse* parsePtr = assemEnvPtr->parsePtr; /* Parse of the line of code */ Tcl_Token* tokenPtr; /* Current token within the line of code */ Tcl_Obj* instNameObj; /* Name of the instruction */ int tblIdx; /* Index in TalInstructionTable of the * instruction */ TalInstType instType; /* Type of the instruction */ Tcl_Obj* operand1Obj = NULL; /* First operand to the instruction */ const char* operand1; /* String rep of the operand */ Tcl_Size operand1Len; /* String length of the operand */ int opnd; /* Integer representation of an operand */ int litIndex; /* Literal pool index of a constant */ Tcl_Size localVar; /* LVT index of a local variable */ int flags; /* Flags for a basic block */ JumptableInfo* jtPtr; /* Pointer to a jumptable */ int infoIndex; /* Index of the jumptable in auxdata */ int status = TCL_ERROR; /* Return value from this function */ /* * Make sure that the instruction name is known at compile time. */ tokenPtr = parsePtr->tokenPtr; if (GetNextOperand(assemEnvPtr, &tokenPtr, &instNameObj) != TCL_OK) { return TCL_ERROR; } /* * Look up the instruction name. */ if (Tcl_GetIndexFromObjStruct(interp, instNameObj, &TalInstructionTable[0].name, sizeof(TalInstDesc), "instruction", TCL_EXACT, &tblIdx) != TCL_OK) { goto cleanup; } /* * Vector on the type of instruction being processed. */ instType = TalInstructionTable[tblIdx].instType; switch (instType) { case ASSEM_PUSH: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "value"); goto cleanup; } if (GetNextOperand(assemEnvPtr, &tokenPtr, &operand1Obj) != TCL_OK) { goto cleanup; } operand1 = TclGetStringFromObj(operand1Obj, &operand1Len); litIndex = TclRegisterLiteral(envPtr, operand1, operand1Len, 0); BBEmitInst1or4(assemEnvPtr, tblIdx, litIndex, 0); break; case ASSEM_1BYTE: if (parsePtr->numWords != 1) { Tcl_WrongNumArgs(interp, 1, &instNameObj, ""); goto cleanup; } BBEmitOpcode(assemEnvPtr, tblIdx, 0); break; case ASSEM_BEGIN_CATCH: /* * Emit the BEGIN_CATCH instruction with the code offset of the * exception branch target instead of the exception range index. The * correct index will be generated and inserted later, when catches * are being resolved. */ if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "label"); goto cleanup; } if (GetNextOperand(assemEnvPtr, &tokenPtr, &operand1Obj) != TCL_OK) { goto cleanup; } assemEnvPtr->curr_bb->jumpLine = assemEnvPtr->cmdLine; assemEnvPtr->curr_bb->jumpOffset = envPtr->codeNext-envPtr->codeStart; BBEmitInstInt4(assemEnvPtr, tblIdx, 0, 0); assemEnvPtr->curr_bb->flags |= BB_BEGINCATCH; StartBasicBlock(assemEnvPtr, BB_FALLTHRU, operand1Obj); break; case ASSEM_BOOL: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "boolean"); goto cleanup; } if (GetBooleanOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) { goto cleanup; } BBEmitInstInt1(assemEnvPtr, tblIdx, opnd, 0); break; case ASSEM_BOOL_LVT4: if (parsePtr->numWords != 3) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "boolean varName"); goto cleanup; } if (GetBooleanOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) { goto cleanup; } localVar = FindLocalVar(assemEnvPtr, &tokenPtr); if (localVar < 0) { goto cleanup; } BBEmitInstInt1(assemEnvPtr, tblIdx, opnd, 0); TclEmitInt4(localVar, envPtr); break; case ASSEM_CLOCK_READ: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "imm8"); goto cleanup; } if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) { goto cleanup; } if (opnd < 0 || opnd > 3) { Tcl_SetObjResult(interp, Tcl_NewStringObj("operand must be [0..3]", -1)); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "OPERAND<0,>3", (char *)NULL); goto cleanup; } BBEmitInstInt1(assemEnvPtr, tblIdx, opnd, opnd); break; case ASSEM_CONCAT1: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "imm8"); goto cleanup; } if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK || CheckOneByte(interp, opnd) != TCL_OK || CheckStrictlyPositive(interp, opnd) != TCL_OK) { goto cleanup; } BBEmitInstInt1(assemEnvPtr, tblIdx, opnd, opnd); break; case ASSEM_DICT_GET: case ASSEM_DICT_GET_DEF: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "count"); goto cleanup; } if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK || CheckStrictlyPositive(interp, opnd) != TCL_OK) { goto cleanup; } BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd+1); break; case ASSEM_DICT_SET: if (parsePtr->numWords != 3) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "count varName"); goto cleanup; } if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK || CheckStrictlyPositive(interp, opnd) != TCL_OK) { goto cleanup; } localVar = FindLocalVar(assemEnvPtr, &tokenPtr); if (localVar < 0) { goto cleanup; } BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd+1); TclEmitInt4(localVar, envPtr); break; case ASSEM_DICT_UNSET: if (parsePtr->numWords != 3) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "count varName"); goto cleanup; } if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK || CheckStrictlyPositive(interp, opnd) != TCL_OK) { goto cleanup; } localVar = FindLocalVar(assemEnvPtr, &tokenPtr); if (localVar < 0) { goto cleanup; } BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd); TclEmitInt4(localVar, envPtr); break; case ASSEM_END_CATCH: if (parsePtr->numWords != 1) { Tcl_WrongNumArgs(interp, 1, &instNameObj, ""); goto cleanup; } assemEnvPtr->curr_bb->flags |= BB_ENDCATCH; BBEmitOpcode(assemEnvPtr, tblIdx, 0); StartBasicBlock(assemEnvPtr, BB_FALLTHRU, NULL); break; case ASSEM_EVAL: /* TODO - Refactor this stuff into a subroutine that takes the inst * code, the message ("script" or "expression") and an evaluator * callback that calls TclCompileScript or TclCompileExpr. */ if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, ((TalInstructionTable[tblIdx].tclInstCode == INST_EVAL_STK) ? "script" : "expression")); goto cleanup; } if (tokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { CompileEmbeddedScript(assemEnvPtr, tokenPtr+1, TalInstructionTable+tblIdx); } else if (GetNextOperand(assemEnvPtr, &tokenPtr, &operand1Obj) != TCL_OK) { goto cleanup; } else { operand1 = TclGetStringFromObj(operand1Obj, &operand1Len); litIndex = TclRegisterLiteral(envPtr, operand1, operand1Len, 0); /* * Assumes that PUSH is the first slot! */ BBEmitInst1or4(assemEnvPtr, 0, litIndex, 0); BBEmitOpcode(assemEnvPtr, tblIdx, 0); } break; case ASSEM_INVOKE: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "count"); goto cleanup; } if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK || CheckStrictlyPositive(interp, opnd) != TCL_OK) { goto cleanup; } BBEmitInst1or4(assemEnvPtr, tblIdx, opnd, opnd); break; case ASSEM_JUMP: case ASSEM_JUMP4: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "label"); goto cleanup; } if (GetNextOperand(assemEnvPtr, &tokenPtr, &operand1Obj) != TCL_OK) { goto cleanup; } assemEnvPtr->curr_bb->jumpOffset = envPtr->codeNext-envPtr->codeStart; if (instType == ASSEM_JUMP) { flags = BB_JUMP1; BBEmitInstInt1(assemEnvPtr, tblIdx, 0, 0); } else { flags = 0; BBEmitInstInt4(assemEnvPtr, tblIdx, 0, 0); } /* * Start a new basic block at the instruction following the jump. */ assemEnvPtr->curr_bb->jumpLine = assemEnvPtr->cmdLine; if (TalInstructionTable[tblIdx].operandsConsumed != 0) { flags |= BB_FALLTHRU; } StartBasicBlock(assemEnvPtr, flags, operand1Obj); break; case ASSEM_JUMPTABLE: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "table"); goto cleanup; } if (GetNextOperand(assemEnvPtr, &tokenPtr, &operand1Obj) != TCL_OK) { goto cleanup; } jtPtr = (JumptableInfo*)Tcl_Alloc(sizeof(JumptableInfo)); Tcl_InitHashTable(&jtPtr->hashTable, TCL_STRING_KEYS); assemEnvPtr->curr_bb->jumpLine = assemEnvPtr->cmdLine; assemEnvPtr->curr_bb->jumpOffset = envPtr->codeNext-envPtr->codeStart; DEBUG_PRINT("bb %p jumpLine %d jumpOffset %d\n", assemEnvPtr->curr_bb, assemEnvPtr->cmdLine, envPtr->codeNext - envPtr->codeStart); infoIndex = TclCreateAuxData(jtPtr, &tclJumptableInfoType, envPtr); DEBUG_PRINT("auxdata index=%d\n", infoIndex); BBEmitInstInt4(assemEnvPtr, tblIdx, infoIndex, 0); if (CreateMirrorJumpTable(assemEnvPtr, operand1Obj) != TCL_OK) { goto cleanup; } StartBasicBlock(assemEnvPtr, BB_JUMPTABLE|BB_FALLTHRU, NULL); break; case ASSEM_LABEL: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "name"); goto cleanup; } if (GetNextOperand(assemEnvPtr, &tokenPtr, &operand1Obj) != TCL_OK) { goto cleanup; } /* * Add the (label_name, address) pair to the hash table. */ if (DefineLabel(assemEnvPtr, TclGetString(operand1Obj)) != TCL_OK) { goto cleanup; } break; case ASSEM_LINDEX_MULTI: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "count"); goto cleanup; } if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK || CheckStrictlyPositive(interp, opnd) != TCL_OK) { goto cleanup; } BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd); break; case ASSEM_LIST: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "count"); goto cleanup; } if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK || CheckNonNegative(interp, opnd) != TCL_OK) { goto cleanup; } BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd); break; case ASSEM_INDEX: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "count"); goto cleanup; } if (GetListIndexOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) { goto cleanup; } BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd); break; case ASSEM_LSET_FLAT: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "count"); goto cleanup; } if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) { goto cleanup; } if (opnd < 2) { if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_NewStringObj("operand must be >=2", -1)); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "OPERAND>=2", (char *)NULL); } goto cleanup; } BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd); break; case ASSEM_LVT: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "varname"); goto cleanup; } localVar = FindLocalVar(assemEnvPtr, &tokenPtr); if (localVar < 0) { goto cleanup; } BBEmitInst1or4(assemEnvPtr, tblIdx, localVar, 0); break; case ASSEM_LVT1: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "varname"); goto cleanup; } localVar = FindLocalVar(assemEnvPtr, &tokenPtr); if (localVar < 0 || CheckOneByte(interp, localVar)) { goto cleanup; } BBEmitInstInt1(assemEnvPtr, tblIdx, localVar, 0); break; case ASSEM_LVT1_SINT1: if (parsePtr->numWords != 3) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "varName imm8"); goto cleanup; } localVar = FindLocalVar(assemEnvPtr, &tokenPtr); if (localVar < 0 || CheckOneByte(interp, localVar) || GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK || CheckSignedOneByte(interp, opnd)) { goto cleanup; } BBEmitInstInt1(assemEnvPtr, tblIdx, localVar, 0); TclEmitInt1(opnd, envPtr); break; case ASSEM_LVT4: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "varname"); goto cleanup; } localVar = FindLocalVar(assemEnvPtr, &tokenPtr); if (localVar < 0) { goto cleanup; } BBEmitInstInt4(assemEnvPtr, tblIdx, localVar, 0); break; case ASSEM_OVER: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "count"); goto cleanup; } if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK || CheckNonNegative(interp, opnd) != TCL_OK) { goto cleanup; } BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd+1); break; case ASSEM_REGEXP: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "boolean"); goto cleanup; } if (GetBooleanOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) { goto cleanup; } { BBEmitInstInt1(assemEnvPtr, tblIdx, TCL_REG_ADVANCED | (opnd ? TCL_REG_NOCASE : 0), 0); } break; case ASSEM_REVERSE: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "count"); goto cleanup; } if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK || CheckNonNegative(interp, opnd) != TCL_OK) { goto cleanup; } BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, opnd); break; case ASSEM_SINT1: if (parsePtr->numWords != 2) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "imm8"); goto cleanup; } if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK || CheckSignedOneByte(interp, opnd) != TCL_OK) { goto cleanup; } BBEmitInstInt1(assemEnvPtr, tblIdx, opnd, 0); break; case ASSEM_SINT4_LVT4: if (parsePtr->numWords != 3) { Tcl_WrongNumArgs(interp, 1, &instNameObj, "count varName"); goto cleanup; } if (GetIntegerOperand(assemEnvPtr, &tokenPtr, &opnd) != TCL_OK) { goto cleanup; } localVar = FindLocalVar(assemEnvPtr, &tokenPtr); if (localVar < 0) { goto cleanup; } BBEmitInstInt4(assemEnvPtr, tblIdx, opnd, 0); TclEmitInt4(localVar, envPtr); break; default: Tcl_Panic("Instruction \"%s\" could not be found, can't happen\n", TclGetString(instNameObj)); } status = TCL_OK; cleanup: Tcl_DecrRefCount(instNameObj); if (operand1Obj) { Tcl_DecrRefCount(operand1Obj); } return status; } /* *----------------------------------------------------------------------------- * * CompileEmbeddedScript -- * * Compile an embedded 'eval' or 'expr' that appears in assembly code. * * This procedure is called when the 'eval' or 'expr' assembly directive is * encountered, and the argument to the directive is a simple word that * requires no substitution. The appropriate compiler (TclCompileScript or * TclCompileExpr) is invoked recursively, and emits bytecode. * * Before the compiler is invoked, the compilation environment's stack * consumption is reset to zero. Upon return from the compilation, the net * stack effect of the compilation is in the compiler env, and this stack * effect is posted to the assembler environment. The compile environment's * stack consumption is then restored to what it was before (which is actually * the state of the stack on entry to the block of assembly code). * * Any exception ranges pushed by the compilation are copied to the basic * block and removed from the compiler environment. They will be rebuilt at * the end of assembly, when the exception stack depth is actually known. * *----------------------------------------------------------------------------- */ static void CompileEmbeddedScript( AssemblyEnv* assemEnvPtr, /* Assembly environment */ Tcl_Token* tokenPtr, /* Tcl_Token containing the script */ const TalInstDesc* instPtr) /* Instruction that determines whether * the script is 'expr' or 'eval' */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ /* * The expression or script is not only known at compile time, but * actually a "simple word". It can be compiled inline by invoking the * compiler recursively. * * Save away the stack depth and reset it before compiling the script. * We'll record the stack usage of the script in the BasicBlock, and * accumulate it together with the stack usage of the enclosing assembly * code. */ size_t savedStackDepth = envPtr->currStackDepth; size_t savedMaxStackDepth = envPtr->maxStackDepth; int savedExceptArrayNext = envPtr->exceptArrayNext; envPtr->currStackDepth = 0; envPtr->maxStackDepth = 0; StartBasicBlock(assemEnvPtr, BB_FALLTHRU, NULL); switch (instPtr->tclInstCode) { case INST_EVAL_STK: TclCompileScript(interp, tokenPtr->start, tokenPtr->size, envPtr); break; case INST_EXPR_STK: TclCompileExpr(interp, tokenPtr->start, tokenPtr->size, envPtr, 1); break; default: Tcl_Panic("no ASSEM_EVAL case for %s (%d), can't happen", instPtr->name, instPtr->tclInstCode); } /* * Roll up the stack usage of the embedded block into the assembler * environment. */ SyncStackDepth(assemEnvPtr); envPtr->currStackDepth = savedStackDepth; envPtr->maxStackDepth = savedMaxStackDepth; /* * Save any exception ranges that were pushed by the compiler; they will * need to be fixed up once the stack depth is known. */ MoveExceptionRangesToBasicBlock(assemEnvPtr, savedExceptArrayNext); /* * Flush the current basic block. */ StartBasicBlock(assemEnvPtr, BB_FALLTHRU, NULL); } /* *----------------------------------------------------------------------------- * * SyncStackDepth -- * * Copies the stack depth from the compile environment to a basic block. * * Side effects: * Current and max stack depth in the current basic block are adjusted. * * This procedure is called on return from invoking the compiler for the * 'eval' and 'expr' operations. It adjusts the stack depth of the current * basic block to reflect the stack required by the just-compiled code. * *----------------------------------------------------------------------------- */ static void SyncStackDepth( AssemblyEnv* assemEnvPtr) /* Assembly environment */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ BasicBlock* curr_bb = assemEnvPtr->curr_bb; /* Current basic block */ int maxStackDepth = curr_bb->finalStackDepth + envPtr->maxStackDepth; /* Max stack depth in the basic block */ if (maxStackDepth > curr_bb->maxStackDepth) { curr_bb->maxStackDepth = maxStackDepth; } curr_bb->finalStackDepth += envPtr->currStackDepth; } /* *----------------------------------------------------------------------------- * * MoveExceptionRangesToBasicBlock -- * * Removes exception ranges that were created by compiling an embedded * script from the CompileEnv, and stores them in the BasicBlock. They * will be reinstalled, at the correct stack depth, after control flow * analysis is complete on the assembly code. * *----------------------------------------------------------------------------- */ static void MoveExceptionRangesToBasicBlock( AssemblyEnv* assemEnvPtr, /* Assembly environment */ int savedExceptArrayNext) /* Saved index of the end of the exception * range array */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ BasicBlock* curr_bb = assemEnvPtr->curr_bb; /* Current basic block */ int exceptionCount = envPtr->exceptArrayNext - savedExceptArrayNext; /* Number of ranges that must be moved */ int i; if (exceptionCount == 0) { /* Nothing to do */ return; } /* * Save the exception ranges in the basic block. They will be re-added at * the conclusion of assembly; at this time, the INST_BEGIN_CATCH * instructions in the block will be adjusted from whatever range indices * they have [savedExceptArrayNext .. envPtr->exceptArrayNext) to the * indices that the exceptions acquire. The saved exception ranges are * converted to a relative nesting depth. The depth will be recomputed * once flow analysis has determined the actual stack depth of the block. */ DEBUG_PRINT("basic block %p has %d exceptions starting at %d\n", curr_bb, exceptionCount, savedExceptArrayNext); curr_bb->foreignExceptionBase = savedExceptArrayNext; curr_bb->foreignExceptionCount = exceptionCount; curr_bb->foreignExceptions = (ExceptionRange*)Tcl_Alloc(exceptionCount * sizeof(ExceptionRange)); memcpy(curr_bb->foreignExceptions, envPtr->exceptArrayPtr + savedExceptArrayNext, exceptionCount * sizeof(ExceptionRange)); for (i = 0; i < exceptionCount; ++i) { curr_bb->foreignExceptions[i].nestingLevel -= envPtr->exceptDepth; } envPtr->exceptArrayNext = savedExceptArrayNext; } /* *----------------------------------------------------------------------------- * * CreateMirrorJumpTable -- * * Makes a jump table with comparison values and assembly code labels. * * Results: * Returns a standard Tcl status, with an error message in the * interpreter on error. * * Side effects: * Initializes the jump table pointer in the current basic block to a * JumptableInfo. The keys in the JumptableInfo are the comparison * strings. The values, instead of being jump displacements, are * Tcl_Obj's with the code labels. */ static int CreateMirrorJumpTable( AssemblyEnv* assemEnvPtr, /* Assembly environment */ Tcl_Obj* jumps) /* List of alternating keywords and labels */ { Tcl_Size objc; /* Number of elements in the 'jumps' list */ Tcl_Obj** objv; /* Pointers to the elements in the list */ CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ BasicBlock* bbPtr = assemEnvPtr->curr_bb; /* Current basic block */ JumptableInfo* jtPtr; Tcl_HashTable* jtHashPtr; /* Hashtable in the JumptableInfo */ Tcl_HashEntry* hashEntry; /* Entry for a key in the hashtable */ int isNew; /* Flag==1 if the key is not yet in the * table. */ Tcl_Size i; if (TclListObjLength(interp, jumps, &objc) != TCL_OK) { return TCL_ERROR; } if (objc % 2 != 0) { if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "jump table must have an even number of list elements", -1)); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADJUMPTABLE", (char *)NULL); } return TCL_ERROR; } if (TclListObjGetElements(interp, jumps, &objc, &objv) != TCL_OK) { return TCL_ERROR; } /* * Allocate the jumptable. */ jtPtr = (JumptableInfo*)Tcl_Alloc(sizeof(JumptableInfo)); jtHashPtr = &jtPtr->hashTable; Tcl_InitHashTable(jtHashPtr, TCL_STRING_KEYS); /* * Fill the keys and labels into the table. */ DEBUG_PRINT("jump table {\n"); for (i = 0; i < objc; i+=2) { DEBUG_PRINT(" %s -> %s\n", TclGetString(objv[i]), TclGetString(objv[i+1])); hashEntry = Tcl_CreateHashEntry(jtHashPtr, TclGetString(objv[i]), &isNew); if (!isNew) { if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "duplicate entry in jump table for \"%s\"", TclGetString(objv[i]))); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "DUPJUMPTABLEENTRY", (char *)NULL); DeleteMirrorJumpTable(jtPtr); return TCL_ERROR; } } Tcl_SetHashValue(hashEntry, objv[i+1]); Tcl_IncrRefCount(objv[i+1]); } DEBUG_PRINT("}\n"); /* * Put the mirror jumptable in the basic block struct. */ bbPtr->jtPtr = jtPtr; return TCL_OK; } /* *----------------------------------------------------------------------------- * * DeleteMirrorJumpTable -- * * Cleans up a jump table when the basic block is deleted. * *----------------------------------------------------------------------------- */ static void DeleteMirrorJumpTable( JumptableInfo* jtPtr) { Tcl_HashTable* jtHashPtr = &jtPtr->hashTable; /* Hash table pointer */ Tcl_HashSearch search; /* Hash search control */ Tcl_HashEntry* entry; /* Hash table entry containing a jump label */ Tcl_Obj* label; /* Jump label from the hash table */ for (entry = Tcl_FirstHashEntry(jtHashPtr, &search); entry != NULL; entry = Tcl_NextHashEntry(&search)) { label = (Tcl_Obj*)Tcl_GetHashValue(entry); Tcl_DecrRefCount(label); Tcl_SetHashValue(entry, NULL); } Tcl_DeleteHashTable(jtHashPtr); Tcl_Free(jtPtr); } /* *----------------------------------------------------------------------------- * * GetNextOperand -- * * Retrieves the next operand in sequence from an assembly instruction, * and makes sure that its value is known at compile time. * * Results: * If successful, returns TCL_OK and leaves a Tcl_Obj with the operand * text in *operandObjPtr. In case of failure, returns TCL_ERROR and * leaves *operandObjPtr untouched. * * Side effects: * Advances *tokenPtrPtr around the token just processed. * *----------------------------------------------------------------------------- */ static int GetNextOperand( AssemblyEnv* assemEnvPtr, /* Assembly environment */ Tcl_Token** tokenPtrPtr, /* INPUT/OUTPUT: Pointer to the token holding * the operand */ Tcl_Obj** operandObjPtr) /* OUTPUT: Tcl object holding the operand text * with \-substitutions done. */ { Tcl_Interp* interp = (Tcl_Interp*) assemEnvPtr->envPtr->iPtr; Tcl_Obj* operandObj; TclNewObj(operandObj); if (!TclWordKnownAtCompileTime(*tokenPtrPtr, operandObj)) { Tcl_DecrRefCount(operandObj); if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "assembly code may not contain substitutions", -1)); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "NOSUBST", (char *)NULL); } return TCL_ERROR; } *tokenPtrPtr = TokenAfter(*tokenPtrPtr); Tcl_IncrRefCount(operandObj); *operandObjPtr = operandObj; return TCL_OK; } /* *----------------------------------------------------------------------------- * * GetBooleanOperand -- * * Retrieves a Boolean operand from the input stream and advances * the token pointer. * * Results: * Returns a standard Tcl result (with an error message in the * interpreter on failure). * * Side effects: * Stores the Boolean value in (*result) and advances (*tokenPtrPtr) * to the next token. * *----------------------------------------------------------------------------- */ static int GetBooleanOperand( AssemblyEnv* assemEnvPtr, /* Assembly environment */ Tcl_Token** tokenPtrPtr, /* Current token from the parser */ int* result) /* OUTPUT: Integer extracted from the token */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ Tcl_Token* tokenPtr = *tokenPtrPtr; /* INOUT: Pointer to the next token in the * source code */ Tcl_Obj* intObj; /* Integer from the source code */ int status; /* Tcl status return */ /* * Extract the next token as a string. */ if (GetNextOperand(assemEnvPtr, tokenPtrPtr, &intObj) != TCL_OK) { return TCL_ERROR; } /* * Convert to an integer, advance to the next token and return. */ status = Tcl_GetBooleanFromObj(interp, intObj, result); Tcl_DecrRefCount(intObj); *tokenPtrPtr = TokenAfter(tokenPtr); return status; } /* *----------------------------------------------------------------------------- * * GetIntegerOperand -- * * Retrieves an integer operand from the input stream and advances the * token pointer. * * Results: * Returns a standard Tcl result (with an error message in the * interpreter on failure). * * Side effects: * Stores the integer value in (*result) and advances (*tokenPtrPtr) to * the next token. * *----------------------------------------------------------------------------- */ static int GetIntegerOperand( AssemblyEnv* assemEnvPtr, /* Assembly environment */ Tcl_Token** tokenPtrPtr, /* Current token from the parser */ int* result) /* OUTPUT: Integer extracted from the token */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ Tcl_Token* tokenPtr = *tokenPtrPtr; /* INOUT: Pointer to the next token in the * source code */ Tcl_Obj* intObj; /* Integer from the source code */ int status; /* Tcl status return */ /* * Extract the next token as a string. */ if (GetNextOperand(assemEnvPtr, tokenPtrPtr, &intObj) != TCL_OK) { return TCL_ERROR; } /* * Convert to an integer, advance to the next token and return. */ status = Tcl_GetIntFromObj(interp, intObj, result); Tcl_DecrRefCount(intObj); *tokenPtrPtr = TokenAfter(tokenPtr); return status; } /* *----------------------------------------------------------------------------- * * GetListIndexOperand -- * * Gets the value of an operand intended to serve as a list index. * * Results: * Returns a standard Tcl result: TCL_OK if the parse is successful and * TCL_ERROR (with an appropriate error message) if the parse fails. * * Side effects: * Stores the list index at '*index'. Values between -1 and 0x7FFFFFFF * have their natural meaning; values between -2 and -0x80000000 * represent 'end-2-N'. * *----------------------------------------------------------------------------- */ static int GetListIndexOperand( AssemblyEnv* assemEnvPtr, /* Assembly environment */ Tcl_Token** tokenPtrPtr, /* Current token from the parser */ int* result) /* OUTPUT: encoded index derived from the token */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ Tcl_Token* tokenPtr = *tokenPtrPtr; /* INOUT: Pointer to the next token in the * source code */ Tcl_Obj *value; int status; /* General operand validity check */ if (GetNextOperand(assemEnvPtr, tokenPtrPtr, &value) != TCL_OK) { return TCL_ERROR; } /* Convert to an integer, advance to the next token and return. */ /* * NOTE: Indexing a list with an index before it yields the * same result as indexing after it, and might be more easily portable * when list size limits grow. */ status = TclIndexEncode(interp, value, TCL_INDEX_NONE,TCL_INDEX_NONE, result); Tcl_DecrRefCount(value); *tokenPtrPtr = TokenAfter(tokenPtr); return status; } /* *----------------------------------------------------------------------------- * * FindLocalVar -- * * Gets the name of a local variable from the input stream and advances * the token pointer. * * Results: * Returns the LVT index of the local variable. Returns -1 if the * variable is non-local, not known at compile time, or cannot be * installed in the LVT (leaving an error message in the interpreter * result if necessary). * * Side effects: * Advances the token pointer. May define a new LVT slot if the variable * has not yet been seen and the execution context allows for it. * *----------------------------------------------------------------------------- */ static size_t FindLocalVar( AssemblyEnv* assemEnvPtr, /* Assembly environment */ Tcl_Token** tokenPtrPtr) { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ Tcl_Token* tokenPtr = *tokenPtrPtr; /* INOUT: Pointer to the next token in the * source code. */ Tcl_Obj* varNameObj; /* Name of the variable */ const char* varNameStr; Tcl_Size varNameLen; Tcl_Size localVar; /* Index of the variable in the LVT */ if (GetNextOperand(assemEnvPtr, tokenPtrPtr, &varNameObj) != TCL_OK) { return TCL_INDEX_NONE; } varNameStr = TclGetStringFromObj(varNameObj, &varNameLen); if (CheckNamespaceQualifiers(interp, varNameStr, varNameLen)) { Tcl_DecrRefCount(varNameObj); return TCL_INDEX_NONE; } localVar = TclFindCompiledLocal(varNameStr, varNameLen, 1, envPtr); Tcl_DecrRefCount(varNameObj); if (localVar < 0) { if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot use this instruction to create a variable" " in a non-proc context", -1)); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "LVT", (char *)NULL); } return TCL_INDEX_NONE; } *tokenPtrPtr = TokenAfter(tokenPtr); return localVar; } /* *----------------------------------------------------------------------------- * * CheckNamespaceQualifiers -- * * Verify that a variable name has no namespace qualifiers before * attempting to install it in the LVT. * * Results: * On success, returns TCL_OK. On failure, returns TCL_ERROR and stores * an error message in the interpreter result. * *----------------------------------------------------------------------------- */ static int CheckNamespaceQualifiers( Tcl_Interp* interp, /* Tcl interpreter for error reporting */ const char* name, /* Variable name to check */ int nameLen) /* Length of the variable */ { const char* p; for (p = name; p+2 < name+nameLen; p++) { if ((*p == ':') && (p[1] == ':')) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "variable \"%s\" is not local", name)); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "NONLOCAL", name, (char *)NULL); return TCL_ERROR; } } return TCL_OK; } /* *----------------------------------------------------------------------------- * * CheckOneByte -- * * Verify that a constant fits in a single byte in the instruction * stream. * * Results: * On success, returns TCL_OK. On failure, returns TCL_ERROR and stores * an error message in the interpreter result. * * This code is here primarily to verify that instructions like INCR_SCALAR1 * are possible on a given local variable. The fact that there is no * INCR_SCALAR4 is puzzling. * *----------------------------------------------------------------------------- */ static int CheckOneByte( Tcl_Interp* interp, /* Tcl interpreter for error reporting */ int value) /* Value to check */ { Tcl_Obj* result; /* Error message */ if (value < 0 || value > 0xFF) { result = Tcl_NewStringObj("operand does not fit in one byte", -1); Tcl_SetObjResult(interp, result); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "1BYTE", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *----------------------------------------------------------------------------- * * CheckSignedOneByte -- * * Verify that a constant fits in a single signed byte in the instruction * stream. * * Results: * On success, returns TCL_OK. On failure, returns TCL_ERROR and stores * an error message in the interpreter result. * * This code is here primarily to verify that instructions like INCR_SCALAR1 * are possible on a given local variable. The fact that there is no * INCR_SCALAR4 is puzzling. * *----------------------------------------------------------------------------- */ static int CheckSignedOneByte( Tcl_Interp* interp, /* Tcl interpreter for error reporting */ int value) /* Value to check */ { Tcl_Obj* result; /* Error message */ if (value > 0x7F || value < -0x80) { result = Tcl_NewStringObj("operand does not fit in one byte", -1); Tcl_SetObjResult(interp, result); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "1BYTE", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *----------------------------------------------------------------------------- * * CheckNonNegative -- * * Verify that a constant is nonnegative * * Results: * On success, returns TCL_OK. On failure, returns TCL_ERROR and stores * an error message in the interpreter result. * * This code is here primarily to verify that instructions like INCR_INVOKE * are consuming a positive number of operands * *----------------------------------------------------------------------------- */ static int CheckNonNegative( Tcl_Interp* interp, /* Tcl interpreter for error reporting */ int value) /* Value to check */ { Tcl_Obj* result; /* Error message */ if (value < 0) { result = Tcl_NewStringObj("operand must be nonnegative", -1); Tcl_SetObjResult(interp, result); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "NONNEGATIVE", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *----------------------------------------------------------------------------- * * CheckStrictlyPositive -- * * Verify that a constant is positive * * Results: * On success, returns TCL_OK. On failure, returns TCL_ERROR and * stores an error message in the interpreter result. * * This code is here primarily to verify that instructions like INCR_INVOKE * are consuming a positive number of operands * *----------------------------------------------------------------------------- */ static int CheckStrictlyPositive( Tcl_Interp* interp, /* Tcl interpreter for error reporting */ int value) /* Value to check */ { Tcl_Obj* result; /* Error message */ if (value <= 0) { result = Tcl_NewStringObj("operand must be positive", -1); Tcl_SetObjResult(interp, result); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "POSITIVE", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *----------------------------------------------------------------------------- * * DefineLabel -- * * Defines a label appearing in the assembly sequence. * * Results: * Returns a standard Tcl result. Returns TCL_OK and an empty result if * the definition succeeds; returns TCL_ERROR and an appropriate message * if a duplicate definition is found. * *----------------------------------------------------------------------------- */ static int DefineLabel( AssemblyEnv* assemEnvPtr, /* Assembly environment */ const char* labelName) /* Label being defined */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ Tcl_HashEntry* entry; /* Label's entry in the symbol table */ int isNew; /* Flag == 1 iff the label was previously * undefined */ /* TODO - This can now be simplified! */ StartBasicBlock(assemEnvPtr, BB_FALLTHRU, NULL); /* * Look up the newly-defined label in the symbol table. */ entry = Tcl_CreateHashEntry(&assemEnvPtr->labelHash, labelName, &isNew); if (!isNew) { /* * This is a duplicate label. */ if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "duplicate definition of label \"%s\"", labelName)); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "DUPLABEL", labelName, (char *)NULL); } return TCL_ERROR; } /* * This is the first appearance of the label in the code. */ Tcl_SetHashValue(entry, assemEnvPtr->curr_bb); return TCL_OK; } /* *----------------------------------------------------------------------------- * * StartBasicBlock -- * * Starts a new basic block when a label or jump is encountered. * * Results: * Returns a pointer to the BasicBlock structure of the new * basic block. * *----------------------------------------------------------------------------- */ static BasicBlock* StartBasicBlock( AssemblyEnv* assemEnvPtr, /* Assembly environment */ int flags, /* Flags to apply to the basic block being * closed, if there is one. */ Tcl_Obj* jumpLabel) /* Label of the location that the block jumps * to, or NULL if the block does not jump */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ BasicBlock* newBB; /* BasicBlock structure for the new block */ BasicBlock* currBB = assemEnvPtr->curr_bb; /* * Coalesce zero-length blocks. */ if (currBB->startOffset == envPtr->codeNext - envPtr->codeStart) { currBB->startLine = assemEnvPtr->cmdLine; return currBB; } /* * Make the new basic block. */ newBB = AllocBB(assemEnvPtr); /* * Record the jump target if there is one. */ currBB->jumpTarget = jumpLabel; if (jumpLabel != NULL) { Tcl_IncrRefCount(currBB->jumpTarget); } /* * Record the fallthrough if there is one. */ currBB->flags |= flags; /* * Record the successor block. */ currBB->successor1 = newBB; assemEnvPtr->curr_bb = newBB; return newBB; } /* *----------------------------------------------------------------------------- * * AllocBB -- * * Allocates a new basic block * * Results: * Returns a pointer to the newly allocated block, which is initialized * to contain no code and begin at the current instruction pointer. * *----------------------------------------------------------------------------- */ static BasicBlock * AllocBB( AssemblyEnv* assemEnvPtr) /* Assembly environment */ { CompileEnv* envPtr = assemEnvPtr->envPtr; BasicBlock *bb = (BasicBlock*)Tcl_Alloc(sizeof(BasicBlock)); bb->originalStartOffset = bb->startOffset = envPtr->codeNext - envPtr->codeStart; bb->startLine = assemEnvPtr->cmdLine + 1; bb->jumpOffset = -1; bb->jumpLine = -1; bb->prevPtr = assemEnvPtr->curr_bb; bb->predecessor = NULL; bb->successor1 = NULL; bb->jumpTarget = NULL; bb->initialStackDepth = 0; bb->minStackDepth = 0; bb->maxStackDepth = 0; bb->finalStackDepth = 0; bb->catchDepth = 0; bb->enclosingCatch = NULL; bb->foreignExceptionBase = -1; bb->foreignExceptionCount = 0; bb->foreignExceptions = NULL; bb->jtPtr = NULL; bb->flags = 0; return bb; } /* *----------------------------------------------------------------------------- * * FinishAssembly -- * * Postprocessing after all bytecode has been generated for a block of * assembly code. * * Results: * Returns a standard Tcl result, with an error message left in the * interpreter if appropriate. * * Side effects: * The program is checked to see if any undefined labels remain. The * initial stack depth of all the basic blocks in the flow graph is * calculated and saved. The stack balance on exit is computed, checked * and saved. * *----------------------------------------------------------------------------- */ static int FinishAssembly( AssemblyEnv* assemEnvPtr) /* Assembly environment */ { int mustMove; /* Amount by which the code needs to be grown * because of expanding jumps */ /* * Resolve the targets of all jumps and determine whether code needs to be * moved around. */ if (CalculateJumpRelocations(assemEnvPtr, &mustMove)) { return TCL_ERROR; } /* * Move the code if necessary. */ if (mustMove) { MoveCodeForJumps(assemEnvPtr, mustMove); } /* * Resolve jump target labels to bytecode offsets. */ FillInJumpOffsets(assemEnvPtr); /* * Label each basic block with its catch context. Quit on inconsistency. */ if (ProcessCatches(assemEnvPtr) != TCL_OK) { return TCL_ERROR; } /* * Make sure that no block accessible from a catch's error exit that hasn't * popped the exception stack can throw an exception. */ if (CheckForThrowInWrongContext(assemEnvPtr) != TCL_OK) { return TCL_ERROR; } /* * Compute stack balance throughout the program. */ if (CheckStack(assemEnvPtr) != TCL_OK) { return TCL_ERROR; } /* * TODO - Check for unreachable code. Or maybe not; unreachable code is * Mostly Harmless. */ return TCL_OK; } /* *----------------------------------------------------------------------------- * * CalculateJumpRelocations -- * * Calculate any movement that has to be done in the assembly code to * expand JUMP1 instructions to JUMP4 (because they jump more than a * 1-byte range). * * Results: * Returns a standard Tcl result, with an appropriate error message if * anything fails. * * Side effects: * Sets the 'startOffset' pointer in every basic block to the new origin * of the block, and turns off JUMP1 flags on instructions that must be * expanded (and adjusts them to the corresponding JUMP4's). Does *not* * store the jump offsets at this point. * * Sets *mustMove to 1 if and only if at least one instruction changed * size so the code must be moved. * * As a side effect, also checks for undefined labels and reports them. * *----------------------------------------------------------------------------- */ static int CalculateJumpRelocations( AssemblyEnv* assemEnvPtr, /* Assembly environment */ int* mustMove) /* OUTPUT: Number of bytes that have been * added to the code */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ BasicBlock* bbPtr; /* Pointer to a basic block being checked */ Tcl_HashEntry* entry; /* Exit label's entry in the symbol table */ BasicBlock* jumpTarget; /* Basic block where the jump goes */ int motion; /* Amount by which the code has expanded */ int offset; /* Offset in the bytecode from a jump * instruction to its target */ unsigned opcode; /* Opcode in the bytecode being adjusted */ /* * Iterate through basic blocks as long as a change results in code * expansion. */ *mustMove = 0; do { motion = 0; for (bbPtr = assemEnvPtr->head_bb; bbPtr != NULL; bbPtr = bbPtr->successor1) { /* * Advance the basic block start offset by however many bytes we * have inserted in the code up to this point */ bbPtr->startOffset += motion; /* * If the basic block references a label (and hence performs a * jump), find the location of the label. Report an error if the * label is missing. */ if (bbPtr->jumpTarget != NULL) { entry = Tcl_FindHashEntry(&assemEnvPtr->labelHash, TclGetString(bbPtr->jumpTarget)); if (entry == NULL) { ReportUndefinedLabel(assemEnvPtr, bbPtr, bbPtr->jumpTarget); return TCL_ERROR; } /* * If the instruction is a JUMP1, turn it into a JUMP4 if its * target is out of range. */ jumpTarget = (BasicBlock*)Tcl_GetHashValue(entry); if (bbPtr->flags & BB_JUMP1) { offset = jumpTarget->startOffset - (bbPtr->jumpOffset + motion); if (offset < -0x80 || offset > 0x7F) { opcode = TclGetUInt1AtPtr(envPtr->codeStart + bbPtr->jumpOffset); ++opcode; TclStoreInt1AtPtr(opcode, envPtr->codeStart + bbPtr->jumpOffset); motion += 3; bbPtr->flags &= ~BB_JUMP1; } } } /* * If the basic block references a jump table, that doesn't affect * the code locations, but resolve the labels now, and store basic * block pointers in the jumptable hash. */ if (bbPtr->flags & BB_JUMPTABLE) { if (CheckJumpTableLabels(assemEnvPtr, bbPtr) != TCL_OK) { return TCL_ERROR; } } } *mustMove += motion; } while (motion != 0); return TCL_OK; } /* *----------------------------------------------------------------------------- * * CheckJumpTableLabels -- * * Make sure that all the labels in a jump table are defined. * * Results: * Returns TCL_OK if they are, TCL_ERROR if they aren't. * *----------------------------------------------------------------------------- */ static int CheckJumpTableLabels( AssemblyEnv* assemEnvPtr, /* Assembly environment */ BasicBlock* bbPtr) /* Basic block that ends in a jump table */ { Tcl_HashTable* symHash = &bbPtr->jtPtr->hashTable; /* Hash table with the symbols */ Tcl_HashSearch search; /* Hash table iterator */ Tcl_HashEntry* symEntryPtr; /* Hash entry for the symbols */ Tcl_Obj* symbolObj; /* Jump target */ Tcl_HashEntry* valEntryPtr; /* Hash entry for the resolutions */ /* * Look up every jump target in the jump hash. */ DEBUG_PRINT("check jump table labels %p {\n", bbPtr); for (symEntryPtr = Tcl_FirstHashEntry(symHash, &search); symEntryPtr != NULL; symEntryPtr = Tcl_NextHashEntry(&search)) { symbolObj = (Tcl_Obj*)Tcl_GetHashValue(symEntryPtr); valEntryPtr = Tcl_FindHashEntry(&assemEnvPtr->labelHash, TclGetString(symbolObj)); DEBUG_PRINT(" %s -> %s (%d)\n", (char *)Tcl_GetHashKey(symHash, symEntryPtr), TclGetString(symbolObj), (valEntryPtr != NULL)); if (valEntryPtr == NULL) { ReportUndefinedLabel(assemEnvPtr, bbPtr, symbolObj); return TCL_ERROR; } } DEBUG_PRINT("}\n"); return TCL_OK; } /* *----------------------------------------------------------------------------- * * ReportUndefinedLabel -- * * Report that a basic block refers to an undefined jump label * * Side effects: * Stores an error message, error code, and line number information in * the assembler's Tcl interpreter. * *----------------------------------------------------------------------------- */ static void ReportUndefinedLabel( AssemblyEnv* assemEnvPtr, /* Assembly environment */ BasicBlock* bbPtr, /* Basic block that contains the undefined * label */ Tcl_Obj* jumpTarget) /* Label of a jump target */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "undefined label \"%s\"", TclGetString(jumpTarget))); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "NOLABEL", TclGetString(jumpTarget), (char *)NULL); Tcl_SetErrorLine(interp, bbPtr->jumpLine); } } /* *----------------------------------------------------------------------------- * * MoveCodeForJumps -- * * Move bytecodes in memory to accommodate JUMP1 instructions that have * expanded to become JUMP4's. * *----------------------------------------------------------------------------- */ static void MoveCodeForJumps( AssemblyEnv* assemEnvPtr, /* Assembler environment */ int mustMove) /* Number of bytes of added code */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ BasicBlock* bbPtr; /* Pointer to a basic block being checked */ int topOffset; /* Bytecode offset of the following basic * block before code motion */ /* * Make sure that there is enough space in the bytecode array to * accommodate the expanded code. */ while (envPtr->codeEnd < envPtr->codeNext + mustMove) { TclExpandCodeArray(envPtr); } /* * Iterate through the bytecodes in reverse order, and move them upward to * their new homes. */ topOffset = envPtr->codeNext - envPtr->codeStart; for (bbPtr = assemEnvPtr->curr_bb; bbPtr != NULL; bbPtr = bbPtr->prevPtr) { DEBUG_PRINT("move code from %d to %d\n", bbPtr->originalStartOffset, bbPtr->startOffset); memmove(envPtr->codeStart + bbPtr->startOffset, envPtr->codeStart + bbPtr->originalStartOffset, topOffset - bbPtr->originalStartOffset); topOffset = bbPtr->originalStartOffset; bbPtr->jumpOffset += (bbPtr->startOffset - bbPtr->originalStartOffset); } envPtr->codeNext += mustMove; } /* *----------------------------------------------------------------------------- * * FillInJumpOffsets -- * * Fill in the final offsets of all jump instructions once bytecode * locations have been completely determined. * *----------------------------------------------------------------------------- */ static void FillInJumpOffsets( AssemblyEnv* assemEnvPtr) /* Assembly environment */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ BasicBlock* bbPtr; /* Pointer to a basic block being checked */ Tcl_HashEntry* entry; /* Hashtable entry for a jump target label */ BasicBlock* jumpTarget; /* Basic block where a jump goes */ int fromOffset; /* Bytecode location of a jump instruction */ int targetOffset; /* Bytecode location of a jump instruction's * target */ for (bbPtr = assemEnvPtr->head_bb; bbPtr != NULL; bbPtr = bbPtr->successor1) { if (bbPtr->jumpTarget != NULL) { entry = Tcl_FindHashEntry(&assemEnvPtr->labelHash, TclGetString(bbPtr->jumpTarget)); jumpTarget = (BasicBlock*)Tcl_GetHashValue(entry); fromOffset = bbPtr->jumpOffset; targetOffset = jumpTarget->startOffset; if (bbPtr->flags & BB_JUMP1) { TclStoreInt1AtPtr(targetOffset - fromOffset, envPtr->codeStart + fromOffset + 1); } else { TclStoreInt4AtPtr(targetOffset - fromOffset, envPtr->codeStart + fromOffset + 1); } } if (bbPtr->flags & BB_JUMPTABLE) { ResolveJumpTableTargets(assemEnvPtr, bbPtr); } } } /* *----------------------------------------------------------------------------- * * ResolveJumpTableTargets -- * * Puts bytecode addresses for the targets of a jumptable into the * table * * Results: * Returns TCL_OK if they are, TCL_ERROR if they aren't. * *----------------------------------------------------------------------------- */ static void ResolveJumpTableTargets( AssemblyEnv* assemEnvPtr, /* Assembly environment */ BasicBlock* bbPtr) /* Basic block that ends in a jump table */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_HashTable* symHash = &bbPtr->jtPtr->hashTable; /* Hash table with the symbols */ Tcl_HashSearch search; /* Hash table iterator */ Tcl_HashEntry* symEntryPtr; /* Hash entry for the symbols */ Tcl_Obj* symbolObj; /* Jump target */ Tcl_HashEntry* valEntryPtr; /* Hash entry for the resolutions */ int auxDataIndex; /* Index of the auxdata */ JumptableInfo* realJumpTablePtr; /* Jump table in the actual code */ Tcl_HashTable* realJumpHashPtr; /* Jump table hash in the actual code */ Tcl_HashEntry* realJumpEntryPtr; /* Entry in the jump table hash in * the actual code */ BasicBlock* jumpTargetBBPtr; /* Basic block that the jump proceeds to */ int junk; auxDataIndex = TclGetInt4AtPtr(envPtr->codeStart + bbPtr->jumpOffset + 1); DEBUG_PRINT("bbPtr = %p jumpOffset = %d auxDataIndex = %d\n", bbPtr, bbPtr->jumpOffset, auxDataIndex); realJumpTablePtr = (JumptableInfo*)TclFetchAuxData(envPtr, auxDataIndex); realJumpHashPtr = &realJumpTablePtr->hashTable; /* * Look up every jump target in the jump hash. */ DEBUG_PRINT("resolve jump table {\n"); for (symEntryPtr = Tcl_FirstHashEntry(symHash, &search); symEntryPtr != NULL; symEntryPtr = Tcl_NextHashEntry(&search)) { symbolObj = (Tcl_Obj*)Tcl_GetHashValue(symEntryPtr); DEBUG_PRINT(" symbol %s\n", TclGetString(symbolObj)); valEntryPtr = Tcl_FindHashEntry(&assemEnvPtr->labelHash, TclGetString(symbolObj)); jumpTargetBBPtr = (BasicBlock*)Tcl_GetHashValue(valEntryPtr); realJumpEntryPtr = Tcl_CreateHashEntry(realJumpHashPtr, Tcl_GetHashKey(symHash, symEntryPtr), &junk); DEBUG_PRINT(" %s -> %s -> bb %p (pc %d) hash entry %p\n", (char *)Tcl_GetHashKey(symHash, symEntryPtr), TclGetString(symbolObj), jumpTargetBBPtr, jumpTargetBBPtr->startOffset, realJumpEntryPtr); Tcl_SetHashValue(realJumpEntryPtr, INT2PTR(jumpTargetBBPtr->startOffset - bbPtr->jumpOffset)); } DEBUG_PRINT("}\n"); } /* *----------------------------------------------------------------------------- * * CheckForThrowInWrongContext -- * * Verify that no beginCatch/endCatch sequence can throw an exception * after an original exception is caught and before its exception context * is removed from the stack. * * Results: * Returns a standard Tcl result. * * Side effects: * Stores an appropriate error message in the interpreter as needed. * *----------------------------------------------------------------------------- */ static int CheckForThrowInWrongContext( AssemblyEnv* assemEnvPtr) /* Assembly environment */ { BasicBlock* blockPtr; /* Current basic block */ /* * Walk through the basic blocks in turn, checking all the ones that have * caught an exception and not disposed of it properly. */ for (blockPtr = assemEnvPtr->head_bb; blockPtr != NULL; blockPtr = blockPtr->successor1) { if (blockPtr->catchState == BBCS_CAUGHT) { /* * Walk through the instructions in the basic block. */ if (CheckNonThrowingBlock(assemEnvPtr, blockPtr) != TCL_OK) { return TCL_ERROR; } } } return TCL_OK; } /* *----------------------------------------------------------------------------- * * CheckNonThrowingBlock -- * * Check that a basic block cannot throw an exception. * * Results: * Returns TCL_ERROR if the block cannot be proven to be nonthrowing. * * Side effects: * Stashes an error message in the interpreter result. * *----------------------------------------------------------------------------- */ static int CheckNonThrowingBlock( AssemblyEnv* assemEnvPtr, /* Assembly environment */ BasicBlock* blockPtr) /* Basic block where exceptions are not * allowed */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ BasicBlock* nextPtr; /* Pointer to the succeeding basic block */ int offset; /* Bytecode offset of the current * instruction */ int bound; /* Bytecode offset following the last * instruction of the block. */ unsigned char opcode; /* Current bytecode instruction */ /* * Determine where in the code array the basic block ends. */ nextPtr = blockPtr->successor1; if (nextPtr == NULL) { bound = envPtr->codeNext - envPtr->codeStart; } else { bound = nextPtr->startOffset; } /* * Walk through the instructions of the block. */ offset = blockPtr->startOffset; while (offset < bound) { /* * Determine whether an instruction is nonthrowing. */ opcode = (envPtr->codeStart)[offset]; if (BytecodeMightThrow(opcode)) { /* * Report an error for a throw in the wrong context. */ if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" instruction may not appear in " "a context where an exception has been " "caught and not disposed of.", tclInstructionTable[opcode].name)); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADTHROW", (char *)NULL); AddBasicBlockRangeToErrorInfo(assemEnvPtr, blockPtr); } return TCL_ERROR; } offset += tclInstructionTable[opcode].numBytes; } return TCL_OK; } /* *----------------------------------------------------------------------------- * * BytecodeMightThrow -- * * Tests if a given bytecode instruction might throw an exception. * * Results: * Returns 1 if the bytecode might throw an exception, 0 if the * instruction is known never to throw. * *----------------------------------------------------------------------------- */ static int BytecodeMightThrow( unsigned char opcode) { /* * Binary search on the non-throwing bytecode list. */ int min = 0; int max = sizeof(NonThrowingByteCodes) - 1; int mid; unsigned char c; while (max >= min) { mid = (min + max) / 2; c = NonThrowingByteCodes[mid]; if (opcode < c) { max = mid-1; } else if (opcode > c) { min = mid+1; } else { /* * Opcode is nonthrowing. */ return 0; } } return 1; } /* *----------------------------------------------------------------------------- * * CheckStack -- * * Audit stack usage in a block of assembly code. * * Results: * Returns a standard Tcl result. * * Side effects: * Updates stack depth on entry for all basic blocks in the flowgraph. * Calculates the max stack depth used in the program, and updates the * compilation environment to reflect it. * *----------------------------------------------------------------------------- */ static int CheckStack( AssemblyEnv* assemEnvPtr) /* Assembly environment */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Size maxDepth; /* Maximum stack depth overall */ /* * Checking the head block will check all the other blocks recursively. */ assemEnvPtr->maxDepth = 0; if (StackCheckBasicBlock(assemEnvPtr, assemEnvPtr->head_bb, NULL, 0) == TCL_ERROR) { return TCL_ERROR; } /* * Post the max stack depth back to the compilation environment. */ maxDepth = assemEnvPtr->maxDepth + envPtr->currStackDepth; if (maxDepth > envPtr->maxStackDepth) { envPtr->maxStackDepth = maxDepth; } /* * If the exit is reachable, make sure that the program exits with 1 * operand on the stack. */ if (StackCheckExit(assemEnvPtr) != TCL_OK) { return TCL_ERROR; } /* * Reset the visited state on all basic blocks. */ ResetVisitedBasicBlocks(assemEnvPtr); return TCL_OK; } /* *----------------------------------------------------------------------------- * * StackCheckBasicBlock -- * * Checks stack consumption for a basic block (and recursively for its * successors). * * Results: * Returns a standard Tcl result. * * Side effects: * Updates initial stack depth for the basic block and its successors. * (Final and maximum stack depth are relative to initial, and are not * touched). * * This procedure eventually checks, for the entire flow graph, whether stack * balance is consistent. It is an error for a given basic block to be * reachable along multiple flow paths with different stack depths. * *----------------------------------------------------------------------------- */ static int StackCheckBasicBlock( AssemblyEnv* assemEnvPtr, /* Assembly environment */ BasicBlock* blockPtr, /* Pointer to the basic block being checked */ BasicBlock* predecessor, /* Pointer to the block that passed control to * this one. */ int initialStackDepth) /* Stack depth on entry to the block */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ BasicBlock* jumpTarget; /* Basic block where a jump goes */ int stackDepth; /* Current stack depth */ int maxDepth; /* Maximum stack depth so far */ int result; /* Tcl status return */ Tcl_HashSearch jtSearch; /* Search structure for the jump table */ Tcl_HashEntry* jtEntry; /* Hash entry in the jump table */ Tcl_Obj* targetLabel; /* Target label from the jump table */ Tcl_HashEntry* entry; /* Hash entry in the label table */ if (blockPtr->flags & BB_VISITED) { /* * If the block is already visited, check stack depth for consistency * among the paths that reach it. */ if (blockPtr->initialStackDepth == initialStackDepth) { return TCL_OK; } if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "inconsistent stack depths on two execution paths", -1)); /* * TODO - add execution trace of both paths */ Tcl_SetErrorLine(interp, blockPtr->startLine); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADSTACK", (char *)NULL); } return TCL_ERROR; } /* * If the block is not already visited, set the 'predecessor' link to * indicate how control got to it. Set the initial stack depth to the * current stack depth in the flow of control. */ blockPtr->flags |= BB_VISITED; blockPtr->predecessor = predecessor; blockPtr->initialStackDepth = initialStackDepth; /* * Calculate minimum stack depth, and flag an error if the block * underflows the stack. */ if (initialStackDepth + blockPtr->minStackDepth < 0) { if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_NewStringObj("stack underflow", -1)); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADSTACK", (char *)NULL); AddBasicBlockRangeToErrorInfo(assemEnvPtr, blockPtr); Tcl_SetErrorLine(interp, blockPtr->startLine); } return TCL_ERROR; } /* * Make sure that the block doesn't try to pop below the stack level of an * enclosing catch. */ if (blockPtr->enclosingCatch != 0 && initialStackDepth + blockPtr->minStackDepth < (blockPtr->enclosingCatch->initialStackDepth + blockPtr->enclosingCatch->finalStackDepth)) { if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "code pops stack below level of enclosing catch", -1)); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADSTACKINCATCH", (char *)NULL); AddBasicBlockRangeToErrorInfo(assemEnvPtr, blockPtr); Tcl_SetErrorLine(interp, blockPtr->startLine); } return TCL_ERROR; } /* * Update maximum stgack depth. */ maxDepth = initialStackDepth + blockPtr->maxStackDepth; if (maxDepth > assemEnvPtr->maxDepth) { assemEnvPtr->maxDepth = maxDepth; } /* * Calculate stack depth on exit from the block, and invoke this procedure * recursively to check successor blocks. */ stackDepth = initialStackDepth + blockPtr->finalStackDepth; result = TCL_OK; if (blockPtr->flags & BB_FALLTHRU) { result = StackCheckBasicBlock(assemEnvPtr, blockPtr->successor1, blockPtr, stackDepth); } if (result == TCL_OK && blockPtr->jumpTarget != NULL) { entry = Tcl_FindHashEntry(&assemEnvPtr->labelHash, TclGetString(blockPtr->jumpTarget)); jumpTarget = (BasicBlock*)Tcl_GetHashValue(entry); result = StackCheckBasicBlock(assemEnvPtr, jumpTarget, blockPtr, stackDepth); } /* * All blocks referenced in a jump table are successors. */ if (blockPtr->flags & BB_JUMPTABLE) { for (jtEntry = Tcl_FirstHashEntry(&blockPtr->jtPtr->hashTable, &jtSearch); result == TCL_OK && jtEntry != NULL; jtEntry = Tcl_NextHashEntry(&jtSearch)) { targetLabel = (Tcl_Obj*)Tcl_GetHashValue(jtEntry); entry = Tcl_FindHashEntry(&assemEnvPtr->labelHash, TclGetString(targetLabel)); jumpTarget = (BasicBlock*)Tcl_GetHashValue(entry); result = StackCheckBasicBlock(assemEnvPtr, jumpTarget, blockPtr, stackDepth); } } return result; } /* *----------------------------------------------------------------------------- * * StackCheckExit -- * * Makes sure that the net stack effect of an entire assembly language * script is to push 1 result. * * Results: * Returns a standard Tcl result, with an error message in the * interpreter result if the stack is wrong. * * Side effects: * If the assembly code had a net stack effect of zero, emits code to the * concluding block to push a null result. In any case, updates the stack * depth in the compile environment to reflect the net effect of the * assembly code. * *----------------------------------------------------------------------------- */ static int StackCheckExit( AssemblyEnv* assemEnvPtr) /* Assembly environment */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ int depth; /* Net stack effect */ int litIndex; /* Index in the literal pool of the empty * string */ BasicBlock* curr_bb = assemEnvPtr->curr_bb; /* Final basic block in the assembly */ /* * Don't perform these checks if execution doesn't reach the exit (either * because of an infinite loop or because the only return is from the * middle. */ if (curr_bb->flags & BB_VISITED) { /* * Exit with no operands; push an empty one. */ depth = curr_bb->finalStackDepth + curr_bb->initialStackDepth; if (depth == 0) { /* * Emit a 'push' of the empty literal. */ litIndex = TclRegisterLiteral(envPtr, "", 0, 0); /* * Assumes that 'push' is at slot 0 in TalInstructionTable. */ BBEmitInst1or4(assemEnvPtr, 0, litIndex, 0); ++depth; } /* * Exit with unbalanced stack. */ if (depth != 1) { if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "stack is unbalanced on exit from the code (depth=%d)", depth)); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADSTACK", (char *)NULL); } return TCL_ERROR; } /* * Record stack usage. */ envPtr->currStackDepth += depth; } return TCL_OK; } /* *----------------------------------------------------------------------------- * * ProcessCatches -- * * First pass of 'catch' processing. * * Results: * Returns a standard Tcl result, with an appropriate error message if * the result is TCL_ERROR. * * Side effects: * Labels all basic blocks with their enclosing catches. * *----------------------------------------------------------------------------- */ static int ProcessCatches( AssemblyEnv* assemEnvPtr) /* Assembly environment */ { BasicBlock* blockPtr; /* Pointer to a basic block */ /* * Clear the catch state of all basic blocks. */ for (blockPtr = assemEnvPtr->head_bb; blockPtr != NULL; blockPtr = blockPtr->successor1) { blockPtr->catchState = BBCS_UNKNOWN; blockPtr->enclosingCatch = NULL; } /* * Start the check recursively from the first basic block, which is * outside any exception context */ if (ProcessCatchesInBasicBlock(assemEnvPtr, assemEnvPtr->head_bb, NULL, BBCS_NONE, 0) != TCL_OK) { return TCL_ERROR; } /* * Check for unclosed catch on exit. */ if (CheckForUnclosedCatches(assemEnvPtr) != TCL_OK) { return TCL_ERROR; } /* * Now there's enough information to build the exception ranges. */ if (BuildExceptionRanges(assemEnvPtr) != TCL_OK) { return TCL_ERROR; } /* * Finally, restore any exception ranges from embedded scripts. */ RestoreEmbeddedExceptionRanges(assemEnvPtr); return TCL_OK; } /* *----------------------------------------------------------------------------- * * ProcessCatchesInBasicBlock -- * * First-pass catch processing for one basic block. * * Results: * Returns a standard Tcl result, with error message in the interpreter * result if an error occurs. * * This procedure checks consistency of the exception context through the * assembler program, and records the enclosing 'catch' for every basic block. * *----------------------------------------------------------------------------- */ static int ProcessCatchesInBasicBlock( AssemblyEnv* assemEnvPtr, /* Assembly environment */ BasicBlock* bbPtr, /* Basic block being processed */ BasicBlock* enclosing, /* Start basic block of the enclosing catch */ enum BasicBlockCatchState state, /* BBCS_NONE, BBCS_INCATCH, or BBCS_CAUGHT */ int catchDepth) /* Depth of nesting of catches */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ int result; /* Return value from this procedure */ BasicBlock* fallThruEnclosing; /* Enclosing catch if execution falls thru */ enum BasicBlockCatchState fallThruState; /* Catch state of the successor block */ BasicBlock* jumpEnclosing; /* Enclosing catch if execution goes to jump * target */ enum BasicBlockCatchState jumpState; /* Catch state of the jump target */ int changed = 0; /* Flag == 1 iff successor blocks need to be * checked because the state of this block has * changed. */ BasicBlock* jumpTarget; /* Basic block where a jump goes */ Tcl_HashSearch jtSearch; /* Hash search control for a jumptable */ Tcl_HashEntry* jtEntry; /* Entry in a jumptable */ Tcl_Obj* targetLabel; /* Target label from a jumptable */ Tcl_HashEntry* entry; /* Entry from the label table */ /* * Update the state of the current block, checking for consistency. Set * 'changed' to 1 if the state changes and successor blocks need to be * rechecked. */ if (bbPtr->catchState == BBCS_UNKNOWN) { bbPtr->enclosingCatch = enclosing; } else if (bbPtr->enclosingCatch != enclosing) { if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "execution reaches an instruction in inconsistent " "exception contexts", -1)); Tcl_SetErrorLine(interp, bbPtr->startLine); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADCATCH", (char *)NULL); } return TCL_ERROR; } if (state > bbPtr->catchState) { bbPtr->catchState = state; changed = 1; } /* * If this block has been visited before, and its state hasn't changed, * we're done with it for now. */ if (!changed) { return TCL_OK; } bbPtr->catchDepth = catchDepth; /* * Determine enclosing catch and 'caught' state for the fallthrough and * the jump target. Default for both is the state of the current block. */ fallThruEnclosing = enclosing; fallThruState = state; jumpEnclosing = enclosing; jumpState = state; /* * TODO: Make sure that the test cases include validating that a natural * loop can't include 'beginCatch' or 'endCatch' */ if (bbPtr->flags & BB_BEGINCATCH) { /* * If the block begins a catch, the state for the successor is 'in * catch'. The jump target is the exception exit, and the state of the * jump target is 'caught.' */ fallThruEnclosing = bbPtr; fallThruState = BBCS_INCATCH; jumpEnclosing = bbPtr; jumpState = BBCS_CAUGHT; ++catchDepth; } if (bbPtr->flags & BB_ENDCATCH) { /* * If the block ends a catch, the state for the successor is whatever * the state was on entry to the catch. */ if (enclosing == NULL) { if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "endCatch without a corresponding beginCatch", -1)); Tcl_SetErrorLine(interp, bbPtr->startLine); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "BADENDCATCH", (char *)NULL); } return TCL_ERROR; } fallThruEnclosing = enclosing->enclosingCatch; fallThruState = enclosing->catchState; --catchDepth; } /* * Visit any successor blocks with the appropriate exception context */ result = TCL_OK; if (bbPtr->flags & BB_FALLTHRU) { result = ProcessCatchesInBasicBlock(assemEnvPtr, bbPtr->successor1, fallThruEnclosing, fallThruState, catchDepth); } if (result == TCL_OK && bbPtr->jumpTarget != NULL) { entry = Tcl_FindHashEntry(&assemEnvPtr->labelHash, TclGetString(bbPtr->jumpTarget)); jumpTarget = (BasicBlock*)Tcl_GetHashValue(entry); result = ProcessCatchesInBasicBlock(assemEnvPtr, jumpTarget, jumpEnclosing, jumpState, catchDepth); } /* * All blocks referenced in a jump table are successors. */ if (bbPtr->flags & BB_JUMPTABLE) { for (jtEntry = Tcl_FirstHashEntry(&bbPtr->jtPtr->hashTable,&jtSearch); result == TCL_OK && jtEntry != NULL; jtEntry = Tcl_NextHashEntry(&jtSearch)) { targetLabel = (Tcl_Obj*)Tcl_GetHashValue(jtEntry); entry = Tcl_FindHashEntry(&assemEnvPtr->labelHash, TclGetString(targetLabel)); jumpTarget = (BasicBlock*)Tcl_GetHashValue(entry); result = ProcessCatchesInBasicBlock(assemEnvPtr, jumpTarget, jumpEnclosing, jumpState, catchDepth); } } return result; } /* *----------------------------------------------------------------------------- * * CheckForUnclosedCatches -- * * Checks that a sequence of assembly code has no unclosed catches on * exit. * * Results: * Returns a standard Tcl result, with an error message for unclosed * catches. * *----------------------------------------------------------------------------- */ static int CheckForUnclosedCatches( AssemblyEnv* assemEnvPtr) /* Assembly environment */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ if (assemEnvPtr->curr_bb->catchState >= BBCS_INCATCH) { if (assemEnvPtr->flags & TCL_EVAL_DIRECT) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "catch still active on exit from assembly code", -1)); Tcl_SetErrorLine(interp, assemEnvPtr->curr_bb->enclosingCatch->startLine); Tcl_SetErrorCode(interp, "TCL", "ASSEM", "UNCLOSEDCATCH", (char *)NULL); } return TCL_ERROR; } return TCL_OK; } /* *----------------------------------------------------------------------------- * * BuildExceptionRanges -- * * Walks through the assembly code and builds exception ranges for the * catches embedded therein. * * Results: * Returns a standard Tcl result with an error message in the interpreter * if anything is unsuccessful. * * Side effects: * Each contiguous block of code with a given catch exit is assigned an * exception range at the appropriate level. * Exception ranges in embedded blocks have their levels corrected and * collated into the table. * Blocks that end with 'beginCatch' are associated with the innermost * exception range of the following block. * *----------------------------------------------------------------------------- */ static int BuildExceptionRanges( AssemblyEnv* assemEnvPtr) /* Assembly environment */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ BasicBlock* bbPtr; /* Current basic block */ BasicBlock* prevPtr = NULL; /* Previous basic block */ int catchDepth = 0; /* Current catch depth */ int maxCatchDepth = 0; /* Maximum catch depth in the program */ BasicBlock** catches; /* Stack of catches in progress */ int* catchIndices; /* Indices of the exception ranges of catches * in progress */ int i; /* * Determine the max catch depth for the entire assembly script * (excluding embedded eval's and expr's, which will be handled later). */ for (bbPtr=assemEnvPtr->head_bb; bbPtr != NULL; bbPtr=bbPtr->successor1) { if (bbPtr->catchDepth > maxCatchDepth) { maxCatchDepth = bbPtr->catchDepth; } } /* * Allocate memory for a stack of active catches. */ catches = (BasicBlock**)Tcl_Alloc(maxCatchDepth * sizeof(BasicBlock*)); catchIndices = (int *)Tcl_Alloc(maxCatchDepth * sizeof(int)); for (i = 0; i < maxCatchDepth; ++i) { catches[i] = NULL; catchIndices[i] = -1; } /* * Walk through the basic blocks and manage exception ranges. */ for (bbPtr=assemEnvPtr->head_bb; bbPtr != NULL; bbPtr=bbPtr->successor1) { UnstackExpiredCatches(envPtr, bbPtr, catchDepth, catches, catchIndices); LookForFreshCatches(bbPtr, catches); StackFreshCatches(assemEnvPtr, bbPtr, catchDepth, catches, catchIndices); /* * If the last block was a 'begin catch', fill in the exception range. */ catchDepth = bbPtr->catchDepth; if (prevPtr != NULL && (prevPtr->flags & BB_BEGINCATCH)) { TclStoreInt4AtPtr(catchIndices[catchDepth-1], envPtr->codeStart + bbPtr->startOffset - 4); } prevPtr = bbPtr; } /* Make sure that all catches are closed */ if (catchDepth != 0) { Tcl_Panic("unclosed catch at end of code in " "tclAssembly.c:BuildExceptionRanges, can't happen"); } /* Free temp storage */ Tcl_Free(catchIndices); Tcl_Free(catches); return TCL_OK; } /* *----------------------------------------------------------------------------- * * UnstackExpiredCatches -- * * Unstacks and closes the exception ranges for any catch contexts that * were active in the previous basic block but are inactive in the * current one. * *----------------------------------------------------------------------------- */ static void UnstackExpiredCatches( CompileEnv* envPtr, /* Compilation environment */ BasicBlock* bbPtr, /* Basic block being processed */ int catchDepth, /* Depth of nesting of catches prior to entry * to this block */ BasicBlock** catches, /* Array of catch contexts */ int* catchIndices) /* Indices of the exception ranges * corresponding to the catch contexts */ { ExceptionRange* range; /* Exception range for a specific catch */ BasicBlock* block; /* Catch block being examined */ BasicBlockCatchState catchState; /* State of the code relative to the catch * block being examined ("in catch" or * "caught"). */ /* * Unstack any catches that are deeper than the nesting level of the basic * block being entered. */ while (catchDepth > bbPtr->catchDepth) { --catchDepth; if (catches[catchDepth] != NULL) { range = envPtr->exceptArrayPtr + catchIndices[catchDepth]; range->numCodeBytes = bbPtr->startOffset - range->codeOffset; catches[catchDepth] = NULL; catchIndices[catchDepth] = -1; } } /* * Unstack any catches that don't match the basic block being entered, * either because they are no longer part of the context, or because the * context has changed from INCATCH to CAUGHT. */ catchState = bbPtr->catchState; block = bbPtr->enclosingCatch; while (catchDepth > 0) { --catchDepth; if (catches[catchDepth] != NULL) { if (catches[catchDepth] != block || catchState >= BBCS_CAUGHT) { range = envPtr->exceptArrayPtr + catchIndices[catchDepth]; range->numCodeBytes = bbPtr->startOffset - range->codeOffset; catches[catchDepth] = NULL; catchIndices[catchDepth] = -1; } catchState = block->catchState; block = block->enclosingCatch; } } } /* *----------------------------------------------------------------------------- * * LookForFreshCatches -- * * Determines whether a basic block being entered needs any exception * ranges that are not already stacked. * * Does not create the ranges: this procedure iterates from the innermost * catch outward, but exception ranges must be created from the outermost * catch inward. * *----------------------------------------------------------------------------- */ static void LookForFreshCatches( BasicBlock* bbPtr, /* Basic block being entered */ BasicBlock** catches) /* Array of catch contexts that are already * entered */ { BasicBlockCatchState catchState; /* State ("in catch" or "caught") of the * current catch. */ BasicBlock* block; /* Current enclosing catch */ int catchDepth; /* Nesting depth of the current catch */ catchState = bbPtr->catchState; block = bbPtr->enclosingCatch; catchDepth = bbPtr->catchDepth; while (catchDepth > 0) { --catchDepth; if (catches[catchDepth] != block && catchState < BBCS_CAUGHT) { catches[catchDepth] = block; } catchState = block->catchState; block = block->enclosingCatch; } } /* *----------------------------------------------------------------------------- * * StackFreshCatches -- * * Make ExceptionRange records for any catches that are in the basic * block being entered and were not in the previous basic block. * *----------------------------------------------------------------------------- */ static void StackFreshCatches( AssemblyEnv* assemEnvPtr, /* Assembly environment */ BasicBlock* bbPtr, /* Basic block being processed */ int catchDepth, /* Depth of nesting of catches prior to entry * to this block */ BasicBlock** catches, /* Array of catch contexts */ int* catchIndices) /* Indices of the exception ranges * corresponding to the catch contexts */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ ExceptionRange* range; /* Exception range for a specific catch */ BasicBlock* block; /* Catch block being examined */ BasicBlock* errorExit; /* Error exit from the catch block */ Tcl_HashEntry* entryPtr; catchDepth = 0; /* * Iterate through the enclosing catch blocks from the outside in, * looking for ones that don't have exception ranges (and are uncaught) */ for (catchDepth = 0; catchDepth < bbPtr->catchDepth; ++catchDepth) { if (catchIndices[catchDepth] == -1 && catches[catchDepth] != NULL) { /* * Create an exception range for a block that needs one. */ block = catches[catchDepth]; catchIndices[catchDepth] = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); range = envPtr->exceptArrayPtr + catchIndices[catchDepth]; range->nestingLevel = envPtr->exceptDepth + catchDepth; envPtr->maxExceptDepth= TclMax(range->nestingLevel + 1, envPtr->maxExceptDepth); range->codeOffset = bbPtr->startOffset; entryPtr = Tcl_FindHashEntry(&assemEnvPtr->labelHash, TclGetString(block->jumpTarget)); if (entryPtr == NULL) { Tcl_Panic("undefined label in tclAssembly.c:" "BuildExceptionRanges, can't happen"); } errorExit = (BasicBlock*)Tcl_GetHashValue(entryPtr); range->catchOffset = errorExit->startOffset; } } } /* *----------------------------------------------------------------------------- * * RestoreEmbeddedExceptionRanges -- * * Processes an assembly script, replacing any exception ranges that * were present in embedded code. * *----------------------------------------------------------------------------- */ static void RestoreEmbeddedExceptionRanges( AssemblyEnv* assemEnvPtr) /* Assembly environment */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ BasicBlock* bbPtr; /* Current basic block */ int rangeBase; /* Base of the foreign exception ranges when * they are reinstalled */ size_t rangeIndex; /* Index of the current foreign exception * range as reinstalled */ ExceptionRange* range; /* Current foreign exception range */ unsigned char opcode; /* Current instruction's opcode */ int catchIndex; /* Index of the exception range to which the * current instruction refers */ int i; /* * Walk the basic blocks looking for exceptions in embedded scripts. */ for (bbPtr = assemEnvPtr->head_bb; bbPtr != NULL; bbPtr = bbPtr->successor1) { if (bbPtr->foreignExceptionCount != 0) { /* * Reinstall the embedded exceptions and track their nesting level */ rangeBase = envPtr->exceptArrayNext; for (i = 0; i < bbPtr->foreignExceptionCount; ++i) { range = bbPtr->foreignExceptions + i; rangeIndex = TclCreateExceptRange(range->type, envPtr); range->nestingLevel += envPtr->exceptDepth + bbPtr->catchDepth; memcpy(envPtr->exceptArrayPtr + rangeIndex, range, sizeof(ExceptionRange)); if (range->nestingLevel + 1 >= envPtr->maxExceptDepth + 1) { envPtr->maxExceptDepth = range->nestingLevel + 1; } } /* * Walk through the bytecode of the basic block, and relocate * INST_BEGIN_CATCH4 instructions to the new locations */ i = bbPtr->startOffset; while (i < bbPtr->successor1->startOffset) { opcode = envPtr->codeStart[i]; if (opcode == INST_BEGIN_CATCH4) { catchIndex = TclGetUInt4AtPtr(envPtr->codeStart + i + 1); if (catchIndex >= bbPtr->foreignExceptionBase && catchIndex < (bbPtr->foreignExceptionBase + bbPtr->foreignExceptionCount)) { catchIndex -= bbPtr->foreignExceptionBase; catchIndex += rangeBase; TclStoreInt4AtPtr(catchIndex, envPtr->codeStart+i+1); } } i += tclInstructionTable[opcode].numBytes; } } } } /* *----------------------------------------------------------------------------- * * ResetVisitedBasicBlocks -- * * Turns off the 'visited' flag in all basic blocks at the conclusion * of a pass. * *----------------------------------------------------------------------------- */ static void ResetVisitedBasicBlocks( AssemblyEnv* assemEnvPtr) /* Assembly environment */ { BasicBlock* block; for (block = assemEnvPtr->head_bb; block != NULL; block = block->successor1) { block->flags &= ~BB_VISITED; } } /* *----------------------------------------------------------------------------- * * AddBasicBlockRangeToErrorInfo -- * * Updates the error info of the Tcl interpreter to show a given basic * block in the code. * * This procedure is used to label the callstack with source location * information when reporting an error in stack checking. * *----------------------------------------------------------------------------- */ static void AddBasicBlockRangeToErrorInfo( AssemblyEnv* assemEnvPtr, /* Assembly environment */ BasicBlock* bbPtr) /* Basic block in which the error is found */ { CompileEnv* envPtr = assemEnvPtr->envPtr; /* Compilation environment */ Tcl_Interp* interp = (Tcl_Interp*) envPtr->iPtr; /* Tcl interpreter */ Tcl_Obj* lineNo; /* Line number in the source */ Tcl_AddErrorInfo(interp, "\n in assembly code between lines "); TclNewIntObj(lineNo, bbPtr->startLine); Tcl_IncrRefCount(lineNo); Tcl_AppendObjToErrorInfo(interp, lineNo); Tcl_AddErrorInfo(interp, " and "); if (bbPtr->successor1 != NULL) { TclSetIntObj(lineNo, bbPtr->successor1->startLine); Tcl_AppendObjToErrorInfo(interp, lineNo); } else { Tcl_AddErrorInfo(interp, "end of assembly code"); } Tcl_DecrRefCount(lineNo); } /* *----------------------------------------------------------------------------- * * DupAssembleCodeInternalRep -- * * Part of the Tcl object type implementation for Tcl assembly language * bytecode. We do not copy the bytecode internalrep. Instead, we return * without setting copyPtr->typePtr, so the copy is a plain string copy * of the assembly source, and if it is to be used as a compiled * expression, it will need to be reprocessed. * * This makes sense, because with Tcl's copy-on-write practices, the * usual (only?) time Tcl_DuplicateObj() will be called is when the copy * is about to be modified, which would invalidate any copied bytecode * anyway. The only reason it might make sense to copy the bytecode is if * we had some modifying routines that operated directly on the internalrep, * as we do for lists and dicts. * * Results: * None. * * Side effects: * None. * *----------------------------------------------------------------------------- */ static void DupAssembleCodeInternalRep( TCL_UNUSED(Tcl_Obj *), TCL_UNUSED(Tcl_Obj *)) { return; } /* *----------------------------------------------------------------------------- * * FreeAssembleCodeInternalRep -- * * Part of the Tcl object type implementation for Tcl expression * bytecode. Frees the storage allocated to hold the internal rep, unless * ref counts indicate bytecode execution is still in progress. * * Results: * None. * * Side effects: * May free allocated memory. Leaves objPtr untyped. * *----------------------------------------------------------------------------- */ static void FreeAssembleCodeInternalRep( Tcl_Obj *objPtr) { ByteCode *codePtr; ByteCodeGetInternalRep(objPtr, &assembleCodeType, codePtr); assert(codePtr != NULL); TclReleaseByteCode(codePtr); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclAsync.c0000644000175000017500000002723514726623136015127 0ustar sergeisergei/* * tclAsync.c -- * * This file provides low-level support needed to invoke signal handlers * in a safe way. The code here doesn't actually handle signals, though. * This code is based on proposals made by Mark Diekhans and Don Libes. * * Copyright © 1993 The Regents of the University of California. * Copyright © 1994 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* Forward declaration */ struct ThreadSpecificData; /* * One of the following structures exists for each asynchronous handler: */ typedef struct AsyncHandler { int ready; /* Non-zero means this handler should be * invoked in the next call to * Tcl_AsyncInvoke. */ struct AsyncHandler *nextPtr, *prevPtr; /* Next, previous in list of all handlers * for the process. */ Tcl_AsyncProc *proc; /* Procedure to call when handler is * invoked. */ void *clientData; /* Value to pass to handler when it is * invoked. */ struct ThreadSpecificData *originTsd; /* Used in Tcl_AsyncMark to modify thread- * specific data from outside the thread it is * associated to. */ Tcl_ThreadId originThrdId; /* Origin thread where this token was created * and where it will be yielded. */ void *notifierData; /* Platform notifier data or NULL. */ } AsyncHandler; typedef struct ThreadSpecificData { int asyncReady; /* This is set to 1 whenever a handler becomes * ready and it is cleared to zero whenever * Tcl_AsyncInvoke is called. It can be * checked elsewhere in the application by * calling Tcl_AsyncReady to see if * Tcl_AsyncInvoke should be invoked. */ int asyncActive; /* Indicates whether Tcl_AsyncInvoke is * currently working. If so then we won't set * asyncReady again until Tcl_AsyncInvoke * returns. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* Mutex to protect linked-list of AsyncHandlers in the process. */ TCL_DECLARE_MUTEX(asyncMutex) /* List of all existing handlers of the process. */ static AsyncHandler *firstHandler = NULL; static AsyncHandler *lastHandler = NULL; /* *---------------------------------------------------------------------- * * TclFinalizeAsync -- * * Finalizes the thread local data structure for the async * subsystem. * * Results: * None. * * Side effects: * Cleans up left-over async handlers for the calling thread. * *---------------------------------------------------------------------- */ void TclFinalizeAsync(void) { AsyncHandler *token, *toDelete = NULL; Tcl_ThreadId self = Tcl_GetCurrentThread(); Tcl_MutexLock(&asyncMutex); for (token = firstHandler; token != NULL;) { AsyncHandler *nextToken = token->nextPtr; if (token->originThrdId == self) { if (token->prevPtr == NULL) { firstHandler = token->nextPtr; if (firstHandler == NULL) { lastHandler = NULL; break; } } else { token->prevPtr->nextPtr = token->nextPtr; if (token == lastHandler) { lastHandler = token->prevPtr; } } if (token->nextPtr != NULL) { token->nextPtr->prevPtr = token->prevPtr; } token->nextPtr = toDelete; token->prevPtr = NULL; toDelete = token; } token = nextToken; } Tcl_MutexUnlock(&asyncMutex); while (toDelete != NULL) { token = toDelete; toDelete = toDelete->nextPtr; Tcl_Free(token); } } /* *---------------------------------------------------------------------- * * Tcl_AsyncCreate -- * * This procedure creates the data structures for an asynchronous * handler, so that no memory has to be allocated when the handler is * activated. * * Results: * The return value is a token for the handler, which can be used to * activate it later on. * * Side effects: * Information about the handler is recorded. * *---------------------------------------------------------------------- */ Tcl_AsyncHandler Tcl_AsyncCreate( Tcl_AsyncProc *proc, /* Procedure to call when handler is * invoked. */ void *clientData) /* Argument to pass to handler. */ { AsyncHandler *asyncPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); asyncPtr = (AsyncHandler*)Tcl_Alloc(sizeof(AsyncHandler)); asyncPtr->ready = 0; asyncPtr->nextPtr = NULL; asyncPtr->prevPtr = NULL; asyncPtr->proc = proc; asyncPtr->clientData = clientData; asyncPtr->originTsd = tsdPtr; asyncPtr->originThrdId = Tcl_GetCurrentThread(); asyncPtr->notifierData = TclpNotifierData(); Tcl_MutexLock(&asyncMutex); if (firstHandler == NULL) { firstHandler = asyncPtr; } else { asyncPtr->prevPtr = lastHandler; lastHandler->nextPtr = asyncPtr; } lastHandler = asyncPtr; Tcl_MutexUnlock(&asyncMutex); return (Tcl_AsyncHandler) asyncPtr; } /* *---------------------------------------------------------------------- * * Tcl_AsyncMark -- * * This procedure is called to request that an asynchronous handler be * invoked as soon as possible. It's typically called from an interrupt * handler, where it isn't safe to do anything that depends on or * modifies application state. * * Results: * None. * * Side effects: * The handler gets marked for invocation later. * *---------------------------------------------------------------------- */ void Tcl_AsyncMark( Tcl_AsyncHandler async) /* Token for handler. */ { AsyncHandler *token = (AsyncHandler *) async; Tcl_MutexLock(&asyncMutex); token->ready = 1; if (!token->originTsd->asyncActive) { token->originTsd->asyncReady = 1; Tcl_ThreadAlert(token->originThrdId); } Tcl_MutexUnlock(&asyncMutex); } /* *---------------------------------------------------------------------- * * Tcl_AsyncMarkFromSignal -- * * This procedure is similar to Tcl_AsyncMark but must be used * in POSIX signal contexts. In addition to Tcl_AsyncMark the * signal number is passed. * * Results: * True, when the handler will be marked, false otherwise. * * Side effects: * The handler gets marked for invocation later. * *---------------------------------------------------------------------- */ int Tcl_AsyncMarkFromSignal( Tcl_AsyncHandler async, /* Token for handler. */ int sigNumber) /* Signal number. */ { #if TCL_THREADS AsyncHandler *token = (AsyncHandler *) async; return TclAsyncNotifier(sigNumber, token->originThrdId, token->notifierData, &token->ready, -1); #else (void)sigNumber; Tcl_AsyncMark(async); return 1; #endif } /* *---------------------------------------------------------------------- * * TclAsyncMarkFromNotifier -- * * This procedure is called from the notifier thread and * invokes Tcl_AsyncMark for specifically marked handlers. * * Results: * None. * * Side effects: * Handlers get marked for invocation later. * *---------------------------------------------------------------------- */ void TclAsyncMarkFromNotifier(void) { AsyncHandler *token; Tcl_MutexLock(&asyncMutex); for (token = firstHandler; token != NULL; token = token->nextPtr) { if (token->ready == -1) { token->ready = 1; if (!token->originTsd->asyncActive) { token->originTsd->asyncReady = 1; Tcl_ThreadAlert(token->originThrdId); } } } Tcl_MutexUnlock(&asyncMutex); } /* *---------------------------------------------------------------------- * * Tcl_AsyncInvoke -- * * This procedure is called at a "safe" time at background level to * invoke any active asynchronous handlers. * * Results: * The return value is a normal Tcl result, which is intended to replace * the code argument as the current completion code for interp. * * Side effects: * Depends on the handlers that are active. * *---------------------------------------------------------------------- */ int Tcl_AsyncInvoke( Tcl_Interp *interp, /* If invoked from Tcl_Eval just after * completing a command, points to * interpreter. Otherwise it is NULL. */ int code) /* If interp is non-NULL, this gives * completion code from command that just * completed. */ { AsyncHandler *asyncPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Tcl_ThreadId self = Tcl_GetCurrentThread(); Tcl_MutexLock(&asyncMutex); if (tsdPtr->asyncReady == 0) { Tcl_MutexUnlock(&asyncMutex); return code; } tsdPtr->asyncReady = 0; tsdPtr->asyncActive = 1; if (interp == NULL) { code = 0; } /* * Make one or more passes over the list of handlers, invoking at most one * handler in each pass. After invoking a handler, go back to the start of * the list again so that (a) if a new higher-priority handler gets marked * while executing a lower priority handler, we execute the higher- * priority handler next, and (b) if a handler gets deleted during the * execution of a handler, then the list structure may change so it isn't * safe to continue down the list anyway. */ while (1) { for (asyncPtr = firstHandler; asyncPtr != NULL; asyncPtr = asyncPtr->nextPtr) { if (asyncPtr->originThrdId != self) { continue; } if (asyncPtr->ready) { break; } } if (asyncPtr == NULL) { break; } asyncPtr->ready = 0; Tcl_MutexUnlock(&asyncMutex); code = asyncPtr->proc(asyncPtr->clientData, interp, code); Tcl_MutexLock(&asyncMutex); } tsdPtr->asyncActive = 0; Tcl_MutexUnlock(&asyncMutex); return code; } /* *---------------------------------------------------------------------- * * Tcl_AsyncDelete -- * * Frees up all the state for an asynchronous handler. The handler should * never be used again. * * Results: * None. * * Side effects: * The state associated with the handler is deleted. * * Failure to locate the handler in current thread private list * of async handlers will result in panic; exception: the list * is already empty (potential trouble?). * Consequently, threads should create and delete handlers * themselves. I.e. a handler created by one should not be * deleted by some other thread. * *---------------------------------------------------------------------- */ void Tcl_AsyncDelete( Tcl_AsyncHandler async) /* Token for handler to delete. */ { AsyncHandler *asyncPtr = (AsyncHandler *) async; /* * Assure early handling of the constraint */ if (asyncPtr->originThrdId != Tcl_GetCurrentThread()) { Tcl_Panic("Tcl_AsyncDelete: async handler deleted by the wrong thread"); } Tcl_MutexLock(&asyncMutex); if (asyncPtr->prevPtr == NULL) { firstHandler = asyncPtr->nextPtr; if (firstHandler == NULL) { lastHandler = NULL; } } else { asyncPtr->prevPtr->nextPtr = asyncPtr->nextPtr; if (asyncPtr == lastHandler) { lastHandler = asyncPtr->prevPtr; } } if (asyncPtr->nextPtr != NULL) { asyncPtr->nextPtr->prevPtr = asyncPtr->prevPtr; } Tcl_MutexUnlock(&asyncMutex); Tcl_Free(asyncPtr); } /* *---------------------------------------------------------------------- * * Tcl_AsyncReady -- * * This procedure can be used to tell whether Tcl_AsyncInvoke needs to be * called. This procedure is the external interface for checking the * thread-specific asyncReady variable. * * Results: * The return value is 1 whenever a handler is ready and is 0 when no * handlers are ready. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_AsyncReady(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); return tsdPtr->asyncReady; } int * TclGetAsyncReadyPtr(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); return &(tsdPtr->asyncReady); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclBasic.c0000644000175000017500000104347514726623136015100 0ustar sergeisergei/* * tclBasic.c -- * * Contains the basic facilities for TCL command interpretation, * including interpreter creation and deletion, command creation and * deletion, and command/script execution. * * Copyright © 1987-1994 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * Copyright © 1998-1999 Scriptics Corporation. * Copyright © 2001, 2002 Kevin B. Kenny. All rights reserved. * Copyright © 2007 Daniel A. Steffen * Copyright © 2006-2008 Joe Mistachkin. All rights reserved. * Copyright © 2008 Miguel Sofer * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclOOInt.h" #include "tclCompile.h" #include "tclTomMath.h" #include #include /* * TCL_FPCLASSIFY_MODE: * 0 - fpclassify * 1 - _fpclass * 2 - simulate * 3 - __builtin_fpclassify */ #ifndef TCL_FPCLASSIFY_MODE #if defined(__MINGW32__) && defined(_X86_) /* mingw 32-bit */ /* * MINGW x86 (tested up to gcc 8.1) seems to have a bug in fpclassify, * [fpclassify 1e-314], x86 => normal, x64 => subnormal, so switch to using a * version using a compiler built-in. */ #define TCL_FPCLASSIFY_MODE 1 #elif defined(fpclassify) /* fpclassify */ /* * This is the C99 standard. */ #include #define TCL_FPCLASSIFY_MODE 0 #elif defined(_FPCLASS_NN) /* _fpclass */ /* * This case handles newer MSVC on Windows, which doesn't have the standard * operation but does have something that can tell us the same thing. */ #define TCL_FPCLASSIFY_MODE 1 #else /* !fpclassify && !_fpclass (older MSVC), simulate */ /* * Older MSVC on Windows. So broken that we just have to do it our way. This * assumes that we're on x86 (or at least a system with classic little-endian * double layout and a 32-bit 'int' type). */ #define TCL_FPCLASSIFY_MODE 2 #endif /* !fpclassify */ /* actually there is no fallback to builtin fpclassify */ #endif /* !TCL_FPCLASSIFY_MODE */ /* * Bug 7371b6270b: to check C call stack depth, prefer an approach which is * compatible with AddressSanitizer (ASan) use-after-return detection. */ #if defined(_MSC_VER) && defined(HAVE_INTRIN_H) #include /* for _AddressOfReturnAddress() */ #endif /* * As suggested by * https://clang.llvm.org/docs/LanguageExtensions.html#has-builtin */ #ifndef __has_builtin #define __has_builtin(x) 0 /* for non-clang compilers */ #endif void * TclGetCStackPtr(void) { #if defined( __GNUC__ ) || __has_builtin(__builtin_frame_address) return __builtin_frame_address(0); #elif defined(_MSC_VER) && defined(HAVE_INTRIN_H) return _AddressOfReturnAddress(); #else ptrdiff_t unused = 0; /* * LLVM recommends using volatile: * https://github.com/llvm/llvm-project/blob/llvmorg-10.0.0-rc1/clang/lib/Basic/Stack.cpp#L31 */ ptrdiff_t *volatile stackLevel = &unused; return (void *)stackLevel; #endif } #define INTERP_STACK_INITIAL_SIZE 2000 #define CORO_STACK_INITIAL_SIZE 200 /* * Determine whether we're using IEEE floating point */ #if (FLT_RADIX == 2) && (DBL_MANT_DIG == 53) && (DBL_MAX_EXP == 1024) # define IEEE_FLOATING_POINT /* Largest odd integer that can be represented exactly in a double */ # define MAX_EXACT 9007199254740991.0 #endif /* * This is the script cancellation struct and hash table. The hash table is * used to keep track of the information necessary to process script * cancellation requests, including the original interp, asynchronous handler * tokens (created by Tcl_AsyncCreate), and the clientData and flags arguments * passed to Tcl_CancelEval on a per-interp basis. The cancelLock mutex is * used for protecting calls to Tcl_CancelEval as well as protecting access to * the hash table below. */ typedef struct { Tcl_Interp *interp; /* Interp this struct belongs to. */ Tcl_AsyncHandler async; /* Async handler token for script * cancellation. */ char *result; /* The script cancellation result or NULL for * a default result. */ Tcl_Size length; /* Length of the above error message. */ void *clientData; /* Not used. */ int flags; /* Additional flags */ } CancelInfo; static Tcl_HashTable cancelTable; static int cancelTableInitialized = 0; /* 0 means not yet initialized. */ TCL_DECLARE_MUTEX(cancelLock); /* * Table used to map command implementation functions to a human-readable type * name, for [info type]. The keys in the table are function addresses, and * the values in the table are static char* containing strings in Tcl's * internal encoding (almost UTF-8). */ static Tcl_HashTable commandTypeTable; static int commandTypeInit = 0; TCL_DECLARE_MUTEX(commandTypeLock); /* * Declarations for managing contexts for non-recursive coroutines. Contexts * are used to save the evaluation state between NR calls to each coro. */ #define SAVE_CONTEXT(context) \ (context).framePtr = iPtr->framePtr; \ (context).varFramePtr = iPtr->varFramePtr; \ (context).cmdFramePtr = iPtr->cmdFramePtr; \ (context).lineLABCPtr = iPtr->lineLABCPtr #define RESTORE_CONTEXT(context) \ iPtr->framePtr = (context).framePtr; \ iPtr->varFramePtr = (context).varFramePtr; \ iPtr->cmdFramePtr = (context).cmdFramePtr; \ iPtr->lineLABCPtr = (context).lineLABCPtr /* * Static functions in this file: */ static Tcl_ObjCmdProc BadEnsembleSubcommand; static char * CallCommandTraces(Interp *iPtr, Command *cmdPtr, const char *oldName, const char *newName, int flags); static int CancelEvalProc(void *clientData, Tcl_Interp *interp, int code); static int CheckDoubleResult(Tcl_Interp *interp, double dResult); static void DeleteCoroutine(void *clientData); static Tcl_FreeProc DeleteInterpProc; static void DeleteOpCmdClientData(void *clientData); #ifdef USE_DTRACE static Tcl_ObjCmdProc DTraceObjCmd; static Tcl_NRPostProc DTraceCmdReturn; #else # define DTraceCmdReturn NULL #endif /* USE_DTRACE */ static Tcl_ObjCmdProc InvokeStringCommand; static Tcl_ObjCmdProc ExprAbsFunc; static Tcl_ObjCmdProc ExprBinaryFunc; static Tcl_ObjCmdProc ExprBoolFunc; static Tcl_ObjCmdProc ExprCeilFunc; static Tcl_ObjCmdProc ExprDoubleFunc; static Tcl_ObjCmdProc ExprFloorFunc; static Tcl_ObjCmdProc ExprIntFunc; static Tcl_ObjCmdProc ExprIsqrtFunc; static Tcl_ObjCmdProc ExprIsFiniteFunc; static Tcl_ObjCmdProc ExprIsInfinityFunc; static Tcl_ObjCmdProc ExprIsNaNFunc; static Tcl_ObjCmdProc ExprIsNormalFunc; static Tcl_ObjCmdProc ExprIsSubnormalFunc; static Tcl_ObjCmdProc ExprIsUnorderedFunc; static Tcl_ObjCmdProc ExprMaxFunc; static Tcl_ObjCmdProc ExprMinFunc; static Tcl_ObjCmdProc ExprRandFunc; static Tcl_ObjCmdProc ExprRoundFunc; static Tcl_ObjCmdProc ExprSqrtFunc; static Tcl_ObjCmdProc ExprSrandFunc; static Tcl_ObjCmdProc ExprUnaryFunc; static Tcl_ObjCmdProc ExprWideFunc; static Tcl_ObjCmdProc FloatClassifyObjCmd; static void MathFuncWrongNumArgs(Tcl_Interp *interp, int expected, int actual, Tcl_Obj *const *objv); static Tcl_NRPostProc NRCoroutineCallerCallback; static Tcl_NRPostProc NRCoroutineExitCallback; static Tcl_NRPostProc NRCommand; static void ProcessUnexpectedResult(Tcl_Interp *interp, int returnCode); static int RewindCoroutine(CoroutineData *corPtr, int result); static void TEOV_SwitchVarFrame(Tcl_Interp *interp); static void TEOV_PushExceptionHandlers(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags); static inline Command * TEOV_LookupCmdFromObj(Tcl_Interp *interp, Tcl_Obj *namePtr, Namespace *lookupNsPtr); static int TEOV_NotFound(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], Namespace *lookupNsPtr); static int TEOV_RunEnterTraces(Tcl_Interp *interp, Command **cmdPtrPtr, Tcl_Obj *commandPtr, Tcl_Size objc, Tcl_Obj *const objv[]); static Tcl_NRPostProc RewindCoroutineCallback; static Tcl_NRPostProc TEOEx_ByteCodeCallback; static Tcl_NRPostProc TEOEx_ListCallback; static Tcl_NRPostProc TEOV_Error; static Tcl_NRPostProc TEOV_Exception; static Tcl_NRPostProc TEOV_NotFoundCallback; static Tcl_NRPostProc TEOV_RestoreVarFrame; static Tcl_NRPostProc TEOV_RunLeaveTraces; static Tcl_NRPostProc EvalObjvCore; static Tcl_NRPostProc Dispatch; static Tcl_NRPostProc NRPostInvoke; static Tcl_ObjCmdProc CoroTypeObjCmd; static Tcl_ObjCmdProc TclNRCoroInjectObjCmd; static Tcl_ObjCmdProc TclNRCoroProbeObjCmd; static Tcl_NRPostProc InjectHandler; static Tcl_NRPostProc InjectHandlerPostCall; MODULE_SCOPE const TclStubs tclStubs; /* * Magical counts for the number of arguments accepted by a coroutine command * after particular kinds of [yield]. */ #define CORO_ACTIVATE_YIELD NULL #define CORO_ACTIVATE_YIELDM INT2PTR(1) #define COROUTINE_ARGUMENTS_SINGLE_OPTIONAL (-1) #define COROUTINE_ARGUMENTS_ARBITRARY (-2) /* * The following structure define the commands in the Tcl core. */ typedef struct { const char *name; /* Name of object-based command. */ Tcl_ObjCmdProc *objProc; /* Object-based function for command. */ CompileProc *compileProc; /* Function called to compile command. */ Tcl_ObjCmdProc *nreProc; /* NR-based function for command */ int flags; /* Various flag bits, as defined below. */ } CmdInfo; #define CMD_IS_SAFE 1 /* Whether this command is part of the set of * commands present by default in a safe * interpreter. */ /* CMD_COMPILES_EXPANDED - Whether the compiler for this command can handle * expansion for itself rather than needing the generic layer to take care of * it for it. Defined in tclInt.h. */ /* * The following struct states that the command it talks about (a subcommand * of one of Tcl's built-in ensembles) is unsafe and must be hidden when an * interpreter is made safe. (TclHideUnsafeCommands accesses an array of these * structs.) Alas, we can't sensibly just store the information directly in * the commands. */ typedef struct { const char *ensembleNsName; /* The ensemble's name within ::tcl. NULL for * the end of the list of commands to hide. */ const char *commandName; /* The name of the command within the * ensemble. If this is NULL, we want to also * make the overall command be hidden, an ugly * hack because it is expected by security * policies in the wild. */ } UnsafeEnsembleInfo; /* * The built-in commands, and the functions that implement them: */ static int procObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { return Tcl_ProcObjCmd(clientData, interp, objc, objv); } static const CmdInfo builtInCmds[] = { /* * Commands in the generic core. */ {"append", Tcl_AppendObjCmd, TclCompileAppendCmd, NULL, CMD_IS_SAFE}, {"apply", Tcl_ApplyObjCmd, NULL, TclNRApplyObjCmd, CMD_IS_SAFE}, {"break", Tcl_BreakObjCmd, TclCompileBreakCmd, NULL, CMD_IS_SAFE}, {"catch", Tcl_CatchObjCmd, TclCompileCatchCmd, TclNRCatchObjCmd, CMD_IS_SAFE}, {"concat", Tcl_ConcatObjCmd, TclCompileConcatCmd, NULL, CMD_IS_SAFE}, {"const", Tcl_ConstObjCmd, TclCompileConstCmd, NULL, CMD_IS_SAFE}, {"continue", Tcl_ContinueObjCmd, TclCompileContinueCmd, NULL, CMD_IS_SAFE}, {"coroinject", NULL, NULL, TclNRCoroInjectObjCmd, CMD_IS_SAFE}, {"coroprobe", NULL, NULL, TclNRCoroProbeObjCmd, CMD_IS_SAFE}, {"coroutine", NULL, NULL, TclNRCoroutineObjCmd, CMD_IS_SAFE}, {"error", Tcl_ErrorObjCmd, TclCompileErrorCmd, NULL, CMD_IS_SAFE}, {"eval", Tcl_EvalObjCmd, NULL, TclNREvalObjCmd, CMD_IS_SAFE}, {"expr", Tcl_ExprObjCmd, TclCompileExprCmd, TclNRExprObjCmd, CMD_IS_SAFE}, {"for", Tcl_ForObjCmd, TclCompileForCmd, TclNRForObjCmd, CMD_IS_SAFE}, {"foreach", Tcl_ForeachObjCmd, TclCompileForeachCmd, TclNRForeachCmd, CMD_IS_SAFE}, {"format", Tcl_FormatObjCmd, TclCompileFormatCmd, NULL, CMD_IS_SAFE}, {"fpclassify", FloatClassifyObjCmd, NULL, NULL, CMD_IS_SAFE}, {"global", Tcl_GlobalObjCmd, TclCompileGlobalCmd, NULL, CMD_IS_SAFE}, {"if", Tcl_IfObjCmd, TclCompileIfCmd, TclNRIfObjCmd, CMD_IS_SAFE}, {"incr", Tcl_IncrObjCmd, TclCompileIncrCmd, NULL, CMD_IS_SAFE}, {"join", Tcl_JoinObjCmd, NULL, NULL, CMD_IS_SAFE}, {"lappend", Tcl_LappendObjCmd, TclCompileLappendCmd, NULL, CMD_IS_SAFE}, {"lassign", Tcl_LassignObjCmd, TclCompileLassignCmd, NULL, CMD_IS_SAFE}, {"ledit", Tcl_LeditObjCmd, NULL, NULL, CMD_IS_SAFE}, {"lindex", Tcl_LindexObjCmd, TclCompileLindexCmd, NULL, CMD_IS_SAFE}, {"linsert", Tcl_LinsertObjCmd, TclCompileLinsertCmd, NULL, CMD_IS_SAFE}, {"list", Tcl_ListObjCmd, TclCompileListCmd, NULL, CMD_IS_SAFE|CMD_COMPILES_EXPANDED}, {"llength", Tcl_LlengthObjCmd, TclCompileLlengthCmd, NULL, CMD_IS_SAFE}, {"lmap", Tcl_LmapObjCmd, TclCompileLmapCmd, TclNRLmapCmd, CMD_IS_SAFE}, {"lpop", Tcl_LpopObjCmd, NULL, NULL, CMD_IS_SAFE}, {"lrange", Tcl_LrangeObjCmd, TclCompileLrangeCmd, NULL, CMD_IS_SAFE}, {"lremove", Tcl_LremoveObjCmd, NULL, NULL, CMD_IS_SAFE}, {"lrepeat", Tcl_LrepeatObjCmd, NULL, NULL, CMD_IS_SAFE}, {"lreplace", Tcl_LreplaceObjCmd, TclCompileLreplaceCmd, NULL, CMD_IS_SAFE}, {"lreverse", Tcl_LreverseObjCmd, NULL, NULL, CMD_IS_SAFE}, {"lsearch", Tcl_LsearchObjCmd, NULL, NULL, CMD_IS_SAFE}, {"lseq", Tcl_LseqObjCmd, NULL, NULL, CMD_IS_SAFE}, {"lset", Tcl_LsetObjCmd, TclCompileLsetCmd, NULL, CMD_IS_SAFE}, {"lsort", Tcl_LsortObjCmd, NULL, NULL, CMD_IS_SAFE}, {"package", Tcl_PackageObjCmd, NULL, TclNRPackageObjCmd, CMD_IS_SAFE}, {"proc", procObjCmd, NULL, NULL, CMD_IS_SAFE}, {"regexp", Tcl_RegexpObjCmd, TclCompileRegexpCmd, NULL, CMD_IS_SAFE}, {"regsub", Tcl_RegsubObjCmd, TclCompileRegsubCmd, NULL, CMD_IS_SAFE}, {"rename", Tcl_RenameObjCmd, NULL, NULL, CMD_IS_SAFE}, {"return", Tcl_ReturnObjCmd, TclCompileReturnCmd, NULL, CMD_IS_SAFE}, {"scan", Tcl_ScanObjCmd, NULL, NULL, CMD_IS_SAFE}, {"set", Tcl_SetObjCmd, TclCompileSetCmd, NULL, CMD_IS_SAFE}, {"split", Tcl_SplitObjCmd, NULL, NULL, CMD_IS_SAFE}, {"subst", Tcl_SubstObjCmd, TclCompileSubstCmd, TclNRSubstObjCmd, CMD_IS_SAFE}, {"switch", Tcl_SwitchObjCmd, TclCompileSwitchCmd, TclNRSwitchObjCmd, CMD_IS_SAFE}, {"tailcall", NULL, TclCompileTailcallCmd, TclNRTailcallObjCmd, CMD_IS_SAFE}, {"throw", Tcl_ThrowObjCmd, TclCompileThrowCmd, NULL, CMD_IS_SAFE}, {"trace", Tcl_TraceObjCmd, NULL, NULL, CMD_IS_SAFE}, {"try", Tcl_TryObjCmd, TclCompileTryCmd, TclNRTryObjCmd, CMD_IS_SAFE}, {"unset", Tcl_UnsetObjCmd, TclCompileUnsetCmd, NULL, CMD_IS_SAFE}, {"uplevel", Tcl_UplevelObjCmd, NULL, TclNRUplevelObjCmd, CMD_IS_SAFE}, {"upvar", Tcl_UpvarObjCmd, TclCompileUpvarCmd, NULL, CMD_IS_SAFE}, {"variable", Tcl_VariableObjCmd, TclCompileVariableCmd, NULL, CMD_IS_SAFE}, {"while", Tcl_WhileObjCmd, TclCompileWhileCmd, TclNRWhileObjCmd, CMD_IS_SAFE}, {"yield", NULL, TclCompileYieldCmd, TclNRYieldObjCmd, CMD_IS_SAFE}, {"yieldto", NULL, TclCompileYieldToCmd, TclNRYieldToObjCmd, CMD_IS_SAFE}, /* * Commands in the OS-interface. Note that many of these are unsafe. */ {"after", Tcl_AfterObjCmd, NULL, NULL, CMD_IS_SAFE}, {"cd", Tcl_CdObjCmd, NULL, NULL, 0}, {"close", Tcl_CloseObjCmd, NULL, NULL, CMD_IS_SAFE}, {"eof", Tcl_EofObjCmd, NULL, NULL, CMD_IS_SAFE}, {"exec", Tcl_ExecObjCmd, NULL, NULL, 0}, {"exit", Tcl_ExitObjCmd, NULL, NULL, 0}, {"fblocked", Tcl_FblockedObjCmd, NULL, NULL, CMD_IS_SAFE}, {"fconfigure", Tcl_FconfigureObjCmd, NULL, NULL, 0}, {"fcopy", Tcl_FcopyObjCmd, NULL, NULL, CMD_IS_SAFE}, {"fileevent", Tcl_FileEventObjCmd, NULL, NULL, CMD_IS_SAFE}, {"flush", Tcl_FlushObjCmd, NULL, NULL, CMD_IS_SAFE}, {"gets", Tcl_GetsObjCmd, NULL, NULL, CMD_IS_SAFE}, {"glob", Tcl_GlobObjCmd, NULL, NULL, 0}, {"load", Tcl_LoadObjCmd, NULL, NULL, 0}, {"open", Tcl_OpenObjCmd, NULL, NULL, 0}, {"pid", Tcl_PidObjCmd, NULL, NULL, CMD_IS_SAFE}, {"puts", Tcl_PutsObjCmd, NULL, NULL, CMD_IS_SAFE}, {"pwd", Tcl_PwdObjCmd, NULL, NULL, 0}, {"read", Tcl_ReadObjCmd, NULL, NULL, CMD_IS_SAFE}, {"seek", Tcl_SeekObjCmd, NULL, NULL, CMD_IS_SAFE}, {"socket", Tcl_SocketObjCmd, NULL, NULL, 0}, {"source", Tcl_SourceObjCmd, NULL, TclNRSourceObjCmd, 0}, {"tell", Tcl_TellObjCmd, NULL, NULL, CMD_IS_SAFE}, {"time", Tcl_TimeObjCmd, NULL, NULL, CMD_IS_SAFE}, {"timerate", Tcl_TimeRateObjCmd, NULL, NULL, CMD_IS_SAFE}, {"unload", Tcl_UnloadObjCmd, NULL, NULL, 0}, {"update", Tcl_UpdateObjCmd, NULL, NULL, CMD_IS_SAFE}, {"vwait", Tcl_VwaitObjCmd, NULL, NULL, CMD_IS_SAFE}, {NULL, NULL, NULL, NULL, 0} }; /* * Information about which pieces of ensembles to hide when making an * interpreter safe: */ static const UnsafeEnsembleInfo unsafeEnsembleCommands[] = { /* [encoding] has two unsafe commands. Assumed by older security policies * to be overall unsafe; it isn't but... */ {"encoding", NULL}, {"encoding", "dirs"}, {"encoding", "system"}, /* [file] has MANY unsafe commands! Assumed by older security policies to * be overall unsafe; it isn't but... */ {"file", NULL}, {"file", "atime"}, {"file", "attributes"}, {"file", "copy"}, {"file", "delete"}, {"file", "dirname"}, {"file", "executable"}, {"file", "exists"}, {"file", "extension"}, {"file", "home"}, {"file", "isdirectory"}, {"file", "isfile"}, {"file", "link"}, {"file", "lstat"}, {"file", "mtime"}, {"file", "mkdir"}, {"file", "nativename"}, {"file", "normalize"}, {"file", "owned"}, {"file", "readable"}, {"file", "readlink"}, {"file", "rename"}, {"file", "rootname"}, {"file", "size"}, {"file", "stat"}, {"file", "tail"}, {"file", "tempdir"}, {"file", "tempfile"}, {"file", "tildeexpand"}, {"file", "type"}, {"file", "volumes"}, {"file", "writable"}, /* [info] has two unsafe commands */ {"info", "cmdtype"}, {"info", "nameofexecutable"}, /* [tcl::process] has ONLY unsafe commands! */ {"process", "list"}, {"process", "status"}, {"process", "purge"}, {"process", "autopurge"}, /* * [zipfs] perhaps has some safe commands. But like file make it inaccessible * until they are analyzed to be safe. */ {"zipfs", NULL}, {"zipfs", "canonical"}, {"zipfs", "exists"}, {"zipfs", "info"}, {"zipfs", "list"}, {"zipfs", "lmkimg"}, {"zipfs", "lmkzip"}, {"zipfs", "mkimg"}, {"zipfs", "mkkey"}, {"zipfs", "mkzip"}, {"zipfs", "mount"}, {"zipfs", "mountdata"}, {"zipfs", "root"}, {"zipfs", "unmount"}, {NULL, NULL} }; /* * Math functions. All are safe. */ typedef double (BuiltinUnaryFunc)(double x); typedef double (BuiltinBinaryFunc)(double x, double y); #define BINARY_TYPECAST(fn) \ (BuiltinUnaryFunc *)(void *)(BuiltinBinaryFunc *) fn typedef struct { const char *name; /* Name of the function. The full name is * "::tcl::mathfunc::". */ Tcl_ObjCmdProc *objCmdProc; /* Function that evaluates the function */ BuiltinUnaryFunc *fn; /* Real function pointer */ } BuiltinFuncDef; static const BuiltinFuncDef BuiltinFuncTable[] = { { "abs", ExprAbsFunc, NULL }, { "acos", ExprUnaryFunc, acos }, { "asin", ExprUnaryFunc, asin }, { "atan", ExprUnaryFunc, atan }, { "atan2", ExprBinaryFunc, BINARY_TYPECAST(atan2) }, { "bool", ExprBoolFunc, NULL }, { "ceil", ExprCeilFunc, NULL }, { "cos", ExprUnaryFunc, cos }, { "cosh", ExprUnaryFunc, cosh }, { "double", ExprDoubleFunc, NULL }, { "entier", ExprIntFunc, NULL }, { "exp", ExprUnaryFunc, exp }, { "floor", ExprFloorFunc, NULL }, { "fmod", ExprBinaryFunc, BINARY_TYPECAST(fmod) }, { "hypot", ExprBinaryFunc, BINARY_TYPECAST(hypot) }, { "int", ExprIntFunc, NULL }, { "isfinite", ExprIsFiniteFunc, NULL }, { "isinf", ExprIsInfinityFunc, NULL }, { "isnan", ExprIsNaNFunc, NULL }, { "isnormal", ExprIsNormalFunc, NULL }, { "isqrt", ExprIsqrtFunc, NULL }, { "issubnormal", ExprIsSubnormalFunc, NULL, }, { "isunordered", ExprIsUnorderedFunc, NULL, }, { "log", ExprUnaryFunc, log }, { "log10", ExprUnaryFunc, log10 }, { "max", ExprMaxFunc, NULL }, { "min", ExprMinFunc, NULL }, { "pow", ExprBinaryFunc, BINARY_TYPECAST(pow) }, { "rand", ExprRandFunc, NULL }, { "round", ExprRoundFunc, NULL }, { "sin", ExprUnaryFunc, sin }, { "sinh", ExprUnaryFunc, sinh }, { "sqrt", ExprSqrtFunc, NULL }, { "srand", ExprSrandFunc, NULL }, { "tan", ExprUnaryFunc, tan }, { "tanh", ExprUnaryFunc, tanh }, { "wide", ExprWideFunc, NULL }, { NULL, NULL, NULL } }; /* * TIP#174's math operators. All are safe. */ typedef struct { const char *name; /* Name of object-based command. */ Tcl_ObjCmdProc *objProc; /* Object-based function for command. */ CompileProc *compileProc; /* Function called to compile command. */ union { int numArgs; int identity; } i; const char *expected; /* For error message, what argument(s) * were expected. */ } OpCmdInfo; static const OpCmdInfo mathOpCmds[] = { { "~", TclSingleOpCmd, TclCompileInvertOpCmd, /* numArgs */ {1}, "integer"}, { "!", TclSingleOpCmd, TclCompileNotOpCmd, /* numArgs */ {1}, "boolean"}, { "+", TclVariadicOpCmd, TclCompileAddOpCmd, /* identity */ {0}, NULL}, { "*", TclVariadicOpCmd, TclCompileMulOpCmd, /* identity */ {1}, NULL}, { "&", TclVariadicOpCmd, TclCompileAndOpCmd, /* identity */ {-1}, NULL}, { "|", TclVariadicOpCmd, TclCompileOrOpCmd, /* identity */ {0}, NULL}, { "^", TclVariadicOpCmd, TclCompileXorOpCmd, /* identity */ {0}, NULL}, { "**", TclVariadicOpCmd, TclCompilePowOpCmd, /* identity */ {1}, NULL}, { "<<", TclSingleOpCmd, TclCompileLshiftOpCmd, /* numArgs */ {2}, "integer shift"}, { ">>", TclSingleOpCmd, TclCompileRshiftOpCmd, /* numArgs */ {2}, "integer shift"}, { "%", TclSingleOpCmd, TclCompileModOpCmd, /* numArgs */ {2}, "integer integer"}, { "!=", TclSingleOpCmd, TclCompileNeqOpCmd, /* numArgs */ {2}, "value value"}, { "ne", TclSingleOpCmd, TclCompileStrneqOpCmd, /* numArgs */ {2}, "value value"}, { "in", TclSingleOpCmd, TclCompileInOpCmd, /* numArgs */ {2}, "value list"}, { "ni", TclSingleOpCmd, TclCompileNiOpCmd, /* numArgs */ {2}, "value list"}, { "-", TclNoIdentOpCmd, TclCompileMinusOpCmd, /* unused */ {0}, "value ?value ...?"}, { "/", TclNoIdentOpCmd, TclCompileDivOpCmd, /* unused */ {0}, "value ?value ...?"}, { "<", TclSortingOpCmd, TclCompileLessOpCmd, /* unused */ {0}, NULL}, { "<=", TclSortingOpCmd, TclCompileLeqOpCmd, /* unused */ {0}, NULL}, { ">", TclSortingOpCmd, TclCompileGreaterOpCmd, /* unused */ {0}, NULL}, { ">=", TclSortingOpCmd, TclCompileGeqOpCmd, /* unused */ {0}, NULL}, { "==", TclSortingOpCmd, TclCompileEqOpCmd, /* unused */ {0}, NULL}, { "eq", TclSortingOpCmd, TclCompileStreqOpCmd, /* unused */ {0}, NULL}, { "lt", TclSortingOpCmd, TclCompileStrLtOpCmd, /* unused */ {0}, NULL}, { "le", TclSortingOpCmd, TclCompileStrLeOpCmd, /* unused */ {0}, NULL}, { "gt", TclSortingOpCmd, TclCompileStrGtOpCmd, /* unused */ {0}, NULL}, { "ge", TclSortingOpCmd, TclCompileStrGeOpCmd, /* unused */ {0}, NULL}, { NULL, NULL, NULL, {0}, NULL} }; /* *---------------------------------------------------------------------- * * TclFinalizeEvaluation -- * * Finalizes the script cancellation hash table. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclFinalizeEvaluation(void) { Tcl_MutexLock(&cancelLock); if (cancelTableInitialized == 1) { Tcl_DeleteHashTable(&cancelTable); cancelTableInitialized = 0; } Tcl_MutexUnlock(&cancelLock); Tcl_MutexLock(&commandTypeLock); if (commandTypeInit) { Tcl_DeleteHashTable(&commandTypeTable); commandTypeInit = 0; } Tcl_MutexUnlock(&commandTypeLock); } /* *---------------------------------------------------------------------- * * buildInfoObjCmd -- * * Implements tcl::build-info command. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int buildInfoObjCmd2( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *buildData = (const char *) clientData; char buf[80]; const char *arg, *p, *q; Tcl_Size len; int idx; static const char *identifiers[] = { "commit", "compiler", "patchlevel", "version", NULL }; enum Identifiers { ID_COMMIT, ID_COMPILER, ID_PATCHLEVEL, ID_VERSION, ID_OTHER }; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?option?"); return TCL_ERROR; } else if (objc < 2) { Tcl_SetObjResult(interp, Tcl_NewStringObj(buildData, TCL_INDEX_NONE)); return TCL_OK; } /* * Query for a specific piece of build info */ if (Tcl_GetIndexFromObj(NULL, objv[1], identifiers, NULL, TCL_EXACT, &idx) != TCL_OK) { idx = ID_OTHER; } switch (idx) { case ID_PATCHLEVEL: if ((p = strchr(buildData, '+')) != NULL) { memcpy(buf, buildData, p - buildData); buf[p - buildData] = '\0'; Tcl_SetObjResult(interp, Tcl_NewStringObj(buf, TCL_INDEX_NONE)); } return TCL_OK; case ID_VERSION: if ((p = strchr(buildData, '.')) != NULL) { const char *r = strchr(p++, '+'); q = strchr(p, '.'); p = (q < r) ? q : r; } if (p != NULL) { memcpy(buf, buildData, p - buildData); buf[p - buildData] = '\0'; Tcl_SetObjResult(interp, Tcl_NewStringObj(buf, TCL_INDEX_NONE)); } return TCL_OK; case ID_COMMIT: if ((p = strchr(buildData, '+')) != NULL) { if ((q = strchr(p++, '.')) != NULL) { memcpy(buf, p, q - p); buf[q - p] = '\0'; Tcl_SetObjResult(interp, Tcl_NewStringObj(buf, TCL_INDEX_NONE)); } else { Tcl_SetObjResult(interp, Tcl_NewStringObj(p, TCL_INDEX_NONE)); } } return TCL_OK; case ID_COMPILER: for (p = strchr(buildData, '.'); p++; p = strchr(p, '.')) { /* * Does the word begin with one of the standard prefixes? */ if (!strncmp(p, "clang-", 6) || !strncmp(p, "gcc-", 4) || !strncmp(p, "icc-", 4) || !strncmp(p, "msvc-", 5)) { if ((q = strchr(p, '.')) != NULL) { memcpy(buf, p, q - p); buf[q - p] = '\0'; Tcl_SetObjResult(interp, Tcl_NewStringObj(buf, TCL_INDEX_NONE)); } else { Tcl_SetObjResult(interp, Tcl_NewStringObj(p, TCL_INDEX_NONE)); } return TCL_OK; } } break; default: /* Boolean test for other identifiers' presence */ arg = TclGetStringFromObj(objv[1], &len); for (p = strchr(buildData, '.'); p++; p = strchr(p, '.')) { if (!strncmp(p, arg, len) && ((p[len] == '.') || (p[len] == '-') || (p[len] == '\0'))) { if (p[len] == '-') { p += len; q = strchr(++p, '.'); if (!q) { q = p + strlen(p); } memcpy(buf, p, q - p); buf[q - p] = '\0'; Tcl_SetObjResult(interp, Tcl_NewStringObj(buf, TCL_INDEX_NONE)); } else { Tcl_SetObjResult(interp, Tcl_NewBooleanObj(1)); } return TCL_OK; } } } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0)); return TCL_OK; } static int buildInfoObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return buildInfoObjCmd2(clientData, interp, objc, objv); } /* *---------------------------------------------------------------------- * * Tcl_CreateInterp -- * * Create a new TCL command interpreter. * * Results: * The return value is a token for the interpreter, which may be used in * calls to functions like Tcl_CreateCmd, Tcl_Eval, or Tcl_DeleteInterp. * * Side effects: * The command interpreter is initialized with the built-in commands and * with the variables documented in tclvars(n). * *---------------------------------------------------------------------- */ Tcl_Interp * Tcl_CreateInterp(void) { Interp *iPtr; Tcl_Interp *interp; Command *cmdPtr; const BuiltinFuncDef *builtinFuncPtr; const OpCmdInfo *opcmdInfoPtr; const CmdInfo *cmdInfoPtr; Tcl_Namespace *nsPtr; Tcl_HashEntry *hPtr; int isNew; CancelInfo *cancelInfo; union { char c[sizeof(short)]; short s; } order; #ifdef TCL_COMPILE_STATS ByteCodeStats *statsPtr; #endif /* TCL_COMPILE_STATS */ char mathFuncName[32]; CallFrame *framePtr; const char *version = Tcl_InitSubsystems(); /* * Panic if someone updated the CallFrame structure without also updating * the Tcl_CallFrame structure (or vice versa). */ if (sizeof(Tcl_CallFrame) < sizeof(CallFrame)) { Tcl_Panic("Tcl_CallFrame must not be smaller than CallFrame"); } #if defined(_WIN32) && !defined(_WIN64) if (sizeof(time_t) != 8) { Tcl_Panic(" is not compatible with VS2005+"); } if ((offsetof(Tcl_StatBuf,st_atime) != 32) || (offsetof(Tcl_StatBuf,st_ctime) != 48)) { Tcl_Panic(" is not compatible with VS2005+"); } #endif if (cancelTableInitialized == 0) { Tcl_MutexLock(&cancelLock); if (cancelTableInitialized == 0) { Tcl_InitHashTable(&cancelTable, TCL_ONE_WORD_KEYS); cancelTableInitialized = 1; } Tcl_MutexUnlock(&cancelLock); } #undef TclObjInterpProc if (commandTypeInit == 0) { TclRegisterCommandTypeName(TclObjInterpProc, "proc"); TclRegisterCommandTypeName(TclEnsembleImplementationCmd, "ensemble"); TclRegisterCommandTypeName(TclAliasObjCmd, "alias"); TclRegisterCommandTypeName(TclLocalAliasObjCmd, "alias"); TclRegisterCommandTypeName(TclChildObjCmd, "interp"); TclRegisterCommandTypeName(TclInvokeImportedCmd, "import"); TclRegisterCommandTypeName(TclOOPublicObjectCmd, "object"); TclRegisterCommandTypeName(TclOOPrivateObjectCmd, "privateObject"); TclRegisterCommandTypeName(TclOOMyClassObjCmd, "privateClass"); TclRegisterCommandTypeName(TclNRInterpCoroutine, "coroutine"); } /* * Initialize support for namespaces and create the global namespace * (whose name is ""; an alias is "::"). This also initializes the Tcl * object type table and other object management code. */ iPtr = (Interp *)Tcl_Alloc(sizeof(Interp)); interp = (Tcl_Interp *) iPtr; iPtr->legacyResult = NULL; /* Special invalid value: Any attempt to free the legacy result * will cause a crash. */ iPtr->legacyFreeProc = (void (*) (void))-1; iPtr->errorLine = 0; iPtr->stubTable = &tclStubs; TclNewObj(iPtr->objResultPtr); Tcl_IncrRefCount(iPtr->objResultPtr); iPtr->handle = TclHandleCreate(iPtr); iPtr->globalNsPtr = NULL; iPtr->hiddenCmdTablePtr = NULL; iPtr->interpInfo = NULL; iPtr->optimizer = TclOptimizeBytecode; iPtr->numLevels = 0; iPtr->maxNestingDepth = MAX_NESTING_DEPTH; iPtr->framePtr = NULL; /* Initialise as soon as :: is available */ iPtr->varFramePtr = NULL; /* Initialise as soon as :: is available */ /* * TIP #280 - Initialize the arrays used to extend the ByteCode and Proc * structures. */ iPtr->cmdFramePtr = NULL; iPtr->linePBodyPtr = (Tcl_HashTable *)Tcl_Alloc(sizeof(Tcl_HashTable)); iPtr->lineBCPtr = (Tcl_HashTable *)Tcl_Alloc(sizeof(Tcl_HashTable)); iPtr->lineLAPtr = (Tcl_HashTable *)Tcl_Alloc(sizeof(Tcl_HashTable)); iPtr->lineLABCPtr = (Tcl_HashTable *)Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(iPtr->linePBodyPtr, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(iPtr->lineBCPtr, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(iPtr->lineLAPtr, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(iPtr->lineLABCPtr, TCL_ONE_WORD_KEYS); iPtr->scriptCLLocPtr = NULL; iPtr->activeVarTracePtr = NULL; iPtr->returnOpts = NULL; iPtr->errorInfo = NULL; TclNewLiteralStringObj(iPtr->eiVar, "::errorInfo"); Tcl_IncrRefCount(iPtr->eiVar); iPtr->errorStack = Tcl_NewListObj(0, NULL); Tcl_IncrRefCount(iPtr->errorStack); iPtr->resetErrorStack = 1; TclNewLiteralStringObj(iPtr->upLiteral,"UP"); Tcl_IncrRefCount(iPtr->upLiteral); TclNewLiteralStringObj(iPtr->callLiteral,"CALL"); Tcl_IncrRefCount(iPtr->callLiteral); TclNewLiteralStringObj(iPtr->innerLiteral,"INNER"); Tcl_IncrRefCount(iPtr->innerLiteral); iPtr->innerContext = Tcl_NewListObj(0, NULL); Tcl_IncrRefCount(iPtr->innerContext); iPtr->errorCode = NULL; TclNewLiteralStringObj(iPtr->ecVar, "::errorCode"); Tcl_IncrRefCount(iPtr->ecVar); iPtr->returnLevel = 1; iPtr->returnCode = TCL_OK; iPtr->rootFramePtr = NULL; /* Initialise as soon as :: is available */ iPtr->lookupNsPtr = NULL; Tcl_InitHashTable(&iPtr->packageTable, TCL_STRING_KEYS); iPtr->packageUnknown = NULL; #ifdef _WIN32 # define getenv(x) _wgetenv(L##x) /* On Windows, use _wgetenv below */ #endif /* TIP #268 */ #if (TCL_RELEASE_LEVEL == TCL_FINAL_RELEASE) if (getenv("TCL_PKG_PREFER_LATEST") == NULL) { iPtr->packagePrefer = PKG_PREFER_STABLE; } else #endif iPtr->packagePrefer = PKG_PREFER_LATEST; iPtr->cmdCount = 0; TclInitLiteralTable(&iPtr->literalTable); iPtr->compileEpoch = 1; iPtr->compiledProcPtr = NULL; iPtr->resolverPtr = NULL; iPtr->evalFlags = 0; iPtr->scriptFile = NULL; iPtr->flags = 0; iPtr->tracePtr = NULL; iPtr->tracesForbiddingInline = 0; iPtr->activeCmdTracePtr = NULL; iPtr->activeInterpTracePtr = NULL; iPtr->assocData = NULL; iPtr->execEnvPtr = NULL; /* Set after namespaces initialized. */ TclNewObj(iPtr->emptyObjPtr); /* Another empty object. */ Tcl_IncrRefCount(iPtr->emptyObjPtr); iPtr->threadId = Tcl_GetCurrentThread(); /* TIP #378 */ #ifdef TCL_INTERP_DEBUG_FRAME iPtr->flags |= INTERP_DEBUG_FRAME; #else if (getenv("TCL_INTERP_DEBUG_FRAME") != NULL) { iPtr->flags |= INTERP_DEBUG_FRAME; } #endif /* * Initialise the tables for variable traces and searches *before* * creating the global ns - so that the trace on errorInfo can be * recorded. */ Tcl_InitHashTable(&iPtr->varTraces, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(&iPtr->varSearches, TCL_ONE_WORD_KEYS); iPtr->globalNsPtr = NULL; /* Force creation of global ns below. */ iPtr->globalNsPtr = (Namespace *) Tcl_CreateNamespace(interp, "", NULL, NULL); if (iPtr->globalNsPtr == NULL) { Tcl_Panic("Tcl_CreateInterp: can't create global namespace"); } /* * Initialise the rootCallframe. It cannot be allocated on the stack, as * it has to be in place before TclCreateExecEnv tries to use a variable. */ /* This is needed to satisfy GCC 3.3's strict aliasing rules */ framePtr = (CallFrame *)Tcl_Alloc(sizeof(CallFrame)); (void) Tcl_PushCallFrame(interp, (Tcl_CallFrame *) framePtr, (Tcl_Namespace *) iPtr->globalNsPtr, /*isProcCallFrame*/ 0); framePtr->objc = 0; iPtr->framePtr = framePtr; iPtr->varFramePtr = framePtr; iPtr->rootFramePtr = framePtr; /* * Initialize support for code compilation and execution. We call * TclCreateExecEnv after initializing namespaces since it tries to * reference a Tcl variable (it links to the Tcl "tcl_traceExec" * variable). */ iPtr->execEnvPtr = TclCreateExecEnv(interp, INTERP_STACK_INITIAL_SIZE); /* * TIP #219, Tcl Channel Reflection API support. */ iPtr->chanMsg = NULL; /* * TIP #285, Script cancellation support. */ TclNewObj(iPtr->asyncCancelMsg); cancelInfo = (CancelInfo *)Tcl_Alloc(sizeof(CancelInfo)); cancelInfo->interp = interp; iPtr->asyncCancel = Tcl_AsyncCreate(CancelEvalProc, cancelInfo); cancelInfo->async = iPtr->asyncCancel; cancelInfo->result = NULL; cancelInfo->length = 0; Tcl_MutexLock(&cancelLock); hPtr = Tcl_CreateHashEntry(&cancelTable, iPtr, &isNew); Tcl_SetHashValue(hPtr, cancelInfo); Tcl_MutexUnlock(&cancelLock); /* * Initialize the compilation and execution statistics kept for this * interpreter. */ #ifdef TCL_COMPILE_STATS statsPtr = &iPtr->stats; statsPtr->numExecutions = 0; statsPtr->numCompilations = 0; statsPtr->numByteCodesFreed = 0; memset(statsPtr->instructionCount, 0, sizeof(statsPtr->instructionCount)); statsPtr->totalSrcBytes = 0.0; statsPtr->totalByteCodeBytes = 0.0; statsPtr->currentSrcBytes = 0.0; statsPtr->currentByteCodeBytes = 0.0; memset(statsPtr->srcCount, 0, sizeof(statsPtr->srcCount)); memset(statsPtr->byteCodeCount, 0, sizeof(statsPtr->byteCodeCount)); memset(statsPtr->lifetimeCount, 0, sizeof(statsPtr->lifetimeCount)); statsPtr->currentInstBytes = 0.0; statsPtr->currentLitBytes = 0.0; statsPtr->currentExceptBytes = 0.0; statsPtr->currentAuxBytes = 0.0; statsPtr->currentCmdMapBytes = 0.0; statsPtr->numLiteralsCreated = 0; statsPtr->totalLitStringBytes = 0.0; statsPtr->currentLitStringBytes = 0.0; memset(statsPtr->literalCount, 0, sizeof(statsPtr->literalCount)); #endif /* TCL_COMPILE_STATS */ /* * Initialize the ensemble error message rewriting support. */ TclResetRewriteEnsemble(interp, 1); /* * TIP#143: Initialise the resource limit support. */ TclInitLimitSupport(interp); /* * Initialise the thread-specific data ekeko. Note that the thread's alloc * cache was already initialised by the call to alloc the interp struct. */ #if TCL_THREADS && defined(USE_THREAD_ALLOC) iPtr->allocCache = (AllocCache *)TclpGetAllocCache(); #else iPtr->allocCache = NULL; #endif iPtr->pendingObjDataPtr = NULL; iPtr->asyncReadyPtr = TclGetAsyncReadyPtr(); iPtr->deferredCallbacks = NULL; /* * Create the core commands. Do it here, rather than calling Tcl_CreateObjCommand, * because it's faster (there's no need to check for a preexisting command * by the same name). Set the Tcl_CmdProc to NULL. */ for (cmdInfoPtr = builtInCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) { if ((cmdInfoPtr->objProc == NULL) && (cmdInfoPtr->compileProc == NULL) && (cmdInfoPtr->nreProc == NULL)) { Tcl_Panic("builtin command with NULL object command proc and a NULL compile proc"); } hPtr = Tcl_CreateHashEntry(&iPtr->globalNsPtr->cmdTable, cmdInfoPtr->name, &isNew); if (isNew) { cmdPtr = (Command *)Tcl_Alloc(sizeof(Command)); cmdPtr->hPtr = hPtr; cmdPtr->nsPtr = iPtr->globalNsPtr; cmdPtr->refCount = 1; cmdPtr->cmdEpoch = 0; cmdPtr->compileProc = cmdInfoPtr->compileProc; cmdPtr->proc = NULL; cmdPtr->clientData = cmdPtr; cmdPtr->objProc = cmdInfoPtr->objProc; cmdPtr->objClientData = NULL; cmdPtr->deleteProc = NULL; cmdPtr->deleteData = NULL; cmdPtr->flags = 0; if (cmdInfoPtr->flags & CMD_COMPILES_EXPANDED) { cmdPtr->flags |= CMD_COMPILES_EXPANDED; } cmdPtr->importRefPtr = NULL; cmdPtr->tracePtr = NULL; cmdPtr->nreProc = cmdInfoPtr->nreProc; Tcl_SetHashValue(hPtr, cmdPtr); } } /* * Create the "array", "binary", "chan", "clock", "dict", "encoding", * "file", "info", "namespace" and "string" ensembles. Note that all these * commands (and their subcommands that are not present in the global * namespace) are wholly safe *except* for "clock", "encoding" and "file". */ TclInitArrayCmd(interp); TclInitBinaryCmd(interp); TclInitChanCmd(interp); TclInitDictCmd(interp); TclInitEncodingCmd(interp); TclInitFileCmd(interp); TclInitInfoCmd(interp); TclInitNamespaceCmd(interp); TclInitStringCmd(interp); TclInitPrefixCmd(interp); TclInitProcessCmd(interp); /* * Register "clock" subcommands. These *do* go through * Tcl_CreateObjCommand, since they aren't in the global namespace and * involve ensembles. */ TclClockInit(interp); /* * Register the built-in functions. This is empty now that they are * implemented as commands in the ::tcl::mathfunc namespace. */ /* * Register the default [interp bgerror] handler. */ Tcl_CreateObjCommand(interp, "::tcl::Bgerror", TclDefaultBgErrorHandlerObjCmd, NULL, NULL); /* * Create unsupported commands for debugging bytecode and objects. */ Tcl_CreateObjCommand(interp, "::tcl::unsupported::disassemble", Tcl_DisassembleObjCmd, INT2PTR(0), NULL); Tcl_CreateObjCommand(interp, "::tcl::unsupported::getbytecode", Tcl_DisassembleObjCmd, INT2PTR(1), NULL); Tcl_CreateObjCommand(interp, "::tcl::unsupported::representation", Tcl_RepresentationCmd, NULL, NULL); /* Adding the bytecode assembler command */ cmdPtr = (Command *) Tcl_NRCreateCommand(interp, "::tcl::unsupported::assemble", Tcl_AssembleObjCmd, TclNRAssembleObjCmd, NULL, NULL); cmdPtr->compileProc = &TclCompileAssembleCmd; /* Coroutine monkeybusiness */ Tcl_CreateObjCommand(interp, "::tcl::unsupported::corotype", CoroTypeObjCmd, NULL, NULL); /* Load and intialize ICU */ Tcl_CreateObjCommand(interp, "::tcl::unsupported::loadIcu", TclLoadIcuObjCmd, NULL, NULL); /* Export unsupported commands */ nsPtr = Tcl_FindNamespace(interp, "::tcl::unsupported", NULL, 0); if (nsPtr) { Tcl_Export(interp, nsPtr, "*", 1); } #ifdef USE_DTRACE /* * Register the tcl::dtrace command. */ Tcl_CreateObjCommand(interp, "::tcl::dtrace", DTraceObjCmd, NULL, NULL); #endif /* USE_DTRACE */ /* * Register the builtin math functions. */ nsPtr = Tcl_CreateNamespace(interp, "::tcl::mathfunc", NULL,NULL); if (nsPtr == NULL) { Tcl_Panic("Can't create math function namespace"); } #define MATH_FUNC_PREFIX_LEN 17 /* == strlen("::tcl::mathfunc::") */ memcpy(mathFuncName, "::tcl::mathfunc::", MATH_FUNC_PREFIX_LEN); for (builtinFuncPtr = BuiltinFuncTable; builtinFuncPtr->name != NULL; builtinFuncPtr++) { strcpy(mathFuncName + MATH_FUNC_PREFIX_LEN, builtinFuncPtr->name); Tcl_CreateObjCommand(interp, mathFuncName, builtinFuncPtr->objCmdProc, (void *)builtinFuncPtr->fn, NULL); Tcl_Export(interp, nsPtr, builtinFuncPtr->name, 0); } /* * Register the mathematical "operator" commands. [TIP #174] */ nsPtr = Tcl_CreateNamespace(interp, "::tcl::mathop", NULL, NULL); if (nsPtr == NULL) { Tcl_Panic("cannot create math operator namespace"); } Tcl_Export(interp, nsPtr, "*", 1); #define MATH_OP_PREFIX_LEN 15 /* == strlen("::tcl::mathop::") */ memcpy(mathFuncName, "::tcl::mathop::", MATH_OP_PREFIX_LEN); for (opcmdInfoPtr=mathOpCmds ; opcmdInfoPtr->name!=NULL ; opcmdInfoPtr++){ TclOpCmdClientData *occdPtr = (TclOpCmdClientData *)Tcl_Alloc(sizeof(TclOpCmdClientData)); occdPtr->op = opcmdInfoPtr->name; occdPtr->i.numArgs = opcmdInfoPtr->i.numArgs; occdPtr->expected = opcmdInfoPtr->expected; strcpy(mathFuncName + MATH_OP_PREFIX_LEN, opcmdInfoPtr->name); cmdPtr = (Command *) Tcl_CreateObjCommand(interp, mathFuncName, opcmdInfoPtr->objProc, occdPtr, DeleteOpCmdClientData); if (cmdPtr == NULL) { Tcl_Panic("failed to create math operator %s", opcmdInfoPtr->name); } else if (opcmdInfoPtr->compileProc != NULL) { cmdPtr->compileProc = opcmdInfoPtr->compileProc; } } /* * Do Multiple/Safe Interps Tcl init stuff */ TclInterpInit(interp); TclSetupEnv(interp); /* * TIP #59: Make embedded configuration information available. */ TclInitEmbeddedConfigurationInformation(interp); /* * TIP #440: Declare the name of the script engine to be "Tcl". */ Tcl_SetVar2(interp, "tcl_platform", "engine", "Tcl", TCL_GLOBAL_ONLY); /* * Compute the byte order of this machine. */ order.s = 1; Tcl_SetVar2(interp, "tcl_platform", "byteOrder", ((order.c[0] == 1) ? "littleEndian" : "bigEndian"), TCL_GLOBAL_ONLY); Tcl_SetVar2Ex(interp, "tcl_platform", "wordSize", Tcl_NewWideIntObj(sizeof(long)), TCL_GLOBAL_ONLY); /* TIP #291 */ Tcl_SetVar2Ex(interp, "tcl_platform", "pointerSize", Tcl_NewWideIntObj(sizeof(void *)), TCL_GLOBAL_ONLY); /* * Set up other variables such as tcl_version and tcl_library */ Tcl_SetVar2(interp, "tcl_patchLevel", NULL, TCL_PATCH_LEVEL, TCL_GLOBAL_ONLY); Tcl_SetVar2(interp, "tcl_version", NULL, TCL_VERSION, TCL_GLOBAL_ONLY); TclpSetVariables(interp); /* * Register Tcl's version number. * TIP #268: Full patchlevel instead of just major.minor * TIP #599: Extended build information "+......" */ Tcl_PkgProvideEx(interp, "Tcl", TCL_PATCH_LEVEL, &tclStubs); Tcl_PkgProvideEx(interp, "tcl", TCL_PATCH_LEVEL, &tclStubs); Tcl_CmdInfo info2; Tcl_Command buildInfoCmd = Tcl_CreateObjCommand(interp, "::tcl::build-info", buildInfoObjCmd, (void *)version, NULL); Tcl_GetCommandInfoFromToken(buildInfoCmd, &info2); info2.objProc2 = buildInfoObjCmd2; info2.objClientData2 = (void *)version; Tcl_SetCommandInfoFromToken(buildInfoCmd, &info2); if (TclTommath_Init(interp) != TCL_OK) { Tcl_Panic("%s", Tcl_GetStringResult(interp)); } if (TclOOInit(interp) != TCL_OK) { Tcl_Panic("%s", Tcl_GetStringResult(interp)); } if (TclZlibInit(interp) != TCL_OK || TclZipfs_Init(interp) != TCL_OK) { Tcl_Panic("%s", Tcl_GetStringResult(interp)); } TOP_CB(iPtr) = NULL; return interp; } static void DeleteOpCmdClientData( void *clientData) { TclOpCmdClientData *occdPtr = (TclOpCmdClientData *)clientData; Tcl_Free(occdPtr); } /* * --------------------------------------------------------------------- * * TclRegisterCommandTypeName, TclGetCommandTypeName -- * * Command type registration and lookup mechanism. Everything is keyed by * the Tcl_ObjCmdProc for the command, and that is used as the *key* into * the hash table that maps to constant strings that are names. (It is * recommended that those names be ASCII.) * * --------------------------------------------------------------------- */ void TclRegisterCommandTypeName( Tcl_ObjCmdProc *implementationProc, const char *nameStr) { Tcl_HashEntry *hPtr; Tcl_MutexLock(&commandTypeLock); if (commandTypeInit == 0) { Tcl_InitHashTable(&commandTypeTable, TCL_ONE_WORD_KEYS); commandTypeInit = 1; } if (nameStr != NULL) { int isNew; hPtr = Tcl_CreateHashEntry(&commandTypeTable, implementationProc, &isNew); Tcl_SetHashValue(hPtr, (void *) nameStr); } else { hPtr = Tcl_FindHashEntry(&commandTypeTable, implementationProc); if (hPtr != NULL) { Tcl_DeleteHashEntry(hPtr); } } Tcl_MutexUnlock(&commandTypeLock); } const char * TclGetCommandTypeName( Tcl_Command command) { Command *cmdPtr = (Command *) command; Tcl_ObjCmdProc *procPtr = cmdPtr->objProc; const char *name = "native"; if (procPtr == NULL) { procPtr = cmdPtr->nreProc; } Tcl_MutexLock(&commandTypeLock); if (commandTypeInit) { Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&commandTypeTable, procPtr); if (hPtr && Tcl_GetHashValue(hPtr)) { name = (const char *) Tcl_GetHashValue(hPtr); } } Tcl_MutexUnlock(&commandTypeLock); return name; } /* *---------------------------------------------------------------------- * * TclHideUnsafeCommands -- * * Hides base commands that are not marked as safe from this interpreter. * * Results: * TCL_OK if it succeeds, TCL_ERROR else. * * Side effects: * Hides functionality in an interpreter. * *---------------------------------------------------------------------- */ int TclHideUnsafeCommands( Tcl_Interp *interp) /* Hide commands in this interpreter. */ { const CmdInfo *cmdInfoPtr; const UnsafeEnsembleInfo *unsafePtr; if (interp == NULL) { return TCL_ERROR; } for (cmdInfoPtr = builtInCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) { if (!(cmdInfoPtr->flags & CMD_IS_SAFE)) { Tcl_HideCommand(interp, cmdInfoPtr->name, cmdInfoPtr->name); } } for (unsafePtr = unsafeEnsembleCommands; unsafePtr->ensembleNsName; unsafePtr++) { if (unsafePtr->commandName) { /* * Hide an ensemble subcommand. */ Tcl_Obj *cmdName = Tcl_ObjPrintf("::tcl::%s::%s", unsafePtr->ensembleNsName, unsafePtr->commandName); Tcl_Obj *hideName = Tcl_ObjPrintf("tcl:%s:%s", unsafePtr->ensembleNsName, unsafePtr->commandName); #define INTERIM_HACK_NAME "___tmp" if (TclRenameCommand(interp, TclGetString(cmdName), INTERIM_HACK_NAME) != TCL_OK || Tcl_HideCommand(interp, INTERIM_HACK_NAME, TclGetString(hideName)) != TCL_OK) { Tcl_Panic("problem making '%s %s' safe: %s", unsafePtr->ensembleNsName, unsafePtr->commandName, Tcl_GetStringResult(interp)); } Tcl_CreateObjCommand(interp, TclGetString(cmdName), BadEnsembleSubcommand, (void *)unsafePtr, NULL); TclDecrRefCount(cmdName); TclDecrRefCount(hideName); } else { /* * Hide an ensemble main command (for compatibility). */ if (Tcl_HideCommand(interp, unsafePtr->ensembleNsName, unsafePtr->ensembleNsName) != TCL_OK) { Tcl_Panic("problem making '%s' safe: %s", unsafePtr->ensembleNsName, Tcl_GetStringResult(interp)); } } } return TCL_OK; } /* *---------------------------------------------------------------------- * * BadEnsembleSubcommand -- * * Command used to act as a backstop implementation when subcommands of * ensembles are unsafe (the real implementations of the subcommands are * hidden). The clientData is description of what was hidden. * * Results: * A standard Tcl result (always a TCL_ERROR). * * Side effects: * None. * *---------------------------------------------------------------------- */ static int BadEnsembleSubcommand( void *clientData, Tcl_Interp *interp, TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *) /* objv */) { const UnsafeEnsembleInfo *infoPtr = (const UnsafeEnsembleInfo *)clientData; Tcl_SetObjResult(interp, Tcl_ObjPrintf( "not allowed to invoke subcommand %s of %s", infoPtr->commandName, infoPtr->ensembleNsName)); Tcl_SetErrorCode(interp, "TCL", "SAFE", "SUBCOMMAND", (char *)NULL); return TCL_ERROR; } /* *-------------------------------------------------------------- * * Tcl_CallWhenDeleted -- * * Arrange for a function to be called before a given interpreter is * deleted. The function is called as soon as Tcl_DeleteInterp is called; * if Tcl_CallWhenDeleted is called on an interpreter that has already * been deleted, the function will be called when the last Tcl_Release is * done on the interpreter. * * Results: * None. * * Side effects: * When Tcl_DeleteInterp is invoked to delete interp, proc will be * invoked. See the manual entry for details. * *-------------------------------------------------------------- */ void Tcl_CallWhenDeleted( Tcl_Interp *interp, /* Interpreter to watch. */ Tcl_InterpDeleteProc *proc, /* Function to call when interpreter is about * to be deleted. */ void *clientData) /* One-word value to pass to proc. */ { Interp *iPtr = (Interp *) interp; static Tcl_ThreadDataKey assocDataCounterKey; int *assocDataCounterPtr = (int *) Tcl_GetThreadData(&assocDataCounterKey, sizeof(int)); int isNew; char buffer[32 + TCL_INTEGER_SPACE]; AssocData *dPtr = (AssocData *)Tcl_Alloc(sizeof(AssocData)); Tcl_HashEntry *hPtr; snprintf(buffer, sizeof(buffer), "Assoc Data Key #%d", *assocDataCounterPtr); (*assocDataCounterPtr)++; if (iPtr->assocData == NULL) { iPtr->assocData = (Tcl_HashTable *)Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(iPtr->assocData, TCL_STRING_KEYS); } hPtr = Tcl_CreateHashEntry(iPtr->assocData, buffer, &isNew); dPtr->proc = proc; dPtr->clientData = clientData; Tcl_SetHashValue(hPtr, dPtr); } /* *-------------------------------------------------------------- * * Tcl_DontCallWhenDeleted -- * * Cancel the arrangement for a function to be called when a given * interpreter is deleted. * * Results: * None. * * Side effects: * If proc and clientData were previously registered as a callback via * Tcl_CallWhenDeleted, they are unregistered. If they weren't previously * registered then nothing happens. * *-------------------------------------------------------------- */ void Tcl_DontCallWhenDeleted( Tcl_Interp *interp, /* Interpreter to watch. */ Tcl_InterpDeleteProc *proc, /* Function to call when interpreter is about * to be deleted. */ void *clientData) /* One-word value to pass to proc. */ { Interp *iPtr = (Interp *) interp; Tcl_HashTable *hTablePtr; Tcl_HashSearch hSearch; Tcl_HashEntry *hPtr; AssocData *dPtr; hTablePtr = iPtr->assocData; if (hTablePtr == NULL) { return; } for (hPtr = Tcl_FirstHashEntry(hTablePtr, &hSearch); hPtr != NULL; hPtr = Tcl_NextHashEntry(&hSearch)) { dPtr = (AssocData *)Tcl_GetHashValue(hPtr); if ((dPtr->proc == proc) && (dPtr->clientData == clientData)) { Tcl_Free(dPtr); Tcl_DeleteHashEntry(hPtr); return; } } } /* *---------------------------------------------------------------------- * * Tcl_SetAssocData -- * * Creates a named association between user-specified data, a delete * function and this interpreter. If the association already exists the * data is overwritten with the new data. The delete function will be * invoked when the interpreter is deleted. * * Results: * None. * * Side effects: * Sets the associated data, creates the association if needed. * *---------------------------------------------------------------------- */ void Tcl_SetAssocData( Tcl_Interp *interp, /* Interpreter to associate with. */ const char *name, /* Name for association. */ Tcl_InterpDeleteProc *proc, /* Proc to call when interpreter is about to * be deleted. */ void *clientData) /* One-word value to pass to proc. */ { Interp *iPtr = (Interp *) interp; AssocData *dPtr; Tcl_HashEntry *hPtr; int isNew; if (iPtr->assocData == NULL) { iPtr->assocData = (Tcl_HashTable *)Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(iPtr->assocData, TCL_STRING_KEYS); } hPtr = Tcl_CreateHashEntry(iPtr->assocData, name, &isNew); if (isNew == 0) { dPtr = (AssocData *)Tcl_GetHashValue(hPtr); } else { dPtr = (AssocData *)Tcl_Alloc(sizeof(AssocData)); } dPtr->proc = proc; dPtr->clientData = clientData; Tcl_SetHashValue(hPtr, dPtr); } /* *---------------------------------------------------------------------- * * Tcl_DeleteAssocData -- * * Deletes a named association of user-specified data with the specified * interpreter. * * Results: * None. * * Side effects: * Deletes the association. * *---------------------------------------------------------------------- */ void Tcl_DeleteAssocData( Tcl_Interp *interp, /* Interpreter to associate with. */ const char *name) /* Name of association. */ { Interp *iPtr = (Interp *) interp; AssocData *dPtr; Tcl_HashEntry *hPtr; if (iPtr->assocData == NULL) { return; } hPtr = Tcl_FindHashEntry(iPtr->assocData, name); if (hPtr == NULL) { return; } dPtr = (AssocData *)Tcl_GetHashValue(hPtr); Tcl_DeleteHashEntry(hPtr); if (dPtr->proc != NULL) { dPtr->proc(dPtr->clientData, interp); } Tcl_Free(dPtr); } /* *---------------------------------------------------------------------- * * Tcl_GetAssocData -- * * Returns the client data associated with this name in the specified * interpreter. * * Results: * The client data in the AssocData record denoted by the named * association, or NULL. * * Side effects: * None. * *---------------------------------------------------------------------- */ void * Tcl_GetAssocData( Tcl_Interp *interp, /* Interpreter associated with. */ const char *name, /* Name of association. */ Tcl_InterpDeleteProc **procPtr) /* Pointer to place to store address of * current deletion callback. */ { Interp *iPtr = (Interp *) interp; AssocData *dPtr; Tcl_HashEntry *hPtr; if (iPtr->assocData == NULL) { return NULL; } hPtr = Tcl_FindHashEntry(iPtr->assocData, name); if (hPtr == NULL) { return NULL; } dPtr = (AssocData *)Tcl_GetHashValue(hPtr); if (procPtr != NULL) { *procPtr = dPtr->proc; } return dPtr->clientData; } /* *---------------------------------------------------------------------- * * Tcl_InterpDeleted -- * * Returns nonzero if the interpreter has been deleted with a call to * Tcl_DeleteInterp. * * Results: * Nonzero if the interpreter is deleted, zero otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_InterpDeleted( Tcl_Interp *interp) { return (((Interp *) interp)->flags & DELETED) ? 1 : 0; } /* *---------------------------------------------------------------------- * * Tcl_DeleteInterp -- * * Ensures that the interpreter will be deleted eventually. If there are * no Tcl_Preserve calls in effect for this interpreter, it is deleted * immediately, otherwise the interpreter is deleted when the last * Tcl_Preserve is matched by a call to Tcl_Release. In either case, the * function runs the currently registered deletion callbacks. * * Results: * None. * * Side effects: * The interpreter is marked as deleted. The caller may still use it * safely if there are calls to Tcl_Preserve in effect for the * interpreter, but further calls to Tcl_Eval etc in this interpreter * will fail. * *---------------------------------------------------------------------- */ void Tcl_DeleteInterp( Tcl_Interp *interp) /* Token for command interpreter (returned by * a previous call to Tcl_CreateInterp). */ { Interp *iPtr = (Interp *) interp; /* * If the interpreter has already been marked deleted, just punt. */ if (iPtr->flags & DELETED) { return; } /* * Mark the interpreter as deleted. No further evals will be allowed. * Increase the compileEpoch as a signal to compiled bytecodes. */ iPtr->flags |= DELETED; iPtr->compileEpoch++; /* * Ensure that the interpreter is eventually deleted. */ Tcl_EventuallyFree(interp, DeleteInterpProc); } /* *---------------------------------------------------------------------- * * DeleteInterpProc -- * * Helper function to delete an interpreter. This function is called when * the last call to Tcl_Preserve on this interpreter is matched by a call * to Tcl_Release. The function cleans up all resources used in the * interpreter and calls all currently registered interpreter deletion * callbacks. * * Results: * None. * * Side effects: * Whatever the interpreter deletion callbacks do. Frees resources used * by the interpreter. * *---------------------------------------------------------------------- */ static void DeleteInterpProc( void *blockPtr) /* Interpreter to delete. */ { Tcl_Interp *interp = (Tcl_Interp *) blockPtr; Interp *iPtr = (Interp *) interp; Tcl_HashEntry *hPtr; Tcl_HashSearch search; Tcl_HashTable *hTablePtr; ResolverScheme *resPtr, *nextResPtr; Tcl_Size i; /* * Punt if there is an error in the Tcl_Release/Tcl_Preserve matchup, * unless we are exiting. */ if ((iPtr->numLevels > 0) && !TclInExit()) { Tcl_Panic("DeleteInterpProc called with active evals"); } /* * The interpreter should already be marked deleted; otherwise how did we * get here? */ if (!(iPtr->flags & DELETED)) { Tcl_Panic("DeleteInterpProc called on interpreter not marked deleted"); } /* * TIP #219, Tcl Channel Reflection API. Discard a leftover state. */ if (iPtr->chanMsg != NULL) { Tcl_DecrRefCount(iPtr->chanMsg); iPtr->chanMsg = NULL; } /* * TIP #285, Script cancellation support. Delete this interp from the * global hash table of CancelInfo structs. */ Tcl_MutexLock(&cancelLock); hPtr = Tcl_FindHashEntry(&cancelTable, iPtr); if (hPtr != NULL) { CancelInfo *cancelInfo = (CancelInfo *)Tcl_GetHashValue(hPtr); if (cancelInfo != NULL) { if (cancelInfo->result != NULL) { Tcl_Free(cancelInfo->result); } Tcl_Free(cancelInfo); } Tcl_DeleteHashEntry(hPtr); } if (iPtr->asyncCancel != NULL) { Tcl_AsyncDelete(iPtr->asyncCancel); iPtr->asyncCancel = NULL; } if (iPtr->asyncCancelMsg != NULL) { Tcl_DecrRefCount(iPtr->asyncCancelMsg); iPtr->asyncCancelMsg = NULL; } Tcl_MutexUnlock(&cancelLock); /* * Shut down all limit handler callback scripts that call back into this * interpreter. Then eliminate all limit handlers for this interpreter. */ TclRemoveScriptLimitCallbacks(interp); TclLimitRemoveAllHandlers(interp); /* * Dismantle the namespace here, before we clear the assocData. If any * background errors occur here, they will be deleted below. * * Dismantle the namespace after freeing the iPtr->handle so that each * bytecode releases its literals without caring to update the literal * table, as it will be freed later in this function without further use. */ TclHandleFree(iPtr->handle); TclTeardownNamespace(iPtr->globalNsPtr); /* * Delete all the hidden commands. */ hTablePtr = iPtr->hiddenCmdTablePtr; if (hTablePtr != NULL) { /* * Non-pernicious deletion. The deletion callbacks will not be allowed * to create any new hidden or non-hidden commands. * Tcl_DeleteCommandFromToken will remove the entry from the * hiddenCmdTablePtr. */ hPtr = Tcl_FirstHashEntry(hTablePtr, &search); for (; hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { Tcl_DeleteCommandFromToken(interp, (Tcl_Command)Tcl_GetHashValue(hPtr)); } Tcl_DeleteHashTable(hTablePtr); Tcl_Free(hTablePtr); } if (iPtr->assocData != NULL) { AssocData *dPtr; hTablePtr = iPtr->assocData; /* * Invoke deletion callbacks; note that a callback can create new * callbacks, so we iterate. */ for (hPtr = Tcl_FirstHashEntry(hTablePtr, &search); hPtr != NULL; hPtr = Tcl_FirstHashEntry(hTablePtr, &search)) { dPtr = (AssocData *)Tcl_GetHashValue(hPtr); Tcl_DeleteHashEntry(hPtr); if (dPtr->proc != NULL) { dPtr->proc(dPtr->clientData, interp); } Tcl_Free(dPtr); } Tcl_DeleteHashTable(hTablePtr); Tcl_Free(hTablePtr); iPtr->assocData = NULL; } /* * Pop the root frame pointer and finish deleting the global * namespace. The order is important [Bug 1658572]. */ if ((iPtr->framePtr != iPtr->rootFramePtr) && !TclInExit()) { Tcl_Panic("DeleteInterpProc: popping rootCallFrame with other frames on top"); } Tcl_PopCallFrame(interp); Tcl_Free(iPtr->rootFramePtr); iPtr->rootFramePtr = NULL; Tcl_DeleteNamespace((Tcl_Namespace *) iPtr->globalNsPtr); /* * Free up the result *after* deleting variables, since variable deletion * could have transferred ownership of the result string to Tcl. */ Tcl_DecrRefCount(iPtr->objResultPtr); iPtr->objResultPtr = NULL; Tcl_DecrRefCount(iPtr->ecVar); if (iPtr->errorCode) { Tcl_DecrRefCount(iPtr->errorCode); iPtr->errorCode = NULL; } Tcl_DecrRefCount(iPtr->eiVar); if (iPtr->errorInfo) { Tcl_DecrRefCount(iPtr->errorInfo); iPtr->errorInfo = NULL; } Tcl_DecrRefCount(iPtr->errorStack); iPtr->errorStack = NULL; Tcl_DecrRefCount(iPtr->upLiteral); Tcl_DecrRefCount(iPtr->callLiteral); Tcl_DecrRefCount(iPtr->innerLiteral); Tcl_DecrRefCount(iPtr->innerContext); if (iPtr->returnOpts) { Tcl_DecrRefCount(iPtr->returnOpts); } TclFreePackageInfo(iPtr); while (iPtr->tracePtr != NULL) { Tcl_DeleteTrace((Tcl_Interp *) iPtr, (Tcl_Trace) iPtr->tracePtr); } if (iPtr->execEnvPtr != NULL) { TclDeleteExecEnv(iPtr->execEnvPtr); } if (iPtr->scriptFile) { Tcl_DecrRefCount(iPtr->scriptFile); iPtr->scriptFile = NULL; } Tcl_DecrRefCount(iPtr->emptyObjPtr); iPtr->emptyObjPtr = NULL; resPtr = iPtr->resolverPtr; while (resPtr) { nextResPtr = resPtr->nextPtr; Tcl_Free(resPtr->name); Tcl_Free(resPtr); resPtr = nextResPtr; } /* * Free up literal objects created for scripts compiled by the * interpreter. */ TclDeleteLiteralTable(interp, &iPtr->literalTable); /* * TIP #280 - Release the arrays for ByteCode/Proc extension, and * contents. */ for (hPtr = Tcl_FirstHashEntry(iPtr->linePBodyPtr, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { CmdFrame *cfPtr = (CmdFrame *)Tcl_GetHashValue(hPtr); Proc *procPtr = (Proc *) Tcl_GetHashKey(iPtr->linePBodyPtr, hPtr); procPtr->iPtr = NULL; if (cfPtr) { if (cfPtr->type == TCL_LOCATION_SOURCE) { Tcl_DecrRefCount(cfPtr->data.eval.path); } Tcl_Free(cfPtr->line); Tcl_Free(cfPtr); } Tcl_DeleteHashEntry(hPtr); } Tcl_DeleteHashTable(iPtr->linePBodyPtr); Tcl_Free(iPtr->linePBodyPtr); iPtr->linePBodyPtr = NULL; /* * See also tclCompile.c, TclCleanupByteCode */ for (hPtr = Tcl_FirstHashEntry(iPtr->lineBCPtr, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { ExtCmdLoc *eclPtr = (ExtCmdLoc *)Tcl_GetHashValue(hPtr); if (eclPtr->type == TCL_LOCATION_SOURCE) { Tcl_DecrRefCount(eclPtr->path); } for (i=0; inuloc; i++) { Tcl_Free(eclPtr->loc[i].line); } if (eclPtr->loc != NULL) { Tcl_Free(eclPtr->loc); } Tcl_Free(eclPtr); Tcl_DeleteHashEntry(hPtr); } Tcl_DeleteHashTable(iPtr->lineBCPtr); Tcl_Free(iPtr->lineBCPtr); iPtr->lineBCPtr = NULL; /* * Location stack for uplevel/eval/... scripts which were passed through * proc arguments. Actually we track all arguments as we do not and cannot * know which arguments will be used as scripts and which will not. */ if (iPtr->lineLAPtr->numEntries && !TclInExit()) { /* * When the interp goes away we have nothing on the stack, so there * are no arguments, so this table has to be empty. */ Tcl_Panic("Argument location tracking table not empty"); } Tcl_DeleteHashTable(iPtr->lineLAPtr); Tcl_Free(iPtr->lineLAPtr); iPtr->lineLAPtr = NULL; if (iPtr->lineLABCPtr->numEntries && !TclInExit()) { /* * When the interp goes away we have nothing on the stack, so there * are no arguments, so this table has to be empty. */ Tcl_Panic("Argument location tracking table not empty"); } Tcl_DeleteHashTable(iPtr->lineLABCPtr); Tcl_Free(iPtr->lineLABCPtr); iPtr->lineLABCPtr = NULL; /* * Squelch the tables of traces on variables and searches over arrays in * the in the interpreter. */ Tcl_DeleteHashTable(&iPtr->varTraces); Tcl_DeleteHashTable(&iPtr->varSearches); Tcl_Free(iPtr); } /* *--------------------------------------------------------------------------- * * Tcl_HideCommand -- * * Makes a command hidden so that it cannot be invoked from within an * interpreter, only from within an ancestor. * * Results: * A standard Tcl result; also leaves a message in the interp's result if * an error occurs. * * Side effects: * Removes a command from the command table and create an entry into the * hidden command table under the specified token name. * *--------------------------------------------------------------------------- */ int Tcl_HideCommand( Tcl_Interp *interp, /* Interpreter in which to hide command. */ const char *cmdName, /* Name of command to hide. */ const char *hiddenCmdToken) /* Token name of the to-be-hidden command. */ { Interp *iPtr = (Interp *) interp; Tcl_Command cmd; Command *cmdPtr; Tcl_HashTable *hiddenCmdTablePtr; Tcl_HashEntry *hPtr; int isNew; if (iPtr->flags & DELETED) { /* * The interpreter is being deleted. Do not create any new structures, * because it is not safe to modify the interpreter. */ return TCL_ERROR; } /* * Disallow hiding of commands that are currently in a namespace or * renaming (as part of hiding) into a namespace (because the current * implementation with a single global table and the needed uniqueness of * names cause problems with namespaces). * * We don't need to check for "::" in cmdName because the real check is on * the nsPtr below. * * hiddenCmdToken is just a string which is not interpreted in any way. It * may contain :: but the string is not interpreted as a namespace * qualifier command name. Thus, hiding foo::bar to foo::bar and then * trying to expose or invoke ::foo::bar will NOT work; but if the * application always uses the same strings it will get consistent * behaviour. * * But as we currently limit ourselves to the global namespace only for * the source, in order to avoid potential confusion, lets prevent "::" in * the token too. - dl */ if (strstr(hiddenCmdToken, "::") != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot use namespace qualifiers in hidden command" " token (rename)", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "HIDDENTOKEN", (char *)NULL); return TCL_ERROR; } /* * Find the command to hide. An error is returned if cmdName can't be * found. Look up the command only from the global namespace. Full path of * the command must be given if using namespaces. */ cmd = Tcl_FindCommand(interp, cmdName, NULL, /*flags*/ TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY); if (cmd == (Tcl_Command) NULL) { return TCL_ERROR; } cmdPtr = (Command *) cmd; /* * Check that the command is really in global namespace */ if (cmdPtr->nsPtr != iPtr->globalNsPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "can only hide global namespace commands (use rename then hide)", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "HIDE", "NON_GLOBAL", (char *)NULL); return TCL_ERROR; } /* * Initialize the hidden command table if necessary. */ hiddenCmdTablePtr = iPtr->hiddenCmdTablePtr; if (hiddenCmdTablePtr == NULL) { hiddenCmdTablePtr = (Tcl_HashTable *)Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(hiddenCmdTablePtr, TCL_STRING_KEYS); iPtr->hiddenCmdTablePtr = hiddenCmdTablePtr; } /* * It is an error to move an exposed command to a hidden command with * hiddenCmdToken if a hidden command with the name hiddenCmdToken already * exists. */ hPtr = Tcl_CreateHashEntry(hiddenCmdTablePtr, hiddenCmdToken, &isNew); if (!isNew) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "hidden command named \"%s\" already exists", hiddenCmdToken)); Tcl_SetErrorCode(interp, "TCL", "HIDE", "ALREADY_HIDDEN", (char *)NULL); return TCL_ERROR; } /* * NB: This code is currently 'like' a rename to a special separate name * table. Changes here and in TclRenameCommand must be kept in synch until * the common parts are actually factorized out. */ /* * Remove the hash entry for the command from the interpreter command * table. This is like deleting the command, so bump its command epoch * to invalidate any cached references that point to the command. */ if (cmdPtr->hPtr != NULL) { Tcl_DeleteHashEntry(cmdPtr->hPtr); cmdPtr->hPtr = NULL; cmdPtr->cmdEpoch++; } /* * The list of command exported from the namespace might have changed. * However, we do not need to recompute this just yet; next time we need * the info will be soon enough. */ TclInvalidateNsCmdLookup(cmdPtr->nsPtr); /* * Now link the hash table entry with the command structure. We ensured * above that the nsPtr was right. */ cmdPtr->hPtr = hPtr; Tcl_SetHashValue(hPtr, cmdPtr); /* * If the command being hidden has a compile function, increment the * interpreter's compileEpoch to invalidate its compiled code. This makes * sure that we don't later try to execute old code compiled with * command-specific (i.e., inline) bytecodes for the now-hidden command. * This field is checked in Tcl_EvalObj and ObjInterpProc, and code whose * compilation epoch doesn't match is recompiled. */ if (cmdPtr->compileProc != NULL) { iPtr->compileEpoch++; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ExposeCommand -- * * Makes a previously hidden command callable from inside the interpreter * instead of only by its ancestors. * * Results: * A standard Tcl result. If an error occurs, a message is left in the * interp's result. * * Side effects: * Moves commands from one hash table to another. * *---------------------------------------------------------------------- */ int Tcl_ExposeCommand( Tcl_Interp *interp, /* Interpreter in which to make command * callable. */ const char *hiddenCmdToken, /* Name of hidden command. */ const char *cmdName) /* Name of to-be-exposed command. */ { Interp *iPtr = (Interp *) interp; Command *cmdPtr; Namespace *nsPtr; Tcl_HashEntry *hPtr; Tcl_HashTable *hiddenCmdTablePtr; int isNew; if (iPtr->flags & DELETED) { /* * The interpreter is being deleted. Do not create any new structures, * because it is not safe to modify the interpreter. */ return TCL_ERROR; } /* * Check that we have a regular name for the command (that the user is not * trying to do an expose and a rename (to another namespace) at the same * time). */ if (strstr(cmdName, "::") != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot expose to a namespace (use expose to toplevel, then rename)", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "EXPOSE", "NON_GLOBAL", (char *)NULL); return TCL_ERROR; } /* * Get the command from the hidden command table: */ hPtr = NULL; hiddenCmdTablePtr = iPtr->hiddenCmdTablePtr; if (hiddenCmdTablePtr != NULL) { hPtr = Tcl_FindHashEntry(hiddenCmdTablePtr, hiddenCmdToken); } if (hPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown hidden command \"%s\"", hiddenCmdToken)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "HIDDENTOKEN", hiddenCmdToken, (char *)NULL); return TCL_ERROR; } cmdPtr = (Command *)Tcl_GetHashValue(hPtr); /* * Check that we have a true global namespace command (enforced by * Tcl_HideCommand but let's double check. (If it was not, we would not * really know how to handle it). */ if (cmdPtr->nsPtr != iPtr->globalNsPtr) { /* * This case is theoretically impossible, we might rather Tcl_Panic * than 'nicely' erroring out ? */ Tcl_SetObjResult(interp, Tcl_NewStringObj( "trying to expose a non-global command namespace command", TCL_INDEX_NONE)); return TCL_ERROR; } /* * This is the global table. */ nsPtr = cmdPtr->nsPtr; /* * It is an error to overwrite an existing exposed command as a result of * exposing a previously hidden command. */ hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, cmdName, &isNew); if (!isNew) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "exposed command \"%s\" already exists", cmdName)); Tcl_SetErrorCode(interp, "TCL", "EXPOSE", "COMMAND_EXISTS", (char *)NULL); return TCL_ERROR; } /* * Command resolvers (per-interp, per-namespace) might have resolved to a * command for the given namespace scope with this command not being * registered with the namespace's command table. During BC compilation, * the so-resolved command turns into a CmdName literal. Without * invalidating a possible CmdName literal here explicitly, such literals * keep being reused while pointing to overhauled commands. */ TclInvalidateCmdLiteral(interp, cmdName, nsPtr); /* * The list of command exported from the namespace might have changed. * However, we do not need to recompute this just yet; next time we need * the info will be soon enough. */ TclInvalidateNsCmdLookup(nsPtr); /* * Remove the hash entry for the command from the interpreter hidden * command table. */ if (cmdPtr->hPtr != NULL) { Tcl_DeleteHashEntry(cmdPtr->hPtr); cmdPtr->hPtr = NULL; } /* * Now link the hash table entry with the command structure. This is like * creating a new command, so deal with any shadowing of commands in the * global namespace. */ cmdPtr->hPtr = hPtr; Tcl_SetHashValue(hPtr, cmdPtr); /* * Not needed as we are only in the global namespace (but would be needed * again if we supported namespace command hiding) * * TclResetShadowedCmdRefs(interp, cmdPtr); */ /* * If the command being exposed has a compile function, increment * interpreter's compileEpoch to invalidate its compiled code. This makes * sure that we don't later try to execute old code compiled assuming the * command is hidden. This field is checked in Tcl_EvalObj and * ObjInterpProc, and code whose compilation epoch doesn't match is * recompiled. */ if (cmdPtr->compileProc != NULL) { iPtr->compileEpoch++; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_CreateCommand -- * * Define a new command in a command table. * * Results: * The return value is a token for the command, which can be used in * future calls to Tcl_GetCommandName. * * Side effects: * If a command named cmdName already exists for interp, it is deleted. * In the future, when cmdName is seen as the name of a command by * Tcl_Eval, proc will be called. To support the bytecode interpreter, * the command is created with a wrapper Tcl_ObjCmdProc * (InvokeStringCommand) that eventually calls proc. When the command * is deleted from the table, deleteProc will be called. See the manual * entry for details on the calling sequence. * *---------------------------------------------------------------------- */ Tcl_Command Tcl_CreateCommand( Tcl_Interp *interp, /* Token for command interpreter returned by a * previous call to Tcl_CreateInterp. */ const char *cmdName, /* Name of command. If it contains namespace * qualifiers, the new command is put in the * specified namespace; otherwise it is put in * the global namespace. */ Tcl_CmdProc *proc, /* Function to associate with cmdName. */ void *clientData, /* Arbitrary value passed to string proc. */ Tcl_CmdDeleteProc *deleteProc) /* If not NULL, gives a function to call when * this command is deleted. */ { Interp *iPtr = (Interp *) interp; ImportRef *oldRefPtr = NULL; Namespace *nsPtr; Command *cmdPtr; Tcl_HashEntry *hPtr; const char *tail; int isNew = 0, deleted = 0; ImportedCmdData *dataPtr; if (iPtr->flags & DELETED) { /* * The interpreter is being deleted. Don't create any new commands; * it's not safe to muck with the interpreter anymore. */ return (Tcl_Command) NULL; } /* * If the command name we seek to create already exists, we need to * delete that first. That can be tricky in the presence of traces. * Loop until we no longer find an existing command in the way, or * until we've deleted one command and that didn't finish the job. */ while (1) { /* * Determine where the command should reside. If its name contains * namespace qualifiers, we put it in the specified namespace; * otherwise, we always put it in the global namespace. */ if (strstr(cmdName, "::") != NULL) { Namespace *dummy1, *dummy2; TclGetNamespaceForQualName(interp, cmdName, NULL, TCL_CREATE_NS_IF_UNKNOWN, &nsPtr, &dummy1, &dummy2, &tail); if ((nsPtr == NULL) || (tail == NULL)) { return (Tcl_Command) NULL; } } else { nsPtr = iPtr->globalNsPtr; tail = cmdName; } hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, tail, &isNew); if (isNew || deleted) { /* * isNew - No conflict with existing command. * deleted - We've already deleted a conflicting command */ break; } /* * An existing command conflicts. Try to delete it... */ cmdPtr = (Command *)Tcl_GetHashValue(hPtr); /* * Be careful to preserve any existing import links so we can restore * them down below. That way, you can redefine a command and its * import status will remain intact. */ cmdPtr->refCount++; if (cmdPtr->importRefPtr) { cmdPtr->flags |= CMD_REDEF_IN_PROGRESS; } Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmdPtr); if (cmdPtr->flags & CMD_REDEF_IN_PROGRESS) { oldRefPtr = cmdPtr->importRefPtr; cmdPtr->importRefPtr = NULL; } TclCleanupCommandMacro(cmdPtr); deleted = 1; } if (!isNew) { /* * If the deletion callback recreated the command, just throw away the * new command (if we try to delete it again, we could get stuck in an * infinite loop). */ Tcl_Free(Tcl_GetHashValue(hPtr)); } if (!deleted) { /* * Command resolvers (per-interp, per-namespace) might have resolved * to a command for the given namespace scope with this command not * being registered with the namespace's command table. During BC * compilation, the so-resolved command turns into a CmdName literal. * Without invalidating a possible CmdName literal here explicitly, * such literals keep being reused while pointing to overhauled * commands. */ TclInvalidateCmdLiteral(interp, tail, nsPtr); /* * The list of command exported from the namespace might have changed. * However, we do not need to recompute this just yet; next time we * need the info will be soon enough. */ TclInvalidateNsCmdLookup(nsPtr); TclInvalidateNsPath(nsPtr); } cmdPtr = (Command *)Tcl_Alloc(sizeof(Command)); Tcl_SetHashValue(hPtr, cmdPtr); cmdPtr->hPtr = hPtr; cmdPtr->nsPtr = nsPtr; cmdPtr->refCount = 1; cmdPtr->cmdEpoch = 0; cmdPtr->compileProc = NULL; cmdPtr->objProc = InvokeStringCommand; cmdPtr->objClientData = cmdPtr; cmdPtr->proc = proc; cmdPtr->clientData = clientData; cmdPtr->deleteProc = deleteProc; cmdPtr->deleteData = clientData; cmdPtr->flags = 0; cmdPtr->importRefPtr = NULL; cmdPtr->tracePtr = NULL; cmdPtr->nreProc = NULL; /* * Plug in any existing import references found above. Be sure to update * all of these references to point to the new command. */ if (oldRefPtr != NULL) { cmdPtr->importRefPtr = oldRefPtr; while (oldRefPtr != NULL) { Command *refCmdPtr = oldRefPtr->importedCmdPtr; dataPtr = (ImportedCmdData *)refCmdPtr->objClientData; dataPtr->realCmdPtr = cmdPtr; oldRefPtr = oldRefPtr->nextPtr; } } /* * We just created a command, so in its namespace and all of its parent * namespaces, it may shadow global commands with the same name. If any * shadowed commands are found, invalidate all cached command references * in the affected namespaces. */ TclResetShadowedCmdRefs(interp, cmdPtr); return (Tcl_Command) cmdPtr; } /* *---------------------------------------------------------------------- * * Tcl_CreateObjCommand -- * * Define a new object-based command in a command table. * * Results: * The return value is a token for the command, which can be used in * future calls to Tcl_GetCommandName. * * Side effects: * If a command named "cmdName" already exists for interp, it is * first deleted. Then the new command is created from the arguments. * * In the future, during bytecode evaluation when "cmdName" is seen as * the name of a command by Tcl_EvalObj or Tcl_Eval, the object-based * Tcl_ObjCmdProc proc will be called. When the command is deleted from * the table, deleteProc will be called. See the manual entry for details * on the calling sequence. * *---------------------------------------------------------------------- */ typedef struct { Tcl_ObjCmdProc2 *proc; void *clientData; /* Arbitrary value to pass to proc function. */ Tcl_CmdDeleteProc *deleteProc; void *deleteData; /* Arbitrary value to pass to deleteProc function. */ Tcl_ObjCmdProc2 *nreProc; } CmdWrapperInfo; static int cmdWrapperProc( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj * const *objv) { CmdWrapperInfo *info = (CmdWrapperInfo *) clientData; if (objc < 0) { objc = -1; } return info->proc(info->clientData, interp, objc, objv); } static void cmdWrapperDeleteProc( void *clientData) { CmdWrapperInfo *info = (CmdWrapperInfo *) clientData; clientData = info->deleteData; Tcl_CmdDeleteProc *deleteProc = info->deleteProc; Tcl_Free(info); if (deleteProc != NULL) { deleteProc(clientData); } } Tcl_Command Tcl_CreateObjCommand2( Tcl_Interp *interp, /* Token for command interpreter (returned by * previous call to Tcl_CreateInterp). */ const char *cmdName, /* Name of command. If it contains namespace * qualifiers, the new command is put in the * specified namespace; otherwise it is put in * the global namespace. */ Tcl_ObjCmdProc2 *proc, /* Object-based function to associate with * name. */ void *clientData, /* Arbitrary value to pass to object * function. */ Tcl_CmdDeleteProc *deleteProc) /* If not NULL, gives a function to call when * this command is deleted. */ { CmdWrapperInfo *info = (CmdWrapperInfo *)Tcl_Alloc(sizeof(CmdWrapperInfo)); info->proc = proc; info->clientData = clientData; info->deleteProc = deleteProc; info->deleteData = clientData; return Tcl_CreateObjCommand(interp, cmdName, (proc ? cmdWrapperProc : NULL), info, cmdWrapperDeleteProc); } Tcl_Command Tcl_CreateObjCommand( Tcl_Interp *interp, /* Token for command interpreter (returned by * previous call to Tcl_CreateInterp). */ const char *cmdName, /* Name of command. If it contains namespace * qualifiers, the new command is put in the * specified namespace; otherwise it is put in * the global namespace. */ Tcl_ObjCmdProc *proc, /* Object-based function to associate with * name. */ void *clientData, /* Arbitrary value to pass to object * function. */ Tcl_CmdDeleteProc *deleteProc) /* If not NULL, gives a function to call when * this command is deleted. */ { Interp *iPtr = (Interp *)interp; Namespace *nsPtr; const char *tail; if (iPtr->flags & DELETED) { /* * The interpreter is being deleted. Don't create any new commands; * it's not safe to muck with the interpreter anymore. */ return NULL; } /* * Determine where the command should reside. If its name contains * namespace qualifiers, we put it in the specified namespace; * otherwise, we always put it in the global namespace. */ if (strstr(cmdName, "::") != NULL) { Namespace *dummy1, *dummy2; TclGetNamespaceForQualName(interp, cmdName, NULL, TCL_CREATE_NS_IF_UNKNOWN, &nsPtr, &dummy1, &dummy2, &tail); if ((nsPtr == NULL) || (tail == NULL)) { return (Tcl_Command) NULL; } } else { nsPtr = iPtr->globalNsPtr; tail = cmdName; } return TclCreateObjCommandInNs(interp, tail, (Tcl_Namespace *) nsPtr, proc, clientData, deleteProc); } Tcl_Command TclCreateObjCommandInNs( Tcl_Interp *interp, const char *cmdName, /* Name of command, without any namespace * components. */ Tcl_Namespace *namesp, /* The namespace to create the command in */ Tcl_ObjCmdProc *proc, /* Object-based function to associate with * name. */ void *clientData, /* Arbitrary value to pass to object * function. */ Tcl_CmdDeleteProc *deleteProc) /* If not NULL, gives a function to call when * this command is deleted. */ { int deleted = 0, isNew = 0; Command *cmdPtr; ImportRef *oldRefPtr = NULL; ImportedCmdData *dataPtr; Tcl_HashEntry *hPtr; Namespace *nsPtr = (Namespace *) namesp; /* * If the command name we seek to create already exists, we need to delete * that first. That can be tricky in the presence of traces. Loop until we * no longer find an existing command in the way, or until we've deleted * one command and that didn't finish the job. */ while (1) { hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, cmdName, &isNew); if (isNew || deleted) { /* * isNew - No conflict with existing command. * deleted - We've already deleted a conflicting command */ break; } /* * An existing command conflicts. Try to delete it... */ cmdPtr = (Command *)Tcl_GetHashValue(hPtr); /* * Command already exists; delete it. Be careful to preserve any * existing import links so we can restore them down below. That way, * you can redefine a command and its import status will remain * intact. */ cmdPtr->refCount++; if (cmdPtr->importRefPtr) { cmdPtr->flags |= CMD_REDEF_IN_PROGRESS; } /* * Make sure namespace doesn't get deallocated. */ cmdPtr->nsPtr->refCount++; Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmdPtr); nsPtr = (Namespace *) TclEnsureNamespace(interp, (Tcl_Namespace *) cmdPtr->nsPtr); TclNsDecrRefCount(cmdPtr->nsPtr); if (cmdPtr->flags & CMD_REDEF_IN_PROGRESS) { oldRefPtr = cmdPtr->importRefPtr; cmdPtr->importRefPtr = NULL; } TclCleanupCommandMacro(cmdPtr); deleted = 1; } if (!isNew) { /* * If the deletion callback recreated the command, just throw away the * new command (if we try to delete it again, we could get stuck in an * infinite loop). */ Tcl_Free(Tcl_GetHashValue(hPtr)); } if (!deleted) { /* * Command resolvers (per-interp, per-namespace) might have resolved * to a command for the given namespace scope with this command not * being registered with the namespace's command table. During BC * compilation, the so-resolved command turns into a CmdName literal. * Without invalidating a possible CmdName literal here explicitly, * such literals keep being reused while pointing to overhauled * commands. */ TclInvalidateCmdLiteral(interp, cmdName, nsPtr); /* * The list of command exported from the namespace might have changed. * However, we do not need to recompute this just yet; next time we * need the info will be soon enough. */ TclInvalidateNsCmdLookup(nsPtr); TclInvalidateNsPath(nsPtr); } cmdPtr = (Command *)Tcl_Alloc(sizeof(Command)); Tcl_SetHashValue(hPtr, cmdPtr); cmdPtr->hPtr = hPtr; cmdPtr->nsPtr = nsPtr; cmdPtr->refCount = 1; cmdPtr->cmdEpoch = 0; cmdPtr->compileProc = NULL; cmdPtr->objProc = proc; cmdPtr->objClientData = clientData; cmdPtr->proc = NULL; cmdPtr->clientData = cmdPtr; cmdPtr->deleteProc = deleteProc; cmdPtr->deleteData = clientData; cmdPtr->flags = 0; cmdPtr->importRefPtr = NULL; cmdPtr->tracePtr = NULL; cmdPtr->nreProc = NULL; /* * Plug in any existing import references found above. Be sure to update * all of these references to point to the new command. */ if (oldRefPtr != NULL) { cmdPtr->importRefPtr = oldRefPtr; while (oldRefPtr != NULL) { Command *refCmdPtr = oldRefPtr->importedCmdPtr; dataPtr = (ImportedCmdData*)refCmdPtr->objClientData; cmdPtr->refCount++; TclCleanupCommandMacro(dataPtr->realCmdPtr); dataPtr->realCmdPtr = cmdPtr; oldRefPtr = oldRefPtr->nextPtr; } } /* * We just created a command, so in its namespace and all of its parent * namespaces, it may shadow global commands with the same name. If any * shadowed commands are found, invalidate all cached command references * in the affected namespaces. */ TclResetShadowedCmdRefs(interp, cmdPtr); return (Tcl_Command) cmdPtr; } /* *---------------------------------------------------------------------- * * InvokeStringCommand -- * * "Wrapper" Tcl_ObjCmdProc used to call an existing string-based * Tcl_CmdProc if no object-based function exists for a command. A * pointer to this function is stored as the Tcl_ObjCmdProc in a Command * structure. It simply turns around and calls the string Tcl_CmdProc in * the Command structure. * * Results: * A standard Tcl object result value. * * Side effects: * Besides those side effects of the called Tcl_CmdProc, * InvokeStringCommand allocates and frees storage. * *---------------------------------------------------------------------- */ int InvokeStringCommand( void *clientData, /* Points to command's Command structure. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Command *cmdPtr = (Command *)clientData; int i, result; const char **argv = (const char **) TclStackAlloc(interp, (objc + 1) * sizeof(char *)); for (i = 0; i < objc; i++) { argv[i] = TclGetString(objv[i]); } argv[objc] = 0; /* * Invoke the command's string-based Tcl_CmdProc. */ result = cmdPtr->proc(cmdPtr->clientData, interp, objc, argv); TclStackFree(interp, (void *) argv); return result; } /* *---------------------------------------------------------------------- * * TclRenameCommand -- * * Called to give an existing Tcl command a different name. Both the old * command name and the new command name can have "::" namespace * qualifiers. If the new command has a different namespace context, the * command will be moved to that namespace and will execute in the * context of that new namespace. * * If the new command name is NULL or the null string, the command is * deleted. * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * If anything goes wrong, an error message is returned in the * interpreter's result object. * *---------------------------------------------------------------------- */ int TclRenameCommand( Tcl_Interp *interp, /* Current interpreter. */ const char *oldName, /* Existing command name. */ const char *newName) /* New command name. */ { Interp *iPtr = (Interp *) interp; const char *newTail; Namespace *cmdNsPtr, *newNsPtr, *dummy1, *dummy2; Tcl_Command cmd; Command *cmdPtr; Tcl_HashEntry *hPtr, *oldHPtr; int isNew, result; Tcl_Obj *oldFullName; Tcl_DString newFullName; /* * Find the existing command. An error is returned if cmdName can't be * found. */ cmd = Tcl_FindCommand(interp, oldName, NULL, /*flags*/ 0); cmdPtr = (Command *) cmd; if (cmdPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't %s \"%s\": command doesn't exist", ((newName == NULL) || (*newName == '\0')) ? "delete" : "rename", oldName)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COMMAND", oldName, (char *)NULL); return TCL_ERROR; } /* * If the new command name is NULL or empty, delete the command. Do this * with Tcl_DeleteCommandFromToken, since we already have the command. */ if ((newName == NULL) || (*newName == '\0')) { Tcl_DeleteCommandFromToken(interp, cmd); return TCL_OK; } cmdNsPtr = cmdPtr->nsPtr; TclNewObj(oldFullName); Tcl_IncrRefCount(oldFullName); Tcl_GetCommandFullName(interp, cmd, oldFullName); /* * Make sure that the destination command does not already exist. The * rename operation is like creating a command, so we should automatically * create the containing namespaces just like Tcl_CreateObjCommand would. */ TclGetNamespaceForQualName(interp, newName, NULL, TCL_CREATE_NS_IF_UNKNOWN, &newNsPtr, &dummy1, &dummy2, &newTail); if ((newNsPtr == NULL) || (newTail == NULL)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't rename to \"%s\": bad command name", newName)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMMAND", (char *)NULL); result = TCL_ERROR; goto done; } if (Tcl_FindHashEntry(&newNsPtr->cmdTable, newTail) != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't rename to \"%s\": command already exists", newName)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "RENAME", "TARGET_EXISTS", (char *)NULL); result = TCL_ERROR; goto done; } /* * Warning: any changes done in the code here are likely to be needed in * Tcl_HideCommand code too (until the common parts are extracted out). * - dl */ /* * Put the command in the new namespace so we can check for an alias loop. * Since we are adding a new command to a namespace, we must handle any * shadowing of the global commands that this might create. */ oldHPtr = cmdPtr->hPtr; hPtr = Tcl_CreateHashEntry(&newNsPtr->cmdTable, newTail, &isNew); Tcl_SetHashValue(hPtr, cmdPtr); cmdPtr->hPtr = hPtr; cmdPtr->nsPtr = newNsPtr; TclResetShadowedCmdRefs(interp, cmdPtr); /* * Now check for an alias loop. If we detect one, put everything back the * way it was and report the error. */ result = TclPreventAliasLoop(interp, interp, (Tcl_Command) cmdPtr); if (result != TCL_OK) { Tcl_DeleteHashEntry(cmdPtr->hPtr); cmdPtr->hPtr = oldHPtr; cmdPtr->nsPtr = cmdNsPtr; goto done; } /* * The list of command exported from the namespace might have changed. * However, we do not need to recompute this just yet; next time we need * the info will be soon enough. These might refer to the same variable, * but that's no big deal. */ TclInvalidateNsCmdLookup(cmdNsPtr); TclInvalidateNsCmdLookup(cmdPtr->nsPtr); /* * Command resolvers (per-interp, per-namespace) might have resolved to a * command for the given namespace scope with this command not being * registered with the namespace's command table. During BC compilation, * the so-resolved command turns into a CmdName literal. Without * invalidating a possible CmdName literal here explicitly, such literals * keep being reused while pointing to overhauled commands. */ TclInvalidateCmdLiteral(interp, newTail, cmdPtr->nsPtr); /* * Script for rename traces can delete the command "oldName". Therefore * increment the reference count for cmdPtr so that it's Command structure * is freed only towards the end of this function by calling * TclCleanupCommand. * * The trace function needs to get a fully qualified name for old and new * commands [Tcl bug #651271], or else there's no way for the trace * function to get the namespace from which the old command is being * renamed! */ Tcl_DStringInit(&newFullName); Tcl_DStringAppend(&newFullName, newNsPtr->fullName, TCL_INDEX_NONE); if (newNsPtr != iPtr->globalNsPtr) { TclDStringAppendLiteral(&newFullName, "::"); } Tcl_DStringAppend(&newFullName, newTail, TCL_INDEX_NONE); cmdPtr->refCount++; CallCommandTraces(iPtr, cmdPtr, TclGetString(oldFullName), Tcl_DStringValue(&newFullName), TCL_TRACE_RENAME); Tcl_DStringFree(&newFullName); /* * The new command name is okay, so remove the command from its current * namespace. This is like deleting the command, so bump the cmdEpoch to * invalidate any cached references to the command. */ Tcl_DeleteHashEntry(oldHPtr); cmdPtr->cmdEpoch++; /* * If the command being renamed has a compile function, increment the * interpreter's compileEpoch to invalidate its compiled code. This makes * sure that we don't later try to execute old code compiled for the * now-renamed command. */ if (cmdPtr->compileProc != NULL) { iPtr->compileEpoch++; } /* * Now free the Command structure, if the "oldName" command has been * deleted by invocation of rename traces. */ TclCleanupCommandMacro(cmdPtr); result = TCL_OK; done: TclDecrRefCount(oldFullName); return result; } /* *---------------------------------------------------------------------- * * Tcl_SetCommandInfo -- * * Modifies various information about a Tcl command. Note that this * function will not change a command's namespace; use TclRenameCommand * to do that. Also, the isNativeObjectProc member of *infoPtr is * ignored. * * Results: * If cmdName exists in interp, then the information at *infoPtr is * stored with the command in place of the current information and 1 is * returned. If the command doesn't exist then 0 is returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_SetCommandInfo( Tcl_Interp *interp, /* Interpreter in which to look for * command. */ const char *cmdName, /* Name of desired command. */ const Tcl_CmdInfo *infoPtr) /* Where to find information to store in the * command. */ { Tcl_Command cmd; cmd = Tcl_FindCommand(interp, cmdName, NULL, /*flags*/ 0); return Tcl_SetCommandInfoFromToken(cmd, infoPtr); } /* *---------------------------------------------------------------------- * * Tcl_SetCommandInfoFromToken -- * * Modifies various information about a Tcl command. Note that this * function will not change a command's namespace; use TclRenameCommand * to do that. Also, the isNativeObjectProc member of *infoPtr is * ignored. * * Results: * If cmdName exists in interp, then the information at *infoPtr is * stored with the command in place of the current information and 1 is * returned. If the command doesn't exist then 0 is returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int invokeObj2Command( void *clientData, /* Points to command's Command structure. */ Tcl_Interp *interp, /* Current interpreter. */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int result; Command *cmdPtr = (Command *)clientData; if (objc > INT_MAX) { return TclCommandWordLimitError(interp, objc); } if (cmdPtr->objProc != NULL) { result = cmdPtr->objProc(cmdPtr->objClientData, interp, objc, objv); } else { result = Tcl_NRCallObjProc(interp, cmdPtr->nreProc, cmdPtr->objClientData, objc, objv); } return result; } static int cmdWrapper2Proc( void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[]) { Command *cmdPtr = (Command *) clientData; if (objc > INT_MAX) { return TclCommandWordLimitError(interp, objc); } return cmdPtr->objProc(cmdPtr->objClientData, interp, objc, objv); } int Tcl_SetCommandInfoFromToken( Tcl_Command cmd, const Tcl_CmdInfo *infoPtr) { Command *cmdPtr; /* Internal representation of the command */ if (cmd == NULL) { return 0; } /* * The isNativeObjectProc and nsPtr members of *infoPtr are ignored. */ cmdPtr = (Command *) cmd; cmdPtr->proc = infoPtr->proc; cmdPtr->clientData = infoPtr->clientData; if (infoPtr->objProc == NULL) { cmdPtr->objProc = InvokeStringCommand; cmdPtr->objClientData = cmdPtr; cmdPtr->nreProc = NULL; } else { if (infoPtr->objProc != cmdPtr->objProc) { cmdPtr->nreProc = NULL; cmdPtr->objProc = infoPtr->objProc; } cmdPtr->objClientData = infoPtr->objClientData; } if (cmdPtr->deleteProc == cmdWrapperDeleteProc) { CmdWrapperInfo *info = (CmdWrapperInfo *) cmdPtr->deleteData; if (infoPtr->objProc2 == NULL) { info->proc = invokeObj2Command; info->clientData = cmdPtr; info->nreProc = NULL; } else { if (infoPtr->objProc2 != info->proc) { info->nreProc = NULL; info->proc = infoPtr->objProc2; } info->clientData = infoPtr->objClientData2; } info->deleteProc = infoPtr->deleteProc; info->deleteData = infoPtr->deleteData; } else { if ((infoPtr->objProc2 != NULL) && (infoPtr->objProc2 != cmdWrapper2Proc)) { CmdWrapperInfo *info = (CmdWrapperInfo *)Tcl_Alloc(sizeof(CmdWrapperInfo)); info->proc = infoPtr->objProc2; info->clientData = infoPtr->objClientData2; info->nreProc = NULL; info->deleteProc = infoPtr->deleteProc; info->deleteData = infoPtr->deleteData; cmdPtr->deleteProc = cmdWrapperDeleteProc; cmdPtr->deleteData = info; } else { cmdPtr->deleteProc = infoPtr->deleteProc; cmdPtr->deleteData = infoPtr->deleteData; } } return 1; } /* *---------------------------------------------------------------------- * * Tcl_GetCommandInfo -- * * Returns various information about a Tcl command. * * Results: * If cmdName exists in interp, then *infoPtr is modified to hold * information about cmdName and 1 is returned. If the command doesn't * exist then 0 is returned and *infoPtr isn't modified. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_GetCommandInfo( Tcl_Interp *interp, /* Interpreter in which to look for * command. */ const char *cmdName, /* Name of desired command. */ Tcl_CmdInfo *infoPtr) /* Where to store information about * command. */ { Tcl_Command cmd; cmd = Tcl_FindCommand(interp, cmdName, NULL, /*flags*/ 0); return Tcl_GetCommandInfoFromToken(cmd, infoPtr); } /* *---------------------------------------------------------------------- * * Tcl_GetCommandInfoFromToken -- * * Returns various information about a Tcl command. * * Results: * Copies information from the command identified by 'cmd' into a * caller-supplied structure and returns 1. If the 'cmd' is NULL, leaves * the structure untouched and returns 0. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_GetCommandInfoFromToken( Tcl_Command cmd, Tcl_CmdInfo *infoPtr) { Command *cmdPtr; /* Internal representation of the command */ if (cmd == NULL) { return 0; } /* * Set isNativeObjectProc 1 if objProc was registered by a call to * Tcl_CreateObjCommand. Set isNativeObjectProc 2 if objProc was * registered by a call to Tcl_CreateObjCommand2. Otherwise set it to 0. */ cmdPtr = (Command *) cmd; infoPtr->isNativeObjectProc = (cmdPtr->objProc != InvokeStringCommand); infoPtr->objProc = cmdPtr->objProc; infoPtr->objClientData = cmdPtr->objClientData; infoPtr->proc = cmdPtr->proc; infoPtr->clientData = cmdPtr->clientData; if (cmdPtr->deleteProc == cmdWrapperDeleteProc) { CmdWrapperInfo *info = (CmdWrapperInfo *)cmdPtr->deleteData; infoPtr->deleteProc = info->deleteProc; infoPtr->deleteData = info->deleteData; infoPtr->objProc2 = info->proc; infoPtr->objClientData2 = info->clientData; if (cmdPtr->objProc == cmdWrapperProc) { infoPtr->isNativeObjectProc = 2; } } else { infoPtr->deleteProc = cmdPtr->deleteProc; infoPtr->deleteData = cmdPtr->deleteData; infoPtr->objProc2 = cmdWrapper2Proc; infoPtr->objClientData2 = cmdPtr; } infoPtr->namespacePtr = (Tcl_Namespace *) cmdPtr->nsPtr; return 1; } /* *---------------------------------------------------------------------- * * Tcl_GetCommandName -- * * Given a token returned by Tcl_CreateObjCommand, this function returns the * current name of the command (which may have changed due to renaming). * * Results: * The return value is the name of the given command. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * Tcl_GetCommandName( TCL_UNUSED(Tcl_Interp *), Tcl_Command command) /* Token for command returned by a previous * call to Tcl_CreateObjCommand. The command must * not have been deleted. */ { Command *cmdPtr = (Command *) command; if ((cmdPtr == NULL) || (cmdPtr->hPtr == NULL)) { /* * This should only happen if command was "created" after the * interpreter began to be deleted, so there isn't really any command. * Just return an empty string. */ return ""; } return (const char *)Tcl_GetHashKey(cmdPtr->hPtr->tablePtr, cmdPtr->hPtr); } /* *---------------------------------------------------------------------- * * Tcl_GetCommandFullName -- * * Given a token returned by, e.g., Tcl_CreateObjCommand or Tcl_FindCommand, * this function appends to an object the command's full name, qualified * by a sequence of parent namespace names. The command's fully-qualified * name may have changed due to renaming. * * Results: * None. * * Side effects: * The command's fully-qualified name is appended to the string * representation of objPtr. * *---------------------------------------------------------------------- */ void Tcl_GetCommandFullName( Tcl_Interp *interp, /* Interpreter containing the command. */ Tcl_Command command, /* Token for command returned by a previous * call to Tcl_CreateObjCommand. The command must * not have been deleted. */ Tcl_Obj *objPtr) /* Points to the object onto which the * command's full name is appended. */ { Interp *iPtr = (Interp *) interp; Command *cmdPtr = (Command *) command; char *name; /* * Add the full name of the containing namespace, followed by the "::" * separator, and the command name. */ if ((cmdPtr != NULL) && TclRoutineHasName(cmdPtr)) { if (cmdPtr->nsPtr != NULL) { Tcl_AppendToObj(objPtr, cmdPtr->nsPtr->fullName, TCL_INDEX_NONE); if (cmdPtr->nsPtr != iPtr->globalNsPtr) { Tcl_AppendToObj(objPtr, "::", 2); } } if (cmdPtr->hPtr != NULL) { name = (char *)Tcl_GetHashKey(cmdPtr->hPtr->tablePtr, cmdPtr->hPtr); Tcl_AppendToObj(objPtr, name, TCL_INDEX_NONE); } } } /* *---------------------------------------------------------------------- * * Tcl_DeleteCommand -- * * Remove the given command from the given interpreter. * * Results: * 0 is returned if the command was deleted successfully. -1 is returned * if there didn't exist a command by that name. * * Side effects: * cmdName will no longer be recognized as a valid command for interp. * *---------------------------------------------------------------------- */ int Tcl_DeleteCommand( Tcl_Interp *interp, /* Token for command interpreter (returned by * a previous Tcl_CreateInterp call). */ const char *cmdName) /* Name of command to remove. */ { Tcl_Command cmd; /* * Find the desired command and delete it. */ cmd = Tcl_FindCommand(interp, cmdName, NULL, /*flags*/ 0); if (cmd == NULL) { return -1; } return Tcl_DeleteCommandFromToken(interp, cmd); } /* *---------------------------------------------------------------------- * * Tcl_DeleteCommandFromToken -- * * Removes the given command from the given interpreter. This function * resembles Tcl_DeleteCommand, but takes a Tcl_Command token instead of * a command name for efficiency. * * Results: * 0 is returned if the command was deleted successfully. -1 is returned * if there didn't exist a command by that name. * * Side effects: * The command specified by "cmd" will no longer be recognized as a valid * command for "interp". * *---------------------------------------------------------------------- */ int Tcl_DeleteCommandFromToken( Tcl_Interp *interp, /* Token for command interpreter returned by a * previous call to Tcl_CreateInterp. */ Tcl_Command cmd) /* Token for command to delete. */ { Interp *iPtr = (Interp *) interp; Command *cmdPtr = (Command *) cmd; ImportRef *refPtr, *nextRefPtr; Tcl_Command importCmd; /* * The code here is tricky. We can't delete the hash table entry before * invoking the deletion callback because there are cases where the * deletion callback needs to invoke the command (e.g. object systems such * as OTcl). However, this means that the callback could try to delete or * rename the command. The deleted flag allows us to detect these cases * and skip nested deletes. */ if (cmdPtr->flags & CMD_DYING) { /* * Another deletion is already in progress. Remove the hash table * entry now, but don't invoke a callback or free the command * structure. Take care to only remove the hash entry if it has not * already been removed; otherwise if we manage to hit this function * three times, everything goes up in smoke. [Bug 1220058] */ if (cmdPtr->hPtr != NULL) { Tcl_DeleteHashEntry(cmdPtr->hPtr); cmdPtr->hPtr = NULL; } /* * Bump the command epoch counter. This will invalidate all cached * references that point to this command. */ cmdPtr->cmdEpoch++; return 0; } /* * We must delete this command, even though both traces and delete procs * may try to avoid this (renaming the command etc). Also traces and * delete procs may try to delete the command themselves. This flag * declares that a delete is in progress and that recursive deletes should * be ignored. */ cmdPtr->flags |= CMD_DYING; /* * Call each functions and then delete the trace. */ cmdPtr->nsPtr->refCount++; if (cmdPtr->tracePtr != NULL) { CommandTrace *tracePtr; /* CallCommandTraces() does not cmdPtr, that's * done just before Tcl_DeleteCommandFromToken() returns */ CallCommandTraces(iPtr,cmdPtr,NULL,NULL,TCL_TRACE_DELETE); /* * Now delete these traces. */ tracePtr = cmdPtr->tracePtr; while (tracePtr != NULL) { CommandTrace *nextPtr = tracePtr->nextPtr; if (tracePtr->refCount-- <= 1) { Tcl_Free(tracePtr); } tracePtr = nextPtr; } cmdPtr->tracePtr = NULL; } /* * The list of commands exported from the namespace might have changed. * However, we do not need to recompute this just yet; next time we need * the info will be soon enough. */ TclInvalidateNsCmdLookup(cmdPtr->nsPtr); TclNsDecrRefCount(cmdPtr->nsPtr); /* * If the command being deleted has a compile function, increment the * interpreter's compileEpoch to invalidate its compiled code. This makes * sure that we don't later try to execute old code compiled with * command-specific (i.e., inline) bytecodes for the now-deleted command. * This field is checked in Tcl_EvalObj and ObjInterpProc, and code whose * compilation epoch doesn't match is recompiled. */ if (cmdPtr->compileProc != NULL) { iPtr->compileEpoch++; } if (!(cmdPtr->flags & CMD_REDEF_IN_PROGRESS)) { /* * Delete any imports of this routine before deleting this routine itself. * See issue 688fcc7082fa. */ for (refPtr = cmdPtr->importRefPtr; refPtr != NULL; refPtr = nextRefPtr) { nextRefPtr = refPtr->nextPtr; importCmd = (Tcl_Command) refPtr->importedCmdPtr; Tcl_DeleteCommandFromToken(interp, importCmd); } } if (cmdPtr->deleteProc != NULL) { /* * Delete the command's client data. If this was an imported command * created when a command was imported into a namespace, this client * data will be a pointer to a ImportedCmdData structure describing * the "real" command that this imported command refers to. * * If you are getting a crash during the call to deleteProc and * cmdPtr->deleteProc is a pointer to the function free(), the most * likely cause is that your extension allocated memory for the * clientData argument to Tcl_CreateObjCommand with the Tcl_Alloc() * macro and you are now trying to deallocate this memory with free() * instead of Tcl_Free(). You should pass a pointer to your own method * that calls Tcl_Free(). */ cmdPtr->deleteProc(cmdPtr->deleteData); } /* * Don't use hPtr to delete the hash entry here, because it's possible * that the deletion callback renamed the command. Instead, use * cmdPtr->hptr, and make sure that no-one else has already deleted the * hash entry. */ if (cmdPtr->hPtr != NULL) { Tcl_DeleteHashEntry(cmdPtr->hPtr); cmdPtr->hPtr = NULL; /* * Bump the command epoch counter. This will invalidate all cached * references that point to this command. */ cmdPtr->cmdEpoch++; } /* * A number of tests for particular kinds of commands are done by checking * whether the objProc field holds a known value. Set the field to NULL so * that such tests won't have false positives when applied to deleted * commands. */ cmdPtr->objProc = NULL; /* * Now free the Command structure, unless there is another reference to it * from a CmdName Tcl object in some ByteCode code sequence. In that case, * delay the cleanup until all references are either discarded (when a * ByteCode is freed) or replaced by a new reference (when a cached * CmdName Command reference is found to be invalid and * TclNRExecuteByteCode looks up the command in the command hashtable). */ cmdPtr->flags |= CMD_DEAD; TclCleanupCommandMacro(cmdPtr); return 0; } /* *---------------------------------------------------------------------- * * CallCommandTraces -- * * Abstraction of the code to call traces on a command. * * Results: * Currently always NULL. * * Side effects: * Anything; this may recursively evaluate scripts and code exists to do * just that. * *---------------------------------------------------------------------- */ static char * CallCommandTraces( Interp *iPtr, /* Interpreter containing command. */ Command *cmdPtr, /* Command whose traces are to be invoked. */ const char *oldName, /* Command's old name, or NULL if we must get * the name from cmdPtr */ const char *newName, /* Command's new name, or NULL if the command * is not being renamed */ int flags) /* Flags indicating the type of traces to * trigger, either TCL_TRACE_DELETE or * TCL_TRACE_RENAME. */ { CommandTrace *tracePtr; ActiveCommandTrace active; char *result; Tcl_Obj *oldNamePtr = NULL; Tcl_InterpState state = NULL; if (cmdPtr->flags & CMD_TRACE_ACTIVE) { /* * While a rename trace is active, we will not process any more rename * traces; while a delete trace is active we will never reach here - * because Tcl_DeleteCommandFromToken checks for the condition * (cmdPtr->flags & CMD_DYING) and returns immediately when a * command deletion is in progress. For all other traces, delete * traces will not be invoked but a call to TraceCommandProc will * ensure that tracePtr->clientData is freed whenever the command * "oldName" is deleted. */ if (cmdPtr->flags & TCL_TRACE_RENAME) { flags &= ~TCL_TRACE_RENAME; } if (flags == 0) { return NULL; } } cmdPtr->flags |= CMD_TRACE_ACTIVE; result = NULL; active.nextPtr = iPtr->activeCmdTracePtr; active.reverseScan = 0; iPtr->activeCmdTracePtr = &active; if (flags & TCL_TRACE_DELETE) { flags |= TCL_TRACE_DESTROYED; } active.cmdPtr = cmdPtr; Tcl_Preserve(iPtr); for (tracePtr = cmdPtr->tracePtr; tracePtr != NULL; tracePtr = active.nextTracePtr) { active.nextTracePtr = tracePtr->nextPtr; if (!(tracePtr->flags & flags)) { continue; } cmdPtr->flags |= tracePtr->flags; if (oldName == NULL) { TclNewObj(oldNamePtr); Tcl_IncrRefCount(oldNamePtr); Tcl_GetCommandFullName((Tcl_Interp *) iPtr, (Tcl_Command) cmdPtr, oldNamePtr); oldName = TclGetString(oldNamePtr); } tracePtr->refCount++; if (state == NULL) { state = Tcl_SaveInterpState((Tcl_Interp *) iPtr, TCL_OK); } tracePtr->traceProc(tracePtr->clientData, (Tcl_Interp *) iPtr, oldName, newName, flags); cmdPtr->flags &= ~tracePtr->flags; if (tracePtr->refCount-- <= 1) { Tcl_Free(tracePtr); } } if (state) { Tcl_RestoreInterpState((Tcl_Interp *) iPtr, state); } /* * If a new object was created to hold the full oldName, free it now. */ if (oldNamePtr != NULL) { TclDecrRefCount(oldNamePtr); } /* * Restore the variable's flags, remove the record of our active traces, * and then return. */ cmdPtr->flags &= ~CMD_TRACE_ACTIVE; iPtr->activeCmdTracePtr = active.nextPtr; Tcl_Release(iPtr); return result; } /* *---------------------------------------------------------------------- * * CancelEvalProc -- * * Marks this interpreter as being canceled. This causes current * executions to be unwound as the interpreter enters a state where it * refuses to execute more commands or handle [catch] or [try], yet the * interpreter is still able to execute further commands after the * cancelation is cleared (unlike if it is deleted). * * Results: * The value given for the code argument. * * Side effects: * Transfers a message from the cancellation message to the interpreter. * *---------------------------------------------------------------------- */ static int CancelEvalProc( void *clientData, /* Interp to cancel the script in progress. */ TCL_UNUSED(Tcl_Interp *), int code) /* Current return code from command. */ { CancelInfo *cancelInfo = (CancelInfo *)clientData; Interp *iPtr; if (cancelInfo != NULL) { Tcl_MutexLock(&cancelLock); iPtr = (Interp *) cancelInfo->interp; if (iPtr != NULL) { /* * Setting the CANCELED flag will cause the script in progress to * be canceled as soon as possible. The core honors this flag at * all the necessary places to ensure script cancellation is * responsive. Extensions can check for this flag by calling * Tcl_Canceled and checking if TCL_ERROR is returned or they can * choose to ignore the script cancellation flag and the * associated functionality altogether. Currently, the only other * flag we care about here is the TCL_CANCEL_UNWIND flag (from * Tcl_CancelEval). We do not want to simply combine all the flags * from original Tcl_CancelEval call with the interp flags here * just in case the caller passed flags that might cause behaviour * unrelated to script cancellation. */ TclSetCancelFlags(iPtr, cancelInfo->flags | CANCELED); /* * Now, we must set the script cancellation flags on all the child * interpreters belonging to this one. */ TclSetChildCancelFlags((Tcl_Interp *) iPtr, cancelInfo->flags | CANCELED, 0); /* * Create the result object now so that Tcl_Canceled can avoid * locking the cancelLock mutex. */ if (cancelInfo->result != NULL) { Tcl_SetStringObj(iPtr->asyncCancelMsg, cancelInfo->result, cancelInfo->length); } else { Tcl_SetObjLength(iPtr->asyncCancelMsg, 0); } } Tcl_MutexUnlock(&cancelLock); } return code; } /* *---------------------------------------------------------------------- * * TclCleanupCommand -- * * This function frees up a Command structure unless it is still * referenced from an interpreter's command hashtable or from a CmdName * Tcl object representing the name of a command in a ByteCode * instruction sequence. * * Results: * None. * * Side effects: * Memory gets freed unless a reference to the Command structure still * exists. In that case the cleanup is delayed until the command is * deleted or when the last ByteCode referring to it is freed. * *---------------------------------------------------------------------- */ void TclCleanupCommand( Command *cmdPtr) /* Points to the Command structure to * be freed. */ { if (cmdPtr->refCount-- <= 1) { Tcl_Free(cmdPtr); } } /* *---------------------------------------------------------------------- * * TclInterpReady -- * * Check if an interpreter is ready to eval commands or scripts, i.e., if * it was not deleted and if the nesting level is not too high. * * Results: * The return value is TCL_OK if it the interpreter is ready, TCL_ERROR * otherwise. * * Side effects: * The interpreter's result is cleared. * *---------------------------------------------------------------------- */ int TclInterpReady( Tcl_Interp *interp) { Interp *iPtr = (Interp *) interp; /* * Reset the interpreter's result and clear out any previous error * information. */ Tcl_ResetResult(interp); /* * If the interpreter has been deleted, return an error. */ if (iPtr->flags & DELETED) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "attempt to call eval in deleted interpreter", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "IDELETE", "attempt to call eval in deleted interpreter", (char *)NULL); return TCL_ERROR; } if (iPtr->execEnvPtr->rewind) { return TCL_ERROR; } /* * Make sure the script being evaluated (if any) has not been canceled. */ if (TclCanceled(iPtr) && (TCL_OK != Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG))) { return TCL_ERROR; } /* * Check depth of nested calls to Tcl_Eval: if this gets too large, it's * probably because of an infinite loop somewhere. */ if ((iPtr->numLevels <= iPtr->maxNestingDepth)) { return TCL_OK; } Tcl_SetObjResult(interp, Tcl_NewStringObj( "too many nested evaluations (infinite loop?)", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "LIMIT", "STACK", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TclResetCancellation -- * * Reset the script cancellation flags if the nesting level * (iPtr->numLevels) for the interp is zero or argument force is * non-zero. * * Results: * A standard Tcl result. * * Side effects: * The script cancellation flags for the interp may be reset. * *---------------------------------------------------------------------- */ int TclResetCancellation( Tcl_Interp *interp, int force) { Interp *iPtr = (Interp *) interp; if (iPtr == NULL) { return TCL_ERROR; } if (force || (iPtr->numLevels == 0)) { TclUnsetCancelFlags(iPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_Canceled -- * * Check if the script in progress has been canceled, i.e., * Tcl_CancelEval was called for this interpreter or any of its parent * interpreters. * * Results: * The return value is TCL_OK if the script evaluation has not been * canceled, TCL_ERROR otherwise. * * If "flags" contains TCL_LEAVE_ERR_MSG, an error message is returned in * the interpreter's result object. Otherwise, the interpreter's result * object is left unchanged. If "flags" contains TCL_CANCEL_UNWIND, * TCL_ERROR will only be returned if the script evaluation is being * completely unwound. * * Side effects: * The CANCELED flag for the interp will be reset if it is set. * *---------------------------------------------------------------------- */ int Tcl_Canceled( Tcl_Interp *interp, int flags) { Interp *iPtr = (Interp *) interp; /* * Has the current script in progress for this interpreter been canceled * or is the stack being unwound due to the previous script cancellation? */ if (!TclCanceled(iPtr)) { return TCL_OK; } /* * The CANCELED flag is a one-shot flag that is reset immediately upon * being detected; however, if the TCL_CANCEL_UNWIND flag is set we will * continue to report that the script in progress has been canceled * thereby allowing the evaluation stack for the interp to be fully * unwound. */ iPtr->flags &= ~CANCELED; /* * The CANCELED flag was detected and reset; however, if the caller * specified the TCL_CANCEL_UNWIND flag, we only return TCL_ERROR * (indicating that the script in progress has been canceled) if the * evaluation stack for the interp is being fully unwound. */ if ((flags & TCL_CANCEL_UNWIND) && !(iPtr->flags & TCL_CANCEL_UNWIND)) { return TCL_OK; } /* * If the TCL_LEAVE_ERR_MSG flags bit is set, place an error in the * interp's result; otherwise, we leave it alone. */ if (flags & TCL_LEAVE_ERR_MSG) { const char *id, *message = NULL; Tcl_Size length; /* * Setup errorCode variables so that we can differentiate between * being canceled and unwound. */ if (iPtr->asyncCancelMsg != NULL) { message = TclGetStringFromObj(iPtr->asyncCancelMsg, &length); } else { length = 0; } if (iPtr->flags & TCL_CANCEL_UNWIND) { id = "IUNWIND"; if (length == 0) { message = "eval unwound"; } } else { id = "ICANCEL"; if (length == 0) { message = "eval canceled"; } } Tcl_SetObjResult(interp, Tcl_NewStringObj(message, TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "CANCEL", id, message, (char *)NULL); } /* * Return TCL_ERROR to the caller (not necessarily just the Tcl core * itself) that indicates further processing of the script or command in * progress should halt gracefully and as soon as possible. */ return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_CancelEval -- * * This function schedules the cancellation of the current script in the * given interpreter. * * Results: * The return value is a standard Tcl completion code such as TCL_OK or * TCL_ERROR. Since the interp may belong to a different thread, no error * message can be left in the interp's result. * * Side effects: * The script in progress in the specified interpreter will be canceled * with TCL_ERROR after asynchronous handlers are invoked at the next * Tcl_Canceled check. * *---------------------------------------------------------------------- */ int Tcl_CancelEval( Tcl_Interp *interp, /* Interpreter in which to cancel the * script. */ Tcl_Obj *resultObjPtr, /* The script cancellation error message or * NULL for a default error message. */ void *clientData, /* Passed to CancelEvalProc. */ int flags) /* Collection of OR-ed bits that control * the cancellation of the script. Only * TCL_CANCEL_UNWIND is currently * supported. */ { Tcl_HashEntry *hPtr; CancelInfo *cancelInfo; int code = TCL_ERROR; const char *result; if (interp == NULL) { return TCL_ERROR; } Tcl_MutexLock(&cancelLock); if (cancelTableInitialized != 1) { /* * No CancelInfo hash table (Tcl_CreateInterp has never been called?) */ goto done; } hPtr = Tcl_FindHashEntry(&cancelTable, interp); if (hPtr == NULL) { /* * No CancelInfo record for this interpreter. */ goto done; } cancelInfo = (CancelInfo *)Tcl_GetHashValue(hPtr); /* * Populate information needed by the interpreter thread to fulfill the * cancellation request. Currently, clientData is ignored. If the * TCL_CANCEL_UNWIND flags bit is set, the script in progress is not * allowed to catch the script cancellation because the evaluation stack * for the interp is completely unwound. */ if (resultObjPtr != NULL) { result = TclGetStringFromObj(resultObjPtr, &cancelInfo->length); cancelInfo->result = (char *)Tcl_Realloc(cancelInfo->result, cancelInfo->length); memcpy(cancelInfo->result, result, cancelInfo->length); TclDecrRefCount(resultObjPtr); /* Discard their result object. */ } else { cancelInfo->result = NULL; cancelInfo->length = 0; } cancelInfo->clientData = clientData; cancelInfo->flags = flags; Tcl_AsyncMark(cancelInfo->async); code = TCL_OK; done: Tcl_MutexUnlock(&cancelLock); return code; } /* *---------------------------------------------------------------------- * * Tcl_InterpActive -- * * Returns non-zero if the specified interpreter is in use, i.e. if there * is an evaluation currently active in the interpreter. * * Results: * See above. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_InterpActive( Tcl_Interp *interp) { return ((Interp *) interp)->numLevels > 0; } /* *---------------------------------------------------------------------- * * Tcl_EvalObjv -- * * This function evaluates a Tcl command that has already been parsed * into words, with one Tcl_Obj holding each word. * * Results: * The return value is a standard Tcl completion code such as TCL_OK or * TCL_ERROR. A result or error message is left in interp's result. * * Side effects: * Always pushes a callback. Other side effects depend on the command. * *---------------------------------------------------------------------- */ int Tcl_EvalObjv( Tcl_Interp *interp, /* Interpreter in which to evaluate the * command. Also used for error reporting. */ Tcl_Size objc, /* Number of words in command. */ Tcl_Obj *const objv[], /* An array of pointers to objects that are * the words that make up the command. */ int flags) /* Collection of OR-ed bits that control the * evaluation of the script. Only * TCL_EVAL_GLOBAL, TCL_EVAL_INVOKE and * TCL_EVAL_NOERR are currently supported. */ { int result; NRE_callback *rootPtr = TOP_CB(interp); result = TclNREvalObjv(interp, objc, objv, flags, NULL); return TclNRRunCallbacks(interp, result, rootPtr); } int TclNREvalObjv( Tcl_Interp *interp, /* Interpreter in which to evaluate the * command. Also used for error reporting. */ Tcl_Size objc, /* Number of words in command. */ Tcl_Obj *const objv[], /* An array of pointers to objects that are * the words that make up the command. */ int flags, /* Collection of OR-ed bits that control the * evaluation of the script. Only * TCL_EVAL_GLOBAL, TCL_EVAL_INVOKE and * TCL_EVAL_NOERR are currently supported. */ Command *cmdPtr) /* NULL if the Command is to be looked up * here, otherwise the pointer to the * requested Command struct to be invoked. */ { Interp *iPtr = (Interp *) interp; /* * data[1] stores a marker for use by tailcalls; it will be set to 1 by * command redirectors (imports, alias, ensembles) so that tailcall skips * this callback (that marks the end of the target command) and goes back * to the end of the source command. */ if (iPtr->deferredCallbacks) { iPtr->deferredCallbacks = NULL; } else { TclNRAddCallback(interp, NRCommand, NULL, NULL, NULL, NULL); } iPtr->numLevels++; TclNRAddCallback(interp, EvalObjvCore, cmdPtr, INT2PTR(flags), INT2PTR(objc), objv); return TCL_OK; } static int EvalObjvCore( void *data[], Tcl_Interp *interp, TCL_UNUSED(int) /*result*/) { Command *cmdPtr = NULL, *preCmdPtr = (Command *)data[0]; int flags = PTR2INT(data[1]); Tcl_Size objc = PTR2INT(data[2]); Tcl_Obj **objv = (Tcl_Obj **)data[3]; Interp *iPtr = (Interp *) interp; Namespace *lookupNsPtr = NULL; int enterTracesDone = 0; /* * Push records for task to be done on return, in INVERSE order. First, if * needed, the exception handlers (as they should happen last). */ if (!(flags & TCL_EVAL_NOERR)) { TEOV_PushExceptionHandlers(interp, objc, objv, flags); } if (TCL_OK != TclInterpReady(interp)) { return TCL_ERROR; } if (objc == 0) { return TCL_OK; } if (TclLimitExceeded(iPtr->limit)) { /* generate error message if not yet already logged at this stage */ if (!(iPtr->flags & ERR_ALREADY_LOGGED)) { Tcl_LimitCheck(interp); } return TCL_ERROR; } /* * Configure evaluation context to match the requested flags. */ if (iPtr->lookupNsPtr) { /* * Capture the namespace we should do command name resolution in, as * instructed by our caller sneaking it in to us in a private interp * field. Clear that field right away so we cannot possibly have its * use leak where it should not. The sneaky message pass is done. * * Use of this mechanism overrides the TCL_EVAL_GLOBAL flag. * TODO: Is that a bug? */ lookupNsPtr = iPtr->lookupNsPtr; iPtr->lookupNsPtr = NULL; } else if (flags & TCL_EVAL_INVOKE) { lookupNsPtr = iPtr->globalNsPtr; } else { /* * TCL_EVAL_INVOKE was not set: clear rewrite rules */ TclResetRewriteEnsemble(interp, 1); if (flags & TCL_EVAL_GLOBAL) { TEOV_SwitchVarFrame(interp); lookupNsPtr = iPtr->globalNsPtr; } } /* * Lookup the Command to dispatch. */ reresolve: assert(cmdPtr == NULL); if (preCmdPtr) { /* * Caller gave it to us. */ if (!(preCmdPtr->flags & CMD_DEAD)) { /* * So long as it exists, use it. */ cmdPtr = preCmdPtr; } else if (flags & TCL_EVAL_NORESOLVE) { /* * When it's been deleted, and we're told not to attempt resolving * it ourselves, all we can do is raise an error. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "attempt to invoke a deleted command")); Tcl_SetErrorCode(interp, "TCL", "EVAL", "DELETEDCOMMAND", (char *)NULL); return TCL_ERROR; } } if (cmdPtr == NULL) { cmdPtr = TEOV_LookupCmdFromObj(interp, objv[0], lookupNsPtr); if (!cmdPtr) { return TEOV_NotFound(interp, objc, objv, lookupNsPtr); } } if (enterTracesDone || iPtr->tracePtr || (cmdPtr->flags & CMD_HAS_EXEC_TRACES)) { Tcl_Obj *commandPtr = TclGetSourceFromFrame( flags & TCL_EVAL_SOURCE_IN_FRAME ? iPtr->cmdFramePtr : NULL, objc, objv); Tcl_IncrRefCount(commandPtr); if (!enterTracesDone) { int code = TEOV_RunEnterTraces(interp, &cmdPtr, commandPtr, objc, objv); /* * Send any exception from enter traces back as an exception * raised by the traced command. * TODO: Is this a bug? Letting an execution trace BREAK or * CONTINUE or RETURN in the place of the traced command? Would * either converting all exceptions to TCL_ERROR, or just * swallowing them be better? (Swallowing them has the problem of * permanently hiding program errors.) */ if (code != TCL_OK) { Tcl_DecrRefCount(commandPtr); return code; } /* * If the enter traces made the resolved cmdPtr unusable, go back * and resolve again, but next time don't run enter traces again. */ if (cmdPtr == NULL) { enterTracesDone = 1; Tcl_DecrRefCount(commandPtr); goto reresolve; } } /* * Schedule leave traces. Raise the refCount on the resolved cmdPtr, * so that when it passes to the leave traces we know it's still * valid. */ cmdPtr->refCount++; TclNRAddCallback(interp, TEOV_RunLeaveTraces, INT2PTR(objc), commandPtr, cmdPtr, objv); } TclNRAddCallback(interp, Dispatch, cmdPtr->nreProc ? cmdPtr->nreProc : cmdPtr->objProc, cmdPtr->objClientData, INT2PTR(objc), objv); return TCL_OK; } static int Dispatch( void *data[], Tcl_Interp *interp, TCL_UNUSED(int) /*result*/) { Tcl_ObjCmdProc *objProc = (Tcl_ObjCmdProc *)data[0]; void *clientData = data[1]; Tcl_Size objc = PTR2INT(data[2]); Tcl_Obj **objv = (Tcl_Obj **)data[3]; Interp *iPtr = (Interp *) interp; #ifdef USE_DTRACE if (TCL_DTRACE_CMD_ARGS_ENABLED()) { const char *a[10]; Tcl_Size i = 0; while (i < 10) { a[i] = i < objc ? TclGetString(objv[i]) : NULL; i++; } TCL_DTRACE_CMD_ARGS(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]); } if (TCL_DTRACE_CMD_INFO_ENABLED() && iPtr->cmdFramePtr) { Tcl_Obj *info = TclInfoFrame(interp, iPtr->cmdFramePtr); const char *a[6]; Tcl_Size i[2]; TclDTraceInfo(info, a, i); TCL_DTRACE_CMD_INFO(a[0], a[1], a[2], a[3], i[0], i[1], a[4], a[5]); TclDecrRefCount(info); } if ((TCL_DTRACE_CMD_RETURN_ENABLED() || TCL_DTRACE_CMD_RESULT_ENABLED()) && objc) { TclNRAddCallback(interp, DTraceCmdReturn, objv[0], NULL, NULL, NULL); } if (TCL_DTRACE_CMD_ENTRY_ENABLED() && objc) { TCL_DTRACE_CMD_ENTRY(TclGetString(objv[0]), objc - 1, (Tcl_Obj **)(objv + 1)); } #endif /* USE_DTRACE */ iPtr->cmdCount++; return objProc(clientData, interp, objc, objv); } int TclNRRunCallbacks( Tcl_Interp *interp, int result, struct NRE_callback *rootPtr) /* All callbacks down to rootPtr not inclusive * are to be run. */ { while (TOP_CB(interp) != rootPtr) { NRE_callback *callbackPtr = TOP_CB(interp); Tcl_NRPostProc *procPtr = callbackPtr->procPtr; TOP_CB(interp) = callbackPtr->nextPtr; result = procPtr(callbackPtr->data, interp, result); TCLNR_FREE(interp, callbackPtr); } return result; } static int NRCommand( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; Tcl_Obj *listPtr; iPtr->numLevels--; /* * If there is a tailcall, schedule it next */ if (data[1] && (data[1] != INT2PTR(1))) { listPtr = (Tcl_Obj *)data[1]; data[1] = NULL; TclNRAddCallback(interp, TclNRTailcallEval, listPtr, NULL, NULL, NULL); } /* OPT ?? * Do not interrupt a series of cleanups with async or limit checks: * just check at the end? */ if (TclAsyncReady(iPtr)) { result = Tcl_AsyncInvoke(interp, result); } if ((result == TCL_OK) && TclCanceled(iPtr)) { result = Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG); } if (result == TCL_OK && TclLimitReady(iPtr->limit)) { result = Tcl_LimitCheck(interp); } return result; } /* *---------------------------------------------------------------------- * * TEOV_Exception - * TEOV_LookupCmdFromObj - * TEOV_RunEnterTraces - * TEOV_RunLeaveTraces - * TEOV_NotFound - * * These are helper functions for Tcl_EvalObjv. * *---------------------------------------------------------------------- */ static void TEOV_PushExceptionHandlers( Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags) { Interp *iPtr = (Interp *) interp; /* * If any error processing is necessary, push the appropriate records. * Note that we have to push them in the inverse order: first the one that * has to run last. */ if (!(flags & TCL_EVAL_INVOKE)) { /* * Error messages */ TclNRAddCallback(interp, TEOV_Error, INT2PTR(objc), objv, NULL, NULL); } if (iPtr->numLevels == 1) { /* * No CONTINUE or BREAK at level 0, manage RETURN */ TclNRAddCallback(interp, TEOV_Exception, INT2PTR(iPtr->evalFlags), NULL, NULL, NULL); } } static void TEOV_SwitchVarFrame( Tcl_Interp *interp) { Interp *iPtr = (Interp *) interp; /* * Change the varFrame to be the rootVarFrame, and push a record to * restore things at the end. */ TclNRAddCallback(interp, TEOV_RestoreVarFrame, iPtr->varFramePtr, NULL, NULL, NULL); iPtr->varFramePtr = iPtr->rootFramePtr; } static int TEOV_RestoreVarFrame( void *data[], Tcl_Interp *interp, int result) { ((Interp *) interp)->varFramePtr = (CallFrame *)data[0]; return result; } static int TEOV_Exception( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; int allowExceptions = (PTR2INT(data[0]) & TCL_ALLOW_EXCEPTIONS); if (result != TCL_OK) { if (result == TCL_RETURN) { result = TclUpdateReturnInfo(iPtr); } if ((result != TCL_OK) && (result != TCL_ERROR) && !allowExceptions) { ProcessUnexpectedResult(interp, result); result = TCL_ERROR; } } /* * We are returning to level 0, so should process TclResetCancellation. As * numLevels has not *yet* been decreased, do not call it: do the thing * here directly. */ TclUnsetCancelFlags(iPtr); return result; } static int TEOV_Error( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; Tcl_Obj *listPtr; const char *cmdString; Tcl_Size cmdLen; Tcl_Size objc = PTR2INT(data[0]); Tcl_Obj **objv = (Tcl_Obj **)data[1]; if ((result == TCL_ERROR) && !(iPtr->flags & ERR_ALREADY_LOGGED)) { /* * If there was an error, a command string will be needed for the * error log: get it out of the itemPtr. The details depend on the * type. */ listPtr = Tcl_NewListObj(objc, objv); cmdString = TclGetStringFromObj(listPtr, &cmdLen); Tcl_LogCommandInfo(interp, cmdString, cmdString, cmdLen); Tcl_DecrRefCount(listPtr); } iPtr->flags &= ~ERR_ALREADY_LOGGED; return result; } static int TEOV_NotFound( Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], Namespace *lookupNsPtr) { Command * cmdPtr; Interp *iPtr = (Interp *) interp; Tcl_Size i, newObjc, handlerObjc; Tcl_Obj **newObjv, **handlerObjv; CallFrame *varFramePtr = iPtr->varFramePtr; Namespace *currNsPtr = NULL;/* Used to check for and invoke any registered * unknown command handler for the current * namespace (TIP 181). */ Namespace *savedNsPtr = NULL; currNsPtr = varFramePtr->nsPtr; if ((currNsPtr == NULL) || (currNsPtr->unknownHandlerPtr == NULL)) { currNsPtr = iPtr->globalNsPtr; if (currNsPtr == NULL) { Tcl_Panic("TEOV_NotFound: NULL global namespace pointer"); } } /* * Check to see if the resolution namespace has lost its unknown handler. * If so, reset it to "::unknown". */ if (currNsPtr->unknownHandlerPtr == NULL) { TclNewLiteralStringObj(currNsPtr->unknownHandlerPtr, "::unknown"); Tcl_IncrRefCount(currNsPtr->unknownHandlerPtr); } /* * Get the list of words for the unknown handler and allocate enough space * to hold both the handler prefix and all words of the command invocation * itself. */ TclListObjGetElements(NULL, currNsPtr->unknownHandlerPtr, &handlerObjc, &handlerObjv); newObjc = objc + handlerObjc; newObjv = (Tcl_Obj **)TclStackAlloc(interp, sizeof(Tcl_Obj *) * newObjc); /* * Copy command prefix from unknown handler and add on the real command's * full argument list. Note that we only use memcpy() once because we have * to increment the reference count of all the handler arguments anyway. */ for (i = 0; i < handlerObjc; ++i) { newObjv[i] = handlerObjv[i]; Tcl_IncrRefCount(newObjv[i]); } memcpy(newObjv + handlerObjc, objv, sizeof(Tcl_Obj *) * objc); /* * Look up and invoke the handler (by recursive call to this function). If * there is no handler at all, instead of doing the recursive call we just * generate a generic error message; it would be an infinite-recursion * nightmare otherwise. * * In this case we worry a bit less about recursion for now, and call the * "blocking" interface. */ cmdPtr = TEOV_LookupCmdFromObj(interp, newObjv[0], lookupNsPtr); if (cmdPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid command name \"%s\"", TclGetString(objv[0]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COMMAND", TclGetString(objv[0]), (char *)NULL); /* * Release any resources we locked and allocated during the handler * call. */ for (i = 0; i < handlerObjc; ++i) { Tcl_DecrRefCount(newObjv[i]); } TclStackFree(interp, newObjv); return TCL_ERROR; } if (lookupNsPtr) { savedNsPtr = varFramePtr->nsPtr; varFramePtr->nsPtr = lookupNsPtr; } TclSkipTailcall(interp); TclNRAddCallback(interp, TEOV_NotFoundCallback, INT2PTR(handlerObjc), newObjv, savedNsPtr, NULL); return TclNREvalObjv(interp, newObjc, newObjv, TCL_EVAL_NOERR, NULL); } static int TEOV_NotFoundCallback( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; Tcl_Size objc = PTR2INT(data[0]); Tcl_Obj **objv = (Tcl_Obj **)data[1]; Namespace *savedNsPtr = (Namespace *)data[2]; Tcl_Size i; if (savedNsPtr) { iPtr->varFramePtr->nsPtr = savedNsPtr; } /* * Release any resources we locked and allocated during the handler call. */ for (i = 0; i < objc; ++i) { Tcl_DecrRefCount(objv[i]); } TclStackFree(interp, objv); return result; } static int TEOV_RunEnterTraces( Tcl_Interp *interp, Command **cmdPtrPtr, Tcl_Obj *commandPtr, Tcl_Size objc, Tcl_Obj *const objv[]) { Interp *iPtr = (Interp *) interp; Command *cmdPtr = *cmdPtrPtr; Tcl_Size length, newEpoch, cmdEpoch = cmdPtr->cmdEpoch; int traceCode = TCL_OK; const char *command = TclGetStringFromObj(commandPtr, &length); /* * Call trace functions. * Execute any command or execution traces. Note that we bump up the * command's reference count for the duration of the calling of the * traces so that the structure doesn't go away underneath our feet. */ cmdPtr->refCount++; if (iPtr->tracePtr) { traceCode = TclCheckInterpTraces(interp, command, length, cmdPtr, TCL_OK, TCL_TRACE_ENTER_EXEC, objc, objv); } if ((cmdPtr->flags & CMD_HAS_EXEC_TRACES) && (traceCode == TCL_OK)) { traceCode = TclCheckExecutionTraces(interp, command, length, cmdPtr, TCL_OK, TCL_TRACE_ENTER_EXEC, objc, objv); } newEpoch = cmdPtr->cmdEpoch; TclCleanupCommandMacro(cmdPtr); if (traceCode != TCL_OK) { if (traceCode == TCL_ERROR) { Tcl_Obj *info; TclNewLiteralStringObj(info, "\n (enter trace on \""); Tcl_AppendLimitedToObj(info, command, length, 55, "..."); Tcl_AppendToObj(info, "\")", 2); Tcl_AppendObjToErrorInfo(interp, info); iPtr->flags |= ERR_ALREADY_LOGGED; } return traceCode; } if (cmdEpoch != newEpoch) { *cmdPtrPtr = NULL; } return TCL_OK; } static int TEOV_RunLeaveTraces( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; int traceCode = TCL_OK; Tcl_Size objc = PTR2INT(data[0]); Tcl_Obj *commandPtr = (Tcl_Obj *)data[1]; Command *cmdPtr = (Command *)data[2]; Tcl_Obj **objv = (Tcl_Obj **)data[3]; Tcl_Size length; const char *command = TclGetStringFromObj(commandPtr, &length); if (!(cmdPtr->flags & CMD_DYING)) { if (cmdPtr->flags & CMD_HAS_EXEC_TRACES) { traceCode = TclCheckExecutionTraces(interp, command, length, cmdPtr, result, TCL_TRACE_LEAVE_EXEC, objc, objv); } if (iPtr->tracePtr != NULL && traceCode == TCL_OK) { traceCode = TclCheckInterpTraces(interp, command, length, cmdPtr, result, TCL_TRACE_LEAVE_EXEC, objc, objv); } } /* * As cmdPtr is set, TclNRRunCallbacks is about to reduce the numlevels. * Prevent that by resetting the cmdPtr field and dealing right here with * cmdPtr->refCount. */ TclCleanupCommandMacro(cmdPtr); if (traceCode != TCL_OK) { if (traceCode == TCL_ERROR) { Tcl_Obj *info; TclNewLiteralStringObj(info, "\n (leave trace on \""); Tcl_AppendLimitedToObj(info, command, length, 55, "..."); Tcl_AppendToObj(info, "\")", 2); Tcl_AppendObjToErrorInfo(interp, info); iPtr->flags |= ERR_ALREADY_LOGGED; } result = traceCode; } Tcl_DecrRefCount(commandPtr); return result; } static inline Command * TEOV_LookupCmdFromObj( Tcl_Interp *interp, Tcl_Obj *namePtr, Namespace *lookupNsPtr) { Interp *iPtr = (Interp *) interp; Command *cmdPtr; Namespace *savedNsPtr = iPtr->varFramePtr->nsPtr; if (lookupNsPtr) { iPtr->varFramePtr->nsPtr = lookupNsPtr; } cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, namePtr); iPtr->varFramePtr->nsPtr = savedNsPtr; return cmdPtr; } /* *---------------------------------------------------------------------- * * Tcl_EvalTokensStandard -- * * Given an array of tokens parsed from a Tcl command (e.g., the tokens * that make up a word or the index for an array variable) this function * evaluates the tokens and concatenates their values to form a single * result value. * * Results: * The return value is a standard Tcl completion code such as TCL_OK or * TCL_ERROR. A result or error message is left in interp's result. * * Side effects: * Depends on the array of tokens being evaled. * *---------------------------------------------------------------------- */ int Tcl_EvalTokensStandard( Tcl_Interp *interp, /* Interpreter in which to lookup variables, * execute nested commands, and report * errors. */ Tcl_Token *tokenPtr, /* Pointer to first in an array of tokens to * evaluate and concatenate. */ Tcl_Size count) /* Number of tokens to consider at tokenPtr. * Must be at least 1. */ { return TclSubstTokens(interp, tokenPtr, count, /* numLeftPtr */ NULL, 1, NULL, NULL); } /* *---------------------------------------------------------------------- * * Tcl_EvalEx, TclEvalEx -- * * This function evaluates a Tcl script without using the compiler or * byte-code interpreter. It just parses the script, creates values for * each word of each command, then calls EvalObjv to execute each * command. * * Results: * The return value is a standard Tcl completion code such as TCL_OK or * TCL_ERROR. A result or error message is left in interp's result. * * Side effects: * Depends on the script. * * TIP #280 : Keep public API, internally extended API. *---------------------------------------------------------------------- */ int Tcl_EvalEx( Tcl_Interp *interp, /* Interpreter in which to evaluate the * script. Also used for error reporting. */ const char *script, /* First character of script to evaluate. */ Tcl_Size numBytes, /* Number of bytes in script. If -1, the * script consists of all bytes up to the * first null character. */ int flags) /* Collection of OR-ed bits that control the * evaluation of the script. Only * TCL_EVAL_GLOBAL is currently supported. */ { return TclEvalEx(interp, script, numBytes, flags, 1, NULL, script); } int TclEvalEx( Tcl_Interp *interp, /* Interpreter in which to evaluate the * script. Also used for error reporting. */ const char *script, /* First character of script to evaluate. */ Tcl_Size numBytes, /* Number of bytes in script. If -1, the * script consists of all bytes up to the * first NUL character. */ int flags, /* Collection of OR-ed bits that control the * evaluation of the script. Only * TCL_EVAL_GLOBAL is currently supported. */ Tcl_Size line, /* The line the script starts on. */ Tcl_Size *clNextOuter, /* Information about an outer context for */ const char *outerScript) /* continuation line data. This is set only in * TclSubstTokens(), to properly handle * [...]-nested commands. The 'outerScript' * refers to the most-outer script containing * the embedded command, which is referred to * by 'script'. The 'clNextOuter' refers to * the current entry in the table of * continuation lines in this "main script", * and the character offsets are relative to * the 'outerScript' as well. * * If outerScript == script, then this call is * for the outer-most script/command. See * Tcl_EvalEx() and TclEvalObjEx() for places * generating arguments for which this is * true. */ { Interp *iPtr = (Interp *) interp; const char *p, *next; const int minObjs = 20; Tcl_Obj **objv, **objvSpace; int *expand; Tcl_Size *lines, *lineSpace; Tcl_Token *tokenPtr; int expandRequested, code = TCL_OK; Tcl_Size bytesLeft, commandLength; CallFrame *savedVarFramePtr;/* Saves old copy of iPtr->varFramePtr in case * TCL_EVAL_GLOBAL was set. */ int allowExceptions = (iPtr->evalFlags & TCL_ALLOW_EXCEPTIONS); int gotParse = 0; Tcl_Size i, objectsUsed = 0; /* These variables keep track of how much * state has been allocated while evaluating * the script, so that it can be freed * properly if an error occurs. */ Tcl_Parse *parsePtr = (Tcl_Parse *)TclStackAlloc(interp, sizeof(Tcl_Parse)); CmdFrame *eeFramePtr = (CmdFrame *)TclStackAlloc(interp, sizeof(CmdFrame)); Tcl_Obj **stackObjArray = (Tcl_Obj **)TclStackAlloc(interp, minObjs * sizeof(Tcl_Obj *)); int *expandStack = (int *)TclStackAlloc(interp, minObjs * sizeof(int)); Tcl_Size *linesStack = (Tcl_Size *)TclStackAlloc(interp, minObjs * sizeof(Tcl_Size)); /* TIP #280 Structures for tracking of command * locations. */ Tcl_Size *clNext = NULL; /* Pointer for the tracking of invisible * continuation lines. Initialized only if the * caller gave us a table of locations to * track, via scriptCLLocPtr. It always refers * to the table entry holding the location of * the next invisible continuation line to * look for, while parsing the script. */ if (iPtr->scriptCLLocPtr) { if (clNextOuter) { clNext = clNextOuter; } else { clNext = &iPtr->scriptCLLocPtr->loc[0]; } } if (numBytes < 0) { numBytes = strlen(script); } Tcl_ResetResult(interp); savedVarFramePtr = iPtr->varFramePtr; if (flags & TCL_EVAL_GLOBAL) { iPtr->varFramePtr = iPtr->rootFramePtr; } /* * Each iteration through the following loop parses the next command from * the script and then executes it. */ objv = objvSpace = stackObjArray; lines = lineSpace = linesStack; expand = expandStack; p = script; bytesLeft = numBytes; /* * TIP #280 Initialize tracking. Do not push on the frame stack yet. * * We open a new context, either for a sourced script, or 'eval'. * For sourced files we always have a path object, even if nothing was * specified in the interp itself. That makes code using it simpler as * NULL checks can be left out. Sourced file without path in the * 'scriptFile' is possible during Tcl initialization. */ eeFramePtr->level = iPtr->cmdFramePtr ? iPtr->cmdFramePtr->level + 1 : 1; eeFramePtr->framePtr = iPtr->framePtr; eeFramePtr->nextPtr = iPtr->cmdFramePtr; eeFramePtr->nline = 0; eeFramePtr->line = NULL; eeFramePtr->cmdObj = NULL; iPtr->cmdFramePtr = eeFramePtr; if (iPtr->evalFlags & TCL_EVAL_FILE) { /* * Set up for a sourced file. */ eeFramePtr->type = TCL_LOCATION_SOURCE; if (iPtr->scriptFile) { /* * Normalization here, to have the correct pwd. Should have * negligible impact on performance, as the norm should have been * done already by the 'source' invoking us, and it caches the * result. */ Tcl_Obj *norm = Tcl_FSGetNormalizedPath(interp, iPtr->scriptFile); if (norm == NULL) { /* * Error message in the interp result. */ code = TCL_ERROR; goto error; } eeFramePtr->data.eval.path = norm; } else { TclNewLiteralStringObj(eeFramePtr->data.eval.path, ""); } Tcl_IncrRefCount(eeFramePtr->data.eval.path); } else { /* * Set up for plain eval. */ eeFramePtr->type = TCL_LOCATION_EVAL; eeFramePtr->data.eval.path = NULL; } iPtr->evalFlags = 0; do { if (Tcl_ParseCommand(interp, p, bytesLeft, 0, parsePtr) != TCL_OK) { code = TCL_ERROR; Tcl_LogCommandInfo(interp, script, parsePtr->commandStart, parsePtr->term + 1 - parsePtr->commandStart); goto posterror; } /* * TIP #280 Track lines. The parser may have skipped text till it * found the command we are now at. We have to count the lines in this * block, and do not forget invisible continuation lines. */ TclAdvanceLines(&line, p, parsePtr->commandStart); TclAdvanceContinuations(&line, &clNext, parsePtr->commandStart - outerScript); gotParse = 1; if (parsePtr->numWords > 0) { /* * TIP #280. Track lines within the words of the current * command. We use a separate pointer into the table of * continuation line locations to not lose our position for the * per-command parsing. */ Tcl_Size wordLine = line; const char *wordStart = parsePtr->commandStart; Tcl_Size *wordCLNext = clNext; Tcl_Size objectsNeeded = 0; Tcl_Size numWords = parsePtr->numWords; /* * Generate an array of objects for the words of the command. */ if (numWords > minObjs) { expand = (int *)Tcl_Alloc(numWords * sizeof(int)); objvSpace = (Tcl_Obj **) Tcl_Alloc(numWords * sizeof(Tcl_Obj *)); lineSpace = (Tcl_Size *) Tcl_Alloc(numWords * sizeof(Tcl_Size)); } expandRequested = 0; objv = objvSpace; lines = lineSpace; iPtr->cmdFramePtr = eeFramePtr->nextPtr; for (objectsUsed = 0, tokenPtr = parsePtr->tokenPtr; objectsUsed < numWords; objectsUsed++, tokenPtr += tokenPtr->numComponents + 1) { Tcl_Size additionalObjsCount; /* * TIP #280. Track lines to current word. Save the information * on a per-word basis, signaling dynamic words as needed. * Make the information available to the recursively called * evaluator as well, including the type of context (source * vs. eval). */ TclAdvanceLines(&wordLine, wordStart, tokenPtr->start); TclAdvanceContinuations(&wordLine, &wordCLNext, tokenPtr->start - outerScript); wordStart = tokenPtr->start; lines[objectsUsed] = TclWordKnownAtCompileTime(tokenPtr, NULL) ? wordLine : -1; if (eeFramePtr->type == TCL_LOCATION_SOURCE) { iPtr->evalFlags |= TCL_EVAL_FILE; } code = TclSubstTokens(interp, tokenPtr + 1, tokenPtr->numComponents, NULL, wordLine, wordCLNext, outerScript); iPtr->evalFlags = 0; if (code != TCL_OK) { break; } objv[objectsUsed] = Tcl_GetObjResult(interp); Tcl_IncrRefCount(objv[objectsUsed]); if (tokenPtr->type == TCL_TOKEN_EXPAND_WORD) { Tcl_Size numElements; code = TclListObjLength(interp, objv[objectsUsed], &numElements); if (code == TCL_ERROR) { /* * Attempt to expand a non-list. */ Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (expanding word %" TCL_SIZE_MODIFIER "d)", objectsUsed)); Tcl_DecrRefCount(objv[objectsUsed]); break; } expandRequested = 1; expand[objectsUsed] = 1; additionalObjsCount = (numElements ? numElements : 1); } else { expand[objectsUsed] = 0; additionalObjsCount = 1; } /* Currently max command words in INT_MAX */ if (additionalObjsCount > INT_MAX || objectsNeeded > (INT_MAX - additionalObjsCount)) { code = TclCommandWordLimitError(interp, -1); Tcl_DecrRefCount(objv[objectsUsed]); break; } objectsNeeded += additionalObjsCount; if (wordCLNext) { TclContinuationsEnterDerived(objv[objectsUsed], wordStart - outerScript, wordCLNext); } } /* for loop */ iPtr->cmdFramePtr = eeFramePtr; if (code != TCL_OK) { goto error; } if (expandRequested) { /* * Some word expansion was requested. Check for objv resize. */ Tcl_Obj **copy = objvSpace; Tcl_Size *lcopy = lineSpace; Tcl_Size wordIdx = numWords; Tcl_Size objIdx = objectsNeeded - 1; if ((numWords > minObjs) || (objectsNeeded > minObjs)) { objv = objvSpace = (Tcl_Obj **)Tcl_Alloc(objectsNeeded * sizeof(Tcl_Obj *)); lines = lineSpace = (Tcl_Size *)Tcl_Alloc(objectsNeeded * sizeof(Tcl_Size)); } objectsUsed = 0; while (wordIdx--) { if (expand[wordIdx]) { Tcl_Size numElements; Tcl_Obj **elements, *temp = copy[wordIdx]; TclListObjGetElements(NULL, temp, &numElements, &elements); objectsUsed += numElements; while (numElements--) { lines[objIdx] = -1; objv[objIdx--] = elements[numElements]; Tcl_IncrRefCount(elements[numElements]); } Tcl_DecrRefCount(temp); } else { lines[objIdx] = lcopy[wordIdx]; objv[objIdx--] = copy[wordIdx]; objectsUsed++; } } objv += objIdx + 1; if (copy != stackObjArray) { Tcl_Free(copy); } if (lcopy != linesStack) { Tcl_Free(lcopy); } } /* * Execute the command and free the objects for its words. * * TIP #280: Remember the command itself for 'info frame'. We * shorten the visible command by one char to exclude the * termination character, if necessary. Here is where we put our * frame on the stack of frames too. _After_ the nested commands * have been executed. */ eeFramePtr->cmd = parsePtr->commandStart; eeFramePtr->len = parsePtr->commandSize; if (parsePtr->term == parsePtr->commandStart + parsePtr->commandSize - 1) { eeFramePtr->len--; } eeFramePtr->nline = objectsUsed; eeFramePtr->line = lines; TclArgumentEnter(interp, objv, objectsUsed, eeFramePtr); code = Tcl_EvalObjv(interp, objectsUsed, objv, TCL_EVAL_NOERR | TCL_EVAL_SOURCE_IN_FRAME); TclArgumentRelease(interp, objv, objectsUsed); eeFramePtr->line = NULL; eeFramePtr->nline = 0; if (eeFramePtr->cmdObj) { Tcl_DecrRefCount(eeFramePtr->cmdObj); eeFramePtr->cmdObj = NULL; } if (code != TCL_OK) { goto error; } for (i = 0; i < objectsUsed; i++) { Tcl_DecrRefCount(objv[i]); } objectsUsed = 0; if (objvSpace != stackObjArray) { Tcl_Free(objvSpace); objvSpace = stackObjArray; Tcl_Free(lineSpace); lineSpace = linesStack; } /* * Free expand separately since objvSpace could have been * reallocated above. */ if (expand != expandStack) { Tcl_Free(expand); expand = expandStack; } } /* * Advance to the next command in the script. * * TIP #280 Track Lines. Now we track how many lines were in the * executed command. */ next = parsePtr->commandStart + parsePtr->commandSize; bytesLeft -= next - p; p = next; TclAdvanceLines(&line, parsePtr->commandStart, p); Tcl_FreeParse(parsePtr); gotParse = 0; } while (bytesLeft > 0); iPtr->varFramePtr = savedVarFramePtr; code = TCL_OK; goto cleanup_return; error: /* * Generate and log various pieces of error information. */ if (iPtr->numLevels == 0) { if (code == TCL_RETURN) { code = TclUpdateReturnInfo(iPtr); } if ((code != TCL_OK) && (code != TCL_ERROR) && !allowExceptions) { ProcessUnexpectedResult(interp, code); code = TCL_ERROR; } } if ((code == TCL_ERROR) && !(iPtr->flags & ERR_ALREADY_LOGGED)) { commandLength = parsePtr->commandSize; if (parsePtr->term == parsePtr->commandStart + commandLength - 1) { /* * The terminator character (such as ; or ]) of the command where * the error occurred is the last character in the parsed command. * Reduce the length by one so that the error message doesn't * include the terminator character. */ commandLength -= 1; } Tcl_LogCommandInfo(interp, script, parsePtr->commandStart, commandLength); } posterror: iPtr->flags &= ~ERR_ALREADY_LOGGED; /* * Then free resources that had been allocated to the command. */ for (i = 0; i < objectsUsed; i++) { Tcl_DecrRefCount(objv[i]); } if (gotParse) { Tcl_FreeParse(parsePtr); } if (objvSpace != stackObjArray) { Tcl_Free(objvSpace); Tcl_Free(lineSpace); } if (expand != expandStack) { Tcl_Free(expand); } iPtr->varFramePtr = savedVarFramePtr; cleanup_return: /* * TIP #280. Release the local CmdFrame, and its contents. */ iPtr->cmdFramePtr = iPtr->cmdFramePtr->nextPtr; if (eeFramePtr->type == TCL_LOCATION_SOURCE) { Tcl_DecrRefCount(eeFramePtr->data.eval.path); } TclStackFree(interp, linesStack); TclStackFree(interp, expandStack); TclStackFree(interp, stackObjArray); TclStackFree(interp, eeFramePtr); TclStackFree(interp, parsePtr); return code; } /* *---------------------------------------------------------------------- * * TclAdvanceLines -- * * This function is a helper which counts the number of lines in a block * of text and advances an external counter. * * Results: * None. * * Side effects: * The specified counter is advanced per the number of lines found. * * TIP #280 *---------------------------------------------------------------------- */ void TclAdvanceLines( Tcl_Size *line, const char *start, const char *end) { const char *p; for (p = start; p < end; p++) { if (*p == '\n') { (*line)++; } } } /* *---------------------------------------------------------------------- * * TclAdvanceContinuations -- * * This procedure is a helper which counts the number of continuation * lines (CL) in a block of text using a table of CL locations and * advances an external counter, and the pointer into the table. * * Results: * None. * * Side effects: * The specified counter is advanced per the number of continuation lines * found. * * TIP #280 *---------------------------------------------------------------------- */ void TclAdvanceContinuations( Tcl_Size *line, Tcl_Size **clNextPtrPtr, int loc) { /* * Track the invisible continuation lines embedded in a script, if any. * Here they are just spaces (already). They were removed by * TclSubstTokens via TclParseBackslash. * * *clNextPtrPtr <=> We have continuation lines to track. * **clNextPtrPtr >= 0 <=> We are not beyond the last possible location. * loc >= **clNextPtrPtr <=> We stepped beyond the current cont. line. */ while (*clNextPtrPtr && (**clNextPtrPtr >= 0) && (loc >= **clNextPtrPtr)) { /* * We just stepped over an invisible continuation line. Adjust the * line counter and step to the table entry holding the location of * the next continuation line to track. */ (*line)++; (*clNextPtrPtr)++; } } /* *---------------------------------------------------------------------- * Note: The whole data structure access for argument location tracking is * hidden behind these three functions. The only parts open are the lineLAPtr * field in the Interp structure. The CFWord definition is internal to here. * Should make it easier to redo the data structures if we find something more * space/time efficient. */ /* *---------------------------------------------------------------------- * * TclArgumentEnter -- * * This procedure is a helper for the TIP #280 uplevel extension. It * enters location references for the arguments of a command to be * invoked. Only the first entry has the actual data, further entries * simply count the usage up. * * Results: * None. * * Side effects: * May allocate memory. * * TIP #280 *---------------------------------------------------------------------- */ void TclArgumentEnter( Tcl_Interp *interp, Tcl_Obj **objv, Tcl_Size objc, CmdFrame *cfPtr) { Interp *iPtr = (Interp *) interp; int isNew; Tcl_Size i; Tcl_HashEntry *hPtr; CFWord *cfwPtr; for (i = 1; i < objc; i++) { /* * Ignore argument words without line information (= dynamic). If they * are variables they may have location information associated with * that, either through globally recorded 'set' invocations, or * literals in bytecode. Either way there is no need to record * something here. */ if (cfPtr->line[i] < 0) { continue; } hPtr = Tcl_CreateHashEntry(iPtr->lineLAPtr, objv[i], &isNew); if (isNew) { /* * The word is not on the stack yet, remember the current location * and initialize references. */ cfwPtr = (CFWord *)Tcl_Alloc(sizeof(CFWord)); cfwPtr->framePtr = cfPtr; cfwPtr->word = i; cfwPtr->refCount = 1; Tcl_SetHashValue(hPtr, cfwPtr); } else { /* * The word is already on the stack, its current location is not * relevant. Just remember the reference to prevent early removal. */ cfwPtr = (CFWord *)Tcl_GetHashValue(hPtr); cfwPtr->refCount++; } } } /* *---------------------------------------------------------------------- * * TclArgumentRelease -- * * This procedure is a helper for the TIP #280 uplevel extension. It * removes the location references for the arguments of a command just * done. Usage is counted down, the data is removed only when no user is * left over. * * Results: * None. * * Side effects: * May release memory. * * TIP #280 *---------------------------------------------------------------------- */ void TclArgumentRelease( Tcl_Interp *interp, Tcl_Obj **objv, Tcl_Size objc) { Interp *iPtr = (Interp *) interp; Tcl_Size i; for (i = 1; i < objc; i++) { CFWord *cfwPtr; Tcl_HashEntry *hPtr = Tcl_FindHashEntry(iPtr->lineLAPtr, objv[i]); if (!hPtr) { continue; } cfwPtr = (CFWord *)Tcl_GetHashValue(hPtr); if (cfwPtr->refCount-- > 1) { continue; } Tcl_Free(cfwPtr); Tcl_DeleteHashEntry(hPtr); } } /* *---------------------------------------------------------------------- * * TclArgumentBCEnter -- * * This procedure is a helper for the TIP #280 uplevel extension. It * enters location references for the literal arguments of commands in * bytecode about to be invoked. Only the first entry has the actual * data, further entries simply count the usage up. * * Results: * None. * * Side effects: * May allocate memory. * * TIP #280 *---------------------------------------------------------------------- */ void TclArgumentBCEnter( Tcl_Interp *interp, Tcl_Obj *objv[], Tcl_Size objc, void *codePtr, CmdFrame *cfPtr, Tcl_Size cmd, Tcl_Size pc) { ExtCmdLoc *eclPtr; Tcl_Size word; ECL *ePtr; CFWordBC *lastPtr = NULL; Interp *iPtr = (Interp *) interp; Tcl_HashEntry *hePtr = Tcl_FindHashEntry(iPtr->lineBCPtr, codePtr); if (!hePtr) { return; } eclPtr = (ExtCmdLoc *)Tcl_GetHashValue(hePtr); ePtr = &eclPtr->loc[cmd]; /* * ePtr->nline is the number of words originally parsed. * * objc is the number of elements getting invoked. * * If they are not the same, we arrived here by compiling an * ensemble dispatch. Ensemble subcommands that lead to script * evaluation are not supposed to get compiled, because a command * such as [info level] in the script can expose some of the dispatch * shenanigans. This means that we don't have to tend to the * housekeeping, and can escape now. */ if (ePtr->nline != objc) { return; } /* * Having disposed of the ensemble cases, we can state... * A few truths ... * (1) ePtr->nline == objc * (2) (ePtr->line[word] < 0) => !literal, for all words * (3) (word == 0) => !literal * * Item (2) is why we can use objv to get the literals, and do not * have to save them at compile time. */ for (word = 1; word < objc; word++) { if (ePtr->line[word] >= 0) { int isNew; Tcl_HashEntry *hPtr = Tcl_CreateHashEntry(iPtr->lineLABCPtr, objv[word], &isNew); CFWordBC *cfwPtr = (CFWordBC *)Tcl_Alloc(sizeof(CFWordBC)); cfwPtr->framePtr = cfPtr; cfwPtr->obj = objv[word]; cfwPtr->pc = pc; cfwPtr->word = word; cfwPtr->nextPtr = lastPtr; lastPtr = cfwPtr; if (isNew) { /* * The word is not on the stack yet, remember the current * location and initialize references. */ cfwPtr->prevPtr = NULL; } else { /* * The object is already on the stack, however it may have * a different location now (literal sharing may map * multiple location to a single Tcl_Obj*. Save the old * information in the new structure. */ cfwPtr->prevPtr = (CFWordBC *)Tcl_GetHashValue(hPtr); } Tcl_SetHashValue(hPtr, cfwPtr); } } /* for */ cfPtr->litarg = lastPtr; } /* *---------------------------------------------------------------------- * * TclArgumentBCRelease -- * * This procedure is a helper for the TIP #280 uplevel extension. It * removes the location references for the literal arguments of commands * in bytecode just done. Usage is counted down, the data is removed only * when no user is left over. * * Results: * None. * * Side effects: * May release memory. * * TIP #280 *---------------------------------------------------------------------- */ void TclArgumentBCRelease( Tcl_Interp *interp, CmdFrame *cfPtr) { Interp *iPtr = (Interp *) interp; CFWordBC *cfwPtr = (CFWordBC *) cfPtr->litarg; while (cfwPtr) { CFWordBC *nextPtr = cfwPtr->nextPtr; Tcl_HashEntry *hPtr = Tcl_FindHashEntry(iPtr->lineLABCPtr, cfwPtr->obj); CFWordBC *xPtr = (CFWordBC *)Tcl_GetHashValue(hPtr); if (xPtr != cfwPtr) { Tcl_Panic("TclArgumentBC Enter/Release Mismatch"); } if (cfwPtr->prevPtr) { Tcl_SetHashValue(hPtr, cfwPtr->prevPtr); } else { Tcl_DeleteHashEntry(hPtr); } Tcl_Free(cfwPtr); cfwPtr = nextPtr; } cfPtr->litarg = NULL; } /* *---------------------------------------------------------------------- * * TclArgumentGet -- * * This procedure is a helper for the TIP #280 uplevel extension. It * finds the location references for a Tcl_Obj, if any. * * Results: * None. * * Side effects: * Writes found location information into the result arguments. * * TIP #280 *---------------------------------------------------------------------- */ void TclArgumentGet( Tcl_Interp *interp, Tcl_Obj *obj, CmdFrame **cfPtrPtr, int *wordPtr) { Interp *iPtr = (Interp *) interp; Tcl_HashEntry *hPtr; CmdFrame *framePtr; /* * An object which either has no string rep or else is a canonical list is * guaranteed to have been generated dynamically: bail out, this cannot * have a usable absolute location. _Do not touch_ the information the set * up by the caller. It knows better than us. */ if (!TclHasStringRep(obj) || TclListObjIsCanonical(obj)) { return; } /* * First look for location information recorded in the argument * stack. That is nearest. */ hPtr = Tcl_FindHashEntry(iPtr->lineLAPtr, obj); if (hPtr) { CFWord *cfwPtr = (CFWord *)Tcl_GetHashValue(hPtr); *wordPtr = cfwPtr->word; *cfPtrPtr = cfwPtr->framePtr; return; } /* * Check if the Tcl_Obj has location information as a bytecode literal, in * that stack. */ hPtr = Tcl_FindHashEntry(iPtr->lineLABCPtr, obj); if (hPtr) { CFWordBC *cfwPtr = (CFWordBC *)Tcl_GetHashValue(hPtr); framePtr = cfwPtr->framePtr; framePtr->data.tebc.pc = (char *) (((ByteCode *) framePtr->data.tebc.codePtr)->codeStart + cfwPtr->pc); *cfPtrPtr = cfwPtr->framePtr; *wordPtr = cfwPtr->word; return; } } /* *---------------------------------------------------------------------- * * Tcl_EvalObjEx, TclEvalObjEx -- * * Execute Tcl commands stored in a Tcl object. These commands are * compiled into bytecodes if necessary, unless TCL_EVAL_DIRECT is * specified. * * If the flag TCL_EVAL_DIRECT is passed in, the value of invoker * must be NULL. Support for non-NULL invokers in that mode has * been removed since it was unused and untested. Failure to * follow this limitation will lead to an assertion panic. * * Results: * The return value is one of the return codes defined in tcl.h (such as * TCL_OK), and the interpreter's result contains a value to supplement * the return code. * * Side effects: * The object is converted, if necessary, to a ByteCode object that holds * the bytecode instructions for the commands. Executing the commands * will almost certainly have side effects that depend on those commands. * * TIP #280 : Keep public API, internally extended API. *---------------------------------------------------------------------- */ int Tcl_EvalObjEx( Tcl_Interp *interp, /* Token for command interpreter (returned by * a previous call to Tcl_CreateInterp). */ Tcl_Obj *objPtr, /* Pointer to object containing commands to * execute. */ int flags) /* Collection of OR-ed bits that control the * evaluation of the script. Supported values * are TCL_EVAL_GLOBAL and TCL_EVAL_DIRECT. */ { return TclEvalObjEx(interp, objPtr, flags, NULL, 0); } int TclEvalObjEx( Tcl_Interp *interp, /* Token for command interpreter (returned by * a previous call to Tcl_CreateInterp). */ Tcl_Obj *objPtr, /* Pointer to object containing commands to * execute. */ int flags, /* Collection of OR-ed bits that control the * evaluation of the script. Supported values * are TCL_EVAL_GLOBAL and TCL_EVAL_DIRECT. */ const CmdFrame *invoker, /* Frame of the command doing the eval. */ int word) /* Index of the word which is in objPtr. */ { int result = TCL_OK; NRE_callback *rootPtr = TOP_CB(interp); result = TclNREvalObjEx(interp, objPtr, flags, invoker, word); return TclNRRunCallbacks(interp, result, rootPtr); } int TclNREvalObjEx( Tcl_Interp *interp, /* Token for command interpreter (returned by * a previous call to Tcl_CreateInterp). */ Tcl_Obj *objPtr, /* Pointer to object containing commands to * execute. */ int flags, /* Collection of OR-ed bits that control the * evaluation of the script. Supported values * are TCL_EVAL_GLOBAL and TCL_EVAL_DIRECT. */ const CmdFrame *invoker, /* Frame of the command doing the eval. */ int word) /* Index of the word which is in objPtr. */ { Interp *iPtr = (Interp *) interp; int result; /* * This function consists of three independent blocks for: direct * evaluation of canonical lists, compilation and bytecode execution and * finally direct evaluation. Precisely one of these blocks will be run. */ if (TclListObjIsCanonical(objPtr)) { CmdFrame *eoFramePtr = NULL; Tcl_Size objc; Tcl_Obj *listPtr, **objv; /* * Canonical List Optimization: In this case, we * can safely use Tcl_EvalObjv instead and get an appreciable * improvement in execution speed. This is because it allows us to * avoid a setFromAny step that would just pack everything into a * string and back out again. * * This also preserves any associations between list elements and * location information for such elements. */ /* * Shimmer protection! Always pass an unshared obj. The caller could * incr the refCount of objPtr AFTER calling us! To be completely safe * we always make a copy. The callback takes care of the refCounts for * both listPtr and objPtr. * * TODO: Create a test to demo this need, or eliminate it. * FIXME OPT: preserve just the internal rep? */ Tcl_IncrRefCount(objPtr); listPtr = TclListObjCopy(interp, objPtr); Tcl_IncrRefCount(listPtr); if (word != INT_MIN) { /* * TIP #280 Structures for tracking lines. As we know that this is * dynamic execution we ignore the invoker, even if known. * * TIP #280. We do _not_ compute all the line numbers for the * words in the command. For the eval of a pure list the most * sensible choice is to put all words on line 1. Given that we * neither need memory for them nor compute anything. 'line' is * left NULL. The two places using this information (TclInfoFrame, * and TclInitCompileEnv), are special-cased to use the proper * line number directly instead of accessing the 'line' array. * * Note that we use (word==INTMIN) to signal that no command frame * should be pushed, as needed by alias and ensemble redirections. */ eoFramePtr = (CmdFrame *)TclStackAlloc(interp, sizeof(CmdFrame)); eoFramePtr->nline = 0; eoFramePtr->line = NULL; eoFramePtr->type = TCL_LOCATION_EVAL; eoFramePtr->level = (iPtr->cmdFramePtr == NULL? 1 : iPtr->cmdFramePtr->level + 1); eoFramePtr->framePtr = iPtr->framePtr; eoFramePtr->nextPtr = iPtr->cmdFramePtr; eoFramePtr->cmdObj = objPtr; eoFramePtr->cmd = NULL; eoFramePtr->len = 0; eoFramePtr->data.eval.path = NULL; iPtr->cmdFramePtr = eoFramePtr; flags |= TCL_EVAL_SOURCE_IN_FRAME; } TclMarkTailcall(interp); TclNRAddCallback(interp, TEOEx_ListCallback, listPtr, eoFramePtr, objPtr, NULL); TclListObjGetElements(NULL, listPtr, &objc, &objv); return TclNREvalObjv(interp, objc, objv, flags, NULL); } if (!(flags & TCL_EVAL_DIRECT)) { /* * Let the compiler/engine subsystem do the evaluation. * * TIP #280 The invoker provides us with the context for the script. * We transfer this to the byte code compiler. */ int allowExceptions = (iPtr->evalFlags & TCL_ALLOW_EXCEPTIONS); ByteCode *codePtr; CallFrame *savedVarFramePtr = NULL; /* Saves old copy of * iPtr->varFramePtr in case * TCL_EVAL_GLOBAL was set. */ if (TclInterpReady(interp) != TCL_OK) { return TCL_ERROR; } if (flags & TCL_EVAL_GLOBAL) { savedVarFramePtr = iPtr->varFramePtr; iPtr->varFramePtr = iPtr->rootFramePtr; } Tcl_IncrRefCount(objPtr); codePtr = TclCompileObj(interp, objPtr, invoker, word); TclNRAddCallback(interp, TEOEx_ByteCodeCallback, savedVarFramePtr, objPtr, INT2PTR(allowExceptions), NULL); return TclNRExecuteByteCode(interp, codePtr); } { /* * We're not supposed to use the compiler or byte-code * interpreter. Let Tcl_EvalEx evaluate the command directly (and * probably more slowly). */ const char *script; Tcl_Size numSrcBytes; /* * Now we check if we have data about invisible continuation lines for * the script, and make it available to the direct script parser and * evaluator we are about to call, if so. * * It may be possible that the script Tcl_Obj* can be free'd while the * evaluator is using it, leading to the release of the associated * ContLineLoc structure as well. To ensure that the latter doesn't * happen we set a lock on it. We release this lock later in this * function, after the evaluator is done. The relevant "lineCLPtr" * hashtable is managed in the file "tclObj.c". * * Another important action is to save (and later restore) the * continuation line information of the caller, in case we are * executing nested commands in the eval/direct path. */ ContLineLoc *saveCLLocPtr = iPtr->scriptCLLocPtr; assert(invoker == NULL); iPtr->scriptCLLocPtr = TclContinuationsGet(objPtr); Tcl_IncrRefCount(objPtr); script = TclGetStringFromObj(objPtr, &numSrcBytes); result = Tcl_EvalEx(interp, script, numSrcBytes, flags); TclDecrRefCount(objPtr); iPtr->scriptCLLocPtr = saveCLLocPtr; return result; } } static int TEOEx_ByteCodeCallback( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; CallFrame *savedVarFramePtr = (CallFrame *)data[0]; Tcl_Obj *objPtr = (Tcl_Obj *)data[1]; int allowExceptions = PTR2INT(data[2]); if (iPtr->numLevels == 0) { if (result == TCL_RETURN) { result = TclUpdateReturnInfo(iPtr); } if ((result != TCL_OK) && (result != TCL_ERROR) && !allowExceptions) { const char *script; Tcl_Size numSrcBytes; ProcessUnexpectedResult(interp, result); result = TCL_ERROR; script = TclGetStringFromObj(objPtr, &numSrcBytes); Tcl_LogCommandInfo(interp, script, script, numSrcBytes); } /* * We are returning to level 0, so should call TclResetCancellation. * Let us just unset the flags inline. */ TclUnsetCancelFlags(iPtr); } iPtr->evalFlags = 0; /* * Restore the callFrame if this was a TCL_EVAL_GLOBAL. */ if (savedVarFramePtr) { iPtr->varFramePtr = savedVarFramePtr; } TclDecrRefCount(objPtr); return result; } static int TEOEx_ListCallback( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; Tcl_Obj *listPtr = (Tcl_Obj *)data[0]; CmdFrame *eoFramePtr = (CmdFrame *)data[1]; Tcl_Obj *objPtr = (Tcl_Obj *)data[2]; /* * Remove the cmdFrame */ if (eoFramePtr) { iPtr->cmdFramePtr = eoFramePtr->nextPtr; TclStackFree(interp, eoFramePtr); } TclDecrRefCount(objPtr); TclDecrRefCount(listPtr); return result; } /* *---------------------------------------------------------------------- * * ProcessUnexpectedResult -- * * Function called by Tcl_EvalObj to set the interpreter's result value * to an appropriate error message when the code it evaluates returns an * unexpected result code (not TCL_OK and not TCL_ERROR) to the topmost * evaluation level. * * Results: * None. * * Side effects: * The interpreter result is set to an error message appropriate to the * result code. * *---------------------------------------------------------------------- */ static void ProcessUnexpectedResult( Tcl_Interp *interp, /* The interpreter in which the unexpected * result code was returned. */ int returnCode) /* The unexpected result code. */ { char buf[TCL_INTEGER_SPACE]; Tcl_ResetResult(interp); if (returnCode == TCL_BREAK) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "invoked \"break\" outside of a loop", TCL_INDEX_NONE)); } else if (returnCode == TCL_CONTINUE) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "invoked \"continue\" outside of a loop", TCL_INDEX_NONE)); } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "command returned bad code: %d", returnCode)); } snprintf(buf, sizeof(buf), "%d", returnCode); Tcl_SetErrorCode(interp, "TCL", "UNEXPECTED_RESULT_CODE", buf, (char *)NULL); } /* *--------------------------------------------------------------------------- * * Tcl_ExprLong, Tcl_ExprDouble, Tcl_ExprBoolean -- * * Functions to evaluate an expression and return its value in a * particular form. * * Results: * Each of the functions below returns a standard Tcl result. If an error * occurs then an error message is left in the interp's result. Otherwise * the value of the expression, in the appropriate form, is stored at * *ptr. If the expression had a result that was incompatible with the * desired form then an error is returned. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int Tcl_ExprLong( Tcl_Interp *interp, /* Context in which to evaluate the * expression. */ const char *exprstring, /* Expression to evaluate. */ long *ptr) /* Where to store result. */ { Tcl_Obj *exprPtr; int result = TCL_OK; if (*exprstring == '\0') { /* * Legacy compatibility - return 0 for the zero-length string. */ *ptr = 0; } else { exprPtr = Tcl_NewStringObj(exprstring, TCL_INDEX_NONE); Tcl_IncrRefCount(exprPtr); result = Tcl_ExprLongObj(interp, exprPtr, ptr); Tcl_DecrRefCount(exprPtr); } return result; } int Tcl_ExprDouble( Tcl_Interp *interp, /* Context in which to evaluate the * expression. */ const char *exprstring, /* Expression to evaluate. */ double *ptr) /* Where to store result. */ { Tcl_Obj *exprPtr; int result = TCL_OK; if (*exprstring == '\0') { /* * Legacy compatibility - return 0 for the zero-length string. */ *ptr = 0.0; } else { exprPtr = Tcl_NewStringObj(exprstring, TCL_INDEX_NONE); Tcl_IncrRefCount(exprPtr); result = Tcl_ExprDoubleObj(interp, exprPtr, ptr); Tcl_DecrRefCount(exprPtr); /* Discard the expression object. */ } return result; } int Tcl_ExprBoolean( Tcl_Interp *interp, /* Context in which to evaluate the * expression. */ const char *exprstring, /* Expression to evaluate. */ int *ptr) /* Where to store 0/1 result. */ { if (*exprstring == '\0') { /* * An empty string. Just set the result boolean to 0 (false). */ *ptr = 0; return TCL_OK; } else { int result; Tcl_Obj *exprPtr = Tcl_NewStringObj(exprstring, TCL_INDEX_NONE); Tcl_IncrRefCount(exprPtr); result = Tcl_ExprBooleanObj(interp, exprPtr, ptr); Tcl_DecrRefCount(exprPtr); return result; } } /* *-------------------------------------------------------------- * * Tcl_ExprLongObj, Tcl_ExprDoubleObj, Tcl_ExprBooleanObj -- * * Functions to evaluate an expression in an object and return its value * in a particular form. * * Results: * Each of the functions below returns a standard Tcl result object. If * an error occurs then an error message is left in the interpreter's * result. Otherwise the value of the expression, in the appropriate * form, is stored at *ptr. If the expression had a result that was * incompatible with the desired form then an error is returned. * * Side effects: * None. * *-------------------------------------------------------------- */ int Tcl_ExprLongObj( Tcl_Interp *interp, /* Context in which to evaluate the * expression. */ Tcl_Obj *objPtr, /* Expression to evaluate. */ long *ptr) /* Where to store long result. */ { Tcl_Obj *resultPtr; int result, type; double d; void *internalPtr; result = Tcl_ExprObj(interp, objPtr, &resultPtr); if (result != TCL_OK) { return TCL_ERROR; } if (Tcl_GetNumberFromObj(interp, resultPtr, &internalPtr, &type) != TCL_OK) { return TCL_ERROR; } switch (type) { case TCL_NUMBER_DOUBLE: { mp_int big; d = *((const double *) internalPtr); Tcl_DecrRefCount(resultPtr); if (Tcl_InitBignumFromDouble(interp, d, &big) != TCL_OK) { return TCL_ERROR; } resultPtr = Tcl_NewBignumObj(&big); } /* FALLTHRU */ case TCL_NUMBER_INT: case TCL_NUMBER_BIG: result = TclGetLongFromObj(interp, resultPtr, ptr); break; case TCL_NUMBER_NAN: Tcl_GetDoubleFromObj(interp, resultPtr, &d); result = TCL_ERROR; } Tcl_DecrRefCount(resultPtr);/* Discard the result object. */ return result; } int Tcl_ExprDoubleObj( Tcl_Interp *interp, /* Context in which to evaluate the * expression. */ Tcl_Obj *objPtr, /* Expression to evaluate. */ double *ptr) /* Where to store double result. */ { Tcl_Obj *resultPtr; int result, type; void *internalPtr; result = Tcl_ExprObj(interp, objPtr, &resultPtr); if (result != TCL_OK) { return TCL_ERROR; } result = Tcl_GetNumberFromObj(interp, resultPtr, &internalPtr, &type); if (result == TCL_OK) { switch (type) { case TCL_NUMBER_NAN: #ifndef ACCEPT_NAN result = Tcl_GetDoubleFromObj(interp, resultPtr, ptr); break; #endif case TCL_NUMBER_DOUBLE: *ptr = *((const double *) internalPtr); result = TCL_OK; break; default: result = Tcl_GetDoubleFromObj(interp, resultPtr, ptr); } } Tcl_DecrRefCount(resultPtr);/* Discard the result object. */ return result; } int Tcl_ExprBooleanObj( Tcl_Interp *interp, /* Context in which to evaluate the * expression. */ Tcl_Obj *objPtr, /* Expression to evaluate. */ int *ptr) /* Where to store 0/1 result. */ { Tcl_Obj *resultPtr; int result; result = Tcl_ExprObj(interp, objPtr, &resultPtr); if (result == TCL_OK) { result = Tcl_GetBooleanFromObj(interp, resultPtr, ptr); Tcl_DecrRefCount(resultPtr); /* Discard the result object. */ } return result; } /* *---------------------------------------------------------------------- * * TclObjInvokeNamespace -- * * Object version: Invokes a Tcl command, given an objv/objc, from either * the exposed or hidden set of commands in the given interpreter. * * NOTE: The command is invoked in the global stack frame of the * interpreter or namespace, thus it cannot see any current state on the * stack of that interpreter. * * Results: * A standard Tcl result. * * Side effects: * Whatever the command does. * *---------------------------------------------------------------------- */ int TclObjInvokeNamespace( Tcl_Interp *interp, /* Interpreter in which command is to be * invoked. */ Tcl_Size objc, /* Count of arguments. */ Tcl_Obj *const objv[], /* Argument objects; objv[0] points to the * name of the command to invoke. */ Tcl_Namespace *nsPtr, /* The namespace to use. */ int flags) /* Combination of flags controlling the call: * TCL_INVOKE_HIDDEN, TCL_INVOKE_NO_UNKNOWN, * or TCL_INVOKE_NO_TRACEBACK. */ { int result; Tcl_CallFrame *framePtr; /* * Make the specified namespace the current namespace and invoke the * command. */ (void) TclPushStackFrame(interp, &framePtr, nsPtr, /*isProcFrame*/0); result = TclObjInvoke(interp, objc, objv, flags); TclPopStackFrame(interp); return result; } /* *---------------------------------------------------------------------- * * TclObjInvoke -- * * Invokes a Tcl command, given an objv/objc, from either the exposed or * the hidden sets of commands in the given interpreter. * * Results: * A standard Tcl object result. * * Side effects: * Whatever the command does. * *---------------------------------------------------------------------- */ int TclObjInvoke( Tcl_Interp *interp, /* Interpreter in which command is to be * invoked. */ Tcl_Size objc, /* Count of arguments. */ Tcl_Obj *const objv[], /* Argument objects; objv[0] points to the * name of the command to invoke. */ int flags) /* Combination of flags controlling the call: * TCL_INVOKE_HIDDEN, TCL_INVOKE_NO_UNKNOWN, * or TCL_INVOKE_NO_TRACEBACK. */ { if (interp == NULL) { return TCL_ERROR; } if ((objc < 1) || (objv == NULL)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "illegal argument vector", TCL_INDEX_NONE)); return TCL_ERROR; } if ((flags & TCL_INVOKE_HIDDEN) == 0) { Tcl_Panic("TclObjInvoke: called without TCL_INVOKE_HIDDEN"); } return Tcl_NRCallObjProc(interp, TclNRInvoke, NULL, objc, objv); } int TclNRInvoke( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Interp *iPtr = (Interp *) interp; Tcl_HashTable *hTblPtr; /* Table of hidden commands. */ const char *cmdName; /* Name of the command from objv[0]. */ Tcl_HashEntry *hPtr = NULL; Command *cmdPtr; cmdName = TclGetString(objv[0]); hTblPtr = iPtr->hiddenCmdTablePtr; if (hTblPtr != NULL) { hPtr = Tcl_FindHashEntry(hTblPtr, cmdName); } if (hPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid hidden command name \"%s\"", cmdName)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "HIDDENTOKEN", cmdName, (char *)NULL); return TCL_ERROR; } cmdPtr = (Command *)Tcl_GetHashValue(hPtr); /* * Avoid the exception-handling brain damage when numLevels == 0 */ iPtr->numLevels++; Tcl_NRAddCallback(interp, NRPostInvoke, NULL, NULL, NULL, NULL); /* * Normal command resolution of objv[0] isn't going to find cmdPtr. * That's the whole point of **hidden** commands. So tell the Eval core * machinery not to even try (and risk finding something wrong). */ return TclNREvalObjv(interp, objc, objv, TCL_EVAL_NORESOLVE, cmdPtr); } static int NRPostInvoke( TCL_UNUSED(void **), Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *)interp; iPtr->numLevels--; return result; } /* *--------------------------------------------------------------------------- * * Tcl_ExprString -- * * Evaluate an expression in a string and return its value in string * form. * * Results: * A standard Tcl result. If the result is TCL_OK, then the interp's * result is set to the string value of the expression. If the result is * TCL_ERROR, then the interp's result contains an error message. * * Side effects: * A Tcl object is allocated to hold a copy of the expression string. * This expression object is passed to Tcl_ExprObj and then deallocated. * *--------------------------------------------------------------------------- */ int Tcl_ExprString( Tcl_Interp *interp, /* Context in which to evaluate the * expression. */ const char *expr) /* Expression to evaluate. */ { int code = TCL_OK; if (expr[0] == '\0') { /* * An empty string. Just set the interpreter's result to 0. */ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(0)); } else { Tcl_Obj *resultPtr, *exprObj = Tcl_NewStringObj(expr, TCL_INDEX_NONE); Tcl_IncrRefCount(exprObj); code = Tcl_ExprObj(interp, exprObj, &resultPtr); Tcl_DecrRefCount(exprObj); if (code == TCL_OK) { Tcl_SetObjResult(interp, resultPtr); Tcl_DecrRefCount(resultPtr); } } return code; } /* *---------------------------------------------------------------------- * * Tcl_AppendObjToErrorInfo -- * * Add a Tcl_Obj value to the errorInfo field that describes the current * error. * * Results: * None. * * Side effects: * The value of the Tcl_obj is appended to the errorInfo field. If we are * just starting to log an error, errorInfo is initialized from the error * message in the interpreter's result. * *---------------------------------------------------------------------- */ void Tcl_AppendObjToErrorInfo( Tcl_Interp *interp, /* Interpreter to which error information * pertains. */ Tcl_Obj *objPtr) /* Message to record. */ { Tcl_Size length; const char *message = TclGetStringFromObj(objPtr, &length); Interp *iPtr = (Interp *) interp; Tcl_IncrRefCount(objPtr); /* * If we are just starting to log an error, errorInfo is initialized from * the error message in the interpreter's result. */ iPtr->flags |= ERR_LEGACY_COPY; if (iPtr->errorInfo == NULL) { iPtr->errorInfo = iPtr->objResultPtr; Tcl_IncrRefCount(iPtr->errorInfo); if (!iPtr->errorCode) { Tcl_SetErrorCode(interp, "NONE", (char *)NULL); } } /* * Now append "message" to the end of errorInfo. */ if (length != 0) { if (Tcl_IsShared(iPtr->errorInfo)) { Tcl_DecrRefCount(iPtr->errorInfo); iPtr->errorInfo = Tcl_DuplicateObj(iPtr->errorInfo); Tcl_IncrRefCount(iPtr->errorInfo); } Tcl_AppendToObj(iPtr->errorInfo, message, length); } Tcl_DecrRefCount(objPtr); } /* *---------------------------------------------------------------------- * * Tcl_VarEval -- * * Given a variable number of string arguments, concatenate them all * together and execute the result as a Tcl command. * * Results: * A standard Tcl return result. An error message or other result may be * left in the interp. * * Side effects: * Depends on what was done by the command. * *---------------------------------------------------------------------- */ int Tcl_VarEval( Tcl_Interp *interp, ...) { va_list argList; int result; Tcl_DString buf; char *string; va_start(argList, interp); /* * Copy the strings one after the other into a single larger string. Use * stack-allocated space for small commands, but if the command gets too * large than call Tcl_Alloc to create the space. */ Tcl_DStringInit(&buf); while (1) { string = va_arg(argList, char *); if (string == NULL) { break; } Tcl_DStringAppend(&buf, string, TCL_INDEX_NONE); } result = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), TCL_INDEX_NONE, 0); Tcl_DStringFree(&buf); return result; } /* *---------------------------------------------------------------------- * * Tcl_SetRecursionLimit -- * * Set the maximum number of recursive calls that may be active for an * interpreter at once. * * Results: * The return value is the old limit on nesting for interp. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_SetRecursionLimit( Tcl_Interp *interp, /* Interpreter whose nesting limit is to be * set. */ Tcl_Size depth) /* New value for maximum depth. */ { Interp *iPtr = (Interp *) interp; Tcl_Size old; old = iPtr->maxNestingDepth; if (depth > 0) { iPtr->maxNestingDepth = depth; } return old; } /* *---------------------------------------------------------------------- * * Tcl_AllowExceptions -- * * Sets a flag in an interpreter so that exceptions can occur in the next * call to Tcl_Eval without them being turned into errors. * * Results: * None. * * Side effects: * The TCL_ALLOW_EXCEPTIONS flag gets set in the interpreter's evalFlags * structure. See the reference documentation for more details. * *---------------------------------------------------------------------- */ void Tcl_AllowExceptions( Tcl_Interp *interp) /* Interpreter in which to set flag. */ { Interp *iPtr = (Interp *) interp; iPtr->evalFlags |= TCL_ALLOW_EXCEPTIONS; } /* *---------------------------------------------------------------------- * * Tcl_GetVersion -- * * Get the Tcl major, minor, and patchlevel version numbers and the * release type. A patch is a release type TCL_FINAL_RELEASE with a * patchLevel > 0. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_GetVersion( int *majorV, int *minorV, int *patchLevelV, int *type) { if (majorV != NULL) { *majorV = TCL_MAJOR_VERSION; } if (minorV != NULL) { *minorV = TCL_MINOR_VERSION; } if (patchLevelV != NULL) { *patchLevelV = TCL_RELEASE_SERIAL; } if (type != NULL) { *type = TCL_RELEASE_LEVEL; } } /* *---------------------------------------------------------------------- * * Math Functions -- * * This page contains the functions that implement all of the built-in * math functions for expressions. * * Results: * Each function returns TCL_OK if it succeeds and pushes an Tcl object * holding the result. If it fails it returns TCL_ERROR and leaves an * error message in the interpreter's result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int ExprCeilFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Actual parameter list. */ { int code; double d; mp_int big; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } code = Tcl_GetDoubleFromObj(interp, objv[1], &d); #ifdef ACCEPT_NAN if (code != TCL_OK) { const Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(objv[1], &tclDoubleType); if (irPtr) { Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } } #endif if (code != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBignumFromObj(NULL, objv[1], &big) == TCL_OK) { Tcl_SetObjResult(interp, Tcl_NewDoubleObj(TclCeil(&big))); mp_clear(&big); } else { Tcl_SetObjResult(interp, Tcl_NewDoubleObj(ceil(d))); } return TCL_OK; } static int ExprFloorFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Actual parameter list. */ { int code; double d; mp_int big; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } code = Tcl_GetDoubleFromObj(interp, objv[1], &d); #ifdef ACCEPT_NAN if (code != TCL_OK) { const Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(objv[1], &tclDoubleType); if (irPtr) { Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } } #endif if (code != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBignumFromObj(NULL, objv[1], &big) == TCL_OK) { Tcl_SetObjResult(interp, Tcl_NewDoubleObj(TclFloor(&big))); mp_clear(&big); } else { Tcl_SetObjResult(interp, Tcl_NewDoubleObj(floor(d))); } return TCL_OK; } static int ExprIsqrtFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Actual parameter list. */ { void *ptr; int type; double d; Tcl_WideInt w; mp_int big; int exact = 0; /* Flag ==1 if the argument can be represented * in a double as an exact integer. */ /* * Check syntax. */ if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } /* * Make sure that the arg is a number. */ if (Tcl_GetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) { return TCL_ERROR; } switch (type) { case TCL_NUMBER_NAN: Tcl_GetDoubleFromObj(interp, objv[1], &d); return TCL_ERROR; case TCL_NUMBER_DOUBLE: d = *((const double *) ptr); if (d < 0) { goto negarg; } #ifdef IEEE_FLOATING_POINT if (d <= MAX_EXACT) { exact = 1; } #endif if (!exact) { if (Tcl_InitBignumFromDouble(interp, d, &big) != TCL_OK) { return TCL_ERROR; } } break; case TCL_NUMBER_BIG: if (Tcl_GetBignumFromObj(interp, objv[1], &big) != TCL_OK) { return TCL_ERROR; } if (mp_isneg(&big)) { mp_clear(&big); goto negarg; } break; default: if (TclGetWideIntFromObj(interp, objv[1], &w) != TCL_OK) { return TCL_ERROR; } if (w < 0) { goto negarg; } d = (double) w; #ifdef IEEE_FLOATING_POINT if (d < MAX_EXACT) { exact = 1; } #endif if (!exact) { Tcl_GetBignumFromObj(interp, objv[1], &big); } break; } if (exact) { Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) sqrt(d))); } else { mp_int root; mp_err err; err = mp_init(&root); if (err == MP_OKAY) { err = mp_sqrt(&big, &root); } mp_clear(&big); if (err != MP_OKAY) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBignumObj(&root)); } return TCL_OK; negarg: Tcl_SetObjResult(interp, Tcl_NewStringObj( "square root of negative argument", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "ARITH", "DOMAIN", "domain error: argument not in valid range", (char *)NULL); return TCL_ERROR; } static int ExprSqrtFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Actual parameter list. */ { int code; double d; mp_int big; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } code = Tcl_GetDoubleFromObj(interp, objv[1], &d); #ifdef ACCEPT_NAN if (code != TCL_OK) { const Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(objv[1], &tclDoubleType); if (irPtr) { Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } } #endif if (code != TCL_OK) { return TCL_ERROR; } if ((d >= 0.0) && isinf(d) && (Tcl_GetBignumFromObj(NULL, objv[1], &big) == TCL_OK)) { mp_int root; mp_err err; err = mp_init(&root); if (err == MP_OKAY) { err = mp_sqrt(&big, &root); } mp_clear(&big); if (err != MP_OKAY) { mp_clear(&root); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewDoubleObj(TclBignumToDouble(&root))); mp_clear(&root); } else { Tcl_SetObjResult(interp, Tcl_NewDoubleObj(sqrt(d))); } return TCL_OK; } static int ExprUnaryFunc( void *clientData, /* Contains the address of a function that * takes one double argument and returns a * double result. */ Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count */ Tcl_Obj *const *objv) /* Actual parameter list */ { int code; double d; BuiltinUnaryFunc *func = (BuiltinUnaryFunc *) clientData; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } code = Tcl_GetDoubleFromObj(interp, objv[1], &d); #ifdef ACCEPT_NAN if (code != TCL_OK) { const Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(objv[1], &tclDoubleType); if (irPtr) { d = irPtr->doubleValue; Tcl_ResetResult(interp); code = TCL_OK; } } #endif if (code != TCL_OK) { return TCL_ERROR; } errno = 0; return CheckDoubleResult(interp, func(d)); } static int CheckDoubleResult( Tcl_Interp *interp, double dResult) { #ifndef ACCEPT_NAN if (isnan(dResult)) { TclExprFloatError(interp, dResult); return TCL_ERROR; } #endif if ((errno == ERANGE) && ((dResult == 0.0) || isinf(dResult))) { /* * When ERANGE signals under/overflow, just accept 0.0 or +/-Inf */ } else if (errno != 0) { /* * Report other errno values as errors. */ TclExprFloatError(interp, dResult); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewDoubleObj(dResult)); return TCL_OK; } static int ExprBinaryFunc( void *clientData, /* Contains the address of a function that * takes two double arguments and returns a * double result. */ Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Parameter vector. */ { int code; double d1, d2; BuiltinBinaryFunc *func = (BuiltinBinaryFunc *)clientData; if (objc != 3) { MathFuncWrongNumArgs(interp, 3, objc, objv); return TCL_ERROR; } code = Tcl_GetDoubleFromObj(interp, objv[1], &d1); #ifdef ACCEPT_NAN if (code != TCL_OK) { const Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(objv[1], &tclDoubleType); if (irPtr) { d1 = irPtr->doubleValue; Tcl_ResetResult(interp); code = TCL_OK; } } #endif if (code != TCL_OK) { return TCL_ERROR; } code = Tcl_GetDoubleFromObj(interp, objv[2], &d2); #ifdef ACCEPT_NAN if (code != TCL_OK) { const Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(objv[1], &tclDoubleType); if (irPtr) { d2 = irPtr->doubleValue; Tcl_ResetResult(interp); code = TCL_OK; } } #endif if (code != TCL_OK) { return TCL_ERROR; } errno = 0; return CheckDoubleResult(interp, func(d1, d2)); } static int ExprAbsFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Parameter vector. */ { void *ptr; int type; mp_int big; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } if (Tcl_GetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) { return TCL_ERROR; } if (type == TCL_NUMBER_INT) { Tcl_WideInt l = *((const Tcl_WideInt *) ptr); if (l > 0) { goto unChanged; } else if (l == 0) { if (TclHasStringRep(objv[1])) { Tcl_Size numBytes; const char *bytes = TclGetStringFromObj(objv[1], &numBytes); while (numBytes) { if (*bytes == '-') { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(0)); return TCL_OK; } bytes++; numBytes--; } } goto unChanged; } else if (l == WIDE_MIN) { if (sizeof(Tcl_WideInt) > sizeof(int64_t)) { Tcl_WideUInt ul = -(Tcl_WideUInt)WIDE_MIN; if (mp_init(&big) != MP_OKAY || mp_unpack(&big, 1, 1, sizeof(Tcl_WideInt), 0, 0, &ul) != MP_OKAY) { return TCL_ERROR; } if (mp_neg(&big, &big) != MP_OKAY) { return TCL_ERROR; } } else if (mp_init_i64(&big, l) != MP_OKAY) { return TCL_ERROR; } goto tooLarge; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(-l)); return TCL_OK; } if (type == TCL_NUMBER_DOUBLE) { double d = *((const double *) ptr); static const double poszero = 0.0; /* * We need to distinguish here between positive 0.0 and negative -0.0. * [Bug 2954959] */ if (d == -0.0) { if (!memcmp(&d, &poszero, sizeof(double))) { goto unChanged; } } else if (d > -0.0) { goto unChanged; } Tcl_SetObjResult(interp, Tcl_NewDoubleObj(-d)); return TCL_OK; } if (type == TCL_NUMBER_BIG) { if (mp_isneg((const mp_int *) ptr)) { Tcl_GetBignumFromObj(NULL, objv[1], &big); tooLarge: if (mp_neg(&big, &big) != MP_OKAY) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBignumObj(&big)); } else { unChanged: Tcl_SetObjResult(interp, objv[1]); } return TCL_OK; } if (type == TCL_NUMBER_NAN) { #ifdef ACCEPT_NAN Tcl_SetObjResult(interp, objv[1]); return TCL_OK; #else double d; Tcl_GetDoubleFromObj(interp, objv[1], &d); return TCL_ERROR; #endif } return TCL_OK; } static int ExprBoolFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Actual parameter vector. */ { int value; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[1], &value) != TCL_OK) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value)); return TCL_OK; } static int ExprDoubleFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Actual parameter vector. */ { double dResult; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } if (Tcl_GetDoubleFromObj(interp, objv[1], &dResult) != TCL_OK) { #ifdef ACCEPT_NAN if (TclHasInternalRep(objv[1], &tclDoubleType)) { Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } #endif return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewDoubleObj(dResult)); return TCL_OK; } static int ExprIntFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Actual parameter vector. */ { double d; int type; void *ptr; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } if (Tcl_GetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) { return TCL_ERROR; } if (type == TCL_NUMBER_DOUBLE) { d = *((const double *) ptr); if ((d >= (double)WIDE_MAX) || (d <= (double)WIDE_MIN)) { mp_int big; if (Tcl_InitBignumFromDouble(interp, d, &big) != TCL_OK) { /* Infinity */ return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBignumObj(&big)); return TCL_OK; } else { Tcl_WideInt result = (Tcl_WideInt) d; Tcl_SetObjResult(interp, Tcl_NewWideIntObj(result)); return TCL_OK; } } if (type != TCL_NUMBER_NAN) { /* * All integers are already of integer type. */ Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } /* * Get the error message for NaN. */ Tcl_GetDoubleFromObj(interp, objv[1], &d); return TCL_ERROR; } static int ExprWideFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Actual parameter vector. */ { Tcl_WideInt wResult; if (ExprIntFunc(NULL, interp, objc, objv) != TCL_OK) { return TCL_ERROR; } TclGetWideBitsFromObj(NULL, Tcl_GetObjResult(interp), &wResult); Tcl_SetObjResult(interp, Tcl_NewWideIntObj(wResult)); return TCL_OK; } /* * Common implmentation of max() and min(). */ static int ExprMaxMinFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv, /* Actual parameter vector. */ int op) /* Comparison direction */ { Tcl_Obj *res; double d; int type; int i; void *ptr; if (objc < 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } res = objv[1]; for (i = 1; i < objc; i++) { if (Tcl_GetNumberFromObj(interp, objv[i], &ptr, &type) != TCL_OK) { return TCL_ERROR; } if (type == TCL_NUMBER_NAN) { /* * Get the error message for NaN. */ Tcl_GetDoubleFromObj(interp, objv[i], &d); return TCL_ERROR; } if (TclCompareTwoNumbers(objv[i], res) == op) { res = objv[i]; } } Tcl_SetObjResult(interp, res); return TCL_OK; } static int ExprMaxFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Actual parameter vector. */ { return ExprMaxMinFunc(NULL, interp, objc, objv, MP_GT); } static int ExprMinFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Actual parameter vector. */ { return ExprMaxMinFunc(NULL, interp, objc, objv, MP_LT); } static int ExprRandFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Actual parameter vector. */ { Interp *iPtr = (Interp *) interp; double dResult; long tmp; /* Algorithm assumes at least 32 bits. Only * long guarantees that. See below. */ Tcl_Obj *oResult; if (objc != 1) { MathFuncWrongNumArgs(interp, 1, objc, objv); return TCL_ERROR; } if (!(iPtr->flags & RAND_SEED_INITIALIZED)) { iPtr->flags |= RAND_SEED_INITIALIZED; /* * To ensure different seeds in different threads (bug #416643), * take into consideration the thread this interp is running in. */ iPtr->randSeed = TclpGetClicks() + PTR2UINT(Tcl_GetCurrentThread()) * 4093U; /* * Make sure 1 <= randSeed <= (2^31) - 2. See below. */ iPtr->randSeed &= 0x7FFFFFFFL; if ((iPtr->randSeed == 0) || (iPtr->randSeed == 0x7FFFFFFFL)) { iPtr->randSeed ^= 123459876L; } } /* * Generate the random number using the linear congruential generator * defined by the following recurrence: * seed = ( IA * seed ) mod IM * where IA is 16807 and IM is (2^31) - 1. The recurrence maps a seed in * the range [1, IM - 1] to a new seed in that same range. The recurrence * maps IM to 0, and maps 0 back to 0, so those two values must not be * allowed as initial values of seed. * * In order to avoid potential problems with integer overflow, the * recurrence is implemented in terms of additional constants IQ and IR * such that * IM = IA*IQ + IR * None of the operations in the implementation overflows a 32-bit signed * integer, and the C type long is guaranteed to be at least 32 bits wide. * * For more details on how this algorithm works, refer to the following * papers: * * S.K. Park & K.W. Miller, "Random number generators: good ones are hard * to find," Comm ACM 31(10):1192-1201, Oct 1988 * * W.H. Press & S.A. Teukolsky, "Portable random number generators," * Computers in Physics 6(5):522-524, Sep/Oct 1992. */ #define RAND_IA 16807 #define RAND_IM 2147483647 #define RAND_IQ 127773 #define RAND_IR 2836 #define RAND_MASK 123459876 tmp = iPtr->randSeed/RAND_IQ; iPtr->randSeed = RAND_IA*(iPtr->randSeed - tmp*RAND_IQ) - RAND_IR*tmp; if (iPtr->randSeed < 0) { iPtr->randSeed += RAND_IM; } /* * Since the recurrence keeps seed values in the range [1, RAND_IM - 1], * dividing by RAND_IM yields a double in the range (0, 1). */ dResult = iPtr->randSeed * (1.0/RAND_IM); /* * Push a Tcl object with the result. */ TclNewDoubleObj(oResult, dResult); Tcl_SetObjResult(interp, oResult); return TCL_OK; } static int ExprRoundFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Parameter vector. */ { double d; void *ptr; int type; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } if (Tcl_GetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) { return TCL_ERROR; } if (type == TCL_NUMBER_DOUBLE) { double fractPart, intPart; Tcl_WideInt max = WIDE_MAX, min = WIDE_MIN; fractPart = modf(*((const double *) ptr), &intPart); if (fractPart <= -0.5) { min++; } else if (fractPart >= 0.5) { max--; } if ((intPart >= (double)max) || (intPart <= (double)min)) { mp_int big; mp_err err = MP_OKAY; if (Tcl_InitBignumFromDouble(interp, intPart, &big) != TCL_OK) { /* Infinity */ return TCL_ERROR; } if (fractPart <= -0.5) { err = mp_sub_d(&big, 1, &big); } else if (fractPart >= 0.5) { err = mp_add_d(&big, 1, &big); } if (err != MP_OKAY) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBignumObj(&big)); return TCL_OK; } else { Tcl_WideInt result = (Tcl_WideInt)intPart; if (fractPart <= -0.5) { result--; } else if (fractPart >= 0.5) { result++; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(result)); return TCL_OK; } } if (type != TCL_NUMBER_NAN) { /* * All integers are already rounded */ Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } /* * Get the error message for NaN. */ Tcl_GetDoubleFromObj(interp, objv[1], &d); return TCL_ERROR; } static int ExprSrandFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Parameter vector. */ { Interp *iPtr = (Interp *) interp; Tcl_WideInt w = 0; /* Initialized to avoid compiler warning. */ /* * Convert argument and use it to reset the seed. */ if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } if (TclGetWideBitsFromObj(NULL, objv[1], &w) != TCL_OK) { return TCL_ERROR; } /* * Reset the seed. Make sure 1 <= randSeed <= 2^31 - 2. See comments in * ExprRandFunc for more details. */ iPtr->flags |= RAND_SEED_INITIALIZED; iPtr->randSeed = (long) w & 0x7FFFFFFF; if ((iPtr->randSeed == 0) || (iPtr->randSeed == 0x7FFFFFFF)) { iPtr->randSeed ^= 123459876; } /* * To avoid duplicating the random number generation code we simply clean * up our state and call the real random number function. That function * will always succeed. */ return ExprRandFunc(NULL, interp, 1, objv); } /* *---------------------------------------------------------------------- * * Double Classification Functions -- * * This page contains the functions that implement all of the built-in * math functions for classifying IEEE doubles. * * These have to be a little bit careful while Tcl_GetDoubleFromObj() * rejects NaN values, which these functions *explicitly* accept. * * Results: * Each function returns TCL_OK if it succeeds and pushes an Tcl object * holding the result. If it fails it returns TCL_ERROR and leaves an * error message in the interpreter's result. * * Side effects: * None. * *---------------------------------------------------------------------- * * Older MSVC is supported by Tcl, but doesn't have fpclassify(). Of course. * But it does sometimes have _fpclass() which does almost the same job; if * even that is absent, we grobble around directly in the platform's binary * representation of double. * * The ClassifyDouble() function makes all that conform to a common API * (effectively the C99 standard API renamed), and just delegates to the * standard macro on platforms that do it correctly. */ static inline int ClassifyDouble( double d) { #if TCL_FPCLASSIFY_MODE == 0 return fpclassify(d); #else /* TCL_FPCLASSIFY_MODE != 0 */ /* * If we don't have fpclassify(), we also don't have the values it returns. * Hence we define those here. */ #ifndef FP_NAN # define FP_NAN 1 /* Value is NaN */ # define FP_INFINITE 2 /* Value is an infinity */ # define FP_ZERO 3 /* Value is a zero */ # define FP_NORMAL 4 /* Value is a normal float */ # define FP_SUBNORMAL 5 /* Value has lost accuracy */ #endif /* !FP_NAN */ #if TCL_FPCLASSIFY_MODE == 3 return __builtin_fpclassify( FP_NAN, FP_INFINITE, FP_NORMAL, FP_SUBNORMAL, FP_ZERO, d); #elif TCL_FPCLASSIFY_MODE == 2 /* * We assume this hack is only needed on little-endian systems. * Specifically, x86 running Windows. It's fairly easy to enable for * others if they need it (because their libc/libm is broken) but we'll * jump that hurdle when requred. We can solve the word ordering then. */ union { double d; /* Interpret as double */ struct { unsigned int low; /* Lower 32 bits */ unsigned int high; /* Upper 32 bits */ } w; /* Interpret as unsigned integer words */ } doubleMeaning; /* So we can look at the representation of a * double directly. Platform (i.e., processor) * specific; this is for x86 (and most other * little-endian processors, but those are * untested). */ unsigned int exponent, mantissaLow, mantissaHigh; /* The pieces extracted from the double. */ int zeroMantissa; /* Was the mantissa zero? That's special. */ /* * Shifts and masks to use with the doubleMeaning variable above. */ #define EXPONENT_MASK 0x7FF /* 11 bits (after shifting) */ #define EXPONENT_SHIFT 20 /* Moves exponent to bottom of word */ #define MANTISSA_MASK 0xFFFFF /* 20 bits (plus 32 from other word) */ /* * Extract the exponent (11 bits) and mantissa (52 bits). Note that we * totally ignore the sign bit. */ doubleMeaning.d = d; exponent = (doubleMeaning.w.high >> EXPONENT_SHIFT) & EXPONENT_MASK; mantissaLow = doubleMeaning.w.low; mantissaHigh = doubleMeaning.w.high & MANTISSA_MASK; zeroMantissa = (mantissaHigh == 0 && mantissaLow == 0); /* * Look for the special cases of exponent. */ switch (exponent) { case 0: /* * When the exponent is all zeros, it's a ZERO or a SUBNORMAL. */ return zeroMantissa ? FP_ZERO : FP_SUBNORMAL; case EXPONENT_MASK: /* * When the exponent is all ones, it's an INF or a NAN. */ return zeroMantissa ? FP_INFINITE : FP_NAN; default: /* * Everything else is a NORMAL double precision float. */ return FP_NORMAL; } #elif TCL_FPCLASSIFY_MODE == 1 switch (_fpclass(d)) { case _FPCLASS_NZ: case _FPCLASS_PZ: return FP_ZERO; case _FPCLASS_NN: case _FPCLASS_PN: return FP_NORMAL; case _FPCLASS_ND: case _FPCLASS_PD: return FP_SUBNORMAL; case _FPCLASS_NINF: case _FPCLASS_PINF: return FP_INFINITE; default: Tcl_Panic("result of _fpclass() outside documented range!"); case _FPCLASS_QNAN: case _FPCLASS_SNAN: return FP_NAN; } #else /* TCL_FPCLASSIFY_MODE not in (0..3) */ #error "unknown or unexpected TCL_FPCLASSIFY_MODE" #endif /* TCL_FPCLASSIFY_MODE */ #endif /* !fpclassify */ } static int ExprIsFiniteFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count */ Tcl_Obj *const *objv) /* Actual parameter list */ { double d; void *ptr; int type, result = 0; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } if (Tcl_GetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) { return TCL_ERROR; } if (type != TCL_NUMBER_NAN) { if (Tcl_GetDoubleFromObj(interp, objv[1], &d) != TCL_OK) { return TCL_ERROR; } type = ClassifyDouble(d); result = (type != FP_INFINITE && type != FP_NAN); } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result)); return TCL_OK; } static int ExprIsInfinityFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count */ Tcl_Obj *const *objv) /* Actual parameter list */ { double d; void *ptr; int type, result = 0; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } if (Tcl_GetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) { return TCL_ERROR; } if (type != TCL_NUMBER_NAN) { if (Tcl_GetDoubleFromObj(interp, objv[1], &d) != TCL_OK) { return TCL_ERROR; } result = (ClassifyDouble(d) == FP_INFINITE); } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result)); return TCL_OK; } static int ExprIsNaNFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count */ Tcl_Obj *const *objv) /* Actual parameter list */ { double d; void *ptr; int type, result = 1; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } if (Tcl_GetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) { return TCL_ERROR; } if (type != TCL_NUMBER_NAN) { if (Tcl_GetDoubleFromObj(interp, objv[1], &d) != TCL_OK) { return TCL_ERROR; } result = (ClassifyDouble(d) == FP_NAN); } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result)); return TCL_OK; } static int ExprIsNormalFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count */ Tcl_Obj *const *objv) /* Actual parameter list */ { double d; void *ptr; int type, result = 0; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } if (Tcl_GetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) { return TCL_ERROR; } if (type != TCL_NUMBER_NAN) { if (Tcl_GetDoubleFromObj(interp, objv[1], &d) != TCL_OK) { return TCL_ERROR; } result = (ClassifyDouble(d) == FP_NORMAL); } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result)); return TCL_OK; } static int ExprIsSubnormalFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count */ Tcl_Obj *const *objv) /* Actual parameter list */ { double d; void *ptr; int type, result = 0; if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } if (Tcl_GetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) { return TCL_ERROR; } if (type != TCL_NUMBER_NAN) { if (Tcl_GetDoubleFromObj(interp, objv[1], &d) != TCL_OK) { return TCL_ERROR; } result = (ClassifyDouble(d) == FP_SUBNORMAL); } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result)); return TCL_OK; } static int ExprIsUnorderedFunc( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count */ Tcl_Obj *const *objv) /* Actual parameter list */ { double d; void *ptr; int type, result = 0; if (objc != 3) { MathFuncWrongNumArgs(interp, 3, objc, objv); return TCL_ERROR; } if (Tcl_GetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) { return TCL_ERROR; } if (type == TCL_NUMBER_NAN) { result = 1; } else { d = *((const double *) ptr); result = (ClassifyDouble(d) == FP_NAN); } if (Tcl_GetNumberFromObj(interp, objv[2], &ptr, &type) != TCL_OK) { return TCL_ERROR; } if (type == TCL_NUMBER_NAN) { result |= 1; } else { d = *((const double *) ptr); result |= (ClassifyDouble(d) == FP_NAN); } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result)); return TCL_OK; } static int FloatClassifyObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter in which to execute the * function. */ int objc, /* Actual parameter count */ Tcl_Obj *const *objv) /* Actual parameter list */ { double d; Tcl_Obj *objPtr; void *ptr; int type; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "floatValue"); return TCL_ERROR; } if (Tcl_GetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) { return TCL_ERROR; } if (type == TCL_NUMBER_NAN) { goto gotNaN; } else if (Tcl_GetDoubleFromObj(interp, objv[1], &d) != TCL_OK) { return TCL_ERROR; } switch (ClassifyDouble(d)) { case FP_INFINITE: TclNewLiteralStringObj(objPtr, "infinite"); break; case FP_NAN: gotNaN: TclNewLiteralStringObj(objPtr, "nan"); break; case FP_NORMAL: TclNewLiteralStringObj(objPtr, "normal"); break; case FP_SUBNORMAL: TclNewLiteralStringObj(objPtr, "subnormal"); break; case FP_ZERO: TclNewLiteralStringObj(objPtr, "zero"); break; default: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unable to classify number: %f", d)); return TCL_ERROR; } Tcl_SetObjResult(interp, objPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * MathFuncWrongNumArgs -- * * Generate an error message when a math function presents the wrong * number of arguments. * * Results: * None. * * Side effects: * An error message is stored in the interpreter result. * *---------------------------------------------------------------------- */ static void MathFuncWrongNumArgs( Tcl_Interp *interp, /* Tcl interpreter */ int expected, /* Formal parameter count. */ int found, /* Actual parameter count. */ Tcl_Obj *const *objv) /* Actual parameter vector. */ { const char *name = TclGetString(objv[0]); const char *tail = name + strlen(name); while (tail > name + 1) { tail--; if (*tail == ':' && tail[-1] == ':') { name = tail + 1; break; } } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s arguments for math function \"%s\"", (found < expected ? "not enough" : "too many"), name)); Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", (char *)NULL); } #ifdef USE_DTRACE /* *---------------------------------------------------------------------- * * DTraceObjCmd -- * * This function is invoked to process the "::tcl::dtrace" Tcl command. * * Results: * A standard Tcl object result. * * Side effects: * The 'tcl-probe' DTrace probe is triggered (if it is enabled). * *---------------------------------------------------------------------- */ static int DTraceObjCmd( TCL_UNUSED(void *), TCL_UNUSED(Tcl_Interp *), int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (TCL_DTRACE_TCL_PROBE_ENABLED()) { char *a[10]; int i = 0; while (i++ < 10) { a[i-1] = i < objc ? TclGetString(objv[i]) : NULL; } TCL_DTRACE_TCL_PROBE(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclDTraceInfo -- * * Extract information from a TIP280 dict for use by DTrace probes. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclDTraceInfo( Tcl_Obj *info, const char **args, Tcl_Size *argsi) { static Tcl_Obj *keys[10] = { NULL }; Tcl_Obj **k = keys, *val; int i = 0; if (!*k) { #define kini(s) TclNewLiteralStringObj(keys[i], s); i++ kini("cmd"); kini("type"); kini("proc"); kini("file"); kini("method"); kini("class"); kini("lambda"); kini("object"); kini("line"); kini("level"); #undef kini } for (i = 0; i < 6; i++) { Tcl_DictObjGet(NULL, info, *k++, &val); args[i] = val ? TclGetString(val) : NULL; } /* * no "proc" -> use "lambda" */ if (!args[2]) { Tcl_DictObjGet(NULL, info, *k, &val); args[2] = val ? TclGetString(val) : NULL; } k++; /* * no "class" -> use "object" */ if (!args[5]) { Tcl_DictObjGet(NULL, info, *k, &val); args[5] = val ? TclGetString(val) : NULL; } k++; for (i = 0; i < 2; i++) { Tcl_DictObjGet(NULL, info, *k++, &val); if (val) { Tcl_GetSizeIntFromObj(NULL, val, &argsi[i]); } else { argsi[i] = 0; } } } /* *---------------------------------------------------------------------- * * DTraceCmdReturn -- * * NR callback for DTrace command return probes. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int DTraceCmdReturn( void *data[], Tcl_Interp *interp, int result) { char *cmdName = TclGetString((Tcl_Obj *) data[0]); if (TCL_DTRACE_CMD_RETURN_ENABLED()) { TCL_DTRACE_CMD_RETURN(cmdName, result); } if (TCL_DTRACE_CMD_RESULT_ENABLED()) { Tcl_Obj *r = Tcl_GetObjResult(interp); TCL_DTRACE_CMD_RESULT(cmdName, result, TclGetString(r), r); } return result; } TCL_DTRACE_DEBUG_LOG() #endif /* USE_DTRACE */ /* *---------------------------------------------------------------------- * * Tcl_NRCallObjProc -- * * This function calls an objProc directly while managing things properly * if it happens to be an NR objProc. It is meant to be used by extenders * that provide an NR implementation of a command, as this function * permits a trivial coding of the non-NR objProc. * * Results: * The return value is a standard Tcl completion code such as TCL_OK or * TCL_ERROR. A result or error message is left in interp's result. * * Side effects: * Depends on the objProc. * *---------------------------------------------------------------------- */ int Tcl_NRCallObjProc( Tcl_Interp *interp, Tcl_ObjCmdProc *objProc, void *clientData, Tcl_Size objc, Tcl_Obj *const objv[]) { NRE_callback *rootPtr = TOP_CB(interp); TclNRAddCallback(interp, Dispatch, objProc, clientData, INT2PTR(objc), objv); return TclNRRunCallbacks(interp, TCL_OK, rootPtr); } static int wrapperNRObjProc( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { CmdWrapperInfo *info = (CmdWrapperInfo *) clientData; clientData = info->clientData; Tcl_ObjCmdProc2 *proc = info->proc; Tcl_Free(info); if (objc < 0) { objc = -1; } return proc(clientData, interp, (Tcl_Size) objc, objv); } int Tcl_NRCallObjProc2( Tcl_Interp *interp, Tcl_ObjCmdProc2 *objProc, void *clientData, Tcl_Size objc, Tcl_Obj *const objv[]) { if (objc > INT_MAX) { Tcl_WrongNumArgs(interp, 1, objv, "?args?"); return TCL_ERROR; } NRE_callback *rootPtr = TOP_CB(interp); CmdWrapperInfo *info = (CmdWrapperInfo *)Tcl_Alloc(sizeof(CmdWrapperInfo)); info->clientData = clientData; info->proc = objProc; TclNRAddCallback(interp, Dispatch, wrapperNRObjProc, info, INT2PTR(objc), objv); return TclNRRunCallbacks(interp, TCL_OK, rootPtr); } /* *---------------------------------------------------------------------- * * Tcl_NRCreateCommand -- * * Define a new NRE-enabled object-based command in a command table. * * Results: * The return value is a token for the command, which can be used in * future calls to Tcl_GetCommandName. * * Side effects: * If no command named "cmdName" already exists for interp, one is * created. Otherwise, if a command does exist, then if the object-based * Tcl_ObjCmdProc is InvokeStringCommand, we assume Tcl_CreateCommand * was called previously for the same command and just set its * Tcl_ObjCmdProc to the argument "proc"; otherwise, we delete the old * command. * * In the future, during bytecode evaluation when "cmdName" is seen as * the name of a command by Tcl_EvalObj or Tcl_Eval, the object-based * Tcl_ObjCmdProc proc will be called. When the command is deleted from * the table, deleteProc will be called. See the manual entry for details * on the calling sequence. * *---------------------------------------------------------------------- */ static int cmdWrapperNreProc( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { CmdWrapperInfo *info = (CmdWrapperInfo *) clientData; if (objc < 0) { objc = -1; } return info->nreProc(info->clientData, interp, objc, objv); } Tcl_Command Tcl_NRCreateCommand2( Tcl_Interp *interp, /* Token for command interpreter (returned by * previous call to Tcl_CreateInterp). */ const char *cmdName, /* Name of command. If it contains namespace * qualifiers, the new command is put in the * specified namespace; otherwise it is put in * the global namespace. */ Tcl_ObjCmdProc2 *proc, /* Object-based function to associate with * name, provides direct access for direct * calls. */ Tcl_ObjCmdProc2 *nreProc, /* Object-based function to associate with * name, provides NR implementation */ void *clientData, /* Arbitrary value to pass to object * function. */ Tcl_CmdDeleteProc *deleteProc) /* If not NULL, gives a function to call when * this command is deleted. */ { CmdWrapperInfo *info = (CmdWrapperInfo *)Tcl_Alloc(sizeof(CmdWrapperInfo)); info->proc = proc; info->clientData = clientData; info->nreProc = nreProc; info->deleteProc = deleteProc; info->deleteData = clientData; return Tcl_NRCreateCommand(interp, cmdName, (proc ? cmdWrapperProc : NULL), (nreProc ? cmdWrapperNreProc : NULL), info, cmdWrapperDeleteProc); } Tcl_Command Tcl_NRCreateCommand( Tcl_Interp *interp, /* Token for command interpreter (returned by * previous call to Tcl_CreateInterp). */ const char *cmdName, /* Name of command. If it contains namespace * qualifiers, the new command is put in the * specified namespace; otherwise it is put in * the global namespace. */ Tcl_ObjCmdProc *proc, /* Object-based function to associate with * name, provides direct access for direct * calls. */ Tcl_ObjCmdProc *nreProc, /* Object-based function to associate with * name, provides NR implementation */ void *clientData, /* Arbitrary value to pass to object * function. */ Tcl_CmdDeleteProc *deleteProc) /* If not NULL, gives a function to call when * this command is deleted. */ { Command *cmdPtr = (Command *) Tcl_CreateObjCommand(interp, cmdName, proc, clientData, deleteProc); cmdPtr->nreProc = nreProc; return (Tcl_Command) cmdPtr; } Tcl_Command TclNRCreateCommandInNs( Tcl_Interp *interp, const char *cmdName, Tcl_Namespace *nsPtr, Tcl_ObjCmdProc *proc, Tcl_ObjCmdProc *nreProc, void *clientData, Tcl_CmdDeleteProc *deleteProc) { Command *cmdPtr = (Command *) TclCreateObjCommandInNs(interp, cmdName, nsPtr, proc, clientData, deleteProc); cmdPtr->nreProc = nreProc; return (Tcl_Command) cmdPtr; } /**************************************************************************** * Stuff for the public api ****************************************************************************/ int Tcl_NREvalObj( Tcl_Interp *interp, Tcl_Obj *objPtr, int flags) { return TclNREvalObjEx(interp, objPtr, flags, NULL, INT_MIN); } int Tcl_NREvalObjv( Tcl_Interp *interp, /* Interpreter in which to evaluate the * command. Also used for error reporting. */ Tcl_Size objc, /* Number of words in command. */ Tcl_Obj *const objv[], /* An array of pointers to objects that are * the words that make up the command. */ int flags) /* Collection of OR-ed bits that control the * evaluation of the script. Only * TCL_EVAL_GLOBAL, TCL_EVAL_INVOKE and * TCL_EVAL_NOERR are currently supported. */ { return TclNREvalObjv(interp, objc, objv, flags, NULL); } int Tcl_NRCmdSwap( Tcl_Interp *interp, Tcl_Command cmd, Tcl_Size objc, Tcl_Obj *const objv[], int flags) { return TclNREvalObjv(interp, objc, objv, flags|TCL_EVAL_NOERR, (Command *) cmd); } /***************************************************************************** * Tailcall related code ***************************************************************************** * * The steps of the tailcall dance are as follows: * * 1. when [tailcall] is invoked, it stores the corresponding callback in * the current CallFrame and returns TCL_RETURN * 2. when the CallFrame is popped, it calls TclSetTailcall to store the * callback in the proper NRCommand callback - the spot where the command * that pushed the CallFrame is completely cleaned up * 3. when the NRCommand callback runs, it schedules the tailcall callback * to run immediately after it returns * * One delicate point is to properly define the NRCommand where the tailcall * will execute. There are functions whose purpose is to help define the * precise spot: * TclMarkTailcall: if the NEXT command to be pushed tailcalls, execution * should continue right here * TclSkipTailcall: if the NEXT command to be pushed tailcalls, execution * should continue after the CURRENT command is fully returned ("skip * the next command: we are redirecting to it, tailcalls should run * after WE return") * TclPushTailcallPoint: the search for a tailcalling spot cannot traverse * this point. This is special for OO, as some of the oo constructs * that behave like commands may not push an NRCommand callback. */ void TclMarkTailcall( Tcl_Interp *interp) { Interp *iPtr = (Interp *) interp; if (iPtr->deferredCallbacks == NULL) { TclNRAddCallback(interp, NRCommand, NULL, NULL, NULL, NULL); iPtr->deferredCallbacks = TOP_CB(interp); } } void TclSkipTailcall( Tcl_Interp *interp) { Interp *iPtr = (Interp *) interp; TclMarkTailcall(interp); iPtr->deferredCallbacks->data[1] = INT2PTR(1); } void TclPushTailcallPoint( Tcl_Interp *interp) { TclNRAddCallback(interp, NRCommand, NULL, NULL, NULL, NULL); ((Interp *) interp)->numLevels++; } /* *---------------------------------------------------------------------- * * TclSetTailcall -- * * Splice a tailcall command in the proper spot of the NRE callback * stack, so that it runs at the right time. * *---------------------------------------------------------------------- */ void TclSetTailcall( Tcl_Interp *interp, Tcl_Obj *listPtr) { /* * Find the splicing spot: right before the NRCommand of the thing * being tailcalled. Note that we skip NRCommands marked by a 1 in data[1] * (used by command redirectors). */ NRE_callback *runPtr; for (runPtr = TOP_CB(interp); runPtr; runPtr = runPtr->nextPtr) { if (((runPtr->procPtr) == NRCommand) && !runPtr->data[1]) { break; } } if (!runPtr) { Tcl_Panic("tailcall cannot find the right splicing spot: should not happen!"); } runPtr->data[1] = listPtr; } /* *---------------------------------------------------------------------- * * TclNRTailcallObjCmd -- * * Prepare the tailcall as a list and store it in the current * varFrame. When the frame is later popped the tailcall will be spliced * at the proper place. * * Results: * The first NRCommand callback that is not marked to be skipped is * updated so that its data[1] field contains the tailcall list. * *---------------------------------------------------------------------- */ int TclNRTailcallObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Interp *iPtr = (Interp *) interp; if (objc < 1) { Tcl_WrongNumArgs(interp, 1, objv, "?command? ?arg ...?"); return TCL_ERROR; } if (!(iPtr->varFramePtr->isProcCallFrame & 1)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "tailcall can only be called from a proc, lambda or method", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "TAILCALL", "ILLEGAL", (char *)NULL); return TCL_ERROR; } /* * Invocation without args just clears a scheduled tailcall; invocation * with an argument replaces any previously scheduled tailcall. */ if (iPtr->varFramePtr->tailcallPtr) { Tcl_DecrRefCount(iPtr->varFramePtr->tailcallPtr); iPtr->varFramePtr->tailcallPtr = NULL; } /* * Create the callback to actually evaluate the tailcalled * command, then set it in the varFrame so that PopCallFrame can use it * at the proper time. */ if (objc > 1) { Tcl_Obj *listPtr; Tcl_Namespace *nsPtr = (Tcl_Namespace *) iPtr->varFramePtr->nsPtr; /* * The tailcall data is in a Tcl list: the first element is the * namespace, the rest the command to be tailcalled. */ listPtr = Tcl_NewListObj(objc, objv); TclListObjSetElement(NULL, listPtr, 0, TclNewNamespaceObj(nsPtr)); iPtr->varFramePtr->tailcallPtr = listPtr; } return TCL_RETURN; } /* *---------------------------------------------------------------------- * * TclNRTailcallEval -- * * This NREcallback actually causes the tailcall to be evaluated. * *---------------------------------------------------------------------- */ int TclNRTailcallEval( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; Tcl_Obj *listPtr = (Tcl_Obj *)data[0], *nsObjPtr; Tcl_Namespace *nsPtr; Tcl_Size objc; Tcl_Obj **objv; TclListObjGetElements(interp, listPtr, &objc, &objv); nsObjPtr = objv[0]; if (result == TCL_OK) { result = TclGetNamespaceFromObj(interp, nsObjPtr, &nsPtr); } if (result != TCL_OK) { /* * Tailcall execution was preempted, eg by an intervening catch or by * a now-gone namespace: cleanup and return. */ Tcl_DecrRefCount(listPtr); return result; } /* * Perform the tailcall */ TclMarkTailcall(interp); TclNRAddCallback(interp, TclNRReleaseValues, listPtr, NULL, NULL,NULL); iPtr->lookupNsPtr = (Namespace *) nsPtr; return TclNREvalObjv(interp, objc - 1, objv + 1, 0, NULL); } int TclNRReleaseValues( void *data[], TCL_UNUSED(Tcl_Interp *), int result) { int i = 0; while (i < 4) { if (data[i]) { Tcl_DecrRefCount((Tcl_Obj *) data[i]); } else { break; } i++; } return result; } void Tcl_NRAddCallback( Tcl_Interp *interp, Tcl_NRPostProc *postProcPtr, void *data0, void *data1, void *data2, void *data3) { if (!(postProcPtr)) { Tcl_Panic("Adding a callback without an objProc?!"); } TclNRAddCallback(interp, postProcPtr, data0, data1, data2, data3); } /* *---------------------------------------------------------------------- * * TclNRCoroutineObjCmd -- (and friends) * * This object-based function is invoked to process the "coroutine" Tcl * command. It is heavily based on "apply". * * Results: * A standard Tcl object result value. * * Side effects: * A new procedure gets created. * * ** FIRST EXPERIMENTAL IMPLEMENTATION ** * * It is fairly amateurish and not up to our standards - mainly in terms of * error messages and [info] interaction. Just to test the infrastructure in * teov and tebc. *---------------------------------------------------------------------- */ #define iPtr ((Interp *) interp) int TclNRYieldObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { CoroutineData *corPtr = iPtr->execEnvPtr->corPtr; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?returnValue?"); return TCL_ERROR; } if (!corPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "yield can only be called in a coroutine", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ILLEGAL_YIELD", (char *)NULL); return TCL_ERROR; } if (objc == 2) { Tcl_SetObjResult(interp, objv[1]); } NRE_ASSERT(!COR_IS_SUSPENDED(corPtr)); TclNRAddCallback(interp, TclNRCoroutineActivateCallback, corPtr, clientData, NULL, NULL); return TCL_OK; } int TclNRYieldToObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { CoroutineData *corPtr = iPtr->execEnvPtr->corPtr; Tcl_Namespace *nsPtr = TclGetCurrentNamespace(interp); Tcl_Obj *listPtr; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "command ?arg ...?"); return TCL_ERROR; } if (!corPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "yieldto can only be called in a coroutine", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ILLEGAL_YIELD", (char *)NULL); return TCL_ERROR; } if (((Namespace *) nsPtr)->flags & NS_DYING) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "yieldto called in deleted namespace", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "YIELDTO_IN_DELETED", (char *)NULL); return TCL_ERROR; } /* * Add the tailcall in the caller env, then just yield. * * This is essentially code from TclNRTailcallObjCmd */ listPtr = Tcl_NewListObj(objc, objv); TclListObjSetElement(NULL, listPtr, 0, TclNewNamespaceObj(nsPtr)); /* * Add the callback in the caller's env, then instruct TEBC to yield. */ iPtr->execEnvPtr = corPtr->callerEEPtr; /* Not calling Tcl_IncrRefCount(listPtr) here because listPtr is private */ TclSetTailcall(interp, listPtr); corPtr->yieldPtr = listPtr; iPtr->execEnvPtr = corPtr->eePtr; return TclNRYieldObjCmd(CORO_ACTIVATE_YIELDM, interp, 1, objv); } static int RewindCoroutineCallback( void *data[], Tcl_Interp *interp, TCL_UNUSED(int) /*result*/) { return Tcl_RestoreInterpState(interp, (Tcl_InterpState)data[0]); } static int RewindCoroutine( CoroutineData *corPtr, int result) { Tcl_Interp *interp = corPtr->eePtr->interp; Tcl_InterpState state = Tcl_SaveInterpState(interp, result); NRE_ASSERT(COR_IS_SUSPENDED(corPtr)); NRE_ASSERT(corPtr->eePtr != NULL); NRE_ASSERT(corPtr->eePtr != iPtr->execEnvPtr); corPtr->eePtr->rewind = 1; TclNRAddCallback(interp, RewindCoroutineCallback, state, NULL, NULL, NULL); return TclNRInterpCoroutine(corPtr, interp, 0, NULL); } static void DeleteCoroutine( void *clientData) { CoroutineData *corPtr = (CoroutineData *)clientData; Tcl_Interp *interp = corPtr->eePtr->interp; NRE_callback *rootPtr = TOP_CB(interp); if (COR_IS_SUSPENDED(corPtr)) { TclNRRunCallbacks(interp, RewindCoroutine(corPtr,TCL_OK), rootPtr); } } static int NRCoroutineCallerCallback( void *data[], Tcl_Interp *interp, int result) { CoroutineData *corPtr = (CoroutineData *)data[0]; Command *cmdPtr = corPtr->cmdPtr; /* * This is the last callback in the caller execEnv, right before switching * to the coroutine's */ NRE_ASSERT(iPtr->execEnvPtr == corPtr->callerEEPtr); if (!corPtr->eePtr) { /* * The execEnv was wound down but not deleted for our sake. We finish * the job here. The caller context has already been restored. */ NRE_ASSERT(iPtr->varFramePtr == corPtr->caller.varFramePtr); NRE_ASSERT(iPtr->framePtr == corPtr->caller.framePtr); NRE_ASSERT(iPtr->cmdFramePtr == corPtr->caller.cmdFramePtr); Tcl_Free(corPtr); return result; } NRE_ASSERT(COR_IS_SUSPENDED(corPtr)); SAVE_CONTEXT(corPtr->running); RESTORE_CONTEXT(corPtr->caller); if (cmdPtr->flags & CMD_DYING) { /* * The command was deleted while it was running: wind down the * execEnv, this will do the complete cleanup. RewindCoroutine will * restore both the caller's context and interp state. */ return RewindCoroutine(corPtr, result); } return result; } static int NRCoroutineExitCallback( void *data[], Tcl_Interp *interp, int result) { CoroutineData *corPtr = (CoroutineData *)data[0]; Command *cmdPtr = corPtr->cmdPtr; /* * This runs at the bottom of the Coroutine's execEnv: it will be executed * when the coroutine returns or is wound down, but not when it yields. It * deletes the coroutine and restores the caller's environment. */ NRE_ASSERT(interp == corPtr->eePtr->interp); NRE_ASSERT(TOP_CB(interp) == NULL); NRE_ASSERT(iPtr->execEnvPtr == corPtr->eePtr); NRE_ASSERT(!COR_IS_SUSPENDED(corPtr)); NRE_ASSERT((corPtr->callerEEPtr->callbackPtr->procPtr == NRCoroutineCallerCallback)); cmdPtr->deleteProc = NULL; Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmdPtr); TclCleanupCommandMacro(cmdPtr); corPtr->eePtr->corPtr = NULL; TclDeleteExecEnv(corPtr->eePtr); corPtr->eePtr = NULL; corPtr->stackLevel = NULL; /* * #280. * Drop the coroutine-owned copy of the lineLABCPtr hashtable for literal * command arguments in bytecode. */ Tcl_DeleteHashTable(corPtr->lineLABCPtr); Tcl_Free(corPtr->lineLABCPtr); corPtr->lineLABCPtr = NULL; RESTORE_CONTEXT(corPtr->caller); iPtr->execEnvPtr = corPtr->callerEEPtr; iPtr->numLevels++; return result; } /* *---------------------------------------------------------------------- * * TclNRCoroutineActivateCallback -- * * This is the workhorse for coroutines: it implements both yield and * resume. * * It is important that both be implemented in the same callback: the * detection of the impossibility to suspend due to a busy C-stack relies * on the precise position of a local variable in the stack. We do not * want the compiler to play tricks on us, either by moving things around * or inlining. * *---------------------------------------------------------------------- */ int TclNRCoroutineActivateCallback( void *data[], Tcl_Interp *interp, TCL_UNUSED(int) /*result*/) { CoroutineData *corPtr = (CoroutineData *)data[0]; void *stackLevel = TclGetCStackPtr(); if (!corPtr->stackLevel) { /* * -- Coroutine is suspended -- * Push the callback to restore the caller's context on yield or * return. */ TclNRAddCallback(interp, NRCoroutineCallerCallback, corPtr, NULL, NULL, NULL); /* * Record the stackLevel at which the resume is happening, then swap * the interp's environment to make it suitable to run this coroutine. */ corPtr->stackLevel = stackLevel; Tcl_Size numLevels = corPtr->auxNumLevels; corPtr->auxNumLevels = iPtr->numLevels; SAVE_CONTEXT(corPtr->caller); corPtr->callerEEPtr = iPtr->execEnvPtr; RESTORE_CONTEXT(corPtr->running); iPtr->execEnvPtr = corPtr->eePtr; iPtr->numLevels += numLevels; } else { /* * Coroutine is active: yield */ if (corPtr->stackLevel != stackLevel) { NRE_callback *runPtr; iPtr->execEnvPtr = corPtr->callerEEPtr; if (corPtr->yieldPtr) { for (runPtr = TOP_CB(interp); runPtr; runPtr = runPtr->nextPtr) { if (runPtr->data[1] == corPtr->yieldPtr) { Tcl_DecrRefCount((Tcl_Obj *)runPtr->data[1]); runPtr->data[1] = NULL; corPtr->yieldPtr = NULL; break; } } } iPtr->execEnvPtr = corPtr->eePtr; Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot yield: C stack busy", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "CANT_YIELD", (char *)NULL); return TCL_ERROR; } void *type = data[1]; if (type == CORO_ACTIVATE_YIELD) { corPtr->nargs = COROUTINE_ARGUMENTS_SINGLE_OPTIONAL; } else if (type == CORO_ACTIVATE_YIELDM) { corPtr->nargs = COROUTINE_ARGUMENTS_ARBITRARY; } else { Tcl_Panic("Yield received an option which is not implemented"); } corPtr->yieldPtr = NULL; corPtr->stackLevel = NULL; Tcl_Size numLevels = iPtr->numLevels; iPtr->numLevels = corPtr->auxNumLevels; corPtr->auxNumLevels = numLevels - corPtr->auxNumLevels; iPtr->execEnvPtr = corPtr->callerEEPtr; } return TCL_OK; } /* *---------------------------------------------------------------------- * * CoroTypeObjCmd -- * * Implementation of [::tcl::unsupported::corotype] command. * *---------------------------------------------------------------------- */ static int CoroTypeObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Command *cmdPtr; CoroutineData *corPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "coroName"); return TCL_ERROR; } /* * Look up the coroutine. */ cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objv[1]); if ((!cmdPtr) || (cmdPtr->nreProc != TclNRInterpCoroutine)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "can only get coroutine type of a coroutine", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COROUTINE", TclGetString(objv[1]), (char *)NULL); return TCL_ERROR; } /* * An active coroutine is "active". Can't tell what it might do in the * future. */ corPtr = (CoroutineData *)cmdPtr->objClientData; if (!COR_IS_SUSPENDED(corPtr)) { Tcl_SetObjResult(interp, Tcl_NewStringObj("active", TCL_INDEX_NONE)); return TCL_OK; } /* * Inactive coroutines are classified by the (effective) command used to * suspend them, which matters when you're injecting a probe. */ switch (corPtr->nargs) { case COROUTINE_ARGUMENTS_SINGLE_OPTIONAL: Tcl_SetObjResult(interp, Tcl_NewStringObj("yield", TCL_INDEX_NONE)); return TCL_OK; case COROUTINE_ARGUMENTS_ARBITRARY: Tcl_SetObjResult(interp, Tcl_NewStringObj("yieldto", TCL_INDEX_NONE)); return TCL_OK; default: Tcl_SetObjResult(interp, Tcl_NewStringObj( "unknown coroutine type", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "BAD_TYPE", (char *)NULL); return TCL_ERROR; } } /* *---------------------------------------------------------------------- * * TclNRCoroInjectObjCmd, TclNRCoroProbeObjCmd -- * * Implementation of [coroinject] and [coroprobe] commands. * *---------------------------------------------------------------------- */ static inline CoroutineData * GetCoroutineFromObj( Tcl_Interp *interp, Tcl_Obj *objPtr, const char *errMsg) { /* * How to get a coroutine from its handle. */ Command *cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objPtr); if ((!cmdPtr) || (cmdPtr->nreProc != TclNRInterpCoroutine)) { Tcl_SetObjResult(interp, Tcl_NewStringObj(errMsg, TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COROUTINE", TclGetString(objPtr), (char *)NULL); return NULL; } return (CoroutineData *)cmdPtr->objClientData; } static int TclNRCoroInjectObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { CoroutineData *corPtr; /* * Usage more or less like tailcall: * coroinject coroName cmd ?arg1 arg2 ...? */ if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "coroName cmd ?arg1 arg2 ...?"); return TCL_ERROR; } corPtr = GetCoroutineFromObj(interp, objv[1], "can only inject a command into a coroutine"); if (!corPtr) { return TCL_ERROR; } if (!COR_IS_SUSPENDED(corPtr)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "can only inject a command into a suspended coroutine", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ACTIVE", (char *)NULL); return TCL_ERROR; } /* * Add the callback to the coro's execEnv, so that it is the first thing * to happen when the coro is resumed. */ ExecEnv *savedEEPtr = iPtr->execEnvPtr; iPtr->execEnvPtr = corPtr->eePtr; TclNRAddCallback(interp, InjectHandler, corPtr, Tcl_NewListObj(objc - 2, objv + 2), INT2PTR(corPtr->nargs), NULL); iPtr->execEnvPtr = savedEEPtr; return TCL_OK; } static int TclNRCoroProbeObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { CoroutineData *corPtr; /* * Usage more or less like tailcall: * coroprobe coroName cmd ?arg1 arg2 ...? */ if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "coroName cmd ?arg1 arg2 ...?"); return TCL_ERROR; } corPtr = GetCoroutineFromObj(interp, objv[1], "can only inject a probe command into a coroutine"); if (!corPtr) { return TCL_ERROR; } if (!COR_IS_SUSPENDED(corPtr)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "can only inject a probe command into a suspended coroutine", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ACTIVE", (char *)NULL); return TCL_ERROR; } /* * Add the callback to the coro's execEnv, so that it is the first thing * to happen when the coro is resumed. */ ExecEnv *savedEEPtr = iPtr->execEnvPtr; iPtr->execEnvPtr = corPtr->eePtr; TclNRAddCallback(interp, InjectHandler, corPtr, Tcl_NewListObj(objc - 2, objv + 2), INT2PTR(corPtr->nargs), corPtr); iPtr->execEnvPtr = savedEEPtr; /* * Now we immediately transfer control to the coroutine to run our probe. * TRICKY STUFF copied from the [yield] implementation. * * Push the callback to restore the caller's context on yield back. */ TclNRAddCallback(interp, NRCoroutineCallerCallback, corPtr, NULL, NULL, NULL); /* * Record the stackLevel at which the resume is happening, then swap * the interp's environment to make it suitable to run this coroutine. */ corPtr->stackLevel = &corPtr; Tcl_Size numLevels = corPtr->auxNumLevels; corPtr->auxNumLevels = iPtr->numLevels; /* * Do the actual stack swap. */ SAVE_CONTEXT(corPtr->caller); corPtr->callerEEPtr = iPtr->execEnvPtr; RESTORE_CONTEXT(corPtr->running); iPtr->execEnvPtr = corPtr->eePtr; iPtr->numLevels += numLevels; return TCL_OK; } /* *---------------------------------------------------------------------- * * InjectHandler, InjectHandlerPostProc -- * * Part of the implementation of [coroinject] and [coroprobe]. These are * run inside the context of the coroutine being injected/probed into. * * InjectHandler runs a script (possibly adding arguments) in the context * of the coroutine. The script is specified as a one-shot list (with * reference count equal to 1) in data[1]. This function also arranges * for InjectHandlerPostProc to be the part that runs after the script * completes. * * InjectHandlerPostProc cleans up after InjectHandler (deleting the * list) and, for the [coroprobe] command *only*, yields back to the * caller context (i.e., where [coroprobe] was run). *s *---------------------------------------------------------------------- */ static int InjectHandler( void *data[], Tcl_Interp *interp, TCL_UNUSED(int) /*result*/) { CoroutineData *corPtr = (CoroutineData *)data[0]; Tcl_Obj *listPtr = (Tcl_Obj *)data[1]; Tcl_Size nargs = PTR2INT(data[2]); void *isProbe = data[3]; Tcl_Size objc; Tcl_Obj **objv; if (!isProbe) { /* * If this is [coroinject], add the extra arguments now. */ if (nargs == COROUTINE_ARGUMENTS_SINGLE_OPTIONAL) { Tcl_ListObjAppendElement(NULL, listPtr, Tcl_NewStringObj("yield", TCL_INDEX_NONE)); } else if (nargs == COROUTINE_ARGUMENTS_ARBITRARY) { Tcl_ListObjAppendElement(NULL, listPtr, Tcl_NewStringObj("yieldto", TCL_INDEX_NONE)); } else { /* * I don't think this is reachable... */ Tcl_Obj *nargsObj; TclNewIndexObj(nargsObj, nargs); Tcl_ListObjAppendElement(NULL, listPtr, nargsObj); } Tcl_ListObjAppendElement(NULL, listPtr, Tcl_GetObjResult(interp)); } /* * Call the user's script; we're in the right place. */ Tcl_IncrRefCount(listPtr); TclMarkTailcall(interp); TclNRAddCallback(interp, InjectHandlerPostCall, corPtr, listPtr, INT2PTR(nargs), isProbe); TclListObjGetElements(NULL, listPtr, &objc, &objv); return TclNREvalObjv(interp, objc, objv, 0, NULL); } static int InjectHandlerPostCall( void *data[], Tcl_Interp *interp, int result) { CoroutineData *corPtr = (CoroutineData *)data[0]; Tcl_Obj *listPtr = (Tcl_Obj *)data[1]; Tcl_Size nargs = PTR2INT(data[2]); void *isProbe = data[3]; /* * Delete the command words for what we just executed. */ Tcl_DecrRefCount(listPtr); /* * If we were doing a probe, splice ourselves back out of the stack * cleanly here. General injection should instead just look after itself. * * Code from guts of [yield] implementation. */ if (isProbe) { if (result == TCL_ERROR) { Tcl_AddErrorInfo(interp, "\n (injected coroutine probe command)"); } corPtr->nargs = nargs; corPtr->stackLevel = NULL; Tcl_Size numLevels = iPtr->numLevels; iPtr->numLevels = corPtr->auxNumLevels; corPtr->auxNumLevels = numLevels - corPtr->auxNumLevels; iPtr->execEnvPtr = corPtr->callerEEPtr; } return result; } int TclNRInterpCoroutine( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { CoroutineData *corPtr = (CoroutineData *)clientData; if (!COR_IS_SUSPENDED(corPtr)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "coroutine \"%s\" is already running", TclGetString(objv[0]))); Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "BUSY", (char *)NULL); return TCL_ERROR; } /* * Parse all the arguments to work out what to feed as the result of the * [yield]. TRICKY POINT: objc==0 happens here! It occurs when a coroutine * is deleted! */ switch (corPtr->nargs) { case COROUTINE_ARGUMENTS_SINGLE_OPTIONAL: if (objc == 2) { Tcl_SetObjResult(interp, objv[1]); } else if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?arg?"); return TCL_ERROR; } break; default: if (corPtr->nargs + 1 != objc) { Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong coro nargs; how did we get here? " "not implemented!", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", (char *)NULL); return TCL_ERROR; } /* fallthrough */ case COROUTINE_ARGUMENTS_ARBITRARY: if (objc > 1) { Tcl_SetObjResult(interp, Tcl_NewListObj(objc - 1, objv + 1)); } break; } TclNRAddCallback(interp, TclNRCoroutineActivateCallback, corPtr, NULL, NULL, NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclNRCoroutineObjCmd -- * * Implementation of [coroutine] command; see documentation for * description of what this does. * *---------------------------------------------------------------------- */ int TclNRCoroutineObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Command *cmdPtr; CoroutineData *corPtr; const char *procName, *simpleName; Namespace *nsPtr, *altNsPtr, *cxtNsPtr, *inNsPtr = (Namespace *)TclGetCurrentNamespace(interp); Namespace *lookupNsPtr = iPtr->varFramePtr->nsPtr; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "name cmd ?arg ...?"); return TCL_ERROR; } procName = TclGetString(objv[1]); TclGetNamespaceForQualName(interp, procName, inNsPtr, 0, &nsPtr, &altNsPtr, &cxtNsPtr, &simpleName); if (nsPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't create procedure \"%s\": unknown namespace", procName)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "NAMESPACE", (char *)NULL); return TCL_ERROR; } if (simpleName == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't create procedure \"%s\": bad procedure name", procName)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMMAND", procName, (char *)NULL); return TCL_ERROR; } /* * We ARE creating the coroutine command: allocate the corresponding * struct and create the corresponding command. */ corPtr = (CoroutineData *)Tcl_Alloc(sizeof(CoroutineData)); cmdPtr = (Command *) TclNRCreateCommandInNs(interp, simpleName, (Tcl_Namespace *)nsPtr, /*objProc*/ NULL, TclNRInterpCoroutine, corPtr, DeleteCoroutine); corPtr->cmdPtr = cmdPtr; cmdPtr->refCount++; /* * #280. * Provide the new coroutine with its own copy of the lineLABCPtr * hashtable for literal command arguments in bytecode. Note that * CFWordBC chains are not duplicated, only the entrypoints to them. This * means that in the presence of coroutines each chain is potentially a * tree. Like the chain -> tree conversion of the CmdFrame stack. */ { Tcl_HashSearch hSearch; Tcl_HashEntry *hePtr; corPtr->lineLABCPtr = (Tcl_HashTable *)Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(corPtr->lineLABCPtr, TCL_ONE_WORD_KEYS); for (hePtr = Tcl_FirstHashEntry(iPtr->lineLABCPtr,&hSearch); hePtr; hePtr = Tcl_NextHashEntry(&hSearch)) { int isNew; Tcl_HashEntry *newPtr = Tcl_CreateHashEntry(corPtr->lineLABCPtr, Tcl_GetHashKey(iPtr->lineLABCPtr, hePtr), &isNew); Tcl_SetHashValue(newPtr, Tcl_GetHashValue(hePtr)); } } /* * Create the base context. */ corPtr->running.framePtr = iPtr->rootFramePtr; corPtr->running.varFramePtr = iPtr->rootFramePtr; corPtr->running.cmdFramePtr = NULL; corPtr->running.lineLABCPtr = corPtr->lineLABCPtr; corPtr->stackLevel = NULL; corPtr->auxNumLevels = 0; corPtr->yieldPtr = NULL; /* * Create the coro's execEnv, switch to it to push the exit and coro * command callbacks, then switch back. */ corPtr->eePtr = TclCreateExecEnv(interp, CORO_STACK_INITIAL_SIZE); corPtr->callerEEPtr = iPtr->execEnvPtr; corPtr->eePtr->corPtr = corPtr; SAVE_CONTEXT(corPtr->caller); corPtr->callerEEPtr = iPtr->execEnvPtr; RESTORE_CONTEXT(corPtr->running); iPtr->execEnvPtr = corPtr->eePtr; TclNRAddCallback(interp, NRCoroutineExitCallback, corPtr, NULL, NULL, NULL); /* * Ensure that the command is looked up in the correct namespace. */ iPtr->lookupNsPtr = lookupNsPtr; Tcl_NREvalObj(interp, Tcl_NewListObj(objc - 2, objv + 2), 0); iPtr->numLevels--; SAVE_CONTEXT(corPtr->running); RESTORE_CONTEXT(corPtr->caller); iPtr->execEnvPtr = corPtr->callerEEPtr; /* * Now just resume the coroutine. */ TclNRAddCallback(interp, TclNRCoroutineActivateCallback, corPtr, NULL, NULL, NULL); return TCL_OK; } /* * This is used in the [info] ensemble */ int TclInfoCoroutineCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { CoroutineData *corPtr = iPtr->execEnvPtr->corPtr; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } if (corPtr && !(corPtr->cmdPtr->flags & CMD_DYING)) { Tcl_Obj *namePtr; TclNewObj(namePtr); Tcl_GetCommandFullName(interp, (Tcl_Command) corPtr->cmdPtr, namePtr); Tcl_SetObjResult(interp, namePtr); } return TCL_OK; } #undef iPtr /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * indent-tabs-mode: nil * End: */ tcl9.0.1/generic/tclBinary.c0000644000175000017500000024030114726623136015265 0ustar sergeisergei/* * tclBinary.c -- * * This file contains the implementation of the "binary" Tcl built-in * command and the Tcl binary data object. * * Copyright © 1997 Sun Microsystems, Inc. * Copyright © 1998-1999 Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclTomMath.h" #include #include /* * The following constants are used by GetFormatSpec to indicate various * special conditions in the parsing of a format specifier. */ #define BINARY_ALL -1 /* Use all elements in the argument. */ #define BINARY_NOCOUNT -2 /* No count was specified in format. */ /* * The following flags may be OR'ed together and returned by GetFormatSpec */ #define BINARY_SIGNED 0 /* Field to be read as signed data */ #define BINARY_UNSIGNED 1 /* Field to be read as unsigned data */ /* * The following defines the maximum number of different (integer) numbers * placed in the object cache by 'binary scan' before it bails out and * switches back to Plan A (creating a new object for each value.) * Theoretically, it would be possible to keep the cache about for the values * that are already in it, but that makes the code slower in practice when * overflow happens, and makes little odds the rest of the time (as measured * on my machine.) It is also slower (on the sample I tried at least) to grow * the cache to hold all items we might want to put in it; presumably the * extra cost of managing the memory for the enlarged table outweighs the * benefit from allocating fewer objects. This is probably because as the * number of objects increases, the likelihood of reuse of any particular one * drops, and there is very little gain from larger maximum cache sizes (the * value below is chosen to allow caching to work in full with conversion of * bytes.) - DKF */ #define BINARY_SCAN_MAX_CACHE 260 /* * Prototypes for local procedures defined in this file: */ static void DupProperByteArrayInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr); static int FormatNumber(Tcl_Interp *interp, int type, Tcl_Obj *src, unsigned char **cursorPtr); static void FreeProperByteArrayInternalRep(Tcl_Obj *objPtr); static int GetFormatSpec(const char **formatPtr, char *cmdPtr, Tcl_Size *countPtr, int *flagsPtr); static Tcl_Obj * ScanNumber(unsigned char *buffer, int type, int flags, Tcl_HashTable **numberCachePtr); static int SetByteArrayFromAny(Tcl_Interp *interp, Tcl_Size limit, Tcl_Obj *objPtr); static void UpdateStringOfByteArray(Tcl_Obj *listPtr); static void DeleteScanNumberCache(Tcl_HashTable *numberCachePtr); static int NeedReversing(int format); static void CopyNumber(const void *from, void *to, size_t length, int type); /* Binary ensemble commands */ static Tcl_ObjCmdProc BinaryFormatCmd; static Tcl_ObjCmdProc BinaryScanCmd; /* Binary encoding sub-ensemble commands */ static Tcl_ObjCmdProc BinaryEncodeHex; static Tcl_ObjCmdProc BinaryDecodeHex; static Tcl_ObjCmdProc BinaryEncode64; static Tcl_ObjCmdProc BinaryDecode64; static Tcl_ObjCmdProc BinaryEncodeUu; static Tcl_ObjCmdProc BinaryDecodeUu; /* * The following tables are used by the binary encoders */ static const char HexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; static const char UueDigits[65] = { '`', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\',']', '^', '_', '`' }; static const char B64Digits[65] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '=' }; /* * How to construct the ensembles. */ static const EnsembleImplMap binaryMap[] = { { "format", BinaryFormatCmd, TclCompileBasicMin1ArgCmd, NULL, NULL, 0 }, { "scan", BinaryScanCmd, TclCompileBasicMin2ArgCmd, NULL, NULL, 0 }, { "encode", NULL, NULL, NULL, NULL, 0 }, { "decode", NULL, NULL, NULL, NULL, 0 }, { NULL, NULL, NULL, NULL, NULL, 0 } }; static const EnsembleImplMap encodeMap[] = { { "hex", BinaryEncodeHex, TclCompileBasic1ArgCmd, NULL, NULL, 0 }, { "uuencode", BinaryEncodeUu, NULL, NULL, NULL, 0 }, { "base64", BinaryEncode64, NULL, NULL, NULL, 0 }, { NULL, NULL, NULL, NULL, NULL, 0 } }; static const EnsembleImplMap decodeMap[] = { { "hex", BinaryDecodeHex, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0 }, { "uuencode", BinaryDecodeUu, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0 }, { "base64", BinaryDecode64, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0 }, { NULL, NULL, NULL, NULL, NULL, 0 } }; /* * The following Tcl_ObjType represents an array of bytes. The intent is to * allow arbitrary binary data to pass through Tcl as a Tcl value without loss * or damage. Such values are useful for things like encoded strings or Tk * images to name just two. * * A bytearray is an ordered sequence of bytes. Each byte is an integer value * in the range [0-255]. To be a Tcl value type, we need a way to encode each * value in the value set as a Tcl string. A simple encoding is to * represent each byte value as the same codepoint value. A bytearray of N * bytes is encoded into a Tcl string of N characters where the codepoint of * each character is the value of corresponding byte. This approach creates a * one-to-one map between all bytearray values and a subset of Tcl string * values. Tcl string values outside that subset do no represent any valid * bytearray value. Attempts to treat those values as bytearrays will lead * to errors. See TIP 568 for how this differs from Tcl 8. */ static const Tcl_ObjType properByteArrayType = { "bytearray", FreeProperByteArrayInternalRep, DupProperByteArrayInternalRep, UpdateStringOfByteArray, NULL, TCL_OBJTYPE_V0 }; /* * The following structure is the internal rep for a ByteArray object. Keeps * track of how much memory has been used and how much has been allocated for * the byte array to enable growing and shrinking of the ByteArray object with * fewer mallocs. */ typedef struct { Tcl_Size used; /* The number of bytes used in the byte * array. */ Tcl_Size allocated; /* The amount of space actually allocated * minus 1 byte. */ unsigned char bytes[TCLFLEXARRAY]; /* The array of bytes. The actual size of this * field depends on the 'allocated' field * above. */ } ByteArray; #define BYTEARRAY_MAX_LEN (TCL_SIZE_MAX - (Tcl_Size)offsetof(ByteArray, bytes)) #define BYTEARRAY_SIZE(len) \ ( (len < 0 || BYTEARRAY_MAX_LEN < (len)) \ ? (Tcl_Panic("negative length specified or max size of a Tcl value exceeded"), 0) \ : (offsetof(ByteArray, bytes) + (len)) ) #define GET_BYTEARRAY(irPtr) ((ByteArray *) (irPtr)->twoPtrValue.ptr1) #define SET_BYTEARRAY(irPtr, baPtr) \ (irPtr)->twoPtrValue.ptr1 = (baPtr) int TclIsPureByteArray( Tcl_Obj * objPtr) { return TclHasInternalRep(objPtr, &properByteArrayType); } /* *---------------------------------------------------------------------- * * Tcl_NewByteArrayObj -- * * This procedure is creates a new ByteArray object and initializes it * from the given array of bytes. * * Results: * The newly created object is returned. This object has no initial * string representation. The returned object has a ref count of 0. * * Side effects: * Memory allocated for new object and copy of byte array argument. * *---------------------------------------------------------------------- */ #undef Tcl_NewByteArrayObj Tcl_Obj * Tcl_NewByteArrayObj( const unsigned char *bytes, /* The array of bytes used to initialize the * new object. */ Tcl_Size numBytes) /* Number of bytes in the array, * must be >= 0. */ { #ifdef TCL_MEM_DEBUG return Tcl_DbNewByteArrayObj(bytes, numBytes, "unknown", 0); #else /* if not TCL_MEM_DEBUG */ Tcl_Obj *objPtr; TclNewObj(objPtr); Tcl_SetByteArrayObj(objPtr, bytes, numBytes); return objPtr; #endif /* TCL_MEM_DEBUG */ } /* *---------------------------------------------------------------------- * * Tcl_DbNewByteArrayObj -- * * This procedure is normally called when debugging: i.e., when * TCL_MEM_DEBUG is defined. It is the same as the Tcl_NewByteArrayObj * above except that it calls Tcl_DbCkalloc directly with the file name * and line number from its caller. This simplifies debugging since then * the [memory active] command will report the correct file name and line * number when reporting objects that haven't been freed. * * When TCL_MEM_DEBUG is not defined, this procedure just returns the * result of calling Tcl_NewByteArrayObj. * * Results: * The newly created object is returned. This object has no initial * string representation. The returned object has a ref count of 0. * * Side effects: * Memory allocated for new object and copy of byte array argument. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG Tcl_Obj * Tcl_DbNewByteArrayObj( const unsigned char *bytes, /* The array of bytes used to initialize the * new object. */ Tcl_Size numBytes, /* Number of bytes in the array, * must be >= 0. */ const char *file, /* The name of the source file calling this * procedure; used for debugging. */ int line) /* Line number in the source file; used for * debugging. */ { Tcl_Obj *objPtr; TclDbNewObj(objPtr, file, line); Tcl_SetByteArrayObj(objPtr, bytes, numBytes); return objPtr; } #else /* if not TCL_MEM_DEBUG */ Tcl_Obj * Tcl_DbNewByteArrayObj( const unsigned char *bytes, /* The array of bytes used to initialize the * new object. */ Tcl_Size numBytes, /* Number of bytes in the array, * must be >= 0. */ TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { return Tcl_NewByteArrayObj(bytes, numBytes); } #endif /* TCL_MEM_DEBUG */ /* *--------------------------------------------------------------------------- * * Tcl_SetByteArrayObj -- * * Modify an object to be a ByteArray object and to have the specified * array of bytes as its value. * * Results: * None. * * Side effects: * The object's old string rep and internal rep is freed. Memory * allocated for copy of byte array argument. * *---------------------------------------------------------------------- */ void Tcl_SetByteArrayObj( Tcl_Obj *objPtr, /* Object to initialize as a ByteArray. */ const unsigned char *bytes, /* The array of bytes to use as the new value. * May be NULL even if numBytes > 0. */ Tcl_Size numBytes) /* Number of bytes in the array, * must be >= 0 */ { ByteArray *byteArrayPtr; Tcl_ObjInternalRep ir; if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_SetByteArrayObj"); } TclInvalidateStringRep(objPtr); assert(numBytes >= 0); byteArrayPtr = (ByteArray *)Tcl_Alloc(BYTEARRAY_SIZE(numBytes)); byteArrayPtr->used = numBytes; byteArrayPtr->allocated = numBytes; if ((bytes != NULL) && (numBytes > 0)) { memcpy(byteArrayPtr->bytes, bytes, numBytes); } SET_BYTEARRAY(&ir, byteArrayPtr); Tcl_StoreInternalRep(objPtr, &properByteArrayType, &ir); } /* *---------------------------------------------------------------------- * * TclGetBytesFromObj -- * * Attempt to extract the value from objPtr in the representation * of a byte sequence. On success return the extracted byte sequence. * On failure, return NULL and record error message and code in * interp (if not NULL). * * Results: * NULL or pointer to array of bytes representing the ByteArray object. * Writes number of bytes in array to *numBytesPtr. * *---------------------------------------------------------------------- */ #undef Tcl_GetBytesFromObj unsigned char * Tcl_GetBytesFromObj( Tcl_Interp *interp, /* For error reporting */ Tcl_Obj *objPtr, /* Value to extract from */ Tcl_Size *numBytesPtr) /* If non-NULL, write the number of bytes * in the array here */ { ByteArray *baPtr; const Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(objPtr, &properByteArrayType); if (irPtr == NULL) { if (TCL_ERROR == SetByteArrayFromAny(interp, TCL_INDEX_NONE, objPtr)) { return NULL; } irPtr = TclFetchInternalRep(objPtr, &properByteArrayType); } baPtr = GET_BYTEARRAY(irPtr); if (numBytesPtr != NULL) { *numBytesPtr = baPtr->used; } return baPtr->bytes; } #if !defined(TCL_NO_DEPRECATED) unsigned char * TclGetBytesFromObj( Tcl_Interp *interp, /* For error reporting */ Tcl_Obj *objPtr, /* Value to extract from */ void *numBytesPtr) /* If non-NULL, write the number of bytes * in the array here */ { Tcl_Size numBytes = 0; unsigned char *bytes = Tcl_GetBytesFromObj(interp, objPtr, &numBytes); if (bytes && numBytesPtr) { if (numBytes > INT_MAX) { /* Caller asked for numBytes to be written to an int, but the * value is outside the int range. */ if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "byte sequence length exceeds INT_MAX", -1)); Tcl_SetErrorCode(interp, "TCL", "API", "OUTDATED", (char *)NULL); } return NULL; } else { *(int *)numBytesPtr = (int) numBytes; } } return bytes; } #endif /* *---------------------------------------------------------------------- * * Tcl_SetByteArrayLength -- * * This procedure changes the length of the byte array for this object. * Once the caller has set the length of the array, it is acceptable to * directly modify the bytes in the array up until Tcl_GetStringFromObj() * has been called on this object. * * Results: * The new byte array of the specified length. * * Side effects: * Allocates enough memory for an array of bytes of the requested size. * When growing the array, the old array is copied to the new array; new * bytes are undefined. When shrinking, the old array is truncated to the * specified length. * *---------------------------------------------------------------------- */ unsigned char * Tcl_SetByteArrayLength( Tcl_Obj *objPtr, /* The ByteArray object. */ Tcl_Size numBytes) /* Number of bytes in resized array * Must be >= 0 */ { ByteArray *byteArrayPtr; Tcl_ObjInternalRep *irPtr; assert(numBytes >= 0); if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_SetByteArrayLength"); } irPtr = TclFetchInternalRep(objPtr, &properByteArrayType); if (irPtr == NULL) { if (TCL_ERROR == SetByteArrayFromAny(NULL, numBytes, objPtr)) { return NULL; } irPtr = TclFetchInternalRep(objPtr, &properByteArrayType); } byteArrayPtr = GET_BYTEARRAY(irPtr); if (numBytes > byteArrayPtr->allocated) { byteArrayPtr = (ByteArray *)Tcl_Realloc(byteArrayPtr, BYTEARRAY_SIZE(numBytes)); byteArrayPtr->allocated = numBytes; SET_BYTEARRAY(irPtr, byteArrayPtr); } TclInvalidateStringRep(objPtr); byteArrayPtr->used = numBytes; return byteArrayPtr->bytes; } /* *---------------------------------------------------------------------- * * MakeByteArray -- * * Generate a ByteArray internal rep from the string rep of objPtr. * The generated byte sequence may have no more than limit bytes. * A negative value for limit indicates no limit imposed. If * boolean argument demandProper is true, then no byte sequence should * be output to the caller (write NULL instead). When no bytes sequence * is output and interp is not NULL, leave an error message and error * code in interp explaining why a proper byte sequence could not be * made. * * Results: * Returns a boolean indicating whether the bytes generated (up to * limit bytes) are a proper representation of (a limited prefix of) * the string. Writes a pointer to the generated ByteArray to * *byteArrayPtrPtr. If not NULL it needs to be released with Tcl_Free(). * *---------------------------------------------------------------------- */ static int MakeByteArray( Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size limit, int demandProper, ByteArray **byteArrayPtrPtr) { Tcl_Size length; const char *src = TclGetStringFromObj(objPtr, &length); Tcl_Size numBytes = (limit >= 0 && limit < length) ? limit : length; ByteArray *byteArrayPtr = (ByteArray *)Tcl_Alloc(BYTEARRAY_SIZE(numBytes)); unsigned char *dst = byteArrayPtr->bytes; unsigned char *dstEnd = dst + numBytes; const char *srcEnd = src + length; int proper = 1; for (; src < srcEnd && dst < dstEnd; ) { int ch; int count = TclUtfToUniChar(src, &ch); if (ch > 255) { proper = 0; if (demandProper) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected byte sequence but character %" TCL_Z_MODIFIER "u was '%1s' (U+%06X)", dst - byteArrayPtr->bytes, src, ch)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "BYTES", (char *)NULL); } Tcl_Free(byteArrayPtr); *byteArrayPtrPtr = NULL; return proper; } } src += count; *dst++ = UCHAR(ch); } byteArrayPtr->used = dst - byteArrayPtr->bytes; byteArrayPtr->allocated = numBytes; *byteArrayPtrPtr = byteArrayPtr; return proper; } static Tcl_Obj * TclNarrowToBytes( Tcl_Obj *objPtr) { if (NULL == TclFetchInternalRep(objPtr, &properByteArrayType)) { Tcl_ObjInternalRep ir; ByteArray *byteArrayPtr; if (0 == MakeByteArray(NULL, objPtr, TCL_INDEX_NONE, 0, &byteArrayPtr)) { TclNewObj(objPtr); TclInvalidateStringRep(objPtr); } SET_BYTEARRAY(&ir, byteArrayPtr); Tcl_StoreInternalRep(objPtr, &properByteArrayType, &ir); } Tcl_IncrRefCount(objPtr); return objPtr; } /* *---------------------------------------------------------------------- * * SetByteArrayFromAny -- * * Generate the ByteArray internal rep from the string rep. * * Results: * Tcl return code indicating OK or ERROR. * * Side effects: * A ByteArray struct may be stored as the internal rep of objPtr. * *---------------------------------------------------------------------- */ static int SetByteArrayFromAny( Tcl_Interp *interp, /* For error reporting. */ Tcl_Size limit, /* Create no more than this many bytes */ Tcl_Obj *objPtr) /* The object to convert to type ByteArray. */ { ByteArray *byteArrayPtr; Tcl_ObjInternalRep ir; if (0 == MakeByteArray(interp, objPtr, limit, 1, &byteArrayPtr)) { return TCL_ERROR; } SET_BYTEARRAY(&ir, byteArrayPtr); Tcl_StoreInternalRep(objPtr, &properByteArrayType, &ir); return TCL_OK; } /* *---------------------------------------------------------------------- * * FreeByteArrayInternalRep -- * * Deallocate the storage associated with a ByteArray data object's * internal representation. * * Results: * None. * * Side effects: * Frees memory. * *---------------------------------------------------------------------- */ static void FreeProperByteArrayInternalRep( Tcl_Obj *objPtr) /* Object with internal rep to free. */ { Tcl_Free(GET_BYTEARRAY(TclFetchInternalRep(objPtr, &properByteArrayType))); } /* *---------------------------------------------------------------------- * * DupByteArrayInternalRep -- * * Initialize the internal representation of a ByteArray Tcl_Obj to a * copy of the internal representation of an existing ByteArray object. * * Results: * None. * * Side effects: * Allocates memory. * *---------------------------------------------------------------------- */ static void DupProperByteArrayInternalRep( Tcl_Obj *srcPtr, /* Object with internal rep to copy. */ Tcl_Obj *copyPtr) /* Object with internal rep to set. */ { Tcl_Size length; ByteArray *srcArrayPtr, *copyArrayPtr; Tcl_ObjInternalRep ir; srcArrayPtr = GET_BYTEARRAY(TclFetchInternalRep(srcPtr, &properByteArrayType)); length = srcArrayPtr->used; copyArrayPtr = (ByteArray *)Tcl_Alloc(BYTEARRAY_SIZE(length)); copyArrayPtr->used = length; copyArrayPtr->allocated = length; memcpy(copyArrayPtr->bytes, srcArrayPtr->bytes, length); SET_BYTEARRAY(&ir, copyArrayPtr); Tcl_StoreInternalRep(copyPtr, &properByteArrayType, &ir); } /* *---------------------------------------------------------------------- * * UpdateStringOfByteArray -- * * Update the string representation for a ByteArray data object. * * Results: * None. * * Side effects: * The object's string is set to a valid string that results from the * ByteArray-to-string conversion. * *---------------------------------------------------------------------- */ static void UpdateStringOfByteArray( Tcl_Obj *objPtr) /* ByteArray object whose string rep to * update. */ { const Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(objPtr, &properByteArrayType); ByteArray *byteArrayPtr = GET_BYTEARRAY(irPtr); unsigned char *src = byteArrayPtr->bytes; Tcl_Size i, length = byteArrayPtr->used; Tcl_Size size = length; /* * How much space will string rep need? */ for (i = 0; i < length; i++) { if ((src[i] == 0) || (src[i] > 127)) { size++; } } if (size == length) { char *dst = Tcl_InitStringRep(objPtr, (char *)src, size); TclOOM(dst, size); } else { char *dst = Tcl_InitStringRep(objPtr, NULL, size); TclOOM(dst, size); for (i = 0; i < length; i++) { dst += Tcl_UniCharToUtf(src[i], dst); } } } /* *---------------------------------------------------------------------- * * TclAppendBytesToByteArray -- * * This function appends an array of bytes to a byte array object. Note * that the object *must* be unshared, and the array of bytes *must not* * refer to the object being appended to. * * Results: * None. * * Side effects: * Allocates enough memory for an array of bytes of the requested total * size, or possibly larger. [Bug 2992970] * *---------------------------------------------------------------------- */ void TclAppendBytesToByteArray( Tcl_Obj *objPtr, const unsigned char *bytes, Tcl_Size len) { ByteArray *byteArrayPtr; Tcl_Size needed; Tcl_ObjInternalRep *irPtr; if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object","TclAppendBytesToByteArray"); } if (len < 0) { Tcl_Panic("%s must be called with definite number of bytes to append", "TclAppendBytesToByteArray"); } if (len == 0) { /* * Append zero bytes is a no-op. */ return; } irPtr = TclFetchInternalRep(objPtr, &properByteArrayType); if (irPtr == NULL) { if (TCL_ERROR == SetByteArrayFromAny(NULL, TCL_INDEX_NONE, objPtr)) { Tcl_Panic("attempt to append bytes to non-bytearray"); } irPtr = TclFetchInternalRep(objPtr, &properByteArrayType); } byteArrayPtr = GET_BYTEARRAY(irPtr); /* * If we need to, resize the allocated space in the byte array. */ if ((BYTEARRAY_MAX_LEN - byteArrayPtr->used) < len) { /* Will wrap around !! */ Tcl_Panic("max size of a byte array exceeded"); } needed = byteArrayPtr->used + len; if (needed > byteArrayPtr->allocated) { Tcl_Size newCapacity; byteArrayPtr = (ByteArray *)TclReallocElemsEx(byteArrayPtr, needed, 1, offsetof(ByteArray, bytes), &newCapacity); byteArrayPtr->allocated = newCapacity; SET_BYTEARRAY(irPtr, byteArrayPtr); } if (bytes) { memcpy(byteArrayPtr->bytes + byteArrayPtr->used, bytes, len); } byteArrayPtr->used += len; TclInvalidateStringRep(objPtr); } /* *---------------------------------------------------------------------- * * TclInitBinaryCmd -- * * This function is called to create the "binary" Tcl command. See the * user documentation for details on what it does. * * Results: * A command token for the new command. * * Side effects: * Creates a new binary command as a mapped ensemble. * *---------------------------------------------------------------------- */ Tcl_Command TclInitBinaryCmd( Tcl_Interp *interp) { Tcl_Command binaryEnsemble; binaryEnsemble = TclMakeEnsemble(interp, "binary", binaryMap); TclMakeEnsemble(interp, "binary encode", encodeMap); TclMakeEnsemble(interp, "binary decode", decodeMap); return binaryEnsemble; } /* *---------------------------------------------------------------------- * * BinaryFormatCmd -- * * This procedure implements the "binary format" Tcl command. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int BinaryFormatCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int arg; /* Index of next argument to consume. */ int value = 0; /* Current integer value to be packed. * Initialized to avoid compiler warning. */ char cmd; /* Current format character. */ Tcl_Size count; /* Count associated with current format * character. */ int flags; /* Format field flags */ const char *format; /* Pointer to current position in format * string. */ Tcl_Obj *resultPtr = NULL; /* Object holding result buffer. */ unsigned char *buffer; /* Start of result buffer. */ unsigned char *cursor; /* Current position within result buffer. */ unsigned char *maxPos; /* Greatest position within result buffer that * cursor has visited.*/ const char *errorString; const char *errorValue, *str; Tcl_Size offset, size, length; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "formatString ?arg ...?"); return TCL_ERROR; } /* * To avoid copying the data, we format the string in two passes. The * first pass computes the size of the output buffer. The second pass * places the formatted data into the buffer. */ format = TclGetString(objv[1]); arg = 2; offset = 0; length = 0; while (*format != '\0') { str = format; flags = 0; if (!GetFormatSpec(&format, &cmd, &count, &flags)) { break; } switch (cmd) { case 'a': case 'A': case 'b': case 'B': case 'h': case 'H': /* * For string-type specifiers, the count corresponds to the number * of bytes in a single argument. */ if (arg >= objc) { goto badIndex; } if (count == BINARY_ALL) { if (Tcl_GetBytesFromObj(NULL, objv[arg], &count) == NULL) { count = Tcl_GetCharLength(objv[arg]); } } else if (count == BINARY_NOCOUNT) { count = 1; } arg++; if (cmd == 'a' || cmd == 'A') { offset += count; } else if (cmd == 'b' || cmd == 'B') { offset += (count + 7) / 8; } else { offset += (count + 1) / 2; } break; case 'c': size = 1; goto doNumbers; case 't': case 's': case 'S': size = 2; goto doNumbers; case 'n': case 'i': case 'I': size = 4; goto doNumbers; case 'm': case 'w': case 'W': size = 8; goto doNumbers; case 'r': case 'R': case 'f': size = sizeof(float); goto doNumbers; case 'q': case 'Q': case 'd': size = sizeof(double); doNumbers: if (arg >= objc) { goto badIndex; } /* * For number-type specifiers, the count corresponds to the number * of elements in the list stored in a single argument. If no * count is specified, then the argument is taken as a single * non-list value. */ if (count == BINARY_NOCOUNT) { arg++; count = 1; } else { Tcl_Size listc; Tcl_Obj **listv; /* * The macro evals its args more than once: avoid arg++ */ if (TclListObjLength(interp, objv[arg], &listc) != TCL_OK) { return TCL_ERROR; } if (count == BINARY_ALL) { count = listc; } else if (count > listc) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "number of elements in list does not match count", -1)); return TCL_ERROR; } if (TclListObjGetElements(interp, objv[arg], &listc, &listv) != TCL_OK) { return TCL_ERROR; } arg++; } offset += count*size; break; case 'x': if (count == BINARY_ALL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot use \"*\" in format string with \"x\"", -1)); return TCL_ERROR; } else if (count == BINARY_NOCOUNT) { count = 1; } offset += count; break; case 'X': if (count == BINARY_NOCOUNT) { count = 1; } if ((count > offset) || (count == BINARY_ALL)) { count = offset; } if (offset > length) { length = offset; } offset -= count; break; case '@': if (offset > length) { length = offset; } if (count == BINARY_ALL) { offset = length; } else if (count == BINARY_NOCOUNT) { goto badCount; } else { offset = count; } break; default: errorString = str; goto badField; } } if (offset > length) { length = offset; } if (length == 0) { return TCL_OK; } /* * Prepare the result object by preallocating the calculated number of * bytes and filling with nulls. */ TclNewObj(resultPtr); buffer = Tcl_SetByteArrayLength(resultPtr, length); memset(buffer, 0, length); /* * Pack the data into the result object. Note that we can skip the error * checking during this pass, since we have already parsed the string * once. */ arg = 2; format = TclGetString(objv[1]); cursor = buffer; maxPos = cursor; while (*format != 0) { flags = 0; if (!GetFormatSpec(&format, &cmd, &count, &flags)) { break; } if ((count == 0) && (cmd != '@')) { if (cmd != 'x') { arg++; } continue; } switch (cmd) { case 'a': case 'A': { char pad = (char) (cmd == 'a' ? '\0' : ' '); unsigned char *bytes; Tcl_Obj *copy = TclNarrowToBytes(objv[arg++]); bytes = Tcl_GetBytesFromObj(NULL, copy, &length); if (count == BINARY_ALL) { count = length; } else if (count == BINARY_NOCOUNT) { count = 1; } if (length >= count) { memcpy(cursor, bytes, count); } else { memcpy(cursor, bytes, length); memset(cursor + length, pad, count - length); } cursor += count; Tcl_DecrRefCount(copy); break; } case 'b': case 'B': { unsigned char *last; str = TclGetStringFromObj(objv[arg], &length); arg++; if (count == BINARY_ALL) { count = length; } else if (count == BINARY_NOCOUNT) { count = 1; } last = cursor + ((count + 7) / 8); if (count > length) { count = length; } value = 0; errorString = "binary"; if (cmd == 'B') { for (offset = 0; offset < count; offset++) { value <<= 1; if (str[offset] == '1') { value |= 1; } else if (str[offset] != '0') { errorValue = str; Tcl_DecrRefCount(resultPtr); goto badValue; } if (((offset + 1) % 8) == 0) { *cursor++ = UCHAR(value); value = 0; } } } else { for (offset = 0; offset < count; offset++) { value >>= 1; if (str[offset] == '1') { value |= 128; } else if (str[offset] != '0') { errorValue = str; Tcl_DecrRefCount(resultPtr); goto badValue; } if (!((offset + 1) % 8)) { *cursor++ = UCHAR(value); value = 0; } } } if ((offset % 8) != 0) { if (cmd == 'B') { value <<= 8 - (offset % 8); } else { value >>= 8 - (offset % 8); } *cursor++ = UCHAR(value); } while (cursor < last) { *cursor++ = '\0'; } break; } case 'h': case 'H': { unsigned char *last; int c; str = TclGetStringFromObj(objv[arg], &length); arg++; if (count == BINARY_ALL) { count = length; } else if (count == BINARY_NOCOUNT) { count = 1; } last = cursor + ((count + 1) / 2); if (count > length) { count = length; } value = 0; errorString = "hexadecimal"; if (cmd == 'H') { for (offset = 0; offset < count; offset++) { value <<= 4; if (!isxdigit(UCHAR(str[offset]))) { /* INTL: digit */ errorValue = str; Tcl_DecrRefCount(resultPtr); goto badValue; } c = str[offset] - '0'; if (c > 9) { c += ('0' - 'A') + 10; } if (c > 16) { c += ('A' - 'a'); } value |= (c & 0xF); if (offset % 2) { *cursor++ = (char) value; value = 0; } } } else { for (offset = 0; offset < count; offset++) { value >>= 4; if (!isxdigit(UCHAR(str[offset]))) { /* INTL: digit */ errorValue = str; Tcl_DecrRefCount(resultPtr); goto badValue; } c = str[offset] - '0'; if (c > 9) { c += ('0' - 'A') + 10; } if (c > 16) { c += ('A' - 'a'); } value |= ((c << 4) & 0xF0); if (offset % 2) { *cursor++ = UCHAR(value & 0xFF); value = 0; } } } if (offset % 2) { if (cmd == 'H') { value <<= 4; } else { value >>= 4; } *cursor++ = UCHAR(value); } while (cursor < last) { *cursor++ = '\0'; } break; } case 'c': case 't': case 's': case 'S': case 'n': case 'i': case 'I': case 'm': case 'w': case 'W': case 'r': case 'R': case 'd': case 'q': case 'Q': case 'f': { Tcl_Size listc, i; Tcl_Obj **listv; if (count == BINARY_NOCOUNT) { /* * Note that we are casting away the const-ness of objv, but * this is safe since we aren't going to modify the array. */ listv = (Tcl_Obj **) (objv + arg); listc = 1; count = 1; } else { TclListObjGetElements(interp, objv[arg], &listc, &listv); if (count == BINARY_ALL) { count = listc; } } arg++; for (i = 0; i < count; i++) { if (FormatNumber(interp, cmd, listv[i], &cursor) != TCL_OK) { Tcl_DecrRefCount(resultPtr); return TCL_ERROR; } } break; } case 'x': if (count == BINARY_NOCOUNT) { count = 1; } memset(cursor, 0, count); cursor += count; break; case 'X': if (cursor > maxPos) { maxPos = cursor; } if (count == BINARY_NOCOUNT) { count = 1; } if ((count == BINARY_ALL) || (count > (cursor - buffer))) { cursor = buffer; } else { cursor -= count; } break; case '@': if (cursor > maxPos) { maxPos = cursor; } if (count == BINARY_ALL) { cursor = maxPos; } else { cursor = buffer + count; } break; } } Tcl_SetObjResult(interp, resultPtr); return TCL_OK; badValue: Tcl_ResetResult(interp); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected %s string but got \"%s\" instead", errorString, errorValue)); return TCL_ERROR; badCount: errorString = "missing count for \"@\" field specifier"; goto error; badIndex: errorString = "not enough arguments for all format specifiers"; goto error; badField: { Tcl_UniChar ch = 0; char buf[5] = ""; TclUtfToUniChar(errorString, &ch); buf[Tcl_UniCharToUtf(ch, buf)] = '\0'; Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad field specifier \"%s\"", buf)); return TCL_ERROR; } error: Tcl_SetObjResult(interp, Tcl_NewStringObj(errorString, -1)); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * BinaryScanCmd -- * * This procedure implements the "binary scan" Tcl command. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int BinaryScanCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int arg; /* Index of next argument to consume. */ int value = 0; /* Current integer value to be packed. * Initialized to avoid compiler warning. */ char cmd; /* Current format character. */ Tcl_Size count; /* Count associated with current format * character. */ int flags; /* Format field flags */ const char *format; /* Pointer to current position in format * string. */ Tcl_Obj *resultPtr = NULL; /* Object holding result buffer. */ unsigned char *buffer; /* Start of result buffer. */ const char *errorString; const char *str; Tcl_Size offset, size, length = 0, i; Tcl_Obj *valuePtr, *elementPtr; Tcl_HashTable numberCacheHash; Tcl_HashTable *numberCachePtr; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "value formatString ?varName ...?"); return TCL_ERROR; } buffer = Tcl_GetBytesFromObj(interp, objv[1], &length); if (buffer == NULL) { return TCL_ERROR; } numberCachePtr = &numberCacheHash; Tcl_InitHashTable(numberCachePtr, TCL_ONE_WORD_KEYS); format = TclGetString(objv[2]); arg = 3; offset = 0; while (*format != '\0') { str = format; flags = 0; if (!GetFormatSpec(&format, &cmd, &count, &flags)) { goto done; } switch (cmd) { case 'a': case 'A': case 'C': { unsigned char *src; if (arg >= objc) { DeleteScanNumberCache(numberCachePtr); goto badIndex; } if (count == BINARY_ALL) { count = length - offset; } else { if (count == BINARY_NOCOUNT) { count = 1; } if (count > (length - offset)) { goto done; } } src = buffer + offset; size = count; /* * Apply C string semantics or trim trailing * nulls and spaces, if necessary. */ if (cmd == 'C') { for (i = 0; i < size; i++) { if (src[i] == '\0') { size = i; break; } } } else if (cmd == 'A') { while (size > 0) { if (src[size - 1] != '\0' && src[size - 1] != ' ') { break; } size--; } } /* * Have to do this #ifdef-fery because (as part of defining * Tcl_NewByteArrayObj) we removed the #def that hides this stuff * normally. If this code ever gets copied to another file, it * should be changed back to the simpler version. */ #ifdef TCL_MEM_DEBUG valuePtr = Tcl_DbNewByteArrayObj(src, size, __FILE__, __LINE__); #else valuePtr = Tcl_NewByteArrayObj(src, size); #endif /* TCL_MEM_DEBUG */ resultPtr = Tcl_ObjSetVar2(interp, objv[arg], NULL, valuePtr, TCL_LEAVE_ERR_MSG); arg++; if (resultPtr == NULL) { DeleteScanNumberCache(numberCachePtr); return TCL_ERROR; } offset += count; break; } case 'b': case 'B': { unsigned char *src; char *dest; if (arg >= objc) { DeleteScanNumberCache(numberCachePtr); goto badIndex; } if (count == BINARY_ALL) { count = (length - offset) * 8; } else { if (count == BINARY_NOCOUNT) { count = 1; } if (count > (length - offset) * 8) { goto done; } } src = buffer + offset; TclNewObj(valuePtr); Tcl_SetObjLength(valuePtr, count); dest = TclGetString(valuePtr); if (cmd == 'b') { for (i = 0; i < count; i++) { if (i % 8) { value >>= 1; } else { value = *src++; } *dest++ = (char) ((value & 1) ? '1' : '0'); } } else { for (i = 0; i < count; i++) { if (i % 8) { value <<= 1; } else { value = *src++; } *dest++ = (char) ((value & 0x80) ? '1' : '0'); } } resultPtr = Tcl_ObjSetVar2(interp, objv[arg], NULL, valuePtr, TCL_LEAVE_ERR_MSG); arg++; if (resultPtr == NULL) { DeleteScanNumberCache(numberCachePtr); return TCL_ERROR; } offset += (count + 7) / 8; break; } case 'h': case 'H': { char *dest; unsigned char *src; static const char hexdigit[] = "0123456789abcdef"; if (arg >= objc) { DeleteScanNumberCache(numberCachePtr); goto badIndex; } if (count == BINARY_ALL) { count = (length - offset)*2; } else { if (count == BINARY_NOCOUNT) { count = 1; } if (count > (length - offset)*2) { goto done; } } src = buffer + offset; TclNewObj(valuePtr); Tcl_SetObjLength(valuePtr, count); dest = TclGetString(valuePtr); if (cmd == 'h') { for (i = 0; i < count; i++) { if (i % 2) { value >>= 4; } else { value = *src++; } *dest++ = hexdigit[value & 0xF]; } } else { for (i = 0; i < count; i++) { if (i % 2) { value <<= 4; } else { value = *src++; } *dest++ = hexdigit[(value >> 4) & 0xF]; } } resultPtr = Tcl_ObjSetVar2(interp, objv[arg], NULL, valuePtr, TCL_LEAVE_ERR_MSG); arg++; if (resultPtr == NULL) { DeleteScanNumberCache(numberCachePtr); return TCL_ERROR; } offset += (count + 1) / 2; break; } case 'c': size = 1; goto scanNumber; case 't': case 's': case 'S': size = 2; goto scanNumber; case 'n': case 'i': case 'I': size = 4; goto scanNumber; case 'm': case 'w': case 'W': size = 8; goto scanNumber; case 'r': case 'R': case 'f': size = sizeof(float); goto scanNumber; case 'q': case 'Q': case 'd': { unsigned char *src; size = sizeof(double); /* fall through */ scanNumber: if (arg >= objc) { DeleteScanNumberCache(numberCachePtr); goto badIndex; } if (count == BINARY_NOCOUNT) { if (length < (size + offset)) { goto done; } valuePtr = ScanNumber(buffer+offset, cmd, flags, &numberCachePtr); offset += size; } else { if (count == BINARY_ALL) { count = (length - offset) / size; } if ((length - offset) < (count * size)) { goto done; } TclNewObj(valuePtr); src = buffer + offset; for (i = 0; i < count; i++) { elementPtr = ScanNumber(src, cmd, flags, &numberCachePtr); src += size; Tcl_ListObjAppendElement(NULL, valuePtr, elementPtr); } offset += count * size; } resultPtr = Tcl_ObjSetVar2(interp, objv[arg], NULL, valuePtr, TCL_LEAVE_ERR_MSG); arg++; if (resultPtr == NULL) { DeleteScanNumberCache(numberCachePtr); return TCL_ERROR; } break; } case 'x': if (count == BINARY_NOCOUNT) { count = 1; } if ((count == BINARY_ALL) || (count > (length - offset))) { offset = length; } else { offset += count; } break; case 'X': if (count == BINARY_NOCOUNT) { count = 1; } if ((count == BINARY_ALL) || (count > offset)) { offset = 0; } else { offset -= count; } break; case '@': if (count == BINARY_NOCOUNT) { DeleteScanNumberCache(numberCachePtr); goto badCount; } if ((count == BINARY_ALL) || (count > length)) { offset = length; } else { offset = count; } break; default: DeleteScanNumberCache(numberCachePtr); errorString = str; goto badField; } } /* * Set the result to the last position of the cursor. */ done: Tcl_SetObjResult(interp, Tcl_NewWideIntObj(arg - 3)); DeleteScanNumberCache(numberCachePtr); return TCL_OK; badCount: errorString = "missing count for \"@\" field specifier"; goto error; badIndex: errorString = "not enough arguments for all format specifiers"; goto error; badField: { Tcl_UniChar ch = 0; char buf[5] = ""; TclUtfToUniChar(errorString, &ch); buf[Tcl_UniCharToUtf(ch, buf)] = '\0'; Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad field specifier \"%s\"", buf)); return TCL_ERROR; } error: Tcl_SetObjResult(interp, Tcl_NewStringObj(errorString, -1)); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * GetFormatSpec -- * * This function parses the format strings used in the binary format and * scan commands. * * Results: * Moves the formatPtr to the start of the next command. Returns the * current command character and count in cmdPtr and countPtr. The count * is set to BINARY_ALL if the count character was '*' or BINARY_NOCOUNT * if no count was specified. Returns 1 on success, or 0 if the string * did not have a format specifier. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int GetFormatSpec( const char **formatPtr, /* Pointer to format string. */ char *cmdPtr, /* Pointer to location of command char. */ Tcl_Size *countPtr, /* Pointer to repeat count value. */ int *flagsPtr) /* Pointer to field flags */ { /* * Skip any leading blanks. */ while (**formatPtr == ' ') { (*formatPtr)++; } /* * The string was empty, except for whitespace, so fail. */ if (!(**formatPtr)) { return 0; } /* * Extract the command character and any trailing digits or '*'. */ *cmdPtr = **formatPtr; (*formatPtr)++; if (**formatPtr == 'u') { (*formatPtr)++; *flagsPtr |= BINARY_UNSIGNED; } if (**formatPtr == '*') { (*formatPtr)++; *countPtr = BINARY_ALL; } else if (isdigit(UCHAR(**formatPtr))) { /* INTL: digit */ unsigned long long count; errno = 0; count = strtoull(*formatPtr, (char **) formatPtr, 10); if (errno || (count > TCL_SIZE_MAX)) { *countPtr = TCL_SIZE_MAX; } else { *countPtr = count; } } else { *countPtr = BINARY_NOCOUNT; } return 1; } /* *---------------------------------------------------------------------- * * NeedReversing -- * * This routine determines, if bytes of a number need to be re-ordered, * and returns a numeric code indicating the re-ordering to be done. * This depends on the endianness of the machine and the desired format. * It is in effect a table (whose contents depend on the endianness of * the system) describing whether a value needs reversing or not. Anyone * porting the code to a big-endian platform should take care to make * sure that they define WORDS_BIGENDIAN though this is already done by * configure for the Unix build; little-endian platforms (including * Windows) don't need to do anything. * * Results: * 0 No re-ordering needed. * 1 Reverse the bytes: 01234567 <-> 76543210 (little to big) * 2 Apply this re-ordering: 01234567 <-> 45670123 (Nokia to little) * 3 Apply this re-ordering: 01234567 <-> 32107654 (Nokia to big) * * Side effects: * None * *---------------------------------------------------------------------- */ static int NeedReversing( int format) { switch (format) { /* native floats and doubles: never reverse */ case 'd': case 'f': /* big endian ints: never reverse */ case 'I': case 'S': case 'W': #ifdef WORDS_BIGENDIAN /* native ints: reverse if we're little-endian */ case 'n': case 't': case 'm': /* f: reverse if we're little-endian */ case 'Q': case 'R': #else /* !WORDS_BIGENDIAN */ /* small endian floats: reverse if we're big-endian */ case 'r': #endif /* WORDS_BIGENDIAN */ return 0; #ifdef WORDS_BIGENDIAN /* small endian floats: reverse if we're big-endian */ case 'q': case 'r': #else /* !WORDS_BIGENDIAN */ /* native ints: reverse if we're little-endian */ case 'n': case 't': case 'm': /* f: reverse if we're little-endian */ case 'R': #endif /* WORDS_BIGENDIAN */ /* small endian ints: always reverse */ case 'i': case 's': case 'w': return 1; #ifndef WORDS_BIGENDIAN /* * The Q and q formats need special handling to account for the unusual * byte ordering of 8-byte floats on Nokia 770 systems, which claim to be * little-endian, but also reverse word order. */ case 'Q': if (TclNokia770Doubles()) { return 3; } return 1; case 'q': if (TclNokia770Doubles()) { return 2; } return 0; #endif } Tcl_Panic("unexpected fallthrough"); return 0; } /* *---------------------------------------------------------------------- * * CopyNumber -- * * This routine is called by FormatNumber and ScanNumber to copy a * floating-point number. If required, bytes are reversed while copying. * The behaviour is only fully defined when used with IEEE float and * double values (guaranteed to be 4 and 8 bytes long, respectively.) * * Results: * None * * Side effects: * Copies length bytes * *---------------------------------------------------------------------- */ static void CopyNumber( const void *from, /* source */ void *to, /* destination */ size_t length, /* Number of bytes to copy */ int type) /* What type of thing are we copying? */ { switch (NeedReversing(type)) { case 0: memcpy(to, from, length); break; case 1: { const unsigned char *fromPtr = (const unsigned char *)from; unsigned char *toPtr = (unsigned char *)to; switch (length) { case 4: toPtr[0] = fromPtr[3]; toPtr[1] = fromPtr[2]; toPtr[2] = fromPtr[1]; toPtr[3] = fromPtr[0]; break; case 8: toPtr[0] = fromPtr[7]; toPtr[1] = fromPtr[6]; toPtr[2] = fromPtr[5]; toPtr[3] = fromPtr[4]; toPtr[4] = fromPtr[3]; toPtr[5] = fromPtr[2]; toPtr[6] = fromPtr[1]; toPtr[7] = fromPtr[0]; break; } break; } case 2: { const unsigned char *fromPtr = (const unsigned char *)from; unsigned char *toPtr = (unsigned char *)to; toPtr[0] = fromPtr[4]; toPtr[1] = fromPtr[5]; toPtr[2] = fromPtr[6]; toPtr[3] = fromPtr[7]; toPtr[4] = fromPtr[0]; toPtr[5] = fromPtr[1]; toPtr[6] = fromPtr[2]; toPtr[7] = fromPtr[3]; break; } case 3: { const unsigned char *fromPtr = (const unsigned char *)from; unsigned char *toPtr = (unsigned char *)to; toPtr[0] = fromPtr[3]; toPtr[1] = fromPtr[2]; toPtr[2] = fromPtr[1]; toPtr[3] = fromPtr[0]; toPtr[4] = fromPtr[7]; toPtr[5] = fromPtr[6]; toPtr[6] = fromPtr[5]; toPtr[7] = fromPtr[4]; break; } } } /* *---------------------------------------------------------------------- * * FormatNumber -- * * This routine is called by Tcl_BinaryObjCmd to format a number into a * location pointed at by cursor. * * Results: * A standard Tcl result. * * Side effects: * Moves the cursor to the next location to be written into. * *---------------------------------------------------------------------- */ static int FormatNumber( Tcl_Interp *interp, /* Current interpreter, used to report * errors. */ int type, /* Type of number to format. */ Tcl_Obj *src, /* Number to format. */ unsigned char **cursorPtr) /* Pointer to index into destination buffer. */ { double dvalue; Tcl_WideInt wvalue; float fvalue; switch (type) { case 'd': case 'q': case 'Q': /* * Double-precision floating point values. Tcl_GetDoubleFromObj * returns TCL_ERROR for NaN, but we can check by comparing the * object's type pointer. */ if (Tcl_GetDoubleFromObj(interp, src, &dvalue) != TCL_OK) { const Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(src, &tclDoubleType); if (irPtr == NULL) { return TCL_ERROR; } dvalue = irPtr->doubleValue; } CopyNumber(&dvalue, *cursorPtr, sizeof(double), type); *cursorPtr += sizeof(double); return TCL_OK; case 'f': case 'r': case 'R': /* * Single-precision floating point values. Tcl_GetDoubleFromObj * returns TCL_ERROR for NaN, but we can check by comparing the * object's type pointer. */ if (Tcl_GetDoubleFromObj(interp, src, &dvalue) != TCL_OK) { const Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(src, &tclDoubleType); if (irPtr == NULL) { return TCL_ERROR; } dvalue = irPtr->doubleValue; } /* * Because some compilers will generate floating point exceptions on * an overflow cast (e.g. Borland), we restrict the values to the * valid range for float. */ if (fabs(dvalue) > (double) FLT_MAX) { if (fabs(dvalue) > (FLT_MAX + pow(2, (FLT_MAX_EXP - FLT_MANT_DIG - 1)))) { fvalue = (dvalue >= 0.0) ? INFINITY : -INFINITY; // c99 } else { fvalue = (dvalue >= 0.0) ? FLT_MAX : -FLT_MAX; } } else { fvalue = (float) dvalue; } CopyNumber(&fvalue, *cursorPtr, sizeof(float), type); *cursorPtr += sizeof(float); return TCL_OK; /* * 64-bit integer values. */ case 'w': case 'W': case 'm': if (TclGetWideBitsFromObj(interp, src, &wvalue) != TCL_OK) { return TCL_ERROR; } if (NeedReversing(type)) { *(*cursorPtr)++ = UCHAR(wvalue); *(*cursorPtr)++ = UCHAR(wvalue >> 8); *(*cursorPtr)++ = UCHAR(wvalue >> 16); *(*cursorPtr)++ = UCHAR(wvalue >> 24); *(*cursorPtr)++ = UCHAR(wvalue >> 32); *(*cursorPtr)++ = UCHAR(wvalue >> 40); *(*cursorPtr)++ = UCHAR(wvalue >> 48); *(*cursorPtr)++ = UCHAR(wvalue >> 56); } else { *(*cursorPtr)++ = UCHAR(wvalue >> 56); *(*cursorPtr)++ = UCHAR(wvalue >> 48); *(*cursorPtr)++ = UCHAR(wvalue >> 40); *(*cursorPtr)++ = UCHAR(wvalue >> 32); *(*cursorPtr)++ = UCHAR(wvalue >> 24); *(*cursorPtr)++ = UCHAR(wvalue >> 16); *(*cursorPtr)++ = UCHAR(wvalue >> 8); *(*cursorPtr)++ = UCHAR(wvalue); } return TCL_OK; /* * 32-bit integer values. */ case 'i': case 'I': case 'n': if (TclGetWideBitsFromObj(interp, src, &wvalue) != TCL_OK) { return TCL_ERROR; } if (NeedReversing(type)) { *(*cursorPtr)++ = UCHAR(wvalue); *(*cursorPtr)++ = UCHAR(wvalue >> 8); *(*cursorPtr)++ = UCHAR(wvalue >> 16); *(*cursorPtr)++ = UCHAR(wvalue >> 24); } else { *(*cursorPtr)++ = UCHAR(wvalue >> 24); *(*cursorPtr)++ = UCHAR(wvalue >> 16); *(*cursorPtr)++ = UCHAR(wvalue >> 8); *(*cursorPtr)++ = UCHAR(wvalue); } return TCL_OK; /* * 16-bit integer values. */ case 's': case 'S': case 't': if (TclGetWideBitsFromObj(interp, src, &wvalue) != TCL_OK) { return TCL_ERROR; } if (NeedReversing(type)) { *(*cursorPtr)++ = UCHAR(wvalue); *(*cursorPtr)++ = UCHAR(wvalue >> 8); } else { *(*cursorPtr)++ = UCHAR(wvalue >> 8); *(*cursorPtr)++ = UCHAR(wvalue); } return TCL_OK; /* * 8-bit integer values. */ case 'c': if (TclGetWideBitsFromObj(interp, src, &wvalue) != TCL_OK) { return TCL_ERROR; } *(*cursorPtr)++ = UCHAR(wvalue); return TCL_OK; default: Tcl_Panic("unexpected fallthrough"); return TCL_ERROR; } } /* *---------------------------------------------------------------------- * * ScanNumber -- * * This routine is called by Tcl_BinaryObjCmd to scan a number out of a * buffer. * * Results: * Returns a newly created object containing the scanned number. This * object has a ref count of zero. * * Side effects: * Might reuse an object in the number cache, place a new object in the * cache, or delete the cache and set the reference to it (itself passed * in by reference) to NULL. * *---------------------------------------------------------------------- */ static Tcl_Obj * ScanNumber( unsigned char *buffer, /* Buffer to scan number from. */ int type, /* Format character from "binary scan" */ int flags, /* Format field flags */ Tcl_HashTable **numberCachePtrPtr) /* Place to look for cache of scanned value * objects, or NULL if too many different * numbers have been scanned. */ { long value; float fvalue; double dvalue; Tcl_WideUInt uwvalue; /* * We cannot rely on the compiler to properly sign extend integer values * when we cast from smaller values to larger values because we don't know * the exact size of the integer types. So, we have to handle sign * extension explicitly by checking the high bit and padding with 1's as * needed. This practice is disabled if the BINARY_UNSIGNED flag is set. */ switch (type) { case 'c': /* * Characters need special handling. We want to produce a signed * result, but on some platforms (such as AIX) chars are unsigned. To * deal with this, check for a value that should be negative but * isn't. */ value = buffer[0]; if (!(flags & BINARY_UNSIGNED)) { if (value & 0x80) { value |= -0x100; } } goto returnNumericObject; /* * 16-bit numeric values. We need the sign extension trick (see above) * here as well. */ case 's': case 'S': case 't': if (NeedReversing(type)) { value = (long) (buffer[0] + (buffer[1] << 8)); } else { value = (long) (buffer[1] + (buffer[0] << 8)); } if (!(flags & BINARY_UNSIGNED)) { if (value & 0x8000) { value |= -0x10000; } } goto returnNumericObject; /* * 32-bit numeric values. */ case 'i': case 'I': case 'n': if (NeedReversing(type)) { value = (long) (buffer[0] + (buffer[1] << 8) + (buffer[2] << 16) + (((unsigned long)buffer[3]) << 24)); } else { value = (long) (buffer[3] + (buffer[2] << 8) + (buffer[1] << 16) + (((unsigned long) buffer[0]) << 24)); } /* * Check to see if the value was sign extended properly on systems * where an int is more than 32-bits. * * We avoid caching unsigned integers as we cannot distinguish between * 32bit signed and unsigned in the hash (short and char are ok). */ if (flags & BINARY_UNSIGNED) { return Tcl_NewWideIntObj((Tcl_WideInt)(unsigned long)value); } if ((value & (1U << 31)) && (value > 0)) { value -= (1U << 31); value -= (1U << 31); } returnNumericObject: if (*numberCachePtrPtr == NULL) { return Tcl_NewWideIntObj(value); } else { Tcl_HashTable *tablePtr = *numberCachePtrPtr; Tcl_HashEntry *hPtr; int isNew; hPtr = Tcl_CreateHashEntry(tablePtr, INT2PTR(value), &isNew); if (!isNew) { return (Tcl_Obj *)Tcl_GetHashValue(hPtr); } if (tablePtr->numEntries <= BINARY_SCAN_MAX_CACHE) { Tcl_Obj *objPtr; TclNewIntObj(objPtr, value); Tcl_IncrRefCount(objPtr); Tcl_SetHashValue(hPtr, objPtr); return objPtr; } /* * We've overflowed the cache! Someone's parsing a LOT of varied * binary data in a single call! Bail out by switching back to the * old behaviour for the rest of the scan. * * Note that anyone just using the 'c' conversion (for bytes) * cannot trigger this. */ DeleteScanNumberCache(tablePtr); *numberCachePtrPtr = NULL; return Tcl_NewWideIntObj(value); } /* * Do not cache wide (64-bit) values; they are already too large to * use as keys. */ case 'w': case 'W': case 'm': if (NeedReversing(type)) { uwvalue = ((Tcl_WideUInt) buffer[0]) | (((Tcl_WideUInt) buffer[1]) << 8) | (((Tcl_WideUInt) buffer[2]) << 16) | (((Tcl_WideUInt) buffer[3]) << 24) | (((Tcl_WideUInt) buffer[4]) << 32) | (((Tcl_WideUInt) buffer[5]) << 40) | (((Tcl_WideUInt) buffer[6]) << 48) | (((Tcl_WideUInt) buffer[7]) << 56); } else { uwvalue = ((Tcl_WideUInt) buffer[7]) | (((Tcl_WideUInt) buffer[6]) << 8) | (((Tcl_WideUInt) buffer[5]) << 16) | (((Tcl_WideUInt) buffer[4]) << 24) | (((Tcl_WideUInt) buffer[3]) << 32) | (((Tcl_WideUInt) buffer[2]) << 40) | (((Tcl_WideUInt) buffer[1]) << 48) | (((Tcl_WideUInt) buffer[0]) << 56); } if (flags & BINARY_UNSIGNED) { Tcl_Obj *bigObj = NULL; mp_int big; if (mp_init_u64(&big, uwvalue) == MP_OKAY) { bigObj = Tcl_NewBignumObj(&big); } return bigObj; } return Tcl_NewWideIntObj((Tcl_WideInt) uwvalue); /* * Do not cache double values; they are already too large to use as * keys and the values stored are utterly incompatible with the * integer part of the cache. */ /* * 32-bit IEEE single-precision floating point. */ case 'f': case 'R': case 'r': CopyNumber(buffer, &fvalue, sizeof(float), type); return Tcl_NewDoubleObj(fvalue); /* * 64-bit IEEE double-precision floating point. */ case 'd': case 'Q': case 'q': CopyNumber(buffer, &dvalue, sizeof(double), type); return Tcl_NewDoubleObj(dvalue); } return NULL; } /* *---------------------------------------------------------------------- * * DeleteScanNumberCache -- * * Deletes the hash table acting as a scan number cache. * * Results: * None * * Side effects: * Decrements the reference counts of the objects in the cache. * *---------------------------------------------------------------------- */ static void DeleteScanNumberCache( Tcl_HashTable *numberCachePtr) /* Pointer to the hash table, or NULL (when * the cache has already been deleted due to * overflow.) */ { Tcl_HashEntry *hEntry; Tcl_HashSearch search; if (numberCachePtr == NULL) { return; } hEntry = Tcl_FirstHashEntry(numberCachePtr, &search); while (hEntry != NULL) { Tcl_Obj *value = (Tcl_Obj *)Tcl_GetHashValue(hEntry); if (value != NULL) { Tcl_DecrRefCount(value); } hEntry = Tcl_NextHashEntry(&search); } Tcl_DeleteHashTable(numberCachePtr); } /* * ---------------------------------------------------------------------- * * NOTES -- * * Some measurements show that it is faster to use a table to perform * uuencode and base64 value encoding than to calculate the output (at * least on intel P4 arch). * * Conversely using a lookup table for the decoding is slower than just * calculating the values. We therefore use the fastest of each method. * * Presumably this has to do with the size of the tables. The base64 * decode table is 255 bytes while the encode table is only 65 bytes. The * choice likely depends on CPU memory cache sizes. */ /* *---------------------------------------------------------------------- * * BinaryEncodeHex -- * * Implement the [binary encode hex] binary encoding. clientData must be * a table to convert values to hexadecimal digits. * * Results: * Interp result set to an encoded byte array object * * Side effects: * None * *---------------------------------------------------------------------- */ static int BinaryEncodeHex( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *resultObj = NULL; unsigned char *data = NULL; unsigned char *cursor = NULL; Tcl_Size offset = 0, count = 0; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "data"); return TCL_ERROR; } data = Tcl_GetBytesFromObj(interp, objv[1], &count); if (data == NULL) { return TCL_ERROR; } TclNewObj(resultObj); cursor = Tcl_SetByteArrayLength(resultObj, count * 2); for (offset = 0; offset < count; ++offset) { *cursor++ = HexDigits[(data[offset] >> 4) & 0x0F]; *cursor++ = HexDigits[data[offset] & 0x0F]; } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* *---------------------------------------------------------------------- * * BinaryDecodeHex -- * * Implement the [binary decode hex] binary encoding. * * Results: * Interp result set to an decoded byte array object * * Side effects: * None * *---------------------------------------------------------------------- */ static int BinaryDecodeHex( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *resultObj = NULL; unsigned char *data, *datastart, *dataend; unsigned char *begin, *cursor, c; int i, index, value, pure = 1, strict = 0; Tcl_Size size, cut = 0, count = 0; int ucs4; enum {OPT_STRICT }; static const char *const optStrings[] = { "-strict", NULL }; if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "?options? data"); return TCL_ERROR; } for (i = 1; i < objc - 1; ++i) { if (Tcl_GetIndexFromObj(interp, objv[i], optStrings, "option", TCL_EXACT, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case OPT_STRICT: strict = 1; break; } } TclNewObj(resultObj); data = Tcl_GetBytesFromObj(NULL, objv[objc - 1], &count); if (data == NULL) { pure = 0; data = (unsigned char *)TclGetStringFromObj(objv[objc - 1], &count); } datastart = data; dataend = data + count; size = (count + 1) / 2; begin = cursor = Tcl_SetByteArrayLength(resultObj, size); while (data < dataend) { value = 0; for (i = 0 ; i < 2 ; i++) { if (data >= dataend) { value <<= 4; break; } c = *data++; if (!isxdigit(UCHAR(c))) { if (strict || !TclIsSpaceProc(c)) { goto badChar; } i--; continue; } value <<= 4; c -= '0'; if (c > 9) { c += ('0' - 'A') + 10; } if (c > 16) { c += ('A' - 'a'); } value |= c & 0xF; } if (i < 2) { cut++; } *cursor++ = UCHAR(value); value = 0; } if (cut > size) { cut = size; } Tcl_SetByteArrayLength(resultObj, cursor - begin - cut); Tcl_SetObjResult(interp, resultObj); return TCL_OK; badChar: if (pure) { ucs4 = c; } else { TclUtfToUniChar((const char *)(data - 1), &ucs4); } TclDecrRefCount(resultObj); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid hexadecimal digit \"%c\" (U+%06X) at position %" TCL_Z_MODIFIER "u", ucs4, ucs4, data - datastart - 1)); Tcl_SetErrorCode(interp, "TCL", "BINARY", "DECODE", "INVALID", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * BinaryEncode64 -- * * This procedure implements the "binary encode base64" Tcl command. * * Results: * The base64 encoded value prescribed by the input arguments. * *---------------------------------------------------------------------- */ #define OUTPUT(c) \ do { \ *cursor++ = (c); \ outindex++; \ if (maxlen > 0 && cursor != limit) { \ if (outindex == maxlen) { \ memcpy(cursor, wrapchar, wrapcharlen); \ cursor += wrapcharlen; \ outindex = 0; \ } \ } \ if (cursor > limit) { \ Tcl_Panic("limit hit"); \ } \ } while (0) static int BinaryEncode64( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *resultObj; unsigned char *data, *limit; Tcl_WideInt maxlen = 0; const char *wrapchar = "\n"; Tcl_Size wrapcharlen = 1; int index, purewrap = 1; Tcl_Size i, offset, size, outindex = 0, count = 0; enum { OPT_MAXLEN, OPT_WRAPCHAR }; static const char *const optStrings[] = { "-maxlen", "-wrapchar", NULL }; if (objc < 2 || objc % 2 != 0) { Tcl_WrongNumArgs(interp, 1, objv, "?-maxlen len? ?-wrapchar char? data"); return TCL_ERROR; } for (i = 1; i < objc - 1; i += 2) { if (Tcl_GetIndexFromObj(interp, objv[i], optStrings, "option", TCL_EXACT, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case OPT_MAXLEN: if (TclGetWideIntFromObj(interp, objv[i + 1], &maxlen) != TCL_OK) { return TCL_ERROR; } if (maxlen < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "line length out of range", -1)); Tcl_SetErrorCode(interp, "TCL", "BINARY", "ENCODE", "LINE_LENGTH", (char *)NULL); return TCL_ERROR; } break; case OPT_WRAPCHAR: wrapchar = (const char *)Tcl_GetBytesFromObj(NULL, objv[i + 1], &wrapcharlen); if (wrapchar == NULL) { purewrap = 0; wrapchar = TclGetStringFromObj(objv[i + 1], &wrapcharlen); } break; } } if (wrapcharlen == 0) { maxlen = 0; } data = Tcl_GetBytesFromObj(interp, objv[objc - 1], &count); if (data == NULL) { return TCL_ERROR; } TclNewObj(resultObj); if (count > 0) { unsigned char *cursor = NULL; size = (((count * 4) / 3) + 3) & ~3; /* ensure 4 byte chunks */ if (maxlen > 0 && size > maxlen) { int adjusted = size + (wrapcharlen * (size / maxlen)); if (size % maxlen == 0) { adjusted -= wrapcharlen; } size = adjusted; if (purewrap == 0) { /* Wrapchar is (possibly) non-byte, so build result as * general string, not bytearray */ Tcl_SetObjLength(resultObj, size); cursor = (unsigned char *) TclGetString(resultObj); } } if (cursor == NULL) { cursor = Tcl_SetByteArrayLength(resultObj, size); } limit = cursor + size; for (offset = 0; offset < count; offset += 3) { unsigned char d[3] = {0, 0, 0}; for (i = 0; i < 3 && offset + i < count; ++i) { d[i] = data[offset + i]; } OUTPUT(B64Digits[d[0] >> 2]); OUTPUT(B64Digits[((d[0] & 0x03) << 4) | (d[1] >> 4)]); if (offset + 1 < count) { OUTPUT(B64Digits[((d[1] & 0x0F) << 2) | (d[2] >> 6)]); } else { OUTPUT(B64Digits[64]); } if (offset+2 < count) { OUTPUT(B64Digits[d[2] & 0x3F]); } else { OUTPUT(B64Digits[64]); } } } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } #undef OUTPUT /* *---------------------------------------------------------------------- * * BinaryEncodeUu -- * * This implements the uuencode binary encoding. Input is broken into 6 * bit chunks and a lookup table is used to turn these values into output * characters. This differs from the generic code above in that line * lengths are also encoded. * * Results: * Interp result set to an encoded byte array object * * Side effects: * None * *---------------------------------------------------------------------- */ static int BinaryEncodeUu( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *resultObj; unsigned char *data, *start, *cursor; int i, bits, index; unsigned int n; int lineLength = 61; const unsigned char SingleNewline[] = { UCHAR('\n') }; const unsigned char *wrapchar = SingleNewline; Tcl_Size j, rawLength, offset, count = 0, wrapcharlen = sizeof(SingleNewline); enum { OPT_MAXLEN, OPT_WRAPCHAR }; static const char *const optStrings[] = { "-maxlen", "-wrapchar", NULL }; if (objc < 2 || objc % 2 != 0) { Tcl_WrongNumArgs(interp, 1, objv, "?-maxlen len? ?-wrapchar char? data"); return TCL_ERROR; } for (i = 1; i < objc - 1; i += 2) { if (Tcl_GetIndexFromObj(interp, objv[i], optStrings, "option", TCL_EXACT, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case OPT_MAXLEN: if (Tcl_GetIntFromObj(interp, objv[i + 1], &lineLength) != TCL_OK) { return TCL_ERROR; } if (lineLength < 5 || lineLength > 85) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "line length out of range", -1)); Tcl_SetErrorCode(interp, "TCL", "BINARY", "ENCODE", "LINE_LENGTH", (char *)NULL); return TCL_ERROR; } lineLength = ((lineLength - 1) & -4) + 1; /* 5, 9, 13 ... */ break; case OPT_WRAPCHAR: wrapchar = (const unsigned char *)TclGetStringFromObj( objv[i + 1], &wrapcharlen); { const unsigned char *p = wrapchar; Tcl_Size numBytes = wrapcharlen; while (numBytes) { switch (*p) { case '\t': case '\v': case '\f': case '\r': p++; numBytes--; continue; case '\n': numBytes--; break; default: badwrap: Tcl_SetObjResult(interp, Tcl_NewStringObj( "invalid wrapchar; will defeat decoding", -1)); Tcl_SetErrorCode(interp, "TCL", "BINARY", "ENCODE", "WRAPCHAR", (char *)NULL); return TCL_ERROR; } } if (numBytes) { goto badwrap; } } break; } } /* * Allocate the buffer. This is a little bit too long, but is "good * enough". */ offset = 0; data = Tcl_GetBytesFromObj(interp, objv[objc - 1], &count); if (data == NULL) { return TCL_ERROR; } TclNewObj(resultObj); rawLength = (lineLength - 1) * 3 / 4; start = cursor = Tcl_SetByteArrayLength(resultObj, (lineLength + wrapcharlen) * ((count + (rawLength - 1)) / rawLength)); n = bits = 0; /* * Encode the data. Each output line first has the length of raw data * encoded by the output line described in it by one encoded byte, then * the encoded data follows (encoding each 6 bits as one character). * Encoded lines are always terminated by a newline. */ while (offset < count) { Tcl_Size lineLen = count - offset; if (lineLen > rawLength) { lineLen = rawLength; } *cursor++ = UueDigits[lineLen]; for (i = 0 ; i < lineLen ; i++) { n <<= 8; n |= data[offset++]; for (bits += 8; bits > 6 ; bits -= 6) { *cursor++ = UueDigits[(n >> (bits - 6)) & 0x3F]; } } if (bits > 0) { n <<= 8; *cursor++ = UueDigits[(n >> (bits + 2)) & 0x3F]; bits = 0; } for (j = 0 ; j < wrapcharlen ; ++j) { *cursor++ = wrapchar[j]; } } /* * Fix the length of the output bytearray. */ Tcl_SetByteArrayLength(resultObj, cursor - start); Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* *---------------------------------------------------------------------- * * BinaryDecodeUu -- * * Decode a uuencoded string. * * Results: * Interp result set to an byte array object * * Side effects: * None * *---------------------------------------------------------------------- */ static int BinaryDecodeUu( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *resultObj = NULL; unsigned char *data, *datastart, *dataend; unsigned char *begin, *cursor; int i, index, pure = 1, strict = 0, lineLen; Tcl_Size size, count = 0; unsigned char c; int ucs4; enum { OPT_STRICT }; static const char *const optStrings[] = { "-strict", NULL }; if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "?options? data"); return TCL_ERROR; } for (i = 1; i < objc - 1; ++i) { if (Tcl_GetIndexFromObj(interp, objv[i], optStrings, "option", TCL_EXACT, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case OPT_STRICT: strict = 1; break; } } TclNewObj(resultObj); data = Tcl_GetBytesFromObj(NULL, objv[objc - 1], &count); if (data == NULL) { pure = 0; data = (unsigned char *) TclGetStringFromObj(objv[objc - 1], &count); } datastart = data; dataend = data + count; size = ((count + 3) & ~3) * 3 / 4; begin = cursor = Tcl_SetByteArrayLength(resultObj, size); lineLen = -1; /* * The decoding loop. First, we get the length of line (strictly, the * number of data bytes we expect to generate from the line) we're * processing this time round if it is not already known (i.e., when the * lineLen variable is set to the magic value, -1). */ while (data < dataend) { char d[4] = {0, 0, 0, 0}; if (lineLen < 0) { c = *data++; if (c < 32 || c > 96) { if (strict || !TclIsSpaceProc(c)) { goto badUu; } i--; continue; } lineLen = (c - 32) & 0x3F; } /* * Now we read a four-character grouping. */ for (i = 0 ; i < 4 ; i++) { if (data < dataend) { d[i] = c = *data++; if (c < 32 || c > 96) { if (strict) { if (!TclIsSpaceProc(c)) { goto badUu; } else if (c == '\n') { goto shortUu; } } i--; continue; } } } /* * Translate that grouping into (up to) three binary bytes output. */ if (lineLen > 0) { *cursor++ = (((d[0] - 0x20) & 0x3F) << 2) | (((d[1] - 0x20) & 0x3F) >> 4); if (--lineLen > 0) { *cursor++ = (((d[1] - 0x20) & 0x3F) << 4) | (((d[2] - 0x20) & 0x3F) >> 2); if (--lineLen > 0) { *cursor++ = (((d[2] - 0x20) & 0x3F) << 6) | (((d[3] - 0x20) & 0x3F)); lineLen--; } } } /* * If we've reached the end of the line, skip until we process a * newline. */ if (lineLen == 0 && data < dataend) { lineLen = -1; do { c = *data++; if (c == '\n') { break; } else if (c >= 32 && c <= 96) { data--; break; } else if (strict || !TclIsSpaceProc(c)) { goto badUu; } } while (data < dataend); } } /* * Sanity check, clean up and finish. */ if (lineLen > 0 && strict) { goto shortUu; } Tcl_SetByteArrayLength(resultObj, cursor - begin); Tcl_SetObjResult(interp, resultObj); return TCL_OK; shortUu: Tcl_SetObjResult(interp, Tcl_ObjPrintf("short uuencode data")); Tcl_SetErrorCode(interp, "TCL", "BINARY", "DECODE", "SHORT", (char *)NULL); TclDecrRefCount(resultObj); return TCL_ERROR; badUu: if (pure) { ucs4 = c; } else { TclUtfToUniChar((const char *)(data - 1), &ucs4); } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid uuencode character \"%c\" (U+%06X) at position %" TCL_Z_MODIFIER "u", ucs4, ucs4, data - datastart - 1)); Tcl_SetErrorCode(interp, "TCL", "BINARY", "DECODE", "INVALID", (char *)NULL); TclDecrRefCount(resultObj); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * BinaryDecode64 -- * * Decode a base64 encoded string. * * Results: * Interp result set to an byte array object * * Side effects: * None * *---------------------------------------------------------------------- */ static int BinaryDecode64( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *resultObj = NULL; unsigned char *data, *datastart, *dataend, c = '\0'; unsigned char *begin = NULL; unsigned char *cursor = NULL; int pure = 1, strict = 0; int i, index, cut = 0; Tcl_Size size, count = 0; int ucs4; enum { OPT_STRICT }; static const char *const optStrings[] = { "-strict", NULL }; if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "?options? data"); return TCL_ERROR; } for (i = 1; i < objc - 1; ++i) { if (Tcl_GetIndexFromObj(interp, objv[i], optStrings, "option", TCL_EXACT, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case OPT_STRICT: strict = 1; break; } } TclNewObj(resultObj); data = Tcl_GetBytesFromObj(NULL, objv[objc - 1], &count); if (data == NULL) { pure = 0; data = (unsigned char *) TclGetStringFromObj(objv[objc - 1], &count); } datastart = data; dataend = data + count; size = ((count + 3) & ~3) * 3 / 4; begin = cursor = Tcl_SetByteArrayLength(resultObj, size); while (data < dataend) { unsigned long value = 0; /* * Decode the current block. Each base64 block consists of four input * characters A-Z, a-z, 0-9, +, or /. Each character supplies six bits * of output data, so each block's output is 24 bits (three bytes) in * length. The final block can be shorter by one or two bytes, denoted * by the input ending with one or two ='s, respectively. */ for (i = 0; i < 4; i++) { /* * Get the next input character. At end of input, pad with at most * two ='s. If more than two ='s would be needed, instead discard * the block read thus far. */ if (data < dataend) { c = *data++; } else if (i > 1) { c = '='; } else { if (strict && i <= 1) { /* * Single resp. unfulfilled char (each 4th next single * char) is rather bad64 error case in strict mode. */ goto bad64; } cut += 3; break; } /* * Load the character into the block value. Handle ='s specially * because they're only valid as the last character or two of the * final block of input. Unless strict mode is enabled, skip any * input whitespace characters. */ if (cut) { if (c == '=' && i > 1) { value <<= 6; cut++; } else if (!strict) { i--; } else { goto bad64; } } else if (c >= 'A' && c <= 'Z') { value = (value << 6) | ((c - 'A') & 0x3F); } else if (c >= 'a' && c <= 'z') { value = (value << 6) | ((c - 'a' + 26) & 0x3F); } else if (c >= '0' && c <= '9') { value = (value << 6) | ((c - '0' + 52) & 0x3F); } else if (c == '+') { value = (value << 6) | 0x3E; } else if (c == '/') { value = (value << 6) | 0x3F; } else if (c == '=' && (!strict || i > 1)) { /* * "=" and "a=" is rather bad64 error case in strict mode. */ value <<= 6; if (i) { cut++; } } else if (strict) { goto bad64; } else { i--; } } *cursor++ = UCHAR((value >> 16) & 0xFF); *cursor++ = UCHAR((value >> 8) & 0xFF); *cursor++ = UCHAR(value & 0xFF); /* * Since = is only valid within the final block, if it was encountered * but there are still more input characters, confirm that strict mode * is off and all subsequent characters are whitespace. */ if (cut && data < dataend) { if (strict) { goto bad64; } } } Tcl_SetByteArrayLength(resultObj, cursor - begin - cut); Tcl_SetObjResult(interp, resultObj); return TCL_OK; bad64: if (pure) { ucs4 = c; } else { /* The decoder is byte-oriented. If we saw a byte that's not a * valid member of the base64 alphabet, it could be the lead byte * of a multi-byte character. */ /* Safe because we know data is NUL-terminated */ TclUtfToUniChar((const char *)(data - 1), &ucs4); } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid base64 character \"%c\" (U+%06X) at position %" TCL_Z_MODIFIER "u", ucs4, ucs4, data - datastart - 1)); Tcl_SetErrorCode(interp, "TCL", "BINARY", "DECODE", "INVALID", (char *)NULL); TclDecrRefCount(resultObj); return TCL_ERROR; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclCkalloc.c0000644000175000017500000010746714726623136015430 0ustar sergeisergei/* * tclCkalloc.c -- * * Interface to malloc and free that provides support for debugging * problems involving overwritten, double freeing memory and loss of * memory. * * Copyright © 1991-1994 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * Copyright © 1998-1999 Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * * This code contributed by Karl Lehenbauer and Mark Diekhans */ #include "tclInt.h" #include #define FALSE 0 #define TRUE 1 #undef Tcl_Alloc #undef Tcl_Free #undef Tcl_Realloc #undef Tcl_AttemptAlloc #undef Tcl_AttemptRealloc #ifdef TCL_MEM_DEBUG /* * One of the following structures is allocated each time the * "memory tag" command is invoked, to hold the current tag. */ typedef struct { size_t refCount; /* Number of mem_headers referencing this * tag. */ char string[TCLFLEXARRAY]; /* Actual size of string will be as large as * needed for actual tag. This must be the * last field in the structure. */ } MemTag; #define TAG_SIZE(bytesInString) ((offsetof(MemTag, string) + 1U) + (bytesInString)) static MemTag *curTagPtr = NULL;/* Tag to use in all future mem_headers (set * by "memory tag" command). */ /* * One of the following structures is allocated just before each dynamically * allocated chunk of memory, both to record information about the chunk and * to help detect chunk under-runs. */ #define LOW_GUARD_SIZE (8 + (32 - (sizeof(size_t) + sizeof(int)))%8) struct mem_header { struct mem_header *flink; struct mem_header *blink; MemTag *tagPtr; /* Tag from "memory tag" command; may be * NULL. */ const char *file; size_t length; int line; unsigned char low_guard[LOW_GUARD_SIZE]; /* Aligns body on 8-byte boundary, plus * provides at least 8 additional guard bytes * to detect underruns. */ char body[TCLFLEXARRAY]; /* First byte of client's space. Actual size * of this field will be larger than one. */ }; static struct mem_header *allocHead = NULL; /* List of allocated structures */ #define GUARD_VALUE 0x61 /* * The following macro determines the amount of guard space *above* each chunk * of memory. */ #define HIGH_GUARD_SIZE 8 /* * The following macro computes the offset of the "body" field within * mem_header. It is used to get back to the header pointer from the body * pointer that's used by clients. */ #define BODY_OFFSET \ ((size_t) (&((struct mem_header *) 0)->body)) static size_t total_mallocs = 0; static size_t total_frees = 0; static size_t current_bytes_malloced = 0; static size_t maximum_bytes_malloced = 0; static size_t current_malloc_packets = 0; static size_t maximum_malloc_packets = 0; static size_t break_on_malloc = 0; static size_t trace_on_at_malloc = 0; static int alloc_tracing = FALSE; static int init_malloced_bodies = TRUE; #ifdef MEM_VALIDATE static int validate_memory = TRUE; #else static int validate_memory = FALSE; #endif /* * The following variable indicates to TclFinalizeMemorySubsystem() that it * should dump out the state of memory before exiting. If the value is * non-NULL, it gives the name of the file in which to dump memory usage * information. */ char *tclMemDumpFileName = NULL; static char *onExitMemDumpFileName = NULL; static char dumpFile[100]; /* Records where to dump memory allocation * information. */ /* * Mutex to serialize allocations. This is a low-level mutex that must be * explicitly initialized. This is necessary because the self initializing * mutexes use Tcl_Alloc... */ static Tcl_Mutex *ckallocMutexPtr; static int ckallocInit = 0; /* *---------------------------------------------------------------------- * * TclInitDbCkalloc -- * * Initialize the locks used by the allocator. This is only appropriate * to call in a single threaded environment, such as during * Tcl_InitSubsystems. * *---------------------------------------------------------------------- */ void TclInitDbCkalloc(void) { if (!ckallocInit) { ckallocInit = 1; ckallocMutexPtr = Tcl_GetAllocMutex(); #if !TCL_THREADS /* Silence compiler warning */ (void)ckallocMutexPtr; #endif } } /* *---------------------------------------------------------------------- * * TclDumpMemoryInfo -- * * Display the global memory management statistics. * *---------------------------------------------------------------------- */ int TclDumpMemoryInfo( void *clientData, int flags) { char buf[1024]; if (clientData == NULL) { return 0; } snprintf(buf, sizeof(buf), "total mallocs %10" TCL_Z_MODIFIER "u\n" "total frees %10" TCL_Z_MODIFIER "u\n" "current packets allocated %10" TCL_Z_MODIFIER "u\n" "current bytes allocated %10" TCL_Z_MODIFIER "u\n" "maximum packets allocated %10" TCL_Z_MODIFIER "u\n" "maximum bytes allocated %10" TCL_Z_MODIFIER "u\n", total_mallocs, total_frees, current_malloc_packets, current_bytes_malloced, maximum_malloc_packets, maximum_bytes_malloced); if (flags == 0) { fprintf((FILE *)clientData, "%s", buf); } else { /* Assume objPtr to append to */ Tcl_AppendToObj((Tcl_Obj *) clientData, buf, -1); } return 1; } /* *---------------------------------------------------------------------- * * ValidateMemory -- * * Validate memory guard zones for a particular chunk of allocated * memory. * * Results: * None. * * Side effects: * Prints validation information about the allocated memory to stderr. * *---------------------------------------------------------------------- */ static void ValidateMemory( struct mem_header *memHeaderP, /* Memory chunk to validate */ const char *file, /* File containing the call to * Tcl_ValidateAllMemory */ int line, /* Line number of call to * Tcl_ValidateAllMemory */ int nukeGuards) /* If non-zero, indicates that the memory * guards are to be reset to 0 after they have * been printed */ { unsigned char *hiPtr; size_t idx; int guard_failed = FALSE; int byte; for (idx = 0; idx < LOW_GUARD_SIZE; idx++) { byte = *(memHeaderP->low_guard + idx); if (byte != GUARD_VALUE) { guard_failed = TRUE; fflush(stdout); byte &= 0xFF; fprintf(stderr, "low guard byte %" TCL_Z_MODIFIER "u is 0x%x \t%c\n", idx, byte, (isprint(UCHAR(byte)) ? byte : ' ')); /* INTL: bytes */ } } if (guard_failed) { TclDumpMemoryInfo(stderr, 0); fprintf(stderr, "low guard failed at %p, %s %d\n", memHeaderP->body, file, line); fflush(stderr); /* In case name pointer is bad. */ fprintf(stderr, "%" TCL_Z_MODIFIER "u bytes allocated at (%s %d)\n", memHeaderP->length, memHeaderP->file, memHeaderP->line); Tcl_Panic("Memory validation failure"); } hiPtr = (unsigned char *)memHeaderP->body + memHeaderP->length; for (idx = 0; idx < HIGH_GUARD_SIZE; idx++) { byte = hiPtr[idx]; if (byte != GUARD_VALUE) { guard_failed = TRUE; fflush(stdout); byte &= 0xFF; fprintf(stderr, "hi guard byte %" TCL_Z_MODIFIER "u is 0x%x \t%c\n", idx, byte, (isprint(UCHAR(byte)) ? byte : ' ')); /* INTL: bytes */ } } if (guard_failed) { TclDumpMemoryInfo(stderr, 0); fprintf(stderr, "high guard failed at %p, %s %d\n", memHeaderP->body, file, line); fflush(stderr); /* In case name pointer is bad. */ fprintf(stderr, "%" TCL_Z_MODIFIER "u bytes allocated at (%s %d)\n", memHeaderP->length, memHeaderP->file, memHeaderP->line); Tcl_Panic("Memory validation failure"); } if (nukeGuards) { memset(memHeaderP->low_guard, 0, LOW_GUARD_SIZE); memset(hiPtr, 0, HIGH_GUARD_SIZE); } } /* *---------------------------------------------------------------------- * * Tcl_ValidateAllMemory -- * * Validate memory guard regions for all allocated memory. * * Results: * None. * * Side effects: * Displays memory validation information to stderr. * *---------------------------------------------------------------------- */ void Tcl_ValidateAllMemory( const char *file, /* File from which Tcl_ValidateAllMemory was * called. */ int line) /* Line number of call to * Tcl_ValidateAllMemory */ { struct mem_header *memScanP; if (!ckallocInit) { TclInitDbCkalloc(); } Tcl_MutexLock(ckallocMutexPtr); for (memScanP = allocHead; memScanP != NULL; memScanP = memScanP->flink) { ValidateMemory(memScanP, file, line, FALSE); } Tcl_MutexUnlock(ckallocMutexPtr); } /* *---------------------------------------------------------------------- * * Tcl_DumpActiveMemory -- * * Displays all allocated memory to a file; if no filename is given, * information will be written to stderr. * * Results: * Return TCL_ERROR if an error accessing the file occurs, `errno' will * have the file error number left in it. * *---------------------------------------------------------------------- */ int Tcl_DumpActiveMemory( const char *fileName) /* Name of the file to write info to */ { FILE *fileP; struct mem_header *memScanP; char *address; if (fileName == NULL) { fileP = stderr; } else { fileP = fopen(fileName, "w"); if (fileP == NULL) { return TCL_ERROR; } } Tcl_MutexLock(ckallocMutexPtr); for (memScanP = allocHead; memScanP != NULL; memScanP = memScanP->flink) { address = &memScanP->body[0]; fprintf(fileP, "%p - %p %" TCL_Z_MODIFIER "u @ %s %d %s", address, address + memScanP->length - 1, memScanP->length, memScanP->file, memScanP->line, (memScanP->tagPtr == NULL) ? "" : memScanP->tagPtr->string); (void) fputc('\n', fileP); } Tcl_MutexUnlock(ckallocMutexPtr); if (fileP != stderr) { fclose(fileP); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_DbCkalloc - debugging Tcl_Alloc * * Allocate the requested amount of space plus some extra for guard bands * at both ends of the request, plus a size, panicking if there isn't * enough space, then write in the guard bands and return the address of * the space in the middle that the user asked for. * * The second and third arguments are file and line, these contain the * filename and line number corresponding to the caller. These are sent * by the Tcl_Alloc macro; it uses the preprocessor autodefines __FILE__ * and __LINE__. * *---------------------------------------------------------------------- */ void * Tcl_DbCkalloc( size_t size, const char *file, int line) { struct mem_header *result = NULL; if (validate_memory) { Tcl_ValidateAllMemory(file, line); } /* Don't let size argument to TclpAlloc overflow */ if (size <= (size_t)-2 - offsetof(struct mem_header, body) - HIGH_GUARD_SIZE) { result = (struct mem_header *) TclpAlloc(size + offsetof(struct mem_header, body) + 1U + HIGH_GUARD_SIZE); } if (result == NULL) { fflush(stdout); TclDumpMemoryInfo(stderr, 0); Tcl_Panic("unable to alloc %" TCL_Z_MODIFIER "u bytes, %s line %d", size, file, line); } /* * Fill in guard zones and size. Also initialize the contents of the block * with bogus bytes to detect uses of initialized data. Link into * allocated list. */ if (init_malloced_bodies) { memset(result, GUARD_VALUE, offsetof(struct mem_header, body) + 1U + HIGH_GUARD_SIZE + size); } else { memset(result->low_guard, GUARD_VALUE, LOW_GUARD_SIZE); memset(result->body + size, GUARD_VALUE, HIGH_GUARD_SIZE); } if (!ckallocInit) { TclInitDbCkalloc(); } Tcl_MutexLock(ckallocMutexPtr); result->length = size; result->tagPtr = curTagPtr; if (curTagPtr != NULL) { curTagPtr->refCount++; } result->file = file; result->line = line; result->flink = allocHead; result->blink = NULL; if (allocHead != NULL) { allocHead->blink = result; } allocHead = result; total_mallocs++; if (trace_on_at_malloc && (total_mallocs >= trace_on_at_malloc)) { (void) fflush(stdout); fprintf(stderr, "reached malloc trace enable point (%" TCL_Z_MODIFIER "u)\n", total_mallocs); fflush(stderr); alloc_tracing = TRUE; trace_on_at_malloc = 0; } if (alloc_tracing) { fprintf(stderr,"Tcl_Alloc %p %" TCL_Z_MODIFIER "u %s %d\n", result->body, size, file, line); } if (break_on_malloc && (total_mallocs >= break_on_malloc)) { break_on_malloc = 0; (void) fflush(stdout); Tcl_Panic("reached malloc break limit (%" TCL_Z_MODIFIER "u)", total_mallocs); } current_malloc_packets++; if (current_malloc_packets > maximum_malloc_packets) { maximum_malloc_packets = current_malloc_packets; } current_bytes_malloced += size; if (current_bytes_malloced > maximum_bytes_malloced) { maximum_bytes_malloced = current_bytes_malloced; } Tcl_MutexUnlock(ckallocMutexPtr); return result->body; } void * Tcl_AttemptDbCkalloc( size_t size, const char *file, int line) { struct mem_header *result = NULL; if (validate_memory) { Tcl_ValidateAllMemory(file, line); } /* Don't let size argument to TclpAlloc overflow */ if (size <= (size_t)-2 - offsetof(struct mem_header, body) - HIGH_GUARD_SIZE) { result = (struct mem_header *) TclpAlloc(size + offsetof(struct mem_header, body) + 1U + HIGH_GUARD_SIZE); } if (result == NULL) { fflush(stdout); TclDumpMemoryInfo(stderr, 0); return NULL; } /* * Fill in guard zones and size. Also initialize the contents of the block * with bogus bytes to detect uses of initialized data. Link into * allocated list. */ if (init_malloced_bodies) { memset(result, GUARD_VALUE, offsetof(struct mem_header, body) + 1U + HIGH_GUARD_SIZE + size); } else { memset(result->low_guard, GUARD_VALUE, LOW_GUARD_SIZE); memset(result->body + size, GUARD_VALUE, HIGH_GUARD_SIZE); } if (!ckallocInit) { TclInitDbCkalloc(); } Tcl_MutexLock(ckallocMutexPtr); result->length = size; result->tagPtr = curTagPtr; if (curTagPtr != NULL) { curTagPtr->refCount++; } result->file = file; result->line = line; result->flink = allocHead; result->blink = NULL; if (allocHead != NULL) { allocHead->blink = result; } allocHead = result; total_mallocs++; if (trace_on_at_malloc && (total_mallocs >= trace_on_at_malloc)) { (void) fflush(stdout); fprintf(stderr, "reached malloc trace enable point (%" TCL_Z_MODIFIER "u)\n", total_mallocs); fflush(stderr); alloc_tracing = TRUE; trace_on_at_malloc = 0; } if (alloc_tracing) { fprintf(stderr,"Tcl_Alloc %p %" TCL_Z_MODIFIER "u %s %d\n", result->body, size, file, line); } if (break_on_malloc && (total_mallocs >= break_on_malloc)) { break_on_malloc = 0; (void) fflush(stdout); Tcl_Panic("reached malloc break limit (%" TCL_Z_MODIFIER "u)", total_mallocs); } current_malloc_packets++; if (current_malloc_packets > maximum_malloc_packets) { maximum_malloc_packets = current_malloc_packets; } current_bytes_malloced += size; if (current_bytes_malloced > maximum_bytes_malloced) { maximum_bytes_malloced = current_bytes_malloced; } Tcl_MutexUnlock(ckallocMutexPtr); return result->body; } /* *---------------------------------------------------------------------- * * Tcl_DbCkfree - debugging Tcl_Free * * Verify that the low and high guards are intact, and if so then free * the buffer else Tcl_Panic. * * The guards are erased after being checked to catch duplicate frees. * * The second and third arguments are file and line, these contain the * filename and line number corresponding to the caller. These are sent * by the Tcl_Free macro; it uses the preprocessor autodefines __FILE__ and * __LINE__. * *---------------------------------------------------------------------- */ void Tcl_DbCkfree( void *ptr, const char *file, int line) { struct mem_header *memp; if (ptr == NULL) { return; } /* * The following cast is *very* tricky. Must convert the pointer to an * integer before doing arithmetic on it, because otherwise the arithmetic * will be done differently (and incorrectly) on word-addressed machines * such as Crays (will subtract only bytes, even though BODY_OFFSET is in * words on these machines). */ memp = (struct mem_header *) (((size_t) ptr) - BODY_OFFSET); if (alloc_tracing) { fprintf(stderr, "Tcl_Free %p %" TCL_Z_MODIFIER "u %s %d\n", memp->body, memp->length, file, line); } if (validate_memory) { Tcl_ValidateAllMemory(file, line); } Tcl_MutexLock(ckallocMutexPtr); ValidateMemory(memp, file, line, TRUE); if (init_malloced_bodies) { memset(ptr, GUARD_VALUE, memp->length); } total_frees++; current_malloc_packets--; current_bytes_malloced -= memp->length; if (memp->tagPtr != NULL) { if ((memp->tagPtr->refCount-- <= 1) && (curTagPtr != memp->tagPtr)) { TclpFree(memp->tagPtr); } } /* * Delink from allocated list */ if (memp->flink != NULL) { memp->flink->blink = memp->blink; } if (memp->blink != NULL) { memp->blink->flink = memp->flink; } if (allocHead == memp) { allocHead = memp->flink; } TclpFree(memp); Tcl_MutexUnlock(ckallocMutexPtr); } /* *-------------------------------------------------------------------- * * Tcl_DbCkrealloc - debugging Tcl_Realloc * * Reallocate a chunk of memory by allocating a new one of the right * size, copying the old data to the new location, and then freeing the * old memory space, using all the memory checking features of this * package. * *-------------------------------------------------------------------- */ void * Tcl_DbCkrealloc( void *ptr, size_t size, const char *file, int line) { char *newPtr; size_t copySize; struct mem_header *memp; if (ptr == NULL) { return Tcl_DbCkalloc(size, file, line); } /* * See comment from Tcl_DbCkfree before you change the following line. */ memp = (struct mem_header *) (((size_t) ptr) - BODY_OFFSET); copySize = size; if (copySize > memp->length) { copySize = memp->length; } newPtr = (char *)Tcl_DbCkalloc(size, file, line); memcpy(newPtr, ptr, copySize); Tcl_DbCkfree(ptr, file, line); return newPtr; } void * Tcl_AttemptDbCkrealloc( void *ptr, size_t size, const char *file, int line) { char *newPtr; size_t copySize; struct mem_header *memp; if (ptr == NULL) { return Tcl_AttemptDbCkalloc(size, file, line); } /* * See comment from Tcl_DbCkfree before you change the following line. */ memp = (struct mem_header *) (((size_t) ptr) - BODY_OFFSET); copySize = size; if (copySize > memp->length) { copySize = memp->length; } newPtr = (char *)Tcl_AttemptDbCkalloc(size, file, line); if (newPtr == NULL) { return NULL; } memcpy(newPtr, ptr, copySize); Tcl_DbCkfree(ptr, file, line); return newPtr; } /* *---------------------------------------------------------------------- * * Tcl_Alloc, et al. -- * * These functions are defined in terms of the debugging versions when * TCL_MEM_DEBUG is set. * * Results: * Same as the debug versions. * * Side effects: * Same as the debug versions. * *---------------------------------------------------------------------- */ void * Tcl_Alloc( size_t size) { return Tcl_DbCkalloc(size, "unknown", 0); } void * Tcl_AttemptAlloc( size_t size) { return Tcl_AttemptDbCkalloc(size, "unknown", 0); } void Tcl_Free( void *ptr) { Tcl_DbCkfree(ptr, "unknown", 0); } void * Tcl_Realloc( void *ptr, size_t size) { return Tcl_DbCkrealloc(ptr, size, "unknown", 0); } void * Tcl_AttemptRealloc( void *ptr, size_t size) { return Tcl_AttemptDbCkrealloc(ptr, size, "unknown", 0); } /* *---------------------------------------------------------------------- * * MemoryCmd -- * * Implements the Tcl "memory" command, which provides Tcl-level control * of Tcl memory debugging information. * memory active $file * memory break_on_malloc $count * memory info * memory init on|off * memory onexit $file * memory tag $string * memory trace on|off * memory trace_on_at_malloc $count * memory validate on|off * * Results: * Standard TCL results. * *---------------------------------------------------------------------- */ static int MemoryCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Obj values of arguments. */ { const char *fileName; FILE *fileP; Tcl_DString buffer; int result; size_t len; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option [args..]"); return TCL_ERROR; } if (strcmp(TclGetString(objv[1]), "active") == 0 || strcmp(TclGetString(objv[1]), "display") == 0) { if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "file"); return TCL_ERROR; } fileName = Tcl_TranslateFileName(interp, TclGetString(objv[2]), &buffer); if (fileName == NULL) { return TCL_ERROR; } result = Tcl_DumpActiveMemory(fileName); Tcl_DStringFree(&buffer); if (result != TCL_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("error accessing %s: %s", TclGetString(objv[2]), Tcl_PosixError(interp))); return TCL_ERROR; } return TCL_OK; } if (strcmp(TclGetString(objv[1]),"break_on_malloc") == 0) { Tcl_WideInt value; if (objc != 3) { goto argError; } if (TclGetWideIntFromObj(interp, objv[2], &value) != TCL_OK) { return TCL_ERROR; } break_on_malloc = value; return TCL_OK; } if (strcmp(TclGetString(objv[1]),"info") == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%-25s %10" TCL_Z_MODIFIER "u\n%-25s %10" TCL_Z_MODIFIER "u\n%-25s %10" TCL_Z_MODIFIER "u\n%-25s %10" TCL_Z_MODIFIER "u\n%-25s %10" TCL_Z_MODIFIER "u\n%-25s %10" TCL_Z_MODIFIER "u\n", "total mallocs", total_mallocs, "total frees", total_frees, "current packets allocated", current_malloc_packets, "current bytes allocated", current_bytes_malloced, "maximum packets allocated", maximum_malloc_packets, "maximum bytes allocated", maximum_bytes_malloced)); return TCL_OK; } if (strcmp(TclGetString(objv[1]), "init") == 0) { if (objc != 3) { goto bad_suboption; } init_malloced_bodies = (strcmp(TclGetString(objv[2]),"on") == 0); return TCL_OK; } if (strcmp(TclGetString(objv[1]), "objs") == 0) { if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "file"); return TCL_ERROR; } fileName = Tcl_TranslateFileName(interp, TclGetString(objv[2]), &buffer); if (fileName == NULL) { return TCL_ERROR; } fileP = fopen(fileName, "w"); if (fileP == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot open output file: %s", Tcl_PosixError(interp))); return TCL_ERROR; } TclDbDumpActiveObjects(fileP); fclose(fileP); Tcl_DStringFree(&buffer); return TCL_OK; } if (strcmp(TclGetString(objv[1]),"onexit") == 0) { if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "file"); return TCL_ERROR; } fileName = Tcl_TranslateFileName(interp, TclGetString(objv[2]), &buffer); if (fileName == NULL) { return TCL_ERROR; } onExitMemDumpFileName = dumpFile; strcpy(onExitMemDumpFileName,fileName); Tcl_DStringFree(&buffer); return TCL_OK; } if (strcmp(TclGetString(objv[1]),"tag") == 0) { if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "file"); return TCL_ERROR; } if ((curTagPtr != NULL) && (curTagPtr->refCount == 0)) { TclpFree(curTagPtr); } len = strlen(TclGetString(objv[2])); curTagPtr = (MemTag *) TclpAlloc(TAG_SIZE(len)); curTagPtr->refCount = 0; memcpy(curTagPtr->string, TclGetString(objv[2]), len + 1); return TCL_OK; } if (strcmp(TclGetString(objv[1]),"trace") == 0) { if (objc != 3) { goto bad_suboption; } alloc_tracing = (strcmp(TclGetString(objv[2]),"on") == 0); return TCL_OK; } if (strcmp(TclGetString(objv[1]),"trace_on_at_malloc") == 0) { Tcl_WideInt value; if (objc != 3) { goto argError; } if (TclGetWideIntFromObj(interp, objv[2], &value) != TCL_OK) { return TCL_ERROR; } trace_on_at_malloc = value; return TCL_OK; } if (strcmp(TclGetString(objv[1]),"validate") == 0) { if (objc != 3) { goto bad_suboption; } validate_memory = (strcmp(TclGetString(objv[2]),"on") == 0); return TCL_OK; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad option \"%s\": should be active, break_on_malloc, info, " "init, objs, onexit, tag, trace, trace_on_at_malloc, or validate", TclGetString(objv[1]))); return TCL_ERROR; argError: Tcl_WrongNumArgs(interp, 2, objv, "count"); return TCL_ERROR; bad_suboption: Tcl_WrongNumArgs(interp, 2, objv, "on|off"); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * CheckmemCmd -- * * This is the command procedure for the "checkmem" command, which causes * the application to exit after printing information about memory usage * to the file passed to this command as its first argument. * * Results: * Returns a standard Tcl completion code. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int CheckmemCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter for evaluation. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Obj values of arguments. */ { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "fileName"); return TCL_ERROR; } tclMemDumpFileName = dumpFile; strcpy(tclMemDumpFileName, TclGetString(objv[1])); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_InitMemory -- * * Create the "memory" and "checkmem" commands in the given interpreter. * * Results: * None. * * Side effects: * New commands are added to the interpreter. * *---------------------------------------------------------------------- */ void Tcl_InitMemory( Tcl_Interp *interp) /* Interpreter in which commands should be * added */ { TclInitDbCkalloc(); Tcl_CreateObjCommand(interp, "memory", MemoryCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "checkmem", CheckmemCmd, NULL, NULL); } #else /* TCL_MEM_DEBUG */ /* This is the !TCL_MEM_DEBUG case */ #undef Tcl_InitMemory #undef Tcl_DumpActiveMemory #undef Tcl_ValidateAllMemory /* *---------------------------------------------------------------------- * * Tcl_Alloc -- * * Interface to TclpAlloc when TCL_MEM_DEBUG is disabled. It does check * that memory was actually allocated. * *---------------------------------------------------------------------- */ void * Tcl_Alloc( size_t size) { void *result = TclpAlloc(size); /* * Most systems will not alloc(0), instead bumping it to one so that NULL * isn't returned. Some systems (AIX, Tru64) will alloc(0) by returning * NULL, so we have to check that the NULL we get is not in response to * alloc(0). * * The ANSI spec actually says that systems either return NULL *or* a * special pointer on failure, but we only check for NULL */ if ((result == NULL) && size) { Tcl_Panic("unable to alloc %" TCL_Z_MODIFIER "u bytes", size); } return result; } void * Tcl_DbCkalloc( size_t size, const char *file, int line) { void *result = TclpAlloc(size); if ((result == NULL) && size) { fflush(stdout); Tcl_Panic("unable to alloc %" TCL_Z_MODIFIER "u bytes, %s line %d", size, file, line); } return result; } /* *---------------------------------------------------------------------- * * Tcl_AttemptAlloc -- * * Interface to TclpAlloc when TCL_MEM_DEBUG is disabled. It does not * check that memory was actually allocated. * *---------------------------------------------------------------------- */ void * Tcl_AttemptAlloc( size_t size) { return (char *)TclpAlloc(size); } void * Tcl_AttemptDbCkalloc( size_t size, TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { return (char *)TclpAlloc(size); } /* *---------------------------------------------------------------------- * * Tcl_Realloc -- * * Interface to TclpRealloc when TCL_MEM_DEBUG is disabled. It does check * that memory was actually allocated. * *---------------------------------------------------------------------- */ void * Tcl_Realloc( void *ptr, size_t size) { void *result = TclpRealloc(ptr, size); if ((result == NULL) && size) { Tcl_Panic("unable to realloc %" TCL_Z_MODIFIER "u bytes", size); } return result; } void * Tcl_DbCkrealloc( void *ptr, size_t size, const char *file, int line) { void *result = TclpRealloc(ptr, size); if ((result == NULL) && size) { fflush(stdout); Tcl_Panic("unable to realloc %" TCL_Z_MODIFIER "u bytes, %s line %d", size, file, line); } return result; } /* *---------------------------------------------------------------------- * * Tcl_AttemptRealloc -- * * Interface to TclpRealloc when TCL_MEM_DEBUG is disabled. It does not * check that memory was actually allocated. * *---------------------------------------------------------------------- */ void * Tcl_AttemptRealloc( void *ptr, size_t size) { return (char *)TclpRealloc(ptr, size); } void * Tcl_AttemptDbCkrealloc( void *ptr, size_t size, TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { return (char *)TclpRealloc(ptr, size); } /* *---------------------------------------------------------------------- * * Tcl_Free -- * * Interface to TclpFree when TCL_MEM_DEBUG is disabled. Done here rather * in the macro to keep some modules from being compiled with * TCL_MEM_DEBUG enabled and some with it disabled. * *---------------------------------------------------------------------- */ void Tcl_Free( void *ptr) { TclpFree(ptr); } void Tcl_DbCkfree( void *ptr, TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { TclpFree(ptr); } /* *---------------------------------------------------------------------- * * Tcl_InitMemory -- * * Dummy initialization for memory command, which is only available if * TCL_MEM_DEBUG is on. * *---------------------------------------------------------------------- */ void Tcl_InitMemory( TCL_UNUSED(Tcl_Interp *) /*interp*/) { } int Tcl_DumpActiveMemory( TCL_UNUSED(const char *) /*fileName*/) { return TCL_OK; } void Tcl_ValidateAllMemory( TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { } int TclDumpMemoryInfo( TCL_UNUSED(void *), TCL_UNUSED(int) /*flags*/) { return 1; } #endif /* TCL_MEM_DEBUG */ /* *------------------------------------------------------------------------ * * TclAllocElemsEx -- * * See TclAttemptAllocElemsEx. This function differs in that it panics * on failure. * * Results: * Non-NULL pointer to allocated memory block. * * Side effects: * Panics if memory of at least the requested size could not be * allocated. * *------------------------------------------------------------------------ */ void * TclAllocElemsEx( Tcl_Size elemCount, /* Allocation will store at least these many... */ Tcl_Size elemSize, /* ...elements of this size */ Tcl_Size leadSize, /* Additional leading space in bytes */ Tcl_Size *capacityPtr) /* OUTPUT: Actual capacity is stored here if * non-NULL. Only modified on success */ { void *ptr = TclAttemptReallocElemsEx( NULL, elemCount, elemSize, leadSize, capacityPtr); if (ptr == NULL) { Tcl_Panic("Failed to allocate %" TCL_SIZE_MODIFIER "d elements of size %" TCL_SIZE_MODIFIER "d bytes.", elemCount, elemSize); } return ptr; } /* *------------------------------------------------------------------------ * * TclAttemptReallocElemsEx -- * * Attempts to allocate (oldPtr == NULL) or reallocate memory of the * requested size plus some more for future growth. The amount of * reallocation is adjusted depending on failure. * * * Results: * Pointer to allocated memory block which is at least as large * as the requested size or NULL if allocation failed. * *------------------------------------------------------------------------ */ void * TclAttemptReallocElemsEx( void *oldPtr, /* Pointer to memory block to reallocate or * NULL to indicate this is a new allocation */ Tcl_Size elemCount, /* Allocation will store at least these many... */ Tcl_Size elemSize, /* ...elements of this size */ Tcl_Size leadSize, /* Additional leading space in bytes */ Tcl_Size *capacityPtr) /* OUTPUT: Actual capacity is stored here if * non-NULL. Only modified on success */ { void *ptr; Tcl_Size limit; Tcl_Size attempt; assert(elemCount > 0); assert(elemSize > 0); assert(elemSize < TCL_SIZE_MAX); assert(leadSize >= 0); assert(leadSize < TCL_SIZE_MAX); limit = (TCL_SIZE_MAX - leadSize) / elemSize; if (elemCount > limit) { return NULL; } /* Loop trying for extra space, reducing request each time */ attempt = TclUpsizeAlloc(0, elemCount, limit); ptr = NULL; while (attempt > elemCount) { if (oldPtr) { ptr = Tcl_AttemptRealloc(oldPtr, leadSize + attempt * elemSize); } else { ptr = Tcl_AttemptAlloc(leadSize + attempt * elemSize); } if (ptr) { break; } attempt = TclUpsizeRetry(elemCount, attempt); } /* Try exact size as a last resort */ if (ptr == NULL) { attempt = elemCount; if (oldPtr) { ptr = Tcl_AttemptRealloc(oldPtr, leadSize + attempt * elemSize); } else { ptr = Tcl_AttemptAlloc(leadSize + attempt * elemSize); } } if (ptr && capacityPtr) { *capacityPtr = attempt; } return ptr; } /* *------------------------------------------------------------------------ * * TclReallocElemsEx -- * * See TclAttemptReallocElemsEx. This function differs in that it panics * on failure. * * Results: * Non-NULL pointer to allocated memory block. * * Side effects: * Panics if memory of at least the requested size could not be * allocated. * *------------------------------------------------------------------------ */ void * TclReallocElemsEx( void *oldPtr, /* Pointer to memory block to reallocate */ Tcl_Size elemCount, /* Allocation will store at least these many... */ Tcl_Size elemSize, /* ...elements of this size */ Tcl_Size leadSize, /* Additional leading space in bytes */ Tcl_Size *capacityPtr) /* OUTPUT: Actual capacity is stored here if * non-NULL. Only modified on success */ { void *ptr = TclAttemptReallocElemsEx( oldPtr, elemCount, elemSize, leadSize, capacityPtr); if (ptr == NULL) { Tcl_Panic("Failed to reallocate %" TCL_SIZE_MODIFIER "d elements of size %" TCL_SIZE_MODIFIER "d bytes.", elemCount, elemSize); } return ptr; } /* *--------------------------------------------------------------------------- * * TclFinalizeMemorySubsystem -- * * This procedure is called to finalize all the structures that are used * by the memory allocator on a per-process basis. * * Results: * None. * * Side effects: * This subsystem is self-initializing, since memory can be allocated * before Tcl is formally initialized. After this call, this subsystem * has been reset to its initial state and is usable again. * *--------------------------------------------------------------------------- */ void TclFinalizeMemorySubsystem(void) { #ifdef TCL_MEM_DEBUG if (tclMemDumpFileName != NULL) { Tcl_DumpActiveMemory(tclMemDumpFileName); } else if (onExitMemDumpFileName != NULL) { Tcl_DumpActiveMemory(onExitMemDumpFileName); } Tcl_MutexLock(ckallocMutexPtr); if (curTagPtr != NULL) { TclpFree(curTagPtr); curTagPtr = NULL; } allocHead = NULL; Tcl_MutexUnlock(ckallocMutexPtr); #endif #if defined(USE_TCLALLOC) && USE_TCLALLOC TclFinalizeAllocSubsystem(); #endif } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * indent-tabs-mode: nil * End: */ tcl9.0.1/generic/tclClock.c0000644000175000017500000037733214726623136015113 0ustar sergeisergei/* * tclClock.c -- * * Contains the time and date related commands. This code is derived from * the time and date facilities of TclX, by Mark Diekhans and Karl * Lehenbauer. * * Copyright © 1991-1995 Karl Lehenbauer & Mark Diekhans. * Copyright © 1995 Sun Microsystems, Inc. * Copyright © 2004 Kevin B. Kenny. All rights reserved. * Copyright © 2015 Sergey G. Brester aka sebres. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclTomMath.h" #include "tclStrIdxTree.h" #include "tclDate.h" /* * Table of the days in each month, leap and common years */ static const int hath[2][12] = { {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} }; static const int daysInPriorMonths[2][13] = { {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366} }; /* * Enumeration of the string literals used in [clock] */ CLOCK_LITERAL_ARRAY(Literals); /* Msgcat literals for exact match (mcKey) */ CLOCK_LOCALE_LITERAL_ARRAY(MsgCtLiterals, ""); /* Msgcat index literals prefixed with _IDX_, used for quick dictionary search */ CLOCK_LOCALE_LITERAL_ARRAY(MsgCtLitIdxs, "_IDX_"); static const char *const eras[] = { "CE", "BCE", NULL }; /* * Thread specific data block holding a 'struct tm' for the 'gmtime' and * 'localtime' library calls. */ static Tcl_ThreadDataKey tmKey; /* * Mutex protecting 'gmtime', 'localtime' and 'mktime' calls and the statics * in the date parsing code. */ TCL_DECLARE_MUTEX(clockMutex) /* * Function prototypes for local procedures in this file: */ static int ConvertUTCToLocalUsingTable(Tcl_Interp *, TclDateFields *, Tcl_Size, Tcl_Obj *const[], Tcl_WideInt *rangesVal); static int ConvertUTCToLocalUsingC(Tcl_Interp *, TclDateFields *, int); static int ConvertLocalToUTC(ClockClientData *, Tcl_Interp *, TclDateFields *, Tcl_Obj *timezoneObj, int); static int ConvertLocalToUTCUsingTable(Tcl_Interp *, TclDateFields *, int, Tcl_Obj *const[], Tcl_WideInt *rangesVal); static int ConvertLocalToUTCUsingC(Tcl_Interp *, TclDateFields *, int); static Tcl_ObjCmdProc ClockConfigureObjCmd; static void GetYearWeekDay(TclDateFields *, int); static void GetGregorianEraYearDay(TclDateFields *, int); static void GetMonthDay(TclDateFields *); static Tcl_WideInt WeekdayOnOrBefore(int, Tcl_WideInt); static Tcl_ObjCmdProc ClockClicksObjCmd; static Tcl_ObjCmdProc ClockConvertlocaltoutcObjCmd; static int ClockGetDateFields(ClockClientData *, Tcl_Interp *interp, TclDateFields *fields, Tcl_Obj *timezoneObj, int changeover); static Tcl_ObjCmdProc ClockGetdatefieldsObjCmd; static Tcl_ObjCmdProc ClockGetjuliandayfromerayearmonthdayObjCmd; static Tcl_ObjCmdProc ClockGetjuliandayfromerayearweekdayObjCmd; static Tcl_ObjCmdProc ClockGetenvObjCmd; static Tcl_ObjCmdProc ClockMicrosecondsObjCmd; static Tcl_ObjCmdProc ClockMillisecondsObjCmd; static Tcl_ObjCmdProc ClockSecondsObjCmd; static Tcl_ObjCmdProc ClockFormatObjCmd; static Tcl_ObjCmdProc ClockScanObjCmd; static int ClockScanCommit(DateInfo *info, ClockFmtScnCmdArgs *opts); static int ClockFreeScan(DateInfo *info, Tcl_Obj *strObj, ClockFmtScnCmdArgs *opts); static int ClockCalcRelTime(DateInfo *info); static Tcl_ObjCmdProc ClockAddObjCmd; static int ClockValidDate(DateInfo *, ClockFmtScnCmdArgs *, int stage); static struct tm * ThreadSafeLocalTime(const time_t *); static size_t TzsetIfNecessary(void); static void ClockDeleteCmdProc(void *); static Tcl_ObjCmdProc ClockSafeCatchCmd; static void ClockFinalize(void *); /* * Structure containing description of "native" clock commands to create. */ struct ClockCommand { const char *name; /* The tail of the command name. The full name * is "::tcl::clock::". When NULL marks * the end of the table. */ Tcl_ObjCmdProc *objCmdProc; /* Function that implements the command. This * will always have the ClockClientData sent * to it, but may well ignore this data. */ CompileProc *compileProc; /* The compiler for the command. */ void *clientData; /* Any clientData to give the command (if NULL * a reference to ClockClientData will be sent) */ }; static const struct ClockCommand clockCommands[] = { {"add", ClockAddObjCmd, TclCompileBasicMin1ArgCmd, NULL}, {"clicks", ClockClicksObjCmd, TclCompileClockClicksCmd, NULL}, {"format", ClockFormatObjCmd, TclCompileBasicMin1ArgCmd, NULL}, {"getenv", ClockGetenvObjCmd, TclCompileBasicMin1ArgCmd, NULL}, {"microseconds", ClockMicrosecondsObjCmd,TclCompileClockReadingCmd, INT2PTR(1)}, {"milliseconds", ClockMillisecondsObjCmd,TclCompileClockReadingCmd, INT2PTR(2)}, {"scan", ClockScanObjCmd, TclCompileBasicMin1ArgCmd, NULL}, {"seconds", ClockSecondsObjCmd, TclCompileClockReadingCmd, INT2PTR(3)}, {"ConvertLocalToUTC", ClockConvertlocaltoutcObjCmd, NULL, NULL}, {"GetDateFields", ClockGetdatefieldsObjCmd, NULL, NULL}, {"GetJulianDayFromEraYearMonthDay", ClockGetjuliandayfromerayearmonthdayObjCmd, NULL, NULL}, {"GetJulianDayFromEraYearWeekDay", ClockGetjuliandayfromerayearweekdayObjCmd, NULL, NULL}, {"catch", ClockSafeCatchCmd, TclCompileBasicMin1ArgCmd, NULL}, {NULL, NULL, NULL, NULL} }; /* *---------------------------------------------------------------------- * * TclClockInit -- * * Registers the 'clock' subcommands with the Tcl interpreter and * initializes its client data (which consists mostly of constant * Tcl_Obj's that it is too much trouble to keep recreating). * * Results: * None. * * Side effects: * Installs the commands and creates the client data * *---------------------------------------------------------------------- */ void TclClockInit( Tcl_Interp *interp) /* Tcl interpreter */ { const struct ClockCommand *clockCmdPtr; char cmdName[50]; /* Buffer large enough to hold the string *::tcl::clock::GetJulianDayFromEraYearMonthDay * plus a terminating NUL. */ Command *cmdPtr; ClockClientData *data; int i; static int initialized = 0; /* global clock engine initialized (in process) */ /* * Register handler to finalize clock on exit. */ if (!initialized) { Tcl_CreateExitHandler(ClockFinalize, NULL); initialized = 1; } /* * Safe interps get [::clock] as alias to a parent, so do not need their * own copies of the support routines. */ if (Tcl_IsSafe(interp)) { return; } /* * Create the client data, which is a refcounted literal pool. */ data = (ClockClientData *)Tcl_Alloc(sizeof(ClockClientData)); data->refCount = 0; data->literals = (Tcl_Obj **)Tcl_Alloc(LIT__END * sizeof(Tcl_Obj*)); for (i = 0; i < LIT__END; ++i) { TclInitObjRef(data->literals[i], Tcl_NewStringObj( Literals[i], TCL_AUTO_LENGTH)); } data->mcLiterals = NULL; data->mcLitIdxs = NULL; data->mcDicts = NULL; data->lastTZEpoch = 0; data->currentYearCentury = ClockDefaultYearCentury; data->yearOfCenturySwitch = ClockDefaultCenturySwitch; data->validMinYear = INT_MIN; data->validMaxYear = INT_MAX; /* corresponds max of JDN in sqlite - 9999-12-31 23:59:59 per default */ data->maxJDN = 5373484.499999994; data->systemTimeZone = NULL; data->systemSetupTZData = NULL; data->gmtSetupTimeZoneUnnorm = NULL; data->gmtSetupTimeZone = NULL; data->gmtSetupTZData = NULL; data->gmtTZName = NULL; data->lastSetupTimeZoneUnnorm = NULL; data->lastSetupTimeZone = NULL; data->lastSetupTZData = NULL; data->prevSetupTimeZoneUnnorm = NULL; data->prevSetupTimeZone = NULL; data->prevSetupTZData = NULL; data->defaultLocale = NULL; data->defaultLocaleDict = NULL; data->currentLocale = NULL; data->currentLocaleDict = NULL; data->lastUsedLocaleUnnorm = NULL; data->lastUsedLocale = NULL; data->lastUsedLocaleDict = NULL; data->prevUsedLocaleUnnorm = NULL; data->prevUsedLocale = NULL; data->prevUsedLocaleDict = NULL; data->lastBase.timezoneObj = NULL; memset(&data->lastTZOffsCache, 0, sizeof(data->lastTZOffsCache)); data->defFlags = CLF_VALIDATE; /* * Install the commands. */ #define TCL_CLOCK_PREFIX_LEN 14 /* == strlen("::tcl::clock::") */ memcpy(cmdName, "::tcl::clock::", TCL_CLOCK_PREFIX_LEN); for (clockCmdPtr=clockCommands ; clockCmdPtr->name!=NULL ; clockCmdPtr++) { void *clientData; strcpy(cmdName + TCL_CLOCK_PREFIX_LEN, clockCmdPtr->name); if (!(clientData = clockCmdPtr->clientData)) { clientData = data; data->refCount++; } cmdPtr = (Command *)Tcl_CreateObjCommand(interp, cmdName, clockCmdPtr->objCmdProc, clientData, clockCmdPtr->clientData ? NULL : ClockDeleteCmdProc); cmdPtr->compileProc = clockCmdPtr->compileProc ? clockCmdPtr->compileProc : TclCompileBasicMin0ArgCmd; } cmdPtr = (Command *) Tcl_CreateObjCommand(interp, "::tcl::unsupported::clock::configure", ClockConfigureObjCmd, data, ClockDeleteCmdProc); data->refCount++; cmdPtr->compileProc = TclCompileBasicMin0ArgCmd; } /* *---------------------------------------------------------------------- * * ClockConfigureClear -- * * Clean up cached resp. run-time storages used in clock commands. * * Shared usage for clean-up (ClockDeleteCmdProc) and "configure -clear". * * Results: * None. * *---------------------------------------------------------------------- */ static void ClockConfigureClear( ClockClientData *data) { ClockFrmScnClearCaches(); data->lastTZEpoch = 0; TclUnsetObjRef(data->systemTimeZone); TclUnsetObjRef(data->systemSetupTZData); TclUnsetObjRef(data->gmtSetupTimeZoneUnnorm); TclUnsetObjRef(data->gmtSetupTimeZone); TclUnsetObjRef(data->gmtSetupTZData); TclUnsetObjRef(data->gmtTZName); TclUnsetObjRef(data->lastSetupTimeZoneUnnorm); TclUnsetObjRef(data->lastSetupTimeZone); TclUnsetObjRef(data->lastSetupTZData); TclUnsetObjRef(data->prevSetupTimeZoneUnnorm); TclUnsetObjRef(data->prevSetupTimeZone); TclUnsetObjRef(data->prevSetupTZData); TclUnsetObjRef(data->defaultLocale); data->defaultLocaleDict = NULL; TclUnsetObjRef(data->currentLocale); data->currentLocaleDict = NULL; TclUnsetObjRef(data->lastUsedLocaleUnnorm); TclUnsetObjRef(data->lastUsedLocale); data->lastUsedLocaleDict = NULL; TclUnsetObjRef(data->prevUsedLocaleUnnorm); TclUnsetObjRef(data->prevUsedLocale); data->prevUsedLocaleDict = NULL; TclUnsetObjRef(data->lastBase.timezoneObj); TclUnsetObjRef(data->lastTZOffsCache[0].timezoneObj); TclUnsetObjRef(data->lastTZOffsCache[0].tzName); TclUnsetObjRef(data->lastTZOffsCache[1].timezoneObj); TclUnsetObjRef(data->lastTZOffsCache[1].tzName); TclUnsetObjRef(data->mcDicts); } /* *---------------------------------------------------------------------- * * ClockDeleteCmdProc -- * * Remove a reference to the clock client data, and clean up memory * when it's all gone. * * Results: * None. * *---------------------------------------------------------------------- */ static void ClockDeleteCmdProc( void *clientData) /* Opaque pointer to the client data */ { ClockClientData *data = (ClockClientData *)clientData; int i; if (data->refCount-- <= 1) { for (i = 0; i < LIT__END; ++i) { Tcl_DecrRefCount(data->literals[i]); } if (data->mcLiterals != NULL) { for (i = 0; i < MCLIT__END; ++i) { Tcl_DecrRefCount(data->mcLiterals[i]); } Tcl_Free(data->mcLiterals); data->mcLiterals = NULL; } if (data->mcLitIdxs != NULL) { for (i = 0; i < MCLIT__END; ++i) { Tcl_DecrRefCount(data->mcLitIdxs[i]); } Tcl_Free(data->mcLitIdxs); data->mcLitIdxs = NULL; } ClockConfigureClear(data); Tcl_Free(data->literals); Tcl_Free(data); } } /* *---------------------------------------------------------------------- * * SavePrevTimezoneObj -- * * Used to store previously used/cached time zone (makes it reusable). * * This enables faster switch between time zones (e. g. to convert from * one to another). * * Results: * None. * *---------------------------------------------------------------------- */ static inline void SavePrevTimezoneObj( ClockClientData *dataPtr) /* Client data containing literal pool */ { Tcl_Obj *timezoneObj = dataPtr->lastSetupTimeZone; if (timezoneObj && timezoneObj != dataPtr->prevSetupTimeZone) { TclSetObjRef(dataPtr->prevSetupTimeZoneUnnorm, dataPtr->lastSetupTimeZoneUnnorm); TclSetObjRef(dataPtr->prevSetupTimeZone, timezoneObj); TclSetObjRef(dataPtr->prevSetupTZData, dataPtr->lastSetupTZData); } } /* *---------------------------------------------------------------------- * * NormTimezoneObj -- * * Normalizes the timezone object (used for caching puposes). * * If already cached time zone could be found, returns this * object (last setup or last used, system (current) or gmt). * * Results: * Normalized tcl object pointer. * *---------------------------------------------------------------------- */ static Tcl_Obj * NormTimezoneObj( ClockClientData *dataPtr, /* Client data containing literal pool */ Tcl_Obj *timezoneObj, /* Name of zone to find */ int *loaded) /* Used to recognized TZ was loaded */ { const char *tz; *loaded = 1; if (timezoneObj == dataPtr->lastSetupTimeZoneUnnorm && dataPtr->lastSetupTimeZone != NULL) { return dataPtr->lastSetupTimeZone; } if (timezoneObj == dataPtr->prevSetupTimeZoneUnnorm && dataPtr->prevSetupTimeZone != NULL) { return dataPtr->prevSetupTimeZone; } if (timezoneObj == dataPtr->gmtSetupTimeZoneUnnorm && dataPtr->gmtSetupTimeZone != NULL) { return dataPtr->literals[LIT_GMT]; } if (timezoneObj == dataPtr->lastSetupTimeZone || timezoneObj == dataPtr->prevSetupTimeZone || timezoneObj == dataPtr->gmtSetupTimeZone || timezoneObj == dataPtr->systemTimeZone) { return timezoneObj; } tz = TclGetString(timezoneObj); if (dataPtr->lastSetupTimeZone != NULL && strcmp(tz, TclGetString(dataPtr->lastSetupTimeZone)) == 0) { TclSetObjRef(dataPtr->lastSetupTimeZoneUnnorm, timezoneObj); return dataPtr->lastSetupTimeZone; } if (dataPtr->prevSetupTimeZone != NULL && strcmp(tz, TclGetString(dataPtr->prevSetupTimeZone)) == 0) { TclSetObjRef(dataPtr->prevSetupTimeZoneUnnorm, timezoneObj); return dataPtr->prevSetupTimeZone; } if (dataPtr->systemTimeZone != NULL && strcmp(tz, TclGetString(dataPtr->systemTimeZone)) == 0) { return dataPtr->systemTimeZone; } if (strcmp(tz, Literals[LIT_GMT]) == 0) { TclSetObjRef(dataPtr->gmtSetupTimeZoneUnnorm, timezoneObj); if (dataPtr->gmtSetupTimeZone == NULL) { *loaded = 0; } return dataPtr->literals[LIT_GMT]; } /* unknown/unloaded tz - recache/revalidate later as last-setup if needed */ *loaded = 0; return timezoneObj; } /* *---------------------------------------------------------------------- * * ClockGetSystemLocale -- * * Returns system locale. * * Executes ::tcl::clock::GetSystemLocale in given interpreter. * * Results: * Returns system locale tcl object. * *---------------------------------------------------------------------- */ static inline Tcl_Obj * ClockGetSystemLocale( ClockClientData *dataPtr, /* Opaque pointer to literal pool, etc. */ Tcl_Interp *interp) /* Tcl interpreter */ { if (Tcl_EvalObjv(interp, 1, &dataPtr->literals[LIT_GETSYSTEMLOCALE], 0) != TCL_OK) { return NULL; } return Tcl_GetObjResult(interp); } /* *---------------------------------------------------------------------- * * ClockGetCurrentLocale -- * * Returns current locale. * * Executes ::tcl::clock::mclocale in given interpreter. * * Results: * Returns current locale tcl object. * *---------------------------------------------------------------------- */ static inline Tcl_Obj * ClockGetCurrentLocale( ClockClientData *dataPtr, /* Client data containing literal pool */ Tcl_Interp *interp) /* Tcl interpreter */ { if (Tcl_EvalObjv(interp, 1, &dataPtr->literals[LIT_GETCURRENTLOCALE], 0) != TCL_OK) { return NULL; } TclSetObjRef(dataPtr->currentLocale, Tcl_GetObjResult(interp)); dataPtr->currentLocaleDict = NULL; Tcl_ResetResult(interp); return dataPtr->currentLocale; } /* *---------------------------------------------------------------------- * * SavePrevLocaleObj -- * * Used to store previously used/cached locale (makes it reusable). * * This enables faster switch between locales (e. g. to convert from one to another). * * Results: * None. * *---------------------------------------------------------------------- */ static inline void SavePrevLocaleObj( ClockClientData *dataPtr) /* Client data containing literal pool */ { Tcl_Obj *localeObj = dataPtr->lastUsedLocale; if (localeObj && localeObj != dataPtr->prevUsedLocale) { TclSetObjRef(dataPtr->prevUsedLocaleUnnorm, dataPtr->lastUsedLocaleUnnorm); TclSetObjRef(dataPtr->prevUsedLocale, localeObj); /* mcDicts owns reference to dict */ dataPtr->prevUsedLocaleDict = dataPtr->lastUsedLocaleDict; } } /* *---------------------------------------------------------------------- * * NormLocaleObj -- * * Normalizes the locale object (used for caching puposes). * * If already cached locale could be found, returns this * object (current, system (OS) or last used locales). * * Results: * Normalized tcl object pointer. * *---------------------------------------------------------------------- */ static Tcl_Obj * NormLocaleObj( ClockClientData *dataPtr, /* Client data containing literal pool */ Tcl_Interp *interp, /* Tcl interpreter */ Tcl_Obj *localeObj, Tcl_Obj **mcDictObj) { const char *loc, *loc2; if (localeObj == NULL || localeObj == dataPtr->literals[LIT_C] || localeObj == dataPtr->defaultLocale) { *mcDictObj = dataPtr->defaultLocaleDict; return dataPtr->defaultLocale ? dataPtr->defaultLocale : dataPtr->literals[LIT_C]; } if (localeObj == dataPtr->currentLocale || localeObj == dataPtr->literals[LIT_CURRENT]) { if (dataPtr->currentLocale == NULL) { ClockGetCurrentLocale(dataPtr, interp); } *mcDictObj = dataPtr->currentLocaleDict; return dataPtr->currentLocale; } if (localeObj == dataPtr->lastUsedLocale || localeObj == dataPtr->lastUsedLocaleUnnorm) { *mcDictObj = dataPtr->lastUsedLocaleDict; return dataPtr->lastUsedLocale; } if (localeObj == dataPtr->prevUsedLocale || localeObj == dataPtr->prevUsedLocaleUnnorm) { *mcDictObj = dataPtr->prevUsedLocaleDict; return dataPtr->prevUsedLocale; } loc = TclGetString(localeObj); if (dataPtr->currentLocale != NULL && (localeObj == dataPtr->currentLocale || (localeObj->length == dataPtr->currentLocale->length && strcasecmp(loc, TclGetString(dataPtr->currentLocale)) == 0))) { *mcDictObj = dataPtr->currentLocaleDict; return dataPtr->currentLocale; } if (dataPtr->lastUsedLocale != NULL && (localeObj == dataPtr->lastUsedLocale || (localeObj->length == dataPtr->lastUsedLocale->length && strcasecmp(loc, TclGetString(dataPtr->lastUsedLocale)) == 0))) { *mcDictObj = dataPtr->lastUsedLocaleDict; TclSetObjRef(dataPtr->lastUsedLocaleUnnorm, localeObj); return dataPtr->lastUsedLocale; } if (dataPtr->prevUsedLocale != NULL && (localeObj == dataPtr->prevUsedLocale || (localeObj->length == dataPtr->prevUsedLocale->length && strcasecmp(loc, TclGetString(dataPtr->prevUsedLocale)) == 0))) { *mcDictObj = dataPtr->prevUsedLocaleDict; TclSetObjRef(dataPtr->prevUsedLocaleUnnorm, localeObj); return dataPtr->prevUsedLocale; } if ((localeObj->length == 1 /* C */ && strcasecmp(loc, Literals[LIT_C]) == 0) || (dataPtr->defaultLocale && (loc2 = TclGetString(dataPtr->defaultLocale)) && localeObj->length == dataPtr->defaultLocale->length && strcasecmp(loc, loc2) == 0)) { *mcDictObj = dataPtr->defaultLocaleDict; return dataPtr->defaultLocale ? dataPtr->defaultLocale : dataPtr->literals[LIT_C]; } if (localeObj->length == 7 /* current */ && strcasecmp(loc, Literals[LIT_CURRENT]) == 0) { if (dataPtr->currentLocale == NULL) { ClockGetCurrentLocale(dataPtr, interp); } *mcDictObj = dataPtr->currentLocaleDict; return dataPtr->currentLocale; } if ((localeObj->length == 6 /* system */ && strcasecmp(loc, Literals[LIT_SYSTEM]) == 0)) { SavePrevLocaleObj(dataPtr); TclSetObjRef(dataPtr->lastUsedLocaleUnnorm, localeObj); localeObj = ClockGetSystemLocale(dataPtr, interp); TclSetObjRef(dataPtr->lastUsedLocale, localeObj); *mcDictObj = NULL; return localeObj; } *mcDictObj = NULL; return localeObj; } /* *---------------------------------------------------------------------- * * ClockMCDict -- * * Retrieves a localized storage dictionary object for the given * locale object. * * This corresponds with call `::tcl::clock::mcget locale`. * Cached representation stored in options (for further access). * * Results: * Tcl-object contains smart reference to msgcat dictionary. * *---------------------------------------------------------------------- */ Tcl_Obj * ClockMCDict( ClockFmtScnCmdArgs *opts) { ClockClientData *dataPtr = opts->dataPtr; /* if dict not yet retrieved */ if (opts->mcDictObj == NULL) { /* if locale was not yet used */ if (!(opts->flags & CLF_LOCALE_USED)) { opts->localeObj = NormLocaleObj(dataPtr, opts->interp, opts->localeObj, &opts->mcDictObj); if (opts->localeObj == NULL) { Tcl_SetObjResult(opts->interp, Tcl_NewStringObj( "locale not specified and no default locale set", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(opts->interp, "CLOCK", "badOption", (char *)NULL); return NULL; } opts->flags |= CLF_LOCALE_USED; /* check locale literals already available (on demand creation) */ if (dataPtr->mcLiterals == NULL) { int i; dataPtr->mcLiterals = (Tcl_Obj **) Tcl_Alloc(MCLIT__END * sizeof(Tcl_Obj*)); for (i = 0; i < MCLIT__END; ++i) { TclInitObjRef(dataPtr->mcLiterals[i], Tcl_NewStringObj( MsgCtLiterals[i], TCL_AUTO_LENGTH)); } } } /* check or obtain mcDictObj (be sure it's modifiable) */ if (opts->mcDictObj == NULL || opts->mcDictObj->refCount > 1) { Tcl_Size ref = 1; /* first try to find locale catalog dict */ if (dataPtr->mcDicts == NULL) { TclSetObjRef(dataPtr->mcDicts, Tcl_NewDictObj()); } Tcl_DictObjGet(NULL, dataPtr->mcDicts, opts->localeObj, &opts->mcDictObj); if (opts->mcDictObj == NULL) { /* get msgcat dictionary - ::tcl::clock::mcget locale */ Tcl_Obj *callargs[2]; callargs[0] = dataPtr->literals[LIT_MCGET]; callargs[1] = opts->localeObj; if (Tcl_EvalObjv(opts->interp, 2, callargs, 0) != TCL_OK) { return NULL; } opts->mcDictObj = Tcl_GetObjResult(opts->interp); Tcl_ResetResult(opts->interp); ref = 0; /* new object is not yet referenced */ } /* be sure that object reference doesn't increase (dict changeable) */ if (opts->mcDictObj->refCount > ref) { /* smart reference (shared dict as object with no ref-counter) */ opts->mcDictObj = TclDictObjSmartRef(opts->interp, opts->mcDictObj); } /* create exactly one reference to catalog / make it searchable for future */ Tcl_DictObjPut(NULL, dataPtr->mcDicts, opts->localeObj, opts->mcDictObj); if (opts->localeObj == dataPtr->literals[LIT_C] || opts->localeObj == dataPtr->defaultLocale) { dataPtr->defaultLocaleDict = opts->mcDictObj; } if (opts->localeObj == dataPtr->currentLocale) { dataPtr->currentLocaleDict = opts->mcDictObj; } else if (opts->localeObj == dataPtr->lastUsedLocale) { dataPtr->lastUsedLocaleDict = opts->mcDictObj; } else { SavePrevLocaleObj(dataPtr); TclSetObjRef(dataPtr->lastUsedLocale, opts->localeObj); TclUnsetObjRef(dataPtr->lastUsedLocaleUnnorm); dataPtr->lastUsedLocaleDict = opts->mcDictObj; } } } return opts->mcDictObj; } /* *---------------------------------------------------------------------- * * ClockMCGet -- * * Retrieves a msgcat value for the given literal integer mcKey * from localized storage (corresponding given locale object) * by mcLiterals[mcKey] (e. g. MONTHS_FULL). * * Results: * Tcl-object contains localized value. * *---------------------------------------------------------------------- */ Tcl_Obj * ClockMCGet( ClockFmtScnCmdArgs *opts, int mcKey) { Tcl_Obj *valObj = NULL; if (opts->mcDictObj == NULL) { ClockMCDict(opts); if (opts->mcDictObj == NULL) { return NULL; } } Tcl_DictObjGet(opts->interp, opts->mcDictObj, opts->dataPtr->mcLiterals[mcKey], &valObj); return valObj; /* or NULL in obscure case if Tcl_DictObjGet failed */ } /* *---------------------------------------------------------------------- * * ClockMCGetIdx -- * * Retrieves an indexed msgcat value for the given literal integer mcKey * from localized storage (corresponding given locale object) * by mcLitIdxs[mcKey] (e. g. _IDX_MONTHS_FULL). * * Results: * Tcl-object contains localized indexed value. * *---------------------------------------------------------------------- */ MODULE_SCOPE Tcl_Obj * ClockMCGetIdx( ClockFmtScnCmdArgs *opts, int mcKey) { ClockClientData *dataPtr = opts->dataPtr; Tcl_Obj *valObj = NULL; if (opts->mcDictObj == NULL) { ClockMCDict(opts); if (opts->mcDictObj == NULL) { return NULL; } } /* try to get indices object */ if (dataPtr->mcLitIdxs == NULL) { return NULL; } if (Tcl_DictObjGet(NULL, opts->mcDictObj, dataPtr->mcLitIdxs[mcKey], &valObj) != TCL_OK) { return NULL; } return valObj; } /* *---------------------------------------------------------------------- * * ClockMCSetIdx -- * * Sets an indexed msgcat value for the given literal integer mcKey * in localized storage (corresponding given locale object) * by mcLitIdxs[mcKey] (e. g. _IDX_MONTHS_FULL). * * Results: * Returns a standard Tcl result. * *---------------------------------------------------------------------- */ int ClockMCSetIdx( ClockFmtScnCmdArgs *opts, int mcKey, Tcl_Obj *valObj) { ClockClientData *dataPtr = opts->dataPtr; if (opts->mcDictObj == NULL) { ClockMCDict(opts); if (opts->mcDictObj == NULL) { return TCL_ERROR; } } /* if literal storage for indices not yet created */ if (dataPtr->mcLitIdxs == NULL) { int i; dataPtr->mcLitIdxs = (Tcl_Obj **)Tcl_Alloc(MCLIT__END * sizeof(Tcl_Obj*)); for (i = 0; i < MCLIT__END; ++i) { TclInitObjRef(dataPtr->mcLitIdxs[i], Tcl_NewStringObj(MsgCtLitIdxs[i], TCL_AUTO_LENGTH)); } } return Tcl_DictObjPut(opts->interp, opts->mcDictObj, dataPtr->mcLitIdxs[mcKey], valObj); } static void TimezoneLoaded( ClockClientData *dataPtr, Tcl_Obj *timezoneObj, /* Name of zone was loaded */ Tcl_Obj *tzUnnormObj) /* Name of zone was loaded */ { /* don't overwrite last-setup with GMT (special case) */ if (timezoneObj == dataPtr->literals[LIT_GMT]) { /* mark GMT zone loaded */ if (dataPtr->gmtSetupTimeZone == NULL) { TclSetObjRef(dataPtr->gmtSetupTimeZone, dataPtr->literals[LIT_GMT]); } TclSetObjRef(dataPtr->gmtSetupTimeZoneUnnorm, tzUnnormObj); return; } /* last setup zone loaded */ if (dataPtr->lastSetupTimeZone != timezoneObj) { SavePrevTimezoneObj(dataPtr); TclSetObjRef(dataPtr->lastSetupTimeZone, timezoneObj); TclUnsetObjRef(dataPtr->lastSetupTZData); } TclSetObjRef(dataPtr->lastSetupTimeZoneUnnorm, tzUnnormObj); } /* *---------------------------------------------------------------------- * * ClockConfigureObjCmd -- * * This function is invoked to process the Tcl "::tcl::unsupported::clock::configure" * (internal, unsupported) command. * * Usage: * ::tcl::unsupported::clock::configure ?-option ?value?? * * Results: * Returns a standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int ClockConfigureObjCmd( void *clientData, /* Client data containing literal pool */ Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const objv[]) /* Parameter vector */ { ClockClientData *dataPtr = (ClockClientData *)clientData; static const char *const options[] = { "-default-locale", "-clear", "-current-locale", "-year-century", "-century-switch", "-min-year", "-max-year", "-max-jdn", "-validate", "-init-complete", "-setup-tz", "-system-tz", NULL }; enum optionInd { CLOCK_DEFAULT_LOCALE, CLOCK_CLEAR_CACHE, CLOCK_CURRENT_LOCALE, CLOCK_YEAR_CENTURY, CLOCK_CENTURY_SWITCH, CLOCK_MIN_YEAR, CLOCK_MAX_YEAR, CLOCK_MAX_JDN, CLOCK_VALIDATE, CLOCK_INIT_COMPLETE, CLOCK_SETUP_TZ, CLOCK_SYSTEM_TZ }; int optionIndex; /* Index of an option. */ Tcl_Size i; for (i = 1; i < objc; i++) { if (Tcl_GetIndexFromObj(interp, objv[i++], options, "option", 0, &optionIndex) != TCL_OK) { Tcl_SetErrorCode(interp, "CLOCK", "badOption", TclGetString(objv[i - 1]), (char *)NULL); return TCL_ERROR; } switch (optionIndex) { case CLOCK_SYSTEM_TZ: { /* validate current tz-epoch */ size_t lastTZEpoch = TzsetIfNecessary(); if (i < objc) { if (dataPtr->systemTimeZone != objv[i]) { TclSetObjRef(dataPtr->systemTimeZone, objv[i]); TclUnsetObjRef(dataPtr->systemSetupTZData); } dataPtr->lastTZEpoch = lastTZEpoch; } if (i + 1 >= objc && dataPtr->systemTimeZone != NULL && dataPtr->lastTZEpoch == lastTZEpoch) { Tcl_SetObjResult(interp, dataPtr->systemTimeZone); } break; } case CLOCK_SETUP_TZ: if (i < objc) { int loaded; Tcl_Obj *timezoneObj = NormTimezoneObj(dataPtr, objv[i], &loaded); if (!loaded) { TimezoneLoaded(dataPtr, timezoneObj, objv[i]); } Tcl_SetObjResult(interp, timezoneObj); } else if (i + 1 >= objc && dataPtr->lastSetupTimeZone != NULL) { Tcl_SetObjResult(interp, dataPtr->lastSetupTimeZone); } break; case CLOCK_DEFAULT_LOCALE: if (i < objc) { if (dataPtr->defaultLocale != objv[i]) { TclSetObjRef(dataPtr->defaultLocale, objv[i]); dataPtr->defaultLocaleDict = NULL; } } if (i + 1 >= objc) { Tcl_SetObjResult(interp, dataPtr->defaultLocale ? dataPtr->defaultLocale : dataPtr->literals[LIT_C]); } break; case CLOCK_CURRENT_LOCALE: if (i < objc) { if (dataPtr->currentLocale != objv[i]) { TclSetObjRef(dataPtr->currentLocale, objv[i]); dataPtr->currentLocaleDict = NULL; } } if (i + 1 >= objc && dataPtr->currentLocale != NULL) { Tcl_SetObjResult(interp, dataPtr->currentLocale); } break; case CLOCK_YEAR_CENTURY: if (i < objc) { int year; if (TclGetIntFromObj(interp, objv[i], &year) != TCL_OK) { return TCL_ERROR; } dataPtr->currentYearCentury = year; if (i + 1 >= objc) { Tcl_SetObjResult(interp, objv[i]); } continue; } if (i + 1 >= objc) { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(dataPtr->currentYearCentury)); } break; case CLOCK_CENTURY_SWITCH: if (i < objc) { int year; if (TclGetIntFromObj(interp, objv[i], &year) != TCL_OK) { return TCL_ERROR; } dataPtr->yearOfCenturySwitch = year; Tcl_SetObjResult(interp, objv[i]); continue; } if (i + 1 >= objc) { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(dataPtr->yearOfCenturySwitch)); } break; case CLOCK_MIN_YEAR: if (i < objc) { int year; if (TclGetIntFromObj(interp, objv[i], &year) != TCL_OK) { return TCL_ERROR; } dataPtr->validMinYear = year; Tcl_SetObjResult(interp, objv[i]); continue; } if (i + 1 >= objc) { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(dataPtr->validMinYear)); } break; case CLOCK_MAX_YEAR: if (i < objc) { int year; if (TclGetIntFromObj(interp, objv[i], &year) != TCL_OK) { return TCL_ERROR; } dataPtr->validMaxYear = year; Tcl_SetObjResult(interp, objv[i]); continue; } if (i + 1 >= objc) { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(dataPtr->validMaxYear)); } break; case CLOCK_MAX_JDN: if (i < objc) { double jd; if (Tcl_GetDoubleFromObj(interp, objv[i], &jd) != TCL_OK) { return TCL_ERROR; } dataPtr->maxJDN = jd; Tcl_SetObjResult(interp, objv[i]); continue; } if (i + 1 >= objc) { Tcl_SetObjResult(interp, Tcl_NewDoubleObj(dataPtr->maxJDN)); } break; case CLOCK_VALIDATE: if (i < objc) { int val; if (Tcl_GetBooleanFromObj(interp, objv[i], &val) != TCL_OK) { return TCL_ERROR; } if (val) { dataPtr->defFlags |= CLF_VALIDATE; } else { dataPtr->defFlags &= ~CLF_VALIDATE; } } if (i + 1 >= objc) { Tcl_SetObjResult(interp, Tcl_NewBooleanObj(dataPtr->defFlags & CLF_VALIDATE)); } break; case CLOCK_CLEAR_CACHE: ClockConfigureClear(dataPtr); break; case CLOCK_INIT_COMPLETE: { /* * Init completed. * Compile clock ensemble (performance purposes). */ Tcl_Command token = Tcl_FindCommand(interp, "::clock", NULL, TCL_GLOBAL_ONLY); if (!token) { return TCL_ERROR; } int ensFlags = 0; if (Tcl_GetEnsembleFlags(interp, token, &ensFlags) != TCL_OK) { return TCL_ERROR; } ensFlags |= ENSEMBLE_COMPILE; if (Tcl_SetEnsembleFlags(interp, token, ensFlags) != TCL_OK) { return TCL_ERROR; } break; } } } return TCL_OK; } /* *---------------------------------------------------------------------- * * ClockGetTZData -- * * Retrieves tzdata table for given normalized timezone. * * Results: * Returns a tcl object with tzdata. * * Side effects: * The tzdata can be cached in ClockClientData structure. * *---------------------------------------------------------------------- */ static inline Tcl_Obj * ClockGetTZData( ClockClientData *dataPtr, /* Opaque pointer to literal pool, etc. */ Tcl_Interp *interp, /* Tcl interpreter */ Tcl_Obj *timezoneObj) /* Name of the timezone */ { Tcl_Obj *ret, **out = NULL; /* if cached (if already setup this one) */ if (timezoneObj == dataPtr->lastSetupTimeZone || timezoneObj == dataPtr->lastSetupTimeZoneUnnorm) { if (dataPtr->lastSetupTZData != NULL) { return dataPtr->lastSetupTZData; } out = &dataPtr->lastSetupTZData; } /* differentiate GMT and system zones, because used often */ /* simple caching, because almost used the tz-data of last timezone */ if (timezoneObj == dataPtr->systemTimeZone) { if (dataPtr->systemSetupTZData != NULL) { return dataPtr->systemSetupTZData; } out = &dataPtr->systemSetupTZData; } else if (timezoneObj == dataPtr->literals[LIT_GMT] || timezoneObj == dataPtr->gmtSetupTimeZoneUnnorm) { if (dataPtr->gmtSetupTZData != NULL) { return dataPtr->gmtSetupTZData; } out = &dataPtr->gmtSetupTZData; } else if (timezoneObj == dataPtr->prevSetupTimeZone || timezoneObj == dataPtr->prevSetupTimeZoneUnnorm) { if (dataPtr->prevSetupTZData != NULL) { return dataPtr->prevSetupTZData; } out = &dataPtr->prevSetupTZData; } ret = Tcl_ObjGetVar2(interp, dataPtr->literals[LIT_TZDATA], timezoneObj, TCL_LEAVE_ERR_MSG); /* cache using corresponding slot and as last used */ if (out != NULL) { TclSetObjRef(*out, ret); } else if (dataPtr->lastSetupTimeZone != timezoneObj) { SavePrevTimezoneObj(dataPtr); TclSetObjRef(dataPtr->lastSetupTimeZone, timezoneObj); TclUnsetObjRef(dataPtr->lastSetupTimeZoneUnnorm); TclSetObjRef(dataPtr->lastSetupTZData, ret); } return ret; } /* *---------------------------------------------------------------------- * * ClockGetSystemTimeZone -- * * Returns system (current) timezone. * * If system zone not yet cached, it executes ::tcl::clock::GetSystemTimeZone * in given interpreter and caches its result. * * Results: * Returns normalized timezone object. * *---------------------------------------------------------------------- */ static Tcl_Obj * ClockGetSystemTimeZone( ClockClientData *dataPtr, /* Pointer to literal pool, etc. */ Tcl_Interp *interp) /* Tcl interpreter */ { /* if known (cached and same epoch) - return now */ if (dataPtr->systemTimeZone != NULL && dataPtr->lastTZEpoch == TzsetIfNecessary()) { return dataPtr->systemTimeZone; } TclUnsetObjRef(dataPtr->systemTimeZone); TclUnsetObjRef(dataPtr->systemSetupTZData); if (Tcl_EvalObjv(interp, 1, &dataPtr->literals[LIT_GETSYSTEMTIMEZONE], 0) != TCL_OK) { return NULL; } if (dataPtr->systemTimeZone == NULL) { TclSetObjRef(dataPtr->systemTimeZone, Tcl_GetObjResult(interp)); } Tcl_ResetResult(interp); return dataPtr->systemTimeZone; } /* *---------------------------------------------------------------------- * * ClockSetupTimeZone -- * * Sets up the timezone. Loads tzdata, etc. * * Results: * Returns normalized timezone object. * *---------------------------------------------------------------------- */ Tcl_Obj * ClockSetupTimeZone( ClockClientData *dataPtr, /* Pointer to literal pool, etc. */ Tcl_Interp *interp, /* Tcl interpreter */ Tcl_Obj *timezoneObj) { int loaded; Tcl_Obj *callargs[2]; /* if cached (if already setup this one) */ if (timezoneObj == dataPtr->literals[LIT_GMT] && dataPtr->gmtSetupTZData != NULL) { return timezoneObj; } if ((timezoneObj == dataPtr->lastSetupTimeZone || timezoneObj == dataPtr->lastSetupTimeZoneUnnorm) && dataPtr->lastSetupTimeZone != NULL) { return dataPtr->lastSetupTimeZone; } if ((timezoneObj == dataPtr->prevSetupTimeZone || timezoneObj == dataPtr->prevSetupTimeZoneUnnorm) && dataPtr->prevSetupTimeZone != NULL) { return dataPtr->prevSetupTimeZone; } /* differentiate normalized (last, GMT and system) zones, because used often and already set */ callargs[1] = NormTimezoneObj(dataPtr, timezoneObj, &loaded); /* if loaded (setup already called for this TZ) */ if (loaded) { return callargs[1]; } /* before setup just take a look in TZData variable */ if (Tcl_ObjGetVar2(interp, dataPtr->literals[LIT_TZDATA], timezoneObj, 0)) { /* put it to last slot and return normalized */ TimezoneLoaded(dataPtr, callargs[1], timezoneObj); return callargs[1]; } /* setup now */ callargs[0] = dataPtr->literals[LIT_SETUPTIMEZONE]; if (Tcl_EvalObjv(interp, 2, callargs, 0) == TCL_OK) { /* save unnormalized last used */ TclSetObjRef(dataPtr->lastSetupTimeZoneUnnorm, timezoneObj); return callargs[1]; } return NULL; } /* *---------------------------------------------------------------------- * * ClockFormatNumericTimeZone -- * * Formats a time zone as +hhmmss * * Parameters: * z - Time zone in seconds east of Greenwich * * Results: * Returns the time zone object (formatted in a numeric form) * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj * ClockFormatNumericTimeZone( int z) { char buf[12 + 1], *p; if (z < 0) { z = -z; *buf = '-'; } else { *buf = '+'; } TclItoAw(buf + 1, z / 3600, '0', 2); z %= 3600; p = TclItoAw(buf + 3, z / 60, '0', 2); z %= 60; if (z != 0) { p = TclItoAw(buf + 5, z, '0', 2); } return Tcl_NewStringObj(buf, p - buf); } /* *---------------------------------------------------------------------- * * ClockConvertlocaltoutcObjCmd -- * * Tcl command that converts a UTC time to a local time by whatever means * is available. * * Usage: * ::tcl::clock::ConvertUTCToLocal dictionary timezone changeover * * Parameters: * dict - Dictionary containing a 'localSeconds' entry. * timezone - Time zone * changeover - Julian Day of the adoption of the Gregorian calendar. * * Results: * Returns a standard Tcl result. * * Side effects: * On success, sets the interpreter result to the given dictionary * augmented with a 'seconds' field giving the UTC time. On failure, * leaves an error message in the interpreter result. * *---------------------------------------------------------------------- */ static int ClockConvertlocaltoutcObjCmd( void *clientData, /* Literal table */ Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const *objv) /* Parameter vector */ { ClockClientData *dataPtr = (ClockClientData *)clientData; Tcl_Obj *secondsObj; Tcl_Obj *dict; int changeover; TclDateFields fields; int created = 0; int status; fields.tzName = NULL; /* * Check params and convert time. */ if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "dict timezone changeover"); return TCL_ERROR; } dict = objv[1]; if (Tcl_DictObjGet(interp, dict, dataPtr->literals[LIT_LOCALSECONDS], &secondsObj)!= TCL_OK) { return TCL_ERROR; } if (secondsObj == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("key \"localseconds\" not " "found in dictionary", TCL_AUTO_LENGTH)); return TCL_ERROR; } if ((TclGetWideIntFromObj(interp, secondsObj, &fields.localSeconds) != TCL_OK) || (TclGetIntFromObj(interp, objv[3], &changeover) != TCL_OK) || ConvertLocalToUTC(dataPtr, interp, &fields, objv[2], changeover)) { return TCL_ERROR; } /* * Copy-on-write; set the 'seconds' field in the dictionary and place the * modified dictionary in the interpreter result. */ if (Tcl_IsShared(dict)) { dict = Tcl_DuplicateObj(dict); created = 1; Tcl_IncrRefCount(dict); } status = Tcl_DictObjPut(interp, dict, dataPtr->literals[LIT_SECONDS], Tcl_NewWideIntObj(fields.seconds)); if (status == TCL_OK) { Tcl_SetObjResult(interp, dict); } if (created) { Tcl_DecrRefCount(dict); } return status; } /* *---------------------------------------------------------------------- * * ClockGetdatefieldsObjCmd -- * * Tcl command that determines the values that [clock format] will use in * formatting a date, and populates a dictionary with them. * * Usage: * ::tcl::clock::GetDateFields seconds timezone changeover * * Parameters: * seconds - Time expressed in seconds from the Posix epoch. * timezone - Time zone in which time is to be expressed. * changeover - Julian Day Number at which the current locale adopted * the Gregorian calendar * * Results: * Returns a dictonary populated with the fields: * seconds - Seconds from the Posix epoch * localSeconds - Nominal seconds from the Posix epoch in the * local time zone. * tzOffset - Time zone offset in seconds east of Greenwich * tzName - Time zone name * julianDay - Julian Day Number in the local time zone * *---------------------------------------------------------------------- */ int ClockGetdatefieldsObjCmd( void *clientData, /* Opaque pointer to literal pool, etc. */ Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const *objv) /* Parameter vector */ { TclDateFields fields; Tcl_Obj *dict; ClockClientData *dataPtr = (ClockClientData *)clientData; Tcl_Obj *const *lit = dataPtr->literals; int changeover; fields.tzName = NULL; /* * Check params. */ if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "seconds timezone changeover"); return TCL_ERROR; } if (TclGetWideIntFromObj(interp, objv[1], &fields.seconds) != TCL_OK || TclGetIntFromObj(interp, objv[3], &changeover) != TCL_OK) { return TCL_ERROR; } /* * fields.seconds could be an unsigned number that overflowed. Make sure * that it isn't. */ if (TclHasInternalRep(objv[1], &tclBignumType)) { Tcl_SetObjResult(interp, lit[LIT_INTEGER_VALUE_TOO_LARGE]); return TCL_ERROR; } /* Extract fields */ if (ClockGetDateFields(dataPtr, interp, &fields, objv[2], changeover) != TCL_OK) { return TCL_ERROR; } /* Make dict of fields */ dict = Tcl_NewDictObj(); Tcl_DictObjPut(NULL, dict, lit[LIT_LOCALSECONDS], Tcl_NewWideIntObj(fields.localSeconds)); Tcl_DictObjPut(NULL, dict, lit[LIT_SECONDS], Tcl_NewWideIntObj(fields.seconds)); Tcl_DictObjPut(NULL, dict, lit[LIT_TZNAME], fields.tzName); Tcl_DecrRefCount(fields.tzName); Tcl_DictObjPut(NULL, dict, lit[LIT_TZOFFSET], Tcl_NewWideIntObj(fields.tzOffset)); Tcl_DictObjPut(NULL, dict, lit[LIT_JULIANDAY], Tcl_NewWideIntObj(fields.julianDay)); Tcl_DictObjPut(NULL, dict, lit[LIT_GREGORIAN], Tcl_NewWideIntObj(fields.gregorian)); Tcl_DictObjPut(NULL, dict, lit[LIT_ERA], lit[fields.isBce ? LIT_BCE : LIT_CE]); Tcl_DictObjPut(NULL, dict, lit[LIT_YEAR], Tcl_NewWideIntObj(fields.year)); Tcl_DictObjPut(NULL, dict, lit[LIT_DAYOFYEAR], Tcl_NewWideIntObj(fields.dayOfYear)); Tcl_DictObjPut(NULL, dict, lit[LIT_MONTH], Tcl_NewWideIntObj(fields.month)); Tcl_DictObjPut(NULL, dict, lit[LIT_DAYOFMONTH], Tcl_NewWideIntObj(fields.dayOfMonth)); Tcl_DictObjPut(NULL, dict, lit[LIT_ISO8601YEAR], Tcl_NewWideIntObj(fields.iso8601Year)); Tcl_DictObjPut(NULL, dict, lit[LIT_ISO8601WEEK], Tcl_NewWideIntObj(fields.iso8601Week)); Tcl_DictObjPut(NULL, dict, lit[LIT_DAYOFWEEK], Tcl_NewWideIntObj(fields.dayOfWeek)); Tcl_SetObjResult(interp, dict); return TCL_OK; } /* *---------------------------------------------------------------------- * * ClockGetDateFields -- * * Converts given UTC time (seconds in a TclDateFields structure) * to local time and determines the values that clock routines will * use in scanning or formatting a date. * * Results: * Date-time values are stored in structure "fields". * Returns a standard Tcl result. * *---------------------------------------------------------------------- */ int ClockGetDateFields( ClockClientData *dataPtr, /* Literal pool, etc. */ Tcl_Interp *interp, /* Tcl interpreter */ TclDateFields *fields, /* Pointer to result fields, where * fields->seconds contains date to extract */ Tcl_Obj *timezoneObj, /* Time zone object or NULL for gmt */ int changeover) /* Julian Day Number */ { /* * Convert UTC time to local. */ if (ConvertUTCToLocal(dataPtr, interp, fields, timezoneObj, changeover) != TCL_OK) { return TCL_ERROR; } /* * Extract Julian day and seconds of the day. */ ClockExtractJDAndSODFromSeconds(fields->julianDay, fields->secondOfDay, fields->localSeconds); /* * Convert to Julian or Gregorian calendar. */ GetGregorianEraYearDay(fields, changeover); GetMonthDay(fields); GetYearWeekDay(fields, changeover); return TCL_OK; } /* *---------------------------------------------------------------------- * * ClockGetjuliandayfromerayearmonthdayObjCmd -- * * Tcl command that converts a time from era-year-month-day to a Julian * Day Number. * * Parameters: * dict - Dictionary that contains 'era', 'year', 'month' and * 'dayOfMonth' keys. * changeover - Julian Day of changeover to the Gregorian calendar * * Results: * Result is either TCL_OK, with the interpreter result being the * dictionary augmented with a 'julianDay' key, or TCL_ERROR, * with the result being an error message. * *---------------------------------------------------------------------- */ static int FetchEraField( Tcl_Interp *interp, Tcl_Obj *dict, Tcl_Obj *key, int *storePtr) { Tcl_Obj *value = NULL; if (Tcl_DictObjGet(interp, dict, key, &value) != TCL_OK) { return TCL_ERROR; } if (value == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "expected key(s) not found in dictionary", TCL_AUTO_LENGTH)); return TCL_ERROR; } return Tcl_GetIndexFromObj(interp, value, eras, "era", TCL_EXACT, storePtr); } static int FetchIntField( Tcl_Interp *interp, Tcl_Obj *dict, Tcl_Obj *key, int *storePtr) { Tcl_Obj *value = NULL; if (Tcl_DictObjGet(interp, dict, key, &value) != TCL_OK) { return TCL_ERROR; } if (value == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "expected key(s) not found in dictionary", TCL_AUTO_LENGTH)); return TCL_ERROR; } return TclGetIntFromObj(interp, value, storePtr); } static int ClockGetjuliandayfromerayearmonthdayObjCmd( void *clientData, /* Opaque pointer to literal pool, etc. */ Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const *objv) /* Parameter vector */ { TclDateFields fields; Tcl_Obj *dict; ClockClientData *data = (ClockClientData *)clientData; Tcl_Obj *const *lit = data->literals; int changeover; int copied = 0; int status; int isBce = 0; fields.tzName = NULL; /* * Check params. */ if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "dict changeover"); return TCL_ERROR; } dict = objv[1]; if (FetchEraField(interp, dict, lit[LIT_ERA], &isBce) != TCL_OK || FetchIntField(interp, dict, lit[LIT_YEAR], &fields.year) != TCL_OK || FetchIntField(interp, dict, lit[LIT_MONTH], &fields.month) != TCL_OK || FetchIntField(interp, dict, lit[LIT_DAYOFMONTH], &fields.dayOfMonth) != TCL_OK || TclGetIntFromObj(interp, objv[2], &changeover) != TCL_OK) { return TCL_ERROR; } fields.isBce = isBce; /* * Get Julian day. */ GetJulianDayFromEraYearMonthDay(&fields, changeover); /* * Store Julian day in the dictionary - copy on write. */ if (Tcl_IsShared(dict)) { dict = Tcl_DuplicateObj(dict); Tcl_IncrRefCount(dict); copied = 1; } status = Tcl_DictObjPut(interp, dict, lit[LIT_JULIANDAY], Tcl_NewWideIntObj(fields.julianDay)); if (status == TCL_OK) { Tcl_SetObjResult(interp, dict); } if (copied) { Tcl_DecrRefCount(dict); } return status; } /* *---------------------------------------------------------------------- * * ClockGetjuliandayfromerayearweekdayObjCmd -- * * Tcl command that converts a time from the ISO calendar to a Julian Day * Number. * * Parameters: * dict - Dictionary that contains 'era', 'iso8601Year', 'iso8601Week' * and 'dayOfWeek' keys. * changeover - Julian Day of changeover to the Gregorian calendar * * Results: * Result is either TCL_OK, with the interpreter result being the * dictionary augmented with a 'julianDay' key, or TCL_ERROR, with the * result being an error message. * *---------------------------------------------------------------------- */ static int ClockGetjuliandayfromerayearweekdayObjCmd( void *clientData, /* Opaque pointer to literal pool, etc. */ Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const *objv) /* Parameter vector */ { TclDateFields fields; Tcl_Obj *dict; ClockClientData *data = (ClockClientData *)clientData; Tcl_Obj *const *lit = data->literals; int changeover; int copied = 0; int status; int isBce = 0; fields.tzName = NULL; /* * Check params. */ if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "dict changeover"); return TCL_ERROR; } dict = objv[1]; if (FetchEraField(interp, dict, lit[LIT_ERA], &isBce) != TCL_OK || FetchIntField(interp, dict, lit[LIT_ISO8601YEAR], &fields.iso8601Year) != TCL_OK || FetchIntField(interp, dict, lit[LIT_ISO8601WEEK], &fields.iso8601Week) != TCL_OK || FetchIntField(interp, dict, lit[LIT_DAYOFWEEK], &fields.dayOfWeek) != TCL_OK || TclGetIntFromObj(interp, objv[2], &changeover) != TCL_OK) { return TCL_ERROR; } fields.isBce = isBce; /* * Get Julian day. */ GetJulianDayFromEraYearWeekDay(&fields, changeover); /* * Store Julian day in the dictionary - copy on write. */ if (Tcl_IsShared(dict)) { dict = Tcl_DuplicateObj(dict); Tcl_IncrRefCount(dict); copied = 1; } status = Tcl_DictObjPut(interp, dict, lit[LIT_JULIANDAY], Tcl_NewWideIntObj(fields.julianDay)); if (status == TCL_OK) { Tcl_SetObjResult(interp, dict); } if (copied) { Tcl_DecrRefCount(dict); } return status; } /* *---------------------------------------------------------------------- * * ConvertLocalToUTC -- * * Converts a time (in a TclDateFields structure) from the local wall * clock to UTC. * * Results: * Returns a standard Tcl result. * * Side effects: * Populates the 'seconds' field if successful; stores an error message * in the interpreter result on failure. * *---------------------------------------------------------------------- */ static int ConvertLocalToUTC( ClockClientData *dataPtr, /* Literal pool, etc. */ Tcl_Interp *interp, /* Tcl interpreter */ TclDateFields *fields, /* Fields of the time */ Tcl_Obj *timezoneObj, /* Time zone */ int changeover) /* Julian Day of the Gregorian transition */ { Tcl_Obj *tzdata; /* Time zone data */ Tcl_Size rowc; /* Number of rows in tzdata */ Tcl_Obj **rowv; /* Pointers to the rows */ Tcl_WideInt seconds; ClockLastTZOffs * ltzoc = NULL; /* fast phase-out for shared GMT-object (don't need to convert UTC 2 UTC) */ if (timezoneObj == dataPtr->literals[LIT_GMT]) { fields->seconds = fields->localSeconds; fields->tzOffset = 0; return TCL_OK; } /* * Check cacheable conversion could be used * (last-period UTC2Local cache within the same TZ and seconds) */ for (rowc = 0; rowc < 2; rowc++) { ltzoc = &dataPtr->lastTZOffsCache[rowc]; if (timezoneObj != ltzoc->timezoneObj || changeover != ltzoc->changeover) { ltzoc = NULL; continue; } seconds = fields->localSeconds - ltzoc->tzOffset; if (seconds >= ltzoc->rangesVal[0] && seconds < ltzoc->rangesVal[1]) { /* the same time zone and offset (UTC time inside the last minute) */ fields->tzOffset = ltzoc->tzOffset; fields->seconds = seconds; return TCL_OK; } /* in the DST-hole (because of the check above) - correct localSeconds */ if (fields->localSeconds == ltzoc->localSeconds) { /* the same time zone and offset (but we'll shift local-time) */ fields->tzOffset = ltzoc->tzOffset; fields->seconds = seconds; goto dstHole; } } /* * Unpack the tz data. */ tzdata = ClockGetTZData(dataPtr, interp, timezoneObj); if (tzdata == NULL) { return TCL_ERROR; } if (TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK) { return TCL_ERROR; } /* * Special case: If the time zone is :localtime, the tzdata will be empty. * Use 'mktime' to convert the time to local */ if (rowc == 0) { if (ConvertLocalToUTCUsingC(interp, fields, changeover) != TCL_OK) { return TCL_ERROR; } /* we cannot cache (ranges unknown yet) - todo: check later the DST-hole here */ return TCL_OK; } else { Tcl_WideInt rangesVal[2]; if (ConvertLocalToUTCUsingTable(interp, fields, rowc, rowv, rangesVal) != TCL_OK) { return TCL_ERROR; } seconds = fields->seconds; /* Cache the last conversion */ if (ltzoc != NULL) { /* slot was found above */ /* timezoneObj and changeover are the same */ TclSetObjRef(ltzoc->tzName, fields->tzName); /* may be NULL */ } else { /* no TZ in cache - just move second slot down and use the first one */ ltzoc = &dataPtr->lastTZOffsCache[0]; TclUnsetObjRef(dataPtr->lastTZOffsCache[1].timezoneObj); TclUnsetObjRef(dataPtr->lastTZOffsCache[1].tzName); memcpy(&dataPtr->lastTZOffsCache[1], ltzoc, sizeof(*ltzoc)); TclInitObjRef(ltzoc->timezoneObj, timezoneObj); ltzoc->changeover = changeover; TclInitObjRef(ltzoc->tzName, fields->tzName); /* may be NULL */ } ltzoc->localSeconds = fields->localSeconds; ltzoc->rangesVal[0] = rangesVal[0]; ltzoc->rangesVal[1] = rangesVal[1]; ltzoc->tzOffset = fields->tzOffset; } /* check DST-hole: if retrieved seconds is out of range */ if (ltzoc->rangesVal[0] > seconds || seconds >= ltzoc->rangesVal[1]) { dstHole: #if 0 printf("given local-time is outside the time-zone (in DST-hole): " "%" TCL_LL_MODIFIER "d - offs %d => %" TCL_LL_MODIFIER "d <= %" TCL_LL_MODIFIER "d < %" TCL_LL_MODIFIER "d\n", fields->localSeconds, fields->tzOffset, ltzoc->rangesVal[0], seconds, ltzoc->rangesVal[1]); #endif /* because we don't know real TZ (we're outsize), just invalidate local * time (which could be verified in ClockValidDate later) */ fields->localSeconds = TCL_INV_SECONDS; /* not valid seconds */ } return TCL_OK; } /* *---------------------------------------------------------------------- * * ConvertLocalToUTCUsingTable -- * * Converts a time (in a TclDateFields structure) from local time in a * given time zone to UTC. * * Results: * Returns a standard Tcl result. * * Side effects: * Stores an error message in the interpreter if an error occurs; if * successful, stores the 'seconds' field in 'fields. * *---------------------------------------------------------------------- */ static int ConvertLocalToUTCUsingTable( Tcl_Interp *interp, /* Tcl interpreter */ TclDateFields *fields, /* Time to convert, with 'seconds' filled in */ int rowc, /* Number of points at which time changes */ Tcl_Obj *const rowv[], /* Points at which time changes */ Tcl_WideInt *rangesVal) /* Return bounds for time period */ { Tcl_Obj *row; Tcl_Size cellc; Tcl_Obj **cellv; struct { Tcl_Obj *tzName; int tzOffset; } have[8]; int nHave = 0; Tcl_Size i; /* * Perform an initial lookup assuming that local == UTC, and locate the * last time conversion prior to that time. Get the offset from that row, * and look up again. Continue until we find an offset that we found * before. This definition, rather than "the same offset" ensures that we * don't enter an endless loop, as would otherwise happen when trying to * convert a non-existent time such as 02:30 during the US Spring Daylight * Saving Time transition. */ fields->tzOffset = 0; fields->seconds = fields->localSeconds; while (1) { row = LookupLastTransition(interp, fields->seconds, rowc, rowv, rangesVal); if ((row == NULL) || TclListObjGetElements(interp, row, &cellc, &cellv) != TCL_OK || TclGetIntFromObj(interp, cellv[1], &fields->tzOffset) != TCL_OK) { return TCL_ERROR; } for (i = 0; i < nHave; ++i) { if (have[i].tzOffset == fields->tzOffset) { goto found; } } if (nHave == 8) { Tcl_Panic("loop in ConvertLocalToUTCUsingTable"); } have[nHave].tzName = cellv[3]; have[nHave++].tzOffset = fields->tzOffset; fields->seconds = fields->localSeconds - fields->tzOffset; } found: fields->tzOffset = have[i].tzOffset; fields->seconds = fields->localSeconds - fields->tzOffset; TclSetObjRef(fields->tzName, have[i].tzName); return TCL_OK; } /* *---------------------------------------------------------------------- * * ConvertLocalToUTCUsingC -- * * Converts a time from local wall clock to UTC when the local time zone * cannot be determined. Uses 'mktime' to do the job. * * Results: * Returns a standard Tcl result. * * Side effects: * Stores an error message in the interpreter if an error occurs; if * successful, stores the 'seconds' field in 'fields. * *---------------------------------------------------------------------- */ static int ConvertLocalToUTCUsingC( Tcl_Interp *interp, /* Tcl interpreter */ TclDateFields *fields, /* Time to convert, with 'seconds' filled in */ int changeover) /* Julian Day of the Gregorian transition */ { struct tm timeVal; int localErrno; int secondOfDay; /* * Convert the given time to a date. */ ClockExtractJDAndSODFromSeconds(fields->julianDay, secondOfDay, fields->localSeconds); GetGregorianEraYearDay(fields, changeover); GetMonthDay(fields); /* * Convert the date/time to a 'struct tm'. */ timeVal.tm_year = fields->year - 1900; timeVal.tm_mon = fields->month - 1; timeVal.tm_mday = fields->dayOfMonth; timeVal.tm_hour = (secondOfDay / 3600) % 24; timeVal.tm_min = (secondOfDay / 60) % 60; timeVal.tm_sec = secondOfDay % 60; timeVal.tm_isdst = -1; timeVal.tm_wday = -1; timeVal.tm_yday = -1; /* * Get local time. It is rumored that mktime is not thread safe on some * platforms, so seize a mutex before attempting this. */ TzsetIfNecessary(); Tcl_MutexLock(&clockMutex); errno = 0; fields->seconds = (Tcl_WideInt) mktime(&timeVal); localErrno = (fields->seconds == -1) ? errno : 0; Tcl_MutexUnlock(&clockMutex); /* * If conversion fails, report an error. */ if (localErrno != 0 || (fields->seconds == -1 && timeVal.tm_yday == -1)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "time value too large/small to represent", TCL_AUTO_LENGTH)); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ConvertUTCToLocal -- * * Converts a time (in a TclDateFields structure) from UTC to local time. * * Results: * Returns a standard Tcl result. * * Side effects: * Populates the 'tzName' and 'tzOffset' fields. * *---------------------------------------------------------------------- */ int ConvertUTCToLocal( ClockClientData *dataPtr, /* Literal pool, etc. */ Tcl_Interp *interp, /* Tcl interpreter */ TclDateFields *fields, /* Fields of the time */ Tcl_Obj *timezoneObj, /* Time zone */ int changeover) /* Julian Day of the Gregorian transition */ { Tcl_Obj *tzdata; /* Time zone data */ Tcl_Size rowc; /* Number of rows in tzdata */ Tcl_Obj **rowv; /* Pointers to the rows */ ClockLastTZOffs * ltzoc = NULL; /* fast phase-out for shared GMT-object (don't need to convert UTC 2 UTC) */ if (timezoneObj == dataPtr->literals[LIT_GMT]) { fields->localSeconds = fields->seconds; fields->tzOffset = 0; if (dataPtr->gmtTZName == NULL) { Tcl_Obj *tzName; tzdata = ClockGetTZData(dataPtr, interp, timezoneObj); if (TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK || Tcl_ListObjIndex(interp, rowv[0], 3, &tzName) != TCL_OK) { return TCL_ERROR; } TclSetObjRef(dataPtr->gmtTZName, tzName); } TclSetObjRef(fields->tzName, dataPtr->gmtTZName); return TCL_OK; } /* * Check cacheable conversion could be used * (last-period UTC2Local cache within the same TZ and seconds) */ for (rowc = 0; rowc < 2; rowc++) { ltzoc = &dataPtr->lastTZOffsCache[rowc]; if (timezoneObj != ltzoc->timezoneObj || changeover != ltzoc->changeover) { ltzoc = NULL; continue; } if (fields->seconds >= ltzoc->rangesVal[0] && fields->seconds < ltzoc->rangesVal[1]) { /* the same time zone and offset (UTC time inside the last minute) */ fields->tzOffset = ltzoc->tzOffset; fields->localSeconds = fields->seconds + fields->tzOffset; TclSetObjRef(fields->tzName, ltzoc->tzName); return TCL_OK; } } /* * Unpack the tz data. */ tzdata = ClockGetTZData(dataPtr, interp, timezoneObj); if (tzdata == NULL) { return TCL_ERROR; } if (TclListObjGetElements(interp, tzdata, &rowc, &rowv) != TCL_OK) { return TCL_ERROR; } /* * Special case: If the time zone is :localtime, the tzdata will be empty. * Use 'localtime' to convert the time to local */ if (rowc == 0) { if (ConvertUTCToLocalUsingC(interp, fields, changeover) != TCL_OK) { return TCL_ERROR; } /* signal we need to revalidate TZ epoch next time fields gets used. */ fields->flags |= CLF_CTZ; /* we cannot cache (ranges unknown yet) */ } else { Tcl_WideInt rangesVal[2]; if (ConvertUTCToLocalUsingTable(interp, fields, rowc, rowv, rangesVal) != TCL_OK) { return TCL_ERROR; } /* converted using table (TZ isn't :localtime) */ fields->flags &= ~CLF_CTZ; /* Cache the last conversion */ if (ltzoc != NULL) { /* slot was found above */ /* timezoneObj and changeover are the same */ TclSetObjRef(ltzoc->tzName, fields->tzName); } else { /* no TZ in cache - just move second slot down and use the first one */ ltzoc = &dataPtr->lastTZOffsCache[0]; TclUnsetObjRef(dataPtr->lastTZOffsCache[1].timezoneObj); TclUnsetObjRef(dataPtr->lastTZOffsCache[1].tzName); memcpy(&dataPtr->lastTZOffsCache[1], ltzoc, sizeof(*ltzoc)); TclInitObjRef(ltzoc->timezoneObj, timezoneObj); ltzoc->changeover = changeover; TclInitObjRef(ltzoc->tzName, fields->tzName); } ltzoc->localSeconds = fields->localSeconds; ltzoc->rangesVal[0] = rangesVal[0]; ltzoc->rangesVal[1] = rangesVal[1]; ltzoc->tzOffset = fields->tzOffset; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ConvertUTCToLocalUsingTable -- * * Converts UTC to local time, given a table of transition points * * Results: * Returns a standard Tcl result * * Side effects: * On success, fills fields->tzName, fields->tzOffset and * fields->localSeconds. On failure, places an error message in the * interpreter result. * *---------------------------------------------------------------------- */ static int ConvertUTCToLocalUsingTable( Tcl_Interp *interp, /* Tcl interpreter */ TclDateFields *fields, /* Fields of the date */ Tcl_Size rowc, /* Number of rows in the conversion table * (>= 1) */ Tcl_Obj *const rowv[], /* Rows of the conversion table */ Tcl_WideInt *rangesVal) /* Return bounds for time period */ { Tcl_Obj *row; /* Row containing the current information */ Tcl_Size cellc; /* Count of cells in the row (must be 4) */ Tcl_Obj **cellv; /* Pointers to the cells */ /* * Look up the nearest transition time. */ row = LookupLastTransition(interp, fields->seconds, rowc, rowv, rangesVal); if (row == NULL || TclListObjGetElements(interp, row, &cellc, &cellv) != TCL_OK || TclGetIntFromObj(interp, cellv[1], &fields->tzOffset) != TCL_OK) { return TCL_ERROR; } /* * Convert the time. */ TclSetObjRef(fields->tzName, cellv[3]); fields->localSeconds = fields->seconds + fields->tzOffset; return TCL_OK; } /* *---------------------------------------------------------------------- * * ConvertUTCToLocalUsingC -- * * Converts UTC to localtime in cases where the local time zone is not * determinable, using the C 'localtime' function to do it. * * Results: * Returns a standard Tcl result. * * Side effects: * On success, fills fields->tzName, fields->tzOffset and * fields->localSeconds. On failure, places an error message in the * interpreter result. * *---------------------------------------------------------------------- */ static int ConvertUTCToLocalUsingC( Tcl_Interp *interp, /* Tcl interpreter */ TclDateFields *fields, /* Time to convert, with 'seconds' filled in */ int changeover) /* Julian Day of the Gregorian transition */ { time_t tock; struct tm *timeVal; /* Time after conversion */ int diff; /* Time zone diff local-Greenwich */ char buffer[16], *p; /* Buffer for time zone name */ /* * Use 'localtime' to determine local year, month, day, time of day. */ tock = (time_t) fields->seconds; if ((Tcl_WideInt) tock != fields->seconds) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "number too large to represent as a Posix time", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "CLOCK", "argTooLarge", (char *)NULL); return TCL_ERROR; } TzsetIfNecessary(); timeVal = ThreadSafeLocalTime(&tock); if (timeVal == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "localtime failed (clock value may be too " "large/small to represent)", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "CLOCK", "localtimeFailed", (char *)NULL); return TCL_ERROR; } /* * Fill in the date in 'fields' and use it to derive Julian Day. */ fields->isBce = 0; fields->year = timeVal->tm_year + 1900; fields->month = timeVal->tm_mon + 1; fields->dayOfMonth = timeVal->tm_mday; GetJulianDayFromEraYearMonthDay(fields, changeover); /* * Convert that value to seconds. */ fields->localSeconds = (((fields->julianDay * 24LL + timeVal->tm_hour) * 60 + timeVal->tm_min) * 60 + timeVal->tm_sec) - JULIAN_SEC_POSIX_EPOCH; /* * Determine a time zone offset and name; just use +hhmm for the name. */ diff = (int) (fields->localSeconds - fields->seconds); fields->tzOffset = diff; if (diff < 0) { *buffer = '-'; diff = -diff; } else { *buffer = '+'; } TclItoAw(buffer + 1, diff / 3600, '0', 2); diff %= 3600; p = TclItoAw(buffer + 3, diff / 60, '0', 2); diff %= 60; if (diff != 0) { p = TclItoAw(buffer + 5, diff, '0', 2); } TclSetObjRef(fields->tzName, Tcl_NewStringObj(buffer, p - buffer)); return TCL_OK; } /* *---------------------------------------------------------------------- * * LookupLastTransition -- * * Given a UTC time and a tzdata array, looks up the last transition on * or before the given time. * * Results: * Returns a pointer to the row, or NULL if an error occurs. * *---------------------------------------------------------------------- */ Tcl_Obj * LookupLastTransition( Tcl_Interp *interp, /* Interpreter for error messages */ Tcl_WideInt tick, /* Time from the epoch */ Tcl_Size rowc, /* Number of rows of tzdata */ Tcl_Obj *const *rowv, /* Rows in tzdata */ Tcl_WideInt *rangesVal) /* Return bounds for time period */ { Tcl_Size l, u; Tcl_Obj *compObj; Tcl_WideInt compVal, fromVal = LLONG_MIN, toVal = LLONG_MAX; /* * Examine the first row to make sure we're in bounds. */ if (Tcl_ListObjIndex(interp, rowv[0], 0, &compObj) != TCL_OK || TclGetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) { return NULL; } /* * Bizarre case - first row doesn't begin at MIN_WIDE_INT. Return it * anyway. */ if (tick < (fromVal = compVal)) { if (rangesVal) { rangesVal[0] = fromVal; rangesVal[1] = toVal; } return rowv[0]; } /* * Binary-search to find the transition. */ l = 0; u = rowc - 1; while (l < u) { Tcl_Size m = (l + u + 1) / 2; if (Tcl_ListObjIndex(interp, rowv[m], 0, &compObj) != TCL_OK || TclGetWideIntFromObj(interp, compObj, &compVal) != TCL_OK) { return NULL; } if (tick >= compVal) { l = m; fromVal = compVal; } else { u = m - 1; toVal = compVal; } } if (rangesVal) { rangesVal[0] = fromVal; rangesVal[1] = toVal; } return rowv[l]; } /* *---------------------------------------------------------------------- * * GetYearWeekDay -- * * Given a date with Julian Calendar Day, compute the year, week, and day * in the ISO8601 calendar. * * Results: * None. * * Side effects: * Stores 'iso8601Year', 'iso8601Week' and 'dayOfWeek' in the date * fields. * *---------------------------------------------------------------------- */ static void GetYearWeekDay( TclDateFields *fields, /* Date to convert, must have 'julianDay' */ int changeover) /* Julian Day Number of the Gregorian * transition */ { TclDateFields temp; int dayOfFiscalYear; temp.tzName = NULL; /* * Find the given date, minus three days, plus one year. That date's * iso8601 year is an upper bound on the ISO8601 year of the given date. */ temp.julianDay = fields->julianDay - 3; GetGregorianEraYearDay(&temp, changeover); if (temp.isBce) { temp.iso8601Year = temp.year - 1; } else { temp.iso8601Year = temp.year + 1; } temp.iso8601Week = 1; temp.dayOfWeek = 1; GetJulianDayFromEraYearWeekDay(&temp, changeover); /* * temp.julianDay is now the start of an ISO8601 year, either the one * corresponding to the given date, or the one after. If we guessed high, * move one year earlier */ if (fields->julianDay < temp.julianDay) { if (temp.isBce) { temp.iso8601Year += 1; } else { temp.iso8601Year -= 1; } GetJulianDayFromEraYearWeekDay(&temp, changeover); } fields->iso8601Year = temp.iso8601Year; dayOfFiscalYear = fields->julianDay - temp.julianDay; fields->iso8601Week = (dayOfFiscalYear / 7) + 1; fields->dayOfWeek = (dayOfFiscalYear + 1) % 7; if (fields->dayOfWeek < 1) { /* Mon .. Sun == 1 .. 7 */ fields->dayOfWeek += 7; } } /* *---------------------------------------------------------------------- * * GetGregorianEraYearDay -- * * Given a Julian Day Number, extracts the year and day of the year and * puts them into TclDateFields, along with the era (BCE or CE) and a * flag indicating whether the date is Gregorian or Julian. * * Results: * None. * * Side effects: * Stores 'era', 'gregorian', 'year', and 'dayOfYear'. * *---------------------------------------------------------------------- */ static void GetGregorianEraYearDay( TclDateFields *fields, /* Date fields containing 'julianDay' */ int changeover) /* Gregorian transition date */ { Tcl_WideInt jday = fields->julianDay; Tcl_WideInt day; Tcl_WideInt year; Tcl_WideInt n; if (jday >= changeover) { /* * Gregorian calendar. */ fields->gregorian = 1; year = 1; /* * n = Number of 400-year cycles since 1 January, 1 CE in the * proleptic Gregorian calendar. day = remaining days. */ day = jday - JDAY_1_JAN_1_CE_GREGORIAN; n = day / FOUR_CENTURIES; day %= FOUR_CENTURIES; if (day < 0) { day += FOUR_CENTURIES; n--; } year += 400 * n; /* * n = number of centuries since the start of (year); * day = remaining days */ n = day / ONE_CENTURY_GREGORIAN; day %= ONE_CENTURY_GREGORIAN; if (n > 3) { /* * 31 December in the last year of a 400-year cycle. */ n = 3; day += ONE_CENTURY_GREGORIAN; } year += 100 * n; } else { /* * Julian calendar. */ fields->gregorian = 0; year = 1; day = jday - JDAY_1_JAN_1_CE_JULIAN; } /* * n = number of 4-year cycles; days = remaining days. */ n = day / FOUR_YEARS; day %= FOUR_YEARS; if (day < 0) { day += FOUR_YEARS; n--; } year += 4 * n; /* * n = number of years; days = remaining days. */ n = day / ONE_YEAR; day %= ONE_YEAR; if (n > 3) { /* * 31 December of a leap year. */ n = 3; day += 365; } year += n; /* * store era/year/day back into fields. */ if (year <= 0) { fields->isBce = 1; fields->year = 1 - year; } else { fields->isBce = 0; fields->year = year; } fields->dayOfYear = day + 1; } /* *---------------------------------------------------------------------- * * GetMonthDay -- * * Given a date as year and day-of-year, find month and day. * * Results: * None. * * Side effects: * Stores 'month' and 'dayOfMonth' in the 'fields' structure. * *---------------------------------------------------------------------- */ static void GetMonthDay( TclDateFields *fields) /* Date to convert */ { int day = fields->dayOfYear; int month; const int *dipm = daysInPriorMonths[IsGregorianLeapYear(fields)]; /* * Estimate month by calculating `dayOfYear / (365/12)` */ month = (day*12) / dipm[12]; /* then do forwards backwards correction */ while (1) { if (day > dipm[month]) { if (month >= 11 || day <= dipm[month + 1]) { break; } month++; } else { if (month == 0) { break; } month--; } } day -= dipm[month]; fields->month = month + 1; fields->dayOfMonth = day; } /* *---------------------------------------------------------------------- * * GetJulianDayFromEraYearWeekDay -- * * Given a TclDateFields structure containing era, ISO8601 year, ISO8601 * week, and day of week, computes the Julian Day Number. * * Results: * None. * * Side effects: * Stores 'julianDay' in the fields. * *---------------------------------------------------------------------- */ void GetJulianDayFromEraYearWeekDay( TclDateFields *fields, /* Date to convert */ int changeover) /* Julian Day Number of the Gregorian * transition */ { Tcl_WideInt firstMonday; /* Julian day number of week 1, day 1 in the * given year */ TclDateFields firstWeek; firstWeek.tzName = NULL; /* * Find January 4 in the ISO8601 year, which will always be in week 1. */ firstWeek.isBce = fields->isBce; firstWeek.year = fields->iso8601Year; firstWeek.month = 1; firstWeek.dayOfMonth = 4; GetJulianDayFromEraYearMonthDay(&firstWeek, changeover); /* * Find Monday of week 1. */ firstMonday = WeekdayOnOrBefore(1, firstWeek.julianDay); /* * Advance to the given week and day. */ fields->julianDay = firstMonday + 7 * (fields->iso8601Week - 1) + fields->dayOfWeek - 1; } /* *---------------------------------------------------------------------- * * GetJulianDayFromEraYearMonthDay -- * * Given era, year, month, and dayOfMonth (in TclDateFields), and the * Gregorian transition date, computes the Julian Day Number. * * Results: * None. * * Side effects: * Stores day number in 'julianDay' * *---------------------------------------------------------------------- */ void GetJulianDayFromEraYearMonthDay( TclDateFields *fields, /* Date to convert */ int changeover) /* Gregorian transition date as a Julian Day */ { Tcl_WideInt year, ym1, ym1o4, ym1o100, ym1o400; int month, mm1, q, r; if (fields->isBce) { year = 1 - fields->year; } else { year = fields->year; } /* * Reduce month modulo 12. */ month = fields->month; mm1 = month - 1; q = mm1 / 12; r = (mm1 % 12); if (r < 0) { r += 12; q -= 1; } year += q; month = r + 1; ym1 = year - 1; /* * Adjust the year after reducing the month. */ fields->gregorian = 1; if (year < 1) { fields->isBce = 1; fields->year = 1 - year; } else { fields->isBce = 0; fields->year = year; } /* * Try an initial conversion in the Gregorian calendar. */ #if 0 /* BUG https://core.tcl-lang.org/tcl/tktview?name=da340d4f32 */ ym1o4 = ym1 / 4; #else /* * Have to make sure quotient is truncated towards 0 when negative. * See above bug for details. The casts are necessary. */ if (ym1 >= 0) { ym1o4 = ym1 / 4; } else { ym1o4 = - (int) (((unsigned int) -ym1) / 4); } #endif if (ym1 % 4 < 0) { ym1o4--; } ym1o100 = ym1 / 100; if (ym1 % 100 < 0) { ym1o100--; } ym1o400 = ym1 / 400; if (ym1 % 400 < 0) { ym1o400--; } fields->julianDay = JDAY_1_JAN_1_CE_GREGORIAN - 1 + fields->dayOfMonth + daysInPriorMonths[IsGregorianLeapYear(fields)][month - 1] + (ONE_YEAR * ym1) + ym1o4 - ym1o100 + ym1o400; /* * If the resulting date is before the Gregorian changeover, convert in * the Julian calendar instead. */ if (fields->julianDay < changeover) { fields->gregorian = 0; fields->julianDay = JDAY_1_JAN_1_CE_JULIAN - 1 + fields->dayOfMonth + daysInPriorMonths[year%4 == 0][month - 1] + (365 * ym1) + ym1o4; } } /* *---------------------------------------------------------------------- * * GetJulianDayFromEraYearDay -- * * Given era, year, and dayOfYear (in TclDateFields), and the * Gregorian transition date, computes the Julian Day Number. * * Results: * None. * * Side effects: * Stores day number in 'julianDay' * *---------------------------------------------------------------------- */ void GetJulianDayFromEraYearDay( TclDateFields *fields, /* Date to convert */ int changeover) /* Gregorian transition date as a Julian Day */ { Tcl_WideInt year, ym1; /* Get absolute year number from the civil year */ if (fields->isBce) { year = 1 - fields->year; } else { year = fields->year; } ym1 = year - 1; /* Try the Gregorian calendar first. */ fields->gregorian = 1; fields->julianDay = 1721425 + fields->dayOfYear + (365 * ym1) + (ym1 / 4) - (ym1 / 100) + (ym1 / 400); /* If the date is before the Gregorian change, use the Julian calendar. */ if (fields->julianDay < changeover) { fields->gregorian = 0; fields->julianDay = 1721423 + fields->dayOfYear + (365 * ym1) + (ym1 / 4); } } /* *---------------------------------------------------------------------- * * IsGregorianLeapYear -- * * Tests whether a given year is a leap year, in either Julian or * Gregorian calendar. * * Results: * Returns 1 for a leap year, 0 otherwise. * *---------------------------------------------------------------------- */ int IsGregorianLeapYear( TclDateFields *fields) /* Date to test */ { Tcl_WideInt year = fields->year; if (fields->isBce) { year = 1 - year; } if (year % 4 != 0) { return 0; } else if (!(fields->gregorian)) { return 1; } else if (year % 400 == 0) { return 1; } else if (year % 100 == 0) { return 0; } else { return 1; } } /* *---------------------------------------------------------------------- * * WeekdayOnOrBefore -- * * Finds the Julian Day Number of a given day of the week that falls on * or before a given date, expressed as Julian Day Number. * * Results: * Returns the Julian Day Number * *---------------------------------------------------------------------- */ static Tcl_WideInt WeekdayOnOrBefore( int dayOfWeek, /* Day of week; Sunday == 0 or 7 */ Tcl_WideInt julianDay) /* Reference date */ { int k = (dayOfWeek + 6) % 7; if (k < 0) { k += 7; } return julianDay - ((julianDay - k) % 7); } /* *---------------------------------------------------------------------- * * ClockGetenvObjCmd -- * * Tcl command that reads an environment variable from the system * * Usage: * ::tcl::clock::getEnv NAME * * Parameters: * NAME - Name of the environment variable desired * * Results: * Returns a standard Tcl result. Returns an error if the variable does * not exist, with a message left in the interpreter. Returns TCL_OK and * the value of the variable if the variable does exist, * *---------------------------------------------------------------------- */ int ClockGetenvObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { #ifdef _WIN32 const WCHAR *varName; const WCHAR *varValue; Tcl_DString ds; #else const char *varName; const char *varValue; #endif if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } #ifdef _WIN32 Tcl_DStringInit(&ds); varName = Tcl_UtfToWCharDString(TclGetString(objv[1]), -1, &ds); varValue = _wgetenv(varName); if (varValue == NULL) { Tcl_DStringFree(&ds); } else { Tcl_DStringSetLength(&ds, 0); Tcl_WCharToUtfDString(varValue, -1, &ds); Tcl_DStringResult(interp, &ds); } #else varName = TclGetString(objv[1]); varValue = getenv(varName); if (varValue != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( varValue, TCL_AUTO_LENGTH)); } #endif return TCL_OK; } /* *---------------------------------------------------------------------- * * ThreadSafeLocalTime -- * * Wrapper around the 'localtime' library function to make it thread * safe. * * Results: * Returns a pointer to a 'struct tm' in thread-specific data. * * Side effects: * Invokes localtime or localtime_r as appropriate. * *---------------------------------------------------------------------- */ static struct tm * ThreadSafeLocalTime( const time_t *timePtr) /* Pointer to the number of seconds since the * local system's epoch */ { /* * Get a thread-local buffer to hold the returned time. */ struct tm *tmPtr = (struct tm *)Tcl_GetThreadData(&tmKey, sizeof(struct tm)); #ifdef HAVE_LOCALTIME_R tmPtr = localtime_r(timePtr, tmPtr); #else struct tm *sysTmPtr; Tcl_MutexLock(&clockMutex); sysTmPtr = localtime(timePtr); if (sysTmPtr == NULL) { Tcl_MutexUnlock(&clockMutex); return NULL; } memcpy(tmPtr, sysTmPtr, sizeof(struct tm)); Tcl_MutexUnlock(&clockMutex); #endif return tmPtr; } /*---------------------------------------------------------------------- * * ClockClicksObjCmd -- * * Returns a high-resolution counter. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * * This function implements the 'clock clicks' Tcl command. Refer to the user * documentation for details on what it does. * *---------------------------------------------------------------------- */ int ClockClicksObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const *objv) /* Parameter values */ { static const char *const clicksSwitches[] = { "-milliseconds", "-microseconds", NULL }; enum ClicksSwitch { CLICKS_MILLIS, CLICKS_MICROS, CLICKS_NATIVE }; int index = CLICKS_NATIVE; Tcl_Time now; Tcl_WideInt clicks = 0; switch (objc) { case 1: break; case 2: if (Tcl_GetIndexFromObj(interp, objv[1], clicksSwitches, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } break; default: Tcl_WrongNumArgs(interp, 0, objv, "clock clicks ?-switch?"); return TCL_ERROR; } switch (index) { case CLICKS_MILLIS: Tcl_GetTime(&now); clicks = now.sec * 1000LL + now.usec / 1000; break; case CLICKS_NATIVE: #ifdef TCL_WIDE_CLICKS clicks = TclpGetWideClicks(); #else clicks = (Tcl_WideInt)TclpGetClicks(); #endif break; case CLICKS_MICROS: clicks = TclpGetMicroseconds(); break; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(clicks)); return TCL_OK; } /*---------------------------------------------------------------------- * * ClockMillisecondsObjCmd - * * Returns a count of milliseconds since the epoch. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * * This function implements the 'clock milliseconds' Tcl command. Refer to the * user documentation for details on what it does. * *---------------------------------------------------------------------- */ int ClockMillisecondsObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const *objv) /* Parameter values */ { Tcl_Time now; Tcl_Obj *timeObj; if (objc != 1) { Tcl_WrongNumArgs(interp, 0, objv, "clock milliseconds"); return TCL_ERROR; } Tcl_GetTime(&now); TclNewUIntObj(timeObj, (Tcl_WideUInt) now.sec * 1000 + now.usec / 1000); Tcl_SetObjResult(interp, timeObj); return TCL_OK; } /*---------------------------------------------------------------------- * * ClockMicrosecondsObjCmd - * * Returns a count of microseconds since the epoch. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * * This function implements the 'clock microseconds' Tcl command. Refer to the * user documentation for details on what it does. * *---------------------------------------------------------------------- */ int ClockMicrosecondsObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const *objv) /* Parameter values */ { if (objc != 1) { Tcl_WrongNumArgs(interp, 0, objv, "clock microseconds"); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(TclpGetMicroseconds())); return TCL_OK; } static inline void ClockInitFmtScnArgs( ClockClientData *dataPtr, Tcl_Interp *interp, ClockFmtScnCmdArgs *opts) { memset(opts, 0, sizeof(*opts)); opts->dataPtr = dataPtr; opts->interp = interp; } /* *----------------------------------------------------------------------------- * * ClockParseFmtScnArgs -- * * Parses the arguments for sub-commands "scan", "format" and "add". * * Note: common options table used here, because for the options often used * the same literals (objects), so it avoids permanent "recompiling" of * option object representation to indexType with another table. * * Results: * Returns a standard Tcl result, and stores parsed options * (format, the locale, timezone and base) in structure "opts". * *----------------------------------------------------------------------------- */ typedef enum ClockOperation { CLC_OP_FMT = 0, /* Doing [clock format] */ CLC_OP_SCN, /* Doing [clock scan] */ CLC_OP_ADD /* Doing [clock add] */ } ClockOperation; static int ClockParseFmtScnArgs( ClockFmtScnCmdArgs *opts, /* Result vector: format, locale, timezone... */ TclDateFields *date, /* Extracted date-time corresponding base * (by scan or add) resp. clockval (by format) */ Tcl_Size objc, /* Parameter count */ Tcl_Obj *const objv[], /* Parameter vector */ ClockOperation operation, /* What operation are we doing: format, scan, add */ const char *syntax) /* Syntax of the current command */ { Tcl_Interp *interp = opts->interp; ClockClientData *dataPtr = opts->dataPtr; int gmtFlag = 0; static const char *const options[] = { "-base", "-format", "-gmt", "-locale", "-timezone", "-validate", NULL }; enum optionInd { CLC_ARGS_BASE, CLC_ARGS_FORMAT, CLC_ARGS_GMT, CLC_ARGS_LOCALE, CLC_ARGS_TIMEZONE, CLC_ARGS_VALIDATE }; int optionIndex; /* Index of an option. */ int saw = 0; /* Flag == 1 if option was seen already. */ Tcl_Size i, baseIdx; Tcl_WideInt baseVal; /* Base time, expressed in seconds from the Epoch */ if (operation == CLC_OP_SCN) { /* default flags (from configure) */ opts->flags |= dataPtr->defFlags & CLF_VALIDATE; } else { /* clock value (as current base) */ opts->baseObj = objv[(baseIdx = 1)]; saw |= 1 << CLC_ARGS_BASE; } /* * Extract values for the keywords. */ for (i = 2; i < objc; i+=2) { /* bypass integers (offsets) by "clock add" */ if (operation == CLC_OP_ADD) { Tcl_WideInt num; if (TclGetWideIntFromObj(NULL, objv[i], &num) == TCL_OK) { continue; } } /* get option */ if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", 0, &optionIndex) != TCL_OK) { goto badOptionMsg; } /* if already specified */ if (saw & (1 << optionIndex)) { if (operation != CLC_OP_SCN && optionIndex == CLC_ARGS_BASE) { goto badOptionMsg; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad option \"%s\": doubly present", TclGetString(objv[i]))); goto badOption; } switch (optionIndex) { case CLC_ARGS_FORMAT: if (operation == CLC_OP_ADD) { goto badOptionMsg; } opts->formatObj = objv[i + 1]; break; case CLC_ARGS_GMT: if (Tcl_GetBooleanFromObj(interp, objv[i + 1], &gmtFlag) != TCL_OK){ return TCL_ERROR; } break; case CLC_ARGS_LOCALE: opts->localeObj = objv[i + 1]; break; case CLC_ARGS_TIMEZONE: opts->timezoneObj = objv[i + 1]; break; case CLC_ARGS_BASE: opts->baseObj = objv[(baseIdx = i + 1)]; break; case CLC_ARGS_VALIDATE: if (operation != CLC_OP_SCN) { goto badOptionMsg; } else { int val; if (Tcl_GetBooleanFromObj(interp, objv[i + 1], &val) != TCL_OK) { return TCL_ERROR; } if (val) { opts->flags |= CLF_VALIDATE; } else { opts->flags &= ~CLF_VALIDATE; } } break; } saw |= 1 << optionIndex; } /* * Check options. */ if ((saw & (1 << CLC_ARGS_GMT)) && (saw & (1 << CLC_ARGS_TIMEZONE))) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot use -gmt and -timezone in same call", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "CLOCK", "gmtWithTimezone", (char *)NULL); return TCL_ERROR; } if (gmtFlag) { opts->timezoneObj = dataPtr->literals[LIT_GMT]; } else if (opts->timezoneObj == NULL || TclGetString(opts->timezoneObj) == NULL || opts->timezoneObj->length == 0) { /* If time zone not specified use system time zone */ opts->timezoneObj = ClockGetSystemTimeZone(dataPtr, interp); if (opts->timezoneObj == NULL) { return TCL_ERROR; } } /* Setup timezone (normalize object if needed and load TZ on demand) */ opts->timezoneObj = ClockSetupTimeZone(dataPtr, interp, opts->timezoneObj); if (opts->timezoneObj == NULL) { return TCL_ERROR; } /* Base (by scan or add) or clock value (by format) */ if (opts->baseObj != NULL) { Tcl_Obj *baseObj = opts->baseObj; /* bypass integer recognition if looks like "now" or "-now" */ if ((baseObj->bytes && ((baseObj->length == 3 && baseObj->bytes[0] == 'n') || (baseObj->length == 4 && baseObj->bytes[1] == 'n'))) || TclGetWideIntFromObj(NULL, baseObj, &baseVal) != TCL_OK) { /* we accept "now" and "-now" as current date-time */ static const char *const nowOpts[] = { "now", "-now", NULL }; int idx; if (Tcl_GetIndexFromObj(NULL, baseObj, nowOpts, "seconds", TCL_EXACT, &idx) == TCL_OK) { goto baseNow; } if (TclHasInternalRep(baseObj, &tclBignumType)) { goto baseOverflow; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad seconds \"%s\": must be now or integer", TclGetString(baseObj))); i = baseIdx; goto badOption; } /* * Seconds could be an unsigned number that overflowed. Make sure * that it isn't. Additionally it may be too complex to calculate * julianday etc (forwards/backwards) by too large/small values, thus * just let accept a bit shorter values to avoid overflow. * Note the year is currently an integer, thus avoid to overflow it also. */ if (TclHasInternalRep(baseObj, &tclBignumType) || baseVal < TCL_MIN_SECONDS || baseVal > TCL_MAX_SECONDS) { baseOverflow: Tcl_SetObjResult(interp, dataPtr->literals[LIT_INTEGER_VALUE_TOO_LARGE]); i = baseIdx; goto badOption; } } else { Tcl_Time now; baseNow: Tcl_GetTime(&now); baseVal = (Tcl_WideInt) now.sec; } /* * Extract year, month and day from the base time for the parser to use as * defaults */ /* check base fields already cached (by TZ, last-second cache) */ if (dataPtr->lastBase.timezoneObj == opts->timezoneObj && dataPtr->lastBase.date.seconds == baseVal && (!(dataPtr->lastBase.date.flags & CLF_CTZ) || dataPtr->lastTZEpoch == TzsetIfNecessary())) { memcpy(date, &dataPtr->lastBase.date, ClockCacheableDateFieldsSize); } else { /* extact fields from base */ date->seconds = baseVal; if (ClockGetDateFields(dataPtr, interp, date, opts->timezoneObj, GREGORIAN_CHANGE_DATE) != TCL_OK) { /* TODO - GREGORIAN_CHANGE_DATE should be locale-dependent */ return TCL_ERROR; } /* cache last base */ memcpy(&dataPtr->lastBase.date, date, ClockCacheableDateFieldsSize); TclSetObjRef(dataPtr->lastBase.timezoneObj, opts->timezoneObj); } return TCL_OK; badOptionMsg: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad option \"%s\": must be %s", TclGetString(objv[i]), syntax)); badOption: Tcl_SetErrorCode(interp, "CLOCK", "badOption", (i < objc) ? TclGetString(objv[i]) : (char *)NULL, (char *)NULL); return TCL_ERROR; } /*---------------------------------------------------------------------- * * ClockFormatObjCmd -- , clock format -- * * This function is invoked to process the Tcl "clock format" command. * * Formats a count of seconds since the Posix Epoch as a time of day. * * The 'clock format' command formats times of day for output. Refer * to the user documentation to see what it does. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int ClockFormatObjCmd( void *clientData, /* Client data containing literal pool */ Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const objv[]) /* Parameter values */ { ClockClientData *dataPtr = (ClockClientData *)clientData; static const char *syntax = "clock format clockval|now " "?-format string? " "?-gmt boolean? " "?-locale LOCALE? ?-timezone ZONE?"; int ret; ClockFmtScnCmdArgs opts; /* Format, locale, timezone and base */ DateFormat dateFmt; /* Common structure used for formatting */ /* even number of arguments */ if ((objc & 1) == 1) { Tcl_WrongNumArgs(interp, 0, objv, syntax); Tcl_SetErrorCode(interp, "CLOCK", "wrongNumArgs", (char *)NULL); return TCL_ERROR; } memset(&dateFmt, 0, sizeof(dateFmt)); /* * Extract values for the keywords. */ ClockInitFmtScnArgs(dataPtr, interp, &opts); ret = ClockParseFmtScnArgs(&opts, &dateFmt.date, objc, objv, CLC_OP_FMT, "-format, -gmt, -locale, or -timezone"); if (ret != TCL_OK) { goto done; } /* Default format */ if (opts.formatObj == NULL) { opts.formatObj = dataPtr->literals[LIT__DEFAULT_FORMAT]; } /* Use compiled version of Format - */ ret = ClockFormat(&dateFmt, &opts); done: TclUnsetObjRef(dateFmt.date.tzName); return ret; } /*---------------------------------------------------------------------- * * ClockScanObjCmd -- , clock scan -- * * This function is invoked to process the Tcl "clock scan" command. * * Inputs a count of seconds since the Posix Epoch as a time of day. * * The 'clock scan' command scans times of day on input. Refer to the * user documentation to see what it does. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int ClockScanObjCmd( void *clientData, /* Client data containing literal pool */ Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const objv[]) /* Parameter values */ { ClockClientData *dataPtr = (ClockClientData *)clientData; static const char *syntax = "clock scan string " "?-base seconds? " "?-format string? " "?-gmt boolean? " "?-locale LOCALE? ?-timezone ZONE? ?-validate boolean?"; int ret; ClockFmtScnCmdArgs opts; /* Format, locale, timezone and base */ DateInfo yy; /* Common structure used for parsing */ DateInfo *info = &yy; /* even number of arguments */ if ((objc & 1) == 1) { Tcl_WrongNumArgs(interp, 0, objv, syntax); Tcl_SetErrorCode(interp, "CLOCK", "wrongNumArgs", (char *)NULL); return TCL_ERROR; } ClockInitDateInfo(&yy); /* * Extract values for the keywords. */ ClockInitFmtScnArgs(dataPtr, interp, &opts); ret = ClockParseFmtScnArgs(&opts, &yy.date, objc, objv, CLC_OP_SCN, "-base, -format, -gmt, -locale, -timezone or -validate"); if (ret != TCL_OK) { goto done; } /* seconds are in localSeconds (relative base date), so reset time here */ yyHour = yyMinutes = yySeconds = yySecondOfDay = 0; yyMeridian = MER24; /* If free scan */ if (opts.formatObj == NULL) { /* Use compiled version of FreeScan - */ /* [SB] TODO: Perhaps someday we'll localize the legacy code. Right now, * it's not localized. */ if (opts.localeObj != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "legacy [clock scan] does not support -locale", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "CLOCK", "flagWithLegacyFormat", (char *)NULL); ret = TCL_ERROR; goto done; } ret = ClockFreeScan(&yy, objv[1], &opts); } else { /* Use compiled version of Scan - */ ret = ClockScan(&yy, objv[1], &opts); } if (ret != TCL_OK) { goto done; } /* * If no GMT and not free-scan (where valid stage 1 is done in-between), * validate with stage 1 before local time conversion, otherwise it may * adjust date/time tokens to valid values */ if ((opts.flags & CLF_VALIDATE_S1) && info->flags & (CLF_ASSEMBLE_SECONDS|CLF_LOCALSEC)) { ret = ClockValidDate(&yy, &opts, CLF_VALIDATE_S1); if (ret != TCL_OK) { goto done; } } /* Convert date info structure into UTC seconds */ ret = ClockScanCommit(&yy, &opts); if (ret != TCL_OK) { goto done; } /* Apply remaining validation rules, if expected */ if (opts.flags & CLF_VALIDATE) { ret = ClockValidDate(&yy, &opts, opts.flags & CLF_VALIDATE); if (ret != TCL_OK) { goto done; } } done: TclUnsetObjRef(yy.date.tzName); if (ret != TCL_OK) { return ret; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(yy.date.seconds)); return TCL_OK; } /*---------------------------------------------------------------------- * * ClockScanCommit -- * * Converts date info structure into UTC seconds. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int ClockScanCommit( DateInfo *info, /* Clock scan info structure */ ClockFmtScnCmdArgs *opts) /* Format, locale, timezone and base */ { /* If needed assemble julianDay using year, month, etc. */ if (info->flags & CLF_ASSEMBLE_JULIANDAY) { if (info->flags & CLF_ISO8601WEEK) { GetJulianDayFromEraYearWeekDay(&yydate, GREGORIAN_CHANGE_DATE); } else if (!(info->flags & CLF_DAYOFYEAR) /* no day of year */ || (info->flags & (CLF_DAYOFMONTH|CLF_MONTH)) /* yymmdd over yyddd */ == (CLF_DAYOFMONTH|CLF_MONTH)) { GetJulianDayFromEraYearMonthDay(&yydate, GREGORIAN_CHANGE_DATE); } else { GetJulianDayFromEraYearDay(&yydate, GREGORIAN_CHANGE_DATE); } info->flags |= CLF_ASSEMBLE_SECONDS; info->flags &= ~CLF_ASSEMBLE_JULIANDAY; } /* some overflow checks */ if (info->flags & CLF_JULIANDAY) { double curJDN = (double)yydate.julianDay + ((double)yySecondOfDay - SECONDS_PER_DAY/2) / SECONDS_PER_DAY; if (curJDN > opts->dataPtr->maxJDN) { Tcl_SetObjResult(opts->interp, Tcl_NewStringObj( "requested date too large to represent", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(opts->interp, "CLOCK", "dateTooLarge", (char *)NULL); return TCL_ERROR; } } /* If seconds overflows the day (not valide case, or 24:00), increase days */ if (yySecondOfDay >= SECONDS_PER_DAY) { yydate.julianDay += (yySecondOfDay / SECONDS_PER_DAY); yySecondOfDay %= SECONDS_PER_DAY; } /* Local seconds to UTC (stored in yydate.seconds) */ if (info->flags & CLF_ASSEMBLE_SECONDS) { yydate.localSeconds = -210866803200LL + (SECONDS_PER_DAY * yydate.julianDay) + yySecondOfDay; } if (info->flags & (CLF_ASSEMBLE_SECONDS | CLF_LOCALSEC)) { if (ConvertLocalToUTC(opts->dataPtr, opts->interp, &yydate, opts->timezoneObj, GREGORIAN_CHANGE_DATE) != TCL_OK) { return TCL_ERROR; } } /* Increment UTC seconds with relative time */ yydate.seconds += yyRelSeconds; return TCL_OK; } /*---------------------------------------------------------------------- * * ClockValidDate -- * * Validate date info structure for wrong data (e. g. out of ranges). * * Results: * Returns a standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int ClockValidDate( DateInfo *info, /* Clock scan info structure */ ClockFmtScnCmdArgs *opts, /* Scan options */ int stage) /* Stage to validate (1, 2 or 3 for both) */ { const char *errMsg = "", *errCode = ""; TclDateFields temp; int tempCpyFlg = 0; ClockClientData *dataPtr = opts->dataPtr; #if 0 printf("yyMonth %d, yyDay %d, yyDayOfYear %d, yyHour %d, yyMinutes %d, yySeconds %" TCL_LL_MODIFIER "d, " "yySecondOfDay %" TCL_LL_MODIFIER "d, sec %" TCL_LL_MODIFIER "d, daySec %" TCL_LL_MODIFIER "d, tzOffset %d\n", yyMonth, yyDay, yydate.dayOfYear, yyHour, yyMinutes, yySeconds, yySecondOfDay, yydate.localSeconds, yydate.localSeconds % SECONDS_PER_DAY, yydate.tzOffset); #endif if (!(stage & CLF_VALIDATE_S1) || !(opts->flags & CLF_VALIDATE_S1)) { goto stage_2; } opts->flags &= ~CLF_VALIDATE_S1; /* stage 1 is done */ /* first year (used later in hath / daysInPriorMonths) */ if ((info->flags & (CLF_YEAR | CLF_ISO8601YEAR))) { if ((info->flags & CLF_ISO8601YEAR)) { if (yydate.iso8601Year < dataPtr->validMinYear || yydate.iso8601Year > dataPtr->validMaxYear) { errMsg = "invalid iso year"; errCode = "iso year"; goto error; } } if (info->flags & CLF_YEAR) { if (yyYear < dataPtr->validMinYear || yyYear > dataPtr->validMaxYear) { errMsg = "invalid year"; errCode = "year"; goto error; } } else if ((info->flags & CLF_ISO8601YEAR)) { yyYear = yydate.iso8601Year; /* used to recognize leap */ } if ((info->flags & (CLF_ISO8601YEAR | CLF_YEAR)) == (CLF_ISO8601YEAR | CLF_YEAR)) { if (yyYear != yydate.iso8601Year) { errMsg = "ambiguous year"; errCode = "year"; goto error; } } } /* and month (used later in hath) */ if (info->flags & CLF_MONTH) { if (yyMonth < 1 || yyMonth > 12) { errMsg = "invalid month"; errCode = "month"; goto error; } } /* day of month */ if (info->flags & (CLF_DAYOFMONTH|CLF_DAYOFWEEK)) { if (yyDay < 1 || yyDay > 31) { errMsg = "invalid day"; errCode = "day"; goto error; } if ((info->flags & CLF_MONTH)) { const int *h = hath[IsGregorianLeapYear(&yydate)]; if (yyDay > h[yyMonth - 1]) { errMsg = "invalid day"; errCode = "day"; goto error; } } } if (info->flags & CLF_DAYOFYEAR) { if (yydate.dayOfYear < 1 || yydate.dayOfYear > daysInPriorMonths[IsGregorianLeapYear(&yydate)][12]) { errMsg = "invalid day of year"; errCode = "day of year"; goto error; } } /* mmdd !~ ddd */ if ((info->flags & (CLF_DAYOFYEAR|CLF_DAYOFMONTH|CLF_MONTH)) == (CLF_DAYOFYEAR|CLF_DAYOFMONTH|CLF_MONTH)) { if (!tempCpyFlg) { memcpy(&temp, &yydate, sizeof(temp)); tempCpyFlg = 1; } GetJulianDayFromEraYearDay(&temp, GREGORIAN_CHANGE_DATE); if (temp.julianDay != yydate.julianDay) { errMsg = "ambiguous day"; errCode = "day"; goto error; } } if (info->flags & CLF_TIME) { /* hour */ if (yyHour < 0 || yyHour > ((yyMeridian == MER24) ? 23 : 12)) { /* allow 24:00:00 as special case, see [aee9f2b916afd976] */ if (yyMeridian == MER24 && yyHour == 24) { if (yyMinutes != 0 || yySeconds != 0) { errMsg = "invalid time"; errCode = "time"; goto error; } /* 24:00 is next day 00:00, correct day of week if given */ if (info->flags & CLF_DAYOFWEEK) { if (++yyDayOfWeek > 7) { /* Mon .. Sun == 1 .. 7 */ yyDayOfWeek = 1; } } } else { errMsg = "invalid time (hour)"; errCode = "hour"; goto error; } } /* minutes */ if (yyMinutes < 0 || yyMinutes > 59) { errMsg = "invalid time (minutes)"; errCode = "minutes"; goto error; } /* oldscan could return secondOfDay -1 by invalid time (see ToSeconds) */ if (yySeconds < 0 || yySeconds > 59 || yySecondOfDay <= -1) { errMsg = "invalid time"; errCode = "seconds"; goto error; } } if (!(stage & CLF_VALIDATE_S2) || !(opts->flags & CLF_VALIDATE_S2)) { return TCL_OK; } /* * Further tests expected ready calculated julianDay (inclusive relative), * and time-zone conversion (local to UTC time). */ stage_2: opts->flags &= ~CLF_VALIDATE_S2; /* stage 2 is done */ /* time, regarding the modifications by the time-zone (looks for given time * in between DST-time hole, so does not exist in this time-zone) */ if (info->flags & CLF_TIME) { /* * we don't need to do the backwards time-conversion (UTC to local) and * compare results, because the after conversion (local to UTC) we * should have valid localSeconds (was not invalidated to TCL_INV_SECONDS), * so if it was invalidated - invalid time, outside the time-zone (in DST-hole) */ if (yydate.localSeconds == TCL_INV_SECONDS) { errMsg = "invalid time (does not exist in this time-zone)"; errCode = "out-of-time"; goto error; } } /* day of week */ if (info->flags & CLF_DAYOFWEEK) { if (!tempCpyFlg) { memcpy(&temp, &yydate, sizeof(temp)); tempCpyFlg = 1; } GetYearWeekDay(&temp, GREGORIAN_CHANGE_DATE); if (temp.dayOfWeek != yyDayOfWeek) { errMsg = "invalid day of week"; errCode = "day of week"; goto error; } } return TCL_OK; error: Tcl_SetObjResult(opts->interp, Tcl_ObjPrintf( "unable to convert input string: %s", errMsg)); Tcl_SetErrorCode(opts->interp, "CLOCK", "invInpStr", errCode, (char *)NULL); return TCL_ERROR; } /*---------------------------------------------------------------------- * * ClockFreeScan -- * * Used by ClockScanObjCmd for free scanning without format. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int ClockFreeScan( DateInfo *info, /* Date fields used for parsing & converting * simultaneously a yy-parse structure of the * TclClockFreeScan */ Tcl_Obj *strObj, /* String containing the time to scan */ ClockFmtScnCmdArgs *opts) /* Command options */ { Tcl_Interp *interp = opts->interp; ClockClientData *dataPtr = opts->dataPtr; int ret = TCL_ERROR; /* * Parse the date. The parser will fill a structure "info" with date, * time, time zone, relative month/day/seconds, relative weekday, ordinal * month. * Notice that many yy-defines point to values in the "info" or "date" * structure, e. g. yySecondOfDay -> info->date.secondOfDay or * yyMonth -> info->date.month (same as yydate.month) */ yyInput = TclGetString(strObj); if (TclClockFreeScan(interp, info) != TCL_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unable to convert date-time string \"%s\": %s", TclGetString(strObj), Tcl_GetString(Tcl_GetObjResult(interp)))); goto done; } /* * If the caller supplied a date in the string, update the date with * the value. If the caller didn't specify a time with the date, default to * midnight. */ if (info->flags & CLF_YEAR) { if (yyYear < 100) { if (yyYear >= dataPtr->yearOfCenturySwitch) { yyYear -= 100; } yyYear += dataPtr->currentYearCentury; } yydate.isBce = 0; info->flags |= CLF_ASSEMBLE_JULIANDAY|CLF_ASSEMBLE_SECONDS; } /* * If the caller supplied a time zone in the string, make it into a time * zone indicator of +-hhmm and setup this time zone. */ if (info->flags & CLF_ZONE) { if (yyTimezone || !yyDSTmode) { /* Real time zone from numeric zone */ Tcl_Obj *tzObjStor = NULL; int minEast = -yyTimezone; int dstFlag = 1 - yyDSTmode; tzObjStor = ClockFormatNumericTimeZone( 60 * minEast + 3600 * dstFlag); Tcl_IncrRefCount(tzObjStor); opts->timezoneObj = ClockSetupTimeZone(dataPtr, interp, tzObjStor); Tcl_DecrRefCount(tzObjStor); } else { /* simplest case - GMT / UTC */ opts->timezoneObj = ClockSetupTimeZone(dataPtr, interp, dataPtr->literals[LIT_GMT]); } if (opts->timezoneObj == NULL) { goto done; } // TclSetObjRef(yydate.tzName, opts->timezoneObj); info->flags |= CLF_ASSEMBLE_SECONDS; } /* * For freescan apply validation rules (stage 1) before mixed with * relative time (otherwise always valid recalculated date & time). */ if (opts->flags & CLF_VALIDATE) { if (ClockValidDate(info, opts, CLF_VALIDATE_S1) != TCL_OK) { goto done; } } /* * Assemble date, time, zone into seconds-from-epoch */ if ((info->flags & (CLF_TIME | CLF_HAVEDATE)) == CLF_HAVEDATE) { yySecondOfDay = 0; info->flags |= CLF_ASSEMBLE_SECONDS; } else if (info->flags & CLF_TIME) { yySecondOfDay = ToSeconds(yyHour, yyMinutes, yySeconds, yyMeridian); info->flags |= CLF_ASSEMBLE_SECONDS; } else if ((info->flags & (CLF_DAYOFWEEK | CLF_HAVEDATE)) == CLF_DAYOFWEEK || (info->flags & CLF_ORDINALMONTH) || ((info->flags & CLF_RELCONV) && (yyRelMonth != 0 || yyRelDay != 0))) { yySecondOfDay = 0; info->flags |= CLF_ASSEMBLE_SECONDS; } else { yySecondOfDay = yydate.localSeconds % SECONDS_PER_DAY; } /* * Do relative times */ ret = ClockCalcRelTime(info); /* Free scanning completed - date ready */ done: return ret; } /*---------------------------------------------------------------------- * * ClockCalcRelTime -- * * Used for calculating of relative times. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int ClockCalcRelTime( DateInfo *info) /* Date fields used for converting */ { int prevDayOfWeek = yyDayOfWeek; /* preserve unchanged day of week */ /* * Because some calculations require in-between conversion of the * julian day, we can repeat this processing multiple times */ repeat_rel: if (info->flags & CLF_RELCONV) { /* * Relative conversion normally possible in UTC time only, because * of possible wrong local time increment if ignores in-between DST-hole. * (see test-cases clock-34.53, clock-34.54). * So increment date in julianDay, but time inside day in UTC (seconds). */ /* add months (or years in months) */ if (yyRelMonth != 0) { int m, h; /* if needed extract year, month, etc. again */ if (info->flags & CLF_ASSEMBLE_DATE) { GetGregorianEraYearDay(&yydate, GREGORIAN_CHANGE_DATE); GetMonthDay(&yydate); GetYearWeekDay(&yydate, GREGORIAN_CHANGE_DATE); info->flags &= ~CLF_ASSEMBLE_DATE; } /* add the requisite number of months */ yyMonth += yyRelMonth - 1; yyYear += yyMonth / 12; m = yyMonth % 12; /* compiler fix for negative offs - wrap y, m = (0, -1) -> (-1, 11) */ if (m < 0) { yyYear--; m = 12 + m; } yyMonth = m + 1; /* if the day doesn't exist in the current month, repair it */ h = hath[IsGregorianLeapYear(&yydate)][m]; if (yyDay > h) { yyDay = h; } /* on demand (lazy) assemble julianDay using new year, month, etc. */ info->flags |= CLF_ASSEMBLE_JULIANDAY | CLF_ASSEMBLE_SECONDS; yyRelMonth = 0; } /* add days (or other parts aligned to days) */ if (yyRelDay) { /* assemble julianDay using new year, month, etc. */ if (info->flags & CLF_ASSEMBLE_JULIANDAY) { GetJulianDayFromEraYearMonthDay(&yydate, GREGORIAN_CHANGE_DATE); info->flags &= ~CLF_ASSEMBLE_JULIANDAY; } yydate.julianDay += yyRelDay; /* julianDay was changed, on demand (lazy) extract year, month, etc. again */ info->flags |= CLF_ASSEMBLE_DATE|CLF_ASSEMBLE_SECONDS; yyRelDay = 0; } /* relative time (seconds), if exceeds current date, do the day conversion and * leave rest of the increment in yyRelSeconds to add it hereafter in UTC seconds */ if (yyRelSeconds) { Tcl_WideInt newSecs = yySecondOfDay + yyRelSeconds; /* if seconds increment outside of current date, increment day */ if (newSecs / SECONDS_PER_DAY != yySecondOfDay / SECONDS_PER_DAY) { yyRelDay += newSecs / SECONDS_PER_DAY; yySecondOfDay = 0; yyRelSeconds = newSecs % SECONDS_PER_DAY; goto repeat_rel; } } info->flags &= ~CLF_RELCONV; } /* * Do relative (ordinal) month */ if (info->flags & CLF_ORDINALMONTH) { int monthDiff; /* if needed extract year, month, etc. again */ if (info->flags & CLF_ASSEMBLE_DATE) { GetGregorianEraYearDay(&yydate, GREGORIAN_CHANGE_DATE); GetMonthDay(&yydate); GetYearWeekDay(&yydate, GREGORIAN_CHANGE_DATE); info->flags &= ~CLF_ASSEMBLE_DATE; } if (yyMonthOrdinalIncr > 0) { monthDiff = yyMonthOrdinal - yyMonth; if (monthDiff <= 0) { monthDiff += 12; } yyMonthOrdinalIncr--; } else { monthDiff = yyMonth - yyMonthOrdinal; if (monthDiff >= 0) { monthDiff -= 12; } yyMonthOrdinalIncr++; } /* process it further via relative times */ yyYear += yyMonthOrdinalIncr; yyRelMonth += monthDiff; info->flags &= ~CLF_ORDINALMONTH; info->flags |= CLF_RELCONV|CLF_ASSEMBLE_JULIANDAY|CLF_ASSEMBLE_SECONDS; goto repeat_rel; } /* * Do relative weekday */ if ((info->flags & (CLF_DAYOFWEEK|CLF_HAVEDATE)) == CLF_DAYOFWEEK) { /* restore scanned day of week */ yyDayOfWeek = prevDayOfWeek; /* if needed assemble julianDay now */ if (info->flags & CLF_ASSEMBLE_JULIANDAY) { GetJulianDayFromEraYearMonthDay(&yydate, GREGORIAN_CHANGE_DATE); info->flags &= ~CLF_ASSEMBLE_JULIANDAY; } yydate.isBce = 0; yydate.julianDay = WeekdayOnOrBefore(yyDayOfWeek, yydate.julianDay + 6) + 7 * yyDayOrdinal; if (yyDayOrdinal > 0) { yydate.julianDay -= 7; } info->flags |= CLF_ASSEMBLE_DATE|CLF_ASSEMBLE_SECONDS; } return TCL_OK; } /*---------------------------------------------------------------------- * * ClockWeekdaysOffs -- * * Get offset in days for the number of week days corresponding the * given day of week (skipping Saturdays and Sundays). * * * Results: * Returns a day increment adjusted the given weekdays * *---------------------------------------------------------------------- */ static inline int ClockWeekdaysOffs( int dayOfWeek, int offs) { int weeks, resDayOfWeek; /* offset in days */ weeks = offs / 5; offs = offs % 5; /* compiler fix for negative offs - wrap (0, -1) -> (-1, 4) */ if (offs < 0) { weeks--; offs = 5 + offs; } offs += 7 * weeks; /* resulting day of week */ { int day = (offs % 7); /* compiler fix for negative offs - wrap (0, -1) -> (-1, 6) */ if (day < 0) { day = 7 + day; } resDayOfWeek = dayOfWeek + day; } /* adjust if we start from a weekend */ if (dayOfWeek > 5) { int adj = 5 - dayOfWeek; offs += adj; resDayOfWeek += adj; } /* adjust if we end up on a weekend */ if (resDayOfWeek > 5) { offs += 2; } return offs; } /*---------------------------------------------------------------------- * * ClockAddObjCmd -- , clock add -- * * Adds an offset to a given time. * * Refer to the user documentation to see what it exactly does. * * Syntax: * clock add clockval ?count unit?... ?-option value? * * Parameters: * clockval -- Starting time value * count -- Amount of a unit of time to add * unit -- Unit of time to add, must be one of: * years year months month weeks week * days day hours hour minutes minute * seconds second * * Options: * -gmt BOOLEAN * Flag synonymous with '-timezone :GMT' * -timezone ZONE * Name of the time zone in which calculations are to be done. * -locale NAME * Name of the locale in which calculations are to be done. * Used to determine the Gregorian change date. * * Results: * Returns a standard Tcl result with the given time adjusted * by the given offset(s) in order. * * Notes: * It is possible that adding a number of months or years will adjust the * day of the month as well. For instance, the time at one month after * 31 January is either 28 or 29 February, because February has fewer * than 31 days. * *---------------------------------------------------------------------- */ int ClockAddObjCmd( void *clientData, /* Client data containing literal pool */ Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const objv[]) /* Parameter values */ { static const char *syntax = "clock add clockval|now ?number units?..." "?-gmt boolean? " "?-locale LOCALE? ?-timezone ZONE?"; ClockClientData *dataPtr = (ClockClientData *)clientData; int ret; ClockFmtScnCmdArgs opts; /* Format, locale, timezone and base */ DateInfo yy; /* Common structure used for parsing */ DateInfo *info = &yy; /* add "week" to units also (because otherwise ambiguous) */ static const char *const units[] = { "years", "months", "week", "weeks", "days", "weekdays", "hours", "minutes", "seconds", NULL }; enum unitInd { CLC_ADD_YEARS, CLC_ADD_MONTHS, CLC_ADD_WEEK, CLC_ADD_WEEKS, CLC_ADD_DAYS, CLC_ADD_WEEKDAYS, CLC_ADD_HOURS, CLC_ADD_MINUTES, CLC_ADD_SECONDS }; int unitIndex; /* Index of an option. */ Tcl_Size i; Tcl_WideInt offs; /* even number of arguments */ if ((objc & 1) == 1) { Tcl_WrongNumArgs(interp, 0, objv, syntax); Tcl_SetErrorCode(interp, "CLOCK", "wrongNumArgs", (char *)NULL); return TCL_ERROR; } ClockInitDateInfo(&yy); /* * Extract values for the keywords. */ ClockInitFmtScnArgs(dataPtr, interp, &opts); ret = ClockParseFmtScnArgs(&opts, &yy.date, objc, objv, CLC_OP_ADD, "-gmt, -locale, or -timezone"); if (ret != TCL_OK) { goto done; } /* time together as seconds of the day */ yySecondOfDay = yySeconds = yydate.localSeconds % SECONDS_PER_DAY; /* seconds are in localSeconds (relative base date), so reset time here */ yyHour = 0; yyMinutes = 0; yyMeridian = MER24; ret = TCL_ERROR; /* * Find each offset and process date increment */ for (i = 2; i < objc; i+=2) { /* bypass not integers (options, allready processed above in ClockParseFmtScnArgs) */ if (TclGetWideIntFromObj(NULL, objv[i], &offs) != TCL_OK) { continue; } /* get unit */ if (Tcl_GetIndexFromObj(interp, objv[i + 1], units, "unit", 0, &unitIndex) != TCL_OK) { goto done; } if (TclHasInternalRep(objv[i], &tclBignumType) || offs > (unitIndex < CLC_ADD_HOURS ? 0x7fffffff : TCL_MAX_SECONDS) || offs < (unitIndex < CLC_ADD_HOURS ? -0x7fffffff : TCL_MIN_SECONDS)) { Tcl_SetObjResult(interp, dataPtr->literals[LIT_INTEGER_VALUE_TOO_LARGE]); goto done; } /* nothing to do if zero quantity */ if (!offs) { continue; } /* if in-between conversion needed (already have relative date/time), * correct date info, because the date may be changed, * so refresh it now */ if ((info->flags & CLF_RELCONV) && (unitIndex == CLC_ADD_WEEKDAYS /* some months can be shorter as another */ || yyRelMonth || yyRelDay /* day changed */ || yySeconds + yyRelSeconds > SECONDS_PER_DAY || yySeconds + yyRelSeconds < 0)) { if (ClockCalcRelTime(info) != TCL_OK) { goto done; } } /* process increment by offset + unit */ info->flags |= CLF_RELCONV; switch (unitIndex) { case CLC_ADD_YEARS: yyRelMonth += offs * 12; break; case CLC_ADD_MONTHS: yyRelMonth += offs; break; case CLC_ADD_WEEK: case CLC_ADD_WEEKS: yyRelDay += offs * 7; break; case CLC_ADD_DAYS: yyRelDay += offs; break; case CLC_ADD_WEEKDAYS: /* add number of week days (skipping Saturdays and Sundays) * to a relative days value. */ offs = ClockWeekdaysOffs(yy.date.dayOfWeek, offs); yyRelDay += offs; break; case CLC_ADD_HOURS: yyRelSeconds += offs * 60 * 60; break; case CLC_ADD_MINUTES: yyRelSeconds += offs * 60; break; case CLC_ADD_SECONDS: yyRelSeconds += offs; break; } } /* * Do relative times (if not yet already processed interim): */ if (info->flags & CLF_RELCONV) { if (ClockCalcRelTime(info) != TCL_OK) { goto done; } } /* Convert date info structure into UTC seconds */ ret = ClockScanCommit(&yy, &opts); done: TclUnsetObjRef(yy.date.tzName); if (ret != TCL_OK) { return ret; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(yy.date.seconds)); return TCL_OK; } /*---------------------------------------------------------------------- * * ClockSecondsObjCmd - * * Returns a count of microseconds since the epoch. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * * This function implements the 'clock seconds' Tcl command. Refer to the user * documentation for details on what it does. * *---------------------------------------------------------------------- */ int ClockSecondsObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const *objv) /* Parameter values */ { Tcl_Time now; Tcl_Obj *timeObj; if (objc != 1) { Tcl_WrongNumArgs(interp, 0, objv, "clock seconds"); return TCL_ERROR; } Tcl_GetTime(&now); TclNewUIntObj(timeObj, (Tcl_WideUInt)now.sec); Tcl_SetObjResult(interp, timeObj); return TCL_OK; } /* *---------------------------------------------------------------------- * * ClockSafeCatchCmd -- * * Same as "::catch" command but avoids overwriting of interp state. * * See [554117edde] for more info (and proper solution). * *---------------------------------------------------------------------- */ int ClockSafeCatchCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { typedef struct { int status; /* return code status */ int flags; /* Each remaining field saves the */ int returnLevel; /* corresponding field of the Interp */ int returnCode; /* struct. These fields taken together are */ Tcl_Obj *errorInfo; /* the "state" of the interp. */ Tcl_Obj *errorCode; Tcl_Obj *returnOpts; Tcl_Obj *objResult; Tcl_Obj *errorStack; int resetErrorStack; } InterpState; Interp *iPtr = (Interp *)interp; int ret, flags = 0; InterpState *statePtr; if (objc == 1) { /* wrong # args : */ return Tcl_CatchObjCmd(NULL, interp, objc, objv); } statePtr = (InterpState *)Tcl_SaveInterpState(interp, 0); if (!statePtr->errorInfo) { /* todo: avoid traced get of errorInfo here */ TclInitObjRef(statePtr->errorInfo, Tcl_ObjGetVar2(interp, iPtr->eiVar, NULL, 0)); flags |= ERR_LEGACY_COPY; } if (!statePtr->errorCode) { /* todo: avoid traced get of errorCode here */ TclInitObjRef(statePtr->errorCode, Tcl_ObjGetVar2(interp, iPtr->ecVar, NULL, 0)); flags |= ERR_LEGACY_COPY; } /* original catch */ ret = Tcl_CatchObjCmd(NULL, interp, objc, objv); if (ret == TCL_ERROR) { Tcl_DiscardInterpState((Tcl_InterpState)statePtr); return TCL_ERROR; } /* overwrite result in state with catch result */ TclSetObjRef(statePtr->objResult, Tcl_GetObjResult(interp)); /* set result (together with restore state) to interpreter */ (void) Tcl_RestoreInterpState(interp, (Tcl_InterpState)statePtr); /* todo: unless ERR_LEGACY_COPY not set in restore (branch [bug-554117edde] not merged yet) */ iPtr->flags |= (flags & ERR_LEGACY_COPY); return ret; } /* *---------------------------------------------------------------------- * * TzsetIfNecessary -- * * Calls the tzset() library function if the contents of the TZ * environment variable has changed. * * Results: * An epoch counter to allow efficient checking if the timezone has * changed. * * Side effects: * Calls tzset. * *---------------------------------------------------------------------- */ #ifdef _WIN32 #define getenv(x) _wgetenv(L##x) #else #define WCHAR char #define wcslen strlen #define wcscmp strcmp #define wcscpy strcpy #endif #define TZ_INIT_MARKER ((WCHAR *) INT2PTR(-1)) typedef struct ClockTzStatic { WCHAR *was; /* Previous value of TZ. */ #if TCL_MAJOR_VERSION > 8 long long lastRefresh; /* Used for latency before next refresh. */ #else long lastRefresh; /* Used for latency before next refresh. */ #endif size_t epoch; /* Epoch, signals that TZ changed. */ size_t envEpoch; /* Last env epoch, for faster signaling, * that TZ changed via TCL */ } ClockTzStatic; static ClockTzStatic tz = { /* Global timezone info; protected by * clockMutex.*/ TZ_INIT_MARKER, 0, 0, 0 }; static size_t TzsetIfNecessary(void) { const WCHAR *tzNow; /* Current value of TZ. */ Tcl_Time now; /* Current time. */ size_t epoch; /* The tz.epoch that the TZ was read at. */ /* * Prevent performance regression on some platforms by resolving of system time zone: * small latency for check whether environment was changed (once per second) * no latency if environment was changed with tcl-env (compare both epoch values) */ Tcl_GetTime(&now); if (now.sec == tz.lastRefresh && tz.envEpoch == TclEnvEpoch) { return tz.epoch; } tz.envEpoch = TclEnvEpoch; tz.lastRefresh = now.sec; /* check in lock */ Tcl_MutexLock(&clockMutex); tzNow = getenv("TCL_TZ"); if (tzNow == NULL) { tzNow = getenv("TZ"); } if (tzNow != NULL && (tz.was == NULL || tz.was == TZ_INIT_MARKER || wcscmp(tzNow, tz.was) != 0)) { tzset(); if (tz.was != NULL && tz.was != TZ_INIT_MARKER) { Tcl_Free(tz.was); } tz.was = (WCHAR *)Tcl_Alloc(sizeof(WCHAR) * (wcslen(tzNow) + 1)); wcscpy(tz.was, tzNow); epoch = ++tz.epoch; } else if (tzNow == NULL && tz.was != NULL) { tzset(); if (tz.was != TZ_INIT_MARKER) { Tcl_Free(tz.was); } tz.was = NULL; epoch = ++tz.epoch; } else { epoch = tz.epoch; } Tcl_MutexUnlock(&clockMutex); return epoch; } static void ClockFinalize( TCL_UNUSED(void *)) { ClockFrmScnFinalize(); if (tz.was && tz.was != TZ_INIT_MARKER) { Tcl_Free(tz.was); } Tcl_MutexFinalize(&clockMutex); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclClockFmt.c0000644000175000017500000026040014726623136015545 0ustar sergeisergei/* * tclClockFmt.c -- * * Contains the date format (and scan) routines. This code is back-ported * from the time and date facilities of tclSE engine, by Serg G. Brester. * * Copyright (c) 2015 by Sergey G. Brester aka sebres. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclStrIdxTree.h" #include "tclDate.h" /* * Miscellaneous forward declarations and functions used within this file */ static void ClockFmtObj_DupInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr); static void ClockFmtObj_FreeInternalRep(Tcl_Obj *objPtr); static int ClockFmtObj_SetFromAny(Tcl_Interp *, Tcl_Obj *objPtr); static void ClockFmtObj_UpdateString(Tcl_Obj *objPtr); static Tcl_HashEntry * ClockFmtScnStorageAllocProc(Tcl_HashTable *, void *keyPtr); static void ClockFmtScnStorageFreeProc(Tcl_HashEntry *hPtr); static void ClockFmtScnStorageDelete(ClockFmtScnStorage *fss); TCL_DECLARE_MUTEX(ClockFmtMutex); /* Serializes access to common format list. */ #ifndef TCL_CLOCK_FULL_COMPAT #define TCL_CLOCK_FULL_COMPAT 1 #endif /* * Derivation of tclStringHashKeyType with extra memory management trickery. */ static const Tcl_HashKeyType ClockFmtScnStorageHashKeyType = { TCL_HASH_KEY_TYPE_VERSION, /* version */ 0, /* flags */ TclHashStringKey, /* hashKeyProc */ TclCompareStringKeys, /* compareKeysProc */ ClockFmtScnStorageAllocProc, /* allocEntryProc */ ClockFmtScnStorageFreeProc /* freeEntryProc */ }; #define IntFieldAt(info, offset) \ ((int *) (((char *) (info)) + (offset))) #define WideFieldAt(info, offset) \ ((Tcl_WideInt *) (((char *) (info)) + (offset))) /* * Clock scan and format facilities. */ /* *---------------------------------------------------------------------- * * Clock_str2int, Clock_str2wideInt -- * * Fast inline-convertion of string to signed int or wide int by given * start/end. * * The given string should contain numbers chars only (because already * pre-validated within parsing routines) * * Results: * Returns a standard Tcl result. * TCL_OK - by successful conversion, TCL_ERROR by (wide) int overflow * *---------------------------------------------------------------------- */ static inline void Clock_str2int_no( int *out, const char *p, const char *e, int sign) { /* assert(e <= p + 10); */ int val = 0; /* overflow impossible for 10 digits ("9..9"), so no needs to check at all */ while (p < e) { /* never overflows */ val = val * 10 + (*p++ - '0'); } if (sign < 0) { val = -val; } *out = val; } static inline void Clock_str2wideInt_no( Tcl_WideInt *out, const char *p, const char *e, int sign) { /* assert(e <= p + 18); */ Tcl_WideInt val = 0; /* overflow impossible for 18 digits ("9..9"), so no needs to check at all */ while (p < e) { /* never overflows */ val = val * 10 + (*p++ - '0'); } if (sign < 0) { val = -val; } *out = val; } /* int & Tcl_WideInt overflows may happens here (expected case) */ #if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) # pragma GCC optimize("no-trapv") #endif static inline int Clock_str2int( int *out, const char *p, const char *e, int sign) { int val = 0; /* overflow impossible for 10 digits ("9..9"), so no needs to check before */ const char *eNO = p + 10; if (eNO > e) { eNO = e; } while (p < eNO) { /* never overflows */ val = val * 10 + (*p++ - '0'); } if (sign >= 0) { while (p < e) { /* check for overflow */ int prev = val; val = val * 10 + (*p++ - '0'); if (val / 10 < prev) { return TCL_ERROR; } } } else { val = -val; while (p < e) { /* check for overflow */ int prev = val; val = val * 10 - (*p++ - '0'); if (val / 10 > prev) { return TCL_ERROR; } } } *out = val; return TCL_OK; } static inline int Clock_str2wideInt( Tcl_WideInt *out, const char *p, const char *e, int sign) { Tcl_WideInt val = 0; /* overflow impossible for 18 digits ("9..9"), so no needs to check before */ const char *eNO = p + 18; if (eNO > e) { eNO = e; } while (p < eNO) { /* never overflows */ val = val * 10 + (*p++ - '0'); } if (sign >= 0) { while (p < e) { /* check for overflow */ Tcl_WideInt prev = val; val = val * 10 + (*p++ - '0'); if (val / 10 < prev) { return TCL_ERROR; } } } else { val = -val; while (p < e) { /* check for overflow */ Tcl_WideInt prev = val; val = val * 10 - (*p++ - '0'); if (val / 10 > prev) { return TCL_ERROR; } } } *out = val; return TCL_OK; } int TclAtoWIe( Tcl_WideInt *out, const char *p, const char *e, int sign) { return Clock_str2wideInt(out, p, e, sign); } #if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) # pragma GCC reset_options #endif /* *---------------------------------------------------------------------- * * Clock_itoaw, Clock_witoaw -- * * Fast inline-convertion of signed int or wide int to string, using * given padding with specified padchar and width (or without padding). * * This is a very fast replacement for sprintf("%02d"). * * Results: * Returns position in buffer after end of conversion result. * *---------------------------------------------------------------------- */ static inline char * Clock_itoaw( char *buf, int val, char padchar, unsigned short width) { char *p; static const int wrange[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; /* positive integer */ if (val >= 0) { /* check resp. recalculate width */ while (width <= 9 && val >= wrange[width]) { width++; } /* number to string backwards */ p = buf + width; *p-- = '\0'; do { char c = val % 10; val /= 10; *p-- = '0' + c; } while (val > 0); /* filling with pad-char */ while (p >= buf) { *p-- = padchar; } return buf + width; } /* negative integer */ if (!width) { width++; } /* check resp. recalculate width (regarding sign) */ width--; while (width <= 9 && val <= -wrange[width]) { width++; } width++; /* number to string backwards */ p = buf + width; *p-- = '\0'; /* differentiate platforms with -1 % 10 == 1 and -1 % 10 == -1 */ if (-1 % 10 == -1) { do { char c = val % 10; val /= 10; *p-- = '0' - c; } while (val < 0); } else { do { char c = val % 10; val /= 10; *p-- = '0' + c; } while (val < 0); } /* sign by 0 padding */ if (padchar != '0') { *p-- = '-'; } /* filling with pad-char */ while (p >= buf + 1) { *p-- = padchar; } /* sign by non 0 padding */ if (padchar == '0') { *p = '-'; } return buf + width; } char * TclItoAw( char *buf, int val, char padchar, unsigned short width) { return Clock_itoaw(buf, val, padchar, width); } static inline char * Clock_witoaw( char *buf, Tcl_WideInt val, char padchar, unsigned short width) { char *p; static const int wrange[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; /* positive integer */ if (val >= 0) { /* check resp. recalculate width */ if (val >= 10000000000LL) { Tcl_WideInt val2 = val / 10000000000LL; while (width <= 9 && val2 >= wrange[width]) { width++; } width += 10; } else { while (width <= 9 && val >= wrange[width]) { width++; } } /* number to string backwards */ p = buf + width; *p-- = '\0'; do { char c = (val % 10); val /= 10; *p-- = '0' + c; } while (val > 0); /* filling with pad-char */ while (p >= buf) { *p-- = padchar; } return buf + width; } /* negative integer */ if (!width) { width++; } /* check resp. recalculate width (regarding sign) */ width--; if (val <= -10000000000LL) { Tcl_WideInt val2 = val / 10000000000LL; while (width <= 9 && val2 <= -wrange[width]) { width++; } width += 10; } else { while (width <= 9 && val <= -wrange[width]) { width++; } } width++; /* number to string backwards */ p = buf + width; *p-- = '\0'; /* differentiate platforms with -1 % 10 == 1 and -1 % 10 == -1 */ if (-1 % 10 == -1) { do { char c = val % 10; val /= 10; *p-- = '0' - c; } while (val < 0); } else { do { char c = val % 10; val /= 10; *p-- = '0' + c; } while (val < 0); } /* sign by 0 padding */ if (padchar != '0') { *p-- = '-'; } /* filling with pad-char */ while (p >= buf + 1) { *p-- = padchar; } /* sign by non 0 padding */ if (padchar == '0') { *p = '-'; } return buf + width; } /* * Global GC as LIFO for released scan/format object storages. * * Used to holds last released CLOCK_FMT_SCN_STORAGE_GC_SIZE formats * (after last reference from Tcl-object will be removed). This is helpful * to avoid continuous (re)creation and compiling by some dynamically resp. * variable format objects, that could be often reused. * * As long as format storage is used resp. belongs to GC, it takes place in * FmtScnHashTable also. */ #if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0 static struct ClockFmtScnStorage_GC { ClockFmtScnStorage *stackPtr; ClockFmtScnStorage *stackBound; unsigned count; } ClockFmtScnStorage_GC = {NULL, NULL, 0}; /* *---------------------------------------------------------------------- * * ClockFmtScnStorageGC_In -- * * Adds an format storage object to GC. * * If current GC is full (size larger as CLOCK_FMT_SCN_STORAGE_GC_SIZE) * this removes last unused storage at begin of GC stack (LIFO). * * Assumes caller holds the ClockFmtMutex. * * Results: * None. * *---------------------------------------------------------------------- */ static inline void ClockFmtScnStorageGC_In( ClockFmtScnStorage *entry) { /* add new entry */ TclSpliceIn(entry, ClockFmtScnStorage_GC.stackPtr); if (ClockFmtScnStorage_GC.stackBound == NULL) { ClockFmtScnStorage_GC.stackBound = entry; } ClockFmtScnStorage_GC.count++; /* if GC ist full */ if (ClockFmtScnStorage_GC.count > CLOCK_FMT_SCN_STORAGE_GC_SIZE) { /* GC stack is LIFO: delete first inserted entry */ ClockFmtScnStorage *delEnt = ClockFmtScnStorage_GC.stackBound; ClockFmtScnStorage_GC.stackBound = delEnt->prevPtr; TclSpliceOut(delEnt, ClockFmtScnStorage_GC.stackPtr); ClockFmtScnStorage_GC.count--; delEnt->prevPtr = delEnt->nextPtr = NULL; /* remove it now */ ClockFmtScnStorageDelete(delEnt); } } /* *---------------------------------------------------------------------- * * ClockFmtScnStorage_GC_Out -- * * Restores (for reusing) given format storage object from GC. * * Assumes caller holds the ClockFmtMutex. * * Results: * None. * *---------------------------------------------------------------------- */ static inline void ClockFmtScnStorage_GC_Out( ClockFmtScnStorage *entry) { TclSpliceOut(entry, ClockFmtScnStorage_GC.stackPtr); ClockFmtScnStorage_GC.count--; if (ClockFmtScnStorage_GC.stackBound == entry) { ClockFmtScnStorage_GC.stackBound = entry->prevPtr; } entry->prevPtr = entry->nextPtr = NULL; } #endif /* * Global format storage hash table of type ClockFmtScnStorageHashKeyType * (contains list of scan/format object storages, shared across all threads). * * Used for fast searching by format string. */ static Tcl_HashTable FmtScnHashTable; static int initialized = 0; /* * Wrappers between pointers to hash entry and format storage object */ static inline Tcl_HashEntry * HashEntry4FmtScn( ClockFmtScnStorage *fss) { return (Tcl_HashEntry*)(fss + 1); } static inline ClockFmtScnStorage * FmtScn4HashEntry( Tcl_HashEntry *hKeyPtr) { return (ClockFmtScnStorage*)(((char*)hKeyPtr) - sizeof(ClockFmtScnStorage)); } /* *---------------------------------------------------------------------- * * ClockFmtScnStorageAllocProc -- * * Allocate space for a hash entry containing format storage together * with the string key. * * Results: * The return value is a pointer to the created entry. * *---------------------------------------------------------------------- */ static Tcl_HashEntry * ClockFmtScnStorageAllocProc( TCL_UNUSED(Tcl_HashTable *),/* Hash table. */ void *keyPtr) /* Key to store in the hash table entry. */ { ClockFmtScnStorage *fss; const char *string = (const char *) keyPtr; Tcl_HashEntry *hPtr; unsigned size = strlen(string) + 1; unsigned allocsize = sizeof(ClockFmtScnStorage) + sizeof(Tcl_HashEntry); allocsize += size; if (size > sizeof(hPtr->key)) { allocsize -= sizeof(hPtr->key); } fss = (ClockFmtScnStorage *)Tcl_Alloc(allocsize); /* initialize */ memset(fss, 0, sizeof(*fss)); hPtr = HashEntry4FmtScn(fss); memcpy(&hPtr->key.string, string, size); hPtr->clientData = 0; /* currently unused */ return hPtr; } /* *---------------------------------------------------------------------- * * ClockFmtScnStorageFreeProc -- * * Free format storage object and space of given hash entry. * * Results: * None. * *---------------------------------------------------------------------- */ static void ClockFmtScnStorageFreeProc( Tcl_HashEntry *hPtr) { ClockFmtScnStorage *fss = FmtScn4HashEntry(hPtr); if (fss->scnTok != NULL) { Tcl_Free(fss->scnTok); fss->scnTok = NULL; fss->scnTokC = 0; } if (fss->fmtTok != NULL) { Tcl_Free(fss->fmtTok); fss->fmtTok = NULL; fss->fmtTokC = 0; } Tcl_Free(fss); } /* *---------------------------------------------------------------------- * * ClockFmtScnStorageDelete -- * * Delete format storage object. * * Results: * None. * *---------------------------------------------------------------------- */ static void ClockFmtScnStorageDelete( ClockFmtScnStorage *fss) { Tcl_HashEntry *hPtr = HashEntry4FmtScn(fss); /* * This will delete a hash entry and call "Tcl_Free" for storage self, if * some additionally handling required, freeEntryProc can be used instead */ Tcl_DeleteHashEntry(hPtr); } /* * Type definition of clock-format tcl object type. */ static const Tcl_ObjType ClockFmtObjType = { "clock-format", /* name */ ClockFmtObj_FreeInternalRep, /* freeIntRepProc */ ClockFmtObj_DupInternalRep, /* dupIntRepProc */ ClockFmtObj_UpdateString, /* updateStringProc */ ClockFmtObj_SetFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; #define ObjClockFmtScn(objPtr) \ (*((ClockFmtScnStorage **)&(objPtr)->internalRep.twoPtrValue.ptr1)) #define ObjLocFmtKey(objPtr) \ (*((Tcl_Obj **)&(objPtr)->internalRep.twoPtrValue.ptr2)) static void ClockFmtObj_DupInternalRep( Tcl_Obj *srcPtr, Tcl_Obj *copyPtr) { ClockFmtScnStorage *fss = ObjClockFmtScn(srcPtr); if (fss != NULL) { Tcl_MutexLock(&ClockFmtMutex); fss->objRefCount++; Tcl_MutexUnlock(&ClockFmtMutex); } ObjClockFmtScn(copyPtr) = fss; /* regards special case - format not localizable */ if (ObjLocFmtKey(srcPtr) != srcPtr) { TclInitObjRef(ObjLocFmtKey(copyPtr), ObjLocFmtKey(srcPtr)); } else { ObjLocFmtKey(copyPtr) = copyPtr; } copyPtr->typePtr = &ClockFmtObjType; /* if no format representation, dup string representation */ if (fss == NULL) { copyPtr->bytes = (char *)Tcl_Alloc(srcPtr->length + 1); memcpy(copyPtr->bytes, srcPtr->bytes, srcPtr->length + 1); copyPtr->length = srcPtr->length; } } static void ClockFmtObj_FreeInternalRep( Tcl_Obj *objPtr) { ClockFmtScnStorage *fss = ObjClockFmtScn(objPtr); if (fss != NULL && initialized) { Tcl_MutexLock(&ClockFmtMutex); /* decrement object reference count of format/scan storage */ if (--fss->objRefCount <= 0) { #if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0 /* don't remove it right now (may be reusable), just add to GC */ ClockFmtScnStorageGC_In(fss); #else /* remove storage (format representation) */ ClockFmtScnStorageDelete(fss); #endif } Tcl_MutexUnlock(&ClockFmtMutex); } ObjClockFmtScn(objPtr) = NULL; if (ObjLocFmtKey(objPtr) != objPtr) { TclUnsetObjRef(ObjLocFmtKey(objPtr)); } else { ObjLocFmtKey(objPtr) = NULL; } objPtr->typePtr = NULL; } static int ClockFmtObj_SetFromAny( TCL_UNUSED(Tcl_Interp *), Tcl_Obj *objPtr) { /* validate string representation before free old internal representation */ (void)TclGetString(objPtr); /* free old internal representation */ TclFreeInternalRep(objPtr); /* initial state of format object */ ObjClockFmtScn(objPtr) = NULL; ObjLocFmtKey(objPtr) = NULL; objPtr->typePtr = &ClockFmtObjType; return TCL_OK; } static void ClockFmtObj_UpdateString( Tcl_Obj *objPtr) { const char *name = "UNKNOWN"; size_t len; ClockFmtScnStorage *fss = ObjClockFmtScn(objPtr); if (fss != NULL) { Tcl_HashEntry *hPtr = HashEntry4FmtScn(fss); name = hPtr->key.string; } len = strlen(name); objPtr->length = len++, objPtr->bytes = (char *)Tcl_AttemptAlloc(len); if (objPtr->bytes) { memcpy(objPtr->bytes, name, len); } } /* *---------------------------------------------------------------------- * * ClockFrmObjGetLocFmtKey -- * * Retrieves format key object used to search localized format. * * This is normally stored in second pointer of internal representation. * If format object is not localizable, it is equal the given format * pointer (special case to fast fallback by not-localizable formats). * * Results: * Returns tcl object with key or format object if not localizable. * * Side effects: * Converts given format object to ClockFmtObjType on demand for caching * the key inside its internal representation. * *---------------------------------------------------------------------- */ Tcl_Obj* ClockFrmObjGetLocFmtKey( Tcl_Interp *interp, Tcl_Obj *objPtr) { Tcl_Obj *keyObj; if (objPtr->typePtr != &ClockFmtObjType) { if (ClockFmtObj_SetFromAny(interp, objPtr) != TCL_OK) { return NULL; } } keyObj = ObjLocFmtKey(objPtr); if (keyObj) { return keyObj; } keyObj = Tcl_ObjPrintf("FMT_%s", TclGetString(objPtr)); TclInitObjRef(ObjLocFmtKey(objPtr), keyObj); return keyObj; } /* *---------------------------------------------------------------------- * * FindOrCreateFmtScnStorage -- * * Retrieves format storage for given string format. * * This will find the given format in the global storage hash table * or create a format storage object on demaind and save the * reference in the first pointer of internal representation of given * object. * * Results: * Returns scan/format storage pointer to ClockFmtScnStorage. * * Side effects: * Converts given format object to ClockFmtObjType on demand for caching * the format storage reference inside its internal representation. * Increments objRefCount of the ClockFmtScnStorage reference. * *---------------------------------------------------------------------- */ static ClockFmtScnStorage * FindOrCreateFmtScnStorage( Tcl_Interp *interp, Tcl_Obj *objPtr) { const char *strFmt = TclGetString(objPtr); ClockFmtScnStorage *fss = NULL; int isNew; Tcl_HashEntry *hPtr; Tcl_MutexLock(&ClockFmtMutex); /* if not yet initialized */ if (!initialized) { /* initialize hash table */ Tcl_InitCustomHashTable(&FmtScnHashTable, TCL_CUSTOM_TYPE_KEYS, &ClockFmtScnStorageHashKeyType); initialized = 1; } /* get or create entry (and alocate storage) */ hPtr = Tcl_CreateHashEntry(&FmtScnHashTable, strFmt, &isNew); if (hPtr != NULL) { fss = FmtScn4HashEntry(hPtr); #if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0 /* unlink if it is currently in GC */ if (isNew == 0 && fss->objRefCount == 0) { ClockFmtScnStorage_GC_Out(fss); } #endif /* new reference, so increment in lock right now */ fss->objRefCount++; ObjClockFmtScn(objPtr) = fss; } Tcl_MutexUnlock(&ClockFmtMutex); if (fss == NULL && interp != NULL) { Tcl_AppendResult(interp, "retrieve clock format failed \"", strFmt ? strFmt : "", "\"", (char *)NULL); Tcl_SetErrorCode(interp, "TCL", "EINVAL", (char *)NULL); } return fss; } /* *---------------------------------------------------------------------- * * Tcl_GetClockFrmScnFromObj -- * * Returns a clock format/scan representation of (*objPtr), if possible. * If something goes wrong, NULL is returned, and if interp is non-NULL, * an error message is written there. * * Results: * Valid representation of type ClockFmtScnStorage. * * Side effects: * Caches the ClockFmtScnStorage reference as the internal rep of (*objPtr) * and in global hash table, shared across all threads. * *---------------------------------------------------------------------- */ ClockFmtScnStorage * Tcl_GetClockFrmScnFromObj( Tcl_Interp *interp, Tcl_Obj *objPtr) { ClockFmtScnStorage *fss; if (objPtr->typePtr != &ClockFmtObjType) { if (ClockFmtObj_SetFromAny(interp, objPtr) != TCL_OK) { return NULL; } } fss = ObjClockFmtScn(objPtr); if (fss == NULL) { fss = FindOrCreateFmtScnStorage(interp, objPtr); } return fss; } /* *---------------------------------------------------------------------- * * ClockLocalizeFormat -- * * Wrap the format object in options to the localized format, * corresponding given locale. * * This searches localized format in locale catalog, and if not yet * exists, it executes ::tcl::clock::LocalizeFormat in given interpreter * and caches its result in the locale catalog. * * Results: * Localized format object. * * Side effects: * Caches the localized format inside locale catalog. * *---------------------------------------------------------------------- */ Tcl_Obj * ClockLocalizeFormat( ClockFmtScnCmdArgs *opts) { ClockClientData *dataPtr = opts->dataPtr; Tcl_Obj *valObj = NULL, *keyObj; keyObj = ClockFrmObjGetLocFmtKey(opts->interp, opts->formatObj); /* special case - format object is not localizable */ if (keyObj == opts->formatObj) { return opts->formatObj; } /* prevents loss of key object if the format object (where key stored) * becomes changed (loses its internal representation during evals) */ Tcl_IncrRefCount(keyObj); if (opts->mcDictObj == NULL) { ClockMCDict(opts); if (opts->mcDictObj == NULL) { goto done; } } /* try to find in cache within locale mc-catalog */ if (Tcl_DictObjGet(NULL, opts->mcDictObj, keyObj, &valObj) != TCL_OK) { goto done; } /* call LocalizeFormat locale format fmtkey */ if (valObj == NULL) { Tcl_Obj *callargs[4]; callargs[0] = dataPtr->literals[LIT_LOCALIZE_FORMAT]; callargs[1] = opts->localeObj; callargs[2] = opts->formatObj; callargs[3] = opts->mcDictObj; if (Tcl_EvalObjv(opts->interp, 4, callargs, 0) == TCL_OK) { valObj = Tcl_GetObjResult(opts->interp); } /* ensure mcDictObj remains unshared */ if (opts->mcDictObj->refCount > 1) { /* smart reference (shared dict as object with no ref-counter) */ opts->mcDictObj = TclDictObjSmartRef(opts->interp, opts->mcDictObj); } if (!valObj) { goto done; } /* cache it inside mc-dictionary (this incr. ref count of keyObj/valObj) */ if (Tcl_DictObjPut(opts->interp, opts->mcDictObj, keyObj, valObj) != TCL_OK) { valObj = NULL; goto done; } Tcl_ResetResult(opts->interp); /* check special case - format object is not localizable */ if (valObj == opts->formatObj) { /* mark it as unlocalizable, by setting self as key (without refcount incr) */ if (valObj->typePtr == &ClockFmtObjType) { TclUnsetObjRef(ObjLocFmtKey(valObj)); ObjLocFmtKey(valObj) = valObj; } } } done: TclUnsetObjRef(keyObj); return (opts->formatObj = valObj); } /* *---------------------------------------------------------------------- * * FindTokenBegin -- * * Find begin of given scan token in string, corresponding token type. * * Results: * Position of token inside string if found. Otherwise - end of string. * * Side effects: * None. * *---------------------------------------------------------------------- */ static const char * FindTokenBegin( const char *p, const char *end, ClockScanToken *tok, int flags) { if (p < end) { char c; /* next token a known token type */ switch (tok->map->type) { case CTOKT_INT: case CTOKT_WIDE: if (!(flags & CLF_STRICT)) { /* should match at least one digit or space */ while (!isdigit(UCHAR(*p)) && !isspace(UCHAR(*p)) && (p = Tcl_UtfNext(p)) < end) {} } else { /* should match at least one digit */ while (!isdigit(UCHAR(*p)) && (p = Tcl_UtfNext(p)) < end) {} } return p; case CTOKT_WORD: c = *(tok->tokWord.start); goto findChar; case CTOKT_SPACE: while (!isspace(UCHAR(*p)) && (p = Tcl_UtfNext(p)) < end) {} return p; case CTOKT_CHAR: c = *((char *)tok->map->data); findChar: if (!(flags & CLF_STRICT)) { /* should match the char or space */ while (*p != c && !isspace(UCHAR(*p)) && (p = Tcl_UtfNext(p)) < end) {} } else { /* should match the char */ while (*p != c && (p = Tcl_UtfNext(p)) < end) {} } return p; } } return p; } /* *---------------------------------------------------------------------- * * DetermineGreedySearchLen -- * * Determine min/max lengths as exact as possible (speed, greedy match). * * Results: * None. Lengths are stored in *minLenPtr, *maxLenPtr. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void DetermineGreedySearchLen( ClockFmtScnCmdArgs *opts, DateInfo *info, ClockScanToken *tok, int *minLenPtr, int *maxLenPtr) { int minLen = tok->map->minSize; int maxLen; const char *p = yyInput + minLen; const char *end = info->dateEnd; /* if still tokens available, try to correct minimum length */ if ((tok + 1)->map) { end -= tok->endDistance + yySpaceCount; /* find position of next known token */ p = FindTokenBegin(p, end, tok + 1, TCL_CLOCK_FULL_COMPAT ? opts->flags : CLF_STRICT); if (p < end) { minLen = p - yyInput; } } /* max length to the end regarding distance to end (min-width of following tokens) */ maxLen = end - yyInput; /* several amendments */ if (maxLen > tok->map->maxSize) { maxLen = tok->map->maxSize; } if (minLen < tok->map->minSize) { minLen = tok->map->minSize; } if (minLen > maxLen) { maxLen = minLen; } if (maxLen > info->dateEnd - yyInput) { maxLen = info->dateEnd - yyInput; } /* check digits rigth now */ if (tok->map->type == CTOKT_INT || tok->map->type == CTOKT_WIDE) { p = yyInput; end = p + maxLen; if (end > info->dateEnd) { end = info->dateEnd; } while (isdigit(UCHAR(*p)) && p < end) { p++; } maxLen = p - yyInput; } /* try to get max length more precise for greedy match, * check the next ahead token available there */ if (minLen < maxLen && tok->lookAhTok) { ClockScanToken *laTok = tok + tok->lookAhTok + 1; p = yyInput + maxLen; /* regards all possible spaces here (because they are optional) */ end = p + tok->lookAhMax + yySpaceCount + 1; if (end > info->dateEnd) { end = info->dateEnd; } p += tok->lookAhMin; if (laTok->map && p < end) { /* try to find laTok between [lookAhMin, lookAhMax] */ while (minLen < maxLen) { const char *f = FindTokenBegin(p, end, laTok, TCL_CLOCK_FULL_COMPAT ? opts->flags : CLF_STRICT); /* if found (not below lookAhMax) */ if (f < end) { break; } /* try again with fewer length */ maxLen--; p--; end--; } } else if (p > end) { maxLen -= (p - end); if (maxLen < minLen) { maxLen = minLen; } } } *minLenPtr = minLen; *maxLenPtr = maxLen; } /* *---------------------------------------------------------------------- * * ObjListSearch -- * * Find largest part of the input string from start regarding min and * max lengths in the given list (utf-8, case sensitive). * * Results: * TCL_OK - match found, TCL_RETURN - not matched, TCL_ERROR in error case. * * Side effects: * Input points to end of the found token in string. * *---------------------------------------------------------------------- */ static inline int ObjListSearch( DateInfo *info, int *val, Tcl_Obj **lstv, Tcl_Size lstc, int minLen, int maxLen) { Tcl_Size i, l, lf = -1; const char *s, *f, *sf; /* search in list */ for (i = 0; i < lstc; i++) { s = TclGetStringFromObj(lstv[i], &l); if (l >= minLen && (f = TclUtfFindEqualNC(yyInput, yyInput + maxLen, s, s + l, &sf)) > yyInput) { l = f - yyInput; if (l < minLen) { continue; } /* found, try to find longest value (greedy search) */ if (l < maxLen && minLen != maxLen) { lf = i; minLen = l + 1; continue; } /* max possible - end of search */ *val = i; yyInput += l; break; } } /* if found */ if (i < lstc) { return TCL_OK; } if (lf >= 0) { *val = lf; yyInput += minLen - 1; return TCL_OK; } return TCL_RETURN; } #if 0 /* currently unused */ static int LocaleListSearch( ClockFmtScnCmdArgs *opts, DateInfo *info, int mcKey, int *val, int minLen, int maxLen) { Tcl_Obj **lstv; Tcl_Size lstc; Tcl_Obj *valObj; /* get msgcat value */ valObj = ClockMCGet(opts, mcKey); if (valObj == NULL) { return TCL_ERROR; } /* is a list */ if (TclListObjGetElements(opts->interp, valObj, &lstc, &lstv) != TCL_OK) { return TCL_ERROR; } /* search in list */ return ObjListSearch(info, val, lstv, lstc, minLen, maxLen); } #endif /* *---------------------------------------------------------------------- * * ClockMCGetListIdxTree -- * * Retrieves localized string indexed tree in the locale catalog for * given literal index mcKey (and builds it on demand). * * Searches localized index in locale catalog, and if not yet exists, * creates string indexed tree and stores it in the locale catalog. * * Results: * Localized string index tree. * * Side effects: * Caches the localized string index tree inside locale catalog. * *---------------------------------------------------------------------- */ static TclStrIdxTree * ClockMCGetListIdxTree( ClockFmtScnCmdArgs *opts, int mcKey) { TclStrIdxTree *idxTree; Tcl_Obj *objPtr = ClockMCGetIdx(opts, mcKey); if (objPtr != NULL && (idxTree = TclStrIdxTreeGetFromObj(objPtr)) != NULL) { return idxTree; } else { /* build new index */ Tcl_Obj **lstv; Tcl_Size lstc; Tcl_Obj *valObj; objPtr = TclStrIdxTreeNewObj(); if ((idxTree = TclStrIdxTreeGetFromObj(objPtr)) == NULL) { goto done; /* unexpected, but ...*/ } valObj = ClockMCGet(opts, mcKey); if (valObj == NULL) { goto done; } if (TclListObjGetElements(opts->interp, valObj, &lstc, &lstv) != TCL_OK) { goto done; } if (TclStrIdxTreeBuildFromList(idxTree, lstc, lstv, NULL) != TCL_OK) { goto done; } ClockMCSetIdx(opts, mcKey, objPtr); objPtr = NULL; } done: if (objPtr) { Tcl_DecrRefCount(objPtr); idxTree = NULL; } return idxTree; } /* *---------------------------------------------------------------------- * * ClockMCGetMultiListIdxTree -- * * Retrieves localized string indexed tree in the locale catalog for * multiple lists by literal indices mcKeys (and builds it on demand). * * Searches localized index in locale catalog for mcKey, and if not * yet exists, creates string indexed tree and stores it in the * locale catalog. * * Results: * Localized string index tree. * * Side effects: * Caches the localized string index tree inside locale catalog. * *---------------------------------------------------------------------- */ static TclStrIdxTree * ClockMCGetMultiListIdxTree( ClockFmtScnCmdArgs *opts, int mcKey, int *mcKeys) { TclStrIdxTree * idxTree; Tcl_Obj *objPtr = ClockMCGetIdx(opts, mcKey); if (objPtr != NULL && (idxTree = TclStrIdxTreeGetFromObj(objPtr)) != NULL) { return idxTree; } else { /* build new index */ Tcl_Obj **lstv; Tcl_Size lstc; Tcl_Obj *valObj; objPtr = TclStrIdxTreeNewObj(); if ((idxTree = TclStrIdxTreeGetFromObj(objPtr)) == NULL) { goto done; /* unexpected, but ...*/ } while (*mcKeys) { valObj = ClockMCGet(opts, *mcKeys); if (valObj == NULL) { goto done; } if (TclListObjGetElements(opts->interp, valObj, &lstc, &lstv) != TCL_OK) { goto done; } if (TclStrIdxTreeBuildFromList(idxTree, lstc, lstv, NULL) != TCL_OK) { goto done; } mcKeys++; } ClockMCSetIdx(opts, mcKey, objPtr); objPtr = NULL; } done: if (objPtr) { Tcl_DecrRefCount(objPtr); idxTree = NULL; } return idxTree; } /* *---------------------------------------------------------------------- * * ClockStrIdxTreeSearch -- * * Find largest part of the input string from start regarding lengths * in the given localized string indexed tree (utf-8, case sensitive). * * Results: * TCL_OK - match found and the index stored in *val, * TCL_RETURN - not matched or ambigous, * TCL_ERROR - in error case. * * Side effects: * Input points to end of the found token in string. * *---------------------------------------------------------------------- */ static inline int ClockStrIdxTreeSearch( DateInfo *info, TclStrIdxTree *idxTree, int *val, int minLen, int maxLen) { TclStrIdx *foundItem; const char *f = TclStrIdxTreeSearch(NULL, &foundItem, idxTree, yyInput, yyInput + maxLen); if (f <= yyInput || (f - yyInput) < minLen) { /* not found */ return TCL_RETURN; } if (!foundItem->value) { /* ambigous */ return TCL_RETURN; } *val = PTR2INT(foundItem->value); /* shift input pointer */ yyInput = f; return TCL_OK; } #if 0 /* currently unused */ static int StaticListSearch( ClockFmtScnCmdArgs *opts, DateInfo *info, const char **lst, int *val) { size_t len; const char **s = lst; while (*s != NULL) { len = strlen(*s); if (len <= info->dateEnd - yyInput && strncasecmp(yyInput, *s, len) == 0) { *val = (s - lst); yyInput += len; break; } s++; } if (*s != NULL) { return TCL_OK; } return TCL_RETURN; } #endif static inline const char * FindWordEnd( ClockScanToken *tok, const char *p, const char *end) { const char *x = tok->tokWord.start; const char *pfnd = p; if (x == tok->tokWord.end - 1) { /* fast phase-out for single char word */ if (*p == *x) { return ++p; } } /* multi-char word */ x = TclUtfFindEqualNC(x, tok->tokWord.end, p, end, &pfnd); if (x < tok->tokWord.end) { /* no match -> error */ return NULL; } return pfnd; } static int ClockScnToken_Month_Proc( ClockFmtScnCmdArgs *opts, DateInfo *info, ClockScanToken *tok) { #if 0 /* currently unused, test purposes only */ static const char * months[] = { /* full */ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", /* abbr */ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; int val; if (StaticListSearch(opts, info, months, &val) != TCL_OK) { return TCL_RETURN; } yyMonth = (val % 12) + 1; #else static int monthsKeys[] = {MCLIT_MONTHS_FULL, MCLIT_MONTHS_ABBREV, 0}; int ret, val; int minLen, maxLen; TclStrIdxTree *idxTree; DetermineGreedySearchLen(opts, info, tok, &minLen, &maxLen); /* get or create tree in msgcat dict */ idxTree = ClockMCGetMultiListIdxTree(opts, MCLIT_MONTHS_COMB, monthsKeys); if (idxTree == NULL) { return TCL_ERROR; } ret = ClockStrIdxTreeSearch(info, idxTree, &val, minLen, maxLen); if (ret != TCL_OK) { return ret; } yyMonth = val; #endif return TCL_OK; } static int ClockScnToken_DayOfWeek_Proc( ClockFmtScnCmdArgs *opts, DateInfo *info, ClockScanToken *tok) { static int dowKeys[] = {MCLIT_DAYS_OF_WEEK_ABBREV, MCLIT_DAYS_OF_WEEK_FULL, 0}; int ret, val; int minLen, maxLen; char curTok = *tok->tokWord.start; TclStrIdxTree *idxTree; DetermineGreedySearchLen(opts, info, tok, &minLen, &maxLen); /* %u %w %Ou %Ow */ if (curTok != 'a' && curTok != 'A' && ((minLen <= 1 && maxLen >= 1) || PTR2INT(tok->map->data))) { val = -1; if (PTR2INT(tok->map->data) == 0) { if (*yyInput >= '0' && *yyInput <= '9') { val = *yyInput - '0'; } } else { idxTree = ClockMCGetListIdxTree(opts, PTR2INT(tok->map->data) /* mcKey */); if (idxTree == NULL) { return TCL_ERROR; } ret = ClockStrIdxTreeSearch(info, idxTree, &val, minLen, maxLen); if (ret != TCL_OK) { return ret; } --val; } if (val == -1) { return TCL_RETURN; } if (val == 0) { val = 7; } if (val > 7) { Tcl_SetObjResult(opts->interp, Tcl_NewStringObj( "day of week is greater than 7", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(opts->interp, "CLOCK", "badDayOfWeek", (char *)NULL); return TCL_ERROR; } info->date.dayOfWeek = val; yyInput++; return TCL_OK; } /* %a %A */ idxTree = ClockMCGetMultiListIdxTree(opts, MCLIT_DAYS_OF_WEEK_COMB, dowKeys); if (idxTree == NULL) { return TCL_ERROR; } ret = ClockStrIdxTreeSearch(info, idxTree, &val, minLen, maxLen); if (ret != TCL_OK) { return ret; } --val; if (val == 0) { val = 7; } info->date.dayOfWeek = val; return TCL_OK; } static int ClockScnToken_amPmInd_Proc( ClockFmtScnCmdArgs *opts, DateInfo *info, ClockScanToken *tok) { int ret, val; int minLen, maxLen; Tcl_Obj *amPmObj[2]; DetermineGreedySearchLen(opts, info, tok, &minLen, &maxLen); amPmObj[0] = ClockMCGet(opts, MCLIT_AM); amPmObj[1] = ClockMCGet(opts, MCLIT_PM); if (amPmObj[0] == NULL || amPmObj[1] == NULL) { return TCL_ERROR; } ret = ObjListSearch(info, &val, amPmObj, 2, minLen, maxLen); if (ret != TCL_OK) { return ret; } if (val == 0) { yyMeridian = MERam; } else { yyMeridian = MERpm; } return TCL_OK; } static int ClockScnToken_LocaleERA_Proc( ClockFmtScnCmdArgs *opts, DateInfo *info, ClockScanToken *tok) { ClockClientData *dataPtr = opts->dataPtr; int ret, val; int minLen, maxLen; Tcl_Obj *eraObj[6]; DetermineGreedySearchLen(opts, info, tok, &minLen, &maxLen); eraObj[0] = ClockMCGet(opts, MCLIT_BCE); eraObj[1] = ClockMCGet(opts, MCLIT_CE); eraObj[2] = dataPtr->mcLiterals[MCLIT_BCE2]; eraObj[3] = dataPtr->mcLiterals[MCLIT_CE2]; eraObj[4] = dataPtr->mcLiterals[MCLIT_BCE3]; eraObj[5] = dataPtr->mcLiterals[MCLIT_CE3]; if (eraObj[0] == NULL || eraObj[1] == NULL) { return TCL_ERROR; } ret = ObjListSearch(info, &val, eraObj, 6, minLen, maxLen); if (ret != TCL_OK) { return ret; } if (val & 1) { yydate.isBce = 0; } else { yydate.isBce = 1; } return TCL_OK; } static int ClockScnToken_LocaleListMatcher_Proc( ClockFmtScnCmdArgs *opts, DateInfo *info, ClockScanToken *tok) { int ret, val; int minLen, maxLen; TclStrIdxTree *idxTree; DetermineGreedySearchLen(opts, info, tok, &minLen, &maxLen); /* get or create tree in msgcat dict */ idxTree = ClockMCGetListIdxTree(opts, PTR2INT(tok->map->data) /* mcKey */); if (idxTree == NULL) { return TCL_ERROR; } ret = ClockStrIdxTreeSearch(info, idxTree, &val, minLen, maxLen); if (ret != TCL_OK) { return ret; } if (tok->map->offs > 0) { *IntFieldAt(info, tok->map->offs) = --val; } return TCL_OK; } static int ClockScnToken_JDN_Proc( ClockFmtScnCmdArgs *opts, DateInfo *info, ClockScanToken *tok) { int minLen, maxLen; const char *p = yyInput, *end, *s; Tcl_WideInt intJD; int fractJD = 0, fractJDDiv = 1; DetermineGreedySearchLen(opts, info, tok, &minLen, &maxLen); end = yyInput + maxLen; /* currently positive astronomic dates only */ if (*p == '+' || *p == '-') { p++; } s = p; while (p < end && isdigit(UCHAR(*p))) { p++; } if (Clock_str2wideInt(&intJD, s, p, (*yyInput != '-' ? 1 : -1)) != TCL_OK) { return TCL_RETURN; } yyInput = p; if (p >= end || *p++ != '.') { /* allow pure integer JDN */ /* by astronomical JD the seconds of day offs is 12 hours */ if (tok->map->offs) { goto done; } /* calendar JD */ yydate.julianDay = intJD; return TCL_OK; } s = p; while (p < end && isdigit(UCHAR(*p))) { fractJDDiv *= 10; p++; } if (Clock_str2int(&fractJD, s, p, 1) != TCL_OK) { return TCL_RETURN; } yyInput = p; done: /* * Build a date from julian day (integer and fraction). * Note, astronomical JDN starts at noon in opposite to calendar julianday. */ fractJD = (int)tok->map->offs /* 0 for calendar or 43200 for astro JD */ + (int)((Tcl_WideInt)SECONDS_PER_DAY * fractJD / fractJDDiv); if (fractJD >= SECONDS_PER_DAY) { fractJD %= SECONDS_PER_DAY; intJD += 1; } yydate.secondOfDay = fractJD; yydate.julianDay = intJD; yydate.seconds = -210866803200LL + (SECONDS_PER_DAY * intJD) + fractJD; info->flags |= CLF_POSIXSEC; return TCL_OK; } static int ClockScnToken_TimeZone_Proc( ClockFmtScnCmdArgs *opts, DateInfo *info, ClockScanToken *tok) { int minLen, maxLen; int len = 0; const char *p = yyInput; Tcl_Obj *tzObjStor = NULL; DetermineGreedySearchLen(opts, info, tok, &minLen, &maxLen); /* numeric timezone */ if (*p == '+' || *p == '-') { /* max chars in numeric zone = "+00:00:00" */ #define MAX_ZONE_LEN 9 char buf[MAX_ZONE_LEN + 1]; char *bp = buf; *bp++ = *p++; len++; if (maxLen > MAX_ZONE_LEN) { maxLen = MAX_ZONE_LEN; } /* cumulate zone into buf without ':' */ while (len + 1 < maxLen) { if (!isdigit(UCHAR(*p))) { break; } *bp++ = *p++; len++; if (!isdigit(UCHAR(*p))) { break; } *bp++ = *p++; len++; if (len + 2 < maxLen) { if (*p == ':') { p++; len++; } } } *bp = '\0'; if (len < minLen) { return TCL_RETURN; } #undef MAX_ZONE_LEN /* timezone */ tzObjStor = Tcl_NewStringObj(buf, bp - buf); } else { /* legacy (alnum) timezone like CEST, etc. */ if (maxLen > 4) { maxLen = 4; } while (len < maxLen) { if ((*p & 0x80) || (!isalpha(UCHAR(*p)) && !isdigit(UCHAR(*p)))) { /* INTL: ISO only. */ break; } p++; len++; } if (len < minLen) { return TCL_RETURN; } /* timezone */ tzObjStor = Tcl_NewStringObj(yyInput, p - yyInput); /* convert using dict */ } /* try to apply new time zone */ Tcl_IncrRefCount(tzObjStor); opts->timezoneObj = ClockSetupTimeZone(opts->dataPtr, opts->interp, tzObjStor); Tcl_DecrRefCount(tzObjStor); if (opts->timezoneObj == NULL) { return TCL_ERROR; } yyInput += len; return TCL_OK; } static int ClockScnToken_StarDate_Proc( ClockFmtScnCmdArgs *opts, DateInfo *info, ClockScanToken *tok) { int minLen, maxLen; const char *p = yyInput, *end, *s; int year, fractYear, fractDayDiv, fractDay; static const char *stardatePref = "stardate "; DetermineGreedySearchLen(opts, info, tok, &minLen, &maxLen); end = yyInput + maxLen; /* stardate string */ p = TclUtfFindEqualNCInLwr(p, end, stardatePref, stardatePref + 9, &s); if (p >= end || p - yyInput < 9) { return TCL_RETURN; } /* bypass spaces */ while (p < end && isspace(UCHAR(*p))) { p++; } if (p >= end) { return TCL_RETURN; } /* currently positive stardate only */ if (*p == '+') { p++; } s = p; while (p < end && isdigit(UCHAR(*p))) { p++; } if (p >= end || p - s < 4) { return TCL_RETURN; } if (Clock_str2int(&year, s, p - 3, 1) != TCL_OK || Clock_str2int(&fractYear, p - 3, p, 1) != TCL_OK) { return TCL_RETURN; } if (*p++ != '.') { return TCL_RETURN; } s = p; fractDayDiv = 1; while (p < end && isdigit(UCHAR(*p))) { fractDayDiv *= 10; p++; } if (Clock_str2int(&fractDay, s, p, 1) != TCL_OK) { return TCL_RETURN; } yyInput = p; /* Build a date from year and fraction. */ yydate.year = year + RODDENBERRY; yydate.isBce = 0; yydate.gregorian = 1; if (IsGregorianLeapYear(&yydate)) { fractYear *= 366; } else { fractYear *= 365; } yydate.dayOfYear = fractYear / 1000 + 1; if (fractYear % 1000 >= 500) { yydate.dayOfYear++; } GetJulianDayFromEraYearDay(&yydate, GREGORIAN_CHANGE_DATE); yydate.localSeconds = -210866803200LL + (SECONDS_PER_DAY * yydate.julianDay) + (SECONDS_PER_DAY * fractDay / fractDayDiv); return TCL_OK; } /* * Descriptors for the various fields in [clock scan]. */ static const char *ScnSTokenMapIndex = "dmbyYHMSpJjCgGVazUsntQ"; static const ClockScanTokenMap ScnSTokenMap[] = { /* %d %e */ {CTOKT_INT, CLF_DAYOFMONTH, 0, 1, 2, offsetof(DateInfo, date.dayOfMonth), NULL, NULL}, /* %m %N */ {CTOKT_INT, CLF_MONTH, 0, 1, 2, offsetof(DateInfo, date.month), NULL, NULL}, /* %b %B %h */ {CTOKT_PARSER, CLF_MONTH, 0, 0, 0xffff, 0, ClockScnToken_Month_Proc, NULL}, /* %y */ {CTOKT_INT, CLF_YEAR, 0, 1, 2, offsetof(DateInfo, date.year), NULL, NULL}, /* %Y */ {CTOKT_INT, CLF_YEAR | CLF_CENTURY, 0, 4, 4, offsetof(DateInfo, date.year), NULL, NULL}, /* %H %k %I %l */ {CTOKT_INT, CLF_TIME, 0, 1, 2, offsetof(DateInfo, date.hour), NULL, NULL}, /* %M */ {CTOKT_INT, CLF_TIME, 0, 1, 2, offsetof(DateInfo, date.minutes), NULL, NULL}, /* %S */ {CTOKT_INT, CLF_TIME, 0, 1, 2, offsetof(DateInfo, date.secondOfMin), NULL, NULL}, /* %p %P */ {CTOKT_PARSER, 0, 0, 0, 0xffff, 0, ClockScnToken_amPmInd_Proc, NULL}, /* %J */ {CTOKT_WIDE, CLF_JULIANDAY | CLF_SIGNED, 0, 1, 0xffff, offsetof(DateInfo, date.julianDay), NULL, NULL}, /* %j */ {CTOKT_INT, CLF_DAYOFYEAR, 0, 1, 3, offsetof(DateInfo, date.dayOfYear), NULL, NULL}, /* %C */ {CTOKT_INT, CLF_CENTURY|CLF_ISO8601CENTURY, 0, 1, 2, offsetof(DateInfo, dateCentury), NULL, NULL}, /* %g */ {CTOKT_INT, CLF_ISO8601YEAR, 0, 2, 2, offsetof(DateInfo, date.iso8601Year), NULL, NULL}, /* %G */ {CTOKT_INT, CLF_ISO8601YEAR | CLF_ISO8601CENTURY, 0, 4, 4, offsetof(DateInfo, date.iso8601Year), NULL, NULL}, /* %V */ {CTOKT_INT, CLF_ISO8601WEEK, 0, 1, 2, offsetof(DateInfo, date.iso8601Week), NULL, NULL}, /* %a %A %u %w */ {CTOKT_PARSER, CLF_DAYOFWEEK, 0, 0, 0xffff, 0, ClockScnToken_DayOfWeek_Proc, NULL}, /* %z %Z */ {CTOKT_PARSER, CLF_OPTIONAL, 0, 0, 0xffff, 0, ClockScnToken_TimeZone_Proc, NULL}, /* %U %W */ {CTOKT_INT, CLF_OPTIONAL, 0, 1, 2, 0, /* currently no capture, parse only token */ NULL, NULL}, /* %s */ {CTOKT_WIDE, CLF_POSIXSEC | CLF_SIGNED, 0, 1, 0xffff, offsetof(DateInfo, date.seconds), NULL, NULL}, /* %n */ {CTOKT_CHAR, 0, 0, 1, 1, 0, NULL, "\n"}, /* %t */ {CTOKT_CHAR, 0, 0, 1, 1, 0, NULL, "\t"}, /* %Q */ {CTOKT_PARSER, CLF_LOCALSEC, 0, 16, 30, 0, ClockScnToken_StarDate_Proc, NULL}, }; static const char *ScnSTokenMapAliasIndex[2] = { "eNBhkIlPAuwZW", "dmbbHHHpaaazU" }; static const char *ScnETokenMapIndex = "EJjys"; static const ClockScanTokenMap ScnETokenMap[] = { /* %EE */ {CTOKT_PARSER, 0, 0, 0, 0xffff, offsetof(DateInfo, date.year), ClockScnToken_LocaleERA_Proc, (void *)MCLIT_LOCALE_NUMERALS}, /* %EJ */ {CTOKT_PARSER, CLF_JULIANDAY | CLF_SIGNED, 0, 1, 0xffff, 0, /* calendar JDN starts at midnight */ ClockScnToken_JDN_Proc, NULL}, /* %Ej */ {CTOKT_PARSER, CLF_JULIANDAY | CLF_SIGNED, 0, 1, 0xffff, (SECONDS_PER_DAY/2), /* astro JDN starts at noon */ ClockScnToken_JDN_Proc, NULL}, /* %Ey */ {CTOKT_PARSER, 0, 0, 0, 0xffff, 0, /* currently no capture, parse only token */ ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, /* %Es */ {CTOKT_WIDE, CLF_LOCALSEC | CLF_SIGNED, 0, 1, 0xffff, offsetof(DateInfo, date.localSeconds), NULL, NULL}, }; static const char *ScnETokenMapAliasIndex[2] = { "", "" }; static const char *ScnOTokenMapIndex = "dmyHMSu"; static const ClockScanTokenMap ScnOTokenMap[] = { /* %Od %Oe */ {CTOKT_PARSER, CLF_DAYOFMONTH, 0, 0, 0xffff, offsetof(DateInfo, date.dayOfMonth), ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, /* %Om */ {CTOKT_PARSER, CLF_MONTH, 0, 0, 0xffff, offsetof(DateInfo, date.month), ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, /* %Oy */ {CTOKT_PARSER, CLF_YEAR, 0, 0, 0xffff, offsetof(DateInfo, date.year), ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, /* %OH %Ok %OI %Ol */ {CTOKT_PARSER, CLF_TIME, 0, 0, 0xffff, offsetof(DateInfo, date.hour), ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, /* %OM */ {CTOKT_PARSER, CLF_TIME, 0, 0, 0xffff, offsetof(DateInfo, date.minutes), ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, /* %OS */ {CTOKT_PARSER, CLF_TIME, 0, 0, 0xffff, offsetof(DateInfo, date.secondOfMin), ClockScnToken_LocaleListMatcher_Proc, (void *)MCLIT_LOCALE_NUMERALS}, /* %Ou Ow */ {CTOKT_PARSER, CLF_DAYOFWEEK, 0, 0, 0xffff, 0, ClockScnToken_DayOfWeek_Proc, (void *)MCLIT_LOCALE_NUMERALS}, }; static const char *ScnOTokenMapAliasIndex[2] = { "ekIlw", "dHHHu" }; /* Token map reserved for CTOKT_SPACE */ static const ClockScanTokenMap ScnSpaceTokenMap = { CTOKT_SPACE, 0, 0, 1, 1, 0, NULL, NULL }; static const ClockScanTokenMap ScnWordTokenMap = { CTOKT_WORD, 0, 0, 1, 1, 0, NULL, NULL }; static inline unsigned EstimateTokenCount( const char *fmt, const char *end) { const char *p = fmt; unsigned tokcnt; /* estimate token count by % char and format length */ tokcnt = 0; while (p <= end) { if (*p++ == '%') { tokcnt++; p++; } } p = fmt + tokcnt * 2; if (p < end) { if ((unsigned)(end - p) < tokcnt) { tokcnt += (end - p); } else { tokcnt += tokcnt; } } return ++tokcnt; } #define AllocTokenInChain(tok, chain, tokCnt, type) \ if (++(tok) >= (chain) + (tokCnt)) { \ chain = (type)Tcl_Realloc((char *)(chain), \ (tokCnt + CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE) * sizeof(*(tok))); \ (tok) = (chain) + (tokCnt); \ (tokCnt) += CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE; \ } \ memset(tok, 0, sizeof(*(tok))); /* *---------------------------------------------------------------------- */ ClockFmtScnStorage * ClockGetOrParseScanFormat( Tcl_Interp *interp, /* Tcl interpreter */ Tcl_Obj *formatObj) /* Format container */ { ClockFmtScnStorage *fss; fss = Tcl_GetClockFrmScnFromObj(interp, formatObj); if (fss == NULL) { return NULL; } /* if format (scnTok) already tokenized */ if (fss->scnTok != NULL) { return fss; } Tcl_MutexLock(&ClockFmtMutex); /* first time scanning - tokenize format */ if (fss->scnTok == NULL) { ClockScanToken *tok, *scnTok; unsigned tokCnt; const char *p, *e, *cp; e = p = HashEntry4FmtScn(fss)->key.string; e += strlen(p); /* estimate token count by % char and format length */ fss->scnTokC = EstimateTokenCount(p, e); fss->scnSpaceCount = 0; scnTok = tok = (ClockScanToken *)Tcl_Alloc(sizeof(*tok) * fss->scnTokC); memset(tok, 0, sizeof(*tok)); tokCnt = 1; while (p < e) { switch (*p) { case '%': { const ClockScanTokenMap *scnMap = ScnSTokenMap; const char *mapIndex = ScnSTokenMapIndex; const char **aliasIndex = ScnSTokenMapAliasIndex; if (p + 1 >= e) { goto word_tok; } p++; /* try to find modifier: */ switch (*p) { case '%': /* begin new word token - don't join with previous word token, * because current mapping should be "...%%..." -> "...%..." */ tok->map = &ScnWordTokenMap; tok->tokWord.start = p; tok->tokWord.end = p + 1; AllocTokenInChain(tok, scnTok, fss->scnTokC, ClockScanToken *); tokCnt++; p++; continue; case 'E': scnMap = ScnETokenMap, mapIndex = ScnETokenMapIndex, aliasIndex = ScnETokenMapAliasIndex; p++; break; case 'O': scnMap = ScnOTokenMap, mapIndex = ScnOTokenMapIndex, aliasIndex = ScnOTokenMapAliasIndex; p++; break; } /* search direct index */ cp = strchr(mapIndex, *p); if (!cp || *cp == '\0') { /* search wrapper index (multiple chars for same token) */ cp = strchr(aliasIndex[0], *p); if (!cp || *cp == '\0') { p--; if (scnMap != ScnSTokenMap) { p--; } goto word_tok; } cp = strchr(mapIndex, aliasIndex[1][cp - aliasIndex[0]]); if (!cp || *cp == '\0') { /* unexpected, but ... */ #ifdef DEBUG Tcl_Panic("token \"%c\" has no map in wrapper resolver", *p); #endif p--; if (scnMap != ScnSTokenMap) { p--; } goto word_tok; } } tok->map = &scnMap[cp - mapIndex]; tok->tokWord.start = p; /* calculate look ahead value by standing together tokens */ if (tok > scnTok) { ClockScanToken *prevTok = tok - 1; while (prevTok >= scnTok) { if (prevTok->map->type != tok->map->type) { break; } prevTok->lookAhMin += tok->map->minSize; prevTok->lookAhMax += tok->map->maxSize; prevTok->lookAhTok++; prevTok--; } } /* increase space count used in format */ if (tok->map->type == CTOKT_CHAR && isspace(UCHAR(*((char *)tok->map->data)))) { fss->scnSpaceCount++; } /* next token */ AllocTokenInChain(tok, scnTok, fss->scnTokC, ClockScanToken *); tokCnt++; p++; continue; } default: if (isspace(UCHAR(*p))) { tok->map = &ScnSpaceTokenMap; tok->tokWord.start = p++; while (p < e && isspace(UCHAR(*p))) { p++; } tok->tokWord.end = p; /* increase space count used in format */ fss->scnSpaceCount++; /* next token */ AllocTokenInChain(tok, scnTok, fss->scnTokC, ClockScanToken *); tokCnt++; continue; } word_tok: { /* try continue with previous word token */ ClockScanToken *wordTok = tok - 1; if (wordTok < scnTok || wordTok->map != &ScnWordTokenMap) { /* start with new word token */ wordTok = tok; wordTok->tokWord.start = p; wordTok->map = &ScnWordTokenMap; } do { if (isspace(UCHAR(*p))) { fss->scnSpaceCount++; } p = Tcl_UtfNext(p); } while (p < e && *p != '%'); wordTok->tokWord.end = p; if (wordTok == tok) { AllocTokenInChain(tok, scnTok, fss->scnTokC, ClockScanToken *); tokCnt++; } } break; } } /* calculate end distance value for each tokens */ if (tok > scnTok) { unsigned endDist = 0; ClockScanToken *prevTok = tok - 1; while (prevTok >= scnTok) { prevTok->endDistance = endDist; if (prevTok->map->type != CTOKT_WORD) { endDist += prevTok->map->minSize; } else { endDist += prevTok->tokWord.end - prevTok->tokWord.start; } prevTok--; } } /* correct count of real used tokens and free mem if desired * (1 is acceptable delta to prevent memory fragmentation) */ if (fss->scnTokC > tokCnt + (CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE / 2)) { if ((tok = (ClockScanToken *) Tcl_AttemptRealloc(scnTok, tokCnt * sizeof(*tok))) != NULL) { scnTok = tok; } } /* now we're ready - assign now to storage (note the threaded race condition) */ fss->scnTok = scnTok; fss->scnTokC = tokCnt; } Tcl_MutexUnlock(&ClockFmtMutex); return fss; } /* *---------------------------------------------------------------------- */ int ClockScan( DateInfo *info, /* Date fields used for parsing & converting */ Tcl_Obj *strObj, /* String containing the time to scan */ ClockFmtScnCmdArgs *opts) /* Command options */ { ClockClientData *dataPtr = opts->dataPtr; ClockFmtScnStorage *fss; ClockScanToken *tok; const ClockScanTokenMap *map; const char *p, *x, *end; unsigned short flags = 0; int ret = TCL_ERROR; /* get localized format */ if (ClockLocalizeFormat(opts) == NULL) { return TCL_ERROR; } if (!(fss = ClockGetOrParseScanFormat(opts->interp, opts->formatObj)) || !(tok = fss->scnTok)) { return TCL_ERROR; } /* prepare parsing */ yyMeridian = MER24; p = TclGetString(strObj); end = p + strObj->length; /* in strict mode - bypass spaces at begin / end only (not between tokens) */ if (opts->flags & CLF_STRICT) { while (p < end && isspace(UCHAR(*p))) { p++; } } yyInput = p; /* look ahead to count spaces (bypass it by count length and distances) */ x = end; while (p < end) { if (isspace(UCHAR(*p))) { x = ++p; /* after first space in space block */ yySpaceCount++; while (p < end && isspace(UCHAR(*p))) { p++; yySpaceCount++; } continue; } x = end; p++; } /* ignore more as 1 space at end */ yySpaceCount -= (end - x); end = x; /* ignore mandatory spaces used in format */ yySpaceCount -= fss->scnSpaceCount; if (yySpaceCount < 0) { yySpaceCount = 0; } info->dateStart = p = yyInput; info->dateEnd = end; /* parse string */ for (; tok->map != NULL; tok++) { map = tok->map; /* bypass spaces at begin of input before parsing each token */ if (!(opts->flags & CLF_STRICT) && (map->type != CTOKT_SPACE && map->type != CTOKT_WORD && map->type != CTOKT_CHAR)) { while (p < end && isspace(UCHAR(*p))) { yySpaceCount--; p++; } } yyInput = p; /* end of input string */ if (p >= end) { break; } switch (map->type) { case CTOKT_INT: case CTOKT_WIDE: { int minLen, size; int sign = 1; if (map->flags & CLF_SIGNED) { if (*p == '+') { yyInput = ++p; } else if (*p == '-') { yyInput = ++p; sign = -1; } } DetermineGreedySearchLen(opts, info, tok, &minLen, &size); if (size < map->minSize) { /* missing input -> error */ if ((map->flags & CLF_OPTIONAL)) { continue; } goto not_match; } /* string 2 number, put number into info structure by offset */ if (map->offs) { p = yyInput; x = p + size; if (map->type == CTOKT_INT) { if (size <= 10) { Clock_str2int_no(IntFieldAt(info, map->offs), p, x, sign); } else if (Clock_str2int( IntFieldAt(info, map->offs), p, x, sign) != TCL_OK) { goto overflow; } p = x; } else { if (size <= 18) { Clock_str2wideInt_no( WideFieldAt(info, map->offs), p, x, sign); } else if (Clock_str2wideInt( WideFieldAt(info, map->offs), p, x, sign) != TCL_OK) { goto overflow; } p = x; } flags = (flags & ~map->clearFlags) | map->flags; } break; } case CTOKT_PARSER: switch (map->parser(opts, info, tok)) { case TCL_OK: break; case TCL_RETURN: if ((map->flags & CLF_OPTIONAL)) { yyInput = p; continue; } goto not_match; default: goto done; } /* decrement count for possible spaces in match */ while (p < yyInput) { if (isspace(UCHAR(*p))) { yySpaceCount--; } p++; } p = yyInput; flags = (flags & ~map->clearFlags) | map->flags; break; case CTOKT_SPACE: /* at least one space */ if (!isspace(UCHAR(*p))) { /* unmatched -> error */ goto not_match; } /* don't decrement yySpaceCount by regular (first expected space), * already considered above with fss->scnSpaceCount */; p++; while (p < end && isspace(UCHAR(*p))) { yySpaceCount--; p++; } break; case CTOKT_WORD: x = FindWordEnd(tok, p, end); if (!x) { /* no match -> error */ goto not_match; } p = x; break; case CTOKT_CHAR: x = (char *)map->data; if (*x != *p) { /* no match -> error */ goto not_match; } if (isspace(UCHAR(*x))) { yySpaceCount--; } p++; break; } } /* check end was reached */ if (p < end) { /* in non-strict mode bypass spaces at end of input */ if (!(opts->flags & CLF_STRICT) && isspace(UCHAR(*p))) { p++; while (p < end && isspace(UCHAR(*p))) { p++; } } /* something after last token - wrong format */ if (p < end) { goto not_match; } } /* end of string, check only optional tokens at end, otherwise - not match */ while (tok->map != NULL) { if (!(opts->flags & CLF_STRICT) && (tok->map->type == CTOKT_SPACE)) { tok++; if (tok->map == NULL) { /* no tokens anymore - trailing spaces are mandatory */ goto not_match; } } if (!(tok->map->flags & CLF_OPTIONAL)) { goto not_match; } tok++; } /* * Invalidate result */ flags |= info->flags; /* seconds token (%s) take precedence over all other tokens */ if ((opts->flags & CLF_EXTENDED) || !(flags & CLF_POSIXSEC)) { if (flags & CLF_DATE) { if (!(flags & CLF_JULIANDAY)) { info->flags |= CLF_ASSEMBLE_SECONDS|CLF_ASSEMBLE_JULIANDAY; /* dd precedence below ddd */ switch (flags & (CLF_MONTH|CLF_DAYOFYEAR|CLF_DAYOFMONTH)) { case (CLF_DAYOFYEAR | CLF_DAYOFMONTH): /* miss month: ddd over dd (without month) */ flags &= ~CLF_DAYOFMONTH; /* fallthrough */ case CLF_DAYOFYEAR: /* ddd over naked weekday */ if (!(flags & CLF_ISO8601YEAR)) { flags &= ~CLF_ISO8601WEEK; } break; case CLF_MONTH | CLF_DAYOFYEAR | CLF_DAYOFMONTH: /* both available: mmdd over ddd */ case CLF_MONTH | CLF_DAYOFMONTH: case CLF_DAYOFMONTH: /* mmdd / dd over naked weekday */ if (!(flags & CLF_ISO8601YEAR)) { flags &= ~CLF_ISO8601WEEK; } break; /* neither mmdd nor ddd available */ case 0: /* but we have day of the week, which can be used */ if (flags & CLF_DAYOFWEEK) { /* prefer week based calculation of julianday */ flags |= CLF_ISO8601WEEK; } } /* YearWeekDay below YearMonthDay */ if ((flags & CLF_ISO8601WEEK) && ((flags & (CLF_YEAR | CLF_DAYOFYEAR)) == (CLF_YEAR | CLF_DAYOFYEAR) || (flags & (CLF_YEAR | CLF_DAYOFMONTH | CLF_MONTH)) == ( CLF_YEAR | CLF_DAYOFMONTH | CLF_MONTH))) { /* yy precedence below yyyy */ if (!(flags & CLF_ISO8601CENTURY) && (flags & CLF_CENTURY)) { /* normally precedence of ISO is higher, but no century - so put it down */ flags &= ~CLF_ISO8601WEEK; } else if (!(flags & CLF_ISO8601YEAR)) { /* yymmdd or yyddd over naked weekday */ flags &= ~CLF_ISO8601WEEK; } } if (flags & CLF_YEAR) { if (yyYear < 100) { if (!(flags & CLF_CENTURY)) { if (yyYear >= dataPtr->yearOfCenturySwitch) { yyYear -= 100; } yyYear += dataPtr->currentYearCentury; } else { yyYear += info->dateCentury * 100; } } } if (flags & (CLF_ISO8601WEEK | CLF_ISO8601YEAR)) { if ((flags & (CLF_ISO8601YEAR | CLF_YEAR)) == CLF_YEAR) { /* for calculations expected iso year */ info->date.iso8601Year = yyYear; } else if (info->date.iso8601Year < 100) { if (!(flags & CLF_ISO8601CENTURY)) { if (info->date.iso8601Year >= dataPtr->yearOfCenturySwitch) { info->date.iso8601Year -= 100; } info->date.iso8601Year += dataPtr->currentYearCentury; } else { info->date.iso8601Year += info->dateCentury * 100; } } if ((flags & (CLF_ISO8601YEAR | CLF_YEAR)) == CLF_ISO8601YEAR) { /* for calculations expected year (e. g. CLF_ISO8601WEEK not set) */ yyYear = info->date.iso8601Year; } } } } /* if no time - reset time */ if (!(flags & (CLF_TIME | CLF_LOCALSEC | CLF_POSIXSEC))) { info->flags |= CLF_ASSEMBLE_SECONDS; yydate.localSeconds = 0; } if (flags & CLF_TIME) { info->flags |= CLF_ASSEMBLE_SECONDS; yySecondOfDay = ToSeconds(yyHour, yyMinutes, yySeconds, yyMeridian); } else if (!(flags & (CLF_LOCALSEC | CLF_POSIXSEC))) { info->flags |= CLF_ASSEMBLE_SECONDS; yySecondOfDay = yydate.localSeconds % SECONDS_PER_DAY; } } /* tell caller which flags were set */ info->flags |= flags; ret = TCL_OK; done: return ret; /* Error case reporting. */ overflow: Tcl_SetObjResult(opts->interp, Tcl_NewStringObj( "integer value too large to represent", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(opts->interp, "CLOCK", "dateTooLarge", (char *)NULL); goto done; not_match: #if 1 Tcl_SetObjResult(opts->interp, Tcl_NewStringObj( "input string does not match supplied format", TCL_AUTO_LENGTH)); #else /* to debug where exactly scan breaks */ Tcl_SetObjResult(opts->interp, Tcl_ObjPrintf( "input string \"%s\" does not match supplied format \"%s\"," " locale \"%s\" - token \"%s\"", info->dateStart, HashEntry4FmtScn(fss)->key.string, TclGetString(opts->localeObj), tok && tok->tokWord.start ? tok->tokWord.start : "NULL")); #endif Tcl_SetErrorCode(opts->interp, "CLOCK", "badInputString", (char *)NULL); goto done; } #define FrmResultIsAllocated(dateFmt) \ (dateFmt->resEnd - dateFmt->resMem > MIN_FMT_RESULT_BLOCK_ALLOC) static inline int FrmResultAllocate( DateFormat *dateFmt, int len) { int needed = dateFmt->output + len - dateFmt->resEnd; if (needed >= 0) { /* >= 0 - regards NTS zero */ int newsize = dateFmt->resEnd - dateFmt->resMem + needed + MIN_FMT_RESULT_BLOCK_ALLOC * 2; char *newRes; /* differentiate between stack and memory */ if (!FrmResultIsAllocated(dateFmt)) { newRes = (char *)Tcl_AttemptAlloc(newsize); if (newRes == NULL) { return TCL_ERROR; } memcpy(newRes, dateFmt->resMem, dateFmt->output - dateFmt->resMem); } else { newRes = (char *)Tcl_AttemptRealloc(dateFmt->resMem, newsize); if (newRes == NULL) { return TCL_ERROR; } } dateFmt->output = newRes + (dateFmt->output - dateFmt->resMem); dateFmt->resMem = newRes; dateFmt->resEnd = newRes + newsize; } return TCL_OK; } static int ClockFmtToken_HourAMPM_Proc( TCL_UNUSED(ClockFmtScnCmdArgs *), TCL_UNUSED(DateFormat *), TCL_UNUSED(ClockFormatToken *), int *val) { *val = ((*val + SECONDS_PER_DAY - 3600) / 3600) % 12 + 1; return TCL_OK; } static int ClockFmtToken_AMPM_Proc( ClockFmtScnCmdArgs *opts, DateFormat *dateFmt, ClockFormatToken *tok, int *val) { Tcl_Obj *mcObj; const char *s; Tcl_Size len; if (*val < (SECONDS_PER_DAY / 2)) { mcObj = ClockMCGet(opts, MCLIT_AM); } else { mcObj = ClockMCGet(opts, MCLIT_PM); } if (mcObj == NULL) { return TCL_ERROR; } s = TclGetStringFromObj(mcObj, &len); if (FrmResultAllocate(dateFmt, len) != TCL_OK) { return TCL_ERROR; } memcpy(dateFmt->output, s, len + 1); if (*tok->tokWord.start == 'p') { len = Tcl_UtfToUpper(dateFmt->output); } dateFmt->output += len; return TCL_OK; } static int ClockFmtToken_StarDate_Proc( TCL_UNUSED(ClockFmtScnCmdArgs *), DateFormat *dateFmt, TCL_UNUSED(ClockFormatToken *), TCL_UNUSED(int *)) { int fractYear; /* Get day of year, zero based */ int v = dateFmt->date.dayOfYear - 1; /* Convert day of year to a fractional year */ if (IsGregorianLeapYear(&dateFmt->date)) { fractYear = 1000 * v / 366; } else { fractYear = 1000 * v / 365; } /* Put together the StarDate as "Stardate %02d%03d.%1d" */ if (FrmResultAllocate(dateFmt, 30) != TCL_OK) { return TCL_ERROR; } memcpy(dateFmt->output, "Stardate ", 9); dateFmt->output += 9; dateFmt->output = Clock_itoaw(dateFmt->output, dateFmt->date.year - RODDENBERRY, '0', 2); dateFmt->output = Clock_itoaw(dateFmt->output, fractYear, '0', 3); *dateFmt->output++ = '.'; /* be sure positive after decimal point (note: clock-value can be negative) */ v = dateFmt->date.secondOfDay / (SECONDS_PER_DAY / 10); if (v < 0) { v = 10 + v; } dateFmt->output = Clock_itoaw(dateFmt->output, v, '0', 1); return TCL_OK; } static int ClockFmtToken_WeekOfYear_Proc( TCL_UNUSED(ClockFmtScnCmdArgs *), DateFormat *dateFmt, ClockFormatToken *tok, int *val) { int dow = dateFmt->date.dayOfWeek; if (*tok->tokWord.start == 'U') { if (dow == 7) { dow = 0; } dow++; } *val = (dateFmt->date.dayOfYear - dow + 7) / 7; return TCL_OK; } static int ClockFmtToken_JDN_Proc( TCL_UNUSED(ClockFmtScnCmdArgs *), DateFormat *dateFmt, ClockFormatToken *tok, TCL_UNUSED(int *)) { Tcl_WideInt intJD = dateFmt->date.julianDay; int fractJD; /* Convert to JDN parts (regarding start offset) and time fraction */ fractJD = dateFmt->date.secondOfDay - (int)tok->map->offs; /* 0 for calendar or 43200 for astro JD */ if (fractJD < 0) { intJD--; fractJD += SECONDS_PER_DAY; } if (fractJD && intJD < 0) { /* avoid jump over 0, by negative JD's */ intJD++; if (intJD == 0) { /* -0.0 / -0.9 has zero integer part, so append "-" extra */ if (FrmResultAllocate(dateFmt, 1) != TCL_OK) { return TCL_ERROR; } *dateFmt->output++ = '-'; } /* and inverse seconds of day, -0(75) -> -0.25 as float */ fractJD = SECONDS_PER_DAY - fractJD; } /* 21 is max width of (negative) wide-int (rather smaller, but anyway a time fraction below) */ if (FrmResultAllocate(dateFmt, 21) != TCL_OK) { return TCL_ERROR; } dateFmt->output = Clock_witoaw(dateFmt->output, intJD, '0', 1); /* simplest cases .0 and .5 */ if (!fractJD || fractJD == (SECONDS_PER_DAY / 2)) { /* point + 0 or 5 */ if (FrmResultAllocate(dateFmt, 1 + 1) != TCL_OK) { return TCL_ERROR; } *dateFmt->output++ = '.'; *dateFmt->output++ = !fractJD ? '0' : '5'; *dateFmt->output = '\0'; return TCL_OK; } else { /* wrap the time fraction */ #define JDN_MAX_PRECISION 8 #define JDN_MAX_PRECBOUND 100000000 /* 10**JDN_MAX_PRECISION */ char *p; /* to float (part after floating point, + 0.5 to round it up) */ fractJD = (int)( (double)fractJD * JDN_MAX_PRECBOUND / SECONDS_PER_DAY + 0.5); /* point + integer (as time fraction after floating point) */ if (FrmResultAllocate(dateFmt, 1 + JDN_MAX_PRECISION) != TCL_OK) { return TCL_ERROR; } *dateFmt->output++ = '.'; p = Clock_itoaw(dateFmt->output, fractJD, '0', JDN_MAX_PRECISION); /* remove trailing zero's */ dateFmt->output++; while (p > dateFmt->output && p[-1] == '0') { p--; } *p = '\0'; dateFmt->output = p; } return TCL_OK; } static int ClockFmtToken_TimeZone_Proc( ClockFmtScnCmdArgs *opts, DateFormat *dateFmt, ClockFormatToken *tok, TCL_UNUSED(int *)) { if (*tok->tokWord.start == 'z') { int z = dateFmt->date.tzOffset; char sign = '+'; if (z < 0) { z = -z; sign = '-'; } if (FrmResultAllocate(dateFmt, 7) != TCL_OK) { return TCL_ERROR; } *dateFmt->output++ = sign; dateFmt->output = Clock_itoaw(dateFmt->output, z / 3600, '0', 2); z %= 3600; dateFmt->output = Clock_itoaw(dateFmt->output, z / 60, '0', 2); z %= 60; if (z != 0) { dateFmt->output = Clock_itoaw(dateFmt->output, z, '0', 2); } } else { Tcl_Obj * objPtr; const char *s; Tcl_Size len; /* convert seconds to local seconds to obtain tzName object */ if (ConvertUTCToLocal(opts->dataPtr, opts->interp, &dateFmt->date, opts->timezoneObj, GREGORIAN_CHANGE_DATE) != TCL_OK) { return TCL_ERROR; } objPtr = dateFmt->date.tzName; s = TclGetStringFromObj(objPtr, &len); if (FrmResultAllocate(dateFmt, len) != TCL_OK) { return TCL_ERROR; } memcpy(dateFmt->output, s, len + 1); dateFmt->output += len; } return TCL_OK; } static int ClockFmtToken_LocaleERA_Proc( ClockFmtScnCmdArgs *opts, DateFormat *dateFmt, TCL_UNUSED(ClockFormatToken *), TCL_UNUSED(int *)) { Tcl_Obj *mcObj; const char *s; Tcl_Size len; if (dateFmt->date.isBce) { mcObj = ClockMCGet(opts, MCLIT_BCE); } else { mcObj = ClockMCGet(opts, MCLIT_CE); } if (mcObj == NULL) { return TCL_ERROR; } s = TclGetStringFromObj(mcObj, &len); if (FrmResultAllocate(dateFmt, len) != TCL_OK) { return TCL_ERROR; } memcpy(dateFmt->output, s, len + 1); dateFmt->output += len; return TCL_OK; } static int ClockFmtToken_LocaleERAYear_Proc( ClockFmtScnCmdArgs *opts, DateFormat *dateFmt, ClockFormatToken *tok, int *val) { Tcl_Size rowc; Tcl_Obj **rowv; if (dateFmt->localeEra == NULL) { Tcl_Obj *mcObj = ClockMCGet(opts, MCLIT_LOCALE_ERAS); if (mcObj == NULL) { return TCL_ERROR; } if (TclListObjGetElements(opts->interp, mcObj, &rowc, &rowv) != TCL_OK) { return TCL_ERROR; } if (rowc != 0) { dateFmt->localeEra = LookupLastTransition(opts->interp, dateFmt->date.localSeconds, rowc, rowv, NULL); } if (dateFmt->localeEra == NULL) { dateFmt->localeEra = (Tcl_Obj*)1; } } /* if no LOCALE_ERAS in catalog or era not found */ if (dateFmt->localeEra == (Tcl_Obj*)1) { if (FrmResultAllocate(dateFmt, 11) != TCL_OK) { return TCL_ERROR; } if (*tok->tokWord.start == 'C') { /* %EC */ *val = dateFmt->date.year / 100; dateFmt->output = Clock_itoaw(dateFmt->output, *val, '0', 2); } else { /* %Ey */ *val = dateFmt->date.year % 100; dateFmt->output = Clock_itoaw(dateFmt->output, *val, '0', 2); } } else { Tcl_Obj *objPtr; const char *s; Tcl_Size len; if (*tok->tokWord.start == 'C') { /* %EC */ if (Tcl_ListObjIndex(opts->interp, dateFmt->localeEra, 1, &objPtr) != TCL_OK) { return TCL_ERROR; } } else { /* %Ey */ if (Tcl_ListObjIndex(opts->interp, dateFmt->localeEra, 2, &objPtr) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetIntFromObj(opts->interp, objPtr, val) != TCL_OK) { return TCL_ERROR; } *val = dateFmt->date.year - *val; /* if year in locale numerals */ if (*val >= 0 && *val < 100) { /* year as integer */ Tcl_Obj * mcObj = ClockMCGet(opts, MCLIT_LOCALE_NUMERALS); if (mcObj == NULL) { return TCL_ERROR; } if (Tcl_ListObjIndex(opts->interp, mcObj, *val, &objPtr) != TCL_OK) { return TCL_ERROR; } } else { /* year as integer */ if (FrmResultAllocate(dateFmt, 11) != TCL_OK) { return TCL_ERROR; } dateFmt->output = Clock_itoaw(dateFmt->output, *val, '0', 2); return TCL_OK; } } s = TclGetStringFromObj(objPtr, &len); if (FrmResultAllocate(dateFmt, len) != TCL_OK) { return TCL_ERROR; } memcpy(dateFmt->output, s, len + 1); dateFmt->output += len; } return TCL_OK; } /* * Descriptors for the various fields in [clock format]. */ static const char *FmtSTokenMapIndex = "demNbByYCHMSIklpaAuwUVzgGjJsntQ"; static const ClockFormatTokenMap FmtSTokenMap[] = { /* %d */ {CTOKT_INT, "0", 2, 0, 0, 0, offsetof(DateFormat, date.dayOfMonth), NULL, NULL}, /* %e */ {CTOKT_INT, " ", 2, 0, 0, 0, offsetof(DateFormat, date.dayOfMonth), NULL, NULL}, /* %m */ {CTOKT_INT, "0", 2, 0, 0, 0, offsetof(DateFormat, date.month), NULL, NULL}, /* %N */ {CTOKT_INT, " ", 2, 0, 0, 0, offsetof(DateFormat, date.month), NULL, NULL}, /* %b %h */ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX | CLFMT_DECR, 0, 12, offsetof(DateFormat, date.month), NULL, (void *)MCLIT_MONTHS_ABBREV}, /* %B */ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX | CLFMT_DECR, 0, 12, offsetof(DateFormat, date.month), NULL, (void *)MCLIT_MONTHS_FULL}, /* %y */ {CTOKT_INT, "0", 2, 0, 0, 100, offsetof(DateFormat, date.year), NULL, NULL}, /* %Y */ {CTOKT_INT, "0", 4, 0, 0, 0, offsetof(DateFormat, date.year), NULL, NULL}, /* %C */ {CTOKT_INT, "0", 2, 0, 100, 0, offsetof(DateFormat, date.year), NULL, NULL}, /* %H */ {CTOKT_INT, "0", 2, 0, 3600, 24, offsetof(DateFormat, date.secondOfDay), NULL, NULL}, /* %M */ {CTOKT_INT, "0", 2, 0, 60, 60, offsetof(DateFormat, date.secondOfDay), NULL, NULL}, /* %S */ {CTOKT_INT, "0", 2, 0, 0, 60, offsetof(DateFormat, date.secondOfDay), NULL, NULL}, /* %I */ {CTOKT_INT, "0", 2, CLFMT_CALC, 0, 0, offsetof(DateFormat, date.secondOfDay), ClockFmtToken_HourAMPM_Proc, NULL}, /* %k */ {CTOKT_INT, " ", 2, 0, 3600, 24, offsetof(DateFormat, date.secondOfDay), NULL, NULL}, /* %l */ {CTOKT_INT, " ", 2, CLFMT_CALC, 0, 0, offsetof(DateFormat, date.secondOfDay), ClockFmtToken_HourAMPM_Proc, NULL}, /* %p %P */ {CTOKT_INT, NULL, 0, 0, 0, 0, offsetof(DateFormat, date.secondOfDay), ClockFmtToken_AMPM_Proc, NULL}, /* %a */ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 7, offsetof(DateFormat, date.dayOfWeek), NULL, (void *)MCLIT_DAYS_OF_WEEK_ABBREV}, /* %A */ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 7, offsetof(DateFormat, date.dayOfWeek), NULL, (void *)MCLIT_DAYS_OF_WEEK_FULL}, /* %u */ {CTOKT_INT, " ", 1, 0, 0, 0, offsetof(DateFormat, date.dayOfWeek), NULL, NULL}, /* %w */ {CTOKT_INT, " ", 1, 0, 0, 7, offsetof(DateFormat, date.dayOfWeek), NULL, NULL}, /* %U %W */ {CTOKT_INT, "0", 2, CLFMT_CALC, 0, 0, offsetof(DateFormat, date.dayOfYear), ClockFmtToken_WeekOfYear_Proc, NULL}, /* %V */ {CTOKT_INT, "0", 2, 0, 0, 0, offsetof(DateFormat, date.iso8601Week), NULL, NULL}, /* %z %Z */ {CFMTT_PROC, NULL, 0, 0, 0, 0, 0, ClockFmtToken_TimeZone_Proc, NULL}, /* %g */ {CTOKT_INT, "0", 2, 0, 0, 100, offsetof(DateFormat, date.iso8601Year), NULL, NULL}, /* %G */ {CTOKT_INT, "0", 4, 0, 0, 0, offsetof(DateFormat, date.iso8601Year), NULL, NULL}, /* %j */ {CTOKT_INT, "0", 3, 0, 0, 0, offsetof(DateFormat, date.dayOfYear), NULL, NULL}, /* %J */ {CTOKT_WIDE, "0", 7, 0, 0, 0, offsetof(DateFormat, date.julianDay), NULL, NULL}, /* %s */ {CTOKT_WIDE, "0", 1, 0, 0, 0, offsetof(DateFormat, date.seconds), NULL, NULL}, /* %n */ {CTOKT_CHAR, "\n", 0, 0, 0, 0, 0, NULL, NULL}, /* %t */ {CTOKT_CHAR, "\t", 0, 0, 0, 0, 0, NULL, NULL}, /* %Q */ {CFMTT_PROC, NULL, 0, 0, 0, 0, 0, ClockFmtToken_StarDate_Proc, NULL}, }; static const char *FmtSTokenMapAliasIndex[2] = { "hPWZ", "bpUz" }; static const char *FmtETokenMapIndex = "EJjys"; static const ClockFormatTokenMap FmtETokenMap[] = { /* %EE */ {CFMTT_PROC, NULL, 0, 0, 0, 0, 0, ClockFmtToken_LocaleERA_Proc, NULL}, /* %EJ */ {CFMTT_PROC, NULL, 0, 0, 0, 0, 0, /* calendar JDN starts at midnight */ ClockFmtToken_JDN_Proc, NULL}, /* %Ej */ {CFMTT_PROC, NULL, 0, 0, 0, 0, (SECONDS_PER_DAY/2), /* astro JDN starts at noon */ ClockFmtToken_JDN_Proc, NULL}, /* %Ey %EC */ {CTOKT_INT, NULL, 0, 0, 0, 0, offsetof(DateFormat, date.year), ClockFmtToken_LocaleERAYear_Proc, NULL}, /* %Es */ {CTOKT_WIDE, "0", 1, 0, 0, 0, offsetof(DateFormat, date.localSeconds), NULL, NULL}, }; static const char *FmtETokenMapAliasIndex[2] = { "C", "y" }; static const char *FmtOTokenMapIndex = "dmyHIMSuw"; static const ClockFormatTokenMap FmtOTokenMap[] = { /* %Od %Oe */ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 100, offsetof(DateFormat, date.dayOfMonth), NULL, (void *)MCLIT_LOCALE_NUMERALS}, /* %Om */ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 100, offsetof(DateFormat, date.month), NULL, (void *)MCLIT_LOCALE_NUMERALS}, /* %Oy */ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 100, offsetof(DateFormat, date.year), NULL, (void *)MCLIT_LOCALE_NUMERALS}, /* %OH %Ok */ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 3600, 24, offsetof(DateFormat, date.secondOfDay), NULL, (void *)MCLIT_LOCALE_NUMERALS}, /* %OI %Ol */ {CTOKT_INT, NULL, 0, CLFMT_CALC | CLFMT_LOCALE_INDX, 0, 0, offsetof(DateFormat, date.secondOfDay), ClockFmtToken_HourAMPM_Proc, (void *)MCLIT_LOCALE_NUMERALS}, /* %OM */ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 60, 60, offsetof(DateFormat, date.secondOfDay), NULL, (void *)MCLIT_LOCALE_NUMERALS}, /* %OS */ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 60, offsetof(DateFormat, date.secondOfDay), NULL, (void *)MCLIT_LOCALE_NUMERALS}, /* %Ou */ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 100, offsetof(DateFormat, date.dayOfWeek), NULL, (void *)MCLIT_LOCALE_NUMERALS}, /* %Ow */ {CTOKT_INT, NULL, 0, CLFMT_LOCALE_INDX, 0, 7, offsetof(DateFormat, date.dayOfWeek), NULL, (void *)MCLIT_LOCALE_NUMERALS}, }; static const char *FmtOTokenMapAliasIndex[2] = { "ekl", "dHI" }; static const ClockFormatTokenMap FmtWordTokenMap = { CTOKT_WORD, NULL, 0, 0, 0, 0, 0, NULL, NULL }; /* *---------------------------------------------------------------------- */ ClockFmtScnStorage * ClockGetOrParseFmtFormat( Tcl_Interp *interp, /* Tcl interpreter */ Tcl_Obj *formatObj) /* Format container */ { ClockFmtScnStorage *fss; fss = Tcl_GetClockFrmScnFromObj(interp, formatObj); if (fss == NULL) { return NULL; } /* if format (fmtTok) already tokenized */ if (fss->fmtTok != NULL) { return fss; } Tcl_MutexLock(&ClockFmtMutex); /* first time formatting - tokenize format */ if (fss->fmtTok == NULL) { ClockFormatToken *tok, *fmtTok; unsigned tokCnt; const char *p, *e, *cp; e = p = HashEntry4FmtScn(fss)->key.string; e += strlen(p); /* estimate token count by % char and format length */ fss->fmtTokC = EstimateTokenCount(p, e); fmtTok = tok = (ClockFormatToken *)Tcl_Alloc(sizeof(*tok) * fss->fmtTokC); memset(tok, 0, sizeof(*tok)); tokCnt = 1; while (p < e) { switch (*p) { case '%': { const ClockFormatTokenMap *fmtMap = FmtSTokenMap; const char *mapIndex = FmtSTokenMapIndex; const char **aliasIndex = FmtSTokenMapAliasIndex; if (p + 1 >= e) { goto word_tok; } p++; /* try to find modifier: */ switch (*p) { case '%': /* begin new word token - don't join with previous word token, * because current mapping should be "...%%..." -> "...%..." */ tok->map = &FmtWordTokenMap; tok->tokWord.start = p; tok->tokWord.end = p + 1; AllocTokenInChain(tok, fmtTok, fss->fmtTokC, ClockFormatToken *); tokCnt++; p++; continue; case 'E': fmtMap = FmtETokenMap, mapIndex = FmtETokenMapIndex, aliasIndex = FmtETokenMapAliasIndex; p++; break; case 'O': fmtMap = FmtOTokenMap, mapIndex = FmtOTokenMapIndex, aliasIndex = FmtOTokenMapAliasIndex; p++; break; } /* search direct index */ cp = strchr(mapIndex, *p); if (!cp || *cp == '\0') { /* search wrapper index (multiple chars for same token) */ cp = strchr(aliasIndex[0], *p); if (!cp || *cp == '\0') { p--; if (fmtMap != FmtSTokenMap) { p--; } goto word_tok; } cp = strchr(mapIndex, aliasIndex[1][cp - aliasIndex[0]]); if (!cp || *cp == '\0') { /* unexpected, but ... */ #ifdef DEBUG Tcl_Panic("token \"%c\" has no map in wrapper resolver", *p); #endif p--; if (fmtMap != FmtSTokenMap) { p--; } goto word_tok; } } tok->map = &fmtMap[cp - mapIndex]; tok->tokWord.start = p; /* next token */ AllocTokenInChain(tok, fmtTok, fss->fmtTokC, ClockFormatToken *); tokCnt++; p++; continue; } default: word_tok: { /* try continue with previous word token */ ClockFormatToken *wordTok = tok - 1; if (wordTok < fmtTok || wordTok->map != &FmtWordTokenMap) { /* start with new word token */ wordTok = tok; wordTok->tokWord.start = p; wordTok->map = &FmtWordTokenMap; } do { p = Tcl_UtfNext(p); } while (p < e && *p != '%'); wordTok->tokWord.end = p; if (wordTok == tok) { AllocTokenInChain(tok, fmtTok, fss->fmtTokC, ClockFormatToken *); tokCnt++; } } break; } } /* correct count of real used tokens and free mem if desired * (1 is acceptable delta to prevent memory fragmentation) */ if (fss->fmtTokC > tokCnt + (CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE / 2)) { if ((tok = (ClockFormatToken *) Tcl_AttemptRealloc(fmtTok, tokCnt * sizeof(*tok))) != NULL) { fmtTok = tok; } } /* now we're ready - assign now to storage (note the threaded race condition) */ fss->fmtTok = fmtTok; fss->fmtTokC = tokCnt; } Tcl_MutexUnlock(&ClockFmtMutex); return fss; } /* *---------------------------------------------------------------------- */ int ClockFormat( DateFormat *dateFmt, /* Date fields used for parsing & converting */ ClockFmtScnCmdArgs *opts) /* Command options */ { ClockFmtScnStorage *fss; ClockFormatToken *tok; const ClockFormatTokenMap *map; char resMem[MIN_FMT_RESULT_BLOCK_ALLOC]; /* get localized format */ if (ClockLocalizeFormat(opts) == NULL) { return TCL_ERROR; } if (!(fss = ClockGetOrParseFmtFormat(opts->interp, opts->formatObj)) || !(tok = fss->fmtTok)) { return TCL_ERROR; } /* result container object */ dateFmt->resMem = resMem; dateFmt->resEnd = dateFmt->resMem + sizeof(resMem); if (fss->fmtMinAlloc > sizeof(resMem)) { dateFmt->resMem = (char *)Tcl_AttemptAlloc(fss->fmtMinAlloc); if (dateFmt->resMem == NULL) { return TCL_ERROR; } dateFmt->resEnd = dateFmt->resMem + fss->fmtMinAlloc; } dateFmt->output = dateFmt->resMem; *dateFmt->output = '\0'; /* do format each token */ for (; tok->map != NULL; tok++) { map = tok->map; switch (map->type) { case CTOKT_INT: { int val = *IntFieldAt(dateFmt, map->offs); if (map->fmtproc == NULL) { if (map->flags & CLFMT_DECR) { val--; } if (map->flags & CLFMT_INCR) { val++; } if (map->divider) { val /= map->divider; } if (map->divmod) { val %= map->divmod; } } else { if (map->fmtproc(opts, dateFmt, tok, &val) != TCL_OK) { goto done; } /* if not calculate only (output inside fmtproc) */ if (!(map->flags & CLFMT_CALC)) { continue; } } if (!(map->flags & CLFMT_LOCALE_INDX)) { if (FrmResultAllocate(dateFmt, 11) != TCL_OK) { goto error; } if (map->width) { dateFmt->output = Clock_itoaw( dateFmt->output, val, *map->tostr, map->width); } else { dateFmt->output += sprintf(dateFmt->output, map->tostr, val); } } else { const char *s; Tcl_Obj * mcObj = ClockMCGet(opts, PTR2INT(map->data) /* mcKey */); if (mcObj == NULL) { goto error; } if (Tcl_ListObjIndex(opts->interp, mcObj, val, &mcObj) != TCL_OK || mcObj == NULL) { goto error; } s = TclGetString(mcObj); if (FrmResultAllocate(dateFmt, mcObj->length) != TCL_OK) { goto error; } memcpy(dateFmt->output, s, mcObj->length + 1); dateFmt->output += mcObj->length; } break; } case CTOKT_WIDE: { Tcl_WideInt val = *WideFieldAt(dateFmt, map->offs); if (FrmResultAllocate(dateFmt, 21) != TCL_OK) { goto error; } if (map->width) { dateFmt->output = Clock_witoaw(dateFmt->output, val, *map->tostr, map->width); } else { dateFmt->output += sprintf(dateFmt->output, map->tostr, val); } break; } case CTOKT_CHAR: if (FrmResultAllocate(dateFmt, 1) != TCL_OK) { goto error; } *dateFmt->output++ = *map->tostr; break; case CFMTT_PROC: if (map->fmtproc(opts, dateFmt, tok, NULL) != TCL_OK) { goto error; } break; case CTOKT_WORD: { Tcl_Size len = tok->tokWord.end - tok->tokWord.start; if (FrmResultAllocate(dateFmt, len) != TCL_OK) { goto error; } if (len == 1) { *dateFmt->output++ = *tok->tokWord.start; } else { memcpy(dateFmt->output, tok->tokWord.start, len); dateFmt->output += len; } break; } } } goto done; error: if (dateFmt->resMem != resMem) { Tcl_Free(dateFmt->resMem); } dateFmt->resMem = NULL; done: if (dateFmt->resMem) { size_t size; Tcl_Obj *result; TclNewObj(result); result->length = dateFmt->output - dateFmt->resMem; size = result->length + 1; if (dateFmt->resMem == resMem) { result->bytes = (char *)Tcl_AttemptAlloc(size); if (result->bytes == NULL) { return TCL_ERROR; } memcpy(result->bytes, dateFmt->resMem, size); } else if ((dateFmt->resEnd - dateFmt->resMem) / size > MAX_FMT_RESULT_THRESHOLD) { result->bytes = (char *)Tcl_AttemptRealloc(dateFmt->resMem, size); if (result->bytes == NULL) { result->bytes = dateFmt->resMem; } } else { result->bytes = dateFmt->resMem; } /* save last used buffer length */ if (dateFmt->resMem != resMem && fss->fmtMinAlloc < size + MIN_FMT_RESULT_BLOCK_DELTA) { fss->fmtMinAlloc = size + MIN_FMT_RESULT_BLOCK_DELTA; } result->bytes[result->length] = '\0'; Tcl_SetObjResult(opts->interp, result); return TCL_OK; } return TCL_ERROR; } void ClockFrmScnClearCaches(void) { Tcl_MutexLock(&ClockFmtMutex); /* clear caches ... */ Tcl_MutexUnlock(&ClockFmtMutex); } void ClockFrmScnFinalize(void) { if (!initialized) { return; } Tcl_MutexLock(&ClockFmtMutex); #if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0 /* clear GC */ ClockFmtScnStorage_GC.stackPtr = NULL; ClockFmtScnStorage_GC.stackBound = NULL; ClockFmtScnStorage_GC.count = 0; #endif if (initialized) { initialized = 0; Tcl_DeleteHashTable(&FmtScnHashTable); } Tcl_MutexUnlock(&ClockFmtMutex); Tcl_MutexFinalize(&ClockFmtMutex); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclCmdAH.c0000644000175000017500000023643014726623136014765 0ustar sergeisergei/* * tclCmdAH.c -- * * This file contains the top-level command routines for most of the Tcl * built-in commands whose names begin with the letters A to H. * * Copyright © 1987-1993 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclIO.h" #include "tclTomMath.h" #ifdef _WIN32 # include "tclWinInt.h" #endif /* * The state structure used by [foreach]. Note that the actual structure has * all its working arrays appended afterwards so they can be allocated and * freed in a single step. */ struct ForeachState { Tcl_Obj *bodyPtr; /* The script body of the command. */ Tcl_Size bodyIdx; /* The argument index of the body. */ Tcl_Size j, maxj; /* Number of loop iterations. */ Tcl_Size numLists; /* Count of value lists. */ Tcl_Size *index; /* Array of value list indices. */ Tcl_Size *varcList; /* # loop variables per list. */ Tcl_Obj ***varvList; /* Array of var name lists. */ Tcl_Obj **vCopyList; /* Copies of var name list arguments. */ Tcl_Size *argcList; /* Array of value list sizes. */ Tcl_Obj ***argvList; /* Array of value lists. */ Tcl_Obj **aCopyList; /* Copies of value list arguments. */ Tcl_Obj *resultList; /* List of result values from the loop body, * or NULL if we're not collecting them * ([lmap] vs [foreach]). */ }; /* * Prototypes for local procedures defined in this file: */ static int CheckAccess(Tcl_Interp *interp, Tcl_Obj *pathPtr, int mode); static Tcl_ObjCmdProc EncodingConvertfromObjCmd; static Tcl_ObjCmdProc EncodingConverttoObjCmd; static Tcl_ObjCmdProc EncodingDirsObjCmd; static Tcl_ObjCmdProc EncodingNamesObjCmd; static Tcl_ObjCmdProc EncodingProfilesObjCmd; static Tcl_ObjCmdProc EncodingSystemObjCmd; static inline int ForeachAssignments(Tcl_Interp *interp, struct ForeachState *statePtr); static inline void ForeachCleanup(Tcl_Interp *interp, struct ForeachState *statePtr); static int GetStatBuf(Tcl_Interp *interp, Tcl_Obj *pathPtr, Tcl_FSStatProc *statProc, Tcl_StatBuf *statPtr); static const char * GetTypeFromMode(int mode); static int StoreStatData(Tcl_Interp *interp, Tcl_Obj *varName, Tcl_StatBuf *statPtr); static int EachloopCmd(Tcl_Interp *interp, int collect, int objc, Tcl_Obj *const objv[]); static Tcl_NRPostProc CatchObjCmdCallback; static Tcl_NRPostProc ExprCallback; static Tcl_NRPostProc ForSetupCallback; static Tcl_NRPostProc ForCondCallback; static Tcl_NRPostProc ForNextCallback; static Tcl_NRPostProc ForPostNextCallback; static Tcl_NRPostProc ForeachLoopStep; static Tcl_NRPostProc EvalCmdErrMsg; static Tcl_ObjCmdProc FileAttrAccessTimeCmd; static Tcl_ObjCmdProc FileAttrIsDirectoryCmd; static Tcl_ObjCmdProc FileAttrIsExecutableCmd; static Tcl_ObjCmdProc FileAttrIsExistingCmd; static Tcl_ObjCmdProc FileAttrIsFileCmd; static Tcl_ObjCmdProc FileAttrIsOwnedCmd; static Tcl_ObjCmdProc FileAttrIsReadableCmd; static Tcl_ObjCmdProc FileAttrIsWritableCmd; static Tcl_ObjCmdProc FileAttrLinkStatCmd; static Tcl_ObjCmdProc FileAttrModifyTimeCmd; static Tcl_ObjCmdProc FileAttrSizeCmd; static Tcl_ObjCmdProc FileAttrStatCmd; static Tcl_ObjCmdProc FileAttrTypeCmd; static Tcl_ObjCmdProc FilesystemSeparatorCmd; static Tcl_ObjCmdProc FilesystemVolumesCmd; static Tcl_ObjCmdProc PathDirNameCmd; static Tcl_ObjCmdProc PathExtensionCmd; static Tcl_ObjCmdProc PathFilesystemCmd; static Tcl_ObjCmdProc PathJoinCmd; static Tcl_ObjCmdProc PathNativeNameCmd; static Tcl_ObjCmdProc PathNormalizeCmd; static Tcl_ObjCmdProc PathRootNameCmd; static Tcl_ObjCmdProc PathSplitCmd; static Tcl_ObjCmdProc PathTailCmd; static Tcl_ObjCmdProc PathTypeCmd; /* *---------------------------------------------------------------------- * * Tcl_BreakObjCmd -- * * This procedure is invoked to process the "break" Tcl command. See the * user documentation for details on what it does. * * With the bytecode compiler, this procedure is only called when a * command name is computed at runtime, and is "break" or the name to * which "break" was renamed: e.g., "set z break; $z" * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_BreakObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } return TCL_BREAK; } /* *---------------------------------------------------------------------- * * Tcl_CatchObjCmd -- * * This object-based procedure is invoked to process the "catch" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_CatchObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRCatchObjCmd, clientData, objc, objv); } int TclNRCatchObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *varNamePtr = NULL; Tcl_Obj *optionVarNamePtr = NULL; Interp *iPtr = (Interp *) interp; if ((objc < 2) || (objc > 4)) { Tcl_WrongNumArgs(interp, 1, objv, "script ?resultVarName? ?optionVarName?"); return TCL_ERROR; } if (objc >= 3) { varNamePtr = objv[2]; } if (objc == 4) { optionVarNamePtr = objv[3]; } TclNRAddCallback(interp, CatchObjCmdCallback, INT2PTR(objc), varNamePtr, optionVarNamePtr, NULL); /* * TIP #280. Make invoking context available to caught script. */ return TclNREvalObjEx(interp, objv[1], 0, iPtr->cmdFramePtr, 1); } static int CatchObjCmdCallback( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; int objc = PTR2INT(data[0]); Tcl_Obj *varNamePtr = (Tcl_Obj *)data[1]; Tcl_Obj *optionVarNamePtr = (Tcl_Obj *)data[2]; int rewind = iPtr->execEnvPtr->rewind; /* * We disable catch in interpreters where the limit has been exceeded. */ if (rewind || Tcl_LimitExceeded(interp)) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (\"catch\" body line %d)", Tcl_GetErrorLine(interp))); return TCL_ERROR; } if (objc >= 3) { if (NULL == Tcl_ObjSetVar2(interp, varNamePtr, NULL, Tcl_GetObjResult(interp), TCL_LEAVE_ERR_MSG)) { return TCL_ERROR; } } if (objc == 4) { Tcl_Obj *options = Tcl_GetReturnOptions(interp, result); if (NULL == Tcl_ObjSetVar2(interp, optionVarNamePtr, NULL, options, TCL_LEAVE_ERR_MSG)) { /* Do not decrRefCount 'options', it was already done by * Tcl_ObjSetVar2 */ return TCL_ERROR; } } Tcl_ResetResult(interp); Tcl_SetObjResult(interp, Tcl_NewWideIntObj(result)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_CdObjCmd -- * * This procedure is invoked to process the "cd" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_CdObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *dir; int result; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?dirName?"); return TCL_ERROR; } if (objc == 2) { dir = objv[1]; } else { dir = TclGetHomeDirObj(interp, NULL); if (dir == NULL) { return TCL_ERROR; } Tcl_IncrRefCount(dir); } if (Tcl_FSConvertToPathType(interp, dir) != TCL_OK) { result = TCL_ERROR; } else { Tcl_DString ds; result = Tcl_UtfToExternalDStringEx(NULL, TCLFSENCODING, TclGetString(dir), -1, 0, &ds, NULL); Tcl_DStringFree(&ds); if (result == TCL_OK) { result = Tcl_FSChdir(dir); } if (result != TCL_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't change working directory to \"%s\": %s", TclGetString(dir), Tcl_PosixError(interp))); result = TCL_ERROR; } } if (objc != 2) { Tcl_DecrRefCount(dir); } return result; } /* *---------------------------------------------------------------------- * * Tcl_ConcatObjCmd -- * * This object-based procedure is invoked to process the "concat" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_ConcatObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc >= 2) { Tcl_SetObjResult(interp, Tcl_ConcatObj(objc-1, objv+1)); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ContinueObjCmd -- * * This procedure is invoked to process the "continue" Tcl command. See * the user documentation for details on what it does. * * With the bytecode compiler, this procedure is only called when a * command name is computed at runtime, and is "continue" or the name to * which "continue" was renamed: e.g., "set z continue; $z" * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_ContinueObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } return TCL_CONTINUE; } /* *----------------------------------------------------------------------------- * * TclInitEncodingCmd -- * * This function creates the 'encoding' ensemble. * * Results: * Returns the Tcl_Command so created. * * Side effects: * The ensemble is initialized. * * This command is hidden in a safe interpreter. */ Tcl_Command TclInitEncodingCmd( Tcl_Interp* interp) /* Tcl interpreter */ { static const EnsembleImplMap encodingImplMap[] = { {"convertfrom", EncodingConvertfromObjCmd, TclCompileBasic1To3ArgCmd, NULL, NULL, 0}, {"convertto", EncodingConverttoObjCmd, TclCompileBasic1To3ArgCmd, NULL, NULL, 0}, {"dirs", EncodingDirsObjCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 1}, {"names", EncodingNamesObjCmd, TclCompileBasic0ArgCmd, NULL, NULL, 0}, {"profiles", EncodingProfilesObjCmd, TclCompileBasic0ArgCmd, NULL, NULL, 0}, {"system", EncodingSystemObjCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 1}, {NULL, NULL, NULL, NULL, NULL, 0} }; return TclMakeEnsemble(interp, "encoding", encodingImplMap); } /* *------------------------------------------------------------------------ * * EncodingConvertParseOptions -- * * Common routine for parsing arguments passed to encoding convertfrom * and encoding convertto. * * Results: * TCL_OK or TCL_ERROR. * * Side effects: * On success, * - *encPtr is set to the encoding. Must be freed with Tcl_FreeEncoding * if non-NULL * - *dataObjPtr is set to the Tcl_Obj containing the data to encode or * decode * - *profilePtr is set to encoding error handling profile * - *failVarPtr is set to -failindex option value or NULL * On error, all of the above are uninitialized. * *------------------------------------------------------------------------ */ static int EncodingConvertParseOptions( Tcl_Interp *interp, /* For error messages. May be NULL */ int objc, /* Number of arguments */ Tcl_Obj *const objv[], /* Argument objects as passed to command. */ Tcl_Encoding *encPtr, /* Where to store the encoding */ Tcl_Obj **dataObjPtr, /* Where to store ptr to Tcl_Obj containing data */ int *profilePtr, /* Bit mask of encoding option profile */ Tcl_Obj **failVarPtr) /* Where to store -failindex option value */ { static const char *const options[] = {"-profile", "-failindex", NULL}; enum convertfromOptions { PROFILE, FAILINDEX } optIndex; Tcl_Encoding encoding; Tcl_Obj *dataObj; Tcl_Obj *failVarObj; int profile = TCL_ENCODING_PROFILE_STRICT; /* * Possible combinations: * 1) data -> objc = 2 * 2) ?options? encoding data -> objc >= 3 * It is intentional that specifying option forces encoding to be * specified. Less prone to user error. This should have always been * the case even in 8.6 imho where there were no options (ie (1) * should never have been allowed) */ if (objc == 1) { numArgsError: /* ONLY jump here if nothing needs to be freed!!! */ Tcl_WrongNumArgs(interp, 1, objv, "?-profile profile? ?-failindex var? encoding data"); ((Interp *)interp)->flags |= INTERP_ALTERNATE_WRONG_ARGS; Tcl_WrongNumArgs(interp, 1, objv, "data"); return TCL_ERROR; } failVarObj = NULL; if (objc == 2) { encoding = Tcl_GetEncoding(interp, NULL); dataObj = objv[1]; } else { int argIndex; for (argIndex = 1; argIndex < (objc-2); ++argIndex) { if (Tcl_GetIndexFromObj(interp, objv[argIndex], options, "option", 0, &optIndex) != TCL_OK) { return TCL_ERROR; } if (++argIndex == (objc - 2)) { goto numArgsError; } switch (optIndex) { case PROFILE: if (TclEncodingProfileNameToId(interp, Tcl_GetString(objv[argIndex]), &profile) != TCL_OK) { return TCL_ERROR; } break; case FAILINDEX: failVarObj = objv[argIndex]; break; } } /* Get encoding after opts so no need to free it on option error */ if (Tcl_GetEncodingFromObj(interp, objv[objc - 2], &encoding) != TCL_OK) { return TCL_ERROR; } dataObj = objv[objc - 1]; } *encPtr = encoding; *dataObjPtr = dataObj; *profilePtr = profile; *failVarPtr = failVarObj; return TCL_OK; } /* *---------------------------------------------------------------------- * * EncodingConvertfromObjCmd -- * * This command converts a byte array in an external encoding into a * Tcl string * * Results: * A standard Tcl result. * *---------------------------------------------------------------------- */ int EncodingConvertfromObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *data; /* Byte array to convert */ Tcl_DString ds; /* Buffer to hold the string */ Tcl_Encoding encoding; /* Encoding to use */ Tcl_Size length = 0; /* Length of the byte array being converted */ const char *bytesPtr; /* Pointer to the first byte of the array */ int flags; int result; Tcl_Obj *failVarObj; Tcl_Size errorLocation; if (EncodingConvertParseOptions(interp, objc, objv, &encoding, &data, &flags, &failVarObj) != TCL_OK) { return TCL_ERROR; } /* * Convert the string into a byte array in 'ds'. */ bytesPtr = (char *) Tcl_GetBytesFromObj(interp, data, &length); if (bytesPtr == NULL) { return TCL_ERROR; } result = Tcl_ExternalToUtfDStringEx(interp, encoding, bytesPtr, length, flags, &ds, failVarObj ? &errorLocation : NULL); /* NOTE: ds must be freed beyond this point even on error */ switch (result) { case TCL_OK: errorLocation = TCL_INDEX_NONE; break; case TCL_ERROR: /* Error in parameters. Should not happen. interp will have error */ Tcl_DStringFree(&ds); return TCL_ERROR; default: /* * One of the TCL_CONVERT_* errors. If we were not interested in the * error location, interp result would already have been filled in * and we can just return the error. Otherwise, we have to return * what could be decoded and the returned error location. */ if (failVarObj == NULL) { Tcl_DStringFree(&ds); return TCL_ERROR; } break; } /* * TCL_OK or a TCL_CONVERT_* error where the caller wants back as much * data as was converted. */ if (failVarObj) { Tcl_Obj *failIndex; TclNewIndexObj(failIndex, errorLocation); if (Tcl_ObjSetVar2(interp, failVarObj, NULL, failIndex, TCL_LEAVE_ERR_MSG) == NULL) { Tcl_DStringFree(&ds); return TCL_ERROR; } } /* * Note that we cannot use Tcl_DStringResult here because it will * truncate the string at the first null byte. */ Tcl_SetObjResult(interp, Tcl_DStringToObj(&ds)); /* We're done with the encoding */ Tcl_FreeEncoding(encoding); return TCL_OK; } /* *---------------------------------------------------------------------- * * EncodingConverttoObjCmd -- * * This command converts a Tcl string into a byte array that * encodes the string according to some encoding. * * Results: * A standard Tcl result. * *---------------------------------------------------------------------- */ int EncodingConverttoObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *data; /* String to convert */ Tcl_DString ds; /* Buffer to hold the byte array */ Tcl_Encoding encoding; /* Encoding to use */ Tcl_Size length; /* Length of the string being converted */ const char *stringPtr; /* Pointer to the first byte of the string */ int result; int flags; Tcl_Obj *failVarObj; Tcl_Size errorLocation; if (EncodingConvertParseOptions(interp, objc, objv, &encoding, &data, &flags, &failVarObj) != TCL_OK) { return TCL_ERROR; } /* * Convert the string to a byte array in 'ds' */ stringPtr = TclGetStringFromObj(data, &length); result = Tcl_UtfToExternalDStringEx(interp, encoding, stringPtr, length, flags, &ds, failVarObj ? &errorLocation : NULL); /* NOTE: ds must be freed beyond this point even on error */ switch (result) { case TCL_OK: errorLocation = TCL_INDEX_NONE; break; case TCL_ERROR: /* Error in parameters. Should not happen. interp will have error */ Tcl_DStringFree(&ds); return TCL_ERROR; default: /* * One of the TCL_CONVERT_* errors. If we were not interested in the * error location, interp result would already have been filled in * and we can just return the error. Otherwise, we have to return * what could be decoded and the returned error location. */ if (failVarObj == NULL) { Tcl_DStringFree(&ds); return TCL_ERROR; } break; } /* * TCL_OK or a TCL_CONVERT_* error where the caller wants back as much * data as was converted. */ if (failVarObj) { Tcl_Obj *failIndex; TclNewIndexObj(failIndex, errorLocation); if (Tcl_ObjSetVar2(interp, failVarObj, NULL, failIndex, TCL_LEAVE_ERR_MSG) == NULL) { Tcl_DStringFree(&ds); return TCL_ERROR; } } Tcl_SetObjResult(interp, Tcl_NewByteArrayObj( (unsigned char*) Tcl_DStringValue(&ds), Tcl_DStringLength(&ds))); Tcl_DStringFree(&ds); /* We're done with the encoding */ Tcl_FreeEncoding(encoding); return TCL_OK; } /* *---------------------------------------------------------------------- * * EncodingDirsObjCmd -- * * This command manipulates the encoding search path. * * Results: * A standard Tcl result. * * Side effects: * Can set the encoding search path. * *---------------------------------------------------------------------- */ int EncodingDirsObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *dirListObj; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?dirList?"); return TCL_ERROR; } if (objc == 1) { Tcl_SetObjResult(interp, Tcl_GetEncodingSearchPath()); return TCL_OK; } dirListObj = objv[1]; if (Tcl_SetEncodingSearchPath(dirListObj) == TCL_ERROR) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected directory list but got \"%s\"", TclGetString(dirListObj))); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "ENCODING", "BADPATH", (char *)NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, dirListObj); return TCL_OK; } /* *----------------------------------------------------------------------------- * * EncodingNamesObjCmd -- * * This command returns a list of the available encoding names * * Results: * Returns a standard Tcl result * *----------------------------------------------------------------------------- */ int EncodingNamesObjCmd( TCL_UNUSED(void *), Tcl_Interp* interp, /* Tcl interpreter */ int objc, /* Number of command line args */ Tcl_Obj* const objv[]) /* Vector of command line args */ { if (objc > 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } Tcl_GetEncodingNames(interp); return TCL_OK; } /* *----------------------------------------------------------------------------- * * EncodingProfilesObjCmd -- * * This command returns a list of the available encoding profiles * * Results: * Returns a standard Tcl result * *----------------------------------------------------------------------------- */ int EncodingProfilesObjCmd( TCL_UNUSED(void *), Tcl_Interp* interp, /* Tcl interpreter */ int objc, /* Number of command line args */ Tcl_Obj* const objv[]) /* Vector of command line args */ { if (objc > 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } TclGetEncodingProfiles(interp); return TCL_OK; } /* *----------------------------------------------------------------------------- * * EncodingSystemObjCmd -- * * This command retrieves or changes the system encoding * * Results: * Returns a standard Tcl result * * Side effects: * May change the system encoding. * *----------------------------------------------------------------------------- */ int EncodingSystemObjCmd( TCL_UNUSED(void *), Tcl_Interp* interp, /* Tcl interpreter */ int objc, /* Number of command line args */ Tcl_Obj* const objv[]) /* Vector of command line args */ { if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?encoding?"); return TCL_ERROR; } if (objc == 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_GetEncodingName(NULL), -1)); } else { return Tcl_SetSystemEncoding(interp, TclGetString(objv[1])); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ErrorObjCmd -- * * This procedure is invoked to process the "error" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_ErrorObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *options, *optName; if ((objc < 2) || (objc > 4)) { Tcl_WrongNumArgs(interp, 1, objv, "message ?errorInfo? ?errorCode?"); return TCL_ERROR; } TclNewLiteralStringObj(options, "-code error -level 0"); if (objc >= 3) { /* Process the optional info argument */ TclNewLiteralStringObj(optName, "-errorinfo"); Tcl_ListObjAppendElement(NULL, options, optName); Tcl_ListObjAppendElement(NULL, options, objv[2]); } if (objc >= 4) { /* Process the optional code argument */ TclNewLiteralStringObj(optName, "-errorcode"); Tcl_ListObjAppendElement(NULL, options, optName); Tcl_ListObjAppendElement(NULL, options, objv[3]); } Tcl_SetObjResult(interp, objv[1]); return Tcl_SetReturnOptions(interp, options); } /* *---------------------------------------------------------------------- * * Tcl_EvalObjCmd -- * * This object-based procedure is invoked to process the "eval" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int EvalCmdErrMsg( TCL_UNUSED(void **), Tcl_Interp *interp, int result) { if (result == TCL_ERROR) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (\"eval\" body line %d)", Tcl_GetErrorLine(interp))); } return result; } int Tcl_EvalObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNREvalObjCmd, clientData, objc, objv); } int TclNREvalObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *objPtr; Interp *iPtr = (Interp *) interp; CmdFrame *invoker = NULL; int word = 0; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "arg ?arg ...?"); return TCL_ERROR; } if (objc == 2) { /* * TIP #280. Make argument location available to eval'd script. */ invoker = iPtr->cmdFramePtr; word = 1; objPtr = objv[1]; TclArgumentGet(interp, objPtr, &invoker, &word); } else { /* * More than one argument: concatenate them together with spaces * between, then evaluate the result. Tcl_EvalObjEx will delete the * object when it decrements its refcount after eval'ing it. * * TIP #280. Make invoking context available to eval'd script, done * with the default values. */ objPtr = Tcl_ConcatObj(objc-1, objv+1); } TclNRAddCallback(interp, EvalCmdErrMsg, NULL, NULL, NULL, NULL); return TclNREvalObjEx(interp, objPtr, 0, invoker, word); } /* *---------------------------------------------------------------------- * * Tcl_ExitObjCmd -- * * This procedure is invoked to process the "exit" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_ExitObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_WideInt value; if ((objc != 1) && (objc != 2)) { Tcl_WrongNumArgs(interp, 1, objv, "?returnCode?"); return TCL_ERROR; } if (objc == 1) { value = 0; } else if (TclGetWideBitsFromObj(interp, objv[1], &value) != TCL_OK) { return TCL_ERROR; } Tcl_Exit((int)value); return TCL_OK; /* Better not ever reach this! */ } /* *---------------------------------------------------------------------- * * Tcl_ExprObjCmd -- * * This object-based procedure is invoked to process the "expr" Tcl * command. See the user documentation for details on what it does. * * With the bytecode compiler, this procedure is called in two * circumstances: 1) to execute expr commands that are too complicated or * too unsafe to try compiling directly into an inline sequence of * instructions, and 2) to execute commands where the command name is * computed at runtime and is "expr" or the name to which "expr" was * renamed (e.g., "set z expr; $z 2+3") * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_ExprObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRExprObjCmd, clientData, objc, objv); } int TclNRExprObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *resultPtr, *objPtr; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "arg ?arg ...?"); return TCL_ERROR; } TclNewObj(resultPtr); Tcl_IncrRefCount(resultPtr); if (objc == 2) { objPtr = objv[1]; TclNRAddCallback(interp, ExprCallback, resultPtr, NULL, NULL, NULL); } else { objPtr = Tcl_ConcatObj(objc-1, objv+1); TclNRAddCallback(interp, ExprCallback, resultPtr, objPtr, NULL, NULL); } return Tcl_NRExprObj(interp, objPtr, resultPtr); } static int ExprCallback( void *data[], Tcl_Interp *interp, int result) { Tcl_Obj *resultPtr = (Tcl_Obj *)data[0]; Tcl_Obj *objPtr = (Tcl_Obj *)data[1]; if (objPtr != NULL) { Tcl_DecrRefCount(objPtr); } if (result == TCL_OK) { Tcl_SetObjResult(interp, resultPtr); } Tcl_DecrRefCount(resultPtr); return result; } /* *---------------------------------------------------------------------- * * TclInitFileCmd -- * * This function builds the "file" Tcl command ensemble. See the user * documentation for details on what that ensemble does. * * PLEASE NOTE THAT THIS FAILS WITH FILENAMES AND PATHS WITH EMBEDDED * NULLS. With the object-based Tcl_FS APIs, the above NOTE may no longer * be true. In any case this assertion should be tested. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ Tcl_Command TclInitFileCmd( Tcl_Interp *interp) { /* * Note that most subcommands are unsafe because either they manipulate * the native filesystem or because they reveal information about the * native filesystem. */ static const EnsembleImplMap initMap[] = { {"atime", FileAttrAccessTimeCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 1}, {"attributes", TclFileAttrsCmd, NULL, NULL, NULL, 1}, {"channels", TclChannelNamesCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"copy", TclFileCopyCmd, NULL, NULL, NULL, 1}, {"delete", TclFileDeleteCmd, TclCompileBasicMin0ArgCmd, NULL, NULL, 1}, {"dirname", PathDirNameCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"executable", FileAttrIsExecutableCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"exists", FileAttrIsExistingCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"extension", PathExtensionCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"home", TclFileHomeCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 1}, {"isdirectory", FileAttrIsDirectoryCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"isfile", FileAttrIsFileCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"join", PathJoinCmd, TclCompileBasicMin1ArgCmd, NULL, NULL, 0}, {"link", TclFileLinkCmd, TclCompileBasic1To3ArgCmd, NULL, NULL, 1}, {"lstat", FileAttrLinkStatCmd, TclCompileBasic2ArgCmd, NULL, NULL, 1}, {"mtime", FileAttrModifyTimeCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 1}, {"mkdir", TclFileMakeDirsCmd, TclCompileBasicMin0ArgCmd, NULL, NULL, 1}, {"nativename", PathNativeNameCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"normalize", PathNormalizeCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"owned", FileAttrIsOwnedCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"pathtype", PathTypeCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"readable", FileAttrIsReadableCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"readlink", TclFileReadLinkCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"rename", TclFileRenameCmd, NULL, NULL, NULL, 1}, {"rootname", PathRootNameCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"separator", FilesystemSeparatorCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"size", FileAttrSizeCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"split", PathSplitCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"stat", FileAttrStatCmd, TclCompileBasic2ArgCmd, NULL, NULL, 1}, {"system", PathFilesystemCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"tail", PathTailCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"tempdir", TclFileTempDirCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 1}, {"tempfile", TclFileTemporaryCmd, TclCompileBasic0To2ArgCmd, NULL, NULL, 1}, {"tildeexpand", TclFileTildeExpandCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"type", FileAttrTypeCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"volumes", FilesystemVolumesCmd, TclCompileBasic0ArgCmd, NULL, NULL, 1}, {"writable", FileAttrIsWritableCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {NULL, NULL, NULL, NULL, NULL, 0} }; return TclMakeEnsemble(interp, "file", initMap); } /* *---------------------------------------------------------------------- * * FileAttrAccessTimeCmd -- * * This function is invoked to process the "file atime" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * May update the access time on the file, if requested by the user. * *---------------------------------------------------------------------- */ static int FileAttrAccessTimeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_StatBuf buf; struct utimbuf tval; if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "name ?time?"); return TCL_ERROR; } if (GetStatBuf(interp, objv[1], Tcl_FSStat, &buf) != TCL_OK) { return TCL_ERROR; } #if defined(_WIN32) /* We use a value of 0 to indicate the access time not available */ if (Tcl_GetAccessTimeFromStat(&buf) == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not get access time for file \"%s\"", TclGetString(objv[1]))); return TCL_ERROR; } #endif if (objc == 3) { /* * Need separate variable for reading longs from an object on 64-bit * platforms. [Bug 698146] */ Tcl_WideInt newTime; if (TclGetWideIntFromObj(interp, objv[2], &newTime) != TCL_OK) { return TCL_ERROR; } tval.actime = newTime; tval.modtime = Tcl_GetModificationTimeFromStat(&buf); if (Tcl_FSUtime(objv[1], &tval) != 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not set access time for file \"%s\": %s", TclGetString(objv[1]), Tcl_PosixError(interp))); return TCL_ERROR; } /* * Do another stat to ensure that the we return the new recognized * atime - hopefully the same as the one we sent in. However, fs's * like FAT don't even know what atime is. */ if (GetStatBuf(interp, objv[1], Tcl_FSStat, &buf) != TCL_OK) { return TCL_ERROR; } } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(Tcl_GetAccessTimeFromStat(&buf))); return TCL_OK; } /* *---------------------------------------------------------------------- * * FileAttrModifyTimeCmd -- * * This function is invoked to process the "file mtime" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * May update the modification time on the file, if requested by the * user. * *---------------------------------------------------------------------- */ static int FileAttrModifyTimeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_StatBuf buf; struct utimbuf tval; if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "name ?time?"); return TCL_ERROR; } if (GetStatBuf(interp, objv[1], Tcl_FSStat, &buf) != TCL_OK) { return TCL_ERROR; } #if defined(_WIN32) /* We use a value of 0 to indicate the modification time not available */ if (Tcl_GetModificationTimeFromStat(&buf) == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not get modification time for file \"%s\"", TclGetString(objv[1]))); return TCL_ERROR; } #endif if (objc == 3) { /* * Need separate variable for reading longs from an object on 64-bit * platforms. [Bug 698146] */ Tcl_WideInt newTime; if (TclGetWideIntFromObj(interp, objv[2], &newTime) != TCL_OK) { return TCL_ERROR; } tval.actime = Tcl_GetAccessTimeFromStat(&buf); tval.modtime = newTime; if (Tcl_FSUtime(objv[1], &tval) != 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not set modification time for file \"%s\": %s", TclGetString(objv[1]), Tcl_PosixError(interp))); return TCL_ERROR; } /* * Do another stat to ensure that the we return the new recognized * mtime - hopefully the same as the one we sent in. */ if (GetStatBuf(interp, objv[1], Tcl_FSStat, &buf) != TCL_OK) { return TCL_ERROR; } } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(Tcl_GetModificationTimeFromStat(&buf))); return TCL_OK; } /* *---------------------------------------------------------------------- * * FileAttrLinkStatCmd -- * * This function is invoked to process the "file lstat" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Writes to an array named by the user. * *---------------------------------------------------------------------- */ static int FileAttrLinkStatCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_StatBuf buf; if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "name ?varName?"); return TCL_ERROR; } if (GetStatBuf(interp, objv[1], Tcl_FSLstat, &buf) != TCL_OK) { return TCL_ERROR; } if (objc == 2) { return StoreStatData(interp, NULL, &buf); } else { return StoreStatData(interp, objv[2], &buf); } } /* *---------------------------------------------------------------------- * * FileAttrStatCmd -- * * This function is invoked to process the "file stat" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Writes to an array named by the user. * *---------------------------------------------------------------------- */ static int FileAttrStatCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_StatBuf buf; if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "name ?varName?"); return TCL_ERROR; } if (GetStatBuf(interp, objv[1], Tcl_FSStat, &buf) != TCL_OK) { return TCL_ERROR; } if (objc == 2) { return StoreStatData(interp, NULL, &buf); } else { return StoreStatData(interp, objv[2], &buf); } } /* *---------------------------------------------------------------------- * * FileAttrTypeCmd -- * * This function is invoked to process the "file type" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FileAttrTypeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_StatBuf buf; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } if (GetStatBuf(interp, objv[1], Tcl_FSLstat, &buf) != TCL_OK) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewStringObj( GetTypeFromMode((unsigned short) buf.st_mode), -1)); return TCL_OK; } /* *---------------------------------------------------------------------- * * FileAttrSizeCmd -- * * This function is invoked to process the "file size" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FileAttrSizeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_StatBuf buf; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } if (GetStatBuf(interp, objv[1], Tcl_FSStat, &buf) != TCL_OK) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(buf.st_size)); return TCL_OK; } /* *---------------------------------------------------------------------- * * FileAttrIsDirectoryCmd -- * * This function is invoked to process the "file isdirectory" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FileAttrIsDirectoryCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_StatBuf buf; int value = 0; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } if (GetStatBuf(NULL, objv[1], Tcl_FSStat, &buf) == TCL_OK) { value = S_ISDIR(buf.st_mode); } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value)); return TCL_OK; } /* *---------------------------------------------------------------------- * * FileAttrIsExecutableCmd -- * * This function is invoked to process the "file executable" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FileAttrIsExecutableCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } return CheckAccess(interp, objv[1], X_OK); } /* *---------------------------------------------------------------------- * * FileAttrIsExistingCmd -- * * This function is invoked to process the "file exists" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FileAttrIsExistingCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } return CheckAccess(interp, objv[1], F_OK); } /* *---------------------------------------------------------------------- * * FileAttrIsFileCmd -- * * This function is invoked to process the "file isfile" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FileAttrIsFileCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_StatBuf buf; int value = 0; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } if (GetStatBuf(NULL, objv[1], Tcl_FSStat, &buf) == TCL_OK) { value = S_ISREG(buf.st_mode); } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value)); return TCL_OK; } /* *---------------------------------------------------------------------- * * FileAttrIsOwnedCmd -- * * This function is invoked to process the "file owned" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FileAttrIsOwnedCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { #ifdef __CYGWIN__ #define geteuid() (short)(geteuid)() #endif #if !defined(_WIN32) Tcl_StatBuf buf; #endif int value = 0; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } Tcl_Obj *normPathPtr = Tcl_FSGetNormalizedPath(interp, objv[1]); /* Note normPathPtr owned by Tcl, no need to free it */ if (normPathPtr) { if (TclIsZipfsPath(TclGetString(normPathPtr))) { return CheckAccess(interp, objv[1], F_OK); } /* Not zipfs, try native. */ } /* * Note use objv[1] below, NOT normPathPtr even if not NULL because * for native paths we may not want links to be resolved. */ #if defined(_WIN32) value = TclWinFileOwned(objv[1]); #else if (GetStatBuf(NULL, objv[1], Tcl_FSStat, &buf) == TCL_OK) { value = (geteuid() == buf.st_uid); } #endif Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value)); return TCL_OK; } /* *---------------------------------------------------------------------- * * FileAttrIsReadableCmd -- * * This function is invoked to process the "file readable" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FileAttrIsReadableCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } return CheckAccess(interp, objv[1], R_OK); } /* *---------------------------------------------------------------------- * * FileAttrIsWritableCmd -- * * This function is invoked to process the "file writable" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FileAttrIsWritableCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } return CheckAccess(interp, objv[1], W_OK); } /* *---------------------------------------------------------------------- * * PathDirNameCmd -- * * This function is invoked to process the "file dirname" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PathDirNameCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *dirPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } dirPtr = TclPathPart(interp, objv[1], TCL_PATH_DIRNAME); if (dirPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, dirPtr); Tcl_DecrRefCount(dirPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * PathExtensionCmd -- * * This function is invoked to process the "file extension" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PathExtensionCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *dirPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } dirPtr = TclPathPart(interp, objv[1], TCL_PATH_EXTENSION); if (dirPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, dirPtr); Tcl_DecrRefCount(dirPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * PathRootNameCmd -- * * This function is invoked to process the "file root" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PathRootNameCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *dirPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } dirPtr = TclPathPart(interp, objv[1], TCL_PATH_ROOT); if (dirPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, dirPtr); Tcl_DecrRefCount(dirPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * PathTailCmd -- * * This function is invoked to process the "file tail" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PathTailCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *dirPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } dirPtr = TclPathPart(interp, objv[1], TCL_PATH_TAIL); if (dirPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, dirPtr); Tcl_DecrRefCount(dirPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * PathFilesystemCmd -- * * This function is invoked to process the "file system" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PathFilesystemCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *fsInfo; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } fsInfo = Tcl_FSFileSystemInfo(objv[1]); if (fsInfo == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("unrecognised path", -1)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "FILESYSTEM", TclGetString(objv[1]), (char *)NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, fsInfo); return TCL_OK; } /* *---------------------------------------------------------------------- * * PathJoinCmd -- * * This function is invoked to process the "file join" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PathJoinCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "name ?name ...?"); return TCL_ERROR; } Tcl_SetObjResult(interp, TclJoinPath(objc - 1, objv + 1, 0)); return TCL_OK; } /* *---------------------------------------------------------------------- * * PathNativeNameCmd -- * * This function is invoked to process the "file nativename" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PathNativeNameCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_DString ds; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } if (Tcl_TranslateFileName(interp, TclGetString(objv[1]), &ds) == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_DStringToObj(&ds)); return TCL_OK; } /* *---------------------------------------------------------------------- * * PathNormalizeCmd -- * * This function is invoked to process the "file normalize" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PathNormalizeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *fileName; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } fileName = Tcl_FSGetNormalizedPath(interp, objv[1]); if (fileName == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, fileName); return TCL_OK; } /* *---------------------------------------------------------------------- * * PathSplitCmd -- * * This function is invoked to process the "file split" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PathSplitCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *res; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } res = Tcl_FSSplitPath(objv[1], (Tcl_Size *)NULL); if (res == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read \"%s\": no such file or directory", TclGetString(objv[1]))); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "PATHSPLIT", "NONESUCH", (char *)NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, res); return TCL_OK; } /* *---------------------------------------------------------------------- * * PathTypeCmd -- * * This function is invoked to process the "file pathtype" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PathTypeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *typeName; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } switch (Tcl_FSGetPathType(objv[1])) { case TCL_PATH_ABSOLUTE: TclNewLiteralStringObj(typeName, "absolute"); break; case TCL_PATH_RELATIVE: TclNewLiteralStringObj(typeName, "relative"); break; case TCL_PATH_VOLUME_RELATIVE: TclNewLiteralStringObj(typeName, "volumerelative"); break; default: /* Should be unreachable */ return TCL_OK; } Tcl_SetObjResult(interp, typeName); return TCL_OK; } /* *---------------------------------------------------------------------- * * FilesystemSeparatorCmd -- * * This function is invoked to process the "file separator" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FilesystemSeparatorCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc < 1 || objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?name?"); return TCL_ERROR; } if (objc == 1) { const char *separator = NULL; switch (tclPlatform) { case TCL_PLATFORM_UNIX: separator = "/"; break; case TCL_PLATFORM_WINDOWS: separator = "\\"; break; } Tcl_SetObjResult(interp, Tcl_NewStringObj(separator, 1)); } else { Tcl_Obj *separatorObj = Tcl_FSPathSeparator(objv[1]); if (separatorObj == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "unrecognised path", -1)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "FILESYSTEM", TclGetString(objv[1]), (char *)NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, separatorObj); } return TCL_OK; } /* *---------------------------------------------------------------------- * * FilesystemVolumesCmd -- * * This function is invoked to process the "file volumes" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FilesystemVolumesCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_FSListVolumes()); return TCL_OK; } /* *--------------------------------------------------------------------------- * * CheckAccess -- * * Utility procedure used by Tcl_FileObjCmd() to query file attributes * available through the access() system call. * * Results: * Always returns TCL_OK. Sets interp's result to boolean true or false * depending on whether the file has the specified attribute. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static int CheckAccess( Tcl_Interp *interp, /* Interp for status return. Must not be * NULL. */ Tcl_Obj *pathPtr, /* Name of file to check. */ int mode) /* Attribute to check; passed as argument to * access(). */ { int value; Tcl_DString ds; if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) { value = 0; } else if (Tcl_UtfToExternalDStringEx(NULL, TCLFSENCODING, TclGetString(pathPtr), TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { value = 0; Tcl_DStringFree(&ds); } else { Tcl_DStringFree(&ds); value = (Tcl_FSAccess(pathPtr, mode) == 0); } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value)); return TCL_OK; } /* *--------------------------------------------------------------------------- * * GetStatBuf -- * * Utility procedure used by Tcl_FileObjCmd() to query file attributes * available through the stat() or lstat() system call. * * Results: * The return value is TCL_OK if the specified file exists and can be * stat'ed, TCL_ERROR otherwise. If TCL_ERROR is returned, an error * message is left in interp's result. If TCL_OK is returned, *statPtr is * filled with information about the specified file. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static int GetStatBuf( Tcl_Interp *interp, /* Interp for error return. May be NULL. */ Tcl_Obj *pathPtr, /* Path name to examine. */ Tcl_FSStatProc *statProc, /* Either stat() or lstat() depending on * desired behavior. */ Tcl_StatBuf *statPtr) /* Filled with info about file obtained by * calling (*statProc)(). */ { int status; Tcl_DString ds; if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) { return TCL_ERROR; } if (Tcl_UtfToExternalDStringEx(NULL, TCLFSENCODING, TclGetString(pathPtr), TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { status = -1; } else { status = statProc(pathPtr, statPtr); } Tcl_DStringFree(&ds); if (status < 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); } return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * StoreStatData -- * * This is a utility procedure that breaks out the fields of a "stat" * structure and stores them in textual form into the elements of an * associative array (if given) or returns a dictionary. * * Results: * Returns a standard Tcl return value. If an error occurs then a message * is left in interp's result. * * Side effects: * Elements of the associative array given by "varName" are modified. * *---------------------------------------------------------------------- */ static int StoreStatData( Tcl_Interp *interp, /* Interpreter for error reports. */ Tcl_Obj *varName, /* Name of associative array variable in which * to store stat results. */ Tcl_StatBuf *statPtr) /* Pointer to buffer containing stat data to * store in varName. */ { Tcl_Obj *field, *value, *result; unsigned short mode; if (varName == NULL) { TclNewObj(result); Tcl_IncrRefCount(result); #define DOBJPUT(key, objValue) \ Tcl_DictObjPut(NULL, result, \ Tcl_NewStringObj((key), -1), \ (objValue)); DOBJPUT("dev", Tcl_NewWideIntObj((long)statPtr->st_dev)); DOBJPUT("ino", Tcl_NewWideIntObj((Tcl_WideInt)statPtr->st_ino)); DOBJPUT("nlink", Tcl_NewWideIntObj((long)statPtr->st_nlink)); DOBJPUT("uid", Tcl_NewWideIntObj((long)statPtr->st_uid)); DOBJPUT("gid", Tcl_NewWideIntObj((long)statPtr->st_gid)); DOBJPUT("size", Tcl_NewWideIntObj((Tcl_WideInt)statPtr->st_size)); #ifdef HAVE_STRUCT_STAT_ST_BLOCKS DOBJPUT("blocks", Tcl_NewWideIntObj((Tcl_WideInt)statPtr->st_blocks)); #endif #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE DOBJPUT("blksize", Tcl_NewWideIntObj((long)statPtr->st_blksize)); #endif DOBJPUT("atime", Tcl_NewWideIntObj(Tcl_GetAccessTimeFromStat(statPtr))); DOBJPUT("mtime", Tcl_NewWideIntObj(Tcl_GetModificationTimeFromStat(statPtr))); DOBJPUT("ctime", Tcl_NewWideIntObj(Tcl_GetChangeTimeFromStat(statPtr))); mode = (unsigned short) statPtr->st_mode; DOBJPUT("mode", Tcl_NewWideIntObj(mode)); DOBJPUT("type", Tcl_NewStringObj(GetTypeFromMode(mode), -1)); #undef DOBJPUT Tcl_SetObjResult(interp, result); Tcl_DecrRefCount(result); return TCL_OK; } /* * Might be a better idea to call Tcl_SetVar2Ex() instead, except we want * to have an object (i.e. possibly cached) array variable name but a * string element name, so no API exists. Messy. */ #define STORE_ARY(fieldName, object) \ TclNewLiteralStringObj(field, fieldName); \ Tcl_IncrRefCount(field); \ value = (object); \ if (Tcl_ObjSetVar2(interp,varName,field,value,TCL_LEAVE_ERR_MSG)==NULL) { \ TclDecrRefCount(field); \ return TCL_ERROR; \ } \ TclDecrRefCount(field); /* * Watch out porters; the inode is meant to be an *unsigned* value, so the * cast might fail when there isn't a real arithmetic 'long long' type... */ STORE_ARY("dev", Tcl_NewWideIntObj((long)statPtr->st_dev)); STORE_ARY("ino", Tcl_NewWideIntObj(statPtr->st_ino)); STORE_ARY("nlink", Tcl_NewWideIntObj((long)statPtr->st_nlink)); STORE_ARY("uid", Tcl_NewWideIntObj((long)statPtr->st_uid)); STORE_ARY("gid", Tcl_NewWideIntObj((long)statPtr->st_gid)); STORE_ARY("size", Tcl_NewWideIntObj(statPtr->st_size)); #ifdef HAVE_STRUCT_STAT_ST_BLOCKS STORE_ARY("blocks", Tcl_NewWideIntObj(statPtr->st_blocks)); #endif #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE STORE_ARY("blksize", Tcl_NewWideIntObj((long)statPtr->st_blksize)); #endif #ifdef HAVE_STRUCT_STAT_ST_RDEV if (S_ISCHR(statPtr->st_mode) || S_ISBLK(statPtr->st_mode)) { STORE_ARY("rdev", Tcl_NewWideIntObj((long) statPtr->st_rdev)); } #endif STORE_ARY("atime", Tcl_NewWideIntObj(Tcl_GetAccessTimeFromStat(statPtr))); STORE_ARY("mtime", Tcl_NewWideIntObj( Tcl_GetModificationTimeFromStat(statPtr))); STORE_ARY("ctime", Tcl_NewWideIntObj(Tcl_GetChangeTimeFromStat(statPtr))); mode = (unsigned short) statPtr->st_mode; STORE_ARY("mode", Tcl_NewWideIntObj(mode)); STORE_ARY("type", Tcl_NewStringObj(GetTypeFromMode(mode), -1)); #undef STORE_ARY return TCL_OK; } /* *---------------------------------------------------------------------- * * GetTypeFromMode -- * * Given a mode word, returns a string identifying the type of a file. * * Results: * A static text string giving the file type from mode. * * Side effects: * None. * *---------------------------------------------------------------------- */ static const char * GetTypeFromMode( int mode) { if (S_ISREG(mode)) { return "file"; } else if (S_ISDIR(mode)) { return "directory"; } else if (S_ISCHR(mode)) { return "characterSpecial"; } else if (S_ISBLK(mode)) { return "blockSpecial"; } else if (S_ISFIFO(mode)) { return "fifo"; #ifdef S_ISLNK } else if (S_ISLNK(mode)) { return "link"; #endif #ifdef S_ISSOCK } else if (S_ISSOCK(mode)) { return "socket"; #endif } return "unknown"; } /* *---------------------------------------------------------------------- * * Tcl_ForObjCmd -- * * This procedure is invoked to process the "for" Tcl command. See the * user documentation for details on what it does. * * With the bytecode compiler, this procedure is only called when a * command name is computed at runtime, and is "for" or the name to which * "for" was renamed: e.g., * "set z for; $z {set i 0} {$i<100} {incr i} {puts $i}" * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * * Notes: * This command is split into a lot of pieces so that it can avoid doing * reentrant TEBC calls. This makes things rather hard to follow, but * here's the plan: * * NR: ---------------_\ * Direct: Tcl_ForObjCmd -> TclNRForObjCmd * | * ForSetupCallback * | * [while] ------------> TclNRForIterCallback <---------. * | | * ForCondCallback | * | | * ForNextCallback ------------| * | | * ForPostNextCallback | * |____________________| * *---------------------------------------------------------------------- */ int Tcl_ForObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRForObjCmd, clientData, objc, objv); } int TclNRForObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; ForIterData *iterPtr; if (objc != 5) { Tcl_WrongNumArgs(interp, 1, objv, "start test next command"); return TCL_ERROR; } TclSmallAllocEx(interp, sizeof(ForIterData), iterPtr); iterPtr->cond = objv[2]; iterPtr->body = objv[4]; iterPtr->next = objv[3]; iterPtr->msg = "\n (\"for\" body line %d)"; iterPtr->word = 4; TclNRAddCallback(interp, ForSetupCallback, iterPtr, NULL, NULL, NULL); /* * TIP #280. Make invoking context available to initial script. */ return TclNREvalObjEx(interp, objv[1], 0, iPtr->cmdFramePtr, 1); } static int ForSetupCallback( void *data[], Tcl_Interp *interp, int result) { ForIterData *iterPtr = (ForIterData *)data[0]; if (result != TCL_OK) { if (result == TCL_ERROR) { Tcl_AddErrorInfo(interp, "\n (\"for\" initial command)"); } TclSmallFreeEx(interp, iterPtr); return result; } TclNRAddCallback(interp, TclNRForIterCallback, iterPtr, NULL, NULL, NULL); return TCL_OK; } int TclNRForIterCallback( void *data[], Tcl_Interp *interp, int result) { ForIterData *iterPtr = (ForIterData *)data[0]; Tcl_Obj *boolObj; switch (result) { case TCL_OK: case TCL_CONTINUE: /* * We need to reset the result before evaluating the expression. * Otherwise, any error message will be appended to the result of the * last evaluation. */ Tcl_ResetResult(interp); TclNewObj(boolObj); TclNRAddCallback(interp, ForCondCallback, iterPtr, boolObj, NULL, NULL); return Tcl_NRExprObj(interp, iterPtr->cond, boolObj); case TCL_BREAK: result = TCL_OK; Tcl_ResetResult(interp); break; case TCL_ERROR: Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(iterPtr->msg, Tcl_GetErrorLine(interp))); } TclSmallFreeEx(interp, iterPtr); return result; } static int ForCondCallback( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; ForIterData *iterPtr = (ForIterData *)data[0]; Tcl_Obj *boolObj = (Tcl_Obj *)data[1]; int value; if (result != TCL_OK) { Tcl_DecrRefCount(boolObj); TclSmallFreeEx(interp, iterPtr); return result; } else if (Tcl_GetBooleanFromObj(interp, boolObj, &value) != TCL_OK) { Tcl_DecrRefCount(boolObj); TclSmallFreeEx(interp, iterPtr); return TCL_ERROR; } Tcl_DecrRefCount(boolObj); if (value) { /* TIP #280. */ if (iterPtr->next) { TclNRAddCallback(interp, ForNextCallback, iterPtr, NULL, NULL, NULL); } else { TclNRAddCallback(interp, TclNRForIterCallback, iterPtr, NULL, NULL, NULL); } return TclNREvalObjEx(interp, iterPtr->body, 0, iPtr->cmdFramePtr, iterPtr->word); } TclSmallFreeEx(interp, iterPtr); return result; } static int ForNextCallback( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; ForIterData *iterPtr = (ForIterData *)data[0]; Tcl_Obj *next = iterPtr->next; if ((result == TCL_OK) || (result == TCL_CONTINUE)) { TclNRAddCallback(interp, ForPostNextCallback, iterPtr, NULL, NULL, NULL); /* * TIP #280. Make invoking context available to next script. */ return TclNREvalObjEx(interp, next, 0, iPtr->cmdFramePtr, 3); } TclNRAddCallback(interp, TclNRForIterCallback, iterPtr, NULL, NULL, NULL); return result; } static int ForPostNextCallback( void *data[], Tcl_Interp *interp, int result) { ForIterData *iterPtr = (ForIterData *)data[0]; if ((result != TCL_BREAK) && (result != TCL_OK)) { if (result == TCL_ERROR) { Tcl_AddErrorInfo(interp, "\n (\"for\" loop-end command)"); TclSmallFreeEx(interp, iterPtr); } return result; } TclNRAddCallback(interp, TclNRForIterCallback, iterPtr, NULL, NULL, NULL); return result; } /* *---------------------------------------------------------------------- * * Tcl_ForeachObjCmd, TclNRForeachCmd, EachloopCmd -- * * This object-based procedure is invoked to process the "foreach" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_ForeachObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRForeachCmd, clientData, objc, objv); } int TclNRForeachCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { return EachloopCmd(interp, TCL_EACH_KEEP_NONE, objc, objv); } int Tcl_LmapObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRLmapCmd, clientData, objc, objv); } int TclNRLmapCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { return EachloopCmd(interp, TCL_EACH_COLLECT, objc, objv); } static int EachloopCmd( Tcl_Interp *interp, /* Our context for variables and script * evaluation. */ int collect, /* Select collecting or accumulating mode * (TCL_EACH_*) */ int objc, /* The arguments being passed in... */ Tcl_Obj *const objv[]) { int numLists = (objc-2) / 2; struct ForeachState *statePtr; int i, result; Tcl_Size j; if (objc < 4 || (objc%2 != 0)) { Tcl_WrongNumArgs(interp, 1, objv, "varList list ?varList list ...? command"); return TCL_ERROR; } /* * Manage numList parallel value lists. * statePtr->argvList[i] is a value list counted by statePtr->argcList[i]; * statePtr->varvList[i] is the list of variables associated with the * value list; * statePtr->varcList[i] is the number of variables associated with the * value list; * statePtr->index[i] is the current pointer into the value list * statePtr->argvList[i]. * * The setting up of all of these pointers is moderately messy, but allows * the rest of this code to be simple and for us to use a single memory * allocation for better performance. */ statePtr = (struct ForeachState *)TclStackAlloc(interp, sizeof(struct ForeachState) + 3 * numLists * sizeof(Tcl_Size) + 2 * numLists * (sizeof(Tcl_Obj **) + sizeof(Tcl_Obj *))); memset(statePtr, 0, sizeof(struct ForeachState) + 3 * numLists * sizeof(Tcl_Size) + 2 * numLists * (sizeof(Tcl_Obj **) + sizeof(Tcl_Obj *))); statePtr->varvList = (Tcl_Obj ***) (statePtr + 1); statePtr->argvList = statePtr->varvList + numLists; statePtr->vCopyList = (Tcl_Obj **) (statePtr->argvList + numLists); statePtr->aCopyList = statePtr->vCopyList + numLists; statePtr->index = (Tcl_Size *) (statePtr->aCopyList + numLists); statePtr->varcList = statePtr->index + numLists; statePtr->argcList = statePtr->varcList + numLists; statePtr->numLists = numLists; statePtr->bodyPtr = objv[objc - 1]; statePtr->bodyIdx = objc - 1; if (collect == TCL_EACH_COLLECT) { statePtr->resultList = Tcl_NewListObj(0, NULL); } else { statePtr->resultList = NULL; } /* * Break up the value lists and variable lists into elements. */ for (i=0 ; ivCopyList[i] = TclListObjCopy(interp, objv[1+i*2]); if (statePtr->vCopyList[i] == NULL) { result = TCL_ERROR; goto done; } result = TclListObjLength(interp, statePtr->vCopyList[i], &statePtr->varcList[i]); if (result != TCL_OK) { result = TCL_ERROR; goto done; } if (statePtr->varcList[i] < 1) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s varlist is empty", (statePtr->resultList != NULL ? "lmap" : "foreach"))); Tcl_SetErrorCode(interp, "TCL", "OPERATION", (statePtr->resultList != NULL ? "LMAP" : "FOREACH"), "NEEDVARS", (char *)NULL); result = TCL_ERROR; goto done; } TclListObjGetElements(NULL, statePtr->vCopyList[i], &statePtr->varcList[i], &statePtr->varvList[i]); /* Values */ if (TclObjTypeHasProc(objv[2+i*2],indexProc)) { /* Special case for AbstractList */ statePtr->aCopyList[i] = Tcl_DuplicateObj(objv[2+i*2]); if (statePtr->aCopyList[i] == NULL) { result = TCL_ERROR; goto done; } /* Don't compute values here, wait until the last moment */ statePtr->argcList[i] = TclObjTypeLength(statePtr->aCopyList[i]); } else { /* List values */ statePtr->aCopyList[i] = TclListObjCopy(interp, objv[2+i*2]); if (statePtr->aCopyList[i] == NULL) { result = TCL_ERROR; goto done; } result = TclListObjGetElements(interp, statePtr->aCopyList[i], &statePtr->argcList[i], &statePtr->argvList[i]); if (result != TCL_OK) { goto done; } } /* account for variable <> value mismatch */ j = statePtr->argcList[i] / statePtr->varcList[i]; if ((statePtr->argcList[i] % statePtr->varcList[i]) != 0) { j++; } if (j > statePtr->maxj) { statePtr->maxj = j; } } /* * If there is any work to do, assign the variables and set things going * non-recursively. */ if (statePtr->maxj > 0) { result = ForeachAssignments(interp, statePtr); if (result == TCL_ERROR) { goto done; } TclNRAddCallback(interp, ForeachLoopStep, statePtr, NULL, NULL, NULL); return TclNREvalObjEx(interp, objv[objc-1], 0, ((Interp *) interp)->cmdFramePtr, objc-1); } /* * This cleanup stage is only used when an error occurs during setup or if * there is no work to do. */ result = TCL_OK; done: ForeachCleanup(interp, statePtr); return result; } /* * Post-body processing handler. */ static int ForeachLoopStep( void *data[], Tcl_Interp *interp, int result) { struct ForeachState *statePtr = (struct ForeachState *)data[0]; /* * Process the result code from this run of the [foreach] body. Note that * this switch uses fallthroughs in several places. Maintainer aware! */ switch (result) { case TCL_CONTINUE: result = TCL_OK; break; case TCL_OK: if (statePtr->resultList != NULL) { result = Tcl_ListObjAppendElement( interp, statePtr->resultList, Tcl_GetObjResult(interp)); if (result != TCL_OK) { /* e.g. memory alloc failure on big data tests */ goto done; } } break; case TCL_BREAK: result = TCL_OK; goto finish; case TCL_ERROR: Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (\"%s\" body line %d)", (statePtr->resultList != NULL ? "lmap" : "foreach"), Tcl_GetErrorLine(interp))); default: goto done; } /* * Test if there is work still to be done. If so, do the next round of * variable assignments, reschedule ourselves and run the body again. */ if (statePtr->maxj > ++statePtr->j) { result = ForeachAssignments(interp, statePtr); if (result == TCL_ERROR) { goto done; } TclNRAddCallback(interp, ForeachLoopStep, statePtr, NULL, NULL, NULL); return TclNREvalObjEx(interp, statePtr->bodyPtr, 0, ((Interp *) interp)->cmdFramePtr, statePtr->bodyIdx); } /* * We're done. Tidy up our work space and finish off. */ finish: if (statePtr->resultList == NULL) { Tcl_ResetResult(interp); } else { Tcl_SetObjResult(interp, statePtr->resultList); statePtr->resultList = NULL; /* Don't clean it up */ } done: ForeachCleanup(interp, statePtr); return result; } /* * Factored out code to do the assignments in [foreach]. */ static inline int ForeachAssignments( Tcl_Interp *interp, struct ForeachState *statePtr) { int i; Tcl_Size v, k; Tcl_Obj *valuePtr, *varValuePtr; for (i=0 ; inumLists ; i++) { int isAbstractList = TclObjTypeHasProc(statePtr->aCopyList[i],indexProc) != NULL; for (v=0 ; vvarcList[i] ; v++) { k = statePtr->index[i]++; if (k < statePtr->argcList[i]) { if (isAbstractList) { if (TclObjTypeIndex(interp, statePtr->aCopyList[i], k, &valuePtr) != TCL_OK) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (setting %s loop variable \"%s\")", (statePtr->resultList != NULL ? "lmap" : "foreach"), TclGetString(statePtr->varvList[i][v]))); return TCL_ERROR; } } else { valuePtr = statePtr->argvList[i][k]; } } else { TclNewObj(valuePtr); /* Empty string */ } varValuePtr = Tcl_ObjSetVar2(interp, statePtr->varvList[i][v], NULL, valuePtr, TCL_LEAVE_ERR_MSG); if (varValuePtr == NULL) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (setting %s loop variable \"%s\")", (statePtr->resultList != NULL ? "lmap" : "foreach"), TclGetString(statePtr->varvList[i][v]))); return TCL_ERROR; } } } return TCL_OK; } /* * Factored out code for cleaning up the state of the foreach. */ static inline void ForeachCleanup( Tcl_Interp *interp, struct ForeachState *statePtr) { int i; for (i=0 ; inumLists ; i++) { if (statePtr->vCopyList[i]) { TclDecrRefCount(statePtr->vCopyList[i]); } if (statePtr->aCopyList[i]) { TclDecrRefCount(statePtr->aCopyList[i]); } } if (statePtr->resultList != NULL) { TclDecrRefCount(statePtr->resultList); } TclStackFree(interp, statePtr); } /* *---------------------------------------------------------------------- * * Tcl_FormatObjCmd -- * * This procedure is invoked to process the "format" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_FormatObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *resultPtr; /* Where result is stored finally. */ if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "formatString ?arg ...?"); return TCL_ERROR; } resultPtr = Tcl_Format(interp, TclGetString(objv[1]), objc-2, objv+2); if (resultPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, resultPtr); return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclCmdIL.c0000644000175000017500000043757714726623136015020 0ustar sergeisergei/* * tclCmdIL.c -- * * This file contains the top-level command routines for most of the Tcl * built-in commands whose names begin with the letters I through L. It * contains only commands in the generic core (i.e., those that don't * depend much upon UNIX facilities). * * Copyright © 1987-1993 The Regents of the University of California. * Copyright © 1993-1997 Lucent Technologies. * Copyright © 1994-1997 Sun Microsystems, Inc. * Copyright © 1998-1999 Scriptics Corporation. * Copyright © 2001 Kevin B. Kenny. All rights reserved. * Copyright © 2005 Donal K. Fellows. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclRegexp.h" #include "tclTomMath.h" #include #include /* * During execution of the "lsort" command, structures of the following type * are used to arrange the objects being sorted into a collection of linked * lists. */ typedef struct SortElement { union { /* The value that we sorting by. */ const char *strValuePtr; Tcl_WideInt wideValue; double doubleValue; Tcl_Obj *objValuePtr; } collationKey; union { /* Object being sorted, or its index. */ Tcl_Obj *objPtr; size_t index; } payload; struct SortElement *nextPtr;/* Next element in the list, or NULL for end * of list. */ } SortElement; /* * These function pointer types are used with the "lsearch" and "lsort" * commands to facilitate the "-nocase" option. */ typedef int (*SortStrCmpFn_t) (const char *, const char *); typedef int (*SortMemCmpFn_t) (const void *, const void *, Tcl_Size); /* * The "lsort" command needs to pass certain information down to the function * that compares two list elements, and the comparison function needs to pass * success or failure information back up to the top-level "lsort" command. * The following structure is used to pass this information. */ typedef struct { int isIncreasing; /* Nonzero means sort in increasing order. */ int sortMode; /* The sort mode. One of SORTMODE_* values * defined below. */ Tcl_Obj *compareCmdPtr; /* The Tcl comparison command when sortMode is * SORTMODE_COMMAND. Preinitialized to hold * base of command. */ int *indexv; /* If the -index option was specified, this * holds an encoding of the indexes contained * in the list supplied as an argument to * that option. * NULL if no indexes supplied, and points to * singleIndex field when only one * supplied. */ Tcl_Size indexc; /* Number of indexes in indexv array. */ int singleIndex; /* Static space for common index case. */ int unique; int numElements; Tcl_Interp *interp; /* The interpreter in which the sort is being * done. */ int resultCode; /* Completion code for the lsort command. If * an error occurs during the sort this is * changed from TCL_OK to TCL_ERROR. */ } SortInfo; /* * The "sortMode" field of the SortInfo structure can take on any of the * following values. */ #define SORTMODE_ASCII 0 #define SORTMODE_INTEGER 1 #define SORTMODE_REAL 2 #define SORTMODE_COMMAND 3 #define SORTMODE_DICTIONARY 4 #define SORTMODE_ASCII_NC 8 /* * Definitions for [lseq] command */ static const char *const seq_operations[] = { "..", "to", "count", "by", NULL }; typedef enum { LSEQ_DOTS, LSEQ_TO, LSEQ_COUNT, LSEQ_BY } SequenceOperators; typedef enum { NoneArg, NumericArg, RangeKeywordArg, ErrArg, LastArg = 8 } SequenceDecoded; /* * Forward declarations for procedures defined in this file: */ static int DictionaryCompare(const char *left, const char *right); static Tcl_NRPostProc IfConditionCallback; static Tcl_ObjCmdProc InfoArgsCmd; static Tcl_ObjCmdProc InfoBodyCmd; static Tcl_ObjCmdProc InfoCmdCountCmd; static Tcl_ObjCmdProc InfoCommandsCmd; static Tcl_ObjCmdProc InfoCompleteCmd; static Tcl_ObjCmdProc InfoDefaultCmd; /* TIP #348 - New 'info' subcommand 'errorstack' */ static Tcl_ObjCmdProc InfoErrorStackCmd; /* TIP #280 - New 'info' subcommand 'frame' */ static Tcl_ObjCmdProc InfoFrameCmd; static Tcl_ObjCmdProc InfoFunctionsCmd; static Tcl_ObjCmdProc InfoHostnameCmd; static Tcl_ObjCmdProc InfoLevelCmd; static Tcl_ObjCmdProc InfoLibraryCmd; static Tcl_ObjCmdProc InfoLoadedCmd; static Tcl_ObjCmdProc InfoNameOfExecutableCmd; static Tcl_ObjCmdProc InfoPatchLevelCmd; static Tcl_ObjCmdProc InfoProcsCmd; static Tcl_ObjCmdProc InfoScriptCmd; static Tcl_ObjCmdProc InfoSharedlibCmd; static Tcl_ObjCmdProc InfoCmdTypeCmd; static Tcl_ObjCmdProc InfoTclVersionCmd; static SortElement * MergeLists(SortElement *leftPtr, SortElement *rightPtr, SortInfo *infoPtr); static int SortCompare(SortElement *firstPtr, SortElement *second, SortInfo *infoPtr); static Tcl_Obj * SelectObjFromSublist(Tcl_Obj *firstPtr, SortInfo *infoPtr); /* * Array of values describing how to implement each standard subcommand of the * "info" command. */ static const EnsembleImplMap defaultInfoMap[] = { {"args", InfoArgsCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"body", InfoBodyCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"cmdcount", InfoCmdCountCmd, TclCompileBasic0ArgCmd, NULL, NULL, 0}, {"cmdtype", InfoCmdTypeCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"commands", InfoCommandsCmd, TclCompileInfoCommandsCmd, NULL, NULL, 0}, {"complete", InfoCompleteCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"constant", TclInfoConstantCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"consts", TclInfoConstsCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"coroutine", TclInfoCoroutineCmd, TclCompileInfoCoroutineCmd, NULL, NULL, 0}, {"default", InfoDefaultCmd, TclCompileBasic3ArgCmd, NULL, NULL, 0}, {"errorstack", InfoErrorStackCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"exists", TclInfoExistsCmd, TclCompileInfoExistsCmd, NULL, NULL, 0}, {"frame", InfoFrameCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"functions", InfoFunctionsCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"globals", TclInfoGlobalsCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"hostname", InfoHostnameCmd, TclCompileBasic0ArgCmd, NULL, NULL, 0}, {"level", InfoLevelCmd, TclCompileInfoLevelCmd, NULL, NULL, 0}, {"library", InfoLibraryCmd, TclCompileBasic0ArgCmd, NULL, NULL, 0}, {"loaded", InfoLoadedCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"locals", TclInfoLocalsCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"nameofexecutable", InfoNameOfExecutableCmd, TclCompileBasic0ArgCmd, NULL, NULL, 1}, {"patchlevel", InfoPatchLevelCmd, TclCompileBasic0ArgCmd, NULL, NULL, 0}, {"procs", InfoProcsCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"script", InfoScriptCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"sharedlibextension", InfoSharedlibCmd, TclCompileBasic0ArgCmd, NULL, NULL, 0}, {"tclversion", InfoTclVersionCmd, TclCompileBasic0ArgCmd, NULL, NULL, 0}, {"vars", TclInfoVarsCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {NULL, NULL, NULL, NULL, NULL, 0} }; /* *---------------------------------------------------------------------- * * Tcl_IfObjCmd -- * * This procedure is invoked to process the "if" Tcl command. See the * user documentation for details on what it does. * * With the bytecode compiler, this procedure is only called when a * command name is computed at runtime, and is "if" or the name to which * "if" was renamed: e.g., "set z if; $z 1 {puts foo}" * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_IfObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRIfObjCmd, clientData, objc, objv); } int TclNRIfObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *boolObj; if (objc <= 1) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "wrong # args: no expression after \"%s\" argument", TclGetString(objv[0]))); Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", (char *)NULL); return TCL_ERROR; } /* * At this point, objv[1] refers to the main expression to test. The * arguments after the expression must be "then" (optional) and a script * to execute if the expression is true. */ TclNewObj(boolObj); Tcl_NRAddCallback(interp, IfConditionCallback, INT2PTR(objc), (void *) objv, INT2PTR(1), boolObj); return Tcl_NRExprObj(interp, objv[1], boolObj); } static int IfConditionCallback( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; int objc = PTR2INT(data[0]); Tcl_Obj *const *objv = (Tcl_Obj *const *)data[1]; int i = PTR2INT(data[2]); Tcl_Obj *boolObj = (Tcl_Obj *)data[3]; int value, thenScriptIndex = 0; const char *clause; if (result != TCL_OK) { TclDecrRefCount(boolObj); return result; } if (Tcl_GetBooleanFromObj(interp, boolObj, &value) != TCL_OK) { TclDecrRefCount(boolObj); return TCL_ERROR; } TclDecrRefCount(boolObj); while (1) { i++; if (i >= objc) { goto missingScript; } clause = TclGetString(objv[i]); if ((i < objc) && (strcmp(clause, "then") == 0)) { i++; } if (i >= objc) { goto missingScript; } if (value) { thenScriptIndex = i; value = 0; } /* * The expression evaluated to false. Skip the command, then see if * there is an "else" or "elseif" clause. */ i++; if (i >= objc) { if (thenScriptIndex) { /* * TIP #280. Make invoking context available to branch. */ return TclNREvalObjEx(interp, objv[thenScriptIndex], 0, iPtr->cmdFramePtr, thenScriptIndex); } return TCL_OK; } clause = TclGetString(objv[i]); if ((clause[0] != 'e') || (strcmp(clause, "elseif") != 0)) { break; } i++; /* * At this point in the loop, objv and objc refer to an expression to * test, either for the main expression or an expression following an * "elseif". The arguments after the expression must be "then" * (optional) and a script to execute if the expression is true. */ if (i >= objc) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "wrong # args: no expression after \"%s\" argument", clause)); Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", (char *)NULL); return TCL_ERROR; } if (!thenScriptIndex) { TclNewObj(boolObj); Tcl_NRAddCallback(interp, IfConditionCallback, data[0], data[1], INT2PTR(i), boolObj); return Tcl_NRExprObj(interp, objv[i], boolObj); } } /* * Couldn't find a "then" or "elseif" clause to execute. Check now for an * "else" clause. We know that there's at least one more argument when we * get here. */ if (strcmp(clause, "else") == 0) { i++; if (i >= objc) { goto missingScript; } } if (i < objc - 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "wrong # args: extra words after \"else\" clause in \"if\" command", -1)); Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", (char *)NULL); return TCL_ERROR; } if (thenScriptIndex) { /* * TIP #280. Make invoking context available to branch/else. */ return TclNREvalObjEx(interp, objv[thenScriptIndex], 0, iPtr->cmdFramePtr, thenScriptIndex); } return TclNREvalObjEx(interp, objv[i], 0, iPtr->cmdFramePtr, i); missingScript: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "wrong # args: no script following \"%s\" argument", TclGetString(objv[i-1]))); Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_IncrObjCmd -- * * This procedure is invoked to process the "incr" Tcl command. See the * user documentation for details on what it does. * * With the bytecode compiler, this procedure is only called when a * command name is computed at runtime, and is "incr" or the name to * which "incr" was renamed: e.g., "set z incr; $z i -1" * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_IncrObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *newValuePtr, *incrPtr; if ((objc != 2) && (objc != 3)) { Tcl_WrongNumArgs(interp, 1, objv, "varName ?increment?"); return TCL_ERROR; } if (objc == 3) { incrPtr = objv[2]; } else { TclNewIntObj(incrPtr, 1); } Tcl_IncrRefCount(incrPtr); newValuePtr = TclIncrObjVar2(interp, objv[1], NULL, incrPtr, TCL_LEAVE_ERR_MSG); Tcl_DecrRefCount(incrPtr); if (newValuePtr == NULL) { return TCL_ERROR; } /* * Set the interpreter's object result to refer to the variable's new * value object. */ Tcl_SetObjResult(interp, newValuePtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclInitInfoCmd -- * * This function is called to create the "info" Tcl command. See the user * documentation for details on what it does. * * Results: * Handle for the info command, or NULL on failure. * * Side effects: * none * *---------------------------------------------------------------------- */ Tcl_Command TclInitInfoCmd( Tcl_Interp *interp) /* Current interpreter. */ { return TclMakeEnsemble(interp, "info", defaultInfoMap); } /* *---------------------------------------------------------------------- * * InfoArgsCmd -- * * Called to implement the "info args" command that returns the argument * list for a procedure. Handles the following syntax: * * info args procName * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoArgsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; const char *name; Proc *procPtr; CompiledLocal *localPtr; Tcl_Obj *listObjPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "procname"); return TCL_ERROR; } name = TclGetString(objv[1]); procPtr = TclFindProc(iPtr, name); if (procPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" isn't a procedure", name)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "PROCEDURE", name, (char *)NULL); return TCL_ERROR; } /* * Build a return list containing the arguments. */ listObjPtr = Tcl_NewListObj(0, NULL); for (localPtr = procPtr->firstLocalPtr; localPtr != NULL; localPtr = localPtr->nextPtr) { if (TclIsVarArgument(localPtr)) { Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(localPtr->name, -1)); } } Tcl_SetObjResult(interp, listObjPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * InfoBodyCmd -- * * Called to implement the "info body" command that returns the body for * a procedure. Handles the following syntax: * * info body procName * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoBodyCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; const char *name, *bytes; Proc *procPtr; Tcl_Size numBytes; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "procname"); return TCL_ERROR; } name = TclGetString(objv[1]); procPtr = TclFindProc(iPtr, name); if (procPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" isn't a procedure", name)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "PROCEDURE", name, (char *)NULL); return TCL_ERROR; } /* * Here we used to return procPtr->bodyPtr, except when the body was * bytecompiled - in that case, the return was a copy of the body's string * rep. In order to better isolate the implementation details of the * compiler/engine subsystem, we now always return a copy of the string * rep. It is important to return a copy so that later manipulations of * the object do not invalidate the internal rep. */ bytes = TclGetStringFromObj(procPtr->bodyPtr, &numBytes); Tcl_SetObjResult(interp, Tcl_NewStringObj(bytes, numBytes)); return TCL_OK; } /* *---------------------------------------------------------------------- * * InfoCmdCountCmd -- * * Called to implement the "info cmdcount" command that returns the * number of commands that have been executed. Handles the following * syntax: * * info cmdcount * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoCmdCountCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(iPtr->cmdCount)); return TCL_OK; } /* *---------------------------------------------------------------------- * * InfoCommandsCmd -- * * Called to implement the "info commands" command that returns the list * of commands in the interpreter that match an optional pattern. The * pattern, if any, consists of an optional sequence of namespace names * separated by "::" qualifiers, which is followed by a glob-style * pattern that restricts which commands are returned. Handles the * following syntax: * * info commands ?pattern? * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoCommandsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *cmdName, *pattern; const char *simplePattern; Tcl_HashEntry *entryPtr; Tcl_HashSearch search; Namespace *nsPtr; Namespace *globalNsPtr = (Namespace *) Tcl_GetGlobalNamespace(interp); Namespace *currNsPtr = (Namespace *) Tcl_GetCurrentNamespace(interp); Tcl_Obj *listPtr, *elemObjPtr; int specificNsInPattern = 0;/* Init. to avoid compiler warning. */ Tcl_Command cmd; Tcl_Size i; /* * Get the pattern and find the "effective namespace" in which to list * commands. */ if (objc == 1) { simplePattern = NULL; nsPtr = currNsPtr; specificNsInPattern = 0; } else if (objc == 2) { /* * From the pattern, get the effective namespace and the simple * pattern (no namespace qualifiers or ::'s) at the end. If an error * was found while parsing the pattern, return it. Otherwise, if the * namespace wasn't found, just leave nsPtr NULL: we will return an * empty list since no commands there can be found. */ Namespace *dummy1NsPtr, *dummy2NsPtr; pattern = TclGetString(objv[1]); TclGetNamespaceForQualName(interp, pattern, NULL, 0, &nsPtr, &dummy1NsPtr, &dummy2NsPtr, &simplePattern); if (nsPtr != NULL) { /* We successfully found the pattern's ns. */ specificNsInPattern = (strcmp(simplePattern, pattern) != 0); } } else { Tcl_WrongNumArgs(interp, 1, objv, "?pattern?"); return TCL_ERROR; } /* * Exit as quickly as possible if we couldn't find the namespace. */ if (nsPtr == NULL) { return TCL_OK; } /* * Scan through the effective namespace's command table and create a list * with all commands that match the pattern. If a specific namespace was * requested in the pattern, qualify the command names with the namespace * name. */ listPtr = Tcl_NewListObj(0, NULL); if (simplePattern != NULL && TclMatchIsTrivial(simplePattern)) { /* * Special case for when the pattern doesn't include any of glob's * special characters. This lets us avoid scans of any hash tables. */ entryPtr = Tcl_FindHashEntry(&nsPtr->cmdTable, simplePattern); if (entryPtr != NULL) { if (specificNsInPattern) { cmd = (Tcl_Command)Tcl_GetHashValue(entryPtr); TclNewObj(elemObjPtr); Tcl_GetCommandFullName(interp, cmd, elemObjPtr); } else { cmdName = (const char *)Tcl_GetHashKey(&nsPtr->cmdTable, entryPtr); elemObjPtr = Tcl_NewStringObj(cmdName, -1); } Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr); Tcl_SetObjResult(interp, listPtr); return TCL_OK; } if ((nsPtr != globalNsPtr) && !specificNsInPattern) { Tcl_HashTable *tablePtr = NULL; /* Quell warning. */ for (i=0 ; icommandPathLength ; i++) { Namespace *pathNsPtr = nsPtr->commandPathArray[i].nsPtr; if (pathNsPtr == NULL) { continue; } tablePtr = &pathNsPtr->cmdTable; entryPtr = Tcl_FindHashEntry(tablePtr, simplePattern); if (entryPtr != NULL) { break; } } if (entryPtr == NULL) { tablePtr = &globalNsPtr->cmdTable; entryPtr = Tcl_FindHashEntry(tablePtr, simplePattern); } if (entryPtr != NULL) { cmdName = (const char *)Tcl_GetHashKey(tablePtr, entryPtr); Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj(cmdName, -1)); Tcl_SetObjResult(interp, listPtr); return TCL_OK; } } } else if (nsPtr->commandPathLength == 0 || specificNsInPattern) { /* * The pattern is non-trivial, but either there is no explicit path or * there is an explicit namespace in the pattern. In both cases, the * old matching scheme is perfect. */ entryPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search); while (entryPtr != NULL) { cmdName = (const char *)Tcl_GetHashKey(&nsPtr->cmdTable, entryPtr); if ((simplePattern == NULL) || Tcl_StringMatch(cmdName, simplePattern)) { if (specificNsInPattern) { cmd = (Tcl_Command)Tcl_GetHashValue(entryPtr); TclNewObj(elemObjPtr); Tcl_GetCommandFullName(interp, cmd, elemObjPtr); } else { elemObjPtr = Tcl_NewStringObj(cmdName, -1); } Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr); } entryPtr = Tcl_NextHashEntry(&search); } /* * If the effective namespace isn't the global :: namespace, and a * specific namespace wasn't requested in the pattern, then add in all * global :: commands that match the simple pattern. Of course, we add * in only those commands that aren't hidden by a command in the * effective namespace. */ if ((nsPtr != globalNsPtr) && !specificNsInPattern) { entryPtr = Tcl_FirstHashEntry(&globalNsPtr->cmdTable, &search); while (entryPtr != NULL) { cmdName = (const char *)Tcl_GetHashKey(&globalNsPtr->cmdTable, entryPtr); if ((simplePattern == NULL) || Tcl_StringMatch(cmdName, simplePattern)) { if (Tcl_FindHashEntry(&nsPtr->cmdTable,cmdName) == NULL) { Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj(cmdName, -1)); } } entryPtr = Tcl_NextHashEntry(&search); } } } else { /* * The pattern is non-trivial (can match more than one command name), * there is an explicit path, and there is no explicit namespace in * the pattern. This means that we have to traverse the path to * discover all the commands defined. */ Tcl_HashTable addedCommandsTable; int isNew; int foundGlobal = (nsPtr == globalNsPtr); /* * We keep a hash of the objects already added to the result list. */ Tcl_InitObjHashTable(&addedCommandsTable); entryPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search); while (entryPtr != NULL) { cmdName = (const char *)Tcl_GetHashKey(&nsPtr->cmdTable, entryPtr); if ((simplePattern == NULL) || Tcl_StringMatch(cmdName, simplePattern)) { elemObjPtr = Tcl_NewStringObj(cmdName, -1); Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr); (void) Tcl_CreateHashEntry(&addedCommandsTable, elemObjPtr, &isNew); } entryPtr = Tcl_NextHashEntry(&search); } /* * Search the path next. */ for (i=0 ; icommandPathLength ; i++) { Namespace *pathNsPtr = nsPtr->commandPathArray[i].nsPtr; if (pathNsPtr == NULL) { continue; } if (pathNsPtr == globalNsPtr) { foundGlobal = 1; } entryPtr = Tcl_FirstHashEntry(&pathNsPtr->cmdTable, &search); while (entryPtr != NULL) { cmdName = (const char *)Tcl_GetHashKey(&pathNsPtr->cmdTable, entryPtr); if ((simplePattern == NULL) || Tcl_StringMatch(cmdName, simplePattern)) { elemObjPtr = Tcl_NewStringObj(cmdName, -1); (void) Tcl_CreateHashEntry(&addedCommandsTable, elemObjPtr, &isNew); if (isNew) { Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr); } else { TclDecrRefCount(elemObjPtr); } } entryPtr = Tcl_NextHashEntry(&search); } } /* * If the effective namespace isn't the global :: namespace, and a * specific namespace wasn't requested in the pattern, then add in all * global :: commands that match the simple pattern. Of course, we add * in only those commands that aren't hidden by a command in the * effective namespace. */ if (!foundGlobal) { entryPtr = Tcl_FirstHashEntry(&globalNsPtr->cmdTable, &search); while (entryPtr != NULL) { cmdName = (const char *)Tcl_GetHashKey(&globalNsPtr->cmdTable, entryPtr); if ((simplePattern == NULL) || Tcl_StringMatch(cmdName, simplePattern)) { elemObjPtr = Tcl_NewStringObj(cmdName, -1); if (Tcl_FindHashEntry(&addedCommandsTable, elemObjPtr) == NULL) { Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr); } else { TclDecrRefCount(elemObjPtr); } } entryPtr = Tcl_NextHashEntry(&search); } } Tcl_DeleteHashTable(&addedCommandsTable); } Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * InfoCompleteCmd -- * * Called to implement the "info complete" command that determines * whether a string is a complete Tcl command. Handles the following * syntax: * * info complete command * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoCompleteCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "command"); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj( TclObjCommandComplete(objv[1]))); return TCL_OK; } /* *---------------------------------------------------------------------- * * InfoDefaultCmd -- * * Called to implement the "info default" command that returns the * default value for a procedure argument. Handles the following syntax: * * info default procName arg varName * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoDefaultCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; const char *procName, *argName; Proc *procPtr; CompiledLocal *localPtr; Tcl_Obj *valueObjPtr; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "procname arg varname"); return TCL_ERROR; } procName = TclGetString(objv[1]); argName = TclGetString(objv[2]); procPtr = TclFindProc(iPtr, procName); if (procPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" isn't a procedure", procName)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "PROCEDURE", procName, (char *)NULL); return TCL_ERROR; } for (localPtr = procPtr->firstLocalPtr; localPtr != NULL; localPtr = localPtr->nextPtr) { if (TclIsVarArgument(localPtr) && (strcmp(argName, localPtr->name) == 0)) { if (localPtr->defValuePtr != NULL) { valueObjPtr = Tcl_ObjSetVar2(interp, objv[3], NULL, localPtr->defValuePtr, TCL_LEAVE_ERR_MSG); if (valueObjPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(1)); } else { Tcl_Obj *nullObjPtr; TclNewObj(nullObjPtr); valueObjPtr = Tcl_ObjSetVar2(interp, objv[3], NULL, nullObjPtr, TCL_LEAVE_ERR_MSG); if (valueObjPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0)); } return TCL_OK; } } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "procedure \"%s\" doesn't have an argument \"%s\"", procName, argName)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "ARGUMENT", argName, (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * InfoErrorStackCmd -- * * Called to implement the "info errorstack" command that returns information * about the last error's call stack. Handles the following syntax: * * info errorstack ?interp? * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoErrorStackCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Interp *target; Interp *iPtr; if ((objc != 1) && (objc != 2)) { Tcl_WrongNumArgs(interp, 1, objv, "?interp?"); return TCL_ERROR; } target = interp; if (objc == 2) { target = Tcl_GetChild(interp, TclGetString(objv[1])); if (target == NULL) { return TCL_ERROR; } } iPtr = (Interp *) target; Tcl_SetObjResult(interp, iPtr->errorStack); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclInfoExistsCmd -- * * Called to implement the "info exists" command that determines whether * a variable exists. Handles the following syntax: * * info exists varName * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ int TclInfoExistsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *varName; Var *varPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "varName"); return TCL_ERROR; } varName = TclGetString(objv[1]); varPtr = TclVarTraceExists(interp, varName); Tcl_SetObjResult(interp, Tcl_NewBooleanObj(varPtr && varPtr->value.objPtr)); return TCL_OK; } /* *---------------------------------------------------------------------- * * InfoFrameCmd -- * TIP #280 * * Called to implement the "info frame" command that returns the location * of either the currently executing command, or its caller. Handles the * following syntax: * * info frame ?number? * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoFrameCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; int level, code = TCL_OK; CmdFrame *framePtr, **cmdFramePtrPtr = &iPtr->cmdFramePtr; CoroutineData *corPtr = iPtr->execEnvPtr->corPtr; int topLevel = 0; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?number?"); return TCL_ERROR; } while (corPtr) { while (*cmdFramePtrPtr) { topLevel++; cmdFramePtrPtr = &((*cmdFramePtrPtr)->nextPtr); } if (corPtr->caller.cmdFramePtr) { *cmdFramePtrPtr = corPtr->caller.cmdFramePtr; } corPtr = corPtr->callerEEPtr->corPtr; } topLevel += *cmdFramePtrPtr ? (*cmdFramePtrPtr)->level : 1; if (iPtr->cmdFramePtr && topLevel != iPtr->cmdFramePtr->level) { framePtr = iPtr->cmdFramePtr; while (framePtr) { framePtr->level = topLevel--; framePtr = framePtr->nextPtr; } if (topLevel) { Tcl_Panic("Broken frame level calculation"); } topLevel = iPtr->cmdFramePtr->level; } if (objc == 1) { /* * Just "info frame". */ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(topLevel)); goto done; } /* * We've got "info frame level" and must parse the level first. */ if (TclGetIntFromObj(interp, objv[1], &level) != TCL_OK) { code = TCL_ERROR; goto done; } if ((level > topLevel) || (level <= - topLevel)) { levelError: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad level \"%s\"", TclGetString(objv[1]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "LEVEL", TclGetString(objv[1]), (char *)NULL); code = TCL_ERROR; goto done; } /* * Let us convert to relative so that we know how many levels to go back */ if (level > 0) { level -= topLevel; } framePtr = iPtr->cmdFramePtr; while (++level <= 0) { framePtr = framePtr->nextPtr; if (!framePtr) { goto levelError; } } Tcl_SetObjResult(interp, TclInfoFrame(interp, framePtr)); done: cmdFramePtrPtr = &iPtr->cmdFramePtr; corPtr = iPtr->execEnvPtr->corPtr; while (corPtr) { CmdFrame *endPtr = corPtr->caller.cmdFramePtr; if (endPtr) { if (*cmdFramePtrPtr == endPtr) { *cmdFramePtrPtr = NULL; } else { CmdFrame *runPtr = *cmdFramePtrPtr; while (runPtr->nextPtr != endPtr) { runPtr->level -= endPtr->level; runPtr = runPtr->nextPtr; } runPtr->level = 1; runPtr->nextPtr = NULL; } cmdFramePtrPtr = &corPtr->caller.cmdFramePtr; } corPtr = corPtr->callerEEPtr->corPtr; } return code; } /* *---------------------------------------------------------------------- * * TclInfoFrame -- * * Core of InfoFrameCmd, returns TIP280 dict for a given frame. * * Results: * Returns TIP280 dict. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj * TclInfoFrame( Tcl_Interp *interp, /* Current interpreter. */ CmdFrame *framePtr) /* Frame to get info for. */ { Interp *iPtr = (Interp *) interp; Tcl_Obj *tmpObj; Tcl_Obj *lv[20] = {NULL}; /* Keep uptodate when more keys are added to * the dict. */ int lc = 0; /* * This array is indexed by the TCL_LOCATION_... values, except * for _LAST. */ static const char *const typeString[TCL_LOCATION_LAST] = { "eval", "eval", "eval", "precompiled", "source", "proc" }; Proc *procPtr = NULL; int needsFree = -1; if (!framePtr) { goto precompiled; } procPtr = framePtr->framePtr ? framePtr->framePtr->procPtr : NULL; /* * Pull the information and construct the dictionary to return, as list. * Regarding use of the CmdFrame fields see tclInt.h, and its definition. */ #define ADD_PAIR(name, value) \ TclNewLiteralStringObj(tmpObj, name); \ lv[lc++] = tmpObj; \ lv[lc++] = (value) switch (framePtr->type) { case TCL_LOCATION_EVAL: /* * Evaluation, dynamic script. Type, line, cmd, the latter through * str. */ ADD_PAIR("type", Tcl_NewStringObj(typeString[framePtr->type], -1)); if (framePtr->line) { ADD_PAIR("line", Tcl_NewWideIntObj(framePtr->line[0])); } else { ADD_PAIR("line", Tcl_NewWideIntObj(1)); } ADD_PAIR("cmd", TclGetSourceFromFrame(framePtr, 0, NULL)); break; case TCL_LOCATION_PREBC: precompiled: /* * Precompiled. Result contains the type as signal, nothing else. */ ADD_PAIR("type", Tcl_NewStringObj(typeString[TCL_LOCATION_PREBC], -1)); break; case TCL_LOCATION_BC: { /* * Execution of bytecode. Talk to the BC engine to fill out the frame. */ CmdFrame *fPtr = (CmdFrame *)TclStackAlloc(interp, sizeof(CmdFrame)); *fPtr = *framePtr; /* * Note: * Type BC => f.data.eval.path is not used. * f.data.tebc.codePtr is used instead. */ TclGetSrcInfoForPc(fPtr); /* * Now filled: cmd.str.(cmd,len), line * Possibly modified: type, path! */ ADD_PAIR("type", Tcl_NewStringObj(typeString[fPtr->type], -1)); if (fPtr->line) { ADD_PAIR("line", Tcl_NewWideIntObj(fPtr->line[0])); } if (fPtr->type == TCL_LOCATION_SOURCE) { ADD_PAIR("file", fPtr->data.eval.path); /* * Death of reference by TclGetSrcInfoForPc. */ Tcl_DecrRefCount(fPtr->data.eval.path); } ADD_PAIR("cmd", TclGetSourceFromFrame(fPtr, 0, NULL)); if (fPtr->cmdObj && framePtr->cmdObj == NULL) { needsFree = lc - 1; } TclStackFree(interp, fPtr); break; } case TCL_LOCATION_SOURCE: /* * Evaluation of a script file. */ ADD_PAIR("type", Tcl_NewStringObj(typeString[framePtr->type], -1)); ADD_PAIR("line", Tcl_NewWideIntObj(framePtr->line[0])); ADD_PAIR("file", framePtr->data.eval.path); /* * Refcount framePtr->data.eval.path goes up when lv is converted into * the result list object. */ ADD_PAIR("cmd", TclGetSourceFromFrame(framePtr, 0, NULL)); break; case TCL_LOCATION_PROC: Tcl_Panic("TCL_LOCATION_PROC found in standard frame"); break; } /* * 'proc'. Common to all frame types. Conditional on having an associated * Procedure CallFrame. */ if (procPtr != NULL) { Tcl_HashEntry *namePtr = procPtr->cmdPtr->hPtr; if (namePtr) { Tcl_Obj *procNameObj; /* * This is a regular command. */ TclNewObj(procNameObj); Tcl_GetCommandFullName(interp, (Tcl_Command) procPtr->cmdPtr, procNameObj); ADD_PAIR("proc", procNameObj); } else if (procPtr->cmdPtr->clientData) { ExtraFrameInfo *efiPtr = (ExtraFrameInfo *)procPtr->cmdPtr->clientData; Tcl_Size i; /* * This is a non-standard command. Luckily, it's told us how to * render extra information about its frame. */ for (i=0 ; ilength ; i++) { lv[lc++] = Tcl_NewStringObj(efiPtr->fields[i].name, -1); if (efiPtr->fields[i].proc) { lv[lc++] = efiPtr->fields[i].proc(efiPtr->fields[i].clientData); } else { lv[lc++] = (Tcl_Obj *)efiPtr->fields[i].clientData; } } } } /* * 'level'. Common to all frame types. Conditional on having an associated * _visible_ CallFrame. */ if (framePtr && (framePtr->framePtr != NULL) && (iPtr->varFramePtr != NULL)) { CallFrame *current = framePtr->framePtr; CallFrame *top = iPtr->varFramePtr; CallFrame *idx; for (idx=top ; idx!=NULL ; idx=idx->callerVarPtr) { if (idx == current) { int c = framePtr->framePtr->level; int t = iPtr->varFramePtr->level; ADD_PAIR("level", Tcl_NewWideIntObj(t - c)); break; } } } tmpObj = Tcl_NewListObj(lc, lv); if (needsFree >= 0) { Tcl_DecrRefCount(lv[needsFree]); } return tmpObj; } /* *---------------------------------------------------------------------- * * InfoFunctionsCmd -- * * Called to implement the "info functions" command that returns the list * of math functions matching an optional pattern. Handles the following * syntax: * * info functions ?pattern? * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoFunctionsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *script; int code; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?pattern?"); return TCL_ERROR; } script = Tcl_NewStringObj( " ::apply [::list {{pattern *}} {\n" " ::set cmds {}\n" " ::foreach cmd [::info commands ::tcl::mathfunc::$pattern] {\n" " ::lappend cmds [::namespace tail $cmd]\n" " }\n" " ::foreach cmd [::info commands tcl::mathfunc::$pattern] {\n" " ::set cmd [::namespace tail $cmd]\n" " ::if {$cmd ni $cmds} {\n" " ::lappend cmds $cmd\n" " }\n" " }\n" " ::return $cmds\n" " } [::namespace current]] ", -1); if (objc == 2) { Tcl_Obj *arg = Tcl_NewListObj(1, &(objv[1])); Tcl_AppendObjToObj(script, arg); Tcl_DecrRefCount(arg); } Tcl_IncrRefCount(script); code = Tcl_EvalObjEx(interp, script, 0); Tcl_DecrRefCount(script); return code; } /* *---------------------------------------------------------------------- * * InfoHostnameCmd -- * * Called to implement the "info hostname" command that returns the host * name. Handles the following syntax: * * info hostname * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoHostnameCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *name; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } name = Tcl_GetHostName(); if (name) { Tcl_SetObjResult(interp, Tcl_NewStringObj(name, -1)); return TCL_OK; } Tcl_SetObjResult(interp, Tcl_NewStringObj( "unable to determine name of host", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "HOSTNAME", "UNKNOWN", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * InfoLevelCmd -- * * Called to implement the "info level" command that returns information * about the call stack. Handles the following syntax: * * info level ?number? * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoLevelCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; if (objc == 1) { /* Just "info level" */ Tcl_SetObjResult(interp, Tcl_NewWideIntObj((int)iPtr->varFramePtr->level)); return TCL_OK; } if (objc == 2) { int level; CallFrame *framePtr, *rootFramePtr = iPtr->rootFramePtr; if (TclGetIntFromObj(interp, objv[1], &level) != TCL_OK) { return TCL_ERROR; } if (level <= 0) { if (iPtr->varFramePtr == rootFramePtr) { goto levelError; } level += iPtr->varFramePtr->level; } for (framePtr=iPtr->varFramePtr ; framePtr!=rootFramePtr; framePtr=framePtr->callerVarPtr) { if ((int)framePtr->level == level) { break; } } if (framePtr == rootFramePtr) { goto levelError; } Tcl_SetObjResult(interp, Tcl_NewListObj(framePtr->objc, framePtr->objv)); return TCL_OK; } Tcl_WrongNumArgs(interp, 1, objv, "?number?"); return TCL_ERROR; levelError: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad level \"%s\"", TclGetString(objv[1]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "LEVEL", TclGetString(objv[1]), (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * InfoLibraryCmd -- * * Called to implement the "info library" command that returns the * library directory for the Tcl installation. Handles the following * syntax: * * info library * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoLibraryCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *libDirName; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } libDirName = Tcl_GetVar2(interp, "tcl_library", NULL, TCL_GLOBAL_ONLY); if (libDirName != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj(libDirName, -1)); return TCL_OK; } Tcl_SetObjResult(interp, Tcl_NewStringObj( "no library has been specified for Tcl", -1)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "VARIABLE", "tcl_library", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * InfoLoadedCmd -- * * Called to implement the "info loaded" command that returns the * packages that have been loaded into an interpreter. Handles the * following syntax: * * info loaded ?interp? * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoLoadedCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *interpName, *prefix; if (objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "?interp? ?prefix?"); return TCL_ERROR; } if (objc < 2) { /* Get loaded pkgs in all interpreters. */ interpName = NULL; } else { /* Get pkgs just in specified interp. */ interpName = TclGetString(objv[1]); } if (objc < 3) { /* Get loaded files in all packages. */ prefix = NULL; } else { /* Get pkgs just in specified interp. */ prefix = TclGetString(objv[2]); } return TclGetLoadedLibraries(interp, interpName, prefix); } /* *---------------------------------------------------------------------- * * InfoNameOfExecutableCmd -- * * Called to implement the "info nameofexecutable" command that returns * the name of the binary file running this application. Handles the * following syntax: * * info nameofexecutable * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoNameOfExecutableCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, TclGetObjNameOfExecutable()); return TCL_OK; } /* *---------------------------------------------------------------------- * * InfoPatchLevelCmd -- * * Called to implement the "info patchlevel" command that returns the * default value for an argument to a procedure. Handles the following * syntax: * * info patchlevel * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoPatchLevelCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *patchlevel; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } patchlevel = Tcl_GetVar2(interp, "tcl_patchLevel", NULL, (TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG)); if (patchlevel != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj(patchlevel, -1)); return TCL_OK; } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * InfoProcsCmd -- * * Called to implement the "info procs" command that returns the list of * procedures in the interpreter that match an optional pattern. The * pattern, if any, consists of an optional sequence of namespace names * separated by "::" qualifiers, which is followed by a glob-style * pattern that restricts which commands are returned. Handles the * following syntax: * * info procs ?pattern? * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoProcsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *cmdName, *pattern; const char *simplePattern; Namespace *nsPtr; Namespace *currNsPtr = (Namespace *) Tcl_GetCurrentNamespace(interp); Tcl_Obj *listPtr, *elemObjPtr; int specificNsInPattern = 0;/* Init. to avoid compiler warning. */ Tcl_HashEntry *entryPtr; Tcl_HashSearch search; Command *cmdPtr, *realCmdPtr; /* * Get the pattern and find the "effective namespace" in which to list * procs. */ if (objc == 1) { simplePattern = NULL; nsPtr = currNsPtr; specificNsInPattern = 0; } else if (objc == 2) { /* * From the pattern, get the effective namespace and the simple * pattern (no namespace qualifiers or ::'s) at the end. If an error * was found while parsing the pattern, return it. Otherwise, if the * namespace wasn't found, just leave nsPtr NULL: we will return an * empty list since no commands there can be found. */ Namespace *dummy1NsPtr, *dummy2NsPtr; pattern = TclGetString(objv[1]); TclGetNamespaceForQualName(interp, pattern, NULL, /*flags*/ 0, &nsPtr, &dummy1NsPtr, &dummy2NsPtr, &simplePattern); if (nsPtr != NULL) { /* We successfully found the pattern's ns. */ specificNsInPattern = (strcmp(simplePattern, pattern) != 0); } } else { Tcl_WrongNumArgs(interp, 1, objv, "?pattern?"); return TCL_ERROR; } if (nsPtr == NULL) { return TCL_OK; } /* * Scan through the effective namespace's command table and create a list * with all procs that match the pattern. If a specific namespace was * requested in the pattern, qualify the command names with the namespace * name. */ listPtr = Tcl_NewListObj(0, NULL); if (simplePattern != NULL && TclMatchIsTrivial(simplePattern)) { entryPtr = Tcl_FindHashEntry(&nsPtr->cmdTable, simplePattern); if (entryPtr != NULL) { cmdPtr = (Command *)Tcl_GetHashValue(entryPtr); if (!TclIsProc(cmdPtr)) { realCmdPtr = (Command *) TclGetOriginalCommand((Tcl_Command) cmdPtr); if (realCmdPtr != NULL && TclIsProc(realCmdPtr)) { goto simpleProcOK; } } else { simpleProcOK: if (specificNsInPattern) { TclNewObj(elemObjPtr); Tcl_GetCommandFullName(interp, (Tcl_Command) cmdPtr, elemObjPtr); } else { elemObjPtr = Tcl_NewStringObj(simplePattern, -1); } Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr); } } } else { entryPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search); while (entryPtr != NULL) { cmdName = (const char *)Tcl_GetHashKey(&nsPtr->cmdTable, entryPtr); if ((simplePattern == NULL) || Tcl_StringMatch(cmdName, simplePattern)) { cmdPtr = (Command *)Tcl_GetHashValue(entryPtr); if (!TclIsProc(cmdPtr)) { realCmdPtr = (Command *) TclGetOriginalCommand((Tcl_Command) cmdPtr); if (realCmdPtr != NULL && TclIsProc(realCmdPtr)) { goto procOK; } } else { procOK: if (specificNsInPattern) { TclNewObj(elemObjPtr); Tcl_GetCommandFullName(interp, (Tcl_Command) cmdPtr, elemObjPtr); } else { elemObjPtr = Tcl_NewStringObj(cmdName, -1); } Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr); } } entryPtr = Tcl_NextHashEntry(&search); } } Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * InfoScriptCmd -- * * Called to implement the "info script" command that returns the script * file that is currently being evaluated. Handles the following syntax: * * info script ?newName? * * If newName is specified, it will set that as the internal name. * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. It may change the internal * script filename. * *---------------------------------------------------------------------- */ static int InfoScriptCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; if ((objc != 1) && (objc != 2)) { Tcl_WrongNumArgs(interp, 1, objv, "?filename?"); return TCL_ERROR; } if (objc == 2) { if (iPtr->scriptFile != NULL) { Tcl_DecrRefCount(iPtr->scriptFile); } iPtr->scriptFile = objv[1]; Tcl_IncrRefCount(iPtr->scriptFile); } if (iPtr->scriptFile != NULL) { Tcl_SetObjResult(interp, iPtr->scriptFile); } return TCL_OK; } /* *---------------------------------------------------------------------- * * InfoSharedlibCmd -- * * Called to implement the "info sharedlibextension" command that returns * the file extension used for shared libraries. Handles the following * syntax: * * info sharedlibextension * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoSharedlibCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } #ifdef TCL_SHLIB_EXT Tcl_SetObjResult(interp, Tcl_NewStringObj(TCL_SHLIB_EXT, -1)); #endif return TCL_OK; } /* *---------------------------------------------------------------------- * * InfoTclVersionCmd -- * * Called to implement the "info tclversion" command that returns the * version number for this Tcl library. Handles the following syntax: * * info tclversion * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ static int InfoTclVersionCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *version; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } version = Tcl_GetVar2Ex(interp, "tcl_version", NULL, (TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG)); if (version != NULL) { Tcl_SetObjResult(interp, version); return TCL_OK; } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * InfoCmdTypeCmd -- * * Called to implement the "info cmdtype" command that returns the type * of a given command. Handles the following syntax: * * info cmdtype cmdName * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a type name. If there is an error, the result is an error * message. * *---------------------------------------------------------------------- */ static int InfoCmdTypeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Command command; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "commandName"); return TCL_ERROR; } command = Tcl_FindCommand(interp, TclGetString(objv[1]), NULL, TCL_LEAVE_ERR_MSG); if (command == NULL) { return TCL_ERROR; } /* * There's one special case: safe child interpreters can't see aliases as * aliases as they're part of the security mechanisms. */ if (Tcl_IsSafe(interp) && (((Command *) command)->objProc == TclAliasObjCmd)) { Tcl_AppendResult(interp, "native", (char *)NULL); } else { Tcl_SetObjResult(interp, Tcl_NewStringObj(TclGetCommandTypeName(command), -1)); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_JoinObjCmd -- * * This procedure is invoked to process the "join" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_JoinObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { Tcl_Size length, listLen; int isAbstractList = 0; Tcl_Obj *resObjPtr = NULL, *joinObjPtr, **elemPtrs; if ((objc < 2) || (objc > 3)) { Tcl_WrongNumArgs(interp, 1, objv, "list ?joinString?"); return TCL_ERROR; } /* * Make sure the list argument is a list object and get its length and a * pointer to its array of element pointers. */ if (TclObjTypeHasProc(objv[1], getElementsProc)) { listLen = TclObjTypeLength(objv[1]); isAbstractList = (listLen ? 1 : 0); if (listLen > 1 && TclObjTypeGetElements(interp, objv[1], &listLen, &elemPtrs) != TCL_OK) { return TCL_ERROR; } } else if (TclListObjGetElements(interp, objv[1], &listLen, &elemPtrs) != TCL_OK) { return TCL_ERROR; } if (listLen == 0) { /* No elements to join; default empty result is correct. */ return TCL_OK; } if (listLen == 1) { /* One element; return it */ if (!isAbstractList) { Tcl_SetObjResult(interp, elemPtrs[0]); } else { Tcl_Obj *elemObj; if (TclObjTypeIndex(interp, objv[1], 0, &elemObj) != TCL_OK) { return TCL_ERROR; } Tcl_SetObjResult(interp, elemObj); } return TCL_OK; } joinObjPtr = (objc == 2) ? Tcl_NewStringObj(" ", 1) : objv[2]; Tcl_IncrRefCount(joinObjPtr); (void)TclGetStringFromObj(joinObjPtr, &length); if (length == 0) { resObjPtr = TclStringCat(interp, listLen, elemPtrs, 0); } else { Tcl_Size i; TclNewObj(resObjPtr); for (i = 0; i < listLen; i++) { if (i > 0) { /* * NOTE: This code is relying on Tcl_AppendObjToObj() **NOT** * to shimmer joinObjPtr. If it did, then the case where * objv[1] and objv[2] are the same value would not be safe. * Accessing elemPtrs would crash. */ Tcl_AppendObjToObj(resObjPtr, joinObjPtr); } Tcl_AppendObjToObj(resObjPtr, elemPtrs[i]); } } Tcl_DecrRefCount(joinObjPtr); if (resObjPtr) { Tcl_SetObjResult(interp, resObjPtr); return TCL_OK; } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_LassignObjCmd -- * * This object-based procedure is invoked to process the "lassign" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_LassignObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *listPtr; Tcl_Size listObjc; /* The length of the list. */ Tcl_Size origListObjc; /* Original length */ int i; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "list ?varName ...?"); return TCL_ERROR; } /* * Note: no need to Dup the list to avoid shimmering. That is only * needed when Tcl_ListObjGetElements is used since that returns * pointers to internal structures. Using Tcl_ListObjIndex does not * have that problem. However, we now have to IncrRef each elemObj * (see below). I see that as preferable as duping lists is potentially * expensive for abstract lists when they have a string representation. */ listPtr = objv[1]; if (TclListObjLength(interp, listPtr, &listObjc) != TCL_OK) { return TCL_ERROR; } origListObjc = listObjc; objc -= 2; objv += 2; for (i = 0; i < objc && i < listObjc; ++i) { Tcl_Obj *elemObj; if (Tcl_ListObjIndex(interp, listPtr, i, &elemObj) != TCL_OK) { return TCL_ERROR; } /* * Must incrref elemObj. If the var name being set is same as the * list value, ObjSetVar2 will shimmer the list to a VAR freeing * the elements in the list (in case list refCount was 1) BEFORE * the elemObj is stored in the var. See tests 6.{25,26} */ Tcl_IncrRefCount(elemObj); if (Tcl_ObjSetVar2(interp, *objv++, NULL, elemObj, TCL_LEAVE_ERR_MSG) == NULL) { Tcl_DecrRefCount(elemObj); return TCL_ERROR; } Tcl_DecrRefCount(elemObj); } objc -= i; listObjc -= i; if (objc > 0) { /* Still some variables left to be assigned */ Tcl_Obj *emptyObj; TclNewObj(emptyObj); Tcl_IncrRefCount(emptyObj); while (objc-- > 0) { if (Tcl_ObjSetVar2(interp, *objv++, NULL, emptyObj, TCL_LEAVE_ERR_MSG) == NULL) { Tcl_DecrRefCount(emptyObj); return TCL_ERROR; } } Tcl_DecrRefCount(emptyObj); } if (listObjc > 0) { Tcl_Obj *resultObjPtr = NULL; Tcl_Size fromIdx = origListObjc - listObjc; Tcl_Size toIdx = origListObjc - 1; if (TclObjTypeHasProc(listPtr, sliceProc)) { if (TclObjTypeSlice( interp, listPtr, fromIdx, toIdx, &resultObjPtr) != TCL_OK) { return TCL_ERROR; } } else { resultObjPtr = TclListObjRange( interp, listPtr, origListObjc - listObjc, origListObjc - 1); if (resultObjPtr == NULL) { return TCL_ERROR; } } Tcl_SetObjResult(interp, resultObjPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_LindexObjCmd -- * * This object-based procedure is invoked to process the "lindex" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_LindexObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *elemPtr; /* Pointer to the element being extracted. */ if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "list ?index ...?"); return TCL_ERROR; } /* * If objc==3, then objv[2] may be either a single index or a list of * indices: go to TclLindexList to determine which. If objc>=4, or * objc==2, then objv[2 .. objc-2] are all single indices and processed as * such in TclLindexFlat. */ if (objc == 3) { elemPtr = TclLindexList(interp, objv[1], objv[2]); } else { elemPtr = TclLindexFlat(interp, objv[1], objc-2, objv+2); } /* * Set the interpreter's object result to the last element extracted. */ if (elemPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, elemPtr); Tcl_DecrRefCount(elemPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_LinsertObjCmd -- * * This object-based procedure is invoked to process the "linsert" Tcl * command. See the user documentation for details on what it does. * * Results: * A new Tcl list object formed by inserting zero or more elements into a * list. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_LinsertObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *listPtr; Tcl_Size len, index; int copied = 0, result; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "list index ?element ...?"); return TCL_ERROR; } result = TclListObjLength(interp, objv[1], &len); if (result != TCL_OK) { return result; } /* * Get the index. "end" is interpreted to be the index after the last * element, such that using it will cause any inserted elements to be * appended to the list. */ result = TclGetIntForIndexM(interp, objv[2], /*end*/ len, &index); if (result != TCL_OK) { return result; } if (index > len) { index = len; } /* * If the list object is unshared we can modify it directly. Otherwise we * create a copy to modify: this is "copy on write". */ listPtr = objv[1]; if (Tcl_IsShared(listPtr)) { listPtr = TclListObjCopy(NULL, listPtr); copied = 1; } if ((objc == 4) && (index == len)) { /* * Special case: insert one element at the end of the list. */ result = Tcl_ListObjAppendElement(NULL, listPtr, objv[3]); if (result != TCL_OK) { if (copied) { Tcl_DecrRefCount(listPtr); } return result; } } else { if (TCL_OK != Tcl_ListObjReplace(interp, listPtr, index, 0, (objc-3), &(objv[3]))) { if (copied) { Tcl_DecrRefCount(listPtr); } return TCL_ERROR; } } /* * Set the interpreter's object result. */ Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ListObjCmd -- * * This procedure is invoked to process the "list" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_ListObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { /* * If there are no list elements, the result is an empty object. * Otherwise set the interpreter's result object to be a list object. */ if (objc > 1) { Tcl_SetObjResult(interp, Tcl_NewListObj(objc-1, &objv[1])); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_LlengthObjCmd -- * * This object-based procedure is invoked to process the "llength" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_LlengthObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size listLen; int result; Tcl_Obj *objPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "list"); return TCL_ERROR; } result = TclListObjLength(interp, objv[1], &listLen); if (result != TCL_OK) { return result; } /* * Set the interpreter's object result to an integer object holding the * length. */ TclNewUIntObj(objPtr, listLen); Tcl_SetObjResult(interp, objPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_LpopObjCmd -- * * This procedure is invoked to process the "lpop" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_LpopObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size listLen; int copied = 0, result; Tcl_Obj *elemPtr, *stored; Tcl_Obj *listPtr; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "listvar ?index?"); return TCL_ERROR; } listPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, TCL_LEAVE_ERR_MSG); if (listPtr == NULL) { return TCL_ERROR; } result = TclListObjLength(interp, listPtr, &listLen); if (result != TCL_OK) { return result; } /* * First, extract the element to be returned. * TclLindexFlat adds a ref count which is handled. */ if (objc == 2) { if (!listLen) { /* empty list, throw the same error as with index "end" */ Tcl_SetObjResult(interp, Tcl_NewStringObj( "index \"end\" out of range", -1)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "INDEX", "OUTOFRANGE", (char *)NULL); return TCL_ERROR; } result = Tcl_ListObjIndex(interp, listPtr, (listLen-1), &elemPtr); if (result != TCL_OK) { return result; } Tcl_IncrRefCount(elemPtr); } else { elemPtr = TclLindexFlat(interp, listPtr, objc-2, objv+2); if (elemPtr == NULL) { return TCL_ERROR; } } Tcl_SetObjResult(interp, elemPtr); Tcl_DecrRefCount(elemPtr); /* * Second, remove the element. * TclLsetFlat adds a ref count which is handled. */ if (objc == 2) { if (Tcl_IsShared(listPtr)) { listPtr = TclListObjCopy(NULL, listPtr); copied = 1; } result = Tcl_ListObjReplace(interp, listPtr, listLen - 1, 1, 0, NULL); if (result != TCL_OK) { if (copied) { Tcl_DecrRefCount(listPtr); } return result; } } else { Tcl_Obj *newListPtr; Tcl_ObjTypeSetElement *proc = TclObjTypeHasProc(listPtr, setElementProc); if (proc) { newListPtr = proc(interp, listPtr, objc-2, objv+2, NULL); } else { newListPtr = TclLsetFlat(interp, listPtr, objc-2, objv+2, NULL); } if (newListPtr == NULL) { if (copied) { Tcl_DecrRefCount(listPtr); } return TCL_ERROR; } else { listPtr = newListPtr; TclUndoRefCount(listPtr); } } stored = Tcl_ObjSetVar2(interp, objv[1], NULL, listPtr, TCL_LEAVE_ERR_MSG); if (stored == NULL) { return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_LrangeObjCmd -- * * This procedure is invoked to process the "lrange" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_LrangeObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int result; Tcl_Size listLen, first, last; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "list first last"); return TCL_ERROR; } result = TclListObjLength(interp, objv[1], &listLen); if (result != TCL_OK) { return result; } result = TclGetIntForIndexM(interp, objv[2], /*endValue*/ listLen - 1, &first); if (result != TCL_OK) { return result; } result = TclGetIntForIndexM(interp, objv[3], /*endValue*/ listLen - 1, &last); if (result != TCL_OK) { return result; } if (TclObjTypeHasProc(objv[1], sliceProc)) { Tcl_Obj *resultObj; int status = TclObjTypeSlice(interp, objv[1], first, last, &resultObj); if (status == TCL_OK) { Tcl_SetObjResult(interp, resultObj); } else { return TCL_ERROR; } } else { Tcl_Obj *resultObj = TclListObjRange(interp, objv[1], first, last); if (resultObj == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, resultObj); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_LremoveObjCmd -- * * This procedure is invoked to process the "lremove" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int LremoveIndexCompare( const void *el1Ptr, const void *el2Ptr) { Tcl_Size idx1 = *((const Tcl_Size *) el1Ptr); Tcl_Size idx2 = *((const Tcl_Size *) el2Ptr); /* * This will put the larger element first. */ return (idx1 < idx2) ? 1 : (idx1 > idx2) ? -1 : 0; } int Tcl_LremoveObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size i, idxc, prevIdx, first, num; Tcl_Size *idxv, listLen; Tcl_Obj *listObj; int copied = 0, status = TCL_OK; /* * Parse the arguments. */ if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "list ?index ...?"); return TCL_ERROR; } listObj = objv[1]; if (TclListObjLength(interp, listObj, &listLen) != TCL_OK) { return TCL_ERROR; } idxc = objc - 2; if (idxc == 0) { Tcl_SetObjResult(interp, listObj); return TCL_OK; } idxv = (Tcl_Size *)Tcl_Alloc((objc - 2) * sizeof(*idxv)); for (i = 2; i < objc; i++) { status = (TclGetIntForIndexM(interp, objv[i], /*endValue*/ listLen - 1, &idxv[i - 2]) != TCL_OK); if (status != TCL_OK) { goto done; } } /* * Sort the indices, large to small so that when we remove an index we * don't change the indices still to be processed. */ if (idxc > 1) { qsort(idxv, idxc, sizeof(*idxv), LremoveIndexCompare); } /* * Make our working copy, then do the actual removes piecemeal. */ if (Tcl_IsShared(listObj)) { listObj = TclListObjCopy(NULL, listObj); copied = 1; } num = 0; first = listLen; for (i = 0, prevIdx = -1 ; i < idxc ; i++) { Tcl_Size idx = idxv[i]; /* * Repeated index and sanity check. */ if (idx == prevIdx) { continue; } prevIdx = idx; if (idx < 0 || idx >= listLen) { continue; } /* * Coalesce adjacent removes to reduce the number of copies. */ if (num == 0) { num = 1; first = idx; } else if (idx + 1 == first) { num++; first = idx; } else { /* * Note that this operation can't fail now; we know we have a list * and we're only ever contracting that list. */ status = Tcl_ListObjReplace(interp, listObj, first, num, 0, NULL); if (status != TCL_OK) { goto done; } listLen -= num; num = 1; first = idx; } } if (num != 0) { status = Tcl_ListObjReplace(interp, listObj, first, num, 0, NULL); if (status != TCL_OK) { if (copied) { Tcl_DecrRefCount(listObj); } goto done; } } Tcl_SetObjResult(interp, listObj); done: Tcl_Free(idxv); return status; } /* *---------------------------------------------------------------------- * * Tcl_LrepeatObjCmd -- * * This procedure is invoked to process the "lrepeat" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_LrepeatObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { Tcl_WideInt elementCount, i; Tcl_Size totalElems; Tcl_Obj *listPtr, **dataArray = NULL; /* * Check arguments for legality: * lrepeat count ?value ...? */ if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "count ?value ...?"); return TCL_ERROR; } if (TCL_OK != TclGetWideIntFromObj(interp, objv[1], &elementCount)) { return TCL_ERROR; } if (elementCount < 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad count \"%" TCL_LL_MODIFIER "d\": must be integer >= 0", elementCount)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LREPEAT", "NEGARG", (char *)NULL); return TCL_ERROR; } /* * Skip forward to the interesting arguments now we've finished parsing. */ objc -= 2; objv += 2; /* Final sanity check. Do not exceed limits on max list length. */ if (elementCount && objc > LIST_MAX/elementCount) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "max length of a Tcl list (%" TCL_SIZE_MODIFIER "d elements) exceeded", LIST_MAX)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); return TCL_ERROR; } totalElems = objc * elementCount; /* * Get an empty list object that is allocated large enough to hold each * init value elementCount times. */ listPtr = Tcl_NewListObj(totalElems, NULL); if (totalElems) { ListRep listRep; ListObjGetRep(listPtr, &listRep); dataArray = ListRepElementsBase(&listRep); listRep.storePtr->numUsed = totalElems; if (listRep.spanPtr) { /* Future proofing in case Tcl_NewListObj returns a span */ listRep.spanPtr->spanStart = listRep.storePtr->firstUsed; listRep.spanPtr->spanLength = listRep.storePtr->numUsed; } } /* * Set the elements. Note that we handle the common degenerate case of a * single value being repeated separately to permit the compiler as much * room as possible to optimize a loop that might be run a very large * number of times. */ CLANG_ASSERT(dataArray || totalElems == 0 ); if (objc == 1) { Tcl_Obj *tmpPtr = objv[0]; tmpPtr->refCount += elementCount; for (i=0 ; i listLen) { first = listLen; } if (last >= listLen) { last = listLen - 1; } if (first <= last) { numToDelete = (size_t)last - (size_t)first + 1; /* See [3d3124d01d] */ } else { numToDelete = 0; } /* * If the list object is unshared we can modify it directly, otherwise we * create a copy to modify: this is "copy on write". */ listPtr = objv[1]; if (Tcl_IsShared(listPtr)) { listPtr = TclListObjCopy(NULL, listPtr); } /* * Note that we call Tcl_ListObjReplace even when numToDelete == 0 and * objc == 4. In this case, the list value of listPtr is not changed (no * elements are removed or added), but by making the call we are assured * we end up with a list in canonical form. Resist any temptation to * optimize this case away. */ if (TCL_OK != Tcl_ListObjReplace(interp, listPtr, first, numToDelete, objc-4, objv+4)) { Tcl_DecrRefCount(listPtr); return TCL_ERROR; } /* * Set the interpreter's object result. */ Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_LreverseObjCmd -- * * This procedure is invoked to process the "lreverse" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_LreverseObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument values. */ { Tcl_Obj **elemv; Tcl_Size elemc, i, j; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "list"); return TCL_ERROR; } /* * Handle AbstractList special case - do not shimmer into a list, if it * supports a private Reverse function, just to reverse it. */ if (TclObjTypeHasProc(objv[1], reverseProc)) { Tcl_Obj *resultObj; if (TclObjTypeReverse(interp, objv[1], &resultObj) == TCL_OK) { Tcl_SetObjResult(interp, resultObj); return TCL_OK; } } /* end Abstract List */ if (TclListObjLength(interp, objv[1], &elemc) != TCL_OK) { return TCL_ERROR; } /* * If the list is empty, just return it. [Bug 1876793] */ if (!elemc) { Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } if (TclListObjGetElements(interp, objv[1], &elemc, &elemv) != TCL_OK) { return TCL_ERROR; } if (Tcl_IsShared(objv[1]) || ListObjRepIsShared(objv[1])) { /* Bug 1675044 */ Tcl_Obj *resultObj, **dataArray; ListRep listRep; resultObj = Tcl_NewListObj(elemc, NULL); /* Modify the internal rep in-place */ ListObjGetRep(resultObj, &listRep); listRep.storePtr->numUsed = elemc; dataArray = ListRepElementsBase(&listRep); if (listRep.spanPtr) { /* Future proofing */ listRep.spanPtr->spanStart = listRep.storePtr->firstUsed; listRep.spanPtr->spanLength = listRep.storePtr->numUsed; } for (i=0,j=elemc-1 ; i objc-4) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "missing starting index", -1)); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", (char *)NULL); result = TCL_ERROR; goto done; } i++; if (objv[i] == objv[objc - 2]) { /* * Take copy to prevent shimmering problems. Note that it does * not matter if the index obj is also a component of the list * being searched. We only need to copy where the list and the * index are one-and-the-same. */ startPtr = Tcl_DuplicateObj(objv[i]); } else { startPtr = objv[i]; } Tcl_IncrRefCount(startPtr); break; case LSEARCH_STRIDE: /* -stride */ if (i > objc-4) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "\"-stride\" option must be " "followed by stride length", -1)); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", (char *)NULL); result = TCL_ERROR; goto done; } if (TclGetWideIntFromObj(interp, objv[i+1], &wide) != TCL_OK) { result = TCL_ERROR; goto done; } if (wide < 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "stride length must be at least 1", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LSEARCH", "BADSTRIDE", (char *)NULL); result = TCL_ERROR; goto done; } groupSize = wide; i++; break; case LSEARCH_INDEX: { /* -index */ Tcl_Obj **indices; Tcl_Size j; if (allocatedIndexVector) { TclStackFree(interp, sortInfo.indexv); allocatedIndexVector = 0; } if (i > objc-4) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "\"-index\" option must be followed by list index", -1)); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", (char *)NULL); result = TCL_ERROR; goto done; } /* * Store the extracted indices for processing by sublist * extraction. Note that we don't do this using objects because * that has shimmering problems. */ i++; if (TclListObjGetElements(interp, objv[i], &sortInfo.indexc, &indices) != TCL_OK) { result = TCL_ERROR; goto done; } switch (sortInfo.indexc) { case 0: sortInfo.indexv = NULL; break; case 1: sortInfo.indexv = &sortInfo.singleIndex; break; default: sortInfo.indexv = (int *) TclStackAlloc(interp, sizeof(int) * sortInfo.indexc); allocatedIndexVector = 1; /* Cannot use indexc field, as it * might be decreased by 1 later. */ } /* * Fill the array by parsing each index. We don't know whether * their scale is sensible yet, but we at least perform the * syntactic check here. */ for (j=0 ; j 1) { if (listc % groupSize) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "list size must be a multiple of the stride length", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LSEARCH", "BADSTRIDE", (char *)NULL); result = TCL_ERROR; goto done; } if (sortInfo.indexc > 0) { /* * Use the first value in the list supplied to -index as the * offset of the element within each group by which to sort. */ groupOffset = TclIndexDecode(sortInfo.indexv[0], groupSize - 1); if (groupOffset < 0 || groupOffset >= groupSize) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "when used with \"-stride\", the leading \"-index\"" " value must be within the group", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LSEARCH", "BADINDEX", (char *)NULL); result = TCL_ERROR; goto done; } if (sortInfo.indexc == 1) { sortInfo.indexc = 0; sortInfo.indexv = NULL; } else { sortInfo.indexc--; for (i = 0; i < sortInfo.indexc; i++) { sortInfo.indexv[i] = sortInfo.indexv[i+1]; } } } } /* * Get the user-specified start offset. */ if (startPtr) { result = TclGetIntForIndexM(interp, startPtr, listc-1, &start); if (result != TCL_OK) { goto done; } if (start == TCL_INDEX_NONE) { start = TCL_INDEX_START; } /* * If the search started past the end of the list, we just return a * "did not match anything at all" result straight away. [Bug 1374778] */ if (start >= listc) { if (allMatches || inlineReturn) { Tcl_ResetResult(interp); } else { TclNewIntObj(itemPtr, -1); Tcl_SetObjResult(interp, itemPtr); } goto done; } /* * If start points within a group, it points to the start of the group. */ if (groupSize > 1) { start -= (start % groupSize); } } patObj = objv[objc - 1]; patternBytes = NULL; if (mode == EXACT || mode == SORTED) { switch (dataType) { case ASCII: case DICTIONARY: patternBytes = TclGetStringFromObj(patObj, &length); break; case INTEGER: result = TclGetWideIntFromObj(interp, patObj, &patWide); if (result != TCL_OK) { goto done; } /* * List representation might have been shimmered; restore it. [Bug * 1844789] */ TclListObjGetElements(NULL, objv[objc - 2], &listc, &listv); break; case REAL: result = Tcl_GetDoubleFromObj(interp, patObj, &patDouble); if (result != TCL_OK) { goto done; } /* * List representation might have been shimmered; restore it. [Bug * 1844789] */ TclListObjGetElements(NULL, objv[objc - 2], &listc, &listv); break; } } else { patternBytes = TclGetStringFromObj(patObj, &length); } /* * Set default index value to -1, indicating failure; if we find the item * in the course of our search, index will be set to the correct value. */ index = -1; match = 0; if (mode == SORTED && !allMatches && !negatedMatch) { /* * If the data is sorted, we can do a more intelligent search. Note * that there is no point in being smart when -all was specified; in * that case, we have to look at all items anyway, and there is no * sense in doing this when the match sense is inverted. */ /* * With -stride, lower, upper and i are kept as multiples of groupSize. */ lower = start - groupSize; upper = listc; itemPtr = NULL; while (lower + groupSize != upper && sortInfo.resultCode == TCL_OK) { i = (lower + upper)/2; i -= i % groupSize; Tcl_BounceRefCount(itemPtr); itemPtr = NULL; if (sortInfo.indexc != 0) { itemPtr = SelectObjFromSublist(listv[i+groupOffset], &sortInfo); if (sortInfo.resultCode != TCL_OK) { result = sortInfo.resultCode; goto done; } } else { itemPtr = listv[i+groupOffset]; } switch (dataType) { case ASCII: bytes = TclGetString(itemPtr); match = strCmpFn(patternBytes, bytes); break; case DICTIONARY: bytes = TclGetString(itemPtr); match = DictionaryCompare(patternBytes, bytes); break; case INTEGER: result = TclGetWideIntFromObj(interp, itemPtr, &objWide); if (result != TCL_OK) { goto done; } if (patWide == objWide) { match = 0; } else if (patWide < objWide) { match = -1; } else { match = 1; } break; case REAL: result = Tcl_GetDoubleFromObj(interp, itemPtr, &objDouble); if (result != TCL_OK) { goto done; } if (patDouble == objDouble) { match = 0; } else if (patDouble < objDouble) { match = -1; } else { match = 1; } break; } if (match == 0) { /* * Normally, binary search is written to stop when it finds a * match. If there are duplicates of an element in the list, * our first match might not be the first occurrence. * Consider: 0 0 0 1 1 1 2 2 2 * * To maintain consistency with standard lsearch semantics, we * must find the leftmost occurrence of the pattern in the * list. Thus we don't just stop searching here. This * variation means that a search always makes log n * comparisons (normal binary search might "get lucky" with an * early comparison). * * In bisect mode though, we want the last of equals. */ index = i; if (bisect) { lower = i; } else { upper = i; } } else if (match > 0) { if (isIncreasing) { lower = i; } else { upper = i; } } else { if (isIncreasing) { upper = i; } else { lower = i; } } } if (bisect && index < 0) { index = lower; } } else { /* * We need to do a linear search, because (at least one) of: * - our matcher can only tell equal vs. not equal * - our matching sense is negated * - we're building a list of all matched items */ if (allMatches) { listPtr = Tcl_NewListObj(0, NULL); } for (i = start; i < listc; i += groupSize) { match = 0; Tcl_BounceRefCount(itemPtr); itemPtr = NULL; if (sortInfo.indexc != 0) { itemPtr = SelectObjFromSublist(listv[i+groupOffset], &sortInfo); if (sortInfo.resultCode != TCL_OK) { if (listPtr != NULL) { Tcl_DecrRefCount(listPtr); } result = sortInfo.resultCode; goto done; } } else { itemPtr = listv[i+groupOffset]; } switch (mode) { case SORTED: case EXACT: switch (dataType) { case ASCII: bytes = TclGetStringFromObj(itemPtr, &elemLen); if (length == elemLen) { /* * This split allows for more optimal compilation of * memcmp/strcasecmp. */ if (noCase) { match = (TclUtfCasecmp(bytes, patternBytes) == 0); } else { match = (memcmp(bytes, patternBytes, length) == 0); } } break; case DICTIONARY: bytes = TclGetString(itemPtr); match = (DictionaryCompare(bytes, patternBytes) == 0); break; case INTEGER: result = TclGetWideIntFromObj(interp, itemPtr, &objWide); if (result != TCL_OK) { if (listPtr != NULL) { Tcl_DecrRefCount(listPtr); } goto done; } match = (objWide == patWide); break; case REAL: result = Tcl_GetDoubleFromObj(interp,itemPtr, &objDouble); if (result != TCL_OK) { if (listPtr) { Tcl_DecrRefCount(listPtr); } goto done; } match = (objDouble == patDouble); break; } break; case GLOB: match = Tcl_StringCaseMatch(TclGetString(itemPtr), patternBytes, noCase); break; case REGEXP: match = Tcl_RegExpExecObj(interp, regexp, itemPtr, 0, 0, 0); if (match < 0) { Tcl_DecrRefCount(patObj); if (listPtr != NULL) { Tcl_DecrRefCount(listPtr); } result = TCL_ERROR; goto done; } break; } /* * Invert match condition for -not. */ if (negatedMatch) { match = !match; } if (!match) { continue; } if (!allMatches) { index = i; break; } else if (inlineReturn) { /* * Note that these appends are not expected to fail. */ if (returnSubindices && (sortInfo.indexc != 0)) { Tcl_BounceRefCount(itemPtr); itemPtr = SelectObjFromSublist(listv[i+groupOffset], &sortInfo); Tcl_ListObjAppendElement(interp, listPtr, itemPtr); } else if (returnSubindices && (sortInfo.indexc == 0) && (groupSize > 1)) { Tcl_BounceRefCount(itemPtr); itemPtr = listv[i + groupOffset]; Tcl_ListObjAppendElement(interp, listPtr, itemPtr); } else if (groupSize > 1) { Tcl_ListObjReplace(interp, listPtr, LIST_MAX, 0, groupSize, &listv[i]); } else { Tcl_BounceRefCount(itemPtr); itemPtr = listv[i]; Tcl_ListObjAppendElement(interp, listPtr, itemPtr); } } else if (returnSubindices) { Tcl_Size j; TclNewIndexObj(itemPtr, i+groupOffset); for (j=0 ; j 1) { Tcl_SetObjResult(interp, Tcl_NewListObj(groupSize, &listv[index])); } else { Tcl_SetObjResult(interp, listv[index]); } } result = TCL_OK; /* * Cleanup the index list array. */ done: /* potential lingering abstract list element */ Tcl_BounceRefCount(itemPtr); if (startPtr != NULL) { Tcl_DecrRefCount(startPtr); } if (allocatedIndexVector) { TclStackFree(interp, sortInfo.indexv); } return result; } /* *---------------------------------------------------------------------- * * SequenceIdentifyArgument -- * (for [lseq] command) * * Given a Tcl_Obj, identify if it is a keyword or a number * * Return Value * 0 - failure, unexpected value * 1 - value is a number * 2 - value is an operand keyword * 3 - value is a by keyword * * The decoded value will be assigned to the appropriate * pointer, numValuePtr reference count is incremented. */ static SequenceDecoded SequenceIdentifyArgument( Tcl_Interp *interp, /* for error reporting */ Tcl_Obj *argPtr, /* Argument to decode */ int allowedArgs, /* Flags if keyword or numeric allowed. */ Tcl_Obj **numValuePtr, /* Return numeric value */ int *keywordIndexPtr) /* Return keyword enum */ { int result = TCL_ERROR; SequenceOperators opmode; void *internalPtr; if (allowedArgs & NumericArg) { /* speed-up a bit (and avoid shimmer for compiled expressions) */ if (TclHasInternalRep(argPtr, &tclExprCodeType)) { goto doExpr; } result = Tcl_GetNumberFromObj(NULL, argPtr, &internalPtr, keywordIndexPtr); if (result == TCL_OK) { *numValuePtr = argPtr; Tcl_IncrRefCount(argPtr); return NumericArg; } } if (allowedArgs & RangeKeywordArg) { result = Tcl_GetIndexFromObj(NULL, argPtr, seq_operations, "range operation", 0, &opmode); } if (result == TCL_OK) { if (allowedArgs & LastArg) { /* keyword found, but no followed number */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "missing \"%s\" value.", TclGetString(argPtr))); return ErrArg; } *keywordIndexPtr = opmode; return RangeKeywordArg; } else { Tcl_Obj *exprValueObj; if (!(allowedArgs & NumericArg)) { return NoneArg; } doExpr: /* Check for an index expression */ if (Tcl_ExprObj(interp, argPtr, &exprValueObj) != TCL_OK) { return ErrArg; } int keyword; /* Determine if result of expression is double or int */ if (Tcl_GetNumberFromObj(interp, exprValueObj, &internalPtr, &keyword) != TCL_OK ) { return ErrArg; } *numValuePtr = exprValueObj; /* incremented in Tcl_ExprObj */ *keywordIndexPtr = keyword; /* type of expression result */ return NumericArg; } } /* *---------------------------------------------------------------------- * * Tcl_LseqObjCmd -- * * This procedure is invoked to process the "lseq" Tcl command. * See the user documentation for details on what it does. * * Enumerated possible argument patterns: * * 1: * lseq n * 2: * lseq n n * 3: * lseq n n n * lseq n 'to' n * lseq n 'count' n * lseq n 'by' n * 4: * lseq n 'to' n n * lseq n n 'by' n * lseq n 'count' n n * 5: * lseq n 'to' n 'by' n * lseq n 'count' n 'by' n * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_LseqObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { Tcl_Obj *elementCount = NULL; Tcl_Obj *start = NULL, *end = NULL, *step = NULL; Tcl_WideInt values[5]; Tcl_Obj *numValues[5]; Tcl_Obj *numberObj; int status = TCL_ERROR, keyword, allowedArgs = NumericArg; int useDoubles = 0; int remNums = 3; Tcl_Obj *arithSeriesPtr; SequenceOperators opmode; SequenceDecoded decoded; int i, arg_key = 0, value_i = 0; /* Default constants */ #define zero ((Interp *)interp)->execEnvPtr->constants[0]; #define one ((Interp *)interp)->execEnvPtr->constants[1]; /* * Create a decoding key by looping through the arguments and identify * what kind of argument each one is. Encode each argument as a decimal * digit. */ if (objc > 6) { /* Too many arguments */ goto syntax; } for (i = 1; i < objc; i++) { arg_key = (arg_key * 10); numValues[value_i] = NULL; decoded = SequenceIdentifyArgument(interp, objv[i], allowedArgs | (i == objc-1 ? LastArg : 0), &numberObj, &keyword); switch (decoded) { case NoneArg: /* * Unrecognizable argument * Reproduce operation error message */ status = Tcl_GetIndexFromObj(interp, objv[i], seq_operations, "operation", 0, &opmode); goto done; case NumericArg: remNums--; arg_key += NumericArg; allowedArgs = RangeKeywordArg; /* if last number but 2 arguments remain, next is not numeric */ if ((remNums != 1) || ((objc-1-i) != 2)) { allowedArgs |= NumericArg; } numValues[value_i] = numberObj; values[value_i] = keyword; /* TCL_NUMBER_* */ useDoubles += (keyword == TCL_NUMBER_DOUBLE) ? 1 : 0; value_i++; break; case RangeKeywordArg: arg_key += RangeKeywordArg; allowedArgs = NumericArg; /* after keyword always numeric only */ values[value_i] = keyword; /* SequenceOperators */ value_i++; break; default: /* Error state */ status = TCL_ERROR; goto done; } } /* * The key encoding defines a valid set of arguments, or indicates an * error condition; process the values accordningly. */ switch (arg_key) { /* lseq n */ case 1: start = zero; elementCount = numValues[0]; end = NULL; step = one; useDoubles = 0; // Can only have Integer value. If a fractional value // is given, this will fail later. In other words, // "3.0" is allowed and used as Integer, but "3.1" // will be flagged as an error. (bug f4a4bd7f1070) break; /* lseq n n */ case 11: start = numValues[0]; end = numValues[1]; break; /* lseq n n n */ case 111: start = numValues[0]; end = numValues[1]; step = numValues[2]; break; /* lseq n 'to' n */ /* lseq n 'count' n */ /* lseq n 'by' n */ case 121: opmode = (SequenceOperators)values[1]; switch (opmode) { case LSEQ_DOTS: case LSEQ_TO: start = numValues[0]; end = numValues[2]; break; case LSEQ_BY: start = zero; elementCount = numValues[0]; step = numValues[2]; break; case LSEQ_COUNT: start = numValues[0]; elementCount = numValues[2]; step = one; break; default: goto done; } break; /* lseq n 'to' n n */ /* lseq n 'count' n n */ case 1211: opmode = (SequenceOperators)values[1]; switch (opmode) { case LSEQ_DOTS: case LSEQ_TO: start = numValues[0]; end = numValues[2]; step = numValues[3]; break; case LSEQ_COUNT: start = numValues[0]; elementCount = numValues[2]; step = numValues[3]; break; case LSEQ_BY: /* Error case */ goto done; break; default: goto done; break; } break; /* lseq n n 'by' n */ case 1121: start = numValues[0]; end = numValues[1]; opmode = (SequenceOperators)values[2]; switch (opmode) { case LSEQ_BY: step = numValues[3]; break; case LSEQ_DOTS: case LSEQ_TO: case LSEQ_COUNT: default: goto done; break; } break; /* lseq n 'to' n 'by' n */ /* lseq n 'count' n 'by' n */ case 12121: start = numValues[0]; opmode = (SequenceOperators)values[3]; switch (opmode) { case LSEQ_BY: step = numValues[4]; break; default: goto done; break; } opmode = (SequenceOperators)values[1]; switch (opmode) { case LSEQ_DOTS: case LSEQ_TO: start = numValues[0]; end = numValues[2]; break; case LSEQ_COUNT: start = numValues[0]; elementCount = numValues[2]; break; default: goto done; break; } break; /* All other argument errors */ default: syntax: Tcl_WrongNumArgs(interp, 1, objv, "n ??op? n ??by? n??"); goto done; break; } /* Count needs to be integer, so try to convert if possible */ if (elementCount && TclHasInternalRep(elementCount, &tclDoubleType)) { double d; // Don't consider Count type to indicate using double values in seqence useDoubles -= (useDoubles > 0) ? 1 : 0; (void)Tcl_GetDoubleFromObj(NULL, elementCount, &d); if (floor(d) == d) { if ((d >= (double)WIDE_MAX) || (d <= (double)WIDE_MIN)) { mp_int big; if (Tcl_InitBignumFromDouble(NULL, d, &big) == TCL_OK) { elementCount = Tcl_NewBignumObj(&big); keyword = TCL_NUMBER_INT; } /* Infinity, don't convert, let fail later */ } else { elementCount = Tcl_NewWideIntObj((Tcl_WideInt)d); keyword = TCL_NUMBER_INT; } } } /* * Success! Now lets create the series object. */ arithSeriesPtr = TclNewArithSeriesObj(interp, useDoubles, start, end, step, elementCount); status = TCL_ERROR; if (arithSeriesPtr) { status = TCL_OK; Tcl_SetObjResult(interp, arithSeriesPtr); } done: // Free number arguments. while (--value_i>=0) { if (numValues[value_i]) { if (elementCount == numValues[value_i]) { elementCount = NULL; } Tcl_DecrRefCount(numValues[value_i]); } } if (elementCount) { Tcl_DecrRefCount(elementCount); } /* Undef constants */ #undef zero #undef one return status; } /* *---------------------------------------------------------------------- * * Tcl_LsetObjCmd -- * * This procedure is invoked to process the "lset" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_LsetObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument values. */ { Tcl_Obj *listPtr; /* Pointer to the list being altered. */ Tcl_Obj *finalValuePtr; /* Value finally assigned to the variable. */ /* * Check parameter count. */ if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "listVar ?index? ?index ...? value"); return TCL_ERROR; } /* * Look up the list variable's value. */ listPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, TCL_LEAVE_ERR_MSG); if (listPtr == NULL) { return TCL_ERROR; } /* * Substitute the value in the value. Return either the value or else an * unshared copy of it. */ if (objc == 4) { finalValuePtr = TclLsetList(interp, listPtr, objv[2], objv[3]); } else { if (TclObjTypeHasProc(listPtr, setElementProc)) { finalValuePtr = TclObjTypeSetElement(interp, listPtr, objc-3, objv+2, objv[objc-1]); if (finalValuePtr) { Tcl_IncrRefCount(finalValuePtr); } } else { finalValuePtr = TclLsetFlat(interp, listPtr, objc-3, objv+2, objv[objc-1]); } } /* * If substitution has failed, bail out. */ if (finalValuePtr == NULL) { return TCL_ERROR; } /* * Finally, update the variable so that traces fire. */ listPtr = Tcl_ObjSetVar2(interp, objv[1], NULL, finalValuePtr, TCL_LEAVE_ERR_MSG); Tcl_DecrRefCount(finalValuePtr); if (listPtr == NULL) { return TCL_ERROR; } /* * Return the new value of the variable as the interpreter result. */ Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_LsortObjCmd -- * * This procedure is invoked to process the "lsort" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_LsortObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument values. */ { int indices, nocase = 0, indexc; int sortMode = SORTMODE_ASCII; int group, allocatedIndexVector = 0; Tcl_Size j, idx, groupOffset, length; Tcl_WideInt wide, groupSize; Tcl_Obj *resultPtr, *cmdPtr, **listObjPtrs, *listObj, *indexPtr; Tcl_Size i, elmArrSize; SortElement *elementArray = NULL, *elementPtr; SortInfo sortInfo; /* Information about this sort that needs to * be passed to the comparison function. */ # define MAXCALLOC 1024000 # define NUM_LISTS 30 SortElement *subList[NUM_LISTS+1]; /* This array holds pointers to temporary * lists built during the merge sort. Element * i of the array holds a list of length * 2**i. */ static const char *const switches[] = { "-ascii", "-command", "-decreasing", "-dictionary", "-increasing", "-index", "-indices", "-integer", "-nocase", "-real", "-stride", "-unique", NULL }; enum Lsort_Switches { LSORT_ASCII, LSORT_COMMAND, LSORT_DECREASING, LSORT_DICTIONARY, LSORT_INCREASING, LSORT_INDEX, LSORT_INDICES, LSORT_INTEGER, LSORT_NOCASE, LSORT_REAL, LSORT_STRIDE, LSORT_UNIQUE } index; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "?-option value ...? list"); return TCL_ERROR; } /* * Parse arguments to set up the mode for the sort. */ sortInfo.isIncreasing = 1; sortInfo.sortMode = SORTMODE_ASCII; sortInfo.indexv = NULL; sortInfo.indexc = 0; sortInfo.unique = 0; sortInfo.interp = interp; sortInfo.resultCode = TCL_OK; cmdPtr = NULL; indices = 0; group = 0; groupSize = 1; groupOffset = 0; indexPtr = NULL; for (i = 1; i < objc-1; i++) { if (Tcl_GetIndexFromObj(interp, objv[i], switches, "option", 0, &index) != TCL_OK) { sortInfo.resultCode = TCL_ERROR; goto done; } switch (index) { case LSORT_ASCII: sortInfo.sortMode = SORTMODE_ASCII; break; case LSORT_COMMAND: if (i == objc-2) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "\"-command\" option must be followed " "by comparison command", -1)); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", (char *)NULL); sortInfo.resultCode = TCL_ERROR; goto done; } sortInfo.sortMode = SORTMODE_COMMAND; cmdPtr = objv[i+1]; i++; break; case LSORT_DECREASING: sortInfo.isIncreasing = 0; break; case LSORT_DICTIONARY: sortInfo.sortMode = SORTMODE_DICTIONARY; break; case LSORT_INCREASING: sortInfo.isIncreasing = 1; break; case LSORT_INDEX: { Tcl_Size sortindex; Tcl_Obj **indexv; if (i == objc-2) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "\"-index\" option must be followed by list index", -1)); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", (char *)NULL); sortInfo.resultCode = TCL_ERROR; goto done; } if (TclListObjGetElements(interp, objv[i+1], &sortindex, &indexv) != TCL_OK) { sortInfo.resultCode = TCL_ERROR; goto done; } /* * Check each of the indices for syntactic correctness. Note that * we do not store the converted values here because we do not * know if this is the only -index option yet and so we can't * allocate any space; that happens after the scan through all the * options is done. */ for (j=0 ; j 0) { /* * Use the first value in the list supplied to -index as the * offset of the element within each group by which to sort. */ groupOffset = TclIndexDecode(sortInfo.indexv[0], groupSize - 1); if (groupOffset < 0 || groupOffset >= groupSize) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "when used with \"-stride\", the leading \"-index\"" " value must be within the group", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LSORT", "BADINDEX", (char *)NULL); sortInfo.resultCode = TCL_ERROR; goto done; } if (sortInfo.indexc == 1) { sortInfo.indexc = 0; sortInfo.indexv = NULL; } else { sortInfo.indexc--; /* * Do not shrink the actual memory block used; that doesn't * work with TclStackAlloc-allocated memory. [Bug 2918962] * * TODO: Consider a pointer increment to replace this * array shift. */ for (i = 0; i < sortInfo.indexc; i++) { sortInfo.indexv[i] = sortInfo.indexv[i+1]; } } } } sortInfo.numElements = length; indexc = sortInfo.indexc; sortMode = sortInfo.sortMode; if ((sortMode == SORTMODE_ASCII_NC) || (sortMode == SORTMODE_DICTIONARY)) { /* * For this function's purpose all string-based modes are equivalent */ sortMode = SORTMODE_ASCII; } /* * Initialize the sublists. After the following loop, subList[i] will * contain a sorted sublist of length 2**i. Use one extra subList at the * end, always at NULL, to indicate the end of the lists. */ for (j=0 ; j<=NUM_LISTS ; j++) { subList[j] = NULL; } /* * The following loop creates a SortElement for each list element and * begins sorting it into the sublists as it appears. */ elmArrSize = length * sizeof(SortElement); if (elmArrSize <= MAXCALLOC) { elementArray = (SortElement *)Tcl_Alloc(elmArrSize); } else { elementArray = (SortElement *)malloc(elmArrSize); } if (!elementArray) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "no enough memory to proccess sort of %" TCL_Z_MODIFIER "u items", length)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); sortInfo.resultCode = TCL_ERROR; goto done; } for (i=0; i < length; i++) { idx = groupSize * i + groupOffset; if (indexc) { /* * If this is an indexed sort, retrieve the corresponding element */ indexPtr = SelectObjFromSublist(listObjPtrs[idx], &sortInfo); if (sortInfo.resultCode != TCL_OK) { goto done; } } else { indexPtr = listObjPtrs[idx]; } /* * Determine the "value" of this object for sorting purposes */ if (sortMode == SORTMODE_ASCII) { elementArray[i].collationKey.strValuePtr = TclGetString(indexPtr); } else if (sortMode == SORTMODE_INTEGER) { Tcl_WideInt a; if (TclGetWideIntFromObj(sortInfo.interp, indexPtr, &a) != TCL_OK) { sortInfo.resultCode = TCL_ERROR; goto done; } elementArray[i].collationKey.wideValue = a; } else if (sortMode == SORTMODE_REAL) { double a; if (Tcl_GetDoubleFromObj(sortInfo.interp, indexPtr, &a) != TCL_OK) { sortInfo.resultCode = TCL_ERROR; goto done; } elementArray[i].collationKey.doubleValue = a; } else { elementArray[i].collationKey.objValuePtr = indexPtr; } /* * Determine the representation of this element in the result: either * the objPtr itself, or its index in the original list. */ if (indices || group) { elementArray[i].payload.index = idx; } else { elementArray[i].payload.objPtr = listObjPtrs[idx]; } /* * Merge this element in the preexisting sublists (and merge together * sublists when we have two of the same size). */ elementArray[i].nextPtr = NULL; elementPtr = &elementArray[i]; for (j=0 ; subList[j] ; j++) { elementPtr = MergeLists(subList[j], elementPtr, &sortInfo); subList[j] = NULL; } if (j >= NUM_LISTS) { j = NUM_LISTS-1; } subList[j] = elementPtr; } /* * Merge all sublists */ elementPtr = subList[0]; for (j=1 ; jnextPtr) { idx = elementPtr->payload.index; for (j = 0; j < groupSize; j++) { if (indices) { TclNewIndexObj(objPtr, idx + j - groupOffset); newArray[i++] = objPtr; Tcl_IncrRefCount(objPtr); } else { objPtr = listObjPtrs[idx + j - groupOffset]; newArray[i++] = objPtr; Tcl_IncrRefCount(objPtr); } } } } else if (indices) { for (i=0; elementPtr != NULL ; elementPtr = elementPtr->nextPtr) { TclNewIndexObj(objPtr, elementPtr->payload.index); newArray[i++] = objPtr; Tcl_IncrRefCount(objPtr); } } else { for (i=0; elementPtr != NULL ; elementPtr = elementPtr->nextPtr) { objPtr = elementPtr->payload.objPtr; newArray[i++] = objPtr; Tcl_IncrRefCount(objPtr); } } listRep.storePtr->numUsed = i; if (listRep.spanPtr) { listRep.spanPtr->spanStart = listRep.storePtr->firstUsed; listRep.spanPtr->spanLength = listRep.storePtr->numUsed; } Tcl_SetObjResult(interp, resultPtr); } done: if (sortMode == SORTMODE_COMMAND) { TclDecrRefCount(sortInfo.compareCmdPtr); TclDecrRefCount(listObj); sortInfo.compareCmdPtr = NULL; } if (allocatedIndexVector) { TclStackFree(interp, sortInfo.indexv); } if (elementArray) { if (elmArrSize <= MAXCALLOC) { Tcl_Free(elementArray); } else { free((char *)elementArray); } } return sortInfo.resultCode; } /* *---------------------------------------------------------------------- * * Tcl_LeditObjCmd -- * * This procedure is invoked to process the "ledit" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_LeditObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument values. */ { Tcl_Obj *listPtr; /* Pointer to the list being altered. */ Tcl_Obj *finalValuePtr; /* Value finally assigned to the variable. */ int createdNewObj; int result; Tcl_Size first; Tcl_Size last; Tcl_Size listLen; Tcl_Size numToDelete; if (objc < 4) { Tcl_WrongNumArgs(interp, 1, objv, "listVar first last ?element ...?"); return TCL_ERROR; } listPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, TCL_LEAVE_ERR_MSG); if (listPtr == NULL) { return TCL_ERROR; } /* * TODO - refactor the index extraction into a common function shared * by Tcl_{Lrange,Lreplace,Ledit}ObjCmd */ result = TclListObjLength(interp, listPtr, &listLen); if (result != TCL_OK) { return result; } result = TclGetIntForIndexM(interp, objv[2], /*end*/ listLen-1, &first); if (result != TCL_OK) { return result; } result = TclGetIntForIndexM(interp, objv[3], /*end*/ listLen-1, &last); if (result != TCL_OK) { return result; } if (first < 0) { first = 0; } else if (first > listLen) { first = listLen; } if (last >= listLen) { last = listLen - 1; } if (first <= last) { numToDelete = (size_t)last - (size_t)first + 1; /* See [3d3124d01d] */ } else { numToDelete = 0; } if (Tcl_IsShared(listPtr)) { listPtr = TclListObjCopy(NULL, listPtr); createdNewObj = 1; } else { createdNewObj = 0; } result = Tcl_ListObjReplace(interp, listPtr, first, numToDelete, objc - 4, objv + 4); if (result != TCL_OK) { if (createdNewObj) { Tcl_DecrRefCount(listPtr); } return result; } /* * Tcl_ObjSetVar2 may return a value different from listPtr in the * presence of traces etc. */ finalValuePtr = Tcl_ObjSetVar2(interp, objv[1], NULL, listPtr, TCL_LEAVE_ERR_MSG); if (finalValuePtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, finalValuePtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * MergeLists - * * This procedure combines two sorted lists of SortElement structures * into a single sorted list. * * Results: * The unified list of SortElement structures. * * Side effects: * If infoPtr->unique is set then infoPtr->numElements may be updated. * Possibly others, if a user-defined comparison command does something * weird. * * Note: * If infoPtr->unique is set, the merge assumes that there are no * "repeated" elements in each of the left and right lists. In that case, * if any element of the left list is equivalent to one in the right list * it is omitted from the merged list. * * This simplified mechanism works because of the special way our * MergeSort creates the sublists to be merged and will fail to eliminate * all repeats in the general case where they are already present in * either the left or right list. A general code would need to skip * adjacent initial repeats in the left and right lists before comparing * their initial elements, at each step. * *---------------------------------------------------------------------- */ static SortElement * MergeLists( SortElement *leftPtr, /* First list to be merged; may be NULL. */ SortElement *rightPtr, /* Second list to be merged; may be NULL. */ SortInfo *infoPtr) /* Information needed by the comparison * operator. */ { SortElement *headPtr, *tailPtr; int cmp; if (leftPtr == NULL) { return rightPtr; } if (rightPtr == NULL) { return leftPtr; } cmp = SortCompare(leftPtr, rightPtr, infoPtr); if (cmp > 0 || (cmp == 0 && infoPtr->unique)) { if (cmp == 0) { infoPtr->numElements--; leftPtr = leftPtr->nextPtr; } tailPtr = rightPtr; rightPtr = rightPtr->nextPtr; } else { tailPtr = leftPtr; leftPtr = leftPtr->nextPtr; } headPtr = tailPtr; if (!infoPtr->unique) { while ((leftPtr != NULL) && (rightPtr != NULL)) { cmp = SortCompare(leftPtr, rightPtr, infoPtr); if (cmp > 0) { tailPtr->nextPtr = rightPtr; tailPtr = rightPtr; rightPtr = rightPtr->nextPtr; } else { tailPtr->nextPtr = leftPtr; tailPtr = leftPtr; leftPtr = leftPtr->nextPtr; } } } else { while ((leftPtr != NULL) && (rightPtr != NULL)) { cmp = SortCompare(leftPtr, rightPtr, infoPtr); if (cmp >= 0) { if (cmp == 0) { infoPtr->numElements--; leftPtr = leftPtr->nextPtr; } tailPtr->nextPtr = rightPtr; tailPtr = rightPtr; rightPtr = rightPtr->nextPtr; } else { tailPtr->nextPtr = leftPtr; tailPtr = leftPtr; leftPtr = leftPtr->nextPtr; } } } if (leftPtr != NULL) { tailPtr->nextPtr = leftPtr; } else { tailPtr->nextPtr = rightPtr; } return headPtr; } /* *---------------------------------------------------------------------- * * SortCompare -- * * This procedure is invoked by MergeLists to determine the proper * ordering between two elements. * * Results: * A negative results means the first element comes before the * second, and a positive results means that the second element should * come first. A result of zero means the two elements are equal and it * doesn't matter which comes first. * * Side effects: * None, unless a user-defined comparison command does something weird. * *---------------------------------------------------------------------- */ static int SortCompare( SortElement *elemPtr1, SortElement *elemPtr2, /* Values to be compared. */ SortInfo *infoPtr) /* Information passed from the top-level * "lsort" command. */ { int order = 0; if (infoPtr->sortMode == SORTMODE_ASCII) { order = TclUtfCmp(elemPtr1->collationKey.strValuePtr, elemPtr2->collationKey.strValuePtr); } else if (infoPtr->sortMode == SORTMODE_ASCII_NC) { order = TclUtfCasecmp(elemPtr1->collationKey.strValuePtr, elemPtr2->collationKey.strValuePtr); } else if (infoPtr->sortMode == SORTMODE_DICTIONARY) { order = DictionaryCompare(elemPtr1->collationKey.strValuePtr, elemPtr2->collationKey.strValuePtr); } else if (infoPtr->sortMode == SORTMODE_INTEGER) { Tcl_WideInt a, b; a = elemPtr1->collationKey.wideValue; b = elemPtr2->collationKey.wideValue; order = ((a >= b) - (a <= b)); } else if (infoPtr->sortMode == SORTMODE_REAL) { double a, b; a = elemPtr1->collationKey.doubleValue; b = elemPtr2->collationKey.doubleValue; order = ((a >= b) - (a <= b)); } else { Tcl_Obj **objv, *paramObjv[2]; Tcl_Size objc; Tcl_Obj *objPtr1, *objPtr2; if (infoPtr->resultCode != TCL_OK) { /* * Once an error has occurred, skip any future comparisons so as * to preserve the error message in sortInterp->result. */ return 0; } objPtr1 = elemPtr1->collationKey.objValuePtr; objPtr2 = elemPtr2->collationKey.objValuePtr; paramObjv[0] = objPtr1; paramObjv[1] = objPtr2; /* * We made space in the command list for the two things to compare. * Replace them and evaluate the result. */ TclListObjLength(infoPtr->interp, infoPtr->compareCmdPtr, &objc); Tcl_ListObjReplace(infoPtr->interp, infoPtr->compareCmdPtr, objc - 2, 2, 2, paramObjv); TclListObjGetElements(infoPtr->interp, infoPtr->compareCmdPtr, &objc, &objv); infoPtr->resultCode = Tcl_EvalObjv(infoPtr->interp, objc, objv, 0); if (infoPtr->resultCode != TCL_OK) { Tcl_AddErrorInfo(infoPtr->interp, "\n (-compare command)"); return 0; } /* * Parse the result of the command. */ if (TclGetIntFromObj(infoPtr->interp, Tcl_GetObjResult(infoPtr->interp), &order) != TCL_OK) { Tcl_SetObjResult(infoPtr->interp, Tcl_NewStringObj( "-compare command returned non-integer result", -1)); Tcl_SetErrorCode(infoPtr->interp, "TCL", "OPERATION", "LSORT", "COMPARISONFAILED", (char *)NULL); infoPtr->resultCode = TCL_ERROR; return 0; } } if (!infoPtr->isIncreasing) { order = -order; } return order; } /* *---------------------------------------------------------------------- * * DictionaryCompare * * This function compares two strings as if they were being used in an * index or card catalog. The case of alphabetic characters is ignored, * except to break ties. Thus "B" comes before "b" but after "a". Also, * integers embedded in the strings compare in numerical order. In other * words, "x10y" comes after "x9y", not * before it as it would when * using strcmp(). * * Results: * A negative result means that the first element comes before the * second, and a positive result means that the second element should * come first. A result of zero means the two elements are equal and it * doesn't matter which comes first. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int DictionaryCompare( const char *left, const char *right) /* The strings to compare. */ { int uniLeft = 0, uniRight = 0, uniLeftLower, uniRightLower; int diff, zeros; int secondaryDiff = 0; while (1) { if (isdigit(UCHAR(*right)) /* INTL: digit */ && isdigit(UCHAR(*left))) { /* INTL: digit */ /* * There are decimal numbers embedded in the two strings. Compare * them as numbers, rather than strings. If one number has more * leading zeros than the other, the number with more leading * zeros sorts later, but only as a secondary choice. */ zeros = 0; while ((*right == '0') && isdigit(UCHAR(right[1]))) { right++; zeros--; } while ((*left == '0') && isdigit(UCHAR(left[1]))) { left++; zeros++; } if (secondaryDiff == 0) { secondaryDiff = zeros; } /* * The code below compares the numbers in the two strings without * ever converting them to integers. It does this by first * comparing the lengths of the numbers and then comparing the * digit values. */ diff = 0; while (1) { if (diff == 0) { diff = UCHAR(*left) - UCHAR(*right); } right++; left++; if (!isdigit(UCHAR(*right))) { /* INTL: digit */ if (isdigit(UCHAR(*left))) { /* INTL: digit */ return 1; } else { /* * The two numbers have the same length. See if their * values are different. */ if (diff != 0) { return diff; } break; } } else if (!isdigit(UCHAR(*left))) { /* INTL: digit */ return -1; } } continue; } /* * Convert character to Unicode for comparison purposes. If either * string is at the terminating null, do a byte-wise comparison and * bail out immediately. */ if ((*left != '\0') && (*right != '\0')) { left += TclUtfToUniChar(left, &uniLeft); right += TclUtfToUniChar(right, &uniRight); /* * Convert both chars to lower for the comparison, because * dictionary sorts are case-insensitive. Covert to lower, not * upper, so chars between Z and a will sort before A (where most * other interesting punctuations occur). */ uniLeftLower = Tcl_UniCharToLower(uniLeft); uniRightLower = Tcl_UniCharToLower(uniRight); } else { diff = UCHAR(*left) - UCHAR(*right); break; } diff = uniLeftLower - uniRightLower; if (diff) { return diff; } if (secondaryDiff == 0) { if (Tcl_UniCharIsUpper(uniLeft) && Tcl_UniCharIsLower(uniRight)) { secondaryDiff = -1; } else if (Tcl_UniCharIsUpper(uniRight) && Tcl_UniCharIsLower(uniLeft)) { secondaryDiff = 1; } } } if (diff == 0) { diff = secondaryDiff; } return diff; } /* *---------------------------------------------------------------------- * * SelectObjFromSublist -- * * This procedure is invoked from lsearch and SortCompare. It is used for * implementing the -index option, for the lsort and lsearch commands. * * Results: * Returns NULL if a failure occurs, and sets the result in the infoPtr. * Otherwise returns the Tcl_Obj* to the item. * * Side effects: * None. * * Note: * No reference counting is done, as the result is only used internally * and never passed directly to user code. * *---------------------------------------------------------------------- */ static Tcl_Obj * SelectObjFromSublist( Tcl_Obj *objPtr, /* Obj to select sublist from. */ SortInfo *infoPtr) /* Information passed from the top-level * "lsearch" or "lsort" command. */ { Tcl_Size i; /* * Quick check for case when no "-index" option is there. */ if (infoPtr->indexc == 0) { return objPtr; } /* * Iterate over the indices, traversing through the nested sublists as we * go. */ for (i=0 ; iindexc ; i++) { Tcl_Size listLen; int index; Tcl_Obj *currentObj, *lastObj=NULL; if (TclListObjLength(infoPtr->interp, objPtr, &listLen) != TCL_OK) { infoPtr->resultCode = TCL_ERROR; return NULL; } index = TclIndexDecode(infoPtr->indexv[i], listLen - 1); if (Tcl_ListObjIndex(infoPtr->interp, objPtr, index, ¤tObj) != TCL_OK) { infoPtr->resultCode = TCL_ERROR; return NULL; } if (currentObj == NULL) { if (index == TCL_INDEX_NONE) { index = TCL_INDEX_END - infoPtr->indexv[i]; Tcl_SetObjResult(infoPtr->interp, Tcl_ObjPrintf( "element end-%d missing from sublist \"%s\"", index, TclGetString(objPtr))); } else { Tcl_SetObjResult(infoPtr->interp, Tcl_ObjPrintf( "element %d missing from sublist \"%s\"", index, TclGetString(objPtr))); } Tcl_SetErrorCode(infoPtr->interp, "TCL", "OPERATION", "LSORT", "INDEXFAILED", (char *)NULL); infoPtr->resultCode = TCL_ERROR; return NULL; } objPtr = currentObj; Tcl_BounceRefCount(lastObj); lastObj = currentObj; } return objPtr; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * End: */ tcl9.0.1/generic/tclCmdMZ.c0000644000175000017500000041234714726623136015026 0ustar sergeisergei/* * tclCmdMZ.c -- * * This file contains the top-level command routines for most of the Tcl * built-in commands whose names begin with the letters M to Z. It * contains only commands in the generic core (i.e. those that don't * depend much upon UNIX facilities). * * Copyright © 1987-1993 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * Copyright © 1998-2000 Scriptics Corporation. * Copyright © 2002 ActiveState Corporation. * Copyright © 2003-2009 Donal K. Fellows. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" #include "tclRegexp.h" #include "tclStringTrim.h" #include "tclTomMath.h" static inline Tcl_Obj * During(Tcl_Interp *interp, int resultCode, Tcl_Obj *oldOptions, Tcl_Obj *errorInfo); static Tcl_NRPostProc SwitchPostProc; static Tcl_NRPostProc TryPostBody; static Tcl_NRPostProc TryPostFinal; static Tcl_NRPostProc TryPostHandler; static int UniCharIsAscii(int character); static int UniCharIsHexDigit(int character); static int StringCmpOpts(Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], int *nocase, Tcl_Size *reqlength); /* * Default set of characters to trim in [string trim] and friends. This is a * UTF-8 literal string containing all Unicode space characters [TIP #413] */ const char tclDefaultTrimSet[] = "\x09\x0A\x0B\x0C\x0D " /* ASCII */ "\xC0\x80" /* nul (U+0000) */ "\xC2\x85" /* next line (U+0085) */ "\xC2\xA0" /* non-breaking space (U+00a0) */ "\xE1\x9A\x80" /* ogham space mark (U+1680) */ "\xE1\xA0\x8E" /* mongolian vowel separator (U+180e) */ "\xE2\x80\x80" /* en quad (U+2000) */ "\xE2\x80\x81" /* em quad (U+2001) */ "\xE2\x80\x82" /* en space (U+2002) */ "\xE2\x80\x83" /* em space (U+2003) */ "\xE2\x80\x84" /* three-per-em space (U+2004) */ "\xE2\x80\x85" /* four-per-em space (U+2005) */ "\xE2\x80\x86" /* six-per-em space (U+2006) */ "\xE2\x80\x87" /* figure space (U+2007) */ "\xE2\x80\x88" /* punctuation space (U+2008) */ "\xE2\x80\x89" /* thin space (U+2009) */ "\xE2\x80\x8A" /* hair space (U+200a) */ "\xE2\x80\x8B" /* zero width space (U+200b) */ "\xE2\x80\xA8" /* line separator (U+2028) */ "\xE2\x80\xA9" /* paragraph separator (U+2029) */ "\xE2\x80\xAF" /* narrow no-break space (U+202f) */ "\xE2\x81\x9F" /* medium mathematical space (U+205f) */ "\xE2\x81\xA0" /* word joiner (U+2060) */ "\xE3\x80\x80" /* ideographic space (U+3000) */ "\xEF\xBB\xBF" /* zero width no-break space (U+feff) */ ; /* *---------------------------------------------------------------------- * * Tcl_PwdObjCmd -- * * This procedure is invoked to process the "pwd" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_PwdObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *retVal; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } retVal = Tcl_FSGetCwd(interp); if (retVal == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, retVal); Tcl_DecrRefCount(retVal); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_RegexpObjCmd -- * * This procedure is invoked to process the "regexp" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_RegexpObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size offset, stringLength, matchLength, cflags, eflags; int i, indices, match, about, all, doinline, numMatchesSaved; Tcl_RegExp regExpr; Tcl_Obj *objPtr, *startIndex = NULL, *resultPtr = NULL; Tcl_RegExpInfo info; static const char *const options[] = { "-all", "-about", "-indices", "-inline", "-expanded", "-line", "-linestop", "-lineanchor", "-nocase", "-start", "--", NULL }; enum regexpoptions { REGEXP_ALL, REGEXP_ABOUT, REGEXP_INDICES, REGEXP_INLINE, REGEXP_EXPANDED,REGEXP_LINE, REGEXP_LINESTOP,REGEXP_LINEANCHOR, REGEXP_NOCASE, REGEXP_START, REGEXP_LAST } index; indices = 0; about = 0; cflags = TCL_REG_ADVANCED; offset = TCL_INDEX_START; all = 0; doinline = 0; for (i = 1; i < objc; i++) { const char *name; name = TclGetString(objv[i]); if (name[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", TCL_EXACT, &index) != TCL_OK) { goto optionError; } switch (index) { case REGEXP_ALL: all = 1; break; case REGEXP_INDICES: indices = 1; break; case REGEXP_INLINE: doinline = 1; break; case REGEXP_NOCASE: cflags |= TCL_REG_NOCASE; break; case REGEXP_ABOUT: about = 1; break; case REGEXP_EXPANDED: cflags |= TCL_REG_EXPANDED; break; case REGEXP_LINE: cflags |= TCL_REG_NEWLINE; break; case REGEXP_LINESTOP: cflags |= TCL_REG_NLSTOP; break; case REGEXP_LINEANCHOR: cflags |= TCL_REG_NLANCH; break; case REGEXP_START: { Tcl_Size temp; if (++i >= objc) { goto endOfForLoop; } if (TclGetIntForIndexM(interp, objv[i], TCL_SIZE_MAX - 1, &temp) != TCL_OK) { goto optionError; } if (startIndex) { Tcl_DecrRefCount(startIndex); } startIndex = objv[i]; Tcl_IncrRefCount(startIndex); break; } case REGEXP_LAST: i++; goto endOfForLoop; } } endOfForLoop: if ((objc - i) < (2 - about)) { Tcl_WrongNumArgs(interp, 1, objv, "?-option ...? exp string ?matchVar? ?subMatchVar ...?"); goto optionError; } objc -= i; objv += i; /* * Check if the user requested -inline, but specified match variables; a * no-no. */ if (doinline && ((objc - 2) != 0)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "regexp match variables not allowed when using -inline", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "REGEXP", "MIX_VAR_INLINE", (char *)NULL); goto optionError; } /* * Handle the odd about case separately. */ if (about) { regExpr = Tcl_GetRegExpFromObj(interp, objv[0], cflags); if ((regExpr == NULL) || (TclRegAbout(interp, regExpr) < 0)) { optionError: if (startIndex) { Tcl_DecrRefCount(startIndex); } return TCL_ERROR; } return TCL_OK; } /* * Get the length of the string that we are matching against so we can do * the termination test for -all matches. Do this before getting the * regexp to avoid shimmering problems. */ objPtr = objv[1]; stringLength = Tcl_GetCharLength(objPtr); if (startIndex) { TclGetIntForIndexM(interp, startIndex, stringLength, &offset); Tcl_DecrRefCount(startIndex); if (offset < 0) { offset = TCL_INDEX_START; } } regExpr = Tcl_GetRegExpFromObj(interp, objv[0], cflags); if (regExpr == NULL) { return TCL_ERROR; } objc -= 2; objv += 2; if (doinline) { /* * Save all the subexpressions, as we will return them as a list */ numMatchesSaved = -1; } else { /* * Save only enough subexpressions for matches we want to keep, expect * in the case of -all, where we need to keep at least one to know * where to move the offset. */ numMatchesSaved = (objc == 0) ? all : objc; } /* * The following loop is to handle multiple matches within the same source * string; each iteration handles one match. If "-all" hasn't been * specified then the loop body only gets executed once. We terminate the * loop when the starting offset is past the end of the string. */ while (1) { /* * Pass either 0 or TCL_REG_NOTBOL in the eflags. Passing * TCL_REG_NOTBOL indicates that the character at offset should not be * considered the start of the line. If for example the pattern {^} is * passed and -start is positive, then the pattern will not match the * start of the string unless the previous character is a newline. */ if (offset == TCL_INDEX_START) { eflags = 0; } else if (offset > stringLength) { eflags = TCL_REG_NOTBOL; } else if (Tcl_GetUniChar(objPtr, offset-1) == '\n') { eflags = 0; } else { eflags = TCL_REG_NOTBOL; } match = Tcl_RegExpExecObj(interp, regExpr, objPtr, offset, numMatchesSaved, eflags); if (match < 0) { return TCL_ERROR; } if (match == 0) { /* * We want to set the value of the interpreter result only when * this is the first time through the loop. */ if (all <= 1) { /* * If inlining, the interpreter's object result remains an * empty list, otherwise set it to an integer object w/ value * 0. */ if (!doinline) { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(0)); } return TCL_OK; } break; } /* * If additional variable names have been specified, return index * information in those variables. */ Tcl_RegExpGetInfo(regExpr, &info); if (doinline) { /* * It's the number of substitutions, plus one for the matchVar at * index 0 */ objc = info.nsubs + 1; if (all <= 1) { TclNewObj(resultPtr); } } for (i = 0; i < objc; i++) { Tcl_Obj *newPtr; if (indices) { Tcl_Size start, end; Tcl_Obj *objs[2]; /* * Only adjust the match area if there was a match for that * area. (Scriptics Bug 4391/SF Bug #219232) */ if (i <= (int)info.nsubs && info.matches[i].start >= 0) { start = offset + info.matches[i].start; end = offset + info.matches[i].end; /* * Adjust index so it refers to the last character in the * match instead of the first character after the match. */ if (end >= offset) { end--; } } else { start = TCL_INDEX_NONE; end = TCL_INDEX_NONE; } TclNewIndexObj(objs[0], start); TclNewIndexObj(objs[1], end); newPtr = Tcl_NewListObj(2, objs); } else { if ((i <= (int)info.nsubs) && (info.matches[i].end > 0)) { newPtr = Tcl_GetRange(objPtr, offset + info.matches[i].start, offset + info.matches[i].end - 1); } else { TclNewObj(newPtr); } } if (doinline) { if (Tcl_ListObjAppendElement(interp, resultPtr, newPtr) != TCL_OK) { Tcl_DecrRefCount(newPtr); Tcl_DecrRefCount(resultPtr); return TCL_ERROR; } } else { if (Tcl_ObjSetVar2(interp, objv[i], NULL, newPtr, TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } } } if (all == 0) { break; } /* * Adjust the offset to the character just after the last one in the * matchVar and increment all to count how many times we are making a * match. We always increment the offset by at least one to prevent * endless looping (as in the case: regexp -all {a*} a). Otherwise, * when we match the NULL string at the end of the input string, we * will loop indefinitely (because the length of the match is 0, so * offset never changes). */ matchLength = (info.matches[0].end - info.matches[0].start); offset += info.matches[0].end; /* * A match of length zero could happen for {^} {$} or {.*} and in * these cases we always want to bump the index up one. */ if (matchLength == 0) { offset++; } all++; if (offset >= stringLength) { break; } } /* * Set the interpreter's object result to an integer object with value 1 * if -all wasn't specified, otherwise it's all-1 (the number of times * through the while - 1). */ if (doinline) { Tcl_SetObjResult(interp, resultPtr); } else { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(all ? all-1 : 1)); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_RegsubObjCmd -- * * This procedure is invoked to process the "regsub" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_RegsubObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int result, cflags, all, match, command; Tcl_Size idx, wlen, wsublen = 0, offset, numMatches, numParts; Tcl_Size start, end, subStart, subEnd; Tcl_RegExp regExpr; Tcl_RegExpInfo info; Tcl_Obj *resultPtr, *subPtr, *objPtr, *startIndex = NULL; Tcl_UniChar ch, *wsrc, *wfirstChar, *wstring, *wsubspec = 0, *wend; static const char *const options[] = { "-all", "-command", "-expanded", "-line", "-linestop", "-lineanchor", "-nocase", "-start", "--", NULL }; enum regsubobjoptions { REGSUB_ALL, REGSUB_COMMAND, REGSUB_EXPANDED, REGSUB_LINE, REGSUB_LINESTOP, REGSUB_LINEANCHOR, REGSUB_NOCASE, REGSUB_START, REGSUB_LAST } index; cflags = TCL_REG_ADVANCED; all = 0; offset = TCL_INDEX_START; command = 0; resultPtr = NULL; for (idx = 1; idx < objc; idx++) { const char *name; name = TclGetString(objv[idx]); if (name[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, objv[idx], options, "option", TCL_EXACT, &index) != TCL_OK) { goto optionError; } switch (index) { case REGSUB_ALL: all = 1; break; case REGSUB_NOCASE: cflags |= TCL_REG_NOCASE; break; case REGSUB_COMMAND: command = 1; break; case REGSUB_EXPANDED: cflags |= TCL_REG_EXPANDED; break; case REGSUB_LINE: cflags |= TCL_REG_NEWLINE; break; case REGSUB_LINESTOP: cflags |= TCL_REG_NLSTOP; break; case REGSUB_LINEANCHOR: cflags |= TCL_REG_NLANCH; break; case REGSUB_START: { Tcl_Size temp; if (++idx >= objc) { goto endOfForLoop; } if (TclGetIntForIndexM(interp, objv[idx], TCL_SIZE_MAX - 1, &temp) != TCL_OK) { goto optionError; } if (startIndex) { Tcl_DecrRefCount(startIndex); } startIndex = objv[idx]; Tcl_IncrRefCount(startIndex); break; } case REGSUB_LAST: idx++; goto endOfForLoop; } } endOfForLoop: if (objc < idx + 3 || objc > idx + 4) { Tcl_WrongNumArgs(interp, 1, objv, "?-option ...? exp string subSpec ?varName?"); optionError: if (startIndex) { Tcl_DecrRefCount(startIndex); } return TCL_ERROR; } objc -= idx; objv += idx; if (startIndex) { Tcl_Size stringLength = Tcl_GetCharLength(objv[1]); TclGetIntForIndexM(interp, startIndex, stringLength, &offset); Tcl_DecrRefCount(startIndex); if (offset < 0) { offset = 0; } } if (all && (offset == 0) && (command == 0) && (strpbrk(TclGetString(objv[2]), "&\\") == NULL) && (strpbrk(TclGetString(objv[0]), "*+?{}()[].\\|^$") == NULL)) { /* * This is a simple one pair string map situation. We make use of a * slightly modified version of the one pair STR_MAP code. */ Tcl_Size slen; int nocase, wsrclc; int (*strCmpFn)(const Tcl_UniChar*,const Tcl_UniChar*,size_t); Tcl_UniChar *p; numMatches = 0; nocase = (cflags & TCL_REG_NOCASE); strCmpFn = nocase ? TclUniCharNcasecmp : TclUniCharNcmp; wsrc = Tcl_GetUnicodeFromObj(objv[0], &slen); wstring = Tcl_GetUnicodeFromObj(objv[1], &wlen); wsubspec = Tcl_GetUnicodeFromObj(objv[2], &wsublen); wend = wstring + wlen - (slen ? slen - 1 : 0); result = TCL_OK; if (slen == 0) { /* * regsub behavior for "" matches between each character. 'string * map' skips the "" case. */ if (wstring < wend) { resultPtr = Tcl_NewUnicodeObj(wstring, 0); Tcl_IncrRefCount(resultPtr); for (; wstring < wend; wstring++) { Tcl_AppendUnicodeToObj(resultPtr, wsubspec, wsublen); Tcl_AppendUnicodeToObj(resultPtr, wstring, 1); numMatches++; } wlen = 0; } } else { wsrclc = Tcl_UniCharToLower(*wsrc); for (p = wfirstChar = wstring; wstring < wend; wstring++) { if ((*wstring == *wsrc || (nocase && Tcl_UniCharToLower(*wstring)==wsrclc)) && (slen==1 || (strCmpFn(wstring, wsrc, slen) == 0))) { if (numMatches == 0) { resultPtr = Tcl_NewUnicodeObj(wstring, 0); Tcl_IncrRefCount(resultPtr); } if (p != wstring) { Tcl_AppendUnicodeToObj(resultPtr, p, wstring - p); p = wstring + slen; } else { p += slen; } wstring = p - 1; Tcl_AppendUnicodeToObj(resultPtr, wsubspec, wsublen); numMatches++; } } if (numMatches) { wlen = wfirstChar + wlen - p; wstring = p; } } objPtr = NULL; subPtr = NULL; goto regsubDone; } regExpr = Tcl_GetRegExpFromObj(interp, objv[0], cflags); if (regExpr == NULL) { return TCL_ERROR; } if (command) { /* * In command-prefix mode, we require that the third non-option * argument be a list, so we enforce that here. Afterwards, we fetch * the RE compilation again in case objv[0] and objv[2] are the same * object. (If they aren't, that's cheap to do.) */ if (TclListObjLength(interp, objv[2], &numParts) != TCL_OK) { return TCL_ERROR; } if (numParts < 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "command prefix must be a list of at least one element", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "REGSUB", "CMDEMPTY", (char *)NULL); return TCL_ERROR; } regExpr = Tcl_GetRegExpFromObj(interp, objv[0], cflags); } /* * Make sure to avoid problems where the objects are shared. This can * cause RegExpObj <> UnicodeObj shimmering that causes data corruption. * [Bug #461322] */ if (objv[1] == objv[0]) { objPtr = Tcl_DuplicateObj(objv[1]); } else { objPtr = objv[1]; } wstring = Tcl_GetUnicodeFromObj(objPtr, &wlen); if (objv[2] == objv[0]) { subPtr = Tcl_DuplicateObj(objv[2]); } else { subPtr = objv[2]; } if (!command) { wsubspec = Tcl_GetUnicodeFromObj(subPtr, &wsublen); } result = TCL_OK; /* * The following loop is to handle multiple matches within the same source * string; each iteration handles one match and its corresponding * substitution. If "-all" hasn't been specified then the loop body only * gets executed once. We must use 'offset <= wlen' in particular for the * case where the regexp pattern can match the empty string - this is * useful when doing, say, 'regsub -- ^ $str ...' when $str might be * empty. */ numMatches = 0; for ( ; offset <= wlen; ) { /* * The flags argument is set if string is part of a larger string, so * that "^" won't match. */ match = Tcl_RegExpExecObj(interp, regExpr, objPtr, offset, 10 /* matches */, ((offset > 0 && (wstring[offset-1] != (Tcl_UniChar)'\n')) ? TCL_REG_NOTBOL : 0)); if (match < 0) { result = TCL_ERROR; goto done; } if (match == 0) { break; } if (numMatches == 0) { resultPtr = Tcl_NewUnicodeObj(wstring, 0); Tcl_IncrRefCount(resultPtr); if (offset > TCL_INDEX_START) { /* * Copy the initial portion of the string in if an offset was * specified. */ Tcl_AppendUnicodeToObj(resultPtr, wstring, offset); } } numMatches++; /* * Copy the portion of the source string before the match to the * result variable. */ Tcl_RegExpGetInfo(regExpr, &info); start = info.matches[0].start; end = info.matches[0].end; Tcl_AppendUnicodeToObj(resultPtr, wstring + offset, start); /* * In command-prefix mode, the substitutions are added as quoted * arguments to the subSpec to form a command, that is then executed * and the result used as the string to substitute in. Actually, * everything is passed through Tcl_EvalObjv, as that's much faster. */ if (command) { Tcl_Obj **args = NULL, **parts; Tcl_Size numArgs; TclListObjGetElements(interp, subPtr, &numParts, &parts); numArgs = numParts + info.nsubs + 1; args = (Tcl_Obj **)Tcl_Alloc(sizeof(Tcl_Obj*) * numArgs); memcpy(args, parts, sizeof(Tcl_Obj*) * numParts); for (idx = 0 ; idx <= info.nsubs ; idx++) { subStart = info.matches[idx].start; subEnd = info.matches[idx].end; if ((subStart >= 0) && (subEnd >= 0)) { args[idx + numParts] = Tcl_NewUnicodeObj( wstring + offset + subStart, subEnd - subStart); } else { args[idx + numParts] = Tcl_NewObj(); } Tcl_IncrRefCount(args[idx + numParts]); } /* * At this point, we're locally holding the references to the * argument words we added for this time round the loop, and the * subPtr is holding the references to the words that the user * supplied directly. None are zero-refcount, which is important * because Tcl_EvalObjv is "hairy monster" in terms of refcount * handling, being able to optionally add references to any of its * argument words. We'll drop the local refs immediately * afterwards; subPtr is handled in the main exit stanza. */ result = Tcl_EvalObjv(interp, numArgs, args, 0); for (idx = 0 ; idx <= info.nsubs ; idx++) { TclDecrRefCount(args[idx + numParts]); } Tcl_Free(args); if (result != TCL_OK) { if (result == TCL_ERROR) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (%s substitution computation script)", options[REGSUB_COMMAND])); } goto done; } Tcl_AppendObjToObj(resultPtr, Tcl_GetObjResult(interp)); Tcl_ResetResult(interp); /* * Refetch the unicode, in case the representation was smashed by * the user code. */ wstring = Tcl_GetUnicodeFromObj(objPtr, &wlen); offset += end; if (end == 0 || start == end) { /* * Always consume at least one character of the input string * in order to prevent infinite loops, even when we * technically matched the empty string; we must not match * again at the same spot. */ if (offset < wlen) { Tcl_AppendUnicodeToObj(resultPtr, wstring + offset, 1); } offset++; } if (all) { continue; } else { break; } } /* * Append the subSpec argument to the variable, making appropriate * substitutions. This code is a bit hairy because of the backslash * conventions and because the code saves up ranges of characters in * subSpec to reduce the number of calls to Tcl_SetVar. */ wsrc = wfirstChar = wsubspec; wend = wsubspec + wsublen; for (ch = *wsrc; wsrc != wend; wsrc++, ch = *wsrc) { if (ch == '&') { idx = 0; } else if (ch == '\\') { ch = wsrc[1]; if ((ch >= '0') && (ch <= '9')) { idx = ch - '0'; } else if ((ch == '\\') || (ch == '&')) { *wsrc = ch; Tcl_AppendUnicodeToObj(resultPtr, wfirstChar, wsrc - wfirstChar + 1); *wsrc = '\\'; wfirstChar = wsrc + 2; wsrc++; continue; } else { continue; } } else { continue; } if (wfirstChar != wsrc) { Tcl_AppendUnicodeToObj(resultPtr, wfirstChar, wsrc - wfirstChar); } if (idx <= info.nsubs) { subStart = info.matches[idx].start; subEnd = info.matches[idx].end; if ((subStart >= 0) && (subEnd >= 0)) { Tcl_AppendUnicodeToObj(resultPtr, wstring + offset + subStart, subEnd - subStart); } } if (*wsrc == '\\') { wsrc++; } wfirstChar = wsrc + 1; } if (wfirstChar != wsrc) { Tcl_AppendUnicodeToObj(resultPtr, wfirstChar, wsrc - wfirstChar); } if (end == 0) { /* * Always consume at least one character of the input string in * order to prevent infinite loops. */ if (offset < wlen) { Tcl_AppendUnicodeToObj(resultPtr, wstring + offset, 1); } offset++; } else { offset += end; if (start == end) { /* * We matched an empty string, which means we must go forward * one more step so we don't match again at the same spot. */ if (offset < wlen) { Tcl_AppendUnicodeToObj(resultPtr, wstring + offset, 1); } offset++; } } if (!all) { break; } } /* * Copy the portion of the source string after the last match to the * result variable. */ regsubDone: if (numMatches == 0) { /* * On zero matches, just ignore the offset, since it shouldn't matter * to us in this case, and the user may have skewed it. */ resultPtr = objv[1]; Tcl_IncrRefCount(resultPtr); } else if (offset < wlen) { Tcl_AppendUnicodeToObj(resultPtr, wstring + offset, wlen - offset); } if (objc == 4) { if (Tcl_ObjSetVar2(interp, objv[3], NULL, resultPtr, TCL_LEAVE_ERR_MSG) == NULL) { result = TCL_ERROR; } else { /* * Set the interpreter's object result to an integer object * holding the number of matches. */ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(numMatches)); } } else { /* * No varname supplied, so just return the modified string. */ Tcl_SetObjResult(interp, resultPtr); } done: if (objPtr && (objv[1] == objv[0])) { Tcl_DecrRefCount(objPtr); } if (subPtr && (objv[2] == objv[0])) { Tcl_DecrRefCount(subPtr); } if (resultPtr) { Tcl_DecrRefCount(resultPtr); } return result; } /* *---------------------------------------------------------------------- * * Tcl_RenameObjCmd -- * * This procedure is invoked to process the "rename" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_RenameObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *oldName, *newName; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "oldName newName"); return TCL_ERROR; } oldName = TclGetString(objv[1]); newName = TclGetString(objv[2]); return TclRenameCommand(interp, oldName, newName); } /* *---------------------------------------------------------------------- * * Tcl_ReturnObjCmd -- * * This object-based procedure is invoked to process the "return" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_ReturnObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int code, level; Tcl_Obj *returnOpts; /* * General syntax: [return ?-option value ...? ?result?] * An even number of words means an explicit result argument is present. */ int explicitResult = (0 == (objc % 2)); int numOptionWords = objc - 1 - explicitResult; if (TCL_ERROR == TclMergeReturnOptions(interp, numOptionWords, objv+1, &returnOpts, &code, &level)) { return TCL_ERROR; } code = TclProcessReturn(interp, code, level, returnOpts); if (explicitResult) { Tcl_SetObjResult(interp, objv[objc-1]); } return code; } /* *---------------------------------------------------------------------- * * Tcl_SourceObjCmd -- * * This procedure is invoked to process the "source" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_SourceObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRSourceObjCmd, clientData, objc, objv); } int TclNRSourceObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *encodingName = NULL; Tcl_Obj *fileName; int result; void **pkgFiles = NULL; void *names = NULL; if (objc < 2 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "?-encoding encoding? fileName"); return TCL_ERROR; } fileName = objv[objc-1]; if (objc == 4) { static const char *const options[] = { "-encoding", NULL }; int index; if (TCL_ERROR == Tcl_GetIndexFromObj(interp, objv[1], options, "option", TCL_EXACT, &index)) { return TCL_ERROR; } encodingName = TclGetString(objv[2]); } else if (objc == 3) { /* Handle undocumented -nopkg option. This should only be * used by the internal ::tcl::Pkg::source utility function. */ static const char *const nopkgoptions[] = { "-nopkg", NULL }; int index; if (TCL_ERROR == Tcl_GetIndexFromObj(interp, objv[1], nopkgoptions, "option", TCL_EXACT, &index)) { return TCL_ERROR; } pkgFiles = (void **)Tcl_GetAssocData(interp, "tclPkgFiles", NULL); /* Make sure that during the following TclNREvalFile no filenames * are recorded for inclusion in the "package files" command */ names = *pkgFiles; *pkgFiles = NULL; } result = TclNREvalFile(interp, fileName, encodingName); if (pkgFiles) { /* restore "tclPkgFiles" assocdata to how it was. */ *pkgFiles = names; } return result; } /* *---------------------------------------------------------------------- * * Tcl_SplitObjCmd -- * * This procedure is invoked to process the "split" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_SplitObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int ch = 0; int len; const char *splitChars; const char *stringPtr; const char *end; Tcl_Size splitCharLen, stringLen; Tcl_Obj *listPtr, *objPtr; if (objc == 2) { splitChars = " \n\t\r"; splitCharLen = 4; } else if (objc == 3) { splitChars = TclGetStringFromObj(objv[2], &splitCharLen); } else { Tcl_WrongNumArgs(interp, 1, objv, "string ?splitChars?"); return TCL_ERROR; } stringPtr = TclGetStringFromObj(objv[1], &stringLen); end = stringPtr + stringLen; TclNewObj(listPtr); if (stringLen == 0) { /* * Do nothing. */ } else if (splitCharLen == 0) { Tcl_HashTable charReuseTable; Tcl_HashEntry *hPtr; int isNew; /* * Handle the special case of splitting on every character. * * Uses a hash table to ensure that each kind of character has only * one Tcl_Obj instance (multiply-referenced) in the final list. This * is a *major* win when splitting on a long string (especially in the * megabyte range!) - DKF */ Tcl_InitHashTable(&charReuseTable, TCL_ONE_WORD_KEYS); for ( ; stringPtr < end; stringPtr += len) { len = TclUtfToUniChar(stringPtr, &ch); hPtr = Tcl_CreateHashEntry(&charReuseTable, INT2PTR(ch), &isNew); if (isNew) { TclNewStringObj(objPtr, stringPtr, len); /* * Don't need to fiddle with refcount... */ Tcl_SetHashValue(hPtr, objPtr); } else { objPtr = (Tcl_Obj *)Tcl_GetHashValue(hPtr); } Tcl_ListObjAppendElement(NULL, listPtr, objPtr); } Tcl_DeleteHashTable(&charReuseTable); } else if (splitCharLen == 1) { const char *p; /* * Handle the special case of splitting on a single character. This is * only true for the one-char ASCII case, as one Unicode char is > 1 * byte in length. */ while (*stringPtr && (p=strchr(stringPtr,*splitChars)) != NULL) { objPtr = Tcl_NewStringObj(stringPtr, p - stringPtr); Tcl_ListObjAppendElement(NULL, listPtr, objPtr); stringPtr = p + 1; } TclNewStringObj(objPtr, stringPtr, end - stringPtr); Tcl_ListObjAppendElement(NULL, listPtr, objPtr); } else { const char *element, *p, *splitEnd; Tcl_Size splitLen; int splitChar; /* * Normal case: split on any of a given set of characters. Discard * instances of the split characters. */ splitEnd = splitChars + splitCharLen; for (element = stringPtr; stringPtr < end; stringPtr += len) { len = TclUtfToUniChar(stringPtr, &ch); for (p = splitChars; p < splitEnd; p += splitLen) { splitLen = TclUtfToUniChar(p, &splitChar); if (ch == splitChar) { TclNewStringObj(objPtr, element, stringPtr - element); Tcl_ListObjAppendElement(NULL, listPtr, objPtr); element = stringPtr + len; break; } } } TclNewStringObj(objPtr, element, stringPtr - element); Tcl_ListObjAppendElement(NULL, listPtr, objPtr); } Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * StringFirstCmd -- * * This procedure is invoked to process the "string first" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringFirstCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size start = TCL_INDEX_START; if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "needleString haystackString ?startIndex?"); return TCL_ERROR; } if (objc == 4) { Tcl_Size end = Tcl_GetCharLength(objv[2]) - 1; if (TCL_OK != TclGetIntForIndexM(interp, objv[3], end, &start)) { return TCL_ERROR; } } Tcl_SetObjResult(interp, TclStringFirst(objv[1], objv[2], start)); return TCL_OK; } /* *---------------------------------------------------------------------- * * StringLastCmd -- * * This procedure is invoked to process the "string last" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringLastCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size last = TCL_SIZE_MAX; if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "needleString haystackString ?lastIndex?"); return TCL_ERROR; } if (objc == 4) { Tcl_Size end = Tcl_GetCharLength(objv[2]) - 1; if (TCL_OK != TclGetIntForIndexM(interp, objv[3], end, &last)) { return TCL_ERROR; } } Tcl_SetObjResult(interp, TclStringLast(objv[1], objv[2], last)); return TCL_OK; } /* *---------------------------------------------------------------------- * * StringIndexCmd -- * * This procedure is invoked to process the "string index" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringIndexCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size index, end; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "string charIndex"); return TCL_ERROR; } /* * Get the char length to calculate what 'end' means. */ end = Tcl_GetCharLength(objv[1]) - 1; if (TclGetIntForIndexM(interp, objv[2], end, &index) != TCL_OK) { return TCL_ERROR; } if ((index >= 0) && (index <= end)) { int ch = Tcl_GetUniChar(objv[1], index); if (ch == -1) { return TCL_OK; } /* * If we have a ByteArray object, we're careful to generate a new * bytearray for a result. */ if (TclIsPureByteArray(objv[1])) { unsigned char uch = UCHAR(ch); Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(&uch, 1)); } else { char buf[4] = ""; end = Tcl_UniCharToUtf(ch, buf); Tcl_SetObjResult(interp, Tcl_NewStringObj(buf, end)); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * StringInsertCmd -- * * This procedure is invoked to process the "string insert" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringInsertCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter */ int objc, /* Number of arguments */ Tcl_Obj *const objv[]) /* Argument objects */ { Tcl_Size length; /* String length */ Tcl_Size index; /* Insert index */ Tcl_Obj *outObj; /* Output object */ if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "string index insertString"); return TCL_ERROR; } length = Tcl_GetCharLength(objv[1]); if (TclGetIntForIndexM(interp, objv[2], length, &index) != TCL_OK) { return TCL_ERROR; } if (index < 0) { index = TCL_INDEX_START; } if (index > length) { index = length; } outObj = TclStringReplace(interp, objv[1], index, 0, objv[3], TCL_STRING_IN_PLACE); if (outObj != NULL) { Tcl_SetObjResult(interp, outObj); return TCL_OK; } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * StringIsCmd -- * * This procedure is invoked to process the "string is" Tcl command. See * the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringIsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *string1, *end, *stop; int (*chcomp)(int) = NULL; /* The UniChar comparison function. */ int i, result = 1, strict = 0; Tcl_Size failat = 0, length1, length2, length3; Tcl_Obj *objPtr, *failVarObj = NULL; Tcl_WideInt w; static const char *const isClasses[] = { "alnum", "alpha", "ascii", "control", "boolean", "dict", "digit", "double", "entier", "false", "graph", "integer", "list", "lower", "print", "punct", "space", "true", "upper", "wideinteger", "wordchar", "xdigit", NULL }; enum isClassesEnum { STR_IS_ALNUM, STR_IS_ALPHA, STR_IS_ASCII, STR_IS_CONTROL, STR_IS_BOOL, STR_IS_DICT, STR_IS_DIGIT, STR_IS_DOUBLE, STR_IS_ENTIER, STR_IS_FALSE, STR_IS_GRAPH, STR_IS_INT, STR_IS_LIST, STR_IS_LOWER, STR_IS_PRINT, STR_IS_PUNCT, STR_IS_SPACE, STR_IS_TRUE, STR_IS_UPPER, STR_IS_WIDE, STR_IS_WORD, STR_IS_XDIGIT } index; static const char *const isOptions[] = { "-strict", "-failindex", NULL }; enum isOptionsEnum { OPT_STRICT, OPT_FAILIDX } idx2; if (objc < 3 || objc > 6) { Tcl_WrongNumArgs(interp, 1, objv, "class ?-strict? ?-failindex var? str"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], isClasses, "class", 0, &index) != TCL_OK) { return TCL_ERROR; } if (objc != 3) { for (i = 2; i < objc-1; i++) { if (Tcl_GetIndexFromObj(interp, objv[i], isOptions, "option", 0, &idx2) != TCL_OK) { return TCL_ERROR; } switch (idx2) { case OPT_STRICT: strict = 1; break; case OPT_FAILIDX: if (i+1 >= objc-1) { Tcl_WrongNumArgs(interp, 2, objv, "?-strict? ?-failindex var? str"); return TCL_ERROR; } failVarObj = objv[++i]; break; } } } /* * We get the objPtr so that we can short-cut for some classes by checking * the object type (int and double), but we need the string otherwise, * because we don't want any conversion of type occurring (as, for example, * Tcl_Get*FromObj would do). */ objPtr = objv[objc-1]; /* * When entering here, result == 1 and failat == 0. */ switch (index) { case STR_IS_ALNUM: chcomp = Tcl_UniCharIsAlnum; break; case STR_IS_ALPHA: chcomp = Tcl_UniCharIsAlpha; break; case STR_IS_ASCII: chcomp = UniCharIsAscii; break; case STR_IS_BOOL: case STR_IS_TRUE: case STR_IS_FALSE: if (!TclHasInternalRep(objPtr, &tclBooleanType) && (TCL_OK != TclSetBooleanFromAny(NULL, objPtr))) { if (strict) { result = 0; } else { string1 = TclGetStringFromObj(objPtr, &length1); result = length1 == 0; } } else if ((objPtr->internalRep.wideValue != 0) ? (index == STR_IS_FALSE) : (index == STR_IS_TRUE)) { result = 0; } break; case STR_IS_CONTROL: chcomp = Tcl_UniCharIsControl; break; case STR_IS_DICT: { int dresult; Tcl_Size dsize; dresult = Tcl_DictObjSize(interp, objPtr, &dsize); Tcl_ResetResult(interp); result = (dresult == TCL_OK) ? 1 : 0; if (dresult != TCL_OK && failVarObj != NULL) { /* * Need to figure out where the list parsing failed, which is * fairly expensive. This is adapted from the core of * SetDictFromAny(). */ const char *elemStart, *nextElem; Tcl_Size lenRemain, elemSize; const char *p; string1 = TclGetStringFromObj(objPtr, &length1); end = string1 + length1; failat = -1; for (p=string1, lenRemain=length1; lenRemain > 0; p=nextElem, lenRemain=end-nextElem) { if (TCL_ERROR == TclFindElement(NULL, p, lenRemain, &elemStart, &nextElem, &elemSize, NULL)) { Tcl_Obj *tmpStr; /* * This is the simplest way of getting the number of * characters parsed. Note that this is not the same as * the number of bytes when parsing strings with non-ASCII * characters in them. * * Skip leading spaces first. This is only really an issue * if it is the first "element" that has the failure. */ while (TclIsSpaceProc(*p)) { p++; } TclNewStringObj(tmpStr, string1, p-string1); failat = Tcl_GetCharLength(tmpStr); TclDecrRefCount(tmpStr); break; } } } break; } case STR_IS_DIGIT: chcomp = Tcl_UniCharIsDigit; break; case STR_IS_DOUBLE: { if (TclHasInternalRep(objPtr, &tclDoubleType) || TclHasInternalRep(objPtr, &tclIntType) || TclHasInternalRep(objPtr, &tclBignumType)) { break; } string1 = TclGetStringFromObj(objPtr, &length1); if (length1 == 0) { if (strict) { result = 0; } goto str_is_done; } end = string1 + length1; if (TclParseNumber(NULL, objPtr, NULL, NULL, TCL_INDEX_NONE, (const char **) &stop, 0) != TCL_OK) { result = 0; failat = 0; } else { failat = stop - string1; if (stop < end) { result = 0; TclFreeInternalRep(objPtr); } } break; } case STR_IS_GRAPH: chcomp = Tcl_UniCharIsGraph; break; case STR_IS_INT: case STR_IS_ENTIER: if (TclHasInternalRep(objPtr, &tclIntType) || TclHasInternalRep(objPtr, &tclBignumType)) { break; } string1 = TclGetStringFromObj(objPtr, &length1); if (length1 == 0) { if (strict) { result = 0; } goto str_is_done; } end = string1 + length1; if (TclParseNumber(NULL, objPtr, NULL, NULL, TCL_INDEX_NONE, (const char **) &stop, TCL_PARSE_INTEGER_ONLY) == TCL_OK) { if (stop == end) { /* * Entire string parses as an integer. */ break; } else { /* * Some prefix parsed as an integer, but not the whole string, * so return failure index as the point where parsing stopped. * Clear out the internal rep, since keeping it would leave * *objPtr in an inconsistent state. */ result = 0; failat = stop - string1; TclFreeInternalRep(objPtr); } } else { /* * No prefix is a valid integer. Fail at beginning. */ result = 0; failat = 0; } break; case STR_IS_WIDE: if (TCL_OK == TclGetWideIntFromObj(NULL, objPtr, &w)) { break; } string1 = TclGetStringFromObj(objPtr, &length1); if (length1 == 0) { if (strict) { result = 0; } goto str_is_done; } result = 0; if (failVarObj == NULL) { /* * Don't bother computing the failure point if we're not going to * return it. */ break; } end = string1 + length1; if (TclParseNumber(NULL, objPtr, NULL, NULL, TCL_INDEX_NONE, (const char **) &stop, TCL_PARSE_INTEGER_ONLY) == TCL_OK) { if (stop == end) { /* * Entire string parses as an integer, but rejected by * Tcl_Get(Wide)IntFromObj() so we must have overflowed the * target type, and our convention is to return failure at * index -1 in that situation. */ failat = -1; } else { /* * Some prefix parsed as an integer, but not the whole string, * so return failure index as the point where parsing stopped. * Clear out the internal rep, since keeping it would leave * *objPtr in an inconsistent state. */ failat = stop - string1; TclFreeInternalRep(objPtr); } } else { /* * No prefix is a valid integer. Fail at beginning. */ failat = 0; } break; case STR_IS_LIST: /* * We ignore the strictness here, since empty strings are always * well-formed lists. */ if (TCL_OK == TclListObjLength(NULL, objPtr, &length3)) { break; } if (failVarObj != NULL) { /* * Need to figure out where the list parsing failed, which is * fairly expensive. This is adapted from the core of * SetListFromAny(). */ const char *elemStart, *nextElem; Tcl_Size lenRemain; Tcl_Size elemSize; const char *p; string1 = TclGetStringFromObj(objPtr, &length1); end = string1 + length1; failat = -1; for (p=string1, lenRemain=length1; lenRemain > 0; p=nextElem, lenRemain=end-nextElem) { if (TCL_ERROR == TclFindElement(NULL, p, lenRemain, &elemStart, &nextElem, &elemSize, NULL)) { Tcl_Obj *tmpStr; /* * This is the simplest way of getting the number of * characters parsed. Note that this is not the same as * the number of bytes when parsing strings with non-ASCII * characters in them. * * Skip leading spaces first. This is only really an issue * if it is the first "element" that has the failure. */ while (TclIsSpaceProcM(*p)) { p++; } TclNewStringObj(tmpStr, string1, p-string1); failat = Tcl_GetCharLength(tmpStr); TclDecrRefCount(tmpStr); break; } } } result = 0; break; case STR_IS_LOWER: chcomp = Tcl_UniCharIsLower; break; case STR_IS_PRINT: chcomp = Tcl_UniCharIsPrint; break; case STR_IS_PUNCT: chcomp = Tcl_UniCharIsPunct; break; case STR_IS_SPACE: chcomp = Tcl_UniCharIsSpace; break; case STR_IS_UPPER: chcomp = Tcl_UniCharIsUpper; break; case STR_IS_WORD: chcomp = Tcl_UniCharIsWordChar; break; case STR_IS_XDIGIT: chcomp = UniCharIsHexDigit; break; } if (chcomp != NULL) { string1 = TclGetStringFromObj(objPtr, &length1); if (length1 == 0) { if (strict) { result = 0; } goto str_is_done; } end = string1 + length1; for (; string1 < end; string1 += length2, failat++) { int ucs4; length2 = TclUtfToUniChar(string1, &ucs4); if (!chcomp(ucs4)) { result = 0; break; } } } /* * Only set the failVarObj when we will return 0 and we have indicated a * valid fail index (>= 0). */ str_is_done: if ((result == 0) && (failVarObj != NULL)) { TclNewIndexObj(objPtr, failat); if (Tcl_ObjSetVar2(interp, failVarObj, NULL, objPtr, TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result)); return TCL_OK; } static int UniCharIsAscii( int character) { return (character >= 0) && (character < 0x80); } static int UniCharIsHexDigit( int character) { return (character >= 0) && (character < 0x80) && isxdigit(UCHAR(character)); } /* *---------------------------------------------------------------------- * * StringMapCmd -- * * This procedure is invoked to process the "string map" Tcl command. See * the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringMapCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size length1, length2, mapElemc, index; int nocase = 0, mapWithDict = 0, copySource = 0; Tcl_Obj **mapElemv, *sourceObj, *resultPtr; Tcl_UniChar *ustring1, *ustring2, *p, *end; int (*strCmpFn)(const Tcl_UniChar*, const Tcl_UniChar*, size_t); if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "?-nocase? charMap string"); return TCL_ERROR; } if (objc == 4) { const char *string = TclGetStringFromObj(objv[1], &length2); if ((length2 > 1) && strncmp(string, "-nocase", length2) == 0) { nocase = 1; } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad option \"%s\": must be -nocase", string)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "INDEX", "option", string, (char *)NULL); return TCL_ERROR; } } /* * This test is tricky, but has to be that way or you get other strange * inconsistencies (see test string-10.20.1 for illustration why!) */ if (!TclHasStringRep(objv[objc-2]) && TclHasInternalRep(objv[objc-2], &tclDictType)) { Tcl_Size i; int done; Tcl_DictSearch search; /* * We know the type exactly, so all dict operations will succeed for * sure. This shortens this code quite a bit. */ Tcl_DictObjSize(interp, objv[objc-2], &i); if (i == 0) { /* * Empty charMap, just return whatever string was given. */ Tcl_SetObjResult(interp, objv[objc-1]); return TCL_OK; } mapElemc = 2 * i; mapWithDict = 1; /* * Copy the dictionary out into an array; that's the easiest way to * adapt this code... */ mapElemv = (Tcl_Obj **)TclStackAlloc(interp, sizeof(Tcl_Obj *) * mapElemc); Tcl_DictObjFirst(interp, objv[objc-2], &search, mapElemv+0, mapElemv+1, &done); for (index=2 ; index30% faster on * larger strings. */ Tcl_Size mapLen; int u2lc; Tcl_UniChar *mapString; ustring2 = Tcl_GetUnicodeFromObj(mapElemv[0], &length2); p = ustring1; if ((length2 > length1) || (length2 == 0)) { /* * Match string is either longer than input or empty. */ ustring1 = end; } else { mapString = Tcl_GetUnicodeFromObj(mapElemv[1], &mapLen); u2lc = (nocase ? Tcl_UniCharToLower(*ustring2) : 0); for (; ustring1 < end; ustring1++) { if (((*ustring1 == *ustring2) || (nocase&&Tcl_UniCharToLower(*ustring1)==u2lc)) && (length2==1 || strCmpFn(ustring1, ustring2, length2) == 0)) { if (p != ustring1) { Tcl_AppendUnicodeToObj(resultPtr, p, ustring1-p); p = ustring1 + length2; } else { p += length2; } ustring1 = p - 1; Tcl_AppendUnicodeToObj(resultPtr, mapString, mapLen); } } } } else { Tcl_UniChar **mapStrings; Tcl_Size *mapLens; int *u2lc = 0; /* * Precompute pointers to the Unicode string and length. This saves us * repeated function calls later, significantly speeding up the * algorithm. We only need the lowercase first char in the nocase * case. */ mapStrings = (Tcl_UniChar **)TclStackAlloc(interp, mapElemc*sizeof(Tcl_UniChar *)*2); mapLens = (Tcl_Size *)TclStackAlloc(interp, mapElemc * sizeof(Tcl_Size) * 2); if (nocase) { u2lc = (int *)TclStackAlloc(interp, mapElemc * sizeof(int)); } for (index = 0; index < mapElemc; index++) { mapStrings[index] = Tcl_GetUnicodeFromObj(mapElemv[index], mapLens+index); if (nocase && ((index % 2) == 0)) { u2lc[index/2] = Tcl_UniCharToLower(*mapStrings[index]); } } for (p = ustring1; ustring1 < end; ustring1++) { for (index = 0; index < mapElemc; index += 2) { /* * Get the key string to match on. */ ustring2 = mapStrings[index]; length2 = mapLens[index]; if ((length2 > 0) && ((*ustring1 == *ustring2) || (nocase && (Tcl_UniCharToLower(*ustring1) == u2lc[index/2]))) && /* Restrict max compare length. */ ((end-ustring1) >= length2) && ((length2 == 1) || !strCmpFn(ustring2, ustring1, length2))) { if (p != ustring1) { /* * Put the skipped chars onto the result first. */ Tcl_AppendUnicodeToObj(resultPtr, p, ustring1-p); p = ustring1 + length2; } else { p += length2; } /* * Adjust len to be full length of matched string. */ ustring1 = p - 1; /* * Append the map value to the Unicode string. */ Tcl_AppendUnicodeToObj(resultPtr, mapStrings[index+1], mapLens[index+1]); break; } } } if (nocase) { TclStackFree(interp, u2lc); } TclStackFree(interp, mapLens); TclStackFree(interp, mapStrings); } if (p != ustring1) { /* * Put the rest of the unmapped chars onto result. */ Tcl_AppendUnicodeToObj(resultPtr, p, ustring1 - p); } Tcl_SetObjResult(interp, resultPtr); done: if (mapWithDict) { TclStackFree(interp, mapElemv); } if (copySource) { Tcl_DecrRefCount(sourceObj); } return TCL_OK; } /* *---------------------------------------------------------------------- * * StringMatchCmd -- * * This procedure is invoked to process the "string match" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringMatchCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int nocase = 0; if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "?-nocase? pattern string"); return TCL_ERROR; } if (objc == 4) { Tcl_Size length; const char *string = TclGetStringFromObj(objv[1], &length); if ((length > 1) && strncmp(string, "-nocase", length) == 0) { nocase = TCL_MATCH_NOCASE; } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad option \"%s\": must be -nocase", string)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "INDEX", "option", string, (char *)NULL); return TCL_ERROR; } } Tcl_SetObjResult(interp, Tcl_NewBooleanObj( TclStringMatchObj(objv[objc-1], objv[objc-2], nocase))); return TCL_OK; } /* *---------------------------------------------------------------------- * * StringRangeCmd -- * * This procedure is invoked to process the "string range" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringRangeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size first, last, end; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "string first last"); return TCL_ERROR; } /* * Get the length in actual characters; Then reduce it by one because * 'end' refers to the last character, not one past it. */ end = Tcl_GetCharLength(objv[1]) - 1; if (TclGetIntForIndexM(interp, objv[2], end, &first) != TCL_OK || TclGetIntForIndexM(interp, objv[3], end, &last) != TCL_OK) { return TCL_ERROR; } if (last >= 0) { Tcl_SetObjResult(interp, Tcl_GetRange(objv[1], first, last)); } return TCL_OK; } /* *---------------------------------------------------------------------- * * StringReptCmd -- * * This procedure is invoked to process the "string repeat" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringReptCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_WideInt count; Tcl_Obj *resultPtr; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "string count"); return TCL_ERROR; } if (TclGetWideIntFromObj(interp, objv[2], &count) != TCL_OK) { return TCL_ERROR; } /* * Check for cases that allow us to skip copying stuff. */ if (count == 1) { Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } else if (count < 1) { return TCL_OK; } resultPtr = TclStringRepeat(interp, objv[1], count, TCL_STRING_IN_PLACE); if (resultPtr) { Tcl_SetObjResult(interp, resultPtr); return TCL_OK; } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * StringRplcCmd -- * * This procedure is invoked to process the "string replace" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringRplcCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size first, last, end; if (objc < 4 || objc > 5) { Tcl_WrongNumArgs(interp, 1, objv, "string first last ?string?"); return TCL_ERROR; } end = Tcl_GetCharLength(objv[1]) - 1; if (TclGetIntForIndexM(interp, objv[2], end, &first) != TCL_OK || TclGetIntForIndexM(interp, objv[3], end, &last) != TCL_OK) { return TCL_ERROR; } /* * The following test screens out most empty substrings as candidates for * replacement. When they are detected, no replacement is done, and the * result is the original string. */ if ((last < 0) || /* Range ends before start of string */ (first > end) || /* Range begins after end of string */ (last < first)) { /* Range begins after it starts */ /* * BUT!!! when (end < 0) -- an empty original string -- we can * have (first <= end < 0 <= last) and an empty string is permitted * to be replaced. */ Tcl_SetObjResult(interp, objv[1]); } else { Tcl_Obj *resultPtr; if (first < 0) { first = TCL_INDEX_START; } if (last > end) { last = end; } resultPtr = TclStringReplace(interp, objv[1], first, last + 1 - first, (objc == 5) ? objv[4] : NULL, TCL_STRING_IN_PLACE); if (resultPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, resultPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * StringRevCmd -- * * This procedure is invoked to process the "string reverse" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringRevCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "string"); return TCL_ERROR; } Tcl_SetObjResult(interp, TclStringReverse(objv[1], TCL_STRING_IN_PLACE)); return TCL_OK; } /* *---------------------------------------------------------------------- * * StringStartCmd -- * * This procedure is invoked to process the "string wordstart" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringStartCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int ch; const Tcl_UniChar *p, *string; Tcl_Size cur, index, length; Tcl_Obj *obj; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "string index"); return TCL_ERROR; } string = Tcl_GetUnicodeFromObj(objv[1], &length); if (TclGetIntForIndexM(interp, objv[2], length-1, &index) != TCL_OK) { return TCL_ERROR; } if (index >= length) { index = length - 1; } cur = 0; if (index > 0) { p = &string[index]; ch = *p; for (cur = index; cur != TCL_INDEX_NONE; cur--) { int delta = 0; const Tcl_UniChar *next; if (!Tcl_UniCharIsWordChar(ch)) { break; } next = ((p > string) ? (p - 1) : p); do { next += delta; ch = *next; delta = 1; } while (next + delta < p); p = next; } if (cur != index) { cur += 1; } } TclNewIndexObj(obj, cur); Tcl_SetObjResult(interp, obj); return TCL_OK; } /* *---------------------------------------------------------------------- * * StringEndCmd -- * * This procedure is invoked to process the "string wordend" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringEndCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int ch; const Tcl_UniChar *p, *end, *string; Tcl_Size cur, index, length; Tcl_Obj *obj; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "string index"); return TCL_ERROR; } string = Tcl_GetUnicodeFromObj(objv[1], &length); if (TclGetIntForIndexM(interp, objv[2], length-1, &index) != TCL_OK) { return TCL_ERROR; } if (index < 0) { index = 0; } if (index < length) { p = &string[index]; end = string+length; for (cur = index; p < end; cur++) { ch = *p++; if (!Tcl_UniCharIsWordChar(ch)) { break; } } if (cur == index) { cur++; } } else { cur = length; } TclNewIndexObj(obj, cur); Tcl_SetObjResult(interp, obj); return TCL_OK; } /* *---------------------------------------------------------------------- * * StringEqualCmd -- * * This procedure is invoked to process the "string equal" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringEqualCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { /* * Remember to keep code here in some sync with the byte-compiled versions * in tclExecute.c (INST_STR_EQ, INST_STR_NEQ and INST_STR_CMP as well as * the expr string comparison in INST_EQ/INST_NEQ/INST_LT/...). */ const char *string2; int i, match, nocase = 0; Tcl_Size length; Tcl_WideInt reqlength = -1; if (objc < 3 || objc > 6) { str_cmp_args: Tcl_WrongNumArgs(interp, 1, objv, "?-nocase? ?-length int? string1 string2"); return TCL_ERROR; } for (i = 1; i < objc-2; i++) { string2 = TclGetStringFromObj(objv[i], &length); if ((length > 1) && !strncmp(string2, "-nocase", length)) { nocase = 1; } else if ((length > 1) && !strncmp(string2, "-length", length)) { if (i+1 >= objc-2) { goto str_cmp_args; } i++; if (TclGetWideIntFromObj(interp, objv[i], &reqlength) != TCL_OK) { return TCL_ERROR; } if ((Tcl_WideUInt)reqlength > TCL_SIZE_MAX) { reqlength = -1; } } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad option \"%s\": must be -nocase or -length", string2)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "INDEX", "option", string2, (char *)NULL); return TCL_ERROR; } } /* * From now on, we only access the two objects at the end of the argument * array. */ objv += objc-2; match = TclStringCmp(objv[0], objv[1], 1, nocase, reqlength); Tcl_SetObjResult(interp, Tcl_NewBooleanObj(match ? 0 : 1)); return TCL_OK; } /* *---------------------------------------------------------------------- * * StringCmpCmd -- * * This procedure is invoked to process the "string compare" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringCmpCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { /* * Remember to keep code here in some sync with the byte-compiled versions * in tclExecute.c (INST_STR_EQ, INST_STR_NEQ and INST_STR_CMP as well as * the expr string comparison in INST_EQ/INST_NEQ/INST_LT/...). */ int match, nocase, status; Tcl_Size reqlength = -1; status = StringCmpOpts(interp, objc, objv, &nocase, &reqlength); if (status != TCL_OK) { return status; } objv += objc-2; match = TclStringCmp(objv[0], objv[1], 0, nocase, reqlength); Tcl_SetObjResult(interp, Tcl_NewWideIntObj(match)); return TCL_OK; } int StringCmpOpts( Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[], /* Argument objects. */ int *nocase, Tcl_Size *reqlength) { int i; Tcl_Size length; const char *string; Tcl_WideInt wreqlength = -1; *nocase = 0; if (objc < 3 || objc > 6) { str_cmp_args: Tcl_WrongNumArgs(interp, 1, objv, "?-nocase? ?-length int? string1 string2"); return TCL_ERROR; } for (i = 1; i < objc-2; i++) { string = TclGetStringFromObj(objv[i], &length); if ((length > 1) && !strncmp(string, "-nocase", length)) { *nocase = 1; } else if ((length > 1) && !strncmp(string, "-length", length)) { if (i+1 >= objc-2) { goto str_cmp_args; } i++; if (TclGetWideIntFromObj(interp, objv[i], &wreqlength) != TCL_OK) { return TCL_ERROR; } if ((Tcl_WideUInt)wreqlength > TCL_SIZE_MAX) { *reqlength = -1; } else { *reqlength = wreqlength; } } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad option \"%s\": must be -nocase or -length", string)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "INDEX", "option", string, (char *)NULL); return TCL_ERROR; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * StringCatCmd -- * * This procedure is invoked to process the "string cat" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringCatCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *objResultPtr; if (objc < 2) { /* * If there are no args, the result is an empty object. * Just leave the preset empty interp result. */ return TCL_OK; } objResultPtr = TclStringCat(interp, objc-1, objv+1, TCL_STRING_IN_PLACE); if (objResultPtr) { Tcl_SetObjResult(interp, objResultPtr); return TCL_OK; } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * StringLenCmd -- * * This procedure is invoked to process the "string length" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringLenCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "string"); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(Tcl_GetCharLength(objv[1]))); return TCL_OK; } /* *---------------------------------------------------------------------- * * StringLowerCmd -- * * This procedure is invoked to process the "string tolower" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringLowerCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size length1, length2; const char *string1; char *string2; if (objc < 2 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "string ?first? ?last?"); return TCL_ERROR; } string1 = TclGetStringFromObj(objv[1], &length1); if (objc == 2) { Tcl_Obj *resultPtr = Tcl_NewStringObj(string1, length1); length1 = Tcl_UtfToLower(TclGetString(resultPtr)); Tcl_SetObjLength(resultPtr, length1); Tcl_SetObjResult(interp, resultPtr); } else { Tcl_Size first, last; const char *start, *end; Tcl_Obj *resultPtr; length1 = Tcl_NumUtfChars(string1, length1) - 1; if (TclGetIntForIndexM(interp,objv[2],length1, &first) != TCL_OK) { return TCL_ERROR; } if (first < 0) { first = 0; } last = first; if ((objc == 4) && (TclGetIntForIndexM(interp, objv[3], length1, &last) != TCL_OK)) { return TCL_ERROR; } if (last >= length1) { last = length1; } if (last < first) { Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } string1 = TclGetStringFromObj(objv[1], &length1); start = Tcl_UtfAtIndex(string1, first); end = Tcl_UtfAtIndex(start, last - first + 1); resultPtr = Tcl_NewStringObj(string1, end - string1); string2 = TclGetString(resultPtr) + (start - string1); length2 = Tcl_UtfToLower(string2); Tcl_SetObjLength(resultPtr, length2 + (start - string1)); Tcl_AppendToObj(resultPtr, end, -1); Tcl_SetObjResult(interp, resultPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * StringUpperCmd -- * * This procedure is invoked to process the "string toupper" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringUpperCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size length1, length2; const char *string1; char *string2; if (objc < 2 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "string ?first? ?last?"); return TCL_ERROR; } string1 = TclGetStringFromObj(objv[1], &length1); if (objc == 2) { Tcl_Obj *resultPtr = Tcl_NewStringObj(string1, length1); length1 = Tcl_UtfToUpper(TclGetString(resultPtr)); Tcl_SetObjLength(resultPtr, length1); Tcl_SetObjResult(interp, resultPtr); } else { Tcl_Size first, last; const char *start, *end; Tcl_Obj *resultPtr; length1 = Tcl_NumUtfChars(string1, length1) - 1; if (TclGetIntForIndexM(interp,objv[2],length1, &first) != TCL_OK) { return TCL_ERROR; } if (first < 0) { first = 0; } last = first; if ((objc == 4) && (TclGetIntForIndexM(interp, objv[3], length1, &last) != TCL_OK)) { return TCL_ERROR; } if (last >= length1) { last = length1; } if (last < first) { Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } string1 = TclGetStringFromObj(objv[1], &length1); start = Tcl_UtfAtIndex(string1, first); end = Tcl_UtfAtIndex(start, last - first + 1); resultPtr = Tcl_NewStringObj(string1, end - string1); string2 = TclGetString(resultPtr) + (start - string1); length2 = Tcl_UtfToUpper(string2); Tcl_SetObjLength(resultPtr, length2 + (start - string1)); Tcl_AppendToObj(resultPtr, end, -1); Tcl_SetObjResult(interp, resultPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * StringTitleCmd -- * * This procedure is invoked to process the "string totitle" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringTitleCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size length1, length2; const char *string1; char *string2; if (objc < 2 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "string ?first? ?last?"); return TCL_ERROR; } string1 = TclGetStringFromObj(objv[1], &length1); if (objc == 2) { Tcl_Obj *resultPtr = Tcl_NewStringObj(string1, length1); length1 = Tcl_UtfToTitle(TclGetString(resultPtr)); Tcl_SetObjLength(resultPtr, length1); Tcl_SetObjResult(interp, resultPtr); } else { Tcl_Size first, last; const char *start, *end; Tcl_Obj *resultPtr; length1 = Tcl_NumUtfChars(string1, length1) - 1; if (TclGetIntForIndexM(interp,objv[2],length1, &first) != TCL_OK) { return TCL_ERROR; } if (first < 0) { first = 0; } last = first; if ((objc == 4) && (TclGetIntForIndexM(interp, objv[3], length1, &last) != TCL_OK)) { return TCL_ERROR; } if (last >= length1) { last = length1; } if (last < first) { Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } string1 = TclGetStringFromObj(objv[1], &length1); start = Tcl_UtfAtIndex(string1, first); end = Tcl_UtfAtIndex(start, last - first + 1); resultPtr = Tcl_NewStringObj(string1, end - string1); string2 = TclGetString(resultPtr) + (start - string1); length2 = Tcl_UtfToTitle(string2); Tcl_SetObjLength(resultPtr, length2 + (start - string1)); Tcl_AppendToObj(resultPtr, end, -1); Tcl_SetObjResult(interp, resultPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * StringTrimCmd -- * * This procedure is invoked to process the "string trim" Tcl command. * See the user documentation for details on what it does. Note that this * command only functions correctly on properly formed Tcl UTF strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringTrimCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *string1, *string2; Tcl_Size triml, trimr, length1, length2; if (objc == 3) { string2 = TclGetStringFromObj(objv[2], &length2); } else if (objc == 2) { string2 = tclDefaultTrimSet; length2 = strlen(tclDefaultTrimSet); } else { Tcl_WrongNumArgs(interp, 1, objv, "string ?chars?"); return TCL_ERROR; } string1 = TclGetStringFromObj(objv[1], &length1); triml = TclTrim(string1, length1, string2, length2, &trimr); Tcl_SetObjResult(interp, Tcl_NewStringObj(string1 + triml, length1 - triml - trimr)); return TCL_OK; } /* *---------------------------------------------------------------------- * * StringTrimLCmd -- * * This procedure is invoked to process the "string trimleft" Tcl * command. See the user documentation for details on what it does. Note * that this command only functions correctly on properly formed Tcl UTF * strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringTrimLCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *string1, *string2; int trim; Tcl_Size length1, length2; if (objc == 3) { string2 = TclGetStringFromObj(objv[2], &length2); } else if (objc == 2) { string2 = tclDefaultTrimSet; length2 = strlen(tclDefaultTrimSet); } else { Tcl_WrongNumArgs(interp, 1, objv, "string ?chars?"); return TCL_ERROR; } string1 = TclGetStringFromObj(objv[1], &length1); trim = TclTrimLeft(string1, length1, string2, length2); Tcl_SetObjResult(interp, Tcl_NewStringObj(string1+trim, length1-trim)); return TCL_OK; } /* *---------------------------------------------------------------------- * * StringTrimRCmd -- * * This procedure is invoked to process the "string trimright" Tcl * command. See the user documentation for details on what it does. Note * that this command only functions correctly on properly formed Tcl UTF * strings. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int StringTrimRCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *string1, *string2; int trim; Tcl_Size length1, length2; if (objc == 3) { string2 = TclGetStringFromObj(objv[2], &length2); } else if (objc == 2) { string2 = tclDefaultTrimSet; length2 = strlen(tclDefaultTrimSet); } else { Tcl_WrongNumArgs(interp, 1, objv, "string ?chars?"); return TCL_ERROR; } string1 = TclGetStringFromObj(objv[1], &length1); trim = TclTrimRight(string1, length1, string2, length2); Tcl_SetObjResult(interp, Tcl_NewStringObj(string1, length1-trim)); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclInitStringCmd -- * * This procedure creates the "string" Tcl command. See the user * documentation for details on what it does. Note that this command only * functions correctly on properly formed Tcl UTF strings. * * Also note that the primary methods here (equal, compare, match, ...) * have bytecode equivalents. You will find the code for those in * tclExecute.c. The code here will only be used in the non-bc case (like * in an 'eval'). * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ Tcl_Command TclInitStringCmd( Tcl_Interp *interp) /* Current interpreter. */ { static const EnsembleImplMap stringImplMap[] = { {"cat", StringCatCmd, TclCompileStringCatCmd, NULL, NULL, 0}, {"compare", StringCmpCmd, TclCompileStringCmpCmd, NULL, NULL, 0}, {"equal", StringEqualCmd, TclCompileStringEqualCmd, NULL, NULL, 0}, {"first", StringFirstCmd, TclCompileStringFirstCmd, NULL, NULL, 0}, {"index", StringIndexCmd, TclCompileStringIndexCmd, NULL, NULL, 0}, {"insert", StringInsertCmd, TclCompileStringInsertCmd, NULL, NULL, 0}, {"is", StringIsCmd, TclCompileStringIsCmd, NULL, NULL, 0}, {"last", StringLastCmd, TclCompileStringLastCmd, NULL, NULL, 0}, {"length", StringLenCmd, TclCompileStringLenCmd, NULL, NULL, 0}, {"map", StringMapCmd, TclCompileStringMapCmd, NULL, NULL, 0}, {"match", StringMatchCmd, TclCompileStringMatchCmd, NULL, NULL, 0}, {"range", StringRangeCmd, TclCompileStringRangeCmd, NULL, NULL, 0}, {"repeat", StringReptCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"replace", StringRplcCmd, TclCompileStringReplaceCmd, NULL, NULL, 0}, {"reverse", StringRevCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"tolower", StringLowerCmd, TclCompileStringToLowerCmd, NULL, NULL, 0}, {"toupper", StringUpperCmd, TclCompileStringToUpperCmd, NULL, NULL, 0}, {"totitle", StringTitleCmd, TclCompileStringToTitleCmd, NULL, NULL, 0}, {"trim", StringTrimCmd, TclCompileStringTrimCmd, NULL, NULL, 0}, {"trimleft", StringTrimLCmd, TclCompileStringTrimLCmd, NULL, NULL, 0}, {"trimright", StringTrimRCmd, TclCompileStringTrimRCmd, NULL, NULL, 0}, {"wordend", StringEndCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"wordstart", StringStartCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {NULL, NULL, NULL, NULL, NULL, 0} }; return TclMakeEnsemble(interp, "string", stringImplMap); } /* *---------------------------------------------------------------------- * * Tcl_SubstObjCmd -- * * This procedure is invoked to process the "subst" Tcl command. See the * user documentation for details on what it does. This command relies on * Tcl_SubstObj() for its implementation. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int TclSubstOptions( Tcl_Interp *interp, Tcl_Size numOpts, Tcl_Obj *const opts[], int *flagPtr) { static const char *const substOptions[] = { "-nobackslashes", "-nocommands", "-novariables", NULL }; enum { SUBST_NOBACKSLASHES, SUBST_NOCOMMANDS, SUBST_NOVARS }; int i, flags = TCL_SUBST_ALL; for (i = 0; i < numOpts; i++) { int optionIndex; if (Tcl_GetIndexFromObj(interp, opts[i], substOptions, "option", 0, &optionIndex) != TCL_OK) { return TCL_ERROR; } switch (optionIndex) { case SUBST_NOBACKSLASHES: flags &= ~TCL_SUBST_BACKSLASHES; break; case SUBST_NOCOMMANDS: flags &= ~TCL_SUBST_COMMANDS; break; case SUBST_NOVARS: flags &= ~TCL_SUBST_VARIABLES; break; default: Tcl_Panic("Tcl_SubstObjCmd: bad option index to SubstOptions"); } } *flagPtr = flags; return TCL_OK; } int Tcl_SubstObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRSubstObjCmd, clientData, objc, objv); } int TclNRSubstObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int flags; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "?-nobackslashes? ?-nocommands? ?-novariables? string"); return TCL_ERROR; } if (TclSubstOptions(interp, objc-2, objv+1, &flags) != TCL_OK) { return TCL_ERROR; } return Tcl_NRSubstObj(interp, objv[objc-1], flags); } /* *---------------------------------------------------------------------- * * Tcl_SwitchObjCmd -- * * This object-based procedure is invoked to process the "switch" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_SwitchObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRSwitchObjCmd, clientData, objc, objv); } int TclNRSwitchObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int i, mode, foundmode, splitObjs, numMatchesSaved; int noCase; Tcl_Size patternLength, j; const char *pattern; Tcl_Obj *stringObj, *indexVarObj, *matchVarObj; Tcl_Obj *const *savedObjv = objv; Tcl_RegExp regExpr = NULL; Interp *iPtr = (Interp *) interp; int pc = 0; int bidx = 0; /* Index of body argument. */ Tcl_Obj *blist = NULL; /* List obj which is the body */ CmdFrame *ctxPtr; /* Copy of the topmost cmdframe, to allow us * to mess with the line information */ /* * If you add options that make -e and -g not unique prefixes of -exact or * -glob, you *must* fix TclCompileSwitchCmd's option parser as well. */ static const char *const options[] = { "-exact", "-glob", "-indexvar", "-matchvar", "-nocase", "-regexp", "--", NULL }; enum switchOptionsEnum { OPT_EXACT, OPT_GLOB, OPT_INDEXV, OPT_MATCHV, OPT_NOCASE, OPT_REGEXP, OPT_LAST } index; typedef int (*strCmpFn_t)(const char *, const char *); strCmpFn_t strCmpFn = TclUtfCmp; mode = OPT_EXACT; foundmode = 0; indexVarObj = NULL; matchVarObj = NULL; numMatchesSaved = 0; noCase = 0; for (i = 1; i < objc-2; i++) { if (TclGetString(objv[i])[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { /* * General options. */ case OPT_LAST: i++; goto finishedOptions; case OPT_NOCASE: strCmpFn = TclUtfCasecmp; noCase = 1; break; /* * Handle the different switch mode options. */ default: if (foundmode) { /* * Mode already set via -exact, -glob, or -regexp. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad option \"%s\": %s option already found", TclGetString(objv[i]), options[mode])); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "SWITCH", "DOUBLEOPT", (char *)NULL); return TCL_ERROR; } foundmode = 1; mode = index; break; /* * Check for TIP#75 options specifying the variables to write * regexp information into. */ case OPT_INDEXV: i++; if (i >= objc-2) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "missing variable name argument to %s option", "-indexvar")); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "SWITCH", "NOVAR", (char *)NULL); return TCL_ERROR; } indexVarObj = objv[i]; numMatchesSaved = -1; break; case OPT_MATCHV: i++; if (i >= objc-2) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "missing variable name argument to %s option", "-matchvar")); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "SWITCH", "NOVAR", (char *)NULL); return TCL_ERROR; } matchVarObj = objv[i]; numMatchesSaved = -1; break; } } finishedOptions: if (objc - i < 2) { Tcl_WrongNumArgs(interp, 1, objv, "?-option ...? string ?pattern body ...? ?default body?"); return TCL_ERROR; } if (indexVarObj != NULL && mode != OPT_REGEXP) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s option requires -regexp option", "-indexvar")); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "SWITCH", "MODERESTRICTION", (char *)NULL); return TCL_ERROR; } if (matchVarObj != NULL && mode != OPT_REGEXP) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s option requires -regexp option", "-matchvar")); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "SWITCH", "MODERESTRICTION", (char *)NULL); return TCL_ERROR; } stringObj = objv[i]; objc -= i + 1; objv += i + 1; bidx = i + 1; /* First after the match string. */ /* * If all of the pattern/command pairs are lumped into a single argument, * split them out again. * * TIP #280: Determine the lines the words in the list start at, based on * the same data for the list word itself. The cmdFramePtr line * information is manipulated directly. */ splitObjs = 0; if (objc == 1) { Tcl_Obj **listv; Tcl_Size listc; blist = objv[0]; if (TclListObjLength(interp, objv[0], &listc) != TCL_OK) { return TCL_ERROR; } /* * Ensure that the list is non-empty. */ if (listc < 1 || listc > INT_MAX) { Tcl_WrongNumArgs(interp, 1, savedObjv, "?-option ...? string {?pattern body ...? ?default body?}"); return TCL_ERROR; } if (TclListObjGetElements(interp, objv[0], &listc, &listv) != TCL_OK) { return TCL_ERROR; } objc = listc; objv = listv; splitObjs = 1; } /* * Complain if there is an odd number of words in the list of patterns and * bodies. */ if (objc % 2) { Tcl_ResetResult(interp); Tcl_SetObjResult(interp, Tcl_NewStringObj( "extra switch pattern with no body", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "SWITCH", "BADARM", (char *)NULL); /* * Check if this can be due to a badly placed comment in the switch * block. * * The following is an heuristic to detect the infamous "comment in * switch" error: just check if a pattern begins with '#'. */ if (splitObjs) { for (i=0 ; i 0) { TclNewIndexObj(rangeObjAry[0], info.matches[j].start); TclNewIndexObj(rangeObjAry[1], info.matches[j].end-1); } else { TclNewIntObj(rangeObjAry[1], -1); rangeObjAry[0] = rangeObjAry[1]; } /* * Never fails; the object is always clean at this point. */ Tcl_ListObjAppendElement(NULL, indicesObj, Tcl_NewListObj(2, rangeObjAry)); } if (matchVarObj != NULL) { Tcl_Obj *substringObj; if (info.matches[j].end > 0) { substringObj = Tcl_GetRange(stringObj, info.matches[j].start, info.matches[j].end-1); } else { TclNewObj(substringObj); } /* * Never fails; the object is always clean at this point. */ Tcl_ListObjAppendElement(NULL, matchesObj, substringObj); } } if (indexVarObj != NULL) { if (Tcl_ObjSetVar2(interp, indexVarObj, NULL, indicesObj, TCL_LEAVE_ERR_MSG) == NULL) { /* * Careful! Check to see if we have allocated the list of * matched strings; if so (but there was an error assigning * the indices list) we have a potential memory leak because * the match list has not been written to a variable. Except * that we'll clean that up right now. */ if (matchesObj != NULL) { Tcl_DecrRefCount(matchesObj); } return TCL_ERROR; } } if (matchVarObj != NULL) { if (Tcl_ObjSetVar2(interp, matchVarObj, NULL, matchesObj, TCL_LEAVE_ERR_MSG) == NULL) { /* * Unlike above, if indicesObj is non-NULL at this point, it * will have been written to a variable already and will hence * not be leaked. */ return TCL_ERROR; } } } /* * We've got a match. Find a body to execute, skipping bodies that are * "-". */ matchFound: ctxPtr = (CmdFrame *)TclStackAlloc(interp, sizeof(CmdFrame)); *ctxPtr = *iPtr->cmdFramePtr; if (splitObjs) { /* * We have to perform the GetSrc and other type dependent handling of * the frame here because we are munging with the line numbers, * something the other commands like if, etc. are not doing. Them are * fine with simply passing the CmdFrame through and having the * special handling done in 'info frame', or the bc compiler */ if (ctxPtr->type == TCL_LOCATION_BC) { /* * Type BC => ctxPtr->data.eval.path is not used. * ctxPtr->data.tebc.codePtr is used instead. */ TclGetSrcInfoForPc(ctxPtr); pc = 1; /* * The line information in the cmdFrame is now a copy we do not * own. */ } if (ctxPtr->type == TCL_LOCATION_SOURCE && ctxPtr->line[bidx] >= 0) { int bline = ctxPtr->line[bidx]; ctxPtr->line = (Tcl_Size *)Tcl_Alloc(objc * sizeof(Tcl_Size)); ctxPtr->nline = objc; TclListLines(blist, bline, objc, ctxPtr->line, objv); } else { /* * This is either a dynamic code word, when all elements are * relative to themselves, or something else less expected and * where we have no information. The result is the same in both * cases; tell the code to come that it doesn't know where it is, * which triggers reversion to the old behavior. */ int k; ctxPtr->line = (Tcl_Size *)Tcl_Alloc(objc * sizeof(Tcl_Size)); ctxPtr->nline = objc; for (k=0; k < objc; k++) { ctxPtr->line[k] = -1; } } } for (j = i + 1; ; j += 2) { if (j >= objc) { /* * This shouldn't happen since we've checked that the last body is * not a continuation... */ Tcl_Panic("fall-out when searching for body to match pattern"); } if (strcmp(TclGetString(objv[j]), "-") != 0) { break; } } /* * TIP #280: Make invoking context available to switch branch. */ Tcl_NRAddCallback(interp, SwitchPostProc, INT2PTR(splitObjs), ctxPtr, INT2PTR(pc), (void *)pattern); return TclNREvalObjEx(interp, objv[j], 0, ctxPtr, splitObjs ? j : bidx+j); } static int SwitchPostProc( void *data[], /* Data passed from Tcl_NRAddCallback above */ Tcl_Interp *interp, /* Tcl interpreter */ int result) /* Result to return*/ { /* Unpack the preserved data */ int splitObjs = PTR2INT(data[0]); CmdFrame *ctxPtr = (CmdFrame *)data[1]; int pc = PTR2INT(data[2]); const char *pattern = (const char *)data[3]; Tcl_Size patternLength = strlen(pattern); /* * Clean up TIP 280 context information */ if (splitObjs) { Tcl_Free(ctxPtr->line); if (pc && (ctxPtr->type == TCL_LOCATION_SOURCE)) { /* * Death of SrcInfo reference. */ Tcl_DecrRefCount(ctxPtr->data.eval.path); } } /* * Generate an error message if necessary. */ if (result == TCL_ERROR) { int limit = 50; int overflow = (patternLength > limit); Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (\"%.*s%s\" arm line %d)", (int) (overflow ? limit : patternLength), pattern, (overflow ? "..." : ""), Tcl_GetErrorLine(interp))); } TclStackFree(interp, ctxPtr); return result; } /* *---------------------------------------------------------------------- * * Tcl_ThrowObjCmd -- * * This procedure is invoked to process the "throw" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_ThrowObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *options; Tcl_Size len; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "type message"); return TCL_ERROR; } /* * The type must be a list of at least length 1. */ if (TclListObjLength(interp, objv[1], &len) != TCL_OK) { return TCL_ERROR; } else if (len < 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "type must be non-empty list", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "THROW", "BADEXCEPTION", (char *)NULL); return TCL_ERROR; } /* * Now prepare the result options dictionary. We use the list API as it is * slightly more convenient. */ TclNewLiteralStringObj(options, "-code error -level 0 -errorcode"); Tcl_ListObjAppendElement(NULL, options, objv[1]); /* * We're ready to go. Fire things into the low-level result machinery. */ Tcl_SetObjResult(interp, objv[2]); return Tcl_SetReturnOptions(interp, options); } /* *---------------------------------------------------------------------- * * Tcl_TimeObjCmd -- * * This object-based procedure is invoked to process the "time" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_TimeObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *objPtr; Tcl_Obj *objs[4]; int i, result; int count; double totalMicroSec; #ifndef TCL_WIDE_CLICKS Tcl_Time start, stop; #else Tcl_WideInt start, stop; #endif if (objc == 2) { count = 1; } else if (objc == 3) { result = TclGetIntFromObj(interp, objv[2], &count); if (result != TCL_OK) { return result; } } else { Tcl_WrongNumArgs(interp, 1, objv, "command ?count?"); return TCL_ERROR; } objPtr = objv[1]; i = count; #ifndef TCL_WIDE_CLICKS Tcl_GetTime(&start); #else start = TclpGetWideClicks(); #endif while (i-- > 0) { result = TclEvalObjEx(interp, objPtr, 0, NULL, 0); if (result != TCL_OK) { return result; } } #ifndef TCL_WIDE_CLICKS Tcl_GetTime(&stop); totalMicroSec = ((double) (stop.sec - start.sec)) * 1.0e6 + (stop.usec - start.usec); #else stop = TclpGetWideClicks(); totalMicroSec = ((double) TclpWideClicksToNanoseconds(stop - start))/1.0e3; #endif if (count <= 1) { /* * Use int obj since we know time is not fractional. [Bug 1202178] */ TclNewIntObj(objs[0], (count <= 0) ? 0 : (Tcl_WideInt)totalMicroSec); } else { TclNewDoubleObj(objs[0], totalMicroSec/count); } /* * Construct the result as a list because many programs have always parsed * as such (extracting the first element, typically). */ TclNewLiteralStringObj(objs[1], "microseconds"); TclNewLiteralStringObj(objs[2], "per"); TclNewLiteralStringObj(objs[3], "iteration"); Tcl_SetObjResult(interp, Tcl_NewListObj(4, objs)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_TimeRateObjCmd -- * * This object-based procedure is invoked to process the "timerate" Tcl * command. * * This is similar to command "time", except the execution limited by * given time (in milliseconds) instead of repetition count. * * Example: * timerate {after 5} 1000; # equivalent to: time {after 5} [expr 1000/5] * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_TimeRateObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static double measureOverhead = 0; /* global measure-overhead */ double overhead = -1; /* given measure-overhead */ Tcl_Obj *objPtr; int result, i; Tcl_Obj *calibrate = NULL, *direct = NULL; Tcl_WideUInt count = 0; /* Holds repetition count */ Tcl_WideInt maxms = WIDE_MIN; /* Maximal running time (in milliseconds) */ Tcl_WideUInt maxcnt = WIDE_MAX; /* Maximal count of iterations. */ Tcl_WideUInt threshold = 1; /* Current threshold for check time (faster * repeat count without time check) */ Tcl_WideUInt maxIterTm = 1; /* Max time of some iteration as max * threshold, additionally avoiding divide to * zero (i.e., never < 1) */ unsigned short factor = 50; /* Factor (4..50) limiting threshold to avoid * growth of execution time. */ Tcl_WideInt start, middle, stop; #ifndef TCL_WIDE_CLICKS Tcl_Time now; #endif /* !TCL_WIDE_CLICKS */ static const char *const options[] = { "-direct", "-overhead", "-calibrate", "--", NULL }; enum timeRateOptionsEnum { TMRT_EV_DIRECT, TMRT_OVERHEAD, TMRT_CALIBRATE, TMRT_LAST }; NRE_callback *rootPtr; ByteCode *codePtr = NULL; for (i = 1; i < objc - 1; i++) { enum timeRateOptionsEnum index; if (Tcl_GetIndexFromObj(NULL, objv[i], options, "option", TCL_EXACT, &index) != TCL_OK) { break; } if (index == TMRT_LAST) { i++; break; } switch (index) { case TMRT_EV_DIRECT: direct = objv[i]; break; case TMRT_OVERHEAD: if (++i >= objc - 1) { goto usage; } if (Tcl_GetDoubleFromObj(interp, objv[i], &overhead) != TCL_OK) { return TCL_ERROR; } break; case TMRT_CALIBRATE: calibrate = objv[i]; break; case TMRT_LAST: break; } } if (i >= objc || i < objc - 3) { usage: Tcl_WrongNumArgs(interp, 1, objv, "?-direct? ?-calibrate? ?-overhead double? " "command ?time ?max-count??"); return TCL_ERROR; } objPtr = objv[i++]; if (i < objc) { /* max-time */ result = TclGetWideIntFromObj(interp, objv[i], &maxms); i++; // Keep this separate from TclGetWideIntFromObj macro above! if (result != TCL_OK) { return result; } if (i < objc) { /* max-count*/ Tcl_WideInt v; result = TclGetWideIntFromObj(interp, objv[i], &v); if (result != TCL_OK) { return result; } maxcnt = (v > 0) ? v : 0; } } /* * If we are doing calibration. */ if (calibrate) { /* * If no time specified for the calibration. */ if (maxms == WIDE_MIN) { Tcl_Obj *clobjv[6]; Tcl_WideInt maxCalTime = 5000; double lastMeasureOverhead = measureOverhead; clobjv[0] = objv[0]; i = 1; if (direct) { clobjv[i++] = direct; } clobjv[i++] = objPtr; /* * Reset last measurement overhead. */ measureOverhead = (double) 0; /* * Self-call with 100 milliseconds to warm-up, before entering the * calibration cycle. */ TclNewIntObj(clobjv[i], 100); Tcl_IncrRefCount(clobjv[i]); result = Tcl_TimeRateObjCmd(NULL, interp, i + 1, clobjv); Tcl_DecrRefCount(clobjv[i]); if (result != TCL_OK) { return result; } i--; clobjv[i++] = calibrate; clobjv[i++] = objPtr; /* * Set last measurement overhead to max. */ measureOverhead = (double) UWIDE_MAX; /* * Run the calibration cycle until it is more precise. */ maxms = -1000; do { lastMeasureOverhead = measureOverhead; TclNewIntObj(clobjv[i], (int) maxms); Tcl_IncrRefCount(clobjv[i]); result = Tcl_TimeRateObjCmd(NULL, interp, i + 1, clobjv); Tcl_DecrRefCount(clobjv[i]); if (result != TCL_OK) { return result; } maxCalTime += maxms; /* * Increase maxms for more precise calibration. */ maxms -= -maxms / 4; /* * As long as new value more as 0.05% better */ } while ((measureOverhead >= lastMeasureOverhead || measureOverhead / lastMeasureOverhead <= 0.9995) && maxCalTime > 0); return result; } if (maxms == 0) { /* * Reset last measurement overhead */ measureOverhead = 0; Tcl_SetObjResult(interp, Tcl_NewWideIntObj(0)); return TCL_OK; } /* * If time is negative, make current overhead more precise. */ if (maxms > 0) { /* * Set last measurement overhead to max. */ measureOverhead = (double) UWIDE_MAX; } else { maxms = -maxms; } } if (maxms == WIDE_MIN) { maxms = 1000; } if (overhead == -1) { overhead = measureOverhead; } /* * Ensure that resetting of result will not smudge the further * measurement. */ Tcl_ResetResult(interp); /* * Compile object if needed. */ if (!direct) { if (TclInterpReady(interp) != TCL_OK) { return TCL_ERROR; } codePtr = TclCompileObj(interp, objPtr, NULL, 0); TclPreserveByteCode(codePtr); } /* * Get start and stop time. */ #ifdef TCL_WIDE_CLICKS start = middle = TclpGetWideClicks(); /* * Time to stop execution (in wide clicks). */ stop = start + (maxms * 1000 / TclpWideClickInMicrosec()); #else Tcl_GetTime(&now); start = now.sec; start *= 1000000; start += now.usec; middle = start; /* * Time to stop execution (in microsecs). */ stop = start + maxms * 1000; #endif /* TCL_WIDE_CLICKS */ /* * Start measurement. */ if (maxcnt > 0) { while (1) { /* * Evaluate a single iteration. */ count++; if (!direct) { /* precompiled */ rootPtr = TOP_CB(interp); /* * Use loop optimized TEBC call (TCL_EVAL_DISCARD_RESULT): it's a part of * iteration, this way evaluation will be more similar to a cycle (also * avoids extra overhead to set result to interp, etc.) */ ((Interp *)interp)->evalFlags |= TCL_EVAL_DISCARD_RESULT; result = TclNRExecuteByteCode(interp, codePtr); result = TclNRRunCallbacks(interp, result, rootPtr); } else { /* eval */ result = TclEvalObjEx(interp, objPtr, 0, NULL, 0); } /* * Allow break and continue from measurement cycle (used for * conditional stop and flow control of iterations). */ switch (result) { case TCL_OK: break; case TCL_BREAK: /* * Force stop immediately. */ threshold = 1; maxcnt = 0; /* FALLTHRU */ case TCL_CONTINUE: result = TCL_OK; break; default: goto done; } /* * Don't check time up to threshold. */ if (--threshold > 0) { continue; } /* * Check stop time reached, estimate new threshold. */ #ifdef TCL_WIDE_CLICKS middle = TclpGetWideClicks(); #else Tcl_GetTime(&now); middle = now.sec; middle *= 1000000; middle += now.usec; #endif /* TCL_WIDE_CLICKS */ if (middle >= stop || count >= maxcnt) { break; } /* * Don't calculate threshold by few iterations, because sometimes * first iteration(s) can be too fast or slow (cached, delayed * clean up, etc). */ if (count < 10) { threshold = 1; continue; } /* * Average iteration time in microsecs. */ threshold = (middle - start) / count; if (threshold > maxIterTm) { maxIterTm = threshold; /* * Iterations seem to be longer. */ if (threshold > maxIterTm * 2) { factor *= 2; if (factor > 50) { factor = 50; } } else { if (factor < 50) { factor++; } } } else if (factor > 4) { /* * Iterations seem to be shorter. */ if (threshold < (maxIterTm / 2)) { factor /= 2; if (factor < 4) { factor = 4; } } else { factor--; } } /* * As relation between remaining time and time since last check, * maximal some % of time (by factor), so avoid growing of the * execution time if iterations are not consistent, e.g. was * continuously on time). */ threshold = ((stop - middle) / maxIterTm) / factor + 1; if (threshold > 100000) { /* fix for too large threshold */ threshold = 100000; } /* * Consider max-count */ if (threshold > maxcnt - count) { threshold = maxcnt - count; } } } { Tcl_Obj *objarr[8], **objs = objarr; Tcl_WideUInt usec, val; int digits; /* * Absolute execution time in microseconds or in wide clicks. */ usec = (Tcl_WideUInt)(middle - start); #ifdef TCL_WIDE_CLICKS /* * convert execution time (in wide clicks) to microsecs. */ usec *= TclpWideClickInMicrosec(); #endif /* TCL_WIDE_CLICKS */ if (!count) { /* no iterations - avoid divide by zero */ TclNewIntObj(objs[4], 0); objs[0] = objs[2] = objs[4]; goto retRes; } /* * If not calibrating... */ if (!calibrate) { /* * Minimize influence of measurement overhead. */ if (overhead > 0) { /* * Estimate the time of overhead (microsecs). */ Tcl_WideUInt curOverhead = overhead * count; if (usec > curOverhead) { usec -= curOverhead; } else { usec = 0; } } } else { /* * Calibration: obtaining new measurement overhead. */ if (measureOverhead > ((double) usec) / count) { measureOverhead = ((double) usec) / count; } TclNewDoubleObj(objs[0], measureOverhead); TclNewLiteralStringObj(objs[1], "\xC2\xB5s/#-overhead"); /* mics */ objs += 2; } val = usec / count; /* microsecs per iteration */ if (val >= 1000000) { TclNewIntObj(objs[0], val); } else { if (val < 10) { digits = 6; } else if (val < 100) { digits = 4; } else if (val < 1000) { digits = 3; } else if (val < 10000) { digits = 2; } else { digits = 1; } objs[0] = Tcl_ObjPrintf("%.*f", digits, ((double) usec)/count); } TclNewIntObj(objs[2], count); /* iterations */ /* * Calculate speed as rate (count) per sec */ if (!usec) { usec++; /* Avoid divide by zero. */ } if (count < (WIDE_MAX / 1000000)) { val = (count * 1000000) / usec; if (val < 100000) { if (val < 100) { digits = 3; } else if (val < 1000) { digits = 2; } else { digits = 1; } objs[4] = Tcl_ObjPrintf("%.*f", digits, ((double) (count * 1000000)) / usec); } else { TclNewIntObj(objs[4], val); } } else { objs[4] = Tcl_NewWideIntObj((count / usec) * 1000000); } retRes: /* * Estimated net execution time (in millisecs). */ if (!calibrate) { if (usec >= 1) { objs[6] = Tcl_ObjPrintf("%.3f", (double)usec / 1000); } else { TclNewIntObj(objs[6], 0); } TclNewLiteralStringObj(objs[7], "net-ms"); } /* * Construct the result as a list because many programs have always * parsed as such (extracting the first element, typically). */ TclNewLiteralStringObj(objs[1], "\xC2\xB5s/#"); /* mics/# */ TclNewLiteralStringObj(objs[3], "#"); TclNewLiteralStringObj(objs[5], "#/sec"); Tcl_SetObjResult(interp, Tcl_NewListObj(8, objarr)); } done: if (codePtr != NULL) { TclReleaseByteCode(codePtr); } return result; } /* *---------------------------------------------------------------------- * * Tcl_TryObjCmd, TclNRTryObjCmd -- * * This procedure is invoked to process the "try" Tcl command. See the * user documentation (or TIP #329) for details on what it does. * * Results: * A standard Tcl object result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_TryObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRTryObjCmd, clientData, objc, objv); } int TclNRTryObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *bodyObj, *handlersObj, *finallyObj = NULL; int i, bodyShared, haveHandlers, code; Tcl_Size dummy; static const char *const handlerNames[] = { "finally", "on", "trap", NULL }; enum Handlers { TryFinally, TryOn, TryTrap }; /* * Parse the arguments. The handlers are passed to subsequent callbacks as * a Tcl_Obj list of the 5-tuples like (type, returnCode, errorCodePrefix, * bindVariables, script), and the finally script is just passed as it is. */ if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "body ?handler ...? ?finally script?"); return TCL_ERROR; } bodyObj = objv[1]; TclNewObj(handlersObj); bodyShared = 0; haveHandlers = 0; for (i=2 ; i objc-4) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "wrong # args to on clause: must be \"... on code" " variableList script\"", -1)); Tcl_DecrRefCount(handlersObj); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "TRY", "ON", "ARGUMENT", (char *)NULL); return TCL_ERROR; } if (TclGetCompletionCodeFromObj(interp, objv[i+1], &code) != TCL_OK) { Tcl_DecrRefCount(handlersObj); return TCL_ERROR; } info[2] = NULL; goto commonHandler; case TryTrap: /* trap pattern variableList script */ if (i > objc-4) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "wrong # args to trap clause: " "must be \"... trap pattern variableList script\"", -1)); Tcl_DecrRefCount(handlersObj); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "TRY", "TRAP", "ARGUMENT", (char *)NULL); return TCL_ERROR; } code = 1; if (TclListObjLength(NULL, objv[i+1], &dummy) != TCL_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad prefix '%s': must be a list", TclGetString(objv[i+1]))); Tcl_DecrRefCount(handlersObj); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "TRY", "TRAP", "EXNFORMAT", (char *)NULL); return TCL_ERROR; } info[2] = objv[i+1]; commonHandler: if (TclListObjLength(interp, objv[i+2], &dummy) != TCL_OK) { Tcl_DecrRefCount(handlersObj); return TCL_ERROR; } info[0] = objv[i]; /* type */ TclNewIntObj(info[1], code); /* returnCode */ if (info[2] == NULL) { /* errorCodePrefix */ TclNewObj(info[2]); } info[3] = objv[i+2]; /* bindVariables */ info[4] = objv[i+3]; /* script */ bodyShared = !strcmp(TclGetString(objv[i+3]), "-"); Tcl_ListObjAppendElement(NULL, handlersObj, Tcl_NewListObj(5, info)); haveHandlers = 1; i += 3; break; } } if (bodyShared) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "last non-finally clause must not have a body of \"-\"", -1)); Tcl_DecrRefCount(handlersObj); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "TRY", "BADFALLTHROUGH", (char *)NULL); return TCL_ERROR; } if (!haveHandlers) { Tcl_DecrRefCount(handlersObj); handlersObj = NULL; } /* * Execute the body. */ Tcl_NRAddCallback(interp, TryPostBody, handlersObj, finallyObj, (void *)objv, INT2PTR(objc)); return TclNREvalObjEx(interp, bodyObj, 0, ((Interp *) interp)->cmdFramePtr, 1); } /* *---------------------------------------------------------------------- * * During -- * * This helper function patches together the updates to the interpreter's * return options that are needed when things fail during the processing * of a handler or finally script for the [try] command. * * Returns: * The new option dictionary. * *---------------------------------------------------------------------- */ static inline Tcl_Obj * During( Tcl_Interp *interp, int resultCode, /* The result code from the just-evaluated * script. */ Tcl_Obj *oldOptions, /* The old option dictionary. */ Tcl_Obj *errorInfo) /* An object to append to the errorinfo and * release, or NULL if nothing is to be added. * Designed to be used with Tcl_ObjPrintf. */ { Tcl_Obj *options; if (errorInfo != NULL) { Tcl_AppendObjToErrorInfo(interp, errorInfo); } options = Tcl_GetReturnOptions(interp, resultCode); TclDictPut(interp, options, "-during", oldOptions); Tcl_IncrRefCount(options); Tcl_DecrRefCount(oldOptions); return options; } /* *---------------------------------------------------------------------- * * TryPostBody -- * * Callback to handle the outcome of the execution of the body of a 'try' * command. * *---------------------------------------------------------------------- */ static int TryPostBody( void *data[], Tcl_Interp *interp, int result) { Tcl_Obj *resultObj, *options, *handlersObj, *finallyObj, *cmdObj, **objv; int code, objc; Tcl_Size i, numHandlers = 0; handlersObj = (Tcl_Obj *)data[0]; finallyObj = (Tcl_Obj *)data[1]; objv = (Tcl_Obj **)data[2]; objc = PTR2INT(data[3]); cmdObj = objv[0]; /* * Check for limits/rewinding, which override normal trapping behaviour. */ if (((Interp*) interp)->execEnvPtr->rewind || Tcl_LimitExceeded(interp)) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (\"%s\" body line %d)", TclGetString(cmdObj), Tcl_GetErrorLine(interp))); if (handlersObj != NULL) { Tcl_DecrRefCount(handlersObj); } return TCL_ERROR; } /* * Basic processing of the outcome of the script, including adding of * errorinfo trace. */ if (result == TCL_ERROR) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (\"%s\" body line %d)", TclGetString(cmdObj), Tcl_GetErrorLine(interp))); } resultObj = Tcl_GetObjResult(interp); Tcl_IncrRefCount(resultObj); options = Tcl_GetReturnOptions(interp, result); Tcl_IncrRefCount(options); Tcl_ResetResult(interp); /* * Handle the results. */ if (handlersObj != NULL) { int found = 0; Tcl_Obj **handlers, **info; TclListObjGetElements(NULL, handlersObj, &numHandlers, &handlers); for (i=0 ; i 0) { Tcl_Obj *varName; Tcl_ListObjIndex(NULL, info[3], 0, &varName); if (Tcl_ObjSetVar2(interp, varName, NULL, resultObj, TCL_LEAVE_ERR_MSG) == NULL) { Tcl_DecrRefCount(resultObj); goto handlerFailed; } Tcl_DecrRefCount(resultObj); if (numElems> 1) { Tcl_ListObjIndex(NULL, info[3], 1, &varName); if (Tcl_ObjSetVar2(interp, varName, NULL, options, TCL_LEAVE_ERR_MSG) == NULL) { goto handlerFailed; } } } else { /* * Dispose of the result to prevent a memleak. [Bug 2910044] */ Tcl_DecrRefCount(resultObj); } /* * Evaluate the handler body and process the outcome. Note that we * need to keep the kind of handler for debugging purposes, and in * any case anything we want from info[] must be extracted right * now because the info[] array is about to become invalid. There * is very little refcount handling here however, since we know * that the objects that we still want to refer to now were input * arguments to [try] and so are still on the Tcl value stack. */ handlerBodyObj = info[4]; Tcl_NRAddCallback(interp, TryPostHandler, objv, options, info[0], INT2PTR((finallyObj == NULL) ? 0 : objc - 1)); Tcl_DecrRefCount(handlersObj); return TclNREvalObjEx(interp, handlerBodyObj, 0, ((Interp *) interp)->cmdFramePtr, 4*i + 5); handlerFailed: resultObj = Tcl_GetObjResult(interp); Tcl_IncrRefCount(resultObj); options = During(interp, result, options, NULL); break; didNotMatch: continue; } /* * No handler matched; get rid of the list of handlers. */ Tcl_DecrRefCount(handlersObj); } /* * Process the finally clause. */ if (finallyObj != NULL) { Tcl_NRAddCallback(interp, TryPostFinal, resultObj, options, cmdObj, NULL); return TclNREvalObjEx(interp, finallyObj, 0, ((Interp *) interp)->cmdFramePtr, objc - 1); } /* * Install the correct result/options into the interpreter and clean up * any temporary storage. */ result = Tcl_SetReturnOptions(interp, options); Tcl_DecrRefCount(options); Tcl_SetObjResult(interp, resultObj); Tcl_DecrRefCount(resultObj); return result; } /* *---------------------------------------------------------------------- * * TryPostHandler -- * * Callback to handle the outcome of the execution of a handler of a * 'try' command. * *---------------------------------------------------------------------- */ static int TryPostHandler( void *data[], Tcl_Interp *interp, int result) { Tcl_Obj *resultObj, *cmdObj, *options, *handlerKindObj, **objv; Tcl_Obj *finallyObj; int finallyIndex; objv = (Tcl_Obj **)data[0]; options = (Tcl_Obj *)data[1]; handlerKindObj = (Tcl_Obj *)data[2]; finallyIndex = PTR2INT(data[3]); cmdObj = objv[0]; finallyObj = finallyIndex ? objv[finallyIndex] : 0; /* * Check for limits/rewinding, which override normal trapping behaviour. */ if (((Interp*) interp)->execEnvPtr->rewind || Tcl_LimitExceeded(interp)) { options = During(interp, result, options, Tcl_ObjPrintf( "\n (\"%s ... %s\" handler line %d)", TclGetString(cmdObj), TclGetString(handlerKindObj), Tcl_GetErrorLine(interp))); Tcl_DecrRefCount(options); return TCL_ERROR; } /* * The handler result completely substitutes for the result of the body. */ resultObj = Tcl_GetObjResult(interp); Tcl_IncrRefCount(resultObj); if (result == TCL_ERROR) { options = During(interp, result, options, Tcl_ObjPrintf( "\n (\"%s ... %s\" handler line %d)", TclGetString(cmdObj), TclGetString(handlerKindObj), Tcl_GetErrorLine(interp))); } else { Tcl_DecrRefCount(options); options = Tcl_GetReturnOptions(interp, result); Tcl_IncrRefCount(options); } /* * Process the finally clause if it is present. */ if (finallyObj != NULL) { Interp *iPtr = (Interp *) interp; Tcl_NRAddCallback(interp, TryPostFinal, resultObj, options, cmdObj, NULL); /* The 'finally' script is always the last argument word. */ return TclNREvalObjEx(interp, finallyObj, 0, iPtr->cmdFramePtr, finallyIndex); } /* * Install the correct result/options into the interpreter and clean up * any temporary storage. */ result = Tcl_SetReturnOptions(interp, options); Tcl_DecrRefCount(options); Tcl_SetObjResult(interp, resultObj); Tcl_DecrRefCount(resultObj); return result; } /* *---------------------------------------------------------------------- * * TryPostFinal -- * * Callback to handle the outcome of the execution of the finally script * of a 'try' command. * *---------------------------------------------------------------------- */ static int TryPostFinal( void *data[], Tcl_Interp *interp, int result) { Tcl_Obj *resultObj, *options, *cmdObj; resultObj = (Tcl_Obj *)data[0]; options = (Tcl_Obj *)data[1]; cmdObj = (Tcl_Obj *)data[2]; /* * If the result wasn't OK, we need to adjust the result options. */ if (result != TCL_OK) { Tcl_DecrRefCount(resultObj); resultObj = NULL; if (result == TCL_ERROR) { options = During(interp, result, options, Tcl_ObjPrintf( "\n (\"%s ... finally\" body line %d)", TclGetString(cmdObj), Tcl_GetErrorLine(interp))); } else { Tcl_Obj *origOptions = options; options = Tcl_GetReturnOptions(interp, result); Tcl_IncrRefCount(options); Tcl_DecrRefCount(origOptions); } } /* * Install the correct result/options into the interpreter and clean up * any temporary storage. */ result = Tcl_SetReturnOptions(interp, options); Tcl_DecrRefCount(options); if (resultObj != NULL) { Tcl_SetObjResult(interp, resultObj); Tcl_DecrRefCount(resultObj); } return result; } /* *---------------------------------------------------------------------- * * Tcl_WhileObjCmd -- * * This procedure is invoked to process the "while" Tcl command. See the * user documentation for details on what it does. * * With the bytecode compiler, this procedure is only called when a * command name is computed at runtime, and is "while" or the name to * which "while" was renamed: e.g., "set z while; $z {$i<100} {}" * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_WhileObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRWhileObjCmd, clientData, objc, objv); } int TclNRWhileObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { ForIterData *iterPtr; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "test command"); return TCL_ERROR; } /* * We reuse [for]'s callback, passing a NULL for the 'next' script. */ TclSmallAllocEx(interp, sizeof(ForIterData), iterPtr); iterPtr->cond = objv[1]; iterPtr->body = objv[2]; iterPtr->next = NULL; iterPtr->msg = "\n (\"while\" body line %d)"; iterPtr->word = 2; TclNRAddCallback(interp, TclNRForIterCallback, iterPtr, NULL, NULL, NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclListLines -- * * ??? * * Results: * Filled in array of line numbers? * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclListLines( Tcl_Obj *listObj, /* Pointer to obj holding a string with list * structure. Assumed to be valid. Assumed to * contain n elements. */ Tcl_Size line, /* Line the list as a whole starts on. */ Tcl_Size n, /* #elements in lines */ Tcl_Size *lines, /* Array of line numbers, to fill. */ Tcl_Obj *const *elems) /* The list elems as Tcl_Obj*, in need of * derived continuation data */ { const char *listStr = TclGetString(listObj); const char *listHead = listStr; Tcl_Size i, length = strlen(listStr); const char *element = NULL, *next = NULL; ContLineLoc *clLocPtr = TclContinuationsGet(listObj); Tcl_Size *clNext = (clLocPtr ? &clLocPtr->loc[0] : NULL); for (i = 0; i < n; i++) { TclFindElement(NULL, listStr, length, &element, &next, NULL, NULL); TclAdvanceLines(&line, listStr, element); /* Leading whitespace */ TclAdvanceContinuations(&line, &clNext, element - listHead); if (elems && clNext) { TclContinuationsEnterDerived(elems[i], element-listHead, clNext); } lines[i] = line; length -= (next - listStr); TclAdvanceLines(&line, element, next); /* Element */ listStr = next; if (*element == 0) { /* ASSERT i == n */ break; } } } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclCompCmds.c0000644000175000017500000031657014726623136015562 0ustar sergeisergei/* * tclCompCmds.c -- * * This file contains compilation procedures that compile various Tcl * commands into a sequence of instructions ("bytecodes"). * * Copyright © 1997-1998 Sun Microsystems, Inc. * Copyright © 2001 Kevin B. Kenny. All rights reserved. * Copyright © 2002 ActiveState Corporation. * Copyright © 2004-2013 Donal K. Fellows. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" #include /* * Prototypes for procedures defined later in this file: */ static AuxDataDupProc DupDictUpdateInfo; static AuxDataFreeProc FreeDictUpdateInfo; static AuxDataPrintProc PrintDictUpdateInfo; static AuxDataPrintProc DisassembleDictUpdateInfo; static AuxDataDupProc DupForeachInfo; static AuxDataFreeProc FreeForeachInfo; static AuxDataPrintProc PrintForeachInfo; static AuxDataPrintProc DisassembleForeachInfo; static AuxDataPrintProc PrintNewForeachInfo; static AuxDataPrintProc DisassembleNewForeachInfo; static int CompileEachloopCmd(Tcl_Interp *interp, Tcl_Parse *parsePtr, Command *cmdPtr, CompileEnv *envPtr, int collect); static int CompileDictEachCmd(Tcl_Interp *interp, Tcl_Parse *parsePtr, Command *cmdPtr, struct CompileEnv *envPtr, int collect); /* * The structures below define the AuxData types defined in this file. */ static const AuxDataType foreachInfoType = { "ForeachInfo", /* name */ DupForeachInfo, /* dupProc */ FreeForeachInfo, /* freeProc */ PrintForeachInfo, /* printProc */ DisassembleForeachInfo /* disassembleProc */ }; static const AuxDataType newForeachInfoType = { "NewForeachInfo", /* name */ DupForeachInfo, /* dupProc */ FreeForeachInfo, /* freeProc */ PrintNewForeachInfo, /* printProc */ DisassembleNewForeachInfo /* disassembleProc */ }; static const AuxDataType dictUpdateInfoType = { "DictUpdateInfo", /* name */ DupDictUpdateInfo, /* dupProc */ FreeDictUpdateInfo, /* freeProc */ PrintDictUpdateInfo, /* printProc */ DisassembleDictUpdateInfo /* disassembleProc */ }; /* *---------------------------------------------------------------------- * * TclGetAuxDataType -- * * This procedure looks up an Auxdata type by name. * * Results: * If an AuxData type with name matching "typeName" is found, a pointer * to its AuxDataType structure is returned; otherwise, NULL is returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ const AuxDataType * TclGetAuxDataType( const char *typeName) /* Name of AuxData type to look up. */ { if (!strcmp(typeName, foreachInfoType.name)) { return &foreachInfoType; } else if (!strcmp(typeName, newForeachInfoType.name)) { return &newForeachInfoType; } else if (!strcmp(typeName, dictUpdateInfoType.name)) { return &dictUpdateInfoType; } else if (!strcmp(typeName, tclJumptableInfoType.name)) { return &tclJumptableInfoType; } return NULL; } /* *---------------------------------------------------------------------- * * TclCompileAppendCmd -- * * Procedure called to compile the "append" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "append" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileAppendCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *varTokenPtr, *valueTokenPtr; int isScalar, localIndex, numWords, i; /* TODO: Consider support for compiling expanded args. */ numWords = parsePtr->numWords; if (numWords == 1) { return TCL_ERROR; } else if (numWords == 2) { /* * append varName == set varName */ return TclCompileSetCmd(interp, parsePtr, cmdPtr, envPtr); } else if (numWords > 3) { /* * APPEND instructions currently only handle one value, but we can * handle some multi-value cases by stringing them together. */ goto appendMultiple; } /* * Decide if we can use a frame slot for the var/array name or if we need * to emit code to compute and push the name at runtime. We use a frame * slot (entry in the array of local vars) if we are compiling a procedure * body and if the name is simple text that does not include namespace * qualifiers. */ varTokenPtr = TokenAfter(parsePtr->tokenPtr); PushVarNameWord(interp, varTokenPtr, envPtr, 0, &localIndex, &isScalar, 1); /* * We are doing an assignment, otherwise TclCompileSetCmd was called, so * push the new value. This will need to be extended to push a value for * each argument. */ valueTokenPtr = TokenAfter(varTokenPtr); CompileWord(envPtr, valueTokenPtr, interp, 2); /* * Emit instructions to set/get the variable. */ if (isScalar) { if (localIndex < 0) { TclEmitOpcode(INST_APPEND_STK, envPtr); } else { Emit14Inst(INST_APPEND_SCALAR, localIndex, envPtr); } } else { if (localIndex < 0) { TclEmitOpcode(INST_APPEND_ARRAY_STK, envPtr); } else { Emit14Inst(INST_APPEND_ARRAY, localIndex, envPtr); } } return TCL_OK; appendMultiple: /* * Can only handle the case where we are appending to a local scalar when * there are multiple values to append. Fortunately, this is common. */ varTokenPtr = TokenAfter(parsePtr->tokenPtr); localIndex = LocalScalarFromToken(varTokenPtr, envPtr); if (localIndex < 0) { return TCL_ERROR; } /* * Definitely appending to a local scalar; generate the words and append * them. */ valueTokenPtr = TokenAfter(varTokenPtr); for (i = 2 ; i < numWords ; i++) { CompileWord(envPtr, valueTokenPtr, interp, i); valueTokenPtr = TokenAfter(valueTokenPtr); } TclEmitInstInt4( INST_REVERSE, numWords-2, envPtr); for (i = 2 ; i < numWords ;) { Emit14Inst( INST_APPEND_SCALAR, localIndex, envPtr); if (++i < numWords) { TclEmitOpcode(INST_POP, envPtr); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileArray*Cmd -- * * Functions called to compile "array" subcommands. * * Results: * All return TCL_OK for a successful compile, and TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "array" subcommand at * runtime. * *---------------------------------------------------------------------- */ int TclCompileArrayExistsCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; int isScalar, localIndex; if (parsePtr->numWords != 2) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); PushVarNameWord(interp, tokenPtr, envPtr, TCL_NO_ELEMENT, &localIndex, &isScalar, 1); if (!isScalar) { return TCL_ERROR; } if (localIndex >= 0) { TclEmitInstInt4(INST_ARRAY_EXISTS_IMM, localIndex, envPtr); } else { TclEmitOpcode( INST_ARRAY_EXISTS_STK, envPtr); } return TCL_OK; } int TclCompileArraySetCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *varTokenPtr, *dataTokenPtr; int isScalar, localIndex, code = TCL_OK; int isDataLiteral, isDataValid, isDataEven; Tcl_Size len; int keyVar, valVar, infoIndex; int fwd, offsetBack, offsetFwd; Tcl_Obj *literalObj; ForeachInfo *infoPtr; if (parsePtr->numWords != 3) { return TCL_ERROR; } varTokenPtr = TokenAfter(parsePtr->tokenPtr); dataTokenPtr = TokenAfter(varTokenPtr); TclNewObj(literalObj); isDataLiteral = TclWordKnownAtCompileTime(dataTokenPtr, literalObj); isDataValid = (isDataLiteral && TclListObjLength(NULL, literalObj, &len) == TCL_OK); isDataEven = (isDataValid && (len & 1) == 0); /* * Special case: literal odd-length argument is always an error. */ if (isDataValid && !isDataEven) { /* Abandon custom compile and let invocation raise the error */ code = TclCompileBasic2ArgCmd(interp, parsePtr, cmdPtr, envPtr); goto done; /* * We used to compile to the bytecode that would throw the error, * but that was wrong because it would not invoke the array trace * on the variable. * PushStringLiteral(envPtr, "list must have an even number of elements"); PushStringLiteral(envPtr, "-errorcode {TCL ARGUMENT FORMAT}"); TclEmitInstInt4(INST_RETURN_IMM, TCL_ERROR, envPtr); TclEmitInt4( 0, envPtr); goto done; * */ } /* * Except for the special "ensure array" case below, when we're not in * a proc, we cannot do a better compile than generic. */ if ((varTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) || (envPtr->procPtr == NULL && !(isDataEven && len == 0))) { code = TclCompileBasic2ArgCmd(interp, parsePtr, cmdPtr, envPtr); goto done; } PushVarNameWord(interp, varTokenPtr, envPtr, TCL_NO_ELEMENT, &localIndex, &isScalar, 1); if (!isScalar) { code = TCL_ERROR; goto done; } /* * Special case: literal empty value argument is just an "ensure array" * operation. */ if (isDataEven && len == 0) { if (localIndex >= 0) { TclEmitInstInt4(INST_ARRAY_EXISTS_IMM, localIndex, envPtr); TclEmitInstInt1(INST_JUMP_TRUE1, 7, envPtr); TclEmitInstInt4(INST_ARRAY_MAKE_IMM, localIndex, envPtr); } else { TclEmitOpcode( INST_DUP, envPtr); TclEmitOpcode( INST_ARRAY_EXISTS_STK, envPtr); TclEmitInstInt1(INST_JUMP_TRUE1, 5, envPtr); TclEmitOpcode( INST_ARRAY_MAKE_STK, envPtr); TclEmitInstInt1(INST_JUMP1, 3, envPtr); /* Each branch decrements stack depth, but we only take one. */ TclAdjustStackDepth(1, envPtr); TclEmitOpcode( INST_POP, envPtr); } PushStringLiteral(envPtr, ""); goto done; } if (localIndex < 0) { /* * a non-local variable: upvar from a local one! This consumes the * variable name that was left at stacktop. */ localIndex = TclFindCompiledLocal(varTokenPtr->start, varTokenPtr->size, 1, envPtr); PushStringLiteral(envPtr, "0"); TclEmitInstInt4(INST_REVERSE, 2, envPtr); TclEmitInstInt4(INST_UPVAR, localIndex, envPtr); TclEmitOpcode(INST_POP, envPtr); } /* * Prepare for the internal foreach. */ keyVar = AnonymousLocal(envPtr); valVar = AnonymousLocal(envPtr); infoPtr = (ForeachInfo *)Tcl_Alloc(offsetof(ForeachInfo, varLists) + sizeof(ForeachVarList *)); infoPtr->numLists = 1; infoPtr->varLists[0] = (ForeachVarList *)Tcl_Alloc(offsetof(ForeachVarList, varIndexes) + 2 * sizeof(Tcl_Size)); infoPtr->varLists[0]->numVars = 2; infoPtr->varLists[0]->varIndexes[0] = keyVar; infoPtr->varLists[0]->varIndexes[1] = valVar; infoIndex = TclCreateAuxData(infoPtr, &newForeachInfoType, envPtr); /* * Start issuing instructions to write to the array. */ TclEmitInstInt4(INST_ARRAY_EXISTS_IMM, localIndex, envPtr); TclEmitInstInt1(INST_JUMP_TRUE1, 7, envPtr); TclEmitInstInt4(INST_ARRAY_MAKE_IMM, localIndex, envPtr); CompileWord(envPtr, dataTokenPtr, interp, 2); if (!isDataLiteral || !isDataValid) { /* * Only need this safety check if we're handling a non-literal or list * containing an invalid literal; with valid list literals, we've * already checked (worth it because literals are a very common * use-case with [array set]). */ TclEmitOpcode( INST_DUP, envPtr); TclEmitOpcode( INST_LIST_LENGTH, envPtr); PushStringLiteral(envPtr, "1"); TclEmitOpcode( INST_BITAND, envPtr); offsetFwd = CurrentOffset(envPtr); TclEmitInstInt1(INST_JUMP_FALSE1, 0, envPtr); PushStringLiteral(envPtr, "list must have an even number of elements"); PushStringLiteral(envPtr, "-errorcode {TCL ARGUMENT FORMAT}"); TclEmitInstInt4(INST_RETURN_IMM, TCL_ERROR, envPtr); TclEmitInt4( 0, envPtr); TclAdjustStackDepth(-1, envPtr); fwd = CurrentOffset(envPtr) - offsetFwd; TclStoreInt1AtPtr(fwd, envPtr->codeStart+offsetFwd+1); } TclEmitInstInt4(INST_FOREACH_START, infoIndex, envPtr); offsetBack = CurrentOffset(envPtr); Emit14Inst( INST_LOAD_SCALAR, keyVar, envPtr); Emit14Inst( INST_LOAD_SCALAR, valVar, envPtr); Emit14Inst( INST_STORE_ARRAY, localIndex, envPtr); TclEmitOpcode( INST_POP, envPtr); infoPtr->loopCtTemp = offsetBack - CurrentOffset(envPtr); /*misuse */ TclEmitOpcode( INST_FOREACH_STEP, envPtr); TclEmitOpcode( INST_FOREACH_END, envPtr); TclAdjustStackDepth(-3, envPtr); PushStringLiteral(envPtr, ""); done: Tcl_DecrRefCount(literalObj); return code; } int TclCompileArrayUnsetCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = TokenAfter(parsePtr->tokenPtr); int isScalar, localIndex; if (parsePtr->numWords != 2) { return TclCompileBasic2ArgCmd(interp, parsePtr, cmdPtr, envPtr); } PushVarNameWord(interp, tokenPtr, envPtr, TCL_NO_ELEMENT, &localIndex, &isScalar, 1); if (!isScalar) { return TCL_ERROR; } if (localIndex >= 0) { TclEmitInstInt4(INST_ARRAY_EXISTS_IMM, localIndex, envPtr); TclEmitInstInt1(INST_JUMP_FALSE1, 8, envPtr); TclEmitInstInt1(INST_UNSET_SCALAR, 1, envPtr); TclEmitInt4( localIndex, envPtr); } else { TclEmitOpcode( INST_DUP, envPtr); TclEmitOpcode( INST_ARRAY_EXISTS_STK, envPtr); TclEmitInstInt1(INST_JUMP_FALSE1, 6, envPtr); TclEmitInstInt1(INST_UNSET_STK, 1, envPtr); TclEmitInstInt1(INST_JUMP1, 3, envPtr); /* Each branch decrements stack depth, but we only take one. */ TclAdjustStackDepth(1, envPtr); TclEmitOpcode( INST_POP, envPtr); } PushStringLiteral(envPtr, ""); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileBreakCmd -- * * Procedure called to compile the "break" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "break" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileBreakCmd( TCL_UNUSED(Tcl_Interp *), Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { ExceptionRange *rangePtr; ExceptionAux *auxPtr; if (parsePtr->numWords != 1) { return TCL_ERROR; } /* * Find the innermost exception range that contains this command. */ rangePtr = TclGetInnermostExceptionRange(envPtr, TCL_BREAK, &auxPtr); if (rangePtr && rangePtr->type == LOOP_EXCEPTION_RANGE) { /* * Found the target! No need for a nasty INST_BREAK here. */ TclCleanupStackForBreakContinue(envPtr, auxPtr); TclAddLoopBreakFixup(envPtr, auxPtr); } else { /* * Emit a real break. */ TclEmitOpcode(INST_BREAK, envPtr); } TclAdjustStackDepth(1, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileCatchCmd -- * * Procedure called to compile the "catch" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "catch" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileCatchCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ JumpFixup jumpFixup; Tcl_Token *cmdTokenPtr, *resultNameTokenPtr, *optsNameTokenPtr; int resultIndex, optsIndex, range, dropScript = 0; int depth = TclGetStackDepth(envPtr); /* * If syntax does not match what we expect for [catch], do not compile. * Let runtime checks determine if syntax has changed. */ if (((int)parsePtr->numWords < 2) || ((int)parsePtr->numWords > 4)) { return TCL_ERROR; } /* * If variables were specified and the catch command is at global level * (not in a procedure), don't compile it inline: the payoff is too small. */ if (((int)parsePtr->numWords >= 3) && !EnvHasLVT(envPtr)) { return TCL_ERROR; } /* * Make sure the variable names, if any, have no substitutions and just * refer to local scalars. */ resultIndex = optsIndex = -1; cmdTokenPtr = TokenAfter(parsePtr->tokenPtr); if ((int)parsePtr->numWords >= 3) { resultNameTokenPtr = TokenAfter(cmdTokenPtr); /* DGP */ resultIndex = LocalScalarFromToken(resultNameTokenPtr, envPtr); if (resultIndex < 0) { return TCL_ERROR; } /* DKF */ if (parsePtr->numWords == 4) { optsNameTokenPtr = TokenAfter(resultNameTokenPtr); optsIndex = LocalScalarFromToken(optsNameTokenPtr, envPtr); if (optsIndex < 0) { return TCL_ERROR; } } } /* * We will compile the catch command. Declare the exception range that it * uses. * * If the body is a simple word, compile a BEGIN_CATCH instruction, * followed by the instructions to eval the body. * Otherwise, compile instructions to substitute the body text before * starting the catch, then BEGIN_CATCH, and then EVAL_STK to evaluate the * substituted body. * Care has to be taken to make sure that substitution happens outside the * catch range so that errors in the substitution are not caught. * [Bug 219184] * The reason for duplicating the script is that EVAL_STK would otherwise * begin by underflowing the stack below the mark set by BEGIN_CATCH4. */ range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); if (cmdTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { TclEmitInstInt4( INST_BEGIN_CATCH4, range, envPtr); ExceptionRangeStarts(envPtr, range); BODY(cmdTokenPtr, 1); } else { SetLineInformation(1); CompileTokens(envPtr, cmdTokenPtr, interp); TclEmitInstInt4( INST_BEGIN_CATCH4, range, envPtr); ExceptionRangeStarts(envPtr, range); TclEmitOpcode( INST_DUP, envPtr); TclEmitInvoke(envPtr, INST_EVAL_STK); /* drop the script */ dropScript = 1; TclEmitInstInt4( INST_REVERSE, 2, envPtr); TclEmitOpcode( INST_POP, envPtr); } ExceptionRangeEnds(envPtr, range); /* * Emit the "no errors" epilogue: push "0" (TCL_OK) as the catch result, * and jump around the "error case" code. */ TclCheckStackDepth(depth+1, envPtr); PushStringLiteral(envPtr, "0"); TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpFixup); /* * Emit the "error case" epilogue. Push the interpreter result and the * return code. */ ExceptionRangeTarget(envPtr, range, catchOffset); TclSetStackDepth(depth + dropScript, envPtr); if (dropScript) { TclEmitOpcode( INST_POP, envPtr); } /* Stack at this point is empty */ TclEmitOpcode( INST_PUSH_RESULT, envPtr); TclEmitOpcode( INST_PUSH_RETURN_CODE, envPtr); /* Stack at this point on both branches: result returnCode */ if (TclFixupForwardJumpToHere(envPtr, &jumpFixup, 127)) { Tcl_Panic("TclCompileCatchCmd: bad jump distance %" TCL_Z_MODIFIER "d", (CurrentOffset(envPtr) - jumpFixup.codeOffset)); } /* * Push the return options if the caller wants them. This needs to happen * before INST_END_CATCH */ if (optsIndex != -1) { TclEmitOpcode( INST_PUSH_RETURN_OPTIONS, envPtr); } /* * End the catch */ TclEmitOpcode( INST_END_CATCH, envPtr); /* * Save the result and return options if the caller wants them. This needs * to happen after INST_END_CATCH (compile-3.6/7). */ if (optsIndex != -1) { Emit14Inst( INST_STORE_SCALAR, optsIndex, envPtr); TclEmitOpcode( INST_POP, envPtr); } /* * At this point, the top of the stack is inconveniently ordered: * result returnCode * Reverse the stack to store the result. */ TclEmitInstInt4( INST_REVERSE, 2, envPtr); if (resultIndex != -1) { Emit14Inst( INST_STORE_SCALAR, resultIndex, envPtr); } TclEmitOpcode( INST_POP, envPtr); TclCheckStackDepth(depth+1, envPtr); return TCL_OK; } /*---------------------------------------------------------------------- * * TclCompileClockClicksCmd -- * * Procedure called to compile the "tcl::clock::clicks" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to run time. * * Side effects: * Instructions are added to envPtr to execute the "clock clicks" * command at runtime. * *---------------------------------------------------------------------- */ int TclCompileClockClicksCmd( TCL_UNUSED(Tcl_Interp *), Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { Tcl_Token* tokenPtr; switch (parsePtr->numWords) { case 1: /* * No args */ TclEmitInstInt1(INST_CLOCK_READ, 0, envPtr); break; case 2: /* * -milliseconds or -microseconds */ tokenPtr = TokenAfter(parsePtr->tokenPtr); if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD || tokenPtr[1].size < 4 || tokenPtr[1].size > 13) { return TCL_ERROR; } else if (!strncmp(tokenPtr[1].start, "-microseconds", tokenPtr[1].size)) { TclEmitInstInt1(INST_CLOCK_READ, 1, envPtr); break; } else if (!strncmp(tokenPtr[1].start, "-milliseconds", tokenPtr[1].size)) { TclEmitInstInt1(INST_CLOCK_READ, 2, envPtr); break; } else { return TCL_ERROR; } default: return TCL_ERROR; } return TCL_OK; } /*---------------------------------------------------------------------- * * TclCompileClockReadingCmd -- * * Procedure called to compile the "tcl::clock::microseconds", * "tcl::clock::milliseconds" and "tcl::clock::seconds" commands. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to run time. * * Side effects: * Instructions are added to envPtr to execute the "clock clicks" * command at runtime. * * Client data is 1 for microseconds, 2 for milliseconds, 3 for seconds. *---------------------------------------------------------------------- */ int TclCompileClockReadingCmd( TCL_UNUSED(Tcl_Interp *), Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { if (parsePtr->numWords != 1) { return TCL_ERROR; } TclEmitInstInt1(INST_CLOCK_READ, PTR2INT(cmdPtr->objClientData), envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileConcatCmd -- * * Procedure called to compile the "concat" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "concat" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileConcatCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Obj *objPtr, *listObj; Tcl_Token *tokenPtr; int i; /* TODO: Consider compiling expansion case. */ if (parsePtr->numWords == 1) { /* * [concat] without arguments just pushes an empty object. */ PushStringLiteral(envPtr, ""); return TCL_OK; } /* * Test if all arguments are compile-time known. If they are, we can * implement with a simple push. */ TclNewObj(listObj); for (i = 1, tokenPtr = parsePtr->tokenPtr; i < (int)parsePtr->numWords; i++) { tokenPtr = TokenAfter(tokenPtr); TclNewObj(objPtr); if (!TclWordKnownAtCompileTime(tokenPtr, objPtr)) { Tcl_DecrRefCount(objPtr); Tcl_DecrRefCount(listObj); listObj = NULL; break; } (void) Tcl_ListObjAppendElement(NULL, listObj, objPtr); } if (listObj != NULL) { Tcl_Obj **objs; const char *bytes; Tcl_Size len, slen; TclListObjGetElements(NULL, listObj, &len, &objs); objPtr = Tcl_ConcatObj(len, objs); Tcl_DecrRefCount(listObj); bytes = TclGetStringFromObj(objPtr, &slen); PushLiteral(envPtr, bytes, slen); Tcl_DecrRefCount(objPtr); return TCL_OK; } /* * General case: runtime concat. */ for (i = 1, tokenPtr = parsePtr->tokenPtr; i < (int)parsePtr->numWords; i++) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, i); } TclEmitInstInt4( INST_CONCAT_STK, i-1, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileConstCmd -- * * Procedure called to compile the "const" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "const" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileConstCmd( Tcl_Interp *interp, /* The interpreter. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *varTokenPtr, *valueTokenPtr; int isScalar, localIndex; /* * Need exactly two arguments. */ if (parsePtr->numWords != 3) { return TCL_ERROR; } /* * Decide if we can use a frame slot for the var/array name or if we need * to emit code to compute and push the name at runtime. We use a frame * slot (entry in the array of local vars) if we are compiling a procedure * body and if the name is simple text that does not include namespace * qualifiers. */ varTokenPtr = TokenAfter(parsePtr->tokenPtr); PushVarNameWord(interp, varTokenPtr, envPtr, 0, &localIndex, &isScalar, 1); /* * If the user specified an array element, we don't bother handling * that. */ if (!isScalar) { return TCL_ERROR; } /* * We are doing an assignment to set the value of the constant. This will * need to be extended to push a value for each argument. */ valueTokenPtr = TokenAfter(varTokenPtr); CompileWord(envPtr, valueTokenPtr, interp, 2); if (localIndex < 0) { TclEmitOpcode(INST_CONST_STK, envPtr); } else { TclEmitInstInt4(INST_CONST_IMM, localIndex, envPtr); } /* * The const command's result is an empty string. */ PushStringLiteral(envPtr, ""); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileContinueCmd -- * * Procedure called to compile the "continue" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "continue" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileContinueCmd( TCL_UNUSED(Tcl_Interp *), Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { ExceptionRange *rangePtr; ExceptionAux *auxPtr; /* * There should be no argument after the "continue". */ if (parsePtr->numWords != 1) { return TCL_ERROR; } /* * See if we can find a valid continueOffset (i.e., not -1) in the * innermost containing exception range. */ rangePtr = TclGetInnermostExceptionRange(envPtr, TCL_CONTINUE, &auxPtr); if (rangePtr && rangePtr->type == LOOP_EXCEPTION_RANGE) { /* * Found the target! No need for a nasty INST_CONTINUE here. */ TclCleanupStackForBreakContinue(envPtr, auxPtr); TclAddLoopContinueFixup(envPtr, auxPtr); } else { /* * Emit a real continue. */ TclEmitOpcode(INST_CONTINUE, envPtr); } TclAdjustStackDepth(1, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileDict*Cmd -- * * Functions called to compile "dict" subcommands. * * Results: * All return TCL_OK for a successful compile, and TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "dict" subcommand at * runtime. * *---------------------------------------------------------------------- */ int TclCompileDictSetCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; int i, dictVarIndex; Tcl_Token *varTokenPtr; /* * There must be at least one argument after the command. */ if ((int)parsePtr->numWords < 4) { return TCL_ERROR; } /* * The dictionary variable must be a local scalar that is knowable at * compile time; anything else exceeds the complexity of the opcode. So * discover what the index is. */ varTokenPtr = TokenAfter(parsePtr->tokenPtr); dictVarIndex = LocalScalarFromToken(varTokenPtr, envPtr); if (dictVarIndex < 0) { return TCL_ERROR; } /* * Remaining words (key path and value to set) can be handled normally. */ tokenPtr = TokenAfter(varTokenPtr); for (i=2 ; i< (int)parsePtr->numWords ; i++) { CompileWord(envPtr, tokenPtr, interp, i); tokenPtr = TokenAfter(tokenPtr); } /* * Now emit the instruction to do the dict manipulation. */ TclEmitInstInt4( INST_DICT_SET, (int)parsePtr->numWords-3, envPtr); TclEmitInt4( dictVarIndex, envPtr); TclAdjustStackDepth(-1, envPtr); return TCL_OK; } int TclCompileDictIncrCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *varTokenPtr, *keyTokenPtr; int dictVarIndex, incrAmount; /* * There must be at least two arguments after the command. */ if ((int)parsePtr->numWords < 3 || (int)parsePtr->numWords > 4) { return TCL_ERROR; } varTokenPtr = TokenAfter(parsePtr->tokenPtr); keyTokenPtr = TokenAfter(varTokenPtr); /* * Parse the increment amount, if present. */ if (parsePtr->numWords == 4) { const char *word; Tcl_Size numBytes; int code; Tcl_Token *incrTokenPtr; Tcl_Obj *intObj; incrTokenPtr = TokenAfter(keyTokenPtr); if (incrTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { return TclCompileBasic2Or3ArgCmd(interp, parsePtr,cmdPtr, envPtr); } word = incrTokenPtr[1].start; numBytes = incrTokenPtr[1].size; intObj = Tcl_NewStringObj(word, numBytes); Tcl_IncrRefCount(intObj); code = TclGetIntFromObj(NULL, intObj, &incrAmount); TclDecrRefCount(intObj); if (code != TCL_OK) { return TclCompileBasic2Or3ArgCmd(interp, parsePtr,cmdPtr, envPtr); } } else { incrAmount = 1; } /* * The dictionary variable must be a local scalar that is knowable at * compile time; anything else exceeds the complexity of the opcode. So * discover what the index is. */ dictVarIndex = LocalScalarFromToken(varTokenPtr, envPtr); if (dictVarIndex < 0) { return TclCompileBasic2Or3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } /* * Emit the key and the code to actually do the increment. */ CompileWord(envPtr, keyTokenPtr, interp, 2); TclEmitInstInt4( INST_DICT_INCR_IMM, incrAmount, envPtr); TclEmitInt4( dictVarIndex, envPtr); return TCL_OK; } int TclCompileDictGetCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; int i; /* * There must be at least two arguments after the command (the single-arg * case is legal, but too special and magic for us to deal with here). */ /* TODO: Consider support for compiling expanded args. */ if ((int)parsePtr->numWords < 3) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); /* * Only compile this because we need INST_DICT_GET anyway. */ for (i=1 ; i<(int)parsePtr->numWords ; i++) { CompileWord(envPtr, tokenPtr, interp, i); tokenPtr = TokenAfter(tokenPtr); } TclEmitInstInt4(INST_DICT_GET, (int)parsePtr->numWords-2, envPtr); TclAdjustStackDepth(-1, envPtr); return TCL_OK; } int TclCompileDictGetWithDefaultCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; int i; /* * There must be at least three arguments after the command. */ /* TODO: Consider support for compiling expanded args. */ if ((int)parsePtr->numWords < 4) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); for (i=1 ; i<(int)parsePtr->numWords ; i++) { CompileWord(envPtr, tokenPtr, interp, i); tokenPtr = TokenAfter(tokenPtr); } TclEmitInstInt4(INST_DICT_GET_DEF, (int)parsePtr->numWords-3, envPtr); TclAdjustStackDepth(-2, envPtr); return TCL_OK; } int TclCompileDictExistsCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; int i; /* * There must be at least two arguments after the command (the single-arg * case is legal, but too special and magic for us to deal with here). */ /* TODO: Consider support for compiling expanded args. */ if ((int)parsePtr->numWords < 3) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); /* * Now we do the code generation. */ for (i=1 ; i<(int)parsePtr->numWords ; i++) { CompileWord(envPtr, tokenPtr, interp, i); tokenPtr = TokenAfter(tokenPtr); } TclEmitInstInt4(INST_DICT_EXISTS, (int)parsePtr->numWords-2, envPtr); TclAdjustStackDepth(-1, envPtr); return TCL_OK; } int TclCompileDictUnsetCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; int i, dictVarIndex; /* * There must be at least one argument after the variable name for us to * compile to bytecode. */ /* TODO: Consider support for compiling expanded args. */ if ((int)parsePtr->numWords < 3) { return TCL_ERROR; } /* * The dictionary variable must be a local scalar that is knowable at * compile time; anything else exceeds the complexity of the opcode. So * discover what the index is. */ tokenPtr = TokenAfter(parsePtr->tokenPtr); dictVarIndex = LocalScalarFromToken(tokenPtr, envPtr); if (dictVarIndex < 0) { return TclCompileBasicMin2ArgCmd(interp, parsePtr, cmdPtr, envPtr); } /* * Remaining words (the key path) can be handled normally. */ for (i=2 ; i<(int)parsePtr->numWords ; i++) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, i); } /* * Now emit the instruction to do the dict manipulation. */ TclEmitInstInt4( INST_DICT_UNSET, (int)parsePtr->numWords-2, envPtr); TclEmitInt4( dictVarIndex, envPtr); return TCL_OK; } int TclCompileDictCreateCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ int worker; /* Temp var for building the value in. */ Tcl_Token *tokenPtr; Tcl_Obj *keyObj, *valueObj, *dictObj; const char *bytes; int i; Tcl_Size len; if ((parsePtr->numWords & 1) == 0) { return TCL_ERROR; } /* * See if we can build the value at compile time... */ tokenPtr = TokenAfter(parsePtr->tokenPtr); TclNewObj(dictObj); Tcl_IncrRefCount(dictObj); for (i=1 ; i<(int)parsePtr->numWords ; i+=2) { TclNewObj(keyObj); Tcl_IncrRefCount(keyObj); if (!TclWordKnownAtCompileTime(tokenPtr, keyObj)) { Tcl_DecrRefCount(keyObj); Tcl_DecrRefCount(dictObj); goto nonConstant; } tokenPtr = TokenAfter(tokenPtr); TclNewObj(valueObj); Tcl_IncrRefCount(valueObj); if (!TclWordKnownAtCompileTime(tokenPtr, valueObj)) { Tcl_DecrRefCount(keyObj); Tcl_DecrRefCount(valueObj); Tcl_DecrRefCount(dictObj); goto nonConstant; } tokenPtr = TokenAfter(tokenPtr); Tcl_DictObjPut(NULL, dictObj, keyObj, valueObj); Tcl_DecrRefCount(keyObj); Tcl_DecrRefCount(valueObj); } /* * We did! Excellent. The "verifyDict" is to do type forcing. */ bytes = TclGetStringFromObj(dictObj, &len); PushLiteral(envPtr, bytes, len); TclEmitOpcode( INST_DUP, envPtr); TclEmitOpcode( INST_DICT_VERIFY, envPtr); Tcl_DecrRefCount(dictObj); return TCL_OK; /* * Otherwise, we've got to issue runtime code to do the building, which we * do by [dict set]ting into an unnamed local variable. This requires that * we are in a context with an LVT. */ nonConstant: worker = AnonymousLocal(envPtr); if (worker < 0) { return TclCompileBasicMin0ArgCmd(interp, parsePtr, cmdPtr, envPtr); } PushStringLiteral(envPtr, ""); Emit14Inst( INST_STORE_SCALAR, worker, envPtr); TclEmitOpcode( INST_POP, envPtr); tokenPtr = TokenAfter(parsePtr->tokenPtr); for (i=1 ; i<(int)parsePtr->numWords ; i+=2) { CompileWord(envPtr, tokenPtr, interp, i); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, i+1); tokenPtr = TokenAfter(tokenPtr); TclEmitInstInt4( INST_DICT_SET, 1, envPtr); TclEmitInt4( worker, envPtr); TclAdjustStackDepth(-1, envPtr); TclEmitOpcode( INST_POP, envPtr); } Emit14Inst( INST_LOAD_SCALAR, worker, envPtr); TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); TclEmitInt4( worker, envPtr); return TCL_OK; } int TclCompileDictMergeCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; int i, workerIndex, infoIndex, outLoop; /* * Deal with some special edge cases. Note that in the case with one * argument, the only thing to do is to verify the dict-ness. */ /* TODO: Consider support for compiling expanded args. (less likely) */ if ((int)parsePtr->numWords < 2) { PushStringLiteral(envPtr, ""); return TCL_OK; } else if (parsePtr->numWords == 2) { tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); TclEmitOpcode( INST_DUP, envPtr); TclEmitOpcode( INST_DICT_VERIFY, envPtr); return TCL_OK; } /* * There's real merging work to do. * * Allocate some working space. This means we'll only ever compile this * command when there's an LVT present. */ workerIndex = AnonymousLocal(envPtr); if (workerIndex < 0) { return TclCompileBasicMin2ArgCmd(interp, parsePtr, cmdPtr, envPtr); } infoIndex = AnonymousLocal(envPtr); /* * Get the first dictionary and verify that it is so. */ tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); TclEmitOpcode( INST_DUP, envPtr); TclEmitOpcode( INST_DICT_VERIFY, envPtr); Emit14Inst( INST_STORE_SCALAR, workerIndex, envPtr); TclEmitOpcode( INST_POP, envPtr); /* * For each of the remaining dictionaries... */ outLoop = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); TclEmitInstInt4( INST_BEGIN_CATCH4, outLoop, envPtr); ExceptionRangeStarts(envPtr, outLoop); for (i=2 ; i<(int)parsePtr->numWords ; i++) { /* * Get the dictionary, and merge its pairs into the first dict (using * a small loop). */ tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, i); TclEmitInstInt4( INST_DICT_FIRST, infoIndex, envPtr); TclEmitInstInt1( INST_JUMP_TRUE1, 24, envPtr); TclEmitInstInt4( INST_REVERSE, 2, envPtr); TclEmitInstInt4( INST_DICT_SET, 1, envPtr); TclEmitInt4( workerIndex, envPtr); TclAdjustStackDepth(-1, envPtr); TclEmitOpcode( INST_POP, envPtr); TclEmitInstInt4( INST_DICT_NEXT, infoIndex, envPtr); TclEmitInstInt1( INST_JUMP_FALSE1, -20, envPtr); TclEmitOpcode( INST_POP, envPtr); TclEmitOpcode( INST_POP, envPtr); TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); TclEmitInt4( infoIndex, envPtr); } ExceptionRangeEnds(envPtr, outLoop); TclEmitOpcode( INST_END_CATCH, envPtr); /* * Clean up any state left over. */ Emit14Inst( INST_LOAD_SCALAR, workerIndex, envPtr); TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); TclEmitInt4( workerIndex, envPtr); TclEmitInstInt1( INST_JUMP1, 18, envPtr); /* * If an exception happens when starting to iterate over the second (and * subsequent) dicts. This is strictly not necessary, but it is nice. */ TclAdjustStackDepth(-1, envPtr); ExceptionRangeTarget(envPtr, outLoop, catchOffset); TclEmitOpcode( INST_PUSH_RETURN_OPTIONS, envPtr); TclEmitOpcode( INST_PUSH_RESULT, envPtr); TclEmitOpcode( INST_END_CATCH, envPtr); TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); TclEmitInt4( workerIndex, envPtr); TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); TclEmitInt4( infoIndex, envPtr); TclEmitOpcode( INST_RETURN_STK, envPtr); return TCL_OK; } int TclCompileDictForCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { return CompileDictEachCmd(interp, parsePtr, cmdPtr, envPtr, TCL_EACH_KEEP_NONE); } int TclCompileDictMapCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { return CompileDictEachCmd(interp, parsePtr, cmdPtr, envPtr, TCL_EACH_COLLECT); } int CompileDictEachCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr, /* Holds resulting instructions. */ int collect) /* Flag == TCL_EACH_COLLECT to collect and * construct a new dictionary with the loop * body result. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *varsTokenPtr, *dictTokenPtr, *bodyTokenPtr; int keyVarIndex, valueVarIndex, nameChars, loopRange, catchRange; int infoIndex, jumpDisplacement, bodyTargetOffset, emptyTargetOffset; Tcl_Size numVars; int endTargetOffset; int collectVar = -1; /* Index of temp var holding the result * dict. */ const char **argv; Tcl_DString buffer; /* * There must be three arguments after the command. */ if (parsePtr->numWords != 4) { return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } varsTokenPtr = TokenAfter(parsePtr->tokenPtr); dictTokenPtr = TokenAfter(varsTokenPtr); bodyTokenPtr = TokenAfter(dictTokenPtr); if (varsTokenPtr->type != TCL_TOKEN_SIMPLE_WORD || bodyTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } /* * Create temporary variable to capture return values from loop body when * we're collecting results. */ if (collect == TCL_EACH_COLLECT) { collectVar = AnonymousLocal(envPtr); if (collectVar < 0) { return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } } /* * Check we've got a pair of variables and that they are local variables. * Then extract their indices in the LVT. */ Tcl_DStringInit(&buffer); TclDStringAppendToken(&buffer, &varsTokenPtr[1]); if (Tcl_SplitList(NULL, Tcl_DStringValue(&buffer), &numVars, &argv) != TCL_OK) { Tcl_DStringFree(&buffer); return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } Tcl_DStringFree(&buffer); if (numVars != 2) { Tcl_Free((void *)argv); return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } nameChars = strlen(argv[0]); keyVarIndex = LocalScalar(argv[0], nameChars, envPtr); nameChars = strlen(argv[1]); valueVarIndex = LocalScalar(argv[1], nameChars, envPtr); Tcl_Free((void *)argv); if ((keyVarIndex < 0) || (valueVarIndex < 0)) { return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } /* * Allocate a temporary variable to store the iterator reference. The * variable will contain a Tcl_DictSearch reference which will be * allocated by INST_DICT_FIRST and disposed when the variable is unset * (at which point it should also have been finished with). */ infoIndex = AnonymousLocal(envPtr); if (infoIndex < 0) { return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } /* * Preparation complete; issue instructions. Note that this code issues * fixed-sized jumps. That simplifies things a lot! * * First up, initialize the accumulator dictionary if needed. */ if (collect == TCL_EACH_COLLECT) { PushStringLiteral(envPtr, ""); Emit14Inst( INST_STORE_SCALAR, collectVar, envPtr); TclEmitOpcode( INST_POP, envPtr); } /* * Get the dictionary and start the iteration. No catching of errors at * this point. */ CompileWord(envPtr, dictTokenPtr, interp, 2); /* * Now we catch errors from here on so that we can finalize the search * started by Tcl_DictObjFirst above. */ catchRange = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); TclEmitInstInt4( INST_BEGIN_CATCH4, catchRange, envPtr); ExceptionRangeStarts(envPtr, catchRange); TclEmitInstInt4( INST_DICT_FIRST, infoIndex, envPtr); emptyTargetOffset = CurrentOffset(envPtr); TclEmitInstInt4( INST_JUMP_TRUE4, 0, envPtr); /* * Inside the iteration, write the loop variables. */ bodyTargetOffset = CurrentOffset(envPtr); Emit14Inst( INST_STORE_SCALAR, keyVarIndex, envPtr); TclEmitOpcode( INST_POP, envPtr); Emit14Inst( INST_STORE_SCALAR, valueVarIndex, envPtr); TclEmitOpcode( INST_POP, envPtr); /* * Set up the loop exception targets. */ loopRange = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); ExceptionRangeStarts(envPtr, loopRange); /* * Compile the loop body itself. It should be stack-neutral. */ BODY(bodyTokenPtr, 3); if (collect == TCL_EACH_COLLECT) { Emit14Inst( INST_LOAD_SCALAR, keyVarIndex, envPtr); TclEmitInstInt4(INST_OVER, 1, envPtr); TclEmitInstInt4(INST_DICT_SET, 1, envPtr); TclEmitInt4( collectVar, envPtr); TclAdjustStackDepth(-1, envPtr); TclEmitOpcode( INST_POP, envPtr); } TclEmitOpcode( INST_POP, envPtr); /* * Both exception target ranges (error and loop) end here. */ ExceptionRangeEnds(envPtr, loopRange); ExceptionRangeEnds(envPtr, catchRange); /* * Continue (or just normally process) by getting the next pair of items * from the dictionary and jumping back to the code to write them into * variables if there is another pair. */ ExceptionRangeTarget(envPtr, loopRange, continueOffset); TclEmitInstInt4( INST_DICT_NEXT, infoIndex, envPtr); jumpDisplacement = bodyTargetOffset - CurrentOffset(envPtr); TclEmitInstInt4( INST_JUMP_FALSE4, jumpDisplacement, envPtr); endTargetOffset = CurrentOffset(envPtr); TclEmitInstInt1( INST_JUMP1, 0, envPtr); /* * Error handler "finally" clause, which force-terminates the iteration * and re-throws the error. */ TclAdjustStackDepth(-1, envPtr); ExceptionRangeTarget(envPtr, catchRange, catchOffset); TclEmitOpcode( INST_PUSH_RETURN_OPTIONS, envPtr); TclEmitOpcode( INST_PUSH_RESULT, envPtr); TclEmitOpcode( INST_END_CATCH, envPtr); TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); TclEmitInt4( infoIndex, envPtr); if (collect == TCL_EACH_COLLECT) { TclEmitInstInt1(INST_UNSET_SCALAR, 0, envPtr); TclEmitInt4( collectVar, envPtr); } TclEmitOpcode( INST_RETURN_STK, envPtr); /* * Otherwise we're done (the jump after the DICT_FIRST points here) and we * need to pop the bogus key/value pair (pushed to keep stack calculations * easy!) Note that we skip the END_CATCH. [Bug 1382528] */ jumpDisplacement = CurrentOffset(envPtr) - emptyTargetOffset; TclUpdateInstInt4AtPc(INST_JUMP_TRUE4, jumpDisplacement, envPtr->codeStart + emptyTargetOffset); jumpDisplacement = CurrentOffset(envPtr) - endTargetOffset; TclUpdateInstInt1AtPc(INST_JUMP1, jumpDisplacement, envPtr->codeStart + endTargetOffset); TclEmitOpcode( INST_POP, envPtr); TclEmitOpcode( INST_POP, envPtr); ExceptionRangeTarget(envPtr, loopRange, breakOffset); TclFinalizeLoopExceptionRange(envPtr, loopRange); TclEmitOpcode( INST_END_CATCH, envPtr); /* * Final stage of the command (normal case) is that we push an empty * object (or push the accumulator as the result object). This is done * last to promote peephole optimization when it's dropped immediately. */ TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); TclEmitInt4( infoIndex, envPtr); if (collect == TCL_EACH_COLLECT) { Emit14Inst( INST_LOAD_SCALAR, collectVar, envPtr); TclEmitInstInt1(INST_UNSET_SCALAR, 0, envPtr); TclEmitInt4( collectVar, envPtr); } else { PushStringLiteral(envPtr, ""); } return TCL_OK; } int TclCompileDictUpdateCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ int i, dictIndex, numVars, range, infoIndex; Tcl_Token **keyTokenPtrs, *dictVarTokenPtr, *bodyTokenPtr, *tokenPtr; DictUpdateInfo *duiPtr; JumpFixup jumpFixup; /* * There must be at least one argument after the command. */ if ((int)parsePtr->numWords < 5) { return TCL_ERROR; } /* * Parse the command. Expect the following: * dict update ? ...? */ if (((int)parsePtr->numWords - 1) & 1) { return TCL_ERROR; } numVars = (parsePtr->numWords - 3) / 2; /* * The dictionary variable must be a local scalar that is knowable at * compile time; anything else exceeds the complexity of the opcode. So * discover what the index is. */ dictVarTokenPtr = TokenAfter(parsePtr->tokenPtr); dictIndex = LocalScalarFromToken(dictVarTokenPtr, envPtr); if (dictIndex < 0) { goto issueFallback; } /* * Assemble the instruction metadata. This is complex enough that it is * represented as auxData; it holds an ordered list of variable indices * that are to be used. */ duiPtr = (DictUpdateInfo *)Tcl_Alloc(offsetof(DictUpdateInfo, varIndices) + sizeof(size_t) * numVars); duiPtr->length = numVars; keyTokenPtrs = (Tcl_Token **)TclStackAlloc(interp, sizeof(Tcl_Token *) * numVars); tokenPtr = TokenAfter(dictVarTokenPtr); for (i=0 ; ivarIndices[i] = LocalScalarFromToken(tokenPtr, envPtr); if (duiPtr->varIndices[i] == TCL_INDEX_NONE) { goto failedUpdateInfoAssembly; } tokenPtr = TokenAfter(tokenPtr); } if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { goto failedUpdateInfoAssembly; } bodyTokenPtr = tokenPtr; /* * The list of variables to bind is stored in auxiliary data so that it * can't be snagged by literal sharing and forced to shimmer dangerously. */ infoIndex = TclCreateAuxData(duiPtr, &dictUpdateInfoType, envPtr); for (i=0 ; inumWords - 1); ExceptionRangeEnds(envPtr, range); /* * Normal termination code: the stack has the key list below the result of * the body evaluation: swap them and finish the update code. */ TclEmitOpcode( INST_END_CATCH, envPtr); TclEmitInstInt4( INST_REVERSE, 2, envPtr); TclEmitInstInt4( INST_DICT_UPDATE_END, dictIndex, envPtr); TclEmitInt4( infoIndex, envPtr); /* * Jump around the exceptional termination code. */ TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpFixup); /* * Termination code for non-ok returns: stash the result and return * options in the stack, bring up the key list, finish the update code, * and finally return with the caught return data */ ExceptionRangeTarget(envPtr, range, catchOffset); TclEmitOpcode( INST_PUSH_RESULT, envPtr); TclEmitOpcode( INST_PUSH_RETURN_OPTIONS, envPtr); TclEmitOpcode( INST_END_CATCH, envPtr); TclEmitInstInt4( INST_REVERSE, 3, envPtr); TclEmitInstInt4( INST_DICT_UPDATE_END, dictIndex, envPtr); TclEmitInt4( infoIndex, envPtr); TclEmitInvoke(envPtr,INST_RETURN_STK); if (TclFixupForwardJumpToHere(envPtr, &jumpFixup, 127)) { Tcl_Panic("TclCompileDictCmd(update): bad jump distance %" TCL_Z_MODIFIER "d", CurrentOffset(envPtr) - jumpFixup.codeOffset); } TclStackFree(interp, keyTokenPtrs); return TCL_OK; /* * Clean up after a failure to create the DictUpdateInfo structure. */ failedUpdateInfoAssembly: Tcl_Free(duiPtr); TclStackFree(interp, keyTokenPtrs); issueFallback: return TclCompileBasicMin2ArgCmd(interp, parsePtr, cmdPtr, envPtr); } int TclCompileDictAppendCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; int i, dictVarIndex; /* * There must be at least two argument after the command. And we impose an * (arbitrary) safe limit; anyone exceeding it should stop worrying about * speed quite so much. ;-) */ /* TODO: Consider support for compiling expanded args. */ if ((int)parsePtr->numWords<4 || (int)parsePtr->numWords>100) { return TCL_ERROR; } /* * Get the index of the local variable that we will be working with. */ tokenPtr = TokenAfter(parsePtr->tokenPtr); dictVarIndex = LocalScalarFromToken(tokenPtr, envPtr); if (dictVarIndex < 0) { return TclCompileBasicMin2ArgCmd(interp, parsePtr,cmdPtr, envPtr); } /* * Produce the string to concatenate onto the dictionary entry. */ tokenPtr = TokenAfter(tokenPtr); for (i=2 ; i<(int)parsePtr->numWords ; i++) { CompileWord(envPtr, tokenPtr, interp, i); tokenPtr = TokenAfter(tokenPtr); } if ((int)parsePtr->numWords > 4) { TclEmitInstInt1(INST_STR_CONCAT1, (int)parsePtr->numWords-3, envPtr); } /* * Do the concatenation. */ TclEmitInstInt4(INST_DICT_APPEND, dictVarIndex, envPtr); return TCL_OK; } int TclCompileDictLappendCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *varTokenPtr, *keyTokenPtr, *valueTokenPtr; int dictVarIndex; /* * There must be three arguments after the command. */ /* TODO: Consider support for compiling expanded args. */ /* Probably not. Why is INST_DICT_LAPPEND limited to one value? */ if (parsePtr->numWords != 4) { return TCL_ERROR; } /* * Parse the arguments. */ varTokenPtr = TokenAfter(parsePtr->tokenPtr); keyTokenPtr = TokenAfter(varTokenPtr); valueTokenPtr = TokenAfter(keyTokenPtr); dictVarIndex = LocalScalarFromToken(varTokenPtr, envPtr); if (dictVarIndex < 0) { return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } /* * Issue the implementation. */ CompileWord(envPtr, keyTokenPtr, interp, 2); CompileWord(envPtr, valueTokenPtr, interp, 3); TclEmitInstInt4( INST_DICT_LAPPEND, dictVarIndex, envPtr); return TCL_OK; } int TclCompileDictWithCmd( Tcl_Interp *interp, /* Used for looking up stuff. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ int i, range, varNameTmp = -1, pathTmp = -1, keysTmp, gotPath; int dictVar, bodyIsEmpty = 1; Tcl_Token *varTokenPtr, *tokenPtr; JumpFixup jumpFixup; const char *ptr, *end; /* * There must be at least one argument after the command. */ /* TODO: Consider support for compiling expanded args. */ if ((int)parsePtr->numWords < 3) { return TCL_ERROR; } /* * Parse the command (trivially). Expect the following: * dict with ? ...? */ varTokenPtr = TokenAfter(parsePtr->tokenPtr); tokenPtr = TokenAfter(varTokenPtr); for (i=3 ; i<(int)parsePtr->numWords ; i++) { tokenPtr = TokenAfter(tokenPtr); } if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { return TclCompileBasicMin2ArgCmd(interp, parsePtr, cmdPtr, envPtr); } /* * Test if the last word is an empty script; if so, we can compile it in * all cases, but if it is non-empty we need local variable table entries * to hold the temporary variables (used to keep stack usage simple). */ for (ptr=tokenPtr[1].start,end=ptr+tokenPtr[1].size ; ptr!=end ; ptr++) { if (*ptr!=' ' && *ptr!='\t' && *ptr!='\n' && *ptr!='\r') { if (envPtr->procPtr == NULL) { return TclCompileBasicMin2ArgCmd(interp, parsePtr, cmdPtr, envPtr); } bodyIsEmpty = 0; break; } } /* * Determine if we're manipulating a dict in a simple local variable. */ gotPath = ((int)parsePtr->numWords > 3); dictVar = LocalScalarFromToken(varTokenPtr, envPtr); /* * Special case: an empty body means we definitely have no need to issue * try-finally style code or to allocate local variable table entries for * storing temporaries. Still need to do both INST_DICT_EXPAND and * INST_DICT_RECOMBINE_* though, because we can't determine if we're free * of traces. */ if (bodyIsEmpty) { if (dictVar >= 0) { if (gotPath) { /* * Case: Path into dict in LVT with empty body. */ tokenPtr = TokenAfter(varTokenPtr); for (i=2 ; i<(int)parsePtr->numWords-1 ; i++) { CompileWord(envPtr, tokenPtr, interp, i); tokenPtr = TokenAfter(tokenPtr); } TclEmitInstInt4(INST_LIST, (int)parsePtr->numWords-3,envPtr); Emit14Inst( INST_LOAD_SCALAR, dictVar, envPtr); TclEmitInstInt4(INST_OVER, 1, envPtr); TclEmitOpcode( INST_DICT_EXPAND, envPtr); TclEmitInstInt4(INST_DICT_RECOMBINE_IMM, dictVar, envPtr); } else { /* * Case: Direct dict in LVT with empty body. */ PushStringLiteral(envPtr, ""); Emit14Inst( INST_LOAD_SCALAR, dictVar, envPtr); PushStringLiteral(envPtr, ""); TclEmitOpcode( INST_DICT_EXPAND, envPtr); TclEmitInstInt4(INST_DICT_RECOMBINE_IMM, dictVar, envPtr); } } else { if (gotPath) { /* * Case: Path into dict in non-simple var with empty body. */ tokenPtr = varTokenPtr; for (i=1 ; i<(int)parsePtr->numWords-1 ; i++) { CompileWord(envPtr, tokenPtr, interp, i); tokenPtr = TokenAfter(tokenPtr); } TclEmitInstInt4(INST_LIST, (int)parsePtr->numWords-3,envPtr); TclEmitInstInt4(INST_OVER, 1, envPtr); TclEmitOpcode( INST_LOAD_STK, envPtr); TclEmitInstInt4(INST_OVER, 1, envPtr); TclEmitOpcode( INST_DICT_EXPAND, envPtr); TclEmitOpcode( INST_DICT_RECOMBINE_STK, envPtr); } else { /* * Case: Direct dict in non-simple var with empty body. */ CompileWord(envPtr, varTokenPtr, interp, 1); TclEmitOpcode( INST_DUP, envPtr); TclEmitOpcode( INST_LOAD_STK, envPtr); PushStringLiteral(envPtr, ""); TclEmitOpcode( INST_DICT_EXPAND, envPtr); PushStringLiteral(envPtr, ""); TclEmitInstInt4(INST_REVERSE, 2, envPtr); TclEmitOpcode( INST_DICT_RECOMBINE_STK, envPtr); } } PushStringLiteral(envPtr, ""); return TCL_OK; } /* * OK, we have a non-trivial body. This means that the focus is on * generating a try-finally structure where the INST_DICT_RECOMBINE_* goes * in the 'finally' clause. * * Start by allocating local (unnamed, untraced) working variables. */ if (dictVar == -1) { varNameTmp = AnonymousLocal(envPtr); } if (gotPath) { pathTmp = AnonymousLocal(envPtr); } keysTmp = AnonymousLocal(envPtr); /* * Issue instructions. First, the part to expand the dictionary. */ if (dictVar == -1) { CompileWord(envPtr, varTokenPtr, interp, 1); Emit14Inst( INST_STORE_SCALAR, varNameTmp, envPtr); } tokenPtr = TokenAfter(varTokenPtr); if (gotPath) { for (i=2 ; i<(int)parsePtr->numWords-1 ; i++) { CompileWord(envPtr, tokenPtr, interp, i); tokenPtr = TokenAfter(tokenPtr); } TclEmitInstInt4( INST_LIST, (int)parsePtr->numWords-3,envPtr); Emit14Inst( INST_STORE_SCALAR, pathTmp, envPtr); TclEmitOpcode( INST_POP, envPtr); } if (dictVar == -1) { TclEmitOpcode( INST_LOAD_STK, envPtr); } else { Emit14Inst( INST_LOAD_SCALAR, dictVar, envPtr); } if (gotPath) { Emit14Inst( INST_LOAD_SCALAR, pathTmp, envPtr); } else { PushStringLiteral(envPtr, ""); } TclEmitOpcode( INST_DICT_EXPAND, envPtr); Emit14Inst( INST_STORE_SCALAR, keysTmp, envPtr); TclEmitOpcode( INST_POP, envPtr); /* * Now the body of the [dict with]. */ range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); TclEmitInstInt4( INST_BEGIN_CATCH4, range, envPtr); ExceptionRangeStarts(envPtr, range); BODY(tokenPtr, parsePtr->numWords - 1); ExceptionRangeEnds(envPtr, range); /* * Now fold the results back into the dictionary in the OK case. */ TclEmitOpcode( INST_END_CATCH, envPtr); if (dictVar == -1) { Emit14Inst( INST_LOAD_SCALAR, varNameTmp, envPtr); } if (gotPath) { Emit14Inst( INST_LOAD_SCALAR, pathTmp, envPtr); } else { PushStringLiteral(envPtr, ""); } Emit14Inst( INST_LOAD_SCALAR, keysTmp, envPtr); if (dictVar == -1) { TclEmitOpcode( INST_DICT_RECOMBINE_STK, envPtr); } else { TclEmitInstInt4( INST_DICT_RECOMBINE_IMM, dictVar, envPtr); } TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpFixup); /* * Now fold the results back into the dictionary in the exception case. */ TclAdjustStackDepth(-1, envPtr); ExceptionRangeTarget(envPtr, range, catchOffset); TclEmitOpcode( INST_PUSH_RETURN_OPTIONS, envPtr); TclEmitOpcode( INST_PUSH_RESULT, envPtr); TclEmitOpcode( INST_END_CATCH, envPtr); if (dictVar == -1) { Emit14Inst( INST_LOAD_SCALAR, varNameTmp, envPtr); } if ((int)parsePtr->numWords > 3) { Emit14Inst( INST_LOAD_SCALAR, pathTmp, envPtr); } else { PushStringLiteral(envPtr, ""); } Emit14Inst( INST_LOAD_SCALAR, keysTmp, envPtr); if (dictVar == -1) { TclEmitOpcode( INST_DICT_RECOMBINE_STK, envPtr); } else { TclEmitInstInt4( INST_DICT_RECOMBINE_IMM, dictVar, envPtr); } TclEmitInvoke(envPtr, INST_RETURN_STK); /* * Prepare for the start of the next command. */ if (TclFixupForwardJumpToHere(envPtr, &jumpFixup, 127)) { Tcl_Panic("TclCompileDictCmd(update): bad jump distance %" TCL_Z_MODIFIER "d", CurrentOffset(envPtr) - jumpFixup.codeOffset); } return TCL_OK; } /* *---------------------------------------------------------------------- * * DupDictUpdateInfo, FreeDictUpdateInfo -- * * Functions to duplicate, release and print the aux data created for use * with the INST_DICT_UPDATE_START and INST_DICT_UPDATE_END instructions. * * Results: * DupDictUpdateInfo: a copy of the auxiliary data * FreeDictUpdateInfo: none * PrintDictUpdateInfo: none * DisassembleDictUpdateInfo: none * * Side effects: * DupDictUpdateInfo: allocates memory * FreeDictUpdateInfo: releases memory * PrintDictUpdateInfo: none * DisassembleDictUpdateInfo: none * *---------------------------------------------------------------------- */ static void * DupDictUpdateInfo( void *clientData) { DictUpdateInfo *dui1Ptr, *dui2Ptr; size_t len; dui1Ptr = (DictUpdateInfo *)clientData; len = offsetof(DictUpdateInfo, varIndices) + sizeof(size_t) * dui1Ptr->length; dui2Ptr = (DictUpdateInfo *)Tcl_Alloc(len); memcpy(dui2Ptr, dui1Ptr, len); return dui2Ptr; } static void FreeDictUpdateInfo( void *clientData) { Tcl_Free(clientData); } static void PrintDictUpdateInfo( void *clientData, Tcl_Obj *appendObj, TCL_UNUSED(ByteCode *), TCL_UNUSED(size_t)) { DictUpdateInfo *duiPtr = (DictUpdateInfo *)clientData; Tcl_Size i; for (i=0 ; ilength ; i++) { if (i) { Tcl_AppendToObj(appendObj, ", ", -1); } Tcl_AppendPrintfToObj(appendObj, "%%v%" TCL_Z_MODIFIER "u", duiPtr->varIndices[i]); } } static void DisassembleDictUpdateInfo( void *clientData, Tcl_Obj *dictObj, TCL_UNUSED(ByteCode *), TCL_UNUSED(size_t)) { DictUpdateInfo *duiPtr = (DictUpdateInfo *)clientData; Tcl_Size i; Tcl_Obj *variables; TclNewObj(variables); for (i=0 ; ilength ; i++) { Tcl_ListObjAppendElement(NULL, variables, Tcl_NewWideIntObj(duiPtr->varIndices[i])); } TclDictPut(NULL, dictObj, "variables", variables); } /* *---------------------------------------------------------------------- * * TclCompileErrorCmd -- * * Procedure called to compile the "error" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "error" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileErrorCmd( Tcl_Interp *interp, /* Used for context. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; /* * General syntax: [error message ?errorInfo? ?errorCode?] */ if ((int)parsePtr->numWords < 2 || (int)parsePtr->numWords > 4) { return TCL_ERROR; } /* * Handle the message. */ tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); /* * Construct the options. Note that -code and -level are not here. */ if (parsePtr->numWords == 2) { PushStringLiteral(envPtr, ""); } else { PushStringLiteral(envPtr, "-errorinfo"); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); if (parsePtr->numWords == 3) { TclEmitInstInt4( INST_LIST, 2, envPtr); } else { PushStringLiteral(envPtr, "-errorcode"); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 3); TclEmitInstInt4( INST_LIST, 4, envPtr); } } /* * Issue the error via 'returnImm error 0'. */ TclEmitInstInt4( INST_RETURN_IMM, TCL_ERROR, envPtr); TclEmitInt4( 0, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileExprCmd -- * * Procedure called to compile the "expr" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "expr" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileExprCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { Tcl_Token *firstWordPtr; if (parsePtr->numWords == 1) { return TCL_ERROR; } /* * TIP #280: Use the per-word line information of the current command. */ envPtr->line = envPtr->extCmdMapPtr->loc[ envPtr->extCmdMapPtr->nuloc-1].line[1]; firstWordPtr = TokenAfter(parsePtr->tokenPtr); TclCompileExprWords(interp, firstWordPtr, (int)parsePtr->numWords-1, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileForCmd -- * * Procedure called to compile the "for" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "for" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileForCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *startTokenPtr, *testTokenPtr, *nextTokenPtr, *bodyTokenPtr; JumpFixup jumpEvalCondFixup; int bodyCodeOffset, nextCodeOffset, jumpDist; int bodyRange, nextRange; if (parsePtr->numWords != 5) { return TCL_ERROR; } /* * If the test expression requires substitutions, don't compile the for * command inline. E.g., the expression might cause the loop to never * execute or execute forever, as in "for {} "$x > 5" {incr x} {}". */ startTokenPtr = TokenAfter(parsePtr->tokenPtr); testTokenPtr = TokenAfter(startTokenPtr); if (testTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { return TCL_ERROR; } /* * Bail out also if the body or the next expression require substitutions * in order to insure correct behaviour [Bug 219166] */ nextTokenPtr = TokenAfter(testTokenPtr); bodyTokenPtr = TokenAfter(nextTokenPtr); if ((nextTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) || (bodyTokenPtr->type != TCL_TOKEN_SIMPLE_WORD)) { return TCL_ERROR; } /* * Inline compile the initial command. */ BODY(startTokenPtr, 1); TclEmitOpcode(INST_POP, envPtr); /* * Jump to the evaluation of the condition. This code uses the "loop * rotation" optimisation (which eliminates one branch from the loop). * "for start cond next body" produces then: * start * goto A * B: body : bodyCodeOffset * next : nextCodeOffset, continueOffset * A: cond -> result : testCodeOffset * if (result) goto B */ TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpEvalCondFixup); /* * Compile the loop body. */ bodyRange = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); bodyCodeOffset = ExceptionRangeStarts(envPtr, bodyRange); BODY(bodyTokenPtr, 4); ExceptionRangeEnds(envPtr, bodyRange); TclEmitOpcode(INST_POP, envPtr); /* * Compile the "next" subcommand. Note that this exception range will not * have a continueOffset (other than -1) connected to it; it won't trap * TCL_CONTINUE but rather just TCL_BREAK. */ nextRange = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); envPtr->exceptAuxArrayPtr[nextRange].supportsContinue = 0; nextCodeOffset = ExceptionRangeStarts(envPtr, nextRange); BODY(nextTokenPtr, 3); ExceptionRangeEnds(envPtr, nextRange); TclEmitOpcode(INST_POP, envPtr); /* * Compile the test expression then emit the conditional jump that * terminates the for. */ if (TclFixupForwardJumpToHere(envPtr, &jumpEvalCondFixup, 127)) { bodyCodeOffset += 3; nextCodeOffset += 3; } SetLineInformation(2); TclCompileExprWords(interp, testTokenPtr, 1, envPtr); jumpDist = CurrentOffset(envPtr) - bodyCodeOffset; if (jumpDist > 127) { TclEmitInstInt4(INST_JUMP_TRUE4, -jumpDist, envPtr); } else { TclEmitInstInt1(INST_JUMP_TRUE1, -jumpDist, envPtr); } /* * Fix the starting points of the exception ranges (may have moved due to * jump type modification) and set where the exceptions target. */ envPtr->exceptArrayPtr[bodyRange].codeOffset = bodyCodeOffset; envPtr->exceptArrayPtr[bodyRange].continueOffset = nextCodeOffset; envPtr->exceptArrayPtr[nextRange].codeOffset = nextCodeOffset; ExceptionRangeTarget(envPtr, bodyRange, breakOffset); ExceptionRangeTarget(envPtr, nextRange, breakOffset); TclFinalizeLoopExceptionRange(envPtr, bodyRange); TclFinalizeLoopExceptionRange(envPtr, nextRange); /* * The for command's result is an empty string. */ PushStringLiteral(envPtr, ""); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileForeachCmd -- * * Procedure called to compile the "foreach" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "foreach" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileForeachCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { return CompileEachloopCmd(interp, parsePtr, cmdPtr, envPtr, TCL_EACH_KEEP_NONE); } /* *---------------------------------------------------------------------- * * TclCompileLmapCmd -- * * Procedure called to compile the "lmap" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "lmap" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileLmapCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to the definition of the command * being compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { return CompileEachloopCmd(interp, parsePtr, cmdPtr, envPtr, TCL_EACH_COLLECT); } /* *---------------------------------------------------------------------- * * CompileEachloopCmd -- * * Procedure called to compile the "foreach" and "lmap" commands. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "foreach" command at * runtime. * *---------------------------------------------------------------------- */ static int CompileEachloopCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr, /* Holds resulting instructions. */ int collect) /* Select collecting or accumulating mode * (TCL_EACH_*) */ { DefineLineInformation; /* TIP #280 */ Proc *procPtr = envPtr->procPtr; ForeachInfo *infoPtr=NULL; /* Points to the structure describing this * foreach command. Stored in a AuxData * record in the ByteCode. */ Tcl_Token *tokenPtr, *bodyTokenPtr; int jumpBackOffset, infoIndex, range; int numWords, numLists, i, code = TCL_OK; Tcl_Size j; Tcl_Obj *varListObj = NULL; /* * If the foreach command isn't in a procedure, don't compile it inline: * the payoff is too small. */ if (procPtr == NULL) { return TCL_ERROR; } numWords = (int)parsePtr->numWords; if ((numWords < 4) || (numWords%2 != 0)) { return TCL_ERROR; } /* * Bail out if the body requires substitutions in order to ensure correct * behaviour. [Bug 219166] */ for (i = 0, tokenPtr = parsePtr->tokenPtr; i < numWords-1; i++) { tokenPtr = TokenAfter(tokenPtr); } bodyTokenPtr = tokenPtr; if (bodyTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { return TCL_ERROR; } /* * Create and initialize the ForeachInfo and ForeachVarList data * structures describing this command. Then create a AuxData record * pointing to the ForeachInfo structure. */ numLists = (numWords - 2)/2; infoPtr = (ForeachInfo *)Tcl_Alloc(offsetof(ForeachInfo, varLists) + numLists * sizeof(ForeachVarList *)); infoPtr->numLists = 0; /* Count this up as we go */ /* * Parse each var list into sequence of var names. Don't * compile the foreach inline if any var name needs substitutions or isn't * a scalar, or if any var list needs substitutions. */ TclNewObj(varListObj); for (i = 0, tokenPtr = parsePtr->tokenPtr; i < numWords-1; i++, tokenPtr = TokenAfter(tokenPtr)) { ForeachVarList *varListPtr; Tcl_Size numVars; if (i%2 != 1) { continue; } /* * If the variable list is empty, we can enter an infinite loop when * the interpreted version would not. Take care to ensure this does * not happen. [Bug 1671138] */ if (!TclWordKnownAtCompileTime(tokenPtr, varListObj) || TCL_OK != TclListObjLength(NULL, varListObj, &numVars) || numVars == 0) { code = TCL_ERROR; goto done; } varListPtr = (ForeachVarList *)Tcl_Alloc(offsetof(ForeachVarList, varIndexes) + numVars * sizeof(varListPtr->varIndexes[0])); varListPtr->numVars = numVars; infoPtr->varLists[i/2] = varListPtr; infoPtr->numLists++; for (j = 0; j < numVars; j++) { Tcl_Obj *varNameObj; const char *bytes; int varIndex; Tcl_Size length; Tcl_ListObjIndex(NULL, varListObj, j, &varNameObj); bytes = TclGetStringFromObj(varNameObj, &length); varIndex = LocalScalar(bytes, length, envPtr); if (varIndex < 0) { code = TCL_ERROR; goto done; } varListPtr->varIndexes[j] = varIndex; } Tcl_SetObjLength(varListObj, 0); } /* * We will compile the foreach command. */ infoIndex = TclCreateAuxData(infoPtr, &newForeachInfoType, envPtr); /* * Create the collecting object, unshared. */ if (collect == TCL_EACH_COLLECT) { TclEmitInstInt4(INST_LIST, 0, envPtr); } /* * Evaluate each value list and leave it on stack. */ for (i = 0, tokenPtr = parsePtr->tokenPtr; i < numWords-1; i++, tokenPtr = TokenAfter(tokenPtr)) { if ((i%2 == 0) && (i > 0)) { CompileWord(envPtr, tokenPtr, interp, i); } } TclEmitInstInt4(INST_FOREACH_START, infoIndex, envPtr); /* * Inline compile the loop body. */ range = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); ExceptionRangeStarts(envPtr, range); BODY(bodyTokenPtr, numWords - 1); ExceptionRangeEnds(envPtr, range); if (collect == TCL_EACH_COLLECT) { TclEmitOpcode(INST_LMAP_COLLECT, envPtr); } else { TclEmitOpcode( INST_POP, envPtr); } /* * Bottom of loop code: assign each loop variable and check whether * to terminate the loop. Set the loop's break target. */ ExceptionRangeTarget(envPtr, range, continueOffset); TclEmitOpcode(INST_FOREACH_STEP, envPtr); ExceptionRangeTarget(envPtr, range, breakOffset); TclFinalizeLoopExceptionRange(envPtr, range); TclEmitOpcode(INST_FOREACH_END, envPtr); TclAdjustStackDepth(-(numLists+2), envPtr); /* * Set the jumpback distance from INST_FOREACH_STEP to the start of the * body's code. Misuse loopCtTemp for storing the jump size. */ jumpBackOffset = envPtr->exceptArrayPtr[range].continueOffset - envPtr->exceptArrayPtr[range].codeOffset; infoPtr->loopCtTemp = -jumpBackOffset; /* * The command's result is an empty string if not collecting. If * collecting, it is automatically left on stack after FOREACH_END. */ if (collect != TCL_EACH_COLLECT) { PushStringLiteral(envPtr, ""); } done: if (code == TCL_ERROR) { FreeForeachInfo(infoPtr); } Tcl_DecrRefCount(varListObj); return code; } /* *---------------------------------------------------------------------- * * DupForeachInfo -- * * This procedure duplicates a ForeachInfo structure created as auxiliary * data during the compilation of a foreach command. * * Results: * A pointer to a newly allocated copy of the existing ForeachInfo * structure is returned. * * Side effects: * Storage for the copied ForeachInfo record is allocated. If the * original ForeachInfo structure pointed to any ForeachVarList records, * these structures are also copied and pointers to them are stored in * the new ForeachInfo record. * *---------------------------------------------------------------------- */ static void * DupForeachInfo( void *clientData) /* The foreach command's compilation auxiliary * data to duplicate. */ { ForeachInfo *srcPtr = (ForeachInfo *)clientData; ForeachInfo *dupPtr; ForeachVarList *srcListPtr, *dupListPtr; int numVars, i, j, numLists = srcPtr->numLists; dupPtr = (ForeachInfo *)Tcl_Alloc(offsetof(ForeachInfo, varLists) + numLists * sizeof(ForeachVarList *)); dupPtr->numLists = numLists; dupPtr->firstValueTemp = srcPtr->firstValueTemp; dupPtr->loopCtTemp = srcPtr->loopCtTemp; for (i = 0; i < numLists; i++) { srcListPtr = srcPtr->varLists[i]; numVars = srcListPtr->numVars; dupListPtr = (ForeachVarList *)Tcl_Alloc(offsetof(ForeachVarList, varIndexes) + numVars * sizeof(size_t)); dupListPtr->numVars = numVars; for (j = 0; j < numVars; j++) { dupListPtr->varIndexes[j] = srcListPtr->varIndexes[j]; } dupPtr->varLists[i] = dupListPtr; } return dupPtr; } /* *---------------------------------------------------------------------- * * FreeForeachInfo -- * * Procedure to free a ForeachInfo structure created as auxiliary data * during the compilation of a foreach command. * * Results: * None. * * Side effects: * Storage for the ForeachInfo structure pointed to by the ClientData * argument is freed as is any ForeachVarList record pointed to by the * ForeachInfo structure. * *---------------------------------------------------------------------- */ static void FreeForeachInfo( void *clientData) /* The foreach command's compilation auxiliary * data to free. */ { ForeachInfo *infoPtr = (ForeachInfo *)clientData; ForeachVarList *listPtr; size_t i, numLists = infoPtr->numLists; for (i = 0; i < numLists; i++) { listPtr = infoPtr->varLists[i]; Tcl_Free(listPtr); } Tcl_Free(infoPtr); } /* *---------------------------------------------------------------------- * * PrintForeachInfo, DisassembleForeachInfo -- * * Functions to write a human-readable or script-readablerepresentation * of a ForeachInfo structure to a Tcl_Obj for debugging. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void PrintForeachInfo( void *clientData, Tcl_Obj *appendObj, TCL_UNUSED(ByteCode *), TCL_UNUSED(size_t)) { ForeachInfo *infoPtr = (ForeachInfo *)clientData; ForeachVarList *varsPtr; Tcl_Size i, j; Tcl_AppendToObj(appendObj, "data=[", -1); for (i=0 ; inumLists ; i++) { if (i) { Tcl_AppendToObj(appendObj, ", ", -1); } Tcl_AppendPrintfToObj(appendObj, "%%v%" TCL_Z_MODIFIER "u", (infoPtr->firstValueTemp + i)); } Tcl_AppendPrintfToObj(appendObj, "], loop=%%v%" TCL_Z_MODIFIER "u", infoPtr->loopCtTemp); for (i=0 ; inumLists ; i++) { if (i) { Tcl_AppendToObj(appendObj, ",", -1); } Tcl_AppendPrintfToObj(appendObj, "\n\t\t it%%v%" TCL_Z_MODIFIER "u\t[", (infoPtr->firstValueTemp + i)); varsPtr = infoPtr->varLists[i]; for (j=0 ; jnumVars ; j++) { if (j) { Tcl_AppendToObj(appendObj, ", ", -1); } Tcl_AppendPrintfToObj(appendObj, "%%v%" TCL_Z_MODIFIER "u", varsPtr->varIndexes[j]); } Tcl_AppendToObj(appendObj, "]", -1); } } static void PrintNewForeachInfo( void *clientData, Tcl_Obj *appendObj, TCL_UNUSED(ByteCode *), TCL_UNUSED(size_t)) { ForeachInfo *infoPtr = (ForeachInfo *)clientData; ForeachVarList *varsPtr; Tcl_Size i, j; Tcl_AppendPrintfToObj(appendObj, "jumpOffset=%+" TCL_Z_MODIFIER "d, vars=", infoPtr->loopCtTemp); for (i=0 ; inumLists ; i++) { if (i) { Tcl_AppendToObj(appendObj, ",", -1); } Tcl_AppendToObj(appendObj, "[", -1); varsPtr = infoPtr->varLists[i]; for (j=0 ; jnumVars ; j++) { if (j) { Tcl_AppendToObj(appendObj, ",", -1); } Tcl_AppendPrintfToObj(appendObj, "%%v%" TCL_Z_MODIFIER "u", varsPtr->varIndexes[j]); } Tcl_AppendToObj(appendObj, "]", -1); } } static void DisassembleForeachInfo( void *clientData, Tcl_Obj *dictObj, TCL_UNUSED(ByteCode *), TCL_UNUSED(size_t)) { ForeachInfo *infoPtr = (ForeachInfo *)clientData; ForeachVarList *varsPtr; Tcl_Size i, j; Tcl_Obj *objPtr, *innerPtr; /* * Data stores. */ TclNewObj(objPtr); for (i=0 ; inumLists ; i++) { Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewWideIntObj(infoPtr->firstValueTemp + i)); } TclDictPut(NULL, dictObj, "data", objPtr); /* * Loop counter. */ TclDictPut(NULL, dictObj, "loop", Tcl_NewWideIntObj(infoPtr->loopCtTemp)); /* * Assignment targets. */ TclNewObj(objPtr); for (i=0 ; inumLists ; i++) { TclNewObj(innerPtr); varsPtr = infoPtr->varLists[i]; for (j=0 ; jnumVars ; j++) { Tcl_ListObjAppendElement(NULL, innerPtr, Tcl_NewWideIntObj(varsPtr->varIndexes[j])); } Tcl_ListObjAppendElement(NULL, objPtr, innerPtr); } TclDictPut(NULL, dictObj, "assign", objPtr); } static void DisassembleNewForeachInfo( void *clientData, Tcl_Obj *dictObj, TCL_UNUSED(ByteCode *), TCL_UNUSED(size_t)) { ForeachInfo *infoPtr = (ForeachInfo *)clientData; ForeachVarList *varsPtr; Tcl_Size i, j; Tcl_Obj *objPtr, *innerPtr; /* * Jump offset. */ TclDictPut(NULL, dictObj, "jumpOffset", Tcl_NewWideIntObj(infoPtr->loopCtTemp)); /* * Assignment targets. */ TclNewObj(objPtr); for (i=0 ; inumLists ; i++) { TclNewObj(innerPtr); varsPtr = infoPtr->varLists[i]; for (j=0 ; jnumVars ; j++) { Tcl_ListObjAppendElement(NULL, innerPtr, Tcl_NewWideIntObj(varsPtr->varIndexes[j])); } Tcl_ListObjAppendElement(NULL, objPtr, innerPtr); } TclDictPut(NULL, dictObj, "assign", objPtr); } /* *---------------------------------------------------------------------- * * TclCompileFormatCmd -- * * Procedure called to compile the "format" command. Handles cases that * can be done as constants or simple string concatenation only. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "format" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileFormatCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = parsePtr->tokenPtr; Tcl_Obj **objv, *formatObj, *tmpObj; const char *bytes, *start; int i, j; Tcl_Size len; /* * Don't handle any guaranteed-error cases. */ if ((int)parsePtr->numWords < 2) { return TCL_ERROR; } /* * Check if the argument words are all compile-time-known literals; that's * a case we can handle by compiling to a constant. */ TclNewObj(formatObj); Tcl_IncrRefCount(formatObj); tokenPtr = TokenAfter(tokenPtr); if (!TclWordKnownAtCompileTime(tokenPtr, formatObj)) { Tcl_DecrRefCount(formatObj); return TCL_ERROR; } objv = (Tcl_Obj **)Tcl_Alloc(((int)parsePtr->numWords-2) * sizeof(Tcl_Obj *)); for (i=0 ; i+2 < (int)parsePtr->numWords ; i++) { tokenPtr = TokenAfter(tokenPtr); TclNewObj(objv[i]); Tcl_IncrRefCount(objv[i]); if (!TclWordKnownAtCompileTime(tokenPtr, objv[i])) { goto checkForStringConcatCase; } } /* * Everything is a literal, so the result is constant too (or an error if * the format is broken). Do the format now. */ tmpObj = Tcl_Format(interp, TclGetString(formatObj), (int)parsePtr->numWords-2, objv); for (; --i>=0 ;) { Tcl_DecrRefCount(objv[i]); } Tcl_Free(objv); Tcl_DecrRefCount(formatObj); if (tmpObj == NULL) { TclCompileSyntaxError(interp, envPtr); return TCL_OK; } /* * Not an error, always a constant result, so just push the result as a * literal. Job done. */ bytes = TclGetStringFromObj(tmpObj, &len); PushLiteral(envPtr, bytes, len); Tcl_DecrRefCount(tmpObj); return TCL_OK; checkForStringConcatCase: /* * See if we can generate a sequence of things to concatenate. This * requires that all the % sequences be %s or %%, as everything else is * sufficiently complex that we don't bother. * * First, get the state of the system relatively sensible (cleaning up * after our attempt to spot a literal). */ for (; i>=0 ; i--) { Tcl_DecrRefCount(objv[i]); } Tcl_Free(objv); tokenPtr = TokenAfter(parsePtr->tokenPtr); tokenPtr = TokenAfter(tokenPtr); i = 0; /* * Now scan through and check for non-%s and non-%% substitutions. */ for (bytes = TclGetString(formatObj) ; *bytes ; bytes++) { if (*bytes == '%') { bytes++; if (*bytes == 's') { i++; continue; } else if (*bytes == '%') { continue; } Tcl_DecrRefCount(formatObj); return TCL_ERROR; } } /* * Check if the number of things to concatenate will fit in a byte. */ if (i+2 != (int)parsePtr->numWords || i > 125) { Tcl_DecrRefCount(formatObj); return TCL_ERROR; } /* * Generate the pushes of the things to concatenate, a sequence of * literals and compiled tokens (of which at least one is non-literal or * we'd have the case in the first half of this function) which we will * concatenate. */ i = 0; /* The count of things to concat. */ j = 2; /* The index into the argument tokens, for * TIP#280 handling. */ start = TclGetString(formatObj); /* The start of the currently-scanned literal * in the format string. */ TclNewObj(tmpObj); /* The buffer used to accumulate the literal * being built. */ for (bytes = start ; *bytes ; bytes++) { if (*bytes == '%') { Tcl_AppendToObj(tmpObj, start, bytes - start); if (*++bytes == '%') { Tcl_AppendToObj(tmpObj, "%", 1); } else { const char *b = TclGetStringFromObj(tmpObj, &len); /* * If there is a non-empty literal from the format string, * push it and reset. */ if (len > 0) { PushLiteral(envPtr, b, len); Tcl_DecrRefCount(tmpObj); TclNewObj(tmpObj); i++; } /* * Push the code to produce the string that would be * substituted with %s, except we'll be concatenating * directly. */ CompileWord(envPtr, tokenPtr, interp, j); tokenPtr = TokenAfter(tokenPtr); j++; i++; } start = bytes + 1; } } /* * Handle the case of a trailing literal. */ Tcl_AppendToObj(tmpObj, start, bytes - start); bytes = TclGetStringFromObj(tmpObj, &len); if (len > 0) { PushLiteral(envPtr, bytes, len); i++; } Tcl_DecrRefCount(tmpObj); Tcl_DecrRefCount(formatObj); if (i > 1) { /* * Do the concatenation, which produces the result. */ TclEmitInstInt1(INST_STR_CONCAT1, i, envPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclLocalScalarFromToken -- * * Get the index into the table of compiled locals that corresponds * to a local scalar variable name. * * Results: * Returns the non-negative integer index value into the table of * compiled locals corresponding to a local scalar variable name. * If the arguments passed in do not identify a local scalar variable * then return TCL_INDEX_NONE. * * Side effects: * May add an entry into the table of compiled locals. * *---------------------------------------------------------------------- */ size_t TclLocalScalarFromToken( Tcl_Token *tokenPtr, CompileEnv *envPtr) { int isScalar, index; TclPushVarName(NULL, tokenPtr, envPtr, TCL_NO_ELEMENT, &index, &isScalar); if (!isScalar) { index = -1; } return index; } size_t TclLocalScalar( const char *bytes, size_t numBytes, CompileEnv *envPtr) { Tcl_Token token[2] = { {TCL_TOKEN_SIMPLE_WORD, NULL, 0, 1}, {TCL_TOKEN_TEXT, NULL, 0, 0} }; token[1].start = bytes; token[1].size = numBytes; return TclLocalScalarFromToken(token, envPtr); } /* *---------------------------------------------------------------------- * * TclPushVarName -- * * Procedure used in the compiling where pushing a variable name is * necessary (append, lappend, set). * * Results: * The values written to *localIndexPtr and *isScalarPtr signal to * the caller what the instructions emitted by this routine will do: * * *isScalarPtr (*localIndexPtr < 0) * 1 1 Push the varname on the stack. (Stack +1) * 1 0 *localIndexPtr is the index of the compiled * local for this varname. No instructions * emitted. (Stack +0) * 0 1 Push part1 and part2 names of array element * on the stack. (Stack +2) * 0 0 *localIndexPtr is the index of the compiled * local for this array. Element name is pushed * on the stack. (Stack +1) * * Side effects: * Instructions are added to envPtr. * *---------------------------------------------------------------------- */ void TclPushVarName( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Token *varTokenPtr, /* Points to a variable token. */ CompileEnv *envPtr, /* Holds resulting instructions. */ int flags, /* TCL_NO_LARGE_INDEX | TCL_NO_ELEMENT. */ int *localIndexPtr, /* Must not be NULL. */ int *isScalarPtr) /* Must not be NULL. */ { const char *p; const char *last, *name, *elName; size_t n; Tcl_Token *elemTokenPtr = NULL; size_t nameLen, elNameLen; int simpleVarName, localIndex; int elemTokenCount = 0, allocedTokens = 0, removedParen = 0; /* * Decide if we can use a frame slot for the var/array name or if we need * to emit code to compute and push the name at runtime. We use a frame * slot (entry in the array of local vars) if we are compiling a procedure * body and if the name is simple text that does not include namespace * qualifiers. */ simpleVarName = 0; name = elName = NULL; nameLen = elNameLen = 0; localIndex = -1; if (varTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { /* * A simple variable name. Divide it up into "name" and "elName" * strings. If it is not a local variable, look it up at runtime. */ simpleVarName = 1; name = varTokenPtr[1].start; nameLen = varTokenPtr[1].size; if (name[nameLen-1] == ')') { /* * last char is ')' => potential array reference. */ last = &name[nameLen-1]; if (*last == ')') { for (p = name; p < last; p++) { if (*p == '(') { elName = p + 1; elNameLen = last - elName; nameLen = p - name; break; } } } if (!(flags & TCL_NO_ELEMENT) && elNameLen) { /* * An array element, the element name is a simple string: * assemble the corresponding token. */ elemTokenPtr = (Tcl_Token *)TclStackAlloc(interp, sizeof(Tcl_Token)); allocedTokens = 1; elemTokenPtr->type = TCL_TOKEN_TEXT; elemTokenPtr->start = elName; elemTokenPtr->size = elNameLen; elemTokenPtr->numComponents = 0; elemTokenCount = 1; } } } else if (interp && ((n = varTokenPtr->numComponents) > 1) && (varTokenPtr[1].type == TCL_TOKEN_TEXT) && (varTokenPtr[n].type == TCL_TOKEN_TEXT) && (*(varTokenPtr[n].start + varTokenPtr[n].size - 1) == ')')) { /* * Check for parentheses inside first token. */ simpleVarName = 0; for (p = varTokenPtr[1].start, last = p + varTokenPtr[1].size; p < last; p++) { if (*p == '(') { simpleVarName = 1; break; } } if (simpleVarName) { size_t remainingLen; /* * Check the last token: if it is just ')', do not count it. * Otherwise, remove the ')' and flag so that it is restored at * the end. */ if (varTokenPtr[n].size == 1) { n--; } else { varTokenPtr[n].size--; removedParen = n; } name = varTokenPtr[1].start; nameLen = p - varTokenPtr[1].start; elName = p + 1; remainingLen = (varTokenPtr[2].start - p) - 1; elNameLen = (varTokenPtr[n].start-p) + varTokenPtr[n].size - 1; if (!(flags & TCL_NO_ELEMENT)) { if (remainingLen) { /* * Make a first token with the extra characters in the first * token. */ elemTokenPtr = (Tcl_Token *)TclStackAlloc(interp, n * sizeof(Tcl_Token)); allocedTokens = 1; elemTokenPtr->type = TCL_TOKEN_TEXT; elemTokenPtr->start = elName; elemTokenPtr->size = remainingLen; elemTokenPtr->numComponents = 0; elemTokenCount = n; /* * Copy the remaining tokens. */ memcpy(elemTokenPtr+1, varTokenPtr+2, (n-1) * sizeof(Tcl_Token)); } else { /* * Use the already available tokens. */ elemTokenPtr = &varTokenPtr[2]; elemTokenCount = n - 1; } } } } if (simpleVarName) { /* * See whether name has any namespace separators (::'s). */ int hasNsQualifiers = 0; for (p = name, last = p + nameLen-1; p < last; p++) { if ((p[0] == ':') && (p[1] == ':')) { hasNsQualifiers = 1; break; } } /* * Look up the var name's index in the array of local vars in the proc * frame. If retrieving the var's value and it doesn't already exist, * push its name and look it up at runtime. */ if (!hasNsQualifiers) { localIndex = TclFindCompiledLocal(name, nameLen, 1, envPtr); if ((flags & TCL_NO_LARGE_INDEX) && (localIndex > 255)) { /* * We'll push the name. */ localIndex = -1; } } if (interp && localIndex < 0) { PushLiteral(envPtr, name, nameLen); } /* * Compile the element script, if any, and only if not inhibited. [Bug * 3600328] */ if (elName != NULL && !(flags & TCL_NO_ELEMENT)) { if (elNameLen) { TclCompileTokens(interp, elemTokenPtr, elemTokenCount, envPtr); } else { PushStringLiteral(envPtr, ""); } } } else if (interp) { /* * The var name isn't simple: compile and push it. */ CompileTokens(envPtr, varTokenPtr, interp); } if (removedParen) { varTokenPtr[removedParen].size++; } if (allocedTokens) { TclStackFree(interp, elemTokenPtr); } *localIndexPtr = localIndex; *isScalarPtr = (elName == NULL); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclCompCmdsGR.c0000644000175000017500000023170114726623136016003 0ustar sergeisergei/* * tclCompCmdsGR.c -- * * This file contains compilation procedures that compile various Tcl * commands (beginning with the letters 'g' through 'r') into a sequence * of instructions ("bytecodes"). * * Copyright © 1997-1998 Sun Microsystems, Inc. * Copyright © 2001 Kevin B. Kenny. All rights reserved. * Copyright © 2002 ActiveState Corporation. * Copyright © 2004-2013 Donal K. Fellows. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" #include /* * Prototypes for procedures defined later in this file: */ static void CompileReturnInternal(CompileEnv *envPtr, unsigned char op, int code, int level, Tcl_Obj *returnOpts); static int IndexTailVarIfKnown(Tcl_Interp *interp, Tcl_Token *varTokenPtr, CompileEnv *envPtr); /* *---------------------------------------------------------------------- * * TclGetIndexFromToken -- * * Parse a token to determine if an index value is known at * compile time. * * Returns: * TCL_OK if parsing succeeded, and TCL_ERROR if it failed. * * Side effects: * When TCL_OK is returned, the encoded index value is written * to *index. * *---------------------------------------------------------------------- */ int TclGetIndexFromToken( Tcl_Token *tokenPtr, size_t before, size_t after, int *indexPtr) { Tcl_Obj *tmpObj; int result = TCL_ERROR; TclNewObj(tmpObj); if (TclWordKnownAtCompileTime(tokenPtr, tmpObj)) { result = TclIndexEncode(NULL, tmpObj, before, after, indexPtr); } Tcl_DecrRefCount(tmpObj); return result; } /* *---------------------------------------------------------------------- * * TclCompileGlobalCmd -- * * Procedure called to compile the "global" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "global" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileGlobalCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *varTokenPtr; int localIndex, numWords, i; /* TODO: Consider support for compiling expanded args. */ numWords = parsePtr->numWords; if (numWords < 2) { return TCL_ERROR; } /* * 'global' has no effect outside of proc bodies; handle that at runtime */ if (envPtr->procPtr == NULL) { return TCL_ERROR; } /* * Push the namespace */ PushStringLiteral(envPtr, "::"); /* * Loop over the variables. */ varTokenPtr = TokenAfter(parsePtr->tokenPtr); for (i=1; itokenPtr; wordIdx = 0; numWords = parsePtr->numWords; for (wordIdx = 0; wordIdx < numWords; wordIdx++) { if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { return TCL_ERROR; } tokenPtr = TokenAfter(tokenPtr); } TclInitJumpFixupArray(&jumpFalseFixupArray); TclInitJumpFixupArray(&jumpEndFixupArray); code = TCL_OK; /* * Each iteration of this loop compiles one "if expr ?then? body" or * "elseif expr ?then? body" clause. */ tokenPtr = parsePtr->tokenPtr; wordIdx = 0; while (wordIdx < numWords) { /* * Stop looping if the token isn't "if" or "elseif". */ word = tokenPtr[1].start; numBytes = tokenPtr[1].size; if ((tokenPtr == parsePtr->tokenPtr) || ((numBytes == 6) && (strncmp(word, "elseif", 6) == 0))) { tokenPtr = TokenAfter(tokenPtr); wordIdx++; } else { break; } if (wordIdx >= numWords) { code = TCL_ERROR; goto done; } /* * Compile the test expression then emit the conditional jump around * the "then" part. */ testTokenPtr = tokenPtr; if (realCond) { /* * Find out if the condition is a constant. */ Tcl_Obj *boolObj = Tcl_NewStringObj(testTokenPtr[1].start, testTokenPtr[1].size); Tcl_IncrRefCount(boolObj); code = Tcl_GetBooleanFromObj(NULL, boolObj, &boolVal); TclDecrRefCount(boolObj); if (code == TCL_OK) { /* * A static condition. */ realCond = 0; if (!boolVal) { compileScripts = 0; } } else { SetLineInformation(wordIdx); Tcl_ResetResult(interp); TclCompileExprWords(interp, testTokenPtr, 1, envPtr); if (jumpFalseFixupArray.next >= jumpFalseFixupArray.end) { TclExpandJumpFixupArray(&jumpFalseFixupArray); } jumpIndex = jumpFalseFixupArray.next; jumpFalseFixupArray.next++; TclEmitForwardJump(envPtr, TCL_FALSE_JUMP, jumpFalseFixupArray.fixup + jumpIndex); } code = TCL_OK; } /* * Skip over the optional "then" before the then clause. */ tokenPtr = TokenAfter(testTokenPtr); wordIdx++; if (wordIdx >= numWords) { code = TCL_ERROR; goto done; } if (tokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { word = tokenPtr[1].start; numBytes = tokenPtr[1].size; if ((numBytes == 4) && (strncmp(word, "then", 4) == 0)) { tokenPtr = TokenAfter(tokenPtr); wordIdx++; if (wordIdx >= numWords) { code = TCL_ERROR; goto done; } } } /* * Compile the "then" command body. */ if (compileScripts) { BODY(tokenPtr, wordIdx); } if (realCond) { /* * Jump to the end of the "if" command. Both jumpFalseFixupArray * and jumpEndFixupArray are indexed by "jumpIndex". */ if (jumpEndFixupArray.next >= jumpEndFixupArray.end) { TclExpandJumpFixupArray(&jumpEndFixupArray); } jumpEndFixupArray.next++; TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, jumpEndFixupArray.fixup + jumpIndex); /* * Fix the target of the jumpFalse after the test. Generate a 4 * byte jump if the distance is > 120 bytes. This is conservative, * and ensures that we won't have to replace this jump if we later * also need to replace the proceeding jump to the end of the "if" * with a 4 byte jump. */ TclAdjustStackDepth(-1, envPtr); if (TclFixupForwardJumpToHere(envPtr, jumpFalseFixupArray.fixup + jumpIndex, 120)) { /* * Adjust the code offset for the proceeding jump to the end * of the "if" command. */ jumpEndFixupArray.fixup[jumpIndex].codeOffset += 3; } } else if (boolVal) { /* * We were processing an "if 1 {...}"; stop compiling scripts. */ compileScripts = 0; } else { /* * We were processing an "if 0 {...}"; reset so that the rest * (elseif, else) is compiled correctly. */ realCond = 1; compileScripts = 1; } tokenPtr = TokenAfter(tokenPtr); wordIdx++; } /* * Check for the optional else clause. Do not compile anything if this was * an "if 1 {...}" case. */ if ((wordIdx < numWords) && (tokenPtr->type == TCL_TOKEN_SIMPLE_WORD)) { /* * There is an else clause. Skip over the optional "else" word. */ word = tokenPtr[1].start; numBytes = tokenPtr[1].size; if ((numBytes == 4) && (strncmp(word, "else", 4) == 0)) { tokenPtr = TokenAfter(tokenPtr); wordIdx++; if (wordIdx >= numWords) { code = TCL_ERROR; goto done; } } if (compileScripts) { /* * Compile the else command body. */ BODY(tokenPtr, wordIdx); } /* * Make sure there are no words after the else clause. */ wordIdx++; if (wordIdx < numWords) { code = TCL_ERROR; goto done; } } else { /* * No else clause: the "if" command's result is an empty string. */ if (compileScripts) { PushStringLiteral(envPtr, ""); } } /* * Fix the unconditional jumps to the end of the "if" command. */ for (j = jumpEndFixupArray.next; j > 0; j--) { jumpIndex = (j - 1); /* i.e. process the closest jump first. */ if (TclFixupForwardJumpToHere(envPtr, jumpEndFixupArray.fixup + jumpIndex, 127)) { /* * Adjust the immediately preceding "ifFalse" jump. We moved it's * target (just after this jump) down three bytes. */ unsigned char *ifFalsePc = envPtr->codeStart + jumpFalseFixupArray.fixup[jumpIndex].codeOffset; unsigned char opCode = *ifFalsePc; if (opCode == INST_JUMP_FALSE1) { jumpFalseDist = TclGetInt1AtPtr(ifFalsePc + 1); jumpFalseDist += 3; TclStoreInt1AtPtr(jumpFalseDist, (ifFalsePc + 1)); } else if (opCode == INST_JUMP_FALSE4) { jumpFalseDist = TclGetInt4AtPtr(ifFalsePc + 1); jumpFalseDist += 3; TclStoreInt4AtPtr(jumpFalseDist, (ifFalsePc + 1)); } else { Tcl_Panic("TclCompileIfCmd: unexpected opcode \"%d\" updating ifFalse jump", opCode); } } } /* * Free the jumpFixupArray array if malloc'ed storage was used. */ done: TclFreeJumpFixupArray(&jumpFalseFixupArray); TclFreeJumpFixupArray(&jumpEndFixupArray); return code; } /* *---------------------------------------------------------------------- * * TclCompileIncrCmd -- * * Procedure called to compile the "incr" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "incr" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileIncrCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *varTokenPtr, *incrTokenPtr; int isScalar, localIndex, haveImmValue; Tcl_WideInt immValue; if ((parsePtr->numWords != 2) && (parsePtr->numWords != 3)) { return TCL_ERROR; } varTokenPtr = TokenAfter(parsePtr->tokenPtr); PushVarNameWord(interp, varTokenPtr, envPtr, TCL_NO_LARGE_INDEX, &localIndex, &isScalar, 1); /* * If an increment is given, push it, but see first if it's a small * integer. */ haveImmValue = 0; immValue = 1; if (parsePtr->numWords == 3) { incrTokenPtr = TokenAfter(varTokenPtr); if (incrTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { const char *word = incrTokenPtr[1].start; size_t numBytes = incrTokenPtr[1].size; int code; Tcl_Obj *intObj = Tcl_NewStringObj(word, numBytes); Tcl_IncrRefCount(intObj); code = TclGetWideIntFromObj(NULL, intObj, &immValue); if ((code == TCL_OK) && (-127 <= immValue) && (immValue <= 127)) { haveImmValue = 1; } TclDecrRefCount(intObj); if (!haveImmValue) { PushLiteral(envPtr, word, numBytes); } } else { SetLineInformation(2); CompileTokens(envPtr, incrTokenPtr, interp); } } else { /* No incr amount given so use 1. */ haveImmValue = 1; } /* * Emit the instruction to increment the variable. */ if (isScalar) { /* Simple scalar variable. */ if (localIndex >= 0) { if (haveImmValue) { TclEmitInstInt1(INST_INCR_SCALAR1_IMM, localIndex, envPtr); TclEmitInt1(immValue, envPtr); } else { TclEmitInstInt1(INST_INCR_SCALAR1, localIndex, envPtr); } } else { if (haveImmValue) { TclEmitInstInt1(INST_INCR_STK_IMM, immValue, envPtr); } else { TclEmitOpcode( INST_INCR_STK, envPtr); } } } else { /* Simple array variable. */ if (localIndex >= 0) { if (haveImmValue) { TclEmitInstInt1(INST_INCR_ARRAY1_IMM, localIndex, envPtr); TclEmitInt1(immValue, envPtr); } else { TclEmitInstInt1(INST_INCR_ARRAY1, localIndex, envPtr); } } else { if (haveImmValue) { TclEmitInstInt1(INST_INCR_ARRAY_STK_IMM, immValue, envPtr); } else { TclEmitOpcode( INST_INCR_ARRAY_STK, envPtr); } } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileInfo*Cmd -- * * Procedures called to compile "info" subcommands. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "info" subcommand at * runtime. * *---------------------------------------------------------------------- */ int TclCompileInfoCommandsCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; Tcl_Obj *objPtr; const char *bytes; /* * We require one compile-time known argument for the case we can compile. */ if (parsePtr->numWords == 1) { return TclCompileBasic0ArgCmd(interp, parsePtr, cmdPtr, envPtr); } else if (parsePtr->numWords != 2) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); TclNewObj(objPtr); Tcl_IncrRefCount(objPtr); if (!TclWordKnownAtCompileTime(tokenPtr, objPtr)) { goto notCompilable; } bytes = TclGetString(objPtr); /* * We require that the argument start with "::" and not have any of "*\[?" * in it. (Theoretically, we should look in only the final component, but * the difference is so slight given current naming practices.) */ if (bytes[0] != ':' || bytes[1] != ':' || !TclMatchIsTrivial(bytes)) { goto notCompilable; } Tcl_DecrRefCount(objPtr); /* * Confirmed as a literal that will not frighten the horses. Compile. * The result must be made into a list. */ /* TODO: Just push the known value */ CompileWord(envPtr, tokenPtr, interp, 1); TclEmitOpcode( INST_RESOLVE_COMMAND, envPtr); TclEmitOpcode( INST_DUP, envPtr); TclEmitOpcode( INST_STR_LEN, envPtr); TclEmitInstInt1( INST_JUMP_FALSE1, 7, envPtr); TclEmitInstInt4( INST_LIST, 1, envPtr); return TCL_OK; notCompilable: Tcl_DecrRefCount(objPtr); return TclCompileBasic1ArgCmd(interp, parsePtr, cmdPtr, envPtr); } int TclCompileInfoCoroutineCmd( TCL_UNUSED(Tcl_Interp *), Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Only compile [info coroutine] without arguments. */ if (parsePtr->numWords != 1) { return TCL_ERROR; } /* * Not much to do; we compile to a single instruction... */ TclEmitOpcode( INST_COROUTINE_NAME, envPtr); return TCL_OK; } int TclCompileInfoExistsCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; int isScalar, localIndex; if (parsePtr->numWords != 2) { return TCL_ERROR; } /* * Decide if we can use a frame slot for the var/array name or if we need * to emit code to compute and push the name at runtime. We use a frame * slot (entry in the array of local vars) if we are compiling a procedure * body and if the name is simple text that does not include namespace * qualifiers. */ tokenPtr = TokenAfter(parsePtr->tokenPtr); PushVarNameWord(interp, tokenPtr, envPtr, 0, &localIndex, &isScalar, 1); /* * Emit instruction to check the variable for existence. */ if (isScalar) { if (localIndex < 0) { TclEmitOpcode( INST_EXIST_STK, envPtr); } else { TclEmitInstInt4( INST_EXIST_SCALAR, localIndex, envPtr); } } else { if (localIndex < 0) { TclEmitOpcode( INST_EXIST_ARRAY_STK, envPtr); } else { TclEmitInstInt4( INST_EXIST_ARRAY, localIndex, envPtr); } } return TCL_OK; } int TclCompileInfoLevelCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Only compile [info level] without arguments or with a single argument. */ if (parsePtr->numWords == 1) { /* * Not much to do; we compile to a single instruction... */ TclEmitOpcode( INST_INFO_LEVEL_NUM, envPtr); } else if (parsePtr->numWords != 2) { return TCL_ERROR; } else { DefineLineInformation; /* TIP #280 */ /* * Compile the argument, then add the instruction to convert it into a * list of arguments. */ CompileWord(envPtr, TokenAfter(parsePtr->tokenPtr), interp, 1); TclEmitOpcode( INST_INFO_LEVEL_ARGS, envPtr); } return TCL_OK; } int TclCompileInfoObjectClassCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = TokenAfter(parsePtr->tokenPtr); if (parsePtr->numWords != 2) { return TCL_ERROR; } CompileWord(envPtr, tokenPtr, interp, 1); TclEmitOpcode( INST_TCLOO_CLASS, envPtr); return TCL_OK; } int TclCompileInfoObjectIsACmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = TokenAfter(parsePtr->tokenPtr); /* * We only handle [info object isa object ]. The first three * words are compressed to a single token by the ensemble compilation * engine. */ if (parsePtr->numWords != 3) { return TCL_ERROR; } if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD || tokenPtr[1].size < 1 || strncmp(tokenPtr[1].start, "object", tokenPtr[1].size)) { return TCL_ERROR; } tokenPtr = TokenAfter(tokenPtr); /* * Issue the code. */ CompileWord(envPtr, tokenPtr, interp, 2); TclEmitOpcode( INST_TCLOO_IS_OBJECT, envPtr); return TCL_OK; } int TclCompileInfoObjectNamespaceCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = TokenAfter(parsePtr->tokenPtr); if (parsePtr->numWords != 2) { return TCL_ERROR; } CompileWord(envPtr, tokenPtr, interp, 1); TclEmitOpcode( INST_TCLOO_NS, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileLappendCmd -- * * Procedure called to compile the "lappend" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "lappend" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileLappendCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *varTokenPtr, *valueTokenPtr; int isScalar, localIndex, numWords, i; /* TODO: Consider support for compiling expanded args. */ numWords = parsePtr->numWords; if (numWords < 3) { return TCL_ERROR; } if (numWords != 3 || envPtr->procPtr == NULL) { goto lappendMultiple; } /* * Decide if we can use a frame slot for the var/array name or if we * need to emit code to compute and push the name at runtime. We use a * frame slot (entry in the array of local vars) if we are compiling a * procedure body and if the name is simple text that does not include * namespace qualifiers. */ varTokenPtr = TokenAfter(parsePtr->tokenPtr); PushVarNameWord(interp, varTokenPtr, envPtr, 0, &localIndex, &isScalar, 1); /* * If we are doing an assignment, push the new value. In the no values * case, create an empty object. */ if (numWords > 2) { valueTokenPtr = TokenAfter(varTokenPtr); CompileWord(envPtr, valueTokenPtr, interp, 2); } /* * Emit instructions to set/get the variable. */ /* * The *_STK opcodes should be refactored to make better use of existing * LOAD/STORE instructions. */ if (isScalar) { if (localIndex < 0) { TclEmitOpcode( INST_LAPPEND_STK, envPtr); } else { Emit14Inst( INST_LAPPEND_SCALAR, localIndex, envPtr); } } else { if (localIndex < 0) { TclEmitOpcode( INST_LAPPEND_ARRAY_STK, envPtr); } else { Emit14Inst( INST_LAPPEND_ARRAY, localIndex, envPtr); } } return TCL_OK; lappendMultiple: varTokenPtr = TokenAfter(parsePtr->tokenPtr); PushVarNameWord(interp, varTokenPtr, envPtr, 0, &localIndex, &isScalar, 1); valueTokenPtr = TokenAfter(varTokenPtr); for (i = 2 ; i < numWords ; i++) { CompileWord(envPtr, valueTokenPtr, interp, i); valueTokenPtr = TokenAfter(valueTokenPtr); } TclEmitInstInt4( INST_LIST, numWords - 2, envPtr); if (isScalar) { if (localIndex < 0) { TclEmitOpcode( INST_LAPPEND_LIST_STK, envPtr); } else { TclEmitInstInt4(INST_LAPPEND_LIST, localIndex, envPtr); } } else { if (localIndex < 0) { TclEmitOpcode( INST_LAPPEND_LIST_ARRAY_STK, envPtr); } else { TclEmitInstInt4(INST_LAPPEND_LIST_ARRAY, localIndex,envPtr); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileLassignCmd -- * * Procedure called to compile the "lassign" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "lassign" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileLassignCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; int isScalar, localIndex, numWords, idx; numWords = parsePtr->numWords; /* * Check for command syntax error, but we'll punt that to runtime. */ if (numWords < 3) { return TCL_ERROR; } /* * Generate code to push list being taken apart by [lassign]. */ tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); /* * Generate code to assign values from the list to variables. */ for (idx=0 ; idx= 0) { TclEmitOpcode( INST_DUP, envPtr); TclEmitInstInt4(INST_LIST_INDEX_IMM, idx, envPtr); Emit14Inst( INST_STORE_SCALAR, localIndex, envPtr); TclEmitOpcode( INST_POP, envPtr); } else { TclEmitInstInt4(INST_OVER, 1, envPtr); TclEmitInstInt4(INST_LIST_INDEX_IMM, idx, envPtr); TclEmitOpcode( INST_STORE_STK, envPtr); TclEmitOpcode( INST_POP, envPtr); } } else { if (localIndex >= 0) { TclEmitInstInt4(INST_OVER, 1, envPtr); TclEmitInstInt4(INST_LIST_INDEX_IMM, idx, envPtr); Emit14Inst( INST_STORE_ARRAY, localIndex, envPtr); TclEmitOpcode( INST_POP, envPtr); } else { TclEmitInstInt4(INST_OVER, 2, envPtr); TclEmitInstInt4(INST_LIST_INDEX_IMM, idx, envPtr); TclEmitOpcode( INST_STORE_ARRAY_STK, envPtr); TclEmitOpcode( INST_POP, envPtr); } } } /* * Generate code to leave the rest of the list on the stack. */ TclEmitInstInt4( INST_LIST_RANGE_IMM, idx, envPtr); TclEmitInt4( (int)TCL_INDEX_END, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileLindexCmd -- * * Procedure called to compile the "lindex" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "lindex" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileLindexCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *idxTokenPtr, *valTokenPtr; int i, idx, numWords = parsePtr->numWords; /* * Quit if not enough args. */ /* TODO: Consider support for compiling expanded args. */ if (numWords <= 1) { return TCL_ERROR; } valTokenPtr = TokenAfter(parsePtr->tokenPtr); if (numWords != 3) { goto emitComplexLindex; } idxTokenPtr = TokenAfter(valTokenPtr); if (TclGetIndexFromToken(idxTokenPtr, TCL_INDEX_NONE, TCL_INDEX_NONE, &idx) == TCL_OK) { /* * The idxTokenPtr parsed as a valid index value and was * encoded as expected by INST_LIST_INDEX_IMM. * * NOTE: that we rely on indexing before a list producing the * same result as indexing after a list. */ CompileWord(envPtr, valTokenPtr, interp, 1); TclEmitInstInt4( INST_LIST_INDEX_IMM, idx, envPtr); return TCL_OK; } /* * If the value was not known at compile time, the conversion failed or * the value was negative, we just keep on going with the more complex * compilation. */ /* * Push the operands onto the stack. */ emitComplexLindex: for (i=1 ; inumWords == 1) { /* * [list] without arguments just pushes an empty object. */ PushStringLiteral(envPtr, ""); return TCL_OK; } /* * Test if all arguments are compile-time known. If they are, we can * implement with a simple push. */ numWords = parsePtr->numWords; valueTokenPtr = TokenAfter(parsePtr->tokenPtr); TclNewObj(listObj); for (i = 1; i < numWords && listObj != NULL; i++) { TclNewObj(objPtr); if (TclWordKnownAtCompileTime(valueTokenPtr, objPtr)) { (void) Tcl_ListObjAppendElement(NULL, listObj, objPtr); } else { Tcl_DecrRefCount(objPtr); Tcl_DecrRefCount(listObj); listObj = NULL; } valueTokenPtr = TokenAfter(valueTokenPtr); } if (listObj != NULL) { TclEmitPush(TclAddLiteralObj(envPtr, listObj, NULL), envPtr); return TCL_OK; } /* * Push the all values onto the stack. */ numWords = parsePtr->numWords; valueTokenPtr = TokenAfter(parsePtr->tokenPtr); concat = build = 0; for (i = 1; i < numWords; i++) { if (valueTokenPtr->type == TCL_TOKEN_EXPAND_WORD && build > 0) { TclEmitInstInt4( INST_LIST, build, envPtr); if (concat) { TclEmitOpcode( INST_LIST_CONCAT, envPtr); } build = 0; concat = 1; } CompileWord(envPtr, valueTokenPtr, interp, i); if (valueTokenPtr->type == TCL_TOKEN_EXPAND_WORD) { if (concat) { TclEmitOpcode( INST_LIST_CONCAT, envPtr); } else { concat = 1; } } else { build++; } valueTokenPtr = TokenAfter(valueTokenPtr); } if (build > 0) { TclEmitInstInt4( INST_LIST, build, envPtr); if (concat) { TclEmitOpcode( INST_LIST_CONCAT, envPtr); } } /* * If there was just one expanded word, we must ensure that it is a list * at this point. We use an [lrange ... 0 end] for this (instead of * [llength], as with literals) as we must drop any string representation * that might be hanging around. */ if (concat && numWords == 2) { TclEmitInstInt4( INST_LIST_RANGE_IMM, 0, envPtr); TclEmitInt4( (int)TCL_INDEX_END, envPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileLlengthCmd -- * * Procedure called to compile the "llength" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "llength" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileLlengthCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *varTokenPtr; if (parsePtr->numWords != 2) { return TCL_ERROR; } varTokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, varTokenPtr, interp, 1); TclEmitOpcode( INST_LIST_LENGTH, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileLrangeCmd -- * * How to compile the "lrange" command. We only bother because we needed * the opcode anyway for "lassign". * *---------------------------------------------------------------------- */ int TclCompileLrangeCmd( Tcl_Interp *interp, /* Tcl interpreter for context. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the * command. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds the resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr, *listTokenPtr; int idx1, idx2; if (parsePtr->numWords != 4) { return TCL_ERROR; } listTokenPtr = TokenAfter(parsePtr->tokenPtr); tokenPtr = TokenAfter(listTokenPtr); if ((TclGetIndexFromToken(tokenPtr, TCL_INDEX_START, TCL_INDEX_NONE, &idx1) != TCL_OK) || (idx1 == (int)TCL_INDEX_NONE)) { return TCL_ERROR; } /* * Token was an index value, and we treat all "first" indices * before the list same as the start of the list. */ tokenPtr = TokenAfter(tokenPtr); if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_NONE, TCL_INDEX_END, &idx2) != TCL_OK) { return TCL_ERROR; } /* * Token was an index value, and we treat all "last" indices * after the list same as the end of the list. */ /* * Issue instructions. It's not safe to skip doing the LIST_RANGE, as * we've not proved that the 'list' argument is really a list. Not that it * is worth trying to do that given current knowledge. */ CompileWord(envPtr, listTokenPtr, interp, 1); TclEmitInstInt4( INST_LIST_RANGE_IMM, idx1, envPtr); TclEmitInt4( idx2, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileLinsertCmd -- * * How to compile the "linsert" command. We only bother with the case * where the index is constant. * *---------------------------------------------------------------------- */ int TclCompileLinsertCmd( Tcl_Interp *interp, /* Tcl interpreter for context. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the * command. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds the resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; int i; if ((int)parsePtr->numWords < 3) { return TCL_ERROR; } /* Push list, insertion index onto the stack */ tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); /* Push new elements to be inserted */ for (i=3 ; i<(int)parsePtr->numWords ; i++) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, i); } /* First operand is count of arguments */ TclEmitInstInt4(INST_LREPLACE4, parsePtr->numWords - 1, envPtr); /* * Second operand is bitmask * TCL_LREPLACE4_END_IS_LAST - end refers to last element * TCL_LREPLACE4_SINGLE_INDEX - second index is not present * indicating this is a pure insert */ TclEmitInt1(TCL_LREPLACE4_SINGLE_INDEX, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileLreplaceCmd -- * * How to compile the "lreplace" command. We only bother with the case * where the indices are constant. * *---------------------------------------------------------------------- */ int TclCompileLreplaceCmd( Tcl_Interp *interp, /* Tcl interpreter for context. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the * command. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds the resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; int i; if (parsePtr->numWords < 4) { return TCL_ERROR; } /* Push list, first, last onto the stack */ tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 3); /* Push new elements to be inserted */ for (i=4 ; i< (int)parsePtr->numWords ; i++) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, i); } /* First operand is count of arguments */ TclEmitInstInt4(INST_LREPLACE4, parsePtr->numWords - 1, envPtr); /* * Second operand is bitmask * TCL_LREPLACE4_END_IS_LAST - end refers to last element */ TclEmitInt1(TCL_LREPLACE4_END_IS_LAST, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileLsetCmd -- * * Procedure called to compile the "lset" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "lset" command at * runtime. * * The general template for execution of the "lset" command is: * (1) Instructions to push the variable name, unless the variable is * local to the stack frame. * (2) If the variable is an array element, instructions to push the * array element name. * (3) Instructions to push each of zero or more "index" arguments to the * stack, followed with the "newValue" element. * (4) Instructions to duplicate the variable name and/or array element * name onto the top of the stack, if either was pushed at steps (1) * and (2). * (5) The appropriate INST_LOAD_* instruction to place the original * value of the list variable at top of stack. * (6) At this point, the stack contains: * varName? arrayElementName? index1 index2 ... newValue oldList * The compiler emits one of INST_LSET_FLAT or INST_LSET_LIST * according as whether there is exactly one index element (LIST) or * either zero or else two or more (FLAT). This instruction removes * everything from the stack except for the two names and pushes the * new value of the variable. * (7) Finally, INST_STORE_* stores the new value in the variable and * cleans up the stack. * *---------------------------------------------------------------------- */ int TclCompileLsetCmd( Tcl_Interp *interp, /* Tcl interpreter for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the * command. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds the resulting instructions. */ { DefineLineInformation; /* TIP #280 */ int tempDepth; /* Depth used for emitting one part of the * code burst. */ Tcl_Token *varTokenPtr; /* Pointer to the Tcl_Token representing the * parse of the variable name. */ int localIndex; /* Index of var in local var table. */ int isScalar; /* Flag == 1 if scalar, 0 if array. */ int i; /* * Check argument count. */ /* TODO: Consider support for compiling expanded args. */ if ((int)parsePtr->numWords < 3) { /* * Fail at run time, not in compilation. */ return TCL_ERROR; } /* * Decide if we can use a frame slot for the var/array name or if we need * to emit code to compute and push the name at runtime. We use a frame * slot (entry in the array of local vars) if we are compiling a procedure * body and if the name is simple text that does not include namespace * qualifiers. */ varTokenPtr = TokenAfter(parsePtr->tokenPtr); PushVarNameWord(interp, varTokenPtr, envPtr, 0, &localIndex, &isScalar, 1); /* * Push the "index" args and the new element value. */ for (i=2 ; i<(int)parsePtr->numWords ; ++i) { varTokenPtr = TokenAfter(varTokenPtr); CompileWord(envPtr, varTokenPtr, interp, i); } /* * Duplicate the variable name if it's been pushed. */ if (localIndex < 0) { if (isScalar) { tempDepth = parsePtr->numWords - 2; } else { tempDepth = parsePtr->numWords - 1; } TclEmitInstInt4( INST_OVER, tempDepth, envPtr); } /* * Duplicate an array index if one's been pushed. */ if (!isScalar) { if (localIndex < 0) { tempDepth = parsePtr->numWords - 1; } else { tempDepth = parsePtr->numWords - 2; } TclEmitInstInt4( INST_OVER, tempDepth, envPtr); } /* * Emit code to load the variable's value. */ if (isScalar) { if (localIndex < 0) { TclEmitOpcode( INST_LOAD_STK, envPtr); } else { Emit14Inst( INST_LOAD_SCALAR, localIndex, envPtr); } } else { if (localIndex < 0) { TclEmitOpcode( INST_LOAD_ARRAY_STK, envPtr); } else { Emit14Inst( INST_LOAD_ARRAY, localIndex, envPtr); } } /* * Emit the correct variety of 'lset' instruction. */ if (parsePtr->numWords == 4) { TclEmitOpcode( INST_LSET_LIST, envPtr); } else { TclEmitInstInt4( INST_LSET_FLAT, parsePtr->numWords-1, envPtr); } /* * Emit code to put the value back in the variable. */ if (isScalar) { if (localIndex < 0) { TclEmitOpcode( INST_STORE_STK, envPtr); } else { Emit14Inst( INST_STORE_SCALAR, localIndex, envPtr); } } else { if (localIndex < 0) { TclEmitOpcode( INST_STORE_ARRAY_STK, envPtr); } else { Emit14Inst( INST_STORE_ARRAY, localIndex, envPtr); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileNamespace*Cmd -- * * Procedures called to compile the "namespace" command; currently, only * the subcommands "namespace current" and "namespace upvar" are compiled * to bytecodes, and the latter only inside a procedure(-like) context. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "namespace upvar" * command at runtime. * *---------------------------------------------------------------------- */ int TclCompileNamespaceCurrentCmd( TCL_UNUSED(Tcl_Interp *), Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Only compile [namespace current] without arguments. */ if (parsePtr->numWords != 1) { return TCL_ERROR; } /* * Not much to do; we compile to a single instruction... */ TclEmitOpcode( INST_NS_CURRENT, envPtr); return TCL_OK; } int TclCompileNamespaceCodeCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; if (parsePtr->numWords != 2) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); /* * The specification of [namespace code] is rather shocking, in that it is * supposed to check if the argument is itself the result of [namespace * code] and not apply itself in that case. Which is excessively cautious, * but what the test suite checks for. */ if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD || (tokenPtr[1].size > 20 && strncmp(tokenPtr[1].start, "::namespace inscope ", 20) == 0)) { /* * Technically, we could just pass a literal '::namespace inscope ' * term through, but that's something which really shouldn't be * occurring as something that the user writes so we'll just punt it. */ return TCL_ERROR; } /* * Now we can compile using the same strategy as [namespace code]'s normal * implementation does internally. Note that we can't bind the namespace * name directly here, because TclOO plays complex games with namespaces; * the value needs to be determined at runtime for safety. */ PushStringLiteral(envPtr, "::namespace"); PushStringLiteral(envPtr, "inscope"); TclEmitOpcode( INST_NS_CURRENT, envPtr); CompileWord(envPtr, tokenPtr, interp, 1); TclEmitInstInt4( INST_LIST, 4, envPtr); return TCL_OK; } int TclCompileNamespaceOriginCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; if (parsePtr->numWords != 2) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); TclEmitOpcode( INST_ORIGIN_COMMAND, envPtr); return TCL_OK; } int TclCompileNamespaceQualifiersCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = TokenAfter(parsePtr->tokenPtr); int off; if (parsePtr->numWords != 2) { return TCL_ERROR; } CompileWord(envPtr, tokenPtr, interp, 1); PushStringLiteral(envPtr, "0"); PushStringLiteral(envPtr, "::"); TclEmitInstInt4( INST_OVER, 2, envPtr); TclEmitOpcode( INST_STR_FIND_LAST, envPtr); off = CurrentOffset(envPtr); PushStringLiteral(envPtr, "1"); TclEmitOpcode( INST_SUB, envPtr); TclEmitInstInt4( INST_OVER, 2, envPtr); TclEmitInstInt4( INST_OVER, 1, envPtr); TclEmitOpcode( INST_STR_INDEX, envPtr); PushStringLiteral(envPtr, ":"); TclEmitOpcode( INST_STR_EQ, envPtr); off = off - CurrentOffset(envPtr); TclEmitInstInt1( INST_JUMP_TRUE1, off, envPtr); TclEmitOpcode( INST_STR_RANGE, envPtr); return TCL_OK; } int TclCompileNamespaceTailCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = TokenAfter(parsePtr->tokenPtr); JumpFixup jumpFixup; if (parsePtr->numWords != 2) { return TCL_ERROR; } /* * Take care; only add 2 to found index if the string was actually found. */ CompileWord(envPtr, tokenPtr, interp, 1); PushStringLiteral(envPtr, "::"); TclEmitInstInt4( INST_OVER, 1, envPtr); TclEmitOpcode( INST_STR_FIND_LAST, envPtr); TclEmitOpcode( INST_DUP, envPtr); PushStringLiteral(envPtr, "0"); TclEmitOpcode( INST_GE, envPtr); TclEmitForwardJump(envPtr, TCL_FALSE_JUMP, &jumpFixup); PushStringLiteral(envPtr, "2"); TclEmitOpcode( INST_ADD, envPtr); TclFixupForwardJumpToHere(envPtr, &jumpFixup, 127); PushStringLiteral(envPtr, "end"); TclEmitOpcode( INST_STR_RANGE, envPtr); return TCL_OK; } int TclCompileNamespaceUpvarCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr, *otherTokenPtr, *localTokenPtr; int localIndex, numWords, i; if (envPtr->procPtr == NULL) { return TCL_ERROR; } /* * Only compile [namespace upvar ...]: needs an even number of args, >=4 */ numWords = (int)parsePtr->numWords; if ((numWords % 2) || (numWords < 4)) { return TCL_ERROR; } /* * Push the namespace */ tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); /* * Loop over the (otherVar, thisVar) pairs. If any of the thisVar is not a * local variable, return an error so that the non-compiled command will * be called at runtime. */ localTokenPtr = tokenPtr; for (i=2; inumWords < 2 || (int)parsePtr->numWords > 3) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); idx = 1; /* * If there's an option, check that it's "-command". We don't handle * "-variable" (currently) and anything else is an error. */ if (parsePtr->numWords == 3) { if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { return TCL_ERROR; } opt = tokenPtr + 1; if (opt->size < 2 || opt->size > 8 || strncmp(opt->start, "-command", opt->size) != 0) { return TCL_ERROR; } tokenPtr = TokenAfter(tokenPtr); idx++; } /* * Issue the bytecode. */ CompileWord(envPtr, tokenPtr, interp, idx); TclEmitOpcode( INST_RESOLVE_COMMAND, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileRegexpCmd -- * * Procedure called to compile the "regexp" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "regexp" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileRegexpCmd( Tcl_Interp *interp, /* Tcl interpreter for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the * command. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds the resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *varTokenPtr; /* Pointer to the Tcl_Token representing the * parse of the RE or string. */ size_t len; int i, nocase, exact, sawLast, simple; const char *str; /* * We are only interested in compiling simple regexp cases. Currently * supported compile cases are: * regexp ?-nocase? ?--? staticString $var * regexp ?-nocase? ?--? {^staticString$} $var */ if ((int)parsePtr->numWords < 3) { return TCL_ERROR; } simple = 0; nocase = 0; sawLast = 0; varTokenPtr = parsePtr->tokenPtr; /* * We only look for -nocase and -- as options. Everything else gets pushed * to runtime execution. This is different than regexp's runtime option * handling, but satisfies our stricter needs. */ for (i = 1; i < (int)parsePtr->numWords - 2; i++) { varTokenPtr = TokenAfter(varTokenPtr); if (varTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { /* * Not a simple string, so punt to runtime. */ return TCL_ERROR; } str = varTokenPtr[1].start; len = varTokenPtr[1].size; if ((len == 2) && (str[0] == '-') && (str[1] == '-')) { sawLast++; i++; break; } else if ((len > 1) && (strncmp(str, "-nocase", len) == 0)) { nocase = 1; } else { /* * Not an option we recognize. */ return TCL_ERROR; } } if (((int)parsePtr->numWords - i) != 2) { /* * We don't support capturing to variables. */ return TCL_ERROR; } /* * Get the regexp string. If it is not a simple string or can't be * converted to a glob pattern, push the word for the INST_REGEXP. * Keep changes here in sync with TclCompileSwitchCmd Switch_Regexp. */ varTokenPtr = TokenAfter(varTokenPtr); if (varTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { Tcl_DString ds; str = varTokenPtr[1].start; len = varTokenPtr[1].size; /* * If it has a '-', it could be an incorrectly formed regexp command. */ if ((*str == '-') && !sawLast) { return TCL_ERROR; } if (len == 0) { /* * The semantics of regexp are always match on re == "". */ PushStringLiteral(envPtr, "1"); return TCL_OK; } /* * Attempt to convert pattern to glob. If successful, push the * converted pattern as a literal. */ if (TclReToGlob(NULL, varTokenPtr[1].start, len, &ds, &exact, NULL) == TCL_OK) { simple = 1; PushLiteral(envPtr, Tcl_DStringValue(&ds),Tcl_DStringLength(&ds)); Tcl_DStringFree(&ds); } } if (!simple) { CompileWord(envPtr, varTokenPtr, interp, (int)parsePtr->numWords - 2); } /* * Push the string arg. */ varTokenPtr = TokenAfter(varTokenPtr); CompileWord(envPtr, varTokenPtr, interp, (int)parsePtr->numWords - 1); if (simple) { if (exact && !nocase) { TclEmitOpcode( INST_STR_EQ, envPtr); } else { TclEmitInstInt1( INST_STR_MATCH, nocase, envPtr); } } else { /* * Pass correct RE compile flags. We use only Int1 (8-bit), but * that handles all the flags we want to pass. * Don't use TCL_REG_NOSUB as we may have backrefs. */ int cflags = TCL_REG_ADVANCED | (nocase ? TCL_REG_NOCASE : 0); TclEmitInstInt1( INST_REGEXP, cflags, envPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileRegsubCmd -- * * Procedure called to compile the "regsub" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "regsub" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileRegsubCmd( Tcl_Interp *interp, /* Tcl interpreter for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the * command. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds the resulting instructions. */ { /* * We only compile the case with [regsub -all] where the pattern is both * known at compile time and simple (i.e., no RE metacharacters). That is, * the pattern must be translatable into a glob like "*foo*" with no other * glob metacharacters inside it; there must be some "foo" in there too. * The substitution string must also be known at compile time and free of * metacharacters ("\digit" and "&"). Finally, there must not be a * variable mentioned in the [regsub] to write the result back to (because * we can't get the count of substitutions that would be the result in * that case). The key is that these are the conditions under which a * [string map] could be used instead, in particular a [string map] of the * form we can compile to bytecode. * * In short, we look for: * * regsub -all [--] simpleRE string simpleReplacement * * The only optional part is the "--", and no other options are handled. */ DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr, *stringTokenPtr; Tcl_Obj *patternObj = NULL, *replacementObj = NULL; Tcl_DString pattern; const char *bytes; int exact, quantified, result = TCL_ERROR; Tcl_Size len; if ((int)parsePtr->numWords < 5 || (int)parsePtr->numWords > 6) { return TCL_ERROR; } /* * Parse the "-all", which must be the first argument (other options not * supported, non-"-all" substitution we can't compile). */ tokenPtr = TokenAfter(parsePtr->tokenPtr); if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD || tokenPtr[1].size != 4 || strncmp(tokenPtr[1].start, "-all", 4)) { return TCL_ERROR; } /* * Get the pattern into patternObj, checking for "--" in the process. */ Tcl_DStringInit(&pattern); tokenPtr = TokenAfter(tokenPtr); TclNewObj(patternObj); if (!TclWordKnownAtCompileTime(tokenPtr, patternObj)) { goto done; } if (TclGetString(patternObj)[0] == '-') { if (strcmp(TclGetString(patternObj), "--") != 0 || parsePtr->numWords == 5) { goto done; } tokenPtr = TokenAfter(tokenPtr); Tcl_DecrRefCount(patternObj); TclNewObj(patternObj); if (!TclWordKnownAtCompileTime(tokenPtr, patternObj)) { goto done; } } else if (parsePtr->numWords == 6) { goto done; } /* * Identify the code which produces the string to apply the substitution * to (stringTokenPtr), and the replacement string (into replacementObj). */ stringTokenPtr = TokenAfter(tokenPtr); tokenPtr = TokenAfter(stringTokenPtr); TclNewObj(replacementObj); if (!TclWordKnownAtCompileTime(tokenPtr, replacementObj)) { goto done; } /* * Next, higher-level checks. Is the RE a very simple glob? Is the * replacement "simple"? */ bytes = TclGetStringFromObj(patternObj, &len); if (TclReToGlob(NULL, bytes, len, &pattern, &exact, &quantified) != TCL_OK || exact || quantified) { goto done; } bytes = Tcl_DStringValue(&pattern); if (*bytes++ != '*') { goto done; } while (1) { switch (*bytes) { case '*': if (bytes[1] == '\0') { /* * OK, we've proved there are no metacharacters except for the * '*' at each end. */ len = Tcl_DStringLength(&pattern) - 2; if (len + 2 > 2) { goto isSimpleGlob; } /* * The pattern is "**"! I believe that should be impossible, * but we definitely can't handle that at all. */ } case '\0': case '?': case '[': case '\\': goto done; } bytes++; } isSimpleGlob: for (bytes = TclGetString(replacementObj); *bytes; bytes++) { switch (*bytes) { case '\\': case '&': goto done; } } /* * Proved the simplicity constraints! Time to issue the code. */ result = TCL_OK; bytes = Tcl_DStringValue(&pattern) + 1; PushLiteral(envPtr, bytes, len); bytes = TclGetStringFromObj(replacementObj, &len); PushLiteral(envPtr, bytes, len); CompileWord(envPtr, stringTokenPtr, interp, (int)parsePtr->numWords - 2); TclEmitOpcode( INST_STR_MAP, envPtr); done: Tcl_DStringFree(&pattern); if (patternObj) { Tcl_DecrRefCount(patternObj); } if (replacementObj) { Tcl_DecrRefCount(replacementObj); } return result; } /* *---------------------------------------------------------------------- * * TclCompileReturnCmd -- * * Procedure called to compile the "return" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "return" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileReturnCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ /* * General syntax: [return ?-option value ...? ?result?] * An even number of words means an explicit result argument is present. */ int level, code, objc, status = TCL_OK; Tcl_Size size; int numWords = parsePtr->numWords; int explicitResult = (0 == (numWords % 2)); int numOptionWords = numWords - 1 - explicitResult; Tcl_Obj *returnOpts, **objv; Tcl_Token *wordTokenPtr = TokenAfter(parsePtr->tokenPtr); /* * Check for special case which can always be compiled: * return -options * Unlike the normal [return] compilation, this version does everything at * runtime so it can handle arbitrary words and not just literals. Note * that if INST_RETURN_STK wasn't already needed for something else * ('finally' clause processing) this piece of code would not be present. */ if ((numWords == 4) && (wordTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) && (wordTokenPtr[1].size == 8) && (strncmp(wordTokenPtr[1].start, "-options", 8) == 0)) { Tcl_Token *optsTokenPtr = TokenAfter(wordTokenPtr); Tcl_Token *msgTokenPtr = TokenAfter(optsTokenPtr); CompileWord(envPtr, optsTokenPtr, interp, 2); CompileWord(envPtr, msgTokenPtr, interp, 3); TclEmitInvoke(envPtr, INST_RETURN_STK); return TCL_OK; } /* * Allocate some working space. */ objv = (Tcl_Obj **)TclStackAlloc(interp, numOptionWords * sizeof(Tcl_Obj *)); /* * Scan through the return options. If any are unknown at compile time, * there is no value in bytecompiling. Save the option values known in an * objv array for merging into a return options dictionary. * * TODO: There is potential for improvement if all option keys are known * at compile time and all option values relating to '-code' and '-level' * are known at compile time. */ for (objc = 0; objc < numOptionWords; objc++) { TclNewObj(objv[objc]); Tcl_IncrRefCount(objv[objc]); if (!TclWordKnownAtCompileTime(wordTokenPtr, objv[objc])) { /* * Non-literal, so punt to run-time assembly of the dictionary. */ for (; objc>=0 ; objc--) { TclDecrRefCount(objv[objc]); } TclStackFree(interp, objv); goto issueRuntimeReturn; } wordTokenPtr = TokenAfter(wordTokenPtr); } status = TclMergeReturnOptions(interp, objc, objv, &returnOpts, &code, &level); while (--objc >= 0) { TclDecrRefCount(objv[objc]); } TclStackFree(interp, objv); if (TCL_ERROR == status) { /* * Something was bogus in the return options. Clear the error message, * and report back to the compiler that this must be interpreted at * runtime. */ Tcl_ResetResult(interp); return TCL_ERROR; } /* * All options are known at compile time, so we're going to bytecompile. * Emit instructions to push the result on the stack. */ if (explicitResult) { CompileWord(envPtr, wordTokenPtr, interp, numWords - 1); } else { /* * No explict result argument, so default result is empty string. */ PushStringLiteral(envPtr, ""); } /* * Check for optimization: When [return] is in a proc, and there's no * enclosing [catch], and there are no return options, then the INST_DONE * instruction is equivalent, and may be more efficient. */ if (numOptionWords == 0 && envPtr->procPtr != NULL) { /* * We have default return options and we're in a proc ... */ int index = envPtr->exceptArrayNext - 1; int enclosingCatch = 0; while (index >= 0) { ExceptionRange range = envPtr->exceptArrayPtr[index]; if ((range.type == CATCH_EXCEPTION_RANGE) && (range.catchOffset == TCL_INDEX_NONE)) { enclosingCatch = 1; break; } index--; } if (!enclosingCatch) { /* * ... and there is no enclosing catch. Issue the maximally * efficient exit instruction. */ Tcl_DecrRefCount(returnOpts); TclEmitOpcode(INST_DONE, envPtr); TclAdjustStackDepth(1, envPtr); return TCL_OK; } } /* Optimize [return -level 0 $x]. */ Tcl_DictObjSize(NULL, returnOpts, &size); if (size == 0 && level == 0 && code == TCL_OK) { Tcl_DecrRefCount(returnOpts); return TCL_OK; } /* * Could not use the optimization, so we push the return options dict, and * emit the INST_RETURN_IMM instruction with code and level as operands. */ CompileReturnInternal(envPtr, INST_RETURN_IMM, code, level, returnOpts); return TCL_OK; issueRuntimeReturn: /* * Assemble the option dictionary (as a list as that's good enough). */ wordTokenPtr = TokenAfter(parsePtr->tokenPtr); for (objc=1 ; objc<=numOptionWords ; objc++) { CompileWord(envPtr, wordTokenPtr, interp, objc); wordTokenPtr = TokenAfter(wordTokenPtr); } TclEmitInstInt4(INST_LIST, numOptionWords, envPtr); /* * Push the result. */ if (explicitResult) { CompileWord(envPtr, wordTokenPtr, interp, numWords - 1); } else { PushStringLiteral(envPtr, ""); } /* * Issue the RETURN itself. */ TclEmitInvoke(envPtr, INST_RETURN_STK); return TCL_OK; } static void CompileReturnInternal( CompileEnv *envPtr, unsigned char op, int code, int level, Tcl_Obj *returnOpts) { if (level == 0 && (code == TCL_BREAK || code == TCL_CONTINUE)) { ExceptionRange *rangePtr; ExceptionAux *exceptAux; rangePtr = TclGetInnermostExceptionRange(envPtr, code, &exceptAux); if (rangePtr && rangePtr->type == LOOP_EXCEPTION_RANGE) { TclCleanupStackForBreakContinue(envPtr, exceptAux); if (code == TCL_BREAK) { TclAddLoopBreakFixup(envPtr, exceptAux); } else { TclAddLoopContinueFixup(envPtr, exceptAux); } Tcl_DecrRefCount(returnOpts); return; } } TclEmitPush(TclAddLiteralObj(envPtr, returnOpts, NULL), envPtr); TclEmitInstInt4(op, code, envPtr); TclEmitInt4(level, envPtr); } void TclCompileSyntaxError( Tcl_Interp *interp, CompileEnv *envPtr) { Tcl_Obj *msg = Tcl_GetObjResult(interp); Tcl_Size numBytes; const char *bytes = TclGetStringFromObj(msg, &numBytes); TclErrorStackResetIf(interp, bytes, numBytes); TclEmitPush(TclRegisterLiteral(envPtr, bytes, numBytes, 0), envPtr); CompileReturnInternal(envPtr, INST_SYNTAX, TCL_ERROR, 0, TclNoErrorStack(interp, Tcl_GetReturnOptions(interp, TCL_ERROR))); Tcl_ResetResult(interp); } /* *---------------------------------------------------------------------- * * TclCompileUpvarCmd -- * * Procedure called to compile the "upvar" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "upvar" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileUpvarCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr, *otherTokenPtr, *localTokenPtr; int localIndex, numWords, i; Tcl_Obj *objPtr; if (envPtr->procPtr == NULL) { return TCL_ERROR; } numWords = parsePtr->numWords; if (numWords < 3) { return TCL_ERROR; } /* * Push the frame index if it is known at compile time */ TclNewObj(objPtr); tokenPtr = TokenAfter(parsePtr->tokenPtr); if (TclWordKnownAtCompileTime(tokenPtr, objPtr)) { CallFrame *framePtr; const Tcl_ObjType *newTypePtr, *typePtr = objPtr->typePtr; /* * Attempt to convert to a level reference. Note that TclObjGetFrame * only changes the obj type when a conversion was successful. */ TclObjGetFrame(interp, objPtr, &framePtr); newTypePtr = objPtr->typePtr; Tcl_DecrRefCount(objPtr); if (newTypePtr != typePtr) { if (numWords%2) { return TCL_ERROR; } /* TODO: Push the known value instead? */ CompileWord(envPtr, tokenPtr, interp, 1); otherTokenPtr = TokenAfter(tokenPtr); i = 2; } else { if (!(numWords%2)) { return TCL_ERROR; } PushStringLiteral(envPtr, "1"); otherTokenPtr = tokenPtr; i = 1; } } else { Tcl_DecrRefCount(objPtr); return TCL_ERROR; } /* * Loop over the (otherVar, thisVar) pairs. If any of the thisVar is not a * local variable, return an error so that the non-compiled command will * be called at runtime. */ for (; inumWords; if (numWords < 2) { return TCL_ERROR; } /* * Bail out if not compiling a proc body */ if (envPtr->procPtr == NULL) { return TCL_ERROR; } /* * Loop over the (var, value) pairs. */ valueTokenPtr = parsePtr->tokenPtr; for (i=1; inumComponents; Tcl_Size len; Tcl_Token *lastTokenPtr; int full, localIndex; /* * Determine if the tail is (a) known at compile time, and (b) not an * array element. Should any of these fail, return an error so that the * non-compiled command will be called at runtime. * * In order for the tail to be known at compile time, the last token in * the word has to be constant and contain "::" if it is not the only one. */ if (!EnvHasLVT(envPtr)) { return -1; } TclNewObj(tailPtr); if (TclWordKnownAtCompileTime(varTokenPtr, tailPtr)) { full = 1; lastTokenPtr = varTokenPtr; } else { full = 0; lastTokenPtr = varTokenPtr + n; if (lastTokenPtr->type != TCL_TOKEN_TEXT) { Tcl_DecrRefCount(tailPtr); return -1; } Tcl_SetStringObj(tailPtr, lastTokenPtr->start, lastTokenPtr->size); } tailName = TclGetStringFromObj(tailPtr, &len); if (len) { if (*(tailName + len - 1) == ')') { /* * Possible array: bail out */ Tcl_DecrRefCount(tailPtr); return -1; } /* * Get the tail: immediately after the last '::' */ for (p = tailName + len -1; p > tailName; p--) { if ((p[0] == ':') && (p[- 1] == ':')) { p++; break; } } if (!full && (p == tailName)) { /* * No :: in the last component. */ Tcl_DecrRefCount(tailPtr); return -1; } len -= p - tailName; tailName = p; } localIndex = TclFindCompiledLocal(tailName, len, 1, envPtr); Tcl_DecrRefCount(tailPtr); return localIndex; } /* * ---------------------------------------------------------------------- * * TclCompileObjectNextCmd, TclCompileObjectSelfCmd -- * * Compilations of the TclOO utility commands [next] and [self]. * * ---------------------------------------------------------------------- */ int TclCompileObjectNextCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = parsePtr->tokenPtr; int i; if ((int)parsePtr->numWords > 255) { return TCL_ERROR; } for (i=0 ; i<(int)parsePtr->numWords ; i++) { CompileWord(envPtr, tokenPtr, interp, i); tokenPtr = TokenAfter(tokenPtr); } TclEmitInstInt1( INST_TCLOO_NEXT, i, envPtr); return TCL_OK; } int TclCompileObjectNextToCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = parsePtr->tokenPtr; int i; if ((int)parsePtr->numWords < 2 || (int)parsePtr->numWords > 255) { return TCL_ERROR; } for (i=0 ; i<(int)parsePtr->numWords ; i++) { CompileWord(envPtr, tokenPtr, interp, i); tokenPtr = TokenAfter(tokenPtr); } TclEmitInstInt1( INST_TCLOO_NEXT_CLASS, i, envPtr); return TCL_OK; } int TclCompileObjectSelfCmd( TCL_UNUSED(Tcl_Interp *), Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * We only handle [self] and [self object] (which is the same operation). * These are the only very common operations on [self] for which * bytecoding is at all reasonable. */ if (parsePtr->numWords == 1) { goto compileSelfObject; } else if (parsePtr->numWords == 2) { Tcl_Token *tokenPtr = TokenAfter(parsePtr->tokenPtr), *subcmd; if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD || tokenPtr[1].size==0) { return TCL_ERROR; } subcmd = tokenPtr + 1; if (strncmp(subcmd->start, "object", subcmd->size) == 0) { goto compileSelfObject; } else if (strncmp(subcmd->start, "namespace", subcmd->size) == 0) { goto compileSelfNamespace; } } /* * Can't compile; handle with runtime call. */ return TCL_ERROR; compileSelfObject: /* * This delegates the entire problem to a single opcode. */ TclEmitOpcode( INST_TCLOO_SELF, envPtr); return TCL_OK; compileSelfNamespace: /* * This is formally only correct with TclOO methods as they are currently * implemented; it assumes that the current namespace is invariably when a * TclOO context is present is the object's namespace, and that's * technically only something that's a matter of current policy. But it * avoids creating another opcode, so that's all good! */ TclEmitOpcode( INST_TCLOO_SELF, envPtr); TclEmitOpcode( INST_POP, envPtr); TclEmitOpcode( INST_NS_CURRENT, envPtr); return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclCompCmdsSZ.c0000644000175000017500000036775014726623136016045 0ustar sergeisergei/* * tclCompCmdsSZ.c -- * * This file contains compilation procedures that compile various Tcl * commands (beginning with the letters 's' through 'z', except for * [upvar] and [variable]) into a sequence of instructions ("bytecodes"). * Also includes the operator command compilers. * * Copyright © 1997-1998 Sun Microsystems, Inc. * Copyright © 2001 Kevin B. Kenny. All rights reserved. * Copyright © 2002 ActiveState Corporation. * Copyright © 2004-2010 Donal K. Fellows. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" #include "tclStringTrim.h" /* * Prototypes for procedures defined later in this file: */ static AuxDataDupProc DupJumptableInfo; static AuxDataFreeProc FreeJumptableInfo; static AuxDataPrintProc PrintJumptableInfo; static AuxDataPrintProc DisassembleJumptableInfo; static int CompileAssociativeBinaryOpCmd(Tcl_Interp *interp, Tcl_Parse *parsePtr, const char *identity, int instruction, CompileEnv *envPtr); static int CompileComparisonOpCmd(Tcl_Interp *interp, Tcl_Parse *parsePtr, int instruction, CompileEnv *envPtr); static int CompileStrictlyBinaryOpCmd(Tcl_Interp *interp, Tcl_Parse *parsePtr, int instruction, CompileEnv *envPtr); static int CompileUnaryOpCmd(Tcl_Interp *interp, Tcl_Parse *parsePtr, int instruction, CompileEnv *envPtr); static void IssueSwitchChainedTests(Tcl_Interp *interp, CompileEnv *envPtr, int mode, int noCase, Tcl_Size numWords, Tcl_Token **bodyToken, Tcl_Size *bodyLines, Tcl_Size **bodyNext); static void IssueSwitchJumpTable(Tcl_Interp *interp, CompileEnv *envPtr, int numWords, Tcl_Token **bodyToken, Tcl_Size *bodyLines, Tcl_Size **bodyContLines); static int IssueTryClausesInstructions(Tcl_Interp *interp, CompileEnv *envPtr, Tcl_Token *bodyToken, int numHandlers, int *matchCodes, Tcl_Obj **matchClauses, int *resultVarIndices, int *optionVarIndices, Tcl_Token **handlerTokens); static int IssueTryClausesFinallyInstructions(Tcl_Interp *interp, CompileEnv *envPtr, Tcl_Token *bodyToken, int numHandlers, int *matchCodes, Tcl_Obj **matchClauses, int *resultVarIndices, int *optionVarIndices, Tcl_Token **handlerTokens, Tcl_Token *finallyToken); static int IssueTryFinallyInstructions(Tcl_Interp *interp, CompileEnv *envPtr, Tcl_Token *bodyToken, Tcl_Token *finallyToken); /* * The structures below define the AuxData types defined in this file. */ const AuxDataType tclJumptableInfoType = { "JumptableInfo", /* name */ DupJumptableInfo, /* dupProc */ FreeJumptableInfo, /* freeProc */ PrintJumptableInfo, /* printProc */ DisassembleJumptableInfo /* disassembleProc */ }; /* * Shorthand macros for instruction issuing. */ #define OP(name) TclEmitOpcode(INST_##name, envPtr) #define OP1(name,val) TclEmitInstInt1(INST_##name,(val),envPtr) #define OP4(name,val) TclEmitInstInt4(INST_##name,(val),envPtr) #define OP14(name,val1,val2) \ TclEmitInstInt1(INST_##name,(val1),envPtr);TclEmitInt4((val2),envPtr) #define OP44(name,val1,val2) \ TclEmitInstInt4(INST_##name,(val1),envPtr);TclEmitInt4((val2),envPtr) #define PUSH(str) \ PushStringLiteral(envPtr, str) #define JUMP4(name,var) \ (var) = CurrentOffset(envPtr);TclEmitInstInt4(INST_##name##4,0,envPtr) #define FIXJUMP4(var) \ TclStoreInt4AtPtr(CurrentOffset(envPtr)-(var),envPtr->codeStart+(var)+1) #define JUMP1(name,var) \ (var) = CurrentOffset(envPtr);TclEmitInstInt1(INST_##name##1,0,envPtr) #define FIXJUMP1(var) \ TclStoreInt1AtPtr(CurrentOffset(envPtr)-(var),envPtr->codeStart+(var)+1) #define LOAD(idx) \ if ((idx)<256) {OP1(LOAD_SCALAR1,(idx));} else {OP4(LOAD_SCALAR4,(idx));} #define STORE(idx) \ if ((idx)<256) {OP1(STORE_SCALAR1,(idx));} else {OP4(STORE_SCALAR4,(idx));} #define INVOKE(name) \ TclEmitInvoke(envPtr,INST_##name) /* *---------------------------------------------------------------------- * * TclCompileSetCmd -- * * Procedure called to compile the "set" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "set" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileSetCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *varTokenPtr, *valueTokenPtr; int isAssignment, isScalar, localIndex, numWords; numWords = parsePtr->numWords; if ((numWords != 2) && (numWords != 3)) { return TCL_ERROR; } isAssignment = (numWords == 3); /* * Decide if we can use a frame slot for the var/array name or if we need * to emit code to compute and push the name at runtime. We use a frame * slot (entry in the array of local vars) if we are compiling a procedure * body and if the name is simple text that does not include namespace * qualifiers. */ varTokenPtr = TokenAfter(parsePtr->tokenPtr); PushVarNameWord(interp, varTokenPtr, envPtr, 0, &localIndex, &isScalar, 1); /* * If we are doing an assignment, push the new value. */ if (isAssignment) { valueTokenPtr = TokenAfter(varTokenPtr); CompileWord(envPtr, valueTokenPtr, interp, 2); } /* * Emit instructions to set/get the variable. */ if (isScalar) { if (localIndex < 0) { TclEmitOpcode((isAssignment? INST_STORE_STK : INST_LOAD_STK), envPtr); } else if (localIndex <= 255) { TclEmitInstInt1((isAssignment? INST_STORE_SCALAR1 : INST_LOAD_SCALAR1), localIndex, envPtr); } else { TclEmitInstInt4((isAssignment? INST_STORE_SCALAR4 : INST_LOAD_SCALAR4), localIndex, envPtr); } } else { if (localIndex < 0) { TclEmitOpcode((isAssignment? INST_STORE_ARRAY_STK : INST_LOAD_ARRAY_STK), envPtr); } else if (localIndex <= 255) { TclEmitInstInt1((isAssignment? INST_STORE_ARRAY1 : INST_LOAD_ARRAY1), localIndex, envPtr); } else { TclEmitInstInt4((isAssignment? INST_STORE_ARRAY4 : INST_LOAD_ARRAY4), localIndex, envPtr); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileString*Cmd -- * * Procedures called to compile various subcommands of the "string" * command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "string" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileStringCatCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ int i, numWords = parsePtr->numWords, numArgs; Tcl_Token *wordTokenPtr; Tcl_Obj *obj, *folded; /* Trivial case, no arg */ if (numWords<2) { PushStringLiteral(envPtr, ""); return TCL_OK; } /* General case: issue CONCAT1's (by chunks of 254 if needed), folding * contiguous constants along the way */ numArgs = 0; folded = NULL; wordTokenPtr = TokenAfter(parsePtr->tokenPtr); for (i = 1; i < numWords; i++) { TclNewObj(obj); if (TclWordKnownAtCompileTime(wordTokenPtr, obj)) { if (folded) { Tcl_AppendObjToObj(folded, obj); Tcl_DecrRefCount(obj); } else { folded = obj; } } else { Tcl_DecrRefCount(obj); if (folded) { Tcl_Size len; const char *bytes = TclGetStringFromObj(folded, &len); PushLiteral(envPtr, bytes, len); Tcl_DecrRefCount(folded); folded = NULL; numArgs ++; } CompileWord(envPtr, wordTokenPtr, interp, i); numArgs ++; if (numArgs >= 254) { /* 254 to take care of the possible +1 of "folded" above */ TclEmitInstInt1(INST_STR_CONCAT1, numArgs, envPtr); numArgs = 1; /* concat pushes 1 obj, the result */ } } wordTokenPtr = TokenAfter(wordTokenPtr); } if (folded) { Tcl_Size len; const char *bytes = TclGetStringFromObj(folded, &len); PushLiteral(envPtr, bytes, len); Tcl_DecrRefCount(folded); folded = NULL; numArgs ++; } if (numArgs > 1) { TclEmitInstInt1(INST_STR_CONCAT1, numArgs, envPtr); } return TCL_OK; } int TclCompileStringCmpCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; /* * We don't support any flags; the bytecode isn't that sophisticated. */ if (parsePtr->numWords != 3) { return TCL_ERROR; } /* * Push the two operands onto the stack and then the test. */ tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); TclEmitOpcode(INST_STR_CMP, envPtr); return TCL_OK; } int TclCompileStringEqualCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; /* * We don't support any flags; the bytecode isn't that sophisticated. */ if (parsePtr->numWords != 3) { return TCL_ERROR; } /* * Push the two operands onto the stack and then the test. */ tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); TclEmitOpcode(INST_STR_EQ, envPtr); return TCL_OK; } int TclCompileStringFirstCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; /* * We don't support any flags; the bytecode isn't that sophisticated. */ if (parsePtr->numWords != 3) { return TCL_ERROR; } /* * Push the two operands onto the stack and then the test. */ tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); OP(STR_FIND); return TCL_OK; } int TclCompileStringLastCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; /* * We don't support any flags; the bytecode isn't that sophisticated. */ if (parsePtr->numWords != 3) { return TCL_ERROR; } /* * Push the two operands onto the stack and then the test. */ tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); OP(STR_FIND_LAST); return TCL_OK; } int TclCompileStringIndexCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; if (parsePtr->numWords != 3) { return TCL_ERROR; } /* * Push the two operands onto the stack and then the index operation. */ tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); TclEmitOpcode(INST_STR_INDEX, envPtr); return TCL_OK; } int TclCompileStringInsertCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; int idx; if (parsePtr->numWords != 4) { return TCL_ERROR; } /* Compute and push the string in which to insert */ tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); /* See what can be discovered about index at compile time */ tokenPtr = TokenAfter(tokenPtr); if (TCL_OK != TclGetIndexFromToken(tokenPtr, TCL_INDEX_START, TCL_INDEX_END, &idx)) { /* Nothing useful knowable - cease compile; let it direct eval */ return TCL_ERROR; } /* Compute and push the string to be inserted */ tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 3); if (idx == (int)TCL_INDEX_START) { /* Prepend the insertion string */ OP4( REVERSE, 2); OP1( STR_CONCAT1, 2); } else if (idx == (int)TCL_INDEX_END) { /* Append the insertion string */ OP1( STR_CONCAT1, 2); } else { /* Prefix + insertion + suffix */ if (idx < (int)TCL_INDEX_END) { /* See comments in compiler for [linsert]. */ idx++; } OP4( OVER, 1); OP44( STR_RANGE_IMM, 0, idx-1); OP4( REVERSE, 3); OP44( STR_RANGE_IMM, idx, TCL_INDEX_END); OP1( STR_CONCAT1, 3); } return TCL_OK; } int TclCompileStringIsCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = TokenAfter(parsePtr->tokenPtr); static const char *const isClasses[] = { "alnum", "alpha", "ascii", "control", "boolean", "dict", "digit", "double", "entier", "false", "graph", "integer", "list", "lower", "print", "punct", "space", "true", "upper", "wideinteger", "wordchar", "xdigit", NULL }; enum isClassesEnum { STR_IS_ALNUM, STR_IS_ALPHA, STR_IS_ASCII, STR_IS_CONTROL, STR_IS_BOOL, STR_IS_DICT, STR_IS_DIGIT, STR_IS_DOUBLE, STR_IS_ENTIER, STR_IS_FALSE, STR_IS_GRAPH, STR_IS_INT, STR_IS_LIST, STR_IS_LOWER, STR_IS_PRINT, STR_IS_PUNCT, STR_IS_SPACE, STR_IS_TRUE, STR_IS_UPPER, STR_IS_WIDE, STR_IS_WORD, STR_IS_XDIGIT } t; int range, allowEmpty = 0, end; InstStringClassType strClassType; Tcl_Obj *isClass; if (parsePtr->numWords < 3 || parsePtr->numWords > 6) { return TCL_ERROR; } TclNewObj(isClass); if (!TclWordKnownAtCompileTime(tokenPtr, isClass)) { Tcl_DecrRefCount(isClass); return TCL_ERROR; } else if (Tcl_GetIndexFromObj(interp, isClass, isClasses, "class", 0, &t) != TCL_OK) { Tcl_DecrRefCount(isClass); TclCompileSyntaxError(interp, envPtr); return TCL_OK; } Tcl_DecrRefCount(isClass); #define GotLiteral(tokenPtr, word) \ ((tokenPtr)->type == TCL_TOKEN_SIMPLE_WORD && \ (tokenPtr)[1].size > 1 && \ (tokenPtr)[1].start[0] == word[0] && \ strncmp((tokenPtr)[1].start, (word), (tokenPtr)[1].size) == 0) /* * Cannot handle the -failindex option at all, and that's the only legal * way to have more than 4 arguments. */ if (parsePtr->numWords != 3 && parsePtr->numWords != 4) { return TCL_ERROR; } tokenPtr = TokenAfter(tokenPtr); if (parsePtr->numWords == 3) { allowEmpty = 1; } else { if (!GotLiteral(tokenPtr, "-strict")) { return TCL_ERROR; } tokenPtr = TokenAfter(tokenPtr); } #undef GotLiteral /* * Compile the code. There are several main classes of check here. * 1. Character classes * 2. Booleans * 3. Integers * 4. Floats * 5. Lists */ CompileWord(envPtr, tokenPtr, interp, (int)parsePtr->numWords-1); switch (t) { case STR_IS_ALNUM: strClassType = STR_CLASS_ALNUM; goto compileStrClass; case STR_IS_ALPHA: strClassType = STR_CLASS_ALPHA; goto compileStrClass; case STR_IS_ASCII: strClassType = STR_CLASS_ASCII; goto compileStrClass; case STR_IS_CONTROL: strClassType = STR_CLASS_CONTROL; goto compileStrClass; case STR_IS_DIGIT: strClassType = STR_CLASS_DIGIT; goto compileStrClass; case STR_IS_GRAPH: strClassType = STR_CLASS_GRAPH; goto compileStrClass; case STR_IS_LOWER: strClassType = STR_CLASS_LOWER; goto compileStrClass; case STR_IS_PRINT: strClassType = STR_CLASS_PRINT; goto compileStrClass; case STR_IS_PUNCT: strClassType = STR_CLASS_PUNCT; goto compileStrClass; case STR_IS_SPACE: strClassType = STR_CLASS_SPACE; goto compileStrClass; case STR_IS_UPPER: strClassType = STR_CLASS_UPPER; goto compileStrClass; case STR_IS_WORD: strClassType = STR_CLASS_WORD; goto compileStrClass; case STR_IS_XDIGIT: strClassType = STR_CLASS_XDIGIT; compileStrClass: if (allowEmpty) { OP1( STR_CLASS, strClassType); } else { int over, over2; OP( DUP); OP1( STR_CLASS, strClassType); JUMP1( JUMP_TRUE, over); OP( POP); PUSH( "0"); JUMP1( JUMP, over2); FIXJUMP1(over); PUSH( ""); OP( STR_NEQ); FIXJUMP1(over2); } return TCL_OK; case STR_IS_BOOL: case STR_IS_FALSE: case STR_IS_TRUE: OP( TRY_CVT_TO_BOOLEAN); switch (t) { int over, over2; case STR_IS_BOOL: if (allowEmpty) { JUMP1( JUMP_TRUE, over); PUSH( ""); OP( STR_EQ); JUMP1( JUMP, over2); FIXJUMP1(over); OP( POP); PUSH( "1"); FIXJUMP1(over2); } else { OP4( REVERSE, 2); OP( POP); } return TCL_OK; case STR_IS_TRUE: JUMP1( JUMP_TRUE, over); if (allowEmpty) { PUSH( ""); OP( STR_EQ); } else { OP( POP); PUSH( "0"); } FIXJUMP1( over); OP( LNOT); OP( LNOT); return TCL_OK; case STR_IS_FALSE: JUMP1( JUMP_TRUE, over); if (allowEmpty) { PUSH( ""); OP( STR_NEQ); } else { OP( POP); PUSH( "1"); } FIXJUMP1( over); OP( LNOT); return TCL_OK; default: break; } break; case STR_IS_DOUBLE: { int satisfied, isEmpty; if (allowEmpty) { OP( DUP); PUSH( ""); OP( STR_EQ); JUMP1( JUMP_TRUE, isEmpty); OP( NUM_TYPE); JUMP1( JUMP_TRUE, satisfied); PUSH( "0"); JUMP1( JUMP, end); FIXJUMP1( isEmpty); OP( POP); FIXJUMP1( satisfied); } else { OP( NUM_TYPE); JUMP1( JUMP_TRUE, satisfied); PUSH( "0"); JUMP1( JUMP, end); TclAdjustStackDepth(-1, envPtr); FIXJUMP1( satisfied); } PUSH( "1"); FIXJUMP1( end); return TCL_OK; } case STR_IS_INT: case STR_IS_WIDE: case STR_IS_ENTIER: if (allowEmpty) { int testNumType; OP( DUP); OP( NUM_TYPE); OP( DUP); JUMP1( JUMP_TRUE, testNumType); OP( POP); PUSH( ""); OP( STR_EQ); JUMP1( JUMP, end); TclAdjustStackDepth(1, envPtr); FIXJUMP1( testNumType); OP4( REVERSE, 2); OP( POP); } else { OP( NUM_TYPE); OP( DUP); JUMP1( JUMP_FALSE, end); } switch (t) { case STR_IS_WIDE: PUSH( "2"); OP( LE); break; case STR_IS_INT: case STR_IS_ENTIER: PUSH( "3"); OP( LE); break; default: break; } FIXJUMP1( end); return TCL_OK; case STR_IS_DICT: range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); OP4( BEGIN_CATCH4, range); ExceptionRangeStarts(envPtr, range); OP( DUP); OP( DICT_VERIFY); ExceptionRangeEnds(envPtr, range); ExceptionRangeTarget(envPtr, range, catchOffset); OP( POP); OP( PUSH_RETURN_CODE); OP( END_CATCH); OP( LNOT); return TCL_OK; case STR_IS_LIST: range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); OP4( BEGIN_CATCH4, range); ExceptionRangeStarts(envPtr, range); OP( DUP); OP( LIST_LENGTH); OP( POP); ExceptionRangeEnds(envPtr, range); ExceptionRangeTarget(envPtr, range, catchOffset); OP( POP); OP( PUSH_RETURN_CODE); OP( END_CATCH); OP( LNOT); return TCL_OK; } return TclCompileBasicMin0ArgCmd(interp, parsePtr, cmdPtr, envPtr); } int TclCompileStringMatchCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; size_t length; int i, exactMatch = 0, nocase = 0; const char *str; if (parsePtr->numWords < 3 || parsePtr->numWords > 4) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); /* * Check if we have a -nocase flag. */ if (parsePtr->numWords == 4) { if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } str = tokenPtr[1].start; length = tokenPtr[1].size; if ((length <= 1) || strncmp(str, "-nocase", length)) { /* * Fail at run time, not in compilation. */ return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } nocase = 1; tokenPtr = TokenAfter(tokenPtr); } /* * Push the strings to match against each other. */ for (i = 0; i < 2; i++) { if (tokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { str = tokenPtr[1].start; length = tokenPtr[1].size; if (!nocase && (i == 0)) { /* * Trivial matches can be done by 'string equal'. If -nocase * was specified, we can't do this because INST_STR_EQ has no * support for nocase. */ Tcl_Obj *copy = Tcl_NewStringObj(str, length); Tcl_IncrRefCount(copy); exactMatch = TclMatchIsTrivial(TclGetString(copy)); TclDecrRefCount(copy); } PushLiteral(envPtr, str, length); } else { SetLineInformation(i+1+nocase); CompileTokens(envPtr, tokenPtr, interp); } tokenPtr = TokenAfter(tokenPtr); } /* * Push the matcher. */ if (exactMatch) { TclEmitOpcode(INST_STR_EQ, envPtr); } else { TclEmitInstInt1(INST_STR_MATCH, nocase, envPtr); } return TCL_OK; } int TclCompileStringLenCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; Tcl_Obj *objPtr; if (parsePtr->numWords != 2) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); TclNewObj(objPtr); if (TclWordKnownAtCompileTime(tokenPtr, objPtr)) { /* * Here someone is asking for the length of a static string (or * something with backslashes). Just push the actual character (not * byte) length. */ char buf[TCL_INTEGER_SPACE]; size_t len = Tcl_GetCharLength(objPtr); len = snprintf(buf, sizeof(buf), "%" TCL_Z_MODIFIER "u", len); PushLiteral(envPtr, buf, len); } else { SetLineInformation(1); CompileTokens(envPtr, tokenPtr, interp); TclEmitOpcode(INST_STR_LEN, envPtr); } TclDecrRefCount(objPtr); return TCL_OK; } int TclCompileStringMapCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *mapTokenPtr, *stringTokenPtr; Tcl_Obj *mapObj, **objv; const char *bytes; Tcl_Size len, slen; /* * We only handle the case: * * string map {foo bar} $thing * * That is, a literal two-element list (doesn't need to be brace-quoted, * but does need to be compile-time knowable) and any old argument (the * thing to map). */ if (parsePtr->numWords != 3) { return TCL_ERROR; } mapTokenPtr = TokenAfter(parsePtr->tokenPtr); stringTokenPtr = TokenAfter(mapTokenPtr); TclNewObj(mapObj); Tcl_IncrRefCount(mapObj); if (!TclWordKnownAtCompileTime(mapTokenPtr, mapObj)) { Tcl_DecrRefCount(mapObj); return TclCompileBasic2ArgCmd(interp, parsePtr, cmdPtr, envPtr); } else if (TclListObjGetElements(NULL, mapObj, &len, &objv) != TCL_OK) { Tcl_DecrRefCount(mapObj); return TclCompileBasic2ArgCmd(interp, parsePtr, cmdPtr, envPtr); } else if (len != 2) { Tcl_DecrRefCount(mapObj); return TclCompileBasic2ArgCmd(interp, parsePtr, cmdPtr, envPtr); } /* * Now issue the opcodes. Note that in the case that we know that the * first word is an empty word, we don't issue the map at all. That is the * correct semantics for mapping. */ bytes = TclGetStringFromObj(objv[0], &slen); if (slen == 0) { CompileWord(envPtr, stringTokenPtr, interp, 2); } else { PushLiteral(envPtr, bytes, slen); bytes = TclGetStringFromObj(objv[1], &slen); PushLiteral(envPtr, bytes, slen); CompileWord(envPtr, stringTokenPtr, interp, 2); OP(STR_MAP); } Tcl_DecrRefCount(mapObj); return TCL_OK; } int TclCompileStringRangeCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *stringTokenPtr, *fromTokenPtr, *toTokenPtr; int idx1, idx2; if (parsePtr->numWords != 4) { return TCL_ERROR; } stringTokenPtr = TokenAfter(parsePtr->tokenPtr); fromTokenPtr = TokenAfter(stringTokenPtr); toTokenPtr = TokenAfter(fromTokenPtr); /* Every path must push the string argument */ CompileWord(envPtr, stringTokenPtr, interp, 1); /* * Parse the two indices. */ if (TclGetIndexFromToken(fromTokenPtr, TCL_INDEX_START, TCL_INDEX_NONE, &idx1) != TCL_OK) { goto nonConstantIndices; } /* * Token parsed as an index expression. We treat all indices before * the string the same as the start of the string. */ if (idx1 == (int)TCL_INDEX_NONE) { /* [string range $s end+1 $last] must be empty string */ OP( POP); PUSH( ""); return TCL_OK; } if (TclGetIndexFromToken(toTokenPtr, TCL_INDEX_NONE, TCL_INDEX_END, &idx2) != TCL_OK) { goto nonConstantIndices; } /* * Token parsed as an index expression. We treat all indices after * the string the same as the end of the string. */ if (idx2 == (int)TCL_INDEX_NONE) { /* [string range $s $first -1] must be empty string */ OP( POP); PUSH( ""); return TCL_OK; } /* * Push the operand onto the stack and then the substring operation. */ OP44( STR_RANGE_IMM, idx1, idx2); return TCL_OK; /* * Push the operands onto the stack and then the substring operation. */ nonConstantIndices: CompileWord(envPtr, fromTokenPtr, interp, 2); CompileWord(envPtr, toTokenPtr, interp, 3); OP( STR_RANGE); return TCL_OK; } int TclCompileStringReplaceCmd( Tcl_Interp *interp, /* Tcl interpreter for context. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the * command. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds the resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr, *valueTokenPtr; int first, last; if ((int)parsePtr->numWords < 4 || (int)parsePtr->numWords > 5) { return TCL_ERROR; } /* Bytecode to compute/push string argument being replaced */ valueTokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, valueTokenPtr, interp, 1); /* * Check for first index known and useful at compile time. */ tokenPtr = TokenAfter(valueTokenPtr); if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_START, TCL_INDEX_NONE, &first) != TCL_OK) { goto genericReplace; } /* * Check for last index known and useful at compile time. */ tokenPtr = TokenAfter(tokenPtr); if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_NONE, TCL_INDEX_END, &last) != TCL_OK) { goto genericReplace; } /* * [string replace] is an odd bird. For many arguments it is * a conventional substring replacer. However it also goes out * of its way to become a no-op for many cases where it would be * replacing an empty substring. Precisely, it is a no-op when * * (last < first) OR * (last < 0) OR * (end < first) * * For some compile-time values we can detect these cases, and * compile direct to bytecode implementing the no-op. */ if ((last == (int)TCL_INDEX_NONE) /* Know (last < 0) */ || (first == (int)TCL_INDEX_NONE) /* Know (first > end) */ /* * Tricky to determine when runtime (last < first) can be * certainly known based on the encoded values. Consider the * cases... * * (first <= TCL_INDEX_END) && * (last <= TCL_INDEX END) && (last < first) => ACCEPT * else => cannot tell REJECT */ || ((first <= (int)TCL_INDEX_END) && (last <= (int)TCL_INDEX_END) && (last < first)) /* Know (last < first) */ /* * (first == TCL_INDEX_NONE) && * (last <= TCL_INDEX_END) => cannot tell REJECT * else => (first < last) REJECT * * else [[first >= TCL_INDEX_START]] && * (last <= TCL_INDEX_END) => cannot tell REJECT * else [[last >= TCL_INDEX START]] && (last < first) => ACCEPT */ || ((first >= (int)TCL_INDEX_START) && (last >= (int)TCL_INDEX_START) && (last < first))) { /* Know (last < first) */ if (parsePtr->numWords == 5) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 4); OP( POP); /* Pop newString */ } /* Original string argument now on TOS as result */ return TCL_OK; } if (parsePtr->numWords == 5) { /* * When we have a string replacement, we have to take care about * not replacing empty substrings that [string replace] promises * not to replace * * The remaining index values might be suitable for conventional * string replacement, but only if they cannot possibly meet the * conditions described above at runtime. If there's a chance they * might, we would have to emit bytecode to check and at that point * we're paying more in bytecode execution time than would make * things worthwhile. Trouble is we are very limited in * how much we can detect that at compile time. After decoding, * we need, first: * * (first <= end) * * The encoded indices (first <= TCL_INDEX END) and * (first == TCL_INDEX_NONE) always meets this condition, but * any other encoded first index has some list for which it fails. * * We also need, second: * * (last >= 0) * * The encoded index (last >= TCL_INDEX_START) always meet this * condition but any other encoded last index has some list for * which it fails. * * Finally we need, third: * * (first <= last) * * Considered in combination with the constraints we already have, * we see that we can proceed when (first == TCL_INDEX_NONE). * These also permit simplification of the prefix|replace|suffix * construction. The other constraints, though, interfere with * getting a guarantee that first <= last. */ if ((first == (int)TCL_INDEX_START) && (last >= (int)TCL_INDEX_START)) { /* empty prefix */ tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 4); OP4( REVERSE, 2); if (last == INT_MAX) { OP( POP); /* Pop original */ } else { OP44( STR_RANGE_IMM, last + 1, (int)TCL_INDEX_END); OP1( STR_CONCAT1, 2); } return TCL_OK; } if ((last == (int)TCL_INDEX_NONE) && (first <= (int)TCL_INDEX_END)) { OP44( STR_RANGE_IMM, 0, first-1); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 4); OP1( STR_CONCAT1, 2); return TCL_OK; } /* FLOW THROUGH TO genericReplace */ } else { /* * When we have no replacement string to worry about, we may * have more luck, because the forbidden empty string replacements * are harmless when they are replaced by another empty string. */ if (first == (int)TCL_INDEX_START) { /* empty prefix - build suffix only */ if (last == (int)TCL_INDEX_END) { /* empty suffix too => empty result */ OP( POP); /* Pop original */ PUSH ( ""); return TCL_OK; } OP44( STR_RANGE_IMM, last + 1, (int)TCL_INDEX_END); return TCL_OK; } else { if (last == (int)TCL_INDEX_END) { /* empty suffix - build prefix only */ OP44( STR_RANGE_IMM, 0, first-1); return TCL_OK; } OP( DUP); OP44( STR_RANGE_IMM, 0, first-1); OP4( REVERSE, 2); OP44( STR_RANGE_IMM, last + 1, (int)TCL_INDEX_END); OP1( STR_CONCAT1, 2); return TCL_OK; } } genericReplace: tokenPtr = TokenAfter(valueTokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 3); if (parsePtr->numWords == 5) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 4); } else { PUSH( ""); } OP( STR_REPLACE); return TCL_OK; } int TclCompileStringTrimLCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; if (parsePtr->numWords != 2 && parsePtr->numWords != 3) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); if (parsePtr->numWords == 3) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); } else { PushLiteral(envPtr, tclDefaultTrimSet, strlen(tclDefaultTrimSet)); } OP( STR_TRIM_LEFT); return TCL_OK; } int TclCompileStringTrimRCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; if (parsePtr->numWords != 2 && parsePtr->numWords != 3) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); if (parsePtr->numWords == 3) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); } else { PushLiteral(envPtr, tclDefaultTrimSet, strlen(tclDefaultTrimSet)); } OP( STR_TRIM_RIGHT); return TCL_OK; } int TclCompileStringTrimCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; if (parsePtr->numWords != 2 && parsePtr->numWords != 3) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); if (parsePtr->numWords == 3) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); } else { PushLiteral(envPtr, tclDefaultTrimSet, strlen(tclDefaultTrimSet)); } OP( STR_TRIM); return TCL_OK; } int TclCompileStringToUpperCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; if (parsePtr->numWords != 2) { return TclCompileBasic1To3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); OP( STR_UPPER); return TCL_OK; } int TclCompileStringToLowerCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; if (parsePtr->numWords != 2) { return TclCompileBasic1To3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); OP( STR_LOWER); return TCL_OK; } int TclCompileStringToTitleCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; if (parsePtr->numWords != 2) { return TclCompileBasic1To3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); OP( STR_TITLE); return TCL_OK; } /* * Support definitions for the [string is] compilation. */ static int UniCharIsAscii( int character) { return (character >= 0) && (character < 0x80); } static int UniCharIsHexDigit( int character) { return (character >= 0) && (character < 0x80) && isxdigit(UCHAR(character)); } StringClassDesc const tclStringClassTable[] = { {"alnum", Tcl_UniCharIsAlnum}, {"alpha", Tcl_UniCharIsAlpha}, {"ascii", UniCharIsAscii}, {"control", Tcl_UniCharIsControl}, {"digit", Tcl_UniCharIsDigit}, {"graph", Tcl_UniCharIsGraph}, {"lower", Tcl_UniCharIsLower}, {"print", Tcl_UniCharIsPrint}, {"punct", Tcl_UniCharIsPunct}, {"space", Tcl_UniCharIsSpace}, {"upper", Tcl_UniCharIsUpper}, {"word", Tcl_UniCharIsWordChar}, {"xdigit", UniCharIsHexDigit}, {"", NULL} }; /* *---------------------------------------------------------------------- * * TclCompileSubstCmd -- * * Procedure called to compile the "subst" command. * * Results: * Returns TCL_OK for successful compile, or TCL_ERROR to defer * evaluation to runtime (either when it is too complex to get the * semantics right, or when we know for sure that it is an error but need * the error to happen at the right time). * * Side effects: * Instructions are added to envPtr to execute the "subst" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileSubstCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ int numArgs = parsePtr->numWords - 1; int numOpts = numArgs - 1; int objc, flags = TCL_SUBST_ALL; Tcl_Obj **objv/*, *toSubst = NULL*/; Tcl_Token *wordTokenPtr = TokenAfter(parsePtr->tokenPtr); int code = TCL_ERROR; if (numArgs == 0) { return TCL_ERROR; } objv = (Tcl_Obj **)TclStackAlloc(interp, /*numArgs*/ numOpts * sizeof(Tcl_Obj *)); for (objc = 0; objc < /*numArgs*/ numOpts; objc++) { TclNewObj(objv[objc]); Tcl_IncrRefCount(objv[objc]); if (!TclWordKnownAtCompileTime(wordTokenPtr, objv[objc])) { objc++; goto cleanup; } wordTokenPtr = TokenAfter(wordTokenPtr); } /* if (TclSubstOptions(NULL, numOpts, objv, &flags) == TCL_OK) { toSubst = objv[numOpts]; Tcl_IncrRefCount(toSubst); } */ /* TODO: Figure out expansion to cover WordKnownAtCompileTime * The difficulty is that WKACT makes a copy, and if TclSubstParse * below parses the copy of the original source string, some deep * parts of the compile machinery get upset. They want all pointers * stored in Tcl_Tokens to point back to the same original string. */ if (wordTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { code = TclSubstOptions(NULL, numOpts, objv, &flags); } cleanup: while (--objc >= 0) { TclDecrRefCount(objv[objc]); } TclStackFree(interp, objv); if (/*toSubst == NULL*/ code != TCL_OK) { return TCL_ERROR; } SetLineInformation(numArgs); TclSubstCompile(interp, wordTokenPtr[1].start, wordTokenPtr[1].size, flags, mapPtr->loc[eclIndex].line[numArgs], envPtr); /* TclDecrRefCount(toSubst);*/ return TCL_OK; } void TclSubstCompile( Tcl_Interp *interp, const char *bytes, Tcl_Size numBytes, int flags, Tcl_Size line, CompileEnv *envPtr) { Tcl_Token *endTokenPtr, *tokenPtr; int breakOffset = 0, count = 0; Tcl_Size bline = line; Tcl_Parse parse; Tcl_InterpState state = NULL; TclSubstParse(interp, bytes, numBytes, flags, &parse, &state); if (state != NULL) { Tcl_ResetResult(interp); } /* * Tricky point! If the first token does not result in a *guaranteed* push * of a Tcl_Obj on the stack, we must push an empty object. Otherwise it * is possible to get to an INST_STR_CONCAT1 or INST_DONE without enough * values on the stack, resulting in a crash. Thanks to Joe Mistachkin for * identifying a script that could trigger this case. */ tokenPtr = parse.tokenPtr; if (tokenPtr->type != TCL_TOKEN_TEXT && tokenPtr->type != TCL_TOKEN_BS) { PUSH(""); count++; } for (endTokenPtr = tokenPtr + parse.numTokens; tokenPtr < endTokenPtr; tokenPtr = TokenAfter(tokenPtr)) { Tcl_Size length; int literal, catchRange, breakJump; char buf[4] = ""; JumpFixup startFixup, okFixup, returnFixup, breakFixup; JumpFixup continueFixup, otherFixup, endFixup; switch (tokenPtr->type) { case TCL_TOKEN_TEXT: literal = TclRegisterLiteral(envPtr, tokenPtr->start, tokenPtr->size, 0); TclEmitPush(literal, envPtr); TclAdvanceLines(&bline, tokenPtr->start, tokenPtr->start + tokenPtr->size); count++; continue; case TCL_TOKEN_BS: length = TclParseBackslash(tokenPtr->start, tokenPtr->size, NULL, buf); literal = TclRegisterLiteral(envPtr, buf, length, 0); TclEmitPush(literal, envPtr); count++; continue; case TCL_TOKEN_VARIABLE: /* * Check for simple variable access; see if we can only generate * TCL_OK or TCL_ERROR from the substituted variable read; if so, * there is no need to generate elaborate exception-management * code. Note that the first component of TCL_TOKEN_VARIABLE is * always TCL_TOKEN_TEXT... */ if (tokenPtr->numComponents > 1) { Tcl_Size i; int foundCommand = 0; for (i=2 ; i<=tokenPtr->numComponents ; i++) { if (tokenPtr[i].type == TCL_TOKEN_COMMAND) { foundCommand = 1; break; } } if (foundCommand) { break; } } envPtr->line = bline; TclCompileVarSubst(interp, tokenPtr, envPtr); bline = envPtr->line; count++; continue; } while (count > 255) { OP1( STR_CONCAT1, 255); count -= 254; } if (count > 1) { OP1( STR_CONCAT1, count); count = 1; } if (breakOffset == 0) { /* Jump to the start (jump over the jump to end) */ TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &startFixup); /* Jump to the end (all BREAKs land here) */ breakOffset = CurrentOffset(envPtr); TclEmitInstInt4(INST_JUMP4, 0, envPtr); /* Start */ if (TclFixupForwardJumpToHere(envPtr, &startFixup, 127)) { Tcl_Panic("TclCompileSubstCmd: bad start jump distance %" TCL_Z_MODIFIER "d", CurrentOffset(envPtr) - startFixup.codeOffset); } } envPtr->line = bline; catchRange = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); OP4( BEGIN_CATCH4, catchRange); ExceptionRangeStarts(envPtr, catchRange); switch (tokenPtr->type) { case TCL_TOKEN_COMMAND: TclCompileScript(interp, tokenPtr->start+1, tokenPtr->size-2, envPtr); count++; break; case TCL_TOKEN_VARIABLE: TclCompileVarSubst(interp, tokenPtr, envPtr); count++; break; default: Tcl_Panic("unexpected token type in TclCompileSubstCmd: %d", tokenPtr->type); } ExceptionRangeEnds(envPtr, catchRange); /* Substitution produced TCL_OK */ OP( END_CATCH); TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &okFixup); TclAdjustStackDepth(-1, envPtr); /* Exceptional return codes processed here */ ExceptionRangeTarget(envPtr, catchRange, catchOffset); OP( PUSH_RETURN_OPTIONS); OP( PUSH_RESULT); OP( PUSH_RETURN_CODE); OP( END_CATCH); OP( RETURN_CODE_BRANCH); /* ERROR -> reraise it; NB: can't require BREAK/CONTINUE handling */ OP( RETURN_STK); OP( NOP); /* RETURN */ TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &returnFixup); /* BREAK */ TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &breakFixup); /* CONTINUE */ TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &continueFixup); /* OTHER */ TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &otherFixup); TclAdjustStackDepth(1, envPtr); /* BREAK destination */ if (TclFixupForwardJumpToHere(envPtr, &breakFixup, 127)) { Tcl_Panic("TclCompileSubstCmd: bad break jump distance %" TCL_Z_MODIFIER "d", CurrentOffset(envPtr) - breakFixup.codeOffset); } OP( POP); OP( POP); breakJump = CurrentOffset(envPtr) - breakOffset; if (breakJump > 127) { OP4(JUMP4, -breakJump); } else { OP1(JUMP1, -breakJump); } TclAdjustStackDepth(2, envPtr); /* CONTINUE destination */ if (TclFixupForwardJumpToHere(envPtr, &continueFixup, 127)) { Tcl_Panic("TclCompileSubstCmd: bad continue jump distance %" TCL_Z_MODIFIER "d", CurrentOffset(envPtr) - continueFixup.codeOffset); } OP( POP); OP( POP); TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &endFixup); TclAdjustStackDepth(2, envPtr); /* RETURN + other destination */ if (TclFixupForwardJumpToHere(envPtr, &returnFixup, 127)) { Tcl_Panic("TclCompileSubstCmd: bad return jump distance %" TCL_Z_MODIFIER "d", CurrentOffset(envPtr) - returnFixup.codeOffset); } if (TclFixupForwardJumpToHere(envPtr, &otherFixup, 127)) { Tcl_Panic("TclCompileSubstCmd: bad other jump distance %" TCL_Z_MODIFIER "d", CurrentOffset(envPtr) - otherFixup.codeOffset); } /* * Pull the result to top of stack, discard options dict. */ OP4( REVERSE, 2); OP( POP); /* OK destination */ if (TclFixupForwardJumpToHere(envPtr, &okFixup, 127)) { Tcl_Panic("TclCompileSubstCmd: bad ok jump distance %" TCL_Z_MODIFIER "d", CurrentOffset(envPtr) - okFixup.codeOffset); } if (count > 1) { OP1(STR_CONCAT1, count); count = 1; } /* CONTINUE jump to here */ if (TclFixupForwardJumpToHere(envPtr, &endFixup, 127)) { Tcl_Panic("TclCompileSubstCmd: bad end jump distance %" TCL_Z_MODIFIER "d", CurrentOffset(envPtr) - endFixup.codeOffset); } bline = envPtr->line; } while (count > 255) { OP1( STR_CONCAT1, 255); count -= 254; } if (count > 1) { OP1( STR_CONCAT1, count); } Tcl_FreeParse(&parse); if (state != NULL) { Tcl_RestoreInterpState(interp, state); TclCompileSyntaxError(interp, envPtr); TclAdjustStackDepth(-1, envPtr); } /* Final target of the multi-jump from all BREAKs */ if (breakOffset > 0) { TclUpdateInstInt4AtPc(INST_JUMP4, CurrentOffset(envPtr) - breakOffset, envPtr->codeStart + breakOffset); } } /* *---------------------------------------------------------------------- * * TclCompileSwitchCmd -- * * Procedure called to compile the "switch" command. * * Results: * Returns TCL_OK for successful compile, or TCL_ERROR to defer * evaluation to runtime (either when it is too complex to get the * semantics right, or when we know for sure that it is an error but need * the error to happen at the right time). * * Side effects: * Instructions are added to envPtr to execute the "switch" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileSwitchCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; /* Pointer to tokens in command. */ int numWords; /* Number of words in command. */ Tcl_Token *valueTokenPtr; /* Token for the value to switch on. */ enum {Switch_Exact, Switch_Glob, Switch_Regexp} mode; /* What kind of switch are we doing? */ Tcl_Token *bodyTokenArray; /* Array of real pattern list items. */ Tcl_Token **bodyToken; /* Array of pointers to pattern list items. */ Tcl_Size *bodyLines; /* Array of line numbers for body list * items. */ Tcl_Size **bodyContLines; /* Array of continuation line info. */ int noCase; /* Has the -nocase flag been given? */ int foundMode = 0; /* Have we seen a mode flag yet? */ int i, valueIndex; int result = TCL_ERROR; Tcl_Size *clNext = envPtr->clNext; /* * Only handle the following versions: * switch ?--? word {pattern body ...} * switch -exact ?--? word {pattern body ...} * switch -glob ?--? word {pattern body ...} * switch -regexp ?--? word {pattern body ...} * switch -- word simpleWordPattern simpleWordBody ... * switch -exact -- word simpleWordPattern simpleWordBody ... * switch -glob -- word simpleWordPattern simpleWordBody ... * switch -regexp -- word simpleWordPattern simpleWordBody ... * When the mode is -glob, can also handle a -nocase flag. * * First off, we don't care how the command's word was generated; we're * compiling it anyway! So skip it... */ tokenPtr = TokenAfter(parsePtr->tokenPtr); valueIndex = 1; numWords = parsePtr->numWords-1; /* * Check for options. */ noCase = 0; mode = Switch_Exact; if (numWords == 2) { /* * There's just the switch value and the bodies list. In that case, we * can skip all option parsing and move on to consider switch values * and the body list. */ goto finishedOptionParse; } /* * There must be at least one option, --, because without that there is no * way to statically avoid the problems you get from strings-to-be-matched * that start with a - (the interpreted code falls apart if it encounters * them, so we punt if we *might* encounter them as that is the easiest * way of emulating the behaviour). */ for (; numWords>=3 ; tokenPtr=TokenAfter(tokenPtr),numWords--) { size_t size = tokenPtr[1].size; const char *chrs = tokenPtr[1].start; /* * We only process literal options, and we assume that -e, -g and -n * are unique prefixes of -exact, -glob and -nocase respectively (true * at time of writing). Note that -exact and -glob may only be given * at most once or we bail out (error case). */ if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD || size < 2) { return TCL_ERROR; } if ((size <= 6) && !memcmp(chrs, "-exact", size)) { if (foundMode) { return TCL_ERROR; } mode = Switch_Exact; foundMode = 1; valueIndex++; continue; } else if ((size <= 5) && !memcmp(chrs, "-glob", size)) { if (foundMode) { return TCL_ERROR; } mode = Switch_Glob; foundMode = 1; valueIndex++; continue; } else if ((size <= 7) && !memcmp(chrs, "-regexp", size)) { if (foundMode) { return TCL_ERROR; } mode = Switch_Regexp; foundMode = 1; valueIndex++; continue; } else if ((size <= 7) && !memcmp(chrs, "-nocase", size)) { noCase = 1; valueIndex++; continue; } else if ((size == 2) && !memcmp(chrs, "--", 2)) { valueIndex++; break; } /* * The switch command has many flags we cannot compile at all (e.g. * all the RE-related ones) which we must have encountered. Either * that or we have run off the end. The action here is the same: punt * to interpreted version. */ return TCL_ERROR; } if (numWords < 3) { return TCL_ERROR; } tokenPtr = TokenAfter(tokenPtr); numWords--; if (noCase && (mode == Switch_Exact)) { /* * Can't compile this case; no opcode for case-insensitive equality! */ return TCL_ERROR; } /* * The value to test against is going to always get pushed on the stack. * But not yet; we need to verify that the rest of the command is * compilable too. */ finishedOptionParse: valueTokenPtr = tokenPtr; /* For valueIndex, see previous loop. */ tokenPtr = TokenAfter(tokenPtr); numWords--; /* * Build an array of tokens for the matcher terms and script bodies. Note * that in the case of the quoted bodies, this is tricky as we cannot use * copies of the string from the input token for the generated tokens (it * causes a crash during exception handling). When multiple tokens are * available at this point, this is pretty easy. */ if (numWords == 1) { const char *bytes; Tcl_Size maxLen, numBytes; Tcl_Size bline; /* TIP #280: line of the pattern/action list, * and start of list for when tracking the * location. This list comes immediately after * the value we switch on. */ if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { return TCL_ERROR; } bytes = tokenPtr[1].start; numBytes = tokenPtr[1].size; /* Allocate enough space to work in. */ maxLen = TclMaxListLength(bytes, numBytes, NULL); if (maxLen < 2) { return TCL_ERROR; } bodyTokenArray = (Tcl_Token *)Tcl_Alloc(sizeof(Tcl_Token) * maxLen); bodyToken = (Tcl_Token **)Tcl_Alloc(sizeof(Tcl_Token *) * maxLen); bodyLines = (Tcl_Size *)Tcl_Alloc(sizeof(Tcl_Size) * maxLen); bodyContLines = (Tcl_Size **)Tcl_Alloc(sizeof(Tcl_Size*) * maxLen); bline = mapPtr->loc[eclIndex].line[valueIndex+1]; numWords = 0; while (numBytes > 0) { const char *prevBytes = bytes; int literal; if (TCL_OK != TclFindElement(NULL, bytes, numBytes, &(bodyTokenArray[numWords].start), &bytes, &(bodyTokenArray[numWords].size), &literal) || !literal) { goto abort; } bodyTokenArray[numWords].type = TCL_TOKEN_TEXT; bodyTokenArray[numWords].numComponents = 0; bodyToken[numWords] = bodyTokenArray + numWords; /* * TIP #280: Now determine the line the list element starts on * (there is no need to do it earlier, due to the possibility of * aborting, see above). */ TclAdvanceLines(&bline, prevBytes, bodyTokenArray[numWords].start); TclAdvanceContinuations(&bline, &clNext, bodyTokenArray[numWords].start - envPtr->source); bodyLines[numWords] = bline; bodyContLines[numWords] = clNext; TclAdvanceLines(&bline, bodyTokenArray[numWords].start, bytes); TclAdvanceContinuations(&bline, &clNext, bytes - envPtr->source); numBytes -= (bytes - prevBytes); numWords++; } if (numWords % 2) { abort: Tcl_Free(bodyToken); Tcl_Free(bodyTokenArray); Tcl_Free(bodyLines); Tcl_Free(bodyContLines); return TCL_ERROR; } } else if (numWords % 2 || numWords == 0) { /* * Odd number of words (>1) available, or no words at all available. * Both are error cases, so punt and let the interpreted-version * generate the error message. Note that the second case probably * should get caught earlier, but it's easy to check here again anyway * because it'd cause a nasty crash otherwise. */ return TCL_ERROR; } else { /* * Multi-word definition of patterns & actions. */ bodyToken = (Tcl_Token **)Tcl_Alloc(sizeof(Tcl_Token *) * numWords); bodyLines = (Tcl_Size *)Tcl_Alloc(sizeof(Tcl_Size) * numWords); bodyContLines = (Tcl_Size **)Tcl_Alloc(sizeof(Tcl_Size*) * numWords); bodyTokenArray = NULL; for (i=0 ; itype != TCL_TOKEN_SIMPLE_WORD) { goto freeTemporaries; } bodyToken[i] = tokenPtr+1; /* * TIP #280: Copy line information from regular cmd info. */ bodyLines[i] = mapPtr->loc[eclIndex].line[valueIndex+1+i]; bodyContLines[i] = mapPtr->loc[eclIndex].next[valueIndex+1+i]; tokenPtr = TokenAfter(tokenPtr); } } /* * Fall back to interpreted if the last body is a continuation (it's * illegal, but this makes the error happen at the right time). */ if (bodyToken[numWords-1]->size == 1 && bodyToken[numWords-1]->start[0] == '-') { goto freeTemporaries; } /* * Now we commit to generating code; the parsing stage per se is done. * Check if we can generate a jump table, since if so that's faster than * doing an explicit compare with each body. Note that we're definitely * over-conservative with determining whether we can do the jump table, * but it handles the most common case well enough. */ /* Both methods push the value to match against onto the stack. */ CompileWord(envPtr, valueTokenPtr, interp, valueIndex); if (mode == Switch_Exact) { IssueSwitchJumpTable(interp, envPtr, numWords, bodyToken, bodyLines, bodyContLines); } else { IssueSwitchChainedTests(interp, envPtr, mode, noCase, numWords, bodyToken, bodyLines, bodyContLines); } result = TCL_OK; /* * Clean up all our temporary space and return. */ freeTemporaries: Tcl_Free(bodyToken); Tcl_Free(bodyLines); Tcl_Free(bodyContLines); if (bodyTokenArray != NULL) { Tcl_Free(bodyTokenArray); } return result; } /* *---------------------------------------------------------------------- * * IssueSwitchChainedTests -- * * Generate instructions for a [switch] command that is to be compiled * into a sequence of tests. This is the generic handle-everything mode * that inherently has performance that is (on average) linear in the * number of tests. It is the only mode that can handle -glob and -regexp * matches, or anything that is case-insensitive. It does not handle the * wild-and-wooly end of regexp matching (i.e., capture of match results) * so that's when we spill to the interpreted version. * *---------------------------------------------------------------------- */ static void IssueSwitchChainedTests( Tcl_Interp *interp, /* Context for compiling script bodies. */ CompileEnv *envPtr, /* Holds resulting instructions. */ int mode, /* Exact, Glob or Regexp */ int noCase, /* Case-insensitivity flag. */ Tcl_Size numBodyTokens, /* Number of tokens describing things the * switch can match against and bodies to * execute when the match succeeds. */ Tcl_Token **bodyToken, /* Array of pointers to pattern list items. */ Tcl_Size *bodyLines, /* Array of line numbers for body list * items. */ Tcl_Size **bodyContLines) /* Array of continuation line info. */ { enum {Switch_Exact, Switch_Glob, Switch_Regexp}; int foundDefault; /* Flag to indicate whether a "default" clause * is present. */ JumpFixup *fixupArray; /* Array of forward-jump fixup records. */ unsigned int *fixupTargetArray; /* Array of places for fixups to point at. */ int fixupCount; /* Number of places to fix up. */ int contFixIndex; /* Where the first of the jumps due to a group * of continuation bodies starts, or -1 if * there aren't any. */ int contFixCount; /* Number of continuation bodies pointing to * the current (or next) real body. */ int nextArmFixupIndex; int simple, exact; /* For extracting the type of regexp. */ int i; /* * Generate a test for each arm. */ contFixIndex = -1; contFixCount = 0; fixupArray = (JumpFixup *)TclStackAlloc(interp, sizeof(JumpFixup) * numBodyTokens); fixupTargetArray = (unsigned int *)TclStackAlloc(interp, sizeof(int) * numBodyTokens); memset(fixupTargetArray, 0, numBodyTokens * sizeof(int)); fixupCount = 0; foundDefault = 0; for (i=0 ; isize != 7 || memcmp(bodyToken[numBodyTokens-2]->start, "default", 7)) { /* * Generate the test for the arm. */ switch (mode) { case Switch_Exact: OP( DUP); TclCompileTokens(interp, bodyToken[i], 1, envPtr); OP( STR_EQ); break; case Switch_Glob: TclCompileTokens(interp, bodyToken[i], 1, envPtr); OP4( OVER, 1); OP1( STR_MATCH, noCase); break; case Switch_Regexp: simple = exact = 0; /* * Keep in sync with TclCompileRegexpCmd. */ if (bodyToken[i]->type == TCL_TOKEN_TEXT) { Tcl_DString ds; if (bodyToken[i]->size == 0) { /* * The semantics of regexps are that they always match * when the RE == "". */ PUSH("1"); break; } /* * Attempt to convert pattern to glob. If successful, push * the converted pattern. */ if (TclReToGlob(NULL, bodyToken[i]->start, bodyToken[i]->size, &ds, &exact, NULL) == TCL_OK){ simple = 1; PushLiteral(envPtr, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds)); Tcl_DStringFree(&ds); } } if (!simple) { TclCompileTokens(interp, bodyToken[i], 1, envPtr); } OP4( OVER, 1); if (!simple) { /* * Pass correct RE compile flags. We use only Int1 * (8-bit), but that handles all the flags we want to * pass. Don't use TCL_REG_NOSUB as we may have backrefs * or capture vars. */ int cflags = TCL_REG_ADVANCED | (noCase ? TCL_REG_NOCASE : 0); OP1(REGEXP, cflags); } else if (exact && !noCase) { OP( STR_EQ); } else { OP1(STR_MATCH, noCase); } break; default: Tcl_Panic("unknown switch mode: %d", mode); } /* * In a fall-through case, we will jump on _true_ to the place * where the body starts (generated later, with guarantee of this * ensured earlier; the final body is never a fall-through). */ if (bodyToken[i+1]->size==1 && bodyToken[i+1]->start[0]=='-') { if (contFixIndex == -1) { contFixIndex = fixupCount; contFixCount = 0; } TclEmitForwardJump(envPtr, TCL_TRUE_JUMP, &fixupArray[contFixIndex+contFixCount]); fixupCount++; contFixCount++; continue; } TclEmitForwardJump(envPtr, TCL_FALSE_JUMP, &fixupArray[fixupCount]); nextArmFixupIndex = fixupCount; fixupCount++; } else { /* * Got a default clause; set a flag to inhibit the generation of * the jump after the body and the cleanup of the intermediate * value that we are switching against. * * Note that default clauses (which are always terminal clauses) * cannot be fall-through clauses as well, since the last clause * is never a fall-through clause (which we have already * verified). */ foundDefault = 1; } /* * Generate the body for the arm. This is guaranteed not to be a * fall-through case, but it might have preceding fall-through cases, * so we must process those first. */ if (contFixIndex != -1) { int j; for (j=0 ; jline = bodyLines[i+1]; /* TIP #280 */ envPtr->clNext = bodyContLines[i+1]; /* TIP #280 */ TclCompileCmdWord(interp, bodyToken[i+1], 1, envPtr); if (!foundDefault) { TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &fixupArray[fixupCount]); fixupCount++; fixupTargetArray[nextArmFixupIndex] = CurrentOffset(envPtr); } } /* * Discard the value we are matching against unless we've had a default * clause (in which case it will already be gone due to the code at the * start of processing an arm, guaranteed) and make the result of the * command an empty string. */ if (!foundDefault) { OP( POP); PUSH(""); } /* * Do jump fixups for arms that were executed. First, fill in the jumps of * all jumps that don't point elsewhere to point to here. */ for (i=0 ; icodeNext-envPtr->codeStart; } } /* * Now scan backwards over all the jumps (all of which are forward jumps) * doing each one. When we do one and there is a size changes, we must * scan back over all the previous ones and see if they need adjusting * before proceeding with further jump fixups (the interleaved nature of * all the jumps makes this impossible to do without nested loops). */ for (i=fixupCount-1 ; i>=0 ; i--) { if (TclFixupForwardJump(envPtr, &fixupArray[i], fixupTargetArray[i] - fixupArray[i].codeOffset, 127)) { int j; for (j=i-1 ; j>=0 ; j--) { if (fixupTargetArray[j] > fixupArray[i].codeOffset) { fixupTargetArray[j] += 3; } } } } TclStackFree(interp, fixupTargetArray); TclStackFree(interp, fixupArray); } /* *---------------------------------------------------------------------- * * IssueSwitchJumpTable -- * * Generate instructions for a [switch] command that is to be compiled * into a jump table. This only handles the case where case-sensitive, * exact matching is used, but this is actually the most common case in * real code. * *---------------------------------------------------------------------- */ static void IssueSwitchJumpTable( Tcl_Interp *interp, /* Context for compiling script bodies. */ CompileEnv *envPtr, /* Holds resulting instructions. */ int numBodyTokens, /* Number of tokens describing things the * switch can match against and bodies to * execute when the match succeeds. */ Tcl_Token **bodyToken, /* Array of pointers to pattern list items. */ Tcl_Size *bodyLines, /* Array of line numbers for body list * items. */ Tcl_Size **bodyContLines) /* Array of continuation line info. */ { JumptableInfo *jtPtr; int infoIndex, isNew, *finalFixups, numRealBodies = 0, jumpLocation; int mustGenerate, foundDefault, jumpToDefault, i; Tcl_DString buffer; Tcl_HashEntry *hPtr; /* * Compile the switch by using a jump table, which is basically a * hashtable that maps from literal values to match against to the offset * (relative to the INST_JUMP_TABLE instruction) to jump to. The jump * table itself is independent of any invocation of the bytecode, and as * such is stored in an auxData block. * * Start by allocating the jump table itself, plus some workspace. */ jtPtr = (JumptableInfo *)Tcl_Alloc(sizeof(JumptableInfo)); Tcl_InitHashTable(&jtPtr->hashTable, TCL_STRING_KEYS); infoIndex = TclCreateAuxData(jtPtr, &tclJumptableInfoType, envPtr); finalFixups = (int *)TclStackAlloc(interp, sizeof(int) * (numBodyTokens/2)); foundDefault = 0; mustGenerate = 1; /* * Next, issue the instruction to do the jump, together with what we want * to do if things do not work out (jump to either the default clause or * the "default" default, which just sets the result to empty). Note that * we will come back and rewrite the jump's offset parameter when we know * what it should be, and that all jumps we issue are of the wide kind * because that makes the code much easier to debug! */ jumpLocation = CurrentOffset(envPtr); OP4( JUMP_TABLE, infoIndex); jumpToDefault = CurrentOffset(envPtr); OP4( JUMP4, 0); for (i=0 ; isize != 7 || memcmp(bodyToken[numBodyTokens-2]->start, "default", 7)) { /* * This is not a default clause, so insert the current location as * a target in the jump table (assuming it isn't already there, * which would indicate that this clause is probably masked by an * earlier one). Note that we use a Tcl_DString here simply * because the hash API does not let us specify the string length. */ Tcl_DStringInit(&buffer); TclDStringAppendToken(&buffer, bodyToken[i]); hPtr = Tcl_CreateHashEntry(&jtPtr->hashTable, Tcl_DStringValue(&buffer), &isNew); if (isNew) { /* * First time we've encountered this match clause, so it must * point to here. */ Tcl_SetHashValue(hPtr, INT2PTR(CurrentOffset(envPtr) - jumpLocation)); } Tcl_DStringFree(&buffer); } else { /* * This is a default clause, so patch up the fallthrough from the * INST_JUMP_TABLE instruction to here. */ foundDefault = 1; isNew = 1; TclStoreInt4AtPtr(CurrentOffset(envPtr)-jumpToDefault, envPtr->codeStart+jumpToDefault+1); } /* * Now, for each arm we must deal with the body of the clause. * * If this is a continuation body (never true of a final clause, * whether default or not) we're done because the next jump target * will also point here, so we advance to the next clause. */ if (bodyToken[i+1]->size == 1 && bodyToken[i+1]->start[0] == '-') { mustGenerate = 1; continue; } /* * Also skip this arm if its only match clause is masked. (We could * probably be more aggressive about this, but that would be much more * difficult to get right.) */ if (!isNew && !mustGenerate) { continue; } mustGenerate = 0; /* * Compile the body of the arm. */ envPtr->line = bodyLines[i+1]; /* TIP #280 */ envPtr->clNext = bodyContLines[i+1]; /* TIP #280 */ TclCompileCmdWord(interp, bodyToken[i+1], 1, envPtr); /* * Compile a jump in to the end of the command if this body is * anything other than a user-supplied default arm (to either skip * over the remaining bodies or the code that generates an empty * result). */ if (i+2 < numBodyTokens || !foundDefault) { finalFixups[numRealBodies++] = CurrentOffset(envPtr); /* * Easier by far to issue this jump as a fixed-width jump, since * otherwise we'd need to do a lot more (and more awkward) * rewriting when we fixed this all up. */ OP4( JUMP4, 0); TclAdjustStackDepth(-1, envPtr); } } /* * We're at the end. If we've not already done so through the processing * of a user-supplied default clause, add in a "default" default clause * now. */ if (!foundDefault) { TclStoreInt4AtPtr(CurrentOffset(envPtr)-jumpToDefault, envPtr->codeStart+jumpToDefault+1); PUSH(""); } /* * No more instructions to be issued; everything that needs to jump to the * end of the command is fixed up at this point. */ for (i=0 ; icodeStart+finalFixups[i]+1); } /* * Clean up all our temporary space and return. */ TclStackFree(interp, finalFixups); } /* *---------------------------------------------------------------------- * * DupJumptableInfo, FreeJumptableInfo -- * * Functions to duplicate, release and print a jump-table created for use * with the INST_JUMP_TABLE instruction. * * Results: * DupJumptableInfo: a copy of the jump-table * FreeJumptableInfo: none * PrintJumptableInfo: none * DisassembleJumptableInfo: none * * Side effects: * DupJumptableInfo: allocates memory * FreeJumptableInfo: releases memory * PrintJumptableInfo: none * DisassembleJumptableInfo: none * *---------------------------------------------------------------------- */ static void * DupJumptableInfo( void *clientData) { JumptableInfo *jtPtr = (JumptableInfo *)clientData; JumptableInfo *newJtPtr = (JumptableInfo *)Tcl_Alloc(sizeof(JumptableInfo)); Tcl_HashEntry *hPtr, *newHPtr; Tcl_HashSearch search; int isNew; Tcl_InitHashTable(&newJtPtr->hashTable, TCL_STRING_KEYS); hPtr = Tcl_FirstHashEntry(&jtPtr->hashTable, &search); for (; hPtr ; hPtr = Tcl_NextHashEntry(&search)) { newHPtr = Tcl_CreateHashEntry(&newJtPtr->hashTable, Tcl_GetHashKey(&jtPtr->hashTable, hPtr), &isNew); Tcl_SetHashValue(newHPtr, Tcl_GetHashValue(hPtr)); } return newJtPtr; } static void FreeJumptableInfo( void *clientData) { JumptableInfo *jtPtr = (JumptableInfo *)clientData; Tcl_DeleteHashTable(&jtPtr->hashTable); Tcl_Free(jtPtr); } static void PrintJumptableInfo( void *clientData, Tcl_Obj *appendObj, TCL_UNUSED(ByteCode *), size_t pcOffset) { JumptableInfo *jtPtr = (JumptableInfo *)clientData; Tcl_HashEntry *hPtr; Tcl_HashSearch search; const char *keyPtr; size_t offset, i = 0; hPtr = Tcl_FirstHashEntry(&jtPtr->hashTable, &search); for (; hPtr ; hPtr = Tcl_NextHashEntry(&search)) { keyPtr = (const char *)Tcl_GetHashKey(&jtPtr->hashTable, hPtr); offset = PTR2INT(Tcl_GetHashValue(hPtr)); if (i++) { Tcl_AppendToObj(appendObj, ", ", -1); if (i%4==0) { Tcl_AppendToObj(appendObj, "\n\t\t", -1); } } Tcl_AppendPrintfToObj(appendObj, "\"%s\"->pc %" TCL_Z_MODIFIER "u", keyPtr, pcOffset + offset); } } static void DisassembleJumptableInfo( void *clientData, Tcl_Obj *dictObj, TCL_UNUSED(ByteCode *), TCL_UNUSED(size_t)) { JumptableInfo *jtPtr = (JumptableInfo *)clientData; Tcl_Obj *mapping; Tcl_HashEntry *hPtr; Tcl_HashSearch search; const char *keyPtr; size_t offset; TclNewObj(mapping); hPtr = Tcl_FirstHashEntry(&jtPtr->hashTable, &search); for (; hPtr ; hPtr = Tcl_NextHashEntry(&search)) { keyPtr = (const char *)Tcl_GetHashKey(&jtPtr->hashTable, hPtr); offset = PTR2INT(Tcl_GetHashValue(hPtr)); TclDictPut(NULL, mapping, keyPtr, Tcl_NewWideIntObj(offset)); } TclDictPut(NULL, dictObj, "mapping", mapping); } /* *---------------------------------------------------------------------- * * TclCompileTailcallCmd -- * * Procedure called to compile the "tailcall" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "tailcall" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileTailcallCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = parsePtr->tokenPtr; int i; if (parsePtr->numWords < 2 || parsePtr->numWords >= 256 || envPtr->procPtr == NULL) { return TCL_ERROR; } /* make room for the nsObjPtr */ /* TODO: Doesn't this have to be a known value? */ CompileWord(envPtr, tokenPtr, interp, 0); for (i=1 ; i<(int)parsePtr->numWords ; i++) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, i); } TclEmitInstInt1( INST_TAILCALL, (int)parsePtr->numWords, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileThrowCmd -- * * Procedure called to compile the "throw" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "throw" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileThrowCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ int numWords = parsePtr->numWords; Tcl_Token *codeToken, *msgToken; Tcl_Obj *objPtr; int codeKnown, codeIsList, codeIsValid; Tcl_Size len; if (numWords != 3) { return TCL_ERROR; } codeToken = TokenAfter(parsePtr->tokenPtr); msgToken = TokenAfter(codeToken); TclNewObj(objPtr); Tcl_IncrRefCount(objPtr); codeKnown = TclWordKnownAtCompileTime(codeToken, objPtr); /* * First we must emit the code to substitute the arguments. This * must come first in case substitution raises errors. */ if (!codeKnown) { CompileWord(envPtr, codeToken, interp, 1); PUSH( "-errorcode"); } CompileWord(envPtr, msgToken, interp, 2); codeIsList = codeKnown && (TCL_OK == TclListObjLength(interp, objPtr, &len)); codeIsValid = codeIsList && (len != 0); if (codeIsValid) { Tcl_Obj *dictPtr; TclNewObj(dictPtr); TclDictPut(NULL, dictPtr, "-errorcode", objPtr); TclEmitPush(TclAddLiteralObj(envPtr, dictPtr, NULL), envPtr); } TclDecrRefCount(objPtr); /* * Simpler bytecodes when we detect invalid arguments at compile time. */ if (codeKnown && !codeIsValid) { OP( POP); if (codeIsList) { /* Must be an empty list */ goto issueErrorForEmptyCode; } TclCompileSyntaxError(interp, envPtr); return TCL_OK; } if (!codeKnown) { /* * Argument validity checking has to be done by bytecode at * run time. */ OP4( REVERSE, 3); OP( DUP); OP( LIST_LENGTH); OP1( JUMP_FALSE1, 16); OP4( LIST, 2); OP44( RETURN_IMM, TCL_ERROR, 0); TclAdjustStackDepth(2, envPtr); OP( POP); OP( POP); OP( POP); issueErrorForEmptyCode: PUSH( "type must be non-empty list"); PUSH( "-errorcode {TCL OPERATION THROW BADEXCEPTION}"); } OP44( RETURN_IMM, TCL_ERROR, 0); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileTryCmd -- * * Procedure called to compile the "try" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "try" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileTryCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { int numWords = parsePtr->numWords, numHandlers, result = TCL_ERROR; Tcl_Token *bodyToken, *finallyToken, *tokenPtr; Tcl_Token **handlerTokens = NULL; Tcl_Obj **matchClauses = NULL; int *matchCodes=NULL, *resultVarIndices=NULL, *optionVarIndices=NULL; int i; if (numWords < 2) { return TCL_ERROR; } bodyToken = TokenAfter(parsePtr->tokenPtr); if (numWords == 2) { /* * No handlers or finally; do nothing beyond evaluating the body. */ DefineLineInformation; /* TIP #280 */ BODY(bodyToken, 1); return TCL_OK; } numWords -= 2; tokenPtr = TokenAfter(bodyToken); /* * Extract information about what handlers there are. */ numHandlers = numWords >> 2; numWords -= numHandlers * 4; if (numHandlers > 0) { handlerTokens = (Tcl_Token**)TclStackAlloc(interp, sizeof(Tcl_Token*)*numHandlers); matchClauses = (Tcl_Obj **)TclStackAlloc(interp, sizeof(Tcl_Obj *) * numHandlers); memset(matchClauses, 0, sizeof(Tcl_Obj *) * numHandlers); matchCodes = (int *)TclStackAlloc(interp, sizeof(int) * numHandlers); resultVarIndices = (int *)TclStackAlloc(interp, sizeof(int) * numHandlers); optionVarIndices = (int *)TclStackAlloc(interp, sizeof(int) * numHandlers); for (i=0 ; itype != TCL_TOKEN_SIMPLE_WORD) { goto failedToCompile; } if (tokenPtr[1].size == 4 && !strncmp(tokenPtr[1].start, "trap", 4)) { /* * Parse the list of errorCode words to match against. */ matchCodes[i] = TCL_ERROR; tokenPtr = TokenAfter(tokenPtr); TclNewObj(tmpObj); Tcl_IncrRefCount(tmpObj); if (!TclWordKnownAtCompileTime(tokenPtr, tmpObj) || TclListObjLength(NULL, tmpObj, &objc) != TCL_OK || (objc == 0)) { TclDecrRefCount(tmpObj); goto failedToCompile; } Tcl_ListObjReplace(NULL, tmpObj, 0, 0, 0, NULL); matchClauses[i] = tmpObj; } else if (tokenPtr[1].size == 2 && !strncmp(tokenPtr[1].start, "on", 2)) { int code; /* * Parse the result code to look for. */ tokenPtr = TokenAfter(tokenPtr); TclNewObj(tmpObj); Tcl_IncrRefCount(tmpObj); if (!TclWordKnownAtCompileTime(tokenPtr, tmpObj)) { TclDecrRefCount(tmpObj); goto failedToCompile; } if (TCL_ERROR == TclGetCompletionCodeFromObj(NULL, tmpObj, &code)) { TclDecrRefCount(tmpObj); goto failedToCompile; } matchCodes[i] = code; TclDecrRefCount(tmpObj); } else { goto failedToCompile; } /* * Parse the variable binding. */ tokenPtr = TokenAfter(tokenPtr); TclNewObj(tmpObj); Tcl_IncrRefCount(tmpObj); if (!TclWordKnownAtCompileTime(tokenPtr, tmpObj)) { TclDecrRefCount(tmpObj); goto failedToCompile; } if (TclListObjGetElements(NULL, tmpObj, &objc, &objv) != TCL_OK || (objc > 2)) { TclDecrRefCount(tmpObj); goto failedToCompile; } if (objc > 0) { Tcl_Size len; const char *varname = TclGetStringFromObj(objv[0], &len); resultVarIndices[i] = LocalScalar(varname, len, envPtr); if (resultVarIndices[i] < 0) { TclDecrRefCount(tmpObj); goto failedToCompile; } } else { resultVarIndices[i] = -1; } if (objc == 2) { Tcl_Size len; const char *varname = TclGetStringFromObj(objv[1], &len); optionVarIndices[i] = LocalScalar(varname, len, envPtr); if (optionVarIndices[i] < 0) { TclDecrRefCount(tmpObj); goto failedToCompile; } } else { optionVarIndices[i] = -1; } TclDecrRefCount(tmpObj); /* * Extract the body for this handler. */ tokenPtr = TokenAfter(tokenPtr); if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { goto failedToCompile; } if (tokenPtr[1].size == 1 && tokenPtr[1].start[0] == '-') { handlerTokens[i] = NULL; } else { handlerTokens[i] = tokenPtr; } tokenPtr = TokenAfter(tokenPtr); } if (handlerTokens[numHandlers-1] == NULL) { goto failedToCompile; } } /* * Parse the finally clause */ if (numWords == 0) { finallyToken = NULL; } else if (numWords == 2) { if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD || tokenPtr[1].size != 7 || strncmp(tokenPtr[1].start, "finally", 7)) { goto failedToCompile; } finallyToken = TokenAfter(tokenPtr); if (finallyToken->type != TCL_TOKEN_SIMPLE_WORD) { goto failedToCompile; } } else { goto failedToCompile; } /* * Issue the bytecode. */ if (!finallyToken) { result = IssueTryClausesInstructions(interp, envPtr, bodyToken, numHandlers, matchCodes, matchClauses, resultVarIndices, optionVarIndices, handlerTokens); } else if (numHandlers == 0) { result = IssueTryFinallyInstructions(interp, envPtr, bodyToken, finallyToken); } else { result = IssueTryClausesFinallyInstructions(interp, envPtr, bodyToken, numHandlers, matchCodes, matchClauses, resultVarIndices, optionVarIndices, handlerTokens, finallyToken); } /* * Delete any temporary state and finish off. */ failedToCompile: if (numHandlers > 0) { for (i=0 ; i= 0) { LOAD( resultVar); STORE( resultVars[i]); OP( POP); if (optionVars[i] >= 0) { LOAD( optionsVar); STORE( optionVars[i]); OP( POP); } } if (!handlerTokens[i]) { forwardsNeedFixing = 1; JUMP4( JUMP, forwardsToFix[i]); TclAdjustStackDepth(1, envPtr); } else { int dontChangeOptions; forwardsToFix[i] = -1; if (forwardsNeedFixing) { forwardsNeedFixing = 0; for (j=0 ; j= 0 || handlerTokens[i]) { range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); OP4( BEGIN_CATCH4, range); ExceptionRangeStarts(envPtr, range); } if (resultVars[i] >= 0) { LOAD( resultVar); STORE( resultVars[i]); OP( POP); if (optionVars[i] >= 0) { LOAD( optionsVar); STORE( optionVars[i]); OP( POP); } if (!handlerTokens[i]) { /* * No handler. Will not be the last handler (that is a * condition that is checked by the caller). Chain to the next * one. */ ExceptionRangeEnds(envPtr, range); OP( END_CATCH); forwardsNeedFixing = 1; JUMP4( JUMP, forwardsToFix[i]); goto finishTrapCatchHandling; } } else if (!handlerTokens[i]) { /* * No handler. Will not be the last handler (that condition is * checked by the caller). Chain to the next one. */ forwardsNeedFixing = 1; JUMP4( JUMP, forwardsToFix[i]); goto endOfThisArm; } /* * Got a handler. Make sure that any pending patch-up actions from * previous unprocessed handlers are dealt with now that we know where * they are to jump to. */ if (forwardsNeedFixing) { forwardsNeedFixing = 0; OP1( JUMP1, 7); for (j=0 ; jtokenPtr ; i<(int)parsePtr->numWords ; i++) { Tcl_Obj *leadingWord; TclNewObj(leadingWord); varTokenPtr = TokenAfter(varTokenPtr); if (!TclWordKnownAtCompileTime(varTokenPtr, leadingWord)) { TclDecrRefCount(leadingWord); /* * We can tolerate non-trivial substitutions in the first variable * to be unset. If a '--' or '-nocomplain' was present, anything * goes in that one place! (All subsequent variable names must be * constants since we don't want to have to push them all first.) */ if (varCount == 0) { if (haveFlags) { continue; } /* * In fact, we're OK as long as we're the first argument *and* * we provably don't start with a '-'. If that is true, then * even if everything else is varying, we still can't be a * flag. Otherwise we'll spill to runtime to place a limit on * the trickiness. */ if (varTokenPtr->type == TCL_TOKEN_WORD && varTokenPtr[1].type == TCL_TOKEN_TEXT && varTokenPtr[1].size > 0 && varTokenPtr[1].start[0] != '-') { continue; } } return TCL_ERROR; } if (varCount == 0) { const char *bytes; Tcl_Size len; bytes = TclGetStringFromObj(leadingWord, &len); if (i == 1 && len == 11 && !strncmp("-nocomplain", bytes, 11)) { flags = 0; haveFlags++; } else if (i == (2 - flags) && len == 2 && !strncmp("--", bytes, 2)) { haveFlags++; } else { varCount++; } } else { varCount++; } TclDecrRefCount(leadingWord); } /* * Issue instructions to unset each of the named variables. */ varTokenPtr = TokenAfter(parsePtr->tokenPtr); for (i=0; inumWords ; i++) { /* * Decide if we can use a frame slot for the var/array name or if we * need to emit code to compute and push the name at runtime. We use a * frame slot (entry in the array of local vars) if we are compiling a * procedure body and if the name is simple text that does not include * namespace qualifiers. */ PushVarNameWord(interp, varTokenPtr, envPtr, 0, &localIndex, &isScalar, i); /* * Emit instructions to unset the variable. */ if (isScalar) { if (localIndex < 0) { OP1( UNSET_STK, flags); } else { OP14( UNSET_SCALAR, flags, localIndex); } } else { if (localIndex < 0) { OP1( UNSET_ARRAY_STK, flags); } else { OP14( UNSET_ARRAY, flags, localIndex); } } varTokenPtr = TokenAfter(varTokenPtr); } PUSH(""); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileWhileCmd -- * * Procedure called to compile the "while" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "while" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileWhileCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *testTokenPtr, *bodyTokenPtr; JumpFixup jumpEvalCondFixup; int testCodeOffset, bodyCodeOffset, jumpDist, range, code, boolVal; int loopMayEnd = 1; /* This is set to 0 if it is recognized as an * infinite loop. */ Tcl_Obj *boolObj; if (parsePtr->numWords != 3) { return TCL_ERROR; } /* * If the test expression requires substitutions, don't compile the while * command inline. E.g., the expression might cause the loop to never * execute or execute forever, as in "while "$x < 5" {}". * * Bail out also if the body expression requires substitutions in order to * insure correct behaviour [Bug 219166] */ testTokenPtr = TokenAfter(parsePtr->tokenPtr); bodyTokenPtr = TokenAfter(testTokenPtr); if ((testTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) || (bodyTokenPtr->type != TCL_TOKEN_SIMPLE_WORD)) { return TCL_ERROR; } /* * Find out if the condition is a constant. */ boolObj = Tcl_NewStringObj(testTokenPtr[1].start, testTokenPtr[1].size); Tcl_IncrRefCount(boolObj); code = Tcl_GetBooleanFromObj(NULL, boolObj, &boolVal); TclDecrRefCount(boolObj); if (code == TCL_OK) { if (boolVal) { /* * It is an infinite loop; flag it so that we generate a more * efficient body. */ loopMayEnd = 0; } else { /* * This is an empty loop: "while 0 {...}" or such. Compile no * bytecodes. */ goto pushResult; } } /* * Create a ExceptionRange record for the loop body. This is used to * implement break and continue. */ range = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); /* * Jump to the evaluation of the condition. This code uses the "loop * rotation" optimisation (which eliminates one branch from the loop). * "while cond body" produces then: * goto A * B: body : bodyCodeOffset * A: cond -> result : testCodeOffset, continueOffset * if (result) goto B * * The infinite loop "while 1 body" produces: * B: body : all three offsets here * goto B */ if (loopMayEnd) { TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpEvalCondFixup); testCodeOffset = 0; /* Avoid compiler warning. */ } else { /* * Make sure that the first command in the body is preceded by an * INST_START_CMD, and hence counted properly. [Bug 1752146] */ envPtr->atCmdStart &= ~1; testCodeOffset = CurrentOffset(envPtr); } /* * Compile the loop body. */ bodyCodeOffset = ExceptionRangeStarts(envPtr, range); if (!loopMayEnd) { envPtr->exceptArrayPtr[range].continueOffset = testCodeOffset; envPtr->exceptArrayPtr[range].codeOffset = bodyCodeOffset; } BODY(bodyTokenPtr, 2); ExceptionRangeEnds(envPtr, range); OP( POP); /* * Compile the test expression then emit the conditional jump that * terminates the while. We already know it's a simple word. */ if (loopMayEnd) { testCodeOffset = CurrentOffset(envPtr); jumpDist = testCodeOffset - jumpEvalCondFixup.codeOffset; if (TclFixupForwardJump(envPtr, &jumpEvalCondFixup, jumpDist, 127)) { bodyCodeOffset += 3; testCodeOffset += 3; } SetLineInformation(1); TclCompileExprWords(interp, testTokenPtr, 1, envPtr); jumpDist = CurrentOffset(envPtr) - bodyCodeOffset; if (jumpDist > 127) { TclEmitInstInt4(INST_JUMP_TRUE4, -jumpDist, envPtr); } else { TclEmitInstInt1(INST_JUMP_TRUE1, -jumpDist, envPtr); } } else { jumpDist = CurrentOffset(envPtr) - bodyCodeOffset; if (jumpDist > 127) { TclEmitInstInt4(INST_JUMP4, -jumpDist, envPtr); } else { TclEmitInstInt1(INST_JUMP1, -jumpDist, envPtr); } } /* * Set the loop's body, continue and break offsets. */ envPtr->exceptArrayPtr[range].continueOffset = testCodeOffset; envPtr->exceptArrayPtr[range].codeOffset = bodyCodeOffset; ExceptionRangeTarget(envPtr, range, breakOffset); TclFinalizeLoopExceptionRange(envPtr, range); /* * The while command's result is an empty string. */ pushResult: PUSH(""); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileYieldCmd -- * * Procedure called to compile the "yield" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "yield" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileYieldCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { if (parsePtr->numWords < 1 || parsePtr->numWords > 2) { return TCL_ERROR; } if (parsePtr->numWords == 1) { PUSH(""); } else { DefineLineInformation; /* TIP #280 */ Tcl_Token *valueTokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, valueTokenPtr, interp, 1); } OP( YIELD); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompileYieldToCmd -- * * Procedure called to compile the "yieldto" command. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the "yieldto" command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileYieldToCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = TokenAfter(parsePtr->tokenPtr); int i; if ((int)parsePtr->numWords < 2) { return TCL_ERROR; } OP( NS_CURRENT); for (i = 1 ; i < (int)parsePtr->numWords ; i++) { CompileWord(envPtr, tokenPtr, interp, i); tokenPtr = TokenAfter(tokenPtr); } OP4( LIST, i); OP( YIELD_TO_INVOKE); return TCL_OK; } /* *---------------------------------------------------------------------- * * CompileUnaryOpCmd -- * * Utility routine to compile the unary operator commands. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the compiled command at * runtime. * *---------------------------------------------------------------------- */ static int CompileUnaryOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, int instruction, CompileEnv *envPtr) { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; if (parsePtr->numWords != 2) { return TCL_ERROR; } tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); TclEmitOpcode(instruction, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * CompileAssociativeBinaryOpCmd -- * * Utility routine to compile the binary operator commands that accept an * arbitrary number of arguments, and that are associative operations. * Because of the associativity, we may combine operations from right to * left, saving us any effort of re-ordering the arguments on the stack * after substitutions are completed. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the compiled command at * runtime. * *---------------------------------------------------------------------- */ static int CompileAssociativeBinaryOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, const char *identity, int instruction, CompileEnv *envPtr) { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = parsePtr->tokenPtr; Tcl_Size words; /* TODO: Consider support for compiling expanded args. */ for (words=1 ; wordsnumWords ; words++) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, words); } if (parsePtr->numWords <= 2) { PushLiteral(envPtr, identity, -1); words++; } if (words > 3) { /* * Reverse order of arguments to get precise agreement with [expr] in * calculations, including roundoff errors. */ OP4( REVERSE, words-1); } while (--words > 1) { TclEmitOpcode(instruction, envPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * CompileStrictlyBinaryOpCmd -- * * Utility routine to compile the binary operator commands, that strictly * accept exactly two arguments. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the compiled command at * runtime. * *---------------------------------------------------------------------- */ static int CompileStrictlyBinaryOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, int instruction, CompileEnv *envPtr) { if (parsePtr->numWords != 3) { return TCL_ERROR; } return CompileAssociativeBinaryOpCmd(interp, parsePtr, NULL, instruction, envPtr); } /* *---------------------------------------------------------------------- * * CompileComparisonOpCmd -- * * Utility routine to compile the n-ary comparison operator commands. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the compiled command at * runtime. * *---------------------------------------------------------------------- */ static int CompileComparisonOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, int instruction, CompileEnv *envPtr) { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr; /* TODO: Consider support for compiling expanded args. */ if ((int)parsePtr->numWords < 3) { PUSH("1"); } else if (parsePtr->numWords == 3) { tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); TclEmitOpcode(instruction, envPtr); } else if (envPtr->procPtr == NULL) { /* * No local variable space! */ return TCL_ERROR; } else { int tmpIndex = AnonymousLocal(envPtr); Tcl_Size words; tokenPtr = TokenAfter(parsePtr->tokenPtr); CompileWord(envPtr, tokenPtr, interp, 1); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); STORE(tmpIndex); TclEmitOpcode(instruction, envPtr); for (words=3 ; wordsnumWords ;) { LOAD(tmpIndex); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, words); if (++words < parsePtr->numWords) { STORE(tmpIndex); } TclEmitOpcode(instruction, envPtr); } for (; words>3 ; words--) { OP( BITAND); } /* * Drop the value from the temp variable; retaining that reference * might be expensive elsewhere. */ OP14( UNSET_SCALAR, 0, tmpIndex); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCompile*OpCmd -- * * Procedures called to compile the corresponding "::tcl::mathop::*" * commands. These are all wrappers around the utility operator command * compiler functions, except for the compilers for subtraction and * division, which are special. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the compiled command at * runtime. * *---------------------------------------------------------------------- */ int TclCompileInvertOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileUnaryOpCmd(interp, parsePtr, INST_BITNOT, envPtr); } int TclCompileNotOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileUnaryOpCmd(interp, parsePtr, INST_LNOT, envPtr); } int TclCompileAddOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileAssociativeBinaryOpCmd(interp, parsePtr, "0", INST_ADD, envPtr); } int TclCompileMulOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileAssociativeBinaryOpCmd(interp, parsePtr, "1", INST_MULT, envPtr); } int TclCompileAndOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileAssociativeBinaryOpCmd(interp, parsePtr, "-1", INST_BITAND, envPtr); } int TclCompileOrOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileAssociativeBinaryOpCmd(interp, parsePtr, "0", INST_BITOR, envPtr); } int TclCompileXorOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileAssociativeBinaryOpCmd(interp, parsePtr, "0", INST_BITXOR, envPtr); } int TclCompilePowOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = parsePtr->tokenPtr; Tcl_Size words; /* * This one has its own implementation because the ** operator is the only * one with right associativity. */ for (words=1 ; wordsnumWords ; words++) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, words); } if (parsePtr->numWords <= 2) { PUSH("1"); words++; } while (--words > 1) { TclEmitOpcode(INST_EXPON, envPtr); } return TCL_OK; } int TclCompileLshiftOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_LSHIFT, envPtr); } int TclCompileRshiftOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_RSHIFT, envPtr); } int TclCompileModOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_MOD, envPtr); } int TclCompileNeqOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_NEQ, envPtr); } int TclCompileStrneqOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_STR_NEQ, envPtr); } int TclCompileInOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_LIST_IN, envPtr); } int TclCompileNiOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_LIST_NOT_IN, envPtr); } int TclCompileLessOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileComparisonOpCmd(interp, parsePtr, INST_LT, envPtr); } int TclCompileLeqOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileComparisonOpCmd(interp, parsePtr, INST_LE, envPtr); } int TclCompileGreaterOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileComparisonOpCmd(interp, parsePtr, INST_GT, envPtr); } int TclCompileGeqOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileComparisonOpCmd(interp, parsePtr, INST_GE, envPtr); } int TclCompileEqOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileComparisonOpCmd(interp, parsePtr, INST_EQ, envPtr); } int TclCompileStreqOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileComparisonOpCmd(interp, parsePtr, INST_STR_EQ, envPtr); } int TclCompileStrLtOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileComparisonOpCmd(interp, parsePtr, INST_STR_LT, envPtr); } int TclCompileStrLeOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileComparisonOpCmd(interp, parsePtr, INST_STR_LE, envPtr); } int TclCompileStrGtOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileComparisonOpCmd(interp, parsePtr, INST_STR_GT, envPtr); } int TclCompileStrGeOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { return CompileComparisonOpCmd(interp, parsePtr, INST_STR_GE, envPtr); } int TclCompileMinusOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = parsePtr->tokenPtr; Tcl_Size words; /* TODO: Consider support for compiling expanded args. */ if (parsePtr->numWords == 1) { /* * Fallback to direct eval to report syntax error. */ return TCL_ERROR; } for (words=1 ; wordsnumWords ; words++) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, words); } if (words == 2) { TclEmitOpcode(INST_UMINUS, envPtr); return TCL_OK; } if (words == 3) { TclEmitOpcode(INST_SUB, envPtr); return TCL_OK; } /* * Reverse order of arguments to get precise agreement with [expr] in * calculations, including roundoff errors. */ TclEmitInstInt4(INST_REVERSE, words-1, envPtr); while (--words > 1) { TclEmitInstInt4(INST_REVERSE, 2, envPtr); TclEmitOpcode(INST_SUB, envPtr); } return TCL_OK; } int TclCompileDivOpCmd( Tcl_Interp *interp, Tcl_Parse *parsePtr, TCL_UNUSED(Command *), CompileEnv *envPtr) { DefineLineInformation; /* TIP #280 */ Tcl_Token *tokenPtr = parsePtr->tokenPtr; Tcl_Size words; /* TODO: Consider support for compiling expanded args. */ if (parsePtr->numWords == 1) { /* * Fallback to direct eval to report syntax error. */ return TCL_ERROR; } if (parsePtr->numWords == 2) { PUSH("1.0"); } for (words=1 ; wordsnumWords ; words++) { tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, words); } if (words <= 3) { TclEmitOpcode(INST_DIV, envPtr); return TCL_OK; } /* * Reverse order of arguments to get precise agreement with [expr] in * calculations, including roundoff errors. */ TclEmitInstInt4(INST_REVERSE, words-1, envPtr); while (--words > 1) { TclEmitInstInt4(INST_REVERSE, 2, envPtr); TclEmitOpcode(INST_DIV, envPtr); } return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclCompExpr.c0000644000175000017500000024702314726623136015606 0ustar sergeisergei/* * tclCompExpr.c -- * * This file contains the code to parse and compile Tcl expressions and * implementations of the Tcl commands corresponding to expression * operators, such as the command ::tcl::mathop::+ . * * Contributions from Don Porter, NIST, 2006-2007. (not subject to US copyright) * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" /* CompileEnv */ /* * Expression parsing takes place in the routine ParseExpr(). It takes a * string as input, parses that string, and generates a representation of the * expression in the form of a tree of operators, a list of literals, a list * of function names, and an array of Tcl_Token's within a Tcl_Parse struct. * The tree is composed of OpNodes. */ typedef struct { int left; /* "Pointer" to the left operand. */ int right; /* "Pointer" to the right operand. */ union { int parent; /* "Pointer" to the parent operand. */ int prev; /* "Pointer" joining incomplete tree stack */ } p; unsigned char lexeme; /* Code that identifies the operator. */ unsigned char precedence; /* Precedence of the operator */ unsigned char mark; /* Mark used to control traversal. */ unsigned char constant; /* Flag marking constant subexpressions. */ } OpNode; /* * The storage for the tree is dynamically allocated array of OpNodes. The * array is grown as parsing needs dictate according to a scheme similar to * Tcl's string growth algorithm, so that the resizing costs are O(N) and so * that we use at least half the memory allocated as expressions get large. * * Each OpNode in the tree represents an operator in the expression, either * unary or binary. When parsing is completed successfully, a binary operator * OpNode will have its left and right fields filled with "pointers" to its * left and right operands. A unary operator OpNode will have its right field * filled with a pointer to its single operand. When an operand is a * subexpression the "pointer" takes the form of the index -- a non-negative * integer -- into the OpNode storage array where the root of that * subexpression parse tree is found. * * Non-operator elements of the expression do not get stored in the OpNode * tree. They are stored in the other structures according to their type. * Literal values get appended to the literal list. Elements that denote forms * of quoting or substitution known to the Tcl parser get stored as * Tcl_Tokens. These non-operator elements of the expression are the leaves of * the completed parse tree. When an operand of an OpNode is one of these leaf * elements, the following negative integer codes are used to indicate which * kind of elements it is. */ enum OperandTypes { OT_LITERAL = -3, /* Operand is a literal in the literal list */ OT_TOKENS = -2, /* Operand is sequence of Tcl_Tokens */ OT_EMPTY = -1 /* "Operand" is an empty string. This is a special * case used only to represent the EMPTY lexeme. See * below. */ }; /* * Readable macros to test whether a "pointer" value points to an operator. * They operate on the "non-negative integer -> operator; negative integer -> * a non-operator OperandType" distinction. */ #define IsOperator(l) ((l) >= 0) #define NotOperator(l) ((l) < 0) /* * Note that it is sufficient to store in the tree just the type of leaf * operand, without any explicit pointer to which leaf. This is true because * the traversals of the completed tree we perform are known to visit the * leaves in the same order as the original parse. * * In a completed parse tree, those OpNodes that are themselves (roots of * subexpression trees that are) operands of some operator store in their * p.parent field a "pointer" to the OpNode of that operator. The p.parent * field permits a traversal of the tree within a non-recursive routine * (ConvertTreeToTokens() and CompileExprTree()). This means that even * expression trees of great depth pose no risk of blowing the C stack. * * While the parse tree is being constructed, the same memory space is used to * hold the p.prev field which chains together a stack of incomplete trees * awaiting their right operands. * * The lexeme field is filled in with the lexeme of the operator that is * returned by the ParseLexeme() routine. Only lexemes for unary and binary * operators get stored in an OpNode. Other lexmes get different treatment. * * The precedence field provides a place to store the precedence of the * operator, so it need not be looked up again and again. * * The mark field is use to control the traversal of the tree, so that it can * be done non-recursively. The mark values are: */ enum Marks { MARK_LEFT, /* Next step of traversal is to visit left subtree */ MARK_RIGHT, /* Next step of traversal is to visit right subtree */ MARK_PARENT /* Next step of traversal is to return to parent */ }; /* * The constant field is a boolean flag marking which subexpressions are * completely known at compile time, and are eligible for computing then * rather than waiting until run time. */ /* * The four category values are LEAF, UNARY, and BINARY, explained below, and * "uncategorized", which is used either temporarily, until context determines * which of the other three categories is correct, or for lexemes like * INVALID, which aren't really lexemes at all, but indicators of a parsing * error. Note that the codes must be distinct to distinguish categories, but * need not take the form of a bit array. */ enum LexemeTypes { /* * Each lexeme belongs to one of four categories, which determine its place * in the parse tree. We use the two high bits of the (unsigned char) value * to store a NODE_TYPE code. */ NODE_TYPE = 0xC0, BINARY = 0x40, /* This lexeme is a binary operator. An OpNode * representing it should go into the parse * tree, and two operands should be parsed for * it in the expression. */ UNARY = 0x80, /* This lexeme is a unary operator. An OpNode * representing it should go into the parse * tree, and one operand should be parsed for * it in the expression. */ LEAF = 0xC0 /* This lexeme is a leaf operand in the parse * tree. No OpNode will be placed in the tree * for it. Either a literal value will be * appended to the list of literals in this * expression, or appropriate Tcl_Tokens will * be appended in a Tcl_Parse struct to * represent those leaves that require some * form of substitution. */ }; enum LexemeCodes { /* Uncategorized lexemes */ PLUS = 1, /* Ambiguous. Resolves to UNARY_PLUS or * BINARY_PLUS according to context. */ MINUS = 2, /* Ambiguous. Resolves to UNARY_MINUS or * BINARY_MINUS according to context. */ BAREWORD = 3, /* Ambiguous. Resolves to BOOL_LIT or to * FUNCTION or a parse error according to * context and value. */ INCOMPLETE = 4, /* A parse error. Used only when the single * "=" is encountered. */ INVALID = 5, /* A parse error. Used when any punctuation * appears that's not a supported operator. */ COMMENT = 6, /* Comment. Lasts to end of line or end of * expression, whichever comes first. */ /* Leaf lexemes */ NUMBER = LEAF | 1, /* For literal numbers */ SCRIPT = LEAF | 2, /* Script substitution; [foo] */ BOOL_LIT = LEAF | BAREWORD, /* For literal booleans */ BRACED = LEAF | 4, /* Braced string; {foo bar} */ VARIABLE = LEAF | 5, /* Variable substitution; $x */ QUOTED = LEAF | 6, /* Quoted string; "foo $bar [soom]" */ EMPTY = LEAF | 7, /* Used only for an empty argument list to a * function. Represents the empty string * within parens in the expression: rand() */ /* Unary operator lexemes */ UNARY_PLUS = UNARY | PLUS, UNARY_MINUS = UNARY | MINUS, FUNCTION = UNARY | BAREWORD, /* This is a bit of "creative interpretation" * on the part of the parser. A function call * is parsed into the parse tree according to * the perspective that the function name is a * unary operator and its argument list, * enclosed in parens, is its operand. The * additional requirements not implied * generally by treatment as a unary operator * -- for example, the requirement that the * operand be enclosed in parens -- are hard * coded in the relevant portions of * ParseExpr(). We trade off the need to * include such exceptional handling in the * code against the need we would otherwise * have for more lexeme categories. */ START = UNARY | 4, /* This lexeme isn't parsed from the * expression text at all. It represents the * start of the expression and sits at the * root of the parse tree where it serves as * the start/end point of traversals. */ OPEN_PAREN = UNARY | 5, /* Another bit of creative interpretation, * where we treat "(" as a unary operator with * the sub-expression between it and its * matching ")" as its operand. See * CLOSE_PAREN below. */ NOT = UNARY | 6, BIT_NOT = UNARY | 7, /* Binary operator lexemes */ BINARY_PLUS = BINARY | PLUS, BINARY_MINUS = BINARY | MINUS, COMMA = BINARY | 3, /* The "," operator is a low precedence binary * operator that separates the arguments in a * function call. The additional constraint * that this operator can only legally appear * at the right places within a function call * argument list are hard coded within * ParseExpr(). */ MULT = BINARY | 4, DIVIDE = BINARY | 5, MOD = BINARY | 6, LESS = BINARY | 7, GREATER = BINARY | 8, BIT_AND = BINARY | 9, BIT_XOR = BINARY | 10, BIT_OR = BINARY | 11, QUESTION = BINARY | 12, /* These two lexemes make up the */ COLON = BINARY | 13, /* ternary conditional operator, $x ? $y : $z. * We treat them as two binary operators to * avoid another lexeme category, and code the * additional constraints directly in * ParseExpr(). For instance, the right * operand of a "?" operator must be a ":" * operator. */ LEFT_SHIFT = BINARY | 14, RIGHT_SHIFT = BINARY | 15, LEQ = BINARY | 16, GEQ = BINARY | 17, EQUAL = BINARY | 18, NEQ = BINARY | 19, AND = BINARY | 20, OR = BINARY | 21, STREQ = BINARY | 22, STRNEQ = BINARY | 23, EXPON = BINARY | 24, /* Unlike the other binary operators, EXPON is * right associative and this distinction is * coded directly in ParseExpr(). */ IN_LIST = BINARY | 25, NOT_IN_LIST = BINARY | 26, CLOSE_PAREN = BINARY | 27, /* By categorizing the CLOSE_PAREN lexeme as a * BINARY operator, the normal parsing rules * for binary operators assure that a close * paren will not directly follow another * operator, and the machinery already in * place to connect operands to operators * according to precedence performs most of * the work of matching open and close parens * for us. In the end though, a close paren is * not really a binary operator, and some * special coding in ParseExpr() make sure we * never put an actual CLOSE_PAREN node in the * parse tree. The sub-expression between * parens becomes the single argument of the * matching OPEN_PAREN unary operator. */ STR_LT = BINARY | 28, STR_GT = BINARY | 29, STR_LEQ = BINARY | 30, STR_GEQ = BINARY | 31, END = BINARY | 32 /* This lexeme represents the end of the * string being parsed. Treating it as a * binary operator follows the same logic as * the CLOSE_PAREN lexeme and END pairs with * START, in the same way that CLOSE_PAREN * pairs with OPEN_PAREN. */ }; /* * When ParseExpr() builds the parse tree it must choose which operands to * connect to which operators. This is done according to operator precedence. * The greater an operator's precedence the greater claim it has to link to an * available operand. The Precedence enumeration lists the precedence values * used by Tcl expression operators, from lowest to highest claim. Each * precedence level is commented with the operators that hold that precedence. */ enum Precedence { PREC_END = 1, /* END */ PREC_START, /* START */ PREC_CLOSE_PAREN, /* ")" */ PREC_OPEN_PAREN, /* "(" */ PREC_COMMA, /* "," */ PREC_CONDITIONAL, /* "?", ":" */ PREC_OR, /* "||" */ PREC_AND, /* "&&" */ PREC_BIT_OR, /* "|" */ PREC_BIT_XOR, /* "^" */ PREC_BIT_AND, /* "&" */ PREC_EQUAL, /* "==", "!=", "eq", "ne", "in", "ni" */ PREC_COMPARE, /* "<", ">", "<=", ">=" */ PREC_SHIFT, /* "<<", ">>" */ PREC_ADD, /* "+", "-" */ PREC_MULT, /* "*", "/", "%" */ PREC_EXPON, /* "**" */ PREC_UNARY /* "+", "-", FUNCTION, "!", "~" */ }; /* * Here the same information contained in the comments above is stored in * inverted form, so that given a lexeme, one can quickly look up its * precedence value. */ static const unsigned char prec[] = { /* Non-operator lexemes */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* Binary operator lexemes */ PREC_ADD, /* BINARY_PLUS */ PREC_ADD, /* BINARY_MINUS */ PREC_COMMA, /* COMMA */ PREC_MULT, /* MULT */ PREC_MULT, /* DIVIDE */ PREC_MULT, /* MOD */ PREC_COMPARE, /* LESS */ PREC_COMPARE, /* GREATER */ PREC_BIT_AND, /* BIT_AND */ PREC_BIT_XOR, /* BIT_XOR */ PREC_BIT_OR, /* BIT_OR */ PREC_CONDITIONAL, /* QUESTION */ PREC_CONDITIONAL, /* COLON */ PREC_SHIFT, /* LEFT_SHIFT */ PREC_SHIFT, /* RIGHT_SHIFT */ PREC_COMPARE, /* LEQ */ PREC_COMPARE, /* GEQ */ PREC_EQUAL, /* EQUAL */ PREC_EQUAL, /* NEQ */ PREC_AND, /* AND */ PREC_OR, /* OR */ PREC_EQUAL, /* STREQ */ PREC_EQUAL, /* STRNEQ */ PREC_EXPON, /* EXPON */ PREC_EQUAL, /* IN_LIST */ PREC_EQUAL, /* NOT_IN_LIST */ PREC_CLOSE_PAREN, /* CLOSE_PAREN */ PREC_COMPARE, /* STR_LT */ PREC_COMPARE, /* STR_GT */ PREC_COMPARE, /* STR_LEQ */ PREC_COMPARE, /* STR_GEQ */ PREC_END, /* END */ /* Expansion room for more binary operators */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* Unary operator lexemes */ PREC_UNARY, /* UNARY_PLUS */ PREC_UNARY, /* UNARY_MINUS */ PREC_UNARY, /* FUNCTION */ PREC_START, /* START */ PREC_OPEN_PAREN, /* OPEN_PAREN */ PREC_UNARY, /* NOT*/ PREC_UNARY, /* BIT_NOT*/ }; /* * A table mapping lexemes to bytecode instructions, used by CompileExprTree(). */ static const unsigned char instruction[] = { /* Non-operator lexemes */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* Binary operator lexemes */ INST_ADD, /* BINARY_PLUS */ INST_SUB, /* BINARY_MINUS */ 0, /* COMMA */ INST_MULT, /* MULT */ INST_DIV, /* DIVIDE */ INST_MOD, /* MOD */ INST_LT, /* LESS */ INST_GT, /* GREATER */ INST_BITAND, /* BIT_AND */ INST_BITXOR, /* BIT_XOR */ INST_BITOR, /* BIT_OR */ 0, /* QUESTION */ 0, /* COLON */ INST_LSHIFT, /* LEFT_SHIFT */ INST_RSHIFT, /* RIGHT_SHIFT */ INST_LE, /* LEQ */ INST_GE, /* GEQ */ INST_EQ, /* EQUAL */ INST_NEQ, /* NEQ */ 0, /* AND */ 0, /* OR */ INST_STR_EQ, /* STREQ */ INST_STR_NEQ, /* STRNEQ */ INST_EXPON, /* EXPON */ INST_LIST_IN, /* IN_LIST */ INST_LIST_NOT_IN, /* NOT_IN_LIST */ 0, /* CLOSE_PAREN */ INST_STR_LT, /* STR_LT */ INST_STR_GT, /* STR_GT */ INST_STR_LE, /* STR_LEQ */ INST_STR_GE, /* STR_GEQ */ 0, /* END */ /* Expansion room for more binary operators */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* Unary operator lexemes */ INST_UPLUS, /* UNARY_PLUS */ INST_UMINUS, /* UNARY_MINUS */ 0, /* FUNCTION */ 0, /* START */ 0, /* OPEN_PAREN */ INST_LNOT, /* NOT*/ INST_BITNOT, /* BIT_NOT*/ }; /* * A table mapping a byte value to the corresponding lexeme for use by * ParseLexeme(). */ static const unsigned char Lexeme[] = { INVALID /* NUL */, INVALID /* SOH */, INVALID /* STX */, INVALID /* ETX */, INVALID /* EOT */, INVALID /* ENQ */, INVALID /* ACK */, INVALID /* BEL */, INVALID /* BS */, INVALID /* HT */, INVALID /* LF */, INVALID /* VT */, INVALID /* FF */, INVALID /* CR */, INVALID /* SO */, INVALID /* SI */, INVALID /* DLE */, INVALID /* DC1 */, INVALID /* DC2 */, INVALID /* DC3 */, INVALID /* DC4 */, INVALID /* NAK */, INVALID /* SYN */, INVALID /* ETB */, INVALID /* CAN */, INVALID /* EM */, INVALID /* SUB */, INVALID /* ESC */, INVALID /* FS */, INVALID /* GS */, INVALID /* RS */, INVALID /* US */, INVALID /* SPACE */, 0 /* ! or != */, QUOTED /* " */, 0 /* # */, VARIABLE /* $ */, MOD /* % */, 0 /* & or && */, INVALID /* ' */, OPEN_PAREN /* ( */, CLOSE_PAREN /* ) */, 0 /* * or ** */, PLUS /* + */, COMMA /* , */, MINUS /* - */, 0 /* . */, DIVIDE /* / */, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-9 */ COLON /* : */, INVALID /* ; */, 0 /* < or << or <= */, 0 /* == or INVALID */, 0 /* > or >> or >= */, QUESTION /* ? */, INVALID /* @ */, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* A-M */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* N-Z */ SCRIPT /* [ */, INVALID /* \ */, INVALID /* ] */, BIT_XOR /* ^ */, INVALID /* _ */, INVALID /* ` */, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a-m */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* n-z */ BRACED /* { */, 0 /* | or || */, INVALID /* } */, BIT_NOT /* ~ */, INVALID /* DEL */ }; /* * The JumpList struct is used to create a stack of data needed for the * TclEmitForwardJump() and TclFixupForwardJump() calls that are performed * when compiling the short-circuiting operators QUESTION/COLON, AND, and OR. * Keeping a stack permits the CompileExprTree() routine to be non-recursive. */ typedef struct JumpList { JumpFixup jump; /* Pass this argument to matching calls of * TclEmitForwardJump() and * TclFixupForwardJump(). */ struct JumpList *next; /* Point to next item on the stack */ } JumpList; /* * Declarations for local functions to this file: */ static void CompileExprTree(Tcl_Interp *interp, OpNode *nodes, int index, Tcl_Obj *const **litObjvPtr, Tcl_Obj *const *funcObjv, Tcl_Token *tokenPtr, CompileEnv *envPtr, int optimize); static void ConvertTreeToTokens(const char *start, Tcl_Size numBytes, OpNode *nodes, Tcl_Token *tokenPtr, Tcl_Parse *parsePtr); static int ExecConstantExprTree(Tcl_Interp *interp, OpNode *nodes, int index, Tcl_Obj * const **litObjvPtr); static int ParseExpr(Tcl_Interp *interp, const char *start, Tcl_Size numBytes, OpNode **opTreePtr, Tcl_Obj *litList, Tcl_Obj *funcList, Tcl_Parse *parsePtr, int parseOnly); static Tcl_Size ParseLexeme(const char *start, Tcl_Size numBytes, unsigned char *lexemePtr, Tcl_Obj **literalPtr); /* *---------------------------------------------------------------------- * * ParseExpr -- * * Given a string, the numBytes bytes starting at start, this function * parses it as a Tcl expression and constructs a tree representing the * structure of the expression. The caller must pass in empty lists as * the funcList and litList arguments. The elements of the parsed * expression are returned to the caller as that tree, a list of literal * values, a list of function names, and in Tcl_Tokens added to a * Tcl_Parse struct passed in by the caller. * * Results: * If the string is successfully parsed as a valid Tcl expression, TCL_OK * is returned, and data about the expression structure is written to the * last four arguments. If the string cannot be parsed as a valid Tcl * expression, TCL_ERROR is returned, and if interp is non-NULL, an error * message is written to interp. * * Side effects: * Memory will be allocated. If TCL_OK is returned, the caller must clean * up the returned data structures. The (OpNode *) value written to * opTreePtr should be passed to Tcl_Free() and the parsePtr argument * should be passed to Tcl_FreeParse(). The elements appended to the * litList and funcList will automatically be freed whenever the refcount * on those lists indicates they can be freed. * *---------------------------------------------------------------------- */ static int ParseExpr( Tcl_Interp *interp, /* Used for error reporting. */ const char *start, /* Start of source string to parse. */ Tcl_Size numBytes, /* Number of bytes in string. */ OpNode **opTreePtr, /* Points to space where a pointer to the * allocated OpNode tree should go. */ Tcl_Obj *litList, /* List to append literals to. */ Tcl_Obj *funcList, /* List to append function names to. */ Tcl_Parse *parsePtr, /* Structure to fill with tokens representing * those operands that require run time * substitutions. */ int parseOnly) /* A boolean indicating whether the caller's * aim is just a parse, or whether it will go * on to compile the expression. Different * optimizations are appropriate for the two * scenarios. */ { OpNode *nodes = NULL; /* Pointer to the OpNode storage array where * we build the parse tree. */ unsigned int nodesAvailable = 64; /* Initial size of the storage array. This * value establishes a minimum tree memory * cost of only about 1 kilobyte, and is large * enough for most expressions to parse with * no need for array growth and * reallocation. */ unsigned int nodesUsed = 0; /* Number of OpNodes filled. */ Tcl_Size scanned = 0; /* Capture number of byte scanned by parsing * routines. */ int lastParsed; /* Stores info about what the lexeme parsed * the previous pass through the parsing loop * was. If it was an operator, lastParsed is * the index of the OpNode for that operator. * If it was not an operator, lastParsed holds * an OperandTypes value encoding what we need * to know about it. */ int incomplete; /* Index of the most recent incomplete tree in * the OpNode array. Heads a stack of * incomplete trees linked by p.prev. */ int complete = OT_EMPTY; /* "Index" of the complete tree (that is, a * complete subexpression) determined at the * moment. OT_EMPTY is a nonsense value used * only to silence compiler warnings. During a * parse, complete will always hold an index * or an OperandTypes value pointing to an * actual leaf at the time the complete tree * is needed. */ /* * These variables control generation of the error message. */ Tcl_Obj *msg = NULL; /* The error message. */ Tcl_Obj *post = NULL; /* In a few cases, an additional postscript * for the error message, supplying more * information after the error msg and * location have been reported. */ const char *errCode = NULL; /* The detail word of the errorCode list, or * NULL to indicate that no changes to the * errorCode are to be done. */ const char *subErrCode = NULL; /* Extra information for use in generating the * errorCode. */ const char *mark = "_@_"; /* In the portion of the complete error * message where the error location is * reported, this "mark" substring is inserted * into the string being parsed to aid in * pinpointing the location of the syntax * error in the expression. */ int insertMark = 0; /* A boolean controlling whether the "mark" * should be inserted. */ const int limit = 25; /* Portions of the error message are * constructed out of substrings of the * original expression. In order to keep the * error message readable, we impose this * limit on the substring size we extract. */ TclParseInit(interp, start, numBytes, parsePtr); nodes = (OpNode *)Tcl_AttemptAlloc(nodesAvailable * sizeof(OpNode)); if (nodes == NULL) { TclNewLiteralStringObj(msg, "not enough memory to parse expression"); errCode = "NOMEM"; goto error; } /* * Initialize the parse tree with the special "START" node. */ nodes->lexeme = START; nodes->precedence = prec[START]; nodes->mark = MARK_RIGHT; nodes->constant = 1; incomplete = lastParsed = nodesUsed; nodesUsed++; /* * Main parsing loop parses one lexeme per iteration. We exit the loop * only when there's a syntax error with a "goto error" which takes us to * the error handling code following the loop, or when we've successfully * completed the parse and we return to the caller. */ while (1) { OpNode *nodePtr; /* Points to the OpNode we may fill this pass * through the loop. */ unsigned char lexeme; /* The lexeme we parse this iteration. */ Tcl_Obj *literal; /* Filled by the ParseLexeme() call when a * literal is parsed that has a Tcl_Obj rep * worth preserving. */ /* * Each pass through this loop adds up to one more OpNode. Allocate * space for one if required. */ if (nodesUsed >= nodesAvailable) { unsigned int size = nodesUsed * 2; OpNode *newPtr = NULL; do { if (size <= UINT_MAX/sizeof(OpNode)) { newPtr = (OpNode *) Tcl_AttemptRealloc(nodes, size * sizeof(OpNode)); } } while ((newPtr == NULL) && ((size -= (size - nodesUsed) / 2) > nodesUsed)); if (newPtr == NULL) { TclNewLiteralStringObj(msg, "not enough memory to parse expression"); errCode = "NOMEM"; goto error; } nodesAvailable = size; nodes = newPtr; } nodePtr = nodes + nodesUsed; /* * Skip white space between lexemes. */ scanned = TclParseAllWhiteSpace(start, numBytes); start += scanned; numBytes -= scanned; scanned = ParseLexeme(start, numBytes, &lexeme, &literal); /* * Use context to categorize the lexemes that are ambiguous. */ if ((NODE_TYPE & lexeme) == 0) { int b; switch (lexeme) { case COMMENT: start += scanned; numBytes -= scanned; continue; case INVALID: msg = Tcl_ObjPrintf("invalid character \"%.*s\"", (int)scanned, start); errCode = "BADCHAR"; goto error; case INCOMPLETE: msg = Tcl_ObjPrintf("incomplete operator \"%.*s\"", (int)scanned, start); errCode = "PARTOP"; goto error; case BAREWORD: /* * Most barewords in an expression are a syntax error. The * exceptions are that when a bareword is followed by an open * paren, it might be a function call, and when the bareword * is a legal literal boolean value, we accept that as well. */ if (start[scanned+TclParseAllWhiteSpace( start+scanned, numBytes-scanned)] == '(') { lexeme = FUNCTION; /* * When we compile the expression we'll need the function * name, and there's no place in the parse tree to store * it, so we keep a separate list of all the function * names we've parsed in the order we found them. */ Tcl_ListObjAppendElement(NULL, funcList, literal); } else if (Tcl_GetBooleanFromObj(NULL,literal,&b) == TCL_OK) { lexeme = BOOL_LIT; } else { /* * Tricky case: see test expr-62.10 */ int scanned2 = scanned; do { scanned2 += TclParseAllWhiteSpace( start + scanned2, numBytes - scanned2); scanned2 += ParseLexeme( start + scanned2, numBytes - scanned2, &lexeme, NULL); } while (lexeme == COMMENT); if (lexeme == OPEN_PAREN) { /* * Actually a function call, but with obscuring * comments. Skip to the start of the parentheses. * Note that we assume that open parentheses are one * byte long. */ lexeme = FUNCTION; Tcl_ListObjAppendElement(NULL, funcList, literal); scanned = scanned2 - 1; break; } Tcl_DecrRefCount(literal); msg = Tcl_ObjPrintf("invalid bareword \"%.*s%s\"", (int)((scanned < limit) ? scanned : limit - 3), start, (scanned < limit) ? "" : "..."); post = Tcl_ObjPrintf( "should be \"$%.*s%s\" or \"{%.*s%s}\"", (int) ((scanned < limit) ? scanned : limit - 3), start, (scanned < limit) ? "" : "...", (int) ((scanned < limit) ? scanned : limit - 3), start, (scanned < limit) ? "" : "..."); Tcl_AppendPrintfToObj(post, " or \"%.*s%s(...)\" or ...", (int) ((scanned < limit) ? scanned : limit - 3), start, (scanned < limit) ? "" : "..."); errCode = "BAREWORD"; if (start[0] == '0') { const char *stop; TclParseNumber(NULL, NULL, NULL, start, scanned, &stop, TCL_PARSE_NO_WHITESPACE); if (isdigit(UCHAR(*stop)) || (stop == start + 1)) { switch (start[1]) { case 'b': Tcl_AppendToObj(post, " (invalid binary number?)", -1); parsePtr->errorType = TCL_PARSE_BAD_NUMBER; errCode = "BADNUMBER"; subErrCode = "BINARY"; break; case 'o': Tcl_AppendToObj(post, " (invalid octal number?)", -1); parsePtr->errorType = TCL_PARSE_BAD_NUMBER; errCode = "BADNUMBER"; subErrCode = "OCTAL"; break; default: if (isdigit(UCHAR(start[1]))) { Tcl_AppendToObj(post, " (invalid octal number?)", -1); parsePtr->errorType = TCL_PARSE_BAD_NUMBER; errCode = "BADNUMBER"; subErrCode = "OCTAL"; } break; } } } goto error; } break; case PLUS: case MINUS: if (IsOperator(lastParsed)) { /* * A "+" or "-" coming just after another operator must be * interpreted as a unary operator. */ lexeme |= UNARY; } else { lexeme |= BINARY; } } } /* Uncategorized lexemes */ /* * Handle lexeme based on its category. */ switch (NODE_TYPE & lexeme) { case LEAF: { /* * Each LEAF results in either a literal getting appended to the * litList, or a sequence of Tcl_Tokens representing a Tcl word * getting appended to the parsePtr->tokens. No OpNode is filled * for this lexeme. */ Tcl_Token *tokenPtr; const char *end = start; int wordIndex; int code = TCL_OK; /* * A leaf operand appearing just after something that's not an * operator is a syntax error. */ if (NotOperator(lastParsed)) { msg = Tcl_ObjPrintf("missing operator at %s", mark); errCode = "MISSING"; scanned = 0; insertMark = 1; /* * Free any literal to avoid a memleak. */ if ((lexeme == NUMBER) || (lexeme == BOOL_LIT)) { Tcl_DecrRefCount(literal); } goto error; } switch (lexeme) { case NUMBER: case BOOL_LIT: /* * TODO: Consider using a dict or hash to collapse all * duplicate literals into a single representative value. * (Like what is done with [split $s {}]). * Pro: ~75% memory saving on expressions like * {1+1+1+1+1+.....+1} (Convert "pointer + Tcl_Obj" cost * to "pointer" cost only) * Con: Cost of the dict store/retrieve on every literal in * every expression when expressions like the above tend * to be uncommon. * The memory savings is temporary; Compiling to bytecode * will collapse things as literals are registered * anyway, so the savings applies only to the time * between parsing and compiling. Possibly important due * to high-water mark nature of memory allocation. */ Tcl_ListObjAppendElement(NULL, litList, literal); complete = lastParsed = OT_LITERAL; start += scanned; numBytes -= scanned; continue; default: break; } /* * Remaining LEAF cases may involve filling Tcl_Tokens, so make * room for at least 2 more tokens. */ TclGrowParseTokenArray(parsePtr, 2); wordIndex = parsePtr->numTokens; tokenPtr = parsePtr->tokenPtr + wordIndex; tokenPtr->type = TCL_TOKEN_WORD; tokenPtr->start = start; parsePtr->numTokens++; switch (lexeme) { case QUOTED: code = Tcl_ParseQuotedString(NULL, start, numBytes, parsePtr, 1, &end); scanned = end - start; break; case BRACED: code = Tcl_ParseBraces(NULL, start, numBytes, parsePtr, 1, &end); scanned = end - start; break; case VARIABLE: code = Tcl_ParseVarName(NULL, start, numBytes, parsePtr, 1); /* * Handle the quirk that Tcl_ParseVarName reports a successful * parse even when it gets only a "$" with no variable name. */ tokenPtr = parsePtr->tokenPtr + wordIndex + 1; if (code == TCL_OK && tokenPtr->type != TCL_TOKEN_VARIABLE) { TclNewLiteralStringObj(msg, "invalid character \"$\""); errCode = "BADCHAR"; goto error; } scanned = tokenPtr->size; break; case SCRIPT: { Tcl_Parse *nestedPtr = (Tcl_Parse *) TclStackAlloc(interp, sizeof(Tcl_Parse)); tokenPtr = parsePtr->tokenPtr + parsePtr->numTokens; tokenPtr->type = TCL_TOKEN_COMMAND; tokenPtr->start = start; tokenPtr->numComponents = 0; end = start + numBytes; start++; while (1) { code = Tcl_ParseCommand(interp, start, end - start, 1, nestedPtr); if (code != TCL_OK) { parsePtr->term = nestedPtr->term; parsePtr->errorType = nestedPtr->errorType; parsePtr->incomplete = nestedPtr->incomplete; break; } start = nestedPtr->commandStart + nestedPtr->commandSize; Tcl_FreeParse(nestedPtr); if ((nestedPtr->term < end) && (nestedPtr->term[0] == ']') && !nestedPtr->incomplete) { break; } if (start == end) { TclNewLiteralStringObj(msg, "missing close-bracket"); parsePtr->term = tokenPtr->start; parsePtr->errorType = TCL_PARSE_MISSING_BRACKET; parsePtr->incomplete = 1; code = TCL_ERROR; errCode = "UNBALANCED"; break; } } TclStackFree(interp, nestedPtr); end = start; start = tokenPtr->start; scanned = end - start; tokenPtr->size = scanned; parsePtr->numTokens++; break; } /* SCRIPT case */ } if (code != TCL_OK) { /* * Here we handle all the syntax errors generated by the * Tcl_Token generating parsing routines called in the switch * just above. If the value of parsePtr->incomplete is 1, then * the error was an unbalanced '[', '(', '{', or '"' and * parsePtr->term is pointing to that unbalanced character. If * the value of parsePtr->incomplete is 0, then the error is * one of lacking whitespace following a quoted word, for * example: expr {[an error {foo}bar]}, and parsePtr->term * points to where the whitespace is missing. We reset our * values of start and scanned so that when our error message * is constructed, the location of the syntax error is sure to * appear in it, even if the quoted expression is truncated. */ start = parsePtr->term; scanned = parsePtr->incomplete; if (parsePtr->incomplete) { errCode = "UNBALANCED"; } goto error; } tokenPtr = parsePtr->tokenPtr + wordIndex; tokenPtr->size = scanned; tokenPtr->numComponents = parsePtr->numTokens - wordIndex - 1; if (!parseOnly && ((lexeme == QUOTED) || (lexeme == BRACED))) { /* * When this expression is destined to be compiled, and a * braced or quoted word within an expression is known at * compile time (no runtime substitutions in it), we can store * it as a literal rather than in its tokenized form. This is * an advantage since the compiled bytecode is going to need * the argument in Tcl_Obj form eventually, so it's just as * well to get there now. Another advantage is that with this * conversion, larger constant expressions might be grown and * optimized. * * On the contrary, if the end goal of this parse is to fill a * Tcl_Parse for a caller of Tcl_ParseExpr(), then it's * wasteful to convert to a literal only to convert back again * later. */ TclNewObj(literal); if (TclWordKnownAtCompileTime(tokenPtr, literal)) { Tcl_ListObjAppendElement(NULL, litList, literal); complete = lastParsed = OT_LITERAL; parsePtr->numTokens = wordIndex; break; } Tcl_DecrRefCount(literal); } complete = lastParsed = OT_TOKENS; break; } /* case LEAF */ case UNARY: /* * A unary operator appearing just after something that's not an * operator is a syntax error -- something trying to be the left * operand of an operator that doesn't take one. */ if (NotOperator(lastParsed)) { msg = Tcl_ObjPrintf("missing operator at %s", mark); scanned = 0; insertMark = 1; errCode = "MISSING"; goto error; } /* * Create an OpNode for the unary operator. */ nodePtr->lexeme = lexeme; nodePtr->precedence = prec[lexeme]; nodePtr->mark = MARK_RIGHT; /* * A FUNCTION cannot be a constant expression, because Tcl allows * functions to return variable results with the same arguments; * for example, rand(). Other unary operators can root a constant * expression, so long as the argument is a constant expression. */ nodePtr->constant = (lexeme != FUNCTION); /* * This unary operator is a new incomplete tree, so push it onto * our stack of incomplete trees. Also remember it as the last * lexeme we parsed. */ nodePtr->p.prev = incomplete; incomplete = lastParsed = nodesUsed; nodesUsed++; break; case BINARY: { OpNode *incompletePtr; unsigned char precedence = prec[lexeme]; /* * A binary operator appearing just after another operator is a * syntax error -- one of the two operators is missing an operand. */ if (IsOperator(lastParsed)) { if ((lexeme == CLOSE_PAREN) && (nodePtr[-1].lexeme == OPEN_PAREN)) { if (nodePtr[-2].lexeme == FUNCTION) { /* * Normally, "()" is a syntax error, but as a special * case accept it as an argument list for a function. * Treat this as a special LEAF lexeme, and restart * the parsing loop with zero characters scanned. We * will parse the ")" again the next time through, but * with the OT_EMPTY leaf as the subexpression between * the parens. */ scanned = 0; complete = lastParsed = OT_EMPTY; break; } msg = Tcl_ObjPrintf("empty subexpression at %s", mark); scanned = 0; insertMark = 1; errCode = "EMPTY"; goto error; } if (nodePtr[-1].precedence > precedence) { if (nodePtr[-1].lexeme == OPEN_PAREN) { TclNewLiteralStringObj(msg, "unbalanced open paren"); parsePtr->errorType = TCL_PARSE_MISSING_PAREN; errCode = "UNBALANCED"; } else if (nodePtr[-1].lexeme == COMMA) { msg = Tcl_ObjPrintf( "missing function argument at %s", mark); scanned = 0; insertMark = 1; errCode = "MISSING"; } else if (nodePtr[-1].lexeme == START) { TclNewLiteralStringObj(msg, "empty expression"); errCode = "EMPTY"; } } else if (lexeme == CLOSE_PAREN) { TclNewLiteralStringObj(msg, "unbalanced close paren"); errCode = "UNBALANCED"; } else if ((lexeme == COMMA) && (nodePtr[-1].lexeme == OPEN_PAREN) && (nodePtr[-2].lexeme == FUNCTION)) { msg = Tcl_ObjPrintf("missing function argument at %s", mark); scanned = 0; insertMark = 1; errCode = "UNBALANCED"; } if (msg == NULL) { msg = Tcl_ObjPrintf("missing operand at %s", mark); scanned = 0; insertMark = 1; errCode = "MISSING"; } goto error; } /* * Here is where the tree comes together. At this point, we have a * stack of incomplete trees corresponding to substrings that are * incomplete expressions, followed by a complete tree * corresponding to a substring that is itself a complete * expression, followed by the binary operator we have just * parsed. The incomplete trees can each be completed by adding a * right operand. * * To illustrate with an example, when we parse the expression * "1+2*3-4" and we reach this point having just parsed the "-" * operator, we have these incomplete trees: START, "1+", and * "2*". Next we have the complete subexpression "3". Last is the * "-" we've just parsed. * * The next step is to join our complete tree to an operator. The * choice is governed by the precedence and associativity of the * competing operators. If we connect it as the right operand of * our most recent incomplete tree, we get a new complete tree, * and we can repeat the process. The while loop following repeats * this until precedence indicates it is time to join the complete * tree as the left operand of the just parsed binary operator. * * Continuing the example, the first pass through the loop will * join "3" to "2*"; the next pass will join "2*3" to "1+". Then * we'll exit the loop and join "1+2*3" to "-". When we return to * parse another lexeme, our stack of incomplete trees is START * and "1+2*3-". */ while (1) { incompletePtr = nodes + incomplete; if (incompletePtr->precedence < precedence) { break; } if (incompletePtr->precedence == precedence) { /* * Right association rules for exponentiation. */ if (lexeme == EXPON) { break; } /* * Special association rules for the conditional * operators. The "?" and ":" operators have equal * precedence, but must be linked up in sensible pairs. */ if ((incompletePtr->lexeme == QUESTION) && (NotOperator(complete) || (nodes[complete].lexeme != COLON))) { break; } if ((incompletePtr->lexeme == COLON) && (lexeme == QUESTION)) { break; } } /* * Some special syntax checks... */ /* Parens must balance */ if ((incompletePtr->lexeme == OPEN_PAREN) && (lexeme != CLOSE_PAREN)) { TclNewLiteralStringObj(msg, "unbalanced open paren"); parsePtr->errorType = TCL_PARSE_MISSING_PAREN; errCode = "UNBALANCED"; goto error; } /* Right operand of "?" must be ":" */ if ((incompletePtr->lexeme == QUESTION) && (NotOperator(complete) || (nodes[complete].lexeme != COLON))) { msg = Tcl_ObjPrintf("missing operator \":\" at %s", mark); scanned = 0; insertMark = 1; errCode = "MISSING"; goto error; } /* Operator ":" may only be right operand of "?" */ if (IsOperator(complete) && (nodes[complete].lexeme == COLON) && (incompletePtr->lexeme != QUESTION)) { TclNewLiteralStringObj(msg, "unexpected operator \":\" " "without preceding \"?\""); errCode = "SURPRISE"; goto error; } /* * Attach complete tree as right operand of most recent * incomplete tree. */ incompletePtr->right = complete; if (IsOperator(complete)) { nodes[complete].p.parent = incomplete; incompletePtr->constant = incompletePtr->constant && nodes[complete].constant; } else { incompletePtr->constant = incompletePtr->constant && (complete == OT_LITERAL); } /* * The QUESTION/COLON and FUNCTION/OPEN_PAREN combinations * each make up a single operator. Force them to agree whether * they have a constant expression. */ if ((incompletePtr->lexeme == QUESTION) || (incompletePtr->lexeme == FUNCTION)) { nodes[complete].constant = incompletePtr->constant; } if (incompletePtr->lexeme == START) { /* * Completing the START tree indicates we're done. * Transfer the parse tree to the caller and return. */ *opTreePtr = nodes; return TCL_OK; } /* * With a right operand attached, last incomplete tree has * become the complete tree. Pop it from the incomplete tree * stack. */ complete = incomplete; incomplete = incompletePtr->p.prev; /* CLOSE_PAREN can only close one OPEN_PAREN. */ if (incompletePtr->lexeme == OPEN_PAREN) { break; } } /* * More syntax checks... */ /* Parens must balance. */ if (lexeme == CLOSE_PAREN) { if (incompletePtr->lexeme != OPEN_PAREN) { TclNewLiteralStringObj(msg, "unbalanced close paren"); errCode = "UNBALANCED"; goto error; } } /* Commas must appear only in function argument lists. */ if (lexeme == COMMA) { if ((incompletePtr->lexeme != OPEN_PAREN) || (incompletePtr[-1].lexeme != FUNCTION)) { TclNewLiteralStringObj(msg, "unexpected \",\" outside function argument list"); errCode = "SURPRISE"; goto error; } } /* Operator ":" may only be right operand of "?" */ if (IsOperator(complete) && (nodes[complete].lexeme == COLON)) { TclNewLiteralStringObj(msg, "unexpected operator \":\" without preceding \"?\""); errCode = "SURPRISE"; goto error; } /* * Create no node for a CLOSE_PAREN lexeme. */ if (lexeme == CLOSE_PAREN) { break; } /* * Link complete tree as left operand of new node. */ nodePtr->lexeme = lexeme; nodePtr->precedence = precedence; nodePtr->mark = MARK_LEFT; nodePtr->left = complete; /* * The COMMA operator cannot be optimized, since the function * needs all of its arguments, and optimization would reduce the * number. Other binary operators root constant expressions when * both arguments are constant expressions. */ nodePtr->constant = (lexeme != COMMA); if (IsOperator(complete)) { nodes[complete].p.parent = nodesUsed; nodePtr->constant = nodePtr->constant && nodes[complete].constant; } else { nodePtr->constant = nodePtr->constant && (complete == OT_LITERAL); } /* * With a left operand attached and a right operand missing, the * just-parsed binary operator is root of a new incomplete tree. * Push it onto the stack of incomplete trees. */ nodePtr->p.prev = incomplete; incomplete = lastParsed = nodesUsed; nodesUsed++; break; } /* case BINARY */ } /* lexeme handler */ /* Advance past the just-parsed lexeme */ start += scanned; numBytes -= scanned; } /* main parsing loop */ /* * We only get here if there's been an error. Any errors that didn't get a * suitable parsePtr->errorType, get recorded as syntax errors. */ error: if (parsePtr->errorType == TCL_PARSE_SUCCESS) { parsePtr->errorType = TCL_PARSE_SYNTAX; } /* * Free any partial parse tree we've built. */ if (nodes != NULL) { Tcl_Free(nodes); } if (interp == NULL) { /* * Nowhere to report an error message, so just free it. */ if (msg) { Tcl_DecrRefCount(msg); } } else { /* * Construct the complete error message. Start with the simple error * message, pulled from the interp result if necessary... */ if (msg == NULL) { msg = Tcl_GetObjResult(interp); } /* * Add a detailed quote from the bad expression, displaying and * sometimes marking the precise location of the syntax error. */ Tcl_AppendPrintfToObj(msg, "\nin expression \"%s%.*s%.*s%s%s%.*s%s\"", ((start - limit) < parsePtr->string) ? "" : "...", ((start - limit) < parsePtr->string) ? (int) (start - parsePtr->string) : (int)limit - 3, ((start - limit) < parsePtr->string) ? parsePtr->string : start - limit + 3, (scanned < limit) ? (int)scanned : (int)limit - 3, start, (scanned < limit) ? "" : "...", insertMark ? mark : "", (start + scanned + limit > parsePtr->end) ? (int) (parsePtr->end - start) - (int)scanned : (int)limit-3, start + scanned, (start + scanned + limit > parsePtr->end) ? "" : "..."); /* * Next, append any postscript message. */ if (post != NULL) { Tcl_AppendToObj(msg, ";\n", -1); Tcl_AppendObjToObj(msg, post); Tcl_DecrRefCount(post); } Tcl_SetObjResult(interp, msg); /* * Finally, place context information in the errorInfo. */ numBytes = parsePtr->end - parsePtr->string; Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (parsing expression \"%.*s%s\")", (numBytes < limit) ? (int)numBytes : (int)limit - 3, parsePtr->string, (numBytes < limit) ? "" : "...")); if (errCode) { Tcl_SetErrorCode(interp, "TCL", "PARSE", "EXPR", errCode, subErrCode, (char *)NULL); } } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * ConvertTreeToTokens -- * * Given a string, the numBytes bytes starting at start, and an OpNode * tree and Tcl_Token array created by passing that same string to * ParseExpr(), this function writes into *parsePtr the sequence of * Tcl_Tokens needed so to satisfy the historical interface provided by * Tcl_ParseExpr(). Note that this routine exists only for the sake of * the public Tcl_ParseExpr() routine. It is not used by Tcl itself at * all. * * Results: * None. * * Side effects: * The Tcl_Parse *parsePtr is filled with Tcl_Tokens representing the * parsed expression. * *---------------------------------------------------------------------- */ static void ConvertTreeToTokens( const char *start, Tcl_Size numBytes, OpNode *nodes, Tcl_Token *tokenPtr, Tcl_Parse *parsePtr) { int subExprTokenIdx = 0; OpNode *nodePtr = nodes; int next = nodePtr->right; while (1) { Tcl_Token *subExprTokenPtr; int scanned, parentIdx; unsigned char lexeme; /* * Advance the mark so the next exit from this node won't retrace * steps over ground already covered. */ nodePtr->mark++; /* * Handle next child node or leaf. */ switch (next) { case OT_EMPTY: /* No tokens and no characters for the OT_EMPTY leaf. */ break; case OT_LITERAL: /* * Skip any white space that comes before the literal. */ scanned = TclParseAllWhiteSpace(start, numBytes); start += scanned; numBytes -= scanned; /* * Reparse the literal to get pointers into source string. */ scanned = ParseLexeme(start, numBytes, &lexeme, NULL); TclGrowParseTokenArray(parsePtr, 2); subExprTokenPtr = parsePtr->tokenPtr + parsePtr->numTokens; subExprTokenPtr->type = TCL_TOKEN_SUB_EXPR; subExprTokenPtr->start = start; subExprTokenPtr->size = scanned; subExprTokenPtr->numComponents = 1; subExprTokenPtr[1].type = TCL_TOKEN_TEXT; subExprTokenPtr[1].start = start; subExprTokenPtr[1].size = scanned; subExprTokenPtr[1].numComponents = 0; parsePtr->numTokens += 2; start += scanned; numBytes -= scanned; break; case OT_TOKENS: { /* * tokenPtr points to a token sequence that came from parsing a * Tcl word. A Tcl word is made up of a sequence of one or more * elements. When the word is only a single element, it's been the * historical practice to replace the TCL_TOKEN_WORD token * directly with a TCL_TOKEN_SUB_EXPR token. However, when the * word has multiple elements, a TCL_TOKEN_WORD token is kept as a * grouping device so that TCL_TOKEN_SUB_EXPR always has only one * element. Wise or not, these are the rules the Tcl expr parser * has followed, and for the sake of those few callers of * Tcl_ParseExpr() we do not change them now. Internally, we can * do better. */ int toCopy = tokenPtr->numComponents + 1; if (tokenPtr->numComponents == tokenPtr[1].numComponents + 1) { /* * Single element word. Copy tokens and convert the leading * token to TCL_TOKEN_SUB_EXPR. */ TclGrowParseTokenArray(parsePtr, toCopy); subExprTokenPtr = parsePtr->tokenPtr + parsePtr->numTokens; memcpy(subExprTokenPtr, tokenPtr, toCopy * sizeof(Tcl_Token)); subExprTokenPtr->type = TCL_TOKEN_SUB_EXPR; parsePtr->numTokens += toCopy; } else { /* * Multiple element word. Create a TCL_TOKEN_SUB_EXPR token to * lead, with fields initialized from the leading token, then * copy entire set of word tokens. */ TclGrowParseTokenArray(parsePtr, toCopy+1); subExprTokenPtr = parsePtr->tokenPtr + parsePtr->numTokens; *subExprTokenPtr = *tokenPtr; subExprTokenPtr->type = TCL_TOKEN_SUB_EXPR; subExprTokenPtr->numComponents++; subExprTokenPtr++; memcpy(subExprTokenPtr, tokenPtr, toCopy * sizeof(Tcl_Token)); parsePtr->numTokens += toCopy + 1; } scanned = tokenPtr->start + tokenPtr->size - start; start += scanned; numBytes -= scanned; tokenPtr += toCopy; break; } default: /* * Advance to the child node, which is an operator. */ nodePtr = nodes + next; /* * Skip any white space that comes before the subexpression. */ scanned = TclParseAllWhiteSpace(start, numBytes); start += scanned; numBytes -= scanned; /* * Generate tokens for the operator / subexpression... */ switch (nodePtr->lexeme) { case OPEN_PAREN: case COMMA: case COLON: /* * Historical practice has been to have no Tcl_Tokens for * these operators. */ break; default: { /* * Remember the index of the last subexpression we were * working on -- that of our parent. We'll stack it later. */ parentIdx = subExprTokenIdx; /* * Verify space for the two leading Tcl_Tokens representing * the subexpression rooted by this operator. The first * Tcl_Token will be of type TCL_TOKEN_SUB_EXPR; the second of * type TCL_TOKEN_OPERATOR. */ TclGrowParseTokenArray(parsePtr, 2); subExprTokenIdx = parsePtr->numTokens; subExprTokenPtr = parsePtr->tokenPtr + subExprTokenIdx; parsePtr->numTokens += 2; subExprTokenPtr->type = TCL_TOKEN_SUB_EXPR; subExprTokenPtr[1].type = TCL_TOKEN_OPERATOR; /* * Our current position scanning the string is the starting * point for this subexpression. */ subExprTokenPtr->start = start; /* * Eventually, we know that the numComponents field of the * Tcl_Token of type TCL_TOKEN_OPERATOR will be 0. This means * we can make other use of this field for now to track the * stack of subexpressions we have pending. */ subExprTokenPtr[1].numComponents = parentIdx; break; } } break; } /* Determine which way to exit the node on this pass. */ router: switch (nodePtr->mark) { case MARK_LEFT: next = nodePtr->left; break; case MARK_RIGHT: next = nodePtr->right; /* * Skip any white space that comes before the operator. */ scanned = TclParseAllWhiteSpace(start, numBytes); start += scanned; numBytes -= scanned; /* * Here we scan from the string the operator corresponding to * nodePtr->lexeme. */ scanned = ParseLexeme(start, numBytes, &lexeme, NULL); switch (nodePtr->lexeme) { case OPEN_PAREN: case COMMA: case COLON: /* * No tokens for these lexemes -> nothing to do. */ break; default: /* * Record in the TCL_TOKEN_OPERATOR token the pointers into * the string marking where the operator is. */ subExprTokenPtr = parsePtr->tokenPtr + subExprTokenIdx; subExprTokenPtr[1].start = start; subExprTokenPtr[1].size = scanned; break; } start += scanned; numBytes -= scanned; break; case MARK_PARENT: switch (nodePtr->lexeme) { case START: /* When we get back to the START node, we're done. */ return; case COMMA: case COLON: /* No tokens for these lexemes -> nothing to do. */ break; case OPEN_PAREN: /* * Skip past matching close paren. */ scanned = TclParseAllWhiteSpace(start, numBytes); start += scanned; numBytes -= scanned; scanned = ParseLexeme(start, numBytes, &lexeme, NULL); start += scanned; numBytes -= scanned; break; default: /* * Before we leave this node/operator/subexpression for the * last time, finish up its tokens.... * * Our current position scanning the string is where the * substring for the subexpression ends. */ subExprTokenPtr = parsePtr->tokenPtr + subExprTokenIdx; subExprTokenPtr->size = start - subExprTokenPtr->start; /* * All the Tcl_Tokens allocated and filled belong to * this subexpression. The first token is the leading * TCL_TOKEN_SUB_EXPR token, and all the rest (one fewer) * are its components. */ subExprTokenPtr->numComponents = ((int)parsePtr->numTokens - subExprTokenIdx) - 1; /* * Finally, as we return up the tree to our parent, pop the * parent subexpression off our subexpression stack, and * fill in the zero numComponents for the operator Tcl_Token. */ parentIdx = subExprTokenPtr[1].numComponents; subExprTokenPtr[1].numComponents = 0; subExprTokenIdx = parentIdx; break; } /* * Since we're returning to parent, skip child handling code. */ nodePtr = nodes + nodePtr->p.parent; goto router; } } } /* *---------------------------------------------------------------------- * * Tcl_ParseExpr -- * * Given a string, the numBytes bytes starting at start, this function * parses it as a Tcl expression and stores information about the * structure of the expression in the Tcl_Parse struct indicated by the * caller. * * Results: * If the string is successfully parsed as a valid Tcl expression, TCL_OK * is returned, and data about the expression structure is written to * *parsePtr. If the string cannot be parsed as a valid Tcl expression, * TCL_ERROR is returned, and if interp is non-NULL, an error message is * written to interp. * * Side effects: * If there is insufficient space in parsePtr to hold all the information * about the expression, then additional space is malloc-ed. If the * function returns TCL_OK then the caller must eventually invoke * Tcl_FreeParse to release any additional space that was allocated. * *---------------------------------------------------------------------- */ int Tcl_ParseExpr( Tcl_Interp *interp, /* Used for error reporting. */ const char *start, /* Start of source string to parse. */ Tcl_Size numBytes, /* Number of bytes in string. If -1, the * string consists of all bytes up to the * first null character. */ Tcl_Parse *parsePtr) /* Structure to fill with information about * the parsed expression; any previous * information in the structure is ignored. */ { int code; OpNode *opTree = NULL; /* Will point to the tree of operators. */ Tcl_Obj *litList; /* List to hold the literals. */ Tcl_Obj *funcList; /* List to hold the functon names. */ Tcl_Parse *exprParsePtr = (Tcl_Parse *)TclStackAlloc(interp, sizeof(Tcl_Parse)); /* Holds the Tcl_Tokens of substitutions. */ TclNewObj(litList); TclNewObj(funcList); if (numBytes < 0) { numBytes = (start ? strlen(start) : 0); } code = ParseExpr(interp, start, numBytes, &opTree, litList, funcList, exprParsePtr, 1 /* parseOnly */); Tcl_DecrRefCount(funcList); Tcl_DecrRefCount(litList); TclParseInit(interp, start, numBytes, parsePtr); if (code == TCL_OK) { ConvertTreeToTokens(start, numBytes, opTree, exprParsePtr->tokenPtr, parsePtr); } else { parsePtr->term = exprParsePtr->term; parsePtr->errorType = exprParsePtr->errorType; } Tcl_FreeParse(exprParsePtr); TclStackFree(interp, exprParsePtr); Tcl_Free(opTree); return code; } /* *---------------------------------------------------------------------- * * ParseLexeme -- * * Parse a single lexeme from the start of a string, scanning no more * than numBytes bytes. * * Results: * Returns the number of bytes scanned to produce the lexeme. * * Side effects: * Code identifying lexeme parsed is written to *lexemePtr. * *---------------------------------------------------------------------- */ static Tcl_Size ParseLexeme( const char *start, /* Start of lexeme to parse. */ Tcl_Size numBytes, /* Number of bytes in string. */ unsigned char *lexemePtr, /* Write code of parsed lexeme to this * storage. */ Tcl_Obj **literalPtr) /* Write corresponding literal value to this * storage, if non-NULL. */ { const char *end; int ch; Tcl_Obj *literal = NULL; unsigned char byte; if (numBytes == 0) { *lexemePtr = END; return 0; } byte = UCHAR(*start); if (byte < sizeof(Lexeme) && Lexeme[byte] != 0) { *lexemePtr = Lexeme[byte]; return 1; } switch (byte) { case '#': { /* * Scan forward over the comment contents. */ Tcl_Size size; for (size = 0; byte != '\n' && byte != 0 && size < numBytes; size++) { byte = UCHAR(start[size]); } *lexemePtr = COMMENT; return size - (byte == '\n'); } case '*': if ((numBytes > 1) && (start[1] == '*')) { *lexemePtr = EXPON; return 2; } *lexemePtr = MULT; return 1; case '=': if ((numBytes > 1) && (start[1] == '=')) { *lexemePtr = EQUAL; return 2; } *lexemePtr = INCOMPLETE; return 1; case '!': if ((numBytes > 1) && (start[1] == '=')) { *lexemePtr = NEQ; return 2; } *lexemePtr = NOT; return 1; case '&': if ((numBytes > 1) && (start[1] == '&')) { *lexemePtr = AND; return 2; } *lexemePtr = BIT_AND; return 1; case '|': if ((numBytes > 1) && (start[1] == '|')) { *lexemePtr = OR; return 2; } *lexemePtr = BIT_OR; return 1; case '<': if (numBytes > 1) { switch (start[1]) { case '<': *lexemePtr = LEFT_SHIFT; return 2; case '=': *lexemePtr = LEQ; return 2; } } *lexemePtr = LESS; return 1; case '>': if (numBytes > 1) { switch (start[1]) { case '>': *lexemePtr = RIGHT_SHIFT; return 2; case '=': *lexemePtr = GEQ; return 2; } } *lexemePtr = GREATER; return 1; case 'i': if ((numBytes > 1) && (start[1] == 'n') && ((numBytes == 2) || start[2] & 0x80 || !isalpha(UCHAR(start[2])))) { /* * Must make this check so we can tell the difference between the * "in" operator and the "int" function name and the "infinity" * numeric value. */ *lexemePtr = IN_LIST; return 2; } break; case 'e': if ((numBytes > 1) && (start[1] == 'q') && ((numBytes == 2) || start[2] & 0x80 || !isalpha(UCHAR(start[2])))) { *lexemePtr = STREQ; return 2; } break; case 'n': if ((numBytes > 1) && ((numBytes == 2) || start[2] & 0x80 || !isalpha(UCHAR(start[2])))) { switch (start[1]) { case 'e': *lexemePtr = STRNEQ; return 2; case 'i': *lexemePtr = NOT_IN_LIST; return 2; } } break; case 'l': if ((numBytes > 1) && ((numBytes == 2) || start[2] & 0x80 || !isalpha(UCHAR(start[2])))) { switch (start[1]) { case 't': *lexemePtr = STR_LT; return 2; case 'e': *lexemePtr = STR_LEQ; return 2; } } break; case 'g': if ((numBytes > 1) && ((numBytes == 2) || start[2] & 0x80 || !isalpha(UCHAR(start[2])))) { switch (start[1]) { case 't': *lexemePtr = STR_GT; return 2; case 'e': *lexemePtr = STR_GEQ; return 2; } } break; } TclNewObj(literal); if (TclParseNumber(NULL, literal, NULL, start, numBytes, &end, TCL_PARSE_NO_WHITESPACE) == TCL_OK) { if (end < start + numBytes && !TclIsBareword(*end)) { number: *lexemePtr = NUMBER; if (literalPtr) { TclInitStringRep(literal, start, end-start); *literalPtr = literal; } else { Tcl_DecrRefCount(literal); } return (end-start); } else { unsigned char lexeme; /* * We have a number followed directly by bareword characters * (alpha, digit, underscore). Is this a number followed by * bareword syntax error? Or should we join into one bareword? * Example: Inf + luence + () becomes a valid function call. * [Bug 3401704] */ if (TclHasInternalRep(literal, &tclDoubleType)) { const char *p = start; while (p < end) { if (!TclIsBareword(*p++)) { /* * The number has non-bareword characters, so we * must treat it as a number. */ goto number; } } } ParseLexeme(end, numBytes-(end-start), &lexeme, NULL); if ((NODE_TYPE & lexeme) == BINARY) { /* * The bareword characters following the number take the * form of an operator (eq, ne, in, ni, ...) so we treat * as number + operator. */ goto number; } /* * Otherwise, fall through and parse the whole as a bareword. */ } } /* * We reject leading underscores in bareword. No sensible reason why. * Might be inspired by reserved identifier rules in C, which of course * have no direct relevance here. */ if (!TclIsBareword(*start) || *start == '_') { Tcl_Size scanned; if (Tcl_UtfCharComplete(start, numBytes)) { scanned = TclUtfToUniChar(start, &ch); } else { char utfBytes[8]; memcpy(utfBytes, start, numBytes); utfBytes[numBytes] = '\0'; scanned = TclUtfToUniChar(utfBytes, &ch); } *lexemePtr = INVALID; Tcl_DecrRefCount(literal); return scanned; } end = start; while (numBytes && TclIsBareword(*end)) { end += 1; numBytes -= 1; } *lexemePtr = BAREWORD; if (literalPtr) { Tcl_SetStringObj(literal, start, end-start); *literalPtr = literal; } else { Tcl_DecrRefCount(literal); } return (end-start); } /* *---------------------------------------------------------------------- * * TclCompileExpr -- * * This procedure compiles a string containing a Tcl expression into Tcl * bytecodes. * * Results: * None. * * Side effects: * Adds instructions to envPtr to evaluate the expression at runtime. * *---------------------------------------------------------------------- */ void TclCompileExpr( Tcl_Interp *interp, /* Used for error reporting. */ const char *script, /* The source script to compile. */ Tcl_Size numBytes, /* Number of bytes in script. */ CompileEnv *envPtr, /* Holds resulting instructions. */ int optimize) /* 0 for one-off expressions. */ { OpNode *opTree = NULL; /* Will point to the tree of operators */ Tcl_Obj *litList; /* List to hold the literals */ Tcl_Obj *funcList; /* List to hold the functon names*/ Tcl_Parse *parsePtr = (Tcl_Parse *)TclStackAlloc(interp, sizeof(Tcl_Parse)); /* Holds the Tcl_Tokens of substitutions */ int code; TclNewObj(litList); TclNewObj(funcList); code = ParseExpr(interp, script, numBytes, &opTree, litList, funcList, parsePtr, 0 /* parseOnly */); if (code == TCL_OK) { /* * Valid parse; compile the tree. */ Tcl_Size objc; Tcl_Obj *const *litObjv; Tcl_Obj **funcObjv; /* TIP #280 : Track Lines within the expression */ TclAdvanceLines(&envPtr->line, script, script + TclParseAllWhiteSpace(script, numBytes)); TclListObjGetElements(NULL, litList, &objc, (Tcl_Obj ***)&litObjv); TclListObjGetElements(NULL, funcList, &objc, &funcObjv); CompileExprTree(interp, opTree, 0, &litObjv, funcObjv, parsePtr->tokenPtr, envPtr, optimize); } else { TclCompileSyntaxError(interp, envPtr); } Tcl_FreeParse(parsePtr); TclStackFree(interp, parsePtr); Tcl_DecrRefCount(funcList); Tcl_DecrRefCount(litList); Tcl_Free(opTree); } /* *---------------------------------------------------------------------- * * ExecConstantExprTree -- * Compiles and executes bytecode for the subexpression tree at index * in the nodes array. This subexpression must be constant, made up * of only constant operators (not functions) and literals. * * Results: * A standard Tcl return code and result left in interp. * * Side effects: * Consumes subtree of nodes rooted at index. Advances the pointer * *litObjvPtr. * *---------------------------------------------------------------------- */ static int ExecConstantExprTree( Tcl_Interp *interp, OpNode *nodes, int index, Tcl_Obj *const **litObjvPtr) { CompileEnv *envPtr; ByteCode *byteCodePtr; int code; NRE_callback *rootPtr = TOP_CB(interp); /* * Note we are compiling an expression with literal arguments. This means * there can be no [info frame] calls when we execute the resulting * bytecode, so there's no need to tend to TIP 280 issues. */ envPtr = (CompileEnv *)TclStackAlloc(interp, sizeof(CompileEnv)); TclInitCompileEnv(interp, envPtr, NULL, 0, NULL, 0); CompileExprTree(interp, nodes, index, litObjvPtr, NULL, NULL, envPtr, 0 /* optimize */); TclEmitOpcode(INST_DONE, envPtr); byteCodePtr = TclInitByteCode(envPtr); TclFreeCompileEnv(envPtr); TclStackFree(interp, envPtr); TclNRExecuteByteCode(interp, byteCodePtr); code = TclNRRunCallbacks(interp, TCL_OK, rootPtr); TclReleaseByteCode(byteCodePtr); return code; } /* *---------------------------------------------------------------------- * * CompileExprTree -- * * Compiles and writes to envPtr instructions for the subexpression tree * at index in the nodes array. (*litObjvPtr) must point to the proper * location in a corresponding literals list. Likewise, when non-NULL, * funcObjv and tokenPtr must point into matching arrays of function * names and Tcl_Token's derived from earlier call to ParseExpr(). When * optimize is true, any constant subexpressions will be precomputed. * * Results: * None. * * Side effects: * Adds instructions to envPtr to evaluate the expression at runtime. * Consumes subtree of nodes rooted at index. Advances the pointer * *litObjvPtr. * *---------------------------------------------------------------------- */ static void CompileExprTree( Tcl_Interp *interp, OpNode *nodes, int index, Tcl_Obj *const **litObjvPtr, Tcl_Obj *const *funcObjv, Tcl_Token *tokenPtr, CompileEnv *envPtr, int optimize) { OpNode *nodePtr = nodes + index; OpNode *rootPtr = nodePtr; int numWords = 0; JumpList *jumpPtr = NULL; int convert = 1; while (1) { int next; JumpList *freePtr, *newJump; if (nodePtr->mark == MARK_LEFT) { next = nodePtr->left; if (nodePtr->lexeme == QUESTION) { convert = 1; } } else if (nodePtr->mark == MARK_RIGHT) { next = nodePtr->right; switch (nodePtr->lexeme) { case FUNCTION: { Tcl_DString cmdName; const char *p; Tcl_Size length; Tcl_DStringInit(&cmdName); TclDStringAppendLiteral(&cmdName, "tcl::mathfunc::"); p = TclGetStringFromObj(*funcObjv, &length); funcObjv++; Tcl_DStringAppend(&cmdName, p, length); TclEmitPush(TclRegisterLiteral(envPtr, Tcl_DStringValue(&cmdName), Tcl_DStringLength(&cmdName), LITERAL_CMD_NAME), envPtr); Tcl_DStringFree(&cmdName); /* * Start a count of the number of words in this function * command invocation. In case there's already a count in * progress (nested functions), save it in our unused "left" * field for restoring later. */ nodePtr->left = numWords; numWords = 2; /* Command plus one argument */ break; } case QUESTION: newJump = (JumpList *)TclStackAlloc(interp, sizeof(JumpList)); newJump->next = jumpPtr; jumpPtr = newJump; TclEmitForwardJump(envPtr, TCL_FALSE_JUMP, &jumpPtr->jump); break; case COLON: newJump = (JumpList *)TclStackAlloc(interp, sizeof(JumpList)); newJump->next = jumpPtr; jumpPtr = newJump; TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpPtr->jump); TclAdjustStackDepth(-1, envPtr); if (convert) { jumpPtr->jump.jumpType = TCL_TRUE_JUMP; } convert = 1; break; case AND: case OR: newJump = (JumpList *)TclStackAlloc(interp, sizeof(JumpList)); newJump->next = jumpPtr; jumpPtr = newJump; TclEmitForwardJump(envPtr, (nodePtr->lexeme == AND) ? TCL_FALSE_JUMP : TCL_TRUE_JUMP, &jumpPtr->jump); break; } } else { int pc1, pc2, target; switch (nodePtr->lexeme) { case START: case QUESTION: if (convert && (nodePtr == rootPtr)) { TclEmitOpcode(INST_TRY_CVT_TO_NUMERIC, envPtr); } break; case OPEN_PAREN: /* do nothing */ break; case FUNCTION: /* * Use the numWords count we've kept to invoke the function * command with the correct number of arguments. */ if (numWords < 255) { TclEmitInvoke(envPtr, INST_INVOKE_STK1, numWords); } else { TclEmitInvoke(envPtr, INST_INVOKE_STK4, numWords); } /* * Restore any saved numWords value. */ numWords = nodePtr->left; convert = 1; break; case COMMA: /* * Each comma implies another function argument. */ numWords++; break; case COLON: CLANG_ASSERT(jumpPtr); if (jumpPtr->jump.jumpType == TCL_TRUE_JUMP) { jumpPtr->jump.jumpType = TCL_UNCONDITIONAL_JUMP; convert = 1; } target = jumpPtr->jump.codeOffset + 2; if (TclFixupForwardJumpToHere(envPtr, &jumpPtr->jump, 127)) { target += 3; } freePtr = jumpPtr; jumpPtr = jumpPtr->next; TclStackFree(interp, freePtr); TclFixupForwardJump(envPtr, &jumpPtr->jump, target - jumpPtr->jump.codeOffset, 127); freePtr = jumpPtr; jumpPtr = jumpPtr->next; TclStackFree(interp, freePtr); break; case AND: case OR: CLANG_ASSERT(jumpPtr); pc1 = CurrentOffset(envPtr); TclEmitInstInt1((nodePtr->lexeme == AND) ? INST_JUMP_FALSE1 : INST_JUMP_TRUE1, 0, envPtr); TclEmitPush(TclRegisterLiteral(envPtr, (nodePtr->lexeme == AND) ? "1" : "0", 1, 0), envPtr); pc2 = CurrentOffset(envPtr); TclEmitInstInt1(INST_JUMP1, 0, envPtr); TclAdjustStackDepth(-1, envPtr); TclStoreInt1AtPtr(CurrentOffset(envPtr) - pc1, envPtr->codeStart + pc1 + 1); if (TclFixupForwardJumpToHere(envPtr, &jumpPtr->jump, 127)) { pc2 += 3; } TclEmitPush(TclRegisterLiteral(envPtr, (nodePtr->lexeme == AND) ? "0" : "1", 1, 0), envPtr); TclStoreInt1AtPtr(CurrentOffset(envPtr) - pc2, envPtr->codeStart + pc2 + 1); convert = 0; freePtr = jumpPtr; jumpPtr = jumpPtr->next; TclStackFree(interp, freePtr); break; default: TclEmitOpcode(instruction[nodePtr->lexeme], envPtr); convert = 0; break; } if (nodePtr == rootPtr) { /* We're done */ return; } nodePtr = nodes + nodePtr->p.parent; continue; } nodePtr->mark++; switch (next) { case OT_EMPTY: numWords = 1; /* No arguments, so just the command */ break; case OT_LITERAL: { Tcl_Obj *const *litObjv = *litObjvPtr; Tcl_Obj *literal = *litObjv; if (optimize) { Tcl_Size length; const char *bytes = TclGetStringFromObj(literal, &length); int idx = TclRegisterLiteral(envPtr, bytes, length, 0); Tcl_Obj *objPtr = TclFetchLiteral(envPtr, idx); if ((objPtr->typePtr == NULL) && (literal->typePtr != NULL)) { /* * Would like to do this: * * lePtr->objPtr = literal; * Tcl_IncrRefCount(literal); * Tcl_DecrRefCount(objPtr); * * However, the design of the "global" and "local" * LiteralTable does not permit the value of lePtr->objPtr * to change. So rather than replace lePtr->objPtr, we do * surgery to transfer our desired internalrep into it. */ objPtr->typePtr = literal->typePtr; objPtr->internalRep = literal->internalRep; literal->typePtr = NULL; } TclEmitPush(idx, envPtr); } else { /* * When optimize==0, we know the expression is a one-off and * there's nothing to be gained from sharing literals when * they won't live long, and the copies we have already have * an appropriate internalrep. In this case, skip literal * registration that would enable sharing, and use the routine * that preserves internalreps. */ TclEmitPush(TclAddLiteralObj(envPtr, literal, NULL), envPtr); } (*litObjvPtr)++; break; } case OT_TOKENS: CompileTokens(envPtr, tokenPtr, interp); tokenPtr += tokenPtr->numComponents + 1; break; default: if (optimize && nodes[next].constant) { Tcl_InterpState save = Tcl_SaveInterpState(interp, TCL_OK); if (ExecConstantExprTree(interp, nodes, next, litObjvPtr) == TCL_OK) { int idx; Tcl_Obj *objPtr = Tcl_GetObjResult(interp); /* * Don't generate a string rep, but if we have one * already, then use it to share via the literal table. */ if (TclHasStringRep(objPtr)) { Tcl_Obj *tableValue; Tcl_Size numBytes; const char *bytes = TclGetStringFromObj(objPtr, &numBytes); idx = TclRegisterLiteral(envPtr, bytes, numBytes, 0); tableValue = TclFetchLiteral(envPtr, idx); if ((tableValue->typePtr == NULL) && (objPtr->typePtr != NULL)) { /* * Same internalrep surgery as for OT_LITERAL. */ tableValue->typePtr = objPtr->typePtr; tableValue->internalRep = objPtr->internalRep; objPtr->typePtr = NULL; } } else { idx = TclAddLiteralObj(envPtr, objPtr, NULL); } TclEmitPush(idx, envPtr); } else { TclCompileSyntaxError(interp, envPtr); } Tcl_RestoreInterpState(interp, save); convert = 0; } else { nodePtr = nodes + next; } } } } /* *---------------------------------------------------------------------- * * TclSingleOpCmd -- * * Implements the commands: ~, !, <<, >>, %, !=, ne, in, ni * in the ::tcl::mathop namespace. These commands have no * extension to arbitrary arguments; they accept only exactly one * or exactly two arguments as suitable for the operator. * * Results: * A standard Tcl return code and result left in interp. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclSingleOpCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { TclOpCmdClientData *occdPtr = (TclOpCmdClientData *)clientData; unsigned char lexeme; OpNode nodes[2]; Tcl_Obj *const *litObjv = objv + 1; if (objc != 1 + occdPtr->i.numArgs) { Tcl_WrongNumArgs(interp, 1, objv, occdPtr->expected); return TCL_ERROR; } ParseLexeme(occdPtr->op, strlen(occdPtr->op), &lexeme, NULL); nodes[0].lexeme = START; nodes[0].mark = MARK_RIGHT; nodes[0].right = 1; nodes[1].lexeme = lexeme; if (objc == 2) { nodes[1].mark = MARK_RIGHT; } else { nodes[1].mark = MARK_LEFT; nodes[1].left = OT_LITERAL; } nodes[1].right = OT_LITERAL; nodes[1].p.parent = 0; return ExecConstantExprTree(interp, nodes, 0, &litObjv); } /* *---------------------------------------------------------------------- * * TclSortingOpCmd -- * Implements the commands: * <, <=, >, >=, ==, eq, lt, le, gt, ge * in the ::tcl::mathop namespace. These commands are defined for * arbitrary number of arguments by computing the AND of the base * operator applied to all neighbor argument pairs. * * Results: * A standard Tcl return code and result left in interp. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclSortingOpCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int code = TCL_OK; if (objc < 3) { Tcl_SetObjResult(interp, Tcl_NewBooleanObj(1)); } else { TclOpCmdClientData *occdPtr = (TclOpCmdClientData *)clientData; Tcl_Obj **litObjv = (Tcl_Obj **)TclStackAlloc(interp, 2 * (objc-2) * sizeof(Tcl_Obj *)); OpNode *nodes = (OpNode *)TclStackAlloc(interp, 2 * (objc-2) * sizeof(OpNode)); unsigned char lexeme; int i, lastAnd = 1; Tcl_Obj *const *litObjPtrPtr = litObjv; ParseLexeme(occdPtr->op, strlen(occdPtr->op), &lexeme, NULL); litObjv[0] = objv[1]; nodes[0].lexeme = START; nodes[0].mark = MARK_RIGHT; for (i=2; ii.identity)); return TCL_OK; } ParseLexeme(occdPtr->op, strlen(occdPtr->op), &lexeme, NULL); lexeme |= BINARY; if (objc == 2) { Tcl_Obj *litObjv[2]; OpNode nodes[2]; int decrMe = 0; Tcl_Obj *const *litObjPtrPtr = litObjv; if (lexeme == EXPON) { TclNewIntObj(litObjv[1], occdPtr->i.identity); Tcl_IncrRefCount(litObjv[1]); decrMe = 1; litObjv[0] = objv[1]; nodes[0].lexeme = START; nodes[0].mark = MARK_RIGHT; nodes[0].right = 1; nodes[1].lexeme = lexeme; nodes[1].mark = MARK_LEFT; nodes[1].left = OT_LITERAL; nodes[1].right = OT_LITERAL; nodes[1].p.parent = 0; } else { if (lexeme == DIVIDE) { TclNewDoubleObj(litObjv[0], 1.0); } else { TclNewIntObj(litObjv[0], occdPtr->i.identity); } Tcl_IncrRefCount(litObjv[0]); litObjv[1] = objv[1]; nodes[0].lexeme = START; nodes[0].mark = MARK_RIGHT; nodes[0].right = 1; nodes[1].lexeme = lexeme; nodes[1].mark = MARK_LEFT; nodes[1].left = OT_LITERAL; nodes[1].right = OT_LITERAL; nodes[1].p.parent = 0; } code = ExecConstantExprTree(interp, nodes, 0, &litObjPtrPtr); Tcl_DecrRefCount(litObjv[decrMe]); return code; } else { Tcl_Obj *const *litObjv = objv + 1; OpNode *nodes = (OpNode *)TclStackAlloc(interp, (objc-1) * sizeof(OpNode)); int i, lastOp = OT_LITERAL; nodes[0].lexeme = START; nodes[0].mark = MARK_RIGHT; if (lexeme == EXPON) { for (i=objc-2; i>0; i--) { nodes[i].lexeme = lexeme; nodes[i].mark = MARK_LEFT; nodes[i].left = OT_LITERAL; nodes[i].right = lastOp; if (lastOp >= 0) { nodes[lastOp].p.parent = i; } lastOp = i; } } else { for (i=1; i= 0) { nodes[lastOp].p.parent = i; } nodes[i].right = OT_LITERAL; lastOp = i; } } nodes[0].right = lastOp; nodes[lastOp].p.parent = 0; code = ExecConstantExprTree(interp, nodes, 0, &litObjv); TclStackFree(interp, nodes); return code; } } /* *---------------------------------------------------------------------- * * TclNoIdentOpCmd -- * Implements the commands: -, / * in the ::tcl::mathop namespace. These commands are defined for * arbitrary non-zero number of arguments by repeatedly applying the base * operator with suitable associative rules. When no arguments are * provided, an error is raised. * * Results: * A standard Tcl return code and result left in interp. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclNoIdentOpCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { TclOpCmdClientData *occdPtr = (TclOpCmdClientData *)clientData; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, occdPtr->expected); return TCL_ERROR; } return TclVariadicOpCmd(clientData, interp, objc, objv); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclCompile.c0000644000175000017500000044545714726623136015454 0ustar sergeisergei/* * tclCompile.c -- * * This file contains procedures that compile Tcl commands or parts of * commands (like quoted strings or nested sub-commands) into a sequence * of instructions ("bytecodes"). * * Copyright © 1996-1998 Sun Microsystems, Inc. * Copyright © 2001 Kevin B. Kenny. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" #include /* * Variable that controls whether compilation tracing is enabled and, if so, * what level of tracing is desired: * 0: no compilation tracing * 1: summarize compilation of top level cmds and proc bodies * 2: display all instructions of each ByteCode compiled * This variable is linked to the Tcl variable "tcl_traceCompile". */ #ifdef TCL_COMPILE_DEBUG int tclTraceCompile = 0; static int traceInitialized = 0; #endif /* * A table describing the Tcl bytecode instructions. Entries in this table * must correspond to the instruction opcode definitions in tclCompile.h. The * names "op1" and "op4" refer to an instruction's one or four byte first * operand. Similarly, "stktop" and "stknext" refer to the topmost and next to * topmost stack elements. * * Note that the load, store, and incr instructions do not distinguish local * from global variables; the bytecode interpreter at runtime uses the * existence of a procedure call frame to distinguish these. */ InstructionDesc const tclInstructionTable[] = { /* Name Bytes stackEffect #Opnds Operand types */ {"done", 1, -1, 0, {OPERAND_NONE}}, /* Finish ByteCode execution and return stktop (top stack item) */ {"push1", 2, +1, 1, {OPERAND_LIT1}}, /* Push object at ByteCode objArray[op1] */ {"push4", 5, +1, 1, {OPERAND_LIT4}}, /* Push object at ByteCode objArray[op4] */ {"pop", 1, -1, 0, {OPERAND_NONE}}, /* Pop the topmost stack object */ {"dup", 1, +1, 0, {OPERAND_NONE}}, /* Duplicate the topmost stack object and push the result */ {"strcat", 2, INT_MIN, 1, {OPERAND_UINT1}}, /* Concatenate the top op1 items and push result */ {"invokeStk1", 2, INT_MIN, 1, {OPERAND_UINT1}}, /* Invoke command named objv[0]; = */ {"invokeStk4", 5, INT_MIN, 1, {OPERAND_UINT4}}, /* Invoke command named objv[0]; = */ {"evalStk", 1, 0, 0, {OPERAND_NONE}}, /* Evaluate command in stktop using Tcl_EvalObj. */ {"exprStk", 1, 0, 0, {OPERAND_NONE}}, /* Execute expression in stktop using Tcl_ExprStringObj. */ {"loadScalar1", 2, 1, 1, {OPERAND_LVT1}}, /* Load scalar variable at index op1 <= 255 in call frame */ {"loadScalar4", 5, 1, 1, {OPERAND_LVT4}}, /* Load scalar variable at index op1 >= 256 in call frame */ {"loadScalarStk", 1, 0, 0, {OPERAND_NONE}}, /* Load scalar variable; scalar's name is stktop */ {"loadArray1", 2, 0, 1, {OPERAND_LVT1}}, /* Load array element; array at slot op1<=255, element is stktop */ {"loadArray4", 5, 0, 1, {OPERAND_LVT4}}, /* Load array element; array at slot op1 > 255, element is stktop */ {"loadArrayStk", 1, -1, 0, {OPERAND_NONE}}, /* Load array element; element is stktop, array name is stknext */ {"loadStk", 1, 0, 0, {OPERAND_NONE}}, /* Load general variable; unparsed variable name is stktop */ {"storeScalar1", 2, 0, 1, {OPERAND_LVT1}}, /* Store scalar variable at op1<=255 in frame; value is stktop */ {"storeScalar4", 5, 0, 1, {OPERAND_LVT4}}, /* Store scalar variable at op1 > 255 in frame; value is stktop */ {"storeScalarStk", 1, -1, 0, {OPERAND_NONE}}, /* Store scalar; value is stktop, scalar name is stknext */ {"storeArray1", 2, -1, 1, {OPERAND_LVT1}}, /* Store array element; array at op1<=255, value is top then elem */ {"storeArray4", 5, -1, 1, {OPERAND_LVT4}}, /* Store array element; array at op1>=256, value is top then elem */ {"storeArrayStk", 1, -2, 0, {OPERAND_NONE}}, /* Store array element; value is stktop, then elem, array names */ {"storeStk", 1, -1, 0, {OPERAND_NONE}}, /* Store general variable; value is stktop, then unparsed name */ {"incrScalar1", 2, 0, 1, {OPERAND_LVT1}}, /* Incr scalar at index op1<=255 in frame; incr amount is stktop */ {"incrScalarStk", 1, -1, 0, {OPERAND_NONE}}, /* Incr scalar; incr amount is stktop, scalar's name is stknext */ {"incrArray1", 2, -1, 1, {OPERAND_LVT1}}, /* Incr array elem; arr at slot op1<=255, amount is top then elem */ {"incrArrayStk", 1, -2, 0, {OPERAND_NONE}}, /* Incr array element; amount is top then elem then array names */ {"incrStk", 1, -1, 0, {OPERAND_NONE}}, /* Incr general variable; amount is stktop then unparsed var name */ {"incrScalar1Imm", 3, +1, 2, {OPERAND_LVT1, OPERAND_INT1}}, /* Incr scalar at slot op1 <= 255; amount is 2nd operand byte */ {"incrScalarStkImm", 2, 0, 1, {OPERAND_INT1}}, /* Incr scalar; scalar name is stktop; incr amount is op1 */ {"incrArray1Imm", 3, 0, 2, {OPERAND_LVT1, OPERAND_INT1}}, /* Incr array elem; array at slot op1 <= 255, elem is stktop, * amount is 2nd operand byte */ {"incrArrayStkImm", 2, -1, 1, {OPERAND_INT1}}, /* Incr array element; elem is top then array name, amount is op1 */ {"incrStkImm", 2, 0, 1, {OPERAND_INT1}}, /* Incr general variable; unparsed name is top, amount is op1 */ {"jump1", 2, 0, 1, {OPERAND_OFFSET1}}, /* Jump relative to (pc + op1) */ {"jump4", 5, 0, 1, {OPERAND_OFFSET4}}, /* Jump relative to (pc + op4) */ {"jumpTrue1", 2, -1, 1, {OPERAND_OFFSET1}}, /* Jump relative to (pc + op1) if stktop expr object is true */ {"jumpTrue4", 5, -1, 1, {OPERAND_OFFSET4}}, /* Jump relative to (pc + op4) if stktop expr object is true */ {"jumpFalse1", 2, -1, 1, {OPERAND_OFFSET1}}, /* Jump relative to (pc + op1) if stktop expr object is false */ {"jumpFalse4", 5, -1, 1, {OPERAND_OFFSET4}}, /* Jump relative to (pc + op4) if stktop expr object is false */ {"bitor", 1, -1, 0, {OPERAND_NONE}}, /* Bitwise or: push (stknext | stktop) */ {"bitxor", 1, -1, 0, {OPERAND_NONE}}, /* Bitwise xor push (stknext ^ stktop) */ {"bitand", 1, -1, 0, {OPERAND_NONE}}, /* Bitwise and: push (stknext & stktop) */ {"eq", 1, -1, 0, {OPERAND_NONE}}, /* Equal: push (stknext == stktop) */ {"neq", 1, -1, 0, {OPERAND_NONE}}, /* Not equal: push (stknext != stktop) */ {"lt", 1, -1, 0, {OPERAND_NONE}}, /* Less: push (stknext < stktop) */ {"gt", 1, -1, 0, {OPERAND_NONE}}, /* Greater: push (stknext > stktop) */ {"le", 1, -1, 0, {OPERAND_NONE}}, /* Less or equal: push (stknext <= stktop) */ {"ge", 1, -1, 0, {OPERAND_NONE}}, /* Greater or equal: push (stknext >= stktop) */ {"lshift", 1, -1, 0, {OPERAND_NONE}}, /* Left shift: push (stknext << stktop) */ {"rshift", 1, -1, 0, {OPERAND_NONE}}, /* Right shift: push (stknext >> stktop) */ {"add", 1, -1, 0, {OPERAND_NONE}}, /* Add: push (stknext + stktop) */ {"sub", 1, -1, 0, {OPERAND_NONE}}, /* Sub: push (stkext - stktop) */ {"mult", 1, -1, 0, {OPERAND_NONE}}, /* Multiply: push (stknext * stktop) */ {"div", 1, -1, 0, {OPERAND_NONE}}, /* Divide: push (stknext / stktop) */ {"mod", 1, -1, 0, {OPERAND_NONE}}, /* Mod: push (stknext % stktop) */ {"uplus", 1, 0, 0, {OPERAND_NONE}}, /* Unary plus: push +stktop */ {"uminus", 1, 0, 0, {OPERAND_NONE}}, /* Unary minus: push -stktop */ {"bitnot", 1, 0, 0, {OPERAND_NONE}}, /* Bitwise not: push ~stktop */ {"not", 1, 0, 0, {OPERAND_NONE}}, /* Logical not: push !stktop */ {"tryCvtToNumeric", 1, 0, 0, {OPERAND_NONE}}, /* Try converting stktop to first int then double if possible. */ {"break", 1, 0, 0, {OPERAND_NONE}}, /* Abort closest enclosing loop; if none, return TCL_BREAK code. */ {"continue", 1, 0, 0, {OPERAND_NONE}}, /* Skip to next iteration of closest enclosing loop; if none, return * TCL_CONTINUE code. */ {"beginCatch4", 5, 0, 1, {OPERAND_UINT4}}, /* Record start of catch with the operand's exception index. Push the * current stack depth onto a special catch stack. */ {"endCatch", 1, 0, 0, {OPERAND_NONE}}, /* End of last catch. Pop the bytecode interpreter's catch stack. */ {"pushResult", 1, +1, 0, {OPERAND_NONE}}, /* Push the interpreter's object result onto the stack. */ {"pushReturnCode", 1, +1, 0, {OPERAND_NONE}}, /* Push interpreter's return code (e.g. TCL_OK or TCL_ERROR) as a new * object onto the stack. */ {"streq", 1, -1, 0, {OPERAND_NONE}}, /* Str Equal: push (stknext eq stktop) */ {"strneq", 1, -1, 0, {OPERAND_NONE}}, /* Str !Equal: push (stknext neq stktop) */ {"strcmp", 1, -1, 0, {OPERAND_NONE}}, /* Str Compare: push (stknext cmp stktop) */ {"strlen", 1, 0, 0, {OPERAND_NONE}}, /* Str Length: push (strlen stktop) */ {"strindex", 1, -1, 0, {OPERAND_NONE}}, /* Str Index: push (strindex stknext stktop) */ {"strmatch", 2, -1, 1, {OPERAND_INT1}}, /* Str Match: push (strmatch stknext stktop) opnd == nocase */ {"list", 5, INT_MIN, 1, {OPERAND_UINT4}}, /* List: push (stk1 stk2 ... stktop) */ {"listIndex", 1, -1, 0, {OPERAND_NONE}}, /* List Index: push (listindex stknext stktop) */ {"listLength", 1, 0, 0, {OPERAND_NONE}}, /* List Len: push (listlength stktop) */ {"appendScalar1", 2, 0, 1, {OPERAND_LVT1}}, /* Append scalar variable at op1<=255 in frame; value is stktop */ {"appendScalar4", 5, 0, 1, {OPERAND_LVT4}}, /* Append scalar variable at op1 > 255 in frame; value is stktop */ {"appendArray1", 2, -1, 1, {OPERAND_LVT1}}, /* Append array element; array at op1<=255, value is top then elem */ {"appendArray4", 5, -1, 1, {OPERAND_LVT4}}, /* Append array element; array at op1>=256, value is top then elem */ {"appendArrayStk", 1, -2, 0, {OPERAND_NONE}}, /* Append array element; value is stktop, then elem, array names */ {"appendStk", 1, -1, 0, {OPERAND_NONE}}, /* Append general variable; value is stktop, then unparsed name */ {"lappendScalar1", 2, 0, 1, {OPERAND_LVT1}}, /* Lappend scalar variable at op1<=255 in frame; value is stktop */ {"lappendScalar4", 5, 0, 1, {OPERAND_LVT4}}, /* Lappend scalar variable at op1 > 255 in frame; value is stktop */ {"lappendArray1", 2, -1, 1, {OPERAND_LVT1}}, /* Lappend array element; array at op1<=255, value is top then elem */ {"lappendArray4", 5, -1, 1, {OPERAND_LVT4}}, /* Lappend array element; array at op1>=256, value is top then elem */ {"lappendArrayStk", 1, -2, 0, {OPERAND_NONE}}, /* Lappend array element; value is stktop, then elem, array names */ {"lappendStk", 1, -1, 0, {OPERAND_NONE}}, /* Lappend general variable; value is stktop, then unparsed name */ {"lindexMulti", 5, INT_MIN, 1, {OPERAND_UINT4}}, /* Lindex with generalized args, operand is number of stacked objs * used: (operand-1) entries from stktop are the indices; then list to * process. */ {"over", 5, +1, 1, {OPERAND_UINT4}}, /* Duplicate the arg-th element from top of stack (TOS=0) */ {"lsetList", 1, -2, 0, {OPERAND_NONE}}, /* Four-arg version of 'lset'. stktop is old value; next is new * element value, next is the index list; pushes new value */ {"lsetFlat", 5, INT_MIN, 1, {OPERAND_UINT4}}, /* Three- or >=5-arg version of 'lset', operand is number of stacked * objs: stktop is old value, next is new element value, next come * (operand-2) indices; pushes the new value. */ {"returnImm", 9, -1, 2, {OPERAND_INT4, OPERAND_UINT4}}, /* Compiled [return], code, level are operands; options and result * are on the stack. */ {"expon", 1, -1, 0, {OPERAND_NONE}}, /* Binary exponentiation operator: push (stknext ** stktop) */ /* * NOTE: the stack effects of expandStkTop and invokeExpanded are wrong - * but it cannot be done right at compile time, the stack effect is only * known at run time. The value for invokeExpanded is estimated better at * compile time. * See the comments further down in this file, where INST_INVOKE_EXPANDED * is emitted. */ {"expandStart", 1, 0, 0, {OPERAND_NONE}}, /* Start of command with {*} (expanded) arguments */ {"expandStkTop", 5, 0, 1, {OPERAND_UINT4}}, /* Expand the list at stacktop: push its elements on the stack */ {"invokeExpanded", 1, 0, 0, {OPERAND_NONE}}, /* Invoke the command marked by the last 'expandStart' */ {"listIndexImm", 5, 0, 1, {OPERAND_IDX4}}, /* List Index: push (lindex stktop op4) */ {"listRangeImm", 9, 0, 2, {OPERAND_IDX4, OPERAND_IDX4}}, /* List Range: push (lrange stktop op4 op4) */ {"startCommand", 9, 0, 2, {OPERAND_OFFSET4, OPERAND_UINT4}}, /* Start of bytecoded command: op is the length of the cmd's code, op2 * is number of commands here */ {"listIn", 1, -1, 0, {OPERAND_NONE}}, /* List containment: push [lsearch stktop stknext]>=0) */ {"listNotIn", 1, -1, 0, {OPERAND_NONE}}, /* List negated containment: push [lsearch stktop stknext]<0) */ {"pushReturnOpts", 1, +1, 0, {OPERAND_NONE}}, /* Push the interpreter's return option dictionary as an object on the * stack. */ {"returnStk", 1, -1, 0, {OPERAND_NONE}}, /* Compiled [return]; options and result are on the stack, code and * level are in the options. */ {"dictGet", 5, INT_MIN, 1, {OPERAND_UINT4}}, /* The top op4 words (min 1) are a key path into the dictionary just * below the keys on the stack, and all those values are replaced by * the value read out of that key-path (like [dict get]). * Stack: ... dict key1 ... keyN => ... value */ {"dictSet", 9, INT_MIN, 2, {OPERAND_UINT4, OPERAND_LVT4}}, /* Update a dictionary value such that the keys are a path pointing to * the value. op4#1 = numKeys, op4#2 = LVTindex * Stack: ... key1 ... keyN value => ... newDict */ {"dictUnset", 9, INT_MIN, 2, {OPERAND_UINT4, OPERAND_LVT4}}, /* Update a dictionary value such that the keys are not a path pointing * to any value. op4#1 = numKeys, op4#2 = LVTindex * Stack: ... key1 ... keyN => ... newDict */ {"dictIncrImm", 9, 0, 2, {OPERAND_INT4, OPERAND_LVT4}}, /* Update a dictionary value such that the value pointed to by key is * incremented by some value (or set to it if the key isn't in the * dictionary at all). op4#1 = incrAmount, op4#2 = LVTindex * Stack: ... key => ... newDict */ {"dictAppend", 5, -1, 1, {OPERAND_LVT4}}, /* Update a dictionary value such that the value pointed to by key has * some value string-concatenated onto it. op4 = LVTindex * Stack: ... key valueToAppend => ... newDict */ {"dictLappend", 5, -1, 1, {OPERAND_LVT4}}, /* Update a dictionary value such that the value pointed to by key has * some value list-appended onto it. op4 = LVTindex * Stack: ... key valueToAppend => ... newDict */ {"dictFirst", 5, +2, 1, {OPERAND_LVT4}}, /* Begin iterating over the dictionary, using the local scalar * indicated by op4 to hold the iterator state. The local scalar * should not refer to a named variable as the value is not wholly * managed correctly. * Stack: ... dict => ... value key doneBool */ {"dictNext", 5, +3, 1, {OPERAND_LVT4}}, /* Get the next iteration from the iterator in op4's local scalar. * Stack: ... => ... value key doneBool */ {"dictUpdateStart", 9, 0, 2, {OPERAND_LVT4, OPERAND_AUX4}}, /* Create the variables (described in the aux data referred to by the * second immediate argument) to mirror the state of the dictionary in * the variable referred to by the first immediate argument. The list * of keys (top of the stack, not popped) must be the same length as * the list of variables. * Stack: ... keyList => ... keyList */ {"dictUpdateEnd", 9, -1, 2, {OPERAND_LVT4, OPERAND_AUX4}}, /* Reflect the state of local variables (described in the aux data * referred to by the second immediate argument) back to the state of * the dictionary in the variable referred to by the first immediate * argument. The list of keys (popped from the stack) must be the same * length as the list of variables. * Stack: ... keyList => ... */ {"jumpTable", 5, -1, 1, {OPERAND_AUX4}}, /* Jump according to the jump-table (in AuxData as indicated by the * operand) and the argument popped from the list. Always executes the * next instruction if no match against the table's entries was found. * Stack: ... value => ... * Note that the jump table contains offsets relative to the PC when * it points to this instruction; the code is relocatable. */ {"upvar", 5, -1, 1, {OPERAND_LVT4}}, /* finds level and otherName in stack, links to local variable at * index op1. Leaves the level on stack. */ {"nsupvar", 5, -1, 1, {OPERAND_LVT4}}, /* finds namespace and otherName in stack, links to local variable at * index op1. Leaves the namespace on stack. */ {"variable", 5, -1, 1, {OPERAND_LVT4}}, /* finds namespace and otherName in stack, links to local variable at * index op1. Leaves the namespace on stack. */ {"syntax", 9, -1, 2, {OPERAND_INT4, OPERAND_UINT4}}, /* Compiled bytecodes to signal syntax error. Equivalent to returnImm * except for the ERR_ALREADY_LOGGED flag in the interpreter. */ {"reverse", 5, 0, 1, {OPERAND_UINT4}}, /* Reverse the order of the arg elements at the top of stack */ {"regexp", 2, -1, 1, {OPERAND_INT1}}, /* Regexp: push (regexp stknext stktop) opnd == nocase */ {"existScalar", 5, 1, 1, {OPERAND_LVT4}}, /* Test if scalar variable at index op1 in call frame exists */ {"existArray", 5, 0, 1, {OPERAND_LVT4}}, /* Test if array element exists; array at slot op1, element is * stktop */ {"existArrayStk", 1, -1, 0, {OPERAND_NONE}}, /* Test if array element exists; element is stktop, array name is * stknext */ {"existStk", 1, 0, 0, {OPERAND_NONE}}, /* Test if general variable exists; unparsed variable name is stktop*/ {"nop", 1, 0, 0, {OPERAND_NONE}}, /* Do nothing */ {"returnCodeBranch", 1, -1, 0, {OPERAND_NONE}}, /* Jump to next instruction based on the return code on top of stack * ERROR: +1; RETURN: +3; BREAK: +5; CONTINUE: +7; * Other non-OK: +9 */ {"unsetScalar", 6, 0, 2, {OPERAND_UINT1, OPERAND_LVT4}}, /* Make scalar variable at index op2 in call frame cease to exist; * op1 is 1 for errors on problems, 0 otherwise */ {"unsetArray", 6, -1, 2, {OPERAND_UINT1, OPERAND_LVT4}}, /* Make array element cease to exist; array at slot op2, element is * stktop; op1 is 1 for errors on problems, 0 otherwise */ {"unsetArrayStk", 2, -2, 1, {OPERAND_UINT1}}, /* Make array element cease to exist; element is stktop, array name is * stknext; op1 is 1 for errors on problems, 0 otherwise */ {"unsetStk", 2, -1, 1, {OPERAND_UINT1}}, /* Make general variable cease to exist; unparsed variable name is * stktop; op1 is 1 for errors on problems, 0 otherwise */ {"dictExpand", 1, -1, 0, {OPERAND_NONE}}, /* Probe into a dict and extract it (or a subdict of it) into * variables with matched names. Produces list of keys bound as * result. Part of [dict with]. * Stack: ... dict path => ... keyList */ {"dictRecombineStk", 1, -3, 0, {OPERAND_NONE}}, /* Map variable contents back into a dictionary in a variable. Part of * [dict with]. * Stack: ... dictVarName path keyList => ... */ {"dictRecombineImm", 5, -2, 1, {OPERAND_LVT4}}, /* Map variable contents back into a dictionary in the local variable * indicated by the LVT index. Part of [dict with]. * Stack: ... path keyList => ... */ {"dictExists", 5, INT_MIN, 1, {OPERAND_UINT4}}, /* The top op4 words (min 1) are a key path into the dictionary just * below the keys on the stack, and all those values are replaced by a * boolean indicating whether it is possible to read out a value from * that key-path (like [dict exists]). * Stack: ... dict key1 ... keyN => ... boolean */ {"verifyDict", 1, -1, 0, {OPERAND_NONE}}, /* Verifies that the word on the top of the stack is a dictionary, * popping it if it is and throwing an error if it is not. * Stack: ... value => ... */ {"strmap", 1, -2, 0, {OPERAND_NONE}}, /* Simplified version of [string map] that only applies one change * string, and only case-sensitively. * Stack: ... from to string => ... changedString */ {"strfind", 1, -1, 0, {OPERAND_NONE}}, /* Find the first index of a needle string in a haystack string, * producing the index (integer) or -1 if nothing found. * Stack: ... needle haystack => ... index */ {"strrfind", 1, -1, 0, {OPERAND_NONE}}, /* Find the last index of a needle string in a haystack string, * producing the index (integer) or -1 if nothing found. * Stack: ... needle haystack => ... index */ {"strrangeImm", 9, 0, 2, {OPERAND_IDX4, OPERAND_IDX4}}, /* String Range: push (string range stktop op4 op4) */ {"strrange", 1, -2, 0, {OPERAND_NONE}}, /* String Range with non-constant arguments. * Stack: ... string idxA idxB => ... substring */ {"yield", 1, 0, 0, {OPERAND_NONE}}, /* Makes the current coroutine yield the value at the top of the * stack, and places the response back on top of the stack when it * resumes. * Stack: ... valueToYield => ... resumeValue */ {"coroName", 1, +1, 0, {OPERAND_NONE}}, /* Push the name of the interpreter's current coroutine as an object * on the stack. */ {"tailcall", 2, INT_MIN, 1, {OPERAND_UINT1}}, /* Do a tailcall with the opnd items on the stack as the thing to * tailcall to; opnd must be greater than 0 for the semantics to work * right. */ {"currentNamespace", 1, +1, 0, {OPERAND_NONE}}, /* Push the name of the interpreter's current namespace as an object * on the stack. */ {"infoLevelNumber", 1, +1, 0, {OPERAND_NONE}}, /* Push the stack depth (i.e., [info level]) of the interpreter as an * object on the stack. */ {"infoLevelArgs", 1, 0, 0, {OPERAND_NONE}}, /* Push the argument words to a stack depth (i.e., [info level ]) * of the interpreter as an object on the stack. * Stack: ... depth => ... argList */ {"resolveCmd", 1, 0, 0, {OPERAND_NONE}}, /* Resolves the command named on the top of the stack to its fully * qualified version, or produces the empty string if no such command * exists. Never generates errors. * Stack: ... cmdName => ... fullCmdName */ {"tclooSelf", 1, +1, 0, {OPERAND_NONE}}, /* Push the identity of the current TclOO object (i.e., the name of * its current public access command) on the stack. */ {"tclooClass", 1, 0, 0, {OPERAND_NONE}}, /* Push the class of the TclOO object named at the top of the stack * onto the stack. * Stack: ... object => ... class */ {"tclooNamespace", 1, 0, 0, {OPERAND_NONE}}, /* Push the namespace of the TclOO object named at the top of the * stack onto the stack. * Stack: ... object => ... namespace */ {"tclooIsObject", 1, 0, 0, {OPERAND_NONE}}, /* Push whether the value named at the top of the stack is a TclOO * object (i.e., a boolean). Can corrupt the interpreter result * despite not throwing, so not safe for use in a post-exception * context. * Stack: ... value => ... boolean */ {"arrayExistsStk", 1, 0, 0, {OPERAND_NONE}}, /* Looks up the element on the top of the stack and tests whether it * is an array. Pushes a boolean describing whether this is the * case. Also runs the whole-array trace on the named variable, so can * throw anything. * Stack: ... varName => ... boolean */ {"arrayExistsImm", 5, +1, 1, {OPERAND_LVT4}}, /* Looks up the variable indexed by opnd and tests whether it is an * array. Pushes a boolean describing whether this is the case. Also * runs the whole-array trace on the named variable, so can throw * anything. * Stack: ... => ... boolean */ {"arrayMakeStk", 1, -1, 0, {OPERAND_NONE}}, /* Forces the element on the top of the stack to be the name of an * array. * Stack: ... varName => ... */ {"arrayMakeImm", 5, 0, 1, {OPERAND_LVT4}}, /* Forces the variable indexed by opnd to be an array. Does not touch * the stack. */ {"invokeReplace", 6, INT_MIN, 2, {OPERAND_UINT4,OPERAND_UINT1}}, /* Invoke command named objv[0], replacing the first two words with * the word at the top of the stack; * = */ {"listConcat", 1, -1, 0, {OPERAND_NONE}}, /* Concatenates the two lists at the top of the stack into a single * list and pushes that resulting list onto the stack. * Stack: ... list1 list2 => ... [lconcat list1 list2] */ {"expandDrop", 1, 0, 0, {OPERAND_NONE}}, /* Drops an element from the auxiliary stack, popping stack elements * until the matching stack depth is reached. */ /* New foreach implementation */ {"foreach_start", 5, +2, 1, {OPERAND_AUX4}}, /* Initialize execution of a foreach loop. Operand is aux data index * of the ForeachInfo structure for the foreach command. It pushes 2 * elements which hold runtime params for foreach_step, they are later * dropped by foreach_end together with the value lists. NOTE that the * iterator-tracker and info reference must not be passed to bytecodes * that handle normal Tcl values. NOTE that this instruction jumps to * the foreach_step instruction paired with it; the stack info below * is only nominal. * Stack: ... listObjs... => ... listObjs... iterTracker info */ {"foreach_step", 1, 0, 0, {OPERAND_NONE}}, /* "Step" or begin next iteration of foreach loop. Assigns to foreach * iteration variables. May jump to straight after the foreach_start * that pushed the iterTracker and info values. MUST be followed * immediately by a foreach_end. * Stack: ... listObjs... iterTracker info => * ... listObjs... iterTracker info */ {"foreach_end", 1, 0, 0, {OPERAND_NONE}}, /* Clean up a foreach loop by dropping the info value, the tracker * value and the lists that were being iterated over. * Stack: ... listObjs... iterTracker info => ... */ {"lmap_collect", 1, -1, 0, {OPERAND_NONE}}, /* Appends the value at the top of the stack to the list located on * the stack the "other side" of the foreach-related values. * Stack: ... collector listObjs... iterTracker info value => * ... collector listObjs... iterTracker info */ {"strtrim", 1, -1, 0, {OPERAND_NONE}}, /* [string trim] core: removes the characters (designated by the value * at the top of the stack) from both ends of the string and pushes * the resulting string. * Stack: ... string charset => ... trimmedString */ {"strtrimLeft", 1, -1, 0, {OPERAND_NONE}}, /* [string trimleft] core: removes the characters (designated by the * value at the top of the stack) from the left of the string and * pushes the resulting string. * Stack: ... string charset => ... trimmedString */ {"strtrimRight", 1, -1, 0, {OPERAND_NONE}}, /* [string trimright] core: removes the characters (designated by the * value at the top of the stack) from the right of the string and * pushes the resulting string. * Stack: ... string charset => ... trimmedString */ {"concatStk", 5, INT_MIN, 1, {OPERAND_UINT4}}, /* Wrapper round Tcl_ConcatObj(), used for [concat] and [eval]. opnd * is number of values to concatenate. * Operation: push concat(stk1 stk2 ... stktop) */ {"strcaseUpper", 1, 0, 0, {OPERAND_NONE}}, /* [string toupper] core: converts whole string to upper case using * the default (extended "C" locale) rules. * Stack: ... string => ... newString */ {"strcaseLower", 1, 0, 0, {OPERAND_NONE}}, /* [string tolower] core: converts whole string to upper case using * the default (extended "C" locale) rules. * Stack: ... string => ... newString */ {"strcaseTitle", 1, 0, 0, {OPERAND_NONE}}, /* [string totitle] core: converts whole string to upper case using * the default (extended "C" locale) rules. * Stack: ... string => ... newString */ {"strreplace", 1, -3, 0, {OPERAND_NONE}}, /* [string replace] core: replaces a non-empty range of one string * with the contents of another. * Stack: ... string fromIdx toIdx replacement => ... newString */ {"originCmd", 1, 0, 0, {OPERAND_NONE}}, /* Reports which command was the origin (via namespace import chain) * of the command named on the top of the stack. * Stack: ... cmdName => ... fullOriginalCmdName */ {"tclooNext", 2, INT_MIN, 1, {OPERAND_UINT1}}, /* Call the next item on the TclOO call chain, passing opnd arguments * (min 1, max 255, *includes* "next"). The result of the invoked * method implementation will be pushed on the stack in place of the * arguments (similar to invokeStk). * Stack: ... "next" arg2 arg3 -- argN => ... result */ {"tclooNextClass", 2, INT_MIN, 1, {OPERAND_UINT1}}, /* Call the following item on the TclOO call chain defined by class * className, passing opnd arguments (min 2, max 255, *includes* * "nextto" and the class name). The result of the invoked method * implementation will be pushed on the stack in place of the * arguments (similar to invokeStk). * Stack: ... "nextto" className arg3 arg4 -- argN => ... result */ {"yieldToInvoke", 1, 0, 0, {OPERAND_NONE}}, /* Makes the current coroutine yield the value at the top of the * stack, invoking the given command/args with resolution in the given * namespace (all packed into a list), and places the list of values * that are the response back on top of the stack when it resumes. * Stack: ... [list ns cmd arg1 ... argN] => ... resumeList */ {"numericType", 1, 0, 0, {OPERAND_NONE}}, /* Pushes the numeric type code of the word at the top of the stack. * Stack: ... value => ... typeCode */ {"tryCvtToBoolean", 1, +1, 0, {OPERAND_NONE}}, /* Try converting stktop to boolean if possible. No errors. * Stack: ... value => ... value isStrictBool */ {"strclass", 2, 0, 1, {OPERAND_SCLS1}}, /* See if all the characters of the given string are a member of the * specified (by opnd) character class. Note that an empty string will * satisfy the class check (standard definition of "all"). * Stack: ... stringValue => ... boolean */ {"lappendList", 5, 0, 1, {OPERAND_LVT4}}, /* Lappend list to scalar variable at op4 in frame. * Stack: ... list => ... listVarContents */ {"lappendListArray", 5, -1, 1, {OPERAND_LVT4}}, /* Lappend list to array element; array at op4. * Stack: ... elem list => ... listVarContents */ {"lappendListArrayStk", 1, -2, 0, {OPERAND_NONE}}, /* Lappend list to array element. * Stack: ... arrayName elem list => ... listVarContents */ {"lappendListStk", 1, -1, 0, {OPERAND_NONE}}, /* Lappend list to general variable. * Stack: ... varName list => ... listVarContents */ {"clockRead", 2, +1, 1, {OPERAND_UINT1}}, /* Read clock out to the stack. Operand is which clock to read * 0=clicks, 1=microseconds, 2=milliseconds, 3=seconds. * Stack: ... => ... time */ {"dictGetDef", 5, INT_MIN, 1, {OPERAND_UINT4}}, /* The top word is the default, the next op4 words (min 1) are a key * path into the dictionary just below the keys on the stack, and all * those values are replaced by the value read out of that key-path * (like [dict get]) except if there is no such key, when instead the * default is pushed instead. * Stack: ... dict key1 ... keyN default => ... value */ {"strlt", 1, -1, 0, {OPERAND_NONE}}, /* String Less: push (stknext < stktop) */ {"strgt", 1, -1, 0, {OPERAND_NONE}}, /* String Greater: push (stknext > stktop) */ {"strle", 1, -1, 0, {OPERAND_NONE}}, /* String Less or equal: push (stknext <= stktop) */ {"strge", 1, -1, 0, {OPERAND_NONE}}, /* String Greater or equal: push (stknext >= stktop) */ {"lreplace4", 6, INT_MIN, 2, {OPERAND_UINT4, OPERAND_UINT1}}, /* Operands: number of arguments, flags * flags: Combination of TCL_LREPLACE4_* flags * Stack: ... listobj index1 ?index2? new1 ... newN => ... newlistobj * where index2 is present only if TCL_LREPLACE_SINGLE_INDEX is not * set in flags. */ {"constImm", 5, -1, 1, {OPERAND_LVT4}}, /* Create constant. Index into LVT is immediate, value is on stack. * Stack: ... value => ... */ {"constStk", 1, -2, 0, {OPERAND_NONE}}, /* Create constant. Variable name and value on stack. * Stack: ... varName value => ... */ {NULL, 0, 0, 0, {OPERAND_NONE}} }; /* * Prototypes for procedures defined later in this file: */ static void CleanupByteCode(ByteCode *codePtr); static ByteCode * CompileSubstObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); static void DupByteCodeInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr); static unsigned char * EncodeCmdLocMap(CompileEnv *envPtr, ByteCode *codePtr, unsigned char *startPtr); static void EnterCmdExtentData(CompileEnv *envPtr, Tcl_Size cmdNumber, Tcl_Size numSrcBytes, Tcl_Size numCodeBytes); static void EnterCmdStartData(CompileEnv *envPtr, Tcl_Size cmdNumber, Tcl_Size srcOffset, Tcl_Size codeOffset); static void FreeByteCodeInternalRep(Tcl_Obj *objPtr); static void FreeSubstCodeInternalRep(Tcl_Obj *objPtr); static int GetCmdLocEncodingSize(CompileEnv *envPtr); static int IsCompactibleCompileEnv(CompileEnv *envPtr); static void PreventCycle(Tcl_Obj *objPtr, CompileEnv *envPtr); #ifdef TCL_COMPILE_STATS static void RecordByteCodeStats(ByteCode *codePtr); #endif /* TCL_COMPILE_STATS */ static int SetByteCodeFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); static void StartExpanding(CompileEnv *envPtr); /* * TIP #280: Helper for building the per-word line information of all compiled * commands. */ static void EnterCmdWordData(ExtCmdLoc *eclPtr, Tcl_Size srcOffset, Tcl_Token *tokenPtr, const char *cmd, Tcl_Size numWords, Tcl_Size line, Tcl_Size *clNext, Tcl_Size **lines, CompileEnv *envPtr); static void ReleaseCmdWordData(ExtCmdLoc *eclPtr); /* * tclByteCodeType provides the standard type management procedures for the * bytecode type. */ const Tcl_ObjType tclByteCodeType = { "bytecode", /* name */ FreeByteCodeInternalRep, /* freeIntRepProc */ DupByteCodeInternalRep, /* dupIntRepProc */ NULL, /* updateStringProc */ SetByteCodeFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; /* * substCodeType provides the standard type management procedures for the * substcode type, which represents substitution within a Tcl value. */ static const Tcl_ObjType substCodeType = { "substcode", /* name */ FreeSubstCodeInternalRep, /* freeIntRepProc */ DupByteCodeInternalRep, /* dupIntRepProc - shared with bytecode */ NULL, /* updateStringProc */ NULL, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; #define SubstFlags(objPtr) (objPtr)->internalRep.twoPtrValue.ptr2 /* * Helper macros. */ #define TclIncrUInt4AtPtr(ptr, delta) \ TclStoreInt4AtPtr(TclGetUInt4AtPtr(ptr)+(delta), (ptr)) /* *---------------------------------------------------------------------- * * TclSetByteCodeFromAny -- * * Part of the bytecode Tcl object type implementation. Attempts to * compile the string representation of the objPtr into bytecode. Accepts * a hook routine that is invoked to perform any needed post-processing on * the compilation results before generating byte codes. interp is the * compilation context and may not be NULL. * * Results: * A standard Tcl object result. If an error occurs during compilation, an * error message is left in the interpreter's result. * * Side effects: * Frees the old internal representation. If no error occurs, then the * compiled code is stored as "objPtr"s bytecode representation. Also, if * debugging, initializes the "tcl_traceCompile" Tcl variable used to * trace compilations. * *---------------------------------------------------------------------- */ int TclSetByteCodeFromAny( Tcl_Interp *interp, /* The interpreter for which the code is being * compiled. Must not be NULL. */ Tcl_Obj *objPtr, /* The object to make a ByteCode object. */ CompileHookProc *hookProc, /* Procedure to invoke after compilation. */ void *clientData) /* Hook procedure private data. */ { Interp *iPtr = (Interp *) interp; CompileEnv compEnv; /* Compilation environment structure allocated * in frame. */ Tcl_Size length; int result = TCL_OK; const char *stringPtr; Proc *procPtr = iPtr->compiledProcPtr; ContLineLoc *clLocPtr; #ifdef TCL_COMPILE_DEBUG if (!traceInitialized) { if (Tcl_LinkVar(interp, "tcl_traceCompile", &tclTraceCompile, TCL_LINK_INT) != TCL_OK) { Tcl_Panic("SetByteCodeFromAny: unable to create link for tcl_traceCompile variable"); } traceInitialized = 1; } #endif stringPtr = TclGetStringFromObj(objPtr, &length); /* * TIP #280: Pick up the CmdFrame in which the BC compiler was invoked, and * use to initialize the tracking in the compiler. This information was * stored by TclCompEvalObj and ProcCompileProc. */ TclInitCompileEnv(interp, &compEnv, stringPtr, length, iPtr->invokeCmdFramePtr, iPtr->invokeWord); /* * Make available to the compilation environment any data about invisible * continuation lines for the script. * * It is not clear if the script Tcl_Obj* can be free'd while the compiler * is using it, leading to the release of the associated ContLineLoc * structure as well. To ensure that the latter doesn't happen set a lock * on it, which is released in TclFreeCompileEnv(). The "lineCLPtr" * hashtable tclObj.c. */ clLocPtr = TclContinuationsGet(objPtr); if (clLocPtr) { compEnv.clNext = &clLocPtr->loc[0]; } TclCompileScript(interp, stringPtr, length, &compEnv); /* * Compilation succeeded. Add a "done" instruction at the end. */ TclEmitOpcode(INST_DONE, &compEnv); /* * Check for optimizations! * * If the generated code is free of most hazards, recompile with generation * of INST_START_CMD disabled to produce code that more compact in many * cases, and also sometimes more performant. */ if (Tcl_GetParent(interp) == NULL && !Tcl_LimitTypeEnabled(interp, TCL_LIMIT_COMMANDS|TCL_LIMIT_TIME) && IsCompactibleCompileEnv(&compEnv)) { TclFreeCompileEnv(&compEnv); iPtr->compiledProcPtr = procPtr; TclInitCompileEnv(interp, &compEnv, stringPtr, length, iPtr->invokeCmdFramePtr, iPtr->invokeWord); if (clLocPtr) { compEnv.clNext = &clLocPtr->loc[0]; } compEnv.atCmdStart = 2; /* The disabling magic. */ TclCompileScript(interp, stringPtr, length, &compEnv); assert (compEnv.atCmdStart > 1); TclEmitOpcode(INST_DONE, &compEnv); assert (compEnv.atCmdStart > 1); } /* * Apply some peephole optimizations that can cross specific/generic * instruction generator boundaries. */ if (iPtr->optimizer) { (iPtr->optimizer)(&compEnv); } /* * Invoke the compilation hook procedure if there is one. */ if (hookProc) { result = hookProc(interp, &compEnv, clientData); } /* * After optimization is all done, check that byte code length limits * are not exceeded. Bug [27b3ce2997]. */ if ((compEnv.codeNext - compEnv.codeStart) > INT_MAX) { /* * Cannot just return TCL_ERROR as callers ignore return value. * TODO - May be use TclCompileSyntaxError here? */ Tcl_Panic("Maximum byte code length %d exceeded.", INT_MAX); } /* * Change the object into a ByteCode object. Ownership of the literal * objects and aux data items passes to the ByteCode object. */ #ifdef TCL_COMPILE_DEBUG TclVerifyLocalLiteralTable(&compEnv); #endif /*TCL_COMPILE_DEBUG*/ if (result == TCL_OK) { (void) TclInitByteCodeObj(objPtr, &tclByteCodeType, &compEnv); TclDebugPrintByteCodeObj(objPtr); } TclFreeCompileEnv(&compEnv); return result; } /* *----------------------------------------------------------------------- * * SetByteCodeFromAny -- * * Part of the bytecode Tcl object type implementation. Attempts to * generate an byte code internal form for the Tcl object "objPtr" by * compiling its string representation. * * Results: * A standard Tcl object result. If an error occurs during compilation and * "interp" is not null, an error message is left in the interpreter's * result. * * Side effects: * Frees the old internal representation. If no error occurs then the * compiled code is stored as "objPtr"s bytecode representation. Also, if * debugging, initializes the "tcl_traceCompile" Tcl variable used to * trace compilations. * *---------------------------------------------------------------------- */ static int SetByteCodeFromAny( Tcl_Interp *interp, /* The interpreter for which the code is being * compiled. Must not be NULL. */ Tcl_Obj *objPtr) /* The object to compile to bytecode */ { if (interp == NULL) { return TCL_ERROR; } return TclSetByteCodeFromAny(interp, objPtr, NULL, NULL); } /* *---------------------------------------------------------------------- * * DupByteCodeInternalRep -- * * Part of the bytecode Tcl object type implementation. However, it does * not copy the internal representation of a bytecode Tcl_Obj, instead * assigning NULL to the type pointer of the new object. Code is compiled * for the new object only if necessary. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void DupByteCodeInternalRep( TCL_UNUSED(Tcl_Obj *) /*srcPtr*/, TCL_UNUSED(Tcl_Obj *) /*copyPtr*/) { return; } /* *---------------------------------------------------------------------- * * FreeByteCodeInternalRep -- * * Part of the bytecode Tcl object type implementation. Frees the storage * associated with a bytecode object's internal representation unless its * code is actively being executed. * * Results: * None. * * Side effects: * The bytecode object's internal rep is invalidated and its code is freed * unless the code is actively being executed, in which case cleanup is * delayed until the last execution of the code completes. * *---------------------------------------------------------------------- */ static void FreeByteCodeInternalRep( Tcl_Obj *objPtr) /* Object whose internal rep to free. */ { ByteCode *codePtr; ByteCodeGetInternalRep(objPtr, &tclByteCodeType, codePtr); assert(codePtr != NULL); TclReleaseByteCode(codePtr); } /* *---------------------------------------------------------------------- * * TclReleaseByteCode -- * * Does all the real work of freeing up a bytecode object's ByteCode * structure. Called only when the structure's reference count * is zero. * * Results: * None. * * Side effects: * Frees objPtr's bytecode internal representation and sets its type to * NULL. Also releases its literals and frees its auxiliary data items. * *---------------------------------------------------------------------- */ void TclPreserveByteCode( ByteCode *codePtr) { codePtr->refCount++; } void TclReleaseByteCode( ByteCode *codePtr) { if (codePtr->refCount-- > 1) { return; } /* Just dropped to refcount==0. Clean up. */ CleanupByteCode(codePtr); } static void CleanupByteCode( ByteCode *codePtr) /* Points to the ByteCode to free. */ { Tcl_Interp *interp = (Tcl_Interp *) *codePtr->interpHandle; Interp *iPtr = (Interp *) interp; int numLitObjects = codePtr->numLitObjects; int numAuxDataItems = codePtr->numAuxDataItems; Tcl_Obj **objArrayPtr, *objPtr; const AuxData *auxDataPtr; int i; #ifdef TCL_COMPILE_STATS if (interp != NULL) { ByteCodeStats *statsPtr; Tcl_Time destroyTime; int lifetimeSec, lifetimeMicroSec, log2; statsPtr = &iPtr->stats; statsPtr->numByteCodesFreed++; statsPtr->currentSrcBytes -= (double)codePtr->numSrcBytes; statsPtr->currentByteCodeBytes -= (double) codePtr->structureSize; statsPtr->currentInstBytes -= (double) codePtr->numCodeBytes; statsPtr->currentLitBytes -= (double) codePtr->numLitObjects * sizeof(Tcl_Obj *); statsPtr->currentExceptBytes -= (double) codePtr->numExceptRanges * sizeof(ExceptionRange); statsPtr->currentAuxBytes -= (double) codePtr->numAuxDataItems * sizeof(AuxData); statsPtr->currentCmdMapBytes -= (double) codePtr->numCmdLocBytes; Tcl_GetTime(&destroyTime); lifetimeSec = destroyTime.sec - codePtr->createTime.sec; if (lifetimeSec > 2000) { /* avoid overflow */ lifetimeSec = 2000; } lifetimeMicroSec = 1000000 * lifetimeSec + (destroyTime.usec - codePtr->createTime.usec); log2 = TclLog2(lifetimeMicroSec); if (log2 > 31) { log2 = 31; } statsPtr->lifetimeCount[log2]++; } #endif /* TCL_COMPILE_STATS */ /* * A single heap object holds the ByteCode structure and its code, object, * command location, and auxiliary data arrays. This means we only need to * 1) decrement the ref counts of each LiteralEntry in the literal array, * 2) call the free procedures for the auxiliary data items, 3) free the * localCache if it is unused, and finally 4) free the ByteCode * structure's heap object. * * The case for TCL_BYTECODE_PRECOMPILED (precompiled ByteCodes, like * those generated from tbcload) is special, as they doesn't make use of * the global literal table. They instead maintain private references to * their literals which must be decremented. * * In order to ensure proper and efficient cleanup of the literal array * when it contains non-shared literals [Bug 983660], distinguish the case * of an interpreter being deleted, which is signaled by interp == NULL. * Also, as the interp deletion will remove the global literal table * anyway, avoid the extra cost of updating it for each literal being * released. */ if (codePtr->flags & TCL_BYTECODE_PRECOMPILED) { objArrayPtr = codePtr->objArrayPtr; for (i = 0; i < numLitObjects; i++) { objPtr = *objArrayPtr; if (objPtr) { Tcl_DecrRefCount(objPtr); } objArrayPtr++; } codePtr->numLitObjects = 0; } else { objArrayPtr = codePtr->objArrayPtr; while (numLitObjects--) { /* TclReleaseLiteral calls Tcl_DecrRefCount() for us */ TclReleaseLiteral(interp, *objArrayPtr++); } } auxDataPtr = codePtr->auxDataArrayPtr; for (i = 0; i < numAuxDataItems; i++) { if (auxDataPtr->type->freeProc != NULL) { auxDataPtr->type->freeProc(auxDataPtr->clientData); } auxDataPtr++; } /* * TIP #280. Release the location data associated with this bytecode * structure, if any. The associated interp may be gone already, and the * data with it. * * See also tclBasic.c, DeleteInterpProc */ if (iPtr) { Tcl_HashEntry *hePtr = Tcl_FindHashEntry(iPtr->lineBCPtr, codePtr); if (hePtr) { ReleaseCmdWordData((ExtCmdLoc *)Tcl_GetHashValue(hePtr)); Tcl_DeleteHashEntry(hePtr); } } if (codePtr->localCachePtr && (codePtr->localCachePtr->refCount-- <= 1)) { TclFreeLocalCache(interp, codePtr->localCachePtr); } TclHandleRelease(codePtr->interpHandle); Tcl_Free(codePtr); } /* * --------------------------------------------------------------------- * * IsCompactibleCompileEnv -- * * Determines whether some basic compaction optimizations may be applied * to a piece of bytecode. Idempotent. * * --------------------------------------------------------------------- */ static int IsCompactibleCompileEnv( CompileEnv *envPtr) { unsigned char *pc; int size; /* * Special: procedures in the '::tcl' namespace (or its children) are * considered to be well-behaved, so compaction can be applied to them even * if it would otherwise be invalid. */ if (envPtr->procPtr != NULL && envPtr->procPtr->cmdPtr != NULL && envPtr->procPtr->cmdPtr->nsPtr != NULL) { Namespace *nsPtr = envPtr->procPtr->cmdPtr->nsPtr; if (strcmp(nsPtr->fullName, "::tcl") == 0 || strncmp(nsPtr->fullName, "::tcl::", 7) == 0) { return 1; } } /* * Go through and ensure that no operation involved can cause a desired * change of bytecode sequence during its execution. This comes down to * ensuring that there are no mapped variables (due to traces) or calls to * external commands (traces, [uplevel] trickery). This is actually a very * conservative check. It turns down a lot of code that is OK in practice. */ for (pc = envPtr->codeStart ; pc < envPtr->codeNext ; pc += size) { switch (*pc) { /* Invokes */ case INST_INVOKE_STK1: case INST_INVOKE_STK4: case INST_INVOKE_EXPANDED: case INST_INVOKE_REPLACE: return 0; /* Runtime evals */ case INST_EVAL_STK: case INST_EXPR_STK: case INST_YIELD: return 0; /* Upvars */ case INST_UPVAR: case INST_NSUPVAR: case INST_VARIABLE: return 0; default: size = tclInstructionTable[*pc].numBytes; assert (size > 0); break; } } return 1; } /* *---------------------------------------------------------------------- * * Tcl_SubstObj -- * * Performs substitutions on the given string as described in the user * documentation for "subst". * * Results: * A Tcl_Obj* containing the substituted string, or NULL to indicate that * an error occurred. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_SubstObj( Tcl_Interp *interp, /* Interpreter in which substitution occurs */ Tcl_Obj *objPtr, /* The value to be substituted. */ int flags) /* What substitutions to do. */ { NRE_callback *rootPtr = TOP_CB(interp); if (TclNRRunCallbacks(interp, Tcl_NRSubstObj(interp, objPtr, flags), rootPtr) != TCL_OK) { return NULL; } return Tcl_GetObjResult(interp); } /* *---------------------------------------------------------------------- * * Tcl_NRSubstObj -- * * Adds substitution within the value of objPtr to the NR execution stack. * * Results: * TCL_OK. * * Side effects: * Compiles objPtr into bytecode that performs the substitutions as * governed by flags, adds a callback to the NR execution stack to execute * the bytecode and store the result in the interp. * *---------------------------------------------------------------------- */ int Tcl_NRSubstObj( Tcl_Interp *interp, Tcl_Obj *objPtr, int flags) { ByteCode *codePtr = CompileSubstObj(interp, objPtr, flags); /* TODO: Confirm we do not need this. */ /* Tcl_ResetResult(interp); */ return TclNRExecuteByteCode(interp, codePtr); } /* *---------------------------------------------------------------------- * * CompileSubstObj -- * * Compiles a value into bytecode that performs substitution within the * value, as governed by flags. * * Results: * A (ByteCode *) is pointing to the resulting ByteCode. * * Side effects: * The Tcl_ObjType of objPtr is changed to the "substcode" type, and the * ByteCode and governing flags value are kept in the internal rep for * faster operations the next time CompileSubstObj is called on the same * value. * *---------------------------------------------------------------------- */ static ByteCode * CompileSubstObj( Tcl_Interp *interp, Tcl_Obj *objPtr, int flags) { Interp *iPtr = (Interp *) interp; ByteCode *codePtr = NULL; ByteCodeGetInternalRep(objPtr, &substCodeType, codePtr); if (codePtr != NULL) { Namespace *nsPtr = iPtr->varFramePtr->nsPtr; if (flags != PTR2INT(SubstFlags(objPtr)) || ((Interp *) *codePtr->interpHandle != iPtr) || (codePtr->compileEpoch != iPtr->compileEpoch) || (codePtr->nsPtr != nsPtr) || (codePtr->nsEpoch != nsPtr->resolverEpoch) || (codePtr->localCachePtr != iPtr->varFramePtr->localCachePtr)) { Tcl_StoreInternalRep(objPtr, &substCodeType, NULL); codePtr = NULL; } } if (codePtr == NULL) { CompileEnv compEnv; Tcl_Size numBytes; const char *bytes = TclGetStringFromObj(objPtr, &numBytes); /* TODO: Check for more TIP 280 */ TclInitCompileEnv(interp, &compEnv, bytes, numBytes, NULL, 0); TclSubstCompile(interp, bytes, numBytes, flags, 1, &compEnv); TclEmitOpcode(INST_DONE, &compEnv); codePtr = TclInitByteCodeObj(objPtr, &substCodeType, &compEnv); TclFreeCompileEnv(&compEnv); SubstFlags(objPtr) = INT2PTR(flags); if (iPtr->varFramePtr->localCachePtr) { codePtr->localCachePtr = iPtr->varFramePtr->localCachePtr; codePtr->localCachePtr->refCount++; } TclDebugPrintByteCodeObj(objPtr); } return codePtr; } /* *---------------------------------------------------------------------- * * FreeSubstCodeInternalRep -- * * Part of the "substcode" Tcl object type implementation. Frees the * storage associated with the substcode internal representation of a * Tcl_Obj unless its code is actively being executed. * * Results: * None. * * Side effects: * The substcode object's internal rep is marked invalid and its code * gets freed unless the code is actively being executed. In that case * the cleanup is delayed until the last execution of the code completes. * *---------------------------------------------------------------------- */ static void FreeSubstCodeInternalRep( Tcl_Obj *objPtr) /* Object whose internal rep to free. */ { ByteCode *codePtr; ByteCodeGetInternalRep(objPtr, &substCodeType, codePtr); assert(codePtr != NULL); TclReleaseByteCode(codePtr); } static void ReleaseCmdWordData( ExtCmdLoc *eclPtr) { Tcl_Size i; if (eclPtr->type == TCL_LOCATION_SOURCE) { Tcl_DecrRefCount(eclPtr->path); } for (i=0 ; inuloc ; i++) { Tcl_Free(eclPtr->loc[i].line); } if (eclPtr->loc != NULL) { Tcl_Free(eclPtr->loc); } Tcl_Free(eclPtr); } /* *---------------------------------------------------------------------- * * TclInitCompileEnv -- * * Initializes a CompileEnv compilation environment structure for the * compilation of a string in an interpreter. * * Results: * None. * * Side effects: * The CompileEnv structure is initialized. * *---------------------------------------------------------------------- */ void TclInitCompileEnv( Tcl_Interp *interp, /* The interpreter for which a CompileEnv * structure is initialized. */ CompileEnv *envPtr,/* Points to the CompileEnv structure to * initialize. */ const char *stringPtr, /* The source string to be compiled. */ size_t numBytes, /* Number of bytes in source string. */ const CmdFrame *invoker, /* Location context invoking the bcc */ int word) /* Index of the word in that context getting * compiled */ { Interp *iPtr = (Interp *) interp; assert(tclInstructionTable[LAST_INST_OPCODE].name == NULL); envPtr->iPtr = iPtr; envPtr->source = stringPtr; envPtr->numSrcBytes = numBytes; envPtr->procPtr = iPtr->compiledProcPtr; iPtr->compiledProcPtr = NULL; envPtr->numCommands = 0; envPtr->exceptDepth = 0; envPtr->maxExceptDepth = 0; envPtr->maxStackDepth = 0; envPtr->currStackDepth = 0; TclInitLiteralTable(&envPtr->localLitTable); envPtr->codeStart = envPtr->staticCodeSpace; envPtr->codeNext = envPtr->codeStart; envPtr->codeEnd = envPtr->codeStart + COMPILEENV_INIT_CODE_BYTES; envPtr->mallocedCodeArray = 0; envPtr->literalArrayPtr = envPtr->staticLiteralSpace; envPtr->literalArrayNext = 0; envPtr->literalArrayEnd = COMPILEENV_INIT_NUM_OBJECTS; envPtr->mallocedLiteralArray = 0; envPtr->exceptArrayPtr = envPtr->staticExceptArraySpace; envPtr->exceptAuxArrayPtr = envPtr->staticExAuxArraySpace; envPtr->exceptArrayNext = 0; envPtr->exceptArrayEnd = COMPILEENV_INIT_EXCEPT_RANGES; envPtr->mallocedExceptArray = 0; envPtr->cmdMapPtr = envPtr->staticCmdMapSpace; envPtr->cmdMapEnd = COMPILEENV_INIT_CMD_MAP_SIZE; envPtr->mallocedCmdMap = 0; envPtr->atCmdStart = 1; envPtr->expandCount = 0; /* * TIP #280: Set up the extended command location information, based on * the context invoking the byte code compiler. This structure is used to * keep the per-word line information for all compiled commands. * * See also tclBasic.c, TclEvalObjEx, for the equivalent code in the * non-compiling evaluator */ envPtr->extCmdMapPtr = (ExtCmdLoc *)Tcl_Alloc(sizeof(ExtCmdLoc)); envPtr->extCmdMapPtr->loc = NULL; envPtr->extCmdMapPtr->nloc = 0; envPtr->extCmdMapPtr->nuloc = 0; envPtr->extCmdMapPtr->path = NULL; if (invoker == NULL) { /* * Initialize the compiler for relative counting in case of a * dynamic context. */ envPtr->line = 1; if (iPtr->evalFlags & TCL_EVAL_FILE) { iPtr->evalFlags &= ~TCL_EVAL_FILE; envPtr->extCmdMapPtr->type = TCL_LOCATION_SOURCE; if (iPtr->scriptFile) { /* * Normalization here, to have the correct pwd. Should have * negligible impact on performance, as the norm should have * been done already by the 'source' invoking us, and it * caches the result. */ Tcl_Obj *norm = Tcl_FSGetNormalizedPath(interp, iPtr->scriptFile); if (norm == NULL) { /* * Error message in the interp result. No place to put it. * And no place to serve the error itself to either. Fake * a path, empty string. */ TclNewLiteralStringObj(envPtr->extCmdMapPtr->path, ""); } else { envPtr->extCmdMapPtr->path = norm; } } else { TclNewLiteralStringObj(envPtr->extCmdMapPtr->path, ""); } Tcl_IncrRefCount(envPtr->extCmdMapPtr->path); } else { envPtr->extCmdMapPtr->type = (envPtr->procPtr ? TCL_LOCATION_PROC : TCL_LOCATION_BC); } } else { /* * Initialize the compiler using the context, making counting absolute * to that context. Note that the context can be byte code execution. * In that case we have to fill out the missing pieces (line, path, * ...) which may make change the type as well. */ CmdFrame *ctxPtr = (CmdFrame *)TclStackAlloc(interp, sizeof(CmdFrame)); int pc = 0; *ctxPtr = *invoker; if (invoker->type == TCL_LOCATION_BC) { /* * Note: Type BC => ctx.data.eval.path is not used. * ctx.data.tebc.codePtr is used instead. */ TclGetSrcInfoForPc(ctxPtr); pc = 1; } if ((ctxPtr->nline <= word) || (ctxPtr->line[word] < 0)) { /* * Word is not a literal, relative counting. */ envPtr->line = 1; envPtr->extCmdMapPtr->type = (envPtr->procPtr ? TCL_LOCATION_PROC : TCL_LOCATION_BC); if (pc && (ctxPtr->type == TCL_LOCATION_SOURCE)) { /* * The reference made by 'TclGetSrcInfoForPc' is dead. */ Tcl_DecrRefCount(ctxPtr->data.eval.path); } } else { envPtr->line = ctxPtr->line[word]; envPtr->extCmdMapPtr->type = ctxPtr->type; if (ctxPtr->type == TCL_LOCATION_SOURCE) { envPtr->extCmdMapPtr->path = ctxPtr->data.eval.path; if (pc) { /* * The reference 'TclGetSrcInfoForPc' made is transfered. */ ctxPtr->data.eval.path = NULL; } else { /* * We have a new reference here. */ Tcl_IncrRefCount(envPtr->extCmdMapPtr->path); } } } TclStackFree(interp, ctxPtr); } envPtr->extCmdMapPtr->start = envPtr->line; /* * Initialize the data about invisible continuation lines as empty, i.e. * not used. The caller (TclSetByteCodeFromAny) will set this up, if such * data is available. */ envPtr->clNext = NULL; envPtr->auxDataArrayPtr = envPtr->staticAuxDataArraySpace; envPtr->auxDataArrayNext = 0; envPtr->auxDataArrayEnd = COMPILEENV_INIT_AUX_DATA_SIZE; envPtr->mallocedAuxDataArray = 0; } /* *---------------------------------------------------------------------- * * TclFreeCompileEnv -- * * Frees the storage allocated in a CompileEnv compilation environment * structure. * * Results: * None. * * Side effects: * Allocated storage in the CompileEnv structure is freed, although its * local literal table is not deleted and its literal objects are not * released. In addition, storage referenced by its auxiliary data items * is not freed. This is done so that, when compilation is successful, * "ownership" of these objects and aux data items is handed over to the * corresponding ByteCode structure. * *---------------------------------------------------------------------- */ void TclFreeCompileEnv( CompileEnv *envPtr)/* Points to the CompileEnv structure. */ { if (envPtr->localLitTable.buckets != envPtr->localLitTable.staticBuckets){ Tcl_Free(envPtr->localLitTable.buckets); envPtr->localLitTable.buckets = envPtr->localLitTable.staticBuckets; } if (envPtr->iPtr) { /* * We never converted to Bytecode, so free the things we would * have transferred to it. */ Tcl_Size i; LiteralEntry *entryPtr = envPtr->literalArrayPtr; AuxData *auxDataPtr = envPtr->auxDataArrayPtr; for (i = 0; i < envPtr->literalArrayNext; i++) { TclReleaseLiteral((Tcl_Interp *)envPtr->iPtr, entryPtr->objPtr); entryPtr++; } #ifdef TCL_COMPILE_DEBUG TclVerifyGlobalLiteralTable(envPtr->iPtr); #endif /*TCL_COMPILE_DEBUG*/ for (i = 0; i < envPtr->auxDataArrayNext; i++) { if (auxDataPtr->type->freeProc != NULL) { auxDataPtr->type->freeProc(auxDataPtr->clientData); } auxDataPtr++; } } if (envPtr->mallocedCodeArray) { Tcl_Free(envPtr->codeStart); } if (envPtr->mallocedLiteralArray) { Tcl_Free(envPtr->literalArrayPtr); } if (envPtr->mallocedExceptArray) { Tcl_Free(envPtr->exceptArrayPtr); Tcl_Free(envPtr->exceptAuxArrayPtr); } if (envPtr->mallocedCmdMap) { Tcl_Free(envPtr->cmdMapPtr); } if (envPtr->mallocedAuxDataArray) { Tcl_Free(envPtr->auxDataArrayPtr); } if (envPtr->extCmdMapPtr) { ReleaseCmdWordData(envPtr->extCmdMapPtr); envPtr->extCmdMapPtr = NULL; } } /* *---------------------------------------------------------------------- * * TclWordKnownAtCompileTime -- * * Determines whether the value of a token is completely known at compile * time. * * Results: * True if the tokenPtr argument points to a word value that is * completely known at compile time. Generally, values that are known at * compile time can be compiled to their values, while values that cannot * be known until substitution at runtime must be compiled to bytecode * instructions that perform that substitution. For several commands, * whether or not arguments are known at compile time determine whether * it is worthwhile to compile at all. * * Side effects: * When returning true, appends the known value of the word to the * unshared Tcl_Obj (*valuePtr), unless valuePtr is NULL. * *---------------------------------------------------------------------- */ int TclWordKnownAtCompileTime( Tcl_Token *tokenPtr, /* Points to Tcl_Token we should check */ Tcl_Obj *valuePtr) /* If not NULL, points to an unshared Tcl_Obj * to which we should append the known value * of the word. */ { int numComponents = tokenPtr->numComponents; Tcl_Obj *tempPtr = NULL; if (tokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { if (valuePtr != NULL) { Tcl_AppendToObj(valuePtr, tokenPtr[1].start, tokenPtr[1].size); } return 1; } if (tokenPtr->type != TCL_TOKEN_WORD) { return 0; } tokenPtr++; if (valuePtr != NULL) { TclNewObj(tempPtr); Tcl_IncrRefCount(tempPtr); } while (numComponents--) { switch (tokenPtr->type) { case TCL_TOKEN_TEXT: if (tempPtr != NULL) { Tcl_AppendToObj(tempPtr, tokenPtr->start, tokenPtr->size); } break; case TCL_TOKEN_BS: if (tempPtr != NULL) { char utfBuf[4] = ""; size_t length = TclParseBackslash(tokenPtr->start, tokenPtr->size, NULL, utfBuf); Tcl_AppendToObj(tempPtr, utfBuf, length); } break; default: if (tempPtr != NULL) { Tcl_DecrRefCount(tempPtr); } return 0; } tokenPtr++; } if (valuePtr != NULL) { Tcl_AppendObjToObj(valuePtr, tempPtr); Tcl_DecrRefCount(tempPtr); } return 1; } /* *---------------------------------------------------------------------- * * TclCompileScript -- * * Compiles a Tcl script in a string. * * Results: * * A standard Tcl result. If an error occurs, an * error message is left in the interpreter's result. * * Side effects: * Adds instructions to envPtr to evaluate the script at runtime. * *---------------------------------------------------------------------- */ static int ExpandRequested( Tcl_Token *tokenPtr, size_t numWords) { /* Determine whether any words of the command require expansion */ while (numWords--) { if (tokenPtr->type == TCL_TOKEN_EXPAND_WORD) { return 1; } tokenPtr = TokenAfter(tokenPtr); } return 0; } static void CompileCmdLiteral( Tcl_Interp *interp, Tcl_Obj *cmdObj, CompileEnv *envPtr) { const char *bytes; Command *cmdPtr; int cmdLitIdx, extraLiteralFlags = LITERAL_CMD_NAME; Tcl_Size length; cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, cmdObj); if ((cmdPtr != NULL) && (cmdPtr->flags & CMD_VIA_RESOLVER)) { extraLiteralFlags |= LITERAL_UNSHARED; } bytes = TclGetStringFromObj(cmdObj, &length); cmdLitIdx = TclRegisterLiteral(envPtr, bytes, length, extraLiteralFlags); if (cmdPtr && TclRoutineHasName(cmdPtr)) { TclSetCmdNameObj(interp, TclFetchLiteral(envPtr, cmdLitIdx), cmdPtr); } TclEmitPush(cmdLitIdx, envPtr); } void TclCompileInvocation( Tcl_Interp *interp, Tcl_Token *tokenPtr, Tcl_Obj *cmdObj, size_t numWords, CompileEnv *envPtr) { DefineLineInformation; size_t wordIdx = 0; int depth = TclGetStackDepth(envPtr); if (cmdObj) { CompileCmdLiteral(interp, cmdObj, envPtr); wordIdx = 1; tokenPtr = TokenAfter(tokenPtr); } for (; wordIdx < numWords; wordIdx++, tokenPtr = TokenAfter(tokenPtr)) { int objIdx; SetLineInformation(wordIdx); if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { CompileTokens(envPtr, tokenPtr, interp); continue; } objIdx = TclRegisterLiteral(envPtr, tokenPtr[1].start, tokenPtr[1].size, 0); if (envPtr->clNext) { TclContinuationsEnterDerived(TclFetchLiteral(envPtr, objIdx), tokenPtr[1].start - envPtr->source, envPtr->clNext); } TclEmitPush(objIdx, envPtr); } if (wordIdx <= 255) { TclEmitInvoke(envPtr, INST_INVOKE_STK1, wordIdx); } else { TclEmitInvoke(envPtr, INST_INVOKE_STK4, wordIdx); } TclCheckStackDepth(depth+1, envPtr); } static void CompileExpanded( Tcl_Interp *interp, Tcl_Token *tokenPtr, Tcl_Obj *cmdObj, int numWords, CompileEnv *envPtr) { DefineLineInformation; int wordIdx = 0; int depth = TclGetStackDepth(envPtr); StartExpanding(envPtr); if (cmdObj) { CompileCmdLiteral(interp, cmdObj, envPtr); wordIdx = 1; tokenPtr = TokenAfter(tokenPtr); } for (; wordIdx < numWords; wordIdx++, tokenPtr = TokenAfter(tokenPtr)) { int objIdx; SetLineInformation(wordIdx); if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { CompileTokens(envPtr, tokenPtr, interp); if (tokenPtr->type == TCL_TOKEN_EXPAND_WORD) { TclEmitInstInt4(INST_EXPAND_STKTOP, envPtr->currStackDepth, envPtr); } continue; } objIdx = TclRegisterLiteral(envPtr, tokenPtr[1].start, tokenPtr[1].size, 0); if (envPtr->clNext) { TclContinuationsEnterDerived(TclFetchLiteral(envPtr, objIdx), tokenPtr[1].start - envPtr->source, envPtr->clNext); } TclEmitPush(objIdx, envPtr); } /* * The stack depth during argument expansion can only be managed at * runtime, as the number of elements in the expanded lists is not known * at compile time. Adjust the stack depth estimate here so that it is * correct after the command with expanded arguments returns. * * The end effect of this command's invocation is that all the words of * the command are popped from the stack and the result is pushed: The * stack top changes by (1-wordIdx). * * The estimates are not correct while the command is being * prepared and run, INST_EXPAND_STKTOP is not stack-neutral in general. */ TclEmitInvoke(envPtr, INST_INVOKE_EXPANDED, wordIdx); TclCheckStackDepth(depth+1, envPtr); } static int CompileCmdCompileProc( Tcl_Interp *interp, Tcl_Parse *parsePtr, Command *cmdPtr, CompileEnv *envPtr) { DefineLineInformation; int unwind = 0; Tcl_Size incrOffset = -1; int depth = TclGetStackDepth(envPtr); /* * Emission of the INST_START_CMD instruction is controlled by the value of * envPtr->atCmdStart: * * atCmdStart == 2 : Don't use the INST_START_CMD instruction. * atCmdStart == 1 : INST_START_CMD was the last instruction emitted, * : so no need to emit another. Instead * : increment the number of cmds started at it, except * : for the special case at the start of a script. * atCmdStart == 0 : The last instruction was something else. * : Emit INST_START_CMD here. */ switch (envPtr->atCmdStart) { case 0: unwind = tclInstructionTable[INST_START_CMD].numBytes; TclEmitInstInt4(INST_START_CMD, 0, envPtr); incrOffset = envPtr->codeNext - envPtr->codeStart; TclEmitInt4(0, envPtr); break; case 1: if (envPtr->codeNext > envPtr->codeStart) { incrOffset = envPtr->codeNext - 4 - envPtr->codeStart; } break; case 2: /* Nothing to do */ ; } if (TCL_OK == TclAttemptCompileProc(interp, parsePtr, 1, cmdPtr, envPtr)) { if (incrOffset >= 0) { /* * Command compiled succesfully. Increment the number of * commands that start at the currently active INST_START_CMD. */ unsigned char *incrPtr = envPtr->codeStart + incrOffset; unsigned char *startPtr = incrPtr - 5; TclIncrUInt4AtPtr(incrPtr, 1); if (unwind) { /* We started the INST_START_CMD. Record the code length. */ TclStoreInt4AtPtr(envPtr->codeNext - startPtr, startPtr + 1); } } TclCheckStackDepth(depth+1, envPtr); return TCL_OK; } envPtr->codeNext -= unwind; /* Unwind INST_START_CMD */ /* * Throw out any line information generated by the failed compile attempt. */ while (mapPtr->nuloc - 1 > eclIndex) { mapPtr->nuloc--; Tcl_Free(mapPtr->loc[mapPtr->nuloc].line); mapPtr->loc[mapPtr->nuloc].line = NULL; } /* * Reset the index of next command. Toss out any from failed nested * partial compiles. */ envPtr->numCommands = mapPtr->nuloc; return TCL_ERROR; } static int CompileCommandTokens( Tcl_Interp *interp, Tcl_Parse *parsePtr, CompileEnv *envPtr) { Interp *iPtr = (Interp *) interp; Tcl_Token *tokenPtr = parsePtr->tokenPtr; ExtCmdLoc *eclPtr = envPtr->extCmdMapPtr; Tcl_Obj *cmdObj; Command *cmdPtr = NULL; int code = TCL_ERROR; int cmdKnown, expand = -1; Tcl_Size *wlines, wlineat; Tcl_Size cmdLine = envPtr->line; Tcl_Size *clNext = envPtr->clNext; Tcl_Size cmdIdx = envPtr->numCommands; Tcl_Size startCodeOffset = envPtr->codeNext - envPtr->codeStart; int depth = TclGetStackDepth(envPtr); assert ((int)parsePtr->numWords > 0); /* Precompile */ TclNewObj(cmdObj); envPtr->numCommands++; EnterCmdStartData(envPtr, cmdIdx, parsePtr->commandStart - envPtr->source, startCodeOffset); /* * TIP #280. Scan the words and compute the extended location information. * At first the map first contains full per-word line information for use by the * compiler. This is later replaced by a reduced form which signals * non-literal words, stored in 'wlines'. */ EnterCmdWordData(eclPtr, parsePtr->commandStart - envPtr->source, parsePtr->tokenPtr, parsePtr->commandStart, parsePtr->numWords, cmdLine, clNext, &wlines, envPtr); wlineat = eclPtr->nuloc - 1; envPtr->line = eclPtr->loc[wlineat].line[0]; envPtr->clNext = eclPtr->loc[wlineat].next[0]; /* Do we know the command word? */ Tcl_IncrRefCount(cmdObj); tokenPtr = parsePtr->tokenPtr; cmdKnown = TclWordKnownAtCompileTime(tokenPtr, cmdObj); /* Is this a command we should (try to) compile with a compileProc ? */ if (cmdKnown && !(iPtr->flags & DONT_COMPILE_CMDS_INLINE)) { cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, cmdObj); if (cmdPtr) { /* * Found a command. Test the ways we can be told not to attempt * to compile it. */ if ((cmdPtr->compileProc == NULL) || (cmdPtr->nsPtr->flags & NS_SUPPRESS_COMPILATION) || (cmdPtr->flags & CMD_HAS_EXEC_TRACES)) { cmdPtr = NULL; } } if (cmdPtr && !(cmdPtr->flags & CMD_COMPILES_EXPANDED)) { expand = ExpandRequested(parsePtr->tokenPtr, (int)parsePtr->numWords); if (expand) { /* We need to expand, but compileProc cannot. */ cmdPtr = NULL; } } } /* If cmdPtr != NULL, try to call cmdPtr->compileProc */ if (cmdPtr) { code = CompileCmdCompileProc(interp, parsePtr, cmdPtr, envPtr); } if (code == TCL_ERROR) { if (expand < 0) { expand = ExpandRequested(parsePtr->tokenPtr, (int)parsePtr->numWords); } if (expand) { CompileExpanded(interp, parsePtr->tokenPtr, cmdKnown ? cmdObj : NULL, (int)parsePtr->numWords, envPtr); } else { TclCompileInvocation(interp, parsePtr->tokenPtr, cmdKnown ? cmdObj : NULL, (int)parsePtr->numWords, envPtr); } } Tcl_DecrRefCount(cmdObj); TclEmitOpcode(INST_POP, envPtr); EnterCmdExtentData(envPtr, cmdIdx, parsePtr->term - parsePtr->commandStart, (envPtr->codeNext-envPtr->codeStart) - startCodeOffset); /* * TIP #280: Free the full form of per-word line data and insert the * reduced form now. */ envPtr->line = cmdLine; envPtr->clNext = clNext; Tcl_Free(eclPtr->loc[wlineat].line); Tcl_Free(eclPtr->loc[wlineat].next); eclPtr->loc[wlineat].line = wlines; eclPtr->loc[wlineat].next = NULL; TclCheckStackDepth(depth, envPtr); return cmdIdx; } void TclCompileScript( Tcl_Interp *interp, /* Used for error and status reporting. Also * serves as context for finding and compiling * commands. May not be NULL. */ const char *script, /* The source script to compile. */ Tcl_Size numBytes, /* Number of bytes in script. If < 0, the * script consists of all bytes up to the * first null character. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { int lastCmdIdx = -1; /* Index into envPtr->cmdMapPtr of the last * command this routine compiles into bytecode. * Initial value of -1 indicates this routine * has not yet generated any bytecode. */ const char *p = script; /* Where we are in our compile. */ int depth = TclGetStackDepth(envPtr); Interp *iPtr = (Interp *) interp; if (envPtr->iPtr == NULL) { Tcl_Panic("TclCompileScript() called on uninitialized CompileEnv"); } /* * Check depth to avoid overflow of the C execution stack by too many * nested calls of TclCompileScript, considering interp recursionlimit. * Use factor 5/4 (1.25) to avoid being too mistaken when recognizing the * limit during "mixed" evaluation and compilation process (nested * eval+compile) and is good enough for default recursionlimit (1000). */ if (iPtr->numLevels / 5 > iPtr->maxNestingDepth / 4) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "too many nested compilations (infinite loop?)", -1)); Tcl_SetErrorCode(interp, "TCL", "LIMIT", "STACK", (char *)NULL); TclCompileSyntaxError(interp, envPtr); return; } if (numBytes < 0) { numBytes = strlen(script); } /* Each iteration compiles one command from the script. */ if (numBytes > 0) { if (numBytes >= INT_MAX) { /* * Note this gets -errorline as 1. Not worth figuring out which line * crosses the limit to get -errorline for this error case. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Script length %" TCL_SIZE_MODIFIER "d exceeds max permitted length %d.", numBytes, INT_MAX-1)); Tcl_SetErrorCode(interp, "TCL", "LIMIT", "SCRIPTLENGTH", (char *)NULL); TclCompileSyntaxError(interp, envPtr); return; } /* * Don't use system stack (size of Tcl_Parse is ca. 400 bytes), so * many nested compilations (body enclosed in body) can cause abnormal * program termination with a stack overflow exception, bug [fec0c17d39]. */ Tcl_Parse *parsePtr = (Tcl_Parse *)Tcl_Alloc(sizeof(Tcl_Parse)); do { const char *next; if (TCL_OK != Tcl_ParseCommand(interp, p, numBytes, 0, parsePtr)) { /* * Compile bytecodes to report the parsePtr error at runtime. */ Tcl_LogCommandInfo(interp, script, parsePtr->commandStart, parsePtr->term + 1 - parsePtr->commandStart); TclCompileSyntaxError(interp, envPtr); Tcl_Free(parsePtr); return; } #ifdef TCL_COMPILE_DEBUG /* * If tracing, print a line for each top level command compiled. * TODO: Suppress when numWords == 0 ? */ if ((tclTraceCompile >= 1) && (envPtr->procPtr == NULL)) { int commandLength = parsePtr->term - parsePtr->commandStart; fprintf(stdout, " Compiling: "); TclPrintSource(stdout, parsePtr->commandStart, TclMin(commandLength, 55)); fprintf(stdout, "\n"); } #endif /* * TIP #280: Count newlines before the command start. * (See test info-30.33). */ TclAdvanceLines(&envPtr->line, p, parsePtr->commandStart); TclAdvanceContinuations(&envPtr->line, &envPtr->clNext, parsePtr->commandStart - envPtr->source); /* * Advance parser to the next command in the script. */ next = parsePtr->commandStart + parsePtr->commandSize; numBytes -= next - p; p = next; if (parsePtr->numWords == 0) { /* * The "command" parsed has no words. In this case we can skip * the rest of the loop body. With no words, clearly * CompileCommandTokens() has nothing to do. Since the parser * aggressively sucks up leading comment and white space, * including newlines, parsePtr->commandStart must be pointing at * either the end of script, or a command-terminating semi-colon. * In either case, the TclAdvance*() calls have nothing to do. * Finally, when no words are parsed, no tokens have been * allocated at parsePtr->tokenPtr so there's also nothing for * Tcl_FreeParse() to do. * * The advantage of this shortcut is that CompileCommandTokens() * can be written with an assumption that (int)parsePtr->numWords > 0, with * the implication the CCT() always generates bytecode. */ continue; } /* * Avoid stack exhaustion by too many nested calls of TclCompileScript * (considering interp recursionlimit). */ iPtr->numLevels++; lastCmdIdx = CompileCommandTokens(interp, parsePtr, envPtr); iPtr->numLevels--; /* * TIP #280: Track lines in the just compiled command. */ TclAdvanceLines(&envPtr->line, parsePtr->commandStart, p); TclAdvanceContinuations(&envPtr->line, &envPtr->clNext, p - envPtr->source); Tcl_FreeParse(parsePtr); } while (numBytes > 0); Tcl_Free(parsePtr); } if (lastCmdIdx == -1) { /* * Compiling the script yielded no bytecode. The script must be all * whitespace, comments, and empty commands. Such scripts are defined * to successfully produce the empty string result, so we emit the * simple bytecode that makes that happen. */ PushStringLiteral(envPtr, ""); } else { /* * We compiled at least one command to bytecode. The routine * CompileCommandTokens() follows the bytecode of each compiled * command with an INST_POP, so that stack balance is maintained when * several commands are in sequence. (The result of each command is * thrown away before moving on to the next command). For the last * command compiled, we need to undo that INST_POP so that the result * of the last command becomes the result of the script. The code * here removes that trailing INST_POP. */ envPtr->cmdMapPtr[lastCmdIdx].numCodeBytes--; envPtr->codeNext--; envPtr->currStackDepth++; } TclCheckStackDepth(depth+1, envPtr); } /* *---------------------------------------------------------------------- * * TclCompileTokens -- * * Given an array of tokens parsed from a Tcl command, e.g. the tokens * that make up a word, emits instructions to evaluate the * tokens and concatenate their values to form a single result value on * the interpreter's runtime evaluation stack. * * Results: * The return value is a standard Tcl result. If an error occurs, an * error message is left in the interpreter's result. * * Side effects: * Instructions are added to envPtr to push and evaluate the tokens at * runtime. * *---------------------------------------------------------------------- */ void TclCompileVarSubst( Tcl_Interp *interp, Tcl_Token *tokenPtr, CompileEnv *envPtr) { const char *p, *name = tokenPtr[1].start; Tcl_Size i, nameBytes = tokenPtr[1].size; Tcl_Size localVar; int localVarName = 1; /* * Determine how the variable name should be handled: if it contains any * namespace qualifiers it is not a local variable (localVarName=-1); if * it looks like an array element and the token has a single component, it * should not be created here [Bug 569438] (localVarName=0); otherwise, * the local variable can safely be created (localVarName=1). */ for (i = 0, p = name; i < nameBytes; i++, p++) { if ((p[0] == ':') && (i < nameBytes-1) && (p[1] == ':')) { localVarName = -1; break; } else if ((p[0] == '(') && (tokenPtr->numComponents == 1) && (name[nameBytes - 1] == ')')) { localVarName = 0; break; } } /* * Either push the variable's name, or find its index in the array * of local variables in a procedure frame. */ localVar = -1; if (localVarName != -1) { localVar = TclFindCompiledLocal(name, nameBytes, localVarName, envPtr); } if (localVar < 0) { PushLiteral(envPtr, name, nameBytes); } /* * Emit instructions to load the variable. */ TclAdvanceLines(&envPtr->line, tokenPtr[1].start, tokenPtr[1].start + tokenPtr[1].size); if (tokenPtr->numComponents == 1) { if (localVar < 0) { TclEmitOpcode(INST_LOAD_STK, envPtr); } else if (localVar <= 255) { TclEmitInstInt1(INST_LOAD_SCALAR1, localVar, envPtr); } else { TclEmitInstInt4(INST_LOAD_SCALAR4, localVar, envPtr); } } else { TclCompileTokens(interp, tokenPtr+2, tokenPtr->numComponents-1, envPtr); if (localVar < 0) { TclEmitOpcode(INST_LOAD_ARRAY_STK, envPtr); } else if (localVar <= 255) { TclEmitInstInt1(INST_LOAD_ARRAY1, localVar, envPtr); } else { TclEmitInstInt4(INST_LOAD_ARRAY4, localVar, envPtr); } } } void TclCompileTokens( Tcl_Interp *interp, /* Used for error and status reporting. */ Tcl_Token *tokenPtr, /* Pointer to first in an array of tokens to * compile. */ size_t count1, /* Number of tokens to consider at tokenPtr. * Must be at least 1. */ CompileEnv *envPtr) /* Holds the resulting instructions. */ { Tcl_DString textBuffer; /* Holds concatenated chars from adjacent * TCL_TOKEN_TEXT, TCL_TOKEN_BS tokens. */ char buffer[4] = ""; Tcl_Size i, numObjsToConcat, adjust; size_t length; unsigned char *entryCodeNext = envPtr->codeNext; #define NUM_STATIC_POS 20 int isLiteral; Tcl_Size maxNumCL, numCL; Tcl_Size *clPosition = NULL; int depth = TclGetStackDepth(envPtr); int count = count1; /* * If this is actually a literal, handle continuation lines by * preallocating a small table to store the locations of any continuation * lines found in this literal. The table is extended if needed. * * Note: In contrast with the analagous code in 'TclSubstTokens()' the * 'adjust' variable seems unneeded here. The code which merges * continuation line information of multiple words which concat'd at * runtime also seems unneeded. Either that or I have not managed to find a * test case for these two possibilities yet. It might be a difference * between compile- versus run-time processing. */ numCL = 0; maxNumCL = 0; isLiteral = 1; for (i=0 ; i < count; i++) { if ((tokenPtr[i].type != TCL_TOKEN_TEXT) && (tokenPtr[i].type != TCL_TOKEN_BS)) { isLiteral = 0; break; } } if (isLiteral) { maxNumCL = NUM_STATIC_POS; clPosition = (Tcl_Size *)Tcl_Alloc(maxNumCL * sizeof(Tcl_Size)); } adjust = 0; Tcl_DStringInit(&textBuffer); numObjsToConcat = 0; for ( ; count > 0; count--, tokenPtr++) { switch (tokenPtr->type) { case TCL_TOKEN_TEXT: TclDStringAppendToken(&textBuffer, tokenPtr); TclAdvanceLines(&envPtr->line, tokenPtr->start, tokenPtr->start + tokenPtr->size); break; case TCL_TOKEN_BS: length = TclParseBackslash(tokenPtr->start, tokenPtr->size, NULL, buffer); Tcl_DStringAppend(&textBuffer, buffer, length); /* * If the identified backslash sequence is in a literal and * represented a continuation line, compute and store its * location (as char offset to the beginning of the _result_ * script). We may have to extend the table of locations. * * The continuation line information is relevant even if the word * being processed is not a literal, as it can affect nested * commands. See the branch below for TCL_TOKEN_COMMAND, where the * adjustment being tracked here is taken into account. The good * thing is a table of everything is not needed, just the number of * lines to add as correction. */ if ((length == 1) && (buffer[0] == ' ') && (tokenPtr->start[1] == '\n')) { if (isLiteral) { int clPos = Tcl_DStringLength(&textBuffer); if (numCL >= maxNumCL) { maxNumCL *= 2; clPosition = (Tcl_Size *)Tcl_Realloc(clPosition, maxNumCL * sizeof(Tcl_Size)); } clPosition[numCL] = clPos; numCL ++; } adjust++; } break; case TCL_TOKEN_COMMAND: /* * Push any accumulated chars appearing before the command. */ if (Tcl_DStringLength(&textBuffer) > 0) { int literal = TclRegisterDStringLiteral(envPtr, &textBuffer); TclEmitPush(literal, envPtr); numObjsToConcat++; Tcl_DStringFree(&textBuffer); if (numCL) { TclContinuationsEnter(TclFetchLiteral(envPtr, literal), numCL, clPosition); } numCL = 0; } envPtr->line += adjust; TclCompileScript(interp, tokenPtr->start+1, tokenPtr->size-2, envPtr); envPtr->line -= adjust; numObjsToConcat++; break; case TCL_TOKEN_VARIABLE: /* * Push any accumulated chars appearing before the $. */ if (Tcl_DStringLength(&textBuffer) > 0) { int literal; literal = TclRegisterDStringLiteral(envPtr, &textBuffer); TclEmitPush(literal, envPtr); numObjsToConcat++; Tcl_DStringFree(&textBuffer); } TclCompileVarSubst(interp, tokenPtr, envPtr); numObjsToConcat++; count -= tokenPtr->numComponents; tokenPtr += tokenPtr->numComponents; break; default: Tcl_Panic("Unexpected token type in TclCompileTokens: %d; %.*s", tokenPtr->type, (int)tokenPtr->size, tokenPtr->start); } } /* * Push any accumulated characters appearing at the end. */ if (Tcl_DStringLength(&textBuffer) > 0) { int literal = TclRegisterDStringLiteral(envPtr, &textBuffer); TclEmitPush(literal, envPtr); numObjsToConcat++; if (numCL) { TclContinuationsEnter(TclFetchLiteral(envPtr, literal), numCL, clPosition); } numCL = 0; } /* * If necessary, concatenate the parts of the word. */ while (numObjsToConcat > 255) { TclEmitInstInt1(INST_STR_CONCAT1, 255, envPtr); numObjsToConcat -= 254; /* concat pushes 1 obj, the result */ } if (numObjsToConcat > 1) { TclEmitInstInt1(INST_STR_CONCAT1, numObjsToConcat, envPtr); } /* * If the tokens yielded no instructions, push an empty string. */ if (envPtr->codeNext == entryCodeNext) { PushStringLiteral(envPtr, ""); } Tcl_DStringFree(&textBuffer); /* * Release the temp table we used to collect the locations of continuation * lines, if any. */ if (maxNumCL) { Tcl_Free(clPosition); } TclCheckStackDepth(depth+1, envPtr); } /* *---------------------------------------------------------------------- * * TclCompileCmdWord -- * * Given an array of parse tokens for a word containing one or more Tcl * commands, emits inline instructions to execute them. In contrast with * TclCompileTokens, a simple word such as a loop body enclosed in braces * is not just pushed as a string, but is itself parsed into tokens and * compiled. * * Results: * A standard Tcl result. If an error occurs, an * error message is left in the interpreter's result. * * Side effects: * Instructions are added to envPtr to execute the tokens at runtime. * *---------------------------------------------------------------------- */ void TclCompileCmdWord( Tcl_Interp *interp, /* Used for error and status reporting. */ Tcl_Token *tokenPtr, /* Pointer to first in an array of tokens for * a command word to compile inline. */ size_t count1, /* Number of tokens to consider at tokenPtr. * Must be at least 1. */ CompileEnv *envPtr) /* Holds the resulting instructions. */ { int count = count1; if ((count == 1) && (tokenPtr->type == TCL_TOKEN_TEXT)) { /* * The common case that there is a single text token. Compile it * into an inline sequence of instructions. */ TclCompileScript(interp, tokenPtr->start, tokenPtr->size, envPtr); } else { /* * Either there are multiple tokens, or the single token involves * substitutions. Emit instructions to invoke the eval command * procedure at runtime on the result of evaluating the tokens. */ TclCompileTokens(interp, tokenPtr, count, envPtr); TclEmitInvoke(envPtr, INST_EVAL_STK); } } /* *---------------------------------------------------------------------- * * TclCompileExprWords -- * * Given an array of parse tokens representing one or more words that * contain a Tcl expression, emits inline instructions to execute the * expression. In contrast with TclCompileExpr, supports Tcl's two-level * substitution semantics for an expression that appears as command words. * * Results: * A standard Tcl result. If an error occurs, an * error message is left in the interpreter's result. * * Side effects: * Instructions are added to envPtr to execute the expression. * *---------------------------------------------------------------------- */ void TclCompileExprWords( Tcl_Interp *interp, /* Used for error and status reporting. */ Tcl_Token *tokenPtr, /* Points to first in an array of word tokens * for the expression to compile inline. */ size_t numWords1, /* Number of word tokens starting at tokenPtr. * Must be at least 1. Each word token * contains one or more subtokens. */ CompileEnv *envPtr) /* Holds the resulting instructions. */ { Tcl_Token *wordPtr; int i, concatItems; int numWords = numWords1; /* * If the expression is a single word that doesn't require substitutions, * just compile its string into inline instructions. */ if ((numWords == 1) && (tokenPtr->type == TCL_TOKEN_SIMPLE_WORD)) { TclCompileExpr(interp, tokenPtr[1].start,tokenPtr[1].size, envPtr, 1); return; } /* * Emit code to call the expr command proc at runtime. Concatenate the * (already substituted once) expr tokens with a space between each. */ wordPtr = tokenPtr; for (i = 0; i < numWords; i++) { CompileTokens(envPtr, wordPtr, interp); if (i < (numWords - 1)) { PushStringLiteral(envPtr, " "); } wordPtr += wordPtr->numComponents + 1; } concatItems = 2*numWords - 1; while (concatItems > 255) { TclEmitInstInt1(INST_STR_CONCAT1, 255, envPtr); concatItems -= 254; } if (concatItems > 1) { TclEmitInstInt1(INST_STR_CONCAT1, concatItems, envPtr); } TclEmitOpcode(INST_EXPR_STK, envPtr); } /* *---------------------------------------------------------------------- * * TclCompileNoOp -- * * Compiles no-op's * * Results: * TCL_OK if completion was successful. * * Side effects: * Instructions are added to envPtr to execute a no-op at runtime. No * result is pushed onto the stack: the compiler has to take care of this * itself if the last compiled command is a NoOp. * *---------------------------------------------------------------------- */ int TclCompileNoOp( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ TCL_UNUSED(Command *), CompileEnv *envPtr) /* Holds resulting instructions. */ { Tcl_Token *tokenPtr; int i; tokenPtr = parsePtr->tokenPtr; for (i = 1; i < (int)parsePtr->numWords; i++) { tokenPtr = tokenPtr + tokenPtr->numComponents + 1; if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { CompileTokens(envPtr, tokenPtr, interp); TclEmitOpcode(INST_POP, envPtr); } } PushStringLiteral(envPtr, ""); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclInitByteCodeObj -- * * Creates a ByteCode structure and initializes it from a CompileEnv * compilation environment structure. The ByteCode structure is smaller * and contains just that information needed to execute the bytecode * instructions resulting from compiling a Tcl script. The resulting * structure is placed in the specified object. * * Results: * A newly-constructed ByteCode object is stored in the internal * representation of the objPtr. * * Side effects: * A single heap object is allocated to hold the new ByteCode structure * and its code, object, command location, and aux data arrays. Note that * "ownership" (i.e., the pointers to) the Tcl objects and aux data items * will be handed over to the new ByteCode structure from the CompileEnv * structure. * *---------------------------------------------------------------------- */ static void PreventCycle( Tcl_Obj *objPtr, CompileEnv *envPtr) { Tcl_Size i; for (i = 0; i < envPtr->literalArrayNext; i++) { if (objPtr == TclFetchLiteral(envPtr, i)) { /* * Prevent circular reference where the bytecode internalrep of * a value contains a literal which is that same value. * If this is allowed to happen, refcount decrements may not * reach zero, and memory may leak. Bugs 467523, 3357771 * * NOTE: [Bugs 3392070, 3389764] We make a copy based completely * on the string value, and do not call Tcl_DuplicateObj() so we * can be sure we do not have any lingering cycles hiding in * the internalrep. */ Tcl_Size numBytes; const char *bytes = TclGetStringFromObj(objPtr, &numBytes); Tcl_Obj *copyPtr = Tcl_NewStringObj(bytes, numBytes); Tcl_IncrRefCount(copyPtr); TclReleaseLiteral((Tcl_Interp *)envPtr->iPtr, objPtr); envPtr->literalArrayPtr[i].objPtr = copyPtr; } } } ByteCode * TclInitByteCode( CompileEnv *envPtr)/* Points to the CompileEnv structure from * which to create a ByteCode structure. */ { ByteCode *codePtr; size_t codeBytes, objArrayBytes, exceptArrayBytes, cmdLocBytes; size_t auxDataArrayBytes, structureSize; unsigned char *p; #ifdef TCL_COMPILE_DEBUG unsigned char *nextPtr; #endif int numLitObjects = envPtr->literalArrayNext; Namespace *namespacePtr; int i, isNew; Interp *iPtr; if (envPtr->iPtr == NULL) { Tcl_Panic("TclInitByteCodeObj() called on uninitialized CompileEnv"); } iPtr = envPtr->iPtr; codeBytes = envPtr->codeNext - envPtr->codeStart; objArrayBytes = envPtr->literalArrayNext * sizeof(Tcl_Obj *); exceptArrayBytes = envPtr->exceptArrayNext * sizeof(ExceptionRange); auxDataArrayBytes = envPtr->auxDataArrayNext * sizeof(AuxData); cmdLocBytes = GetCmdLocEncodingSize(envPtr); /* * Compute the total number of bytes needed for this bytecode. * * Note that code bytes need not be aligned but since later elements are we * need to pad anyway, either directly after ByteCode or after codeBytes, * and it's easier and more consistent to do the former. */ structureSize = TCL_ALIGN(sizeof(ByteCode)); /* align code bytes */ structureSize += TCL_ALIGN(codeBytes); /* align object array */ structureSize += TCL_ALIGN(objArrayBytes); /* align exc range arr */ structureSize += TCL_ALIGN(exceptArrayBytes); /* align AuxData array */ structureSize += auxDataArrayBytes; structureSize += cmdLocBytes; if (envPtr->iPtr->varFramePtr != NULL) { namespacePtr = envPtr->iPtr->varFramePtr->nsPtr; } else { namespacePtr = envPtr->iPtr->globalNsPtr; } p = (unsigned char *)Tcl_Alloc(structureSize); codePtr = (ByteCode *) p; codePtr->interpHandle = TclHandlePreserve(iPtr->handle); codePtr->compileEpoch = iPtr->compileEpoch; codePtr->nsPtr = namespacePtr; codePtr->nsEpoch = namespacePtr->resolverEpoch; codePtr->refCount = 0; TclPreserveByteCode(codePtr); if (namespacePtr->compiledVarResProc || iPtr->resolverPtr) { codePtr->flags = TCL_BYTECODE_RESOLVE_VARS; } else { codePtr->flags = 0; } codePtr->source = envPtr->source; codePtr->procPtr = envPtr->procPtr; codePtr->numCommands = envPtr->numCommands; codePtr->numSrcBytes = envPtr->numSrcBytes; codePtr->numCodeBytes = codeBytes; codePtr->numLitObjects = numLitObjects; codePtr->numExceptRanges = envPtr->exceptArrayNext; codePtr->numAuxDataItems = envPtr->auxDataArrayNext; codePtr->numCmdLocBytes = cmdLocBytes; codePtr->maxExceptDepth = envPtr->maxExceptDepth; codePtr->maxStackDepth = envPtr->maxStackDepth; p += TCL_ALIGN(sizeof(ByteCode)); /* align code bytes */ codePtr->codeStart = p; memcpy(p, envPtr->codeStart, codeBytes); p += TCL_ALIGN(codeBytes); /* align object array */ codePtr->objArrayPtr = (Tcl_Obj **) p; for (i = 0; i < numLitObjects; i++) { codePtr->objArrayPtr[i] = TclFetchLiteral(envPtr, i); } p += TCL_ALIGN(objArrayBytes); /* align exception range array */ if (exceptArrayBytes > 0) { codePtr->exceptArrayPtr = (ExceptionRange *) p; memcpy(p, envPtr->exceptArrayPtr, exceptArrayBytes); } else { codePtr->exceptArrayPtr = NULL; } p += TCL_ALIGN(exceptArrayBytes); /* align AuxData array */ if (auxDataArrayBytes > 0) { codePtr->auxDataArrayPtr = (AuxData *) p; memcpy(p, envPtr->auxDataArrayPtr, auxDataArrayBytes); } else { codePtr->auxDataArrayPtr = NULL; } p += auxDataArrayBytes; #ifndef TCL_COMPILE_DEBUG EncodeCmdLocMap(envPtr, codePtr, (unsigned char *) p); #else nextPtr = EncodeCmdLocMap(envPtr, codePtr, (unsigned char *) p); if (((size_t)(nextPtr - p)) != cmdLocBytes) { Tcl_Panic("TclInitByteCodeObj: encoded cmd location bytes %lu != expected size %lu", (unsigned long)(nextPtr - p), (unsigned long)cmdLocBytes); } #endif /* * Record various compilation-related statistics about the new ByteCode * structure. Don't include overhead for statistics-related fields. */ #ifdef TCL_COMPILE_STATS codePtr->structureSize = structureSize - (sizeof(size_t) + sizeof(Tcl_Time)); Tcl_GetTime(&codePtr->createTime); RecordByteCodeStats(codePtr); #endif /* TCL_COMPILE_STATS */ /* * TIP #280. Associate the extended per-word line information with the * byte code object (internal rep), for use with the bc compiler. */ Tcl_SetHashValue(Tcl_CreateHashEntry(iPtr->lineBCPtr, codePtr, &isNew), envPtr->extCmdMapPtr); envPtr->extCmdMapPtr = NULL; /* We've used up the CompileEnv. Mark as uninitialized. */ envPtr->iPtr = NULL; codePtr->localCachePtr = NULL; return codePtr; } ByteCode * TclInitByteCodeObj( Tcl_Obj *objPtr, /* Points object that should be initialized, * and whose string rep contains the source * code. */ const Tcl_ObjType *typePtr, CompileEnv *envPtr)/* Points to the CompileEnv structure from * which to create a ByteCode structure. */ { ByteCode *codePtr; PreventCycle(objPtr, envPtr); codePtr = TclInitByteCode(envPtr); /* * Free the old internal rep then convert the object to a bytecode object * by making its internal rep point to the just compiled ByteCode. */ ByteCodeSetInternalRep(objPtr, typePtr, codePtr); return codePtr; } /* *---------------------------------------------------------------------- * * TclFindCompiledLocal -- * * This procedure is called at compile time to look up and optionally * allocate an entry ("slot") for a variable in a procedure's array of * local variables. If the variable's name is NULL, a new temporary * variable is always created. (Such temporary variables can only be * referenced using their slot index.) * * Results: * If create is 0 and the name is non-NULL, then if the variable is * found, the index of its entry in the procedure's array of local * variables is returned; otherwise -1 is returned. If name is NULL, the * index of a new temporary variable is returned. Finally, if create is 1 * and name is non-NULL, the index of a new entry is returned. * * Side effects: * Creates and registers a new local variable if create is 1 and the * variable is unknown, or if the name is NULL. * *---------------------------------------------------------------------- */ Tcl_Size TclFindCompiledLocal( const char *name, /* Points to first character of the name of a * scalar or array variable. If NULL, a * temporary var should be created. */ Tcl_Size nameBytes, /* Number of bytes in the name. */ int create, /* If 1, allocate a local frame entry for the * variable if it is new. */ CompileEnv *envPtr) /* Points to the current compile environment*/ { CompiledLocal *localPtr; Tcl_Size localVar = TCL_INDEX_NONE; Tcl_Size i; Proc *procPtr; /* * If not creating a temporary, does a local variable of the specified * name already exist? */ procPtr = envPtr->procPtr; if (procPtr == NULL) { /* * Compiling a non-body script: give it read access to the LVT in the * current localCache */ LocalCache *cachePtr = envPtr->iPtr->varFramePtr->localCachePtr; const char *localName; Tcl_Obj **varNamePtr; Tcl_Size len; if (!cachePtr || !name) { return TCL_INDEX_NONE; } varNamePtr = &cachePtr->varName0; for (i=0; i < cachePtr->numVars; varNamePtr++, i++) { if (*varNamePtr) { localName = TclGetStringFromObj(*varNamePtr, &len); if ((len == nameBytes) && !strncmp(name, localName, len)) { return i; } } } return TCL_INDEX_NONE; } if (name != NULL) { Tcl_Size localCt = procPtr->numCompiledLocals; localPtr = procPtr->firstLocalPtr; for (i = 0; i < localCt; i++) { if (!TclIsVarTemporary(localPtr)) { char *localName = localPtr->name; if ((nameBytes == localPtr->nameLength) && (strncmp(name,localName,nameBytes) == 0)) { return i; } } localPtr = localPtr->nextPtr; } } /* * Create a new variable if appropriate. */ if (create || (name == NULL)) { localVar = procPtr->numCompiledLocals; localPtr = (CompiledLocal *)Tcl_Alloc(offsetof(CompiledLocal, name) + 1U + nameBytes); if (procPtr->firstLocalPtr == NULL) { procPtr->firstLocalPtr = procPtr->lastLocalPtr = localPtr; } else { procPtr->lastLocalPtr->nextPtr = localPtr; procPtr->lastLocalPtr = localPtr; } localPtr->nextPtr = NULL; localPtr->nameLength = nameBytes; localPtr->frameIndex = localVar; localPtr->flags = 0; if (name == NULL) { localPtr->flags |= VAR_TEMPORARY; } localPtr->defValuePtr = NULL; localPtr->resolveInfo = NULL; if (name != NULL) { memcpy(localPtr->name, name, nameBytes); } localPtr->name[nameBytes] = '\0'; procPtr->numCompiledLocals++; } return localVar; } /* *---------------------------------------------------------------------- * * TclExpandCodeArray -- * * Uses malloc to allocate more storage for a CompileEnv's code array. * * Results: * None. * * Side effects: * The size of the bytecode array is doubled. If envPtr->mallocedCodeArray * is non-zero the old array is freed. Byte codes are copied from the old * array to the new one. * *---------------------------------------------------------------------- */ void TclExpandCodeArray( void *envArgPtr) /* Points to the CompileEnv whose code array * must be enlarged. */ { CompileEnv *envPtr = (CompileEnv *)envArgPtr; /* The CompileEnv containing the code array to * be doubled in size. */ /* * envPtr->codeNext is equal to envPtr->codeEnd. The currently defined * code bytes are stored between envPtr->codeStart and envPtr->codeNext-1 * [inclusive]. */ size_t currBytes = envPtr->codeNext - envPtr->codeStart; size_t newBytes = 2 * (envPtr->codeEnd - envPtr->codeStart); if (envPtr->mallocedCodeArray) { envPtr->codeStart = (unsigned char *)Tcl_Realloc(envPtr->codeStart, newBytes); } else { /* * envPtr->exceptArrayPtr isn't a Tcl_Alloc'd pointer, so * perform the equivalent of Tcl_Realloc directly. */ unsigned char *newPtr = (unsigned char *)Tcl_Alloc(newBytes); memcpy(newPtr, envPtr->codeStart, currBytes); envPtr->codeStart = newPtr; envPtr->mallocedCodeArray = 1; } envPtr->codeNext = envPtr->codeStart + currBytes; envPtr->codeEnd = envPtr->codeStart + newBytes; } /* *---------------------------------------------------------------------- * * EnterCmdStartData -- * * Registers the starting source and bytecode location of a command. This * information is used at runtime to map between instruction pc and * source locations. * * Results: * None. * * Side effects: * Inserts source and code location information into the compilation * environment envPtr for the command at index cmdIndex. The compilation * environment's CmdLocation array is grown if necessary. * *---------------------------------------------------------------------- */ static void EnterCmdStartData( CompileEnv *envPtr, /* Points to the compilation environment * structure in which to enter command * location information. */ Tcl_Size cmdIndex, /* Index of the command whose start data is * being set. */ Tcl_Size srcOffset, /* Offset of first char of the command. */ Tcl_Size codeOffset) /* Offset of first byte of command code. */ { CmdLocation *cmdLocPtr; if (cmdIndex < 0 || cmdIndex >= envPtr->numCommands) { Tcl_Panic("EnterCmdStartData: bad command index %" TCL_Z_MODIFIER "u", cmdIndex); } if (cmdIndex >= envPtr->cmdMapEnd) { /* * Expand the command location array by allocating more storage from * the heap. The currently allocated CmdLocation entries are stored * from cmdMapPtr[0] up to cmdMapPtr[envPtr->cmdMapEnd] (inclusive). */ size_t currElems = envPtr->cmdMapEnd; size_t newElems = 2 * currElems; size_t currBytes = currElems * sizeof(CmdLocation); size_t newBytes = newElems * sizeof(CmdLocation); if (envPtr->mallocedCmdMap) { envPtr->cmdMapPtr = (CmdLocation *)Tcl_Realloc(envPtr->cmdMapPtr, newBytes); } else { /* * envPtr->cmdMapPtr isn't a Tcl_Alloc'd pointer, so we must code a * Tcl_Realloc equivalent for ourselves. */ CmdLocation *newPtr = (CmdLocation *)Tcl_Alloc(newBytes); memcpy(newPtr, envPtr->cmdMapPtr, currBytes); envPtr->cmdMapPtr = newPtr; envPtr->mallocedCmdMap = 1; } envPtr->cmdMapEnd = newElems; } if (cmdIndex > 0) { if (codeOffset < envPtr->cmdMapPtr[cmdIndex-1].codeOffset) { Tcl_Panic("EnterCmdStartData: cmd map not sorted by code offset"); } } cmdLocPtr = &envPtr->cmdMapPtr[cmdIndex]; cmdLocPtr->codeOffset = codeOffset; cmdLocPtr->srcOffset = srcOffset; cmdLocPtr->numSrcBytes = TCL_INDEX_NONE; cmdLocPtr->numCodeBytes = TCL_INDEX_NONE; } /* *---------------------------------------------------------------------- * * EnterCmdExtentData -- * * Registers the source and bytecode length for a command. This * information is used at runtime to map between instruction pc and * source locations. * * Results: * None. * * Side effects: * Inserts source and code length information into the compilation * environment envPtr for the command at index cmdIndex. Starting source * and bytecode information for the command must already have been * registered. * *---------------------------------------------------------------------- */ static void EnterCmdExtentData( CompileEnv *envPtr, /* Points to the compilation environment * structure in which to enter command * location information. */ Tcl_Size cmdIndex, /* Index of the command whose source and code * length data is being set. */ Tcl_Size numSrcBytes, /* Number of command source chars. */ Tcl_Size numCodeBytes) /* Offset of last byte of command code. */ { CmdLocation *cmdLocPtr; if (cmdIndex < 0 || cmdIndex >= envPtr->numCommands) { Tcl_Panic("EnterCmdExtentData: bad command index %" TCL_Z_MODIFIER "u", cmdIndex); } if (cmdIndex > envPtr->cmdMapEnd) { Tcl_Panic("EnterCmdExtentData: missing start data for command %" TCL_Z_MODIFIER "u", cmdIndex); } cmdLocPtr = &envPtr->cmdMapPtr[cmdIndex]; cmdLocPtr->numSrcBytes = numSrcBytes; cmdLocPtr->numCodeBytes = numCodeBytes; } /* *---------------------------------------------------------------------- * TIP #280 * * EnterCmdWordData -- * * Registers the lines for the words of a command. This information is * used at runtime by 'info frame'. * * Results: * None. * * Side effects: * Inserts word location information into the compilation environment * envPtr for the command at index cmdIndex. The compilation * environment's ExtCmdLoc.ECL array is grown if necessary. * *---------------------------------------------------------------------- */ static void EnterCmdWordData( ExtCmdLoc *eclPtr, /* Points to the map environment structure in * which to enter command location * information. */ Tcl_Size srcOffset, /* Offset of first char of the command. */ Tcl_Token *tokenPtr, const char *cmd, Tcl_Size numWords, Tcl_Size line, Tcl_Size *clNext, Tcl_Size **wlines, CompileEnv *envPtr) { ECL *ePtr; const char *last; Tcl_Size wordIdx, wordLine; Tcl_Size *wwlines, *wordNext; if (eclPtr->nuloc >= eclPtr->nloc) { /* * Expand the ECL array by allocating more storage from the heap. The * currently allocated ECL entries are stored from eclPtr->loc[0] up * to eclPtr->loc[eclPtr->nuloc-1] (inclusive). */ size_t currElems = eclPtr->nloc; size_t newElems = (currElems ? 2*currElems : 1); size_t newBytes = newElems * sizeof(ECL); eclPtr->loc = (ECL *)Tcl_Realloc(eclPtr->loc, newBytes); eclPtr->nloc = newElems; } ePtr = &eclPtr->loc[eclPtr->nuloc]; ePtr->srcOffset = srcOffset; ePtr->line = (Tcl_Size *)Tcl_Alloc(numWords * sizeof(Tcl_Size)); ePtr->next = (Tcl_Size **)Tcl_Alloc(numWords * sizeof(Tcl_Size *)); ePtr->nline = numWords; wwlines = (Tcl_Size *)Tcl_Alloc(numWords * sizeof(Tcl_Size)); last = cmd; wordLine = line; wordNext = clNext; for (wordIdx=0 ; wordIdxnumComponents + 1) { TclAdvanceLines(&wordLine, last, tokenPtr->start); TclAdvanceContinuations(&wordLine, &wordNext, tokenPtr->start - envPtr->source); /* See Ticket 4b61afd660 */ wwlines[wordIdx] = ((wordIdx == 0) || TclWordKnownAtCompileTime(tokenPtr, NULL)) ? wordLine : -1; ePtr->line[wordIdx] = wordLine; ePtr->next[wordIdx] = wordNext; last = tokenPtr->start; } *wlines = wwlines; eclPtr->nuloc ++; } /* *---------------------------------------------------------------------- * * TclCreateExceptRange -- * * Procedure that allocates and initializes a new ExceptionRange * structure of the specified kind in a CompileEnv. * * Results: * Returns the index for the newly created ExceptionRange. * * Side effects: * If there is not enough room in the CompileEnv's ExceptionRange array, * the array in expanded: a new array of double the size is allocated, if * envPtr->mallocedExceptArray is non-zero the old array is freed, and * ExceptionRange entries are copied from the old array to the new one. * *---------------------------------------------------------------------- */ Tcl_Size TclCreateExceptRange( ExceptionRangeType type, /* The kind of ExceptionRange desired. */ CompileEnv *envPtr)/* Points to CompileEnv for which to create a * new ExceptionRange structure. */ { ExceptionRange *rangePtr; ExceptionAux *auxPtr; Tcl_Size index = envPtr->exceptArrayNext; if (index >= envPtr->exceptArrayEnd) { /* * Expand the ExceptionRange array. The currently allocated entries * are stored between elements 0 and (envPtr->exceptArrayNext - 1) * [inclusive]. */ size_t currBytes = envPtr->exceptArrayNext * sizeof(ExceptionRange); size_t currBytes2 = envPtr->exceptArrayNext * sizeof(ExceptionAux); size_t newElems = 2*envPtr->exceptArrayEnd; size_t newBytes = newElems * sizeof(ExceptionRange); size_t newBytes2 = newElems * sizeof(ExceptionAux); if (envPtr->mallocedExceptArray) { envPtr->exceptArrayPtr = (ExceptionRange *)Tcl_Realloc(envPtr->exceptArrayPtr, newBytes); envPtr->exceptAuxArrayPtr = (ExceptionAux *)Tcl_Realloc(envPtr->exceptAuxArrayPtr, newBytes2); } else { /* * envPtr->exceptArrayPtr isn't a Tcl_Alloc'd pointer, so we must * code a Tcl_Realloc equivalent for ourselves. */ ExceptionRange *newPtr = (ExceptionRange *)Tcl_Alloc(newBytes); ExceptionAux *newPtr2 = (ExceptionAux *)Tcl_Alloc(newBytes2); memcpy(newPtr, envPtr->exceptArrayPtr, currBytes); memcpy(newPtr2, envPtr->exceptAuxArrayPtr, currBytes2); envPtr->exceptArrayPtr = newPtr; envPtr->exceptAuxArrayPtr = newPtr2; envPtr->mallocedExceptArray = 1; } envPtr->exceptArrayEnd = newElems; } envPtr->exceptArrayNext++; rangePtr = &envPtr->exceptArrayPtr[index]; rangePtr->type = type; rangePtr->nestingLevel = envPtr->exceptDepth; rangePtr->codeOffset = TCL_INDEX_NONE; rangePtr->numCodeBytes = TCL_INDEX_NONE; rangePtr->breakOffset = TCL_INDEX_NONE; rangePtr->continueOffset = TCL_INDEX_NONE; rangePtr->catchOffset = TCL_INDEX_NONE; auxPtr = &envPtr->exceptAuxArrayPtr[index]; auxPtr->supportsContinue = 1; auxPtr->stackDepth = envPtr->currStackDepth; auxPtr->expandTarget = envPtr->expandCount; auxPtr->expandTargetDepth = TCL_INDEX_NONE; auxPtr->numBreakTargets = 0; auxPtr->breakTargets = NULL; auxPtr->allocBreakTargets = 0; auxPtr->numContinueTargets = 0; auxPtr->continueTargets = NULL; auxPtr->allocContinueTargets = 0; return index; } /* * --------------------------------------------------------------------- * * TclGetInnermostExceptionRange -- * * Returns the innermost exception range that covers the current code * creation point, and optionally the stack depth that is expected at * that point. Relies on the fact that the range has a numCodeBytes = -1 * when it is being populated and that inner ranges come after outer * ranges. * * --------------------------------------------------------------------- */ ExceptionRange * TclGetInnermostExceptionRange( CompileEnv *envPtr, int returnCode, ExceptionAux **auxPtrPtr) { size_t i = envPtr->exceptArrayNext; ExceptionRange *rangePtr = envPtr->exceptArrayPtr + i; while (i > 0) { rangePtr--; i--; if (CurrentOffset(envPtr) >= (int)rangePtr->codeOffset && (rangePtr->numCodeBytes == TCL_INDEX_NONE || CurrentOffset(envPtr) < (int)rangePtr->codeOffset+(int)rangePtr->numCodeBytes) && (returnCode != TCL_CONTINUE || envPtr->exceptAuxArrayPtr[i].supportsContinue)) { if (auxPtrPtr) { *auxPtrPtr = envPtr->exceptAuxArrayPtr + i; } return rangePtr; } } return NULL; } /* * --------------------------------------------------------------------- * * TclAddLoopBreakFixup, TclAddLoopContinueFixup -- * * Adds a place that wants to break/continue to the loop exception range * tracking that will be fixed up once the loop can be finalized. These * functions generate an INST_JUMP4 that is fixed up during the * loop finalization. * * --------------------------------------------------------------------- */ void TclAddLoopBreakFixup( CompileEnv *envPtr, ExceptionAux *auxPtr) { int range = auxPtr - envPtr->exceptAuxArrayPtr; if (envPtr->exceptArrayPtr[range].type != LOOP_EXCEPTION_RANGE) { Tcl_Panic("trying to add 'break' fixup to full exception range"); } if (++auxPtr->numBreakTargets > auxPtr->allocBreakTargets) { auxPtr->allocBreakTargets *= 2; auxPtr->allocBreakTargets += 2; if (auxPtr->breakTargets) { auxPtr->breakTargets = (size_t *)Tcl_Realloc(auxPtr->breakTargets, sizeof(size_t) * auxPtr->allocBreakTargets); } else { auxPtr->breakTargets = (size_t *)Tcl_Alloc(sizeof(size_t) * auxPtr->allocBreakTargets); } } auxPtr->breakTargets[auxPtr->numBreakTargets - 1] = CurrentOffset(envPtr); TclEmitInstInt4(INST_JUMP4, 0, envPtr); } void TclAddLoopContinueFixup( CompileEnv *envPtr, ExceptionAux *auxPtr) { int range = auxPtr - envPtr->exceptAuxArrayPtr; if (envPtr->exceptArrayPtr[range].type != LOOP_EXCEPTION_RANGE) { Tcl_Panic("trying to add 'continue' fixup to full exception range"); } if (++auxPtr->numContinueTargets > auxPtr->allocContinueTargets) { auxPtr->allocContinueTargets *= 2; auxPtr->allocContinueTargets += 2; if (auxPtr->continueTargets) { auxPtr->continueTargets = (size_t *)Tcl_Realloc(auxPtr->continueTargets, sizeof(size_t) * auxPtr->allocContinueTargets); } else { auxPtr->continueTargets = (size_t *)Tcl_Alloc(sizeof(size_t) * auxPtr->allocContinueTargets); } } auxPtr->continueTargets[auxPtr->numContinueTargets - 1] = CurrentOffset(envPtr); TclEmitInstInt4(INST_JUMP4, 0, envPtr); } /* * --------------------------------------------------------------------- * * TclCleanupStackForBreakContinue -- * * Removes the extra elements from the auxiliary stack and the main stack. * How this is done depends on whether there are any elements on * the auxiliary stack to pop. * * --------------------------------------------------------------------- */ void TclCleanupStackForBreakContinue( CompileEnv *envPtr, ExceptionAux *auxPtr) { size_t savedStackDepth = envPtr->currStackDepth; int toPop = envPtr->expandCount - auxPtr->expandTarget; if (toPop > 0) { while (toPop --> 0) { TclEmitOpcode(INST_EXPAND_DROP, envPtr); } TclAdjustStackDepth((int)(auxPtr->expandTargetDepth - envPtr->currStackDepth), envPtr); envPtr->currStackDepth = auxPtr->expandTargetDepth; } toPop = envPtr->currStackDepth - auxPtr->stackDepth; while (toPop --> 0) { TclEmitOpcode(INST_POP, envPtr); } envPtr->currStackDepth = savedStackDepth; } /* * --------------------------------------------------------------------- * * StartExpanding -- * * Pushes an INST_EXPAND_START and does some additional housekeeping so * that the [break] and [continue] compilers can use an exception-free * issue to discard it. * * --------------------------------------------------------------------- */ static void StartExpanding( CompileEnv *envPtr) { int i; TclEmitOpcode(INST_EXPAND_START, envPtr); /* * Update inner exception ranges with information about the environment * where this expansion started. */ for (i=0 ; i<(int)envPtr->exceptArrayNext ; i++) { ExceptionRange *rangePtr = &envPtr->exceptArrayPtr[i]; ExceptionAux *auxPtr = &envPtr->exceptAuxArrayPtr[i]; /* * Ignore loops unless they're still being built. */ if ((int)rangePtr->codeOffset > CurrentOffset(envPtr)) { continue; } if (rangePtr->numCodeBytes != TCL_INDEX_NONE) { continue; } /* * Adequate condition: loops further out and exceptions further in * don't actually need this information. */ if (auxPtr->expandTarget == envPtr->expandCount) { auxPtr->expandTargetDepth = envPtr->currStackDepth; } } /* * One more expansion is now being processed on the auxiliary stack. */ envPtr->expandCount++; } /* * --------------------------------------------------------------------- * * TclFinalizeLoopExceptionRange -- * * Finalizes a loop exception range, binding the registered [break] and * [continue] implementations so that they jump to the correct place. * This must be called only after *all* the exception range * target offsets have been set. * * --------------------------------------------------------------------- */ void TclFinalizeLoopExceptionRange( CompileEnv *envPtr, int range) { ExceptionRange *rangePtr = &envPtr->exceptArrayPtr[range]; ExceptionAux *auxPtr = &envPtr->exceptAuxArrayPtr[range]; int i, offset; unsigned char *site; if (rangePtr->type != LOOP_EXCEPTION_RANGE) { Tcl_Panic("trying to finalize a loop exception range"); } /* * Do the jump fixups. Note that these are always issued as INST_JUMP4 so * there is no need to fuss around with updating code offsets. */ for (i=0 ; i<(int)auxPtr->numBreakTargets ; i++) { site = envPtr->codeStart + auxPtr->breakTargets[i]; offset = rangePtr->breakOffset - auxPtr->breakTargets[i]; TclUpdateInstInt4AtPc(INST_JUMP4, offset, site); } for (i=0 ; i<(int)auxPtr->numContinueTargets ; i++) { site = envPtr->codeStart + auxPtr->continueTargets[i]; if (rangePtr->continueOffset == TCL_INDEX_NONE) { int j; /* * WTF? Can't bind, so revert to an INST_CONTINUE. Not enough * space to do anything else. */ *site = INST_CONTINUE; for (j=0 ; j<4 ; j++) { *++site = INST_NOP; } } else { offset = rangePtr->continueOffset - auxPtr->continueTargets[i]; TclUpdateInstInt4AtPc(INST_JUMP4, offset, site); } } /* * Drop the arrays we were holding the only reference to. */ if (auxPtr->breakTargets) { Tcl_Free(auxPtr->breakTargets); auxPtr->breakTargets = NULL; auxPtr->numBreakTargets = 0; } if (auxPtr->continueTargets) { Tcl_Free(auxPtr->continueTargets); auxPtr->continueTargets = NULL; auxPtr->numContinueTargets = 0; } } /* *---------------------------------------------------------------------- * * TclCreateAuxData -- * * Allocates and initializes a new AuxData structure in a * CompileEnv's array of compilation auxiliary data records. These * AuxData records hold information created during compilation by * CompileProcs and used by instructions during execution. * * Results: * The index of the newly-created AuxData structure in the array. * * Side effects: * If there is not enough room in the CompileEnv's AuxData array, its size * is doubled. *---------------------------------------------------------------------- */ Tcl_Size TclCreateAuxData( void *clientData, /* The compilation auxiliary data to store in * the new aux data record. */ const AuxDataType *typePtr, /* Pointer to the type to attach to this * AuxData */ CompileEnv *envPtr)/* Points to the CompileEnv for which a new * aux data structure is to be allocated. */ { Tcl_Size index; /* Index for the new AuxData structure. */ AuxData *auxDataPtr; /* Points to the new AuxData structure */ index = envPtr->auxDataArrayNext; if (index >= envPtr->auxDataArrayEnd) { /* * Expand the AuxData array. The currently allocated entries are * stored between elements 0 and (envPtr->auxDataArrayNext - 1) * [inclusive]. */ size_t currBytes = envPtr->auxDataArrayNext * sizeof(AuxData); size_t newElems = 2*envPtr->auxDataArrayEnd; size_t newBytes = newElems * sizeof(AuxData); if (envPtr->mallocedAuxDataArray) { envPtr->auxDataArrayPtr = (AuxData *)Tcl_Realloc(envPtr->auxDataArrayPtr, newBytes); } else { /* * envPtr->auxDataArrayPtr isn't a Tcl_Alloc'd pointer, so we must * code a Tcl_Realloc equivalent for ourselves. */ AuxData *newPtr = (AuxData *)Tcl_Alloc(newBytes); memcpy(newPtr, envPtr->auxDataArrayPtr, currBytes); envPtr->auxDataArrayPtr = newPtr; envPtr->mallocedAuxDataArray = 1; } envPtr->auxDataArrayEnd = newElems; } envPtr->auxDataArrayNext++; auxDataPtr = &envPtr->auxDataArrayPtr[index]; auxDataPtr->clientData = clientData; auxDataPtr->type = typePtr; return index; } /* *---------------------------------------------------------------------- * * TclInitJumpFixupArray -- * * Initializes a JumpFixupArray structure to hold some number of jump * fixup entries. * * Results: * None. * * Side effects: * The JumpFixupArray structure is initialized. * *---------------------------------------------------------------------- */ void TclInitJumpFixupArray( JumpFixupArray *fixupArrayPtr) /* Points to the JumpFixupArray structure to * initialize. */ { fixupArrayPtr->fixup = fixupArrayPtr->staticFixupSpace; fixupArrayPtr->next = 0; fixupArrayPtr->end = JUMPFIXUP_INIT_ENTRIES - 1; fixupArrayPtr->mallocedArray = 0; } /* *---------------------------------------------------------------------- * * TclExpandJumpFixupArray -- * * Uses malloc to allocate more storage for a jump fixup array. * * Results: * None. * * Side effects: * The jump fixup array in *fixupArrayPtr is reallocated to a new array * of double the size, and if fixupArrayPtr->mallocedArray is non-zero * the old array is freed. Jump fixup structures are copied from the old * array to the new one. * *---------------------------------------------------------------------- */ void TclExpandJumpFixupArray( JumpFixupArray *fixupArrayPtr) /* Points to the JumpFixupArray structure to * enlarge. */ { /* * The currently allocated jump fixup entries are stored from fixup[0] up * to fixup[fixupArrayPtr->fixupNext] (*not* inclusive). We assume * fixupArrayPtr->fixupNext is equal to fixupArrayPtr->fixupEnd. */ size_t currBytes = fixupArrayPtr->next * sizeof(JumpFixup); size_t newElems = 2*(fixupArrayPtr->end + 1); size_t newBytes = newElems * sizeof(JumpFixup); if (fixupArrayPtr->mallocedArray) { fixupArrayPtr->fixup = (JumpFixup *)Tcl_Realloc(fixupArrayPtr->fixup, newBytes); } else { /* * fixupArrayPtr->fixup isn't a Tcl_Alloc'd pointer, so we must code a * Tcl_Realloc equivalent for ourselves. */ JumpFixup *newPtr = (JumpFixup *)Tcl_Alloc(newBytes); memcpy(newPtr, fixupArrayPtr->fixup, currBytes); fixupArrayPtr->fixup = newPtr; fixupArrayPtr->mallocedArray = 1; } fixupArrayPtr->end = newElems; } /* *---------------------------------------------------------------------- * * TclFreeJumpFixupArray -- * * Free any storage allocated in a jump fixup array structure. * * Results: * None. * * Side effects: * Allocated storage in the JumpFixupArray structure is freed. * *---------------------------------------------------------------------- */ void TclFreeJumpFixupArray( JumpFixupArray *fixupArrayPtr) /* Points to the JumpFixupArray structure to * free. */ { if (fixupArrayPtr->mallocedArray) { Tcl_Free(fixupArrayPtr->fixup); } } /* *---------------------------------------------------------------------- * * TclEmitForwardJump -- * * Emits a two-byte forward jump of kind "jumpType". Also initializes a * JumpFixup record with information about the jump. Since may later be * necessary to increase the size of the jump instruction to five bytes if * the jump target is more than, say, 127 bytes away. * * * Results: * None. * * Side effects: * The JumpFixup record pointed to by "jumpFixupPtr" is initialized with * information needed later if the jump is to be grown. Also, a two byte * jump of the designated type is emitted at the current point in the * bytecode stream. * *---------------------------------------------------------------------- */ void TclEmitForwardJump( CompileEnv *envPtr, /* Points to the CompileEnv structure that * holds the resulting instruction. */ TclJumpType jumpType, /* Indicates the kind of jump: if true or * false or unconditional. */ JumpFixup *jumpFixupPtr) /* Points to the JumpFixup structure to * initialize with information about this * forward jump. */ { /* * Initialize the JumpFixup structure: * - codeOffset is offset of first byte of jump below * - cmdIndex is index of the command after the current one * - exceptIndex is the index of the first ExceptionRange after the * current one. */ jumpFixupPtr->jumpType = jumpType; jumpFixupPtr->codeOffset = envPtr->codeNext - envPtr->codeStart; jumpFixupPtr->cmdIndex = envPtr->numCommands; jumpFixupPtr->exceptIndex = envPtr->exceptArrayNext; switch (jumpType) { case TCL_UNCONDITIONAL_JUMP: TclEmitInstInt1(INST_JUMP1, 0, envPtr); break; case TCL_TRUE_JUMP: TclEmitInstInt1(INST_JUMP_TRUE1, 0, envPtr); break; default: TclEmitInstInt1(INST_JUMP_FALSE1, 0, envPtr); break; } } /* *---------------------------------------------------------------------- * * TclFixupForwardJump -- * * Modifies a previously-emitted forward jump to jump a specified number * of bytes, "jumpDist". If necessary, the size of the jump instruction is * increased from two to five bytes. This is done if the jump distance is * greater than "distThreshold" (normally 127 bytes). The jump is * described by a JumpFixup record previously initialized by * TclEmitForwardJump. * * Results: * 1 if the jump was grown and subsequent instructions had to be moved, or * 0 otherwsie. This allows callers to update any additional code offsets * they may hold. * * Side effects: * The jump may be grown and subsequent instructions moved. If this * happens, the code offsets for any commands and any ExceptionRange * records between the jump and the current code address will be updated * to reflect the moved code. Also, the bytecode instruction array in the * CompileEnv structure may be grown and reallocated. * *---------------------------------------------------------------------- */ int TclFixupForwardJump( CompileEnv *envPtr, /* Points to the CompileEnv structure that * holds the resulting instruction. */ JumpFixup *jumpFixupPtr, /* Points to the JumpFixup structure that * describes the forward jump. */ int jumpDist, /* Jump distance to set in jump instr. */ int distThreshold) /* Maximum distance before the two byte jump * is grown to five bytes. */ { unsigned char *jumpPc, *p; int firstCmd, lastCmd, firstRange, lastRange, k; size_t numBytes; if (jumpDist <= distThreshold) { jumpPc = envPtr->codeStart + jumpFixupPtr->codeOffset; switch (jumpFixupPtr->jumpType) { case TCL_UNCONDITIONAL_JUMP: TclUpdateInstInt1AtPc(INST_JUMP1, jumpDist, jumpPc); break; case TCL_TRUE_JUMP: TclUpdateInstInt1AtPc(INST_JUMP_TRUE1, jumpDist, jumpPc); break; default: TclUpdateInstInt1AtPc(INST_JUMP_FALSE1, jumpDist, jumpPc); break; } return 0; } /* * Increase the size of the jump instruction, and then move subsequent * instructions down. Expanding the space for generated instructions means * that code addresses might change. Be careful about updating any of * these addresses held in variables. */ if ((envPtr->codeNext + 3) > envPtr->codeEnd) { TclExpandCodeArray(envPtr); } jumpPc = envPtr->codeStart + jumpFixupPtr->codeOffset; numBytes = envPtr->codeNext-jumpPc-2; p = jumpPc+2; memmove(p+3, p, numBytes); envPtr->codeNext += 3; jumpDist += 3; switch (jumpFixupPtr->jumpType) { case TCL_UNCONDITIONAL_JUMP: TclUpdateInstInt4AtPc(INST_JUMP4, jumpDist, jumpPc); break; case TCL_TRUE_JUMP: TclUpdateInstInt4AtPc(INST_JUMP_TRUE4, jumpDist, jumpPc); break; default: TclUpdateInstInt4AtPc(INST_JUMP_FALSE4, jumpDist, jumpPc); break; } /* * Adjust the code offsets for any commands and any ExceptionRange records * between the jump and the current code address. */ firstCmd = jumpFixupPtr->cmdIndex; lastCmd = envPtr->numCommands - 1; if (firstCmd < lastCmd) { for (k = firstCmd; k <= lastCmd; k++) { envPtr->cmdMapPtr[k].codeOffset += 3; } } firstRange = jumpFixupPtr->exceptIndex; lastRange = envPtr->exceptArrayNext - 1; for (k = firstRange; k <= lastRange; k++) { ExceptionRange *rangePtr = &envPtr->exceptArrayPtr[k]; rangePtr->codeOffset += 3; switch (rangePtr->type) { case LOOP_EXCEPTION_RANGE: rangePtr->breakOffset += 3; if (rangePtr->continueOffset != TCL_INDEX_NONE) { rangePtr->continueOffset += 3; } break; case CATCH_EXCEPTION_RANGE: rangePtr->catchOffset += 3; break; default: Tcl_Panic("TclFixupForwardJump: bad ExceptionRange type %d", rangePtr->type); } } for (k = 0 ; k < (int)envPtr->exceptArrayNext ; k++) { ExceptionAux *auxPtr = &envPtr->exceptAuxArrayPtr[k]; int i; for (i=0 ; i<(int)auxPtr->numBreakTargets ; i++) { if (jumpFixupPtr->codeOffset < auxPtr->breakTargets[i]) { auxPtr->breakTargets[i] += 3; } } for (i=0 ; i<(int)auxPtr->numContinueTargets ; i++) { if (jumpFixupPtr->codeOffset < auxPtr->continueTargets[i]) { auxPtr->continueTargets[i] += 3; } } } return 1; /* the jump was grown */ } /* *---------------------------------------------------------------------- * * TclEmitInvoke -- * * Emits one of the invoke-related instructions, wrapping it if necessary * in code that ensures that any break or continue operation passing * through it gets the stack unwinding correct, converting it into an * internal jump if in an appropriate context. * * Results: * None * * Side effects: * Issues the jump with all correct stack management. May create another * loop exception range. Pointers to ExceptionRange and ExceptionAux * structures should not be held across this call. * *---------------------------------------------------------------------- */ void TclEmitInvoke( CompileEnv *envPtr, int opcode, ...) { va_list argList; ExceptionRange *rangePtr; ExceptionAux *auxBreakPtr, *auxContinuePtr; int arg1, arg2, wordCount = 0, expandCount = 0; int loopRange = 0, breakRange = 0, continueRange = 0; int cleanup, depth = TclGetStackDepth(envPtr); /* * Parse the arguments. */ va_start(argList, opcode); switch (opcode) { case INST_INVOKE_STK1: wordCount = arg1 = cleanup = va_arg(argList, int); arg2 = 0; break; case INST_INVOKE_STK4: wordCount = arg1 = cleanup = va_arg(argList, int); arg2 = 0; break; case INST_INVOKE_REPLACE: arg1 = va_arg(argList, int); arg2 = va_arg(argList, int); wordCount = arg1 + arg2 - 1; cleanup = arg1 + 1; break; default: Tcl_Panic("unexpected opcode"); case INST_EVAL_STK: wordCount = cleanup = 1; arg1 = arg2 = 0; break; case INST_RETURN_STK: wordCount = cleanup = 2; arg1 = arg2 = 0; break; case INST_INVOKE_EXPANDED: wordCount = arg1 = cleanup = va_arg(argList, int); arg2 = 0; expandCount = 1; break; } va_end(argList); /* * If the exceptions is for break or continue handle it with special * handling exception range so the stack may be correctly unwound. * * These must be done separately since they can be different, especially * for calls from inside a [for] increment clause. */ rangePtr = TclGetInnermostExceptionRange(envPtr, TCL_CONTINUE, &auxContinuePtr); if (rangePtr == NULL || rangePtr->type != LOOP_EXCEPTION_RANGE) { auxContinuePtr = NULL; } else if (auxContinuePtr->stackDepth == envPtr->currStackDepth-wordCount && (auxContinuePtr->expandTarget+expandCount == envPtr->expandCount)) { auxContinuePtr = NULL; } else { continueRange = auxContinuePtr - envPtr->exceptAuxArrayPtr; } rangePtr = TclGetInnermostExceptionRange(envPtr, TCL_BREAK, &auxBreakPtr); if (rangePtr == NULL || rangePtr->type != LOOP_EXCEPTION_RANGE) { auxBreakPtr = NULL; } else if (auxContinuePtr == NULL && auxBreakPtr->stackDepth+wordCount == envPtr->currStackDepth && auxBreakPtr->expandTarget+expandCount == envPtr->expandCount) { auxBreakPtr = NULL; } else { breakRange = auxBreakPtr - envPtr->exceptAuxArrayPtr; } if (auxBreakPtr != NULL || auxContinuePtr != NULL) { loopRange = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); ExceptionRangeStarts(envPtr, loopRange); } /* * Issue the invoke itself. */ switch (opcode) { case INST_INVOKE_STK1: TclEmitInstInt1(INST_INVOKE_STK1, arg1, envPtr); break; case INST_INVOKE_STK4: TclEmitInstInt4(INST_INVOKE_STK4, arg1, envPtr); break; case INST_INVOKE_EXPANDED: TclEmitOpcode(INST_INVOKE_EXPANDED, envPtr); envPtr->expandCount--; TclAdjustStackDepth(1 - arg1, envPtr); break; case INST_EVAL_STK: TclEmitOpcode(INST_EVAL_STK, envPtr); break; case INST_RETURN_STK: TclEmitOpcode(INST_RETURN_STK, envPtr); break; case INST_INVOKE_REPLACE: TclEmitInstInt4(INST_INVOKE_REPLACE, arg1, envPtr); TclEmitInt1(arg2, envPtr); TclAdjustStackDepth(-1, envPtr); /* Correction to stack depth calcs */ break; } /* * If we're generating a special wrapper exception range, we need to * finish that up now. */ if (auxBreakPtr != NULL || auxContinuePtr != NULL) { size_t savedStackDepth = envPtr->currStackDepth; size_t savedExpandCount = envPtr->expandCount; JumpFixup nonTrapFixup; if (auxBreakPtr != NULL) { auxBreakPtr = envPtr->exceptAuxArrayPtr + breakRange; } if (auxContinuePtr != NULL) { auxContinuePtr = envPtr->exceptAuxArrayPtr + continueRange; } ExceptionRangeEnds(envPtr, loopRange); TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &nonTrapFixup); /* * Careful! When generating these stack unwinding sequences, the depth * of stack in the cases where they are taken is not the same as if * the exception is not taken. */ if (auxBreakPtr != NULL) { TclAdjustStackDepth(-1, envPtr); ExceptionRangeTarget(envPtr, loopRange, breakOffset); TclCleanupStackForBreakContinue(envPtr, auxBreakPtr); TclAddLoopBreakFixup(envPtr, auxBreakPtr); TclAdjustStackDepth(1, envPtr); envPtr->currStackDepth = savedStackDepth; envPtr->expandCount = savedExpandCount; } if (auxContinuePtr != NULL) { TclAdjustStackDepth(-1, envPtr); ExceptionRangeTarget(envPtr, loopRange, continueOffset); TclCleanupStackForBreakContinue(envPtr, auxContinuePtr); TclAddLoopContinueFixup(envPtr, auxContinuePtr); TclAdjustStackDepth(1, envPtr); envPtr->currStackDepth = savedStackDepth; envPtr->expandCount = savedExpandCount; } TclFinalizeLoopExceptionRange(envPtr, loopRange); TclFixupForwardJumpToHere(envPtr, &nonTrapFixup, 127); } TclCheckStackDepth(depth+1-cleanup, envPtr); } /* *---------------------------------------------------------------------- * * TclGetInstructionTable -- * * Returns a pointer to the table describing Tcl bytecode instructions. * This procedure is defined so that clients can access the pointer from * outside the TCL DLLs. * * Results: * Returns a pointer to the global instruction table, same as the * expression (&tclInstructionTable[0]). * * Side effects: * None. * *---------------------------------------------------------------------- */ const void * /* == InstructionDesc* == */ TclGetInstructionTable(void) { return &tclInstructionTable[0]; } /* *---------------------------------------------------------------------- * * GetCmdLocEncodingSize -- * * Computes the total number of bytes needed to encode the command * location information for some compiled code. * * Results: * The byte count needed to encode the compiled location information. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int GetCmdLocEncodingSize( CompileEnv *envPtr) /* Points to compilation environment structure * containing the CmdLocation structure to * encode. */ { CmdLocation *mapPtr = envPtr->cmdMapPtr; int numCmds = envPtr->numCommands; int codeDelta, codeLen, srcDelta, srcLen; int codeDeltaNext, codeLengthNext, srcDeltaNext, srcLengthNext; /* The offsets in their respective byte * sequences where the next encoded offset or * length should go. */ int prevCodeOffset, prevSrcOffset, i; codeDeltaNext = codeLengthNext = srcDeltaNext = srcLengthNext = 0; prevCodeOffset = prevSrcOffset = 0; for (i = 0; i < numCmds; i++) { codeDelta = mapPtr[i].codeOffset - prevCodeOffset; if (codeDelta < 0) { Tcl_Panic("GetCmdLocEncodingSize: bad code offset"); } else if (codeDelta <= 127) { codeDeltaNext++; } else { codeDeltaNext += 5; /* 1 byte for 0xFF, 4 for positive delta */ } prevCodeOffset = mapPtr[i].codeOffset; codeLen = mapPtr[i].numCodeBytes; if (codeLen < 0) { Tcl_Panic("GetCmdLocEncodingSize: bad code length"); } else if (codeLen <= 127) { codeLengthNext++; } else { codeLengthNext += 5;/* 1 byte for 0xFF, 4 for length */ } srcDelta = mapPtr[i].srcOffset - prevSrcOffset; if ((-127 <= srcDelta) && (srcDelta <= 127) && (srcDelta != -1)) { srcDeltaNext++; } else { srcDeltaNext += 5; /* 1 byte for 0xFF, 4 for delta */ } prevSrcOffset = mapPtr[i].srcOffset; srcLen = mapPtr[i].numSrcBytes; if (srcLen < 0) { Tcl_Panic("GetCmdLocEncodingSize: bad source length"); } else if (srcLen <= 127) { srcLengthNext++; } else { srcLengthNext += 5; /* 1 byte for 0xFF, 4 for length */ } } return (codeDeltaNext + codeLengthNext + srcDeltaNext + srcLengthNext); } /* *---------------------------------------------------------------------- * * EncodeCmdLocMap -- * * Encodes the command location information for some compiled code into a * ByteCode structure. The encoded command location map is stored as * three-adjacent-byte sequences. * * Results: * A pointer to the first byte after the encoded command location * information. * * Side effects: * Stores encoded information into the block of memory headed by * codePtr. Also records pointers to the start of the four byte sequences * in fields in codePtr's ByteCode header structure. * *---------------------------------------------------------------------- */ static unsigned char * EncodeCmdLocMap( CompileEnv *envPtr, /* Points to compilation environment structure * containing the CmdLocation structure to * encode. */ ByteCode *codePtr, /* ByteCode in which to encode envPtr's * command location information. */ unsigned char *startPtr) /* Points to the first byte in codePtr's * memory block where the location information * is to be stored. */ { CmdLocation *mapPtr = envPtr->cmdMapPtr; Tcl_Size i, codeDelta, codeLen, srcLen, prevOffset; Tcl_Size numCmds = envPtr->numCommands; unsigned char *p = startPtr; int srcDelta; /* * Encode the code offset for each command as a sequence of deltas. */ codePtr->codeDeltaStart = p; prevOffset = 0; for (i = 0; i < numCmds; i++) { codeDelta = mapPtr[i].codeOffset - prevOffset; if (codeDelta < 0) { Tcl_Panic("EncodeCmdLocMap: bad code offset"); } else if (codeDelta <= 127) { TclStoreInt1AtPtr(codeDelta, p); p++; } else { TclStoreInt1AtPtr(0xFF, p); p++; TclStoreInt4AtPtr(codeDelta, p); p += 4; } prevOffset = mapPtr[i].codeOffset; } /* * Encode the code length for each command. */ codePtr->codeLengthStart = p; for (i = 0; i < numCmds; i++) { codeLen = mapPtr[i].numCodeBytes; if (codeLen < 0) { Tcl_Panic("EncodeCmdLocMap: bad code length"); } else if (codeLen <= 127) { TclStoreInt1AtPtr(codeLen, p); p++; } else { TclStoreInt1AtPtr(0xFF, p); p++; TclStoreInt4AtPtr(codeLen, p); p += 4; } } /* * Encode the source offset for each command as a sequence of deltas. */ codePtr->srcDeltaStart = p; prevOffset = 0; for (i = 0; i < numCmds; i++) { srcDelta = mapPtr[i].srcOffset - prevOffset; if ((-127 <= srcDelta) && (srcDelta <= 127) && (srcDelta != -1)) { TclStoreInt1AtPtr(srcDelta, p); p++; } else { TclStoreInt1AtPtr(0xFF, p); p++; TclStoreInt4AtPtr(srcDelta, p); p += 4; } prevOffset = mapPtr[i].srcOffset; } /* * Encode the source length for each command. */ codePtr->srcLengthStart = p; for (i = 0; i < numCmds; i++) { srcLen = mapPtr[i].numSrcBytes; if (srcLen < 0) { Tcl_Panic("EncodeCmdLocMap: bad source length"); } else if (srcLen <= 127) { TclStoreInt1AtPtr(srcLen, p); p++; } else { TclStoreInt1AtPtr(0xFF, p); p++; TclStoreInt4AtPtr(srcLen, p); p += 4; } } return p; } #ifdef TCL_COMPILE_STATS /* *---------------------------------------------------------------------- * * RecordByteCodeStats -- * * Accumulates compilation-related statistics for each newly-compiled * ByteCode. Called by the TclInitByteCodeObj when Tcl is compiled with * the -DTCL_COMPILE_STATS flag * * Results: * None. * * Side effects: * Accumulates aggregate code-related statistics in the interpreter's * ByteCodeStats structure. Records statistics specific to a ByteCode in * its ByteCode structure. * *---------------------------------------------------------------------- */ void RecordByteCodeStats( ByteCode *codePtr) /* Points to ByteCode structure with info * to add to accumulated statistics. */ { Interp *iPtr = (Interp *) *codePtr->interpHandle; ByteCodeStats *statsPtr; if (iPtr == NULL) { /* Avoid segfaulting in case we're called in a deleted interp */ return; } statsPtr = &(iPtr->stats); statsPtr->numCompilations++; statsPtr->totalSrcBytes += (double)codePtr->numSrcBytes; statsPtr->totalByteCodeBytes += (double) codePtr->structureSize; statsPtr->currentSrcBytes += (double) (int)codePtr->numSrcBytes; statsPtr->currentByteCodeBytes += (double) codePtr->structureSize; statsPtr->srcCount[TclLog2((int)codePtr->numSrcBytes)]++; statsPtr->byteCodeCount[TclLog2((int) codePtr->structureSize)]++; statsPtr->currentInstBytes += (double) codePtr->numCodeBytes; statsPtr->currentLitBytes += (double) codePtr->numLitObjects * sizeof(Tcl_Obj *); statsPtr->currentExceptBytes += (double) codePtr->numExceptRanges * sizeof(ExceptionRange); statsPtr->currentAuxBytes += (double) codePtr->numAuxDataItems * sizeof(AuxData); statsPtr->currentCmdMapBytes += (double) codePtr->numCmdLocBytes; } #endif /* TCL_COMPILE_STATS */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * End: */ tcl9.0.1/generic/tclCompile.h0000644000175000017500000021062714726623136015446 0ustar sergeisergei/* * tclCompile.h -- * * Copyright (c) 1996-1998 Sun Microsystems, Inc. * Copyright (c) 1998-2000 by Scriptics Corporation. * Copyright (c) 2001 by Kevin B. Kenny. All rights reserved. * Copyright (c) 2007 Daniel A. Steffen * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef _TCLCOMPILATION #define _TCLCOMPILATION 1 #include "tclInt.h" struct ByteCode; /* Forward declaration. */ /* *------------------------------------------------------------------------ * Variables related to compilation. These are used in tclCompile.c, * tclExecute.c, tclBasic.c, and their clients. *------------------------------------------------------------------------ */ #ifdef TCL_COMPILE_DEBUG /* * Variable that controls whether compilation tracing is enabled and, if so, * what level of tracing is desired: * 0: no compilation tracing * 1: summarize compilation of top level cmds and proc bodies * 2: display all instructions of each ByteCode compiled * This variable is linked to the Tcl variable "tcl_traceCompile". */ MODULE_SCOPE int tclTraceCompile; /* * Variable that controls whether execution tracing is enabled and, if so, * what level of tracing is desired: * 0: no execution tracing * 1: trace invocations of Tcl procs only * 2: trace invocations of all (not compiled away) commands * 3: display each instruction executed * This variable is linked to the Tcl variable "tcl_traceExec". */ MODULE_SCOPE int tclTraceExec; #endif /* * The type of lambda expressions. Note that every lambda will *always* have a * string representation. */ MODULE_SCOPE const Tcl_ObjType tclLambdaType; /* *------------------------------------------------------------------------ * Data structures related to compilation. *------------------------------------------------------------------------ */ /* * The structure used to implement Tcl "exceptions" (exceptional returns): for * example, those generated in loops by the break and continue commands, and * those generated by scripts and caught by the catch command. This * ExceptionRange structure describes a range of code (e.g., a loop body), the * kind of exceptions (e.g., a break or continue) that might occur, and the PC * offsets to jump to if a matching exception does occur. Exception ranges can * nest so this structure includes a nesting level that is used at runtime to * find the closest exception range surrounding a PC. For example, when a * break command is executed, the ExceptionRange structure for the most deeply * nested loop, if any, is found and used. These structures are also generated * for the "next" subcommands of for loops since a break there terminates the * for command. This means a for command actually generates two LoopInfo * structures. */ typedef enum { LOOP_EXCEPTION_RANGE, /* Exception's range is part of a loop. Break * and continue "exceptions" cause jumps to * appropriate PC offsets. */ CATCH_EXCEPTION_RANGE /* Exception's range is controlled by a catch * command. Errors in the range cause a jump * to a catch PC offset. */ } ExceptionRangeType; typedef struct { ExceptionRangeType type; /* The kind of ExceptionRange. */ Tcl_Size nestingLevel; /* Static depth of the exception range. Used * to find the most deeply-nested range * surrounding a PC at runtime. */ Tcl_Size codeOffset; /* Offset of the first instruction byte of the * code range. */ Tcl_Size numCodeBytes; /* Number of bytes in the code range. */ Tcl_Size breakOffset; /* If LOOP_EXCEPTION_RANGE, the target PC * offset for a break command in the range. */ Tcl_Size continueOffset; /* If LOOP_EXCEPTION_RANGE and not TCL_INDEX_NONE, * the target PC offset for a continue command * in the code range. Otherwise, ignore this * range when processing a continue * command. */ Tcl_Size catchOffset; /* If a CATCH_EXCEPTION_RANGE, the target PC * offset for any "exception" in range. */ } ExceptionRange; /* * Auxiliary data used when issuing (currently just loop) exception ranges, * but which is not required during execution. */ typedef struct ExceptionAux { int supportsContinue; /* Whether this exception range will have a * continueOffset created for it; if it is a * loop exception range that *doesn't* have * one (see [for] next-clause) then we must * not pick up the range when scanning for a * target to continue to. */ Tcl_Size stackDepth; /* The stack depth at the point where the * exception range was created. This is used * to calculate the number of POPs required to * restore the stack to its prior state. */ Tcl_Size expandTarget; /* The number of expansions expected on the * auxData stack at the time the loop starts; * we can't currently discard them except by * doing INST_INVOKE_EXPANDED; this is a known * problem. */ Tcl_Size expandTargetDepth; /* The stack depth expected at the outermost * expansion within the loop. Not meaningful * if there are no open expansions between the * looping level and the point of jump * issue. */ Tcl_Size numBreakTargets; /* The number of [break]s that want to be * targeted to the place where this loop * exception will be bound to. */ TCL_HASH_TYPE *breakTargets;/* The offsets of the INST_JUMP4 instructions * issued by the [break]s that we must * update. Note that resizing a jump (via * TclFixupForwardJump) can cause the contents * of this array to be updated. When * numBreakTargets==0, this is NULL. */ Tcl_Size allocBreakTargets; /* The size of the breakTargets array. */ Tcl_Size numContinueTargets;/* The number of [continue]s that want to be * targeted to the place where this loop * exception will be bound to. */ TCL_HASH_TYPE *continueTargets; /* The offsets of the INST_JUMP4 instructions * issued by the [continue]s that we must * update. Note that resizing a jump (via * TclFixupForwardJump) can cause the contents * of this array to be updated. When * numContinueTargets==0, this is NULL. */ Tcl_Size allocContinueTargets; /* The size of the continueTargets array. */ } ExceptionAux; /* * Structure used to map between instruction pc and source locations. It * defines for each compiled Tcl command its code's starting offset and its * source's starting offset and length. Note that the code offset increases * monotonically: that is, the table is sorted in code offset order. The * source offset is not monotonic. */ typedef struct { Tcl_Size codeOffset; /* Offset of first byte of command code. */ Tcl_Size numCodeBytes; /* Number of bytes for command's code. */ Tcl_Size srcOffset; /* Offset of first char of the command. */ Tcl_Size numSrcBytes; /* Number of command source chars. */ } CmdLocation; /* * TIP #280 * Structure to record additional location information for byte code. This * information is internal and not saved. i.e. tbcload'ed code will not have * this information. It records the lines for all words of all commands found * in the byte code. The association with a ByteCode structure BC is done * through the 'lineBCPtr' HashTable in Interp, keyed by the address of BC. * Also recorded is information coming from the context, i.e. type of the * frame and associated information, like the path of a sourced file. */ typedef struct { Tcl_Size srcOffset; /* Command location to find the entry. */ Tcl_Size nline; /* Number of words in the command */ Tcl_Size *line; /* Line information for all words in the * command. */ Tcl_Size **next; /* Transient information used by the compiler * for tracking of hidden continuation * lines. */ } ECL; typedef struct { int type; /* Context type. */ Tcl_Size start; /* Starting line for compiled script. Needed * for the extended recompile check in * tclCompileObj. */ Tcl_Obj *path; /* Path of the sourced file the command is * in. */ ECL *loc; /* Command word locations (lines). */ Tcl_Size nloc; /* Number of allocated entries in 'loc'. */ Tcl_Size nuloc; /* Number of used entries in 'loc'. */ } ExtCmdLoc; /* * CompileProcs need the ability to record information during compilation that * can be used by bytecode instructions during execution. The AuxData * structure provides this "auxiliary data" mechanism. An arbitrary number of * these structures can be stored in the ByteCode record (during compilation * they are stored in a CompileEnv structure). Each AuxData record holds one * word of client-specified data (often a pointer) and is given an index that * instructions can later use to look up the structure and its data. * * The following definitions declare the types of procedures that are called * to duplicate or free this auxiliary data when the containing ByteCode * objects are duplicated and freed. Pointers to these procedures are kept in * the AuxData structure. */ typedef void * (AuxDataDupProc) (void *clientData); typedef void (AuxDataFreeProc) (void *clientData); typedef void (AuxDataPrintProc) (void *clientData, Tcl_Obj *appendObj, struct ByteCode *codePtr, TCL_HASH_TYPE pcOffset); /* * We define a separate AuxDataType struct to hold type-related information * for the AuxData structure. This separation makes it possible for clients * outside of the TCL core to manipulate (in a limited fashion!) AuxData; for * example, it makes it possible to pickle and unpickle AuxData structs. */ typedef struct AuxDataType { const char *name; /* The name of the type. Types can be * registered and found by name */ AuxDataDupProc *dupProc; /* Callback procedure to invoke when the aux * data is duplicated (e.g., when the ByteCode * structure containing the aux data is * duplicated). NULL means just copy the * source clientData bits; no proc need be * called. */ AuxDataFreeProc *freeProc; /* Callback procedure to invoke when the aux * data is freed. NULL means no proc need be * called. */ AuxDataPrintProc *printProc;/* Callback function to invoke when printing * the aux data as part of debugging. NULL * means that the data can't be printed. */ AuxDataPrintProc *disassembleProc; /* Callback function to invoke when doing a * disassembly of the aux data (like the * printProc, except that the output is * intended to be script-readable). The * appendObj argument should be filled in with * a descriptive dictionary; it will start out * with "name" mapped to the content of the * name field. NULL means that the printProc * should be used instead. */ } AuxDataType; /* * The definition of the AuxData structure that holds information created * during compilation by CompileProcs and used by instructions during * execution. */ typedef struct AuxData { const AuxDataType *type; /* Pointer to the AuxData type associated with * this ClientData. */ void *clientData; /* The compilation data itself. */ } AuxData; /* * Structure defining the compilation environment. After compilation, fields * describing bytecode instructions are copied out into the more compact * ByteCode structure defined below. */ #define COMPILEENV_INIT_CODE_BYTES 250 #define COMPILEENV_INIT_NUM_OBJECTS 60 #define COMPILEENV_INIT_EXCEPT_RANGES 5 #define COMPILEENV_INIT_CMD_MAP_SIZE 40 #define COMPILEENV_INIT_AUX_DATA_SIZE 5 typedef struct CompileEnv { Interp *iPtr; /* Interpreter containing the code being * compiled. Commands and their compile procs * are specific to an interpreter so the code * emitted will depend on the interpreter. */ const char *source; /* The source string being compiled by * SetByteCodeFromAny. This pointer is not * owned by the CompileEnv and must not be * freed or changed by it. */ Tcl_Size numSrcBytes; /* Number of bytes in source. */ Proc *procPtr; /* If a procedure is being compiled, a pointer * to its Proc structure; otherwise NULL. Used * to compile local variables. Set from * information provided by ObjInterpProc in * tclProc.c. */ Tcl_Size numCommands; /* Number of commands compiled. */ Tcl_Size exceptDepth; /* Current exception range nesting level; * TCL_INDEX_NONE if not in any range * currently. */ Tcl_Size maxExceptDepth; /* Max nesting level of exception ranges; * TCL_INDEX_NONE if no ranges have been * compiled. */ Tcl_Size maxStackDepth; /* Maximum number of stack elements needed to * execute the code. Set by compilation * procedures before returning. */ Tcl_Size currStackDepth; /* Current stack depth. */ LiteralTable localLitTable; /* Contains LiteralEntry's describing all Tcl * objects referenced by this compiled code. * Indexed by the string representations of * the literals. Used to avoid creating * duplicate objects. */ unsigned char *codeStart; /* Points to the first byte of the code. */ unsigned char *codeNext; /* Points to next code array byte to use. */ unsigned char *codeEnd; /* Points just after the last allocated code * array byte. */ int mallocedCodeArray; /* Set 1 if code array was expanded and * codeStart points into the heap.*/ #if TCL_MAJOR_VERSION > 8 int mallocedExceptArray; /* 1 if ExceptionRange array was expanded and * exceptArrayPtr points in heap, else 0. */ #endif LiteralEntry *literalArrayPtr; /* Points to start of LiteralEntry array. */ Tcl_Size literalArrayNext; /* Index of next free object array entry. */ Tcl_Size literalArrayEnd; /* Index just after last obj array entry. */ int mallocedLiteralArray; /* 1 if object array was expanded and objArray * points into the heap, else 0. */ ExceptionRange *exceptArrayPtr; /* Points to start of the ExceptionRange * array. */ Tcl_Size exceptArrayNext; /* Next free ExceptionRange array index. * exceptArrayNext is the number of ranges and * (exceptArrayNext-1) is the index of the * current range's array entry. */ Tcl_Size exceptArrayEnd; /* Index after the last ExceptionRange array * entry. */ #if TCL_MAJOR_VERSION < 9 int mallocedExceptArray; #endif ExceptionAux *exceptAuxArrayPtr; /* Array of information used to restore the * state when processing BREAK/CONTINUE * exceptions. Must be the same size as the * exceptArrayPtr. */ CmdLocation *cmdMapPtr; /* Points to start of CmdLocation array. * numCommands is the index of the next entry * to use; (numCommands-1) is the entry index * for the last command. */ Tcl_Size cmdMapEnd; /* Index after last CmdLocation entry. */ int mallocedCmdMap; /* 1 if command map array was expanded and * cmdMapPtr points in the heap, else 0. */ #if TCL_MAJOR_VERSION > 8 int mallocedAuxDataArray; /* 1 if aux data array was expanded and * auxDataArrayPtr points in heap else 0. */ #endif AuxData *auxDataArrayPtr; /* Points to auxiliary data array start. */ Tcl_Size auxDataArrayNext; /* Next free compile aux data array index. * auxDataArrayNext is the number of aux data * items and (auxDataArrayNext-1) is index of * current aux data array entry. */ Tcl_Size auxDataArrayEnd; /* Index after last aux data array entry. */ #if TCL_MAJOR_VERSION < 9 int mallocedAuxDataArray; #endif unsigned char staticCodeSpace[COMPILEENV_INIT_CODE_BYTES]; /* Initial storage for code. */ LiteralEntry staticLiteralSpace[COMPILEENV_INIT_NUM_OBJECTS]; /* Initial storage of LiteralEntry array. */ ExceptionRange staticExceptArraySpace[COMPILEENV_INIT_EXCEPT_RANGES]; /* Initial ExceptionRange array storage. */ ExceptionAux staticExAuxArraySpace[COMPILEENV_INIT_EXCEPT_RANGES]; /* Initial static except auxiliary info array * storage. */ CmdLocation staticCmdMapSpace[COMPILEENV_INIT_CMD_MAP_SIZE]; /* Initial storage for cmd location map. */ AuxData staticAuxDataArraySpace[COMPILEENV_INIT_AUX_DATA_SIZE]; /* Initial storage for aux data array. */ /* TIP #280 */ ExtCmdLoc *extCmdMapPtr; /* Extended command location information for * 'info frame'. */ Tcl_Size line; /* First line of the script, based on the * invoking context, then the line of the * command currently compiled. */ int atCmdStart; /* Flag to say whether an INST_START_CMD * should be issued; they should never be * issued repeatedly, as that is significantly * inefficient. If set to 2, that instruction * should not be issued at all (by the generic * part of the command compiler). */ Tcl_Size expandCount; /* Number of INST_EXPAND_START instructions * encountered that have not yet been paired * with a corresponding * INST_INVOKE_EXPANDED. */ Tcl_Size *clNext; /* If not NULL, it refers to the next slot in * clLoc to check for an invisible * continuation line. */ } CompileEnv; /* * The structure defining the bytecode instructions resulting from compiling a * Tcl script. Note that this structure is variable length: a single heap * object is allocated to hold the ByteCode structure immediately followed by * the code bytes, the literal object array, the ExceptionRange array, the * CmdLocation map, and the compilation AuxData array. */ /* * A PRECOMPILED bytecode struct is one that was generated from a compiled * image rather than implicitly compiled from source */ #define TCL_BYTECODE_PRECOMPILED 0x0001 /* * When a bytecode is compiled, interp or namespace resolvers have not been * applied yet: this is indicated by the TCL_BYTECODE_RESOLVE_VARS flag. */ #define TCL_BYTECODE_RESOLVE_VARS 0x0002 #define TCL_BYTECODE_RECOMPILE 0x0004 typedef struct ByteCode { TclHandle interpHandle; /* Handle for interpreter containing the * compiled code. Commands and their compile * procs are specific to an interpreter so the * code emitted will depend on the * interpreter. */ Tcl_Size compileEpoch; /* Value of iPtr->compileEpoch when this * ByteCode was compiled. Used to invalidate * code when, e.g., commands with compile * procs are redefined. */ Namespace *nsPtr; /* Namespace context in which this code was * compiled. If the code is executed if a * different namespace, it must be * recompiled. */ Tcl_Size nsEpoch; /* Value of nsPtr->resolverEpoch when this * ByteCode was compiled. Used to invalidate * code when new namespace resolution rules * are put into effect. */ Tcl_Size refCount; /* Reference count: set 1 when created plus 1 * for each execution of the code currently * active. This structure can be freed when * refCount becomes zero. */ unsigned int flags; /* flags describing state for the codebyte. * this variable holds OR'ed values from the * TCL_BYTECODE_ masks defined above */ const char *source; /* The source string from which this ByteCode * was compiled. Note that this pointer is not * owned by the ByteCode and must not be freed * or modified by it. */ Proc *procPtr; /* If the ByteCode was compiled from a * procedure body, this is a pointer to its * Proc structure; otherwise NULL. This * pointer is also not owned by the ByteCode * and must not be freed by it. */ size_t structureSize; /* Number of bytes in the ByteCode structure * itself. Does not include heap space for * literal Tcl objects or storage referenced * by AuxData entries. */ Tcl_Size numCommands; /* Number of commands compiled. */ Tcl_Size numSrcBytes; /* Number of source bytes compiled. */ Tcl_Size numCodeBytes; /* Number of code bytes. */ Tcl_Size numLitObjects; /* Number of objects in literal array. */ Tcl_Size numExceptRanges; /* Number of ExceptionRange array elems. */ Tcl_Size numAuxDataItems; /* Number of AuxData items. */ Tcl_Size numCmdLocBytes; /* Number of bytes needed for encoded command * location information. */ Tcl_Size maxExceptDepth; /* Maximum nesting level of ExceptionRanges; * TCL_INDEX_NONE if no ranges were compiled. */ Tcl_Size maxStackDepth; /* Maximum number of stack elements needed to * execute the code. */ unsigned char *codeStart; /* Points to the first byte of the code. This * is just after the final ByteCode member * cmdMapPtr. */ Tcl_Obj **objArrayPtr; /* Points to the start of the literal object * array. This is just after the last code * byte. */ ExceptionRange *exceptArrayPtr; /* Points to the start of the ExceptionRange * array. This is just after the last object * in the object array. */ AuxData *auxDataArrayPtr; /* Points to the start of the auxiliary data * array. This is just after the last entry in * the ExceptionRange array. */ unsigned char *codeDeltaStart; /* Points to the first of a sequence of bytes * that encode the change in the starting * offset of each command's code. If -127 <= * delta <= 127, it is encoded as 1 byte, * otherwise 0xFF (128) appears and the delta * is encoded by the next 4 bytes. Code deltas * are always positive. This sequence is just * after the last entry in the AuxData * array. */ unsigned char *codeLengthStart; /* Points to the first of a sequence of bytes * that encode the length of each command's * code. The encoding is the same as for code * deltas. Code lengths are always positive. * This sequence is just after the last entry * in the code delta sequence. */ unsigned char *srcDeltaStart; /* Points to the first of a sequence of bytes * that encode the change in the starting * offset of each command's source. The * encoding is the same as for code deltas. * Source deltas can be negative. This * sequence is just after the last byte in the * code length sequence. */ unsigned char *srcLengthStart; /* Points to the first of a sequence of bytes * that encode the length of each command's * source. The encoding is the same as for * code deltas. Source lengths are always * positive. This sequence is just after the * last byte in the source delta sequence. */ LocalCache *localCachePtr; /* Pointer to the start of the cached variable * names and initialisation data for local * variables. */ #ifdef TCL_COMPILE_STATS Tcl_Time createTime; /* Absolute time when the ByteCode was * created. */ #endif /* TCL_COMPILE_STATS */ } ByteCode; #define ByteCodeSetInternalRep(objPtr, typePtr, codePtr) \ do { \ Tcl_ObjInternalRep ir; \ ir.twoPtrValue.ptr1 = (codePtr); \ ir.twoPtrValue.ptr2 = NULL; \ Tcl_StoreInternalRep((objPtr), (typePtr), &ir); \ } while (0) #define ByteCodeGetInternalRep(objPtr, typePtr, codePtr) \ do { \ const Tcl_ObjInternalRep *irPtr; \ irPtr = TclFetchInternalRep((objPtr), (typePtr)); \ (codePtr) = irPtr ? (ByteCode*)irPtr->twoPtrValue.ptr1 : NULL; \ } while (0) /* * Opcodes for the Tcl bytecode instructions. These must correspond to the * entries in the table of instruction descriptions, tclInstructionTable, in * tclCompile.c. Also, the order and number of the expression opcodes (e.g., * INST_BITOR) must match the entries in the array operatorStrings in * tclExecute.c. */ enum TclInstruction { /* Opcodes 0 to 9 */ INST_DONE = 0, INST_PUSH1, INST_PUSH4, INST_POP, INST_DUP, INST_STR_CONCAT1, INST_INVOKE_STK1, INST_INVOKE_STK4, INST_EVAL_STK, INST_EXPR_STK, /* Opcodes 10 to 23 */ INST_LOAD_SCALAR1, INST_LOAD_SCALAR4, INST_LOAD_SCALAR_STK, INST_LOAD_ARRAY1, INST_LOAD_ARRAY4, INST_LOAD_ARRAY_STK, INST_LOAD_STK, INST_STORE_SCALAR1, INST_STORE_SCALAR4, INST_STORE_SCALAR_STK, INST_STORE_ARRAY1, INST_STORE_ARRAY4, INST_STORE_ARRAY_STK, INST_STORE_STK, /* Opcodes 24 to 33 */ INST_INCR_SCALAR1, INST_INCR_SCALAR_STK, INST_INCR_ARRAY1, INST_INCR_ARRAY_STK, INST_INCR_STK, INST_INCR_SCALAR1_IMM, INST_INCR_SCALAR_STK_IMM, INST_INCR_ARRAY1_IMM, INST_INCR_ARRAY_STK_IMM, INST_INCR_STK_IMM, /* Opcodes 34 to 39 */ INST_JUMP1, INST_JUMP4, INST_JUMP_TRUE1, INST_JUMP_TRUE4, INST_JUMP_FALSE1, INST_JUMP_FALSE4, /* Opcodes 42 to 64 */ INST_BITOR, INST_BITXOR, INST_BITAND, INST_EQ, INST_NEQ, INST_LT, INST_GT, INST_LE, INST_GE, INST_LSHIFT, INST_RSHIFT, INST_ADD, INST_SUB, INST_MULT, INST_DIV, INST_MOD, INST_UPLUS, INST_UMINUS, INST_BITNOT, INST_LNOT, INST_TRY_CVT_TO_NUMERIC, /* Opcodes 65 to 66 */ INST_BREAK, INST_CONTINUE, /* Opcodes 69 to 72 */ INST_BEGIN_CATCH4, INST_END_CATCH, INST_PUSH_RESULT, INST_PUSH_RETURN_CODE, /* Opcodes 73 to 78 */ INST_STR_EQ, INST_STR_NEQ, INST_STR_CMP, INST_STR_LEN, INST_STR_INDEX, INST_STR_MATCH, /* Opcodes 79 to 81 */ INST_LIST, INST_LIST_INDEX, INST_LIST_LENGTH, /* Opcodes 82 to 87 */ INST_APPEND_SCALAR1, INST_APPEND_SCALAR4, INST_APPEND_ARRAY1, INST_APPEND_ARRAY4, INST_APPEND_ARRAY_STK, INST_APPEND_STK, /* Opcodes 88 to 93 */ INST_LAPPEND_SCALAR1, INST_LAPPEND_SCALAR4, INST_LAPPEND_ARRAY1, INST_LAPPEND_ARRAY4, INST_LAPPEND_ARRAY_STK, INST_LAPPEND_STK, /* TIP #22 - LINDEX operator with flat arg list */ INST_LIST_INDEX_MULTI, /* * TIP #33 - 'lset' command. Code gen also required a Forth-like * OVER operation. */ INST_OVER, INST_LSET_LIST, INST_LSET_FLAT, /* TIP#90 - 'return' command. */ INST_RETURN_IMM, /* TIP#123 - exponentiation operator. */ INST_EXPON, /* TIP #157 - {*}... (word expansion) language syntax support. */ INST_EXPAND_START, INST_EXPAND_STKTOP, INST_INVOKE_EXPANDED, /* * TIP #57 - 'lassign' command. Code generation requires immediate * LINDEX and LRANGE operators. */ INST_LIST_INDEX_IMM, INST_LIST_RANGE_IMM, INST_START_CMD, INST_LIST_IN, INST_LIST_NOT_IN, INST_PUSH_RETURN_OPTIONS, INST_RETURN_STK, /* * Dictionary (TIP#111) related commands. */ INST_DICT_GET, INST_DICT_SET, INST_DICT_UNSET, INST_DICT_INCR_IMM, INST_DICT_APPEND, INST_DICT_LAPPEND, INST_DICT_FIRST, INST_DICT_NEXT, INST_DICT_UPDATE_START, INST_DICT_UPDATE_END, /* * Instruction to support jumps defined by tables (instead of the classic * [switch] technique of chained comparisons). */ INST_JUMP_TABLE, /* * Instructions to support compilation of global, variable, upvar and * [namespace upvar]. */ INST_UPVAR, INST_NSUPVAR, INST_VARIABLE, /* Instruction to support compiling syntax error to bytecode */ INST_SYNTAX, /* Instruction to reverse N items on top of stack */ INST_REVERSE, /* regexp instruction */ INST_REGEXP, /* For [info exists] compilation */ INST_EXIST_SCALAR, INST_EXIST_ARRAY, INST_EXIST_ARRAY_STK, INST_EXIST_STK, /* For [subst] compilation */ INST_NOP, INST_RETURN_CODE_BRANCH, /* For [unset] compilation */ INST_UNSET_SCALAR, INST_UNSET_ARRAY, INST_UNSET_ARRAY_STK, INST_UNSET_STK, /* For [dict with], [dict exists], [dict create] and [dict merge] */ INST_DICT_EXPAND, INST_DICT_RECOMBINE_STK, INST_DICT_RECOMBINE_IMM, INST_DICT_EXISTS, INST_DICT_VERIFY, /* For [string map] and [regsub] compilation */ INST_STR_MAP, INST_STR_FIND, INST_STR_FIND_LAST, INST_STR_RANGE_IMM, INST_STR_RANGE, /* For operations to do with coroutines and other NRE-manipulators */ INST_YIELD, INST_COROUTINE_NAME, INST_TAILCALL, /* For compilation of basic information operations */ INST_NS_CURRENT, INST_INFO_LEVEL_NUM, INST_INFO_LEVEL_ARGS, INST_RESOLVE_COMMAND, /* For compilation relating to TclOO */ INST_TCLOO_SELF, INST_TCLOO_CLASS, INST_TCLOO_NS, INST_TCLOO_IS_OBJECT, /* For compilation of [array] subcommands */ INST_ARRAY_EXISTS_STK, INST_ARRAY_EXISTS_IMM, INST_ARRAY_MAKE_STK, INST_ARRAY_MAKE_IMM, INST_INVOKE_REPLACE, INST_LIST_CONCAT, INST_EXPAND_DROP, /* New foreach implementation */ INST_FOREACH_START, INST_FOREACH_STEP, INST_FOREACH_END, INST_LMAP_COLLECT, /* For compilation of [string trim] and related */ INST_STR_TRIM, INST_STR_TRIM_LEFT, INST_STR_TRIM_RIGHT, INST_CONCAT_STK, INST_STR_UPPER, INST_STR_LOWER, INST_STR_TITLE, INST_STR_REPLACE, INST_ORIGIN_COMMAND, INST_TCLOO_NEXT, INST_TCLOO_NEXT_CLASS, INST_YIELD_TO_INVOKE, INST_NUM_TYPE, INST_TRY_CVT_TO_BOOLEAN, INST_STR_CLASS, INST_LAPPEND_LIST, INST_LAPPEND_LIST_ARRAY, INST_LAPPEND_LIST_ARRAY_STK, INST_LAPPEND_LIST_STK, INST_CLOCK_READ, INST_DICT_GET_DEF, /* TIP 461 */ INST_STR_LT, INST_STR_GT, INST_STR_LE, INST_STR_GE, INST_LREPLACE4, /* TIP 667: const */ INST_CONST_IMM, INST_CONST_STK, /* The last opcode */ LAST_INST_OPCODE }; /* * Table describing the Tcl bytecode instructions: their name (for displaying * code), total number of code bytes required (including operand bytes), and a * description of the type of each operand. These operand types include signed * and unsigned integers of length one and four bytes. The unsigned integers * are used for indexes or for, e.g., the count of objects to push in a "push" * instruction. */ #define MAX_INSTRUCTION_OPERANDS 2 typedef enum InstOperandType { OPERAND_NONE, OPERAND_INT1, /* One byte signed integer. */ OPERAND_INT4, /* Four byte signed integer. */ OPERAND_UINT1, /* One byte unsigned integer. */ OPERAND_UINT4, /* Four byte unsigned integer. */ OPERAND_IDX4, /* Four byte signed index (actually an * integer, but displayed differently.) */ OPERAND_LVT1, /* One byte unsigned index into the local * variable table. */ OPERAND_LVT4, /* Four byte unsigned index into the local * variable table. */ OPERAND_AUX4, /* Four byte unsigned index into the aux data * table. */ OPERAND_OFFSET1, /* One byte signed jump offset. */ OPERAND_OFFSET4, /* Four byte signed jump offset. */ OPERAND_LIT1, /* One byte unsigned index into table of * literals. */ OPERAND_LIT4, /* Four byte unsigned index into table of * literals. */ OPERAND_SCLS1 /* Index into tclStringClassTable. */ } InstOperandType; typedef struct InstructionDesc { const char *name; /* Name of instruction. */ Tcl_Size numBytes; /* Total number of bytes for instruction. */ int stackEffect; /* The worst-case balance stack effect of the * instruction, used for stack requirements * computations. The value INT_MIN signals * that the instruction's worst case effect is * (1-opnd1). */ int numOperands; /* Number of operands. */ InstOperandType opTypes[MAX_INSTRUCTION_OPERANDS]; /* The type of each operand. */ } InstructionDesc; MODULE_SCOPE InstructionDesc const tclInstructionTable[]; /* * Constants used by INST_STRING_CLASS to indicate character classes. These * correspond closely by name with what [string is] can support, but there is * no requirement to keep the values the same. */ typedef enum InstStringClassType { STR_CLASS_ALNUM, /* Unicode alphabet or digit characters. */ STR_CLASS_ALPHA, /* Unicode alphabet characters. */ STR_CLASS_ASCII, /* Characters in range U+000000..U+00007F. */ STR_CLASS_CONTROL, /* Unicode control characters. */ STR_CLASS_DIGIT, /* Unicode digit characters. */ STR_CLASS_GRAPH, /* Unicode printing characters, excluding * space. */ STR_CLASS_LOWER, /* Unicode lower-case alphabet characters. */ STR_CLASS_PRINT, /* Unicode printing characters, including * spaces. */ STR_CLASS_PUNCT, /* Unicode punctuation characters. */ STR_CLASS_SPACE, /* Unicode space characters. */ STR_CLASS_UPPER, /* Unicode upper-case alphabet characters. */ STR_CLASS_WORD, /* Unicode word (alphabetic, digit, connector * punctuation) characters. */ STR_CLASS_XDIGIT, /* Characters that can be used as digits in * hexadecimal numbers ([0-9A-Fa-f]). */ } InstStringClassType; typedef struct StringClassDesc { char name[8]; /* Name of the class. */ int (*comparator)(int); /* Function to test if a single unicode * character is a member of the class. */ } StringClassDesc; MODULE_SCOPE StringClassDesc const tclStringClassTable[]; /* * Compilation of some Tcl constructs such as if commands and the logical or * (||) and logical and (&&) operators in expressions requires the generation * of forward jumps. Since the PC target of these jumps isn't known when the * jumps are emitted, we record the offset of each jump in an array of * JumpFixup structures. There is one array for each sequence of jumps to one * target PC. When we learn the target PC, we update the jumps with the * correct distance. Also, if the distance is too great (> 127 bytes), we * replace the single-byte jump with a four byte jump instruction, move the * instructions after the jump down, and update the code offsets for any * commands between the jump and the target. */ typedef enum { TCL_UNCONDITIONAL_JUMP, TCL_TRUE_JUMP, TCL_FALSE_JUMP } TclJumpType; typedef struct JumpFixup { TclJumpType jumpType; /* Indicates the kind of jump. */ unsigned int codeOffset; /* Offset of the first byte of the one-byte * forward jump's code. */ int cmdIndex; /* Index of the first command after the one * for which the jump was emitted. Used to * update the code offsets for subsequent * commands if the two-byte jump at jumpPc * must be replaced with a five-byte one. */ int exceptIndex; /* Index of the first range entry in the * ExceptionRange array after the current one. * This field is used to adjust the code * offsets in subsequent ExceptionRange * records when a jump is grown from 2 bytes * to 5 bytes. */ } JumpFixup; #define JUMPFIXUP_INIT_ENTRIES 10 typedef struct JumpFixupArray { JumpFixup *fixup; /* Points to start of jump fixup array. */ Tcl_Size next; /* Index of next free array entry. */ Tcl_Size end; /* Index of last usable entry in array. */ int mallocedArray; /* 1 if array was expanded and fixups points * into the heap, else 0. */ JumpFixup staticFixupSpace[JUMPFIXUP_INIT_ENTRIES]; /* Initial storage for jump fixup array. */ } JumpFixupArray; /* * The structure describing one variable list of a foreach command. Note that * only foreach commands inside procedure bodies are compiled inline so a * ForeachVarList structure always describes local variables. Furthermore, * only scalar variables are supported for inline-compiled foreach loops. */ typedef struct ForeachVarList { Tcl_Size numVars; /* The number of variables in the list. */ Tcl_Size varIndexes[TCLFLEXARRAY]; /* An array of the indexes ("slot numbers") * for each variable in the procedure's array * of local variables. Only scalar variables * are supported. The actual size of this * field will be large enough to numVars * indexes. THIS MUST BE THE LAST FIELD IN THE * STRUCTURE! */ } ForeachVarList; /* * Structure used to hold information about a foreach command that is needed * during program execution. These structures are stored in CompileEnv and * ByteCode structures as auxiliary data. */ typedef struct ForeachInfo { Tcl_Size numLists; /* The number of both the variable and value * lists of the foreach command. */ Tcl_Size firstValueTemp; /* Index of the first temp var in a proc frame * used to point to a value list. */ Tcl_Size loopCtTemp; /* Index of temp var in a proc frame holding * the loop's iteration count. Used to * determine next value list element to assign * each loop var. */ ForeachVarList *varLists[TCLFLEXARRAY]; /* An array of pointers to ForeachVarList * structures describing each var list. The * actual size of this field will be large * enough to numVars indexes. THIS MUST BE THE * LAST FIELD IN THE STRUCTURE! */ } ForeachInfo; /* * Structure used to hold information about a switch command that is needed * during program execution. These structures are stored in CompileEnv and * ByteCode structures as auxiliary data. */ typedef struct JumptableInfo { Tcl_HashTable hashTable; /* Hash that maps strings to signed ints (PC * offsets). */ } JumptableInfo; MODULE_SCOPE const AuxDataType tclJumptableInfoType; #define JUMPTABLEINFO(envPtr, index) \ ((JumptableInfo*)((envPtr)->auxDataArrayPtr[TclGetUInt4AtPtr(index)].clientData)) /* * Structure used to hold information about a [dict update] command that is * needed during program execution. These structures are stored in CompileEnv * and ByteCode structures as auxiliary data. */ typedef struct { Tcl_Size length; /* Size of array */ Tcl_Size varIndices[TCLFLEXARRAY]; /* Array of variable indices to manage when * processing the start and end of a [dict * update]. There is really more than one * entry, and the structure is allocated to * take account of this. MUST BE LAST FIELD IN * STRUCTURE. */ } DictUpdateInfo; /* * ClientData type used by the math operator commands. */ typedef struct { const char *op; /* Do not call it 'operator': C++ reserved */ const char *expected; union { int numArgs; int identity; } i; } TclOpCmdClientData; /* *---------------------------------------------------------------- * Procedures exported by tclBasic.c to be used within the engine. *---------------------------------------------------------------- */ #if TCL_MAJOR_VERSION > 8 MODULE_SCOPE Tcl_ObjCmdProc TclNRInterpCoroutine; /* *---------------------------------------------------------------- * Procedures exported by the engine to be used by tclBasic.c *---------------------------------------------------------------- */ MODULE_SCOPE ByteCode * TclCompileObj(Tcl_Interp *interp, Tcl_Obj *objPtr, const CmdFrame *invoker, int word); /* *---------------------------------------------------------------- * Procedures shared among Tcl bytecode compilation and execution modules but * not used outside: *---------------------------------------------------------------- */ MODULE_SCOPE int TclAttemptCompileProc(Tcl_Interp *interp, Tcl_Parse *parsePtr, Tcl_Size depth, Command *cmdPtr, CompileEnv *envPtr); MODULE_SCOPE void TclCleanupStackForBreakContinue(CompileEnv *envPtr, ExceptionAux *auxPtr); MODULE_SCOPE void TclCompileCmdWord(Tcl_Interp *interp, Tcl_Token *tokenPtr, size_t count, CompileEnv *envPtr); MODULE_SCOPE void TclCompileExpr(Tcl_Interp *interp, const char *script, Tcl_Size numBytes, CompileEnv *envPtr, int optimize); MODULE_SCOPE void TclCompileExprWords(Tcl_Interp *interp, Tcl_Token *tokenPtr, size_t numWords, CompileEnv *envPtr); MODULE_SCOPE void TclCompileInvocation(Tcl_Interp *interp, Tcl_Token *tokenPtr, Tcl_Obj *cmdObj, size_t numWords, CompileEnv *envPtr); MODULE_SCOPE void TclCompileScript(Tcl_Interp *interp, const char *script, Tcl_Size numBytes, CompileEnv *envPtr); MODULE_SCOPE void TclCompileSyntaxError(Tcl_Interp *interp, CompileEnv *envPtr); MODULE_SCOPE void TclCompileTokens(Tcl_Interp *interp, Tcl_Token *tokenPtr, size_t count, CompileEnv *envPtr); MODULE_SCOPE void TclCompileVarSubst(Tcl_Interp *interp, Tcl_Token *tokenPtr, CompileEnv *envPtr); MODULE_SCOPE Tcl_Size TclCreateAuxData(void *clientData, const AuxDataType *typePtr, CompileEnv *envPtr); MODULE_SCOPE Tcl_Size TclCreateExceptRange(ExceptionRangeType type, CompileEnv *envPtr); MODULE_SCOPE ExecEnv * TclCreateExecEnv(Tcl_Interp *interp, size_t size); MODULE_SCOPE Tcl_Obj * TclCreateLiteral(Interp *iPtr, const char *bytes, Tcl_Size length, size_t hash, int *newPtr, Namespace *nsPtr, int flags, LiteralEntry **globalPtrPtr); MODULE_SCOPE void TclDeleteExecEnv(ExecEnv *eePtr); MODULE_SCOPE void TclDeleteLiteralTable(Tcl_Interp *interp, LiteralTable *tablePtr); MODULE_SCOPE void TclEmitForwardJump(CompileEnv *envPtr, TclJumpType jumpType, JumpFixup *jumpFixupPtr); MODULE_SCOPE void TclEmitInvoke(CompileEnv *envPtr, int opcode, ...); MODULE_SCOPE ExceptionRange * TclGetExceptionRangeForPc(unsigned char *pc, int catchOnly, ByteCode *codePtr); MODULE_SCOPE void TclExpandJumpFixupArray(JumpFixupArray *fixupArrayPtr); MODULE_SCOPE int TclNRExecuteByteCode(Tcl_Interp *interp, ByteCode *codePtr); MODULE_SCOPE Tcl_Obj * TclFetchLiteral(CompileEnv *envPtr, Tcl_Size index); MODULE_SCOPE Tcl_Size TclFindCompiledLocal(const char *name, Tcl_Size nameChars, int create, CompileEnv *envPtr); MODULE_SCOPE int TclFixupForwardJump(CompileEnv *envPtr, JumpFixup *jumpFixupPtr, int jumpDist, int distThreshold); MODULE_SCOPE void TclFreeCompileEnv(CompileEnv *envPtr); MODULE_SCOPE void TclFreeJumpFixupArray(JumpFixupArray *fixupArrayPtr); MODULE_SCOPE int TclGetIndexFromToken(Tcl_Token *tokenPtr, size_t before, size_t after, int *indexPtr); MODULE_SCOPE ByteCode * TclInitByteCode(CompileEnv *envPtr); MODULE_SCOPE ByteCode * TclInitByteCodeObj(Tcl_Obj *objPtr, const Tcl_ObjType *typePtr, CompileEnv *envPtr); MODULE_SCOPE void TclInitCompileEnv(Tcl_Interp *interp, CompileEnv *envPtr, const char *string, size_t numBytes, const CmdFrame *invoker, int word); MODULE_SCOPE void TclInitJumpFixupArray(JumpFixupArray *fixupArrayPtr); MODULE_SCOPE void TclInitLiteralTable(LiteralTable *tablePtr); MODULE_SCOPE ExceptionRange *TclGetInnermostExceptionRange(CompileEnv *envPtr, int returnCode, ExceptionAux **auxPtrPtr); MODULE_SCOPE void TclAddLoopBreakFixup(CompileEnv *envPtr, ExceptionAux *auxPtr); MODULE_SCOPE void TclAddLoopContinueFixup(CompileEnv *envPtr, ExceptionAux *auxPtr); MODULE_SCOPE void TclFinalizeLoopExceptionRange(CompileEnv *envPtr, int range); #ifdef TCL_COMPILE_STATS MODULE_SCOPE char * TclLiteralStats(LiteralTable *tablePtr); MODULE_SCOPE int TclLog2(int value); #endif MODULE_SCOPE size_t TclLocalScalar(const char *bytes, size_t numBytes, CompileEnv *envPtr); MODULE_SCOPE size_t TclLocalScalarFromToken(Tcl_Token *tokenPtr, CompileEnv *envPtr); MODULE_SCOPE void TclOptimizeBytecode(void *envPtr); #ifdef TCL_COMPILE_DEBUG MODULE_SCOPE void TclDebugPrintByteCodeObj(Tcl_Obj *objPtr); #else #define TclDebugPrintByteCodeObj(objPtr) (void)(objPtr) #endif MODULE_SCOPE int TclPrintInstruction(ByteCode *codePtr, const unsigned char *pc); MODULE_SCOPE void TclPrintObject(FILE *outFile, Tcl_Obj *objPtr, Tcl_Size maxChars); MODULE_SCOPE void TclPrintSource(FILE *outFile, const char *string, Tcl_Size maxChars); MODULE_SCOPE void TclPushVarName(Tcl_Interp *interp, Tcl_Token *varTokenPtr, CompileEnv *envPtr, int flags, int *localIndexPtr, int *isScalarPtr); MODULE_SCOPE void TclPreserveByteCode(ByteCode *codePtr); MODULE_SCOPE void TclReleaseByteCode(ByteCode *codePtr); MODULE_SCOPE void TclReleaseLiteral(Tcl_Interp *interp, Tcl_Obj *objPtr); MODULE_SCOPE void TclInvalidateCmdLiteral(Tcl_Interp *interp, const char *name, Namespace *nsPtr); MODULE_SCOPE Tcl_ObjCmdProc TclSingleOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclSortingOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclVariadicOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNoIdentOpCmd; #ifdef TCL_COMPILE_DEBUG MODULE_SCOPE void TclVerifyGlobalLiteralTable(Interp *iPtr); MODULE_SCOPE void TclVerifyLocalLiteralTable(CompileEnv *envPtr); #endif MODULE_SCOPE int TclWordKnownAtCompileTime(Tcl_Token *tokenPtr, Tcl_Obj *valuePtr); MODULE_SCOPE void TclLogCommandInfo(Tcl_Interp *interp, const char *script, const char *command, Tcl_Size length, const unsigned char *pc, Tcl_Obj **tosPtr); MODULE_SCOPE Tcl_Obj * TclGetInnerContext(Tcl_Interp *interp, const unsigned char *pc, Tcl_Obj **tosPtr); MODULE_SCOPE Tcl_Obj * TclNewInstNameObj(unsigned char inst); MODULE_SCOPE int TclPushProcCallFrame(void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int isLambda); #endif /* TCL_MAJOR_VERSION > 8 */ /* *---------------------------------------------------------------- * Macros and flag values used by Tcl bytecode compilation and execution * modules inside the Tcl core but not used outside. *---------------------------------------------------------------- */ /* * Simplified form to access AuxData. * * void *TclFetchAuxData(CompileEng *envPtr, int index); */ #define TclFetchAuxData(envPtr, index) \ (envPtr)->auxDataArrayPtr[(index)].clientData #define LITERAL_ON_HEAP 0x01 #define LITERAL_CMD_NAME 0x02 #define LITERAL_UNSHARED 0x04 /* * Adjust the stack requirements. Manually used in cases where the stack * effect cannot be computed from the opcode and its operands, but is still * known at compile time. */ static inline void TclAdjustStackDepth( int delta, CompileEnv *envPtr) { if (delta < 0) { if ((int) envPtr->maxStackDepth < (int) envPtr->currStackDepth) { envPtr->maxStackDepth = envPtr->currStackDepth; } } envPtr->currStackDepth += delta; } #define TclGetStackDepth(envPtr) \ ((envPtr)->currStackDepth) #define TclSetStackDepth(depth, envPtr) \ (envPtr)->currStackDepth = (depth) /* * Verify that the current stack depth is what we think it should be. When * this is wrong, code generation is broken! */ static inline void TclCheckStackDepth( size_t depth, CompileEnv *envPtr) { if (depth != (size_t) envPtr->currStackDepth) { Tcl_Panic("bad stack depth computations: " "is %" TCL_Z_MODIFIER "u, should be %" TCL_Z_MODIFIER "u", (size_t) envPtr->currStackDepth, depth); } } /* * Update the stack requirements based on the instruction definition. It is * called by the macros TclEmitOpCode, TclEmitInst1 and TclEmitInst4. * Remark that the very last instruction of a bytecode always reduces the * stack level: INST_DONE or INST_POP, so that the maxStackdepth is always * updated. */ static inline void TclUpdateStackReqs( unsigned char op, int i, CompileEnv *envPtr) { int delta = tclInstructionTable[op].stackEffect; if (delta) { if (delta == INT_MIN) { delta = 1 - i; } TclAdjustStackDepth(delta, envPtr); } } /* * Macros used to update the flag that indicates if we are at the start of a * command, based on whether the opcode is INST_START_COMMAND. * * void TclUpdateAtCmdStart(unsigned char op, CompileEnv *envPtr); */ #define TclUpdateAtCmdStart(op, envPtr) \ if ((envPtr)->atCmdStart < 2) { \ (envPtr)->atCmdStart = ((op) == INST_START_CMD ? 1 : 0); \ } /* * Macro to emit an opcode byte into a CompileEnv's code array. The ANSI C * "prototype" for this macro is: * * void TclEmitOpcode(unsigned char op, CompileEnv *envPtr); */ #define TclEmitOpcode(op, envPtr) \ do { \ if ((envPtr)->codeNext == (envPtr)->codeEnd) { \ TclExpandCodeArray(envPtr); \ } \ *(envPtr)->codeNext++ = (unsigned char) (op); \ TclUpdateAtCmdStart(op, envPtr); \ TclUpdateStackReqs(op, 0, envPtr); \ } while (0) /* * Macros to emit an integer operand. The ANSI C "prototype" for these macros * are: * * void TclEmitInt1(int i, CompileEnv *envPtr); * void TclEmitInt4(int i, CompileEnv *envPtr); */ #define TclEmitInt1(i, envPtr) \ do { \ if ((envPtr)->codeNext == (envPtr)->codeEnd) { \ TclExpandCodeArray(envPtr); \ } \ *(envPtr)->codeNext++ = (unsigned char) ((unsigned int) (i)); \ } while (0) #define TclEmitInt4(i, envPtr) \ do { \ if (((envPtr)->codeNext + 4) > (envPtr)->codeEnd) { \ TclExpandCodeArray(envPtr); \ } \ *(envPtr)->codeNext++ = \ (unsigned char) ((unsigned int) (i) >> 24); \ *(envPtr)->codeNext++ = \ (unsigned char) ((unsigned int) (i) >> 16); \ *(envPtr)->codeNext++ = \ (unsigned char) ((unsigned int) (i) >> 8); \ *(envPtr)->codeNext++ = \ (unsigned char) ((unsigned int) (i) ); \ } while (0) /* * Macros to emit an instruction with signed or unsigned integer operands. * Four byte integers are stored in "big-endian" order with the high order * byte stored at the lowest address. The ANSI C "prototypes" for these macros * are: * * void TclEmitInstInt1(unsigned char op, int i, CompileEnv *envPtr); * void TclEmitInstInt4(unsigned char op, int i, CompileEnv *envPtr); */ #define TclEmitInstInt1(op, i, envPtr) \ do { \ if (((envPtr)->codeNext + 2) > (envPtr)->codeEnd) { \ TclExpandCodeArray(envPtr); \ } \ *(envPtr)->codeNext++ = (unsigned char) (op); \ *(envPtr)->codeNext++ = (unsigned char) ((unsigned int) (i)); \ TclUpdateAtCmdStart(op, envPtr); \ TclUpdateStackReqs(op, i, envPtr); \ } while (0) #define TclEmitInstInt4(op, i, envPtr) \ do { \ if (((envPtr)->codeNext + 5) > (envPtr)->codeEnd) { \ TclExpandCodeArray(envPtr); \ } \ *(envPtr)->codeNext++ = (unsigned char) (op); \ *(envPtr)->codeNext++ = \ (unsigned char) ((unsigned int) (i) >> 24); \ *(envPtr)->codeNext++ = \ (unsigned char) ((unsigned int) (i) >> 16); \ *(envPtr)->codeNext++ = \ (unsigned char) ((unsigned int) (i) >> 8); \ *(envPtr)->codeNext++ = \ (unsigned char) ((unsigned int) (i) ); \ TclUpdateAtCmdStart(op, envPtr); \ TclUpdateStackReqs(op, i, envPtr); \ } while (0) /* * Macro to push a Tcl object onto the Tcl evaluation stack. It emits the * object's one or four byte array index into the CompileEnv's code array. * These support, respectively, a maximum of 256 (2**8) and 2**32 objects in a * CompileEnv. The ANSI C "prototype" for this macro is: * * void TclEmitPush(int objIndex, CompileEnv *envPtr); */ #define TclEmitPush(objIndex, envPtr) \ do { \ int _objIndexCopy = (objIndex); \ if (_objIndexCopy <= 255) { \ TclEmitInstInt1(INST_PUSH1, _objIndexCopy, (envPtr)); \ } else { \ TclEmitInstInt4(INST_PUSH4, _objIndexCopy, (envPtr)); \ } \ } while (0) /* * Macros to update a (signed or unsigned) integer starting at a pointer. The * two variants depend on the number of bytes. The ANSI C "prototypes" for * these macros are: * * void TclStoreInt1AtPtr(int i, unsigned char *p); * void TclStoreInt4AtPtr(int i, unsigned char *p); */ #define TclStoreInt1AtPtr(i, p) \ *(p) = (unsigned char) ((unsigned int) (i)) #define TclStoreInt4AtPtr(i, p) \ do { \ *(p) = (unsigned char) ((unsigned int) (i) >> 24); \ *(p+1) = (unsigned char) ((unsigned int) (i) >> 16); \ *(p+2) = (unsigned char) ((unsigned int) (i) >> 8); \ *(p+3) = (unsigned char) ((unsigned int) (i) ); \ } while (0) /* * Macros to update instructions at a particular pc with a new op code and a * (signed or unsigned) int operand. The ANSI C "prototypes" for these macros * are: * * void TclUpdateInstInt1AtPc(unsigned char op, int i, unsigned char *pc); * void TclUpdateInstInt4AtPc(unsigned char op, int i, unsigned char *pc); */ #define TclUpdateInstInt1AtPc(op, i, pc) \ do { \ *(pc) = (unsigned char) (op); \ TclStoreInt1AtPtr((i), ((pc)+1)); \ } while (0) #define TclUpdateInstInt4AtPc(op, i, pc) \ do { \ *(pc) = (unsigned char) (op); \ TclStoreInt4AtPtr((i), ((pc)+1)); \ } while (0) /* * Macro to fix up a forward jump to point to the current code-generation * position in the bytecode being created (the most common case). The ANSI C * "prototypes" for this macro is: * * int TclFixupForwardJumpToHere(CompileEnv *envPtr, JumpFixup *fixupPtr, * int threshold); */ #define TclFixupForwardJumpToHere(envPtr, fixupPtr, threshold) \ TclFixupForwardJump((envPtr), (fixupPtr), \ (envPtr)->codeNext-(envPtr)->codeStart-(int)(fixupPtr)->codeOffset, \ (threshold)) /* * Macros to get a signed integer (GET_INT{1,2}) or an unsigned int * (GET_UINT{1,2}) from a pointer. There are two variants for each return type * that depend on the number of bytes fetched. The ANSI C "prototypes" for * these macros are: * * int TclGetInt1AtPtr(unsigned char *p); * int TclGetInt4AtPtr(unsigned char *p); * unsigned int TclGetUInt1AtPtr(unsigned char *p); * unsigned int TclGetUInt4AtPtr(unsigned char *p); */ /* * The TclGetInt1AtPtr macro is tricky because we want to do sign extension on * the 1-byte value. Unfortunately the "char" type isn't signed on all * platforms so sign-extension doesn't always happen automatically. Sometimes * we can explicitly declare the pointer to be signed, but other times we have * to explicitly sign-extend the value in software. */ #ifndef __CHAR_UNSIGNED__ # define TclGetInt1AtPtr(p) ((int) *((char *) p)) #elif defined(HAVE_SIGNED_CHAR) # define TclGetInt1AtPtr(p) ((int) *((signed char *) p)) #else # define TclGetInt1AtPtr(p) \ ((int) ((*((char *) p)) | ((*(p) & 0200) ? (-256) : 0))) #endif #define TclGetInt4AtPtr(p) \ ((int) ((TclGetUInt1AtPtr(p) << 24) | \ (*((p)+1) << 16) | \ (*((p)+2) << 8) | \ (*((p)+3)))) #define TclGetUInt1AtPtr(p) \ ((unsigned int) *(p)) #define TclGetUInt4AtPtr(p) \ ((unsigned int) ((*(p) << 24) | \ (*((p)+1) << 16) | \ (*((p)+2) << 8) | \ (*((p)+3)))) /* * Macros used to compute the minimum and maximum of two values. The ANSI C * "prototypes" for these macros are: * * size_t TclMin(size_t i, size_t j); * size_t TclMax(size_t i, size_t j); */ #define TclMin(i, j) ((((size_t) i) + 1 < ((size_t) j) + 1 )? (i) : (j)) #define TclMax(i, j) ((((size_t) i) + 1 > ((size_t) j) + 1 )? (i) : (j)) /* * Convenience macros for use when compiling bodies of commands. The ANSI C * "prototype" for these macros are: * * static void BODY(Tcl_Token *tokenPtr, int word); */ #define BODY(tokenPtr, word) \ SetLineInformation((word)); \ TclCompileCmdWord(interp, (tokenPtr)+1, (tokenPtr)->numComponents, \ envPtr) /* * Convenience macro for use when compiling tokens to be pushed. The ANSI C * "prototype" for this macro is: * * static void CompileTokens(CompileEnv *envPtr, Tcl_Token *tokenPtr, * Tcl_Interp *interp); */ #define CompileTokens(envPtr, tokenPtr, interp) \ TclCompileTokens((interp), (tokenPtr)+1, (tokenPtr)->numComponents, \ (envPtr)); /* * Convenience macros for use when pushing literals. The ANSI C "prototype" for * these macros are: * * static void PushLiteral(CompileEnv *envPtr, * const char *string, Tcl_Size length); * static void PushStringLiteral(CompileEnv *envPtr, * const char *string); */ #define PushLiteral(envPtr, string, length) \ TclEmitPush(TclRegisterLiteral((envPtr), (string), (length), 0), (envPtr)) #define PushStringLiteral(envPtr, string) \ PushLiteral((envPtr), (string), sizeof(string "") - 1) /* * Macro to advance to the next token; it is more mnemonic than the address * arithmetic that it replaces. The ANSI C "prototype" for this macro is: * * static Tcl_Token * TokenAfter(Tcl_Token *tokenPtr); */ #define TokenAfter(tokenPtr) \ ((tokenPtr) + ((tokenPtr)->numComponents + 1)) /* * Macro to get the offset to the next instruction to be issued. The ANSI C * "prototype" for this macro is: * * static ptrdiff_t CurrentOffset(CompileEnv *envPtr); */ #define CurrentOffset(envPtr) \ ((envPtr)->codeNext - (envPtr)->codeStart) /* * Note: the exceptDepth is a bit of a misnomer: TEBC only needs the * maximal depth of nested CATCH ranges in order to alloc runtime * memory. These macros should compute precisely that? OTOH, the nesting depth * of LOOP ranges is an interesting datum for debugging purposes, and that is * what we compute now. * * static int ExceptionRangeStarts(CompileEnv *envPtr, Tcl_Size index); * static void ExceptionRangeEnds(CompileEnv *envPtr, Tcl_Size index); * static void ExceptionRangeTarget(CompileEnv *envPtr, Tcl_Size index, LABEL); */ #define ExceptionRangeStarts(envPtr, index) \ (((envPtr)->exceptDepth++), \ ((envPtr)->maxExceptDepth = \ TclMax((envPtr)->exceptDepth, (envPtr)->maxExceptDepth)), \ ((envPtr)->exceptArrayPtr[(index)].codeOffset = CurrentOffset(envPtr))) #define ExceptionRangeEnds(envPtr, index) \ (((envPtr)->exceptDepth--), \ ((envPtr)->exceptArrayPtr[(index)].numCodeBytes = \ CurrentOffset(envPtr) - (int)(envPtr)->exceptArrayPtr[(index)].codeOffset)) #define ExceptionRangeTarget(envPtr, index, targetType) \ ((envPtr)->exceptArrayPtr[(index)].targetType = CurrentOffset(envPtr)) /* * Check if there is an LVT for compiled locals */ #define EnvHasLVT(envPtr) \ (envPtr->procPtr || envPtr->iPtr->varFramePtr->localCachePtr) /* * Macros for making it easier to deal with tokens and DStrings. */ #define TclDStringAppendToken(dsPtr, tokenPtr) \ Tcl_DStringAppend((dsPtr), (tokenPtr)->start, (tokenPtr)->size) #define TclRegisterDStringLiteral(envPtr, dsPtr) \ TclRegisterLiteral(envPtr, Tcl_DStringValue(dsPtr), \ Tcl_DStringLength(dsPtr), /*flags*/ 0) /* * Macro that encapsulates an efficiency trick that avoids a function call for * the simplest of compiles. The ANSI C "prototype" for this macro is: * * static void CompileWord(CompileEnv *envPtr, Tcl_Token *tokenPtr, * Tcl_Interp *interp, int word); */ #define CompileWord(envPtr, tokenPtr, interp, word) \ if ((tokenPtr)->type == TCL_TOKEN_SIMPLE_WORD) { \ PushLiteral((envPtr), (tokenPtr)[1].start, (tokenPtr)[1].size); \ } else { \ SetLineInformation((word)); \ CompileTokens((envPtr), (tokenPtr), (interp)); \ } /* * TIP #280: Remember the per-word line information of the current command. An * index is used instead of a pointer as recursive compilation may reallocate, * i.e. move, the array. This is also the reason to save the nuloc now, it may * change during the course of the function. * * Macro to encapsulate the variable definition and setup. */ #define DefineLineInformation \ ExtCmdLoc *mapPtr = envPtr->extCmdMapPtr; \ Tcl_Size eclIndex = mapPtr->nuloc - 1 #define SetLineInformation(word) \ envPtr->line = mapPtr->loc[eclIndex].line[(word)]; \ envPtr->clNext = mapPtr->loc[eclIndex].next[(word)] #define PushVarNameWord(i,v,e,f,l,sc,word) \ SetLineInformation(word); \ TclPushVarName(i,v,e,f,l,sc) /* * Often want to issue one of two versions of an instruction based on whether * the argument will fit in a single byte or not. This makes it much clearer. */ #define Emit14Inst(nm,idx,envPtr) \ if (idx <= 255) { \ TclEmitInstInt1(nm##1,idx,envPtr); \ } else { \ TclEmitInstInt4(nm##4,idx,envPtr); \ } /* * How to get an anonymous local variable (used for holding temporary values * off the stack) or a local simple scalar. */ #define AnonymousLocal(envPtr) \ (TclFindCompiledLocal(NULL, /*nameChars*/ 0, /*create*/ 1, (envPtr))) #define LocalScalar(chars,len,envPtr) \ TclLocalScalar(chars, len, envPtr) #define LocalScalarFromToken(tokenPtr,envPtr) \ TclLocalScalarFromToken(tokenPtr, envPtr) /* * Flags bits used by TclPushVarName. */ #define TCL_NO_LARGE_INDEX 1 /* Do not return localIndex value > 255 */ #define TCL_NO_ELEMENT 2 /* Do not push the array element. */ /* * Flags bits used by lreplace4 instruction */ #define TCL_LREPLACE4_END_IS_LAST 1 /* "end" refers to last element */ #define TCL_LREPLACE4_SINGLE_INDEX 2 /* Second index absent (pure insert) */ /* * DTrace probe macros (NOPs if DTrace support is not enabled). */ /* * Define the following macros to enable debug logging of the DTrace proc, * cmd, and inst probes. Note that this does _not_ require a platform with * DTrace, it simply logs all probe output to /tmp/tclDTraceDebug-[pid].log. * * If the second macro is defined, logging to file starts immediately, * otherwise only after the first call to [tcl::dtrace]. Note that the debug * probe data is always computed, even when it is not logged to file. * * Defining the third macro enables debug logging of inst probes (disabled * by default due to the significant performance impact). */ /* #define TCL_DTRACE_DEBUG 1 #define TCL_DTRACE_DEBUG_LOG_ENABLED 1 #define TCL_DTRACE_DEBUG_INST_PROBES 1 */ #if !(defined(TCL_DTRACE_DEBUG) && defined(__GNUC__)) #ifdef USE_DTRACE #if defined(__GNUC__) && __GNUC__ > 2 /* * Use gcc branch prediction hint to minimize cost of DTrace ENABLED checks. */ #define unlikely(x) (__builtin_expect((x), 0)) #else #define unlikely(x) (x) #endif #define TCL_DTRACE_PROC_ENTRY_ENABLED() unlikely(TCL_PROC_ENTRY_ENABLED()) #define TCL_DTRACE_PROC_RETURN_ENABLED() unlikely(TCL_PROC_RETURN_ENABLED()) #define TCL_DTRACE_PROC_RESULT_ENABLED() unlikely(TCL_PROC_RESULT_ENABLED()) #define TCL_DTRACE_PROC_ARGS_ENABLED() unlikely(TCL_PROC_ARGS_ENABLED()) #define TCL_DTRACE_PROC_INFO_ENABLED() unlikely(TCL_PROC_INFO_ENABLED()) #define TCL_DTRACE_PROC_ENTRY(a0, a1, a2) TCL_PROC_ENTRY(a0, a1, a2) #define TCL_DTRACE_PROC_RETURN(a0, a1) TCL_PROC_RETURN(a0, a1) #define TCL_DTRACE_PROC_RESULT(a0, a1, a2, a3) TCL_PROC_RESULT(a0, a1, a2, a3) #define TCL_DTRACE_PROC_ARGS(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) \ TCL_PROC_ARGS(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) #define TCL_DTRACE_PROC_INFO(a0, a1, a2, a3, a4, a5, a6, a7) \ TCL_PROC_INFO(a0, a1, a2, a3, a4, a5, a6, a7) #define TCL_DTRACE_CMD_ENTRY_ENABLED() unlikely(TCL_CMD_ENTRY_ENABLED()) #define TCL_DTRACE_CMD_RETURN_ENABLED() unlikely(TCL_CMD_RETURN_ENABLED()) #define TCL_DTRACE_CMD_RESULT_ENABLED() unlikely(TCL_CMD_RESULT_ENABLED()) #define TCL_DTRACE_CMD_ARGS_ENABLED() unlikely(TCL_CMD_ARGS_ENABLED()) #define TCL_DTRACE_CMD_INFO_ENABLED() unlikely(TCL_CMD_INFO_ENABLED()) #define TCL_DTRACE_CMD_ENTRY(a0, a1, a2) TCL_CMD_ENTRY(a0, a1, a2) #define TCL_DTRACE_CMD_RETURN(a0, a1) TCL_CMD_RETURN(a0, a1) #define TCL_DTRACE_CMD_RESULT(a0, a1, a2, a3) TCL_CMD_RESULT(a0, a1, a2, a3) #define TCL_DTRACE_CMD_ARGS(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) \ TCL_CMD_ARGS(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) #define TCL_DTRACE_CMD_INFO(a0, a1, a2, a3, a4, a5, a6, a7) \ TCL_CMD_INFO(a0, a1, a2, a3, a4, a5, a6, a7) #define TCL_DTRACE_INST_START_ENABLED() unlikely(TCL_INST_START_ENABLED()) #define TCL_DTRACE_INST_DONE_ENABLED() unlikely(TCL_INST_DONE_ENABLED()) #define TCL_DTRACE_INST_START(a0, a1, a2) TCL_INST_START(a0, a1, a2) #define TCL_DTRACE_INST_DONE(a0, a1, a2) TCL_INST_DONE(a0, a1, a2) #define TCL_DTRACE_TCL_PROBE_ENABLED() unlikely(TCL_TCL_PROBE_ENABLED()) #define TCL_DTRACE_TCL_PROBE(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) \ TCL_TCL_PROBE(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) #define TCL_DTRACE_DEBUG_LOG() MODULE_SCOPE void TclDTraceInfo(Tcl_Obj *info, const char **args, Tcl_Size *argsi); #else /* USE_DTRACE */ #define TCL_DTRACE_PROC_ENTRY_ENABLED() 0 #define TCL_DTRACE_PROC_RETURN_ENABLED() 0 #define TCL_DTRACE_PROC_RESULT_ENABLED() 0 #define TCL_DTRACE_PROC_ARGS_ENABLED() 0 #define TCL_DTRACE_PROC_INFO_ENABLED() 0 #define TCL_DTRACE_PROC_ENTRY(a0, a1, a2) {if (a0) {}} #define TCL_DTRACE_PROC_RETURN(a0, a1) {if (a0) {}} #define TCL_DTRACE_PROC_RESULT(a0, a1, a2, a3) {if (a0) {}; if (a3) {}} #define TCL_DTRACE_PROC_ARGS(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) {} #define TCL_DTRACE_PROC_INFO(a0, a1, a2, a3, a4, a5, a6, a7) {} #define TCL_DTRACE_CMD_ENTRY_ENABLED() 0 #define TCL_DTRACE_CMD_RETURN_ENABLED() 0 #define TCL_DTRACE_CMD_RESULT_ENABLED() 0 #define TCL_DTRACE_CMD_ARGS_ENABLED() 0 #define TCL_DTRACE_CMD_INFO_ENABLED() 0 #define TCL_DTRACE_CMD_ENTRY(a0, a1, a2) {} #define TCL_DTRACE_CMD_RETURN(a0, a1) {} #define TCL_DTRACE_CMD_RESULT(a0, a1, a2, a3) {} #define TCL_DTRACE_CMD_ARGS(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) {} #define TCL_DTRACE_CMD_INFO(a0, a1, a2, a3, a4, a5, a6, a7) {} #define TCL_DTRACE_INST_START_ENABLED() 0 #define TCL_DTRACE_INST_DONE_ENABLED() 0 #define TCL_DTRACE_INST_START(a0, a1, a2) {} #define TCL_DTRACE_INST_DONE(a0, a1, a2) {} #define TCL_DTRACE_TCL_PROBE_ENABLED() 0 #define TCL_DTRACE_TCL_PROBE(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) {} #define TclDTraceInfo(info, args, argsi) {*args = ""; *argsi = 0;} #endif /* USE_DTRACE */ #else /* TCL_DTRACE_DEBUG */ #define USE_DTRACE 1 #if !defined(TCL_DTRACE_DEBUG_LOG_ENABLED) || !(TCL_DTRACE_DEBUG_LOG_ENABLED) #undef TCL_DTRACE_DEBUG_LOG_ENABLED #define TCL_DTRACE_DEBUG_LOG_ENABLED 0 #endif #if !defined(TCL_DTRACE_DEBUG_INST_PROBES) || !(TCL_DTRACE_DEBUG_INST_PROBES) #undef TCL_DTRACE_DEBUG_INST_PROBES #define TCL_DTRACE_DEBUG_INST_PROBES 0 #endif MODULE_SCOPE int tclDTraceDebugEnabled, tclDTraceDebugIndent; MODULE_SCOPE FILE *tclDTraceDebugLog; MODULE_SCOPE void TclDTraceOpenDebugLog(void); MODULE_SCOPE void TclDTraceInfo(Tcl_Obj *info, const char **args, Tcl_Size *argsi); #define TCL_DTRACE_DEBUG_LOG() \ int tclDTraceDebugEnabled = TCL_DTRACE_DEBUG_LOG_ENABLED; \ int tclDTraceDebugIndent = 0; \ FILE *tclDTraceDebugLog = NULL; \ void TclDTraceOpenDebugLog(void) { \ char n[35]; \ snprintf(n, sizeof(n), "/tmp/tclDTraceDebug-%" TCL_Z_MODIFIER "u.log", \ (size_t) getpid()); \ tclDTraceDebugLog = fopen(n, "a"); \ } #define TclDTraceDbgMsg(p, m, ...) \ do { \ if (tclDTraceDebugEnabled) { \ int _l, _t = 0; \ if (!tclDTraceDebugLog) { TclDTraceOpenDebugLog(); } \ fprintf(tclDTraceDebugLog, "%.12s:%.4d:%n", \ strrchr(__FILE__, '/')+1, __LINE__, &_l); _t += _l; \ fprintf(tclDTraceDebugLog, " %.*s():%n", \ (_t < 18 ? 18 - _t : 0) + 18, __func__, &_l); _t += _l; \ fprintf(tclDTraceDebugLog, "%*s" p "%n", \ (_t < 40 ? 40 - _t : 0) + 2 * tclDTraceDebugIndent, \ "", &_l); _t += _l; \ fprintf(tclDTraceDebugLog, "%*s" m "\n", \ (_t < 64 ? 64 - _t : 1), "", ##__VA_ARGS__); \ fflush(tclDTraceDebugLog); \ } \ } while (0) #define TCL_DTRACE_PROC_ENTRY_ENABLED() 1 #define TCL_DTRACE_PROC_RETURN_ENABLED() 1 #define TCL_DTRACE_PROC_RESULT_ENABLED() 1 #define TCL_DTRACE_PROC_ARGS_ENABLED() 1 #define TCL_DTRACE_PROC_INFO_ENABLED() 1 #define TCL_DTRACE_PROC_ENTRY(a0, a1, a2) \ tclDTraceDebugIndent++; \ TclDTraceDbgMsg("-> proc-entry", "%s %" TCL_SIZE_MODIFIER "d %p", a0, a1, a2) #define TCL_DTRACE_PROC_RETURN(a0, a1) \ TclDTraceDbgMsg("<- proc-return", "%s %d", a0, a1); \ tclDTraceDebugIndent-- #define TCL_DTRACE_PROC_RESULT(a0, a1, a2, a3) \ TclDTraceDbgMsg(" | proc-result", "%s %d %s %p", a0, a1, a2, a3) #define TCL_DTRACE_PROC_ARGS(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) \ TclDTraceDbgMsg(" | proc-args", "%s %s %s %s %s %s %s %s %s %s", a0, \ a1, a2, a3, a4, a5, a6, a7, a8, a9) #define TCL_DTRACE_PROC_INFO(a0, a1, a2, a3, a4, a5, a6, a7) \ TclDTraceDbgMsg(" | proc-info", "%s %s %s %s %d %d %s %s", a0, a1, \ a2, a3, a4, a5, a6, a7) #define TCL_DTRACE_CMD_ENTRY_ENABLED() 1 #define TCL_DTRACE_CMD_RETURN_ENABLED() 1 #define TCL_DTRACE_CMD_RESULT_ENABLED() 1 #define TCL_DTRACE_CMD_ARGS_ENABLED() 1 #define TCL_DTRACE_CMD_INFO_ENABLED() 1 #define TCL_DTRACE_CMD_ENTRY(a0, a1, a2) \ tclDTraceDebugIndent++; \ TclDTraceDbgMsg("-> cmd-entry", "%s %" TCL_SIZE_MODIFIER "d %p", a0, a1, a2) #define TCL_DTRACE_CMD_RETURN(a0, a1) \ TclDTraceDbgMsg("<- cmd-return", "%s %d", a0, a1); \ tclDTraceDebugIndent-- #define TCL_DTRACE_CMD_RESULT(a0, a1, a2, a3) \ TclDTraceDbgMsg(" | cmd-result", "%s %d %s %p", a0, a1, a2, a3) #define TCL_DTRACE_CMD_ARGS(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) \ TclDTraceDbgMsg(" | cmd-args", "%s %s %s %s %s %s %s %s %s %s", a0, \ a1, a2, a3, a4, a5, a6, a7, a8, a9) #define TCL_DTRACE_CMD_INFO(a0, a1, a2, a3, a4, a5, a6, a7) \ TclDTraceDbgMsg(" | cmd-info", "%s %s %s %s %" TCL_SIZE_MODIFIER "d %" TCL_SIZE_MODIFIER "d %s %s", a0, a1, \ a2, a3, a4, a5, a6, a7) #define TCL_DTRACE_INST_START_ENABLED() TCL_DTRACE_DEBUG_INST_PROBES #define TCL_DTRACE_INST_DONE_ENABLED() TCL_DTRACE_DEBUG_INST_PROBES #define TCL_DTRACE_INST_START(a0, a1, a2) \ TclDTraceDbgMsg(" | inst-start", "%s %d %p", a0, a1, a2) #define TCL_DTRACE_INST_DONE(a0, a1, a2) \ TclDTraceDbgMsg(" | inst-end", "%s %d %p", a0, a1, a2) #define TCL_DTRACE_TCL_PROBE_ENABLED() 1 #define TCL_DTRACE_TCL_PROBE(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) \ do { \ tclDTraceDebugEnabled = 1; \ TclDTraceDbgMsg(" | tcl-probe", "%s %s %s %s %s %s %s %s %s %s", a0, \ a1, a2, a3, a4, a5, a6, a7, a8, a9); \ } while (0) #endif /* TCL_DTRACE_DEBUG */ #endif /* _TCLCOMPILATION */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclConfig.c0000644000175000017500000002477614726623136015266 0ustar sergeisergei/* * tclConfig.c -- * * This file provides the facilities which allow Tcl and other packages * to embed configuration information into their binary libraries. * * Copyright © 2002 Andreas Kupries * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * Internal structure to hold embedded configuration information. * * Our structure is a two-level dictionary associated with the 'interp'. The * first level is keyed with the package name and maps to the dictionary for * that package. The package dictionary is keyed with metadata keys and maps * to the metadata value for that key. This is package specific. The metadata * values are in UTF-8, converted from the external representation given to us * by the caller. */ #define ASSOC_KEY "tclPackageAboutDict" /* * A ClientData struct for the QueryConfig command. Store the three bits * of data we need; the package name for which we store a config dict, * the (Tcl_Interp *) in which it is stored, and the encoding. */ typedef struct { Tcl_Obj *pkg; Tcl_Interp *interp; char *encoding; } QCCD; /* * Static functions in this file: */ static Tcl_ObjCmdProc QueryConfigObjCmd; static Tcl_CmdDeleteProc QueryConfigDelete; static Tcl_InterpDeleteProc ConfigDictDeleteProc; static Tcl_Obj * GetConfigDict(Tcl_Interp *interp); /* *---------------------------------------------------------------------- * * Tcl_RegisterConfig -- * * See TIP#59 for details on what this function does. * * Results: * None. * * Side effects: * Creates namespace and cfg query command in it as per TIP #59. * *---------------------------------------------------------------------- */ void Tcl_RegisterConfig( Tcl_Interp *interp, /* Interpreter the configuration command is * registered in. */ const char *pkgName, /* Name of the package registering the * embedded configuration. ASCII, thus in * UTF-8 too. */ const Tcl_Config *configuration, /* Embedded configuration. */ const char *valEncoding) /* Name of the encoding used to store the * configuration values, ASCII, thus UTF-8. */ { Tcl_Obj *pDB, *pkgDict; Tcl_DString cmdName; const Tcl_Config *cfg; QCCD *cdPtr = (QCCD *)Tcl_Alloc(sizeof(QCCD)); cdPtr->interp = interp; if (valEncoding) { cdPtr->encoding = (char *)Tcl_Alloc(strlen(valEncoding)+1); strcpy(cdPtr->encoding, valEncoding); } else { cdPtr->encoding = NULL; } cdPtr->pkg = Tcl_NewStringObj(pkgName, -1); /* * Phase I: Adding the provided information to the internal database of * package meta data. * * Phase II: Create a command for querying this database, specific to the * package registering its configuration. This is the approved interface * in TIP 59. In the future a more general interface should be done, as * follow-up to TIP 59. Simply because our database is now general across * packages, and not a structure tied to one package. * * Note, the created command will have a reference through its clientdata. */ Tcl_IncrRefCount(cdPtr->pkg); /* * For venc == NULL aka bogus encoding we skip the step setting up the * dictionaries visible at Tcl level. I.e. they are not filled */ pDB = GetConfigDict(interp); /* * Retrieve package specific configuration... */ if (Tcl_DictObjGet(interp, pDB, cdPtr->pkg, &pkgDict) != TCL_OK || (pkgDict == NULL)) { pkgDict = Tcl_NewDictObj(); } else if (Tcl_IsShared(pkgDict)) { pkgDict = Tcl_DuplicateObj(pkgDict); } /* * Extend the package configuration... * We cannot assume that the encodings are initialized, therefore * store the value as-is in a byte array. See Bug [9b2e636361]. */ for (cfg=configuration ; cfg->key!=NULL && cfg->key[0]!='\0' ; cfg++) { TclDictPut(interp, pkgDict, cfg->key, Tcl_NewByteArrayObj((unsigned char *)cfg->value, strlen(cfg->value))); } /* * Write the changes back into the overall database. */ Tcl_DictObjPut(interp, pDB, cdPtr->pkg, pkgDict); /* * Now create the interface command for retrieval of the package * information. */ Tcl_DStringInit(&cmdName); TclDStringAppendLiteral(&cmdName, "::"); Tcl_DStringAppend(&cmdName, pkgName, -1); /* * The incomplete command name is the name of the namespace to place it * in. */ if (Tcl_FindNamespace(interp, Tcl_DStringValue(&cmdName), NULL, TCL_GLOBAL_ONLY) == NULL) { if (Tcl_CreateNamespace(interp, Tcl_DStringValue(&cmdName), NULL, NULL) == NULL) { Tcl_Panic("%s.\n%s: %s", Tcl_GetStringResult(interp), "Tcl_RegisterConfig", "Unable to create namespace for package configuration."); } } TclDStringAppendLiteral(&cmdName, "::pkgconfig"); if (Tcl_CreateObjCommand(interp, Tcl_DStringValue(&cmdName), QueryConfigObjCmd, cdPtr, QueryConfigDelete) == NULL) { Tcl_Panic("%s: %s", "Tcl_RegisterConfig", "Unable to create query command for package configuration"); } Tcl_DStringFree(&cmdName); } /* *---------------------------------------------------------------------- * * QueryConfigObjCmd -- * * Implementation of "::::pkgconfig", the command to query * configuration information embedded into a library. * * Results: * A standard Tcl result. * * Side effects: * See the manual for what this command does. * *---------------------------------------------------------------------- */ static int QueryConfigObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { QCCD *cdPtr = (QCCD *)clientData; Tcl_Obj *pkgName = cdPtr->pkg; Tcl_Obj *pDB, *pkgDict, *val, *listPtr; Tcl_Size m, n = 0; static const char *const subcmdStrings[] = { "get", "list", NULL }; enum subcmds { CFG_GET, CFG_LIST } index; Tcl_DString conv; Tcl_Encoding venc = NULL; const char *value; if ((objc < 2) || (objc > 3)) { Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?arg?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], subcmdStrings, "subcommand", 0, &index) != TCL_OK) { return TCL_ERROR; } pDB = GetConfigDict(interp); if (Tcl_DictObjGet(interp, pDB, pkgName, &pkgDict) != TCL_OK || pkgDict == NULL) { /* * Maybe a Tcl_Panic is better, because the package data has to be * present. */ Tcl_SetObjResult(interp, Tcl_NewStringObj("package not known", -1)); Tcl_SetErrorCode(interp, "TCL", "FATAL", "PKGCFG_BASE", TclGetString(pkgName), (char *)NULL); return TCL_ERROR; } switch (index) { case CFG_GET: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "key"); return TCL_ERROR; } if (Tcl_DictObjGet(interp, pkgDict, objv[2], &val) != TCL_OK || val == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("key not known", -1)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CONFIG", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; } if (cdPtr->encoding) { venc = Tcl_GetEncoding(interp, cdPtr->encoding); if (!venc) { return TCL_ERROR; } } /* * Value is stored as-is in a byte array, see Bug [9b2e636361], * so we have to decode it first. */ value = (const char *) Tcl_GetBytesFromObj(interp, val, &n); if (value == NULL) { return TCL_ERROR; } value = Tcl_ExternalToUtfDString(venc, value, n, &conv); Tcl_SetObjResult(interp, Tcl_NewStringObj(value, Tcl_DStringLength(&conv))); Tcl_DStringFree(&conv); return TCL_OK; case CFG_LIST: if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } Tcl_DictObjSize(interp, pkgDict, &m); listPtr = Tcl_NewListObj(m, NULL); if (!listPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "insufficient memory to create list", -1)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); return TCL_ERROR; } if (m) { Tcl_DictSearch s; Tcl_Obj *key; int done; for (Tcl_DictObjFirst(interp, pkgDict, &s, &key, NULL, &done); !done; Tcl_DictObjNext(&s, &key, NULL, &done)) { Tcl_ListObjAppendElement(NULL, listPtr, key); } } Tcl_SetObjResult(interp, listPtr); return TCL_OK; default: Tcl_Panic("QueryConfigObjCmd: Unknown subcommand to 'pkgconfig'. This can't happen"); break; } return TCL_ERROR; } /* *------------------------------------------------------------------------- * * QueryConfigDelete -- * * Command delete function. Cleans up after the configuration query * command when it is deleted by the user or during finalization. * * Results: * None. * * Side effects: * Deallocates all non-transient memory allocated by Tcl_RegisterConfig. * *------------------------------------------------------------------------- */ static void QueryConfigDelete( void *clientData) { QCCD *cdPtr = (QCCD *)clientData; Tcl_Obj *pkgName = cdPtr->pkg; Tcl_Obj *pDB = GetConfigDict(cdPtr->interp); Tcl_DictObjRemove(NULL, pDB, pkgName); Tcl_DecrRefCount(pkgName); if (cdPtr->encoding) { Tcl_Free(cdPtr->encoding); } Tcl_Free(cdPtr); } /* *------------------------------------------------------------------------- * * GetConfigDict -- * * Retrieve the package metadata database from the interpreter. * Initializes it, if not present yet. * * Results: * A Tcl_Obj reference * * Side effects: * May allocate a Tcl_Obj. * *------------------------------------------------------------------------- */ static Tcl_Obj * GetConfigDict( Tcl_Interp *interp) { Tcl_Obj *pDB = (Tcl_Obj *)Tcl_GetAssocData(interp, ASSOC_KEY, NULL); if (pDB == NULL) { pDB = Tcl_NewDictObj(); Tcl_IncrRefCount(pDB); Tcl_SetAssocData(interp, ASSOC_KEY, ConfigDictDeleteProc, pDB); } return pDB; } /* *---------------------------------------------------------------------- * * ConfigDictDeleteProc -- * * This function is associated with the "Package About dict" assoc data * for an interpreter; it is invoked when the interpreter is deleted in * order to free the information associated with any pending error * reports. * * Results: * None. * * Side effects: * The package metadata database is freed. * *---------------------------------------------------------------------- */ static void ConfigDictDeleteProc( void *clientData, /* Pointer to Tcl_Obj. */ TCL_UNUSED(Tcl_Interp *)) { Tcl_DecrRefCount((Tcl_Obj *)clientData); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclDTrace.d0000644000175000017500000001672114726623136015213 0ustar sergeisergei/* * tclDTrace.d -- * * Tcl DTrace provider. * * Copyright (c) 2007-2008 Daniel A. Steffen * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ typedef struct Tcl_Obj Tcl_Obj; typedef ptrdiff_t Tcl_Size; /* * Tcl DTrace probes */ provider tcl { /***************************** proc probes *****************************/ /* * tcl*:::proc-entry probe * triggered immediately before proc bytecode execution * arg0: proc name (string) * arg1: number of arguments (Tcl_Size) * arg2: array of proc argument objects (Tcl_Obj**) */ probe proc__entry(const char *name, Tcl_Size objc, struct Tcl_Obj **objv); /* * tcl*:::proc-return probe * triggered immediately after proc bytecode execution * arg0: proc name (string) * arg1: return code (int) */ probe proc__return(const char *name, int code); /* * tcl*:::proc-result probe * triggered after proc-return probe and result processing * arg0: proc name (string) * arg1: return code (int) * arg2: proc result (string) * arg3: proc result object (Tcl_Obj*) */ probe proc__result(const char *name, int code, const char *result, struct Tcl_Obj *resultobj); /* * tcl*:::proc-args probe * triggered before proc-entry probe, gives access to string * representation of proc arguments * arg0: proc name (string) * arg1-arg9: proc arguments or NULL (strings) */ probe proc__args(const char *name, const char *arg1, const char *arg2, const char *arg3, const char *arg4, const char *arg5, const char *arg6, const char *arg7, const char *arg8, const char *arg9); /* * tcl*:::proc-info probe * triggered before proc-entry probe, gives access to TIP 280 * information for the proc invocation (i.e. [info frame 0]) * arg0: TIP 280 cmd (string) * arg1: TIP 280 type (string) * arg2: TIP 280 proc (string) * arg3: TIP 280 file (string) * arg4: TIP 280 line (int) * arg5: TIP 280 level (Tcl_Size) * arg6: TclOO method (string) * arg7: TclOO class/object (string) */ probe proc__info(const char *cmd, const char *type, const char *proc, const char *file, int line, Tcl_Size level, const char *method, const char *class); /***************************** cmd probes ******************************/ /* * tcl*:::cmd-entry probe * triggered immediately before commmand execution * arg0: command name (string) * arg1: number of arguments (Tcl_Size) * arg2: array of command argument objects (Tcl_Obj**) */ probe cmd__entry(const char *name, Tcl_Size objc, struct Tcl_Obj **objv); /* * tcl*:::cmd-return probe * triggered immediately after commmand execution * arg0: command name (string) * arg1: return code (int) */ probe cmd__return(const char *name, int code); /* * tcl*:::cmd-result probe * triggered after cmd-return probe and result processing * arg0: command name (string) * arg1: return code (int) * arg2: command result (string) * arg3: command result object (Tcl_Obj*) */ probe cmd__result(const char *name, int code, const char *result, struct Tcl_Obj *resultobj); /* * tcl*:::cmd-args probe * triggered before cmd-entry probe, gives access to string * representation of command arguments * arg0: command name (string) * arg1-arg9: command arguments or NULL (strings) */ probe cmd__args(const char *name, const char *arg1, const char *arg2, const char *arg3, const char *arg4, const char *arg5, const char *arg6, const char *arg7, const char *arg8, const char *arg9); /* * tcl*:::cmd-info probe * triggered before cmd-entry probe, gives access to TIP 280 * information for the command invocation (i.e. [info frame 0]) * arg0: TIP 280 cmd (string) * arg1: TIP 280 type (string) * arg2: TIP 280 proc (string) * arg3: TIP 280 file (string) * arg4: TIP 280 line (int) * arg5: TIP 280 level (int) * arg6: TclOO method (string) * arg7: TclOO class/object (string) */ probe cmd__info(const char *cmd, const char *type, const char *proc, const char *file, int line, Tcl_Size level, const char *method, const char *class); /***************************** inst probes *****************************/ /* * tcl*:::inst-start probe * triggered immediately before execution of a bytecode * arg0: bytecode name (string) * arg1: depth of stack (Tcl_Size) * arg2: top of stack (Tcl_Obj**) */ probe inst__start(const char *name, Tcl_Size depth, struct Tcl_Obj **stack); /* * tcl*:::inst-done probe * triggered immediately after execution of a bytecode * arg0: bytecode name (string) * arg1: depth of stack (Tcl_Size) * arg2: top of stack (Tcl_Obj**) */ probe inst__done(const char *name, Tcl_Size depth, struct Tcl_Obj **stack); /***************************** obj probes ******************************/ /* * tcl*:::obj-create probe * triggered immediately after a new Tcl_Obj has been created * arg0: object created (Tcl_Obj*) */ probe obj__create(struct Tcl_Obj* obj); /* * tcl*:::obj-free probe * triggered immediately before a Tcl_Obj is freed * arg0: object to be freed (Tcl_Obj*) */ probe obj__free(struct Tcl_Obj* obj); /***************************** tcl probes ******************************/ /* * tcl*:::tcl-probe probe * triggered when the ::tcl::dtrace command is called * arg0-arg9: command arguments (strings) */ probe tcl__probe(const char *arg0, const char *arg1, const char *arg2, const char *arg3, const char *arg4, const char *arg5, const char *arg6, const char *arg7, const char *arg8, const char *arg9); }; /* * Tcl types and constants for use in DTrace scripts */ typedef struct Tcl_ObjType { const char *name; void *freeIntRepProc; void *dupIntRepProc; void *updateStringProc; void *setFromAnyProc; size_t version; void *lengthProc; void *indexProc; void *sliceProc; void *reverseProc; void *getElementsProc; void *setElementProc; void *replaceProc; void *inOperProc; } Tcl_ObjType; struct Tcl_Obj { Tcl_Size refCount; char *bytes; Tcl_Size length; const Tcl_ObjType *typePtr; union { long longValue; double doubleValue; void *otherValuePtr; int64_t wideValue; struct { void *ptr1; void *ptr2; } twoPtrValue; struct { void *ptr; unsigned long value; } ptrAndLongRep; struct { void *ptr; Tcl_Size size; } ptrAndSize; } internalRep; }; enum return_codes { TCL_OK = 0, TCL_ERROR, TCL_RETURN, TCL_BREAK, TCL_CONTINUE }; #pragma D attributes Evolving/Evolving/Common provider tcl provider #pragma D attributes Private/Private/Common provider tcl module #pragma D attributes Private/Private/Common provider tcl function #pragma D attributes Evolving/Evolving/Common provider tcl name #pragma D attributes Evolving/Evolving/Common provider tcl args /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclDate.c0000644000175000017500000024271114726623136014725 0ustar sergeisergei/* A Bison parser, made by GNU Bison 3.8.2. */ /* Bison implementation for Yacc-like parsers in C Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* C LALR(1) parser skeleton written by Richard Stallman, by simplifying the original so-called "semantic" parser. */ /* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, especially those whose name start with YY_ or yy_. They are private implementation details that can be changed or removed. */ /* All symbols defined below should begin with yy or YY, to avoid infringing on user name space. This should be done even for local variables, as they might otherwise be expanded by user macros. There are some unavoidable exceptions within include files to define necessary library symbols; they are noted "INFRINGES ON USER NAME SPACE" below. */ /* Identify Bison output, and Bison version. */ #define YYBISON 30802 /* Bison version string. */ #define YYBISON_VERSION "3.8.2" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" /* Pure parsers. */ #define YYPURE 1 /* Push parsers. */ #define YYPUSH 0 /* Pull parsers. */ #define YYPULL 1 /* Substitute the variable and function names. */ #define yyparse TclDateparse #define yylex TclDatelex #define yyerror TclDateerror #define yydebug TclDatedebug #define yynerrs TclDatenerrs /* First part of user prologue. */ /* * tclDate.c -- * * This file is generated from a yacc grammar defined in the file * tclGetDate.y. It should not be edited directly. * * Copyright © 1992-1995 Karl Lehenbauer & Mark Diekhans. * Copyright © 1995-1997 Sun Microsystems, Inc. * Copyright © 2015 Sergey G. Brester aka sebres. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * */ #include "tclInt.h" /* * Bison generates several labels that happen to be unused. MS Visual C++ * doesn't like that, and complains. Tell it to shut up. */ #ifdef _MSC_VER #pragma warning( disable : 4102 ) #endif /* _MSC_VER */ #if 0 #define YYDEBUG 1 #endif /* * yyparse will accept a 'struct DateInfo' as its parameter; that's where the * parsed fields will be returned. */ #include "tclDate.h" #define YYMALLOC Tcl_Alloc #define YYFREE(x) (Tcl_Free((void*) (x))) #define EPOCH 1970 #define START_OF_TIME 1902 #define END_OF_TIME 2037 /* * The offset of tm_year of struct tm returned by localtime, gmtime, etc. * Posix requires 1900. */ #define TM_YEAR_BASE 1900 #define HOUR(x) ((60 * (int)(x))) #define IsLeapYear(x) (((x) % 4 == 0) && ((x) % 100 != 0 || (x) % 400 == 0)) #define yyIncrFlags(f) \ do { \ info->errFlags |= (info->flags & (f)); \ if (info->errFlags) { YYABORT; } \ info->flags |= (f); \ } while (0); /* * An entry in the lexical lookup table. */ typedef struct { const char *name; int type; int value; } TABLE; /* * Daylight-savings mode: on, off, or not yet known. */ typedef enum _DSTMODE { DSTon, DSToff, DSTmaybe } DSTMODE; # ifndef YY_CAST # ifdef __cplusplus # define YY_CAST(Type, Val) static_cast (Val) # define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast (Val) # else # define YY_CAST(Type, Val) ((Type) (Val)) # define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) # endif # endif # ifndef YY_NULLPTR # if defined __cplusplus # if 201103L <= __cplusplus # define YY_NULLPTR nullptr # else # define YY_NULLPTR 0 # endif # else # define YY_NULLPTR ((void*)0) # endif # endif /* Debug traces. */ #ifndef YYDEBUG # define YYDEBUG 0 #endif #if YYDEBUG extern int TclDatedebug; #endif /* Token kinds. */ #ifndef YYTOKENTYPE # define YYTOKENTYPE enum yytokentype { YYEMPTY = -2, YYEOF = 0, /* "end of file" */ YYerror = 256, /* error */ YYUNDEF = 257, /* "invalid token" */ tAGO = 258, /* tAGO */ tDAY = 259, /* tDAY */ tDAYZONE = 260, /* tDAYZONE */ tID = 261, /* tID */ tMERIDIAN = 262, /* tMERIDIAN */ tMONTH = 263, /* tMONTH */ tMONTH_UNIT = 264, /* tMONTH_UNIT */ tSTARDATE = 265, /* tSTARDATE */ tSEC_UNIT = 266, /* tSEC_UNIT */ tUNUMBER = 267, /* tUNUMBER */ tZONE = 268, /* tZONE */ tZONEwO4 = 269, /* tZONEwO4 */ tZONEwO2 = 270, /* tZONEwO2 */ tEPOCH = 271, /* tEPOCH */ tDST = 272, /* tDST */ tISOBAS8 = 273, /* tISOBAS8 */ tISOBAS6 = 274, /* tISOBAS6 */ tISOBASL = 275, /* tISOBASL */ tDAY_UNIT = 276, /* tDAY_UNIT */ tNEXT = 277, /* tNEXT */ SP = 278 /* SP */ }; typedef enum yytokentype yytoken_kind_t; #endif /* Value type. */ #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED union YYSTYPE { Tcl_WideInt Number; enum _MERIDIAN Meridian; }; typedef union YYSTYPE YYSTYPE; # define YYSTYPE_IS_TRIVIAL 1 # define YYSTYPE_IS_DECLARED 1 #endif /* Location type. */ #if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED typedef struct YYLTYPE YYLTYPE; struct YYLTYPE { int first_line; int first_column; int last_line; int last_column; }; # define YYLTYPE_IS_DECLARED 1 # define YYLTYPE_IS_TRIVIAL 1 #endif int TclDateparse (DateInfo* info); /* Symbol kind. */ enum yysymbol_kind_t { YYSYMBOL_YYEMPTY = -2, YYSYMBOL_YYEOF = 0, /* "end of file" */ YYSYMBOL_YYerror = 1, /* error */ YYSYMBOL_YYUNDEF = 2, /* "invalid token" */ YYSYMBOL_tAGO = 3, /* tAGO */ YYSYMBOL_tDAY = 4, /* tDAY */ YYSYMBOL_tDAYZONE = 5, /* tDAYZONE */ YYSYMBOL_tID = 6, /* tID */ YYSYMBOL_tMERIDIAN = 7, /* tMERIDIAN */ YYSYMBOL_tMONTH = 8, /* tMONTH */ YYSYMBOL_tMONTH_UNIT = 9, /* tMONTH_UNIT */ YYSYMBOL_tSTARDATE = 10, /* tSTARDATE */ YYSYMBOL_tSEC_UNIT = 11, /* tSEC_UNIT */ YYSYMBOL_tUNUMBER = 12, /* tUNUMBER */ YYSYMBOL_tZONE = 13, /* tZONE */ YYSYMBOL_tZONEwO4 = 14, /* tZONEwO4 */ YYSYMBOL_tZONEwO2 = 15, /* tZONEwO2 */ YYSYMBOL_tEPOCH = 16, /* tEPOCH */ YYSYMBOL_tDST = 17, /* tDST */ YYSYMBOL_tISOBAS8 = 18, /* tISOBAS8 */ YYSYMBOL_tISOBAS6 = 19, /* tISOBAS6 */ YYSYMBOL_tISOBASL = 20, /* tISOBASL */ YYSYMBOL_tDAY_UNIT = 21, /* tDAY_UNIT */ YYSYMBOL_tNEXT = 22, /* tNEXT */ YYSYMBOL_SP = 23, /* SP */ YYSYMBOL_24_ = 24, /* ':' */ YYSYMBOL_25_ = 25, /* ',' */ YYSYMBOL_26_ = 26, /* '-' */ YYSYMBOL_27_ = 27, /* '/' */ YYSYMBOL_28_T_ = 28, /* 'T' */ YYSYMBOL_29_ = 29, /* '.' */ YYSYMBOL_30_ = 30, /* '+' */ YYSYMBOL_YYACCEPT = 31, /* $accept */ YYSYMBOL_spec = 32, /* spec */ YYSYMBOL_item = 33, /* item */ YYSYMBOL_iextime = 34, /* iextime */ YYSYMBOL_time = 35, /* time */ YYSYMBOL_zone = 36, /* zone */ YYSYMBOL_comma = 37, /* comma */ YYSYMBOL_day = 38, /* day */ YYSYMBOL_iexdate = 39, /* iexdate */ YYSYMBOL_date = 40, /* date */ YYSYMBOL_ordMonth = 41, /* ordMonth */ YYSYMBOL_isosep = 42, /* isosep */ YYSYMBOL_isodate = 43, /* isodate */ YYSYMBOL_isotime = 44, /* isotime */ YYSYMBOL_iso = 45, /* iso */ YYSYMBOL_trek = 46, /* trek */ YYSYMBOL_relspec = 47, /* relspec */ YYSYMBOL_relunits = 48, /* relunits */ YYSYMBOL_sign = 49, /* sign */ YYSYMBOL_unit = 50, /* unit */ YYSYMBOL_INTNUM = 51, /* INTNUM */ YYSYMBOL_numitem = 52, /* numitem */ YYSYMBOL_o_merid = 53 /* o_merid */ }; typedef enum yysymbol_kind_t yysymbol_kind_t; /* Second part of user prologue. */ /* * Prototypes of internal functions. */ static int LookupWord(YYSTYPE* yylvalPtr, char *buff); static void TclDateerror(YYLTYPE* location, DateInfo* info, const char *s); static int TclDatelex(YYSTYPE* yylvalPtr, YYLTYPE* location, DateInfo* info); MODULE_SCOPE int yyparse(DateInfo*); #ifdef short # undef short #endif /* On compilers that do not define __PTRDIFF_MAX__ etc., make sure and (if available) are included so that the code can choose integer types of a good width. */ #ifndef __PTRDIFF_MAX__ # include /* INFRINGES ON USER NAME SPACE */ # if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ # include /* INFRINGES ON USER NAME SPACE */ # define YY_STDINT_H # endif #endif /* Narrow types that promote to a signed type and that can represent a signed or unsigned integer of at least N bits. In tables they can save space and decrease cache pressure. Promoting to a signed type helps avoid bugs in integer arithmetic. */ #ifdef __INT_LEAST8_MAX__ typedef __INT_LEAST8_TYPE__ yytype_int8; #elif defined YY_STDINT_H typedef int_least8_t yytype_int8; #else typedef signed char yytype_int8; #endif #ifdef __INT_LEAST16_MAX__ typedef __INT_LEAST16_TYPE__ yytype_int16; #elif defined YY_STDINT_H typedef int_least16_t yytype_int16; #else typedef short yytype_int16; #endif /* Work around bug in HP-UX 11.23, which defines these macros incorrectly for preprocessor constants. This workaround can likely be removed in 2023, as HPE has promised support for HP-UX 11.23 (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of . */ #ifdef __hpux # undef UINT_LEAST8_MAX # undef UINT_LEAST16_MAX # define UINT_LEAST8_MAX 255 # define UINT_LEAST16_MAX 65535 #endif #if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ typedef __UINT_LEAST8_TYPE__ yytype_uint8; #elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ && UINT_LEAST8_MAX <= INT_MAX) typedef uint_least8_t yytype_uint8; #elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX typedef unsigned char yytype_uint8; #else typedef short yytype_uint8; #endif #if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ typedef __UINT_LEAST16_TYPE__ yytype_uint16; #elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ && UINT_LEAST16_MAX <= INT_MAX) typedef uint_least16_t yytype_uint16; #elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX typedef unsigned short yytype_uint16; #else typedef int yytype_uint16; #endif #ifndef YYPTRDIFF_T # if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ # define YYPTRDIFF_T __PTRDIFF_TYPE__ # define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ # elif defined PTRDIFF_MAX # ifndef ptrdiff_t # include /* INFRINGES ON USER NAME SPACE */ # endif # define YYPTRDIFF_T ptrdiff_t # define YYPTRDIFF_MAXIMUM PTRDIFF_MAX # else # define YYPTRDIFF_T long # define YYPTRDIFF_MAXIMUM LONG_MAX # endif #endif #ifndef YYSIZE_T # ifdef __SIZE_TYPE__ # define YYSIZE_T __SIZE_TYPE__ # elif defined size_t # define YYSIZE_T size_t # elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ # include /* INFRINGES ON USER NAME SPACE */ # define YYSIZE_T size_t # else # define YYSIZE_T unsigned # endif #endif #define YYSIZE_MAXIMUM \ YY_CAST (YYPTRDIFF_T, \ (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ ? YYPTRDIFF_MAXIMUM \ : YY_CAST (YYSIZE_T, -1))) #define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) /* Stored state numbers (used for stacks). */ typedef yytype_int8 yy_state_t; /* State numbers in computations. */ typedef int yy_state_fast_t; #ifndef YY_ # if defined YYENABLE_NLS && YYENABLE_NLS # if ENABLE_NLS # include /* INFRINGES ON USER NAME SPACE */ # define YY_(Msgid) dgettext ("bison-runtime", Msgid) # endif # endif # ifndef YY_ # define YY_(Msgid) Msgid # endif #endif #ifndef YY_ATTRIBUTE_PURE # if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) # define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) # else # define YY_ATTRIBUTE_PURE # endif #endif #ifndef YY_ATTRIBUTE_UNUSED # if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) # define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) # else # define YY_ATTRIBUTE_UNUSED # endif #endif /* Suppress unused-variable warnings by "using" E. */ #if ! defined lint || defined __GNUC__ # define YY_USE(E) ((void) (E)) #else # define YY_USE(E) /* empty */ #endif /* Suppress an incorrect diagnostic about yylval being uninitialized. */ #if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__ # if __GNUC__ * 100 + __GNUC_MINOR__ < 407 # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ _Pragma ("GCC diagnostic push") \ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") # else # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ _Pragma ("GCC diagnostic push") \ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") # endif # define YY_IGNORE_MAYBE_UNINITIALIZED_END \ _Pragma ("GCC diagnostic pop") #else # define YY_INITIAL_VALUE(Value) Value #endif #ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN # define YY_IGNORE_MAYBE_UNINITIALIZED_END #endif #ifndef YY_INITIAL_VALUE # define YY_INITIAL_VALUE(Value) /* Nothing. */ #endif #if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ # define YY_IGNORE_USELESS_CAST_BEGIN \ _Pragma ("GCC diagnostic push") \ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") # define YY_IGNORE_USELESS_CAST_END \ _Pragma ("GCC diagnostic pop") #endif #ifndef YY_IGNORE_USELESS_CAST_BEGIN # define YY_IGNORE_USELESS_CAST_BEGIN # define YY_IGNORE_USELESS_CAST_END #endif #define YY_ASSERT(E) ((void) (0 && (E))) #if !defined yyoverflow /* The parser invokes alloca or malloc; define the necessary symbols. */ # ifdef YYSTACK_USE_ALLOCA # if YYSTACK_USE_ALLOCA # ifdef __GNUC__ # define YYSTACK_ALLOC __builtin_alloca # elif defined __BUILTIN_VA_ARG_INCR # include /* INFRINGES ON USER NAME SPACE */ # elif defined _AIX # define YYSTACK_ALLOC __alloca # elif defined _MSC_VER # include /* INFRINGES ON USER NAME SPACE */ # define alloca _alloca # else # define YYSTACK_ALLOC alloca # if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS # include /* INFRINGES ON USER NAME SPACE */ /* Use EXIT_SUCCESS as a witness for stdlib.h. */ # ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 # endif # endif # endif # endif # endif # ifdef YYSTACK_ALLOC /* Pacify GCC's 'empty if-body' warning. */ # define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) # ifndef YYSTACK_ALLOC_MAXIMUM /* The OS might guarantee only one guard page at the bottom of the stack, and a page size can be as small as 4096 bytes. So we cannot safely invoke alloca (N) if N exceeds 4096. Use a slightly smaller number to allow for a few compiler-allocated temporary stack slots. */ # define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ # endif # else # define YYSTACK_ALLOC YYMALLOC # define YYSTACK_FREE YYFREE # ifndef YYSTACK_ALLOC_MAXIMUM # define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM # endif # if (defined __cplusplus && ! defined EXIT_SUCCESS \ && ! ((defined YYMALLOC || defined malloc) \ && (defined YYFREE || defined free))) # include /* INFRINGES ON USER NAME SPACE */ # ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 # endif # endif # ifndef YYMALLOC # define YYMALLOC malloc # if ! defined malloc && ! defined EXIT_SUCCESS void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ # endif # endif # ifndef YYFREE # define YYFREE free # if ! defined free && ! defined EXIT_SUCCESS void free (void *); /* INFRINGES ON USER NAME SPACE */ # endif # endif # endif #endif /* !defined yyoverflow */ #if (! defined yyoverflow \ && (! defined __cplusplus \ || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \ && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) /* A type that is properly aligned for any stack member. */ union yyalloc { yy_state_t yyss_alloc; YYSTYPE yyvs_alloc; YYLTYPE yyls_alloc; }; /* The size of the maximum gap between one aligned stack and the next. */ # define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) /* The size of an array large to enough to hold all stacks, each with N elements. */ # define YYSTACK_BYTES(N) \ ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE) \ + YYSIZEOF (YYLTYPE)) \ + 2 * YYSTACK_GAP_MAXIMUM) # define YYCOPY_NEEDED 1 /* Relocate STACK from its old location to the new one. The local variables YYSIZE and YYSTACKSIZE give the old and new number of elements in the stack, and YYPTR gives the new location of the stack. Advance YYPTR to a properly aligned location for the next stack. */ # define YYSTACK_RELOCATE(Stack_alloc, Stack) \ do \ { \ YYPTRDIFF_T yynewbytes; \ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ Stack = &yyptr->Stack_alloc; \ yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ yyptr += yynewbytes / YYSIZEOF (*yyptr); \ } \ while (0) #endif #if defined YYCOPY_NEEDED && YYCOPY_NEEDED /* Copy COUNT objects from SRC to DST. The source and destination do not overlap. */ # ifndef YYCOPY # if defined __GNUC__ && 1 < __GNUC__ # define YYCOPY(Dst, Src, Count) \ __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) # else # define YYCOPY(Dst, Src, Count) \ do \ { \ YYPTRDIFF_T yyi; \ for (yyi = 0; yyi < (Count); yyi++) \ (Dst)[yyi] = (Src)[yyi]; \ } \ while (0) # endif # endif #endif /* !YYCOPY_NEEDED */ /* YYFINAL -- State number of the termination state. */ #define YYFINAL 2 /* YYLAST -- Last index in YYTABLE. */ #define YYLAST 98 /* YYNTOKENS -- Number of terminals. */ #define YYNTOKENS 31 /* YYNNTS -- Number of nonterminals. */ #define YYNNTS 23 /* YYNRULES -- Number of rules. */ #define YYNRULES 72 /* YYNSTATES -- Number of states. */ #define YYNSTATES 103 /* YYMAXUTOK -- Last valid token kind. */ #define YYMAXUTOK 278 /* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM as returned by yylex, with out-of-bounds checking. */ #define YYTRANSLATE(YYX) \ (0 <= (YYX) && (YYX) <= YYMAXUTOK \ ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \ : YYSYMBOL_YYUNDEF) /* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM as returned by yylex. */ static const yytype_int8 yytranslate[] = { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 30, 25, 26, 29, 27, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 24, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 28, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 }; #if YYDEBUG /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ static const yytype_int16 yyrline[] = { 0, 171, 171, 172, 176, 179, 182, 185, 188, 191, 194, 197, 201, 204, 209, 215, 221, 226, 230, 234, 238, 242, 246, 252, 253, 256, 260, 264, 268, 272, 276, 282, 288, 292, 297, 298, 303, 307, 312, 316, 321, 328, 332, 338, 338, 340, 345, 350, 352, 357, 359, 360, 368, 379, 393, 398, 401, 404, 407, 410, 413, 416, 421, 424, 429, 433, 437, 443, 446, 449, 454, 472, 475 }; #endif /** Accessing symbol of state STATE. */ #define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State]) #if YYDEBUG || 0 /* The user-facing name of the symbol whose (internal) number is YYSYMBOL. No bounds checking. */ static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED; /* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. First, the terminals, then, starting at YYNTOKENS, nonterminals. */ static const char *const yytname[] = { "\"end of file\"", "error", "\"invalid token\"", "tAGO", "tDAY", "tDAYZONE", "tID", "tMERIDIAN", "tMONTH", "tMONTH_UNIT", "tSTARDATE", "tSEC_UNIT", "tUNUMBER", "tZONE", "tZONEwO4", "tZONEwO2", "tEPOCH", "tDST", "tISOBAS8", "tISOBAS6", "tISOBASL", "tDAY_UNIT", "tNEXT", "SP", "':'", "','", "'-'", "'/'", "'T'", "'.'", "'+'", "$accept", "spec", "item", "iextime", "time", "zone", "comma", "day", "iexdate", "date", "ordMonth", "isosep", "isodate", "isotime", "iso", "trek", "relspec", "relunits", "sign", "unit", "INTNUM", "numitem", "o_merid", YY_NULLPTR }; static const char * yysymbol_name (yysymbol_kind_t yysymbol) { return yytname[yysymbol]; } #endif #define YYPACT_NINF (-21) #define yypact_value_is_default(Yyn) \ ((Yyn) == YYPACT_NINF) #define YYTABLE_NINF (-68) #define yytable_value_is_error(Yyn) \ 0 /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing STATE-NUM. */ static const yytype_int8 yypact[] = { -21, 11, -21, -20, -21, 5, -21, -9, -21, 46, 17, 9, 9, -21, -21, -21, 24, -21, 57, -21, -21, -21, 33, -21, -21, -21, -21, -21, -21, -15, -21, -21, -21, 45, 26, -21, -7, -21, 51, -21, -20, -21, -21, -21, 48, -21, -21, 67, 68, 52, 69, -21, -9, -9, -21, -21, -21, -21, 74, -21, -7, -21, -21, -21, -21, 44, -21, 79, 40, -7, -21, -21, 72, 73, -21, 62, 61, 63, 64, -21, -21, -21, -21, 66, -21, -21, -21, -21, 84, -7, -21, -21, -21, 80, 81, 82, 83, -21, -21, -21, -21, -21, -21 }; /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. Performed when YYTABLE does not specify something else to do. Zero means the default is an error. */ static const yytype_int8 yydefact[] = { 2, 0, 1, 25, 19, 0, 66, 0, 64, 70, 18, 0, 0, 39, 45, 46, 0, 65, 0, 62, 63, 3, 71, 4, 5, 8, 47, 6, 7, 34, 10, 11, 9, 55, 0, 61, 0, 12, 23, 26, 36, 67, 69, 68, 0, 27, 15, 38, 0, 0, 0, 17, 0, 0, 52, 51, 30, 41, 67, 59, 0, 72, 16, 44, 43, 0, 54, 67, 0, 22, 58, 24, 0, 0, 40, 14, 0, 0, 32, 20, 21, 42, 60, 0, 48, 49, 50, 29, 67, 0, 57, 37, 53, 0, 0, 0, 0, 28, 56, 13, 35, 31, 33 }; /* YYPGOTO[NTERM-NUM]. */ static const yytype_int8 yypgoto[] = { -21, -21, -21, 31, -21, -21, 58, -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, -5, -18, -6, -21, -21 }; /* YYDEFGOTO[NTERM-NUM]. */ static const yytype_int8 yydefgoto[] = { 0, 1, 21, 22, 23, 24, 39, 25, 26, 27, 28, 65, 29, 86, 30, 31, 32, 33, 34, 35, 36, 37, 62 }; /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If positive, shift that token. If negative, reduce the rule whose number is the opposite. If YYTABLE_NINF, syntax error. */ static const yytype_int8 yytable[] = { 59, 44, 6, 41, 8, 38, 52, 53, 63, 42, 43, 2, 60, 64, 17, 3, 4, 40, 70, 5, 6, 7, 8, 9, 10, 11, 12, 13, 69, 14, 15, 16, 17, 18, 51, 19, 54, 19, 67, 20, 61, 20, 82, 55, 42, 43, 79, 80, 66, 68, 45, 90, 88, 46, 47, -67, 83, -67, 42, 43, 76, 56, 89, 84, 77, 57, 6, -67, 8, 58, 48, 98, 49, 50, 71, 42, 43, 73, 17, 74, 75, 78, 81, 87, 91, 92, 93, 94, 97, 95, 48, 96, 99, 100, 101, 102, 85, 0, 72 }; static const yytype_int8 yycheck[] = { 18, 7, 9, 12, 11, 25, 11, 12, 23, 18, 19, 0, 18, 28, 21, 4, 5, 12, 36, 8, 9, 10, 11, 12, 13, 14, 15, 16, 34, 18, 19, 20, 21, 22, 17, 26, 12, 26, 12, 30, 7, 30, 60, 19, 18, 19, 52, 53, 3, 23, 4, 69, 12, 7, 8, 9, 12, 11, 18, 19, 8, 4, 68, 19, 12, 8, 9, 21, 11, 12, 24, 89, 26, 27, 23, 18, 19, 29, 21, 12, 12, 12, 8, 4, 12, 12, 24, 26, 4, 26, 24, 27, 12, 12, 12, 12, 65, -1, 40 }; /* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of state STATE-NUM. */ static const yytype_int8 yystos[] = { 0, 32, 0, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22, 26, 30, 33, 34, 35, 36, 38, 39, 40, 41, 43, 45, 46, 47, 48, 49, 50, 51, 52, 25, 37, 12, 12, 18, 19, 51, 4, 7, 8, 24, 26, 27, 17, 49, 49, 12, 19, 4, 8, 12, 50, 51, 7, 53, 23, 28, 42, 3, 12, 23, 51, 50, 23, 37, 29, 12, 12, 8, 12, 12, 51, 51, 8, 50, 12, 19, 34, 44, 4, 12, 51, 50, 12, 12, 24, 26, 26, 27, 4, 50, 12, 12, 12, 12 }; /* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */ static const yytype_int8 yyr1[] = { 0, 31, 32, 32, 33, 33, 33, 33, 33, 33, 33, 33, 33, 34, 34, 35, 35, 36, 36, 36, 36, 36, 36, 37, 37, 38, 38, 38, 38, 38, 38, 39, 40, 40, 40, 40, 40, 40, 40, 40, 40, 41, 41, 42, 42, 43, 43, 43, 44, 44, 45, 45, 45, 46, 47, 47, 48, 48, 48, 48, 48, 48, 49, 49, 50, 50, 50, 51, 51, 51, 52, 53, 53 }; /* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */ static const yytype_int8 yyr2[] = { 0, 2, 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 3, 2, 2, 2, 1, 1, 3, 3, 2, 1, 2, 1, 2, 2, 4, 3, 2, 5, 3, 5, 1, 5, 2, 4, 2, 1, 3, 2, 3, 1, 1, 1, 1, 1, 1, 1, 3, 2, 2, 4, 2, 1, 4, 3, 2, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1 }; enum { YYENOMEM = -2 }; #define yyerrok (yyerrstatus = 0) #define yyclearin (yychar = YYEMPTY) #define YYACCEPT goto yyacceptlab #define YYABORT goto yyabortlab #define YYERROR goto yyerrorlab #define YYNOMEM goto yyexhaustedlab #define YYRECOVERING() (!!yyerrstatus) #define YYBACKUP(Token, Value) \ do \ if (yychar == YYEMPTY) \ { \ yychar = (Token); \ yylval = (Value); \ YYPOPSTACK (yylen); \ yystate = *yyssp; \ goto yybackup; \ } \ else \ { \ yyerror (&yylloc, info, YY_("syntax error: cannot back up")); \ YYERROR; \ } \ while (0) /* Backward compatibility with an undocumented macro. Use YYerror or YYUNDEF. */ #define YYERRCODE YYUNDEF /* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. If N is 0, then set CURRENT to the empty location which ends the previous symbol: RHS[0] (always defined). */ #ifndef YYLLOC_DEFAULT # define YYLLOC_DEFAULT(Current, Rhs, N) \ do \ if (N) \ { \ (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ } \ else \ { \ (Current).first_line = (Current).last_line = \ YYRHSLOC (Rhs, 0).last_line; \ (Current).first_column = (Current).last_column = \ YYRHSLOC (Rhs, 0).last_column; \ } \ while (0) #endif #define YYRHSLOC(Rhs, K) ((Rhs)[K]) /* Enable debugging if requested. */ #if YYDEBUG # ifndef YYFPRINTF # include /* INFRINGES ON USER NAME SPACE */ # define YYFPRINTF fprintf # endif # define YYDPRINTF(Args) \ do { \ if (yydebug) \ YYFPRINTF Args; \ } while (0) /* YYLOCATION_PRINT -- Print the location on the stream. This macro was not mandated originally: define only if we know we won't break user code: when these are the locations we know. */ # ifndef YYLOCATION_PRINT # if defined YY_LOCATION_PRINT /* Temporary convenience wrapper in case some people defined the undocumented and private YY_LOCATION_PRINT macros. */ # define YYLOCATION_PRINT(File, Loc) YY_LOCATION_PRINT(File, *(Loc)) # elif defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL /* Print *YYLOCP on YYO. Private, do not rely on its existence. */ YY_ATTRIBUTE_UNUSED static int yy_location_print_ (FILE *yyo, YYLTYPE const * const yylocp) { int res = 0; int end_col = 0 != yylocp->last_column ? yylocp->last_column - 1 : 0; if (0 <= yylocp->first_line) { res += YYFPRINTF (yyo, "%d", yylocp->first_line); if (0 <= yylocp->first_column) res += YYFPRINTF (yyo, ".%d", yylocp->first_column); } if (0 <= yylocp->last_line) { if (yylocp->first_line < yylocp->last_line) { res += YYFPRINTF (yyo, "-%d", yylocp->last_line); if (0 <= end_col) res += YYFPRINTF (yyo, ".%d", end_col); } else if (0 <= end_col && yylocp->first_column < end_col) res += YYFPRINTF (yyo, "-%d", end_col); } return res; } # define YYLOCATION_PRINT yy_location_print_ /* Temporary convenience wrapper in case some people defined the undocumented and private YY_LOCATION_PRINT macros. */ # define YY_LOCATION_PRINT(File, Loc) YYLOCATION_PRINT(File, &(Loc)) # else # define YYLOCATION_PRINT(File, Loc) ((void) 0) /* Temporary convenience wrapper in case some people defined the undocumented and private YY_LOCATION_PRINT macros. */ # define YY_LOCATION_PRINT YYLOCATION_PRINT # endif # endif /* !defined YYLOCATION_PRINT */ # define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \ do { \ if (yydebug) \ { \ YYFPRINTF (stderr, "%s ", Title); \ yy_symbol_print (stderr, \ Kind, Value, Location, info); \ YYFPRINTF (stderr, "\n"); \ } \ } while (0) /*-----------------------------------. | Print this symbol's value on YYO. | `-----------------------------------*/ static void yy_symbol_value_print (FILE *yyo, yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, DateInfo* info) { FILE *yyoutput = yyo; YY_USE (yyoutput); YY_USE (yylocationp); YY_USE (info); if (!yyvaluep) return; YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN YY_USE (yykind); YY_IGNORE_MAYBE_UNINITIALIZED_END } /*---------------------------. | Print this symbol on YYO. | `---------------------------*/ static void yy_symbol_print (FILE *yyo, yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, DateInfo* info) { YYFPRINTF (yyo, "%s %s (", yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind)); YYLOCATION_PRINT (yyo, yylocationp); YYFPRINTF (yyo, ": "); yy_symbol_value_print (yyo, yykind, yyvaluep, yylocationp, info); YYFPRINTF (yyo, ")"); } /*------------------------------------------------------------------. | yy_stack_print -- Print the state stack from its BOTTOM up to its | | TOP (included). | `------------------------------------------------------------------*/ static void yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) { YYFPRINTF (stderr, "Stack now"); for (; yybottom <= yytop; yybottom++) { int yybot = *yybottom; YYFPRINTF (stderr, " %d", yybot); } YYFPRINTF (stderr, "\n"); } # define YY_STACK_PRINT(Bottom, Top) \ do { \ if (yydebug) \ yy_stack_print ((Bottom), (Top)); \ } while (0) /*------------------------------------------------. | Report that the YYRULE is going to be reduced. | `------------------------------------------------*/ static void yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, DateInfo* info) { int yylno = yyrline[yyrule]; int yynrhs = yyr2[yyrule]; int yyi; YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", yyrule - 1, yylno); /* The symbols being reduced. */ for (yyi = 0; yyi < yynrhs; yyi++) { YYFPRINTF (stderr, " $%d = ", yyi + 1); yy_symbol_print (stderr, YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]), &yyvsp[(yyi + 1) - (yynrhs)], &(yylsp[(yyi + 1) - (yynrhs)]), info); YYFPRINTF (stderr, "\n"); } } # define YY_REDUCE_PRINT(Rule) \ do { \ if (yydebug) \ yy_reduce_print (yyssp, yyvsp, yylsp, Rule, info); \ } while (0) /* Nonzero means print parse trace. It is left uninitialized so that multiple parsers can coexist. */ int yydebug; #else /* !YYDEBUG */ # define YYDPRINTF(Args) ((void) 0) # define YY_SYMBOL_PRINT(Title, Kind, Value, Location) # define YY_STACK_PRINT(Bottom, Top) # define YY_REDUCE_PRINT(Rule) #endif /* !YYDEBUG */ /* YYINITDEPTH -- initial size of the parser's stacks. */ #ifndef YYINITDEPTH # define YYINITDEPTH 200 #endif /* YYMAXDEPTH -- maximum size the stacks can grow to (effective only if the built-in stack extension method is used). Do not make this value too large; the results are undefined if YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) evaluated with infinite-precision integer arithmetic. */ #ifndef YYMAXDEPTH # define YYMAXDEPTH 10000 #endif /*-----------------------------------------------. | Release the memory associated to this symbol. | `-----------------------------------------------*/ static void yydestruct (const char *yymsg, yysymbol_kind_t yykind, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, DateInfo* info) { YY_USE (yyvaluep); YY_USE (yylocationp); YY_USE (info); if (!yymsg) yymsg = "Deleting"; YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp); YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN YY_USE (yykind); YY_IGNORE_MAYBE_UNINITIALIZED_END } /*----------. | yyparse. | `----------*/ int yyparse (DateInfo* info) { /* Lookahead token kind. */ int yychar; /* The semantic value of the lookahead symbol. */ /* Default value used for initialization, for pacifying older GCCs or non-GCC compilers. */ YY_INITIAL_VALUE (static YYSTYPE yyval_default;) YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); /* Location data for the lookahead symbol. */ static YYLTYPE yyloc_default # if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL = { 1, 1, 1, 1 } # endif ; YYLTYPE yylloc = yyloc_default; /* Number of syntax errors so far. */ int yynerrs = 0; yy_state_fast_t yystate = 0; /* Number of tokens to shift before error messages enabled. */ int yyerrstatus = 0; /* Refer to the stacks through separate pointers, to allow yyoverflow to reallocate them elsewhere. */ /* Their size. */ YYPTRDIFF_T yystacksize = YYINITDEPTH; /* The state stack: array, bottom, top. */ yy_state_t yyssa[YYINITDEPTH]; yy_state_t *yyss = yyssa; yy_state_t *yyssp = yyss; /* The semantic value stack: array, bottom, top. */ YYSTYPE yyvsa[YYINITDEPTH]; YYSTYPE *yyvs = yyvsa; YYSTYPE *yyvsp = yyvs; /* The location stack: array, bottom, top. */ YYLTYPE yylsa[YYINITDEPTH]; YYLTYPE *yyls = yylsa; YYLTYPE *yylsp = yyls; int yyn; /* The return value of yyparse. */ int yyresult; /* Lookahead symbol kind. */ yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY; /* The variables used to return semantic value and location from the action routines. */ YYSTYPE yyval; YYLTYPE yyloc; /* The locations where the error started and ended. */ YYLTYPE yyerror_range[3]; #define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) /* The number of symbols on the RHS of the reduced rule. Keep to zero when no symbol should be popped. */ int yylen = 0; YYDPRINTF ((stderr, "Starting parse\n")); yychar = YYEMPTY; /* Cause a token to be read. */ yylsp[0] = yylloc; goto yysetstate; /*------------------------------------------------------------. | yynewstate -- push a new state, which is found in yystate. | `------------------------------------------------------------*/ yynewstate: /* In all cases, when you get here, the value and location stacks have just been pushed. So pushing a state here evens the stacks. */ yyssp++; /*--------------------------------------------------------------------. | yysetstate -- set current state (the top of the stack) to yystate. | `--------------------------------------------------------------------*/ yysetstate: YYDPRINTF ((stderr, "Entering state %d\n", yystate)); YY_ASSERT (0 <= yystate && yystate < YYNSTATES); YY_IGNORE_USELESS_CAST_BEGIN *yyssp = YY_CAST (yy_state_t, yystate); YY_IGNORE_USELESS_CAST_END YY_STACK_PRINT (yyss, yyssp); if (yyss + yystacksize - 1 <= yyssp) #if !defined yyoverflow && !defined YYSTACK_RELOCATE YYNOMEM; #else { /* Get the current used size of the three stacks, in elements. */ YYPTRDIFF_T yysize = yyssp - yyss + 1; # if defined yyoverflow { /* Give user a chance to reallocate the stack. Use copies of these so that the &'s don't force the real ones into memory. */ yy_state_t *yyss1 = yyss; YYSTYPE *yyvs1 = yyvs; YYLTYPE *yyls1 = yyls; /* Each stack pointer address is followed by the size of the data in use in that stack, in bytes. This used to be a conditional around just the two extra args, but that might be undefined if yyoverflow is a macro. */ yyoverflow (YY_("memory exhausted"), &yyss1, yysize * YYSIZEOF (*yyssp), &yyvs1, yysize * YYSIZEOF (*yyvsp), &yyls1, yysize * YYSIZEOF (*yylsp), &yystacksize); yyss = yyss1; yyvs = yyvs1; yyls = yyls1; } # else /* defined YYSTACK_RELOCATE */ /* Extend the stack our own way. */ if (YYMAXDEPTH <= yystacksize) YYNOMEM; yystacksize *= 2; if (YYMAXDEPTH < yystacksize) yystacksize = YYMAXDEPTH; { yy_state_t *yyss1 = yyss; union yyalloc *yyptr = YY_CAST (union yyalloc *, YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); if (! yyptr) YYNOMEM; YYSTACK_RELOCATE (yyss_alloc, yyss); YYSTACK_RELOCATE (yyvs_alloc, yyvs); YYSTACK_RELOCATE (yyls_alloc, yyls); # undef YYSTACK_RELOCATE if (yyss1 != yyssa) YYSTACK_FREE (yyss1); } # endif yyssp = yyss + yysize - 1; yyvsp = yyvs + yysize - 1; yylsp = yyls + yysize - 1; YY_IGNORE_USELESS_CAST_BEGIN YYDPRINTF ((stderr, "Stack size increased to %ld\n", YY_CAST (long, yystacksize))); YY_IGNORE_USELESS_CAST_END if (yyss + yystacksize - 1 <= yyssp) YYABORT; } #endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ if (yystate == YYFINAL) YYACCEPT; goto yybackup; /*-----------. | yybackup. | `-----------*/ yybackup: /* Do appropriate processing given the current state. Read a lookahead token if we need one and don't already have one. */ /* First try to decide what to do without reference to lookahead token. */ yyn = yypact[yystate]; if (yypact_value_is_default (yyn)) goto yydefault; /* Not known => get a lookahead token if don't already have one. */ /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */ if (yychar == YYEMPTY) { YYDPRINTF ((stderr, "Reading a token\n")); yychar = yylex (&yylval, &yylloc, info); } if (yychar <= YYEOF) { yychar = YYEOF; yytoken = YYSYMBOL_YYEOF; YYDPRINTF ((stderr, "Now at end of input.\n")); } else if (yychar == YYerror) { /* The scanner already issued an error message, process directly to error recovery. But do not keep the error token as lookahead, it is too special and may lead us to an endless loop in error recovery. */ yychar = YYUNDEF; yytoken = YYSYMBOL_YYerror; yyerror_range[1] = yylloc; goto yyerrlab1; } else { yytoken = YYTRANSLATE (yychar); YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); } /* If the proper action on seeing token YYTOKEN is to reduce or to detect an error, take that action. */ yyn += yytoken; if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) goto yydefault; yyn = yytable[yyn]; if (yyn <= 0) { if (yytable_value_is_error (yyn)) goto yyerrlab; yyn = -yyn; goto yyreduce; } /* Count tokens shifted since error; after three, turn off error status. */ if (yyerrstatus) yyerrstatus--; /* Shift the lookahead token. */ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); yystate = yyn; YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END *++yylsp = yylloc; /* Discard the shifted token. */ yychar = YYEMPTY; goto yynewstate; /*-----------------------------------------------------------. | yydefault -- do the default action for the current state. | `-----------------------------------------------------------*/ yydefault: yyn = yydefact[yystate]; if (yyn == 0) goto yyerrlab; goto yyreduce; /*-----------------------------. | yyreduce -- do a reduction. | `-----------------------------*/ yyreduce: /* yyn is the number of a rule to reduce with. */ yylen = yyr2[yyn]; /* If YYLEN is nonzero, implement the default value of the action: '$$ = $1'. Otherwise, the following line sets YYVAL to garbage. This behavior is undocumented and Bison users should not rely upon it. Assigning to YYVAL unconditionally makes the parser a bit smaller, and it avoids a GCC warning that YYVAL may be used uninitialized. */ yyval = yyvsp[1-yylen]; /* Default location. */ YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); yyerror_range[1] = yyloc; YY_REDUCE_PRINT (yyn); switch (yyn) { case 4: /* item: time */ { yyIncrFlags(CLF_TIME); } break; case 5: /* item: zone */ { yyIncrFlags(CLF_ZONE); } break; case 6: /* item: date */ { yyIncrFlags(CLF_HAVEDATE); } break; case 7: /* item: ordMonth */ { yyIncrFlags(CLF_ORDINALMONTH); } break; case 8: /* item: day */ { yyIncrFlags(CLF_DAYOFWEEK); } break; case 9: /* item: relspec */ { info->flags |= CLF_RELCONV; } break; case 10: /* item: iso */ { yyIncrFlags(CLF_TIME|CLF_HAVEDATE); } break; case 11: /* item: trek */ { yyIncrFlags(CLF_TIME|CLF_HAVEDATE); info->flags |= CLF_RELCONV; } break; case 13: /* iextime: tUNUMBER ':' tUNUMBER ':' tUNUMBER */ { yyHour = (yyvsp[-4].Number); yyMinutes = (yyvsp[-2].Number); yySeconds = (yyvsp[0].Number); } break; case 14: /* iextime: tUNUMBER ':' tUNUMBER */ { yyHour = (yyvsp[-2].Number); yyMinutes = (yyvsp[0].Number); yySeconds = 0; } break; case 15: /* time: tUNUMBER tMERIDIAN */ { yyHour = (yyvsp[-1].Number); yyMinutes = 0; yySeconds = 0; yyMeridian = (yyvsp[0].Meridian); } break; case 16: /* time: iextime o_merid */ { yyMeridian = (yyvsp[0].Meridian); } break; case 17: /* zone: tZONE tDST */ { yyTimezone = (yyvsp[-1].Number); yyDSTmode = DSTon; } break; case 18: /* zone: tZONE */ { yyTimezone = (yyvsp[0].Number); yyDSTmode = DSToff; } break; case 19: /* zone: tDAYZONE */ { yyTimezone = (yyvsp[0].Number); yyDSTmode = DSTon; } break; case 20: /* zone: tZONEwO4 sign INTNUM */ { /* GMT+0100, GMT-1000, etc. */ yyTimezone = (yyvsp[-2].Number) - (yyvsp[-1].Number)*((yyvsp[0].Number) % 100 + ((yyvsp[0].Number) / 100) * 60); yyDSTmode = DSToff; } break; case 21: /* zone: tZONEwO2 sign INTNUM */ { /* GMT+1, GMT-10, etc. */ yyTimezone = (yyvsp[-2].Number) - (yyvsp[-1].Number)*((yyvsp[0].Number) * 60); yyDSTmode = DSToff; } break; case 22: /* zone: sign INTNUM */ { /* +0100, -0100 */ yyTimezone = -(yyvsp[-1].Number)*((yyvsp[0].Number) % 100 + ((yyvsp[0].Number) / 100) * 60); yyDSTmode = DSToff; } break; case 25: /* day: tDAY */ { yyDayOrdinal = 1; yyDayOfWeek = (yyvsp[0].Number); } break; case 26: /* day: tDAY comma */ { yyDayOrdinal = 1; yyDayOfWeek = (yyvsp[-1].Number); } break; case 27: /* day: tUNUMBER tDAY */ { yyDayOrdinal = (yyvsp[-1].Number); yyDayOfWeek = (yyvsp[0].Number); } break; case 28: /* day: sign SP tUNUMBER tDAY */ { yyDayOrdinal = (yyvsp[-3].Number) * (yyvsp[-1].Number); yyDayOfWeek = (yyvsp[0].Number); } break; case 29: /* day: sign tUNUMBER tDAY */ { yyDayOrdinal = (yyvsp[-2].Number) * (yyvsp[-1].Number); yyDayOfWeek = (yyvsp[0].Number); } break; case 30: /* day: tNEXT tDAY */ { yyDayOrdinal = 2; yyDayOfWeek = (yyvsp[0].Number); } break; case 31: /* iexdate: tUNUMBER '-' tUNUMBER '-' tUNUMBER */ { yyMonth = (yyvsp[-2].Number); yyDay = (yyvsp[0].Number); yyYear = (yyvsp[-4].Number); } break; case 32: /* date: tUNUMBER '/' tUNUMBER */ { yyMonth = (yyvsp[-2].Number); yyDay = (yyvsp[0].Number); } break; case 33: /* date: tUNUMBER '/' tUNUMBER '/' tUNUMBER */ { yyMonth = (yyvsp[-4].Number); yyDay = (yyvsp[-2].Number); yyYear = (yyvsp[0].Number); } break; case 35: /* date: tUNUMBER '-' tMONTH '-' tUNUMBER */ { yyDay = (yyvsp[-4].Number); yyMonth = (yyvsp[-2].Number); yyYear = (yyvsp[0].Number); } break; case 36: /* date: tMONTH tUNUMBER */ { yyMonth = (yyvsp[-1].Number); yyDay = (yyvsp[0].Number); } break; case 37: /* date: tMONTH tUNUMBER comma tUNUMBER */ { yyMonth = (yyvsp[-3].Number); yyDay = (yyvsp[-2].Number); yyYear = (yyvsp[0].Number); } break; case 38: /* date: tUNUMBER tMONTH */ { yyMonth = (yyvsp[0].Number); yyDay = (yyvsp[-1].Number); } break; case 39: /* date: tEPOCH */ { yyMonth = 1; yyDay = 1; yyYear = EPOCH; } break; case 40: /* date: tUNUMBER tMONTH tUNUMBER */ { yyMonth = (yyvsp[-1].Number); yyDay = (yyvsp[-2].Number); yyYear = (yyvsp[0].Number); } break; case 41: /* ordMonth: tNEXT tMONTH */ { yyMonthOrdinalIncr = 1; yyMonthOrdinal = (yyvsp[0].Number); } break; case 42: /* ordMonth: tNEXT tUNUMBER tMONTH */ { yyMonthOrdinalIncr = (yyvsp[-1].Number); yyMonthOrdinal = (yyvsp[0].Number); } break; case 45: /* isodate: tISOBAS8 */ { /* YYYYMMDD */ yyYear = (yyvsp[0].Number) / 10000; yyMonth = ((yyvsp[0].Number) % 10000)/100; yyDay = (yyvsp[0].Number) % 100; } break; case 46: /* isodate: tISOBAS6 */ { /* YYMMDD */ yyYear = (yyvsp[0].Number) / 10000; yyMonth = ((yyvsp[0].Number) % 10000)/100; yyDay = (yyvsp[0].Number) % 100; } break; case 48: /* isotime: tISOBAS6 */ { yyHour = (yyvsp[0].Number) / 10000; yyMinutes = ((yyvsp[0].Number) % 10000)/100; yySeconds = (yyvsp[0].Number) % 100; } break; case 51: /* iso: tISOBASL tISOBAS6 */ { /* YYYYMMDDhhmmss */ yyYear = (yyvsp[-1].Number) / 10000; yyMonth = ((yyvsp[-1].Number) % 10000)/100; yyDay = (yyvsp[-1].Number) % 100; yyHour = (yyvsp[0].Number) / 10000; yyMinutes = ((yyvsp[0].Number) % 10000)/100; yySeconds = (yyvsp[0].Number) % 100; } break; case 52: /* iso: tISOBASL tUNUMBER */ { /* YYYYMMDDhhmm */ if (yyDigitCount != 4) YYABORT; /* normally unreached */ yyYear = (yyvsp[-1].Number) / 10000; yyMonth = ((yyvsp[-1].Number) % 10000)/100; yyDay = (yyvsp[-1].Number) % 100; yyHour = (yyvsp[0].Number) / 100; yyMinutes = ((yyvsp[0].Number) % 100); yySeconds = 0; } break; case 53: /* trek: tSTARDATE INTNUM '.' tUNUMBER */ { /* * Offset computed year by -377 so that the returned years will be * in a range accessible with a 32 bit clock seconds value. */ yyYear = (yyvsp[-2].Number)/1000 + 2323 - 377; yyDay = 1; yyMonth = 1; yyRelDay += (((yyvsp[-2].Number)%1000)*(365 + IsLeapYear(yyYear)))/1000; yyRelSeconds += (yyvsp[0].Number) * (144LL * 60LL); } break; case 54: /* relspec: relunits tAGO */ { yyRelSeconds *= -1; yyRelMonth *= -1; yyRelDay *= -1; } break; case 56: /* relunits: sign SP INTNUM unit */ { *yyRelPointer += (yyvsp[-3].Number) * (yyvsp[-1].Number) * (yyvsp[0].Number); } break; case 57: /* relunits: sign INTNUM unit */ { *yyRelPointer += (yyvsp[-2].Number) * (yyvsp[-1].Number) * (yyvsp[0].Number); } break; case 58: /* relunits: INTNUM unit */ { *yyRelPointer += (yyvsp[-1].Number) * (yyvsp[0].Number); } break; case 59: /* relunits: tNEXT unit */ { *yyRelPointer += (yyvsp[0].Number); } break; case 60: /* relunits: tNEXT INTNUM unit */ { *yyRelPointer += (yyvsp[-1].Number) * (yyvsp[0].Number); } break; case 61: /* relunits: unit */ { *yyRelPointer += (yyvsp[0].Number); } break; case 62: /* sign: '-' */ { (yyval.Number) = -1; } break; case 63: /* sign: '+' */ { (yyval.Number) = 1; } break; case 64: /* unit: tSEC_UNIT */ { (yyval.Number) = (yyvsp[0].Number); yyRelPointer = &yyRelSeconds; } break; case 65: /* unit: tDAY_UNIT */ { (yyval.Number) = (yyvsp[0].Number); yyRelPointer = &yyRelDay; } break; case 66: /* unit: tMONTH_UNIT */ { (yyval.Number) = (yyvsp[0].Number); yyRelPointer = &yyRelMonth; } break; case 67: /* INTNUM: tUNUMBER */ { (yyval.Number) = (yyvsp[0].Number); } break; case 68: /* INTNUM: tISOBAS6 */ { (yyval.Number) = (yyvsp[0].Number); } break; case 69: /* INTNUM: tISOBAS8 */ { (yyval.Number) = (yyvsp[0].Number); } break; case 70: /* numitem: tUNUMBER */ { if ((info->flags & (CLF_TIME|CLF_HAVEDATE|CLF_RELCONV)) == (CLF_TIME|CLF_HAVEDATE)) { yyYear = (yyvsp[0].Number); } else { yyIncrFlags(CLF_TIME); if (yyDigitCount <= 2) { yyHour = (yyvsp[0].Number); yyMinutes = 0; } else { yyHour = (yyvsp[0].Number) / 100; yyMinutes = (yyvsp[0].Number) % 100; } yySeconds = 0; yyMeridian = MER24; } } break; case 71: /* o_merid: %empty */ { (yyval.Meridian) = MER24; } break; case 72: /* o_merid: tMERIDIAN */ { (yyval.Meridian) = (yyvsp[0].Meridian); } break; default: break; } /* User semantic actions sometimes alter yychar, and that requires that yytoken be updated with the new translation. We take the approach of translating immediately before every use of yytoken. One alternative is translating here after every semantic action, but that translation would be missed if the semantic action invokes YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an incorrect destructor might then be invoked immediately. In the case of YYERROR or YYBACKUP, subsequent parser actions might lead to an incorrect destructor call or verbose syntax error message before the lookahead is translated. */ YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc); YYPOPSTACK (yylen); yylen = 0; *++yyvsp = yyval; *++yylsp = yyloc; /* Now 'shift' the result of the reduction. Determine what state that goes to, based on the state we popped back to and the rule number reduced by. */ { const int yylhs = yyr1[yyn] - YYNTOKENS; const int yyi = yypgoto[yylhs] + *yyssp; yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp ? yytable[yyi] : yydefgoto[yylhs]); } goto yynewstate; /*--------------------------------------. | yyerrlab -- here on detecting error. | `--------------------------------------*/ yyerrlab: /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar); /* If not already recovering from an error, report this error. */ if (!yyerrstatus) { ++yynerrs; yyerror (&yylloc, info, YY_("syntax error")); } yyerror_range[1] = yylloc; if (yyerrstatus == 3) { /* If just tried and failed to reuse lookahead token after an error, discard it. */ if (yychar <= YYEOF) { /* Return failure if at end of input. */ if (yychar == YYEOF) YYABORT; } else { yydestruct ("Error: discarding", yytoken, &yylval, &yylloc, info); yychar = YYEMPTY; } } /* Else will try to reuse lookahead token after shifting the error token. */ goto yyerrlab1; /*---------------------------------------------------. | yyerrorlab -- error raised explicitly by YYERROR. | `---------------------------------------------------*/ yyerrorlab: /* Pacify compilers when the user code never invokes YYERROR and the label yyerrorlab therefore never appears in user code. */ if (0) YYERROR; ++yynerrs; /* Do not reclaim the symbols of the rule whose action triggered this YYERROR. */ YYPOPSTACK (yylen); yylen = 0; YY_STACK_PRINT (yyss, yyssp); yystate = *yyssp; goto yyerrlab1; /*-------------------------------------------------------------. | yyerrlab1 -- common code for both syntax error and YYERROR. | `-------------------------------------------------------------*/ yyerrlab1: yyerrstatus = 3; /* Each real token shifted decrements this. */ /* Pop stack until we find a state that shifts the error token. */ for (;;) { yyn = yypact[yystate]; if (!yypact_value_is_default (yyn)) { yyn += YYSYMBOL_YYerror; if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror) { yyn = yytable[yyn]; if (0 < yyn) break; } } /* Pop the current state because it cannot handle the error token. */ if (yyssp == yyss) YYABORT; yyerror_range[1] = *yylsp; yydestruct ("Error: popping", YY_ACCESSING_SYMBOL (yystate), yyvsp, yylsp, info); YYPOPSTACK (1); yystate = *yyssp; YY_STACK_PRINT (yyss, yyssp); } YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END yyerror_range[2] = yylloc; ++yylsp; YYLLOC_DEFAULT (*yylsp, yyerror_range, 2); /* Shift the error token. */ YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp); yystate = yyn; goto yynewstate; /*-------------------------------------. | yyacceptlab -- YYACCEPT comes here. | `-------------------------------------*/ yyacceptlab: yyresult = 0; goto yyreturnlab; /*-----------------------------------. | yyabortlab -- YYABORT comes here. | `-----------------------------------*/ yyabortlab: yyresult = 1; goto yyreturnlab; /*-----------------------------------------------------------. | yyexhaustedlab -- YYNOMEM (memory exhaustion) comes here. | `-----------------------------------------------------------*/ yyexhaustedlab: yyerror (&yylloc, info, YY_("memory exhausted")); yyresult = 2; goto yyreturnlab; /*----------------------------------------------------------. | yyreturnlab -- parsing is finished, clean up and return. | `----------------------------------------------------------*/ yyreturnlab: if (yychar != YYEMPTY) { /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ yytoken = YYTRANSLATE (yychar); yydestruct ("Cleanup: discarding lookahead", yytoken, &yylval, &yylloc, info); } /* Do not reclaim the symbols of the rule whose action triggered this YYABORT or YYACCEPT. */ YYPOPSTACK (yylen); YY_STACK_PRINT (yyss, yyssp); while (yyssp != yyss) { yydestruct ("Cleanup: popping", YY_ACCESSING_SYMBOL (+*yyssp), yyvsp, yylsp, info); YYPOPSTACK (1); } #ifndef yyoverflow if (yyss != yyssa) YYSTACK_FREE (yyss); #endif return yyresult; } /* * Month and day table. */ static const TABLE MonthDayTable[] = { { "january", tMONTH, 1 }, { "february", tMONTH, 2 }, { "march", tMONTH, 3 }, { "april", tMONTH, 4 }, { "may", tMONTH, 5 }, { "june", tMONTH, 6 }, { "july", tMONTH, 7 }, { "august", tMONTH, 8 }, { "september", tMONTH, 9 }, { "sept", tMONTH, 9 }, { "october", tMONTH, 10 }, { "november", tMONTH, 11 }, { "december", tMONTH, 12 }, { "sunday", tDAY, 7 }, { "monday", tDAY, 1 }, { "tuesday", tDAY, 2 }, { "tues", tDAY, 2 }, { "wednesday", tDAY, 3 }, { "wednes", tDAY, 3 }, { "thursday", tDAY, 4 }, { "thur", tDAY, 4 }, { "thurs", tDAY, 4 }, { "friday", tDAY, 5 }, { "saturday", tDAY, 6 }, { NULL, 0, 0 } }; /* * Time units table. */ static const TABLE UnitsTable[] = { { "year", tMONTH_UNIT, 12 }, { "month", tMONTH_UNIT, 1 }, { "fortnight", tDAY_UNIT, 14 }, { "week", tDAY_UNIT, 7 }, { "day", tDAY_UNIT, 1 }, { "hour", tSEC_UNIT, 60 * 60 }, { "minute", tSEC_UNIT, 60 }, { "min", tSEC_UNIT, 60 }, { "second", tSEC_UNIT, 1 }, { "sec", tSEC_UNIT, 1 }, { NULL, 0, 0 } }; /* * Assorted relative-time words. */ static const TABLE OtherTable[] = { { "tomorrow", tDAY_UNIT, 1 }, { "yesterday", tDAY_UNIT, -1 }, { "today", tDAY_UNIT, 0 }, { "now", tSEC_UNIT, 0 }, { "last", tUNUMBER, -1 }, { "this", tSEC_UNIT, 0 }, { "next", tNEXT, 1 }, { "ago", tAGO, 1 }, { "epoch", tEPOCH, 0 }, { "stardate", tSTARDATE, 0 }, { NULL, 0, 0 } }; /* * The timezone table. (Note: This table was modified to not use any floating * point constants to work around an SGI compiler bug). */ static const TABLE TimezoneTable[] = { { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */ { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */ { "utc", tZONE, HOUR( 0) }, { "uct", tZONE, HOUR( 0) }, /* Universal Coordinated Time */ { "wet", tZONE, HOUR( 0) }, /* Western European */ { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */ { "wat", tZONE, HOUR( 1) }, /* West Africa */ { "at", tZONE, HOUR( 2) }, /* Azores */ #if 0 /* For completeness. BST is also British Summer, and GST is * also Guam Standard. */ { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */ { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */ #endif { "nft", tZONE, HOUR( 7/2) }, /* Newfoundland */ { "nst", tZONE, HOUR( 7/2) }, /* Newfoundland Standard */ { "ndt", tDAYZONE, HOUR( 7/2) }, /* Newfoundland Daylight */ { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */ { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */ { "est", tZONE, HOUR( 5) }, /* Eastern Standard */ { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */ { "cst", tZONE, HOUR( 6) }, /* Central Standard */ { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */ { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */ { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */ { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */ { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */ { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */ { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */ { "akst", tZONE, HOUR( 9) }, /* Alaska Standard */ { "akdt", tDAYZONE, HOUR( 9) }, /* Alaska Daylight */ { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */ { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */ { "cat", tZONE, HOUR(10) }, /* Central Alaska */ { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */ { "nt", tZONE, HOUR(11) }, /* Nome */ { "idlw", tZONE, HOUR(12) }, /* International Date Line West */ { "cet", tZONE, -HOUR( 1) }, /* Central European */ { "cest", tDAYZONE, -HOUR( 1) }, /* Central European Summer */ { "met", tZONE, -HOUR( 1) }, /* Middle European */ { "mewt", tZONE, -HOUR( 1) }, /* Middle European Winter */ { "mest", tDAYZONE, -HOUR( 1) }, /* Middle European Summer */ { "swt", tZONE, -HOUR( 1) }, /* Swedish Winter */ { "sst", tDAYZONE, -HOUR( 1) }, /* Swedish Summer */ { "fwt", tZONE, -HOUR( 1) }, /* French Winter */ { "fst", tDAYZONE, -HOUR( 1) }, /* French Summer */ { "eet", tZONE, -HOUR( 2) }, /* Eastern Europe, USSR Zone 1 */ { "bt", tZONE, -HOUR( 3) }, /* Baghdad, USSR Zone 2 */ { "it", tZONE, -HOUR( 7/2) }, /* Iran */ { "zp4", tZONE, -HOUR( 4) }, /* USSR Zone 3 */ { "zp5", tZONE, -HOUR( 5) }, /* USSR Zone 4 */ { "ist", tZONE, -HOUR(11/2) }, /* Indian Standard */ { "zp6", tZONE, -HOUR( 6) }, /* USSR Zone 5 */ #if 0 /* For completeness. NST is also Newfoundland Standard, and SST is * also Swedish Summer. */ { "nst", tZONE, -HOUR(13/2) }, /* North Sumatra */ { "sst", tZONE, -HOUR( 7) }, /* South Sumatra, USSR Zone 6 */ #endif /* 0 */ { "wast", tZONE, -HOUR( 7) }, /* West Australian Standard */ { "wadt", tDAYZONE, -HOUR( 7) }, /* West Australian Daylight */ { "jt", tZONE, -HOUR(15/2) }, /* Java (3pm in Cronusland!) */ { "cct", tZONE, -HOUR( 8) }, /* China Coast, USSR Zone 7 */ { "jst", tZONE, -HOUR( 9) }, /* Japan Standard, USSR Zone 8 */ { "jdt", tDAYZONE, -HOUR( 9) }, /* Japan Daylight */ { "kst", tZONE, -HOUR( 9) }, /* Korea Standard */ { "kdt", tDAYZONE, -HOUR( 9) }, /* Korea Daylight */ { "cast", tZONE, -HOUR(19/2) }, /* Central Australian Standard */ { "cadt", tDAYZONE, -HOUR(19/2) }, /* Central Australian Daylight */ { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */ { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */ { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */ { "nzt", tZONE, -HOUR(12) }, /* New Zealand */ { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */ { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */ { "idle", tZONE, -HOUR(12) }, /* International Date Line East */ /* ADDED BY Marco Nijdam */ { "dst", tDST, HOUR( 0) }, /* DST on (hour is ignored) */ /* End ADDED */ { NULL, 0, 0 } }; /* * Military timezone table. */ static const TABLE MilitaryTable[] = { { "a", tZONE, -HOUR( 1) }, { "b", tZONE, -HOUR( 2) }, { "c", tZONE, -HOUR( 3) }, { "d", tZONE, -HOUR( 4) }, { "e", tZONE, -HOUR( 5) }, { "f", tZONE, -HOUR( 6) }, { "g", tZONE, -HOUR( 7) }, { "h", tZONE, -HOUR( 8) }, { "i", tZONE, -HOUR( 9) }, { "k", tZONE, -HOUR(10) }, { "l", tZONE, -HOUR(11) }, { "m", tZONE, -HOUR(12) }, { "n", tZONE, HOUR( 1) }, { "o", tZONE, HOUR( 2) }, { "p", tZONE, HOUR( 3) }, { "q", tZONE, HOUR( 4) }, { "r", tZONE, HOUR( 5) }, { "s", tZONE, HOUR( 6) }, { "t", tZONE, HOUR( 7) }, { "u", tZONE, HOUR( 8) }, { "v", tZONE, HOUR( 9) }, { "w", tZONE, HOUR( 10) }, { "x", tZONE, HOUR( 11) }, { "y", tZONE, HOUR( 12) }, { "z", tZONE, HOUR( 0) }, { NULL, 0, 0 } }; static inline const char * bypassSpaces( const char *s) { while (TclIsSpaceProc(*s)) { s++; } return s; } /* * Dump error messages in the bit bucket. */ static void TclDateerror( YYLTYPE* location, DateInfo* infoPtr, const char *s) { Tcl_Obj* t; if (!infoPtr->messages) { TclNewObj(infoPtr->messages); } Tcl_AppendToObj(infoPtr->messages, infoPtr->separatrix, -1); Tcl_AppendToObj(infoPtr->messages, s, -1); Tcl_AppendToObj(infoPtr->messages, " (characters ", -1); TclNewIntObj(t, location->first_column); Tcl_IncrRefCount(t); Tcl_AppendObjToObj(infoPtr->messages, t); Tcl_DecrRefCount(t); Tcl_AppendToObj(infoPtr->messages, "-", -1); TclNewIntObj(t, location->last_column); Tcl_IncrRefCount(t); Tcl_AppendObjToObj(infoPtr->messages, t); Tcl_DecrRefCount(t); Tcl_AppendToObj(infoPtr->messages, ")", -1); infoPtr->separatrix = "\n"; } int ToSeconds( int Hours, int Minutes, int Seconds, MERIDIAN Meridian) { switch (Meridian) { case MER24: return (Hours * 60 + Minutes) * 60 + Seconds; case MERam: return (((Hours / 24) * 24 + (Hours % 12)) * 60 + Minutes) * 60 + Seconds; case MERpm: return (((Hours / 24) * 24 + (Hours % 12) + 12) * 60 + Minutes) * 60 + Seconds; } return -1; /* Should never be reached */ } static int LookupWord( YYSTYPE* yylvalPtr, char *buff) { char *p; char *q; const TABLE *tp; int i, abbrev; /* * Make it lowercase. */ Tcl_UtfToLower(buff); if (*buff == 'a' && (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0)) { yylvalPtr->Meridian = MERam; return tMERIDIAN; } if (*buff == 'p' && (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0)) { yylvalPtr->Meridian = MERpm; return tMERIDIAN; } /* * See if we have an abbreviation for a month. */ if (strlen(buff) == 3) { abbrev = 1; } else if (strlen(buff) == 4 && buff[3] == '.') { abbrev = 1; buff[3] = '\0'; } else { abbrev = 0; } for (tp = MonthDayTable; tp->name; tp++) { if (abbrev) { if (strncmp(buff, tp->name, 3) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } else if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } for (tp = TimezoneTable; tp->name; tp++) { if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } for (tp = UnitsTable; tp->name; tp++) { if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } /* * Strip off any plural and try the units table again. */ i = strlen(buff) - 1; if (i > 0 && buff[i] == 's') { buff[i] = '\0'; for (tp = UnitsTable; tp->name; tp++) { if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } } for (tp = OtherTable; tp->name; tp++) { if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } /* * Military timezones. */ if (buff[1] == '\0' && !(*buff & 0x80) && isalpha(UCHAR(*buff))) { /* INTL: ISO only */ for (tp = MilitaryTable; tp->name; tp++) { if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } } /* * Drop out any periods and try the timezone table again. */ for (i = 0, p = q = buff; *q; q++) { if (*q != '.') { *p++ = *q; } else { i++; } } *p = '\0'; if (i) { for (tp = TimezoneTable; tp->name; tp++) { if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } } return tID; } static int TclDatelex( YYSTYPE* yylvalPtr, YYLTYPE* location, DateInfo *info) { char c; char *p; char buff[20]; int Count; const char *tokStart; location->first_column = yyInput - info->dateStart; for ( ; ; ) { if (isspace(UCHAR(*yyInput))) { yyInput = bypassSpaces(yyInput); /* ignore space at end of text and before some words */ c = *yyInput; if (c != '\0' && !isalpha(UCHAR(c))) { return SP; } } tokStart = yyInput; if (isdigit(UCHAR(c = *yyInput))) { /* INTL: digit */ /* * Count the number of digits. */ p = (char *)yyInput; while (isdigit(UCHAR(*++p))) {}; yyDigitCount = p - yyInput; /* * A number with 12 or 14 digits is considered an ISO 8601 date. */ if (yyDigitCount == 14 || yyDigitCount == 12) { /* long form of ISO 8601 (without separator), either * YYYYMMDDhhmmss or YYYYMMDDhhmm, so reduce to date * (8 chars is isodate) */ p = (char *)yyInput+8; if (TclAtoWIe(&yylvalPtr->Number, yyInput, p, 1) != TCL_OK) { return tID; /* overflow*/ } yyDigitCount = 8; yyInput = p; location->last_column = yyInput - info->dateStart - 1; return tISOBASL; } /* * Convert the string into a number */ if (TclAtoWIe(&yylvalPtr->Number, yyInput, p, 1) != TCL_OK) { return tID; /* overflow*/ } yyInput = p; /* * A number with 6 or more digits is considered an ISO 8601 base. */ location->last_column = yyInput - info->dateStart - 1; if (yyDigitCount >= 6) { if (yyDigitCount == 8) { return tISOBAS8; } if (yyDigitCount == 6) { return tISOBAS6; } } /* ignore spaces after digits (optional) */ yyInput = bypassSpaces(yyInput); return tUNUMBER; } if (!(c & 0x80) && isalpha(UCHAR(c))) { /* INTL: ISO only. */ int ret; for (p = buff; isalpha(UCHAR(c = *yyInput++)) /* INTL: ISO only. */ || c == '.'; ) { if (p < &buff[sizeof(buff) - 1]) { *p++ = c; } } *p = '\0'; yyInput--; location->last_column = yyInput - info->dateStart - 1; ret = LookupWord(yylvalPtr, buff); /* * lookahead: * for spaces to consider word boundaries (for instance * literal T in isodateTisotimeZ is not a TZ, but Z is UTC); * for +/- digit, to differentiate between "GMT+1000 day" and "GMT +1000 day"; * bypass spaces after token (but ignore by TZ+OFFS), because should * recognize next SP token, if TZ only. */ if (ret == tZONE || ret == tDAYZONE) { c = *yyInput; if (isdigit(UCHAR(c))) { /* literal not a TZ */ yyInput = tokStart; return *yyInput++; } if ((c == '+' || c == '-') && isdigit(UCHAR(*(yyInput+1)))) { if ( !isdigit(UCHAR(*(yyInput+2))) || !isdigit(UCHAR(*(yyInput+3)))) { /* GMT+1, GMT-10, etc. */ return tZONEwO2; } if ( isdigit(UCHAR(*(yyInput+4))) && !isdigit(UCHAR(*(yyInput+5)))) { /* GMT+1000, etc. */ return tZONEwO4; } } } yyInput = bypassSpaces(yyInput); return ret; } if (c != '(') { location->last_column = yyInput - info->dateStart; return *yyInput++; } Count = 0; do { c = *yyInput++; if (c == '\0') { location->last_column = yyInput - info->dateStart - 1; return c; } else if (c == '(') { Count++; } else if (c == ')') { Count--; } } while (Count > 0); } } int TclClockFreeScan( Tcl_Interp *interp, /* Tcl interpreter */ DateInfo *info) /* Input and result parameters */ { int status; #if YYDEBUG /* enable debugging if compiled with YYDEBUG */ yydebug = 1; #endif /* * yyInput = stringToParse; * * ClockInitDateInfo(info) should be executed to pre-init info; */ yyDSTmode = DSTmaybe; info->separatrix = ""; info->dateStart = yyInput; /* ignore spaces at begin */ yyInput = bypassSpaces(yyInput); /* parse */ status = yyparse(info); if (status == 1) { const char *msg = NULL; if (info->errFlags & CLF_HAVEDATE) { msg = "more than one date in string"; } else if (info->errFlags & CLF_TIME) { msg = "more than one time of day in string"; } else if (info->errFlags & CLF_ZONE) { msg = "more than one time zone in string"; } else if (info->errFlags & CLF_DAYOFWEEK) { msg = "more than one weekday in string"; } else if (info->errFlags & CLF_ORDINALMONTH) { msg = "more than one ordinal month in string"; } if (msg) { Tcl_SetObjResult(interp, Tcl_NewStringObj(msg, -1)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL); } else { Tcl_SetObjResult(interp, info->messages ? info->messages : Tcl_NewObj()); info->messages = NULL; Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "PARSE", (char *)NULL); } status = TCL_ERROR; } else if (status == 2) { Tcl_SetObjResult(interp, Tcl_NewStringObj("memory exhausted", -1)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); status = TCL_ERROR; } else if (status != 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj("Unknown status returned " "from date parser. Please " "report this error as a " "bug in Tcl.", -1)); Tcl_SetErrorCode(interp, "TCL", "BUG", (char *)NULL); status = TCL_ERROR; } if (info->messages) { Tcl_DecrRefCount(info->messages); } return status; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclDate.h0000644000175000017500000004061414726623136014730 0ustar sergeisergei/* * tclDate.h -- * * This header file handles common usage of clock primitives * between tclDate.c (yacc), tclClock.c and tclClockFmt.c. * * Copyright (c) 2014 Serg G. Brester (aka sebres) * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef _TCLCLOCK_H #define _TCLCLOCK_H /* * Constants */ #define JULIAN_DAY_POSIX_EPOCH 2440588 #define GREGORIAN_CHANGE_DATE 2361222 #define SECONDS_PER_DAY 86400 #define JULIAN_SEC_POSIX_EPOCH (((Tcl_WideInt) JULIAN_DAY_POSIX_EPOCH) \ * SECONDS_PER_DAY) #define FOUR_CENTURIES 146097 /* days */ #define JDAY_1_JAN_1_CE_JULIAN 1721424 #define JDAY_1_JAN_1_CE_GREGORIAN 1721426 #define ONE_CENTURY_GREGORIAN 36524 /* days */ #define FOUR_YEARS 1461 /* days */ #define ONE_YEAR 365 /* days */ #define RODDENBERRY 1946 /* Another epoch (Hi, Jeff!) */ enum DateInfoFlags { CLF_OPTIONAL = 1 << 0, /* token is non mandatory */ CLF_POSIXSEC = 1 << 1, CLF_LOCALSEC = 1 << 2, CLF_JULIANDAY = 1 << 3, CLF_TIME = 1 << 4, CLF_ZONE = 1 << 5, CLF_CENTURY = 1 << 6, CLF_DAYOFMONTH = 1 << 7, CLF_DAYOFYEAR = 1 << 8, CLF_MONTH = 1 << 9, CLF_YEAR = 1 << 10, CLF_DAYOFWEEK = 1 << 11, CLF_ISO8601YEAR = 1 << 12, CLF_ISO8601WEEK = 1 << 13, CLF_ISO8601CENTURY = 1 << 14, CLF_SIGNED = 1 << 15, /* Compounds */ CLF_HAVEDATE = (CLF_DAYOFMONTH | CLF_MONTH | CLF_YEAR), CLF_DATE = (CLF_JULIANDAY | CLF_DAYOFMONTH | CLF_DAYOFYEAR | CLF_MONTH | CLF_YEAR | CLF_ISO8601YEAR | CLF_DAYOFWEEK | CLF_ISO8601WEEK), /* * Extra flags used outside of scan/format-tokens too (int, not a short). */ CLF_RELCONV = 1 << 17, CLF_ORDINALMONTH = 1 << 18, /* On demand (lazy) assemble flags */ CLF_ASSEMBLE_DATE = 1 << 28,/* assemble year, month, etc. using julianDay */ CLF_ASSEMBLE_JULIANDAY = 1 << 29, /* assemble julianDay using year, month, etc. */ CLF_ASSEMBLE_SECONDS = 1 << 30 /* assemble localSeconds (and seconds at end) */ }; #define TCL_MIN_SECONDS -0x00F0000000000000LL #define TCL_MAX_SECONDS 0x00F0000000000000LL #define TCL_INV_SECONDS (TCL_MIN_SECONDS - 1) /* * Enumeration of the string literals used in [clock] */ typedef enum ClockLiteral { LIT__NIL, LIT__DEFAULT_FORMAT, LIT_SYSTEM, LIT_CURRENT, LIT_C, LIT_BCE, LIT_CE, LIT_DAYOFMONTH, LIT_DAYOFWEEK, LIT_DAYOFYEAR, LIT_ERA, LIT_GMT, LIT_GREGORIAN, LIT_INTEGER_VALUE_TOO_LARGE, LIT_ISO8601WEEK, LIT_ISO8601YEAR, LIT_JULIANDAY, LIT_LOCALSECONDS, LIT_MONTH, LIT_SECONDS, LIT_TZNAME, LIT_TZOFFSET, LIT_YEAR, LIT_TZDATA, LIT_GETSYSTEMTIMEZONE, LIT_SETUPTIMEZONE, LIT_MCGET, LIT_GETSYSTEMLOCALE, LIT_GETCURRENTLOCALE, LIT_LOCALIZE_FORMAT, LIT__END } ClockLiteral; #define CLOCK_LITERAL_ARRAY(litarr) static const char *const litarr[] = { \ "", \ "%a %b %d %H:%M:%S %Z %Y", \ "system", "current", "C", \ "BCE", "CE", \ "dayOfMonth", "dayOfWeek", "dayOfYear", \ "era", ":GMT", "gregorian", \ "integer value too large to represent", \ "iso8601Week", "iso8601Year", \ "julianDay", "localSeconds", \ "month", \ "seconds", "tzName", "tzOffset", \ "year", \ "::tcl::clock::TZData", \ "::tcl::clock::GetSystemTimeZone", \ "::tcl::clock::SetupTimeZone", \ "::tcl::clock::mcget", \ "::tcl::clock::GetSystemLocale", "::tcl::clock::mclocale", \ "::tcl::clock::LocalizeFormat" \ } /* * Enumeration of the msgcat literals used in [clock] */ typedef enum ClockMsgCtLiteral { MCLIT__NIL, /* placeholder */ MCLIT_MONTHS_FULL, MCLIT_MONTHS_ABBREV, MCLIT_MONTHS_COMB, MCLIT_DAYS_OF_WEEK_FULL, MCLIT_DAYS_OF_WEEK_ABBREV, MCLIT_DAYS_OF_WEEK_COMB, MCLIT_AM, MCLIT_PM, MCLIT_LOCALE_ERAS, MCLIT_BCE, MCLIT_CE, MCLIT_BCE2, MCLIT_CE2, MCLIT_BCE3, MCLIT_CE3, MCLIT_LOCALE_NUMERALS, MCLIT__END } ClockMsgCtLiteral; #define CLOCK_LOCALE_LITERAL_ARRAY(litarr, pref) static const char *const litarr[] = { \ pref "", \ pref "MONTHS_FULL", pref "MONTHS_ABBREV", pref "MONTHS_COMB", \ pref "DAYS_OF_WEEK_FULL", pref "DAYS_OF_WEEK_ABBREV", pref "DAYS_OF_WEEK_COMB", \ pref "AM", pref "PM", \ pref "LOCALE_ERAS", \ pref "BCE", pref "CE", \ pref "b.c.e.", pref "c.e.", \ pref "b.c.", pref "a.d.", \ pref "LOCALE_NUMERALS", \ } /* * Structure containing the fields used in [clock format] and [clock scan] */ enum TclDateFieldsFlags { CLF_CTZ = (1 << 4) }; typedef struct TclDateFields { /* Cacheable fields: */ Tcl_WideInt seconds; /* Time expressed in seconds from the Posix * epoch */ Tcl_WideInt localSeconds; /* Local time expressed in nominal seconds * from the Posix epoch */ int tzOffset; /* Time zone offset in seconds east of * Greenwich */ Tcl_WideInt julianDay; /* Julian Day Number in local time zone */ int isBce; /* 1 if BCE */ int gregorian; /* Flag == 1 if the date is Gregorian */ int year; /* Year of the era */ int dayOfYear; /* Day of the year (1 January == 1) */ int month; /* Month number */ int dayOfMonth; /* Day of the month */ int iso8601Year; /* ISO8601 week-based year */ int iso8601Week; /* ISO8601 week number */ int dayOfWeek; /* Day of the week */ int hour; /* Hours of day (in-between time only calculation) */ int minutes; /* Minutes of hour (in-between time only calculation) */ Tcl_WideInt secondOfMin; /* Seconds of minute (in-between time only calculation) */ Tcl_WideInt secondOfDay; /* Seconds of day (in-between time only calculation) */ int flags; /* 0 or CLF_CTZ */ /* Non cacheable fields: */ Tcl_Obj *tzName; /* Name (or corresponding DST-abbreviation) of the * time zone, if set the refCount is incremented */ } TclDateFields; #define ClockCacheableDateFieldsSize \ offsetof(TclDateFields, tzName) /* * Meridian: am, pm, or 24-hour style. */ typedef enum _MERIDIAN { MERam, MERpm, MER24 } MERIDIAN; /* * Structure contains return parsed fields. */ typedef struct DateInfo { const char *dateStart; const char *dateInput; const char *dateEnd; TclDateFields date; int flags; /* Signals parts of date/time get found */ int errFlags; /* Signals error (part of date/time found twice) */ MERIDIAN dateMeridian; int dateTimezone; int dateDSTmode; Tcl_WideInt dateRelMonth; Tcl_WideInt dateRelDay; Tcl_WideInt dateRelSeconds; int dateMonthOrdinalIncr; int dateMonthOrdinal; int dateDayOrdinal; Tcl_WideInt *dateRelPointer; int dateSpaceCount; int dateDigitCount; int dateCentury; Tcl_Obj *messages; /* Error messages */ const char* separatrix; /* String separating messages */ } DateInfo; #define yydate (info->date) /* Date fields used for converting */ #define yyDay (info->date.dayOfMonth) #define yyMonth (info->date.month) #define yyYear (info->date.year) #define yyHour (info->date.hour) #define yyMinutes (info->date.minutes) #define yySeconds (info->date.secondOfMin) #define yySecondOfDay (info->date.secondOfDay) #define yyDSTmode (info->dateDSTmode) #define yyDayOrdinal (info->dateDayOrdinal) #define yyDayOfWeek (info->date.dayOfWeek) #define yyMonthOrdinalIncr (info->dateMonthOrdinalIncr) #define yyMonthOrdinal (info->dateMonthOrdinal) #define yyTimezone (info->dateTimezone) #define yyMeridian (info->dateMeridian) #define yyRelMonth (info->dateRelMonth) #define yyRelDay (info->dateRelDay) #define yyRelSeconds (info->dateRelSeconds) #define yyRelPointer (info->dateRelPointer) #define yyInput (info->dateInput) #define yyDigitCount (info->dateDigitCount) #define yySpaceCount (info->dateSpaceCount) static inline void ClockInitDateInfo( DateInfo *info) { memset(info, 0, sizeof(DateInfo)); } /* * Structure containing the command arguments supplied to [clock format] and [clock scan] */ enum ClockFmtScnCmdArgsFlags { CLF_VALIDATE_S1 = (1 << 0), CLF_VALIDATE_S2 = (1 << 1), CLF_VALIDATE = (CLF_VALIDATE_S1|CLF_VALIDATE_S2), CLF_EXTENDED = (1 << 4), CLF_STRICT = (1 << 8), CLF_LOCALE_USED = (1 << 15) }; typedef struct ClockClientData ClockClientData; typedef struct ClockFmtScnCmdArgs { ClockClientData *dataPtr; /* Pointer to literal pool, etc. */ Tcl_Interp *interp; /* Tcl interpreter */ Tcl_Obj *formatObj; /* Format */ Tcl_Obj *localeObj; /* Name of the locale where the time will be expressed. */ Tcl_Obj *timezoneObj; /* Default time zone in which the time will be expressed */ Tcl_Obj *baseObj; /* Base (scan and add) or clockValue (format) */ int flags; /* Flags control scanning */ Tcl_Obj *mcDictObj; /* Current dictionary of tcl::clock package for given localeObj*/ } ClockFmtScnCmdArgs; /* Last-period cache for fast UTC to local and backwards conversion */ typedef struct ClockLastTZOffs { /* keys */ Tcl_Obj *timezoneObj; int changeover; Tcl_WideInt localSeconds; Tcl_WideInt rangesVal[2]; /* Bounds for cached time zone offset */ /* values */ int tzOffset; Tcl_Obj *tzName; /* Name (abbreviation) of this area in TZ */ } ClockLastTZOffs; /* * Structure containing the client data for [clock] */ typedef struct ClockClientData { size_t refCount; /* Number of live references. */ Tcl_Obj **literals; /* Pool of object literals (common, locale independent). */ Tcl_Obj **mcLiterals; /* Msgcat object literals with mc-keys for search with locale. */ Tcl_Obj **mcLitIdxs; /* Msgcat object indices prefixed with _IDX_, * used for quick dictionary search */ Tcl_Obj *mcDicts; /* Msgcat collection, contains weak pointers to locale * catalogs, and owns it references (onetime referenced) */ /* Cache for current clock parameters, imparted via "configure" */ size_t lastTZEpoch; int currentYearCentury; int yearOfCenturySwitch; int validMinYear; int validMaxYear; double maxJDN; Tcl_Obj *systemTimeZone; Tcl_Obj *systemSetupTZData; Tcl_Obj *gmtSetupTimeZoneUnnorm; Tcl_Obj *gmtSetupTimeZone; Tcl_Obj *gmtSetupTZData; Tcl_Obj *gmtTZName; Tcl_Obj *lastSetupTimeZoneUnnorm; Tcl_Obj *lastSetupTimeZone; Tcl_Obj *lastSetupTZData; Tcl_Obj *prevSetupTimeZoneUnnorm; Tcl_Obj *prevSetupTimeZone; Tcl_Obj *prevSetupTZData; Tcl_Obj *defaultLocale; Tcl_Obj *defaultLocaleDict; Tcl_Obj *currentLocale; Tcl_Obj *currentLocaleDict; Tcl_Obj *lastUsedLocaleUnnorm; Tcl_Obj *lastUsedLocale; Tcl_Obj *lastUsedLocaleDict; Tcl_Obj *prevUsedLocaleUnnorm; Tcl_Obj *prevUsedLocale; Tcl_Obj *prevUsedLocaleDict; /* Cache for last base (last-second fast convert if base/tz not changed) */ struct { Tcl_Obj *timezoneObj; TclDateFields date; } lastBase; /* Last-period cache for fast UTC to Local and backwards conversion */ ClockLastTZOffs lastTZOffsCache[2]; int defFlags; /* Default flags (from configure), ATM * only CLF_VALIDATE supported */ } ClockClientData; #define ClockDefaultYearCentury 2000 #define ClockDefaultCenturySwitch 38 /* * Clock scan and format facilities. */ #ifndef TCL_MEM_DEBUG # define CLOCK_FMT_SCN_STORAGE_GC_SIZE 32 #else # define CLOCK_FMT_SCN_STORAGE_GC_SIZE 0 #endif #define CLOCK_MIN_TOK_CHAIN_BLOCK_SIZE 2 typedef struct ClockScanToken ClockScanToken; typedef int ClockScanTokenProc( ClockFmtScnCmdArgs *opts, DateInfo *info, ClockScanToken *tok); typedef enum _CLCKTOK_TYPE { CTOKT_INT = 1, CTOKT_WIDE, CTOKT_PARSER, CTOKT_SPACE, CTOKT_WORD, CTOKT_CHAR, CFMTT_PROC } CLCKTOK_TYPE; typedef struct ClockScanTokenMap { unsigned short type; unsigned short flags; unsigned short clearFlags; unsigned short minSize; unsigned short maxSize; unsigned short offs; ClockScanTokenProc *parser; const void *data; } ClockScanTokenMap; struct ClockScanToken { const ClockScanTokenMap *map; struct { const char *start; const char *end; } tokWord; unsigned short endDistance; unsigned short lookAhMin; unsigned short lookAhMax; unsigned short lookAhTok; }; #define MIN_FMT_RESULT_BLOCK_ALLOC 80 #define MIN_FMT_RESULT_BLOCK_DELTA 0 /* Maximal permitted threshold (buffer size > result size) in percent, * to directly return the buffer without reallocate */ #define MAX_FMT_RESULT_THRESHOLD 2 typedef struct DateFormat { char *resMem; char *resEnd; char *output; TclDateFields date; Tcl_Obj *localeEra; } DateFormat; enum ClockFormatTokenMapFlags { CLFMT_INCR = (1 << 3), CLFMT_DECR = (1 << 4), CLFMT_CALC = (1 << 5), CLFMT_LOCALE_INDX = (1 << 8) }; typedef struct ClockFormatToken ClockFormatToken; typedef int ClockFormatTokenProc( ClockFmtScnCmdArgs *opts, DateFormat *dateFmt, ClockFormatToken *tok, int *val); typedef struct ClockFormatTokenMap { unsigned short type; const char *tostr; unsigned short width; unsigned short flags; unsigned short divider; unsigned short divmod; unsigned short offs; ClockFormatTokenProc *fmtproc; void *data; } ClockFormatTokenMap; struct ClockFormatToken { const ClockFormatTokenMap *map; struct { const char *start; const char *end; } tokWord; }; typedef struct ClockFmtScnStorage ClockFmtScnStorage; struct ClockFmtScnStorage { int objRefCount; /* Reference count shared across threads */ ClockScanToken *scnTok; unsigned scnTokC; unsigned scnSpaceCount; /* Count of mandatory spaces used in format */ ClockFormatToken *fmtTok; unsigned fmtTokC; #if CLOCK_FMT_SCN_STORAGE_GC_SIZE > 0 ClockFmtScnStorage *nextPtr; ClockFmtScnStorage *prevPtr; #endif size_t fmtMinAlloc; #if 0 Tcl_HashEntry hashEntry /* ClockFmtScnStorage is a derivate of Tcl_HashEntry, * stored by offset +sizeof(self) */ #endif }; /* * Clock macros. */ /* * Extracts Julian day and seconds of the day from posix seconds (tm). */ #define ClockExtractJDAndSODFromSeconds(jd, sod, tm) \ do { \ jd = (tm + JULIAN_SEC_POSIX_EPOCH); \ if (jd >= SECONDS_PER_DAY || jd <= -SECONDS_PER_DAY) { \ jd /= SECONDS_PER_DAY; \ sod = (int)(tm % SECONDS_PER_DAY); \ } else { \ sod = (int)jd, jd = 0; \ } \ if (sod < 0) { \ sod += SECONDS_PER_DAY; \ /* JD is affected, if switched into negative (avoid 24 hours difference) */ \ if (jd <= 0) { \ jd--; \ } \ } \ } while(0) /* * Prototypes of module functions. */ MODULE_SCOPE int ToSeconds(int Hours, int Minutes, int Seconds, MERIDIAN Meridian); MODULE_SCOPE int IsGregorianLeapYear(TclDateFields *); MODULE_SCOPE void GetJulianDayFromEraYearWeekDay( TclDateFields *fields, int changeover); MODULE_SCOPE void GetJulianDayFromEraYearMonthDay( TclDateFields *fields, int changeover); MODULE_SCOPE void GetJulianDayFromEraYearDay( TclDateFields *fields, int changeover); MODULE_SCOPE int ConvertUTCToLocal(ClockClientData *dataPtr, Tcl_Interp *, TclDateFields *, Tcl_Obj *timezoneObj, int); MODULE_SCOPE Tcl_Obj * LookupLastTransition(Tcl_Interp *, Tcl_WideInt, Tcl_Size, Tcl_Obj *const *, Tcl_WideInt *rangesVal); MODULE_SCOPE int TclClockFreeScan(Tcl_Interp *interp, DateInfo *info); /* tclClock.c module declarations */ MODULE_SCOPE Tcl_Obj * ClockSetupTimeZone(ClockClientData *dataPtr, Tcl_Interp *interp, Tcl_Obj *timezoneObj); MODULE_SCOPE Tcl_Obj * ClockMCDict(ClockFmtScnCmdArgs *opts); MODULE_SCOPE Tcl_Obj * ClockMCGet(ClockFmtScnCmdArgs *opts, int mcKey); MODULE_SCOPE Tcl_Obj * ClockMCGetIdx(ClockFmtScnCmdArgs *opts, int mcKey); MODULE_SCOPE int ClockMCSetIdx(ClockFmtScnCmdArgs *opts, int mcKey, Tcl_Obj *valObj); /* tclClockFmt.c module declarations */ MODULE_SCOPE char * TclItoAw(char *buf, int val, char padchar, unsigned short width); MODULE_SCOPE int TclAtoWIe(Tcl_WideInt *out, const char *p, const char *e, int sign); MODULE_SCOPE Tcl_Obj* ClockFrmObjGetLocFmtKey(Tcl_Interp *interp, Tcl_Obj *objPtr); MODULE_SCOPE ClockFmtScnStorage *Tcl_GetClockFrmScnFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr); MODULE_SCOPE Tcl_Obj * ClockLocalizeFormat(ClockFmtScnCmdArgs *opts); MODULE_SCOPE int ClockScan(DateInfo *info, Tcl_Obj *strObj, ClockFmtScnCmdArgs *opts); MODULE_SCOPE int ClockFormat(DateFormat *dateFmt, ClockFmtScnCmdArgs *opts); MODULE_SCOPE void ClockFrmScnClearCaches(void); MODULE_SCOPE void ClockFrmScnFinalize(); #endif /* _TCLCLOCK_H */ tcl9.0.1/generic/tclDecls.h0000644000175000017500000056236014731057471015113 0ustar sergeisergei/* * tclDecls.h -- * * Declarations of functions in the platform independent public Tcl API. * * Copyright (c) 1998-1999 by Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef _TCLDECLS #define _TCLDECLS #include /* for size_t */ #undef TCL_STORAGE_CLASS #ifdef BUILD_tcl # define TCL_STORAGE_CLASS DLLEXPORT #else # ifdef USE_TCL_STUBS # define TCL_STORAGE_CLASS # else # define TCL_STORAGE_CLASS DLLIMPORT # endif #endif #if !defined(BUILD_tcl) # define TCL_DEPRECATED(msg) EXTERN TCL_DEPRECATED_API(msg) #elif defined(TCL_NO_DEPRECATED) # define TCL_DEPRECATED(msg) MODULE_SCOPE #else # define TCL_DEPRECATED(msg) EXTERN #endif /* * WARNING: This file is automatically generated by the tools/genStubs.tcl * script. Any modifications to the function declarations below should be made * in the generic/tcl.decls script. */ /* !BEGIN!: Do not edit below this line. */ #ifdef __cplusplus extern "C" { #endif /* * Exported function declarations: */ /* 0 */ EXTERN int Tcl_PkgProvideEx(Tcl_Interp *interp, const char *name, const char *version, const void *clientData); /* 1 */ EXTERN const char * Tcl_PkgRequireEx(Tcl_Interp *interp, const char *name, const char *version, int exact, void *clientDataPtr); /* 2 */ EXTERN TCL_NORETURN void Tcl_Panic(const char *format, ...) TCL_FORMAT_PRINTF(1, 2); /* 3 */ EXTERN void * Tcl_Alloc(TCL_HASH_TYPE size); /* 4 */ EXTERN void Tcl_Free(void *ptr); /* 5 */ EXTERN void * Tcl_Realloc(void *ptr, TCL_HASH_TYPE size); /* 6 */ EXTERN void * Tcl_DbCkalloc(TCL_HASH_TYPE size, const char *file, int line); /* 7 */ EXTERN void Tcl_DbCkfree(void *ptr, const char *file, int line); /* 8 */ EXTERN void * Tcl_DbCkrealloc(void *ptr, TCL_HASH_TYPE size, const char *file, int line); /* 9 */ EXTERN void Tcl_CreateFileHandler(int fd, int mask, Tcl_FileProc *proc, void *clientData); /* 10 */ EXTERN void Tcl_DeleteFileHandler(int fd); /* 11 */ EXTERN void Tcl_SetTimer(const Tcl_Time *timePtr); /* 12 */ EXTERN void Tcl_Sleep(int ms); /* 13 */ EXTERN int Tcl_WaitForEvent(const Tcl_Time *timePtr); /* 14 */ EXTERN int Tcl_AppendAllObjTypes(Tcl_Interp *interp, Tcl_Obj *objPtr); /* 15 */ EXTERN void Tcl_AppendStringsToObj(Tcl_Obj *objPtr, ...); /* 16 */ EXTERN void Tcl_AppendToObj(Tcl_Obj *objPtr, const char *bytes, Tcl_Size length); /* 17 */ EXTERN Tcl_Obj * Tcl_ConcatObj(Tcl_Size objc, Tcl_Obj *const objv[]); /* 18 */ EXTERN int Tcl_ConvertToType(Tcl_Interp *interp, Tcl_Obj *objPtr, const Tcl_ObjType *typePtr); /* 19 */ EXTERN void Tcl_DbDecrRefCount(Tcl_Obj *objPtr, const char *file, int line); /* 20 */ EXTERN void Tcl_DbIncrRefCount(Tcl_Obj *objPtr, const char *file, int line); /* 21 */ EXTERN int Tcl_DbIsShared(Tcl_Obj *objPtr, const char *file, int line); /* Slot 22 is reserved */ /* 23 */ EXTERN Tcl_Obj * Tcl_DbNewByteArrayObj(const unsigned char *bytes, Tcl_Size numBytes, const char *file, int line); /* 24 */ EXTERN Tcl_Obj * Tcl_DbNewDoubleObj(double doubleValue, const char *file, int line); /* 25 */ EXTERN Tcl_Obj * Tcl_DbNewListObj(Tcl_Size objc, Tcl_Obj *const *objv, const char *file, int line); /* Slot 26 is reserved */ /* 27 */ EXTERN Tcl_Obj * Tcl_DbNewObj(const char *file, int line); /* 28 */ EXTERN Tcl_Obj * Tcl_DbNewStringObj(const char *bytes, Tcl_Size length, const char *file, int line); /* 29 */ EXTERN Tcl_Obj * Tcl_DuplicateObj(Tcl_Obj *objPtr); /* 30 */ EXTERN void TclFreeObj(Tcl_Obj *objPtr); /* 31 */ EXTERN int Tcl_GetBoolean(Tcl_Interp *interp, const char *src, int *intPtr); /* 32 */ EXTERN int Tcl_GetBooleanFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *intPtr); /* 33 */ EXTERN unsigned char * Tcl_GetByteArrayFromObj(Tcl_Obj *objPtr, Tcl_Size *numBytesPtr); /* 34 */ EXTERN int Tcl_GetDouble(Tcl_Interp *interp, const char *src, double *doublePtr); /* 35 */ EXTERN int Tcl_GetDoubleFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, double *doublePtr); /* Slot 36 is reserved */ /* 37 */ EXTERN int Tcl_GetInt(Tcl_Interp *interp, const char *src, int *intPtr); /* 38 */ EXTERN int Tcl_GetIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *intPtr); /* 39 */ EXTERN int Tcl_GetLongFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, long *longPtr); /* 40 */ EXTERN const Tcl_ObjType * Tcl_GetObjType(const char *typeName); /* 41 */ EXTERN char * TclGetStringFromObj(Tcl_Obj *objPtr, void *lengthPtr); /* 42 */ EXTERN void Tcl_InvalidateStringRep(Tcl_Obj *objPtr); /* 43 */ EXTERN int Tcl_ListObjAppendList(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Obj *elemListPtr); /* 44 */ EXTERN int Tcl_ListObjAppendElement(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Obj *objPtr); /* 45 */ EXTERN int TclListObjGetElements(Tcl_Interp *interp, Tcl_Obj *listPtr, void *objcPtr, Tcl_Obj ***objvPtr); /* 46 */ EXTERN int Tcl_ListObjIndex(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size index, Tcl_Obj **objPtrPtr); /* 47 */ EXTERN int TclListObjLength(Tcl_Interp *interp, Tcl_Obj *listPtr, void *lengthPtr); /* 48 */ EXTERN int Tcl_ListObjReplace(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size first, Tcl_Size count, Tcl_Size objc, Tcl_Obj *const objv[]); /* Slot 49 is reserved */ /* 50 */ EXTERN Tcl_Obj * Tcl_NewByteArrayObj(const unsigned char *bytes, Tcl_Size numBytes); /* 51 */ EXTERN Tcl_Obj * Tcl_NewDoubleObj(double doubleValue); /* Slot 52 is reserved */ /* 53 */ EXTERN Tcl_Obj * Tcl_NewListObj(Tcl_Size objc, Tcl_Obj *const objv[]); /* Slot 54 is reserved */ /* 55 */ EXTERN Tcl_Obj * Tcl_NewObj(void); /* 56 */ EXTERN Tcl_Obj * Tcl_NewStringObj(const char *bytes, Tcl_Size length); /* Slot 57 is reserved */ /* 58 */ EXTERN unsigned char * Tcl_SetByteArrayLength(Tcl_Obj *objPtr, Tcl_Size numBytes); /* 59 */ EXTERN void Tcl_SetByteArrayObj(Tcl_Obj *objPtr, const unsigned char *bytes, Tcl_Size numBytes); /* 60 */ EXTERN void Tcl_SetDoubleObj(Tcl_Obj *objPtr, double doubleValue); /* Slot 61 is reserved */ /* 62 */ EXTERN void Tcl_SetListObj(Tcl_Obj *objPtr, Tcl_Size objc, Tcl_Obj *const objv[]); /* Slot 63 is reserved */ /* 64 */ EXTERN void Tcl_SetObjLength(Tcl_Obj *objPtr, Tcl_Size length); /* 65 */ EXTERN void Tcl_SetStringObj(Tcl_Obj *objPtr, const char *bytes, Tcl_Size length); /* Slot 66 is reserved */ /* Slot 67 is reserved */ /* 68 */ EXTERN void Tcl_AllowExceptions(Tcl_Interp *interp); /* 69 */ EXTERN void Tcl_AppendElement(Tcl_Interp *interp, const char *element); /* 70 */ EXTERN void Tcl_AppendResult(Tcl_Interp *interp, ...); /* 71 */ EXTERN Tcl_AsyncHandler Tcl_AsyncCreate(Tcl_AsyncProc *proc, void *clientData); /* 72 */ EXTERN void Tcl_AsyncDelete(Tcl_AsyncHandler async); /* 73 */ EXTERN int Tcl_AsyncInvoke(Tcl_Interp *interp, int code); /* 74 */ EXTERN void Tcl_AsyncMark(Tcl_AsyncHandler async); /* 75 */ EXTERN int Tcl_AsyncReady(void); /* Slot 76 is reserved */ /* Slot 77 is reserved */ /* 78 */ EXTERN int Tcl_BadChannelOption(Tcl_Interp *interp, const char *optionName, const char *optionList); /* 79 */ EXTERN void Tcl_CallWhenDeleted(Tcl_Interp *interp, Tcl_InterpDeleteProc *proc, void *clientData); /* 80 */ EXTERN void Tcl_CancelIdleCall(Tcl_IdleProc *idleProc, void *clientData); /* 81 */ EXTERN int Tcl_Close(Tcl_Interp *interp, Tcl_Channel chan); /* 82 */ EXTERN int Tcl_CommandComplete(const char *cmd); /* 83 */ EXTERN char * Tcl_Concat(Tcl_Size argc, const char *const *argv); /* 84 */ EXTERN Tcl_Size Tcl_ConvertElement(const char *src, char *dst, int flags); /* 85 */ EXTERN Tcl_Size Tcl_ConvertCountedElement(const char *src, Tcl_Size length, char *dst, int flags); /* 86 */ EXTERN int Tcl_CreateAlias(Tcl_Interp *childInterp, const char *childCmd, Tcl_Interp *target, const char *targetCmd, Tcl_Size argc, const char *const *argv); /* 87 */ EXTERN int Tcl_CreateAliasObj(Tcl_Interp *childInterp, const char *childCmd, Tcl_Interp *target, const char *targetCmd, Tcl_Size objc, Tcl_Obj *const objv[]); /* 88 */ EXTERN Tcl_Channel Tcl_CreateChannel(const Tcl_ChannelType *typePtr, const char *chanName, void *instanceData, int mask); /* 89 */ EXTERN void Tcl_CreateChannelHandler(Tcl_Channel chan, int mask, Tcl_ChannelProc *proc, void *clientData); /* 90 */ EXTERN void Tcl_CreateCloseHandler(Tcl_Channel chan, Tcl_CloseProc *proc, void *clientData); /* 91 */ EXTERN Tcl_Command Tcl_CreateCommand(Tcl_Interp *interp, const char *cmdName, Tcl_CmdProc *proc, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 92 */ EXTERN void Tcl_CreateEventSource(Tcl_EventSetupProc *setupProc, Tcl_EventCheckProc *checkProc, void *clientData); /* 93 */ EXTERN void Tcl_CreateExitHandler(Tcl_ExitProc *proc, void *clientData); /* 94 */ EXTERN Tcl_Interp * Tcl_CreateInterp(void); /* Slot 95 is reserved */ /* 96 */ EXTERN Tcl_Command Tcl_CreateObjCommand(Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc *proc, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 97 */ EXTERN Tcl_Interp * Tcl_CreateChild(Tcl_Interp *interp, const char *name, int isSafe); /* 98 */ EXTERN Tcl_TimerToken Tcl_CreateTimerHandler(int milliseconds, Tcl_TimerProc *proc, void *clientData); /* 99 */ EXTERN Tcl_Trace Tcl_CreateTrace(Tcl_Interp *interp, Tcl_Size level, Tcl_CmdTraceProc *proc, void *clientData); /* 100 */ EXTERN void Tcl_DeleteAssocData(Tcl_Interp *interp, const char *name); /* 101 */ EXTERN void Tcl_DeleteChannelHandler(Tcl_Channel chan, Tcl_ChannelProc *proc, void *clientData); /* 102 */ EXTERN void Tcl_DeleteCloseHandler(Tcl_Channel chan, Tcl_CloseProc *proc, void *clientData); /* 103 */ EXTERN int Tcl_DeleteCommand(Tcl_Interp *interp, const char *cmdName); /* 104 */ EXTERN int Tcl_DeleteCommandFromToken(Tcl_Interp *interp, Tcl_Command command); /* 105 */ EXTERN void Tcl_DeleteEvents(Tcl_EventDeleteProc *proc, void *clientData); /* 106 */ EXTERN void Tcl_DeleteEventSource(Tcl_EventSetupProc *setupProc, Tcl_EventCheckProc *checkProc, void *clientData); /* 107 */ EXTERN void Tcl_DeleteExitHandler(Tcl_ExitProc *proc, void *clientData); /* 108 */ EXTERN void Tcl_DeleteHashEntry(Tcl_HashEntry *entryPtr); /* 109 */ EXTERN void Tcl_DeleteHashTable(Tcl_HashTable *tablePtr); /* 110 */ EXTERN void Tcl_DeleteInterp(Tcl_Interp *interp); /* 111 */ EXTERN void Tcl_DetachPids(Tcl_Size numPids, Tcl_Pid *pidPtr); /* 112 */ EXTERN void Tcl_DeleteTimerHandler(Tcl_TimerToken token); /* 113 */ EXTERN void Tcl_DeleteTrace(Tcl_Interp *interp, Tcl_Trace trace); /* 114 */ EXTERN void Tcl_DontCallWhenDeleted(Tcl_Interp *interp, Tcl_InterpDeleteProc *proc, void *clientData); /* 115 */ EXTERN int Tcl_DoOneEvent(int flags); /* 116 */ EXTERN void Tcl_DoWhenIdle(Tcl_IdleProc *proc, void *clientData); /* 117 */ EXTERN char * Tcl_DStringAppend(Tcl_DString *dsPtr, const char *bytes, Tcl_Size length); /* 118 */ EXTERN char * Tcl_DStringAppendElement(Tcl_DString *dsPtr, const char *element); /* 119 */ EXTERN void Tcl_DStringEndSublist(Tcl_DString *dsPtr); /* 120 */ EXTERN void Tcl_DStringFree(Tcl_DString *dsPtr); /* 121 */ EXTERN void Tcl_DStringGetResult(Tcl_Interp *interp, Tcl_DString *dsPtr); /* 122 */ EXTERN void Tcl_DStringInit(Tcl_DString *dsPtr); /* 123 */ EXTERN void Tcl_DStringResult(Tcl_Interp *interp, Tcl_DString *dsPtr); /* 124 */ EXTERN void Tcl_DStringSetLength(Tcl_DString *dsPtr, Tcl_Size length); /* 125 */ EXTERN void Tcl_DStringStartSublist(Tcl_DString *dsPtr); /* 126 */ EXTERN int Tcl_Eof(Tcl_Channel chan); /* 127 */ EXTERN const char * Tcl_ErrnoId(void); /* 128 */ EXTERN const char * Tcl_ErrnoMsg(int err); /* Slot 129 is reserved */ /* 130 */ EXTERN int Tcl_EvalFile(Tcl_Interp *interp, const char *fileName); /* Slot 131 is reserved */ /* 132 */ EXTERN void Tcl_EventuallyFree(void *clientData, Tcl_FreeProc *freeProc); /* 133 */ EXTERN TCL_NORETURN void Tcl_Exit(int status); /* 134 */ EXTERN int Tcl_ExposeCommand(Tcl_Interp *interp, const char *hiddenCmdToken, const char *cmdName); /* 135 */ EXTERN int Tcl_ExprBoolean(Tcl_Interp *interp, const char *expr, int *ptr); /* 136 */ EXTERN int Tcl_ExprBooleanObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *ptr); /* 137 */ EXTERN int Tcl_ExprDouble(Tcl_Interp *interp, const char *expr, double *ptr); /* 138 */ EXTERN int Tcl_ExprDoubleObj(Tcl_Interp *interp, Tcl_Obj *objPtr, double *ptr); /* 139 */ EXTERN int Tcl_ExprLong(Tcl_Interp *interp, const char *expr, long *ptr); /* 140 */ EXTERN int Tcl_ExprLongObj(Tcl_Interp *interp, Tcl_Obj *objPtr, long *ptr); /* 141 */ EXTERN int Tcl_ExprObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj **resultPtrPtr); /* 142 */ EXTERN int Tcl_ExprString(Tcl_Interp *interp, const char *expr); /* 143 */ EXTERN void Tcl_Finalize(void); /* Slot 144 is reserved */ /* 145 */ EXTERN Tcl_HashEntry * Tcl_FirstHashEntry(Tcl_HashTable *tablePtr, Tcl_HashSearch *searchPtr); /* 146 */ EXTERN int Tcl_Flush(Tcl_Channel chan); /* Slot 147 is reserved */ /* Slot 148 is reserved */ /* 149 */ EXTERN int TclGetAliasObj(Tcl_Interp *interp, const char *childCmd, Tcl_Interp **targetInterpPtr, const char **targetCmdPtr, int *objcPtr, Tcl_Obj ***objvPtr); /* 150 */ EXTERN void * Tcl_GetAssocData(Tcl_Interp *interp, const char *name, Tcl_InterpDeleteProc **procPtr); /* 151 */ EXTERN Tcl_Channel Tcl_GetChannel(Tcl_Interp *interp, const char *chanName, int *modePtr); /* 152 */ EXTERN Tcl_Size Tcl_GetChannelBufferSize(Tcl_Channel chan); /* 153 */ EXTERN int Tcl_GetChannelHandle(Tcl_Channel chan, int direction, void **handlePtr); /* 154 */ EXTERN void * Tcl_GetChannelInstanceData(Tcl_Channel chan); /* 155 */ EXTERN int Tcl_GetChannelMode(Tcl_Channel chan); /* 156 */ EXTERN const char * Tcl_GetChannelName(Tcl_Channel chan); /* 157 */ EXTERN int Tcl_GetChannelOption(Tcl_Interp *interp, Tcl_Channel chan, const char *optionName, Tcl_DString *dsPtr); /* 158 */ EXTERN const Tcl_ChannelType * Tcl_GetChannelType(Tcl_Channel chan); /* 159 */ EXTERN int Tcl_GetCommandInfo(Tcl_Interp *interp, const char *cmdName, Tcl_CmdInfo *infoPtr); /* 160 */ EXTERN const char * Tcl_GetCommandName(Tcl_Interp *interp, Tcl_Command command); /* 161 */ EXTERN int Tcl_GetErrno(void); /* 162 */ EXTERN const char * Tcl_GetHostName(void); /* 163 */ EXTERN int Tcl_GetInterpPath(Tcl_Interp *interp, Tcl_Interp *childInterp); /* 164 */ EXTERN Tcl_Interp * Tcl_GetParent(Tcl_Interp *interp); /* 165 */ EXTERN const char * Tcl_GetNameOfExecutable(void); /* 166 */ EXTERN Tcl_Obj * Tcl_GetObjResult(Tcl_Interp *interp); /* 167 */ EXTERN int Tcl_GetOpenFile(Tcl_Interp *interp, const char *chanID, int forWriting, int checkUsage, void **filePtr); /* 168 */ EXTERN Tcl_PathType Tcl_GetPathType(const char *path); /* 169 */ EXTERN Tcl_Size Tcl_Gets(Tcl_Channel chan, Tcl_DString *dsPtr); /* 170 */ EXTERN Tcl_Size Tcl_GetsObj(Tcl_Channel chan, Tcl_Obj *objPtr); /* 171 */ EXTERN int Tcl_GetServiceMode(void); /* 172 */ EXTERN Tcl_Interp * Tcl_GetChild(Tcl_Interp *interp, const char *name); /* 173 */ EXTERN Tcl_Channel Tcl_GetStdChannel(int type); /* Slot 174 is reserved */ /* Slot 175 is reserved */ /* 176 */ EXTERN const char * Tcl_GetVar2(Tcl_Interp *interp, const char *part1, const char *part2, int flags); /* Slot 177 is reserved */ /* Slot 178 is reserved */ /* 179 */ EXTERN int Tcl_HideCommand(Tcl_Interp *interp, const char *cmdName, const char *hiddenCmdToken); /* 180 */ EXTERN int Tcl_Init(Tcl_Interp *interp); /* 181 */ EXTERN void Tcl_InitHashTable(Tcl_HashTable *tablePtr, int keyType); /* 182 */ EXTERN int Tcl_InputBlocked(Tcl_Channel chan); /* 183 */ EXTERN int Tcl_InputBuffered(Tcl_Channel chan); /* 184 */ EXTERN int Tcl_InterpDeleted(Tcl_Interp *interp); /* 185 */ EXTERN int Tcl_IsSafe(Tcl_Interp *interp); /* 186 */ EXTERN char * Tcl_JoinPath(Tcl_Size argc, const char *const *argv, Tcl_DString *resultPtr); /* 187 */ EXTERN int Tcl_LinkVar(Tcl_Interp *interp, const char *varName, void *addr, int type); /* Slot 188 is reserved */ /* 189 */ EXTERN Tcl_Channel Tcl_MakeFileChannel(void *handle, int mode); /* Slot 190 is reserved */ /* 191 */ EXTERN Tcl_Channel Tcl_MakeTcpClientChannel(void *tcpSocket); /* 192 */ EXTERN char * Tcl_Merge(Tcl_Size argc, const char *const *argv); /* 193 */ EXTERN Tcl_HashEntry * Tcl_NextHashEntry(Tcl_HashSearch *searchPtr); /* 194 */ EXTERN void Tcl_NotifyChannel(Tcl_Channel channel, int mask); /* 195 */ EXTERN Tcl_Obj * Tcl_ObjGetVar2(Tcl_Interp *interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags); /* 196 */ EXTERN Tcl_Obj * Tcl_ObjSetVar2(Tcl_Interp *interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *newValuePtr, int flags); /* 197 */ EXTERN Tcl_Channel Tcl_OpenCommandChannel(Tcl_Interp *interp, Tcl_Size argc, const char **argv, int flags); /* 198 */ EXTERN Tcl_Channel Tcl_OpenFileChannel(Tcl_Interp *interp, const char *fileName, const char *modeString, int permissions); /* 199 */ EXTERN Tcl_Channel Tcl_OpenTcpClient(Tcl_Interp *interp, int port, const char *address, const char *myaddr, int myport, int flags); /* 200 */ EXTERN Tcl_Channel Tcl_OpenTcpServer(Tcl_Interp *interp, int port, const char *host, Tcl_TcpAcceptProc *acceptProc, void *callbackData); /* 201 */ EXTERN void Tcl_Preserve(void *data); /* 202 */ EXTERN void Tcl_PrintDouble(Tcl_Interp *interp, double value, char *dst); /* 203 */ EXTERN int Tcl_PutEnv(const char *assignment); /* 204 */ EXTERN const char * Tcl_PosixError(Tcl_Interp *interp); /* 205 */ EXTERN void Tcl_QueueEvent(Tcl_Event *evPtr, int position); /* 206 */ EXTERN Tcl_Size Tcl_Read(Tcl_Channel chan, char *bufPtr, Tcl_Size toRead); /* 207 */ EXTERN void Tcl_ReapDetachedProcs(void); /* 208 */ EXTERN int Tcl_RecordAndEval(Tcl_Interp *interp, const char *cmd, int flags); /* 209 */ EXTERN int Tcl_RecordAndEvalObj(Tcl_Interp *interp, Tcl_Obj *cmdPtr, int flags); /* 210 */ EXTERN void Tcl_RegisterChannel(Tcl_Interp *interp, Tcl_Channel chan); /* 211 */ EXTERN void Tcl_RegisterObjType(const Tcl_ObjType *typePtr); /* 212 */ EXTERN Tcl_RegExp Tcl_RegExpCompile(Tcl_Interp *interp, const char *pattern); /* 213 */ EXTERN int Tcl_RegExpExec(Tcl_Interp *interp, Tcl_RegExp regexp, const char *text, const char *start); /* 214 */ EXTERN int Tcl_RegExpMatch(Tcl_Interp *interp, const char *text, const char *pattern); /* 215 */ EXTERN void Tcl_RegExpRange(Tcl_RegExp regexp, Tcl_Size index, const char **startPtr, const char **endPtr); /* 216 */ EXTERN void Tcl_Release(void *clientData); /* 217 */ EXTERN void Tcl_ResetResult(Tcl_Interp *interp); /* 218 */ EXTERN Tcl_Size Tcl_ScanElement(const char *src, int *flagPtr); /* 219 */ EXTERN Tcl_Size Tcl_ScanCountedElement(const char *src, Tcl_Size length, int *flagPtr); /* Slot 220 is reserved */ /* 221 */ EXTERN int Tcl_ServiceAll(void); /* 222 */ EXTERN int Tcl_ServiceEvent(int flags); /* 223 */ EXTERN void Tcl_SetAssocData(Tcl_Interp *interp, const char *name, Tcl_InterpDeleteProc *proc, void *clientData); /* 224 */ EXTERN void Tcl_SetChannelBufferSize(Tcl_Channel chan, Tcl_Size sz); /* 225 */ EXTERN int Tcl_SetChannelOption(Tcl_Interp *interp, Tcl_Channel chan, const char *optionName, const char *newValue); /* 226 */ EXTERN int Tcl_SetCommandInfo(Tcl_Interp *interp, const char *cmdName, const Tcl_CmdInfo *infoPtr); /* 227 */ EXTERN void Tcl_SetErrno(int err); /* 228 */ EXTERN void Tcl_SetErrorCode(Tcl_Interp *interp, ...); /* 229 */ EXTERN void Tcl_SetMaxBlockTime(const Tcl_Time *timePtr); /* Slot 230 is reserved */ /* 231 */ EXTERN Tcl_Size Tcl_SetRecursionLimit(Tcl_Interp *interp, Tcl_Size depth); /* Slot 232 is reserved */ /* 233 */ EXTERN int Tcl_SetServiceMode(int mode); /* 234 */ EXTERN void Tcl_SetObjErrorCode(Tcl_Interp *interp, Tcl_Obj *errorObjPtr); /* 235 */ EXTERN void Tcl_SetObjResult(Tcl_Interp *interp, Tcl_Obj *resultObjPtr); /* 236 */ EXTERN void Tcl_SetStdChannel(Tcl_Channel channel, int type); /* Slot 237 is reserved */ /* 238 */ EXTERN const char * Tcl_SetVar2(Tcl_Interp *interp, const char *part1, const char *part2, const char *newValue, int flags); /* 239 */ EXTERN const char * Tcl_SignalId(int sig); /* 240 */ EXTERN const char * Tcl_SignalMsg(int sig); /* 241 */ EXTERN void Tcl_SourceRCFile(Tcl_Interp *interp); /* 242 */ EXTERN int TclSplitList(Tcl_Interp *interp, const char *listStr, void *argcPtr, const char ***argvPtr); /* 243 */ EXTERN void TclSplitPath(const char *path, void *argcPtr, const char ***argvPtr); /* Slot 244 is reserved */ /* Slot 245 is reserved */ /* Slot 246 is reserved */ /* Slot 247 is reserved */ /* 248 */ EXTERN int Tcl_TraceVar2(Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *proc, void *clientData); /* 249 */ EXTERN char * Tcl_TranslateFileName(Tcl_Interp *interp, const char *name, Tcl_DString *bufferPtr); /* 250 */ EXTERN Tcl_Size Tcl_Ungets(Tcl_Channel chan, const char *str, Tcl_Size len, int atHead); /* 251 */ EXTERN void Tcl_UnlinkVar(Tcl_Interp *interp, const char *varName); /* 252 */ EXTERN int Tcl_UnregisterChannel(Tcl_Interp *interp, Tcl_Channel chan); /* Slot 253 is reserved */ /* 254 */ EXTERN int Tcl_UnsetVar2(Tcl_Interp *interp, const char *part1, const char *part2, int flags); /* Slot 255 is reserved */ /* 256 */ EXTERN void Tcl_UntraceVar2(Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *proc, void *clientData); /* 257 */ EXTERN void Tcl_UpdateLinkedVar(Tcl_Interp *interp, const char *varName); /* Slot 258 is reserved */ /* 259 */ EXTERN int Tcl_UpVar2(Tcl_Interp *interp, const char *frameName, const char *part1, const char *part2, const char *localName, int flags); /* 260 */ EXTERN int Tcl_VarEval(Tcl_Interp *interp, ...); /* Slot 261 is reserved */ /* 262 */ EXTERN void * Tcl_VarTraceInfo2(Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *procPtr, void *prevClientData); /* 263 */ EXTERN Tcl_Size Tcl_Write(Tcl_Channel chan, const char *s, Tcl_Size slen); /* 264 */ EXTERN void Tcl_WrongNumArgs(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], const char *message); /* 265 */ EXTERN int Tcl_DumpActiveMemory(const char *fileName); /* 266 */ EXTERN void Tcl_ValidateAllMemory(const char *file, int line); /* Slot 267 is reserved */ /* Slot 268 is reserved */ /* 269 */ EXTERN char * Tcl_HashStats(Tcl_HashTable *tablePtr); /* 270 */ EXTERN const char * Tcl_ParseVar(Tcl_Interp *interp, const char *start, const char **termPtr); /* Slot 271 is reserved */ /* 272 */ EXTERN const char * Tcl_PkgPresentEx(Tcl_Interp *interp, const char *name, const char *version, int exact, void *clientDataPtr); /* Slot 273 is reserved */ /* Slot 274 is reserved */ /* Slot 275 is reserved */ /* Slot 276 is reserved */ /* 277 */ EXTERN Tcl_Pid Tcl_WaitPid(Tcl_Pid pid, int *statPtr, int options); /* Slot 278 is reserved */ /* 279 */ EXTERN void Tcl_GetVersion(int *major, int *minor, int *patchLevel, int *type); /* 280 */ EXTERN void Tcl_InitMemory(Tcl_Interp *interp); /* 281 */ EXTERN Tcl_Channel Tcl_StackChannel(Tcl_Interp *interp, const Tcl_ChannelType *typePtr, void *instanceData, int mask, Tcl_Channel prevChan); /* 282 */ EXTERN int Tcl_UnstackChannel(Tcl_Interp *interp, Tcl_Channel chan); /* 283 */ EXTERN Tcl_Channel Tcl_GetStackedChannel(Tcl_Channel chan); /* 284 */ EXTERN void Tcl_SetMainLoop(Tcl_MainLoopProc *proc); /* 285 */ EXTERN int Tcl_GetAliasObj(Tcl_Interp *interp, const char *childCmd, Tcl_Interp **targetInterpPtr, const char **targetCmdPtr, Tcl_Size *objcPtr, Tcl_Obj ***objvPtr); /* 286 */ EXTERN void Tcl_AppendObjToObj(Tcl_Obj *objPtr, Tcl_Obj *appendObjPtr); /* 287 */ EXTERN Tcl_Encoding Tcl_CreateEncoding(const Tcl_EncodingType *typePtr); /* 288 */ EXTERN void Tcl_CreateThreadExitHandler(Tcl_ExitProc *proc, void *clientData); /* 289 */ EXTERN void Tcl_DeleteThreadExitHandler(Tcl_ExitProc *proc, void *clientData); /* Slot 290 is reserved */ /* 291 */ EXTERN int Tcl_EvalEx(Tcl_Interp *interp, const char *script, Tcl_Size numBytes, int flags); /* 292 */ EXTERN int Tcl_EvalObjv(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags); /* 293 */ EXTERN int Tcl_EvalObjEx(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 294 */ EXTERN TCL_NORETURN void Tcl_ExitThread(int status); /* 295 */ EXTERN int Tcl_ExternalToUtf(Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, Tcl_Size dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr); /* 296 */ EXTERN char * Tcl_ExternalToUtfDString(Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, Tcl_DString *dsPtr); /* 297 */ EXTERN void Tcl_FinalizeThread(void); /* 298 */ EXTERN void Tcl_FinalizeNotifier(void *clientData); /* 299 */ EXTERN void Tcl_FreeEncoding(Tcl_Encoding encoding); /* 300 */ EXTERN Tcl_ThreadId Tcl_GetCurrentThread(void); /* 301 */ EXTERN Tcl_Encoding Tcl_GetEncoding(Tcl_Interp *interp, const char *name); /* 302 */ EXTERN const char * Tcl_GetEncodingName(Tcl_Encoding encoding); /* 303 */ EXTERN void Tcl_GetEncodingNames(Tcl_Interp *interp); /* 304 */ EXTERN int Tcl_GetIndexFromObjStruct(Tcl_Interp *interp, Tcl_Obj *objPtr, const void *tablePtr, Tcl_Size offset, const char *msg, int flags, void *indexPtr); /* 305 */ EXTERN void * Tcl_GetThreadData(Tcl_ThreadDataKey *keyPtr, Tcl_Size size); /* 306 */ EXTERN Tcl_Obj * Tcl_GetVar2Ex(Tcl_Interp *interp, const char *part1, const char *part2, int flags); /* 307 */ EXTERN void * Tcl_InitNotifier(void); /* 308 */ EXTERN void Tcl_MutexLock(Tcl_Mutex *mutexPtr); /* 309 */ EXTERN void Tcl_MutexUnlock(Tcl_Mutex *mutexPtr); /* 310 */ EXTERN void Tcl_ConditionNotify(Tcl_Condition *condPtr); /* 311 */ EXTERN void Tcl_ConditionWait(Tcl_Condition *condPtr, Tcl_Mutex *mutexPtr, const Tcl_Time *timePtr); /* 312 */ EXTERN Tcl_Size TclNumUtfChars(const char *src, Tcl_Size length); /* 313 */ EXTERN Tcl_Size Tcl_ReadChars(Tcl_Channel channel, Tcl_Obj *objPtr, Tcl_Size charsToRead, int appendFlag); /* Slot 314 is reserved */ /* Slot 315 is reserved */ /* 316 */ EXTERN int Tcl_SetSystemEncoding(Tcl_Interp *interp, const char *name); /* 317 */ EXTERN Tcl_Obj * Tcl_SetVar2Ex(Tcl_Interp *interp, const char *part1, const char *part2, Tcl_Obj *newValuePtr, int flags); /* 318 */ EXTERN void Tcl_ThreadAlert(Tcl_ThreadId threadId); /* 319 */ EXTERN void Tcl_ThreadQueueEvent(Tcl_ThreadId threadId, Tcl_Event *evPtr, int position); /* 320 */ EXTERN int Tcl_UniCharAtIndex(const char *src, Tcl_Size index); /* 321 */ EXTERN int Tcl_UniCharToLower(int ch); /* 322 */ EXTERN int Tcl_UniCharToTitle(int ch); /* 323 */ EXTERN int Tcl_UniCharToUpper(int ch); /* 324 */ EXTERN Tcl_Size Tcl_UniCharToUtf(int ch, char *buf); /* 325 */ EXTERN const char * TclUtfAtIndex(const char *src, Tcl_Size index); /* 326 */ EXTERN int TclUtfCharComplete(const char *src, Tcl_Size length); /* 327 */ EXTERN Tcl_Size Tcl_UtfBackslash(const char *src, int *readPtr, char *dst); /* 328 */ EXTERN const char * Tcl_UtfFindFirst(const char *src, int ch); /* 329 */ EXTERN const char * Tcl_UtfFindLast(const char *src, int ch); /* 330 */ EXTERN const char * TclUtfNext(const char *src); /* 331 */ EXTERN const char * TclUtfPrev(const char *src, const char *start); /* 332 */ EXTERN int Tcl_UtfToExternal(Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, Tcl_Size dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr); /* 333 */ EXTERN char * Tcl_UtfToExternalDString(Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, Tcl_DString *dsPtr); /* 334 */ EXTERN Tcl_Size Tcl_UtfToLower(char *src); /* 335 */ EXTERN Tcl_Size Tcl_UtfToTitle(char *src); /* 336 */ EXTERN Tcl_Size Tcl_UtfToChar16(const char *src, unsigned short *chPtr); /* 337 */ EXTERN Tcl_Size Tcl_UtfToUpper(char *src); /* 338 */ EXTERN Tcl_Size Tcl_WriteChars(Tcl_Channel chan, const char *src, Tcl_Size srcLen); /* 339 */ EXTERN Tcl_Size Tcl_WriteObj(Tcl_Channel chan, Tcl_Obj *objPtr); /* 340 */ EXTERN char * Tcl_GetString(Tcl_Obj *objPtr); /* Slot 341 is reserved */ /* Slot 342 is reserved */ /* 343 */ EXTERN void Tcl_AlertNotifier(void *clientData); /* 344 */ EXTERN void Tcl_ServiceModeHook(int mode); /* 345 */ EXTERN int Tcl_UniCharIsAlnum(int ch); /* 346 */ EXTERN int Tcl_UniCharIsAlpha(int ch); /* 347 */ EXTERN int Tcl_UniCharIsDigit(int ch); /* 348 */ EXTERN int Tcl_UniCharIsLower(int ch); /* 349 */ EXTERN int Tcl_UniCharIsSpace(int ch); /* 350 */ EXTERN int Tcl_UniCharIsUpper(int ch); /* 351 */ EXTERN int Tcl_UniCharIsWordChar(int ch); /* 352 */ EXTERN Tcl_Size Tcl_Char16Len(const unsigned short *uniStr); /* Slot 353 is reserved */ /* 354 */ EXTERN char * Tcl_Char16ToUtfDString(const unsigned short *uniStr, Tcl_Size uniLength, Tcl_DString *dsPtr); /* 355 */ EXTERN unsigned short * Tcl_UtfToChar16DString(const char *src, Tcl_Size length, Tcl_DString *dsPtr); /* 356 */ EXTERN Tcl_RegExp Tcl_GetRegExpFromObj(Tcl_Interp *interp, Tcl_Obj *patObj, int flags); /* Slot 357 is reserved */ /* 358 */ EXTERN void Tcl_FreeParse(Tcl_Parse *parsePtr); /* 359 */ EXTERN void Tcl_LogCommandInfo(Tcl_Interp *interp, const char *script, const char *command, Tcl_Size length); /* 360 */ EXTERN int Tcl_ParseBraces(Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr, int append, const char **termPtr); /* 361 */ EXTERN int Tcl_ParseCommand(Tcl_Interp *interp, const char *start, Tcl_Size numBytes, int nested, Tcl_Parse *parsePtr); /* 362 */ EXTERN int Tcl_ParseExpr(Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr); /* 363 */ EXTERN int Tcl_ParseQuotedString(Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr, int append, const char **termPtr); /* 364 */ EXTERN int Tcl_ParseVarName(Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr, int append); /* 365 */ EXTERN char * Tcl_GetCwd(Tcl_Interp *interp, Tcl_DString *cwdPtr); /* 366 */ EXTERN int Tcl_Chdir(const char *dirName); /* 367 */ EXTERN int Tcl_Access(const char *path, int mode); /* 368 */ EXTERN int Tcl_Stat(const char *path, struct stat *bufPtr); /* 369 */ EXTERN int TclUtfNcmp(const char *s1, const char *s2, size_t n); /* 370 */ EXTERN int TclUtfNcasecmp(const char *s1, const char *s2, size_t n); /* 371 */ EXTERN int Tcl_StringCaseMatch(const char *str, const char *pattern, int nocase); /* 372 */ EXTERN int Tcl_UniCharIsControl(int ch); /* 373 */ EXTERN int Tcl_UniCharIsGraph(int ch); /* 374 */ EXTERN int Tcl_UniCharIsPrint(int ch); /* 375 */ EXTERN int Tcl_UniCharIsPunct(int ch); /* 376 */ EXTERN int Tcl_RegExpExecObj(Tcl_Interp *interp, Tcl_RegExp regexp, Tcl_Obj *textObj, Tcl_Size offset, Tcl_Size nmatches, int flags); /* 377 */ EXTERN void Tcl_RegExpGetInfo(Tcl_RegExp regexp, Tcl_RegExpInfo *infoPtr); /* 378 */ EXTERN Tcl_Obj * Tcl_NewUnicodeObj(const Tcl_UniChar *unicode, Tcl_Size numChars); /* 379 */ EXTERN void Tcl_SetUnicodeObj(Tcl_Obj *objPtr, const Tcl_UniChar *unicode, Tcl_Size numChars); /* 380 */ EXTERN Tcl_Size TclGetCharLength(Tcl_Obj *objPtr); /* 381 */ EXTERN int TclGetUniChar(Tcl_Obj *objPtr, Tcl_Size index); /* Slot 382 is reserved */ /* 383 */ EXTERN Tcl_Obj * TclGetRange(Tcl_Obj *objPtr, Tcl_Size first, Tcl_Size last); /* 384 */ EXTERN void Tcl_AppendUnicodeToObj(Tcl_Obj *objPtr, const Tcl_UniChar *unicode, Tcl_Size length); /* 385 */ EXTERN int Tcl_RegExpMatchObj(Tcl_Interp *interp, Tcl_Obj *textObj, Tcl_Obj *patternObj); /* 386 */ EXTERN void Tcl_SetNotifier( const Tcl_NotifierProcs *notifierProcPtr); /* 387 */ EXTERN Tcl_Mutex * Tcl_GetAllocMutex(void); /* 388 */ EXTERN int Tcl_GetChannelNames(Tcl_Interp *interp); /* 389 */ EXTERN int Tcl_GetChannelNamesEx(Tcl_Interp *interp, const char *pattern); /* 390 */ EXTERN int Tcl_ProcObjCmd(void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[]); /* 391 */ EXTERN void Tcl_ConditionFinalize(Tcl_Condition *condPtr); /* 392 */ EXTERN void Tcl_MutexFinalize(Tcl_Mutex *mutex); /* 393 */ EXTERN int Tcl_CreateThread(Tcl_ThreadId *idPtr, Tcl_ThreadCreateProc *proc, void *clientData, TCL_HASH_TYPE stackSize, int flags); /* 394 */ EXTERN Tcl_Size Tcl_ReadRaw(Tcl_Channel chan, char *dst, Tcl_Size bytesToRead); /* 395 */ EXTERN Tcl_Size Tcl_WriteRaw(Tcl_Channel chan, const char *src, Tcl_Size srcLen); /* 396 */ EXTERN Tcl_Channel Tcl_GetTopChannel(Tcl_Channel chan); /* 397 */ EXTERN int Tcl_ChannelBuffered(Tcl_Channel chan); /* 398 */ EXTERN const char * Tcl_ChannelName(const Tcl_ChannelType *chanTypePtr); /* 399 */ EXTERN Tcl_ChannelTypeVersion Tcl_ChannelVersion( const Tcl_ChannelType *chanTypePtr); /* 400 */ EXTERN Tcl_DriverBlockModeProc * Tcl_ChannelBlockModeProc( const Tcl_ChannelType *chanTypePtr); /* Slot 401 is reserved */ /* 402 */ EXTERN Tcl_DriverClose2Proc * Tcl_ChannelClose2Proc( const Tcl_ChannelType *chanTypePtr); /* 403 */ EXTERN Tcl_DriverInputProc * Tcl_ChannelInputProc( const Tcl_ChannelType *chanTypePtr); /* 404 */ EXTERN Tcl_DriverOutputProc * Tcl_ChannelOutputProc( const Tcl_ChannelType *chanTypePtr); /* Slot 405 is reserved */ /* 406 */ EXTERN Tcl_DriverSetOptionProc * Tcl_ChannelSetOptionProc( const Tcl_ChannelType *chanTypePtr); /* 407 */ EXTERN Tcl_DriverGetOptionProc * Tcl_ChannelGetOptionProc( const Tcl_ChannelType *chanTypePtr); /* 408 */ EXTERN Tcl_DriverWatchProc * Tcl_ChannelWatchProc( const Tcl_ChannelType *chanTypePtr); /* 409 */ EXTERN Tcl_DriverGetHandleProc * Tcl_ChannelGetHandleProc( const Tcl_ChannelType *chanTypePtr); /* 410 */ EXTERN Tcl_DriverFlushProc * Tcl_ChannelFlushProc( const Tcl_ChannelType *chanTypePtr); /* 411 */ EXTERN Tcl_DriverHandlerProc * Tcl_ChannelHandlerProc( const Tcl_ChannelType *chanTypePtr); /* 412 */ EXTERN int Tcl_JoinThread(Tcl_ThreadId threadId, int *result); /* 413 */ EXTERN int Tcl_IsChannelShared(Tcl_Channel channel); /* 414 */ EXTERN int Tcl_IsChannelRegistered(Tcl_Interp *interp, Tcl_Channel channel); /* 415 */ EXTERN void Tcl_CutChannel(Tcl_Channel channel); /* 416 */ EXTERN void Tcl_SpliceChannel(Tcl_Channel channel); /* 417 */ EXTERN void Tcl_ClearChannelHandlers(Tcl_Channel channel); /* 418 */ EXTERN int Tcl_IsChannelExisting(const char *channelName); /* Slot 419 is reserved */ /* Slot 420 is reserved */ /* Slot 421 is reserved */ /* Slot 422 is reserved */ /* 423 */ EXTERN void Tcl_InitCustomHashTable(Tcl_HashTable *tablePtr, int keyType, const Tcl_HashKeyType *typePtr); /* 424 */ EXTERN void Tcl_InitObjHashTable(Tcl_HashTable *tablePtr); /* 425 */ EXTERN void * Tcl_CommandTraceInfo(Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *procPtr, void *prevClientData); /* 426 */ EXTERN int Tcl_TraceCommand(Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *proc, void *clientData); /* 427 */ EXTERN void Tcl_UntraceCommand(Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *proc, void *clientData); /* 428 */ EXTERN void * Tcl_AttemptAlloc(TCL_HASH_TYPE size); /* 429 */ EXTERN void * Tcl_AttemptDbCkalloc(TCL_HASH_TYPE size, const char *file, int line); /* 430 */ EXTERN void * Tcl_AttemptRealloc(void *ptr, TCL_HASH_TYPE size); /* 431 */ EXTERN void * Tcl_AttemptDbCkrealloc(void *ptr, TCL_HASH_TYPE size, const char *file, int line); /* 432 */ EXTERN int Tcl_AttemptSetObjLength(Tcl_Obj *objPtr, Tcl_Size length); /* 433 */ EXTERN Tcl_ThreadId Tcl_GetChannelThread(Tcl_Channel channel); /* 434 */ EXTERN Tcl_UniChar * TclGetUnicodeFromObj(Tcl_Obj *objPtr, void *lengthPtr); /* Slot 435 is reserved */ /* Slot 436 is reserved */ /* 437 */ EXTERN Tcl_Obj * Tcl_SubstObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 438 */ EXTERN int Tcl_DetachChannel(Tcl_Interp *interp, Tcl_Channel channel); /* 439 */ EXTERN int Tcl_IsStandardChannel(Tcl_Channel channel); /* 440 */ EXTERN int Tcl_FSCopyFile(Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); /* 441 */ EXTERN int Tcl_FSCopyDirectory(Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr); /* 442 */ EXTERN int Tcl_FSCreateDirectory(Tcl_Obj *pathPtr); /* 443 */ EXTERN int Tcl_FSDeleteFile(Tcl_Obj *pathPtr); /* 444 */ EXTERN int Tcl_FSLoadFile(Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *sym1, const char *sym2, Tcl_LibraryInitProc **proc1Ptr, Tcl_LibraryInitProc **proc2Ptr, Tcl_LoadHandle *handlePtr, Tcl_FSUnloadFileProc **unloadProcPtr); /* 445 */ EXTERN int Tcl_FSMatchInDirectory(Tcl_Interp *interp, Tcl_Obj *result, Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types); /* 446 */ EXTERN Tcl_Obj * Tcl_FSLink(Tcl_Obj *pathPtr, Tcl_Obj *toPtr, int linkAction); /* 447 */ EXTERN int Tcl_FSRemoveDirectory(Tcl_Obj *pathPtr, int recursive, Tcl_Obj **errorPtr); /* 448 */ EXTERN int Tcl_FSRenameFile(Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); /* 449 */ EXTERN int Tcl_FSLstat(Tcl_Obj *pathPtr, Tcl_StatBuf *buf); /* 450 */ EXTERN int Tcl_FSUtime(Tcl_Obj *pathPtr, struct utimbuf *tval); /* 451 */ EXTERN int Tcl_FSFileAttrsGet(Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); /* 452 */ EXTERN int Tcl_FSFileAttrsSet(Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj *objPtr); /* 453 */ EXTERN const char *const * Tcl_FSFileAttrStrings(Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); /* 454 */ EXTERN int Tcl_FSStat(Tcl_Obj *pathPtr, Tcl_StatBuf *buf); /* 455 */ EXTERN int Tcl_FSAccess(Tcl_Obj *pathPtr, int mode); /* 456 */ EXTERN Tcl_Channel Tcl_FSOpenFileChannel(Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *modeString, int permissions); /* 457 */ EXTERN Tcl_Obj * Tcl_FSGetCwd(Tcl_Interp *interp); /* 458 */ EXTERN int Tcl_FSChdir(Tcl_Obj *pathPtr); /* 459 */ EXTERN int Tcl_FSConvertToPathType(Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 460 */ EXTERN Tcl_Obj * Tcl_FSJoinPath(Tcl_Obj *listObj, Tcl_Size elements); /* 461 */ EXTERN Tcl_Obj * TclFSSplitPath(Tcl_Obj *pathPtr, void *lenPtr); /* 462 */ EXTERN int Tcl_FSEqualPaths(Tcl_Obj *firstPtr, Tcl_Obj *secondPtr); /* 463 */ EXTERN Tcl_Obj * Tcl_FSGetNormalizedPath(Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 464 */ EXTERN Tcl_Obj * Tcl_FSJoinToPath(Tcl_Obj *pathPtr, Tcl_Size objc, Tcl_Obj *const objv[]); /* 465 */ EXTERN void * Tcl_FSGetInternalRep(Tcl_Obj *pathPtr, const Tcl_Filesystem *fsPtr); /* 466 */ EXTERN Tcl_Obj * Tcl_FSGetTranslatedPath(Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 467 */ EXTERN int Tcl_FSEvalFile(Tcl_Interp *interp, Tcl_Obj *fileName); /* 468 */ EXTERN Tcl_Obj * Tcl_FSNewNativePath( const Tcl_Filesystem *fromFilesystem, void *clientData); /* 469 */ EXTERN const void * Tcl_FSGetNativePath(Tcl_Obj *pathPtr); /* 470 */ EXTERN Tcl_Obj * Tcl_FSFileSystemInfo(Tcl_Obj *pathPtr); /* 471 */ EXTERN Tcl_Obj * Tcl_FSPathSeparator(Tcl_Obj *pathPtr); /* 472 */ EXTERN Tcl_Obj * Tcl_FSListVolumes(void); /* 473 */ EXTERN int Tcl_FSRegister(void *clientData, const Tcl_Filesystem *fsPtr); /* 474 */ EXTERN int Tcl_FSUnregister(const Tcl_Filesystem *fsPtr); /* 475 */ EXTERN void * Tcl_FSData(const Tcl_Filesystem *fsPtr); /* 476 */ EXTERN const char * Tcl_FSGetTranslatedStringPath(Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 477 */ EXTERN const Tcl_Filesystem * Tcl_FSGetFileSystemForPath(Tcl_Obj *pathPtr); /* 478 */ EXTERN Tcl_PathType Tcl_FSGetPathType(Tcl_Obj *pathPtr); /* 479 */ EXTERN int Tcl_OutputBuffered(Tcl_Channel chan); /* 480 */ EXTERN void Tcl_FSMountsChanged(const Tcl_Filesystem *fsPtr); /* 481 */ EXTERN int Tcl_EvalTokensStandard(Tcl_Interp *interp, Tcl_Token *tokenPtr, Tcl_Size count); /* 482 */ EXTERN void Tcl_GetTime(Tcl_Time *timeBuf); /* 483 */ EXTERN Tcl_Trace Tcl_CreateObjTrace(Tcl_Interp *interp, Tcl_Size level, int flags, Tcl_CmdObjTraceProc *objProc, void *clientData, Tcl_CmdObjTraceDeleteProc *delProc); /* 484 */ EXTERN int Tcl_GetCommandInfoFromToken(Tcl_Command token, Tcl_CmdInfo *infoPtr); /* 485 */ EXTERN int Tcl_SetCommandInfoFromToken(Tcl_Command token, const Tcl_CmdInfo *infoPtr); /* 486 */ EXTERN Tcl_Obj * Tcl_DbNewWideIntObj(Tcl_WideInt wideValue, const char *file, int line); /* 487 */ EXTERN int Tcl_GetWideIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_WideInt *widePtr); /* 488 */ EXTERN Tcl_Obj * Tcl_NewWideIntObj(Tcl_WideInt wideValue); /* 489 */ EXTERN void Tcl_SetWideIntObj(Tcl_Obj *objPtr, Tcl_WideInt wideValue); /* 490 */ EXTERN Tcl_StatBuf * Tcl_AllocStatBuf(void); /* 491 */ EXTERN long long Tcl_Seek(Tcl_Channel chan, long long offset, int mode); /* 492 */ EXTERN long long Tcl_Tell(Tcl_Channel chan); /* 493 */ EXTERN Tcl_DriverWideSeekProc * Tcl_ChannelWideSeekProc( const Tcl_ChannelType *chanTypePtr); /* 494 */ EXTERN int Tcl_DictObjPut(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr, Tcl_Obj *valuePtr); /* 495 */ EXTERN int Tcl_DictObjGet(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr, Tcl_Obj **valuePtrPtr); /* 496 */ EXTERN int Tcl_DictObjRemove(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr); /* 497 */ EXTERN int TclDictObjSize(Tcl_Interp *interp, Tcl_Obj *dictPtr, void *sizePtr); /* 498 */ EXTERN int Tcl_DictObjFirst(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_DictSearch *searchPtr, Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr, int *donePtr); /* 499 */ EXTERN void Tcl_DictObjNext(Tcl_DictSearch *searchPtr, Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr, int *donePtr); /* 500 */ EXTERN void Tcl_DictObjDone(Tcl_DictSearch *searchPtr); /* 501 */ EXTERN int Tcl_DictObjPutKeyList(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size keyc, Tcl_Obj *const *keyv, Tcl_Obj *valuePtr); /* 502 */ EXTERN int Tcl_DictObjRemoveKeyList(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size keyc, Tcl_Obj *const *keyv); /* 503 */ EXTERN Tcl_Obj * Tcl_NewDictObj(void); /* 504 */ EXTERN Tcl_Obj * Tcl_DbNewDictObj(const char *file, int line); /* 505 */ EXTERN void Tcl_RegisterConfig(Tcl_Interp *interp, const char *pkgName, const Tcl_Config *configuration, const char *valEncoding); /* 506 */ EXTERN Tcl_Namespace * Tcl_CreateNamespace(Tcl_Interp *interp, const char *name, void *clientData, Tcl_NamespaceDeleteProc *deleteProc); /* 507 */ EXTERN void Tcl_DeleteNamespace(Tcl_Namespace *nsPtr); /* 508 */ EXTERN int Tcl_AppendExportList(Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *objPtr); /* 509 */ EXTERN int Tcl_Export(Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern, int resetListFirst); /* 510 */ EXTERN int Tcl_Import(Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern, int allowOverwrite); /* 511 */ EXTERN int Tcl_ForgetImport(Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern); /* 512 */ EXTERN Tcl_Namespace * Tcl_GetCurrentNamespace(Tcl_Interp *interp); /* 513 */ EXTERN Tcl_Namespace * Tcl_GetGlobalNamespace(Tcl_Interp *interp); /* 514 */ EXTERN Tcl_Namespace * Tcl_FindNamespace(Tcl_Interp *interp, const char *name, Tcl_Namespace *contextNsPtr, int flags); /* 515 */ EXTERN Tcl_Command Tcl_FindCommand(Tcl_Interp *interp, const char *name, Tcl_Namespace *contextNsPtr, int flags); /* 516 */ EXTERN Tcl_Command Tcl_GetCommandFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr); /* 517 */ EXTERN void Tcl_GetCommandFullName(Tcl_Interp *interp, Tcl_Command command, Tcl_Obj *objPtr); /* 518 */ EXTERN int Tcl_FSEvalFileEx(Tcl_Interp *interp, Tcl_Obj *fileName, const char *encodingName); /* Slot 519 is reserved */ /* 520 */ EXTERN void Tcl_LimitAddHandler(Tcl_Interp *interp, int type, Tcl_LimitHandlerProc *handlerProc, void *clientData, Tcl_LimitHandlerDeleteProc *deleteProc); /* 521 */ EXTERN void Tcl_LimitRemoveHandler(Tcl_Interp *interp, int type, Tcl_LimitHandlerProc *handlerProc, void *clientData); /* 522 */ EXTERN int Tcl_LimitReady(Tcl_Interp *interp); /* 523 */ EXTERN int Tcl_LimitCheck(Tcl_Interp *interp); /* 524 */ EXTERN int Tcl_LimitExceeded(Tcl_Interp *interp); /* 525 */ EXTERN void Tcl_LimitSetCommands(Tcl_Interp *interp, Tcl_Size commandLimit); /* 526 */ EXTERN void Tcl_LimitSetTime(Tcl_Interp *interp, Tcl_Time *timeLimitPtr); /* 527 */ EXTERN void Tcl_LimitSetGranularity(Tcl_Interp *interp, int type, int granularity); /* 528 */ EXTERN int Tcl_LimitTypeEnabled(Tcl_Interp *interp, int type); /* 529 */ EXTERN int Tcl_LimitTypeExceeded(Tcl_Interp *interp, int type); /* 530 */ EXTERN void Tcl_LimitTypeSet(Tcl_Interp *interp, int type); /* 531 */ EXTERN void Tcl_LimitTypeReset(Tcl_Interp *interp, int type); /* 532 */ EXTERN int Tcl_LimitGetCommands(Tcl_Interp *interp); /* 533 */ EXTERN void Tcl_LimitGetTime(Tcl_Interp *interp, Tcl_Time *timeLimitPtr); /* 534 */ EXTERN int Tcl_LimitGetGranularity(Tcl_Interp *interp, int type); /* 535 */ EXTERN Tcl_InterpState Tcl_SaveInterpState(Tcl_Interp *interp, int status); /* 536 */ EXTERN int Tcl_RestoreInterpState(Tcl_Interp *interp, Tcl_InterpState state); /* 537 */ EXTERN void Tcl_DiscardInterpState(Tcl_InterpState state); /* 538 */ EXTERN int Tcl_SetReturnOptions(Tcl_Interp *interp, Tcl_Obj *options); /* 539 */ EXTERN Tcl_Obj * Tcl_GetReturnOptions(Tcl_Interp *interp, int result); /* 540 */ EXTERN int Tcl_IsEnsemble(Tcl_Command token); /* 541 */ EXTERN Tcl_Command Tcl_CreateEnsemble(Tcl_Interp *interp, const char *name, Tcl_Namespace *namespacePtr, int flags); /* 542 */ EXTERN Tcl_Command Tcl_FindEnsemble(Tcl_Interp *interp, Tcl_Obj *cmdNameObj, int flags); /* 543 */ EXTERN int Tcl_SetEnsembleSubcommandList(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *subcmdList); /* 544 */ EXTERN int Tcl_SetEnsembleMappingDict(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *mapDict); /* 545 */ EXTERN int Tcl_SetEnsembleUnknownHandler(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *unknownList); /* 546 */ EXTERN int Tcl_SetEnsembleFlags(Tcl_Interp *interp, Tcl_Command token, int flags); /* 547 */ EXTERN int Tcl_GetEnsembleSubcommandList(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **subcmdListPtr); /* 548 */ EXTERN int Tcl_GetEnsembleMappingDict(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **mapDictPtr); /* 549 */ EXTERN int Tcl_GetEnsembleUnknownHandler(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **unknownListPtr); /* 550 */ EXTERN int Tcl_GetEnsembleFlags(Tcl_Interp *interp, Tcl_Command token, int *flagsPtr); /* 551 */ EXTERN int Tcl_GetEnsembleNamespace(Tcl_Interp *interp, Tcl_Command token, Tcl_Namespace **namespacePtrPtr); /* 552 */ EXTERN void Tcl_SetTimeProc(Tcl_GetTimeProc *getProc, Tcl_ScaleTimeProc *scaleProc, void *clientData); /* 553 */ EXTERN void Tcl_QueryTimeProc(Tcl_GetTimeProc **getProc, Tcl_ScaleTimeProc **scaleProc, void **clientData); /* 554 */ EXTERN Tcl_DriverThreadActionProc * Tcl_ChannelThreadActionProc( const Tcl_ChannelType *chanTypePtr); /* 555 */ EXTERN Tcl_Obj * Tcl_NewBignumObj(void *value); /* 556 */ EXTERN Tcl_Obj * Tcl_DbNewBignumObj(void *value, const char *file, int line); /* 557 */ EXTERN void Tcl_SetBignumObj(Tcl_Obj *obj, void *value); /* 558 */ EXTERN int Tcl_GetBignumFromObj(Tcl_Interp *interp, Tcl_Obj *obj, void *value); /* 559 */ EXTERN int Tcl_TakeBignumFromObj(Tcl_Interp *interp, Tcl_Obj *obj, void *value); /* 560 */ EXTERN int Tcl_TruncateChannel(Tcl_Channel chan, long long length); /* 561 */ EXTERN Tcl_DriverTruncateProc * Tcl_ChannelTruncateProc( const Tcl_ChannelType *chanTypePtr); /* 562 */ EXTERN void Tcl_SetChannelErrorInterp(Tcl_Interp *interp, Tcl_Obj *msg); /* 563 */ EXTERN void Tcl_GetChannelErrorInterp(Tcl_Interp *interp, Tcl_Obj **msg); /* 564 */ EXTERN void Tcl_SetChannelError(Tcl_Channel chan, Tcl_Obj *msg); /* 565 */ EXTERN void Tcl_GetChannelError(Tcl_Channel chan, Tcl_Obj **msg); /* 566 */ EXTERN int Tcl_InitBignumFromDouble(Tcl_Interp *interp, double initval, void *toInit); /* 567 */ EXTERN Tcl_Obj * Tcl_GetNamespaceUnknownHandler(Tcl_Interp *interp, Tcl_Namespace *nsPtr); /* 568 */ EXTERN int Tcl_SetNamespaceUnknownHandler(Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *handlerPtr); /* 569 */ EXTERN int Tcl_GetEncodingFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Encoding *encodingPtr); /* 570 */ EXTERN Tcl_Obj * Tcl_GetEncodingSearchPath(void); /* 571 */ EXTERN int Tcl_SetEncodingSearchPath(Tcl_Obj *searchPath); /* 572 */ EXTERN const char * Tcl_GetEncodingNameFromEnvironment( Tcl_DString *bufPtr); /* 573 */ EXTERN int Tcl_PkgRequireProc(Tcl_Interp *interp, const char *name, Tcl_Size objc, Tcl_Obj *const objv[], void *clientDataPtr); /* 574 */ EXTERN void Tcl_AppendObjToErrorInfo(Tcl_Interp *interp, Tcl_Obj *objPtr); /* 575 */ EXTERN void Tcl_AppendLimitedToObj(Tcl_Obj *objPtr, const char *bytes, Tcl_Size length, Tcl_Size limit, const char *ellipsis); /* 576 */ EXTERN Tcl_Obj * Tcl_Format(Tcl_Interp *interp, const char *format, Tcl_Size objc, Tcl_Obj *const objv[]); /* 577 */ EXTERN int Tcl_AppendFormatToObj(Tcl_Interp *interp, Tcl_Obj *objPtr, const char *format, Tcl_Size objc, Tcl_Obj *const objv[]); /* 578 */ EXTERN Tcl_Obj * Tcl_ObjPrintf(const char *format, ...) TCL_FORMAT_PRINTF(1, 2); /* 579 */ EXTERN void Tcl_AppendPrintfToObj(Tcl_Obj *objPtr, const char *format, ...) TCL_FORMAT_PRINTF(2, 3); /* 580 */ EXTERN int Tcl_CancelEval(Tcl_Interp *interp, Tcl_Obj *resultObjPtr, void *clientData, int flags); /* 581 */ EXTERN int Tcl_Canceled(Tcl_Interp *interp, int flags); /* 582 */ EXTERN int Tcl_CreatePipe(Tcl_Interp *interp, Tcl_Channel *rchan, Tcl_Channel *wchan, int flags); /* 583 */ EXTERN Tcl_Command Tcl_NRCreateCommand(Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc *proc, Tcl_ObjCmdProc *nreProc, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 584 */ EXTERN int Tcl_NREvalObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 585 */ EXTERN int Tcl_NREvalObjv(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags); /* 586 */ EXTERN int Tcl_NRCmdSwap(Tcl_Interp *interp, Tcl_Command cmd, Tcl_Size objc, Tcl_Obj *const objv[], int flags); /* 587 */ EXTERN void Tcl_NRAddCallback(Tcl_Interp *interp, Tcl_NRPostProc *postProcPtr, void *data0, void *data1, void *data2, void *data3); /* 588 */ EXTERN int Tcl_NRCallObjProc(Tcl_Interp *interp, Tcl_ObjCmdProc *objProc, void *clientData, Tcl_Size objc, Tcl_Obj *const objv[]); /* 589 */ EXTERN unsigned Tcl_GetFSDeviceFromStat(const Tcl_StatBuf *statPtr); /* 590 */ EXTERN unsigned Tcl_GetFSInodeFromStat(const Tcl_StatBuf *statPtr); /* 591 */ EXTERN unsigned Tcl_GetModeFromStat(const Tcl_StatBuf *statPtr); /* 592 */ EXTERN int Tcl_GetLinkCountFromStat(const Tcl_StatBuf *statPtr); /* 593 */ EXTERN int Tcl_GetUserIdFromStat(const Tcl_StatBuf *statPtr); /* 594 */ EXTERN int Tcl_GetGroupIdFromStat(const Tcl_StatBuf *statPtr); /* 595 */ EXTERN int Tcl_GetDeviceTypeFromStat(const Tcl_StatBuf *statPtr); /* 596 */ EXTERN long long Tcl_GetAccessTimeFromStat(const Tcl_StatBuf *statPtr); /* 597 */ EXTERN long long Tcl_GetModificationTimeFromStat( const Tcl_StatBuf *statPtr); /* 598 */ EXTERN long long Tcl_GetChangeTimeFromStat(const Tcl_StatBuf *statPtr); /* 599 */ EXTERN unsigned long long Tcl_GetSizeFromStat(const Tcl_StatBuf *statPtr); /* 600 */ EXTERN unsigned long long Tcl_GetBlocksFromStat(const Tcl_StatBuf *statPtr); /* 601 */ EXTERN unsigned Tcl_GetBlockSizeFromStat(const Tcl_StatBuf *statPtr); /* 602 */ EXTERN int Tcl_SetEnsembleParameterList(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *paramList); /* 603 */ EXTERN int Tcl_GetEnsembleParameterList(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **paramListPtr); /* 604 */ EXTERN int TclParseArgsObjv(Tcl_Interp *interp, const Tcl_ArgvInfo *argTable, void *objcPtr, Tcl_Obj *const *objv, Tcl_Obj ***remObjv); /* 605 */ EXTERN int Tcl_GetErrorLine(Tcl_Interp *interp); /* 606 */ EXTERN void Tcl_SetErrorLine(Tcl_Interp *interp, int lineNum); /* 607 */ EXTERN void Tcl_TransferResult(Tcl_Interp *sourceInterp, int code, Tcl_Interp *targetInterp); /* 608 */ EXTERN int Tcl_InterpActive(Tcl_Interp *interp); /* 609 */ EXTERN void Tcl_BackgroundException(Tcl_Interp *interp, int code); /* 610 */ EXTERN int Tcl_ZlibDeflate(Tcl_Interp *interp, int format, Tcl_Obj *data, int level, Tcl_Obj *gzipHeaderDictObj); /* 611 */ EXTERN int Tcl_ZlibInflate(Tcl_Interp *interp, int format, Tcl_Obj *data, Tcl_Size buffersize, Tcl_Obj *gzipHeaderDictObj); /* 612 */ EXTERN unsigned int Tcl_ZlibCRC32(unsigned int crc, const unsigned char *buf, Tcl_Size len); /* 613 */ EXTERN unsigned int Tcl_ZlibAdler32(unsigned int adler, const unsigned char *buf, Tcl_Size len); /* 614 */ EXTERN int Tcl_ZlibStreamInit(Tcl_Interp *interp, int mode, int format, int level, Tcl_Obj *dictObj, Tcl_ZlibStream *zshandle); /* 615 */ EXTERN Tcl_Obj * Tcl_ZlibStreamGetCommandName(Tcl_ZlibStream zshandle); /* 616 */ EXTERN int Tcl_ZlibStreamEof(Tcl_ZlibStream zshandle); /* 617 */ EXTERN int Tcl_ZlibStreamChecksum(Tcl_ZlibStream zshandle); /* 618 */ EXTERN int Tcl_ZlibStreamPut(Tcl_ZlibStream zshandle, Tcl_Obj *data, int flush); /* 619 */ EXTERN int Tcl_ZlibStreamGet(Tcl_ZlibStream zshandle, Tcl_Obj *data, Tcl_Size count); /* 620 */ EXTERN int Tcl_ZlibStreamClose(Tcl_ZlibStream zshandle); /* 621 */ EXTERN int Tcl_ZlibStreamReset(Tcl_ZlibStream zshandle); /* 622 */ EXTERN void Tcl_SetStartupScript(Tcl_Obj *path, const char *encoding); /* 623 */ EXTERN Tcl_Obj * Tcl_GetStartupScript(const char **encodingPtr); /* 624 */ EXTERN int Tcl_CloseEx(Tcl_Interp *interp, Tcl_Channel chan, int flags); /* 625 */ EXTERN int Tcl_NRExprObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj *resultPtr); /* 626 */ EXTERN int Tcl_NRSubstObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 627 */ EXTERN int Tcl_LoadFile(Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *const symv[], int flags, void *procPtrs, Tcl_LoadHandle *handlePtr); /* 628 */ EXTERN void * Tcl_FindSymbol(Tcl_Interp *interp, Tcl_LoadHandle handle, const char *symbol); /* 629 */ EXTERN int Tcl_FSUnloadFile(Tcl_Interp *interp, Tcl_LoadHandle handlePtr); /* 630 */ EXTERN void Tcl_ZlibStreamSetCompressionDictionary( Tcl_ZlibStream zhandle, Tcl_Obj *compressionDictionaryObj); /* 631 */ EXTERN Tcl_Channel Tcl_OpenTcpServerEx(Tcl_Interp *interp, const char *service, const char *host, unsigned int flags, int backlog, Tcl_TcpAcceptProc *acceptProc, void *callbackData); /* 632 */ EXTERN int TclZipfs_Mount(Tcl_Interp *interp, const char *zipname, const char *mountPoint, const char *passwd); /* 633 */ EXTERN int TclZipfs_Unmount(Tcl_Interp *interp, const char *mountPoint); /* 634 */ EXTERN Tcl_Obj * TclZipfs_TclLibrary(void); /* 635 */ EXTERN int TclZipfs_MountBuffer(Tcl_Interp *interp, const void *data, size_t datalen, const char *mountPoint, int copy); /* 636 */ EXTERN void Tcl_FreeInternalRep(Tcl_Obj *objPtr); /* 637 */ EXTERN char * Tcl_InitStringRep(Tcl_Obj *objPtr, const char *bytes, TCL_HASH_TYPE numBytes); /* 638 */ EXTERN Tcl_ObjInternalRep * Tcl_FetchInternalRep(Tcl_Obj *objPtr, const Tcl_ObjType *typePtr); /* 639 */ EXTERN void Tcl_StoreInternalRep(Tcl_Obj *objPtr, const Tcl_ObjType *typePtr, const Tcl_ObjInternalRep *irPtr); /* 640 */ EXTERN int Tcl_HasStringRep(Tcl_Obj *objPtr); /* 641 */ EXTERN void Tcl_IncrRefCount(Tcl_Obj *objPtr); /* 642 */ EXTERN void Tcl_DecrRefCount(Tcl_Obj *objPtr); /* 643 */ EXTERN int Tcl_IsShared(Tcl_Obj *objPtr); /* 644 */ EXTERN int Tcl_LinkArray(Tcl_Interp *interp, const char *varName, void *addr, int type, Tcl_Size size); /* 645 */ EXTERN int Tcl_GetIntForIndex(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size endValue, Tcl_Size *indexPtr); /* 646 */ EXTERN Tcl_Size Tcl_UtfToUniChar(const char *src, int *chPtr); /* 647 */ EXTERN char * Tcl_UniCharToUtfDString(const int *uniStr, Tcl_Size uniLength, Tcl_DString *dsPtr); /* 648 */ EXTERN int * Tcl_UtfToUniCharDString(const char *src, Tcl_Size length, Tcl_DString *dsPtr); /* 649 */ EXTERN unsigned char * TclGetBytesFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, void *numBytesPtr); /* 650 */ EXTERN unsigned char * Tcl_GetBytesFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size *numBytesPtr); /* 651 */ EXTERN char * Tcl_GetStringFromObj(Tcl_Obj *objPtr, Tcl_Size *lengthPtr); /* 652 */ EXTERN Tcl_UniChar * Tcl_GetUnicodeFromObj(Tcl_Obj *objPtr, Tcl_Size *lengthPtr); /* 653 */ EXTERN int Tcl_GetSizeIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size *sizePtr); /* 654 */ EXTERN int Tcl_UtfCharComplete(const char *src, Tcl_Size length); /* 655 */ EXTERN const char * Tcl_UtfNext(const char *src); /* 656 */ EXTERN const char * Tcl_UtfPrev(const char *src, const char *start); /* 657 */ EXTERN int Tcl_FSTildeExpand(Tcl_Interp *interp, const char *path, Tcl_DString *dsPtr); /* 658 */ EXTERN int Tcl_ExternalToUtfDStringEx(Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr, Tcl_Size *errorLocationPtr); /* 659 */ EXTERN int Tcl_UtfToExternalDStringEx(Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr, Tcl_Size *errorLocationPtr); /* 660 */ EXTERN int Tcl_AsyncMarkFromSignal(Tcl_AsyncHandler async, int sigNumber); /* 661 */ EXTERN int Tcl_ListObjGetElements(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size *objcPtr, Tcl_Obj ***objvPtr); /* 662 */ EXTERN int Tcl_ListObjLength(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size *lengthPtr); /* 663 */ EXTERN int Tcl_DictObjSize(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size *sizePtr); /* 664 */ EXTERN int Tcl_SplitList(Tcl_Interp *interp, const char *listStr, Tcl_Size *argcPtr, const char ***argvPtr); /* 665 */ EXTERN void Tcl_SplitPath(const char *path, Tcl_Size *argcPtr, const char ***argvPtr); /* 666 */ EXTERN Tcl_Obj * Tcl_FSSplitPath(Tcl_Obj *pathPtr, Tcl_Size *lenPtr); /* 667 */ EXTERN int Tcl_ParseArgsObjv(Tcl_Interp *interp, const Tcl_ArgvInfo *argTable, Tcl_Size *objcPtr, Tcl_Obj *const *objv, Tcl_Obj ***remObjv); /* 668 */ EXTERN Tcl_Size Tcl_UniCharLen(const int *uniStr); /* 669 */ EXTERN Tcl_Size Tcl_NumUtfChars(const char *src, Tcl_Size length); /* 670 */ EXTERN Tcl_Size Tcl_GetCharLength(Tcl_Obj *objPtr); /* 671 */ EXTERN const char * Tcl_UtfAtIndex(const char *src, Tcl_Size index); /* 672 */ EXTERN Tcl_Obj * Tcl_GetRange(Tcl_Obj *objPtr, Tcl_Size first, Tcl_Size last); /* 673 */ EXTERN int Tcl_GetUniChar(Tcl_Obj *objPtr, Tcl_Size index); /* 674 */ EXTERN int Tcl_GetBool(Tcl_Interp *interp, const char *src, int flags, char *charPtr); /* 675 */ EXTERN int Tcl_GetBoolFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags, char *charPtr); /* 676 */ EXTERN Tcl_Command Tcl_CreateObjCommand2(Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc2 *proc2, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 677 */ EXTERN Tcl_Trace Tcl_CreateObjTrace2(Tcl_Interp *interp, Tcl_Size level, int flags, Tcl_CmdObjTraceProc2 *objProc2, void *clientData, Tcl_CmdObjTraceDeleteProc *delProc); /* 678 */ EXTERN Tcl_Command Tcl_NRCreateCommand2(Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc2 *proc, Tcl_ObjCmdProc2 *nreProc2, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 679 */ EXTERN int Tcl_NRCallObjProc2(Tcl_Interp *interp, Tcl_ObjCmdProc2 *objProc2, void *clientData, Tcl_Size objc, Tcl_Obj *const objv[]); /* 680 */ EXTERN int Tcl_GetNumberFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, void **clientDataPtr, int *typePtr); /* 681 */ EXTERN int Tcl_GetNumber(Tcl_Interp *interp, const char *bytes, Tcl_Size numBytes, void **clientDataPtr, int *typePtr); /* 682 */ EXTERN int Tcl_RemoveChannelMode(Tcl_Interp *interp, Tcl_Channel chan, int mode); /* 683 */ EXTERN Tcl_Size Tcl_GetEncodingNulLength(Tcl_Encoding encoding); /* 684 */ EXTERN int Tcl_GetWideUIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_WideUInt *uwidePtr); /* 685 */ EXTERN Tcl_Obj * Tcl_DStringToObj(Tcl_DString *dsPtr); /* 686 */ EXTERN int Tcl_UtfNcmp(const char *s1, const char *s2, size_t n); /* 687 */ EXTERN int Tcl_UtfNcasecmp(const char *s1, const char *s2, size_t n); /* 688 */ EXTERN Tcl_Obj * Tcl_NewWideUIntObj(Tcl_WideUInt wideValue); /* 689 */ EXTERN void Tcl_SetWideUIntObj(Tcl_Obj *objPtr, Tcl_WideUInt uwideValue); /* 690 */ EXTERN void TclUnusedStubEntry(void); typedef struct { const struct TclPlatStubs *tclPlatStubs; const struct TclIntStubs *tclIntStubs; const struct TclIntPlatStubs *tclIntPlatStubs; } TclStubHooks; typedef struct TclStubs { int magic; const TclStubHooks *hooks; int (*tcl_PkgProvideEx) (Tcl_Interp *interp, const char *name, const char *version, const void *clientData); /* 0 */ const char * (*tcl_PkgRequireEx) (Tcl_Interp *interp, const char *name, const char *version, int exact, void *clientDataPtr); /* 1 */ TCL_NORETURN1 void (*tcl_Panic) (const char *format, ...) TCL_FORMAT_PRINTF(1, 2); /* 2 */ void * (*tcl_Alloc) (TCL_HASH_TYPE size); /* 3 */ void (*tcl_Free) (void *ptr); /* 4 */ void * (*tcl_Realloc) (void *ptr, TCL_HASH_TYPE size); /* 5 */ void * (*tcl_DbCkalloc) (TCL_HASH_TYPE size, const char *file, int line); /* 6 */ void (*tcl_DbCkfree) (void *ptr, const char *file, int line); /* 7 */ void * (*tcl_DbCkrealloc) (void *ptr, TCL_HASH_TYPE size, const char *file, int line); /* 8 */ void (*tcl_CreateFileHandler) (int fd, int mask, Tcl_FileProc *proc, void *clientData); /* 9 */ void (*tcl_DeleteFileHandler) (int fd); /* 10 */ void (*tcl_SetTimer) (const Tcl_Time *timePtr); /* 11 */ void (*tcl_Sleep) (int ms); /* 12 */ int (*tcl_WaitForEvent) (const Tcl_Time *timePtr); /* 13 */ int (*tcl_AppendAllObjTypes) (Tcl_Interp *interp, Tcl_Obj *objPtr); /* 14 */ void (*tcl_AppendStringsToObj) (Tcl_Obj *objPtr, ...); /* 15 */ void (*tcl_AppendToObj) (Tcl_Obj *objPtr, const char *bytes, Tcl_Size length); /* 16 */ Tcl_Obj * (*tcl_ConcatObj) (Tcl_Size objc, Tcl_Obj *const objv[]); /* 17 */ int (*tcl_ConvertToType) (Tcl_Interp *interp, Tcl_Obj *objPtr, const Tcl_ObjType *typePtr); /* 18 */ void (*tcl_DbDecrRefCount) (Tcl_Obj *objPtr, const char *file, int line); /* 19 */ void (*tcl_DbIncrRefCount) (Tcl_Obj *objPtr, const char *file, int line); /* 20 */ int (*tcl_DbIsShared) (Tcl_Obj *objPtr, const char *file, int line); /* 21 */ void (*reserved22)(void); Tcl_Obj * (*tcl_DbNewByteArrayObj) (const unsigned char *bytes, Tcl_Size numBytes, const char *file, int line); /* 23 */ Tcl_Obj * (*tcl_DbNewDoubleObj) (double doubleValue, const char *file, int line); /* 24 */ Tcl_Obj * (*tcl_DbNewListObj) (Tcl_Size objc, Tcl_Obj *const *objv, const char *file, int line); /* 25 */ void (*reserved26)(void); Tcl_Obj * (*tcl_DbNewObj) (const char *file, int line); /* 27 */ Tcl_Obj * (*tcl_DbNewStringObj) (const char *bytes, Tcl_Size length, const char *file, int line); /* 28 */ Tcl_Obj * (*tcl_DuplicateObj) (Tcl_Obj *objPtr); /* 29 */ void (*tclFreeObj) (Tcl_Obj *objPtr); /* 30 */ int (*tcl_GetBoolean) (Tcl_Interp *interp, const char *src, int *intPtr); /* 31 */ int (*tcl_GetBooleanFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int *intPtr); /* 32 */ unsigned char * (*tcl_GetByteArrayFromObj) (Tcl_Obj *objPtr, Tcl_Size *numBytesPtr); /* 33 */ int (*tcl_GetDouble) (Tcl_Interp *interp, const char *src, double *doublePtr); /* 34 */ int (*tcl_GetDoubleFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, double *doublePtr); /* 35 */ void (*reserved36)(void); int (*tcl_GetInt) (Tcl_Interp *interp, const char *src, int *intPtr); /* 37 */ int (*tcl_GetIntFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int *intPtr); /* 38 */ int (*tcl_GetLongFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, long *longPtr); /* 39 */ const Tcl_ObjType * (*tcl_GetObjType) (const char *typeName); /* 40 */ char * (*tclGetStringFromObj) (Tcl_Obj *objPtr, void *lengthPtr); /* 41 */ void (*tcl_InvalidateStringRep) (Tcl_Obj *objPtr); /* 42 */ int (*tcl_ListObjAppendList) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Obj *elemListPtr); /* 43 */ int (*tcl_ListObjAppendElement) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Obj *objPtr); /* 44 */ int (*tclListObjGetElements) (Tcl_Interp *interp, Tcl_Obj *listPtr, void *objcPtr, Tcl_Obj ***objvPtr); /* 45 */ int (*tcl_ListObjIndex) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size index, Tcl_Obj **objPtrPtr); /* 46 */ int (*tclListObjLength) (Tcl_Interp *interp, Tcl_Obj *listPtr, void *lengthPtr); /* 47 */ int (*tcl_ListObjReplace) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size first, Tcl_Size count, Tcl_Size objc, Tcl_Obj *const objv[]); /* 48 */ void (*reserved49)(void); Tcl_Obj * (*tcl_NewByteArrayObj) (const unsigned char *bytes, Tcl_Size numBytes); /* 50 */ Tcl_Obj * (*tcl_NewDoubleObj) (double doubleValue); /* 51 */ void (*reserved52)(void); Tcl_Obj * (*tcl_NewListObj) (Tcl_Size objc, Tcl_Obj *const objv[]); /* 53 */ void (*reserved54)(void); Tcl_Obj * (*tcl_NewObj) (void); /* 55 */ Tcl_Obj * (*tcl_NewStringObj) (const char *bytes, Tcl_Size length); /* 56 */ void (*reserved57)(void); unsigned char * (*tcl_SetByteArrayLength) (Tcl_Obj *objPtr, Tcl_Size numBytes); /* 58 */ void (*tcl_SetByteArrayObj) (Tcl_Obj *objPtr, const unsigned char *bytes, Tcl_Size numBytes); /* 59 */ void (*tcl_SetDoubleObj) (Tcl_Obj *objPtr, double doubleValue); /* 60 */ void (*reserved61)(void); void (*tcl_SetListObj) (Tcl_Obj *objPtr, Tcl_Size objc, Tcl_Obj *const objv[]); /* 62 */ void (*reserved63)(void); void (*tcl_SetObjLength) (Tcl_Obj *objPtr, Tcl_Size length); /* 64 */ void (*tcl_SetStringObj) (Tcl_Obj *objPtr, const char *bytes, Tcl_Size length); /* 65 */ void (*reserved66)(void); void (*reserved67)(void); void (*tcl_AllowExceptions) (Tcl_Interp *interp); /* 68 */ void (*tcl_AppendElement) (Tcl_Interp *interp, const char *element); /* 69 */ void (*tcl_AppendResult) (Tcl_Interp *interp, ...); /* 70 */ Tcl_AsyncHandler (*tcl_AsyncCreate) (Tcl_AsyncProc *proc, void *clientData); /* 71 */ void (*tcl_AsyncDelete) (Tcl_AsyncHandler async); /* 72 */ int (*tcl_AsyncInvoke) (Tcl_Interp *interp, int code); /* 73 */ void (*tcl_AsyncMark) (Tcl_AsyncHandler async); /* 74 */ int (*tcl_AsyncReady) (void); /* 75 */ void (*reserved76)(void); void (*reserved77)(void); int (*tcl_BadChannelOption) (Tcl_Interp *interp, const char *optionName, const char *optionList); /* 78 */ void (*tcl_CallWhenDeleted) (Tcl_Interp *interp, Tcl_InterpDeleteProc *proc, void *clientData); /* 79 */ void (*tcl_CancelIdleCall) (Tcl_IdleProc *idleProc, void *clientData); /* 80 */ int (*tcl_Close) (Tcl_Interp *interp, Tcl_Channel chan); /* 81 */ int (*tcl_CommandComplete) (const char *cmd); /* 82 */ char * (*tcl_Concat) (Tcl_Size argc, const char *const *argv); /* 83 */ Tcl_Size (*tcl_ConvertElement) (const char *src, char *dst, int flags); /* 84 */ Tcl_Size (*tcl_ConvertCountedElement) (const char *src, Tcl_Size length, char *dst, int flags); /* 85 */ int (*tcl_CreateAlias) (Tcl_Interp *childInterp, const char *childCmd, Tcl_Interp *target, const char *targetCmd, Tcl_Size argc, const char *const *argv); /* 86 */ int (*tcl_CreateAliasObj) (Tcl_Interp *childInterp, const char *childCmd, Tcl_Interp *target, const char *targetCmd, Tcl_Size objc, Tcl_Obj *const objv[]); /* 87 */ Tcl_Channel (*tcl_CreateChannel) (const Tcl_ChannelType *typePtr, const char *chanName, void *instanceData, int mask); /* 88 */ void (*tcl_CreateChannelHandler) (Tcl_Channel chan, int mask, Tcl_ChannelProc *proc, void *clientData); /* 89 */ void (*tcl_CreateCloseHandler) (Tcl_Channel chan, Tcl_CloseProc *proc, void *clientData); /* 90 */ Tcl_Command (*tcl_CreateCommand) (Tcl_Interp *interp, const char *cmdName, Tcl_CmdProc *proc, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 91 */ void (*tcl_CreateEventSource) (Tcl_EventSetupProc *setupProc, Tcl_EventCheckProc *checkProc, void *clientData); /* 92 */ void (*tcl_CreateExitHandler) (Tcl_ExitProc *proc, void *clientData); /* 93 */ Tcl_Interp * (*tcl_CreateInterp) (void); /* 94 */ void (*reserved95)(void); Tcl_Command (*tcl_CreateObjCommand) (Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc *proc, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 96 */ Tcl_Interp * (*tcl_CreateChild) (Tcl_Interp *interp, const char *name, int isSafe); /* 97 */ Tcl_TimerToken (*tcl_CreateTimerHandler) (int milliseconds, Tcl_TimerProc *proc, void *clientData); /* 98 */ Tcl_Trace (*tcl_CreateTrace) (Tcl_Interp *interp, Tcl_Size level, Tcl_CmdTraceProc *proc, void *clientData); /* 99 */ void (*tcl_DeleteAssocData) (Tcl_Interp *interp, const char *name); /* 100 */ void (*tcl_DeleteChannelHandler) (Tcl_Channel chan, Tcl_ChannelProc *proc, void *clientData); /* 101 */ void (*tcl_DeleteCloseHandler) (Tcl_Channel chan, Tcl_CloseProc *proc, void *clientData); /* 102 */ int (*tcl_DeleteCommand) (Tcl_Interp *interp, const char *cmdName); /* 103 */ int (*tcl_DeleteCommandFromToken) (Tcl_Interp *interp, Tcl_Command command); /* 104 */ void (*tcl_DeleteEvents) (Tcl_EventDeleteProc *proc, void *clientData); /* 105 */ void (*tcl_DeleteEventSource) (Tcl_EventSetupProc *setupProc, Tcl_EventCheckProc *checkProc, void *clientData); /* 106 */ void (*tcl_DeleteExitHandler) (Tcl_ExitProc *proc, void *clientData); /* 107 */ void (*tcl_DeleteHashEntry) (Tcl_HashEntry *entryPtr); /* 108 */ void (*tcl_DeleteHashTable) (Tcl_HashTable *tablePtr); /* 109 */ void (*tcl_DeleteInterp) (Tcl_Interp *interp); /* 110 */ void (*tcl_DetachPids) (Tcl_Size numPids, Tcl_Pid *pidPtr); /* 111 */ void (*tcl_DeleteTimerHandler) (Tcl_TimerToken token); /* 112 */ void (*tcl_DeleteTrace) (Tcl_Interp *interp, Tcl_Trace trace); /* 113 */ void (*tcl_DontCallWhenDeleted) (Tcl_Interp *interp, Tcl_InterpDeleteProc *proc, void *clientData); /* 114 */ int (*tcl_DoOneEvent) (int flags); /* 115 */ void (*tcl_DoWhenIdle) (Tcl_IdleProc *proc, void *clientData); /* 116 */ char * (*tcl_DStringAppend) (Tcl_DString *dsPtr, const char *bytes, Tcl_Size length); /* 117 */ char * (*tcl_DStringAppendElement) (Tcl_DString *dsPtr, const char *element); /* 118 */ void (*tcl_DStringEndSublist) (Tcl_DString *dsPtr); /* 119 */ void (*tcl_DStringFree) (Tcl_DString *dsPtr); /* 120 */ void (*tcl_DStringGetResult) (Tcl_Interp *interp, Tcl_DString *dsPtr); /* 121 */ void (*tcl_DStringInit) (Tcl_DString *dsPtr); /* 122 */ void (*tcl_DStringResult) (Tcl_Interp *interp, Tcl_DString *dsPtr); /* 123 */ void (*tcl_DStringSetLength) (Tcl_DString *dsPtr, Tcl_Size length); /* 124 */ void (*tcl_DStringStartSublist) (Tcl_DString *dsPtr); /* 125 */ int (*tcl_Eof) (Tcl_Channel chan); /* 126 */ const char * (*tcl_ErrnoId) (void); /* 127 */ const char * (*tcl_ErrnoMsg) (int err); /* 128 */ void (*reserved129)(void); int (*tcl_EvalFile) (Tcl_Interp *interp, const char *fileName); /* 130 */ void (*reserved131)(void); void (*tcl_EventuallyFree) (void *clientData, Tcl_FreeProc *freeProc); /* 132 */ TCL_NORETURN1 void (*tcl_Exit) (int status); /* 133 */ int (*tcl_ExposeCommand) (Tcl_Interp *interp, const char *hiddenCmdToken, const char *cmdName); /* 134 */ int (*tcl_ExprBoolean) (Tcl_Interp *interp, const char *expr, int *ptr); /* 135 */ int (*tcl_ExprBooleanObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int *ptr); /* 136 */ int (*tcl_ExprDouble) (Tcl_Interp *interp, const char *expr, double *ptr); /* 137 */ int (*tcl_ExprDoubleObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, double *ptr); /* 138 */ int (*tcl_ExprLong) (Tcl_Interp *interp, const char *expr, long *ptr); /* 139 */ int (*tcl_ExprLongObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, long *ptr); /* 140 */ int (*tcl_ExprObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj **resultPtrPtr); /* 141 */ int (*tcl_ExprString) (Tcl_Interp *interp, const char *expr); /* 142 */ void (*tcl_Finalize) (void); /* 143 */ void (*reserved144)(void); Tcl_HashEntry * (*tcl_FirstHashEntry) (Tcl_HashTable *tablePtr, Tcl_HashSearch *searchPtr); /* 145 */ int (*tcl_Flush) (Tcl_Channel chan); /* 146 */ void (*reserved147)(void); void (*reserved148)(void); int (*tclGetAliasObj) (Tcl_Interp *interp, const char *childCmd, Tcl_Interp **targetInterpPtr, const char **targetCmdPtr, int *objcPtr, Tcl_Obj ***objvPtr); /* 149 */ void * (*tcl_GetAssocData) (Tcl_Interp *interp, const char *name, Tcl_InterpDeleteProc **procPtr); /* 150 */ Tcl_Channel (*tcl_GetChannel) (Tcl_Interp *interp, const char *chanName, int *modePtr); /* 151 */ Tcl_Size (*tcl_GetChannelBufferSize) (Tcl_Channel chan); /* 152 */ int (*tcl_GetChannelHandle) (Tcl_Channel chan, int direction, void **handlePtr); /* 153 */ void * (*tcl_GetChannelInstanceData) (Tcl_Channel chan); /* 154 */ int (*tcl_GetChannelMode) (Tcl_Channel chan); /* 155 */ const char * (*tcl_GetChannelName) (Tcl_Channel chan); /* 156 */ int (*tcl_GetChannelOption) (Tcl_Interp *interp, Tcl_Channel chan, const char *optionName, Tcl_DString *dsPtr); /* 157 */ const Tcl_ChannelType * (*tcl_GetChannelType) (Tcl_Channel chan); /* 158 */ int (*tcl_GetCommandInfo) (Tcl_Interp *interp, const char *cmdName, Tcl_CmdInfo *infoPtr); /* 159 */ const char * (*tcl_GetCommandName) (Tcl_Interp *interp, Tcl_Command command); /* 160 */ int (*tcl_GetErrno) (void); /* 161 */ const char * (*tcl_GetHostName) (void); /* 162 */ int (*tcl_GetInterpPath) (Tcl_Interp *interp, Tcl_Interp *childInterp); /* 163 */ Tcl_Interp * (*tcl_GetParent) (Tcl_Interp *interp); /* 164 */ const char * (*tcl_GetNameOfExecutable) (void); /* 165 */ Tcl_Obj * (*tcl_GetObjResult) (Tcl_Interp *interp); /* 166 */ int (*tcl_GetOpenFile) (Tcl_Interp *interp, const char *chanID, int forWriting, int checkUsage, void **filePtr); /* 167 */ Tcl_PathType (*tcl_GetPathType) (const char *path); /* 168 */ Tcl_Size (*tcl_Gets) (Tcl_Channel chan, Tcl_DString *dsPtr); /* 169 */ Tcl_Size (*tcl_GetsObj) (Tcl_Channel chan, Tcl_Obj *objPtr); /* 170 */ int (*tcl_GetServiceMode) (void); /* 171 */ Tcl_Interp * (*tcl_GetChild) (Tcl_Interp *interp, const char *name); /* 172 */ Tcl_Channel (*tcl_GetStdChannel) (int type); /* 173 */ void (*reserved174)(void); void (*reserved175)(void); const char * (*tcl_GetVar2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags); /* 176 */ void (*reserved177)(void); void (*reserved178)(void); int (*tcl_HideCommand) (Tcl_Interp *interp, const char *cmdName, const char *hiddenCmdToken); /* 179 */ int (*tcl_Init) (Tcl_Interp *interp); /* 180 */ void (*tcl_InitHashTable) (Tcl_HashTable *tablePtr, int keyType); /* 181 */ int (*tcl_InputBlocked) (Tcl_Channel chan); /* 182 */ int (*tcl_InputBuffered) (Tcl_Channel chan); /* 183 */ int (*tcl_InterpDeleted) (Tcl_Interp *interp); /* 184 */ int (*tcl_IsSafe) (Tcl_Interp *interp); /* 185 */ char * (*tcl_JoinPath) (Tcl_Size argc, const char *const *argv, Tcl_DString *resultPtr); /* 186 */ int (*tcl_LinkVar) (Tcl_Interp *interp, const char *varName, void *addr, int type); /* 187 */ void (*reserved188)(void); Tcl_Channel (*tcl_MakeFileChannel) (void *handle, int mode); /* 189 */ void (*reserved190)(void); Tcl_Channel (*tcl_MakeTcpClientChannel) (void *tcpSocket); /* 191 */ char * (*tcl_Merge) (Tcl_Size argc, const char *const *argv); /* 192 */ Tcl_HashEntry * (*tcl_NextHashEntry) (Tcl_HashSearch *searchPtr); /* 193 */ void (*tcl_NotifyChannel) (Tcl_Channel channel, int mask); /* 194 */ Tcl_Obj * (*tcl_ObjGetVar2) (Tcl_Interp *interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags); /* 195 */ Tcl_Obj * (*tcl_ObjSetVar2) (Tcl_Interp *interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *newValuePtr, int flags); /* 196 */ Tcl_Channel (*tcl_OpenCommandChannel) (Tcl_Interp *interp, Tcl_Size argc, const char **argv, int flags); /* 197 */ Tcl_Channel (*tcl_OpenFileChannel) (Tcl_Interp *interp, const char *fileName, const char *modeString, int permissions); /* 198 */ Tcl_Channel (*tcl_OpenTcpClient) (Tcl_Interp *interp, int port, const char *address, const char *myaddr, int myport, int flags); /* 199 */ Tcl_Channel (*tcl_OpenTcpServer) (Tcl_Interp *interp, int port, const char *host, Tcl_TcpAcceptProc *acceptProc, void *callbackData); /* 200 */ void (*tcl_Preserve) (void *data); /* 201 */ void (*tcl_PrintDouble) (Tcl_Interp *interp, double value, char *dst); /* 202 */ int (*tcl_PutEnv) (const char *assignment); /* 203 */ const char * (*tcl_PosixError) (Tcl_Interp *interp); /* 204 */ void (*tcl_QueueEvent) (Tcl_Event *evPtr, int position); /* 205 */ Tcl_Size (*tcl_Read) (Tcl_Channel chan, char *bufPtr, Tcl_Size toRead); /* 206 */ void (*tcl_ReapDetachedProcs) (void); /* 207 */ int (*tcl_RecordAndEval) (Tcl_Interp *interp, const char *cmd, int flags); /* 208 */ int (*tcl_RecordAndEvalObj) (Tcl_Interp *interp, Tcl_Obj *cmdPtr, int flags); /* 209 */ void (*tcl_RegisterChannel) (Tcl_Interp *interp, Tcl_Channel chan); /* 210 */ void (*tcl_RegisterObjType) (const Tcl_ObjType *typePtr); /* 211 */ Tcl_RegExp (*tcl_RegExpCompile) (Tcl_Interp *interp, const char *pattern); /* 212 */ int (*tcl_RegExpExec) (Tcl_Interp *interp, Tcl_RegExp regexp, const char *text, const char *start); /* 213 */ int (*tcl_RegExpMatch) (Tcl_Interp *interp, const char *text, const char *pattern); /* 214 */ void (*tcl_RegExpRange) (Tcl_RegExp regexp, Tcl_Size index, const char **startPtr, const char **endPtr); /* 215 */ void (*tcl_Release) (void *clientData); /* 216 */ void (*tcl_ResetResult) (Tcl_Interp *interp); /* 217 */ Tcl_Size (*tcl_ScanElement) (const char *src, int *flagPtr); /* 218 */ Tcl_Size (*tcl_ScanCountedElement) (const char *src, Tcl_Size length, int *flagPtr); /* 219 */ void (*reserved220)(void); int (*tcl_ServiceAll) (void); /* 221 */ int (*tcl_ServiceEvent) (int flags); /* 222 */ void (*tcl_SetAssocData) (Tcl_Interp *interp, const char *name, Tcl_InterpDeleteProc *proc, void *clientData); /* 223 */ void (*tcl_SetChannelBufferSize) (Tcl_Channel chan, Tcl_Size sz); /* 224 */ int (*tcl_SetChannelOption) (Tcl_Interp *interp, Tcl_Channel chan, const char *optionName, const char *newValue); /* 225 */ int (*tcl_SetCommandInfo) (Tcl_Interp *interp, const char *cmdName, const Tcl_CmdInfo *infoPtr); /* 226 */ void (*tcl_SetErrno) (int err); /* 227 */ void (*tcl_SetErrorCode) (Tcl_Interp *interp, ...); /* 228 */ void (*tcl_SetMaxBlockTime) (const Tcl_Time *timePtr); /* 229 */ void (*reserved230)(void); Tcl_Size (*tcl_SetRecursionLimit) (Tcl_Interp *interp, Tcl_Size depth); /* 231 */ void (*reserved232)(void); int (*tcl_SetServiceMode) (int mode); /* 233 */ void (*tcl_SetObjErrorCode) (Tcl_Interp *interp, Tcl_Obj *errorObjPtr); /* 234 */ void (*tcl_SetObjResult) (Tcl_Interp *interp, Tcl_Obj *resultObjPtr); /* 235 */ void (*tcl_SetStdChannel) (Tcl_Channel channel, int type); /* 236 */ void (*reserved237)(void); const char * (*tcl_SetVar2) (Tcl_Interp *interp, const char *part1, const char *part2, const char *newValue, int flags); /* 238 */ const char * (*tcl_SignalId) (int sig); /* 239 */ const char * (*tcl_SignalMsg) (int sig); /* 240 */ void (*tcl_SourceRCFile) (Tcl_Interp *interp); /* 241 */ int (*tclSplitList) (Tcl_Interp *interp, const char *listStr, void *argcPtr, const char ***argvPtr); /* 242 */ void (*tclSplitPath) (const char *path, void *argcPtr, const char ***argvPtr); /* 243 */ void (*reserved244)(void); void (*reserved245)(void); void (*reserved246)(void); void (*reserved247)(void); int (*tcl_TraceVar2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *proc, void *clientData); /* 248 */ char * (*tcl_TranslateFileName) (Tcl_Interp *interp, const char *name, Tcl_DString *bufferPtr); /* 249 */ Tcl_Size (*tcl_Ungets) (Tcl_Channel chan, const char *str, Tcl_Size len, int atHead); /* 250 */ void (*tcl_UnlinkVar) (Tcl_Interp *interp, const char *varName); /* 251 */ int (*tcl_UnregisterChannel) (Tcl_Interp *interp, Tcl_Channel chan); /* 252 */ void (*reserved253)(void); int (*tcl_UnsetVar2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags); /* 254 */ void (*reserved255)(void); void (*tcl_UntraceVar2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *proc, void *clientData); /* 256 */ void (*tcl_UpdateLinkedVar) (Tcl_Interp *interp, const char *varName); /* 257 */ void (*reserved258)(void); int (*tcl_UpVar2) (Tcl_Interp *interp, const char *frameName, const char *part1, const char *part2, const char *localName, int flags); /* 259 */ int (*tcl_VarEval) (Tcl_Interp *interp, ...); /* 260 */ void (*reserved261)(void); void * (*tcl_VarTraceInfo2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *procPtr, void *prevClientData); /* 262 */ Tcl_Size (*tcl_Write) (Tcl_Channel chan, const char *s, Tcl_Size slen); /* 263 */ void (*tcl_WrongNumArgs) (Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], const char *message); /* 264 */ int (*tcl_DumpActiveMemory) (const char *fileName); /* 265 */ void (*tcl_ValidateAllMemory) (const char *file, int line); /* 266 */ void (*reserved267)(void); void (*reserved268)(void); char * (*tcl_HashStats) (Tcl_HashTable *tablePtr); /* 269 */ const char * (*tcl_ParseVar) (Tcl_Interp *interp, const char *start, const char **termPtr); /* 270 */ void (*reserved271)(void); const char * (*tcl_PkgPresentEx) (Tcl_Interp *interp, const char *name, const char *version, int exact, void *clientDataPtr); /* 272 */ void (*reserved273)(void); void (*reserved274)(void); void (*reserved275)(void); void (*reserved276)(void); Tcl_Pid (*tcl_WaitPid) (Tcl_Pid pid, int *statPtr, int options); /* 277 */ void (*reserved278)(void); void (*tcl_GetVersion) (int *major, int *minor, int *patchLevel, int *type); /* 279 */ void (*tcl_InitMemory) (Tcl_Interp *interp); /* 280 */ Tcl_Channel (*tcl_StackChannel) (Tcl_Interp *interp, const Tcl_ChannelType *typePtr, void *instanceData, int mask, Tcl_Channel prevChan); /* 281 */ int (*tcl_UnstackChannel) (Tcl_Interp *interp, Tcl_Channel chan); /* 282 */ Tcl_Channel (*tcl_GetStackedChannel) (Tcl_Channel chan); /* 283 */ void (*tcl_SetMainLoop) (Tcl_MainLoopProc *proc); /* 284 */ int (*tcl_GetAliasObj) (Tcl_Interp *interp, const char *childCmd, Tcl_Interp **targetInterpPtr, const char **targetCmdPtr, Tcl_Size *objcPtr, Tcl_Obj ***objvPtr); /* 285 */ void (*tcl_AppendObjToObj) (Tcl_Obj *objPtr, Tcl_Obj *appendObjPtr); /* 286 */ Tcl_Encoding (*tcl_CreateEncoding) (const Tcl_EncodingType *typePtr); /* 287 */ void (*tcl_CreateThreadExitHandler) (Tcl_ExitProc *proc, void *clientData); /* 288 */ void (*tcl_DeleteThreadExitHandler) (Tcl_ExitProc *proc, void *clientData); /* 289 */ void (*reserved290)(void); int (*tcl_EvalEx) (Tcl_Interp *interp, const char *script, Tcl_Size numBytes, int flags); /* 291 */ int (*tcl_EvalObjv) (Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags); /* 292 */ int (*tcl_EvalObjEx) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 293 */ TCL_NORETURN1 void (*tcl_ExitThread) (int status); /* 294 */ int (*tcl_ExternalToUtf) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, Tcl_Size dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr); /* 295 */ char * (*tcl_ExternalToUtfDString) (Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, Tcl_DString *dsPtr); /* 296 */ void (*tcl_FinalizeThread) (void); /* 297 */ void (*tcl_FinalizeNotifier) (void *clientData); /* 298 */ void (*tcl_FreeEncoding) (Tcl_Encoding encoding); /* 299 */ Tcl_ThreadId (*tcl_GetCurrentThread) (void); /* 300 */ Tcl_Encoding (*tcl_GetEncoding) (Tcl_Interp *interp, const char *name); /* 301 */ const char * (*tcl_GetEncodingName) (Tcl_Encoding encoding); /* 302 */ void (*tcl_GetEncodingNames) (Tcl_Interp *interp); /* 303 */ int (*tcl_GetIndexFromObjStruct) (Tcl_Interp *interp, Tcl_Obj *objPtr, const void *tablePtr, Tcl_Size offset, const char *msg, int flags, void *indexPtr); /* 304 */ void * (*tcl_GetThreadData) (Tcl_ThreadDataKey *keyPtr, Tcl_Size size); /* 305 */ Tcl_Obj * (*tcl_GetVar2Ex) (Tcl_Interp *interp, const char *part1, const char *part2, int flags); /* 306 */ void * (*tcl_InitNotifier) (void); /* 307 */ void (*tcl_MutexLock) (Tcl_Mutex *mutexPtr); /* 308 */ void (*tcl_MutexUnlock) (Tcl_Mutex *mutexPtr); /* 309 */ void (*tcl_ConditionNotify) (Tcl_Condition *condPtr); /* 310 */ void (*tcl_ConditionWait) (Tcl_Condition *condPtr, Tcl_Mutex *mutexPtr, const Tcl_Time *timePtr); /* 311 */ Tcl_Size (*tclNumUtfChars) (const char *src, Tcl_Size length); /* 312 */ Tcl_Size (*tcl_ReadChars) (Tcl_Channel channel, Tcl_Obj *objPtr, Tcl_Size charsToRead, int appendFlag); /* 313 */ void (*reserved314)(void); void (*reserved315)(void); int (*tcl_SetSystemEncoding) (Tcl_Interp *interp, const char *name); /* 316 */ Tcl_Obj * (*tcl_SetVar2Ex) (Tcl_Interp *interp, const char *part1, const char *part2, Tcl_Obj *newValuePtr, int flags); /* 317 */ void (*tcl_ThreadAlert) (Tcl_ThreadId threadId); /* 318 */ void (*tcl_ThreadQueueEvent) (Tcl_ThreadId threadId, Tcl_Event *evPtr, int position); /* 319 */ int (*tcl_UniCharAtIndex) (const char *src, Tcl_Size index); /* 320 */ int (*tcl_UniCharToLower) (int ch); /* 321 */ int (*tcl_UniCharToTitle) (int ch); /* 322 */ int (*tcl_UniCharToUpper) (int ch); /* 323 */ Tcl_Size (*tcl_UniCharToUtf) (int ch, char *buf); /* 324 */ const char * (*tclUtfAtIndex) (const char *src, Tcl_Size index); /* 325 */ int (*tclUtfCharComplete) (const char *src, Tcl_Size length); /* 326 */ Tcl_Size (*tcl_UtfBackslash) (const char *src, int *readPtr, char *dst); /* 327 */ const char * (*tcl_UtfFindFirst) (const char *src, int ch); /* 328 */ const char * (*tcl_UtfFindLast) (const char *src, int ch); /* 329 */ const char * (*tclUtfNext) (const char *src); /* 330 */ const char * (*tclUtfPrev) (const char *src, const char *start); /* 331 */ int (*tcl_UtfToExternal) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, Tcl_Size dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr); /* 332 */ char * (*tcl_UtfToExternalDString) (Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, Tcl_DString *dsPtr); /* 333 */ Tcl_Size (*tcl_UtfToLower) (char *src); /* 334 */ Tcl_Size (*tcl_UtfToTitle) (char *src); /* 335 */ Tcl_Size (*tcl_UtfToChar16) (const char *src, unsigned short *chPtr); /* 336 */ Tcl_Size (*tcl_UtfToUpper) (char *src); /* 337 */ Tcl_Size (*tcl_WriteChars) (Tcl_Channel chan, const char *src, Tcl_Size srcLen); /* 338 */ Tcl_Size (*tcl_WriteObj) (Tcl_Channel chan, Tcl_Obj *objPtr); /* 339 */ char * (*tcl_GetString) (Tcl_Obj *objPtr); /* 340 */ void (*reserved341)(void); void (*reserved342)(void); void (*tcl_AlertNotifier) (void *clientData); /* 343 */ void (*tcl_ServiceModeHook) (int mode); /* 344 */ int (*tcl_UniCharIsAlnum) (int ch); /* 345 */ int (*tcl_UniCharIsAlpha) (int ch); /* 346 */ int (*tcl_UniCharIsDigit) (int ch); /* 347 */ int (*tcl_UniCharIsLower) (int ch); /* 348 */ int (*tcl_UniCharIsSpace) (int ch); /* 349 */ int (*tcl_UniCharIsUpper) (int ch); /* 350 */ int (*tcl_UniCharIsWordChar) (int ch); /* 351 */ Tcl_Size (*tcl_Char16Len) (const unsigned short *uniStr); /* 352 */ void (*reserved353)(void); char * (*tcl_Char16ToUtfDString) (const unsigned short *uniStr, Tcl_Size uniLength, Tcl_DString *dsPtr); /* 354 */ unsigned short * (*tcl_UtfToChar16DString) (const char *src, Tcl_Size length, Tcl_DString *dsPtr); /* 355 */ Tcl_RegExp (*tcl_GetRegExpFromObj) (Tcl_Interp *interp, Tcl_Obj *patObj, int flags); /* 356 */ void (*reserved357)(void); void (*tcl_FreeParse) (Tcl_Parse *parsePtr); /* 358 */ void (*tcl_LogCommandInfo) (Tcl_Interp *interp, const char *script, const char *command, Tcl_Size length); /* 359 */ int (*tcl_ParseBraces) (Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr, int append, const char **termPtr); /* 360 */ int (*tcl_ParseCommand) (Tcl_Interp *interp, const char *start, Tcl_Size numBytes, int nested, Tcl_Parse *parsePtr); /* 361 */ int (*tcl_ParseExpr) (Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr); /* 362 */ int (*tcl_ParseQuotedString) (Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr, int append, const char **termPtr); /* 363 */ int (*tcl_ParseVarName) (Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr, int append); /* 364 */ char * (*tcl_GetCwd) (Tcl_Interp *interp, Tcl_DString *cwdPtr); /* 365 */ int (*tcl_Chdir) (const char *dirName); /* 366 */ int (*tcl_Access) (const char *path, int mode); /* 367 */ int (*tcl_Stat) (const char *path, struct stat *bufPtr); /* 368 */ int (*tclUtfNcmp) (const char *s1, const char *s2, size_t n); /* 369 */ int (*tclUtfNcasecmp) (const char *s1, const char *s2, size_t n); /* 370 */ int (*tcl_StringCaseMatch) (const char *str, const char *pattern, int nocase); /* 371 */ int (*tcl_UniCharIsControl) (int ch); /* 372 */ int (*tcl_UniCharIsGraph) (int ch); /* 373 */ int (*tcl_UniCharIsPrint) (int ch); /* 374 */ int (*tcl_UniCharIsPunct) (int ch); /* 375 */ int (*tcl_RegExpExecObj) (Tcl_Interp *interp, Tcl_RegExp regexp, Tcl_Obj *textObj, Tcl_Size offset, Tcl_Size nmatches, int flags); /* 376 */ void (*tcl_RegExpGetInfo) (Tcl_RegExp regexp, Tcl_RegExpInfo *infoPtr); /* 377 */ Tcl_Obj * (*tcl_NewUnicodeObj) (const Tcl_UniChar *unicode, Tcl_Size numChars); /* 378 */ void (*tcl_SetUnicodeObj) (Tcl_Obj *objPtr, const Tcl_UniChar *unicode, Tcl_Size numChars); /* 379 */ Tcl_Size (*tclGetCharLength) (Tcl_Obj *objPtr); /* 380 */ int (*tclGetUniChar) (Tcl_Obj *objPtr, Tcl_Size index); /* 381 */ void (*reserved382)(void); Tcl_Obj * (*tclGetRange) (Tcl_Obj *objPtr, Tcl_Size first, Tcl_Size last); /* 383 */ void (*tcl_AppendUnicodeToObj) (Tcl_Obj *objPtr, const Tcl_UniChar *unicode, Tcl_Size length); /* 384 */ int (*tcl_RegExpMatchObj) (Tcl_Interp *interp, Tcl_Obj *textObj, Tcl_Obj *patternObj); /* 385 */ void (*tcl_SetNotifier) (const Tcl_NotifierProcs *notifierProcPtr); /* 386 */ Tcl_Mutex * (*tcl_GetAllocMutex) (void); /* 387 */ int (*tcl_GetChannelNames) (Tcl_Interp *interp); /* 388 */ int (*tcl_GetChannelNamesEx) (Tcl_Interp *interp, const char *pattern); /* 389 */ int (*tcl_ProcObjCmd) (void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[]); /* 390 */ void (*tcl_ConditionFinalize) (Tcl_Condition *condPtr); /* 391 */ void (*tcl_MutexFinalize) (Tcl_Mutex *mutex); /* 392 */ int (*tcl_CreateThread) (Tcl_ThreadId *idPtr, Tcl_ThreadCreateProc *proc, void *clientData, TCL_HASH_TYPE stackSize, int flags); /* 393 */ Tcl_Size (*tcl_ReadRaw) (Tcl_Channel chan, char *dst, Tcl_Size bytesToRead); /* 394 */ Tcl_Size (*tcl_WriteRaw) (Tcl_Channel chan, const char *src, Tcl_Size srcLen); /* 395 */ Tcl_Channel (*tcl_GetTopChannel) (Tcl_Channel chan); /* 396 */ int (*tcl_ChannelBuffered) (Tcl_Channel chan); /* 397 */ const char * (*tcl_ChannelName) (const Tcl_ChannelType *chanTypePtr); /* 398 */ Tcl_ChannelTypeVersion (*tcl_ChannelVersion) (const Tcl_ChannelType *chanTypePtr); /* 399 */ Tcl_DriverBlockModeProc * (*tcl_ChannelBlockModeProc) (const Tcl_ChannelType *chanTypePtr); /* 400 */ void (*reserved401)(void); Tcl_DriverClose2Proc * (*tcl_ChannelClose2Proc) (const Tcl_ChannelType *chanTypePtr); /* 402 */ Tcl_DriverInputProc * (*tcl_ChannelInputProc) (const Tcl_ChannelType *chanTypePtr); /* 403 */ Tcl_DriverOutputProc * (*tcl_ChannelOutputProc) (const Tcl_ChannelType *chanTypePtr); /* 404 */ void (*reserved405)(void); Tcl_DriverSetOptionProc * (*tcl_ChannelSetOptionProc) (const Tcl_ChannelType *chanTypePtr); /* 406 */ Tcl_DriverGetOptionProc * (*tcl_ChannelGetOptionProc) (const Tcl_ChannelType *chanTypePtr); /* 407 */ Tcl_DriverWatchProc * (*tcl_ChannelWatchProc) (const Tcl_ChannelType *chanTypePtr); /* 408 */ Tcl_DriverGetHandleProc * (*tcl_ChannelGetHandleProc) (const Tcl_ChannelType *chanTypePtr); /* 409 */ Tcl_DriverFlushProc * (*tcl_ChannelFlushProc) (const Tcl_ChannelType *chanTypePtr); /* 410 */ Tcl_DriverHandlerProc * (*tcl_ChannelHandlerProc) (const Tcl_ChannelType *chanTypePtr); /* 411 */ int (*tcl_JoinThread) (Tcl_ThreadId threadId, int *result); /* 412 */ int (*tcl_IsChannelShared) (Tcl_Channel channel); /* 413 */ int (*tcl_IsChannelRegistered) (Tcl_Interp *interp, Tcl_Channel channel); /* 414 */ void (*tcl_CutChannel) (Tcl_Channel channel); /* 415 */ void (*tcl_SpliceChannel) (Tcl_Channel channel); /* 416 */ void (*tcl_ClearChannelHandlers) (Tcl_Channel channel); /* 417 */ int (*tcl_IsChannelExisting) (const char *channelName); /* 418 */ void (*reserved419)(void); void (*reserved420)(void); void (*reserved421)(void); void (*reserved422)(void); void (*tcl_InitCustomHashTable) (Tcl_HashTable *tablePtr, int keyType, const Tcl_HashKeyType *typePtr); /* 423 */ void (*tcl_InitObjHashTable) (Tcl_HashTable *tablePtr); /* 424 */ void * (*tcl_CommandTraceInfo) (Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *procPtr, void *prevClientData); /* 425 */ int (*tcl_TraceCommand) (Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *proc, void *clientData); /* 426 */ void (*tcl_UntraceCommand) (Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *proc, void *clientData); /* 427 */ void * (*tcl_AttemptAlloc) (TCL_HASH_TYPE size); /* 428 */ void * (*tcl_AttemptDbCkalloc) (TCL_HASH_TYPE size, const char *file, int line); /* 429 */ void * (*tcl_AttemptRealloc) (void *ptr, TCL_HASH_TYPE size); /* 430 */ void * (*tcl_AttemptDbCkrealloc) (void *ptr, TCL_HASH_TYPE size, const char *file, int line); /* 431 */ int (*tcl_AttemptSetObjLength) (Tcl_Obj *objPtr, Tcl_Size length); /* 432 */ Tcl_ThreadId (*tcl_GetChannelThread) (Tcl_Channel channel); /* 433 */ Tcl_UniChar * (*tclGetUnicodeFromObj) (Tcl_Obj *objPtr, void *lengthPtr); /* 434 */ void (*reserved435)(void); void (*reserved436)(void); Tcl_Obj * (*tcl_SubstObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 437 */ int (*tcl_DetachChannel) (Tcl_Interp *interp, Tcl_Channel channel); /* 438 */ int (*tcl_IsStandardChannel) (Tcl_Channel channel); /* 439 */ int (*tcl_FSCopyFile) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); /* 440 */ int (*tcl_FSCopyDirectory) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr); /* 441 */ int (*tcl_FSCreateDirectory) (Tcl_Obj *pathPtr); /* 442 */ int (*tcl_FSDeleteFile) (Tcl_Obj *pathPtr); /* 443 */ int (*tcl_FSLoadFile) (Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *sym1, const char *sym2, Tcl_LibraryInitProc **proc1Ptr, Tcl_LibraryInitProc **proc2Ptr, Tcl_LoadHandle *handlePtr, Tcl_FSUnloadFileProc **unloadProcPtr); /* 444 */ int (*tcl_FSMatchInDirectory) (Tcl_Interp *interp, Tcl_Obj *result, Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types); /* 445 */ Tcl_Obj * (*tcl_FSLink) (Tcl_Obj *pathPtr, Tcl_Obj *toPtr, int linkAction); /* 446 */ int (*tcl_FSRemoveDirectory) (Tcl_Obj *pathPtr, int recursive, Tcl_Obj **errorPtr); /* 447 */ int (*tcl_FSRenameFile) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); /* 448 */ int (*tcl_FSLstat) (Tcl_Obj *pathPtr, Tcl_StatBuf *buf); /* 449 */ int (*tcl_FSUtime) (Tcl_Obj *pathPtr, struct utimbuf *tval); /* 450 */ int (*tcl_FSFileAttrsGet) (Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); /* 451 */ int (*tcl_FSFileAttrsSet) (Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj *objPtr); /* 452 */ const char *const * (*tcl_FSFileAttrStrings) (Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); /* 453 */ int (*tcl_FSStat) (Tcl_Obj *pathPtr, Tcl_StatBuf *buf); /* 454 */ int (*tcl_FSAccess) (Tcl_Obj *pathPtr, int mode); /* 455 */ Tcl_Channel (*tcl_FSOpenFileChannel) (Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *modeString, int permissions); /* 456 */ Tcl_Obj * (*tcl_FSGetCwd) (Tcl_Interp *interp); /* 457 */ int (*tcl_FSChdir) (Tcl_Obj *pathPtr); /* 458 */ int (*tcl_FSConvertToPathType) (Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 459 */ Tcl_Obj * (*tcl_FSJoinPath) (Tcl_Obj *listObj, Tcl_Size elements); /* 460 */ Tcl_Obj * (*tclFSSplitPath) (Tcl_Obj *pathPtr, void *lenPtr); /* 461 */ int (*tcl_FSEqualPaths) (Tcl_Obj *firstPtr, Tcl_Obj *secondPtr); /* 462 */ Tcl_Obj * (*tcl_FSGetNormalizedPath) (Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 463 */ Tcl_Obj * (*tcl_FSJoinToPath) (Tcl_Obj *pathPtr, Tcl_Size objc, Tcl_Obj *const objv[]); /* 464 */ void * (*tcl_FSGetInternalRep) (Tcl_Obj *pathPtr, const Tcl_Filesystem *fsPtr); /* 465 */ Tcl_Obj * (*tcl_FSGetTranslatedPath) (Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 466 */ int (*tcl_FSEvalFile) (Tcl_Interp *interp, Tcl_Obj *fileName); /* 467 */ Tcl_Obj * (*tcl_FSNewNativePath) (const Tcl_Filesystem *fromFilesystem, void *clientData); /* 468 */ const void * (*tcl_FSGetNativePath) (Tcl_Obj *pathPtr); /* 469 */ Tcl_Obj * (*tcl_FSFileSystemInfo) (Tcl_Obj *pathPtr); /* 470 */ Tcl_Obj * (*tcl_FSPathSeparator) (Tcl_Obj *pathPtr); /* 471 */ Tcl_Obj * (*tcl_FSListVolumes) (void); /* 472 */ int (*tcl_FSRegister) (void *clientData, const Tcl_Filesystem *fsPtr); /* 473 */ int (*tcl_FSUnregister) (const Tcl_Filesystem *fsPtr); /* 474 */ void * (*tcl_FSData) (const Tcl_Filesystem *fsPtr); /* 475 */ const char * (*tcl_FSGetTranslatedStringPath) (Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 476 */ const Tcl_Filesystem * (*tcl_FSGetFileSystemForPath) (Tcl_Obj *pathPtr); /* 477 */ Tcl_PathType (*tcl_FSGetPathType) (Tcl_Obj *pathPtr); /* 478 */ int (*tcl_OutputBuffered) (Tcl_Channel chan); /* 479 */ void (*tcl_FSMountsChanged) (const Tcl_Filesystem *fsPtr); /* 480 */ int (*tcl_EvalTokensStandard) (Tcl_Interp *interp, Tcl_Token *tokenPtr, Tcl_Size count); /* 481 */ void (*tcl_GetTime) (Tcl_Time *timeBuf); /* 482 */ Tcl_Trace (*tcl_CreateObjTrace) (Tcl_Interp *interp, Tcl_Size level, int flags, Tcl_CmdObjTraceProc *objProc, void *clientData, Tcl_CmdObjTraceDeleteProc *delProc); /* 483 */ int (*tcl_GetCommandInfoFromToken) (Tcl_Command token, Tcl_CmdInfo *infoPtr); /* 484 */ int (*tcl_SetCommandInfoFromToken) (Tcl_Command token, const Tcl_CmdInfo *infoPtr); /* 485 */ Tcl_Obj * (*tcl_DbNewWideIntObj) (Tcl_WideInt wideValue, const char *file, int line); /* 486 */ int (*tcl_GetWideIntFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_WideInt *widePtr); /* 487 */ Tcl_Obj * (*tcl_NewWideIntObj) (Tcl_WideInt wideValue); /* 488 */ void (*tcl_SetWideIntObj) (Tcl_Obj *objPtr, Tcl_WideInt wideValue); /* 489 */ Tcl_StatBuf * (*tcl_AllocStatBuf) (void); /* 490 */ long long (*tcl_Seek) (Tcl_Channel chan, long long offset, int mode); /* 491 */ long long (*tcl_Tell) (Tcl_Channel chan); /* 492 */ Tcl_DriverWideSeekProc * (*tcl_ChannelWideSeekProc) (const Tcl_ChannelType *chanTypePtr); /* 493 */ int (*tcl_DictObjPut) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr, Tcl_Obj *valuePtr); /* 494 */ int (*tcl_DictObjGet) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr, Tcl_Obj **valuePtrPtr); /* 495 */ int (*tcl_DictObjRemove) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr); /* 496 */ int (*tclDictObjSize) (Tcl_Interp *interp, Tcl_Obj *dictPtr, void *sizePtr); /* 497 */ int (*tcl_DictObjFirst) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_DictSearch *searchPtr, Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr, int *donePtr); /* 498 */ void (*tcl_DictObjNext) (Tcl_DictSearch *searchPtr, Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr, int *donePtr); /* 499 */ void (*tcl_DictObjDone) (Tcl_DictSearch *searchPtr); /* 500 */ int (*tcl_DictObjPutKeyList) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size keyc, Tcl_Obj *const *keyv, Tcl_Obj *valuePtr); /* 501 */ int (*tcl_DictObjRemoveKeyList) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size keyc, Tcl_Obj *const *keyv); /* 502 */ Tcl_Obj * (*tcl_NewDictObj) (void); /* 503 */ Tcl_Obj * (*tcl_DbNewDictObj) (const char *file, int line); /* 504 */ void (*tcl_RegisterConfig) (Tcl_Interp *interp, const char *pkgName, const Tcl_Config *configuration, const char *valEncoding); /* 505 */ Tcl_Namespace * (*tcl_CreateNamespace) (Tcl_Interp *interp, const char *name, void *clientData, Tcl_NamespaceDeleteProc *deleteProc); /* 506 */ void (*tcl_DeleteNamespace) (Tcl_Namespace *nsPtr); /* 507 */ int (*tcl_AppendExportList) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *objPtr); /* 508 */ int (*tcl_Export) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern, int resetListFirst); /* 509 */ int (*tcl_Import) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern, int allowOverwrite); /* 510 */ int (*tcl_ForgetImport) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern); /* 511 */ Tcl_Namespace * (*tcl_GetCurrentNamespace) (Tcl_Interp *interp); /* 512 */ Tcl_Namespace * (*tcl_GetGlobalNamespace) (Tcl_Interp *interp); /* 513 */ Tcl_Namespace * (*tcl_FindNamespace) (Tcl_Interp *interp, const char *name, Tcl_Namespace *contextNsPtr, int flags); /* 514 */ Tcl_Command (*tcl_FindCommand) (Tcl_Interp *interp, const char *name, Tcl_Namespace *contextNsPtr, int flags); /* 515 */ Tcl_Command (*tcl_GetCommandFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr); /* 516 */ void (*tcl_GetCommandFullName) (Tcl_Interp *interp, Tcl_Command command, Tcl_Obj *objPtr); /* 517 */ int (*tcl_FSEvalFileEx) (Tcl_Interp *interp, Tcl_Obj *fileName, const char *encodingName); /* 518 */ void (*reserved519)(void); void (*tcl_LimitAddHandler) (Tcl_Interp *interp, int type, Tcl_LimitHandlerProc *handlerProc, void *clientData, Tcl_LimitHandlerDeleteProc *deleteProc); /* 520 */ void (*tcl_LimitRemoveHandler) (Tcl_Interp *interp, int type, Tcl_LimitHandlerProc *handlerProc, void *clientData); /* 521 */ int (*tcl_LimitReady) (Tcl_Interp *interp); /* 522 */ int (*tcl_LimitCheck) (Tcl_Interp *interp); /* 523 */ int (*tcl_LimitExceeded) (Tcl_Interp *interp); /* 524 */ void (*tcl_LimitSetCommands) (Tcl_Interp *interp, Tcl_Size commandLimit); /* 525 */ void (*tcl_LimitSetTime) (Tcl_Interp *interp, Tcl_Time *timeLimitPtr); /* 526 */ void (*tcl_LimitSetGranularity) (Tcl_Interp *interp, int type, int granularity); /* 527 */ int (*tcl_LimitTypeEnabled) (Tcl_Interp *interp, int type); /* 528 */ int (*tcl_LimitTypeExceeded) (Tcl_Interp *interp, int type); /* 529 */ void (*tcl_LimitTypeSet) (Tcl_Interp *interp, int type); /* 530 */ void (*tcl_LimitTypeReset) (Tcl_Interp *interp, int type); /* 531 */ int (*tcl_LimitGetCommands) (Tcl_Interp *interp); /* 532 */ void (*tcl_LimitGetTime) (Tcl_Interp *interp, Tcl_Time *timeLimitPtr); /* 533 */ int (*tcl_LimitGetGranularity) (Tcl_Interp *interp, int type); /* 534 */ Tcl_InterpState (*tcl_SaveInterpState) (Tcl_Interp *interp, int status); /* 535 */ int (*tcl_RestoreInterpState) (Tcl_Interp *interp, Tcl_InterpState state); /* 536 */ void (*tcl_DiscardInterpState) (Tcl_InterpState state); /* 537 */ int (*tcl_SetReturnOptions) (Tcl_Interp *interp, Tcl_Obj *options); /* 538 */ Tcl_Obj * (*tcl_GetReturnOptions) (Tcl_Interp *interp, int result); /* 539 */ int (*tcl_IsEnsemble) (Tcl_Command token); /* 540 */ Tcl_Command (*tcl_CreateEnsemble) (Tcl_Interp *interp, const char *name, Tcl_Namespace *namespacePtr, int flags); /* 541 */ Tcl_Command (*tcl_FindEnsemble) (Tcl_Interp *interp, Tcl_Obj *cmdNameObj, int flags); /* 542 */ int (*tcl_SetEnsembleSubcommandList) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *subcmdList); /* 543 */ int (*tcl_SetEnsembleMappingDict) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *mapDict); /* 544 */ int (*tcl_SetEnsembleUnknownHandler) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *unknownList); /* 545 */ int (*tcl_SetEnsembleFlags) (Tcl_Interp *interp, Tcl_Command token, int flags); /* 546 */ int (*tcl_GetEnsembleSubcommandList) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **subcmdListPtr); /* 547 */ int (*tcl_GetEnsembleMappingDict) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **mapDictPtr); /* 548 */ int (*tcl_GetEnsembleUnknownHandler) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **unknownListPtr); /* 549 */ int (*tcl_GetEnsembleFlags) (Tcl_Interp *interp, Tcl_Command token, int *flagsPtr); /* 550 */ int (*tcl_GetEnsembleNamespace) (Tcl_Interp *interp, Tcl_Command token, Tcl_Namespace **namespacePtrPtr); /* 551 */ void (*tcl_SetTimeProc) (Tcl_GetTimeProc *getProc, Tcl_ScaleTimeProc *scaleProc, void *clientData); /* 552 */ void (*tcl_QueryTimeProc) (Tcl_GetTimeProc **getProc, Tcl_ScaleTimeProc **scaleProc, void **clientData); /* 553 */ Tcl_DriverThreadActionProc * (*tcl_ChannelThreadActionProc) (const Tcl_ChannelType *chanTypePtr); /* 554 */ Tcl_Obj * (*tcl_NewBignumObj) (void *value); /* 555 */ Tcl_Obj * (*tcl_DbNewBignumObj) (void *value, const char *file, int line); /* 556 */ void (*tcl_SetBignumObj) (Tcl_Obj *obj, void *value); /* 557 */ int (*tcl_GetBignumFromObj) (Tcl_Interp *interp, Tcl_Obj *obj, void *value); /* 558 */ int (*tcl_TakeBignumFromObj) (Tcl_Interp *interp, Tcl_Obj *obj, void *value); /* 559 */ int (*tcl_TruncateChannel) (Tcl_Channel chan, long long length); /* 560 */ Tcl_DriverTruncateProc * (*tcl_ChannelTruncateProc) (const Tcl_ChannelType *chanTypePtr); /* 561 */ void (*tcl_SetChannelErrorInterp) (Tcl_Interp *interp, Tcl_Obj *msg); /* 562 */ void (*tcl_GetChannelErrorInterp) (Tcl_Interp *interp, Tcl_Obj **msg); /* 563 */ void (*tcl_SetChannelError) (Tcl_Channel chan, Tcl_Obj *msg); /* 564 */ void (*tcl_GetChannelError) (Tcl_Channel chan, Tcl_Obj **msg); /* 565 */ int (*tcl_InitBignumFromDouble) (Tcl_Interp *interp, double initval, void *toInit); /* 566 */ Tcl_Obj * (*tcl_GetNamespaceUnknownHandler) (Tcl_Interp *interp, Tcl_Namespace *nsPtr); /* 567 */ int (*tcl_SetNamespaceUnknownHandler) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *handlerPtr); /* 568 */ int (*tcl_GetEncodingFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Encoding *encodingPtr); /* 569 */ Tcl_Obj * (*tcl_GetEncodingSearchPath) (void); /* 570 */ int (*tcl_SetEncodingSearchPath) (Tcl_Obj *searchPath); /* 571 */ const char * (*tcl_GetEncodingNameFromEnvironment) (Tcl_DString *bufPtr); /* 572 */ int (*tcl_PkgRequireProc) (Tcl_Interp *interp, const char *name, Tcl_Size objc, Tcl_Obj *const objv[], void *clientDataPtr); /* 573 */ void (*tcl_AppendObjToErrorInfo) (Tcl_Interp *interp, Tcl_Obj *objPtr); /* 574 */ void (*tcl_AppendLimitedToObj) (Tcl_Obj *objPtr, const char *bytes, Tcl_Size length, Tcl_Size limit, const char *ellipsis); /* 575 */ Tcl_Obj * (*tcl_Format) (Tcl_Interp *interp, const char *format, Tcl_Size objc, Tcl_Obj *const objv[]); /* 576 */ int (*tcl_AppendFormatToObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, const char *format, Tcl_Size objc, Tcl_Obj *const objv[]); /* 577 */ Tcl_Obj * (*tcl_ObjPrintf) (const char *format, ...) TCL_FORMAT_PRINTF(1, 2); /* 578 */ void (*tcl_AppendPrintfToObj) (Tcl_Obj *objPtr, const char *format, ...) TCL_FORMAT_PRINTF(2, 3); /* 579 */ int (*tcl_CancelEval) (Tcl_Interp *interp, Tcl_Obj *resultObjPtr, void *clientData, int flags); /* 580 */ int (*tcl_Canceled) (Tcl_Interp *interp, int flags); /* 581 */ int (*tcl_CreatePipe) (Tcl_Interp *interp, Tcl_Channel *rchan, Tcl_Channel *wchan, int flags); /* 582 */ Tcl_Command (*tcl_NRCreateCommand) (Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc *proc, Tcl_ObjCmdProc *nreProc, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 583 */ int (*tcl_NREvalObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 584 */ int (*tcl_NREvalObjv) (Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags); /* 585 */ int (*tcl_NRCmdSwap) (Tcl_Interp *interp, Tcl_Command cmd, Tcl_Size objc, Tcl_Obj *const objv[], int flags); /* 586 */ void (*tcl_NRAddCallback) (Tcl_Interp *interp, Tcl_NRPostProc *postProcPtr, void *data0, void *data1, void *data2, void *data3); /* 587 */ int (*tcl_NRCallObjProc) (Tcl_Interp *interp, Tcl_ObjCmdProc *objProc, void *clientData, Tcl_Size objc, Tcl_Obj *const objv[]); /* 588 */ unsigned (*tcl_GetFSDeviceFromStat) (const Tcl_StatBuf *statPtr); /* 589 */ unsigned (*tcl_GetFSInodeFromStat) (const Tcl_StatBuf *statPtr); /* 590 */ unsigned (*tcl_GetModeFromStat) (const Tcl_StatBuf *statPtr); /* 591 */ int (*tcl_GetLinkCountFromStat) (const Tcl_StatBuf *statPtr); /* 592 */ int (*tcl_GetUserIdFromStat) (const Tcl_StatBuf *statPtr); /* 593 */ int (*tcl_GetGroupIdFromStat) (const Tcl_StatBuf *statPtr); /* 594 */ int (*tcl_GetDeviceTypeFromStat) (const Tcl_StatBuf *statPtr); /* 595 */ long long (*tcl_GetAccessTimeFromStat) (const Tcl_StatBuf *statPtr); /* 596 */ long long (*tcl_GetModificationTimeFromStat) (const Tcl_StatBuf *statPtr); /* 597 */ long long (*tcl_GetChangeTimeFromStat) (const Tcl_StatBuf *statPtr); /* 598 */ unsigned long long (*tcl_GetSizeFromStat) (const Tcl_StatBuf *statPtr); /* 599 */ unsigned long long (*tcl_GetBlocksFromStat) (const Tcl_StatBuf *statPtr); /* 600 */ unsigned (*tcl_GetBlockSizeFromStat) (const Tcl_StatBuf *statPtr); /* 601 */ int (*tcl_SetEnsembleParameterList) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *paramList); /* 602 */ int (*tcl_GetEnsembleParameterList) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **paramListPtr); /* 603 */ int (*tclParseArgsObjv) (Tcl_Interp *interp, const Tcl_ArgvInfo *argTable, void *objcPtr, Tcl_Obj *const *objv, Tcl_Obj ***remObjv); /* 604 */ int (*tcl_GetErrorLine) (Tcl_Interp *interp); /* 605 */ void (*tcl_SetErrorLine) (Tcl_Interp *interp, int lineNum); /* 606 */ void (*tcl_TransferResult) (Tcl_Interp *sourceInterp, int code, Tcl_Interp *targetInterp); /* 607 */ int (*tcl_InterpActive) (Tcl_Interp *interp); /* 608 */ void (*tcl_BackgroundException) (Tcl_Interp *interp, int code); /* 609 */ int (*tcl_ZlibDeflate) (Tcl_Interp *interp, int format, Tcl_Obj *data, int level, Tcl_Obj *gzipHeaderDictObj); /* 610 */ int (*tcl_ZlibInflate) (Tcl_Interp *interp, int format, Tcl_Obj *data, Tcl_Size buffersize, Tcl_Obj *gzipHeaderDictObj); /* 611 */ unsigned int (*tcl_ZlibCRC32) (unsigned int crc, const unsigned char *buf, Tcl_Size len); /* 612 */ unsigned int (*tcl_ZlibAdler32) (unsigned int adler, const unsigned char *buf, Tcl_Size len); /* 613 */ int (*tcl_ZlibStreamInit) (Tcl_Interp *interp, int mode, int format, int level, Tcl_Obj *dictObj, Tcl_ZlibStream *zshandle); /* 614 */ Tcl_Obj * (*tcl_ZlibStreamGetCommandName) (Tcl_ZlibStream zshandle); /* 615 */ int (*tcl_ZlibStreamEof) (Tcl_ZlibStream zshandle); /* 616 */ int (*tcl_ZlibStreamChecksum) (Tcl_ZlibStream zshandle); /* 617 */ int (*tcl_ZlibStreamPut) (Tcl_ZlibStream zshandle, Tcl_Obj *data, int flush); /* 618 */ int (*tcl_ZlibStreamGet) (Tcl_ZlibStream zshandle, Tcl_Obj *data, Tcl_Size count); /* 619 */ int (*tcl_ZlibStreamClose) (Tcl_ZlibStream zshandle); /* 620 */ int (*tcl_ZlibStreamReset) (Tcl_ZlibStream zshandle); /* 621 */ void (*tcl_SetStartupScript) (Tcl_Obj *path, const char *encoding); /* 622 */ Tcl_Obj * (*tcl_GetStartupScript) (const char **encodingPtr); /* 623 */ int (*tcl_CloseEx) (Tcl_Interp *interp, Tcl_Channel chan, int flags); /* 624 */ int (*tcl_NRExprObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj *resultPtr); /* 625 */ int (*tcl_NRSubstObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 626 */ int (*tcl_LoadFile) (Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *const symv[], int flags, void *procPtrs, Tcl_LoadHandle *handlePtr); /* 627 */ void * (*tcl_FindSymbol) (Tcl_Interp *interp, Tcl_LoadHandle handle, const char *symbol); /* 628 */ int (*tcl_FSUnloadFile) (Tcl_Interp *interp, Tcl_LoadHandle handlePtr); /* 629 */ void (*tcl_ZlibStreamSetCompressionDictionary) (Tcl_ZlibStream zhandle, Tcl_Obj *compressionDictionaryObj); /* 630 */ Tcl_Channel (*tcl_OpenTcpServerEx) (Tcl_Interp *interp, const char *service, const char *host, unsigned int flags, int backlog, Tcl_TcpAcceptProc *acceptProc, void *callbackData); /* 631 */ int (*tclZipfs_Mount) (Tcl_Interp *interp, const char *zipname, const char *mountPoint, const char *passwd); /* 632 */ int (*tclZipfs_Unmount) (Tcl_Interp *interp, const char *mountPoint); /* 633 */ Tcl_Obj * (*tclZipfs_TclLibrary) (void); /* 634 */ int (*tclZipfs_MountBuffer) (Tcl_Interp *interp, const void *data, size_t datalen, const char *mountPoint, int copy); /* 635 */ void (*tcl_FreeInternalRep) (Tcl_Obj *objPtr); /* 636 */ char * (*tcl_InitStringRep) (Tcl_Obj *objPtr, const char *bytes, TCL_HASH_TYPE numBytes); /* 637 */ Tcl_ObjInternalRep * (*tcl_FetchInternalRep) (Tcl_Obj *objPtr, const Tcl_ObjType *typePtr); /* 638 */ void (*tcl_StoreInternalRep) (Tcl_Obj *objPtr, const Tcl_ObjType *typePtr, const Tcl_ObjInternalRep *irPtr); /* 639 */ int (*tcl_HasStringRep) (Tcl_Obj *objPtr); /* 640 */ void (*tcl_IncrRefCount) (Tcl_Obj *objPtr); /* 641 */ void (*tcl_DecrRefCount) (Tcl_Obj *objPtr); /* 642 */ int (*tcl_IsShared) (Tcl_Obj *objPtr); /* 643 */ int (*tcl_LinkArray) (Tcl_Interp *interp, const char *varName, void *addr, int type, Tcl_Size size); /* 644 */ int (*tcl_GetIntForIndex) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size endValue, Tcl_Size *indexPtr); /* 645 */ Tcl_Size (*tcl_UtfToUniChar) (const char *src, int *chPtr); /* 646 */ char * (*tcl_UniCharToUtfDString) (const int *uniStr, Tcl_Size uniLength, Tcl_DString *dsPtr); /* 647 */ int * (*tcl_UtfToUniCharDString) (const char *src, Tcl_Size length, Tcl_DString *dsPtr); /* 648 */ unsigned char * (*tclGetBytesFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, void *numBytesPtr); /* 649 */ unsigned char * (*tcl_GetBytesFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size *numBytesPtr); /* 650 */ char * (*tcl_GetStringFromObj) (Tcl_Obj *objPtr, Tcl_Size *lengthPtr); /* 651 */ Tcl_UniChar * (*tcl_GetUnicodeFromObj) (Tcl_Obj *objPtr, Tcl_Size *lengthPtr); /* 652 */ int (*tcl_GetSizeIntFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size *sizePtr); /* 653 */ int (*tcl_UtfCharComplete) (const char *src, Tcl_Size length); /* 654 */ const char * (*tcl_UtfNext) (const char *src); /* 655 */ const char * (*tcl_UtfPrev) (const char *src, const char *start); /* 656 */ int (*tcl_FSTildeExpand) (Tcl_Interp *interp, const char *path, Tcl_DString *dsPtr); /* 657 */ int (*tcl_ExternalToUtfDStringEx) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr, Tcl_Size *errorLocationPtr); /* 658 */ int (*tcl_UtfToExternalDStringEx) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr, Tcl_Size *errorLocationPtr); /* 659 */ int (*tcl_AsyncMarkFromSignal) (Tcl_AsyncHandler async, int sigNumber); /* 660 */ int (*tcl_ListObjGetElements) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size *objcPtr, Tcl_Obj ***objvPtr); /* 661 */ int (*tcl_ListObjLength) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size *lengthPtr); /* 662 */ int (*tcl_DictObjSize) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size *sizePtr); /* 663 */ int (*tcl_SplitList) (Tcl_Interp *interp, const char *listStr, Tcl_Size *argcPtr, const char ***argvPtr); /* 664 */ void (*tcl_SplitPath) (const char *path, Tcl_Size *argcPtr, const char ***argvPtr); /* 665 */ Tcl_Obj * (*tcl_FSSplitPath) (Tcl_Obj *pathPtr, Tcl_Size *lenPtr); /* 666 */ int (*tcl_ParseArgsObjv) (Tcl_Interp *interp, const Tcl_ArgvInfo *argTable, Tcl_Size *objcPtr, Tcl_Obj *const *objv, Tcl_Obj ***remObjv); /* 667 */ Tcl_Size (*tcl_UniCharLen) (const int *uniStr); /* 668 */ Tcl_Size (*tcl_NumUtfChars) (const char *src, Tcl_Size length); /* 669 */ Tcl_Size (*tcl_GetCharLength) (Tcl_Obj *objPtr); /* 670 */ const char * (*tcl_UtfAtIndex) (const char *src, Tcl_Size index); /* 671 */ Tcl_Obj * (*tcl_GetRange) (Tcl_Obj *objPtr, Tcl_Size first, Tcl_Size last); /* 672 */ int (*tcl_GetUniChar) (Tcl_Obj *objPtr, Tcl_Size index); /* 673 */ int (*tcl_GetBool) (Tcl_Interp *interp, const char *src, int flags, char *charPtr); /* 674 */ int (*tcl_GetBoolFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags, char *charPtr); /* 675 */ Tcl_Command (*tcl_CreateObjCommand2) (Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc2 *proc2, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 676 */ Tcl_Trace (*tcl_CreateObjTrace2) (Tcl_Interp *interp, Tcl_Size level, int flags, Tcl_CmdObjTraceProc2 *objProc2, void *clientData, Tcl_CmdObjTraceDeleteProc *delProc); /* 677 */ Tcl_Command (*tcl_NRCreateCommand2) (Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc2 *proc, Tcl_ObjCmdProc2 *nreProc2, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 678 */ int (*tcl_NRCallObjProc2) (Tcl_Interp *interp, Tcl_ObjCmdProc2 *objProc2, void *clientData, Tcl_Size objc, Tcl_Obj *const objv[]); /* 679 */ int (*tcl_GetNumberFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, void **clientDataPtr, int *typePtr); /* 680 */ int (*tcl_GetNumber) (Tcl_Interp *interp, const char *bytes, Tcl_Size numBytes, void **clientDataPtr, int *typePtr); /* 681 */ int (*tcl_RemoveChannelMode) (Tcl_Interp *interp, Tcl_Channel chan, int mode); /* 682 */ Tcl_Size (*tcl_GetEncodingNulLength) (Tcl_Encoding encoding); /* 683 */ int (*tcl_GetWideUIntFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_WideUInt *uwidePtr); /* 684 */ Tcl_Obj * (*tcl_DStringToObj) (Tcl_DString *dsPtr); /* 685 */ int (*tcl_UtfNcmp) (const char *s1, const char *s2, size_t n); /* 686 */ int (*tcl_UtfNcasecmp) (const char *s1, const char *s2, size_t n); /* 687 */ Tcl_Obj * (*tcl_NewWideUIntObj) (Tcl_WideUInt wideValue); /* 688 */ void (*tcl_SetWideUIntObj) (Tcl_Obj *objPtr, Tcl_WideUInt uwideValue); /* 689 */ void (*tclUnusedStubEntry) (void); /* 690 */ } TclStubs; extern const TclStubs *tclStubsPtr; #ifdef __cplusplus } #endif #if defined(USE_TCL_STUBS) /* * Inline function declarations: */ #define Tcl_PkgProvideEx \ (tclStubsPtr->tcl_PkgProvideEx) /* 0 */ #define Tcl_PkgRequireEx \ (tclStubsPtr->tcl_PkgRequireEx) /* 1 */ #define Tcl_Panic \ (tclStubsPtr->tcl_Panic) /* 2 */ #define Tcl_Alloc \ (tclStubsPtr->tcl_Alloc) /* 3 */ #define Tcl_Free \ (tclStubsPtr->tcl_Free) /* 4 */ #define Tcl_Realloc \ (tclStubsPtr->tcl_Realloc) /* 5 */ #define Tcl_DbCkalloc \ (tclStubsPtr->tcl_DbCkalloc) /* 6 */ #define Tcl_DbCkfree \ (tclStubsPtr->tcl_DbCkfree) /* 7 */ #define Tcl_DbCkrealloc \ (tclStubsPtr->tcl_DbCkrealloc) /* 8 */ #define Tcl_CreateFileHandler \ (tclStubsPtr->tcl_CreateFileHandler) /* 9 */ #define Tcl_DeleteFileHandler \ (tclStubsPtr->tcl_DeleteFileHandler) /* 10 */ #define Tcl_SetTimer \ (tclStubsPtr->tcl_SetTimer) /* 11 */ #define Tcl_Sleep \ (tclStubsPtr->tcl_Sleep) /* 12 */ #define Tcl_WaitForEvent \ (tclStubsPtr->tcl_WaitForEvent) /* 13 */ #define Tcl_AppendAllObjTypes \ (tclStubsPtr->tcl_AppendAllObjTypes) /* 14 */ #define Tcl_AppendStringsToObj \ (tclStubsPtr->tcl_AppendStringsToObj) /* 15 */ #define Tcl_AppendToObj \ (tclStubsPtr->tcl_AppendToObj) /* 16 */ #define Tcl_ConcatObj \ (tclStubsPtr->tcl_ConcatObj) /* 17 */ #define Tcl_ConvertToType \ (tclStubsPtr->tcl_ConvertToType) /* 18 */ #define Tcl_DbDecrRefCount \ (tclStubsPtr->tcl_DbDecrRefCount) /* 19 */ #define Tcl_DbIncrRefCount \ (tclStubsPtr->tcl_DbIncrRefCount) /* 20 */ #define Tcl_DbIsShared \ (tclStubsPtr->tcl_DbIsShared) /* 21 */ /* Slot 22 is reserved */ #define Tcl_DbNewByteArrayObj \ (tclStubsPtr->tcl_DbNewByteArrayObj) /* 23 */ #define Tcl_DbNewDoubleObj \ (tclStubsPtr->tcl_DbNewDoubleObj) /* 24 */ #define Tcl_DbNewListObj \ (tclStubsPtr->tcl_DbNewListObj) /* 25 */ /* Slot 26 is reserved */ #define Tcl_DbNewObj \ (tclStubsPtr->tcl_DbNewObj) /* 27 */ #define Tcl_DbNewStringObj \ (tclStubsPtr->tcl_DbNewStringObj) /* 28 */ #define Tcl_DuplicateObj \ (tclStubsPtr->tcl_DuplicateObj) /* 29 */ #define TclFreeObj \ (tclStubsPtr->tclFreeObj) /* 30 */ #define Tcl_GetBoolean \ (tclStubsPtr->tcl_GetBoolean) /* 31 */ #define Tcl_GetBooleanFromObj \ (tclStubsPtr->tcl_GetBooleanFromObj) /* 32 */ #define Tcl_GetByteArrayFromObj \ (tclStubsPtr->tcl_GetByteArrayFromObj) /* 33 */ #define Tcl_GetDouble \ (tclStubsPtr->tcl_GetDouble) /* 34 */ #define Tcl_GetDoubleFromObj \ (tclStubsPtr->tcl_GetDoubleFromObj) /* 35 */ /* Slot 36 is reserved */ #define Tcl_GetInt \ (tclStubsPtr->tcl_GetInt) /* 37 */ #define Tcl_GetIntFromObj \ (tclStubsPtr->tcl_GetIntFromObj) /* 38 */ #define Tcl_GetLongFromObj \ (tclStubsPtr->tcl_GetLongFromObj) /* 39 */ #define Tcl_GetObjType \ (tclStubsPtr->tcl_GetObjType) /* 40 */ #define TclGetStringFromObj \ (tclStubsPtr->tclGetStringFromObj) /* 41 */ #define Tcl_InvalidateStringRep \ (tclStubsPtr->tcl_InvalidateStringRep) /* 42 */ #define Tcl_ListObjAppendList \ (tclStubsPtr->tcl_ListObjAppendList) /* 43 */ #define Tcl_ListObjAppendElement \ (tclStubsPtr->tcl_ListObjAppendElement) /* 44 */ #define TclListObjGetElements \ (tclStubsPtr->tclListObjGetElements) /* 45 */ #define Tcl_ListObjIndex \ (tclStubsPtr->tcl_ListObjIndex) /* 46 */ #define TclListObjLength \ (tclStubsPtr->tclListObjLength) /* 47 */ #define Tcl_ListObjReplace \ (tclStubsPtr->tcl_ListObjReplace) /* 48 */ /* Slot 49 is reserved */ #define Tcl_NewByteArrayObj \ (tclStubsPtr->tcl_NewByteArrayObj) /* 50 */ #define Tcl_NewDoubleObj \ (tclStubsPtr->tcl_NewDoubleObj) /* 51 */ /* Slot 52 is reserved */ #define Tcl_NewListObj \ (tclStubsPtr->tcl_NewListObj) /* 53 */ /* Slot 54 is reserved */ #define Tcl_NewObj \ (tclStubsPtr->tcl_NewObj) /* 55 */ #define Tcl_NewStringObj \ (tclStubsPtr->tcl_NewStringObj) /* 56 */ /* Slot 57 is reserved */ #define Tcl_SetByteArrayLength \ (tclStubsPtr->tcl_SetByteArrayLength) /* 58 */ #define Tcl_SetByteArrayObj \ (tclStubsPtr->tcl_SetByteArrayObj) /* 59 */ #define Tcl_SetDoubleObj \ (tclStubsPtr->tcl_SetDoubleObj) /* 60 */ /* Slot 61 is reserved */ #define Tcl_SetListObj \ (tclStubsPtr->tcl_SetListObj) /* 62 */ /* Slot 63 is reserved */ #define Tcl_SetObjLength \ (tclStubsPtr->tcl_SetObjLength) /* 64 */ #define Tcl_SetStringObj \ (tclStubsPtr->tcl_SetStringObj) /* 65 */ /* Slot 66 is reserved */ /* Slot 67 is reserved */ #define Tcl_AllowExceptions \ (tclStubsPtr->tcl_AllowExceptions) /* 68 */ #define Tcl_AppendElement \ (tclStubsPtr->tcl_AppendElement) /* 69 */ #define Tcl_AppendResult \ (tclStubsPtr->tcl_AppendResult) /* 70 */ #define Tcl_AsyncCreate \ (tclStubsPtr->tcl_AsyncCreate) /* 71 */ #define Tcl_AsyncDelete \ (tclStubsPtr->tcl_AsyncDelete) /* 72 */ #define Tcl_AsyncInvoke \ (tclStubsPtr->tcl_AsyncInvoke) /* 73 */ #define Tcl_AsyncMark \ (tclStubsPtr->tcl_AsyncMark) /* 74 */ #define Tcl_AsyncReady \ (tclStubsPtr->tcl_AsyncReady) /* 75 */ /* Slot 76 is reserved */ /* Slot 77 is reserved */ #define Tcl_BadChannelOption \ (tclStubsPtr->tcl_BadChannelOption) /* 78 */ #define Tcl_CallWhenDeleted \ (tclStubsPtr->tcl_CallWhenDeleted) /* 79 */ #define Tcl_CancelIdleCall \ (tclStubsPtr->tcl_CancelIdleCall) /* 80 */ #define Tcl_Close \ (tclStubsPtr->tcl_Close) /* 81 */ #define Tcl_CommandComplete \ (tclStubsPtr->tcl_CommandComplete) /* 82 */ #define Tcl_Concat \ (tclStubsPtr->tcl_Concat) /* 83 */ #define Tcl_ConvertElement \ (tclStubsPtr->tcl_ConvertElement) /* 84 */ #define Tcl_ConvertCountedElement \ (tclStubsPtr->tcl_ConvertCountedElement) /* 85 */ #define Tcl_CreateAlias \ (tclStubsPtr->tcl_CreateAlias) /* 86 */ #define Tcl_CreateAliasObj \ (tclStubsPtr->tcl_CreateAliasObj) /* 87 */ #define Tcl_CreateChannel \ (tclStubsPtr->tcl_CreateChannel) /* 88 */ #define Tcl_CreateChannelHandler \ (tclStubsPtr->tcl_CreateChannelHandler) /* 89 */ #define Tcl_CreateCloseHandler \ (tclStubsPtr->tcl_CreateCloseHandler) /* 90 */ #define Tcl_CreateCommand \ (tclStubsPtr->tcl_CreateCommand) /* 91 */ #define Tcl_CreateEventSource \ (tclStubsPtr->tcl_CreateEventSource) /* 92 */ #define Tcl_CreateExitHandler \ (tclStubsPtr->tcl_CreateExitHandler) /* 93 */ #define Tcl_CreateInterp \ (tclStubsPtr->tcl_CreateInterp) /* 94 */ /* Slot 95 is reserved */ #define Tcl_CreateObjCommand \ (tclStubsPtr->tcl_CreateObjCommand) /* 96 */ #define Tcl_CreateChild \ (tclStubsPtr->tcl_CreateChild) /* 97 */ #define Tcl_CreateTimerHandler \ (tclStubsPtr->tcl_CreateTimerHandler) /* 98 */ #define Tcl_CreateTrace \ (tclStubsPtr->tcl_CreateTrace) /* 99 */ #define Tcl_DeleteAssocData \ (tclStubsPtr->tcl_DeleteAssocData) /* 100 */ #define Tcl_DeleteChannelHandler \ (tclStubsPtr->tcl_DeleteChannelHandler) /* 101 */ #define Tcl_DeleteCloseHandler \ (tclStubsPtr->tcl_DeleteCloseHandler) /* 102 */ #define Tcl_DeleteCommand \ (tclStubsPtr->tcl_DeleteCommand) /* 103 */ #define Tcl_DeleteCommandFromToken \ (tclStubsPtr->tcl_DeleteCommandFromToken) /* 104 */ #define Tcl_DeleteEvents \ (tclStubsPtr->tcl_DeleteEvents) /* 105 */ #define Tcl_DeleteEventSource \ (tclStubsPtr->tcl_DeleteEventSource) /* 106 */ #define Tcl_DeleteExitHandler \ (tclStubsPtr->tcl_DeleteExitHandler) /* 107 */ #define Tcl_DeleteHashEntry \ (tclStubsPtr->tcl_DeleteHashEntry) /* 108 */ #define Tcl_DeleteHashTable \ (tclStubsPtr->tcl_DeleteHashTable) /* 109 */ #define Tcl_DeleteInterp \ (tclStubsPtr->tcl_DeleteInterp) /* 110 */ #define Tcl_DetachPids \ (tclStubsPtr->tcl_DetachPids) /* 111 */ #define Tcl_DeleteTimerHandler \ (tclStubsPtr->tcl_DeleteTimerHandler) /* 112 */ #define Tcl_DeleteTrace \ (tclStubsPtr->tcl_DeleteTrace) /* 113 */ #define Tcl_DontCallWhenDeleted \ (tclStubsPtr->tcl_DontCallWhenDeleted) /* 114 */ #define Tcl_DoOneEvent \ (tclStubsPtr->tcl_DoOneEvent) /* 115 */ #define Tcl_DoWhenIdle \ (tclStubsPtr->tcl_DoWhenIdle) /* 116 */ #define Tcl_DStringAppend \ (tclStubsPtr->tcl_DStringAppend) /* 117 */ #define Tcl_DStringAppendElement \ (tclStubsPtr->tcl_DStringAppendElement) /* 118 */ #define Tcl_DStringEndSublist \ (tclStubsPtr->tcl_DStringEndSublist) /* 119 */ #define Tcl_DStringFree \ (tclStubsPtr->tcl_DStringFree) /* 120 */ #define Tcl_DStringGetResult \ (tclStubsPtr->tcl_DStringGetResult) /* 121 */ #define Tcl_DStringInit \ (tclStubsPtr->tcl_DStringInit) /* 122 */ #define Tcl_DStringResult \ (tclStubsPtr->tcl_DStringResult) /* 123 */ #define Tcl_DStringSetLength \ (tclStubsPtr->tcl_DStringSetLength) /* 124 */ #define Tcl_DStringStartSublist \ (tclStubsPtr->tcl_DStringStartSublist) /* 125 */ #define Tcl_Eof \ (tclStubsPtr->tcl_Eof) /* 126 */ #define Tcl_ErrnoId \ (tclStubsPtr->tcl_ErrnoId) /* 127 */ #define Tcl_ErrnoMsg \ (tclStubsPtr->tcl_ErrnoMsg) /* 128 */ /* Slot 129 is reserved */ #define Tcl_EvalFile \ (tclStubsPtr->tcl_EvalFile) /* 130 */ /* Slot 131 is reserved */ #define Tcl_EventuallyFree \ (tclStubsPtr->tcl_EventuallyFree) /* 132 */ #define Tcl_Exit \ (tclStubsPtr->tcl_Exit) /* 133 */ #define Tcl_ExposeCommand \ (tclStubsPtr->tcl_ExposeCommand) /* 134 */ #define Tcl_ExprBoolean \ (tclStubsPtr->tcl_ExprBoolean) /* 135 */ #define Tcl_ExprBooleanObj \ (tclStubsPtr->tcl_ExprBooleanObj) /* 136 */ #define Tcl_ExprDouble \ (tclStubsPtr->tcl_ExprDouble) /* 137 */ #define Tcl_ExprDoubleObj \ (tclStubsPtr->tcl_ExprDoubleObj) /* 138 */ #define Tcl_ExprLong \ (tclStubsPtr->tcl_ExprLong) /* 139 */ #define Tcl_ExprLongObj \ (tclStubsPtr->tcl_ExprLongObj) /* 140 */ #define Tcl_ExprObj \ (tclStubsPtr->tcl_ExprObj) /* 141 */ #define Tcl_ExprString \ (tclStubsPtr->tcl_ExprString) /* 142 */ #define Tcl_Finalize \ (tclStubsPtr->tcl_Finalize) /* 143 */ /* Slot 144 is reserved */ #define Tcl_FirstHashEntry \ (tclStubsPtr->tcl_FirstHashEntry) /* 145 */ #define Tcl_Flush \ (tclStubsPtr->tcl_Flush) /* 146 */ /* Slot 147 is reserved */ /* Slot 148 is reserved */ #define TclGetAliasObj \ (tclStubsPtr->tclGetAliasObj) /* 149 */ #define Tcl_GetAssocData \ (tclStubsPtr->tcl_GetAssocData) /* 150 */ #define Tcl_GetChannel \ (tclStubsPtr->tcl_GetChannel) /* 151 */ #define Tcl_GetChannelBufferSize \ (tclStubsPtr->tcl_GetChannelBufferSize) /* 152 */ #define Tcl_GetChannelHandle \ (tclStubsPtr->tcl_GetChannelHandle) /* 153 */ #define Tcl_GetChannelInstanceData \ (tclStubsPtr->tcl_GetChannelInstanceData) /* 154 */ #define Tcl_GetChannelMode \ (tclStubsPtr->tcl_GetChannelMode) /* 155 */ #define Tcl_GetChannelName \ (tclStubsPtr->tcl_GetChannelName) /* 156 */ #define Tcl_GetChannelOption \ (tclStubsPtr->tcl_GetChannelOption) /* 157 */ #define Tcl_GetChannelType \ (tclStubsPtr->tcl_GetChannelType) /* 158 */ #define Tcl_GetCommandInfo \ (tclStubsPtr->tcl_GetCommandInfo) /* 159 */ #define Tcl_GetCommandName \ (tclStubsPtr->tcl_GetCommandName) /* 160 */ #define Tcl_GetErrno \ (tclStubsPtr->tcl_GetErrno) /* 161 */ #define Tcl_GetHostName \ (tclStubsPtr->tcl_GetHostName) /* 162 */ #define Tcl_GetInterpPath \ (tclStubsPtr->tcl_GetInterpPath) /* 163 */ #define Tcl_GetParent \ (tclStubsPtr->tcl_GetParent) /* 164 */ #define Tcl_GetNameOfExecutable \ (tclStubsPtr->tcl_GetNameOfExecutable) /* 165 */ #define Tcl_GetObjResult \ (tclStubsPtr->tcl_GetObjResult) /* 166 */ #define Tcl_GetOpenFile \ (tclStubsPtr->tcl_GetOpenFile) /* 167 */ #define Tcl_GetPathType \ (tclStubsPtr->tcl_GetPathType) /* 168 */ #define Tcl_Gets \ (tclStubsPtr->tcl_Gets) /* 169 */ #define Tcl_GetsObj \ (tclStubsPtr->tcl_GetsObj) /* 170 */ #define Tcl_GetServiceMode \ (tclStubsPtr->tcl_GetServiceMode) /* 171 */ #define Tcl_GetChild \ (tclStubsPtr->tcl_GetChild) /* 172 */ #define Tcl_GetStdChannel \ (tclStubsPtr->tcl_GetStdChannel) /* 173 */ /* Slot 174 is reserved */ /* Slot 175 is reserved */ #define Tcl_GetVar2 \ (tclStubsPtr->tcl_GetVar2) /* 176 */ /* Slot 177 is reserved */ /* Slot 178 is reserved */ #define Tcl_HideCommand \ (tclStubsPtr->tcl_HideCommand) /* 179 */ #define Tcl_Init \ (tclStubsPtr->tcl_Init) /* 180 */ #define Tcl_InitHashTable \ (tclStubsPtr->tcl_InitHashTable) /* 181 */ #define Tcl_InputBlocked \ (tclStubsPtr->tcl_InputBlocked) /* 182 */ #define Tcl_InputBuffered \ (tclStubsPtr->tcl_InputBuffered) /* 183 */ #define Tcl_InterpDeleted \ (tclStubsPtr->tcl_InterpDeleted) /* 184 */ #define Tcl_IsSafe \ (tclStubsPtr->tcl_IsSafe) /* 185 */ #define Tcl_JoinPath \ (tclStubsPtr->tcl_JoinPath) /* 186 */ #define Tcl_LinkVar \ (tclStubsPtr->tcl_LinkVar) /* 187 */ /* Slot 188 is reserved */ #define Tcl_MakeFileChannel \ (tclStubsPtr->tcl_MakeFileChannel) /* 189 */ /* Slot 190 is reserved */ #define Tcl_MakeTcpClientChannel \ (tclStubsPtr->tcl_MakeTcpClientChannel) /* 191 */ #define Tcl_Merge \ (tclStubsPtr->tcl_Merge) /* 192 */ #define Tcl_NextHashEntry \ (tclStubsPtr->tcl_NextHashEntry) /* 193 */ #define Tcl_NotifyChannel \ (tclStubsPtr->tcl_NotifyChannel) /* 194 */ #define Tcl_ObjGetVar2 \ (tclStubsPtr->tcl_ObjGetVar2) /* 195 */ #define Tcl_ObjSetVar2 \ (tclStubsPtr->tcl_ObjSetVar2) /* 196 */ #define Tcl_OpenCommandChannel \ (tclStubsPtr->tcl_OpenCommandChannel) /* 197 */ #define Tcl_OpenFileChannel \ (tclStubsPtr->tcl_OpenFileChannel) /* 198 */ #define Tcl_OpenTcpClient \ (tclStubsPtr->tcl_OpenTcpClient) /* 199 */ #define Tcl_OpenTcpServer \ (tclStubsPtr->tcl_OpenTcpServer) /* 200 */ #define Tcl_Preserve \ (tclStubsPtr->tcl_Preserve) /* 201 */ #define Tcl_PrintDouble \ (tclStubsPtr->tcl_PrintDouble) /* 202 */ #define Tcl_PutEnv \ (tclStubsPtr->tcl_PutEnv) /* 203 */ #define Tcl_PosixError \ (tclStubsPtr->tcl_PosixError) /* 204 */ #define Tcl_QueueEvent \ (tclStubsPtr->tcl_QueueEvent) /* 205 */ #define Tcl_Read \ (tclStubsPtr->tcl_Read) /* 206 */ #define Tcl_ReapDetachedProcs \ (tclStubsPtr->tcl_ReapDetachedProcs) /* 207 */ #define Tcl_RecordAndEval \ (tclStubsPtr->tcl_RecordAndEval) /* 208 */ #define Tcl_RecordAndEvalObj \ (tclStubsPtr->tcl_RecordAndEvalObj) /* 209 */ #define Tcl_RegisterChannel \ (tclStubsPtr->tcl_RegisterChannel) /* 210 */ #define Tcl_RegisterObjType \ (tclStubsPtr->tcl_RegisterObjType) /* 211 */ #define Tcl_RegExpCompile \ (tclStubsPtr->tcl_RegExpCompile) /* 212 */ #define Tcl_RegExpExec \ (tclStubsPtr->tcl_RegExpExec) /* 213 */ #define Tcl_RegExpMatch \ (tclStubsPtr->tcl_RegExpMatch) /* 214 */ #define Tcl_RegExpRange \ (tclStubsPtr->tcl_RegExpRange) /* 215 */ #define Tcl_Release \ (tclStubsPtr->tcl_Release) /* 216 */ #define Tcl_ResetResult \ (tclStubsPtr->tcl_ResetResult) /* 217 */ #define Tcl_ScanElement \ (tclStubsPtr->tcl_ScanElement) /* 218 */ #define Tcl_ScanCountedElement \ (tclStubsPtr->tcl_ScanCountedElement) /* 219 */ /* Slot 220 is reserved */ #define Tcl_ServiceAll \ (tclStubsPtr->tcl_ServiceAll) /* 221 */ #define Tcl_ServiceEvent \ (tclStubsPtr->tcl_ServiceEvent) /* 222 */ #define Tcl_SetAssocData \ (tclStubsPtr->tcl_SetAssocData) /* 223 */ #define Tcl_SetChannelBufferSize \ (tclStubsPtr->tcl_SetChannelBufferSize) /* 224 */ #define Tcl_SetChannelOption \ (tclStubsPtr->tcl_SetChannelOption) /* 225 */ #define Tcl_SetCommandInfo \ (tclStubsPtr->tcl_SetCommandInfo) /* 226 */ #define Tcl_SetErrno \ (tclStubsPtr->tcl_SetErrno) /* 227 */ #define Tcl_SetErrorCode \ (tclStubsPtr->tcl_SetErrorCode) /* 228 */ #define Tcl_SetMaxBlockTime \ (tclStubsPtr->tcl_SetMaxBlockTime) /* 229 */ /* Slot 230 is reserved */ #define Tcl_SetRecursionLimit \ (tclStubsPtr->tcl_SetRecursionLimit) /* 231 */ /* Slot 232 is reserved */ #define Tcl_SetServiceMode \ (tclStubsPtr->tcl_SetServiceMode) /* 233 */ #define Tcl_SetObjErrorCode \ (tclStubsPtr->tcl_SetObjErrorCode) /* 234 */ #define Tcl_SetObjResult \ (tclStubsPtr->tcl_SetObjResult) /* 235 */ #define Tcl_SetStdChannel \ (tclStubsPtr->tcl_SetStdChannel) /* 236 */ /* Slot 237 is reserved */ #define Tcl_SetVar2 \ (tclStubsPtr->tcl_SetVar2) /* 238 */ #define Tcl_SignalId \ (tclStubsPtr->tcl_SignalId) /* 239 */ #define Tcl_SignalMsg \ (tclStubsPtr->tcl_SignalMsg) /* 240 */ #define Tcl_SourceRCFile \ (tclStubsPtr->tcl_SourceRCFile) /* 241 */ #define TclSplitList \ (tclStubsPtr->tclSplitList) /* 242 */ #define TclSplitPath \ (tclStubsPtr->tclSplitPath) /* 243 */ /* Slot 244 is reserved */ /* Slot 245 is reserved */ /* Slot 246 is reserved */ /* Slot 247 is reserved */ #define Tcl_TraceVar2 \ (tclStubsPtr->tcl_TraceVar2) /* 248 */ #define Tcl_TranslateFileName \ (tclStubsPtr->tcl_TranslateFileName) /* 249 */ #define Tcl_Ungets \ (tclStubsPtr->tcl_Ungets) /* 250 */ #define Tcl_UnlinkVar \ (tclStubsPtr->tcl_UnlinkVar) /* 251 */ #define Tcl_UnregisterChannel \ (tclStubsPtr->tcl_UnregisterChannel) /* 252 */ /* Slot 253 is reserved */ #define Tcl_UnsetVar2 \ (tclStubsPtr->tcl_UnsetVar2) /* 254 */ /* Slot 255 is reserved */ #define Tcl_UntraceVar2 \ (tclStubsPtr->tcl_UntraceVar2) /* 256 */ #define Tcl_UpdateLinkedVar \ (tclStubsPtr->tcl_UpdateLinkedVar) /* 257 */ /* Slot 258 is reserved */ #define Tcl_UpVar2 \ (tclStubsPtr->tcl_UpVar2) /* 259 */ #define Tcl_VarEval \ (tclStubsPtr->tcl_VarEval) /* 260 */ /* Slot 261 is reserved */ #define Tcl_VarTraceInfo2 \ (tclStubsPtr->tcl_VarTraceInfo2) /* 262 */ #define Tcl_Write \ (tclStubsPtr->tcl_Write) /* 263 */ #define Tcl_WrongNumArgs \ (tclStubsPtr->tcl_WrongNumArgs) /* 264 */ #define Tcl_DumpActiveMemory \ (tclStubsPtr->tcl_DumpActiveMemory) /* 265 */ #define Tcl_ValidateAllMemory \ (tclStubsPtr->tcl_ValidateAllMemory) /* 266 */ /* Slot 267 is reserved */ /* Slot 268 is reserved */ #define Tcl_HashStats \ (tclStubsPtr->tcl_HashStats) /* 269 */ #define Tcl_ParseVar \ (tclStubsPtr->tcl_ParseVar) /* 270 */ /* Slot 271 is reserved */ #define Tcl_PkgPresentEx \ (tclStubsPtr->tcl_PkgPresentEx) /* 272 */ /* Slot 273 is reserved */ /* Slot 274 is reserved */ /* Slot 275 is reserved */ /* Slot 276 is reserved */ #define Tcl_WaitPid \ (tclStubsPtr->tcl_WaitPid) /* 277 */ /* Slot 278 is reserved */ #define Tcl_GetVersion \ (tclStubsPtr->tcl_GetVersion) /* 279 */ #define Tcl_InitMemory \ (tclStubsPtr->tcl_InitMemory) /* 280 */ #define Tcl_StackChannel \ (tclStubsPtr->tcl_StackChannel) /* 281 */ #define Tcl_UnstackChannel \ (tclStubsPtr->tcl_UnstackChannel) /* 282 */ #define Tcl_GetStackedChannel \ (tclStubsPtr->tcl_GetStackedChannel) /* 283 */ #define Tcl_SetMainLoop \ (tclStubsPtr->tcl_SetMainLoop) /* 284 */ #define Tcl_GetAliasObj \ (tclStubsPtr->tcl_GetAliasObj) /* 285 */ #define Tcl_AppendObjToObj \ (tclStubsPtr->tcl_AppendObjToObj) /* 286 */ #define Tcl_CreateEncoding \ (tclStubsPtr->tcl_CreateEncoding) /* 287 */ #define Tcl_CreateThreadExitHandler \ (tclStubsPtr->tcl_CreateThreadExitHandler) /* 288 */ #define Tcl_DeleteThreadExitHandler \ (tclStubsPtr->tcl_DeleteThreadExitHandler) /* 289 */ /* Slot 290 is reserved */ #define Tcl_EvalEx \ (tclStubsPtr->tcl_EvalEx) /* 291 */ #define Tcl_EvalObjv \ (tclStubsPtr->tcl_EvalObjv) /* 292 */ #define Tcl_EvalObjEx \ (tclStubsPtr->tcl_EvalObjEx) /* 293 */ #define Tcl_ExitThread \ (tclStubsPtr->tcl_ExitThread) /* 294 */ #define Tcl_ExternalToUtf \ (tclStubsPtr->tcl_ExternalToUtf) /* 295 */ #define Tcl_ExternalToUtfDString \ (tclStubsPtr->tcl_ExternalToUtfDString) /* 296 */ #define Tcl_FinalizeThread \ (tclStubsPtr->tcl_FinalizeThread) /* 297 */ #define Tcl_FinalizeNotifier \ (tclStubsPtr->tcl_FinalizeNotifier) /* 298 */ #define Tcl_FreeEncoding \ (tclStubsPtr->tcl_FreeEncoding) /* 299 */ #define Tcl_GetCurrentThread \ (tclStubsPtr->tcl_GetCurrentThread) /* 300 */ #define Tcl_GetEncoding \ (tclStubsPtr->tcl_GetEncoding) /* 301 */ #define Tcl_GetEncodingName \ (tclStubsPtr->tcl_GetEncodingName) /* 302 */ #define Tcl_GetEncodingNames \ (tclStubsPtr->tcl_GetEncodingNames) /* 303 */ #define Tcl_GetIndexFromObjStruct \ (tclStubsPtr->tcl_GetIndexFromObjStruct) /* 304 */ #define Tcl_GetThreadData \ (tclStubsPtr->tcl_GetThreadData) /* 305 */ #define Tcl_GetVar2Ex \ (tclStubsPtr->tcl_GetVar2Ex) /* 306 */ #define Tcl_InitNotifier \ (tclStubsPtr->tcl_InitNotifier) /* 307 */ #define Tcl_MutexLock \ (tclStubsPtr->tcl_MutexLock) /* 308 */ #define Tcl_MutexUnlock \ (tclStubsPtr->tcl_MutexUnlock) /* 309 */ #define Tcl_ConditionNotify \ (tclStubsPtr->tcl_ConditionNotify) /* 310 */ #define Tcl_ConditionWait \ (tclStubsPtr->tcl_ConditionWait) /* 311 */ #define TclNumUtfChars \ (tclStubsPtr->tclNumUtfChars) /* 312 */ #define Tcl_ReadChars \ (tclStubsPtr->tcl_ReadChars) /* 313 */ /* Slot 314 is reserved */ /* Slot 315 is reserved */ #define Tcl_SetSystemEncoding \ (tclStubsPtr->tcl_SetSystemEncoding) /* 316 */ #define Tcl_SetVar2Ex \ (tclStubsPtr->tcl_SetVar2Ex) /* 317 */ #define Tcl_ThreadAlert \ (tclStubsPtr->tcl_ThreadAlert) /* 318 */ #define Tcl_ThreadQueueEvent \ (tclStubsPtr->tcl_ThreadQueueEvent) /* 319 */ #define Tcl_UniCharAtIndex \ (tclStubsPtr->tcl_UniCharAtIndex) /* 320 */ #define Tcl_UniCharToLower \ (tclStubsPtr->tcl_UniCharToLower) /* 321 */ #define Tcl_UniCharToTitle \ (tclStubsPtr->tcl_UniCharToTitle) /* 322 */ #define Tcl_UniCharToUpper \ (tclStubsPtr->tcl_UniCharToUpper) /* 323 */ #define Tcl_UniCharToUtf \ (tclStubsPtr->tcl_UniCharToUtf) /* 324 */ #define TclUtfAtIndex \ (tclStubsPtr->tclUtfAtIndex) /* 325 */ #define TclUtfCharComplete \ (tclStubsPtr->tclUtfCharComplete) /* 326 */ #define Tcl_UtfBackslash \ (tclStubsPtr->tcl_UtfBackslash) /* 327 */ #define Tcl_UtfFindFirst \ (tclStubsPtr->tcl_UtfFindFirst) /* 328 */ #define Tcl_UtfFindLast \ (tclStubsPtr->tcl_UtfFindLast) /* 329 */ #define TclUtfNext \ (tclStubsPtr->tclUtfNext) /* 330 */ #define TclUtfPrev \ (tclStubsPtr->tclUtfPrev) /* 331 */ #define Tcl_UtfToExternal \ (tclStubsPtr->tcl_UtfToExternal) /* 332 */ #define Tcl_UtfToExternalDString \ (tclStubsPtr->tcl_UtfToExternalDString) /* 333 */ #define Tcl_UtfToLower \ (tclStubsPtr->tcl_UtfToLower) /* 334 */ #define Tcl_UtfToTitle \ (tclStubsPtr->tcl_UtfToTitle) /* 335 */ #define Tcl_UtfToChar16 \ (tclStubsPtr->tcl_UtfToChar16) /* 336 */ #define Tcl_UtfToUpper \ (tclStubsPtr->tcl_UtfToUpper) /* 337 */ #define Tcl_WriteChars \ (tclStubsPtr->tcl_WriteChars) /* 338 */ #define Tcl_WriteObj \ (tclStubsPtr->tcl_WriteObj) /* 339 */ #define Tcl_GetString \ (tclStubsPtr->tcl_GetString) /* 340 */ /* Slot 341 is reserved */ /* Slot 342 is reserved */ #define Tcl_AlertNotifier \ (tclStubsPtr->tcl_AlertNotifier) /* 343 */ #define Tcl_ServiceModeHook \ (tclStubsPtr->tcl_ServiceModeHook) /* 344 */ #define Tcl_UniCharIsAlnum \ (tclStubsPtr->tcl_UniCharIsAlnum) /* 345 */ #define Tcl_UniCharIsAlpha \ (tclStubsPtr->tcl_UniCharIsAlpha) /* 346 */ #define Tcl_UniCharIsDigit \ (tclStubsPtr->tcl_UniCharIsDigit) /* 347 */ #define Tcl_UniCharIsLower \ (tclStubsPtr->tcl_UniCharIsLower) /* 348 */ #define Tcl_UniCharIsSpace \ (tclStubsPtr->tcl_UniCharIsSpace) /* 349 */ #define Tcl_UniCharIsUpper \ (tclStubsPtr->tcl_UniCharIsUpper) /* 350 */ #define Tcl_UniCharIsWordChar \ (tclStubsPtr->tcl_UniCharIsWordChar) /* 351 */ #define Tcl_Char16Len \ (tclStubsPtr->tcl_Char16Len) /* 352 */ /* Slot 353 is reserved */ #define Tcl_Char16ToUtfDString \ (tclStubsPtr->tcl_Char16ToUtfDString) /* 354 */ #define Tcl_UtfToChar16DString \ (tclStubsPtr->tcl_UtfToChar16DString) /* 355 */ #define Tcl_GetRegExpFromObj \ (tclStubsPtr->tcl_GetRegExpFromObj) /* 356 */ /* Slot 357 is reserved */ #define Tcl_FreeParse \ (tclStubsPtr->tcl_FreeParse) /* 358 */ #define Tcl_LogCommandInfo \ (tclStubsPtr->tcl_LogCommandInfo) /* 359 */ #define Tcl_ParseBraces \ (tclStubsPtr->tcl_ParseBraces) /* 360 */ #define Tcl_ParseCommand \ (tclStubsPtr->tcl_ParseCommand) /* 361 */ #define Tcl_ParseExpr \ (tclStubsPtr->tcl_ParseExpr) /* 362 */ #define Tcl_ParseQuotedString \ (tclStubsPtr->tcl_ParseQuotedString) /* 363 */ #define Tcl_ParseVarName \ (tclStubsPtr->tcl_ParseVarName) /* 364 */ #define Tcl_GetCwd \ (tclStubsPtr->tcl_GetCwd) /* 365 */ #define Tcl_Chdir \ (tclStubsPtr->tcl_Chdir) /* 366 */ #define Tcl_Access \ (tclStubsPtr->tcl_Access) /* 367 */ #define Tcl_Stat \ (tclStubsPtr->tcl_Stat) /* 368 */ #define TclUtfNcmp \ (tclStubsPtr->tclUtfNcmp) /* 369 */ #define TclUtfNcasecmp \ (tclStubsPtr->tclUtfNcasecmp) /* 370 */ #define Tcl_StringCaseMatch \ (tclStubsPtr->tcl_StringCaseMatch) /* 371 */ #define Tcl_UniCharIsControl \ (tclStubsPtr->tcl_UniCharIsControl) /* 372 */ #define Tcl_UniCharIsGraph \ (tclStubsPtr->tcl_UniCharIsGraph) /* 373 */ #define Tcl_UniCharIsPrint \ (tclStubsPtr->tcl_UniCharIsPrint) /* 374 */ #define Tcl_UniCharIsPunct \ (tclStubsPtr->tcl_UniCharIsPunct) /* 375 */ #define Tcl_RegExpExecObj \ (tclStubsPtr->tcl_RegExpExecObj) /* 376 */ #define Tcl_RegExpGetInfo \ (tclStubsPtr->tcl_RegExpGetInfo) /* 377 */ #define Tcl_NewUnicodeObj \ (tclStubsPtr->tcl_NewUnicodeObj) /* 378 */ #define Tcl_SetUnicodeObj \ (tclStubsPtr->tcl_SetUnicodeObj) /* 379 */ #define TclGetCharLength \ (tclStubsPtr->tclGetCharLength) /* 380 */ #define TclGetUniChar \ (tclStubsPtr->tclGetUniChar) /* 381 */ /* Slot 382 is reserved */ #define TclGetRange \ (tclStubsPtr->tclGetRange) /* 383 */ #define Tcl_AppendUnicodeToObj \ (tclStubsPtr->tcl_AppendUnicodeToObj) /* 384 */ #define Tcl_RegExpMatchObj \ (tclStubsPtr->tcl_RegExpMatchObj) /* 385 */ #define Tcl_SetNotifier \ (tclStubsPtr->tcl_SetNotifier) /* 386 */ #define Tcl_GetAllocMutex \ (tclStubsPtr->tcl_GetAllocMutex) /* 387 */ #define Tcl_GetChannelNames \ (tclStubsPtr->tcl_GetChannelNames) /* 388 */ #define Tcl_GetChannelNamesEx \ (tclStubsPtr->tcl_GetChannelNamesEx) /* 389 */ #define Tcl_ProcObjCmd \ (tclStubsPtr->tcl_ProcObjCmd) /* 390 */ #define Tcl_ConditionFinalize \ (tclStubsPtr->tcl_ConditionFinalize) /* 391 */ #define Tcl_MutexFinalize \ (tclStubsPtr->tcl_MutexFinalize) /* 392 */ #define Tcl_CreateThread \ (tclStubsPtr->tcl_CreateThread) /* 393 */ #define Tcl_ReadRaw \ (tclStubsPtr->tcl_ReadRaw) /* 394 */ #define Tcl_WriteRaw \ (tclStubsPtr->tcl_WriteRaw) /* 395 */ #define Tcl_GetTopChannel \ (tclStubsPtr->tcl_GetTopChannel) /* 396 */ #define Tcl_ChannelBuffered \ (tclStubsPtr->tcl_ChannelBuffered) /* 397 */ #define Tcl_ChannelName \ (tclStubsPtr->tcl_ChannelName) /* 398 */ #define Tcl_ChannelVersion \ (tclStubsPtr->tcl_ChannelVersion) /* 399 */ #define Tcl_ChannelBlockModeProc \ (tclStubsPtr->tcl_ChannelBlockModeProc) /* 400 */ /* Slot 401 is reserved */ #define Tcl_ChannelClose2Proc \ (tclStubsPtr->tcl_ChannelClose2Proc) /* 402 */ #define Tcl_ChannelInputProc \ (tclStubsPtr->tcl_ChannelInputProc) /* 403 */ #define Tcl_ChannelOutputProc \ (tclStubsPtr->tcl_ChannelOutputProc) /* 404 */ /* Slot 405 is reserved */ #define Tcl_ChannelSetOptionProc \ (tclStubsPtr->tcl_ChannelSetOptionProc) /* 406 */ #define Tcl_ChannelGetOptionProc \ (tclStubsPtr->tcl_ChannelGetOptionProc) /* 407 */ #define Tcl_ChannelWatchProc \ (tclStubsPtr->tcl_ChannelWatchProc) /* 408 */ #define Tcl_ChannelGetHandleProc \ (tclStubsPtr->tcl_ChannelGetHandleProc) /* 409 */ #define Tcl_ChannelFlushProc \ (tclStubsPtr->tcl_ChannelFlushProc) /* 410 */ #define Tcl_ChannelHandlerProc \ (tclStubsPtr->tcl_ChannelHandlerProc) /* 411 */ #define Tcl_JoinThread \ (tclStubsPtr->tcl_JoinThread) /* 412 */ #define Tcl_IsChannelShared \ (tclStubsPtr->tcl_IsChannelShared) /* 413 */ #define Tcl_IsChannelRegistered \ (tclStubsPtr->tcl_IsChannelRegistered) /* 414 */ #define Tcl_CutChannel \ (tclStubsPtr->tcl_CutChannel) /* 415 */ #define Tcl_SpliceChannel \ (tclStubsPtr->tcl_SpliceChannel) /* 416 */ #define Tcl_ClearChannelHandlers \ (tclStubsPtr->tcl_ClearChannelHandlers) /* 417 */ #define Tcl_IsChannelExisting \ (tclStubsPtr->tcl_IsChannelExisting) /* 418 */ /* Slot 419 is reserved */ /* Slot 420 is reserved */ /* Slot 421 is reserved */ /* Slot 422 is reserved */ #define Tcl_InitCustomHashTable \ (tclStubsPtr->tcl_InitCustomHashTable) /* 423 */ #define Tcl_InitObjHashTable \ (tclStubsPtr->tcl_InitObjHashTable) /* 424 */ #define Tcl_CommandTraceInfo \ (tclStubsPtr->tcl_CommandTraceInfo) /* 425 */ #define Tcl_TraceCommand \ (tclStubsPtr->tcl_TraceCommand) /* 426 */ #define Tcl_UntraceCommand \ (tclStubsPtr->tcl_UntraceCommand) /* 427 */ #define Tcl_AttemptAlloc \ (tclStubsPtr->tcl_AttemptAlloc) /* 428 */ #define Tcl_AttemptDbCkalloc \ (tclStubsPtr->tcl_AttemptDbCkalloc) /* 429 */ #define Tcl_AttemptRealloc \ (tclStubsPtr->tcl_AttemptRealloc) /* 430 */ #define Tcl_AttemptDbCkrealloc \ (tclStubsPtr->tcl_AttemptDbCkrealloc) /* 431 */ #define Tcl_AttemptSetObjLength \ (tclStubsPtr->tcl_AttemptSetObjLength) /* 432 */ #define Tcl_GetChannelThread \ (tclStubsPtr->tcl_GetChannelThread) /* 433 */ #define TclGetUnicodeFromObj \ (tclStubsPtr->tclGetUnicodeFromObj) /* 434 */ /* Slot 435 is reserved */ /* Slot 436 is reserved */ #define Tcl_SubstObj \ (tclStubsPtr->tcl_SubstObj) /* 437 */ #define Tcl_DetachChannel \ (tclStubsPtr->tcl_DetachChannel) /* 438 */ #define Tcl_IsStandardChannel \ (tclStubsPtr->tcl_IsStandardChannel) /* 439 */ #define Tcl_FSCopyFile \ (tclStubsPtr->tcl_FSCopyFile) /* 440 */ #define Tcl_FSCopyDirectory \ (tclStubsPtr->tcl_FSCopyDirectory) /* 441 */ #define Tcl_FSCreateDirectory \ (tclStubsPtr->tcl_FSCreateDirectory) /* 442 */ #define Tcl_FSDeleteFile \ (tclStubsPtr->tcl_FSDeleteFile) /* 443 */ #define Tcl_FSLoadFile \ (tclStubsPtr->tcl_FSLoadFile) /* 444 */ #define Tcl_FSMatchInDirectory \ (tclStubsPtr->tcl_FSMatchInDirectory) /* 445 */ #define Tcl_FSLink \ (tclStubsPtr->tcl_FSLink) /* 446 */ #define Tcl_FSRemoveDirectory \ (tclStubsPtr->tcl_FSRemoveDirectory) /* 447 */ #define Tcl_FSRenameFile \ (tclStubsPtr->tcl_FSRenameFile) /* 448 */ #define Tcl_FSLstat \ (tclStubsPtr->tcl_FSLstat) /* 449 */ #define Tcl_FSUtime \ (tclStubsPtr->tcl_FSUtime) /* 450 */ #define Tcl_FSFileAttrsGet \ (tclStubsPtr->tcl_FSFileAttrsGet) /* 451 */ #define Tcl_FSFileAttrsSet \ (tclStubsPtr->tcl_FSFileAttrsSet) /* 452 */ #define Tcl_FSFileAttrStrings \ (tclStubsPtr->tcl_FSFileAttrStrings) /* 453 */ #define Tcl_FSStat \ (tclStubsPtr->tcl_FSStat) /* 454 */ #define Tcl_FSAccess \ (tclStubsPtr->tcl_FSAccess) /* 455 */ #define Tcl_FSOpenFileChannel \ (tclStubsPtr->tcl_FSOpenFileChannel) /* 456 */ #define Tcl_FSGetCwd \ (tclStubsPtr->tcl_FSGetCwd) /* 457 */ #define Tcl_FSChdir \ (tclStubsPtr->tcl_FSChdir) /* 458 */ #define Tcl_FSConvertToPathType \ (tclStubsPtr->tcl_FSConvertToPathType) /* 459 */ #define Tcl_FSJoinPath \ (tclStubsPtr->tcl_FSJoinPath) /* 460 */ #define TclFSSplitPath \ (tclStubsPtr->tclFSSplitPath) /* 461 */ #define Tcl_FSEqualPaths \ (tclStubsPtr->tcl_FSEqualPaths) /* 462 */ #define Tcl_FSGetNormalizedPath \ (tclStubsPtr->tcl_FSGetNormalizedPath) /* 463 */ #define Tcl_FSJoinToPath \ (tclStubsPtr->tcl_FSJoinToPath) /* 464 */ #define Tcl_FSGetInternalRep \ (tclStubsPtr->tcl_FSGetInternalRep) /* 465 */ #define Tcl_FSGetTranslatedPath \ (tclStubsPtr->tcl_FSGetTranslatedPath) /* 466 */ #define Tcl_FSEvalFile \ (tclStubsPtr->tcl_FSEvalFile) /* 467 */ #define Tcl_FSNewNativePath \ (tclStubsPtr->tcl_FSNewNativePath) /* 468 */ #define Tcl_FSGetNativePath \ (tclStubsPtr->tcl_FSGetNativePath) /* 469 */ #define Tcl_FSFileSystemInfo \ (tclStubsPtr->tcl_FSFileSystemInfo) /* 470 */ #define Tcl_FSPathSeparator \ (tclStubsPtr->tcl_FSPathSeparator) /* 471 */ #define Tcl_FSListVolumes \ (tclStubsPtr->tcl_FSListVolumes) /* 472 */ #define Tcl_FSRegister \ (tclStubsPtr->tcl_FSRegister) /* 473 */ #define Tcl_FSUnregister \ (tclStubsPtr->tcl_FSUnregister) /* 474 */ #define Tcl_FSData \ (tclStubsPtr->tcl_FSData) /* 475 */ #define Tcl_FSGetTranslatedStringPath \ (tclStubsPtr->tcl_FSGetTranslatedStringPath) /* 476 */ #define Tcl_FSGetFileSystemForPath \ (tclStubsPtr->tcl_FSGetFileSystemForPath) /* 477 */ #define Tcl_FSGetPathType \ (tclStubsPtr->tcl_FSGetPathType) /* 478 */ #define Tcl_OutputBuffered \ (tclStubsPtr->tcl_OutputBuffered) /* 479 */ #define Tcl_FSMountsChanged \ (tclStubsPtr->tcl_FSMountsChanged) /* 480 */ #define Tcl_EvalTokensStandard \ (tclStubsPtr->tcl_EvalTokensStandard) /* 481 */ #define Tcl_GetTime \ (tclStubsPtr->tcl_GetTime) /* 482 */ #define Tcl_CreateObjTrace \ (tclStubsPtr->tcl_CreateObjTrace) /* 483 */ #define Tcl_GetCommandInfoFromToken \ (tclStubsPtr->tcl_GetCommandInfoFromToken) /* 484 */ #define Tcl_SetCommandInfoFromToken \ (tclStubsPtr->tcl_SetCommandInfoFromToken) /* 485 */ #define Tcl_DbNewWideIntObj \ (tclStubsPtr->tcl_DbNewWideIntObj) /* 486 */ #define Tcl_GetWideIntFromObj \ (tclStubsPtr->tcl_GetWideIntFromObj) /* 487 */ #define Tcl_NewWideIntObj \ (tclStubsPtr->tcl_NewWideIntObj) /* 488 */ #define Tcl_SetWideIntObj \ (tclStubsPtr->tcl_SetWideIntObj) /* 489 */ #define Tcl_AllocStatBuf \ (tclStubsPtr->tcl_AllocStatBuf) /* 490 */ #define Tcl_Seek \ (tclStubsPtr->tcl_Seek) /* 491 */ #define Tcl_Tell \ (tclStubsPtr->tcl_Tell) /* 492 */ #define Tcl_ChannelWideSeekProc \ (tclStubsPtr->tcl_ChannelWideSeekProc) /* 493 */ #define Tcl_DictObjPut \ (tclStubsPtr->tcl_DictObjPut) /* 494 */ #define Tcl_DictObjGet \ (tclStubsPtr->tcl_DictObjGet) /* 495 */ #define Tcl_DictObjRemove \ (tclStubsPtr->tcl_DictObjRemove) /* 496 */ #define TclDictObjSize \ (tclStubsPtr->tclDictObjSize) /* 497 */ #define Tcl_DictObjFirst \ (tclStubsPtr->tcl_DictObjFirst) /* 498 */ #define Tcl_DictObjNext \ (tclStubsPtr->tcl_DictObjNext) /* 499 */ #define Tcl_DictObjDone \ (tclStubsPtr->tcl_DictObjDone) /* 500 */ #define Tcl_DictObjPutKeyList \ (tclStubsPtr->tcl_DictObjPutKeyList) /* 501 */ #define Tcl_DictObjRemoveKeyList \ (tclStubsPtr->tcl_DictObjRemoveKeyList) /* 502 */ #define Tcl_NewDictObj \ (tclStubsPtr->tcl_NewDictObj) /* 503 */ #define Tcl_DbNewDictObj \ (tclStubsPtr->tcl_DbNewDictObj) /* 504 */ #define Tcl_RegisterConfig \ (tclStubsPtr->tcl_RegisterConfig) /* 505 */ #define Tcl_CreateNamespace \ (tclStubsPtr->tcl_CreateNamespace) /* 506 */ #define Tcl_DeleteNamespace \ (tclStubsPtr->tcl_DeleteNamespace) /* 507 */ #define Tcl_AppendExportList \ (tclStubsPtr->tcl_AppendExportList) /* 508 */ #define Tcl_Export \ (tclStubsPtr->tcl_Export) /* 509 */ #define Tcl_Import \ (tclStubsPtr->tcl_Import) /* 510 */ #define Tcl_ForgetImport \ (tclStubsPtr->tcl_ForgetImport) /* 511 */ #define Tcl_GetCurrentNamespace \ (tclStubsPtr->tcl_GetCurrentNamespace) /* 512 */ #define Tcl_GetGlobalNamespace \ (tclStubsPtr->tcl_GetGlobalNamespace) /* 513 */ #define Tcl_FindNamespace \ (tclStubsPtr->tcl_FindNamespace) /* 514 */ #define Tcl_FindCommand \ (tclStubsPtr->tcl_FindCommand) /* 515 */ #define Tcl_GetCommandFromObj \ (tclStubsPtr->tcl_GetCommandFromObj) /* 516 */ #define Tcl_GetCommandFullName \ (tclStubsPtr->tcl_GetCommandFullName) /* 517 */ #define Tcl_FSEvalFileEx \ (tclStubsPtr->tcl_FSEvalFileEx) /* 518 */ /* Slot 519 is reserved */ #define Tcl_LimitAddHandler \ (tclStubsPtr->tcl_LimitAddHandler) /* 520 */ #define Tcl_LimitRemoveHandler \ (tclStubsPtr->tcl_LimitRemoveHandler) /* 521 */ #define Tcl_LimitReady \ (tclStubsPtr->tcl_LimitReady) /* 522 */ #define Tcl_LimitCheck \ (tclStubsPtr->tcl_LimitCheck) /* 523 */ #define Tcl_LimitExceeded \ (tclStubsPtr->tcl_LimitExceeded) /* 524 */ #define Tcl_LimitSetCommands \ (tclStubsPtr->tcl_LimitSetCommands) /* 525 */ #define Tcl_LimitSetTime \ (tclStubsPtr->tcl_LimitSetTime) /* 526 */ #define Tcl_LimitSetGranularity \ (tclStubsPtr->tcl_LimitSetGranularity) /* 527 */ #define Tcl_LimitTypeEnabled \ (tclStubsPtr->tcl_LimitTypeEnabled) /* 528 */ #define Tcl_LimitTypeExceeded \ (tclStubsPtr->tcl_LimitTypeExceeded) /* 529 */ #define Tcl_LimitTypeSet \ (tclStubsPtr->tcl_LimitTypeSet) /* 530 */ #define Tcl_LimitTypeReset \ (tclStubsPtr->tcl_LimitTypeReset) /* 531 */ #define Tcl_LimitGetCommands \ (tclStubsPtr->tcl_LimitGetCommands) /* 532 */ #define Tcl_LimitGetTime \ (tclStubsPtr->tcl_LimitGetTime) /* 533 */ #define Tcl_LimitGetGranularity \ (tclStubsPtr->tcl_LimitGetGranularity) /* 534 */ #define Tcl_SaveInterpState \ (tclStubsPtr->tcl_SaveInterpState) /* 535 */ #define Tcl_RestoreInterpState \ (tclStubsPtr->tcl_RestoreInterpState) /* 536 */ #define Tcl_DiscardInterpState \ (tclStubsPtr->tcl_DiscardInterpState) /* 537 */ #define Tcl_SetReturnOptions \ (tclStubsPtr->tcl_SetReturnOptions) /* 538 */ #define Tcl_GetReturnOptions \ (tclStubsPtr->tcl_GetReturnOptions) /* 539 */ #define Tcl_IsEnsemble \ (tclStubsPtr->tcl_IsEnsemble) /* 540 */ #define Tcl_CreateEnsemble \ (tclStubsPtr->tcl_CreateEnsemble) /* 541 */ #define Tcl_FindEnsemble \ (tclStubsPtr->tcl_FindEnsemble) /* 542 */ #define Tcl_SetEnsembleSubcommandList \ (tclStubsPtr->tcl_SetEnsembleSubcommandList) /* 543 */ #define Tcl_SetEnsembleMappingDict \ (tclStubsPtr->tcl_SetEnsembleMappingDict) /* 544 */ #define Tcl_SetEnsembleUnknownHandler \ (tclStubsPtr->tcl_SetEnsembleUnknownHandler) /* 545 */ #define Tcl_SetEnsembleFlags \ (tclStubsPtr->tcl_SetEnsembleFlags) /* 546 */ #define Tcl_GetEnsembleSubcommandList \ (tclStubsPtr->tcl_GetEnsembleSubcommandList) /* 547 */ #define Tcl_GetEnsembleMappingDict \ (tclStubsPtr->tcl_GetEnsembleMappingDict) /* 548 */ #define Tcl_GetEnsembleUnknownHandler \ (tclStubsPtr->tcl_GetEnsembleUnknownHandler) /* 549 */ #define Tcl_GetEnsembleFlags \ (tclStubsPtr->tcl_GetEnsembleFlags) /* 550 */ #define Tcl_GetEnsembleNamespace \ (tclStubsPtr->tcl_GetEnsembleNamespace) /* 551 */ #define Tcl_SetTimeProc \ (tclStubsPtr->tcl_SetTimeProc) /* 552 */ #define Tcl_QueryTimeProc \ (tclStubsPtr->tcl_QueryTimeProc) /* 553 */ #define Tcl_ChannelThreadActionProc \ (tclStubsPtr->tcl_ChannelThreadActionProc) /* 554 */ #define Tcl_NewBignumObj \ (tclStubsPtr->tcl_NewBignumObj) /* 555 */ #define Tcl_DbNewBignumObj \ (tclStubsPtr->tcl_DbNewBignumObj) /* 556 */ #define Tcl_SetBignumObj \ (tclStubsPtr->tcl_SetBignumObj) /* 557 */ #define Tcl_GetBignumFromObj \ (tclStubsPtr->tcl_GetBignumFromObj) /* 558 */ #define Tcl_TakeBignumFromObj \ (tclStubsPtr->tcl_TakeBignumFromObj) /* 559 */ #define Tcl_TruncateChannel \ (tclStubsPtr->tcl_TruncateChannel) /* 560 */ #define Tcl_ChannelTruncateProc \ (tclStubsPtr->tcl_ChannelTruncateProc) /* 561 */ #define Tcl_SetChannelErrorInterp \ (tclStubsPtr->tcl_SetChannelErrorInterp) /* 562 */ #define Tcl_GetChannelErrorInterp \ (tclStubsPtr->tcl_GetChannelErrorInterp) /* 563 */ #define Tcl_SetChannelError \ (tclStubsPtr->tcl_SetChannelError) /* 564 */ #define Tcl_GetChannelError \ (tclStubsPtr->tcl_GetChannelError) /* 565 */ #define Tcl_InitBignumFromDouble \ (tclStubsPtr->tcl_InitBignumFromDouble) /* 566 */ #define Tcl_GetNamespaceUnknownHandler \ (tclStubsPtr->tcl_GetNamespaceUnknownHandler) /* 567 */ #define Tcl_SetNamespaceUnknownHandler \ (tclStubsPtr->tcl_SetNamespaceUnknownHandler) /* 568 */ #define Tcl_GetEncodingFromObj \ (tclStubsPtr->tcl_GetEncodingFromObj) /* 569 */ #define Tcl_GetEncodingSearchPath \ (tclStubsPtr->tcl_GetEncodingSearchPath) /* 570 */ #define Tcl_SetEncodingSearchPath \ (tclStubsPtr->tcl_SetEncodingSearchPath) /* 571 */ #define Tcl_GetEncodingNameFromEnvironment \ (tclStubsPtr->tcl_GetEncodingNameFromEnvironment) /* 572 */ #define Tcl_PkgRequireProc \ (tclStubsPtr->tcl_PkgRequireProc) /* 573 */ #define Tcl_AppendObjToErrorInfo \ (tclStubsPtr->tcl_AppendObjToErrorInfo) /* 574 */ #define Tcl_AppendLimitedToObj \ (tclStubsPtr->tcl_AppendLimitedToObj) /* 575 */ #define Tcl_Format \ (tclStubsPtr->tcl_Format) /* 576 */ #define Tcl_AppendFormatToObj \ (tclStubsPtr->tcl_AppendFormatToObj) /* 577 */ #define Tcl_ObjPrintf \ (tclStubsPtr->tcl_ObjPrintf) /* 578 */ #define Tcl_AppendPrintfToObj \ (tclStubsPtr->tcl_AppendPrintfToObj) /* 579 */ #define Tcl_CancelEval \ (tclStubsPtr->tcl_CancelEval) /* 580 */ #define Tcl_Canceled \ (tclStubsPtr->tcl_Canceled) /* 581 */ #define Tcl_CreatePipe \ (tclStubsPtr->tcl_CreatePipe) /* 582 */ #define Tcl_NRCreateCommand \ (tclStubsPtr->tcl_NRCreateCommand) /* 583 */ #define Tcl_NREvalObj \ (tclStubsPtr->tcl_NREvalObj) /* 584 */ #define Tcl_NREvalObjv \ (tclStubsPtr->tcl_NREvalObjv) /* 585 */ #define Tcl_NRCmdSwap \ (tclStubsPtr->tcl_NRCmdSwap) /* 586 */ #define Tcl_NRAddCallback \ (tclStubsPtr->tcl_NRAddCallback) /* 587 */ #define Tcl_NRCallObjProc \ (tclStubsPtr->tcl_NRCallObjProc) /* 588 */ #define Tcl_GetFSDeviceFromStat \ (tclStubsPtr->tcl_GetFSDeviceFromStat) /* 589 */ #define Tcl_GetFSInodeFromStat \ (tclStubsPtr->tcl_GetFSInodeFromStat) /* 590 */ #define Tcl_GetModeFromStat \ (tclStubsPtr->tcl_GetModeFromStat) /* 591 */ #define Tcl_GetLinkCountFromStat \ (tclStubsPtr->tcl_GetLinkCountFromStat) /* 592 */ #define Tcl_GetUserIdFromStat \ (tclStubsPtr->tcl_GetUserIdFromStat) /* 593 */ #define Tcl_GetGroupIdFromStat \ (tclStubsPtr->tcl_GetGroupIdFromStat) /* 594 */ #define Tcl_GetDeviceTypeFromStat \ (tclStubsPtr->tcl_GetDeviceTypeFromStat) /* 595 */ #define Tcl_GetAccessTimeFromStat \ (tclStubsPtr->tcl_GetAccessTimeFromStat) /* 596 */ #define Tcl_GetModificationTimeFromStat \ (tclStubsPtr->tcl_GetModificationTimeFromStat) /* 597 */ #define Tcl_GetChangeTimeFromStat \ (tclStubsPtr->tcl_GetChangeTimeFromStat) /* 598 */ #define Tcl_GetSizeFromStat \ (tclStubsPtr->tcl_GetSizeFromStat) /* 599 */ #define Tcl_GetBlocksFromStat \ (tclStubsPtr->tcl_GetBlocksFromStat) /* 600 */ #define Tcl_GetBlockSizeFromStat \ (tclStubsPtr->tcl_GetBlockSizeFromStat) /* 601 */ #define Tcl_SetEnsembleParameterList \ (tclStubsPtr->tcl_SetEnsembleParameterList) /* 602 */ #define Tcl_GetEnsembleParameterList \ (tclStubsPtr->tcl_GetEnsembleParameterList) /* 603 */ #define TclParseArgsObjv \ (tclStubsPtr->tclParseArgsObjv) /* 604 */ #define Tcl_GetErrorLine \ (tclStubsPtr->tcl_GetErrorLine) /* 605 */ #define Tcl_SetErrorLine \ (tclStubsPtr->tcl_SetErrorLine) /* 606 */ #define Tcl_TransferResult \ (tclStubsPtr->tcl_TransferResult) /* 607 */ #define Tcl_InterpActive \ (tclStubsPtr->tcl_InterpActive) /* 608 */ #define Tcl_BackgroundException \ (tclStubsPtr->tcl_BackgroundException) /* 609 */ #define Tcl_ZlibDeflate \ (tclStubsPtr->tcl_ZlibDeflate) /* 610 */ #define Tcl_ZlibInflate \ (tclStubsPtr->tcl_ZlibInflate) /* 611 */ #define Tcl_ZlibCRC32 \ (tclStubsPtr->tcl_ZlibCRC32) /* 612 */ #define Tcl_ZlibAdler32 \ (tclStubsPtr->tcl_ZlibAdler32) /* 613 */ #define Tcl_ZlibStreamInit \ (tclStubsPtr->tcl_ZlibStreamInit) /* 614 */ #define Tcl_ZlibStreamGetCommandName \ (tclStubsPtr->tcl_ZlibStreamGetCommandName) /* 615 */ #define Tcl_ZlibStreamEof \ (tclStubsPtr->tcl_ZlibStreamEof) /* 616 */ #define Tcl_ZlibStreamChecksum \ (tclStubsPtr->tcl_ZlibStreamChecksum) /* 617 */ #define Tcl_ZlibStreamPut \ (tclStubsPtr->tcl_ZlibStreamPut) /* 618 */ #define Tcl_ZlibStreamGet \ (tclStubsPtr->tcl_ZlibStreamGet) /* 619 */ #define Tcl_ZlibStreamClose \ (tclStubsPtr->tcl_ZlibStreamClose) /* 620 */ #define Tcl_ZlibStreamReset \ (tclStubsPtr->tcl_ZlibStreamReset) /* 621 */ #define Tcl_SetStartupScript \ (tclStubsPtr->tcl_SetStartupScript) /* 622 */ #define Tcl_GetStartupScript \ (tclStubsPtr->tcl_GetStartupScript) /* 623 */ #define Tcl_CloseEx \ (tclStubsPtr->tcl_CloseEx) /* 624 */ #define Tcl_NRExprObj \ (tclStubsPtr->tcl_NRExprObj) /* 625 */ #define Tcl_NRSubstObj \ (tclStubsPtr->tcl_NRSubstObj) /* 626 */ #define Tcl_LoadFile \ (tclStubsPtr->tcl_LoadFile) /* 627 */ #define Tcl_FindSymbol \ (tclStubsPtr->tcl_FindSymbol) /* 628 */ #define Tcl_FSUnloadFile \ (tclStubsPtr->tcl_FSUnloadFile) /* 629 */ #define Tcl_ZlibStreamSetCompressionDictionary \ (tclStubsPtr->tcl_ZlibStreamSetCompressionDictionary) /* 630 */ #define Tcl_OpenTcpServerEx \ (tclStubsPtr->tcl_OpenTcpServerEx) /* 631 */ #define TclZipfs_Mount \ (tclStubsPtr->tclZipfs_Mount) /* 632 */ #define TclZipfs_Unmount \ (tclStubsPtr->tclZipfs_Unmount) /* 633 */ #define TclZipfs_TclLibrary \ (tclStubsPtr->tclZipfs_TclLibrary) /* 634 */ #define TclZipfs_MountBuffer \ (tclStubsPtr->tclZipfs_MountBuffer) /* 635 */ #define Tcl_FreeInternalRep \ (tclStubsPtr->tcl_FreeInternalRep) /* 636 */ #define Tcl_InitStringRep \ (tclStubsPtr->tcl_InitStringRep) /* 637 */ #define Tcl_FetchInternalRep \ (tclStubsPtr->tcl_FetchInternalRep) /* 638 */ #define Tcl_StoreInternalRep \ (tclStubsPtr->tcl_StoreInternalRep) /* 639 */ #define Tcl_HasStringRep \ (tclStubsPtr->tcl_HasStringRep) /* 640 */ #define Tcl_IncrRefCount \ (tclStubsPtr->tcl_IncrRefCount) /* 641 */ #define Tcl_DecrRefCount \ (tclStubsPtr->tcl_DecrRefCount) /* 642 */ #define Tcl_IsShared \ (tclStubsPtr->tcl_IsShared) /* 643 */ #define Tcl_LinkArray \ (tclStubsPtr->tcl_LinkArray) /* 644 */ #define Tcl_GetIntForIndex \ (tclStubsPtr->tcl_GetIntForIndex) /* 645 */ #define Tcl_UtfToUniChar \ (tclStubsPtr->tcl_UtfToUniChar) /* 646 */ #define Tcl_UniCharToUtfDString \ (tclStubsPtr->tcl_UniCharToUtfDString) /* 647 */ #define Tcl_UtfToUniCharDString \ (tclStubsPtr->tcl_UtfToUniCharDString) /* 648 */ #define TclGetBytesFromObj \ (tclStubsPtr->tclGetBytesFromObj) /* 649 */ #define Tcl_GetBytesFromObj \ (tclStubsPtr->tcl_GetBytesFromObj) /* 650 */ #define Tcl_GetStringFromObj \ (tclStubsPtr->tcl_GetStringFromObj) /* 651 */ #define Tcl_GetUnicodeFromObj \ (tclStubsPtr->tcl_GetUnicodeFromObj) /* 652 */ #define Tcl_GetSizeIntFromObj \ (tclStubsPtr->tcl_GetSizeIntFromObj) /* 653 */ #define Tcl_UtfCharComplete \ (tclStubsPtr->tcl_UtfCharComplete) /* 654 */ #define Tcl_UtfNext \ (tclStubsPtr->tcl_UtfNext) /* 655 */ #define Tcl_UtfPrev \ (tclStubsPtr->tcl_UtfPrev) /* 656 */ #define Tcl_FSTildeExpand \ (tclStubsPtr->tcl_FSTildeExpand) /* 657 */ #define Tcl_ExternalToUtfDStringEx \ (tclStubsPtr->tcl_ExternalToUtfDStringEx) /* 658 */ #define Tcl_UtfToExternalDStringEx \ (tclStubsPtr->tcl_UtfToExternalDStringEx) /* 659 */ #define Tcl_AsyncMarkFromSignal \ (tclStubsPtr->tcl_AsyncMarkFromSignal) /* 660 */ #define Tcl_ListObjGetElements \ (tclStubsPtr->tcl_ListObjGetElements) /* 661 */ #define Tcl_ListObjLength \ (tclStubsPtr->tcl_ListObjLength) /* 662 */ #define Tcl_DictObjSize \ (tclStubsPtr->tcl_DictObjSize) /* 663 */ #define Tcl_SplitList \ (tclStubsPtr->tcl_SplitList) /* 664 */ #define Tcl_SplitPath \ (tclStubsPtr->tcl_SplitPath) /* 665 */ #define Tcl_FSSplitPath \ (tclStubsPtr->tcl_FSSplitPath) /* 666 */ #define Tcl_ParseArgsObjv \ (tclStubsPtr->tcl_ParseArgsObjv) /* 667 */ #define Tcl_UniCharLen \ (tclStubsPtr->tcl_UniCharLen) /* 668 */ #define Tcl_NumUtfChars \ (tclStubsPtr->tcl_NumUtfChars) /* 669 */ #define Tcl_GetCharLength \ (tclStubsPtr->tcl_GetCharLength) /* 670 */ #define Tcl_UtfAtIndex \ (tclStubsPtr->tcl_UtfAtIndex) /* 671 */ #define Tcl_GetRange \ (tclStubsPtr->tcl_GetRange) /* 672 */ #define Tcl_GetUniChar \ (tclStubsPtr->tcl_GetUniChar) /* 673 */ #define Tcl_GetBool \ (tclStubsPtr->tcl_GetBool) /* 674 */ #define Tcl_GetBoolFromObj \ (tclStubsPtr->tcl_GetBoolFromObj) /* 675 */ #define Tcl_CreateObjCommand2 \ (tclStubsPtr->tcl_CreateObjCommand2) /* 676 */ #define Tcl_CreateObjTrace2 \ (tclStubsPtr->tcl_CreateObjTrace2) /* 677 */ #define Tcl_NRCreateCommand2 \ (tclStubsPtr->tcl_NRCreateCommand2) /* 678 */ #define Tcl_NRCallObjProc2 \ (tclStubsPtr->tcl_NRCallObjProc2) /* 679 */ #define Tcl_GetNumberFromObj \ (tclStubsPtr->tcl_GetNumberFromObj) /* 680 */ #define Tcl_GetNumber \ (tclStubsPtr->tcl_GetNumber) /* 681 */ #define Tcl_RemoveChannelMode \ (tclStubsPtr->tcl_RemoveChannelMode) /* 682 */ #define Tcl_GetEncodingNulLength \ (tclStubsPtr->tcl_GetEncodingNulLength) /* 683 */ #define Tcl_GetWideUIntFromObj \ (tclStubsPtr->tcl_GetWideUIntFromObj) /* 684 */ #define Tcl_DStringToObj \ (tclStubsPtr->tcl_DStringToObj) /* 685 */ #define Tcl_UtfNcmp \ (tclStubsPtr->tcl_UtfNcmp) /* 686 */ #define Tcl_UtfNcasecmp \ (tclStubsPtr->tcl_UtfNcasecmp) /* 687 */ #define Tcl_NewWideUIntObj \ (tclStubsPtr->tcl_NewWideUIntObj) /* 688 */ #define Tcl_SetWideUIntObj \ (tclStubsPtr->tcl_SetWideUIntObj) /* 689 */ #define TclUnusedStubEntry \ (tclStubsPtr->tclUnusedStubEntry) /* 690 */ #endif /* defined(USE_TCL_STUBS) */ /* !END!: Do not edit above this line. */ #undef TclUnusedStubEntry #ifdef _WIN32 # undef Tcl_CreateFileHandler # undef Tcl_DeleteFileHandler # undef Tcl_GetOpenFile #endif #undef TCL_STORAGE_CLASS #define TCL_STORAGE_CLASS DLLIMPORT #define Tcl_PkgPresent(interp, name, version, exact) \ Tcl_PkgPresentEx(interp, name, version, exact, NULL) #define Tcl_PkgProvide(interp, name, version) \ Tcl_PkgProvideEx(interp, name, version, NULL) #define Tcl_PkgRequire(interp, name, version, exact) \ Tcl_PkgRequireEx(interp, name, version, exact, NULL) #define Tcl_GetIndexFromObj(interp, objPtr, tablePtr, msg, flags, indexPtr) \ Tcl_GetIndexFromObjStruct(interp, objPtr, tablePtr, \ sizeof(char *), msg, flags, indexPtr) #define Tcl_NewBooleanObj(intValue) \ Tcl_NewWideIntObj((intValue)!=0) #define Tcl_DbNewBooleanObj(intValue, file, line) \ Tcl_DbNewWideIntObj((intValue)!=0, file, line) #define Tcl_SetBooleanObj(objPtr, intValue) \ Tcl_SetWideIntObj(objPtr, (intValue)!=0) #define Tcl_SetVar(interp, varName, newValue, flags) \ Tcl_SetVar2(interp, varName, NULL, newValue, flags) #define Tcl_UnsetVar(interp, varName, flags) \ Tcl_UnsetVar2(interp, varName, NULL, flags) #define Tcl_GetVar(interp, varName, flags) \ Tcl_GetVar2(interp, varName, NULL, flags) #define Tcl_TraceVar(interp, varName, flags, proc, clientData) \ Tcl_TraceVar2(interp, varName, NULL, flags, proc, clientData) #define Tcl_UntraceVar(interp, varName, flags, proc, clientData) \ Tcl_UntraceVar2(interp, varName, NULL, flags, proc, clientData) #define Tcl_VarTraceInfo(interp, varName, flags, proc, prevClientData) \ Tcl_VarTraceInfo2(interp, varName, NULL, flags, proc, prevClientData) #define Tcl_UpVar(interp, frameName, varName, localName, flags) \ Tcl_UpVar2(interp, frameName, varName, NULL, localName, flags) #define Tcl_AddErrorInfo(interp, message) \ Tcl_AppendObjToErrorInfo(interp, Tcl_NewStringObj(message, -1)) #define Tcl_AddObjErrorInfo(interp, message, length) \ Tcl_AppendObjToErrorInfo(interp, Tcl_NewStringObj(message, length)) #define Tcl_Eval(interp, objPtr) \ Tcl_EvalEx(interp, objPtr, TCL_INDEX_NONE, 0) #define Tcl_GlobalEval(interp, objPtr) \ Tcl_EvalEx(interp, objPtr, TCL_INDEX_NONE, TCL_EVAL_GLOBAL) #define Tcl_GetStringResult(interp) Tcl_GetString(Tcl_GetObjResult(interp)) #define Tcl_SetResult(interp, result, freeProc) \ do { \ const char *__result = result; \ Tcl_FreeProc *__freeProc = freeProc; \ Tcl_SetObjResult(interp, Tcl_NewStringObj(__result, -1)); \ if (__result != NULL && __freeProc != NULL && __freeProc != TCL_VOLATILE) { \ if (__freeProc == TCL_DYNAMIC) { \ Tcl_Free((void *)__result); \ } else { \ (*__freeProc)((void *)__result); \ } \ } \ } while(0) #if defined(USE_TCL_STUBS) # if defined(_WIN32) && defined(_WIN64) && TCL_MAJOR_VERSION < 9 # undef Tcl_GetTime /* Handle Win64 tk.dll being loaded in Cygwin64 (only needed for Tcl 8). */ # define Tcl_GetTime(t) \ do { \ struct { \ Tcl_Time now; \ long long reserved; \ } _t; \ _t.reserved = -1; \ tclStubsPtr->tcl_GetTime((&_t.now)); \ if (_t.reserved != -1) { \ _t.now.usec = (long) _t.reserved; \ } \ *(t) = _t.now; \ } while (0) # endif # if defined(__CYGWIN__) && defined(TCL_WIDE_INT_IS_LONG) /* On Cygwin64, long is 64-bit while on Win64 long is 32-bit. Therefore * we have to make sure that all stub entries on Cygwin64 follow the * Win64 signature. Cygwin64 stubbed extensions cannot use those stub * entries any more, they should use the 64-bit alternatives where * possible. Tcl 9 must find a better solution, but that cannot be done * without introducing a binary incompatibility. */ # undef Tcl_GetLongFromObj # undef Tcl_ExprLong # undef Tcl_ExprLongObj # define Tcl_GetLongFromObj ((int(*)(Tcl_Interp*,Tcl_Obj*,long*))Tcl_GetWideIntFromObj) # define Tcl_ExprLong TclExprLong static inline int TclExprLong(Tcl_Interp *interp, const char *string, long *ptr){ int intValue; int result = tclStubsPtr->tcl_ExprLong(interp, string, (long *)&intValue); if (result == TCL_OK) *ptr = (long)intValue; return result; } # define Tcl_ExprLongObj TclExprLongObj static inline int TclExprLongObj(Tcl_Interp *interp, Tcl_Obj *obj, long *ptr){ int intValue; int result = tclStubsPtr->tcl_ExprLongObj(interp, obj, (long *)&intValue); if (result == TCL_OK) *ptr = (long)intValue; return result; } # endif #endif #undef Tcl_GetString #undef Tcl_GetUnicode #define Tcl_GetString(objPtr) \ Tcl_GetStringFromObj(objPtr, (Tcl_Size *)NULL) #define Tcl_GetUnicode(objPtr) \ Tcl_GetUnicodeFromObj(objPtr, (Tcl_Size *)NULL) #undef Tcl_GetIndexFromObjStruct #undef Tcl_GetBooleanFromObj #undef Tcl_GetBoolean #if !defined(TCLBOOLWARNING) #if !defined(__cplusplus) && !defined(BUILD_tcl) && !defined(BUILD_tk) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) # define TCLBOOLWARNING(boolPtr) (void)(sizeof(struct {_Static_assert(sizeof(*(boolPtr)) <= sizeof(int), "sizeof(boolPtr) too large");int dummy;})), #elif defined(__GNUC__) && !defined(__STRICT_ANSI__) /* If this gives: "error: size of array ‘_bool_Var’ is negative", it means that sizeof(*boolPtr)>sizeof(int), which is not allowed */ # define TCLBOOLWARNING(boolPtr) ({__attribute__((unused)) char _bool_Var[sizeof(*(boolPtr)) <= sizeof(int) ? 1 : -1];}), #else # define TCLBOOLWARNING(boolPtr) #endif #endif /* !TCLBOOLWARNING */ #if defined(USE_TCL_STUBS) #define Tcl_GetIndexFromObjStruct(interp, objPtr, tablePtr, offset, msg, flags, indexPtr) \ (tclStubsPtr->tcl_GetIndexFromObjStruct((interp), (objPtr), (tablePtr), (offset), (msg), \ (flags)|(int)(sizeof(*(indexPtr))<<1), (indexPtr))) #define Tcl_GetBooleanFromObj(interp, objPtr, boolPtr) \ ((sizeof(*(boolPtr)) == sizeof(int) && (TCL_MAJOR_VERSION == 8)) ? tclStubsPtr->tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr)) : \ ((sizeof(*(boolPtr)) <= sizeof(int)) ? Tcl_GetBoolFromObj(interp, objPtr, (TCL_NULL_OK-2)&(int)sizeof((*(boolPtr))), (char *)(boolPtr)) : \ (TCLBOOLWARNING(boolPtr)Tcl_Panic("sizeof(%s) must be <= sizeof(int)", & #boolPtr [1]),TCL_ERROR))) #define Tcl_GetBoolean(interp, src, boolPtr) \ ((sizeof(*(boolPtr)) == sizeof(int) && (TCL_MAJOR_VERSION == 8)) ? tclStubsPtr->tcl_GetBoolean(interp, src, (int *)(boolPtr)) : \ ((sizeof(*(boolPtr)) <= sizeof(int)) ? Tcl_GetBool(interp, src, (TCL_NULL_OK-2)&(int)sizeof((*(boolPtr))), (char *)(boolPtr)) : \ (TCLBOOLWARNING(boolPtr)Tcl_Panic("sizeof(%s) must be <= sizeof(int)", & #boolPtr [1]),TCL_ERROR))) #else #define Tcl_GetIndexFromObjStruct(interp, objPtr, tablePtr, offset, msg, flags, indexPtr) \ ((Tcl_GetIndexFromObjStruct)((interp), (objPtr), (tablePtr), (offset), (msg), \ (flags)|(int)(sizeof(*(indexPtr))<<1), (indexPtr))) #define Tcl_GetBooleanFromObj(interp, objPtr, boolPtr) \ ((sizeof(*(boolPtr)) == sizeof(int) && (TCL_MAJOR_VERSION == 8)) ? Tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr)) : \ ((sizeof(*(boolPtr)) <= sizeof(int)) ? Tcl_GetBoolFromObj(interp, objPtr, (TCL_NULL_OK-2)&(int)sizeof((*(boolPtr))), (char *)(boolPtr)) : \ (TCLBOOLWARNING(boolPtr)Tcl_Panic("sizeof(%s) must be <= sizeof(int)", & #boolPtr [1]),TCL_ERROR))) #define Tcl_GetBoolean(interp, src, boolPtr) \ ((sizeof(*(boolPtr)) == sizeof(int) && (TCL_MAJOR_VERSION == 8)) ? Tcl_GetBoolean(interp, src, (int *)(boolPtr)) : \ ((sizeof(*(boolPtr)) <= sizeof(int)) ? Tcl_GetBool(interp, src, (TCL_NULL_OK-2)&(int)sizeof((*(boolPtr))), (char *)(boolPtr)) : \ (TCLBOOLWARNING(boolPtr)Tcl_Panic("sizeof(%s) must be <= sizeof(int)", & #boolPtr [1]),TCL_ERROR))) #endif #ifdef TCL_MEM_DEBUG # undef Tcl_Alloc # define Tcl_Alloc(x) \ (Tcl_DbCkalloc((x), __FILE__, __LINE__)) # undef Tcl_Free # define Tcl_Free(x) \ Tcl_DbCkfree((x), __FILE__, __LINE__) # undef Tcl_Realloc # define Tcl_Realloc(x,y) \ (Tcl_DbCkrealloc((x), (y), __FILE__, __LINE__)) # undef Tcl_AttemptAlloc # define Tcl_AttemptAlloc(x) \ (Tcl_AttemptDbCkalloc((x), __FILE__, __LINE__)) # undef Tcl_AttemptRealloc # define Tcl_AttemptRealloc(x,y) \ (Tcl_AttemptDbCkrealloc((x), (y), __FILE__, __LINE__)) #endif /* !TCL_MEM_DEBUG */ #define Tcl_NewLongObj(value) Tcl_NewWideIntObj((long)(value)) #define Tcl_NewIntObj(value) Tcl_NewWideIntObj((int)(value)) #define Tcl_DbNewLongObj(value, file, line) Tcl_DbNewWideIntObj((long)(value), file, line) #define Tcl_SetIntObj(objPtr, value) Tcl_SetWideIntObj((objPtr), (int)(value)) #define Tcl_SetLongObj(objPtr, value) Tcl_SetWideIntObj((objPtr), (long)(value)) #define Tcl_BackgroundError(interp) Tcl_BackgroundException((interp), TCL_ERROR) #define Tcl_StringMatch(str, pattern) Tcl_StringCaseMatch((str), (pattern), 0) #if TCL_UTF_MAX < 4 # undef Tcl_UniCharToUtfDString # define Tcl_UniCharToUtfDString Tcl_Char16ToUtfDString # undef Tcl_UtfToUniCharDString # define Tcl_UtfToUniCharDString Tcl_UtfToChar16DString # undef Tcl_UtfToUniChar # define Tcl_UtfToUniChar Tcl_UtfToChar16 # undef Tcl_UniCharLen # define Tcl_UniCharLen Tcl_Char16Len # undef Tcl_UniCharToUtf # if defined(USE_TCL_STUBS) # define Tcl_UniCharToUtf(c, p) \ (tclStubsPtr->tcl_UniCharToUtf((c)|TCL_COMBINE, (p))) # else # define Tcl_UniCharToUtf(c, p) \ ((Tcl_UniCharToUtf)((c)|TCL_COMBINE, (p))) # endif # undef Tcl_NumUtfChars # define Tcl_NumUtfChars TclNumUtfChars # undef Tcl_GetCharLength # define Tcl_GetCharLength TclGetCharLength # undef Tcl_UtfAtIndex # define Tcl_UtfAtIndex TclUtfAtIndex # undef Tcl_GetRange # define Tcl_GetRange TclGetRange # undef Tcl_GetUniChar # define Tcl_GetUniChar TclGetUniChar # undef Tcl_UtfNcmp # define Tcl_UtfNcmp TclUtfNcmp # undef Tcl_UtfNcasecmp # define Tcl_UtfNcasecmp TclUtfNcasecmp #endif #if defined(USE_TCL_STUBS) # define Tcl_WCharToUtfDString (sizeof(wchar_t) != sizeof(short) \ ? (char *(*)(const wchar_t *, Tcl_Size, Tcl_DString *))tclStubsPtr->tcl_UniCharToUtfDString \ : (char *(*)(const wchar_t *, Tcl_Size, Tcl_DString *))Tcl_Char16ToUtfDString) # define Tcl_UtfToWCharDString (sizeof(wchar_t) != sizeof(short) \ ? (wchar_t *(*)(const char *, Tcl_Size, Tcl_DString *))tclStubsPtr->tcl_UtfToUniCharDString \ : (wchar_t *(*)(const char *, Tcl_Size, Tcl_DString *))Tcl_UtfToChar16DString) # define Tcl_UtfToWChar (sizeof(wchar_t) != sizeof(short) \ ? (Tcl_Size (*)(const char *, wchar_t *))tclStubsPtr->tcl_UtfToUniChar \ : (Tcl_Size (*)(const char *, wchar_t *))Tcl_UtfToChar16) # define Tcl_WCharLen (sizeof(wchar_t) != sizeof(short) \ ? (Tcl_Size (*)(wchar_t *))tclStubsPtr->tcl_UniCharLen \ : (Tcl_Size (*)(wchar_t *))Tcl_Char16Len) #else # define Tcl_WCharToUtfDString (sizeof(wchar_t) != sizeof(short) \ ? (char *(*)(const wchar_t *, Tcl_Size, Tcl_DString *))Tcl_UniCharToUtfDString \ : (char *(*)(const wchar_t *, Tcl_Size, Tcl_DString *))Tcl_Char16ToUtfDString) # define Tcl_UtfToWCharDString (sizeof(wchar_t) != sizeof(short) \ ? (wchar_t *(*)(const char *, Tcl_Size, Tcl_DString *))Tcl_UtfToUniCharDString \ : (wchar_t *(*)(const char *, Tcl_Size, Tcl_DString *))Tcl_UtfToChar16DString) # define Tcl_UtfToWChar (sizeof(wchar_t) != sizeof(short) \ ? (Tcl_Size (*)(const char *, wchar_t *))Tcl_UtfToUniChar \ : (Tcl_Size (*)(const char *, wchar_t *))Tcl_UtfToChar16) # define Tcl_WCharLen (sizeof(wchar_t) != sizeof(short) \ ? (Tcl_Size (*)(wchar_t *))Tcl_UniCharLen \ : (Tcl_Size (*)(wchar_t *))Tcl_Char16Len) #endif /* * Deprecated Tcl procedures: */ #define Tcl_EvalObj(interp, objPtr) \ Tcl_EvalObjEx(interp, objPtr, 0) #define Tcl_GlobalEvalObj(interp, objPtr) \ Tcl_EvalObjEx(interp, objPtr, TCL_EVAL_GLOBAL) #if TCL_MAJOR_VERSION > 8 # undef Tcl_Close # define Tcl_Close(interp, chan) Tcl_CloseEx(interp, chan, 0) #endif #undef TclUtfCharComplete #undef TclUtfNext #undef TclUtfPrev #ifndef TCL_NO_DEPRECATED # define Tcl_CreateSlave Tcl_CreateChild # define Tcl_GetSlave Tcl_GetChild # define Tcl_GetMaster Tcl_GetParent #endif /* Protect those 11 functions, make them useless through the stub table */ #undef TclGetStringFromObj #undef TclGetBytesFromObj #undef TclGetUnicodeFromObj #undef TclListObjGetElements #undef TclListObjLength #undef TclDictObjSize #undef TclSplitList #undef TclSplitPath #undef TclFSSplitPath #undef TclParseArgsObjv #undef TclGetAliasObj #if TCL_MAJOR_VERSION < 9 /* TIP #627 for 8.7 */ # undef Tcl_CreateObjCommand2 # define Tcl_CreateObjCommand2 Tcl_CreateObjCommand # undef Tcl_CreateObjTrace2 # define Tcl_CreateObjTrace2 Tcl_CreateObjTrace # undef Tcl_NRCreateCommand2 # define Tcl_NRCreateCommand2 Tcl_NRCreateCommand # undef Tcl_NRCallObjProc2 # define Tcl_NRCallObjProc2 Tcl_NRCallObjProc /* TIP #660 for 8.7 */ # undef Tcl_GetSizeIntFromObj # define Tcl_GetSizeIntFromObj Tcl_GetIntFromObj # undef Tcl_GetBytesFromObj # define Tcl_GetBytesFromObj(interp, objPtr, sizePtr) \ tclStubsPtr->tclGetBytesFromObj((interp), (objPtr), (sizePtr)) # undef Tcl_GetStringFromObj # define Tcl_GetStringFromObj(objPtr, sizePtr) \ tclStubsPtr->tclGetStringFromObj((objPtr), (sizePtr)) # undef Tcl_GetUnicodeFromObj # define Tcl_GetUnicodeFromObj(objPtr, sizePtr) \ tclStubsPtr->tclGetUnicodeFromObj((objPtr), (sizePtr)) # undef Tcl_ListObjGetElements # define Tcl_ListObjGetElements(interp, listPtr, objcPtr, objvPtr) \ tclStubsPtr->tclListObjGetElements((interp), (listPtr), (objcPtr), (objvPtr)) # undef Tcl_ListObjLength # define Tcl_ListObjLength(interp, listPtr, lengthPtr) \ tclStubsPtr->tclListObjLength((interp), (listPtr), (lengthPtr)) # undef Tcl_DictObjSize # define Tcl_DictObjSize(interp, dictPtr, sizePtr) \ tclStubsPtr->tclDictObjSize((interp), (dictPtr), (sizePtr)) # undef Tcl_SplitList # define Tcl_SplitList(interp, listStr, argcPtr, argvPtr) \ tclStubsPtr->tclSplitList((interp), (listStr), (argcPtr), (argvPtr)) # undef Tcl_SplitPath # define Tcl_SplitPath(path, argcPtr, argvPtr) \ tclStubsPtr->tclSplitPath((path), (argcPtr), (argvPtr)) # undef Tcl_FSSplitPath # define Tcl_FSSplitPath(pathPtr, lenPtr) \ tclStubsPtr->tclFSSplitPath((pathPtr), (lenPtr)) # undef Tcl_ParseArgsObjv # define Tcl_ParseArgsObjv(interp, argTable, objcPtr, objv, remObjv) \ tclStubsPtr->tclParseArgsObjv((interp), (argTable), (objcPtr), (objv), (remObjv)) # undef Tcl_GetAliasObj # define Tcl_GetAliasObj(interp, childCmd, targetInterpPtr, targetCmdPtr, objcPtr, objv) \ tclStubsPtr->tclGetAliasObj((interp), (childCmd), (targetInterpPtr), (targetCmdPtr), (objcPtr), (objv)) #elif defined(TCL_8_API) # undef Tcl_GetByteArrayFromObj # undef Tcl_GetBytesFromObj # undef Tcl_GetStringFromObj # undef Tcl_GetUnicodeFromObj # undef Tcl_ListObjGetElements # undef Tcl_ListObjLength # undef Tcl_DictObjSize # undef Tcl_SplitList # undef Tcl_SplitPath # undef Tcl_FSSplitPath # undef Tcl_ParseArgsObjv # undef Tcl_GetAliasObj # if !defined(USE_TCL_STUBS) # define Tcl_GetByteArrayFromObj(objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \ TclGetBytesFromObj(NULL, (objPtr), (sizePtr)) : \ (Tcl_GetBytesFromObj)(NULL, (objPtr), (Tcl_Size *)(void *)(sizePtr))) # define Tcl_GetBytesFromObj(interp, objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \ TclGetBytesFromObj((interp), (objPtr), (sizePtr)) : \ (Tcl_GetBytesFromObj)((interp), (objPtr), (Tcl_Size *)(void *)(sizePtr))) # define Tcl_GetStringFromObj(objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \ (TclGetStringFromObj)((objPtr), (sizePtr)) : \ (Tcl_GetStringFromObj)((objPtr), (Tcl_Size *)(void *)(sizePtr))) # define Tcl_GetUnicodeFromObj(objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \ TclGetUnicodeFromObj((objPtr), (sizePtr)) : \ (Tcl_GetUnicodeFromObj)((objPtr), (Tcl_Size *)(void *)(sizePtr))) # define Tcl_ListObjGetElements(interp, listPtr, objcPtr, objvPtr) (sizeof(*(objcPtr)) <= sizeof(int) ? \ (TclListObjGetElements)((interp), (listPtr), (objcPtr), (objvPtr)) : \ (Tcl_ListObjGetElements)((interp), (listPtr), (Tcl_Size *)(void *)(objcPtr), (objvPtr))) # define Tcl_ListObjLength(interp, listPtr, lengthPtr) (sizeof(*(lengthPtr)) <= sizeof(int) ? \ (TclListObjLength)((interp), (listPtr), (lengthPtr)) : \ (Tcl_ListObjLength)((interp), (listPtr), (Tcl_Size *)(void *)(lengthPtr))) # define Tcl_DictObjSize(interp, dictPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \ TclDictObjSize((interp), (dictPtr), (sizePtr)) : \ (Tcl_DictObjSize)((interp), (dictPtr), (Tcl_Size *)(void *)(sizePtr))) # define Tcl_SplitList(interp, listStr, argcPtr, argvPtr) (sizeof(*(argcPtr)) <= sizeof(int) ? \ TclSplitList((interp), (listStr), (argcPtr), (argvPtr)) : \ (Tcl_SplitList)((interp), (listStr), (Tcl_Size *)(void *)(argcPtr), (argvPtr))) # define Tcl_SplitPath(path, argcPtr, argvPtr) (sizeof(*(argcPtr)) <= sizeof(int) ? \ TclSplitPath((path), (argcPtr), (argvPtr)) : \ (Tcl_SplitPath)((path), (Tcl_Size *)(void *)(argcPtr), (argvPtr))) # define Tcl_FSSplitPath(pathPtr, lenPtr) (sizeof(*(lenPtr)) <= sizeof(int) ? \ TclFSSplitPath((pathPtr), (lenPtr)) : \ (Tcl_FSSplitPath)((pathPtr), (Tcl_Size *)(void *)(lenPtr))) # define Tcl_ParseArgsObjv(interp, argTable, objcPtr, objv, remObjv) (sizeof(*(objcPtr)) <= sizeof(int) ? \ TclParseArgsObjv((interp), (argTable), (objcPtr), (objv), (remObjv)) : \ (Tcl_ParseArgsObjv)((interp), (argTable), (Tcl_Size *)(void *)(objcPtr), (objv), (remObjv))) # define Tcl_GetAliasObj(interp, childCmd, targetInterpPtr, targetCmdPtr, objcPtr, objv) (sizeof(*(objcPtr)) <= sizeof(int) ? \ TclGetAliasObj((interp), (childCmd), (targetInterpPtr), (targetCmdPtr), (objcPtr), (objv)) : \ (Tcl_GetAliasObj)((interp), (childCmd), (targetInterpPtr), (targetCmdPtr), (Tcl_Size *)(void *)(objcPtr), (objv))) # elif !defined(BUILD_tcl) # define Tcl_GetByteArrayFromObj(objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \ tclStubsPtr->tclGetBytesFromObj(NULL, (objPtr), (sizePtr)) : \ tclStubsPtr->tcl_GetBytesFromObj(NULL, (objPtr), (Tcl_Size *)(void *)(sizePtr))) # define Tcl_GetBytesFromObj(interp, objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \ tclStubsPtr->tclGetBytesFromObj((interp), (objPtr), (sizePtr)) : \ tclStubsPtr->tcl_GetBytesFromObj((interp), (objPtr), (Tcl_Size *)(void *)(sizePtr))) # define Tcl_GetStringFromObj(objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \ tclStubsPtr->tclGetStringFromObj((objPtr), (sizePtr)) : \ tclStubsPtr->tcl_GetStringFromObj((objPtr), (Tcl_Size *)(void *)(sizePtr))) # define Tcl_GetUnicodeFromObj(objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \ tclStubsPtr->tclGetUnicodeFromObj((objPtr), (sizePtr)) : \ tclStubsPtr->tcl_GetUnicodeFromObj((objPtr), (Tcl_Size *)(void *)(sizePtr))) # define Tcl_ListObjGetElements(interp, listPtr, objcPtr, objvPtr) (sizeof(*(objcPtr)) <= sizeof(int) ? \ tclStubsPtr->tclListObjGetElements((interp), (listPtr), (objcPtr), (objvPtr)) : \ tclStubsPtr->tcl_ListObjGetElements((interp), (listPtr), (Tcl_Size *)(void *)(objcPtr), (objvPtr))) # define Tcl_ListObjLength(interp, listPtr, lengthPtr) (sizeof(*(lengthPtr)) <= sizeof(int) ? \ tclStubsPtr->tclListObjLength((interp), (listPtr), (lengthPtr)) : \ tclStubsPtr->tcl_ListObjLength((interp), (listPtr), (Tcl_Size *)(void *)(lengthPtr))) # define Tcl_DictObjSize(interp, dictPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \ tclStubsPtr->tclDictObjSize((interp), (dictPtr), (sizePtr)) : \ tclStubsPtr->tcl_DictObjSize((interp), (dictPtr), (Tcl_Size *)(void *)(sizePtr))) # define Tcl_SplitList(interp, listStr, argcPtr, argvPtr) (sizeof(*(argcPtr)) <= sizeof(int) ? \ tclStubsPtr->tclSplitList((interp), (listStr), (argcPtr), (argvPtr)) : \ tclStubsPtr->tcl_SplitList((interp), (listStr), (Tcl_Size *)(void *)(argcPtr), (argvPtr))) # define Tcl_SplitPath(path, argcPtr, argvPtr) (sizeof(*(argcPtr)) <= sizeof(int) ? \ tclStubsPtr->tclSplitPath((path), (argcPtr), (argvPtr)) : \ tclStubsPtr->tcl_SplitPath((path), (Tcl_Size *)(void *)(argcPtr), (argvPtr))) # define Tcl_FSSplitPath(pathPtr, lenPtr) (sizeof(*(lenPtr)) <= sizeof(int) ? \ tclStubsPtr->tclFSSplitPath((pathPtr), (lenPtr)) : \ tclStubsPtr->tcl_FSSplitPath((pathPtr), (Tcl_Size *)(void *)(lenPtr))) # define Tcl_ParseArgsObjv(interp, argTable, objcPtr, objv, remObjv) (sizeof(*(objcPtr)) <= sizeof(int) ? \ tclStubsPtr->tclParseArgsObjv((interp), (argTable), (objcPtr), (objv), (remObjv)) : \ tclStubsPtr->tcl_ParseArgsObjv((interp), (argTable), (Tcl_Size *)(void *)(objcPtr), (objv), (remObjv))) # define Tcl_GetAliasObj(interp, childCmd, targetInterpPtr, targetCmdPtr, objcPtr, objv) (sizeof(*(objcPtr)) <= sizeof(int) ? \ tclStubsPtr->tclGetAliasObj((interp), (childCmd), (targetInterpPtr), (targetCmdPtr), (objcPtr), (objv)) : \ tclStubsPtr->tcl_GetAliasObj((interp), (childCmd), (targetInterpPtr), (targetCmdPtr), (Tcl_Size *)(void *)(objcPtr), (objv))) # endif /* defined(USE_TCL_STUBS) */ #else /* !defined(TCL_8_API) */ # undef Tcl_GetByteArrayFromObj # define Tcl_GetByteArrayFromObj(objPtr, sizePtr) \ Tcl_GetBytesFromObj(NULL, (objPtr), (sizePtr)) #endif /* defined(TCL_8_API) */ #endif /* _TCLDECLS */ tcl9.0.1/generic/tclDictObj.c0000644000175000017500000031211414726623136015361 0ustar sergeisergei/* * tclDictObj.c -- * * This file contains functions that implement the Tcl dict object type * and its accessor command. * * Copyright © 2002-2010 Donal K. Fellows. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclTomMath.h" #include /* * Forward declaration. */ struct Dict; /* * Prototypes for functions defined later in this file: */ static void DeleteDict(struct Dict *dict); static Tcl_ObjCmdProc DictAppendCmd; static Tcl_ObjCmdProc DictCreateCmd; static Tcl_ObjCmdProc DictExistsCmd; static Tcl_ObjCmdProc DictFilterCmd; static Tcl_ObjCmdProc DictGetCmd; static Tcl_ObjCmdProc DictGetDefCmd; static Tcl_ObjCmdProc DictIncrCmd; static Tcl_ObjCmdProc DictInfoCmd; static Tcl_ObjCmdProc DictKeysCmd; static Tcl_ObjCmdProc DictLappendCmd; static Tcl_ObjCmdProc DictMergeCmd; static Tcl_ObjCmdProc DictRemoveCmd; static Tcl_ObjCmdProc DictReplaceCmd; static Tcl_ObjCmdProc DictSetCmd; static Tcl_ObjCmdProc DictSizeCmd; static Tcl_ObjCmdProc DictUnsetCmd; static Tcl_ObjCmdProc DictUpdateCmd; static Tcl_ObjCmdProc DictValuesCmd; static Tcl_ObjCmdProc DictWithCmd; static Tcl_DupInternalRepProc DupDictInternalRep; static Tcl_FreeInternalRepProc FreeDictInternalRep; static void InvalidateDictChain(Tcl_Obj *dictObj); static Tcl_SetFromAnyProc SetDictFromAny; static Tcl_UpdateStringProc UpdateStringOfDict; static Tcl_AllocHashEntryProc AllocChainEntry; static inline void InitChainTable(struct Dict *dict); static inline void DeleteChainTable(struct Dict *dict); static inline Tcl_HashEntry * CreateChainEntry(struct Dict *dict, Tcl_Obj *keyPtr, int *newPtr); static inline int DeleteChainEntry(struct Dict *dict, Tcl_Obj *keyPtr); static Tcl_NRPostProc FinalizeDictUpdate; static Tcl_NRPostProc FinalizeDictWith; static Tcl_ObjCmdProc DictForNRCmd; static Tcl_ObjCmdProc DictMapNRCmd; static Tcl_NRPostProc DictForLoopCallback; static Tcl_NRPostProc DictMapLoopCallback; /* * Table of dict subcommand names and implementations. */ static const EnsembleImplMap implementationMap[] = { {"append", DictAppendCmd, TclCompileDictAppendCmd, NULL, NULL, 0 }, {"create", DictCreateCmd, TclCompileDictCreateCmd, NULL, NULL, 0 }, {"exists", DictExistsCmd, TclCompileDictExistsCmd, NULL, NULL, 0 }, {"filter", DictFilterCmd, NULL, NULL, NULL, 0 }, {"for", NULL, TclCompileDictForCmd, DictForNRCmd, NULL, 0 }, {"get", DictGetCmd, TclCompileDictGetCmd, NULL, NULL, 0 }, {"getdef", DictGetDefCmd, TclCompileDictGetWithDefaultCmd, NULL,NULL,0}, {"getwithdefault", DictGetDefCmd, TclCompileDictGetWithDefaultCmd, NULL, NULL, 0 }, {"incr", DictIncrCmd, TclCompileDictIncrCmd, NULL, NULL, 0 }, {"info", DictInfoCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0 }, {"keys", DictKeysCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0 }, {"lappend", DictLappendCmd, TclCompileDictLappendCmd, NULL, NULL, 0 }, {"map", NULL, TclCompileDictMapCmd, DictMapNRCmd, NULL, 0 }, {"merge", DictMergeCmd, TclCompileDictMergeCmd, NULL, NULL, 0 }, {"remove", DictRemoveCmd, TclCompileBasicMin1ArgCmd, NULL, NULL, 0 }, {"replace", DictReplaceCmd, NULL, NULL, NULL, 0 }, {"set", DictSetCmd, TclCompileDictSetCmd, NULL, NULL, 0 }, {"size", DictSizeCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0 }, {"unset", DictUnsetCmd, TclCompileDictUnsetCmd, NULL, NULL, 0 }, {"update", DictUpdateCmd, TclCompileDictUpdateCmd, NULL, NULL, 0 }, {"values", DictValuesCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0 }, {"with", DictWithCmd, TclCompileDictWithCmd, NULL, NULL, 0 }, {NULL, NULL, NULL, NULL, NULL, 0} }; /* * Internal representation of the entries in the hash table that backs a * dictionary. */ typedef struct ChainEntry { Tcl_HashEntry entry; struct ChainEntry *prevPtr; struct ChainEntry *nextPtr; } ChainEntry; /* * Internal representation of a dictionary. * * The internal representation of a dictionary object is a hash table (with * Tcl_Objs for both keys and values), a reference count and epoch number for * detecting concurrent modifications of the dictionary, and a pointer to the * parent object (used when invalidating string reps of pathed dictionary * trees) which is NULL in normal use. The fact that hash tables know (with * appropriate initialisation) already about objects makes key management /so/ * much easier! * * Reference counts are used to enable safe iteration across hashes while * allowing the type of the containing object to be modified. */ typedef struct Dict { Tcl_HashTable table; /* Object hash table to store mapping in. */ ChainEntry *entryChainHead; /* Linked list of all entries in the * dictionary. Used for doing traversal of the * entries in the order that they are * created. */ ChainEntry *entryChainTail; /* Other end of linked list of all entries in * the dictionary. Used for doing traversal of * the entries in the order that they are * created. */ size_t epoch; /* Epoch counter */ size_t refCount; /* Reference counter (see above) */ Tcl_Obj *chain; /* Linked list used for invalidating the * string representations of updated nested * dictionaries. */ } Dict; /* * The structure below defines the dictionary object type by means of * functions that can be invoked by generic object code. */ const Tcl_ObjType tclDictType = { "dict", FreeDictInternalRep, /* freeIntRepProc */ DupDictInternalRep, /* dupIntRepProc */ UpdateStringOfDict, /* updateStringProc */ SetDictFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; #define DictSetInternalRep(objPtr, dictRepPtr) \ do { \ Tcl_ObjInternalRep ir; \ ir.twoPtrValue.ptr1 = (dictRepPtr); \ ir.twoPtrValue.ptr2 = NULL; \ Tcl_StoreInternalRep((objPtr), &tclDictType, &ir); \ } while (0) #define DictGetInternalRep(objPtr, dictRepPtr) \ do { \ const Tcl_ObjInternalRep *irPtr; \ irPtr = TclFetchInternalRep((objPtr), &tclDictType); \ (dictRepPtr) = irPtr ? (Dict *)irPtr->twoPtrValue.ptr1 : NULL; \ } while (0) /* * The type of the specially adapted version of the Tcl_Obj*-containing hash * table defined in the tclObj.c code. This version differs in that it * allocates a bit more space in each hash entry in order to hold the pointers * used to keep the hash entries in a linked list. * * Note that this type of hash table is *only* suitable for direct use in * *this* file. Everything else should use the dict iterator API. */ static const Tcl_HashKeyType chainHashType = { TCL_HASH_KEY_TYPE_VERSION, TCL_HASH_KEY_DIRECT_COMPARE, /* allows compare keys by pointers */ TclHashObjKey, TclCompareObjKeys, AllocChainEntry, TclFreeObjEntry }; /* * Structure used in implementation of 'dict map' to hold the state that gets * passed between parts of the implementation. */ typedef struct { Tcl_Obj *keyVarObj; /* The name of the variable that will have * keys assigned to it. */ Tcl_Obj *valueVarObj; /* The name of the variable that will have * values assigned to it. */ Tcl_DictSearch search; /* The dictionary search structure. */ Tcl_Obj *scriptObj; /* The script to evaluate each time through * the loop. */ Tcl_Obj *accumulatorObj; /* The dictionary used to accumulate the * results. */ } DictMapStorage; /***** START OF FUNCTIONS IMPLEMENTING DICT CORE API *****/ /* *---------------------------------------------------------------------- * * AllocChainEntry -- * * Allocate space for a Tcl_HashEntry containing the Tcl_Obj * key, and * which has a bit of extra space afterwards for storing pointers to the * rest of the chain of entries (the extra pointers are left NULL). * * Results: * The return value is a pointer to the created entry. * * Side effects: * Increments the reference count on the object. * *---------------------------------------------------------------------- */ static Tcl_HashEntry * AllocChainEntry( TCL_UNUSED(Tcl_HashTable *), void *keyPtr) { Tcl_Obj *objPtr = (Tcl_Obj *)keyPtr; ChainEntry *cPtr; cPtr = (ChainEntry *)Tcl_Alloc(sizeof(ChainEntry)); cPtr->entry.key.objPtr = objPtr; Tcl_IncrRefCount(objPtr); Tcl_SetHashValue(&cPtr->entry, NULL); cPtr->prevPtr = cPtr->nextPtr = NULL; return &cPtr->entry; } /* * Helper functions that disguise most of the details relating to how the * linked list of hash entries is managed. In particular, these manage the * creation of the table and initializing of the chain, the deletion of the * table and chain, the adding of an entry to the chain, and the removal of an * entry from the chain. */ static inline void InitChainTable( Dict *dict) { Tcl_InitCustomHashTable(&dict->table, TCL_CUSTOM_PTR_KEYS, &chainHashType); dict->entryChainHead = dict->entryChainTail = NULL; } static inline void DeleteChainTable( Dict *dict) { ChainEntry *cPtr; for (cPtr=dict->entryChainHead ; cPtr!=NULL ; cPtr=cPtr->nextPtr) { Tcl_Obj *valuePtr = (Tcl_Obj *)Tcl_GetHashValue(&cPtr->entry); TclDecrRefCount(valuePtr); } Tcl_DeleteHashTable(&dict->table); } static inline Tcl_HashEntry * CreateChainEntry( Dict *dict, Tcl_Obj *keyPtr, int *newPtr) { ChainEntry *cPtr = (ChainEntry *) Tcl_CreateHashEntry(&dict->table, keyPtr, newPtr); /* * If this is a new entry in the hash table, stitch it into the chain. */ if (*newPtr) { cPtr->nextPtr = NULL; if (dict->entryChainHead == NULL) { cPtr->prevPtr = NULL; dict->entryChainHead = cPtr; dict->entryChainTail = cPtr; } else { cPtr->prevPtr = dict->entryChainTail; dict->entryChainTail->nextPtr = cPtr; dict->entryChainTail = cPtr; } } return &cPtr->entry; } static inline int DeleteChainEntry( Dict *dict, Tcl_Obj *keyPtr) { ChainEntry *cPtr = (ChainEntry *) Tcl_FindHashEntry(&dict->table, keyPtr); if (cPtr == NULL) { return 0; } else { Tcl_Obj *valuePtr = (Tcl_Obj *)Tcl_GetHashValue(&cPtr->entry); TclDecrRefCount(valuePtr); } /* * Unstitch from the chain. */ if (cPtr->nextPtr) { cPtr->nextPtr->prevPtr = cPtr->prevPtr; } else { dict->entryChainTail = cPtr->prevPtr; } if (cPtr->prevPtr) { cPtr->prevPtr->nextPtr = cPtr->nextPtr; } else { dict->entryChainHead = cPtr->nextPtr; } Tcl_DeleteHashEntry(&cPtr->entry); return 1; } /* *---------------------------------------------------------------------- * * DupDictInternalRep -- * * Initialize the internal representation of a dictionary Tcl_Obj to a * copy of the internal representation of an existing dictionary object. * * Results: * None. * * Side effects: * "srcPtr"s dictionary internal rep pointer should not be NULL and we * assume it is not NULL. We set "copyPtr"s internal rep to a pointer to * a newly allocated dictionary rep that, in turn, points to "srcPtr"s * key and value objects. Those objects are not actually copied but are * shared between "srcPtr" and "copyPtr". The ref count of each key and * value object is incremented. * *---------------------------------------------------------------------- */ static void DupDictInternalRep( Tcl_Obj *srcPtr, Tcl_Obj *copyPtr) { Dict *oldDict, *newDict = (Dict *)Tcl_Alloc(sizeof(Dict)); ChainEntry *cPtr; DictGetInternalRep(srcPtr, oldDict); /* * Copy values across from the old hash table. */ InitChainTable(newDict); for (cPtr=oldDict->entryChainHead ; cPtr!=NULL ; cPtr=cPtr->nextPtr) { Tcl_Obj *key = (Tcl_Obj *)Tcl_GetHashKey(&oldDict->table, &cPtr->entry); Tcl_Obj *valuePtr = (Tcl_Obj *)Tcl_GetHashValue(&cPtr->entry); int n; Tcl_HashEntry *hPtr = CreateChainEntry(newDict, key, &n); /* * Fill in the contents. */ Tcl_SetHashValue(hPtr, valuePtr); Tcl_IncrRefCount(valuePtr); } /* * Initialise other fields. */ newDict->epoch = 1; newDict->chain = NULL; newDict->refCount = 1; /* * Store in the object. */ DictSetInternalRep(copyPtr, newDict); } /* *---------------------------------------------------------------------- * * FreeDictInternalRep -- * * Deallocate the storage associated with a dictionary object's internal * representation. * * Results: * None * * Side effects: * Frees the memory holding the dictionary's internal hash table unless * it is locked by an iteration going over it. * *---------------------------------------------------------------------- */ static void FreeDictInternalRep( Tcl_Obj *dictPtr) { Dict *dict; DictGetInternalRep(dictPtr, dict); if (dict->refCount-- <= 1) { DeleteDict(dict); } } /* *---------------------------------------------------------------------- * * DeleteDict -- * * Delete the structure that is used to implement a dictionary's internal * representation. Called when either the dictionary object loses its * internal representation or when the last iteration over the dictionary * completes. * * Results: * None * * Side effects: * Decrements the reference count of all key and value objects in the * dictionary, which may free them. * *---------------------------------------------------------------------- */ static void DeleteDict( Dict *dict) { DeleteChainTable(dict); Tcl_Free(dict); } /* *---------------------------------------------------------------------- * * UpdateStringOfDict -- * * Update the string representation for a dictionary object. Note: This * function does not invalidate an existing old string rep so storage * will be lost if this has not already been done. This code is based on * UpdateStringOfList in tclListObj.c * * Results: * None. * * Side effects: * The object's string is set to a valid string that results from the * dict-to-string conversion. This string will be empty if the dictionary * has no key/value pairs. The dictionary internal representation should * not be NULL and we assume it is not NULL. * *---------------------------------------------------------------------- */ static void UpdateStringOfDict( Tcl_Obj *dictPtr) { #define LOCAL_SIZE 64 char localFlags[LOCAL_SIZE], *flagPtr = NULL; Dict *dict; ChainEntry *cPtr; Tcl_Obj *keyPtr, *valuePtr; Tcl_Size i, length; size_t bytesNeeded = 0; const char *elem; char *dst; /* * This field is the most useful one in the whole hash structure, and it * is not exposed by any API function... */ Tcl_Size numElems; DictGetInternalRep(dictPtr, dict); assert (dict != NULL); numElems = dict->table.numEntries * 2; /* Handle empty list case first, simplifies what follows */ if (numElems == 0) { Tcl_InitStringRep(dictPtr, NULL, 0); return; } /* * Pass 1: estimate space, gather flags. */ if (numElems <= LOCAL_SIZE) { flagPtr = localFlags; } else { flagPtr = (char *)Tcl_Alloc(numElems); } for (i=0,cPtr=dict->entryChainHead; inextPtr) { /* * Assume that cPtr is never NULL since we know the number of array * elements already. */ flagPtr[i] = ( i ? TCL_DONT_QUOTE_HASH : 0 ); keyPtr = (Tcl_Obj *)Tcl_GetHashKey(&dict->table, &cPtr->entry); elem = TclGetStringFromObj(keyPtr, &length); bytesNeeded += TclScanElement(elem, length, flagPtr+i); flagPtr[i+1] = TCL_DONT_QUOTE_HASH; valuePtr = (Tcl_Obj *)Tcl_GetHashValue(&cPtr->entry); elem = TclGetStringFromObj(valuePtr, &length); bytesNeeded += TclScanElement(elem, length, flagPtr+i+1); } bytesNeeded += numElems; /* * Pass 2: copy into string rep buffer. */ dst = Tcl_InitStringRep(dictPtr, NULL, bytesNeeded - 1); TclOOM(dst, bytesNeeded); for (i=0,cPtr=dict->entryChainHead; inextPtr) { flagPtr[i] |= ( i ? TCL_DONT_QUOTE_HASH : 0 ); keyPtr = (Tcl_Obj *)Tcl_GetHashKey(&dict->table, &cPtr->entry); elem = TclGetStringFromObj(keyPtr, &length); dst += TclConvertElement(elem, length, dst, flagPtr[i]); *dst++ = ' '; flagPtr[i+1] |= TCL_DONT_QUOTE_HASH; valuePtr = (Tcl_Obj *)Tcl_GetHashValue(&cPtr->entry); elem = TclGetStringFromObj(valuePtr, &length); dst += TclConvertElement(elem, length, dst, flagPtr[i+1]); *dst++ = ' '; } /* Last space overwrote the terminating NUL; cal T_ISR again to restore */ (void)Tcl_InitStringRep(dictPtr, NULL, bytesNeeded - 1); if (flagPtr != localFlags) { Tcl_Free(flagPtr); } } /* *---------------------------------------------------------------------- * * SetDictFromAny -- * * Convert a non-dictionary object into a dictionary object. This code is * very closely related to SetListFromAny in tclListObj.c but does not * actually guarantee that a dictionary object will have a string rep (as * conversions from lists are handled with a special case.) * * Results: * A standard Tcl result. * * Side effects: * If the string can be converted, it loses any old internal * representation that it had and gains a dictionary's internalRep. * *---------------------------------------------------------------------- */ static int SetDictFromAny( Tcl_Interp *interp, Tcl_Obj *objPtr) { Tcl_HashEntry *hPtr; int isNew; Dict *dict = (Dict *)Tcl_Alloc(sizeof(Dict)); InitChainTable(dict); /* * Since lists and dictionaries have very closely-related string * representations (i.e. the same parsing code) we can safely special-case * the conversion from lists to dictionaries. */ if (TclHasInternalRep(objPtr, &tclListType)) { Tcl_Size objc, i; Tcl_Obj **objv; /* Cannot fail, we already know the Tcl_ObjType is "list". */ TclListObjGetElements(NULL, objPtr, &objc, &objv); if (objc & 1) { goto missingValue; } for (i=0 ; iepoch = 1; dict->chain = NULL; dict->refCount = 1; DictSetInternalRep(objPtr, dict); return TCL_OK; missingValue: if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "missing value to go with key", -1)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "DICTIONARY", (char *)NULL); } errorInFindDictElement: DeleteChainTable(dict); Tcl_Free(dict); return TCL_ERROR; } static Dict * GetDictFromObj( Tcl_Interp *interp, Tcl_Obj *dictPtr) { Dict *dict; DictGetInternalRep(dictPtr, dict); if (dict == NULL) { if (SetDictFromAny(interp, dictPtr) != TCL_OK) { return NULL; } DictGetInternalRep(dictPtr, dict); } return dict; } /* *---------------------------------------------------------------------- * * TclTraceDictPath -- * * Trace through a tree of dictionaries using the array of keys given. If * the flags argument has the DICT_PATH_UPDATE flag is set, a * backward-pointing chain of dictionaries is also built (in the Dict's * chain field) and the chained dictionaries are made into unshared * dictionaries (if they aren't already.) * * Results: * The object at the end of the path, or NULL if there was an error. Note * that this it is an error for an intermediate dictionary on the path to * not exist. If the flags argument has the DICT_PATH_EXISTS set, a * non-existent path gives a DICT_PATH_NON_EXISTENT result. * * Side effects: * If the flags argument is zero or DICT_PATH_EXISTS, there are no side * effects (other than potential conversion of objects to dictionaries.) * If the flags argument is DICT_PATH_UPDATE, the following additional * side effects occur. Shared dictionaries along the path are converted * into unshared objects, and a backward-pointing chain is built using * the chain fields of the dictionaries (for easy invalidation of string * representations using InvalidateDictChain). If the flags argument has * the DICT_PATH_CREATE bits set (and not the DICT_PATH_EXISTS bit), * non-extant keys will be inserted with a value of an empty * dictionary, resulting in the path being built. * *---------------------------------------------------------------------- */ Tcl_Obj * TclTraceDictPath( Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size keyc, Tcl_Obj *const keyv[], int flags) { Dict *dict, *newDict; Tcl_Size i; DictGetInternalRep(dictPtr, dict); if (dict == NULL) { if (SetDictFromAny(interp, dictPtr) != TCL_OK) { return NULL; } DictGetInternalRep(dictPtr, dict); } if (flags & DICT_PATH_UPDATE) { dict->chain = NULL; } for (i=0 ; itable, keyv[i]); Tcl_Obj *tmpObj; if (hPtr == NULL) { int isNew; /* Dummy */ if (flags & DICT_PATH_EXISTS) { return DICT_PATH_NON_EXISTENT; } if ((flags & DICT_PATH_CREATE) != DICT_PATH_CREATE) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "key \"%s\" not known in dictionary", TclGetString(keyv[i]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "DICT", TclGetString(keyv[i]), (char *)NULL); } return NULL; } /* * The next line should always set isNew to 1. */ hPtr = CreateChainEntry(dict, keyv[i], &isNew); tmpObj = Tcl_NewDictObj(); Tcl_IncrRefCount(tmpObj); Tcl_SetHashValue(hPtr, tmpObj); } else { tmpObj = (Tcl_Obj *)Tcl_GetHashValue(hPtr); DictGetInternalRep(tmpObj, newDict); if (newDict == NULL) { if (SetDictFromAny(interp, tmpObj) != TCL_OK) { return NULL; } } } DictGetInternalRep(tmpObj, newDict); if (flags & DICT_PATH_UPDATE) { if (Tcl_IsShared(tmpObj)) { TclDecrRefCount(tmpObj); tmpObj = Tcl_DuplicateObj(tmpObj); Tcl_IncrRefCount(tmpObj); Tcl_SetHashValue(hPtr, tmpObj); dict->epoch++; DictGetInternalRep(tmpObj, newDict); } newDict->chain = dictPtr; } dict = newDict; dictPtr = tmpObj; } return dictPtr; } /* *---------------------------------------------------------------------- * * InvalidateDictChain -- * * Go through a dictionary chain (built by an updating invocation of * TclTraceDictPath) and invalidate the string representations of all the * dictionaries on the chain. * * Results: * None * * Side effects: * String reps are invalidated and epoch counters (for detecting illegal * concurrent modifications) are updated through the chain of updated * dictionaries. * *---------------------------------------------------------------------- */ static void InvalidateDictChain( Tcl_Obj *dictObj) { Dict *dict; DictGetInternalRep(dictObj, dict); assert( dict != NULL); do { dict->refCount++; TclInvalidateStringRep(dictObj); TclFreeInternalRep(dictObj); DictSetInternalRep(dictObj, dict); dict->epoch++; dictObj = dict->chain; if (dictObj == NULL) { break; } dict->chain = NULL; DictGetInternalRep(dictObj, dict); } while (dict != NULL); } /* *---------------------------------------------------------------------- * * Tcl_DictObjPut -- * * Add a key,value pair to a dictionary, or update the value for a key if * that key already has a mapping in the dictionary. * * Results: * A standard Tcl result. * * Side effects: * The object pointed to by dictPtr is converted to a dictionary if it is * not already one, and any string representation that it has is * invalidated. * *---------------------------------------------------------------------- */ int Tcl_DictObjPut( Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr, Tcl_Obj *valuePtr) { Dict *dict; Tcl_HashEntry *hPtr; int isNew; if (Tcl_IsShared(dictPtr)) { Tcl_Panic("%s called with shared object", "Tcl_DictObjPut"); } dict = GetDictFromObj(interp, dictPtr); if (dict == NULL) { return TCL_ERROR; } TclInvalidateStringRep(dictPtr); hPtr = CreateChainEntry(dict, keyPtr, &isNew); dict->refCount++; TclFreeInternalRep(dictPtr) DictSetInternalRep(dictPtr, dict); Tcl_IncrRefCount(valuePtr); if (!isNew) { Tcl_Obj *oldValuePtr = (Tcl_Obj *)Tcl_GetHashValue(hPtr); TclDecrRefCount(oldValuePtr); } Tcl_SetHashValue(hPtr, valuePtr); dict->epoch++; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_DictObjGet -- * * Given a key, get its value from the dictionary (or NULL if key is not * found in dictionary.) * * Results: * A standard Tcl result. The variable pointed to by valuePtrPtr is * updated with the value for the key. Note that it is not an error for * the key to have no mapping in the dictionary. * * Side effects: * The object pointed to by dictPtr is converted to a dictionary if it is * not already one. * *---------------------------------------------------------------------- */ int Tcl_DictObjGet( Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr, Tcl_Obj **valuePtrPtr) { Dict *dict; Tcl_HashEntry *hPtr; dict = GetDictFromObj(interp, dictPtr); if (dict == NULL) { *valuePtrPtr = NULL; return TCL_ERROR; } hPtr = Tcl_FindHashEntry(&dict->table, keyPtr); if (hPtr == NULL) { *valuePtrPtr = NULL; } else { *valuePtrPtr = (Tcl_Obj *)Tcl_GetHashValue(hPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_DictObjRemove -- * * Remove the key,value pair with the given key from the dictionary; the * key does not need to be present in the dictionary. * * Results: * A standard Tcl result. * * Side effects: * The object pointed to by dictPtr is converted to a dictionary if it is * not already one, and any string representation that it has is * invalidated. * *---------------------------------------------------------------------- */ int Tcl_DictObjRemove( Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr) { Dict *dict; if (Tcl_IsShared(dictPtr)) { Tcl_Panic("%s called with shared object", "Tcl_DictObjRemove"); } dict = GetDictFromObj(interp, dictPtr); if (dict == NULL) { return TCL_ERROR; } if (DeleteChainEntry(dict, keyPtr)) { TclInvalidateStringRep(dictPtr); dict->epoch++; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_DictGetSize * * Returns the size of dictPtr. Caller must ensure that dictPtr has type * 'tclDicttype'. * * *---------------------------------------------------------------------- */ Tcl_Size TclDictGetSize( Tcl_Obj *dictPtr) { Dict *dict; DictGetInternalRep(dictPtr, dict); return dict->table.numEntries; } /* *---------------------------------------------------------------------- * * Tcl_DictObjSize -- * * How many key,value pairs are there in the dictionary? * * Results: * A standard Tcl result. Updates the variable pointed to by sizePtr with * the number of key,value pairs in the dictionary. * * Side effects: * The dictPtr object is converted to a dictionary type if it is not a * dictionary already. * *---------------------------------------------------------------------- */ #undef Tcl_DictObjSize int Tcl_DictObjSize( Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size *sizePtr) { Dict *dict; dict = GetDictFromObj(interp, dictPtr); if (dict == NULL) { return TCL_ERROR; } *sizePtr = dict->table.numEntries; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_DictObjFirst -- * * Start a traversal of the dictionary. Caller must supply the search * context, pointers for returning key and value, and a pointer to allow * indication of whether the dictionary has been traversed (i.e. the * dictionary is empty). The order of traversal is undefined. * * Results: * A standard Tcl result. Updates the variables pointed to by keyPtrPtr, * valuePtrPtr and donePtr. Either of keyPtrPtr and valuePtrPtr may be * NULL, in which case the key/value is not made available to the caller. * * Side effects: * The dictPtr object is converted to a dictionary type if it is not a * dictionary already. The search context is initialised if the search * has not finished. The dictionary's internal rep is Tcl_Preserve()d if * the dictionary has at least one element. * *---------------------------------------------------------------------- */ int Tcl_DictObjFirst( Tcl_Interp *interp, /* For error messages, or NULL if no error * messages desired. */ Tcl_Obj *dictPtr, /* Dictionary to traverse. */ Tcl_DictSearch *searchPtr, /* Pointer to a dict search context. */ Tcl_Obj **keyPtrPtr, /* Pointer to a variable to have the first key * written into, or NULL. */ Tcl_Obj **valuePtrPtr, /* Pointer to a variable to have the first * value written into, or NULL.*/ int *donePtr) /* Pointer to a variable which will have a 1 * written into when there are no further * values in the dictionary, or a 0 * otherwise. */ { Dict *dict; ChainEntry *cPtr; dict = GetDictFromObj(interp, dictPtr); if (dict == NULL) { return TCL_ERROR; } cPtr = dict->entryChainHead; if (cPtr == NULL) { searchPtr->epoch = 0; *donePtr = 1; } else { *donePtr = 0; searchPtr->dictionaryPtr = (Tcl_Dict) dict; searchPtr->epoch = dict->epoch; searchPtr->next = cPtr->nextPtr; dict->refCount++; if (keyPtrPtr != NULL) { *keyPtrPtr = (Tcl_Obj *)Tcl_GetHashKey(&dict->table, &cPtr->entry); } if (valuePtrPtr != NULL) { *valuePtrPtr = (Tcl_Obj *)Tcl_GetHashValue(&cPtr->entry); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_DictObjNext -- * * Continue a traversal of a dictionary previously started with * Tcl_DictObjFirst. This function is safe against concurrent * modification of the underlying object (including type shimmering), * treating such situations as if the search has terminated, though it is * up to the caller to ensure that the object itself is not disposed * until the search has finished. It is _not_ safe against modifications * from other threads. * * Results: * Updates the variables pointed to by keyPtrPtr, valuePtrPtr and * donePtr. Either of keyPtrPtr and valuePtrPtr may be NULL, in which * case the key/value is not made available to the caller. * * Side effects: * Removes a reference to the dictionary's internal rep if the search * terminates. * *---------------------------------------------------------------------- */ void Tcl_DictObjNext( Tcl_DictSearch *searchPtr, /* Pointer to a hash search context. */ Tcl_Obj **keyPtrPtr, /* Pointer to a variable to have the first key * written into, or NULL. */ Tcl_Obj **valuePtrPtr, /* Pointer to a variable to have the first * value written into, or NULL.*/ int *donePtr) /* Pointer to a variable which will have a 1 * written into when there are no further * values in the dictionary, or a 0 * otherwise. */ { ChainEntry *cPtr; /* * If the search is done; we do no work. */ if (!searchPtr->epoch) { *donePtr = 1; return; } /* * Bail out if the dictionary has had any elements added, modified or * removed. This *shouldn't* happen, but... */ if (((Dict *)searchPtr->dictionaryPtr)->epoch != searchPtr->epoch) { Tcl_Panic("concurrent dictionary modification and search"); } cPtr = (ChainEntry *)searchPtr->next; if (cPtr == NULL) { Tcl_DictObjDone(searchPtr); *donePtr = 1; return; } searchPtr->next = cPtr->nextPtr; *donePtr = 0; if (keyPtrPtr != NULL) { *keyPtrPtr = (Tcl_Obj *)Tcl_GetHashKey( &((Dict *)searchPtr->dictionaryPtr)->table, &cPtr->entry); } if (valuePtrPtr != NULL) { *valuePtrPtr = (Tcl_Obj *)Tcl_GetHashValue(&cPtr->entry); } } /* *---------------------------------------------------------------------- * * Tcl_DictObjDone -- * * Call this if you want to stop a search before you reach the end of the * dictionary (e.g. because of abnormal termination of the search). It * need not be used if the search reaches its natural end (i.e. if either * Tcl_DictObjFirst or Tcl_DictObjNext sets its donePtr variable to 1). * * Results: * None. * * Side effects: * Removes a reference to the dictionary's internal rep. * *---------------------------------------------------------------------- */ void Tcl_DictObjDone( Tcl_DictSearch *searchPtr) /* Pointer to a hash search context. */ { Dict *dict; if (searchPtr->epoch) { searchPtr->epoch = 0; dict = (Dict *) searchPtr->dictionaryPtr; if (dict->refCount-- <= 1) { DeleteDict(dict); } } } /* *---------------------------------------------------------------------- * * Tcl_DictObjPutKeyList -- * * Add a key...key,value pair to a dictionary tree. The main dictionary * value must not be shared, though sub-dictionaries may be. All * intermediate dictionaries on the path must exist. * * Results: * A standard Tcl result. Note that in the error case, a message is left * in interp unless that is NULL. * * Side effects: * If the dictionary and any of its sub-dictionaries on the path have * string representations, these are invalidated. * *---------------------------------------------------------------------- */ int Tcl_DictObjPutKeyList( Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size keyc, Tcl_Obj *const keyv[], Tcl_Obj *valuePtr) { Dict *dict; Tcl_HashEntry *hPtr; int isNew; if (Tcl_IsShared(dictPtr)) { Tcl_Panic("%s called with shared object", "Tcl_DictObjPutKeyList"); } if (keyc < 1) { Tcl_Panic("%s called with empty key list", "Tcl_DictObjPutKeyList"); } dictPtr = TclTraceDictPath(interp, dictPtr, keyc-1,keyv, DICT_PATH_CREATE); if (dictPtr == NULL) { return TCL_ERROR; } DictGetInternalRep(dictPtr, dict); assert(dict != NULL); hPtr = CreateChainEntry(dict, keyv[keyc-1], &isNew); Tcl_IncrRefCount(valuePtr); if (!isNew) { Tcl_Obj *oldValuePtr = (Tcl_Obj *)Tcl_GetHashValue(hPtr); TclDecrRefCount(oldValuePtr); } Tcl_SetHashValue(hPtr, valuePtr); InvalidateDictChain(dictPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_DictObjRemoveKeyList -- * * Remove a key...key,value pair from a dictionary tree (the value * removed is implicit in the key path). The main dictionary value must * not be shared, though sub-dictionaries may be. It is not an error if * there is no value associated with the given key list, but all * intermediate dictionaries on the key path must exist. * * Results: * A standard Tcl result. Note that in the error case, a message is left * in interp unless that is NULL. * * Side effects: * If the dictionary and any of its sub-dictionaries on the key path have * string representations, these are invalidated. * *---------------------------------------------------------------------- */ int Tcl_DictObjRemoveKeyList( Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size keyc, Tcl_Obj *const keyv[]) { Dict *dict; if (Tcl_IsShared(dictPtr)) { Tcl_Panic("%s called with shared object", "Tcl_DictObjRemoveKeyList"); } if (keyc < 1) { Tcl_Panic("%s called with empty key list", "Tcl_DictObjRemoveKeyList"); } dictPtr = TclTraceDictPath(interp, dictPtr, keyc-1,keyv, DICT_PATH_UPDATE); if (dictPtr == NULL) { return TCL_ERROR; } DictGetInternalRep(dictPtr, dict); assert(dict != NULL); DeleteChainEntry(dict, keyv[keyc-1]); InvalidateDictChain(dictPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_NewDictObj -- * * This function is normally called when not debugging: i.e., when * TCL_MEM_DEBUG is not defined. It creates a new dict object without any * content. * * When TCL_MEM_DEBUG is defined, this function just returns the result * of calling the debugging version Tcl_DbNewDictObj. * * Results: * A new dict object is returned; it has no keys defined in it. The new * object's string representation is left NULL, and the ref count of the * object is 0. * * Side Effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_NewDictObj(void) { #ifdef TCL_MEM_DEBUG return Tcl_DbNewDictObj("unknown", 0); #else /* !TCL_MEM_DEBUG */ Tcl_Obj *dictPtr; Dict *dict; TclNewObj(dictPtr); TclInvalidateStringRep(dictPtr); dict = (Dict *)Tcl_Alloc(sizeof(Dict)); InitChainTable(dict); dict->epoch = 1; dict->chain = NULL; dict->refCount = 1; DictSetInternalRep(dictPtr, dict); return dictPtr; #endif } /* *---------------------------------------------------------------------- * * Tcl_DbNewDictObj -- * * This function is normally called when debugging: i.e., when * TCL_MEM_DEBUG is defined. It creates new dict objects. It is the same * as the Tcl_NewDictObj function above except that it calls * Tcl_DbCkalloc directly with the file name and line number from its * caller. This simplifies debugging since then the [memory active] * command will report the correct file name and line number when * reporting objects that haven't been freed. * * When TCL_MEM_DEBUG is not defined, this function just returns the * result of calling Tcl_NewDictObj. * * Results: * A new dict object is returned; it has no keys defined in it. The new * object's string representation is left NULL, and the ref count of the * object is 0. * * Side Effects: * None. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG Tcl_Obj * Tcl_DbNewDictObj( const char *file, int line) { Tcl_Obj *dictPtr; Dict *dict; TclDbNewObj(dictPtr, file, line); TclInvalidateStringRep(dictPtr); dict = (Dict *)Tcl_Alloc(sizeof(Dict)); InitChainTable(dict); dict->epoch = 1; dict->chain = NULL; dict->refCount = 1; DictSetInternalRep(dictPtr, dict); return dictPtr; } #else /* !TCL_MEM_DEBUG */ Tcl_Obj * Tcl_DbNewDictObj( TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { return Tcl_NewDictObj(); } #endif /***** START OF FUNCTIONS ACTING AS HELPERS *****/ /* *---------------------------------------------------------------------- * * TclDictGet -- * * Given a key, get its value from the dictionary (or NULL if key is not * found in dictionary.) * * Results: * A standard Tcl result. The variable pointed to by valuePtrPtr is * updated with the value for the key. Note that it is not an error for * the key to have no mapping in the dictionary. * * Side effects: * The object pointed to by dictPtr is converted to a dictionary if it is * not already one. * *---------------------------------------------------------------------- */ int TclDictGet( Tcl_Interp *interp, Tcl_Obj *dictPtr, const char *key, /* The key in a C string. */ Tcl_Obj **valuePtrPtr) /* Where to write the value. */ { Tcl_Obj *keyPtr = Tcl_NewStringObj(key, -1); int code; Tcl_IncrRefCount(keyPtr); code = Tcl_DictObjGet(interp, dictPtr, keyPtr, valuePtrPtr); Tcl_DecrRefCount(keyPtr); return code; } /* *---------------------------------------------------------------------- * * TclDictPut -- * * Add a key,value pair to a dictionary, or update the value for a key if * that key already has a mapping in the dictionary. * * If valuePtr is a zero-count object and is not written into the * dictionary because of an error, it is freed by this routine. The caller * does NOT need to do reference count management. * * Results: * A standard Tcl result. * * Side effects: * The object pointed to by dictPtr is converted to a dictionary if it is * not already one, and any string representation that it has is * invalidated. * *---------------------------------------------------------------------- */ int TclDictPut( Tcl_Interp *interp, Tcl_Obj *dictPtr, const char *key, /* The key in a C string. */ Tcl_Obj *valuePtr) /* The value to write in. */ { Tcl_Obj *keyPtr = Tcl_NewStringObj(key, -1); int code; Tcl_IncrRefCount(keyPtr); Tcl_IncrRefCount(valuePtr); code = Tcl_DictObjPut(interp, dictPtr, keyPtr, valuePtr); Tcl_DecrRefCount(keyPtr); Tcl_DecrRefCount(valuePtr); return code; } /* *---------------------------------------------------------------------- * * TclDictPutString -- * * Add a key,value pair to a dictionary, or update the value for a key if * that key already has a mapping in the dictionary. * * Results: * A standard Tcl result. * * Side effects: * The object pointed to by dictPtr is converted to a dictionary if it is * not already one, and any string representation that it has is * invalidated. * *---------------------------------------------------------------------- */ int TclDictPutString( Tcl_Interp *interp, Tcl_Obj *dictPtr, const char *key, /* The key in a C string. */ const char *value) /* The value in a C string. */ { Tcl_Obj *keyPtr = Tcl_NewStringObj(key, -1); Tcl_Obj *valuePtr = Tcl_NewStringObj(value, -1); int code; Tcl_IncrRefCount(keyPtr); Tcl_IncrRefCount(valuePtr); code = Tcl_DictObjPut(interp, dictPtr, keyPtr, valuePtr); Tcl_DecrRefCount(keyPtr); Tcl_DecrRefCount(valuePtr); return code; } /* *---------------------------------------------------------------------- * * TclDictRemove -- * * Remove the key,value pair with the given key from the dictionary; the * key does not need to be present in the dictionary. * * Results: * A standard Tcl result. * * Side effects: * The object pointed to by dictPtr is converted to a dictionary if it is * not already one, and any string representation that it has is * invalidated. * *---------------------------------------------------------------------- */ int TclDictRemove( Tcl_Interp *interp, Tcl_Obj *dictPtr, const char *key) /* The key in a C string. */ { Tcl_Obj *keyPtr = Tcl_NewStringObj(key, -1); int code; Tcl_IncrRefCount(keyPtr); code = Tcl_DictObjRemove(interp, dictPtr, keyPtr); Tcl_DecrRefCount(keyPtr); return code; } /***** START OF FUNCTIONS IMPLEMENTING TCL COMMANDS *****/ /* *---------------------------------------------------------------------- * * DictCreateCmd -- * * This function implements the "dict create" Tcl command. See the user * documentation for details on what it does, and TIP#111 for the formal * specification. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int DictCreateCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Tcl_Obj *dictObj; int i; /* * Must have an even number of arguments; note that number of preceding * arguments (i.e. "dict create" is also even, which makes this much * easier.) */ if ((objc & 1) == 0) { Tcl_WrongNumArgs(interp, 1, objv, "?key value ...?"); return TCL_ERROR; } dictObj = Tcl_NewDictObj(); for (i=1 ; irefCount++; result->internalRep.twoPtrValue.ptr2 = NULL; result->typePtr = &tclDictType; return result; } /* *---------------------------------------------------------------------- * * DictExistsCmd -- * * This function implements the "dict exists" Tcl command. See the user * documentation for details on what it does, and TIP#111 for the formal * specification. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int DictExistsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Tcl_Obj *dictPtr, *valuePtr; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "dictionary key ?key ...?"); return TCL_ERROR; } dictPtr = TclTraceDictPath(NULL, objv[1], objc-3, objv+2,DICT_PATH_EXISTS); if (dictPtr == NULL || dictPtr == DICT_PATH_NON_EXISTENT || Tcl_DictObjGet(NULL, dictPtr, objv[objc-1], &valuePtr) != TCL_OK) { Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0)); } else { Tcl_SetObjResult(interp, Tcl_NewBooleanObj(valuePtr != NULL)); } return TCL_OK; } /* *---------------------------------------------------------------------- * * DictInfoCmd -- * * This function implements the "dict info" Tcl command. See the user * documentation for details on what it does, and TIP#111 for the formal * specification. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int DictInfoCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Dict *dict; char *statsStr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "dictionary"); return TCL_ERROR; } dict = GetDictFromObj(interp, objv[1]); if (dict == NULL) { return TCL_ERROR; } statsStr = Tcl_HashStats(&dict->table); Tcl_SetObjResult(interp, Tcl_NewStringObj(statsStr, -1)); Tcl_Free(statsStr); return TCL_OK; } /* *---------------------------------------------------------------------- * * DictIncrCmd -- * * This function implements the "dict incr" Tcl command. See the user * documentation for details on what it does, and TIP#111 for the formal * specification. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int DictIncrCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { int code = TCL_OK; Tcl_Obj *dictPtr, *valuePtr = NULL; if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "dictVarName key ?increment?"); return TCL_ERROR; } dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, 0); if (dictPtr == NULL) { /* * Variable didn't yet exist. Create new dictionary value. */ dictPtr = Tcl_NewDictObj(); } else if (Tcl_DictObjGet(interp, dictPtr, objv[2], &valuePtr) != TCL_OK) { /* * Variable contents are not a dict, report error. */ return TCL_ERROR; } if (Tcl_IsShared(dictPtr)) { /* * A little internals surgery to avoid copying a string rep that will * soon be no good. */ Tcl_Obj *oldPtr = dictPtr; TclNewObj(dictPtr); TclInvalidateStringRep(dictPtr); DupDictInternalRep(oldPtr, dictPtr); } if (valuePtr == NULL) { /* * Key not in dictionary. Create new key with increment as value. */ if (objc == 4) { /* * Verify increment is an integer. */ mp_int increment; code = Tcl_GetBignumFromObj(interp, objv[3], &increment); if (code != TCL_OK) { Tcl_AddErrorInfo(interp, "\n (reading increment)"); } else { /* * Remember to dispose with the bignum as we're not actually * using it directly. [Bug 2874678] */ mp_clear(&increment); Tcl_DictObjPut(NULL, dictPtr, objv[2], objv[3]); } } else { Tcl_DictObjPut(NULL, dictPtr, objv[2], Tcl_NewWideIntObj(1)); } } else { /* * Key in dictionary. Increment its value with minimum dup. */ if (Tcl_IsShared(valuePtr)) { valuePtr = Tcl_DuplicateObj(valuePtr); Tcl_DictObjPut(NULL, dictPtr, objv[2], valuePtr); } if (objc == 4) { code = TclIncrObj(interp, valuePtr, objv[3]); } else { Tcl_Obj *incrPtr; TclNewIntObj(incrPtr, 1); Tcl_IncrRefCount(incrPtr); code = TclIncrObj(interp, valuePtr, incrPtr); TclDecrRefCount(incrPtr); } } if (code == TCL_OK) { TclInvalidateStringRep(dictPtr); valuePtr = Tcl_ObjSetVar2(interp, objv[1], NULL, dictPtr, TCL_LEAVE_ERR_MSG); if (valuePtr == NULL) { code = TCL_ERROR; } else { Tcl_SetObjResult(interp, valuePtr); } } else if (dictPtr->refCount == 0) { TclDecrRefCount(dictPtr); } return code; } /* *---------------------------------------------------------------------- * * DictLappendCmd -- * * This function implements the "dict lappend" Tcl command. See the user * documentation for details on what it does, and TIP#111 for the formal * specification. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int DictLappendCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Tcl_Obj *dictPtr, *valuePtr, *resultPtr; int i, allocatedDict = 0, allocatedValue = 0; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "dictVarName key ?value ...?"); return TCL_ERROR; } dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, 0); if (dictPtr == NULL) { allocatedDict = 1; dictPtr = Tcl_NewDictObj(); } else if (Tcl_IsShared(dictPtr)) { allocatedDict = 1; dictPtr = Tcl_DuplicateObj(dictPtr); } if (Tcl_DictObjGet(interp, dictPtr, objv[2], &valuePtr) != TCL_OK) { if (allocatedDict) { TclDecrRefCount(dictPtr); } return TCL_ERROR; } if (valuePtr == NULL) { valuePtr = Tcl_NewListObj(objc-3, objv+3); allocatedValue = 1; } else { if (Tcl_IsShared(valuePtr)) { allocatedValue = 1; valuePtr = Tcl_DuplicateObj(valuePtr); } for (i=3 ; i 3) || (valuePtr == NULL)) { /* Only go through append activites when something will change. */ Tcl_Obj *appendObjPtr = NULL; if (objc > 3) { /* Something to append */ if (objc == 4) { appendObjPtr = objv[3]; } else { appendObjPtr = TclStringCat(interp, objc-3, objv+3, TCL_STRING_IN_PLACE); if (appendObjPtr == NULL) { return TCL_ERROR; } } } if (appendObjPtr == NULL) { /* => (objc == 3) => (valuePtr == NULL) */ TclNewObj(valuePtr); } else if (valuePtr == NULL) { valuePtr = appendObjPtr; appendObjPtr = NULL; } if (appendObjPtr) { if (Tcl_IsShared(valuePtr)) { valuePtr = Tcl_DuplicateObj(valuePtr); } Tcl_IncrRefCount(appendObjPtr); Tcl_AppendObjToObj(valuePtr, appendObjPtr); Tcl_DecrRefCount(appendObjPtr); } Tcl_DictObjPut(NULL, dictPtr, objv[2], valuePtr); } /* * Even if nothing changed, we still overwrite so that variable * trace expectations are met. */ resultPtr = Tcl_ObjSetVar2(interp, objv[1], NULL, dictPtr, TCL_LEAVE_ERR_MSG); if (resultPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, resultPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * DictForNRCmd -- * * These functions implement the "dict for" Tcl command. See the user * documentation for details on what it does, and TIP#111 for the formal * specification. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int DictForNRCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Interp *iPtr = (Interp *) interp; Tcl_Obj *scriptObj, *keyVarObj, *valueVarObj; Tcl_Obj **varv, *keyObj, *valueObj; Tcl_DictSearch *searchPtr; Tcl_Size varc; int done; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "{keyVarName valueVarName} dictionary script"); return TCL_ERROR; } /* * Parse arguments. */ if (TclListObjGetElements(interp, objv[1], &varc, &varv) != TCL_OK) { return TCL_ERROR; } if (varc != 2) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "must have exactly two variable names", -1)); Tcl_SetErrorCode(interp, "TCL", "SYNTAX", "dict", "for", (char *)NULL); return TCL_ERROR; } searchPtr = (Tcl_DictSearch *)TclStackAlloc(interp, sizeof(Tcl_DictSearch)); if (Tcl_DictObjFirst(interp, objv[2], searchPtr, &keyObj, &valueObj, &done) != TCL_OK) { TclStackFree(interp, searchPtr); return TCL_ERROR; } if (done) { TclStackFree(interp, searchPtr); return TCL_OK; } TclListObjGetElements(NULL, objv[1], &varc, &varv); keyVarObj = varv[0]; valueVarObj = varv[1]; scriptObj = objv[3]; /* * Make sure that these objects (which we need throughout the body of the * loop) don't vanish. Note that the dictionary internal rep is locked * internally so that updates, shimmering, etc are not a problem. */ Tcl_IncrRefCount(keyVarObj); Tcl_IncrRefCount(valueVarObj); Tcl_IncrRefCount(scriptObj); /* * Stop the value from getting hit in any way by any traces on the key * variable. */ Tcl_IncrRefCount(valueObj); if (Tcl_ObjSetVar2(interp, keyVarObj, NULL, keyObj, TCL_LEAVE_ERR_MSG) == NULL) { TclDecrRefCount(valueObj); goto error; } TclDecrRefCount(valueObj); if (Tcl_ObjSetVar2(interp, valueVarObj, NULL, valueObj, TCL_LEAVE_ERR_MSG) == NULL) { goto error; } /* * Run the script. */ TclNRAddCallback(interp, DictForLoopCallback, searchPtr, keyVarObj, valueVarObj, scriptObj); return TclNREvalObjEx(interp, scriptObj, 0, iPtr->cmdFramePtr, 3); /* * For unwinding everything on error. */ error: TclDecrRefCount(keyVarObj); TclDecrRefCount(valueVarObj); TclDecrRefCount(scriptObj); Tcl_DictObjDone(searchPtr); TclStackFree(interp, searchPtr); return TCL_ERROR; } static int DictForLoopCallback( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; Tcl_DictSearch *searchPtr = (Tcl_DictSearch *)data[0]; Tcl_Obj *keyVarObj = (Tcl_Obj *)data[1]; Tcl_Obj *valueVarObj = (Tcl_Obj *)data[2]; Tcl_Obj *scriptObj = (Tcl_Obj *)data[3]; Tcl_Obj *keyObj, *valueObj; int done; /* * Process the result from the previous execution of the script body. */ if (result == TCL_CONTINUE) { result = TCL_OK; } else if (result != TCL_OK) { if (result == TCL_BREAK) { Tcl_ResetResult(interp); result = TCL_OK; } else if (result == TCL_ERROR) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (\"dict for\" body line %d)", Tcl_GetErrorLine(interp))); } goto done; } /* * Get the next mapping from the dictionary. */ Tcl_DictObjNext(searchPtr, &keyObj, &valueObj, &done); if (done) { Tcl_ResetResult(interp); goto done; } /* * Stop the value from getting hit in any way by any traces on the key * variable. */ Tcl_IncrRefCount(valueObj); if (Tcl_ObjSetVar2(interp, keyVarObj, NULL, keyObj, TCL_LEAVE_ERR_MSG) == NULL) { TclDecrRefCount(valueObj); result = TCL_ERROR; goto done; } TclDecrRefCount(valueObj); if (Tcl_ObjSetVar2(interp, valueVarObj, NULL, valueObj, TCL_LEAVE_ERR_MSG) == NULL) { result = TCL_ERROR; goto done; } /* * Run the script. */ TclNRAddCallback(interp, DictForLoopCallback, searchPtr, keyVarObj, valueVarObj, scriptObj); return TclNREvalObjEx(interp, scriptObj, 0, iPtr->cmdFramePtr, 3); /* * For unwinding everything once the iterating is done. */ done: TclDecrRefCount(keyVarObj); TclDecrRefCount(valueVarObj); TclDecrRefCount(scriptObj); Tcl_DictObjDone(searchPtr); TclStackFree(interp, searchPtr); return result; } /* *---------------------------------------------------------------------- * * DictMapNRCmd -- * * These functions implement the "dict map" Tcl command. See the user * documentation for details on what it does, and TIP#405 for the formal * specification. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int DictMapNRCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Interp *iPtr = (Interp *) interp; Tcl_Obj **varv, *keyObj, *valueObj; DictMapStorage *storagePtr; Tcl_Size varc; int done; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "{keyVarName valueVarName} dictionary script"); return TCL_ERROR; } /* * Parse arguments. */ if (TclListObjGetElements(interp, objv[1], &varc, &varv) != TCL_OK) { return TCL_ERROR; } if (varc != 2) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "must have exactly two variable names", -1)); Tcl_SetErrorCode(interp, "TCL", "SYNTAX", "dict", "map", (char *)NULL); return TCL_ERROR; } storagePtr = (DictMapStorage *)TclStackAlloc(interp, sizeof(DictMapStorage)); if (Tcl_DictObjFirst(interp, objv[2], &storagePtr->search, &keyObj, &valueObj, &done) != TCL_OK) { TclStackFree(interp, storagePtr); return TCL_ERROR; } if (done) { /* * Note that this exit leaves an empty value in the result (due to * command calling conventions) but that is OK since an empty value is * an empty dictionary. */ TclStackFree(interp, storagePtr); return TCL_OK; } TclNewObj(storagePtr->accumulatorObj); TclListObjGetElements(NULL, objv[1], &varc, &varv); storagePtr->keyVarObj = varv[0]; storagePtr->valueVarObj = varv[1]; storagePtr->scriptObj = objv[3]; /* * Make sure that these objects (which we need throughout the body of the * loop) don't vanish. Note that the dictionary internal rep is locked * internally so that updates, shimmering, etc are not a problem. */ Tcl_IncrRefCount(storagePtr->accumulatorObj); Tcl_IncrRefCount(storagePtr->keyVarObj); Tcl_IncrRefCount(storagePtr->valueVarObj); Tcl_IncrRefCount(storagePtr->scriptObj); /* * Stop the value from getting hit in any way by any traces on the key * variable. */ Tcl_IncrRefCount(valueObj); if (Tcl_ObjSetVar2(interp, storagePtr->keyVarObj, NULL, keyObj, TCL_LEAVE_ERR_MSG) == NULL) { TclDecrRefCount(valueObj); goto error; } if (Tcl_ObjSetVar2(interp, storagePtr->valueVarObj, NULL, valueObj, TCL_LEAVE_ERR_MSG) == NULL) { TclDecrRefCount(valueObj); goto error; } TclDecrRefCount(valueObj); /* * Run the script. */ TclNRAddCallback(interp, DictMapLoopCallback, storagePtr, NULL,NULL,NULL); return TclNREvalObjEx(interp, storagePtr->scriptObj, 0, iPtr->cmdFramePtr, 3); /* * For unwinding everything on error. */ error: TclDecrRefCount(storagePtr->keyVarObj); TclDecrRefCount(storagePtr->valueVarObj); TclDecrRefCount(storagePtr->scriptObj); TclDecrRefCount(storagePtr->accumulatorObj); Tcl_DictObjDone(&storagePtr->search); TclStackFree(interp, storagePtr); return TCL_ERROR; } static int DictMapLoopCallback( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; DictMapStorage *storagePtr = (DictMapStorage *)data[0]; Tcl_Obj *keyObj, *valueObj; int done; /* * Process the result from the previous execution of the script body. */ if (result == TCL_CONTINUE) { result = TCL_OK; } else if (result != TCL_OK) { if (result == TCL_BREAK) { Tcl_ResetResult(interp); result = TCL_OK; } else if (result == TCL_ERROR) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (\"dict map\" body line %d)", Tcl_GetErrorLine(interp))); } goto done; } else { keyObj = Tcl_ObjGetVar2(interp, storagePtr->keyVarObj, NULL, TCL_LEAVE_ERR_MSG); if (keyObj == NULL) { result = TCL_ERROR; goto done; } Tcl_DictObjPut(NULL, storagePtr->accumulatorObj, keyObj, Tcl_GetObjResult(interp)); } /* * Get the next mapping from the dictionary. */ Tcl_DictObjNext(&storagePtr->search, &keyObj, &valueObj, &done); if (done) { Tcl_SetObjResult(interp, storagePtr->accumulatorObj); goto done; } /* * Stop the value from getting hit in any way by any traces on the key * variable. */ Tcl_IncrRefCount(valueObj); if (Tcl_ObjSetVar2(interp, storagePtr->keyVarObj, NULL, keyObj, TCL_LEAVE_ERR_MSG) == NULL) { TclDecrRefCount(valueObj); result = TCL_ERROR; goto done; } if (Tcl_ObjSetVar2(interp, storagePtr->valueVarObj, NULL, valueObj, TCL_LEAVE_ERR_MSG) == NULL) { TclDecrRefCount(valueObj); result = TCL_ERROR; goto done; } TclDecrRefCount(valueObj); /* * Run the script. */ TclNRAddCallback(interp, DictMapLoopCallback, storagePtr, NULL,NULL,NULL); return TclNREvalObjEx(interp, storagePtr->scriptObj, 0, iPtr->cmdFramePtr, 3); /* * For unwinding everything once the iterating is done. */ done: TclDecrRefCount(storagePtr->keyVarObj); TclDecrRefCount(storagePtr->valueVarObj); TclDecrRefCount(storagePtr->scriptObj); TclDecrRefCount(storagePtr->accumulatorObj); Tcl_DictObjDone(&storagePtr->search); TclStackFree(interp, storagePtr); return result; } /* *---------------------------------------------------------------------- * * DictSetCmd -- * * This function implements the "dict set" Tcl command. See the user * documentation for details on what it does, and TIP#111 for the formal * specification. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int DictSetCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Tcl_Obj *dictPtr, *resultPtr; int result, allocatedDict = 0; if (objc < 4) { Tcl_WrongNumArgs(interp, 1, objv, "dictVarName key ?key ...? value"); return TCL_ERROR; } dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, 0); if (dictPtr == NULL) { allocatedDict = 1; dictPtr = Tcl_NewDictObj(); } else if (Tcl_IsShared(dictPtr)) { allocatedDict = 1; dictPtr = Tcl_DuplicateObj(dictPtr); } result = Tcl_DictObjPutKeyList(interp, dictPtr, objc-3, objv+2, objv[objc-1]); if (result != TCL_OK) { if (allocatedDict) { TclDecrRefCount(dictPtr); } return TCL_ERROR; } resultPtr = Tcl_ObjSetVar2(interp, objv[1], NULL, dictPtr, TCL_LEAVE_ERR_MSG); if (resultPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, resultPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * DictUnsetCmd -- * * This function implements the "dict unset" Tcl command. See the user * documentation for details on what it does, and TIP#111 for the formal * specification. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int DictUnsetCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Tcl_Obj *dictPtr, *resultPtr; int result, allocatedDict = 0; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "dictVarName key ?key ...?"); return TCL_ERROR; } dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, 0); if (dictPtr == NULL) { allocatedDict = 1; dictPtr = Tcl_NewDictObj(); } else if (Tcl_IsShared(dictPtr)) { allocatedDict = 1; dictPtr = Tcl_DuplicateObj(dictPtr); } result = Tcl_DictObjRemoveKeyList(interp, dictPtr, objc-2, objv+2); if (result != TCL_OK) { if (allocatedDict) { TclDecrRefCount(dictPtr); } return TCL_ERROR; } resultPtr = Tcl_ObjSetVar2(interp, objv[1], NULL, dictPtr, TCL_LEAVE_ERR_MSG); if (resultPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, resultPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * DictFilterCmd -- * * This function implements the "dict filter" Tcl command. See the user * documentation for details on what it does, and TIP#111 for the formal * specification. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int DictFilterCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Interp *iPtr = (Interp *) interp; static const char *const filters[] = { "key", "script", "value", NULL }; enum FilterTypes { FILTER_KEYS, FILTER_SCRIPT, FILTER_VALUES } index; Tcl_Obj *scriptObj, *keyVarObj, *valueVarObj; Tcl_Obj **varv, *keyObj = NULL, *valueObj = NULL, *resultObj, *boolObj; Tcl_DictSearch search; int done, result, satisfied; Tcl_Size varc; const char *pattern; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "dictionary filterType ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[2], filters, "filterType", 0, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case FILTER_KEYS: /* * Create a dictionary whose keys all match a certain pattern. */ if (Tcl_DictObjFirst(interp, objv[1], &search, &keyObj, &valueObj, &done) != TCL_OK) { return TCL_ERROR; } if (objc == 3) { /* * Nothing to match, so return nothing (== empty dictionary). */ Tcl_DictObjDone(&search); return TCL_OK; } else if (objc == 4) { pattern = TclGetString(objv[3]); resultObj = Tcl_NewDictObj(); if (TclMatchIsTrivial(pattern)) { /* * Must release the search lock here to prevent a memory leak * since we are not exhausing the search. [Bug 1705778, leak * K05] */ Tcl_DictObjDone(&search); Tcl_DictObjGet(interp, objv[1], objv[3], &valueObj); if (valueObj != NULL) { Tcl_DictObjPut(NULL, resultObj, objv[3], valueObj); } } else { while (!done) { if (Tcl_StringMatch(TclGetString(keyObj), pattern)) { Tcl_DictObjPut(NULL, resultObj, keyObj, valueObj); } Tcl_DictObjNext(&search, &keyObj, &valueObj, &done); } } } else { /* * Can't optimize this match for trivial globbing: would disturb * order. */ resultObj = Tcl_NewDictObj(); while (!done) { int i; for (i=3 ; icmdFramePtr, 4); switch (result) { case TCL_OK: boolObj = Tcl_GetObjResult(interp); Tcl_IncrRefCount(boolObj); Tcl_ResetResult(interp); if (Tcl_GetBooleanFromObj(interp, boolObj, &satisfied) != TCL_OK) { TclDecrRefCount(boolObj); result = TCL_ERROR; goto abnormalResult; } TclDecrRefCount(boolObj); if (satisfied) { Tcl_DictObjPut(NULL, resultObj, keyObj, valueObj); } break; case TCL_BREAK: /* * Force loop termination by calling Tcl_DictObjDone; this * makes the next Tcl_DictObjNext say there is nothing more to * do. */ Tcl_ResetResult(interp); Tcl_DictObjDone(&search); /* FALLTHRU */ case TCL_CONTINUE: result = TCL_OK; break; case TCL_ERROR: Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (\"dict filter\" script line %d)", Tcl_GetErrorLine(interp))); default: goto abnormalResult; } TclDecrRefCount(keyObj); TclDecrRefCount(valueObj); Tcl_DictObjNext(&search, &keyObj, &valueObj, &done); } /* * Stop holding a reference to these objects. */ TclDecrRefCount(keyVarObj); TclDecrRefCount(valueVarObj); TclDecrRefCount(scriptObj); Tcl_DictObjDone(&search); if (result == TCL_OK) { Tcl_SetObjResult(interp, resultObj); } else { TclDecrRefCount(resultObj); } return result; abnormalResult: Tcl_DictObjDone(&search); TclDecrRefCount(keyObj); TclDecrRefCount(valueObj); TclDecrRefCount(keyVarObj); TclDecrRefCount(valueVarObj); TclDecrRefCount(scriptObj); TclDecrRefCount(resultObj); return result; } Tcl_Panic("unexpected fallthrough"); /* Control never reaches this point. */ return TCL_ERROR; } /* *---------------------------------------------------------------------- * * DictUpdateCmd -- * * This function implements the "dict update" Tcl command. See the user * documentation for details on what it does, and TIP#212 for the formal * specification. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int DictUpdateCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Interp *iPtr = (Interp *) interp; Tcl_Obj *dictPtr, *objPtr; int i; Tcl_Size dummy; if (objc < 5 || !(objc & 1)) { Tcl_WrongNumArgs(interp, 1, objv, "dictVarName key varName ?key varName ...? script"); return TCL_ERROR; } dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, TCL_LEAVE_ERR_MSG); if (dictPtr == NULL) { return TCL_ERROR; } if (Tcl_DictObjSize(interp, dictPtr, &dummy) != TCL_OK) { return TCL_ERROR; } Tcl_IncrRefCount(dictPtr); for (i=2 ; i+2cmdFramePtr, objc-1); } static int FinalizeDictUpdate( void *data[], Tcl_Interp *interp, int result) { Tcl_Obj *dictPtr, *objPtr, **objv; Tcl_InterpState state; Tcl_Size i, objc; Tcl_Obj *varName = (Tcl_Obj *)data[0]; Tcl_Obj *argsObj = (Tcl_Obj *)data[1]; /* * ErrorInfo handling. */ if (result == TCL_ERROR) { Tcl_AddErrorInfo(interp, "\n (body of \"dict update\")"); } /* * If the dictionary variable doesn't exist, drop everything silently. */ dictPtr = Tcl_ObjGetVar2(interp, varName, NULL, 0); if (dictPtr == NULL) { TclDecrRefCount(varName); TclDecrRefCount(argsObj); return result; } /* * Double-check that it is still a dictionary. */ state = Tcl_SaveInterpState(interp, result); if (Tcl_DictObjSize(interp, dictPtr, &objc) != TCL_OK) { Tcl_DiscardInterpState(state); TclDecrRefCount(varName); TclDecrRefCount(argsObj); return TCL_ERROR; } if (Tcl_IsShared(dictPtr)) { dictPtr = Tcl_DuplicateObj(dictPtr); } /* * Write back the values from the variables, treating failure to read as * an instruction to remove the key. */ TclListObjGetElements(NULL, argsObj, &objc, &objv); for (i=0 ; i 3) { pathPtr = Tcl_NewListObj(objc-3, objv+2); Tcl_IncrRefCount(pathPtr); } Tcl_IncrRefCount(objv[1]); TclNRAddCallback(interp, FinalizeDictWith, objv[1], keysPtr, pathPtr, NULL); return TclNREvalObjEx(interp, objv[objc-1], 0, iPtr->cmdFramePtr, objc-1); } static int FinalizeDictWith( void *data[], Tcl_Interp *interp, int result) { Tcl_Obj **pathv; Tcl_Size pathc; Tcl_InterpState state; Tcl_Obj *varName = (Tcl_Obj *)data[0]; Tcl_Obj *keysPtr = (Tcl_Obj *)data[1]; Tcl_Obj *pathPtr = (Tcl_Obj *)data[2]; Var *varPtr, *arrayPtr; if (result == TCL_ERROR) { Tcl_AddErrorInfo(interp, "\n (body of \"dict with\")"); } /* * Save the result state; TDWF doesn't guarantee to not modify that on * TCL_OK result. */ state = Tcl_SaveInterpState(interp, result); if (pathPtr != NULL) { TclListObjGetElements(NULL, pathPtr, &pathc, &pathv); } else { pathc = 0; pathv = NULL; } /* * Pack from local variables back into the dictionary. */ varPtr = TclObjLookupVarEx(interp, varName, NULL, TCL_LEAVE_ERR_MSG, "set", /*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr); if (varPtr == NULL) { result = TCL_ERROR; } else { result = TclDictWithFinish(interp, varPtr, arrayPtr, varName, NULL, -1, pathc, pathv, keysPtr); } /* * Tidy up and return the real result (unless we had an error). */ TclDecrRefCount(varName); TclDecrRefCount(keysPtr); if (pathPtr != NULL) { TclDecrRefCount(pathPtr); } if (result != TCL_OK) { Tcl_DiscardInterpState(state); return TCL_ERROR; } return Tcl_RestoreInterpState(interp, state); } /* *---------------------------------------------------------------------- * * TclDictWithInit -- * * Part of the core of [dict with]. Pokes into a dictionary and converts * the mappings there into assignments to (presumably) local variables. * Returns a list of all the names that were mapped so that removal of * either the variable or the dictionary entry won't surprise us when we * come to stuffing everything back. * * Result: * List of mapped names, or NULL if there was an error. * * Side effects: * Assigns to variables, so potentially legion due to traces. * *---------------------------------------------------------------------- */ Tcl_Obj * TclDictWithInit( Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size pathc, Tcl_Obj *const pathv[]) { Tcl_DictSearch s; Tcl_Obj *keyPtr, *valPtr, *keysPtr; int done; if (pathc > 0) { dictPtr = TclTraceDictPath(interp, dictPtr, pathc, pathv, DICT_PATH_READ); if (dictPtr == NULL) { return NULL; } } /* * Go over the list of keys and write each corresponding value to a * variable in the current context with the same name. Also keep a copy of * the keys so we can write back properly later on even if the dictionary * has been structurally modified. */ if (Tcl_DictObjFirst(interp, dictPtr, &s, &keyPtr, &valPtr, &done) != TCL_OK) { return NULL; } TclNewObj(keysPtr); for (; !done ; Tcl_DictObjNext(&s, &keyPtr, &valPtr, &done)) { Tcl_ListObjAppendElement(NULL, keysPtr, keyPtr); if (Tcl_ObjSetVar2(interp, keyPtr, NULL, valPtr, TCL_LEAVE_ERR_MSG) == NULL) { TclDecrRefCount(keysPtr); Tcl_DictObjDone(&s); return NULL; } } return keysPtr; } /* *---------------------------------------------------------------------- * * TclDictWithFinish -- * * Part of the core of [dict with]. Reassembles the piece of the dict (in * varName, location given by pathc/pathv) from the variables named in * the keysPtr argument. NB, does not try to preserve errors or manage * argument lifetimes. * * Result: * TCL_OK if we succeeded, or TCL_ERROR if we failed. * * Side effects: * Assigns to a variable, so potentially legion due to traces. Updates * the dictionary in the named variable. * *---------------------------------------------------------------------- */ int TclDictWithFinish( Tcl_Interp *interp, /* Command interpreter in which variable * exists. Used for state management, traces * and error reporting. */ Var *varPtr, /* Reference to the variable holding the * dictionary. */ Var *arrayPtr, /* Reference to the array containing the * variable, or NULL if the variable is a * scalar. */ Tcl_Obj *part1Ptr, /* Name of an array (if part2 is non-NULL) or * the name of a variable. NULL if the 'index' * parameter is >= 0 */ Tcl_Obj *part2Ptr, /* If non-NULL, gives the name of an element * in the array part1. */ int index, /* Index into the local variable table of the * variable, or -1. Only used when part1Ptr is * NULL. */ int pathc, /* The number of elements in the path into the * dictionary. */ Tcl_Obj *const pathv[], /* The elements of the path to the subdict. */ Tcl_Obj *keysPtr) /* List of keys to be synchronized. This is * the result value from TclDictWithInit. */ { Tcl_Obj *dictPtr, *leafPtr, *valPtr; Tcl_Size i, allocdict, keyc; Tcl_Obj **keyv; /* * If the dictionary variable doesn't exist, drop everything silently. */ dictPtr = TclPtrGetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, TCL_LEAVE_ERR_MSG, index); if (dictPtr == NULL) { return TCL_OK; } /* * Double-check that it is still a dictionary. */ if (Tcl_DictObjSize(interp, dictPtr, &i) != TCL_OK) { return TCL_ERROR; } if (Tcl_IsShared(dictPtr)) { dictPtr = Tcl_DuplicateObj(dictPtr); allocdict = 1; } else { allocdict = 0; } if (pathc > 0) { /* * Want to get to the dictionary which we will update; need to do * prepare-for-update unsharing along the path *but* avoid generating * an error on a non-extant path (we'll treat that the same as a * non-extant variable. Luckily, the unsharing operation isn't * deeply damaging if we don't go on to update; it's just less than * perfectly efficient (but no memory should be leaked). */ leafPtr = TclTraceDictPath(interp, dictPtr, pathc, pathv, DICT_PATH_EXISTS | DICT_PATH_UPDATE); if (leafPtr == NULL) { if (allocdict) { TclDecrRefCount(dictPtr); } return TCL_ERROR; } if (leafPtr == DICT_PATH_NON_EXISTENT) { if (allocdict) { TclDecrRefCount(dictPtr); } return TCL_OK; } } else { leafPtr = dictPtr; } /* * Now process our updates on the leaf dictionary. */ TclListObjGetElements(NULL, keysPtr, &keyc, &keyv); for (i=0 ; i 0) { InvalidateDictChain(leafPtr); } /* * Write back the outermost dictionary to the variable. */ if (TclPtrSetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, dictPtr, TCL_LEAVE_ERR_MSG, index) == NULL) { if (allocdict) { TclDecrRefCount(dictPtr); } return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclInitDictCmd -- * * This function is create the "dict" Tcl command. See the user * documentation for details on what it does, and TIP#111 for the formal * specification. * * Results: * A Tcl command handle. * * Side effects: * May advance compilation epoch. * *---------------------------------------------------------------------- */ Tcl_Command TclInitDictCmd( Tcl_Interp *interp) { return TclMakeEnsemble(interp, "dict", implementationMap); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclDisassemble.c0000644000175000017500000013122014726623136016273 0ustar sergeisergei/* * tclDisassemble.c -- * * This file contains procedures that disassemble bytecode into either * human-readable or Tcl-processable forms. * * Copyright © 1996-1998 Sun Microsystems, Inc. * Copyright © 2001 Kevin B. Kenny. All rights reserved. * Copyright © 2013-2016 Donal K. Fellows. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" #include "tclOOInt.h" #include /* * Prototypes for procedures defined later in this file: */ static Tcl_Obj * DisassembleByteCodeAsDicts(Tcl_Obj *objPtr); static Tcl_Obj * DisassembleByteCodeObj(Tcl_Obj *objPtr); static int FormatInstruction(ByteCode *codePtr, const unsigned char *pc, Tcl_Obj *bufferObj); static void GetLocationInformation(Proc *procPtr, Tcl_Obj **fileObjPtr, int *linePtr); static void PrintSourceToObj(Tcl_Obj *appendObj, const char *stringPtr, Tcl_Size maxChars); static void UpdateStringOfInstName(Tcl_Obj *objPtr); /* * The structure below defines an instruction name Tcl object to allow * reporting of inner contexts in errorstack without string allocation. */ static const Tcl_ObjType instNameType = { "instname", /* name */ NULL, /* freeIntRepProc */ NULL, /* dupIntRepProc */ UpdateStringOfInstName, /* updateStringProc */ NULL, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; #define InstNameSetInternalRep(objPtr, inst) \ do { \ Tcl_ObjInternalRep ir; \ ir.wideValue = (inst); \ Tcl_StoreInternalRep((objPtr), &instNameType, &ir); \ } while (0) #define InstNameGetInternalRep(objPtr, inst) \ do { \ const Tcl_ObjInternalRep *irPtr; \ irPtr = TclFetchInternalRep((objPtr), &instNameType); \ assert(irPtr != NULL); \ (inst) = irPtr->wideValue; \ } while (0) /* *---------------------------------------------------------------------- * * GetLocationInformation -- * * This procedure looks up the information about where a procedure was * originally declared. * * Results: * Writes to the variables pointed at by fileObjPtr and linePtr. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void GetLocationInformation( Proc *procPtr, /* What to look up the information for. */ Tcl_Obj **fileObjPtr, /* Where to write the information about what * file the code came from. Will be written * to, either with the object (assume shared!) * that describes what the file was, or with * NULL if the information is not * available. */ int *linePtr) /* Where to write the information about what * line number represented the start of the * code in question. Will be written to, * either with the line number or with -1 if * the information is not available. */ { CmdFrame *cfPtr = TclGetCmdFrameForProcedure(procPtr); *fileObjPtr = NULL; *linePtr = -1; if (cfPtr == NULL) { return; } /* * Get the source location data out of the CmdFrame. */ *linePtr = cfPtr->line[0]; if (cfPtr->type == TCL_LOCATION_SOURCE) { *fileObjPtr = cfPtr->data.eval.path; } } #ifdef TCL_COMPILE_DEBUG /* *---------------------------------------------------------------------- * * TclDebugPrintByteCodeObj -- * * This procedure prints ("disassembles") the instructions of a bytecode * object to stdout. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclDebugPrintByteCodeObj( Tcl_Obj *objPtr) /* The bytecode object to disassemble. */ { if (tclTraceCompile >= 2) { Tcl_Obj *bufPtr = DisassembleByteCodeObj(objPtr); fprintf(stdout, "\n%s", TclGetString(bufPtr)); Tcl_DecrRefCount(bufPtr); fflush(stdout); } } /* *---------------------------------------------------------------------- * * TclPrintInstruction -- * * This procedure prints ("disassembles") one instruction from a bytecode * object to stdout. * * Results: * Returns the length in bytes of the current instruiction. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclPrintInstruction( ByteCode *codePtr, /* Bytecode containing the instruction. */ const unsigned char *pc) /* Points to first byte of instruction. */ { Tcl_Obj *bufferObj; int numBytes; TclNewObj(bufferObj); numBytes = FormatInstruction(codePtr, pc, bufferObj); fprintf(stdout, "%s", TclGetString(bufferObj)); Tcl_DecrRefCount(bufferObj); return numBytes; } /* *---------------------------------------------------------------------- * * TclPrintObject -- * * This procedure prints up to a specified number of characters from the * argument Tcl object's string representation to a specified file. * * Results: * None. * * Side effects: * Outputs characters to the specified file. * *---------------------------------------------------------------------- */ void TclPrintObject( FILE *outFile, /* The file to print the source to. */ Tcl_Obj *objPtr, /* Points to the Tcl object whose string * representation should be printed. */ Tcl_Size maxChars) /* Maximum number of chars to print. */ { char *bytes; Tcl_Size length; bytes = TclGetStringFromObj(objPtr, &length); TclPrintSource(outFile, bytes, TclMin(length, maxChars)); } /* *---------------------------------------------------------------------- * * TclPrintSource -- * * This procedure prints up to a specified number of characters from the * argument string to a specified file. It tries to produce legible * output by adding backslashes as necessary. * * Results: * None. * * Side effects: * Outputs characters to the specified file. * *---------------------------------------------------------------------- */ void TclPrintSource( FILE *outFile, /* The file to print the source to. */ const char *stringPtr, /* The string to print. */ Tcl_Size maxChars) /* Maximum number of chars to print. */ { Tcl_Obj *bufferObj; TclNewObj(bufferObj); PrintSourceToObj(bufferObj, stringPtr, maxChars); fprintf(outFile, "%s", TclGetString(bufferObj)); Tcl_DecrRefCount(bufferObj); } #endif /* TCL_COMPILE_DEBUG */ /* *---------------------------------------------------------------------- * * DisassembleByteCodeObj -- * * Given an object which is of bytecode type, return a disassembled * version of the bytecode (in a new refcount 0 object). No guarantees * are made about the details of the contents of the result. * *---------------------------------------------------------------------- */ static Tcl_Obj * DisassembleByteCodeObj( Tcl_Obj *objPtr) /* The bytecode object to disassemble. */ { ByteCode *codePtr; unsigned char *codeStart, *codeLimit, *pc; unsigned char *codeDeltaNext, *codeLengthNext; unsigned char *srcDeltaNext, *srcLengthNext; int codeOffset, codeLen, srcOffset, srcLen, numCmds, delta, line; Tcl_Size i; Interp *iPtr; Tcl_Obj *bufferObj, *fileObj; ByteCodeGetInternalRep(objPtr, &tclByteCodeType, codePtr); iPtr = (Interp *) *codePtr->interpHandle; TclNewObj(bufferObj); if (!codePtr->refCount) { return bufferObj; /* Already freed. */ } codeStart = codePtr->codeStart; codeLimit = codeStart + codePtr->numCodeBytes; numCmds = codePtr->numCommands; /* * Print header lines describing the ByteCode. */ Tcl_AppendPrintfToObj(bufferObj, "ByteCode %p, refCt %" TCL_SIZE_MODIFIER "d, epoch %" TCL_SIZE_MODIFIER "d, interp %p (epoch %" TCL_SIZE_MODIFIER "d)\n", codePtr, codePtr->refCount, codePtr->compileEpoch, iPtr, iPtr->compileEpoch); Tcl_AppendToObj(bufferObj, " Source ", -1); PrintSourceToObj(bufferObj, codePtr->source, TclMin(codePtr->numSrcBytes, 55)); GetLocationInformation(codePtr->procPtr, &fileObj, &line); if (line >= 0 && fileObj != NULL) { Tcl_AppendPrintfToObj(bufferObj, "\n File \"%s\" Line %d", TclGetString(fileObj), line); } Tcl_AppendPrintfToObj(bufferObj, "\n Cmds %d, src %" TCL_SIZE_MODIFIER "d, inst %" TCL_SIZE_MODIFIER "d, litObjs %" TCL_SIZE_MODIFIER "d, aux %" TCL_SIZE_MODIFIER "d, stkDepth %" TCL_SIZE_MODIFIER "d, code/src %.2f\n", numCmds, codePtr->numSrcBytes, codePtr->numCodeBytes, codePtr->numLitObjects, codePtr->numAuxDataItems, codePtr->maxStackDepth, #ifdef TCL_COMPILE_STATS codePtr->numSrcBytes? codePtr->structureSize/(float)codePtr->numSrcBytes : #endif 0.0); #ifdef TCL_COMPILE_STATS Tcl_AppendPrintfToObj(bufferObj, " Code %" TCL_Z_MODIFIER "u = header %" TCL_Z_MODIFIER "u+inst %" TCL_SIZE_MODIFIER "d+litObj %" TCL_Z_MODIFIER "u+exc %" TCL_Z_MODIFIER "u+aux %" TCL_Z_MODIFIER "u+cmdMap %" TCL_SIZE_MODIFIER "d\n", codePtr->structureSize, offsetof(ByteCode, localCachePtr), codePtr->numCodeBytes, codePtr->numLitObjects * sizeof(Tcl_Obj *), codePtr->numExceptRanges*sizeof(ExceptionRange), codePtr->numAuxDataItems * sizeof(AuxData), codePtr->numCmdLocBytes); #endif /* TCL_COMPILE_STATS */ /* * If the ByteCode is the compiled body of a Tcl procedure, print * information about that procedure. Note that we don't know the * procedure's name since ByteCode's can be shared among procedures. */ if (codePtr->procPtr != NULL) { Proc *procPtr = codePtr->procPtr; Tcl_Size numCompiledLocals = procPtr->numCompiledLocals; Tcl_AppendPrintfToObj(bufferObj, " Proc %p, refCt %" TCL_SIZE_MODIFIER "d, args %" TCL_SIZE_MODIFIER "d, compiled locals %" TCL_SIZE_MODIFIER "d\n", procPtr, procPtr->refCount, procPtr->numArgs, numCompiledLocals); if (numCompiledLocals > 0) { CompiledLocal *localPtr = procPtr->firstLocalPtr; for (i = 0; i < numCompiledLocals; i++) { Tcl_AppendPrintfToObj(bufferObj, " slot %" TCL_SIZE_MODIFIER "d%s%s%s%s%s%s", i, (localPtr->flags & (VAR_ARRAY|VAR_LINK)) ? "" : ", scalar", (localPtr->flags & VAR_ARRAY) ? ", array" : "", (localPtr->flags & VAR_LINK) ? ", link" : "", (localPtr->flags & VAR_ARGUMENT) ? ", arg" : "", (localPtr->flags & VAR_TEMPORARY) ? ", temp" : "", (localPtr->flags & VAR_RESOLVED) ? ", resolved" : ""); if (TclIsVarTemporary(localPtr)) { Tcl_AppendToObj(bufferObj, "\n", -1); } else { Tcl_AppendPrintfToObj(bufferObj, ", \"%s\"\n", localPtr->name); } localPtr = localPtr->nextPtr; } } } /* * Print the ExceptionRange array. */ if ((int)codePtr->numExceptRanges > 0) { Tcl_AppendPrintfToObj(bufferObj, " Exception ranges %" TCL_SIZE_MODIFIER "d, depth %" TCL_SIZE_MODIFIER "d:\n", codePtr->numExceptRanges, codePtr->maxExceptDepth); for (i = 0; i < (int)codePtr->numExceptRanges; i++) { ExceptionRange *rangePtr = &codePtr->exceptArrayPtr[i]; Tcl_AppendPrintfToObj(bufferObj, " %" TCL_SIZE_MODIFIER "d: level %" TCL_SIZE_MODIFIER "d, %s, pc %" TCL_SIZE_MODIFIER "d-%" TCL_SIZE_MODIFIER "d, ", i, rangePtr->nestingLevel, (rangePtr->type==LOOP_EXCEPTION_RANGE ? "loop" : "catch"), rangePtr->codeOffset, (rangePtr->codeOffset + rangePtr->numCodeBytes - 1)); switch (rangePtr->type) { case LOOP_EXCEPTION_RANGE: Tcl_AppendPrintfToObj(bufferObj, "continue %" TCL_SIZE_MODIFIER "d, break %" TCL_SIZE_MODIFIER "d\n", rangePtr->continueOffset, rangePtr->breakOffset); break; case CATCH_EXCEPTION_RANGE: Tcl_AppendPrintfToObj(bufferObj, "catch %" TCL_SIZE_MODIFIER "d\n", rangePtr->catchOffset); break; default: Tcl_Panic("DisassembleByteCodeObj: bad ExceptionRange type %d", rangePtr->type); } } } /* * If there were no commands (e.g., an expression or an empty string was * compiled), just print all instructions and return. */ if (numCmds == 0) { pc = codeStart; while (pc < codeLimit) { Tcl_AppendToObj(bufferObj, " ", -1); pc += FormatInstruction(codePtr, pc, bufferObj); } return bufferObj; } /* * Print table showing the code offset, source offset, and source length * for each command. These are encoded as a sequence of bytes. */ Tcl_AppendPrintfToObj(bufferObj, " Commands %d:", numCmds); codeDeltaNext = codePtr->codeDeltaStart; codeLengthNext = codePtr->codeLengthStart; srcDeltaNext = codePtr->srcDeltaStart; srcLengthNext = codePtr->srcLengthStart; codeOffset = srcOffset = 0; for (i = 0; i < numCmds; i++) { if (*codeDeltaNext == 0xFF) { codeDeltaNext++; delta = TclGetInt4AtPtr(codeDeltaNext); codeDeltaNext += 4; } else { delta = TclGetInt1AtPtr(codeDeltaNext); codeDeltaNext++; } codeOffset += delta; if (*codeLengthNext == 0xFF) { codeLengthNext++; codeLen = TclGetInt4AtPtr(codeLengthNext); codeLengthNext += 4; } else { codeLen = TclGetInt1AtPtr(codeLengthNext); codeLengthNext++; } if (*srcDeltaNext == 0xFF) { srcDeltaNext++; delta = TclGetInt4AtPtr(srcDeltaNext); srcDeltaNext += 4; } else { delta = TclGetInt1AtPtr(srcDeltaNext); srcDeltaNext++; } srcOffset += delta; if (*srcLengthNext == 0xFF) { srcLengthNext++; srcLen = TclGetInt4AtPtr(srcLengthNext); srcLengthNext += 4; } else { srcLen = TclGetInt1AtPtr(srcLengthNext); srcLengthNext++; } Tcl_AppendPrintfToObj(bufferObj, "%s%4" TCL_SIZE_MODIFIER "d: pc %d-%d, src %d-%d", ((i % 2)? " " : "\n "), (i+1), codeOffset, (codeOffset + codeLen - 1), srcOffset, (srcOffset + srcLen - 1)); } if (numCmds > 0) { Tcl_AppendToObj(bufferObj, "\n", -1); } /* * Print each instruction. If the instruction corresponds to the start of * a command, print the command's source. Note that we don't need the code * length here. */ codeDeltaNext = codePtr->codeDeltaStart; srcDeltaNext = codePtr->srcDeltaStart; srcLengthNext = codePtr->srcLengthStart; codeOffset = srcOffset = 0; pc = codeStart; for (i = 0; i < numCmds; i++) { if (*codeDeltaNext == 0xFF) { codeDeltaNext++; delta = TclGetInt4AtPtr(codeDeltaNext); codeDeltaNext += 4; } else { delta = TclGetInt1AtPtr(codeDeltaNext); codeDeltaNext++; } codeOffset += delta; if (*srcDeltaNext == 0xFF) { srcDeltaNext++; delta = TclGetInt4AtPtr(srcDeltaNext); srcDeltaNext += 4; } else { delta = TclGetInt1AtPtr(srcDeltaNext); srcDeltaNext++; } srcOffset += delta; if (*srcLengthNext == 0xFF) { srcLengthNext++; srcLen = TclGetInt4AtPtr(srcLengthNext); srcLengthNext += 4; } else { srcLen = TclGetInt1AtPtr(srcLengthNext); srcLengthNext++; } /* * Print instructions before command i. */ while ((pc-codeStart) < codeOffset) { Tcl_AppendToObj(bufferObj, " ", -1); pc += FormatInstruction(codePtr, pc, bufferObj); } Tcl_AppendPrintfToObj(bufferObj, " Command %" TCL_SIZE_MODIFIER "d: ", i+1); PrintSourceToObj(bufferObj, (codePtr->source + srcOffset), TclMin(srcLen, 55)); Tcl_AppendToObj(bufferObj, "\n", -1); } if (pc < codeLimit) { /* * Print instructions after the last command. */ while (pc < codeLimit) { Tcl_AppendToObj(bufferObj, " ", -1); pc += FormatInstruction(codePtr, pc, bufferObj); } } return bufferObj; } /* *---------------------------------------------------------------------- * * FormatInstruction -- * * Appends a representation of a bytecode instruction to a Tcl_Obj. * *---------------------------------------------------------------------- */ static int FormatInstruction( ByteCode *codePtr, /* Bytecode containing the instruction. */ const unsigned char *pc, /* Points to first byte of instruction. */ Tcl_Obj *bufferObj) /* Object to append instruction info to. */ { Proc *procPtr = codePtr->procPtr; unsigned char opCode = *pc; const InstructionDesc *instDesc = &tclInstructionTable[opCode]; unsigned char *codeStart = codePtr->codeStart; unsigned pcOffset = pc - codeStart; int opnd = 0, i, j, numBytes = 1; Tcl_Size localCt = procPtr ? procPtr->numCompiledLocals : 0; CompiledLocal *localPtr = procPtr ? procPtr->firstLocalPtr : NULL; char suffixBuffer[128]; /* Additional info to print after main opcode * and immediates. */ char *suffixSrc = NULL; Tcl_Obj *suffixObj = NULL; AuxData *auxPtr = NULL; suffixBuffer[0] = '\0'; Tcl_AppendPrintfToObj(bufferObj, "(%u) %s ", pcOffset, instDesc->name); for (i = 0; i < instDesc->numOperands; i++) { switch (instDesc->opTypes[i]) { case OPERAND_INT1: opnd = TclGetInt1AtPtr(pc+numBytes); numBytes++; Tcl_AppendPrintfToObj(bufferObj, "%+d ", opnd); break; case OPERAND_INT4: opnd = TclGetInt4AtPtr(pc+numBytes); numBytes += 4; Tcl_AppendPrintfToObj(bufferObj, "%+d ", opnd); break; case OPERAND_UINT1: opnd = TclGetUInt1AtPtr(pc+numBytes); numBytes++; Tcl_AppendPrintfToObj(bufferObj, "%u ", opnd); break; case OPERAND_UINT4: opnd = TclGetUInt4AtPtr(pc+numBytes); numBytes += 4; if (opCode == INST_START_CMD) { snprintf(suffixBuffer+strlen(suffixBuffer), sizeof(suffixBuffer) - strlen(suffixBuffer), ", %u cmds start here", opnd); } Tcl_AppendPrintfToObj(bufferObj, "%u ", opnd); break; case OPERAND_OFFSET1: opnd = TclGetInt1AtPtr(pc+numBytes); numBytes++; snprintf(suffixBuffer, sizeof(suffixBuffer), "pc %u", pcOffset+opnd); Tcl_AppendPrintfToObj(bufferObj, "%+d ", opnd); break; case OPERAND_OFFSET4: opnd = TclGetInt4AtPtr(pc+numBytes); numBytes += 4; if (opCode == INST_START_CMD) { snprintf(suffixBuffer, sizeof(suffixBuffer), "next cmd at pc %u", pcOffset+opnd); } else { snprintf(suffixBuffer, sizeof(suffixBuffer), "pc %u", pcOffset+opnd); } Tcl_AppendPrintfToObj(bufferObj, "%+d ", opnd); break; case OPERAND_LIT1: opnd = TclGetUInt1AtPtr(pc+numBytes); numBytes++; suffixObj = codePtr->objArrayPtr[opnd]; Tcl_AppendPrintfToObj(bufferObj, "%u ", opnd); break; case OPERAND_LIT4: opnd = TclGetUInt4AtPtr(pc+numBytes); numBytes += 4; suffixObj = codePtr->objArrayPtr[opnd]; Tcl_AppendPrintfToObj(bufferObj, "%u ", opnd); break; case OPERAND_AUX4: opnd = TclGetUInt4AtPtr(pc+numBytes); numBytes += 4; Tcl_AppendPrintfToObj(bufferObj, "%u ", opnd); auxPtr = &codePtr->auxDataArrayPtr[opnd]; break; case OPERAND_IDX4: opnd = TclGetInt4AtPtr(pc+numBytes); numBytes += 4; if (opnd >= -1) { Tcl_AppendPrintfToObj(bufferObj, "%d ", opnd); } else if (opnd == -2) { Tcl_AppendPrintfToObj(bufferObj, "end "); } else { Tcl_AppendPrintfToObj(bufferObj, "end-%d ", -2-opnd); } break; case OPERAND_LVT1: opnd = TclGetUInt1AtPtr(pc+numBytes); numBytes++; goto printLVTindex; case OPERAND_LVT4: opnd = TclGetUInt4AtPtr(pc+numBytes); numBytes += 4; printLVTindex: if (localPtr != NULL) { if (opnd >= localCt) { Tcl_Panic("FormatInstruction: bad local var index %u (%" TCL_SIZE_MODIFIER "d locals)", opnd, localCt); } for (j = 0; j < opnd; j++) { localPtr = localPtr->nextPtr; } if (TclIsVarTemporary(localPtr)) { snprintf(suffixBuffer, sizeof(suffixBuffer), "temp var %u", opnd); } else { snprintf(suffixBuffer, sizeof(suffixBuffer), "var "); suffixSrc = localPtr->name; } } Tcl_AppendPrintfToObj(bufferObj, "%%v%u ", opnd); break; case OPERAND_SCLS1: opnd = TclGetUInt1AtPtr(pc+numBytes); numBytes++; Tcl_AppendPrintfToObj(bufferObj, "%s ", tclStringClassTable[opnd].name); break; case OPERAND_NONE: default: break; } } if (suffixObj) { const char *bytes; Tcl_Size length; Tcl_AppendToObj(bufferObj, "\t# ", -1); bytes = TclGetStringFromObj(codePtr->objArrayPtr[opnd], &length); PrintSourceToObj(bufferObj, bytes, TclMin(length, 40)); } else if (suffixBuffer[0]) { Tcl_AppendPrintfToObj(bufferObj, "\t# %s", suffixBuffer); if (suffixSrc) { PrintSourceToObj(bufferObj, suffixSrc, 40); } } Tcl_AppendToObj(bufferObj, "\n", -1); if (auxPtr && auxPtr->type->printProc) { Tcl_AppendToObj(bufferObj, "\t\t[", -1); auxPtr->type->printProc(auxPtr->clientData, bufferObj, codePtr, pcOffset); Tcl_AppendToObj(bufferObj, "]\n", -1); } return numBytes; } /* *---------------------------------------------------------------------- * * TclGetInnerContext -- * * If possible, returns a list capturing the inner context. Otherwise * return NULL. * *---------------------------------------------------------------------- */ Tcl_Obj * TclGetInnerContext( Tcl_Interp *interp, const unsigned char *pc, Tcl_Obj **tosPtr) { Tcl_Size objc = 0; Tcl_Obj *result; Interp *iPtr = (Interp *) interp; switch (*pc) { case INST_STR_LEN: case INST_LNOT: case INST_BITNOT: case INST_UMINUS: case INST_UPLUS: case INST_TRY_CVT_TO_NUMERIC: case INST_EXPAND_STKTOP: case INST_EXPR_STK: objc = 1; break; case INST_LIST_IN: case INST_LIST_NOT_IN: /* Basic list containment operators. */ case INST_STR_EQ: case INST_STR_NEQ: /* String (in)equality check */ case INST_STR_CMP: /* String compare. */ case INST_STR_INDEX: case INST_STR_MATCH: case INST_REGEXP: case INST_EQ: case INST_NEQ: case INST_LT: case INST_GT: case INST_LE: case INST_GE: case INST_MOD: case INST_LSHIFT: case INST_RSHIFT: case INST_BITOR: case INST_BITXOR: case INST_BITAND: case INST_EXPON: case INST_ADD: case INST_SUB: case INST_DIV: case INST_MULT: objc = 2; break; case INST_RETURN_STK: /* early pop. TODO: dig out opt dict too :/ */ objc = 1; break; case INST_SYNTAX: case INST_RETURN_IMM: objc = 2; break; case INST_INVOKE_STK4: objc = TclGetUInt4AtPtr(pc+1); break; case INST_INVOKE_STK1: objc = TclGetUInt1AtPtr(pc+1); break; } result = iPtr->innerContext; if (Tcl_IsShared(result)) { Tcl_DecrRefCount(result); iPtr->innerContext = result = Tcl_NewListObj(objc + 1, NULL); Tcl_IncrRefCount(result); } else { Tcl_Size len; /* * Reset while keeping the list internalrep as much as possible. */ TclListObjLength(interp, result, &len); Tcl_ListObjReplace(interp, result, 0, len, 0, NULL); } Tcl_ListObjAppendElement(NULL, result, TclNewInstNameObj(*pc)); for (; objc>0 ; objc--) { Tcl_Obj *objPtr; objPtr = tosPtr[1 - objc]; if (!objPtr) { Tcl_Panic("InnerContext: bad tos -- appending null object"); } if ((objPtr->refCount <= 0) #ifdef TCL_MEM_DEBUG || (objPtr->refCount == 0x61616161) #endif ) { Tcl_Panic("InnerContext: bad tos -- appending freed object %p", objPtr); } Tcl_ListObjAppendElement(NULL, result, objPtr); } return result; } /* *---------------------------------------------------------------------- * * TclNewInstNameObj -- * * Creates a new InstName Tcl_Obj based on the given instruction * *---------------------------------------------------------------------- */ Tcl_Obj * TclNewInstNameObj( unsigned char inst) { Tcl_Obj *objPtr; TclNewObj(objPtr); TclInvalidateStringRep(objPtr); InstNameSetInternalRep(objPtr, inst); return objPtr; } /* *---------------------------------------------------------------------- * * UpdateStringOfInstName -- * * Update the string representation for an instruction name object. * *---------------------------------------------------------------------- */ static void UpdateStringOfInstName( Tcl_Obj *objPtr) { size_t inst; /* NOTE: We know this is really an unsigned char */ char *dst; InstNameGetInternalRep(objPtr, inst); if (inst >= LAST_INST_OPCODE) { dst = Tcl_InitStringRep(objPtr, NULL, TCL_INTEGER_SPACE + 5); TclOOM(dst, TCL_INTEGER_SPACE + 5); snprintf(dst, TCL_INTEGER_SPACE + 5, "inst_%" TCL_Z_MODIFIER "u", inst); (void) Tcl_InitStringRep(objPtr, NULL, strlen(dst)); } else { const char *s = tclInstructionTable[inst].name; size_t len = strlen(s); dst = Tcl_InitStringRep(objPtr, s, len); TclOOM(dst, len); } } /* *---------------------------------------------------------------------- * * PrintSourceToObj -- * * Appends a quoted representation of a string to a Tcl_Obj. * *---------------------------------------------------------------------- */ static void PrintSourceToObj( Tcl_Obj *appendObj, /* The object to print the source to. */ const char *stringPtr, /* The string to print. */ Tcl_Size maxChars) /* Maximum number of chars to print. */ { const char *p; Tcl_Size i = 0, len; if (stringPtr == NULL) { Tcl_AppendToObj(appendObj, "\"\"", -1); return; } Tcl_AppendToObj(appendObj, "\"", -1); p = stringPtr; for (; (*p != '\0') && (i < maxChars); p+=len) { int ucs4; len = TclUtfToUniChar(p, &ucs4); switch (ucs4) { case '"': Tcl_AppendToObj(appendObj, "\\\"", -1); i += 2; continue; case '\f': Tcl_AppendToObj(appendObj, "\\f", -1); i += 2; continue; case '\n': Tcl_AppendToObj(appendObj, "\\n", -1); i += 2; continue; case '\r': Tcl_AppendToObj(appendObj, "\\r", -1); i += 2; continue; case '\t': Tcl_AppendToObj(appendObj, "\\t", -1); i += 2; continue; case '\v': Tcl_AppendToObj(appendObj, "\\v", -1); i += 2; continue; default: if (ucs4 > 0xFFFF) { Tcl_AppendPrintfToObj(appendObj, "\\U%08x", ucs4); i += 10; } else if (ucs4 < 0x20 || ucs4 >= 0x7F) { Tcl_AppendPrintfToObj(appendObj, "\\u%04x", ucs4); i += 6; } else { Tcl_AppendPrintfToObj(appendObj, "%c", ucs4); i++; } continue; } } if (*p != '\0') { Tcl_AppendToObj(appendObj, "...", -1); } Tcl_AppendToObj(appendObj, "\"", -1); } /* *---------------------------------------------------------------------- * * DisassembleByteCodeAsDicts -- * * Given an object which is of bytecode type, return a disassembled * version of the bytecode (in a new refcount 0 object) in a dictionary. * No guarantees are made about the details of the contents of the * result, but it is intended to be more readable than the old output * format. * *---------------------------------------------------------------------- */ static Tcl_Obj * DisassembleByteCodeAsDicts( Tcl_Obj *objPtr) /* The bytecode-holding value to take apart */ { ByteCode *codePtr; Tcl_Obj *description, *literals, *variables, *instructions, *inst; Tcl_Obj *aux, *exn, *commands, *file; unsigned char *pc, *opnd, *codeOffPtr, *codeLenPtr, *srcOffPtr, *srcLenPtr; int codeOffset, codeLength, sourceOffset, sourceLength, val, line; Tcl_Size i; ByteCodeGetInternalRep(objPtr, &tclByteCodeType, codePtr); /* * Get the literals from the bytecode. */ TclNewObj(literals); for (i=0 ; inumLitObjects ; i++) { Tcl_ListObjAppendElement(NULL, literals, codePtr->objArrayPtr[i]); } /* * Get the variables from the bytecode. */ TclNewObj(variables); if (codePtr->procPtr) { Tcl_Size localCount = codePtr->procPtr->numCompiledLocals; CompiledLocal *localPtr = codePtr->procPtr->firstLocalPtr; for (i=0 ; inextPtr) { Tcl_Obj *descriptor[2]; TclNewObj(descriptor[0]); if (!(localPtr->flags & (VAR_ARRAY|VAR_LINK))) { Tcl_ListObjAppendElement(NULL, descriptor[0], Tcl_NewStringObj("scalar", -1)); } if (localPtr->flags & VAR_ARRAY) { Tcl_ListObjAppendElement(NULL, descriptor[0], Tcl_NewStringObj("array", -1)); } if (localPtr->flags & VAR_LINK) { Tcl_ListObjAppendElement(NULL, descriptor[0], Tcl_NewStringObj("link", -1)); } if (localPtr->flags & VAR_ARGUMENT) { Tcl_ListObjAppendElement(NULL, descriptor[0], Tcl_NewStringObj("arg", -1)); } if (localPtr->flags & VAR_TEMPORARY) { Tcl_ListObjAppendElement(NULL, descriptor[0], Tcl_NewStringObj("temp", -1)); } if (localPtr->flags & VAR_RESOLVED) { Tcl_ListObjAppendElement(NULL, descriptor[0], Tcl_NewStringObj("resolved", -1)); } if (localPtr->flags & VAR_TEMPORARY) { Tcl_ListObjAppendElement(NULL, variables, Tcl_NewListObj(1, descriptor)); } else { descriptor[1] = Tcl_NewStringObj(localPtr->name, -1); Tcl_ListObjAppendElement(NULL, variables, Tcl_NewListObj(2, descriptor)); } } } /* * Get the instructions from the bytecode. */ TclNewObj(instructions); for (pc=codePtr->codeStart; pccodeStart+codePtr->numCodeBytes;){ const InstructionDesc *instDesc = &tclInstructionTable[*pc]; int address = pc - codePtr->codeStart; TclNewObj(inst); Tcl_ListObjAppendElement(NULL, inst, Tcl_NewStringObj( instDesc->name, -1)); opnd = pc + 1; for (i=0 ; inumOperands ; i++) { switch (instDesc->opTypes[i]) { case OPERAND_INT1: val = TclGetInt1AtPtr(opnd); opnd += 1; goto formatNumber; case OPERAND_UINT1: val = TclGetUInt1AtPtr(opnd); opnd += 1; goto formatNumber; case OPERAND_INT4: val = TclGetInt4AtPtr(opnd); opnd += 4; goto formatNumber; case OPERAND_UINT4: val = TclGetUInt4AtPtr(opnd); opnd += 4; formatNumber: Tcl_ListObjAppendElement(NULL, inst, Tcl_NewWideIntObj(val)); break; case OPERAND_OFFSET1: val = TclGetInt1AtPtr(opnd); opnd += 1; goto formatAddress; case OPERAND_OFFSET4: val = TclGetInt4AtPtr(opnd); opnd += 4; formatAddress: Tcl_ListObjAppendElement(NULL, inst, Tcl_ObjPrintf( "pc %d", address + val)); break; case OPERAND_LIT1: val = TclGetUInt1AtPtr(opnd); opnd += 1; goto formatLiteral; case OPERAND_LIT4: val = TclGetUInt4AtPtr(opnd); opnd += 4; formatLiteral: Tcl_ListObjAppendElement(NULL, inst, Tcl_ObjPrintf( "@%d", val)); break; case OPERAND_LVT1: val = TclGetUInt1AtPtr(opnd); opnd += 1; goto formatVariable; case OPERAND_LVT4: val = TclGetUInt4AtPtr(opnd); opnd += 4; formatVariable: Tcl_ListObjAppendElement(NULL, inst, Tcl_ObjPrintf( "%%%d", val)); break; case OPERAND_IDX4: val = TclGetInt4AtPtr(opnd); opnd += 4; if (val >= -1) { Tcl_ListObjAppendElement(NULL, inst, Tcl_ObjPrintf( ".%d", val)); } else if (val == -2) { Tcl_ListObjAppendElement(NULL, inst, Tcl_NewStringObj( ".end", -1)); } else { Tcl_ListObjAppendElement(NULL, inst, Tcl_ObjPrintf( ".end-%d", -2-val)); } break; case OPERAND_AUX4: val = TclGetInt4AtPtr(opnd); opnd += 4; Tcl_ListObjAppendElement(NULL, inst, Tcl_ObjPrintf( "?%d", val)); break; case OPERAND_SCLS1: val = TclGetUInt1AtPtr(opnd); opnd++; Tcl_ListObjAppendElement(NULL, inst, Tcl_ObjPrintf( "=%s", tclStringClassTable[val].name)); break; case OPERAND_NONE: Tcl_Panic("opcode %d with more than zero 'no' operands", *pc); } } Tcl_DictObjPut(NULL, instructions, Tcl_NewWideIntObj(address), inst); pc += instDesc->numBytes; } /* * Get the auxiliary data from the bytecode. */ TclNewObj(aux); for (i=0 ; i<(int)codePtr->numAuxDataItems ; i++) { AuxData *auxData = &codePtr->auxDataArrayPtr[i]; Tcl_Obj *auxDesc = Tcl_NewStringObj(auxData->type->name, -1); if (auxData->type->disassembleProc) { Tcl_Obj *desc; TclNewObj(desc); TclDictPut(NULL, desc, "name", auxDesc); auxDesc = desc; auxData->type->disassembleProc(auxData->clientData, auxDesc, codePtr, 0); } else if (auxData->type->printProc) { Tcl_Obj *desc; TclNewObj(desc); auxData->type->printProc(auxData->clientData, desc, codePtr, 0); Tcl_ListObjAppendElement(NULL, auxDesc, desc); } Tcl_ListObjAppendElement(NULL, aux, auxDesc); } /* * Get the exception ranges from the bytecode. */ TclNewObj(exn); for (i=0 ; i<(int)codePtr->numExceptRanges ; i++) { ExceptionRange *rangePtr = &codePtr->exceptArrayPtr[i]; switch (rangePtr->type) { case LOOP_EXCEPTION_RANGE: Tcl_ListObjAppendElement(NULL, exn, Tcl_ObjPrintf( "type %s level %" TCL_SIZE_MODIFIER "d from %" TCL_SIZE_MODIFIER "d to %" TCL_SIZE_MODIFIER "d break %" TCL_SIZE_MODIFIER "d continue %" TCL_SIZE_MODIFIER "d", "loop", rangePtr->nestingLevel, rangePtr->codeOffset, rangePtr->codeOffset + rangePtr->numCodeBytes - 1, rangePtr->breakOffset, rangePtr->continueOffset)); break; case CATCH_EXCEPTION_RANGE: Tcl_ListObjAppendElement(NULL, exn, Tcl_ObjPrintf( "type %s level %" TCL_SIZE_MODIFIER "d from %" TCL_SIZE_MODIFIER "d to %" TCL_SIZE_MODIFIER "d catch %" TCL_SIZE_MODIFIER "d", "catch", rangePtr->nestingLevel, rangePtr->codeOffset, rangePtr->codeOffset + rangePtr->numCodeBytes - 1, rangePtr->catchOffset)); break; } } /* * Get the command information from the bytecode. * * The way these are encoded in the bytecode is non-trivial; the Decode * macro (which updates its argument and returns the next decoded value) * handles this so that the rest of the code does not. */ #define Decode(ptr) \ ((TclGetUInt1AtPtr(ptr) == 0xFF) \ ? ((ptr)+=5 , TclGetInt4AtPtr((ptr)-4)) \ : ((ptr)+=1 , TclGetInt1AtPtr((ptr)-1))) TclNewObj(commands); codeOffPtr = codePtr->codeDeltaStart; codeLenPtr = codePtr->codeLengthStart; srcOffPtr = codePtr->srcDeltaStart; srcLenPtr = codePtr->srcLengthStart; codeOffset = sourceOffset = 0; for (i=0 ; i<(int)codePtr->numCommands ; i++) { Tcl_Obj *cmd; codeOffset += Decode(codeOffPtr); codeLength = Decode(codeLenPtr); sourceOffset += Decode(srcOffPtr); sourceLength = Decode(srcLenPtr); TclNewObj(cmd); TclDictPut(NULL, cmd, "codefrom", Tcl_NewWideIntObj(codeOffset)); TclDictPut(NULL, cmd, "codeto", Tcl_NewWideIntObj( codeOffset + codeLength - 1)); /* * Convert byte offsets to character offsets; important if multibyte * characters are present in the source! */ TclDictPut(NULL, cmd, "scriptfrom", Tcl_NewWideIntObj( Tcl_NumUtfChars(codePtr->source, sourceOffset))); TclDictPut(NULL, cmd, "scriptto", Tcl_NewWideIntObj( Tcl_NumUtfChars(codePtr->source, sourceOffset + sourceLength - 1))); TclDictPut(NULL, cmd, "script", Tcl_NewStringObj(codePtr->source+sourceOffset, sourceLength)); Tcl_ListObjAppendElement(NULL, commands, cmd); } #undef Decode /* * Get the source file and line number information from the CmdFrame * system if it is available. */ GetLocationInformation(codePtr->procPtr, &file, &line); /* * Build the overall result. */ TclNewObj(description); TclDictPut(NULL, description, "literals", literals); TclDictPut(NULL, description, "variables", variables); TclDictPut(NULL, description, "exception", exn); TclDictPut(NULL, description, "instructions", instructions); TclDictPut(NULL, description, "auxiliary", aux); TclDictPut(NULL, description, "commands", commands); TclDictPut(NULL, description, "script", Tcl_NewStringObj(codePtr->source, codePtr->numSrcBytes)); TclDictPut(NULL, description, "namespace", TclNewNamespaceObj((Tcl_Namespace *) codePtr->nsPtr)); TclDictPut(NULL, description, "stackdepth", Tcl_NewWideIntObj(codePtr->maxStackDepth)); TclDictPut(NULL, description, "exceptdepth", Tcl_NewWideIntObj(codePtr->maxExceptDepth)); if (line >= 0) { TclDictPut(NULL, description, "initiallinenumber", Tcl_NewWideIntObj(line)); } if (file) { TclDictPut(NULL, description, "sourcefile", file); } return description; } /* *---------------------------------------------------------------------- * * Tcl_DisassembleObjCmd -- * * Implementation of the "::tcl::unsupported::disassemble" command. This * command is not documented, but will disassemble procedures, lambda * terms and general scripts. Note that will compile terms if necessary * in order to disassemble them. * *---------------------------------------------------------------------- */ int Tcl_DisassembleObjCmd( void *clientData, /* What type of operation. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const types[] = { "constructor", "destructor", "lambda", "method", "objmethod", "proc", "script", NULL }; enum Types { DISAS_CLASS_CONSTRUCTOR, DISAS_CLASS_DESTRUCTOR, DISAS_LAMBDA, DISAS_CLASS_METHOD, DISAS_OBJECT_METHOD, DISAS_PROC, DISAS_SCRIPT } idx; int result; Tcl_Obj *codeObjPtr = NULL; Proc *procPtr = NULL; Tcl_HashEntry *hPtr; Object *oPtr; ByteCode *codePtr; Method *methodPtr; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "type ..."); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], types, "type", 0, &idx)!=TCL_OK){ return TCL_ERROR; } switch (idx) { case DISAS_LAMBDA: { Command cmd; Tcl_Obj *nsObjPtr; Tcl_Namespace *nsPtr; /* * Compile (if uncompiled) and disassemble a lambda term. */ if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "lambdaTerm"); return TCL_ERROR; } procPtr = TclGetLambdaFromObj(interp, objv[2], &nsObjPtr); if (procPtr == NULL) { return TCL_ERROR; } memset(&cmd, 0, sizeof(Command)); result = TclGetNamespaceFromObj(interp, nsObjPtr, &nsPtr); if (result != TCL_OK) { return result; } cmd.nsPtr = (Namespace *) nsPtr; procPtr->cmdPtr = &cmd; result = TclPushProcCallFrame(procPtr, interp, objc, objv, 1); if (result != TCL_OK) { return result; } TclPopStackFrame(interp); codeObjPtr = procPtr->bodyPtr; break; } case DISAS_PROC: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "procName"); return TCL_ERROR; } procPtr = TclFindProc((Interp *) interp, TclGetString(objv[2])); if (procPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" isn't a procedure", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "PROC", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; } /* * Compile (if uncompiled) and disassemble a procedure. */ result = TclPushProcCallFrame(procPtr, interp, 2, objv+1, 1); if (result != TCL_OK) { return result; } TclPopStackFrame(interp); codeObjPtr = procPtr->bodyPtr; break; case DISAS_SCRIPT: /* * Compile and disassemble a script. */ if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "script"); return TCL_ERROR; } if (!TclHasInternalRep(objv[2], &tclByteCodeType) && (TCL_OK != TclSetByteCodeFromAny(interp, objv[2], NULL, NULL))) { return TCL_ERROR; } codeObjPtr = objv[2]; break; case DISAS_CLASS_CONSTRUCTOR: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "className"); return TCL_ERROR; } /* * Look up the body of a constructor. */ oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[2]); if (oPtr == NULL) { return TCL_ERROR; } if (oPtr->classPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" is not a class", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CLASS", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; } methodPtr = oPtr->classPtr->constructorPtr; if (methodPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" has no defined constructor", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "DISASSEMBLE", "CONSRUCTOR", (char *)NULL); return TCL_ERROR; } procPtr = TclOOGetProcFromMethod(methodPtr); if (procPtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "body not available for this kind of constructor", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "DISASSEMBLE", "METHODTYPE", (char *)NULL); return TCL_ERROR; } /* * Compile if necessary. */ if (!TclHasInternalRep(procPtr->bodyPtr, &tclByteCodeType)) { Command cmd; /* * Yes, this is ugly, but we need to pass the namespace in to the * compiler in two places. */ cmd.nsPtr = (Namespace *) oPtr->namespacePtr; procPtr->cmdPtr = &cmd; result = TclProcCompileProc(interp, procPtr, procPtr->bodyPtr, (Namespace *) oPtr->namespacePtr, "body of constructor", TclGetString(objv[2])); procPtr->cmdPtr = NULL; if (result != TCL_OK) { return result; } } codeObjPtr = procPtr->bodyPtr; break; case DISAS_CLASS_DESTRUCTOR: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "className"); return TCL_ERROR; } /* * Look up the body of a destructor. */ oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[2]); if (oPtr == NULL) { return TCL_ERROR; } if (oPtr->classPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" is not a class", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CLASS", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; } methodPtr = oPtr->classPtr->destructorPtr; if (methodPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" has no defined destructor", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "DISASSEMBLE", "DESRUCTOR", (char *)NULL); return TCL_ERROR; } procPtr = TclOOGetProcFromMethod(methodPtr); if (procPtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "body not available for this kind of destructor", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "DISASSEMBLE", "METHODTYPE", (char *)NULL); return TCL_ERROR; } /* * Compile if necessary. */ if (!TclHasInternalRep(procPtr->bodyPtr, &tclByteCodeType)) { Command cmd; /* * Yes, this is ugly, but we need to pass the namespace in to the * compiler in two places. */ cmd.nsPtr = (Namespace *) oPtr->namespacePtr; procPtr->cmdPtr = &cmd; result = TclProcCompileProc(interp, procPtr, procPtr->bodyPtr, (Namespace *) oPtr->namespacePtr, "body of destructor", TclGetString(objv[2])); procPtr->cmdPtr = NULL; if (result != TCL_OK) { return result; } } codeObjPtr = procPtr->bodyPtr; break; case DISAS_CLASS_METHOD: if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "className methodName"); return TCL_ERROR; } /* * Look up the body of a class method. */ oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[2]); if (oPtr == NULL) { return TCL_ERROR; } if (oPtr->classPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" is not a class", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CLASS", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; } hPtr = Tcl_FindHashEntry(&oPtr->classPtr->classMethods, objv[3]); goto methodBody; case DISAS_OBJECT_METHOD: if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "objectName methodName"); return TCL_ERROR; } /* * Look up the body of an instance method. */ oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[2]); if (oPtr == NULL) { return TCL_ERROR; } if (oPtr->methodsPtr == NULL) { goto unknownMethod; } hPtr = Tcl_FindHashEntry(oPtr->methodsPtr, objv[3]); /* * Compile (if necessary) and disassemble a method body. */ methodBody: if (hPtr == NULL) { unknownMethod: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown method \"%s\"", TclGetString(objv[3]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(objv[3]), (char *)NULL); return TCL_ERROR; } procPtr = TclOOGetProcFromMethod((Method *)Tcl_GetHashValue(hPtr)); if (procPtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "body not available for this kind of method", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "DISASSEMBLE", "METHODTYPE", (char *)NULL); return TCL_ERROR; } if (!TclHasInternalRep(procPtr->bodyPtr, &tclByteCodeType)) { Command cmd; /* * Yes, this is ugly, but we need to pass the namespace in to the * compiler in two places. */ cmd.nsPtr = (Namespace *) oPtr->namespacePtr; procPtr->cmdPtr = &cmd; result = TclProcCompileProc(interp, procPtr, procPtr->bodyPtr, (Namespace *) oPtr->namespacePtr, "body of method", TclGetString(objv[3])); procPtr->cmdPtr = NULL; if (result != TCL_OK) { return result; } } codeObjPtr = procPtr->bodyPtr; break; default: CLANG_ASSERT(0); } /* * Do the actual disassembly. */ ByteCodeGetInternalRep(codeObjPtr, &tclByteCodeType, codePtr); if (codePtr->flags & TCL_BYTECODE_PRECOMPILED) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "may not disassemble prebuilt bytecode", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "DISASSEMBLE", "BYTECODE", (char *)NULL); return TCL_ERROR; } if (clientData) { Tcl_SetObjResult(interp, DisassembleByteCodeAsDicts(codeObjPtr)); } else { Tcl_SetObjResult(interp, DisassembleByteCodeObj(codeObjPtr)); } return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * End: */ tcl9.0.1/generic/tclEncoding.c0000644000175000017500000040625014731032403015561 0ustar sergeisergei/* * tclEncoding.c -- * * Contains the implementation of the encoding conversion package. * * Copyright © 1996-1998 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include typedef size_t (LengthProc)(const char *src); /* * The following data structure represents an encoding, which describes how to * convert between various character sets and UTF-8. */ typedef struct { char *name; /* Name of encoding. Malloced because (1) hash * table entry that owns this encoding may be * freed prior to this encoding being freed, * (2) string passed in the Tcl_EncodingType * structure may not be persistent. */ Tcl_EncodingConvertProc *toUtfProc; /* Function to convert from external encoding * into UTF-8. */ Tcl_EncodingConvertProc *fromUtfProc; /* Function to convert from UTF-8 into * external encoding. */ Tcl_EncodingFreeProc *freeProc; /* If non-NULL, function to call when this * encoding is deleted. */ void *clientData; /* Arbitrary value associated with encoding * type. Passed to conversion functions. */ Tcl_Size nullSize; /* Number of 0x00 bytes that signify * end-of-string in this encoding. This number * is used to determine the source string * length when the srcLen argument is * negative. This number can be 1, 2, or 4. */ LengthProc *lengthProc; /* Function to compute length of * null-terminated strings in this encoding. * If nullSize is 1, this is strlen; if * nullSize is 2, this is a function that * returns the number of bytes in a 0x0000 * terminated string; if nullSize is 4, this * is a function that returns the number of * bytes in a 0x00000000 terminated string. */ size_t refCount; /* Number of uses of this structure. */ Tcl_HashEntry *hPtr; /* Hash table entry that owns this encoding. */ } Encoding; /* * The following structure is the clientData for a dynamically-loaded, * table-driven encoding created by LoadTableEncoding(). It maps between * Unicode and a single-byte, double-byte, or multibyte (1 or 2 bytes only) * encoding. */ typedef struct { int fallback; /* Character (in this encoding) to substitute * when this encoding cannot represent a UTF-8 * character. */ char prefixBytes[256]; /* If a byte in the input stream is a lead * byte for a 2-byte sequence, the * corresponding entry in this array is 1, * otherwise it is 0. */ unsigned short **toUnicode; /* Two dimensional sparse matrix to map * characters from the encoding to Unicode. * Each element of the toUnicode array points * to an array of 256 shorts. If there is no * corresponding character in Unicode, the * value in the matrix is 0x0000. * malloc'd. */ unsigned short **fromUnicode; /* Two dimensional sparse matrix to map * characters from Unicode to the encoding. * Each element of the fromUnicode array * points to an array of 256 shorts. If there * is no corresponding character the encoding, * the value in the matrix is 0x0000. * malloc'd. */ } TableEncodingData; /* * Each of the following structures is the clientData for a dynamically-loaded * escape-driven encoding that is itself comprised of other simpler encodings. * An example is "iso-2022-jp", which uses escape sequences to switch between * ascii, jis0208, jis0212, gb2312, and ksc5601. Note that "escape-driven" * does not necessarily mean that the ESCAPE character is the character used * for switching character sets. */ typedef struct { unsigned sequenceLen; /* Length of following string. */ char sequence[16]; /* Escape code that marks this encoding. */ char name[32]; /* Name for encoding. */ Encoding *encodingPtr; /* Encoding loaded using above name, or NULL * if this sub-encoding has not been needed * yet. */ } EscapeSubTable; typedef struct { int fallback; /* Character (in this encoding) to substitute * when this encoding cannot represent a UTF-8 * character. */ unsigned initLen; /* Length of following string. */ char init[16]; /* String to emit or expect before first char * in conversion. */ unsigned finalLen; /* Length of following string. */ char final[16]; /* String to emit or expect after last char in * conversion. */ char prefixBytes[256]; /* If a byte in the input stream is the first * character of one of the escape sequences in * the following array, the corresponding * entry in this array is 1, otherwise it is * 0. */ int numSubTables; /* Length of following array. */ EscapeSubTable subTables[TCLFLEXARRAY]; /* Information about each EscapeSubTable used * by this encoding type. The actual size is * as large as necessary to hold all * EscapeSubTables. */ } EscapeEncodingData; /* * Values used when loading an encoding file to identify the type of the * file. */ enum EncodingTypes { ENCODING_SINGLEBYTE = 0, /* Encoding is single byte per character. */ ENCODING_DOUBLEBYTE = 1, /* Encoding is two bytes per character. */ ENCODING_MULTIBYTE = 2, /* Encoding is variable bytes per character. */ ENCODING_ESCAPE = 3 /* Encoding has modes with escapes to move * between them. */ }; /* * A list of directories in which Tcl should look for *.enc files. This list * is shared by all threads. Access is governed by a mutex lock. */ static TclInitProcessGlobalValueProc InitializeEncodingSearchPath; static ProcessGlobalValue encodingSearchPath = { 0, 0, NULL, NULL, InitializeEncodingSearchPath, NULL, NULL }; /* * A map from encoding names to the directories in which their data files have * been seen. The string value of the map is shared by all threads. Access to * the shared string is governed by a mutex lock. */ static ProcessGlobalValue encodingFileMap = { 0, 0, NULL, NULL, NULL, NULL, NULL }; /* * A list of directories making up the "library path". Historically this * search path has served many uses, but the only one remaining is a base for * the encodingSearchPath above. If the application does not explicitly set * the encodingSearchPath, then it is initialized by appending /encoding * to each directory in this "libraryPath". */ static ProcessGlobalValue libraryPath = { 0, 0, NULL, NULL, TclpInitLibraryPath, NULL, NULL }; static int encodingsInitialized = 0; /* * Hash table that keeps track of all loaded Encodings. Keys are the string * names that represent the encoding, values are (Encoding *). */ static Tcl_HashTable encodingTable; TCL_DECLARE_MUTEX(encodingMutex) /* * The following are used to hold the default and current system encodings. * If NULL is passed to one of the conversion routines, the current setting of * the system encoding is used to perform the conversion. */ static Tcl_Encoding defaultEncoding = NULL; static Tcl_Encoding systemEncoding = NULL; Tcl_Encoding tclIdentityEncoding = NULL; Tcl_Encoding tclUtf8Encoding = NULL; /* * Names of encoding profiles and corresponding integer values. * Keep alphabetical order for error messages. */ static const struct TclEncodingProfiles { const char *name; int value; } encodingProfiles[] = { {"replace", TCL_ENCODING_PROFILE_REPLACE}, {"strict", TCL_ENCODING_PROFILE_STRICT}, {"tcl8", TCL_ENCODING_PROFILE_TCL8}, }; #define PROFILE_TCL8(flags_) \ (ENCODING_PROFILE_GET(flags_) == TCL_ENCODING_PROFILE_TCL8) #define PROFILE_REPLACE(flags_) \ (ENCODING_PROFILE_GET(flags_) == TCL_ENCODING_PROFILE_REPLACE) #define PROFILE_STRICT(flags_) \ (!PROFILE_TCL8(flags_) && !PROFILE_REPLACE(flags_)) #define UNICODE_REPLACE_CHAR 0xFFFD #define SURROGATE(c_) (((c_) & ~0x7FF) == 0xD800) #define HIGH_SURROGATE(c_) (((c_) & ~0x3FF) == 0xD800) #define LOW_SURROGATE(c_) (((c_) & ~0x3FF) == 0xDC00) /* * The following variable is used in the sparse matrix code for a * TableEncoding to represent a page in the table that has no entries. */ static unsigned short emptyPage[256]; /* * Functions used only in this module. */ static Tcl_EncodingConvertProc BinaryProc; static Tcl_DupInternalRepProc DupEncodingInternalRep; static Tcl_EncodingFreeProc EscapeFreeProc; static Tcl_EncodingConvertProc EscapeFromUtfProc; static Tcl_EncodingConvertProc EscapeToUtfProc; static void FillEncodingFileMap(void); static void FreeEncoding(Tcl_Encoding encoding); static Tcl_FreeInternalRepProc FreeEncodingInternalRep; static Encoding * GetTableEncoding(EscapeEncodingData *dataPtr, int state); static Tcl_Encoding LoadEncodingFile(Tcl_Interp *interp, const char *name); static Tcl_Encoding LoadTableEncoding(const char *name, int type, Tcl_Channel chan); static Tcl_Encoding LoadEscapeEncoding(const char *name, Tcl_Channel chan); static Tcl_Channel OpenEncodingFileChannel(Tcl_Interp *interp, const char *name); static Tcl_EncodingFreeProc TableFreeProc; static Tcl_EncodingConvertProc TableFromUtfProc; static Tcl_EncodingConvertProc TableToUtfProc; static size_t unilen(const char *src); static size_t unilen4(const char *src); static Tcl_EncodingConvertProc Utf32ToUtfProc; static Tcl_EncodingConvertProc UtfToUtf32Proc; static Tcl_EncodingConvertProc Utf16ToUtfProc; static Tcl_EncodingConvertProc UtfToUtf16Proc; static Tcl_EncodingConvertProc UtfToUcs2Proc; static Tcl_EncodingConvertProc UtfToUtfProc; static Tcl_EncodingConvertProc Iso88591FromUtfProc; static Tcl_EncodingConvertProc Iso88591ToUtfProc; /* * A Tcl_ObjType for holding a cached Tcl_Encoding in the twoPtrValue.ptr1 * field of the internalrep. This should help the lifetime of encodings be more * useful. See concerns raised in [Bug 1077262]. */ static const Tcl_ObjType encodingType = { "encoding", FreeEncodingInternalRep, DupEncodingInternalRep, NULL, NULL, TCL_OBJTYPE_V0 }; #define EncodingSetInternalRep(objPtr, encoding) \ do { \ Tcl_ObjInternalRep ir; \ ir.twoPtrValue.ptr1 = (encoding); \ ir.twoPtrValue.ptr2 = NULL; \ Tcl_StoreInternalRep((objPtr), &encodingType, &ir); \ } while (0) #define EncodingGetInternalRep(objPtr, encoding) \ do { \ const Tcl_ObjInternalRep *irPtr; \ irPtr = TclFetchInternalRep ((objPtr), &encodingType); \ (encoding) = irPtr ? (Tcl_Encoding)irPtr->twoPtrValue.ptr1 : NULL; \ } while (0) /* *---------------------------------------------------------------------- * * Tcl_GetEncodingFromObj -- * * Writes to (*encodingPtr) the Tcl_Encoding value of (*objPtr), if * possible, and returns TCL_OK. If no such encoding exists, TCL_ERROR is * returned, and if interp is non-NULL, an error message is written * there. * * Results: * Standard Tcl return code. * * Side effects: * Caches the Tcl_Encoding value as the internal rep of (*objPtr). * *---------------------------------------------------------------------- */ int Tcl_GetEncodingFromObj( Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Encoding *encodingPtr) { Tcl_Encoding encoding; const char *name = TclGetString(objPtr); EncodingGetInternalRep(objPtr, encoding); if (encoding == NULL) { encoding = Tcl_GetEncoding(interp, name); if (encoding == NULL) { return TCL_ERROR; } EncodingSetInternalRep(objPtr, encoding); } *encodingPtr = Tcl_GetEncoding(NULL, name); return TCL_OK; } /* *---------------------------------------------------------------------- * * FreeEncodingInternalRep -- * * The Tcl_FreeInternalRepProc for the "encoding" Tcl_ObjType. * *---------------------------------------------------------------------- */ static void FreeEncodingInternalRep( Tcl_Obj *objPtr) { Tcl_Encoding encoding; EncodingGetInternalRep(objPtr, encoding); Tcl_FreeEncoding(encoding); } /* *---------------------------------------------------------------------- * * DupEncodingInternalRep -- * * The Tcl_DupInternalRepProc for the "encoding" Tcl_ObjType. * *---------------------------------------------------------------------- */ static void DupEncodingInternalRep( Tcl_Obj *srcPtr, Tcl_Obj *dupPtr) { Tcl_Encoding encoding = Tcl_GetEncoding(NULL, TclGetString(srcPtr)); EncodingSetInternalRep(dupPtr, encoding); } /* *---------------------------------------------------------------------- * * Tcl_GetEncodingSearchPath -- * * Keeps the per-thread copy of the encoding search path current with * changes to the global copy. * * Results: * Returns a "list" (Tcl_Obj *) that contains the encoding search path. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_GetEncodingSearchPath(void) { return TclGetProcessGlobalValue(&encodingSearchPath); } /* *---------------------------------------------------------------------- * * Tcl_SetEncodingSearchPath -- * * Keeps the per-thread copy of the encoding search path current with * changes to the global copy. * *---------------------------------------------------------------------- */ int Tcl_SetEncodingSearchPath( Tcl_Obj *searchPath) { Tcl_Size dummy; if (TCL_ERROR == TclListObjLength(NULL, searchPath, &dummy)) { return TCL_ERROR; } TclSetProcessGlobalValue(&encodingSearchPath, searchPath); return TCL_OK; } /* *--------------------------------------------------------------------------- * * FillEncodingFileMap -- * * Called to update the encoding file map with the current value * of the encoding search path. * * Finds *.end files in the directories on the encoding search path and * stores the found pathnames in a map associated with the encoding name. * * If $dir is on the encoding search path and the file $dir/foo.enc is * found, stores a "foo" -> $dir entry in the map. if the "foo" encoding * is needed later, the $dir/foo.enc name can be quickly constructed in * order to read the encoding data. * * Results: * None. * * Side effects: * Entries are added to the encoding file map. * *--------------------------------------------------------------------------- */ static void FillEncodingFileMap(void) { Tcl_Size i, numDirs = 0; Tcl_Obj *map, *searchPath; searchPath = Tcl_GetEncodingSearchPath(); Tcl_IncrRefCount(searchPath); TclListObjLength(NULL, searchPath, &numDirs); map = Tcl_NewDictObj(); Tcl_IncrRefCount(map); for (i = numDirs-1; i != TCL_INDEX_NONE; i--) { /* * Iterate backwards through the search path so as we overwrite * entries found, we favor files earlier on the search path. */ Tcl_Size j, numFiles; Tcl_Obj *directory, *matchFileList; Tcl_Obj **filev; Tcl_GlobTypeData readableFiles = { TCL_GLOB_TYPE_FILE, TCL_GLOB_PERM_R, NULL, NULL }; TclNewObj(matchFileList); Tcl_ListObjIndex(NULL, searchPath, i, &directory); Tcl_IncrRefCount(directory); Tcl_IncrRefCount(matchFileList); Tcl_FSMatchInDirectory(NULL, matchFileList, directory, "*.enc", &readableFiles); TclListObjGetElements(NULL, matchFileList, &numFiles, &filev); for (j=0; j internal */ }; void TclInitEncodingSubsystem(void) { Tcl_EncodingType type; TableEncodingData *dataPtr; unsigned size; unsigned short i; union { char c; short s; } isLe; int leFlags; if (encodingsInitialized) { return; } /* Note: This DEPENDS on TCL_ENCODING_LE being defined in least sig byte */ isLe.s = 1; leFlags = isLe.c ? TCL_ENCODING_LE : 0; Tcl_MutexLock(&encodingMutex); Tcl_InitHashTable(&encodingTable, TCL_STRING_KEYS); Tcl_MutexUnlock(&encodingMutex); /* * Create a few initial encodings. UTF-8 to UTF-8 translation is not a * no-op because it turns a stream of improperly formed UTF-8 into a * properly formed stream. */ type.encodingName = NULL; type.toUtfProc = BinaryProc; type.fromUtfProc = BinaryProc; type.freeProc = NULL; type.nullSize = 1; type.clientData = NULL; tclIdentityEncoding = Tcl_CreateEncoding(&type); type.encodingName = "utf-8"; type.toUtfProc = UtfToUtfProc; type.fromUtfProc = UtfToUtfProc; type.freeProc = NULL; type.nullSize = 1; type.clientData = INT2PTR(ENCODING_UTF); tclUtf8Encoding = Tcl_CreateEncoding(&type); type.clientData = NULL; type.encodingName = "cesu-8"; Tcl_CreateEncoding(&type); type.toUtfProc = Utf16ToUtfProc; type.fromUtfProc = UtfToUcs2Proc; type.freeProc = NULL; type.nullSize = 2; type.encodingName = "ucs-2le"; type.clientData = INT2PTR(TCL_ENCODING_LE); Tcl_CreateEncoding(&type); type.encodingName = "ucs-2be"; type.clientData = NULL; Tcl_CreateEncoding(&type); type.encodingName = "ucs-2"; type.clientData = INT2PTR(leFlags); Tcl_CreateEncoding(&type); type.toUtfProc = Utf32ToUtfProc; type.fromUtfProc = UtfToUtf32Proc; type.freeProc = NULL; type.nullSize = 4; type.encodingName = "utf-32le"; type.clientData = INT2PTR(TCL_ENCODING_LE); Tcl_CreateEncoding(&type); type.encodingName = "utf-32be"; type.clientData = NULL; Tcl_CreateEncoding(&type); type.encodingName = "utf-32"; type.clientData = INT2PTR(leFlags); Tcl_CreateEncoding(&type); type.toUtfProc = Utf16ToUtfProc; type.fromUtfProc = UtfToUtf16Proc; type.freeProc = NULL; type.nullSize = 2; type.encodingName = "utf-16le"; type.clientData = INT2PTR(TCL_ENCODING_LE); Tcl_CreateEncoding(&type); type.encodingName = "utf-16be"; type.clientData = NULL; Tcl_CreateEncoding(&type); type.encodingName = "utf-16"; type.clientData = INT2PTR(leFlags); Tcl_CreateEncoding(&type); #ifndef TCL_NO_DEPRECATED type.encodingName = "unicode"; Tcl_CreateEncoding(&type); #endif /* * Need the iso8859-1 encoding in order to process binary data, so force * it to always be embedded. Note that this encoding *must* be a proper * table encoding or some of the escape encodings crash! Hence the ugly * code to duplicate the structure of a table encoding here. */ dataPtr = (TableEncodingData *)Tcl_Alloc(sizeof(TableEncodingData)); memset(dataPtr, 0, sizeof(TableEncodingData)); dataPtr->fallback = '?'; size = 256*(sizeof(unsigned short *) + sizeof(unsigned short)); dataPtr->toUnicode = (unsigned short **)Tcl_Alloc(size); memset(dataPtr->toUnicode, 0, size); dataPtr->fromUnicode = (unsigned short **)Tcl_Alloc(size); memset(dataPtr->fromUnicode, 0, size); dataPtr->toUnicode[0] = (unsigned short *) (dataPtr->toUnicode + 256); dataPtr->fromUnicode[0] = (unsigned short *) (dataPtr->fromUnicode + 256); for (i=1 ; i<256 ; i++) { dataPtr->toUnicode[i] = emptyPage; dataPtr->fromUnicode[i] = emptyPage; } for (i=0 ; i<256 ; i++) { dataPtr->toUnicode[0][i] = i; dataPtr->fromUnicode[0][i] = i; } type.encodingName = "iso8859-1"; type.toUtfProc = Iso88591ToUtfProc; type.fromUtfProc = Iso88591FromUtfProc; type.freeProc = TableFreeProc; type.nullSize = 1; type.clientData = dataPtr; defaultEncoding = Tcl_CreateEncoding(&type); systemEncoding = Tcl_GetEncoding(NULL, type.encodingName); encodingsInitialized = 1; } /* *---------------------------------------------------------------------- * * TclFinalizeEncodingSubsystem -- * * Release the state associated with the encoding subsystem. * * Results: * None. * * Side effects: * Frees all of the encodings. * *---------------------------------------------------------------------- */ void TclFinalizeEncodingSubsystem(void) { Tcl_HashSearch search; Tcl_HashEntry *hPtr; Tcl_MutexLock(&encodingMutex); encodingsInitialized = 0; FreeEncoding(systemEncoding); systemEncoding = NULL; defaultEncoding = NULL; FreeEncoding(tclIdentityEncoding); tclIdentityEncoding = NULL; FreeEncoding(tclUtf8Encoding); tclUtf8Encoding = NULL; hPtr = Tcl_FirstHashEntry(&encodingTable, &search); while (hPtr != NULL) { /* * Call FreeEncoding instead of doing it directly to handle refcounts * like escape encodings use. [Bug 524674] Make sure to call * Tcl_FirstHashEntry repeatedly so that all encodings are eventually * cleaned up. */ FreeEncoding((Tcl_Encoding)Tcl_GetHashValue(hPtr)); hPtr = Tcl_FirstHashEntry(&encodingTable, &search); } Tcl_DeleteHashTable(&encodingTable); Tcl_MutexUnlock(&encodingMutex); } /* *------------------------------------------------------------------------- * * Tcl_GetEncoding -- * * Given the name of a encoding, find the corresponding Tcl_Encoding * token. If the encoding did not already exist, Tcl attempts to * dynamically load an encoding by that name. * * Results: * Returns a token that represents the encoding. If the name didn't refer * to any known or loadable encoding, NULL is returned. If NULL was * returned, an error message is left in interp's result object, unless * interp was NULL. * * Side effects: * LoadEncodingFile is called if necessary. * *------------------------------------------------------------------------- */ Tcl_Encoding Tcl_GetEncoding( Tcl_Interp *interp, /* Interp for error reporting, if not NULL. */ const char *name) /* The name of the desired encoding. */ { Tcl_HashEntry *hPtr; Encoding *encodingPtr; Tcl_MutexLock(&encodingMutex); if (name == NULL) { encodingPtr = (Encoding *) systemEncoding; encodingPtr->refCount++; Tcl_MutexUnlock(&encodingMutex); return systemEncoding; } hPtr = Tcl_FindHashEntry(&encodingTable, name); if (hPtr != NULL) { encodingPtr = (Encoding *)Tcl_GetHashValue(hPtr); encodingPtr->refCount++; Tcl_MutexUnlock(&encodingMutex); return (Tcl_Encoding) encodingPtr; } Tcl_MutexUnlock(&encodingMutex); return LoadEncodingFile(interp, name); } /* *--------------------------------------------------------------------------- * * Tcl_FreeEncoding -- * * Releases an encoding allocated by Tcl_CreateEncoding() or * Tcl_GetEncoding(). * * Results: * None. * * Side effects: * The reference count associated with the encoding is decremented and * the encoding is deleted if nothing is using it anymore. * *--------------------------------------------------------------------------- */ void Tcl_FreeEncoding( Tcl_Encoding encoding) { Tcl_MutexLock(&encodingMutex); FreeEncoding(encoding); Tcl_MutexUnlock(&encodingMutex); } /* *---------------------------------------------------------------------- * * FreeEncoding -- * * Decrements the reference count of an encoding. The caller must hold * encodingMutes. * * Results: * None. * * Side effects: * Releases the resource for an encoding if it is now unused. * The reference count associated with the encoding is decremented and * the encoding may be deleted if nothing is using it anymore. * *---------------------------------------------------------------------- */ static void FreeEncoding( Tcl_Encoding encoding) { Encoding *encodingPtr = (Encoding *) encoding; if (encodingPtr == NULL) { return; } if (encodingPtr->refCount-- <= 1) { if (encodingPtr->freeProc != NULL) { encodingPtr->freeProc(encodingPtr->clientData); } if (encodingPtr->hPtr != NULL) { Tcl_DeleteHashEntry(encodingPtr->hPtr); } if (encodingPtr->name) { Tcl_Free(encodingPtr->name); } Tcl_Free(encodingPtr); } } /* *------------------------------------------------------------------------- * * Tcl_GetEncodingName -- * * Given an encoding, return the name that was used to construct the * encoding. * * Results: * The name of the encoding. * * Side effects: * None. * *--------------------------------------------------------------------------- */ const char * Tcl_GetEncodingName( Tcl_Encoding encoding) /* The encoding whose name to fetch. */ { if (encoding == NULL) { encoding = systemEncoding; } return ((Encoding *) encoding)->name; } /* *------------------------------------------------------------------------- * * Tcl_GetEncodingNames -- * * Get the list of all known encodings, including the ones stored as * files on disk in the encoding path. * * Results: * Modifies interp's result object to hold a list of all the available * encodings. * * Side effects: * None. * *------------------------------------------------------------------------- */ void Tcl_GetEncodingNames( Tcl_Interp *interp) /* Interp to hold result. */ { Tcl_HashTable table; Tcl_HashSearch search; Tcl_HashEntry *hPtr; Tcl_Obj *map, *name, *result; Tcl_DictSearch mapSearch; int dummy, done = 0; TclNewObj(result); Tcl_InitObjHashTable(&table); /* * Copy encoding names from loaded encoding table to table. */ Tcl_MutexLock(&encodingMutex); for (hPtr = Tcl_FirstHashEntry(&encodingTable, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { Encoding *encodingPtr = (Encoding *)Tcl_GetHashValue(hPtr); Tcl_CreateHashEntry(&table, Tcl_NewStringObj(encodingPtr->name, TCL_INDEX_NONE), &dummy); } Tcl_MutexUnlock(&encodingMutex); FillEncodingFileMap(); map = TclGetProcessGlobalValue(&encodingFileMap); /* * Copy encoding names from encoding file map to table. */ Tcl_DictObjFirst(NULL, map, &mapSearch, &name, NULL, &done); for (; !done; Tcl_DictObjNext(&mapSearch, &name, NULL, &done)) { Tcl_CreateHashEntry(&table, name, &dummy); } /* * Pull all encoding names from table into the result list. */ for (hPtr = Tcl_FirstHashEntry(&table, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { Tcl_ListObjAppendElement(NULL, result, (Tcl_Obj *) Tcl_GetHashKey(&table, hPtr)); } Tcl_SetObjResult(interp, result); Tcl_DeleteHashTable(&table); } /* *------------------------------------------------------------------------- * * Tcl_GetEncodingNulLength -- * * Given an encoding, return the number of nul bytes used for the * string termination. * * Results: * The number of nul bytes used for the string termination. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Size Tcl_GetEncodingNulLength( Tcl_Encoding encoding) { if (encoding == NULL) { encoding = systemEncoding; } return ((Encoding *) encoding)->nullSize; } /* *------------------------------------------------------------------------ * * Tcl_SetSystemEncoding -- * * Sets the default encoding that should be used whenever the user passes * a NULL value in to one of the conversion routines. If the supplied * name is NULL, the system encoding is reset to the default system * encoding. * * Results: * The return value is TCL_OK if the system encoding was successfully set * to the encoding specified by name, TCL_ERROR otherwise. If TCL_ERROR * is returned, an error message is left in interp's result object, * unless interp was NULL. * * Side effects: * The reference count of the new system encoding is incremented. The * reference count of the old system encoding is decremented and it may * be freed. All VFS cached information is invalidated. * *------------------------------------------------------------------------ */ int Tcl_SetSystemEncoding( Tcl_Interp *interp, /* Interp for error reporting, if not NULL. */ const char *name) /* The name of the desired encoding, or NULL/"" * to reset to default encoding. */ { Tcl_Encoding encoding; Encoding *encodingPtr; if (!name || !*name) { Tcl_MutexLock(&encodingMutex); encoding = defaultEncoding; encodingPtr = (Encoding *) encoding; encodingPtr->refCount++; Tcl_MutexUnlock(&encodingMutex); } else { encoding = Tcl_GetEncoding(interp, name); if (encoding == NULL) { return TCL_ERROR; } } Tcl_MutexLock(&encodingMutex); FreeEncoding(systemEncoding); systemEncoding = encoding; Tcl_MutexUnlock(&encodingMutex); Tcl_FSMountsChanged(NULL); return TCL_OK; } /* *--------------------------------------------------------------------------- * * Tcl_CreateEncoding -- * * Defines a new encoding, along with the functions that are used to * convert to and from Unicode. * * Results: * Returns a token that represents the encoding. If an encoding with the * same name already existed, the old encoding token remains valid and * continues to behave as it used to, and is eventually garbage collected * when the last reference to it goes away. Any subsequent calls to * Tcl_GetEncoding with the specified name retrieve the most recent * encoding token. * * Side effects: * A new record having the name of the encoding is entered into a table of * encodings visible to all interpreters. For each call to this function, * there should eventually be a call to Tcl_FreeEncoding, which cleans * deletes the record in the table when an encoding is no longer needed. * *--------------------------------------------------------------------------- */ Tcl_Encoding Tcl_CreateEncoding( const Tcl_EncodingType *typePtr) /* The encoding type. */ { Encoding *encodingPtr = (Encoding *)Tcl_Alloc(sizeof(Encoding)); encodingPtr->name = NULL; encodingPtr->toUtfProc = typePtr->toUtfProc; encodingPtr->fromUtfProc = typePtr->fromUtfProc; encodingPtr->freeProc = typePtr->freeProc; encodingPtr->nullSize = typePtr->nullSize; encodingPtr->clientData = typePtr->clientData; if (typePtr->nullSize == 2) { encodingPtr->lengthProc = (LengthProc *) unilen; } else if (typePtr->nullSize == 4) { encodingPtr->lengthProc = (LengthProc *) unilen4; } else { encodingPtr->lengthProc = (LengthProc *) strlen; } encodingPtr->refCount = 1; encodingPtr->hPtr = NULL; if (typePtr->encodingName) { Tcl_HashEntry *hPtr; int isNew; char *name; Tcl_MutexLock(&encodingMutex); hPtr = Tcl_CreateHashEntry(&encodingTable, typePtr->encodingName, &isNew); if (isNew == 0) { /* * Remove old encoding from hash table, but don't delete it until last * reference goes away. */ Encoding *replaceMe = (Encoding *)Tcl_GetHashValue(hPtr); replaceMe->hPtr = NULL; } name = (char *)Tcl_Alloc(strlen(typePtr->encodingName) + 1); encodingPtr->name = strcpy(name, typePtr->encodingName); encodingPtr->hPtr = hPtr; Tcl_SetHashValue(hPtr, encodingPtr); Tcl_MutexUnlock(&encodingMutex); } return (Tcl_Encoding) encodingPtr; } /* *------------------------------------------------------------------------- * * Tcl_ExternalToUtfDString -- * * Convert a source buffer from the specified encoding into UTF-8. If any * of the bytes in the source buffer are invalid or cannot be represented * in the target encoding, a default fallback character will be * substituted. * * Results: * The converted bytes are stored in the DString, which is then NULL * terminated. The return value is a pointer to the value stored in the * DString. * * Side effects: * None. * *------------------------------------------------------------------------- */ #undef Tcl_ExternalToUtfDString char * Tcl_ExternalToUtfDString( Tcl_Encoding encoding, /* The encoding for the source string, or NULL * for the default system encoding. */ const char *src, /* Source string in specified encoding. */ Tcl_Size srcLen, /* Source string length in bytes, or < 0 for * encoding-specific string length. */ Tcl_DString *dstPtr) /* Uninitialized or free DString in which the * converted string is stored. */ { Tcl_ExternalToUtfDStringEx( NULL, encoding, src, srcLen, TCL_ENCODING_PROFILE_TCL8, dstPtr, NULL); return Tcl_DStringValue(dstPtr); } /* *------------------------------------------------------------------------- * * Tcl_ExternalToUtfDStringEx -- * * Convert a source buffer from the specified encoding into UTF-8. * "flags" controls the behavior if any of the bytes in * the source buffer are invalid or cannot be represented in utf-8. * Possible flags values: * target encoding. It should be composed by OR-ing the following: * - *At most one* of TCL_ENCODING_PROFILE{DEFAULT,TCL8,STRICT} * * Results: * The return value is one of * TCL_OK: success. Converted string in *dstPtr * TCL_ERROR: error in passed parameters. Error message in interp * TCL_CONVERT_MULTIBYTE: source ends in truncated multibyte sequence * TCL_CONVERT_SYNTAX: source is not conformant to encoding definition * TCL_CONVERT_UNKNOWN: source contained a character that could not * be represented in target encoding. * * Side effects: * TCL_OK: The converted bytes are stored in the DString and NUL * terminated in an encoding-specific manner. * TCL_ERROR: an error, message is stored in the interp if not NULL. * TCL_CONVERT_*: if errorLocPtr is NULL, an error message is stored * in the interpreter (if not NULL). If errorLocPtr is not NULL, * no error message is stored as it is expected the caller is * interested in whatever is decoded so far and not treating this * as an error condition. * * In addition, *dstPtr is always initialized and must be cleared * by the caller irrespective of the return code. * *------------------------------------------------------------------------- */ int Tcl_ExternalToUtfDStringEx( Tcl_Interp *interp, /* For error messages. May be NULL. */ Tcl_Encoding encoding, /* The encoding for the source string, or NULL * for the default system encoding. */ const char *src, /* Source string in specified encoding. */ Tcl_Size srcLen, /* Source string length in bytes, or < 0 for * encoding-specific string length. */ int flags, /* Conversion control flags. */ Tcl_DString *dstPtr, /* Uninitialized or free DString in which the * converted string is stored. */ Tcl_Size *errorLocPtr) /* Where to store the error location * (or TCL_INDEX_NONE if no error). May * be NULL. */ { char *dst; Tcl_EncodingState state; const Encoding *encodingPtr; int result; Tcl_Size dstLen, soFar; const char *srcStart = src; /* DO FIRST - Must always be initialized before returning */ Tcl_DStringInit(dstPtr); dst = Tcl_DStringValue(dstPtr); dstLen = dstPtr->spaceAvl - 1; if (encoding == NULL) { encoding = systemEncoding; } encodingPtr = (Encoding *)encoding; if (src == NULL) { srcLen = 0; } else if (srcLen == TCL_INDEX_NONE) { srcLen = encodingPtr->lengthProc(src); } flags &= ~TCL_ENCODING_END; flags |= TCL_ENCODING_START; if (encodingPtr->toUtfProc == UtfToUtfProc) { flags |= ENCODING_INPUT; } while (1) { int srcChunkLen, srcChunkRead; int dstChunkLen, dstChunkWrote, dstChunkChars; if (srcLen > INT_MAX) { srcChunkLen = INT_MAX; } else { srcChunkLen = srcLen; flags |= TCL_ENCODING_END; /* Last chunk */ } dstChunkLen = dstLen > INT_MAX ? INT_MAX : dstLen; result = encodingPtr->toUtfProc(encodingPtr->clientData, src, srcChunkLen, flags, &state, dst, dstChunkLen, &srcChunkRead, &dstChunkWrote, &dstChunkChars); soFar = dst + dstChunkWrote - Tcl_DStringValue(dstPtr); src += srcChunkRead; /* * Keep looping in two case - * - our destination buffer did not have enough room * - we had not passed in all the data and error indicated fragment * of a multibyte character * In both cases we have to grow buffer, move the input source pointer * and loop. Otherwise, return the result we got. */ if ((result != TCL_CONVERT_NOSPACE) && !(result == TCL_CONVERT_MULTIBYTE && (flags & TCL_ENCODING_END))) { Tcl_Size nBytesProcessed = (src - srcStart); Tcl_DStringSetLength(dstPtr, soFar); if (errorLocPtr) { /* * Do not write error message into interpreter if caller * wants to know error location. */ *errorLocPtr = result == TCL_OK ? TCL_INDEX_NONE : nBytesProcessed; } else { /* Caller wants error message on failure */ if (result != TCL_OK && interp != NULL) { char buf[TCL_INTEGER_SPACE]; snprintf(buf, sizeof(buf), "%" TCL_SIZE_MODIFIER "d", nBytesProcessed); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unexpected byte sequence starting at index %" TCL_SIZE_MODIFIER "d: '\\x%02X'", nBytesProcessed, UCHAR(srcStart[nBytesProcessed]))); Tcl_SetErrorCode( interp, "TCL", "ENCODING", "ILLEGALSEQUENCE", buf, (char *)NULL); } } if (result != TCL_OK) { errno = (result == TCL_CONVERT_NOSPACE) ? ENOMEM : EILSEQ; } return result; } /* Expand space and continue */ flags &= ~TCL_ENCODING_START; srcLen -= srcChunkRead; if (Tcl_DStringLength(dstPtr) == 0) { Tcl_DStringSetLength(dstPtr, dstLen); } Tcl_DStringSetLength(dstPtr, 2 * Tcl_DStringLength(dstPtr) + 1); dst = Tcl_DStringValue(dstPtr) + soFar; dstLen = Tcl_DStringLength(dstPtr) - soFar - 1; } } /* *------------------------------------------------------------------------- * * Tcl_ExternalToUtf -- * * Convert a source buffer from the specified encoding into UTF-8. * * Results: * The return value is one of TCL_OK, TCL_CONVERT_MULTIBYTE, * TCL_CONVERT_SYNTAX, TCL_CONVERT_UNKNOWN, or TCL_CONVERT_NOSPACE, as * documented in tcl.h. * * Side effects: * The converted bytes are stored in the output buffer. * *------------------------------------------------------------------------- */ int Tcl_ExternalToUtf( TCL_UNUSED(Tcl_Interp *), /* TODO: Re-examine this. */ Tcl_Encoding encoding, /* The encoding for the source string, or NULL * for the default system encoding. */ const char *src, /* Source string in specified encoding. */ Tcl_Size srcLen, /* Source string length in bytes, or * TCL_INDEX_NONE for encoding-specific string * length. */ int flags, /* Conversion control flags. */ Tcl_EncodingState *statePtr,/* Place for conversion routine to store state * information used during a piecewise * conversion. Contents of statePtr are * initialized and/or reset by conversion * routine under control of flags argument. */ char *dst, /* Output buffer in which converted string is * stored. */ Tcl_Size dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { const Encoding *encodingPtr; int result, srcRead, dstWrote, dstChars = 0; int noTerminate = flags & TCL_ENCODING_NO_TERMINATE; int charLimited = (flags & TCL_ENCODING_CHAR_LIMIT) && dstCharsPtr; int maxChars = INT_MAX; Tcl_EncodingState state; if (encoding == NULL) { encoding = systemEncoding; } encodingPtr = (Encoding *) encoding; if (src == NULL) { srcLen = 0; } else if (srcLen == TCL_INDEX_NONE) { srcLen = encodingPtr->lengthProc(src); } if (statePtr == NULL) { flags |= TCL_ENCODING_START | TCL_ENCODING_END; statePtr = &state; } if (srcLen > INT_MAX) { srcLen = INT_MAX; flags &= ~TCL_ENCODING_END; } if (dstLen > INT_MAX) { dstLen = INT_MAX; } if (srcReadPtr == NULL) { srcReadPtr = &srcRead; } if (dstWrotePtr == NULL) { dstWrotePtr = &dstWrote; } if (dstCharsPtr == NULL) { dstCharsPtr = &dstChars; flags &= ~TCL_ENCODING_CHAR_LIMIT; } else if (charLimited) { maxChars = *dstCharsPtr; } if (!noTerminate) { if (dstLen < 1) { return TCL_CONVERT_NOSPACE; } /* * If there are any null characters in the middle of the buffer, * they will converted to the UTF-8 null character (\xC0\x80). To get * the actual \0 at the end of the destination buffer, we need to * append it manually. First make room for it... */ dstLen--; } else { if (dstLen <= 0 && srcLen > 0) { return TCL_CONVERT_NOSPACE; } } if (encodingPtr->toUtfProc == UtfToUtfProc) { flags |= ENCODING_INPUT; } do { Tcl_EncodingState savedState = *statePtr; result = encodingPtr->toUtfProc(encodingPtr->clientData, src, srcLen, flags, statePtr, dst, dstLen, srcReadPtr, dstWrotePtr, dstCharsPtr); if (*dstCharsPtr <= maxChars) { break; } dstLen = Tcl_UtfAtIndex(dst, maxChars) - dst + (TCL_UTF_MAX - 1); *statePtr = savedState; } while (1); if (!noTerminate) { /* ...and then append it */ dst[*dstWrotePtr] = '\0'; } return result; } /* *------------------------------------------------------------------------- * * Tcl_UtfToExternalDString -- * * Convert a source buffer from UTF-8 to the specified encoding. If any * of the bytes in the source buffer are invalid or cannot be represented * in the target encoding, a default fallback character is substituted. * * Results: * The converted bytes are stored in the DString, which is then NULL * terminated in an encoding-specific manner. The return value is a * pointer to the value stored in the DString. * * Side effects: * None. * *------------------------------------------------------------------------- */ #undef Tcl_UtfToExternalDString char * Tcl_UtfToExternalDString( Tcl_Encoding encoding, /* The encoding for the converted string, or * NULL for the default system encoding. */ const char *src, /* Source string in UTF-8. */ Tcl_Size srcLen, /* Source string length in bytes, or < 0 for * strlen(). */ Tcl_DString *dstPtr) /* Uninitialized or free DString in which the * converted string is stored. */ { Tcl_UtfToExternalDStringEx( NULL, encoding, src, srcLen, TCL_ENCODING_PROFILE_TCL8, dstPtr, NULL); return Tcl_DStringValue(dstPtr); } /* *------------------------------------------------------------------------- * * Tcl_UtfToExternalDStringEx -- * * Convert a source buffer from UTF-8 to the specified encoding. * The parameter flags controls the behavior, if any of the bytes in * the source buffer are invalid or cannot be represented in the * target encoding. It should be composed by OR-ing the following: * - *At most one* of TCL_ENCODING_PROFILE_* * * Results: * The return value is one of * TCL_OK: success. Converted string in *dstPtr * TCL_ERROR: error in passed parameters. Error message in interp * TCL_CONVERT_MULTIBYTE: source ends in truncated multibyte sequence * TCL_CONVERT_SYNTAX: source is not conformant to encoding definition * TCL_CONVERT_UNKNOWN: source contained a character that could not * be represented in target encoding. * * Side effects: * TCL_OK: The converted bytes are stored in the DString and NUL * terminated in an encoding-specific manner * TCL_ERROR: an error, message is stored in the interp if not NULL. * TCL_CONVERT_*: if errorLocPtr is NULL, an error message is stored * in the interpreter (if not NULL). If errorLocPtr is not NULL, * no error message is stored as it is expected the caller is * interested in whatever is decoded so far and not treating this * as an error condition. * * In addition, *dstPtr is always initialized and must be cleared * by the caller irrespective of the return code. * *------------------------------------------------------------------------- */ int Tcl_UtfToExternalDStringEx( Tcl_Interp *interp, /* For error messages. May be NULL. */ Tcl_Encoding encoding, /* The encoding for the converted string, or * NULL for the default system encoding. */ const char *src, /* Source string in UTF-8. */ Tcl_Size srcLen, /* Source string length in bytes, or < 0 for * strlen(). */ int flags, /* Conversion control flags. */ Tcl_DString *dstPtr, /* Uninitialized or free DString in which the * converted string is stored. */ Tcl_Size *errorLocPtr) /* Where to store the error location * (or TCL_INDEX_NONE if no error). May * be NULL. */ { char *dst; Tcl_EncodingState state; const Encoding *encodingPtr; int result; const char *srcStart = src; Tcl_Size dstLen, soFar; /* DO FIRST - must always be initialized on return */ Tcl_DStringInit(dstPtr); dst = Tcl_DStringValue(dstPtr); dstLen = dstPtr->spaceAvl - 1; if (encoding == NULL) { encoding = systemEncoding; } encodingPtr = (Encoding *) encoding; if (src == NULL) { srcLen = 0; } else if (srcLen == TCL_INDEX_NONE) { srcLen = strlen(src); } flags &= ~TCL_ENCODING_END; flags |= TCL_ENCODING_START; while (1) { int srcChunkLen, srcChunkRead; int dstChunkLen, dstChunkWrote, dstChunkChars; if (srcLen > INT_MAX) { srcChunkLen = INT_MAX; } else { srcChunkLen = srcLen; flags |= TCL_ENCODING_END; /* Last chunk */ } dstChunkLen = dstLen > INT_MAX ? INT_MAX : dstLen; result = encodingPtr->fromUtfProc(encodingPtr->clientData, src, srcChunkLen, flags, &state, dst, dstChunkLen, &srcChunkRead, &dstChunkWrote, &dstChunkChars); soFar = dst + dstChunkWrote - Tcl_DStringValue(dstPtr); /* Move past the part processed in this go around */ src += srcChunkRead; /* * Keep looping in two case - * - our destination buffer did not have enough room * - we had not passed in all the data and error indicated fragment * of a multibyte character * In both cases we have to grow buffer, move the input source pointer * and loop. Otherwise, return the result we got. */ if ((result != TCL_CONVERT_NOSPACE) && !(result == TCL_CONVERT_MULTIBYTE && (flags & TCL_ENCODING_END))) { Tcl_Size nBytesProcessed = (src - srcStart); Tcl_Size i = soFar + encodingPtr->nullSize - 1; /* Loop as DStringSetLength only stores one nul byte at a time */ while (i >= soFar) { Tcl_DStringSetLength(dstPtr, i--); } if (errorLocPtr) { /* * Do not write error message into interpreter if caller * wants to know error location. */ *errorLocPtr = result == TCL_OK ? TCL_INDEX_NONE : nBytesProcessed; } else { /* Caller wants error message on failure */ if (result != TCL_OK && interp != NULL) { Tcl_Size pos = Tcl_NumUtfChars(srcStart, nBytesProcessed); int ucs4; char buf[TCL_INTEGER_SPACE]; TclUtfToUniChar(&srcStart[nBytesProcessed], &ucs4); snprintf(buf, sizeof(buf), "%" TCL_SIZE_MODIFIER "d", nBytesProcessed); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unexpected character at index %" TCL_SIZE_MODIFIER "u: 'U+%06X'", pos, ucs4)); Tcl_SetErrorCode(interp, "TCL", "ENCODING", "ILLEGALSEQUENCE", buf, (char *)NULL); } } if (result != TCL_OK) { errno = (result == TCL_CONVERT_NOSPACE) ? ENOMEM : EILSEQ; } return result; } flags &= ~TCL_ENCODING_START; srcLen -= srcChunkRead; if (Tcl_DStringLength(dstPtr) == 0) { Tcl_DStringSetLength(dstPtr, dstLen); } Tcl_DStringSetLength(dstPtr, 2 * Tcl_DStringLength(dstPtr) + 1); dst = Tcl_DStringValue(dstPtr) + soFar; dstLen = Tcl_DStringLength(dstPtr) - soFar - 1; } } /* *------------------------------------------------------------------------- * * Tcl_UtfToExternal -- * * Convert a buffer from UTF-8 into the specified encoding. * * Results: * The return value is one of TCL_OK, TCL_CONVERT_MULTIBYTE, * TCL_CONVERT_SYNTAX, TCL_CONVERT_UNKNOWN, or TCL_CONVERT_NOSPACE, as * documented in tcl.h. * * Side effects: * The converted bytes are stored in the output buffer. * *------------------------------------------------------------------------- */ int Tcl_UtfToExternal( TCL_UNUSED(Tcl_Interp *), /* TODO: Re-examine this. */ Tcl_Encoding encoding, /* The encoding for the converted string, or * NULL for the default system encoding. */ const char *src, /* Source string in UTF-8. */ Tcl_Size srcLen, /* Source string length in bytes, or * TCL_INDEX_NONE for strlen(). */ int flags, /* Conversion control flags. */ Tcl_EncodingState *statePtr,/* Place for conversion routine to store state * information used during a piecewise * conversion. Contents of statePtr are * initialized and/or reset by conversion * routine under control of flags argument. */ char *dst, /* Output buffer in which converted string * is stored. */ Tcl_Size dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { const Encoding *encodingPtr; int result, srcRead, dstWrote, dstChars; Tcl_EncodingState state; if (encoding == NULL) { encoding = systemEncoding; } encodingPtr = (Encoding *) encoding; if (src == NULL) { srcLen = 0; } else if (srcLen == TCL_INDEX_NONE) { srcLen = strlen(src); } if (statePtr == NULL) { flags |= TCL_ENCODING_START | TCL_ENCODING_END; statePtr = &state; } if (srcLen > INT_MAX) { srcLen = INT_MAX; flags &= ~TCL_ENCODING_END; } if (dstLen > INT_MAX) { dstLen = INT_MAX; } if (srcReadPtr == NULL) { srcReadPtr = &srcRead; } if (dstWrotePtr == NULL) { dstWrotePtr = &dstWrote; } if (dstCharsPtr == NULL) { dstCharsPtr = &dstChars; } if (dstLen < encodingPtr->nullSize) { return TCL_CONVERT_NOSPACE; } dstLen -= encodingPtr->nullSize; result = encodingPtr->fromUtfProc(encodingPtr->clientData, src, srcLen, flags, statePtr, dst, dstLen, srcReadPtr, dstWrotePtr, dstCharsPtr); /* * Buffer is terminated irrespective of result. Not sure this is * reasonable but keep for historical/compatibility reasons. */ memset(&dst[*dstWrotePtr], '\0', encodingPtr->nullSize); return result; } /* *--------------------------------------------------------------------------- * * Tcl_FindExecutable -- * * This function computes the absolute path name of the current * application, given its argv[0] value. * * Results: * None. * * Side effects: * The absolute pathname for the application is computed and stored to be * returned later by [info nameofexecutable]. * *--------------------------------------------------------------------------- */ #undef Tcl_FindExecutable const char * Tcl_FindExecutable( const char *argv0) /* The value of the application's argv[0] * (native). */ { const char *version = Tcl_InitSubsystems(); TclpSetInitialEncodings(); TclpFindExecutable(argv0); return version; } /* *--------------------------------------------------------------------------- * * OpenEncodingFileChannel -- * * Open the file believed to hold data for the encoding, "name". * * Results: * Returns the readable Tcl_Channel from opening the file, or NULL if the * file could not be successfully opened. If NULL was returned, an error * message is left in interp's result object, unless interp was NULL. * * Side effects: * Channel may be opened. Information about the filesystem may be cached * to speed later calls. * *--------------------------------------------------------------------------- */ static Tcl_Channel OpenEncodingFileChannel( Tcl_Interp *interp, /* Interp for error reporting, if not NULL. */ const char *name) /* The name of the encoding file on disk and * also the name for new encoding. */ { Tcl_Obj *fileNameObj = Tcl_ObjPrintf("%s.enc", name); Tcl_Obj *searchPath = Tcl_DuplicateObj(Tcl_GetEncodingSearchPath()); Tcl_Obj *map = TclGetProcessGlobalValue(&encodingFileMap); Tcl_Obj **dir, *path, *directory = NULL; Tcl_Channel chan = NULL; Tcl_Size i, numDirs; TclListObjGetElements(NULL, searchPath, &numDirs, &dir); Tcl_IncrRefCount(fileNameObj); TclDictGet(NULL, map, name, &directory); /* * Check that any cached directory is still on the encoding search path. */ if (NULL != directory) { int verified = 0; for (i=0; i 256) { numPages = 256; } memset(used, 0, sizeof(used)); #undef PAGESIZE #define PAGESIZE (256 * sizeof(unsigned short)) dataPtr = (TableEncodingData *)Tcl_Alloc(sizeof(TableEncodingData)); memset(dataPtr, 0, sizeof(TableEncodingData)); dataPtr->fallback = fallback; /* * Read the table that maps characters to Unicode. Performs a single * malloc to get the memory for the array and all the pages needed by the * array. */ size = 256 * sizeof(unsigned short *) + numPages * PAGESIZE; dataPtr->toUnicode = (unsigned short **)Tcl_Alloc(size); memset(dataPtr->toUnicode, 0, size); pageMemPtr = (unsigned short *) (dataPtr->toUnicode + 256); TclNewObj(objPtr); Tcl_IncrRefCount(objPtr); for (i = 0; i < numPages; i++) { int ch; const char *p; Tcl_Size expected = 3 + 16 * (16 * 4 + 1); if (Tcl_ReadChars(chan, objPtr, expected, 0) != expected) { return NULL; } p = TclGetString(objPtr); hi = (staticHex[UCHAR(p[0])] << 4) + staticHex[UCHAR(p[1])]; dataPtr->toUnicode[hi] = pageMemPtr; p += 2; for (lo = 0; lo < 256; lo++) { if ((lo & 0x0F) == 0) { p++; } ch = (staticHex[UCHAR(p[0])] << 12) + (staticHex[UCHAR(p[1])] << 8) + (staticHex[UCHAR(p[2])] << 4) + staticHex[UCHAR(p[3])]; if (ch != 0) { used[ch >> 8] = 1; } *pageMemPtr = (unsigned short) ch; pageMemPtr++; p += 4; } } TclDecrRefCount(objPtr); if (type == ENCODING_DOUBLEBYTE) { memset(dataPtr->prefixBytes, 1, sizeof(dataPtr->prefixBytes)); } else { for (hi = 1; hi < 256; hi++) { if (dataPtr->toUnicode[hi] != NULL) { dataPtr->prefixBytes[hi] = 1; } } } /* * Invert the toUnicode array to produce the fromUnicode array. Performs a * single malloc to get the memory for the array and all the pages needed * by the array. While reading in the toUnicode array remember what * pages are needed for the fromUnicode array. */ if (symbol) { used[0] = 1; } numPages = 0; for (hi = 0; hi < 256; hi++) { if (used[hi]) { numPages++; } } size = 256 * sizeof(unsigned short *) + numPages * PAGESIZE; dataPtr->fromUnicode = (unsigned short **)Tcl_Alloc(size); memset(dataPtr->fromUnicode, 0, size); pageMemPtr = (unsigned short *) (dataPtr->fromUnicode + 256); for (hi = 0; hi < 256; hi++) { if (dataPtr->toUnicode[hi] == NULL) { dataPtr->toUnicode[hi] = emptyPage; continue; } for (lo = 0; lo < 256; lo++) { int ch = dataPtr->toUnicode[hi][lo]; if (ch != 0) { page = dataPtr->fromUnicode[ch >> 8]; if (page == NULL) { page = pageMemPtr; pageMemPtr += 256; dataPtr->fromUnicode[ch >> 8] = page; } page[ch & 0xFF] = (unsigned short) ((hi << 8) + lo); } } } if (type == ENCODING_MULTIBYTE) { /* * If multibyte encodings don't have a backslash character, define * one. Otherwise, on Windows, native file names don't work because * the backslash in the file name maps to the unknown character * (question mark) when converting from UTF-8 to external encoding. */ if (dataPtr->fromUnicode[0] != NULL) { if (dataPtr->fromUnicode[0][(int)'\\'] == '\0') { dataPtr->fromUnicode[0][(int)'\\'] = '\\'; } } } if (symbol) { /* * Make a special symbol encoding that maps each symbol character from * its Unicode code point down into page 0, and also ensure that each * characters on page 0 maps to itself so that a symbol font can be * used to display a simple string like "abcd" and have alpha, beta, * chi, delta show up, rather than have "unknown" chars show up because * strictly speaking the symbol font doesn't have glyphs for those low * ASCII chars. */ page = dataPtr->fromUnicode[0]; if (page == NULL) { page = pageMemPtr; dataPtr->fromUnicode[0] = page; } for (lo = 0; lo < 256; lo++) { if (dataPtr->toUnicode[0][lo] != 0) { page[lo] = (unsigned short) lo; } } } for (hi = 0; hi < 256; hi++) { if (dataPtr->fromUnicode[hi] == NULL) { dataPtr->fromUnicode[hi] = emptyPage; } } /* * For trailing 'R'everse encoding, see [Patch 689341] */ Tcl_DStringInit(&lineString); /* * Skip leading empty lines. */ while ((len = Tcl_Gets(chan, &lineString)) == 0) { /* empty body */ } if (len < 0) { goto doneParse; } /* * Require that it starts with an 'R'. */ line = Tcl_DStringValue(&lineString); if (line[0] != 'R') { goto doneParse; } /* * Read lines until EOF. */ for (TclDStringClear(&lineString); (len = Tcl_Gets(chan, &lineString)) != -1; TclDStringClear(&lineString)) { const unsigned char *p; int to, from; /* * Skip short lines. */ if (len < 5) { continue; } /* * Parse the line as a sequence of hex digits. */ p = (const unsigned char *) Tcl_DStringValue(&lineString); to = (staticHex[p[0]] << 12) + (staticHex[p[1]] << 8) + (staticHex[p[2]] << 4) + staticHex[p[3]]; if (to == 0) { continue; } for (p += 5, len -= 5; len >= 0 && *p; p += 5, len -= 5) { from = (staticHex[p[0]] << 12) + (staticHex[p[1]] << 8) + (staticHex[p[2]] << 4) + staticHex[p[3]]; if (from == 0) { continue; } dataPtr->fromUnicode[from >> 8][from & 0xFF] = to; } } doneParse: Tcl_DStringFree(&lineString); /* * Package everything into an encoding structure. */ encType.encodingName = name; encType.toUtfProc = TableToUtfProc; encType.fromUtfProc = TableFromUtfProc; encType.freeProc = TableFreeProc; encType.nullSize = (type == ENCODING_DOUBLEBYTE) ? 2 : 1; encType.clientData = dataPtr; return Tcl_CreateEncoding(&encType); } /* *------------------------------------------------------------------------- * * LoadEscapeEncoding -- * * Helper function for LoadEncodingTable(). Loads a state machine that * converts between Unicode and some other encoding. * * File contains text data that describes the escape sequences that are * used to choose an encoding and the associated names for the * sub-encodings. * * Results: * The return value is the new encoding, or NULL if the encoding could * not be created (because the file contained invalid data). * * Side effects: * None. * *------------------------------------------------------------------------- */ static Tcl_Encoding LoadEscapeEncoding( const char *name, /* Name of the new encoding. */ Tcl_Channel chan) /* File containing new encoding. */ { int i; unsigned size; Tcl_DString escapeData; char init[16], final[16]; EscapeEncodingData *dataPtr; Tcl_EncodingType type; init[0] = '\0'; final[0] = '\0'; Tcl_DStringInit(&escapeData); while (1) { Tcl_Size argc; const char **argv; char *line; Tcl_DString lineString; Tcl_DStringInit(&lineString); if (Tcl_Gets(chan, &lineString) < 0) { break; } line = Tcl_DStringValue(&lineString); if (Tcl_SplitList(NULL, line, &argc, &argv) != TCL_OK) { Tcl_DStringFree(&lineString); continue; } if (argc >= 2) { if (strcmp(argv[0], "name") == 0) { /* do nothing */ } else if (strcmp(argv[0], "init") == 0) { strncpy(init, argv[1], sizeof(init)); init[sizeof(init) - 1] = '\0'; } else if (strcmp(argv[0], "final") == 0) { strncpy(final, argv[1], sizeof(final)); final[sizeof(final) - 1] = '\0'; } else { EscapeSubTable est; Encoding *e; strncpy(est.sequence, argv[1], sizeof(est.sequence)); est.sequence[sizeof(est.sequence) - 1] = '\0'; est.sequenceLen = strlen(est.sequence); strncpy(est.name, argv[0], sizeof(est.name)); est.name[sizeof(est.name) - 1] = '\0'; /* * To avoid infinite recursion in [encoding system iso2022-*] */ e = (Encoding *) Tcl_GetEncoding(NULL, est.name); if ((e != NULL) && (e->toUtfProc != TableToUtfProc) && (e->toUtfProc != Iso88591ToUtfProc)) { Tcl_FreeEncoding((Tcl_Encoding) e); e = NULL; } est.encodingPtr = e; Tcl_DStringAppend(&escapeData, (char *) &est, sizeof(est)); } } Tcl_Free(argv); Tcl_DStringFree(&lineString); } size = offsetof(EscapeEncodingData, subTables) + Tcl_DStringLength(&escapeData); dataPtr = (EscapeEncodingData *)Tcl_Alloc(size); dataPtr->initLen = strlen(init); memcpy(dataPtr->init, init, dataPtr->initLen + 1); dataPtr->finalLen = strlen(final); memcpy(dataPtr->final, final, dataPtr->finalLen + 1); dataPtr->numSubTables = Tcl_DStringLength(&escapeData) / sizeof(EscapeSubTable); memcpy(dataPtr->subTables, Tcl_DStringValue(&escapeData), Tcl_DStringLength(&escapeData)); Tcl_DStringFree(&escapeData); memset(dataPtr->prefixBytes, 0, sizeof(dataPtr->prefixBytes)); for (i = 0; i < dataPtr->numSubTables; i++) { dataPtr->prefixBytes[UCHAR(dataPtr->subTables[i].sequence[0])] = 1; } if (dataPtr->init[0] != '\0') { dataPtr->prefixBytes[UCHAR(dataPtr->init[0])] = 1; } if (dataPtr->final[0] != '\0') { dataPtr->prefixBytes[UCHAR(dataPtr->final[0])] = 1; } /* * Package everything into an encoding structure. */ type.encodingName = name; type.toUtfProc = EscapeToUtfProc; type.fromUtfProc = EscapeFromUtfProc; type.freeProc = EscapeFreeProc; type.nullSize = 1; type.clientData = dataPtr; return Tcl_CreateEncoding(&type); } /* *------------------------------------------------------------------------- * * BinaryProc -- * * The default conversion when no other conversion is specified. No * translation is done; source bytes are copied directly to destination * bytes. * * Results: * Returns TCL_OK if conversion was successful. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int BinaryProc( TCL_UNUSED(void *), const char *src, /* Source string (unknown encoding). */ int srcLen, /* Source string length in bytes. */ int flags, /* Conversion control flags. */ TCL_UNUSED(Tcl_EncodingState *), char *dst, /* Output buffer in which converted string is * stored. */ int dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { int result; result = TCL_OK; dstLen -= TCL_UTF_MAX - 1; if (dstLen < 0) { dstLen = 0; } if ((flags & TCL_ENCODING_CHAR_LIMIT) && srcLen > *dstCharsPtr) { srcLen = *dstCharsPtr; } if (srcLen > dstLen) { srcLen = dstLen; result = TCL_CONVERT_NOSPACE; } *srcReadPtr = srcLen; *dstWrotePtr = srcLen; *dstCharsPtr = srcLen; memcpy(dst, src, srcLen); return result; } /* *------------------------------------------------------------------------- * * UtfToUtfProc -- * * Converts from UTF-8 to UTF-8. Note that the UTF-8 to UTF-8 translation * is not a no-op, because it turns a stream of improperly formed * UTF-8 into a properly-formed stream. * * Results: * Returns TCL_OK if conversion was successful. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int UtfToUtfProc( void *clientData, /* additional flags */ const char *src, /* Source string in UTF-8. */ int srcLen, /* Source string length in bytes. */ int flags, /* TCL_ENCODING_* conversion control flags. */ Tcl_EncodingState *statePtr,/* Place for conversion routine to store state * information used during a piecewise * conversion. Contents of statePtr are * initialized and/or reset by conversion * routine under control of flags argument. */ char *dst, /* Output buffer in which converted string is * stored. */ int dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { const char *srcStart, *srcEnd, *srcClose; const char *dstStart, *dstEnd; int result, numChars, charLimit = INT_MAX; int ch; int profile; if (flags & TCL_ENCODING_START) { /* *statePtr will hold high surrogate in a split surrogate pair */ *statePtr = 0; } result = TCL_OK; srcStart = src; srcEnd = src + srcLen; srcClose = srcEnd; if ((flags & TCL_ENCODING_END) == 0) { srcClose -= 6; } if (flags & TCL_ENCODING_CHAR_LIMIT) { charLimit = *dstCharsPtr; } dstStart = dst; flags |= PTR2INT(clientData); /* * If output is UTF-8 or encoding for Tcl's internal encoding, * max space needed is TCL_UTF_MAX. Otherwise, need 6 bytes (CESU-8) */ dstEnd = dst + dstLen - ((flags & (ENCODING_INPUT|ENCODING_UTF)) ? TCL_UTF_MAX : 6); /* * Macro to output an isolated high surrogate when it is not followed * by a low surrogate. NOT to be called for strict profile since * that should raise an error. */ #define OUTPUT_ISOLATEDSURROGATE \ do { \ Tcl_UniChar high; \ if (PROFILE_REPLACE(profile)) { \ high = UNICODE_REPLACE_CHAR; \ } else { \ high = (Tcl_UniChar)(ptrdiff_t) *statePtr; \ } \ assert(!(flags & ENCODING_UTF)); /* Must be CESU-8 */ \ assert(HIGH_SURROGATE(high)); \ assert(!PROFILE_STRICT(profile)); \ dst += Tcl_UniCharToUtf(high, dst); \ *statePtr = 0; /* Reset state */ \ } while (0) /* * Macro to check for isolated surrogate and either break with * an error if profile is strict, or output an appropriate * character for replace and tcl8 profiles and continue. */ #define CHECK_ISOLATEDSURROGATE \ if (*statePtr) { \ if (PROFILE_STRICT(profile)) { \ result = TCL_CONVERT_SYNTAX; \ break; \ } \ OUTPUT_ISOLATEDSURROGATE; \ continue; /* Rerun loop so length checks etc. repeated */ \ } else \ (void) 0 profile = ENCODING_PROFILE_GET(flags); for (numChars = 0; src < srcEnd && numChars <= charLimit; numChars++) { if ((src > srcClose) && (!Tcl_UtfCharComplete(src, srcEnd - src))) { /* * If there is more string to follow, this will ensure that the * last UTF-8 character in the source buffer hasn't been cut off. */ result = TCL_CONVERT_MULTIBYTE; break; } if (dst > dstEnd) { result = TCL_CONVERT_NOSPACE; break; } if (UCHAR(*src) < 0x80 && !((UCHAR(*src) == 0) && (flags & ENCODING_INPUT))) { CHECK_ISOLATEDSURROGATE; /* * Copy 7bit characters, but skip null-bytes when we are in input * mode, so that they get converted to \xC0\x80. */ *dst++ = *src++; } else if ((UCHAR(*src) == 0xC0) && (src + 1 < srcEnd) && (UCHAR(src[1]) == 0x80) && (!(flags & ENCODING_INPUT) || !PROFILE_TCL8(profile))) { /* Special sequence \xC0\x80 */ CHECK_ISOLATEDSURROGATE; if (!PROFILE_TCL8(profile) && (flags & ENCODING_INPUT)) { if (PROFILE_REPLACE(profile)) { dst += Tcl_UniCharToUtf(UNICODE_REPLACE_CHAR, dst); src += 2; } else { /* PROFILE_STRICT */ result = TCL_CONVERT_SYNTAX; break; } } else { /* * Convert 0xC080 to real nulls when we are in output mode, * irrespective of the profile. */ *dst++ = 0; src += 2; } } else if (!Tcl_UtfCharComplete(src, srcEnd - src)) { /* * Incomplete byte sequence not because there are insufficient * bytes in source buffer (have already checked that above) but * because the UTF-8 sequence is truncated. */ CHECK_ISOLATEDSURROGATE; if (flags & ENCODING_INPUT) { /* Incomplete bytes for modified UTF-8 target */ if (PROFILE_STRICT(profile)) { result = (flags & TCL_ENCODING_CHAR_LIMIT) ? TCL_CONVERT_MULTIBYTE : TCL_CONVERT_SYNTAX; break; } } if (PROFILE_REPLACE(profile)) { ch = UNICODE_REPLACE_CHAR; ++src; } else { /* TCL_ENCODING_PROFILE_TCL8 */ char chbuf[2]; chbuf[0] = UCHAR(*src++); chbuf[1] = 0; TclUtfToUniChar(chbuf, &ch); } dst += Tcl_UniCharToUtf(ch, dst); } else { /* Have a complete character */ size_t len = TclUtfToUniChar(src, &ch); Tcl_UniChar savedSurrogate = (Tcl_UniChar) (ptrdiff_t)*statePtr; *statePtr = 0; /* Reset surrogate */ if (flags & ENCODING_INPUT) { if (((len < 2) && (ch != 0)) || ((ch > 0xFFFF) && !(flags & ENCODING_UTF))) { if (PROFILE_STRICT(profile)) { result = TCL_CONVERT_SYNTAX; break; } else if (PROFILE_REPLACE(profile)) { ch = UNICODE_REPLACE_CHAR; } } } const char *saveSrc = src; src += len; if (!(flags & ENCODING_UTF) && !(flags & ENCODING_INPUT) && (ch > 0x7FF)) { assert(savedSurrogate == 0); /* Since this flag combo will never set *statePtr */ if (ch > 0xFFFF) { /* CESU-8 6-byte sequence for chars > U+FFFF */ ch -= 0x10000; *dst++ = 0xED; *dst++ = (char) (((ch >> 16) & 0x0F) | 0xA0); *dst++ = (char) (((ch >> 10) & 0x3F) | 0x80); ch = (ch & 0x03FF) | 0xDC00; } *dst++ = (char)(((ch >> 12) | 0xE0) & 0xEF); *dst++ = (char)(((ch >> 6) | 0x80) & 0xBF); *dst++ = (char)((ch | 0x80) & 0xBF); continue; } else if (SURROGATE(ch)) { if ((flags & ENCODING_UTF)) { /* UTF-8, not CESU-8, so surrogates should not appear */ if (PROFILE_STRICT(profile)) { result = (flags & ENCODING_INPUT) ? TCL_CONVERT_SYNTAX : TCL_CONVERT_UNKNOWN; src = saveSrc; break; } else if (PROFILE_REPLACE(profile)) { ch = UNICODE_REPLACE_CHAR; } else { /* PROFILE_TCL8 - output as is */ } } else { /* CESU-8 */ if (LOW_SURROGATE(ch)) { if (savedSurrogate) { assert(HIGH_SURROGATE(savedSurrogate)); ch = 0x10000 + ((savedSurrogate - 0xd800) << 10) + (ch - 0xdc00); } else { /* Isolated low surrogate */ if (PROFILE_STRICT(profile)) { result = (flags & ENCODING_INPUT) ? TCL_CONVERT_SYNTAX : TCL_CONVERT_UNKNOWN; src = saveSrc; break; } else if (PROFILE_REPLACE(profile)) { ch = UNICODE_REPLACE_CHAR; } else { /* Tcl8 profile. Output low surrogate as is */ } } } else { assert(HIGH_SURROGATE(ch)); /* Save the high surrogate */ *statePtr = (Tcl_EncodingState) (ptrdiff_t) ch; if (savedSurrogate) { assert(HIGH_SURROGATE(savedSurrogate)); if (PROFILE_STRICT(profile)) { result = (flags & ENCODING_INPUT) ? TCL_CONVERT_SYNTAX : TCL_CONVERT_UNKNOWN; src = saveSrc; break; } else if (PROFILE_REPLACE(profile)) { ch = UNICODE_REPLACE_CHAR; } else { /* Output the isolated high surrogate */ ch = savedSurrogate; } } else { /* High surrogate saved in *statePtr. Do not output anything just yet. */ --numChars; /* Cancel the increment at end of loop */ continue; } } } } else { /* Normal character */ CHECK_ISOLATEDSURROGATE; } dst += Tcl_UniCharToUtf(ch, dst); } } /* Check if an high surrogate left over */ if (*statePtr) { assert(!(flags & ENCODING_UTF)); /* CESU-8, Not UTF-8 */ if (!(flags & TCL_ENCODING_END)) { /* More data coming */ } else { /* No more data coming */ if (PROFILE_STRICT(profile)) { result = (flags & ENCODING_INPUT) ? TCL_CONVERT_SYNTAX : TCL_CONVERT_UNKNOWN; } else { if (PROFILE_REPLACE(profile)) { ch = UNICODE_REPLACE_CHAR; } else { ch = (Tcl_UniChar) (ptrdiff_t) *statePtr; } if (dst < dstEnd) { dst += Tcl_UniCharToUtf(ch, dst); ++numChars; } else { /* No room in destination */ result = TCL_CONVERT_NOSPACE; } } } } *srcReadPtr = src - srcStart; *dstWrotePtr = dst - dstStart; *dstCharsPtr = numChars; return result; } /* *------------------------------------------------------------------------- * * Utf32ToUtfProc -- * * Convert from UTF-32 to UTF-8. * * Results: * Returns TCL_OK if conversion was successful. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int Utf32ToUtfProc( void *clientData, /* additional flags, e.g. TCL_ENCODING_LE */ const char *src, /* Source string in Unicode. */ int srcLen, /* Source string length in bytes. */ int flags, /* Conversion control flags. */ TCL_UNUSED(Tcl_EncodingState *), char *dst, /* Output buffer in which converted string is * stored. */ int dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { const char *srcStart, *srcEnd; const char *dstEnd, *dstStart; int result, numChars, charLimit = INT_MAX; int ch = 0, bytesLeft = srcLen % 4; flags |= PTR2INT(clientData); if (flags & TCL_ENCODING_CHAR_LIMIT) { charLimit = *dstCharsPtr; } result = TCL_OK; /* * Check alignment with utf-32 (4 == sizeof(UTF-32)) */ if (bytesLeft != 0) { /* We have a truncated code unit */ result = TCL_CONVERT_MULTIBYTE; srcLen -= bytesLeft; } srcStart = src; srcEnd = src + srcLen; dstStart = dst; dstEnd = dst + dstLen - TCL_UTF_MAX; for (numChars = 0; src < srcEnd && numChars <= charLimit; numChars++) { if (dst > dstEnd) { result = TCL_CONVERT_NOSPACE; break; } if (flags & TCL_ENCODING_LE) { ch = (unsigned int)(src[3] & 0xFF) << 24 | (src[2] & 0xFF) << 16 | (src[1] & 0xFF) << 8 | (src[0] & 0xFF); } else { ch = (unsigned int)(src[0] & 0xFF) << 24 | (src[1] & 0xFF) << 16 | (src[2] & 0xFF) << 8 | (src[3] & 0xFF); } if ((unsigned)ch > 0x10FFFF) { if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_SYNTAX; break; } ch = UNICODE_REPLACE_CHAR; } else if (SURROGATE(ch)) { if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_SYNTAX; break; } if (PROFILE_REPLACE(flags)) { ch = UNICODE_REPLACE_CHAR; } } /* * Special case for 1-byte utf chars for speed. Make sure we work with * unsigned short-size data. */ if ((unsigned)ch - 1 < 0x7F) { *dst++ = (ch & 0xFF); } else { dst += Tcl_UniCharToUtf(ch, dst); } src += 4; } if ((flags & TCL_ENCODING_END) && (result == TCL_CONVERT_MULTIBYTE)) { /* We have a code fragment left-over at the end */ if (dst > dstEnd) { result = TCL_CONVERT_NOSPACE; } else { /* destination is not full, so we really are at the end now */ if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_SYNTAX; } else { /* PROFILE_REPLACE or PROFILE_TCL8 */ result = TCL_OK; dst += Tcl_UniCharToUtf(UNICODE_REPLACE_CHAR, dst); numChars++; src += bytesLeft; /* Go past truncated code unit */ } } } *srcReadPtr = src - srcStart; *dstWrotePtr = dst - dstStart; *dstCharsPtr = numChars; return result; } /* *------------------------------------------------------------------------- * * UtfToUtf32Proc -- * * Convert from UTF-8 to UTF-32. * * Results: * Returns TCL_OK if conversion was successful. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int UtfToUtf32Proc( void *clientData, /* additional flags, e.g. TCL_ENCODING_LE */ const char *src, /* Source string in UTF-8. */ int srcLen, /* Source string length in bytes. */ int flags, /* Conversion control flags. */ TCL_UNUSED(Tcl_EncodingState *), char *dst, /* Output buffer in which converted string is * stored. */ int dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { const char *srcStart, *srcEnd, *srcClose, *dstStart, *dstEnd; int result, numChars; int ch, len; srcStart = src; srcEnd = src + srcLen; srcClose = srcEnd; if ((flags & TCL_ENCODING_END) == 0) { srcClose -= TCL_UTF_MAX; } dstStart = dst; dstEnd = dst + dstLen - sizeof(Tcl_UniChar); flags |= PTR2INT(clientData); result = TCL_OK; for (numChars = 0; src < srcEnd; numChars++) { if ((src > srcClose) && (!Tcl_UtfCharComplete(src, srcEnd - src))) { /* * If there is more string to follow, this will ensure that the * last UTF-8 character in the source buffer hasn't been cut off. */ result = TCL_CONVERT_MULTIBYTE; break; } if (dst > dstEnd) { result = TCL_CONVERT_NOSPACE; break; } len = TclUtfToUniChar(src, &ch); if (SURROGATE(ch)) { if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_UNKNOWN; break; } if (PROFILE_REPLACE(flags)) { ch = UNICODE_REPLACE_CHAR; } } src += len; if (flags & TCL_ENCODING_LE) { *dst++ = (ch & 0xFF); *dst++ = ((ch >> 8) & 0xFF); *dst++ = ((ch >> 16) & 0xFF); *dst++ = ((ch >> 24) & 0xFF); } else { *dst++ = ((ch >> 24) & 0xFF); *dst++ = ((ch >> 16) & 0xFF); *dst++ = ((ch >> 8) & 0xFF); *dst++ = (ch & 0xFF); } } *srcReadPtr = src - srcStart; *dstWrotePtr = dst - dstStart; *dstCharsPtr = numChars; return result; } /* *------------------------------------------------------------------------- * * Utf16ToUtfProc -- * * Convert from UTF-16 to UTF-8. * * Results: * Returns TCL_OK if conversion was successful. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int Utf16ToUtfProc( void *clientData, /* additional flags, e.g. TCL_ENCODING_LE */ const char *src, /* Source string in Unicode. */ int srcLen, /* Source string length in bytes. */ int flags, /* Conversion control flags. */ TCL_UNUSED(Tcl_EncodingState *), char *dst, /* Output buffer in which converted string is * stored. */ int dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { const char *srcStart, *srcEnd; const char *dstEnd, *dstStart; int result, numChars, charLimit = INT_MAX; unsigned short ch = 0; flags |= PTR2INT(clientData); if (flags & TCL_ENCODING_CHAR_LIMIT) { charLimit = *dstCharsPtr; } result = TCL_OK; /* * Check alignment with utf-16 (2 == sizeof(UTF-16)) */ if ((srcLen % 2) != 0) { result = TCL_CONVERT_MULTIBYTE; srcLen--; } #if 0 /* * If last code point is a high surrogate, we cannot handle that yet, * unless we are at the end. */ if (!(flags & TCL_ENCODING_END) && (srcLen >= 2) && ((src[srcLen - ((flags & TCL_ENCODING_LE)?1:2)] & 0xFC) == 0xD8)) { result = TCL_CONVERT_MULTIBYTE; srcLen-= 2; } #endif srcStart = src; srcEnd = src + srcLen; dstStart = dst; dstEnd = dst + dstLen - TCL_UTF_MAX; for (numChars = 0; src < srcEnd && numChars <= charLimit; src += 2, numChars++) { if (dst > dstEnd && !HIGH_SURROGATE(ch)) { result = TCL_CONVERT_NOSPACE; break; } unsigned short prev = ch; if (flags & TCL_ENCODING_LE) { ch = (src[1] & 0xFF) << 8 | (src[0] & 0xFF); } else { ch = (src[0] & 0xFF) << 8 | (src[1] & 0xFF); } if (HIGH_SURROGATE(prev)) { if (LOW_SURROGATE(ch)) { /* * High surrogate was followed by a low surrogate. * Tcl_UniCharToUtf would have stashed away the state in dst. * Call it again to combine that state with the low surrogate. * We also have to compensate the numChars as two UTF-16 units * have been combined into one character. */ dst += Tcl_UniCharToUtf(ch | TCL_COMBINE, dst); } else { /* High surrogate was not followed by a low surrogate */ if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_SYNTAX; src -= 2; /* Go back to beginning of high surrogate */ dst--; /* Also undo writing a single byte too much */ break; } if (PROFILE_REPLACE(flags)) { /* * Previous loop wrote a single byte to mark the high surrogate. * Replace it with the replacement character. */ ch = UNICODE_REPLACE_CHAR; dst--; numChars++; dst += Tcl_UniCharToUtf(ch, dst); } else { /* * Bug [10c2c17c32]. If Hi surrogate not followed by Lo * surrogate, finish 3-byte UTF-8 */ dst += Tcl_UniCharToUtf(-1, dst); } /* Loop around again so destination space and other checks are done */ prev = 0; /* Reset high surrogate tracker */ src -= 2; } } else { /* Previous char was not a high surrogate */ /* * Special case for 1-byte utf chars for speed. Make sure we work with * unsigned short-size data. Order checks based on expected frequency. */ if ((unsigned)ch - 1 < 0x7F) { /* ASCII except nul */ *dst++ = (ch & 0xFF); } else if (!SURROGATE(ch)) { /* Not ASCII, not surrogate */ dst += Tcl_UniCharToUtf(ch, dst); } else if (HIGH_SURROGATE(ch)) { dst += Tcl_UniCharToUtf(ch | TCL_COMBINE, dst); /* Do not count this just yet. Compensate for numChars++ in loop counter */ numChars--; } else { assert(LOW_SURROGATE(ch)); if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_SYNTAX; break; } if (PROFILE_REPLACE(flags)) { ch = UNICODE_REPLACE_CHAR; } dst += Tcl_UniCharToUtf(ch, dst); } } } /* * When the above loop ends, result may have the following values: * 1. TCL_OK - full source buffer was completely processed. * src, dst, numChars will hold values up to that point BUT * there may be a leftover high surrogate we need to deal with. * 2. TCL_CONVERT_NOSPACE - Ran out of room in the destination buffer. * Same considerations as (1) * 3. TCL_CONVERT_SYNTAX - decoding error. * 4. TCL_CONVERT_MULTIBYTE - the buffer passed in was not fully * processed, because there was a trailing single byte. However, * we *may* have processed the requested number of characters already * in which case the trailing byte does not matter. We still * *may* still be a leftover high surrogate as in (1) and (2). */ switch (result) { case TCL_CONVERT_MULTIBYTE: /* FALLTHRU */ case TCL_OK: /* FALLTHRU */ case TCL_CONVERT_NOSPACE: if (HIGH_SURROGATE(ch)) { if (flags & TCL_ENCODING_END) { /* * No more data expected. There will be space for output of * one character (essentially overwriting the dst area holding * high surrogate state) */ assert((dst-1) <= dstEnd); if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_SYNTAX; src -= 2; dst--; } else if (PROFILE_REPLACE(flags)) { dst--; numChars++; dst += Tcl_UniCharToUtf(UNICODE_REPLACE_CHAR, dst); } else { /* Bug [10c2c17c32]. If Hi surrogate, finish 3-byte UTF-8 */ numChars++; dst += Tcl_UniCharToUtf(-1, dst); } } else { /* More data is expected. Revert the surrogate state */ src -= 2; dst--; /* Note: leave result of TCL_CONVERT_NOSPACE as is */ if (result == TCL_OK) { result = TCL_CONVERT_MULTIBYTE; } } } else if ((flags & TCL_ENCODING_END) && (result == TCL_CONVERT_MULTIBYTE)) { /* * If we had a trailing byte at the end AND this is the last * fragment AND profile is not "strict", stick FFFD in its place. * Note in this case we DO need to check for room in dst. */ if (dst > dstEnd) { result = TCL_CONVERT_NOSPACE; } else { if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_SYNTAX; } else { /* PROFILE_REPLACE or PROFILE_TCL8 */ result = TCL_OK; dst += Tcl_UniCharToUtf(UNICODE_REPLACE_CHAR, dst); numChars++; src++; } } } break; case TCL_CONVERT_SYNTAX: break; /* Nothing to do */ } *srcReadPtr = src - srcStart; *dstWrotePtr = dst - dstStart; *dstCharsPtr = numChars; return result; } /* *------------------------------------------------------------------------- * * UtfToUtf16Proc -- * * Convert from UTF-8 to UTF-16. * * Results: * Returns TCL_OK if conversion was successful. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int UtfToUtf16Proc( void *clientData, /* additional flags, e.g. TCL_ENCODING_LE */ const char *src, /* Source string in UTF-8. */ int srcLen, /* Source string length in bytes. */ int flags, /* Conversion control flags. */ TCL_UNUSED(Tcl_EncodingState *), char *dst, /* Output buffer in which converted string is * stored. */ int dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { const char *srcStart, *srcEnd, *srcClose, *dstStart, *dstEnd; int result, numChars; int ch, len; srcStart = src; srcEnd = src + srcLen; srcClose = srcEnd; if ((flags & TCL_ENCODING_END) == 0) { srcClose -= TCL_UTF_MAX; } dstStart = dst; dstEnd = dst + dstLen - 2; /* 2 -> sizeof a UTF-16 code unit */ flags |= PTR2INT(clientData); result = TCL_OK; for (numChars = 0; src < srcEnd; numChars++) { if ((src > srcClose) && (!Tcl_UtfCharComplete(src, srcEnd - src))) { /* * If there is more string to follow, this will ensure that the * last UTF-8 character in the source buffer hasn't been cut off. */ result = TCL_CONVERT_MULTIBYTE; break; } if (dst > dstEnd) { result = TCL_CONVERT_NOSPACE; break; } len = TclUtfToUniChar(src, &ch); if (SURROGATE(ch)) { if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_UNKNOWN; break; } if (PROFILE_REPLACE(flags)) { ch = UNICODE_REPLACE_CHAR; } } if (ch <= 0xFFFF) { if (flags & TCL_ENCODING_LE) { *dst++ = (ch & 0xFF); *dst++ = (ch >> 8); } else { *dst++ = (ch >> 8); *dst++ = (ch & 0xFF); } } else { if ((dst+2) > dstEnd) { /* Surrogates need 2 more bytes! Bug [66da4d4228] */ result = TCL_CONVERT_NOSPACE; break; } if (flags & TCL_ENCODING_LE) { *dst++ = (((ch - 0x10000) >> 10) & 0xFF); *dst++ = (((ch - 0x10000) >> 18) & 0x3) | 0xD8; *dst++ = (ch & 0xFF); *dst++ = ((ch >> 8) & 0x3) | 0xDC; } else { *dst++ = (((ch - 0x10000) >> 18) & 0x3) | 0xD8; *dst++ = (((ch - 0x10000) >> 10) & 0xFF); *dst++ = ((ch >> 8) & 0x3) | 0xDC; *dst++ = (ch & 0xFF); } } src += len; } *srcReadPtr = src - srcStart; *dstWrotePtr = dst - dstStart; *dstCharsPtr = numChars; return result; } /* *------------------------------------------------------------------------- * * UtfToUcs2Proc -- * * Convert from UTF-8 to UCS-2. * * Results: * Returns TCL_OK if conversion was successful. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int UtfToUcs2Proc( void *clientData, /* additional flags, e.g. TCL_ENCODING_LE */ const char *src, /* Source string in UTF-8. */ int srcLen, /* Source string length in bytes. */ int flags, /* Conversion control flags. */ TCL_UNUSED(Tcl_EncodingState *), char *dst, /* Output buffer in which converted string is * stored. */ int dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { const char *srcStart, *srcEnd, *srcClose, *dstStart, *dstEnd; int result, numChars, len; Tcl_UniChar ch = 0; flags |= PTR2INT(clientData); srcStart = src; srcEnd = src + srcLen; srcClose = srcEnd; if ((flags & TCL_ENCODING_END) == 0) { srcClose -= TCL_UTF_MAX; } dstStart = dst; dstEnd = dst + dstLen - 2; /* 2 - size of UCS code unit */ result = TCL_OK; for (numChars = 0; src < srcEnd; numChars++) { if ((src > srcClose) && (!Tcl_UtfCharComplete(src, srcEnd - src))) { /* * If there is more string to follow, this will ensure that the * last UTF-8 character in the source buffer hasn't been cut off. */ result = TCL_CONVERT_MULTIBYTE; break; } if (dst > dstEnd) { result = TCL_CONVERT_NOSPACE; break; } len = TclUtfToUniChar(src, &ch); if (ch > 0xFFFF) { if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_UNKNOWN; break; } ch = UNICODE_REPLACE_CHAR; } if (PROFILE_STRICT(flags) && SURROGATE(ch)) { result = TCL_CONVERT_SYNTAX; break; } src += len; /* * Need to handle this in a way that won't cause misalignment by * casting dst to a Tcl_UniChar. [Bug 1122671] */ if (flags & TCL_ENCODING_LE) { *dst++ = (ch & 0xFF); *dst++ = (ch >> 8); } else { *dst++ = (ch >> 8); *dst++ = (ch & 0xFF); } } *srcReadPtr = src - srcStart; *dstWrotePtr = dst - dstStart; *dstCharsPtr = numChars; return result; } /* *------------------------------------------------------------------------- * * TableToUtfProc -- * * Convert from the encoding specified by the TableEncodingData into * UTF-8. * * Results: * Returns TCL_OK if conversion was successful. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int TableToUtfProc( void *clientData, /* TableEncodingData that specifies * encoding. */ const char *src, /* Source string in specified encoding. */ int srcLen, /* Source string length in bytes. */ int flags, /* Conversion control flags. */ TCL_UNUSED(Tcl_EncodingState *), char *dst, /* Output buffer in which converted string is * stored. */ int dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { const char *srcStart, *srcEnd; const char *dstEnd, *dstStart, *prefixBytes; int result, byte, numChars, charLimit = INT_MAX; Tcl_UniChar ch = 0; const unsigned short *const *toUnicode; const unsigned short *pageZero; TableEncodingData *dataPtr = (TableEncodingData *)clientData; if (flags & TCL_ENCODING_CHAR_LIMIT) { charLimit = *dstCharsPtr; } srcStart = src; srcEnd = src + srcLen; dstStart = dst; dstEnd = dst + dstLen - TCL_UTF_MAX; toUnicode = (const unsigned short *const *) dataPtr->toUnicode; prefixBytes = dataPtr->prefixBytes; pageZero = toUnicode[0]; result = TCL_OK; for (numChars = 0; src < srcEnd && numChars <= charLimit; numChars++) { if (dst > dstEnd) { result = TCL_CONVERT_NOSPACE; break; } byte = *((unsigned char *) src); if (prefixBytes[byte]) { if (src >= srcEnd-1) { /* Prefix byte but nothing after it */ if (!(flags & TCL_ENCODING_END)) { /* More data to come */ result = TCL_CONVERT_MULTIBYTE; break; } else if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_SYNTAX; break; } else if (PROFILE_REPLACE(flags)) { ch = UNICODE_REPLACE_CHAR; } else { /* For prefix bytes, we don't fallback to cp1252, see [1355b9a874] */ ch = byte; } } else { ch = toUnicode[byte][*((unsigned char *)++src)]; } } else { ch = pageZero[byte]; } if ((ch == 0) && (byte != 0)) { /* Prefix+suffix pair is invalid */ if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_SYNTAX; break; } if (prefixBytes[byte]) { src--; } if (PROFILE_REPLACE(flags)) { ch = UNICODE_REPLACE_CHAR; } else { char chbuf[2]; chbuf[0] = byte; chbuf[1] = 0; TclUtfToUniChar(chbuf, &ch); } } /* * Special case for 1-byte Utf chars for speed. */ if ((unsigned)ch - 1 < 0x7F) { *dst++ = (char) ch; } else { dst += Tcl_UniCharToUtf(ch, dst); } src++; } assert(src <= srcEnd); *srcReadPtr = src - srcStart; *dstWrotePtr = dst - dstStart; *dstCharsPtr = numChars; return result; } /* *------------------------------------------------------------------------- * * TableFromUtfProc -- * * Convert from UTF-8 into the encoding specified by the * TableEncodingData. * * Results: * Returns TCL_OK if conversion was successful. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int TableFromUtfProc( void *clientData, /* TableEncodingData that specifies * encoding. */ const char *src, /* Source string in UTF-8. */ int srcLen, /* Source string length in bytes. */ int flags, /* Conversion control flags. */ TCL_UNUSED(Tcl_EncodingState *), char *dst, /* Output buffer in which converted string is * stored. */ int dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { const char *srcStart, *srcEnd, *srcClose; const char *dstStart, *dstEnd, *prefixBytes; Tcl_UniChar ch = 0; int result, len, word, numChars; TableEncodingData *dataPtr = (TableEncodingData *)clientData; const unsigned short *const *fromUnicode; result = TCL_OK; prefixBytes = dataPtr->prefixBytes; fromUnicode = (const unsigned short *const *) dataPtr->fromUnicode; srcStart = src; srcEnd = src + srcLen; srcClose = srcEnd; if ((flags & TCL_ENCODING_END) == 0) { srcClose -= TCL_UTF_MAX; } dstStart = dst; dstEnd = dst + dstLen - 1; for (numChars = 0; src < srcEnd; numChars++) { if ((src > srcClose) && (!Tcl_UtfCharComplete(src, srcEnd - src))) { /* * If there is more string to follow, this will ensure that the * last UTF-8 character in the source buffer hasn't been cut off. */ result = TCL_CONVERT_MULTIBYTE; break; } len = TclUtfToUniChar(src, &ch); /* Unicode chars > +U0FFFF cannot be represented in any table encoding */ if (ch & 0xFFFF0000) { word = 0; } else { word = fromUnicode[(ch >> 8)][ch & 0xFF]; } if ((word == 0) && (ch != 0)) { if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_UNKNOWN; break; } word = dataPtr->fallback; /* Both profiles REPLACE and TCL8 */ } if (prefixBytes[(word >> 8)] != 0) { if (dst + 1 > dstEnd) { result = TCL_CONVERT_NOSPACE; break; } dst[0] = (char) (word >> 8); dst[1] = (char) word; dst += 2; } else { if (dst > dstEnd) { result = TCL_CONVERT_NOSPACE; break; } dst[0] = (char) word; dst++; } src += len; } *srcReadPtr = src - srcStart; *dstWrotePtr = dst - dstStart; *dstCharsPtr = numChars; return result; } /* *------------------------------------------------------------------------- * * Iso88591ToUtfProc -- * * Convert from the "iso8859-1" encoding into UTF-8. * * Results: * Returns TCL_OK if conversion was successful. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int Iso88591ToUtfProc( TCL_UNUSED(void *), const char *src, /* Source string in specified encoding. */ int srcLen, /* Source string length in bytes. */ int flags, /* Conversion control flags. */ TCL_UNUSED(Tcl_EncodingState *), char *dst, /* Output buffer in which converted string is * stored. */ int dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { const char *srcStart, *srcEnd; const char *dstEnd, *dstStart; int result, numChars, charLimit = INT_MAX; if (flags & TCL_ENCODING_CHAR_LIMIT) { charLimit = *dstCharsPtr; } srcStart = src; srcEnd = src + srcLen; dstStart = dst; dstEnd = dst + dstLen - TCL_UTF_MAX; result = TCL_OK; for (numChars = 0; src < srcEnd && numChars <= charLimit; numChars++) { Tcl_UniChar ch = 0; if (dst > dstEnd) { result = TCL_CONVERT_NOSPACE; break; } ch = *((unsigned char *) src); /* * Special case for 1-byte utf chars for speed. */ if ((unsigned)ch - 1 < 0x7F) { *dst++ = (char) ch; } else { dst += Tcl_UniCharToUtf(ch, dst); } src++; } *srcReadPtr = src - srcStart; *dstWrotePtr = dst - dstStart; *dstCharsPtr = numChars; return result; } /* *------------------------------------------------------------------------- * * Iso88591FromUtfProc -- * * Convert from UTF-8 into the encoding "iso8859-1". * * Results: * Returns TCL_OK if conversion was successful. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int Iso88591FromUtfProc( TCL_UNUSED(void *), const char *src, /* Source string in UTF-8. */ int srcLen, /* Source string length in bytes. */ int flags, /* Conversion control flags. */ TCL_UNUSED(Tcl_EncodingState *), char *dst, /* Output buffer in which converted string is * stored. */ int dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { const char *srcStart, *srcEnd, *srcClose; const char *dstStart, *dstEnd; int result = TCL_OK, numChars; Tcl_UniChar ch = 0; srcStart = src; srcEnd = src + srcLen; srcClose = srcEnd; if ((flags & TCL_ENCODING_END) == 0) { srcClose -= TCL_UTF_MAX; } dstStart = dst; dstEnd = dst + dstLen - 1; for (numChars = 0; src < srcEnd; numChars++) { int len; if ((src > srcClose) && (!Tcl_UtfCharComplete(src, srcEnd - src))) { /* * If there is more string to follow, this will ensure that the * last UTF-8 character in the source buffer hasn't been cut off. */ result = TCL_CONVERT_MULTIBYTE; break; } len = TclUtfToUniChar(src, &ch); /* * Check for illegal characters. */ if (ch > 0xFF) { if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_UNKNOWN; break; } /* * Plunge on, using '?' as a fallback character. */ ch = '?'; /* Profiles TCL8 and REPLACE */ } if (dst > dstEnd) { result = TCL_CONVERT_NOSPACE; break; } *(dst++) = (char) ch; src += len; } *srcReadPtr = src - srcStart; *dstWrotePtr = dst - dstStart; *dstCharsPtr = numChars; return result; } /* *--------------------------------------------------------------------------- * * TableFreeProc -- * * This function is invoked when an encoding is deleted. It deletes the * memory used by the TableEncodingData. * * Results: * None. * * Side effects: * Memory freed. * *--------------------------------------------------------------------------- */ static void TableFreeProc( void *clientData) /* TableEncodingData that specifies * encoding. */ { TableEncodingData *dataPtr = (TableEncodingData *)clientData; /* * Make sure we aren't freeing twice on shutdown. [Bug 219314] */ Tcl_Free(dataPtr->toUnicode); dataPtr->toUnicode = NULL; Tcl_Free(dataPtr->fromUnicode); dataPtr->fromUnicode = NULL; Tcl_Free(dataPtr); } /* *------------------------------------------------------------------------- * * EscapeToUtfProc -- * * Convert from the encoding specified by the EscapeEncodingData into * UTF-8. * * Results: * Returns TCL_OK if conversion was successful. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int EscapeToUtfProc( void *clientData, /* EscapeEncodingData that specifies * encoding. */ const char *src, /* Source string in specified encoding. */ int srcLen, /* Source string length in bytes. */ int flags, /* Conversion control flags. */ Tcl_EncodingState *statePtr,/* Place for conversion routine to store state * information used during a piecewise * conversion. Contents of statePtr are * initialized and/or reset by conversion * routine under control of flags argument. */ char *dst, /* Output buffer in which converted string is * stored. */ int dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { EscapeEncodingData *dataPtr = (EscapeEncodingData *)clientData; const char *prefixBytes, *tablePrefixBytes, *srcStart, *srcEnd; const unsigned short *const *tableToUnicode; const Encoding *encodingPtr; int state, result, numChars, charLimit = INT_MAX; const char *dstStart, *dstEnd; if (flags & TCL_ENCODING_CHAR_LIMIT) { charLimit = *dstCharsPtr; } result = TCL_OK; tablePrefixBytes = NULL; tableToUnicode = NULL; prefixBytes = dataPtr->prefixBytes; encodingPtr = NULL; srcStart = src; srcEnd = src + srcLen; dstStart = dst; dstEnd = dst + dstLen - TCL_UTF_MAX; state = PTR2INT(*statePtr); if (flags & TCL_ENCODING_START) { state = 0; } for (numChars = 0; src < srcEnd && numChars <= charLimit; ) { int byte, hi, lo, ch; if (dst > dstEnd) { result = TCL_CONVERT_NOSPACE; break; } byte = *((unsigned char *) src); if (prefixBytes[byte]) { unsigned left, len, longest; int checked, i; const EscapeSubTable *subTablePtr; /* * Saw the beginning of an escape sequence. */ left = srcEnd - src; len = dataPtr->initLen; longest = len; checked = 0; if (len <= left) { checked++; if ((len > 0) && (memcmp(src, dataPtr->init, len) == 0)) { /* * If we see initialization string, skip it, even if we're * not at the beginning of the buffer. */ src += len; continue; } } len = dataPtr->finalLen; if (len > longest) { longest = len; } if (len <= left) { checked++; if ((len > 0) && (memcmp(src, dataPtr->final, len) == 0)) { /* * If we see finalization string, skip it, even if we're * not at the end of the buffer. */ src += len; continue; } } subTablePtr = dataPtr->subTables; for (i = 0; i < dataPtr->numSubTables; i++) { len = subTablePtr->sequenceLen; if (len > longest) { longest = len; } if (len <= left) { checked++; if ((len > 0) && (memcmp(src, subTablePtr->sequence, len) == 0)) { state = i; encodingPtr = NULL; subTablePtr = NULL; src += len; break; } } subTablePtr++; } if (subTablePtr == NULL) { /* * A match was found, the escape sequence was consumed, and * the state was updated. */ continue; } /* * We have a split-up or unrecognized escape sequence. If we * checked all the sequences, then it's a syntax error, otherwise * we need more bytes to determine a match. */ if ((checked == dataPtr->numSubTables + 2) || (flags & TCL_ENCODING_END)) { if (!PROFILE_STRICT(flags)) { unsigned skip = longest > left ? left : longest; /* Unknown escape sequence */ dst += Tcl_UniCharToUtf(UNICODE_REPLACE_CHAR, dst); src += skip; continue; } result = TCL_CONVERT_SYNTAX; } else { result = TCL_CONVERT_MULTIBYTE; } break; } if (encodingPtr == NULL) { TableEncodingData *tableDataPtr; encodingPtr = GetTableEncoding(dataPtr, state); tableDataPtr = (TableEncodingData *)encodingPtr->clientData; tablePrefixBytes = tableDataPtr->prefixBytes; tableToUnicode = (const unsigned short *const*) tableDataPtr->toUnicode; } if (tablePrefixBytes[byte]) { src++; if (src >= srcEnd) { src--; result = TCL_CONVERT_MULTIBYTE; break; } hi = byte; lo = *((unsigned char *) src); } else { hi = 0; lo = byte; } ch = tableToUnicode[hi][lo]; dst += Tcl_UniCharToUtf(ch, dst); src++; numChars++; } *statePtr = (Tcl_EncodingState) INT2PTR(state); *srcReadPtr = src - srcStart; *dstWrotePtr = dst - dstStart; *dstCharsPtr = numChars; return result; } /* *------------------------------------------------------------------------- * * EscapeFromUtfProc -- * * Convert from UTF-8 into the encoding specified by the * EscapeEncodingData. * * Results: * Returns TCL_OK if conversion was successful. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int EscapeFromUtfProc( void *clientData, /* EscapeEncodingData that specifies * encoding. */ const char *src, /* Source string in UTF-8. */ int srcLen, /* Source string length in bytes. */ int flags, /* Conversion control flags. */ Tcl_EncodingState *statePtr,/* Place for conversion routine to store state * information used during a piecewise * conversion. Contents of statePtr are * initialized and/or reset by conversion * routine under control of flags argument. */ char *dst, /* Output buffer in which converted string is * stored. */ int dstLen, /* The maximum length of output buffer in * bytes. */ int *srcReadPtr, /* Filled with the number of bytes from the * source string that were converted. This may * be less than the original source length if * there was a problem converting some source * characters. */ int *dstWrotePtr, /* Filled with the number of bytes that were * stored in the output buffer as a result of * the conversion. */ int *dstCharsPtr) /* Filled with the number of characters that * correspond to the bytes stored in the * output buffer. */ { EscapeEncodingData *dataPtr = (EscapeEncodingData *)clientData; const Encoding *encodingPtr; const char *srcStart, *srcEnd, *srcClose; const char *dstStart, *dstEnd; int state, result, numChars; const TableEncodingData *tableDataPtr; const char *tablePrefixBytes; const unsigned short *const *tableFromUnicode; Tcl_UniChar ch = 0; result = TCL_OK; srcStart = src; srcEnd = src + srcLen; srcClose = srcEnd; if ((flags & TCL_ENCODING_END) == 0) { srcClose -= TCL_UTF_MAX; } dstStart = dst; dstEnd = dst + dstLen - 1; /* * RFC 1468 states that the text starts in ASCII, and switches to Japanese * characters, and that the text must end in ASCII. [Patch 474358] */ if (flags & TCL_ENCODING_START) { state = 0; if ((dst + dataPtr->initLen) > dstEnd) { *srcReadPtr = 0; *dstWrotePtr = 0; return TCL_CONVERT_NOSPACE; } memcpy(dst, dataPtr->init, dataPtr->initLen); dst += dataPtr->initLen; } else { state = PTR2INT(*statePtr); } encodingPtr = GetTableEncoding(dataPtr, state); tableDataPtr = (const TableEncodingData *)encodingPtr->clientData; tablePrefixBytes = tableDataPtr->prefixBytes; tableFromUnicode = (const unsigned short *const *) tableDataPtr->fromUnicode; for (numChars = 0; src < srcEnd; numChars++) { unsigned len; int word; if ((src > srcClose) && (!Tcl_UtfCharComplete(src, srcEnd - src))) { /* * If there is more string to follow, this will ensure that the * last UTF-8 character in the source buffer hasn't been cut off. */ result = TCL_CONVERT_MULTIBYTE; break; } len = TclUtfToUniChar(src, &ch); if (ch > 0xFFFF) { /* Bug 201c7a3aa6 crash - tables are 256x256 (64K) */ if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_SYNTAX; break; } /* Will be encoded as encoding specific replacement below */ ch = UNICODE_REPLACE_CHAR; } word = tableFromUnicode[(ch >> 8)][ch & 0xFF]; if ((word == 0) && (ch != 0)) { int oldState; const EscapeSubTable *subTablePtr; oldState = state; for (state = 0; state < dataPtr->numSubTables; state++) { encodingPtr = GetTableEncoding(dataPtr, state); tableDataPtr = (const TableEncodingData *)encodingPtr->clientData; word = tableDataPtr->fromUnicode[(ch >> 8)][ch & 0xFF]; if (word != 0) { break; } } if (word == 0) { state = oldState; if (PROFILE_STRICT(flags)) { result = TCL_CONVERT_UNKNOWN; break; } encodingPtr = GetTableEncoding(dataPtr, state); tableDataPtr = (const TableEncodingData *)encodingPtr->clientData; word = tableDataPtr->fallback; } tablePrefixBytes = (const char *) tableDataPtr->prefixBytes; tableFromUnicode = (const unsigned short *const *) tableDataPtr->fromUnicode; /* * The state variable has the value of oldState when word is 0. * In this case, the escape sequence should not be copied to dst * because the current character set is not changed. */ if (state != oldState) { subTablePtr = &dataPtr->subTables[state]; if ((dst + subTablePtr->sequenceLen) > dstEnd) { /* * If there is no space to write the escape sequence, the * state variable must be changed to the value of oldState * variable because this escape sequence must be written * in the next conversion. */ state = oldState; result = TCL_CONVERT_NOSPACE; break; } memcpy(dst, subTablePtr->sequence, subTablePtr->sequenceLen); dst += subTablePtr->sequenceLen; } } if (tablePrefixBytes[(word >> 8)] != 0) { if (dst + 1 > dstEnd) { result = TCL_CONVERT_NOSPACE; break; } dst[0] = (char) (word >> 8); dst[1] = (char) word; dst += 2; } else { if (dst > dstEnd) { result = TCL_CONVERT_NOSPACE; break; } dst[0] = (char) word; dst++; } src += len; } if ((result == TCL_OK) && (flags & TCL_ENCODING_END)) { unsigned len = dataPtr->subTables[0].sequenceLen; /* * Certain encodings like iso2022-jp need to write an escape sequence * after all characters have been converted. This logic checks that * enough room is available in the buffer for the escape bytes. The * TCL_ENCODING_END flag is cleared after a final escape sequence has * been added to the buffer so that another call to this method does * not attempt to append escape bytes a second time. */ if ((dst + dataPtr->finalLen + (state?len:0)) > dstEnd) { result = TCL_CONVERT_NOSPACE; } else { if (state) { memcpy(dst, dataPtr->subTables[0].sequence, len); dst += len; } memcpy(dst, dataPtr->final, dataPtr->finalLen); dst += dataPtr->finalLen; state &= ~TCL_ENCODING_END; } } *statePtr = (Tcl_EncodingState) INT2PTR(state); *srcReadPtr = src - srcStart; *dstWrotePtr = dst - dstStart; *dstCharsPtr = numChars; return result; } /* *--------------------------------------------------------------------------- * * EscapeFreeProc -- * * Frees resources used by the encoding. * * Results: * None. * * Side effects: * Memory is freed. * *--------------------------------------------------------------------------- */ static void EscapeFreeProc( void *clientData) /* EscapeEncodingData that specifies * encoding. */ { EscapeEncodingData *dataPtr = (EscapeEncodingData *)clientData; EscapeSubTable *subTablePtr; int i; if (dataPtr == NULL) { return; } /* * The subTables should be freed recursively in normal operation but not * during TclFinalizeEncodingSubsystem because they are also present as a * weak reference in the toplevel encodingTable (i.e., they don't have a * +1 refcount for this), and unpredictable nuking order could remove them * from under the following loop's feet. [Bug 2891556] * * The encodingsInitialized flag, being reset on entry to TFES, can serve * as a "not in finalization" test. */ if (encodingsInitialized) { subTablePtr = dataPtr->subTables; for (i = 0; i < dataPtr->numSubTables; i++) { FreeEncoding((Tcl_Encoding) subTablePtr->encodingPtr); subTablePtr->encodingPtr = NULL; subTablePtr++; } } Tcl_Free(dataPtr); } /* *--------------------------------------------------------------------------- * * GetTableEncoding -- * * Helper function for the EscapeEncodingData conversions. Gets the * encoding (of type TextEncodingData) that represents the specified * state. * * Results: * The return value is the encoding. * * Side effects: * If the encoding that represents the specified state has not already * been used by this EscapeEncoding, it will be loaded and cached in the * dataPtr. * *--------------------------------------------------------------------------- */ static Encoding * GetTableEncoding( EscapeEncodingData *dataPtr,/* Contains names of encodings. */ int state) /* Index in dataPtr of desired Encoding. */ { EscapeSubTable *subTablePtr = &dataPtr->subTables[state]; Encoding *encodingPtr = subTablePtr->encodingPtr; if (encodingPtr == NULL) { encodingPtr = (Encoding *) Tcl_GetEncoding(NULL, subTablePtr->name); if ((encodingPtr == NULL) || (encodingPtr->toUtfProc != TableToUtfProc && encodingPtr->toUtfProc != Iso88591ToUtfProc)) { Tcl_Panic("EscapeToUtfProc: invalid sub table"); } subTablePtr->encodingPtr = encodingPtr; } return encodingPtr; } /* *--------------------------------------------------------------------------- * * unilen, unilen4 -- * * A helper function for the Tcl_ExternalToUtf functions. This function * is similar to strlen for double-byte characters: it returns the number * of bytes in a 0x0000 terminated string. * * Results: * As above. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static size_t unilen( const char *src) { unsigned short *p; p = (unsigned short *) src; while (*p != 0x0000) { p++; } return (char *) p - src; } static size_t unilen4( const char *src) { unsigned int *p; p = (unsigned int *) src; while (*p != 0x00000000) { p++; } return (char *) p - src; } /* *------------------------------------------------------------------------- * * InitializeEncodingSearchPath -- * * This is the fallback routine that sets the default value of the * encoding search path if the application has not set one via a call to * Tcl_SetEncodingSearchPath() by the first time the search path is needed * to load encoding data. * * The default encoding search path is produced by taking each directory * in the library path, appending a subdirectory named "encoding", and if * the resulting directory exists, adding it to the encoding search path. * * Results: * None. * * Side effects: * Sets the encoding search path to an initial value. * *------------------------------------------------------------------------- */ static void InitializeEncodingSearchPath( char **valuePtr, size_t *lengthPtr, Tcl_Encoding *encodingPtr) { const char *bytes; Tcl_Size i, numDirs, numBytes; Tcl_Obj *libPathObj, *encodingObj, *searchPathObj; TclNewLiteralStringObj(encodingObj, "encoding"); TclNewObj(searchPathObj); Tcl_IncrRefCount(encodingObj); Tcl_IncrRefCount(searchPathObj); libPathObj = TclGetProcessGlobalValue(&libraryPath); Tcl_IncrRefCount(libPathObj); TclListObjLength(NULL, libPathObj, &numDirs); for (i = 0; i < numDirs; i++) { Tcl_Obj *directoryObj, *pathObj; Tcl_StatBuf stat; Tcl_ListObjIndex(NULL, libPathObj, i, &directoryObj); pathObj = Tcl_FSJoinToPath(directoryObj, 1, &encodingObj); Tcl_IncrRefCount(pathObj); if ((0 == Tcl_FSStat(pathObj, &stat)) && S_ISDIR(stat.st_mode)) { Tcl_ListObjAppendElement(NULL, searchPathObj, pathObj); } Tcl_DecrRefCount(pathObj); } Tcl_DecrRefCount(libPathObj); Tcl_DecrRefCount(encodingObj); *encodingPtr = libraryPath.encoding; if (*encodingPtr) { ((Encoding *)(*encodingPtr))->refCount++; } bytes = TclGetStringFromObj(searchPathObj, &numBytes); *lengthPtr = numBytes; *valuePtr = (char *)Tcl_Alloc(numBytes + 1); memcpy(*valuePtr, bytes, numBytes + 1); Tcl_DecrRefCount(searchPathObj); } /* *------------------------------------------------------------------------ * * TclEncodingProfileParseName -- * * Maps an encoding profile name to its integer equivalent. * * Results: * TCL_OK on success or TCL_ERROR on failure. * * Side effects: * Returns the profile enum value in *profilePtr * *------------------------------------------------------------------------ */ int TclEncodingProfileNameToId( Tcl_Interp *interp, /* For error messages. May be NULL */ const char *profileName, /* Name of profile */ int *profilePtr) /* Output */ { size_t i; size_t numProfiles = sizeof(encodingProfiles) / sizeof(encodingProfiles[0]); for (i = 0; i < numProfiles; ++i) { if (!strcmp(profileName, encodingProfiles[i].name)) { *profilePtr = encodingProfiles[i].value; return TCL_OK; } } if (interp) { /* This code assumes at least two profiles :-) */ Tcl_Obj *errorObj = Tcl_ObjPrintf("bad profile name \"%s\": must be", profileName); for (i = 0; i < (numProfiles - 1); ++i) { Tcl_AppendStringsToObj( errorObj, " ", encodingProfiles[i].name, ",", (char *)NULL); } Tcl_AppendStringsToObj( errorObj, " or ", encodingProfiles[numProfiles-1].name, (char *)NULL); Tcl_SetObjResult(interp, errorObj); Tcl_SetErrorCode( interp, "TCL", "ENCODING", "PROFILE", profileName, (char *)NULL); } return TCL_ERROR; } /* *------------------------------------------------------------------------ * * TclEncodingProfileValueToName -- * * Maps an encoding profile value to its name. * * Results: * Pointer to the name or NULL on failure. Caller must not make * not modify the string and must make a copy to hold on to it. * * Side effects: * None. *------------------------------------------------------------------------ */ const char * TclEncodingProfileIdToName( Tcl_Interp *interp, /* For error messages. May be NULL */ int profileValue) /* Profile #define value */ { size_t i; for (i = 0; i < sizeof(encodingProfiles) / sizeof(encodingProfiles[0]); ++i) { if (profileValue == encodingProfiles[i].value) { return encodingProfiles[i].name; } } if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Internal error. Bad profile id \"%d\".", profileValue)); Tcl_SetErrorCode( interp, "TCL", "ENCODING", "PROFILEID", (char *)NULL); } return NULL; } /* *------------------------------------------------------------------------ * * TclGetEncodingProfiles -- * * Get the list of supported encoding profiles. * * Results: * None. * * Side effects: * The list of profile names is stored in the interpreter result. * *------------------------------------------------------------------------ */ void TclGetEncodingProfiles( Tcl_Interp *interp) { size_t i, n; Tcl_Obj *objPtr; n = sizeof(encodingProfiles) / sizeof(encodingProfiles[0]); objPtr = Tcl_NewListObj(n, NULL); for (i = 0; i < n; ++i) { Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewStringObj(encodingProfiles[i].name, TCL_INDEX_NONE)); } Tcl_SetObjResult(interp, objPtr); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclEnsemble.c0000644000175000017500000032570114726623136015603 0ustar sergeisergei/* * tclEnsemble.c -- * * Contains support for ensembles (see TIP#112), which provide simple * mechanism for creating composite commands on top of namespaces. * * Copyright © 2005-2013 Donal K. Fellows. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" /* * Declarations for functions local to this file: */ static Tcl_Command InitEnsembleFromOptions(Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int ReadOneEnsembleOption(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *optionObj); static int ReadAllEnsembleOptions(Tcl_Interp *interp, Tcl_Command token); static int SetEnsembleConfigOptions(Tcl_Interp *interp, Tcl_Command token, int objc, Tcl_Obj *const objv[]); static inline int EnsembleUnknownCallback(Tcl_Interp *interp, EnsembleConfig *ensemblePtr, int objc, Tcl_Obj *const objv[], Tcl_Obj **prefixObjPtr); static int NsEnsembleImplementationCmdNR(void *clientData, Tcl_Interp *interp,int objc,Tcl_Obj *const objv[]); static void BuildEnsembleConfig(EnsembleConfig *ensemblePtr); static int NsEnsembleStringOrder(const void *strPtr1, const void *strPtr2); static void DeleteEnsembleConfig(void *clientData); static void MakeCachedEnsembleCommand(Tcl_Obj *objPtr, EnsembleConfig *ensemblePtr, Tcl_HashEntry *hPtr, Tcl_Obj *fix); static void FreeEnsembleCmdRep(Tcl_Obj *objPtr); static void DupEnsembleCmdRep(Tcl_Obj *objPtr, Tcl_Obj *copyPtr); static void CompileToInvokedCommand(Tcl_Interp *interp, Tcl_Parse *parsePtr, Tcl_Obj *replacements, Command *cmdPtr, CompileEnv *envPtr); static int CompileBasicNArgCommand(Tcl_Interp *interp, Tcl_Parse *parsePtr, Command *cmdPtr, CompileEnv *envPtr); static Tcl_NRPostProc FreeER; /* * The lists of subcommands and options for the [namespace ensemble] command. */ static const char *const ensembleSubcommands[] = { "configure", "create", "exists", NULL }; enum EnsSubcmds { ENS_CONFIG, ENS_CREATE, ENS_EXISTS }; static const char *const ensembleCreateOptions[] = { "-command", "-map", "-parameters", "-prefixes", "-subcommands", "-unknown", NULL }; enum EnsCreateOpts { CRT_CMD, CRT_MAP, CRT_PARAM, CRT_PREFIX, CRT_SUBCMDS, CRT_UNKNOWN }; static const char *const ensembleConfigOptions[] = { "-map", "-namespace", "-parameters", "-prefixes", "-subcommands", "-unknown", NULL }; enum EnsConfigOpts { CONF_MAP, CONF_NAMESPACE, CONF_PARAM, CONF_PREFIX, CONF_SUBCMDS, CONF_UNKNOWN }; /* * ensembleCmdType is a Tcl object type that contains a reference to an * ensemble subcommand, e.g. the "length" in [string length ab]. It is used * to cache the mapping between the subcommand itself and the real command * that implements it. */ static const Tcl_ObjType ensembleCmdType = { "ensembleCommand", /* the type's name */ FreeEnsembleCmdRep, /* freeIntRepProc */ DupEnsembleCmdRep, /* dupIntRepProc */ NULL, /* updateStringProc */ NULL, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; #define ECRSetInternalRep(objPtr, ecRepPtr) \ do { \ Tcl_ObjInternalRep ir; \ ir.twoPtrValue.ptr1 = (ecRepPtr); \ ir.twoPtrValue.ptr2 = NULL; \ Tcl_StoreInternalRep((objPtr), &ensembleCmdType, &ir); \ } while (0) #define ECRGetInternalRep(objPtr, ecRepPtr) \ do { \ const Tcl_ObjInternalRep *irPtr; \ irPtr = TclFetchInternalRep((objPtr), &ensembleCmdType); \ (ecRepPtr) = irPtr ? (EnsembleCmdRep *) \ irPtr->twoPtrValue.ptr1 : NULL; \ } while (0) /* * The internal rep for caching ensemble subcommand lookups and spelling * corrections. */ typedef struct { Tcl_Size epoch; /* Used to confirm when the data in this * really structure matches up with the * ensemble. */ Command *token; /* Reference to the command for which this * structure is a cache of the resolution. */ Tcl_Obj *fix; /* Corrected spelling, if needed. */ Tcl_HashEntry *hPtr; /* Direct link to entry in the subcommand hash * table. */ } EnsembleCmdRep; /* *---------------------------------------------------------------------- * * TclNamespaceEnsembleCmd -- * * Invoked to implement the "namespace ensemble" command that creates and * manipulates ensembles built on top of namespaces. Handles the * following syntax: * * namespace ensemble name ?dictionary? * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Creates the ensemble for the namespace if one did not previously * exist. Alternatively, alters the way that the ensemble's subcommand => * implementation prefix is configured. * *---------------------------------------------------------------------- */ int TclNamespaceEnsembleCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Namespace *nsPtr = (Namespace *) TclGetCurrentNamespace(interp); Tcl_Command token; /* The ensemble command. */ enum EnsSubcmds index; if (nsPtr == NULL || nsPtr->flags & NS_DEAD) { if (!Tcl_InterpDeleted(interp)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "tried to manipulate ensemble of deleted namespace", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ENSEMBLE", "DEAD", (char *)NULL); } return TCL_ERROR; } if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?arg ...?"); return TCL_ERROR; } else if (Tcl_GetIndexFromObj(interp, objv[1], ensembleSubcommands, "subcommand", 0, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case ENS_CREATE: /* * Check that we've got option-value pairs... [Bug 1558654] */ if (objc & 1) { Tcl_WrongNumArgs(interp, 2, objv, "?option value ...?"); return TCL_ERROR; } token = InitEnsembleFromOptions(interp, objc - 2, objv + 2); if (token == NULL) { return TCL_ERROR; } /* * Tricky! Must ensure that the result is not shared (command delete * traces could have corrupted the pristine object that we started * with). [Snit test rename-1.5] */ Tcl_ResetResult(interp); Tcl_GetCommandFullName(interp, token, Tcl_GetObjResult(interp)); return TCL_OK; case ENS_EXISTS: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "cmdname"); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj( Tcl_FindEnsemble(interp, objv[2], 0) != NULL)); return TCL_OK; case ENS_CONFIG: if (objc < 3 || (objc != 4 && !(objc & 1))) { Tcl_WrongNumArgs(interp, 2, objv, "cmdname ?-option value ...? ?arg ...?"); return TCL_ERROR; } token = Tcl_FindEnsemble(interp, objv[2], TCL_LEAVE_ERR_MSG); if (token == NULL) { return TCL_ERROR; } if (objc == 4) { return ReadOneEnsembleOption(interp, token, objv[3]); } else if (objc == 3) { return ReadAllEnsembleOptions(interp, token); } else { return SetEnsembleConfigOptions(interp, token, objc - 3, objv + 3); } default: Tcl_Panic("unexpected ensemble command"); } return TCL_OK; } /* *---------------------------------------------------------------------- * * InitEnsembleFromOptions -- * * Core of implementation of "namespace ensemble create". * * Results: * Returns created ensemble's command token if successful, and NULL if * anything goes wrong. * * Side effects: * Creates the ensemble for the namespace if one did not previously * exist. * * Note: * Can't use SetEnsembleConfigOptions() here. Different (but overlapping) * options are supported. * *---------------------------------------------------------------------- */ static Tcl_Command InitEnsembleFromOptions( Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Namespace *nsPtr = (Namespace *) TclGetCurrentNamespace(interp); Namespace *cxtPtr = nsPtr->parentPtr; Namespace *altFoundNsPtr, *actualCxtPtr; const char *name = nsPtr->name; Tcl_Size len; int allocatedMapFlag = 0; enum EnsCreateOpts index; Tcl_Command token; /* The created ensemble command. */ Namespace *foundNsPtr; const char *simpleName; /* * Defaults */ Tcl_Obj *subcmdObj = NULL; Tcl_Obj *mapObj = NULL; int permitPrefix = 1; Tcl_Obj *unknownObj = NULL; Tcl_Obj *paramObj = NULL; /* * Parse the option list, applying type checks as we go. Note that we are * not incrementing any reference counts in the objects at this stage, so * the presence of an option multiple times won't cause any memory leaks. */ for (; objc>1 ; objc-=2,objv+=2) { if (Tcl_GetIndexFromObj(interp, objv[0], ensembleCreateOptions, "option", 0, &index) != TCL_OK) { goto error; } switch (index) { case CRT_CMD: name = TclGetString(objv[1]); cxtPtr = nsPtr; continue; case CRT_SUBCMDS: if (TclListObjLength(interp, objv[1], &len) != TCL_OK) { goto error; } subcmdObj = (len > 0 ? objv[1] : NULL); continue; case CRT_PARAM: if (TclListObjLength(interp, objv[1], &len) != TCL_OK) { goto error; } paramObj = (len > 0 ? objv[1] : NULL); continue; case CRT_MAP: { Tcl_Obj *patchedDict = NULL, *subcmdWordsObj, *listObj; Tcl_DictSearch search; int done; /* * Verify that the map is sensible. */ if (Tcl_DictObjFirst(interp, objv[1], &search, &subcmdWordsObj, &listObj, &done) != TCL_OK) { goto error; } else if (done) { mapObj = NULL; continue; } do { Tcl_Obj **listv; const char *cmd; if (TclListObjGetElements(interp, listObj, &len, &listv) != TCL_OK) { goto mapError; } if (len < 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "ensemble subcommand implementations " "must be non-empty lists", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ENSEMBLE", "EMPTY_TARGET", (char *)NULL); goto mapError; } cmd = TclGetString(listv[0]); if (!(cmd[0] == ':' && cmd[1] == ':')) { Tcl_Obj *newList = Tcl_NewListObj(len, listv); Tcl_Obj *newCmd = TclNewNamespaceObj( (Tcl_Namespace *) nsPtr); if (nsPtr->parentPtr) { Tcl_AppendStringsToObj(newCmd, "::", (char *)NULL); } Tcl_AppendObjToObj(newCmd, listv[0]); Tcl_ListObjReplace(NULL, newList, 0, 1, 1, &newCmd); if (patchedDict == NULL) { patchedDict = Tcl_DuplicateObj(objv[1]); } Tcl_DictObjPut(NULL, patchedDict, subcmdWordsObj, newList); } Tcl_DictObjNext(&search, &subcmdWordsObj, &listObj, &done); } while (!done); if (allocatedMapFlag) { Tcl_DecrRefCount(mapObj); } mapObj = (patchedDict ? patchedDict : objv[1]); if (patchedDict) { allocatedMapFlag = 1; } continue; mapError: Tcl_DictObjDone(&search); if (patchedDict) { Tcl_DecrRefCount(patchedDict); } goto error; } case CRT_PREFIX: if (Tcl_GetBooleanFromObj(interp, objv[1], &permitPrefix) != TCL_OK) { goto error; } continue; case CRT_UNKNOWN: if (TclListObjLength(interp, objv[1], &len) != TCL_OK) { goto error; } unknownObj = (len > 0 ? objv[1] : NULL); continue; } } TclGetNamespaceForQualName(interp, name, cxtPtr, TCL_CREATE_NS_IF_UNKNOWN, &foundNsPtr, &altFoundNsPtr, &actualCxtPtr, &simpleName); /* * Create the ensemble. Note that this might delete another ensemble * linked to the same namespace, so we must be careful. However, we * should be OK because we only link the namespace into the list once * we've created it (and after any deletions have occurred.) */ token = TclCreateEnsembleInNs(interp, simpleName, (Tcl_Namespace *) foundNsPtr, (Tcl_Namespace *) nsPtr, (permitPrefix ? TCL_ENSEMBLE_PREFIX : 0)); Tcl_SetEnsembleSubcommandList(interp, token, subcmdObj); Tcl_SetEnsembleMappingDict(interp, token, mapObj); Tcl_SetEnsembleUnknownHandler(interp, token, unknownObj); Tcl_SetEnsembleParameterList(interp, token, paramObj); return token; error: if (allocatedMapFlag) { Tcl_DecrRefCount(mapObj); } return NULL; } /* *---------------------------------------------------------------------- * * ReadOneEnsembleOption -- * * Core of implementation of "namespace ensemble configure" with just a * single option name. * * Results: * Tcl result code. Modifies the interpreter result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int ReadOneEnsembleOption( Tcl_Interp *interp, Tcl_Command token, /* The ensemble to read from. */ Tcl_Obj *optionObj) /* The name of the option to read. */ { Tcl_Obj *resultObj = NULL; /* silence gcc 4 warning */ enum EnsConfigOpts index; if (Tcl_GetIndexFromObj(interp, optionObj, ensembleConfigOptions, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case CONF_SUBCMDS: Tcl_GetEnsembleSubcommandList(NULL, token, &resultObj); if (resultObj != NULL) { Tcl_SetObjResult(interp, resultObj); } break; case CONF_PARAM: Tcl_GetEnsembleParameterList(NULL, token, &resultObj); if (resultObj != NULL) { Tcl_SetObjResult(interp, resultObj); } break; case CONF_MAP: Tcl_GetEnsembleMappingDict(NULL, token, &resultObj); if (resultObj != NULL) { Tcl_SetObjResult(interp, resultObj); } break; case CONF_NAMESPACE: { Tcl_Namespace *namespacePtr = NULL; /* silence gcc 4 warning */ Tcl_GetEnsembleNamespace(NULL, token, &namespacePtr); Tcl_SetObjResult(interp, TclNewNamespaceObj(namespacePtr)); break; } case CONF_PREFIX: { int flags = 0; /* silence gcc 4 warning */ Tcl_GetEnsembleFlags(NULL, token, &flags); Tcl_SetObjResult(interp, Tcl_NewBooleanObj(flags & TCL_ENSEMBLE_PREFIX)); break; } case CONF_UNKNOWN: Tcl_GetEnsembleUnknownHandler(NULL, token, &resultObj); if (resultObj != NULL) { Tcl_SetObjResult(interp, resultObj); } break; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ReadAllEnsembleOptions -- * * Core of implementation of "namespace ensemble configure" without * option names. * * Results: * Tcl result code. Modifies the interpreter result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int ReadAllEnsembleOptions( Tcl_Interp *interp, Tcl_Command token) /* The ensemble to read from. */ { Tcl_Obj *resultObj, *tmpObj = NULL; /* silence gcc 4 warning */ int flags = 0; /* silence gcc 4 warning */ Tcl_Namespace *namespacePtr = NULL; /* silence gcc 4 warning */ TclNewObj(resultObj); /* -map option */ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(ensembleConfigOptions[CONF_MAP], TCL_AUTO_LENGTH)); Tcl_GetEnsembleMappingDict(NULL, token, &tmpObj); Tcl_ListObjAppendElement(NULL, resultObj, (tmpObj != NULL) ? tmpObj : Tcl_NewObj()); /* -namespace option */ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(ensembleConfigOptions[CONF_NAMESPACE], TCL_AUTO_LENGTH)); Tcl_GetEnsembleNamespace(NULL, token, &namespacePtr); Tcl_ListObjAppendElement(NULL, resultObj, TclNewNamespaceObj(namespacePtr)); /* -parameters option */ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(ensembleConfigOptions[CONF_PARAM], TCL_AUTO_LENGTH)); Tcl_GetEnsembleParameterList(NULL, token, &tmpObj); Tcl_ListObjAppendElement(NULL, resultObj, (tmpObj != NULL) ? tmpObj : Tcl_NewObj()); /* -prefix option */ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(ensembleConfigOptions[CONF_PREFIX], TCL_AUTO_LENGTH)); Tcl_GetEnsembleFlags(NULL, token, &flags); Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewBooleanObj(flags & TCL_ENSEMBLE_PREFIX)); /* -subcommands option */ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(ensembleConfigOptions[CONF_SUBCMDS], TCL_AUTO_LENGTH)); Tcl_GetEnsembleSubcommandList(NULL, token, &tmpObj); Tcl_ListObjAppendElement(NULL, resultObj, (tmpObj != NULL) ? tmpObj : Tcl_NewObj()); /* -unknown option */ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(ensembleConfigOptions[CONF_UNKNOWN], TCL_AUTO_LENGTH)); Tcl_GetEnsembleUnknownHandler(NULL, token, &tmpObj); Tcl_ListObjAppendElement(NULL, resultObj, (tmpObj != NULL) ? tmpObj : Tcl_NewObj()); Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* *---------------------------------------------------------------------- * * SetEnsembleConfigOptions -- * * Core of implementation of "namespace ensemble configure" with even * number of arguments (where there is at least one pair). * * Results: * Tcl result code. Modifies the interpreter result. * * Side effects: * Modifies the ensemble's configuration. * *---------------------------------------------------------------------- */ static int SetEnsembleConfigOptions( Tcl_Interp *interp, Tcl_Command token, /* The ensemble to configure. */ int objc, /* The count of option-related arguments. */ Tcl_Obj *const objv[]) /* Option-related arguments. */ { Tcl_Size len; int allocatedMapFlag = 0; Tcl_Obj *subcmdObj = NULL, *mapObj = NULL, *paramObj = NULL, *unknownObj = NULL; /* Defaults, silence gcc 4 warnings */ Tcl_Obj *listObj; Tcl_DictSearch search; int permitPrefix, flags = 0; /* silence gcc 4 warning */ enum EnsConfigOpts index; int done; Tcl_GetEnsembleSubcommandList(NULL, token, &subcmdObj); Tcl_GetEnsembleMappingDict(NULL, token, &mapObj); Tcl_GetEnsembleParameterList(NULL, token, ¶mObj); Tcl_GetEnsembleUnknownHandler(NULL, token, &unknownObj); Tcl_GetEnsembleFlags(NULL, token, &flags); permitPrefix = (flags & TCL_ENSEMBLE_PREFIX) != 0; /* * Parse the option list, applying type checks as we go. Note that * we are not incrementing any reference counts in the objects at * this stage, so the presence of an option multiple times won't * cause any memory leaks. */ for (; objc>0 ; objc-=2,objv+=2) { if (Tcl_GetIndexFromObj(interp, objv[0], ensembleConfigOptions, "option", 0, &index) != TCL_OK) { goto freeMapAndError; } switch (index) { case CONF_SUBCMDS: if (TclListObjLength(interp, objv[1], &len) != TCL_OK) { goto freeMapAndError; } subcmdObj = (len > 0 ? objv[1] : NULL); continue; case CONF_PARAM: if (TclListObjLength(interp, objv[1], &len) != TCL_OK) { goto freeMapAndError; } paramObj = (len > 0 ? objv[1] : NULL); continue; case CONF_MAP: { Tcl_Obj *patchedDict = NULL, *subcmdWordsObj, **listv; Namespace *nsPtr = (Namespace *) TclGetCurrentNamespace(interp); const char *cmd; /* * Verify that the map is sensible. */ if (Tcl_DictObjFirst(interp, objv[1], &search, &subcmdWordsObj, &listObj, &done) != TCL_OK) { goto freeMapAndError; } else if (done) { mapObj = NULL; continue; } do { if (TclListObjLength(interp, listObj, &len) != TCL_OK) { goto finishSearchAndError; } if (len < 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "ensemble subcommand implementations " "must be non-empty lists", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ENSEMBLE", "EMPTY_TARGET", (char *)NULL); goto finishSearchAndError; } if (TclListObjGetElements(interp, listObj, &len, &listv) != TCL_OK) { goto finishSearchAndError; } cmd = TclGetString(listv[0]); if (!(cmd[0] == ':' && cmd[1] == ':')) { Tcl_Obj *newList = Tcl_DuplicateObj(listObj); Tcl_Obj *newCmd = TclNewNamespaceObj( (Tcl_Namespace*) nsPtr); if (nsPtr->parentPtr) { Tcl_AppendStringsToObj(newCmd, "::", (char *)NULL); } Tcl_AppendObjToObj(newCmd, listv[0]); Tcl_ListObjReplace(NULL, newList, 0, 1, 1, &newCmd); if (patchedDict == NULL) { patchedDict = Tcl_DuplicateObj(objv[1]); } Tcl_DictObjPut(NULL, patchedDict, subcmdWordsObj, newList); } Tcl_DictObjNext(&search, &subcmdWordsObj, &listObj, &done); } while (!done); if (allocatedMapFlag) { Tcl_DecrRefCount(mapObj); } mapObj = (patchedDict ? patchedDict : objv[1]); if (patchedDict) { allocatedMapFlag = 1; } continue; finishSearchAndError: Tcl_DictObjDone(&search); if (patchedDict) { Tcl_DecrRefCount(patchedDict); } goto freeMapAndError; } case CONF_NAMESPACE: Tcl_SetObjResult(interp, Tcl_NewStringObj( "option -namespace is read-only", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ENSEMBLE", "READ_ONLY", (char *)NULL); goto freeMapAndError; case CONF_PREFIX: if (Tcl_GetBooleanFromObj(interp, objv[1], &permitPrefix) != TCL_OK) { goto freeMapAndError; } continue; case CONF_UNKNOWN: if (TclListObjLength(interp, objv[1], &len) != TCL_OK) { goto freeMapAndError; } unknownObj = (len > 0 ? objv[1] : NULL); continue; } } /* * Update the namespace now that we've finished the parsing stage. */ flags = (permitPrefix ? flags | TCL_ENSEMBLE_PREFIX : flags & ~TCL_ENSEMBLE_PREFIX); Tcl_SetEnsembleSubcommandList(interp, token, subcmdObj); Tcl_SetEnsembleMappingDict(interp, token, mapObj); Tcl_SetEnsembleParameterList(interp, token, paramObj); Tcl_SetEnsembleUnknownHandler(interp, token, unknownObj); Tcl_SetEnsembleFlags(interp, token, flags); return TCL_OK; freeMapAndError: if (allocatedMapFlag) { Tcl_DecrRefCount(mapObj); } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TclCreateEnsembleInNs -- * * Like Tcl_CreateEnsemble, but additionally accepts as an argument the * name of the namespace to create the command in. * *---------------------------------------------------------------------- */ Tcl_Command TclCreateEnsembleInNs( Tcl_Interp *interp, const char *name, /* Simple name of command to create (no * namespace components). */ Tcl_Namespace *nameNsPtr, /* Name of namespace to create the command * in. */ Tcl_Namespace *ensembleNsPtr, /* Name of the namespace for the ensemble. */ int flags) /* Whether we need exact matching and whether * we bytecode-compile the ensemble's uses. */ { Namespace *nsPtr = (Namespace *) ensembleNsPtr; EnsembleConfig *ensemblePtr; Tcl_Command token; ensemblePtr = (EnsembleConfig *) Tcl_Alloc(sizeof(EnsembleConfig)); token = TclNRCreateCommandInNs(interp, name, (Tcl_Namespace *) nameNsPtr, TclEnsembleImplementationCmd, NsEnsembleImplementationCmdNR, ensemblePtr, DeleteEnsembleConfig); if (token == NULL) { Tcl_Free(ensemblePtr); return NULL; } ensemblePtr->nsPtr = nsPtr; ensemblePtr->epoch = 0; Tcl_InitHashTable(&ensemblePtr->subcommandTable, TCL_STRING_KEYS); ensemblePtr->subcommandArrayPtr = NULL; ensemblePtr->subcmdList = NULL; ensemblePtr->subcommandDict = NULL; ensemblePtr->flags = flags; ensemblePtr->numParameters = 0; ensemblePtr->parameterList = NULL; ensemblePtr->unknownHandler = NULL; ensemblePtr->token = token; ensemblePtr->next = (EnsembleConfig *) nsPtr->ensembles; nsPtr->ensembles = (Tcl_Ensemble *) ensemblePtr; /* * Trigger an eventual recomputation of the ensemble command set. Note * that this is slightly tricky, as it means that we are not actually * counting the number of namespace export actions, but it is the simplest * way to go! */ nsPtr->exportLookupEpoch++; if (flags & ENSEMBLE_COMPILE) { ((Command *) ensemblePtr->token)->compileProc = TclCompileEnsemble; } return ensemblePtr->token; } /* *---------------------------------------------------------------------- * * Tcl_CreateEnsemble * * Create a simple ensemble attached to the given namespace. Deprecated * (internally) by TclCreateEnsembleInNs. * * Value * * The token for the command created. * * Effect * The ensemble is created and marked for compilation. * *---------------------------------------------------------------------- */ Tcl_Command Tcl_CreateEnsemble( Tcl_Interp *interp, const char *name, /* The ensemble name. */ Tcl_Namespace *namespacePtr,/* Context namespace. */ int flags) /* Whether we need exact matching and whether * we bytecode-compile the ensemble's uses. */ { Namespace *nsPtr = (Namespace *) namespacePtr, *foundNsPtr, *altNsPtr, *actualNsPtr; const char * simpleName; if (nsPtr == NULL) { nsPtr = (Namespace *) TclGetCurrentNamespace(interp); } TclGetNamespaceForQualName(interp, name, nsPtr, TCL_CREATE_NS_IF_UNKNOWN, &foundNsPtr, &altNsPtr, &actualNsPtr, &simpleName); return TclCreateEnsembleInNs(interp, simpleName, (Tcl_Namespace *) foundNsPtr, (Tcl_Namespace *) nsPtr, flags); } /* *---------------------------------------------------------------------- * * GetEnsembleFromCommand -- * * Standard check to see if a command is an ensemble. * * Results: * The ensemble implementation if the command is an ensemble. NULL if it * isn't. * * Side effects: * Reports an error in the interpreter (if non-NULL) if the command is * not an ensemble. * *---------------------------------------------------------------------- */ static inline EnsembleConfig * GetEnsembleFromCommand( Tcl_Interp *interp, /* Where to report an error. May be NULL. */ Tcl_Command token) /* What to check for ensemble-ness. */ { Command *cmdPtr = (Command *) token; if (cmdPtr->objProc != TclEnsembleImplementationCmd) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "command is not an ensemble", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ENSEMBLE", "NOT_ENSEMBLE", (char *)NULL); } return NULL; } return (EnsembleConfig *) cmdPtr->objClientData; } /* *---------------------------------------------------------------------- * * BumpEpochIfNecessary -- * * Increments the compilation epoch if the (ensemble) command is one where * changes would be seen by the compiler in some cases. * * Results: * None. * * Side effects: * May trigger later bytecode recompilations. * *---------------------------------------------------------------------- */ static inline void BumpEpochIfNecessary( Tcl_Interp *interp, Tcl_Command token) /* The ensemble command to check. */ { /* * Special hack to make compiling of [info exists] work when the * dictionary is modified. */ if (((Command *) token)->compileProc != NULL) { ((Interp *) interp)->compileEpoch++; } } /* *---------------------------------------------------------------------- * * Tcl_SetEnsembleSubcommandList -- * * Set the subcommand list for a particular ensemble. * * Results: * Tcl result code (error if command token does not indicate an ensemble * or the subcommand list - if non-NULL - is not a list). * * Side effects: * The ensemble is updated and marked for recompilation. * *---------------------------------------------------------------------- */ int Tcl_SetEnsembleSubcommandList( Tcl_Interp *interp, Tcl_Command token, /* The ensemble command to write to. */ Tcl_Obj *subcmdList) { EnsembleConfig *ensemblePtr = GetEnsembleFromCommand(interp, token); Tcl_Obj *oldList; if (ensemblePtr == NULL) { return TCL_ERROR; } if (subcmdList != NULL) { Tcl_Size length; if (TclListObjLength(interp, subcmdList, &length) != TCL_OK) { return TCL_ERROR; } if (length < 1) { subcmdList = NULL; } } oldList = ensemblePtr->subcmdList; ensemblePtr->subcmdList = subcmdList; if (subcmdList != NULL) { Tcl_IncrRefCount(subcmdList); } if (oldList != NULL) { TclDecrRefCount(oldList); } /* * Trigger an eventual recomputation of the ensemble command set. Note * that this is slightly tricky, as it means that we are not actually * counting the number of namespace export actions, but it is the simplest * way to go! */ ensemblePtr->nsPtr->exportLookupEpoch++; BumpEpochIfNecessary(interp, token); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_SetEnsembleParameterList -- * * Set the parameter list for a particular ensemble. * * Results: * Tcl result code (error if command token does not indicate an ensemble * or the parameter list - if non-NULL - is not a list). * * Side effects: * The ensemble is updated and marked for recompilation. * *---------------------------------------------------------------------- */ int Tcl_SetEnsembleParameterList( Tcl_Interp *interp, Tcl_Command token, /* The ensemble command to write to. */ Tcl_Obj *paramList) { EnsembleConfig *ensemblePtr = GetEnsembleFromCommand(interp, token); Tcl_Obj *oldList; Tcl_Size length; if (ensemblePtr == NULL) { return TCL_ERROR; } if (paramList == NULL) { length = 0; } else { if (TclListObjLength(interp, paramList, &length) != TCL_OK) { return TCL_ERROR; } if (length < 1) { paramList = NULL; } } oldList = ensemblePtr->parameterList; ensemblePtr->parameterList = paramList; if (paramList != NULL) { Tcl_IncrRefCount(paramList); } if (oldList != NULL) { TclDecrRefCount(oldList); } ensemblePtr->numParameters = length; /* * Trigger an eventual recomputation of the ensemble command set. Note * that this is slightly tricky, as it means that we are not actually * counting the number of namespace export actions, but it is the simplest * way to go! */ ensemblePtr->nsPtr->exportLookupEpoch++; BumpEpochIfNecessary(interp, token); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_SetEnsembleMappingDict -- * * Set the mapping dictionary for a particular ensemble. * * Results: * Tcl result code (error if command token does not indicate an ensemble * or the mapping - if non-NULL - is not a dict). * * Side effects: * The ensemble is updated and marked for recompilation. * *---------------------------------------------------------------------- */ int Tcl_SetEnsembleMappingDict( Tcl_Interp *interp, Tcl_Command token, /* The ensemble command to write to. */ Tcl_Obj *mapDict) { EnsembleConfig *ensemblePtr = GetEnsembleFromCommand(interp, token); Tcl_Obj *oldDict; if (ensemblePtr == NULL) { return TCL_ERROR; } if (mapDict != NULL) { Tcl_Size size; int done; Tcl_DictSearch search; Tcl_Obj *valuePtr; if (Tcl_DictObjSize(interp, mapDict, &size) != TCL_OK) { return TCL_ERROR; } for (Tcl_DictObjFirst(NULL, mapDict, &search, NULL, &valuePtr, &done); !done; Tcl_DictObjNext(&search, NULL, &valuePtr, &done)) { Tcl_Obj *cmdObjPtr; const char *bytes; if (Tcl_ListObjIndex(interp, valuePtr, 0, &cmdObjPtr) != TCL_OK) { Tcl_DictObjDone(&search); return TCL_ERROR; } bytes = TclGetString(cmdObjPtr); if (bytes[0] != ':' || bytes[1] != ':') { Tcl_SetObjResult(interp, Tcl_NewStringObj( "ensemble target is not a fully-qualified command", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ENSEMBLE", "UNQUALIFIED_TARGET", (char *)NULL); Tcl_DictObjDone(&search); return TCL_ERROR; } } if (size < 1) { mapDict = NULL; } } oldDict = ensemblePtr->subcommandDict; ensemblePtr->subcommandDict = mapDict; if (mapDict != NULL) { Tcl_IncrRefCount(mapDict); } if (oldDict != NULL) { TclDecrRefCount(oldDict); } /* * Trigger an eventual recomputation of the ensemble command set. Note * that this is slightly tricky, as it means that we are not actually * counting the number of namespace export actions, but it is the simplest * way to go! */ ensemblePtr->nsPtr->exportLookupEpoch++; BumpEpochIfNecessary(interp, token); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_SetEnsembleUnknownHandler -- * * Set the unknown handler for a particular ensemble. * * Results: * Tcl result code (error if command token does not indicate an ensemble * or the unknown handler - if non-NULL - is not a list). * * Side effects: * The ensemble is updated and marked for recompilation. * *---------------------------------------------------------------------- */ int Tcl_SetEnsembleUnknownHandler( Tcl_Interp *interp, Tcl_Command token, /* The ensemble command to write to. */ Tcl_Obj *unknownList) { EnsembleConfig *ensemblePtr = GetEnsembleFromCommand(interp, token); Tcl_Obj *oldList; if (ensemblePtr == NULL) { return TCL_ERROR; } if (unknownList != NULL) { Tcl_Size length; if (TclListObjLength(interp, unknownList, &length) != TCL_OK) { return TCL_ERROR; } if (length < 1) { unknownList = NULL; } } oldList = ensemblePtr->unknownHandler; ensemblePtr->unknownHandler = unknownList; if (unknownList != NULL) { Tcl_IncrRefCount(unknownList); } if (oldList != NULL) { TclDecrRefCount(oldList); } /* * Trigger an eventual recomputation of the ensemble command set. Note * that this is slightly tricky, as it means that we are not actually * counting the number of namespace export actions, but it is the simplest * way to go! */ ensemblePtr->nsPtr->exportLookupEpoch++; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_SetEnsembleFlags -- * * Set the flags for a particular ensemble. * * Results: * Tcl result code (error if command token does not indicate an * ensemble). * * Side effects: * The ensemble is updated and marked for recompilation. * *---------------------------------------------------------------------- */ int Tcl_SetEnsembleFlags( Tcl_Interp *interp, Tcl_Command token, /* The ensemble command to write to. */ int flags) { EnsembleConfig *ensemblePtr = GetEnsembleFromCommand(interp, token); int changedFlags = flags ^ ensemblePtr->flags; if (ensemblePtr == NULL) { return TCL_ERROR; } /* * This API refuses to set the ENSEMBLE_DEAD flag... */ ensemblePtr->flags &= ENSEMBLE_DEAD; ensemblePtr->flags |= flags & ~ENSEMBLE_DEAD; /* * Trigger an eventual recomputation of the ensemble command set. Note * that this is slightly tricky, as it means that we are not actually * counting the number of namespace export actions, but it is the simplest * way to go! */ ensemblePtr->nsPtr->exportLookupEpoch++; /* * If the ENSEMBLE_COMPILE flag status was changed, install or remove the * compiler function and bump the interpreter's compilation epoch so that * bytecode gets regenerated. */ if (changedFlags & ENSEMBLE_COMPILE) { ((Command*) ensemblePtr->token)->compileProc = ((flags & ENSEMBLE_COMPILE) ? TclCompileEnsemble : NULL); ((Interp *) interp)->compileEpoch++; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_GetEnsembleSubcommandList -- * * Get the list of subcommands associated with a particular ensemble. * * Results: * Tcl result code (error if command token does not indicate an * ensemble). The list of subcommands is returned by updating the * variable pointed to by the last parameter (NULL if this is to be * derived from the mapping dictionary or the associated namespace's * exported commands). * * Side effects: * None * *---------------------------------------------------------------------- */ int Tcl_GetEnsembleSubcommandList( Tcl_Interp *interp, Tcl_Command token, /* The ensemble command to read from. */ Tcl_Obj **subcmdListPtr) { EnsembleConfig *ensemblePtr = GetEnsembleFromCommand(interp, token); if (ensemblePtr == NULL) { return TCL_ERROR; } *subcmdListPtr = ensemblePtr->subcmdList; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_GetEnsembleParameterList -- * * Get the list of parameters associated with a particular ensemble. * * Results: * Tcl result code (error if command token does not indicate an * ensemble). The list of parameters is returned by updating the * variable pointed to by the last parameter (NULL if there are * no parameters). * * Side effects: * None * *---------------------------------------------------------------------- */ int Tcl_GetEnsembleParameterList( Tcl_Interp *interp, Tcl_Command token, /* The ensemble command to read from. */ Tcl_Obj **paramListPtr) { EnsembleConfig *ensemblePtr = GetEnsembleFromCommand(interp, token); if (ensemblePtr == NULL) { return TCL_ERROR; } *paramListPtr = ensemblePtr->parameterList; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_GetEnsembleMappingDict -- * * Get the command mapping dictionary associated with a particular * ensemble. * * Results: * Tcl result code (error if command token does not indicate an * ensemble). The mapping dict is returned by updating the variable * pointed to by the last parameter (NULL if none is installed). * * Side effects: * None * *---------------------------------------------------------------------- */ int Tcl_GetEnsembleMappingDict( Tcl_Interp *interp, Tcl_Command token, /* The ensemble command to read from. */ Tcl_Obj **mapDictPtr) { EnsembleConfig *ensemblePtr = GetEnsembleFromCommand(interp, token); if (ensemblePtr == NULL) { return TCL_ERROR; } *mapDictPtr = ensemblePtr->subcommandDict; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_GetEnsembleUnknownHandler -- * * Get the unknown handler associated with a particular ensemble. * * Results: * Tcl result code (error if command token does not indicate an * ensemble). The unknown handler is returned by updating the variable * pointed to by the last parameter (NULL if no handler is installed). * * Side effects: * None * *---------------------------------------------------------------------- */ int Tcl_GetEnsembleUnknownHandler( Tcl_Interp *interp, Tcl_Command token, /* The ensemble command to read from. */ Tcl_Obj **unknownListPtr) { EnsembleConfig *ensemblePtr = GetEnsembleFromCommand(interp, token); if (ensemblePtr == NULL) { return TCL_ERROR; } *unknownListPtr = ensemblePtr->unknownHandler; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_GetEnsembleFlags -- * * Get the flags for a particular ensemble. * * Results: * Tcl result code (error if command token does not indicate an * ensemble). The flags are returned by updating the variable pointed to * by the last parameter. * * Side effects: * None * *---------------------------------------------------------------------- */ int Tcl_GetEnsembleFlags( Tcl_Interp *interp, Tcl_Command token, /* The ensemble command to read from. */ int *flagsPtr) { EnsembleConfig *ensemblePtr = GetEnsembleFromCommand(interp, token); if (ensemblePtr == NULL) { return TCL_ERROR; } *flagsPtr = ensemblePtr->flags; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_GetEnsembleNamespace -- * * Get the namespace associated with a particular ensemble. * * Results: * Tcl result code (error if command token does not indicate an * ensemble). Namespace is returned by updating the variable pointed to * by the last parameter. * * Side effects: * None * *---------------------------------------------------------------------- */ int Tcl_GetEnsembleNamespace( Tcl_Interp *interp, Tcl_Command token, /* The ensemble command to read from. */ Tcl_Namespace **namespacePtrPtr) { EnsembleConfig *ensemblePtr = GetEnsembleFromCommand(interp, token); if (ensemblePtr == NULL) { return TCL_ERROR; } *namespacePtrPtr = (Tcl_Namespace *) ensemblePtr->nsPtr; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_FindEnsemble -- * * Given a command name, get the ensemble token for it, allowing for * [namespace import]s. [Bug 1017022] * * Results: * The token for the ensemble command with the given name, or NULL if the * command either does not exist or is not an ensemble (when an error * message will be written into the interp if thats non-NULL). * * Side effects: * None * *---------------------------------------------------------------------- */ Tcl_Command Tcl_FindEnsemble( Tcl_Interp *interp, /* Where to do the lookup, and where to write * the errors if TCL_LEAVE_ERR_MSG is set in * the flags. */ Tcl_Obj *cmdNameObj, /* Name of command to look up. */ int flags) /* Either 0 or TCL_LEAVE_ERR_MSG; other flags * are probably not useful. */ { Tcl_Command token; token = Tcl_FindCommand(interp, TclGetString(cmdNameObj), NULL, flags); if (token == NULL) { return NULL; } if (((Command *) token)->objProc != TclEnsembleImplementationCmd) { /* * Reuse existing infrastructure for following import link chains * rather than duplicating it. */ token = TclGetOriginalCommand(token); if (token == NULL || ((Command *) token)->objProc != TclEnsembleImplementationCmd) { if (flags & TCL_LEAVE_ERR_MSG) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" is not an ensemble command", TclGetString(cmdNameObj))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "ENSEMBLE", TclGetString(cmdNameObj), (char *)NULL); } return NULL; } } return token; } /* *---------------------------------------------------------------------- * * Tcl_IsEnsemble -- * * Simple test for ensemble-hood that takes into account imported * ensemble commands as well. * * Results: * Boolean value * * Side effects: * None * *---------------------------------------------------------------------- */ int Tcl_IsEnsemble( Tcl_Command token) /* The command to check. */ { Command *cmdPtr = (Command *) token; if (cmdPtr->objProc == TclEnsembleImplementationCmd) { return 1; } cmdPtr = (Command *) TclGetOriginalCommand((Tcl_Command) cmdPtr); if (cmdPtr == NULL || cmdPtr->objProc != TclEnsembleImplementationCmd) { return 0; } return 1; } /* *---------------------------------------------------------------------- * * TclMakeEnsemble -- * * Create an ensemble from a table of implementation commands. The * ensemble will be subject to (limited) compilation if any of the * implementation commands are compilable. * * The 'name' parameter may be a single command name or a list if * creating an ensemble subcommand (see the binary implementation). * * Currently, the TCL_ENSEMBLE_PREFIX ensemble flag is only used on * top-level ensemble commands. * * This code is not safe to run in Safe interpreter after user code has * executed. That's OK right now because it's just used to set up Tcl, * but it means we mustn't expose it at all, not even to Tk (until we can * hide commands in namespaces directly). * * Results: * Handle for the new ensemble, or NULL on failure. * * Side effects: * May advance the bytecode compilation epoch. * *---------------------------------------------------------------------- */ Tcl_Command TclMakeEnsemble( Tcl_Interp *interp, const char *name, /* The ensemble name (as explained above) */ const EnsembleImplMap map[])/* The subcommands to create */ { Tcl_Command ensemble; Tcl_Namespace *ns; Tcl_DString buf, hiddenBuf; const char **nameParts = NULL; const char *cmdName = NULL; Tcl_Size i, nameCount = 0; int ensembleFlags = 0, hiddenLen; /* * Construct the path for the ensemble namespace and create it. */ Tcl_DStringInit(&buf); Tcl_DStringInit(&hiddenBuf); TclDStringAppendLiteral(&hiddenBuf, "tcl:"); Tcl_DStringAppend(&hiddenBuf, name, TCL_AUTO_LENGTH); TclDStringAppendLiteral(&hiddenBuf, ":"); hiddenLen = Tcl_DStringLength(&hiddenBuf); if (name[0] == ':' && name[1] == ':') { /* * An absolute name, so use it directly. */ cmdName = name; Tcl_DStringAppend(&buf, name, TCL_AUTO_LENGTH); ensembleFlags = TCL_ENSEMBLE_PREFIX; } else { /* * Not an absolute name, so do munging of it. Note that this treats a * multi-word list differently to a single word. */ TclDStringAppendLiteral(&buf, "::tcl"); if (Tcl_SplitList(NULL, name, &nameCount, &nameParts) != TCL_OK) { Tcl_Panic("invalid ensemble name '%s'", name); } for (i = 0; i < nameCount; ++i) { TclDStringAppendLiteral(&buf, "::"); Tcl_DStringAppend(&buf, nameParts[i], TCL_AUTO_LENGTH); } } ns = Tcl_FindNamespace(interp, Tcl_DStringValue(&buf), NULL, TCL_CREATE_NS_IF_UNKNOWN); if (!ns) { Tcl_Panic("unable to find or create %s namespace!", Tcl_DStringValue(&buf)); } /* * Create the named ensemble in the correct namespace */ if (cmdName == NULL) { if (nameCount == 1) { ensembleFlags = TCL_ENSEMBLE_PREFIX; cmdName = Tcl_DStringValue(&buf) + 5; } else { ns = ns->parentPtr; cmdName = nameParts[nameCount - 1]; } } /* * Switch on compilation always for core ensembles now that we can do * nice bytecode things with them. Do it now. Waiting until later will * just cause pointless epoch bumps. */ ensembleFlags |= ENSEMBLE_COMPILE; ensemble = Tcl_CreateEnsemble(interp, cmdName, ns, ensembleFlags); /* * Create the ensemble mapping dictionary and the ensemble command procs. */ if (ensemble != NULL) { Tcl_Obj *mapDict, *toObj; Command *cmdPtr; TclDStringAppendLiteral(&buf, "::"); TclNewObj(mapDict); for (i=0 ; map[i].name != NULL ; i++) { TclNewStringObj(toObj, Tcl_DStringValue(&buf), Tcl_DStringLength(&buf)); Tcl_AppendToObj(toObj, map[i].name, TCL_AUTO_LENGTH); TclDictPut(NULL, mapDict, map[i].name, toObj); if (map[i].proc || map[i].nreProc) { /* * If the command is unsafe, hide it when we're in a safe * interpreter. The code to do this is really hokey! It also * doesn't work properly yet; this function is always * currently called before the safe-interp flag is set so the * Tcl_IsSafe check fails. */ if (map[i].unsafe && Tcl_IsSafe(interp)) { cmdPtr = (Command *) Tcl_NRCreateCommand(interp, "___tmp", map[i].proc, map[i].nreProc, map[i].clientData, NULL); Tcl_DStringSetLength(&hiddenBuf, hiddenLen); if (Tcl_HideCommand(interp, "___tmp", Tcl_DStringAppend(&hiddenBuf, map[i].name, TCL_AUTO_LENGTH))) { Tcl_Panic("%s", Tcl_GetStringResult(interp)); } /* don't compile unsafe subcommands in safe interp */ cmdPtr->compileProc = NULL; } else { /* * Not hidden, so just create it. Yay! */ cmdPtr = (Command *) Tcl_NRCreateCommand(interp, TclGetString(toObj), map[i].proc, map[i].nreProc, map[i].clientData, NULL); cmdPtr->compileProc = map[i].compileProc; } } } Tcl_SetEnsembleMappingDict(interp, ensemble, mapDict); } Tcl_DStringFree(&buf); Tcl_DStringFree(&hiddenBuf); if (nameParts != NULL) { Tcl_Free((void *)nameParts); } return ensemble; } /* *---------------------------------------------------------------------- * * TclEnsembleImplementationCmd -- * * Implements an ensemble of commands (being those exported by a * namespace other than the global namespace) as a command with the same * (short) name as the namespace in the parent namespace. * * Results: * A standard Tcl result code. Will be TCL_ERROR if the command is not an * unambiguous prefix of any command exported by the ensemble's * namespace. * * Side effects: * Depends on the command within the namespace that gets executed. If the * ensemble itself returns TCL_ERROR, a descriptive error message will be * placed in the interpreter's result. * *---------------------------------------------------------------------- */ int TclEnsembleImplementationCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { return Tcl_NRCallObjProc(interp, NsEnsembleImplementationCmdNR, clientData, objc, objv); } static int NsEnsembleImplementationCmdNR( void *clientData, /* The ensemble this is the impl. of. */ Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { EnsembleConfig *ensemblePtr = (EnsembleConfig *) clientData; /* The ensemble itself. */ Tcl_Obj *prefixObj; /* An object containing the prefix words of * the command that implements the * subcommand. */ Tcl_HashEntry *hPtr; /* Used for efficient lookup of fully * specified but not yet cached command * names. */ int reparseCount = 0; /* Number of reparses. */ Tcl_Obj *errorObj; /* Used for building error messages. */ Tcl_Obj *subObj; Tcl_Size subIdx; /* * Must recheck objc since numParameters might have changed. See test * namespace-53.9. */ restartEnsembleParse: subIdx = 1 + ensemblePtr->numParameters; if (objc < subIdx + 1) { /* * No subcommand argument. Make error message. */ Tcl_DString buf; /* Message being built */ Tcl_DStringInit(&buf); if (ensemblePtr->parameterList) { TclDStringAppendObj(&buf, ensemblePtr->parameterList); TclDStringAppendLiteral(&buf, " "); } TclDStringAppendLiteral(&buf, "subcommand ?arg ...?"); Tcl_WrongNumArgs(interp, 1, objv, Tcl_DStringValue(&buf)); Tcl_DStringFree(&buf); return TCL_ERROR; } if (ensemblePtr->nsPtr->flags & NS_DEAD) { /* * Don't know how we got here, but make things give up quickly. */ if (!Tcl_InterpDeleted(interp)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "ensemble activated for deleted namespace", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ENSEMBLE", "DEAD", (char *)NULL); } return TCL_ERROR; } /* * If the table of subcommands is valid just lookup up the command there * and go to dispatch. */ subObj = objv[subIdx]; if (ensemblePtr->epoch == ensemblePtr->nsPtr->exportLookupEpoch) { /* * Table of subcommands is still valid so if the internal representtion * is an ensembleCmd, just call it. */ EnsembleCmdRep *ensembleCmd; ECRGetInternalRep(subObj, ensembleCmd); if (ensembleCmd) { if (ensembleCmd->epoch == ensemblePtr->epoch && ensembleCmd->token == (Command *) ensemblePtr->token) { prefixObj = (Tcl_Obj *) Tcl_GetHashValue(ensembleCmd->hPtr); Tcl_IncrRefCount(prefixObj); if (ensembleCmd->fix) { TclSpellFix(interp, objv, objc, subIdx, subObj, ensembleCmd->fix); } goto runResultingSubcommand; } } } else { BuildEnsembleConfig(ensemblePtr); ensemblePtr->epoch = ensemblePtr->nsPtr->exportLookupEpoch; } /* * Look in the hashtable for the named subcommand. This is the fastest * path if there is no cache in operation. */ hPtr = Tcl_FindHashEntry(&ensemblePtr->subcommandTable, TclGetString(subObj)); if (hPtr != NULL) { /* * Cache ensemble in the subcommand object for later. */ MakeCachedEnsembleCommand(subObj, ensemblePtr, hPtr, NULL); } else if (!(ensemblePtr->flags & TCL_ENSEMBLE_PREFIX)) { /* * Could not map. No prefixing. Go to unknown/error handling. */ goto unknownOrAmbiguousSubcommand; } else { /* * If the command isn't yet confirmed with the hash as part of building * the export table, scan the sorted array for matches. */ const char *subcmdName; /* Name of the subcommand or unique prefix of * it (a non-unique prefix produces an error). */ char *fullName = NULL; /* Full name of the subcommand. */ Tcl_Size stringLength, i; Tcl_Size tableLength = ensemblePtr->subcommandTable.numEntries; Tcl_Obj *fix; subcmdName = TclGetStringFromObj(subObj, &stringLength); for (i=0 ; isubcommandArrayPtr[i], stringLength); if (cmp == 0) { if (fullName != NULL) { /* * Hash search filters out the exact-match case, so getting * here indicates that the subcommand is an ambiguous * prefix of at least two exported subcommands, which is an * error case. */ goto unknownOrAmbiguousSubcommand; } fullName = ensemblePtr->subcommandArrayPtr[i]; } else if (cmp < 0) { /* * The table is sorted so stop searching because a match would * have been found already. */ break; } } if (fullName == NULL) { /* * The subcommand is not a prefix of anything. Bail out! */ goto unknownOrAmbiguousSubcommand; } hPtr = Tcl_FindHashEntry(&ensemblePtr->subcommandTable, fullName); if (hPtr == NULL) { Tcl_Panic("full name %s not found in supposedly synchronized hash", fullName); } /* * Record the spelling correction for usage message. */ fix = Tcl_NewStringObj(fullName, TCL_AUTO_LENGTH); /* * Cache for later in the subcommand object. */ MakeCachedEnsembleCommand(subObj, ensemblePtr, hPtr, fix); TclSpellFix(interp, objv, objc, subIdx, subObj, fix); } prefixObj = (Tcl_Obj *) Tcl_GetHashValue(hPtr); Tcl_IncrRefCount(prefixObj); runResultingSubcommand: /* * Execute the subcommand by populating an array of objects, which might * not be the same length as the number of arguments to this ensemble * command, and then handing it to the main command-lookup engine. In * theory, the command could be looked up right here using the namespace in * which it is guaranteed to exist, * * ((Q: That's not true if the -map option is used, is it?)) * * but don't do that because caching of the command object should help. */ { Tcl_Obj *copyPtr; /* The list of words to dispatch on. * Will be freed by the dispatch engine. */ Tcl_Obj **copyObjv; Tcl_Size copyObjc, prefixObjc; TclListObjLength(NULL, prefixObj, &prefixObjc); if (objc == 2) { copyPtr = TclListObjCopy(NULL, prefixObj); } else { copyPtr = Tcl_NewListObj(objc - 2 + prefixObjc, NULL); Tcl_ListObjAppendList(NULL, copyPtr, prefixObj); Tcl_ListObjReplace(NULL, copyPtr, LIST_MAX, 0, ensemblePtr->numParameters, objv + 1); Tcl_ListObjReplace(NULL, copyPtr, LIST_MAX, 0, objc - 2 - ensemblePtr->numParameters, objv + 2 + ensemblePtr->numParameters); } Tcl_IncrRefCount(copyPtr); TclNRAddCallback(interp, TclNRReleaseValues, copyPtr, NULL, NULL, NULL); TclDecrRefCount(prefixObj); /* * Record the words of the command as given so that routines like * Tcl_WrongNumArgs can produce the correct error message. Parameters * count both as inserted and removed arguments. */ if (TclInitRewriteEnsemble(interp, 2 + ensemblePtr->numParameters, prefixObjc + ensemblePtr->numParameters, objv)) { TclNRAddCallback(interp, TclClearRootEnsemble, NULL, NULL, NULL, NULL); } /* * Hand off to the target command. */ TclSkipTailcall(interp); TclListObjGetElements(NULL, copyPtr, ©Objc, ©Objv); ((Interp *) interp)->lookupNsPtr = ensemblePtr->nsPtr; return TclNREvalObjv(interp, copyObjc, copyObjv, TCL_EVAL_INVOKE, NULL); } unknownOrAmbiguousSubcommand: /* * The named subcommand did not match any exported command. If there is a * handler registered unknown subcommands, call it, but not more than once * for this call. */ if (ensemblePtr->unknownHandler != NULL && reparseCount++ < 1) { switch (EnsembleUnknownCallback(interp, ensemblePtr, objc, objv, &prefixObj)) { case TCL_OK: goto runResultingSubcommand; case TCL_ERROR: return TCL_ERROR; case TCL_CONTINUE: goto restartEnsembleParse; } } /* * Could not find a routine for the named subcommand so generate a standard * failure message. The one odd case compared with a standard * ensemble-like command is where a namespace has no exported commands at * all... */ Tcl_ResetResult(interp); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "SUBCOMMAND", TclGetString(subObj), (char *)NULL); if (ensemblePtr->subcommandTable.numEntries == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown subcommand \"%s\": namespace %s does not" " export any commands", TclGetString(subObj), ensemblePtr->nsPtr->fullName)); return TCL_ERROR; } errorObj = Tcl_ObjPrintf("unknown%s subcommand \"%s\": must be ", (ensemblePtr->flags & TCL_ENSEMBLE_PREFIX ? " or ambiguous" : ""), TclGetString(subObj)); if (ensemblePtr->subcommandTable.numEntries == 1) { Tcl_AppendToObj(errorObj, ensemblePtr->subcommandArrayPtr[0], TCL_AUTO_LENGTH); } else { Tcl_Size i; for (i=0 ; isubcommandTable.numEntries-1 ; i++) { Tcl_AppendToObj(errorObj, ensemblePtr->subcommandArrayPtr[i], TCL_AUTO_LENGTH); Tcl_AppendToObj(errorObj, ", ", 2); } Tcl_AppendPrintfToObj(errorObj, "or %s", ensemblePtr->subcommandArrayPtr[i]); } Tcl_SetObjResult(interp, errorObj); return TCL_ERROR; } int TclClearRootEnsemble( TCL_UNUSED(void **), Tcl_Interp *interp, int result) { TclResetRewriteEnsemble(interp, 1); return result; } /* *---------------------------------------------------------------------- * * TclInitRewriteEnsemble -- * * Applies a rewrite of arguments so that an ensemble subcommand * correctly reports any error messages for the overall command. * * Results: * Whether this is the first rewrite applied, a value which must be * passed to TclResetRewriteEnsemble when undoing this command's * behaviour. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclInitRewriteEnsemble( Tcl_Interp *interp, Tcl_Size numRemoved, Tcl_Size numInserted, Tcl_Obj *const *objv) { Interp *iPtr = (Interp *) interp; int isRootEnsemble = (iPtr->ensembleRewrite.sourceObjs == NULL); if (isRootEnsemble) { iPtr->ensembleRewrite.sourceObjs = objv; iPtr->ensembleRewrite.numRemovedObjs = numRemoved; iPtr->ensembleRewrite.numInsertedObjs = numInserted; } else { Tcl_Size numIns = iPtr->ensembleRewrite.numInsertedObjs; if (numIns < numRemoved) { iPtr->ensembleRewrite.numRemovedObjs += numRemoved - numIns; iPtr->ensembleRewrite.numInsertedObjs = numInserted; } else { iPtr->ensembleRewrite.numInsertedObjs += numInserted - numRemoved; } } return isRootEnsemble; } /* *---------------------------------------------------------------------- * * TclResetRewriteEnsemble -- * * Removes any rewrites applied to support proper reporting of error * messages used in ensembles. Should be paired with * TclInitRewriteEnsemble. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclResetRewriteEnsemble( Tcl_Interp *interp, int isRootEnsemble) { Interp *iPtr = (Interp *) interp; if (isRootEnsemble) { iPtr->ensembleRewrite.sourceObjs = NULL; iPtr->ensembleRewrite.numRemovedObjs = 0; iPtr->ensembleRewrite.numInsertedObjs = 0; } } /* *---------------------------------------------------------------------- * * TclSpellFix -- * * Records a spelling correction that needs making in the generation of * the WrongNumArgs usage message. * * Results: * None. * * Side effects: * Can create an alternative ensemble rewrite structure. * *---------------------------------------------------------------------- */ static int FreeER( void *data[], TCL_UNUSED(Tcl_Interp *), int result) { Tcl_Obj **tmp = (Tcl_Obj **) data[0]; Tcl_Obj **store = (Tcl_Obj **) data[1]; Tcl_Free(store); Tcl_Free(tmp); return result; } void TclSpellFix( Tcl_Interp *interp, Tcl_Obj *const *objv, Tcl_Size objc, Tcl_Size badIdx, Tcl_Obj *bad, Tcl_Obj *fix) { Interp *iPtr = (Interp *) interp; Tcl_Obj *const *search; Tcl_Obj **store; Tcl_Size idx; Tcl_Size size; if (iPtr->ensembleRewrite.sourceObjs == NULL) { iPtr->ensembleRewrite.sourceObjs = objv; iPtr->ensembleRewrite.numRemovedObjs = 0; iPtr->ensembleRewrite.numInsertedObjs = 0; } /* * Compute the valid length of the ensemble root. */ size = iPtr->ensembleRewrite.numRemovedObjs + objc - iPtr->ensembleRewrite.numInsertedObjs; search = iPtr->ensembleRewrite.sourceObjs; if (search[0] == NULL) { /* * Awful casting abuse here! */ search = (Tcl_Obj *const *) search[1]; } if (badIdx < iPtr->ensembleRewrite.numInsertedObjs) { /* * Misspelled value was inserted. Cannot directly jump to the bad * value. Must search. */ idx = 1; while (idx < size) { if (search[idx] == bad) { break; } idx++; } if (idx == size) { return; } } else { /* * Jump to the misspelled value. */ idx = iPtr->ensembleRewrite.numRemovedObjs + badIdx - iPtr->ensembleRewrite.numInsertedObjs; /* Verify */ if (search[idx] != bad) { Tcl_Panic("SpellFix: programming error"); } } search = iPtr->ensembleRewrite.sourceObjs; if (search[0] == NULL) { store = (Tcl_Obj **) search[2]; } else { Tcl_Obj **tmp = (Tcl_Obj **) Tcl_Alloc(3 * sizeof(Tcl_Obj *)); store = (Tcl_Obj **) Tcl_Alloc(size * sizeof(Tcl_Obj *)); memcpy(store, iPtr->ensembleRewrite.sourceObjs, size * sizeof(Tcl_Obj *)); /* * Awful casting abuse here! Note that the NULL in the first element * indicates that the initial objects are a raw array in the second * element and the rewritten ones are a raw array in the third. */ tmp[0] = NULL; tmp[1] = (Tcl_Obj *) iPtr->ensembleRewrite.sourceObjs; tmp[2] = (Tcl_Obj *) store; iPtr->ensembleRewrite.sourceObjs = (Tcl_Obj *const *) tmp; TclNRAddCallback(interp, FreeER, tmp, store, NULL, NULL); } store[idx] = fix; Tcl_IncrRefCount(fix); TclNRAddCallback(interp, TclNRReleaseValues, fix, NULL, NULL, NULL); } /* *---------------------------------------------------------------------- * * TclEnsembleGetRewriteValues -- * * Get the original arguments to the current command before any rewrite * rules (from aliases, ensembles, and method forwards) were applied. * *---------------------------------------------------------------------- */ Tcl_Obj *const * TclEnsembleGetRewriteValues( Tcl_Interp *interp) /* Current interpreter. */ { Interp *iPtr = (Interp *) interp; Tcl_Obj *const *origObjv = iPtr->ensembleRewrite.sourceObjs; if (origObjv[0] == NULL) { origObjv = (Tcl_Obj *const *) origObjv[2]; } return origObjv; } /* *---------------------------------------------------------------------- * * TclFetchEnsembleRoot -- * * Returns the root of ensemble rewriting, if any. * If no root exists, returns objv instead. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj *const * TclFetchEnsembleRoot( Tcl_Interp *interp, Tcl_Obj *const *objv, Tcl_Size objc, Tcl_Size *objcPtr) { Tcl_Obj *const *sourceObjs; Interp *iPtr = (Interp *) interp; if (iPtr->ensembleRewrite.sourceObjs) { *objcPtr = objc + iPtr->ensembleRewrite.numRemovedObjs - iPtr->ensembleRewrite.numInsertedObjs; if (iPtr->ensembleRewrite.sourceObjs[0] == NULL) { sourceObjs = (Tcl_Obj *const *) iPtr->ensembleRewrite.sourceObjs[1]; } else { sourceObjs = iPtr->ensembleRewrite.sourceObjs; } return sourceObjs; } *objcPtr = objc; return objv; } /* * ---------------------------------------------------------------------- * * EnsembleUnknownCallback -- * * Helper for the ensemble engine. Calls the routine registered for * "ensemble unknown" case. See the user documentation of the * ensemble unknown handler for details. Only called when such a * function is defined, and is only called once per ensemble dispatch. * I.e. even if a reparse still fails, this isn't called again. * * Results: * TCL_OK - *prefixObjPtr contains the command words to dispatch * to. * TCL_CONTINUE - Need to reparse, i.e. *prefixObjPtr is invalid * TCL_ERROR - Something went wrong. Error message in interpreter. * * Side effects: * Arbitrary, due to evaluation of script provided by client. * * ---------------------------------------------------------------------- */ static inline int EnsembleUnknownCallback( Tcl_Interp *interp, EnsembleConfig *ensemblePtr,/* The ensemble structure. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[], /* Actual arguments. */ Tcl_Obj **prefixObjPtr) /* Where to write the prefix suggested by the * unknown callback. Must not be NULL. Only has * a meaningful value on TCL_OK. */ { Tcl_Size paramc; int result; Tcl_Size i, prefixObjc; Tcl_Obj **paramv, *unknownCmd, *ensObj; /* * Create the "unknown" command callback to determine what to do. */ unknownCmd = Tcl_DuplicateObj(ensemblePtr->unknownHandler); TclNewObj(ensObj); Tcl_GetCommandFullName(interp, ensemblePtr->token, ensObj); Tcl_ListObjAppendElement(NULL, unknownCmd, ensObj); for (i = 1 ; i < objc ; i++) { Tcl_ListObjAppendElement(NULL, unknownCmd, objv[i]); } TclListObjGetElements(NULL, unknownCmd, ¶mc, ¶mv); Tcl_IncrRefCount(unknownCmd); /* * Call the "unknown" handler. No attempt to NRE-enable this as deep * recursion through unknown handlers is perverse. It is always an error * for an unknown handler to delete its ensemble. Don't do that. */ Tcl_Preserve(ensemblePtr); TclSkipTailcall(interp); result = Tcl_EvalObjv(interp, paramc, paramv, 0); if ((result == TCL_OK) && (ensemblePtr->flags & ENSEMBLE_DEAD)) { if (!Tcl_InterpDeleted(interp)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "unknown subcommand handler deleted its ensemble", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ENSEMBLE", "UNKNOWN_DELETED", (char *)NULL); } result = TCL_ERROR; } Tcl_Release(ensemblePtr); /* * On success the result is a list of words that form the command to be * executed. If the list is empty, the ensemble should have been updated, * so ask the ensemble engine to reparse the original command. */ if (result == TCL_OK) { *prefixObjPtr = Tcl_GetObjResult(interp); Tcl_IncrRefCount(*prefixObjPtr); TclDecrRefCount(unknownCmd); Tcl_ResetResult(interp); /* A non-empty list is the replacement command. */ if (TclListObjLength(interp, *prefixObjPtr, &prefixObjc) != TCL_OK) { TclDecrRefCount(*prefixObjPtr); Tcl_AddErrorInfo(interp, "\n while parsing result of " "ensemble unknown subcommand handler"); return TCL_ERROR; } if (prefixObjc > 0) { return TCL_OK; } /* * Empty result => reparse. */ TclDecrRefCount(*prefixObjPtr); return TCL_CONTINUE; } /* * Convert exceptional result to an error. */ if (!Tcl_InterpDeleted(interp)) { if (result != TCL_ERROR) { Tcl_ResetResult(interp); Tcl_SetObjResult(interp, Tcl_NewStringObj( "unknown subcommand handler returned bad code: ", TCL_AUTO_LENGTH)); switch (result) { case TCL_RETURN: Tcl_AppendToObj(Tcl_GetObjResult(interp), "return", TCL_AUTO_LENGTH); break; case TCL_BREAK: Tcl_AppendToObj(Tcl_GetObjResult(interp), "break", TCL_AUTO_LENGTH); break; case TCL_CONTINUE: Tcl_AppendToObj(Tcl_GetObjResult(interp), "continue", TCL_AUTO_LENGTH); break; default: Tcl_AppendPrintfToObj(Tcl_GetObjResult(interp), "%d", result); } Tcl_AddErrorInfo(interp, "\n result of " "ensemble unknown subcommand handler: "); Tcl_AppendObjToErrorInfo(interp, unknownCmd); Tcl_SetErrorCode(interp, "TCL", "ENSEMBLE", "UNKNOWN_RESULT", (char *)NULL); } else { Tcl_AddErrorInfo(interp, "\n (ensemble unknown subcommand handler)"); } } TclDecrRefCount(unknownCmd); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * MakeCachedEnsembleCommand -- * * Caches what has been computed so far to minimize string copying. * Starts by deleting any existing representation but reusing the existing * structure if it is an ensembleCmd. * * Results: * None. * * Side effects: * Converts the internal representation of the given object to an * ensembleCmd. * *---------------------------------------------------------------------- */ static void MakeCachedEnsembleCommand( Tcl_Obj *objPtr, /* Object to cache in. */ EnsembleConfig *ensemblePtr,/* Ensemble implementation. */ Tcl_HashEntry *hPtr, /* What to cache; what the object maps to. */ Tcl_Obj *fix) /* Spelling correction for later error, or NULL * if no correction. */ { EnsembleCmdRep *ensembleCmd; ECRGetInternalRep(objPtr, ensembleCmd); if (ensembleCmd) { TclCleanupCommandMacro(ensembleCmd->token); if (ensembleCmd->fix) { Tcl_DecrRefCount(ensembleCmd->fix); } } else { /* * Replace any old internal representation with a new one. */ ensembleCmd = (EnsembleCmdRep *) Tcl_Alloc(sizeof(EnsembleCmdRep)); ECRSetInternalRep(objPtr, ensembleCmd); } /* * Populate the internal rep. */ ensembleCmd->epoch = ensemblePtr->epoch; ensembleCmd->token = (Command *) ensemblePtr->token; ensembleCmd->token->refCount++; if (fix) { Tcl_IncrRefCount(fix); } ensembleCmd->fix = fix; ensembleCmd->hPtr = hPtr; } /* *---------------------------------------------------------------------- * * DeleteEnsembleConfig -- * * Destroys the data structure used to represent an ensemble. Called when * the procedure for the ensemble is deleted, which happens automatically * if the namespace for the ensemble is deleted. Deleting the procedure * for an ensemble is the right way to initiate cleanup. * * Results: * None. * * Side effects: * Memory is eventually deallocated. * *---------------------------------------------------------------------- */ static void ClearTable( EnsembleConfig *ensemblePtr)/* Ensemble to clear table of. */ { Tcl_HashTable *hash = &ensemblePtr->subcommandTable; if (hash->numEntries != 0) { Tcl_HashSearch search; Tcl_HashEntry *hPtr = Tcl_FirstHashEntry(hash, &search); while (hPtr != NULL) { Tcl_Obj *prefixObj = (Tcl_Obj *) Tcl_GetHashValue(hPtr); Tcl_DecrRefCount(prefixObj); hPtr = Tcl_NextHashEntry(&search); } Tcl_Free(ensemblePtr->subcommandArrayPtr); } Tcl_DeleteHashTable(hash); } static void DeleteEnsembleConfig( void *clientData) /* Ensemble to delete. */ { EnsembleConfig *ensemblePtr = (EnsembleConfig *) clientData; Namespace *nsPtr = ensemblePtr->nsPtr; /* Unlink from the ensemble chain if it not already marked as unlinked. */ if (ensemblePtr->next != ensemblePtr) { EnsembleConfig *ensPtr = (EnsembleConfig *) nsPtr->ensembles; if (ensPtr == ensemblePtr) { nsPtr->ensembles = (Tcl_Ensemble *) ensemblePtr->next; } else { while (ensPtr != NULL) { if (ensPtr->next == ensemblePtr) { ensPtr->next = ensemblePtr->next; break; } ensPtr = ensPtr->next; } } } /* * Mark the namespace as dead so code that uses Tcl_Preserve() can tell * whether disaster happened anyway. */ ensemblePtr->flags |= ENSEMBLE_DEAD; /* * Release the fields that contain pointers. */ ClearTable(ensemblePtr); if (ensemblePtr->subcmdList != NULL) { Tcl_DecrRefCount(ensemblePtr->subcmdList); } if (ensemblePtr->parameterList != NULL) { Tcl_DecrRefCount(ensemblePtr->parameterList); } if (ensemblePtr->subcommandDict != NULL) { Tcl_DecrRefCount(ensemblePtr->subcommandDict); } if (ensemblePtr->unknownHandler != NULL) { Tcl_DecrRefCount(ensemblePtr->unknownHandler); } /* * Arrange for the structure to be reclaimed. This is complex because it is * necessary to react sensibly when an ensemble is deleted during its * initialisation, particularly in the case of an unknown callback. */ Tcl_EventuallyFree(ensemblePtr, TCL_DYNAMIC); } /* *---------------------------------------------------------------------- * * BuildEnsembleConfig -- * * Creates the internal data structures that describe how an ensemble * looks. The structures are a hash map from the full command name to the * Tcl list that describes the implementation prefix words, and a sorted * array of all the full command names to allow for reasonably efficient * handling of an unambiguous prefix. * * Results: * None. * * Side effects: * Reallocates and rebuilds the hash table and array stored at the * ensemblePtr argument. For large ensembles or large namespaces, this is * may be an expensive operation. * *---------------------------------------------------------------------- */ static void BuildEnsembleConfig( EnsembleConfig *ensemblePtr)/* Ensemble to set up. */ { Tcl_HashSearch search; /* Used for scanning the commands in * the namespace for this ensemble. */ Tcl_Size i, j; int isNew; Tcl_HashTable *hash = &ensemblePtr->subcommandTable; Tcl_HashEntry *hPtr; Tcl_Obj *mapDict = ensemblePtr->subcommandDict; Tcl_Obj *subList = ensemblePtr->subcmdList; ClearTable(ensemblePtr); Tcl_InitHashTable(hash, TCL_STRING_KEYS); if (subList) { Tcl_Size subc; Tcl_Obj **subv, *target, *cmdObj, *cmdPrefixObj; const char *name; /* * There is a list of exactly what subcommands go in the table. * Determine the target for each. */ TclListObjGetElements(NULL, subList, &subc, &subv); if (subList == mapDict) { /* * Unusual case where explicit list of subcommands is same value * as the dict mapping to targets. */ for (i = 0; i < subc; i += 2) { name = TclGetString(subv[i]); hPtr = Tcl_CreateHashEntry(hash, name, &isNew); if (!isNew) { cmdObj = (Tcl_Obj *) Tcl_GetHashValue(hPtr); Tcl_DecrRefCount(cmdObj); } Tcl_SetHashValue(hPtr, subv[i + 1]); Tcl_IncrRefCount(subv[i + 1]); name = TclGetString(subv[i + 1]); hPtr = Tcl_CreateHashEntry(hash, name, &isNew); if (isNew) { cmdObj = Tcl_NewStringObj(name, TCL_AUTO_LENGTH); cmdPrefixObj = Tcl_NewListObj(1, &cmdObj); Tcl_SetHashValue(hPtr, cmdPrefixObj); Tcl_IncrRefCount(cmdPrefixObj); } } } else { /* * Usual case where we can freely act on the list and dict. */ for (i = 0; i < subc; i++) { name = TclGetString(subv[i]); hPtr = Tcl_CreateHashEntry(hash, name, &isNew); if (!isNew) { continue; } /* * Lookup target in the dictionary. */ if (mapDict) { Tcl_DictObjGet(NULL, mapDict, subv[i], &target); if (target) { Tcl_SetHashValue(hPtr, target); Tcl_IncrRefCount(target); continue; } } /* * Target was not in the dictionary. Map onto the namespace. * In this case there is no guarantee that the command is * actually there. It is the responsibility of the programmer * (or [::unknown] of course) to provide the procedure. */ cmdObj = Tcl_NewStringObj(name, TCL_AUTO_LENGTH); cmdPrefixObj = Tcl_NewListObj(1, &cmdObj); Tcl_SetHashValue(hPtr, cmdPrefixObj); Tcl_IncrRefCount(cmdPrefixObj); } } } else if (mapDict) { /* * No subcmd list, but there is a mapping dictionary, so use * the keys of that. Convert the contents of the dictionary into the * form required for the internal hashtable of the ensemble. */ Tcl_DictSearch dictSearch; Tcl_Obj *keyObj, *valueObj; int done; Tcl_DictObjFirst(NULL, ensemblePtr->subcommandDict, &dictSearch, &keyObj, &valueObj, &done); while (!done) { const char *name = TclGetString(keyObj); hPtr = Tcl_CreateHashEntry(hash, name, &isNew); Tcl_SetHashValue(hPtr, valueObj); Tcl_IncrRefCount(valueObj); Tcl_DictObjNext(&dictSearch, &keyObj, &valueObj, &done); } } else { /* * Use the array of patterns and the hash table whose keys are the * commands exported by the namespace. The corresponding values do not * matter here. Filter the commands in the namespace against the * patterns in the export list to find out what commands are actually * exported. Use an intermediate hash table to make memory management * easier and to make exact matching much easier. * * Suggestion for future enhancement: Compute the unique prefixes and * place them in the hash too for even faster matching. */ hPtr = Tcl_FirstHashEntry(&ensemblePtr->nsPtr->cmdTable, &search); for (; hPtr!= NULL ; hPtr=Tcl_NextHashEntry(&search)) { char *nsCmdName = (char *) /* Name of command in namespace. */ Tcl_GetHashKey(&ensemblePtr->nsPtr->cmdTable, hPtr); for (i=0 ; insPtr->numExportPatterns ; i++) { if (Tcl_StringMatch(nsCmdName, ensemblePtr->nsPtr->exportArrayPtr[i])) { hPtr = Tcl_CreateHashEntry(hash, nsCmdName, &isNew); /* * Remember, hash entries have a full reference to the * substituted part of the command (as a list) as their * content! */ if (isNew) { Tcl_Obj *cmdObj, *cmdPrefixObj; TclNewObj(cmdObj); Tcl_AppendStringsToObj(cmdObj, ensemblePtr->nsPtr->fullName, (ensemblePtr->nsPtr->parentPtr ? "::" : ""), nsCmdName, (char *)NULL); cmdPrefixObj = Tcl_NewListObj(1, &cmdObj); Tcl_SetHashValue(hPtr, cmdPrefixObj); Tcl_IncrRefCount(cmdPrefixObj); } break; } } } } if (hash->numEntries == 0) { ensemblePtr->subcommandArrayPtr = NULL; return; } /* * Create a sorted array of all subcommands in the ensemble. Hash tables * are all very well for a quick look for an exact match, but they can't * determine things like whether a string is a prefix of another, at least * not without a lot of preparation, and they're not useful for generating * the error message either. * * Do this by filling an array with the names: Use the hash keys * directly to save a copy since any time we change the array we change * the hash too, and vice versa, and run quicksort over the array. */ ensemblePtr->subcommandArrayPtr = (char **) Tcl_Alloc(sizeof(char *) * hash->numEntries); /* * Fill the array from both ends as this reduces the likelihood of * performance problems in qsort(). This makes this code much more opaque, * but the naive alternatve: * * for (hPtr=Tcl_FirstHashEntry(hash,&search),i=0 ; * hPtr!=NULL ; hPtr=Tcl_NextHashEntry(&search),i++) { * ensemblePtr->subcommandArrayPtr[i] = Tcl_GetHashKey(hash, &hPtr); * } * * can produce long runs of precisely ordered table entries when the * commands in the namespace are declared in a sorted fashion, which is an * ordering some people like, and the hashing functions or the command * names themselves are fairly unfortunate. Filling from both ends means * that it requires active malice, and probably a debugger, to get qsort() * to have awful runtime behaviour. */ i = 0; j = hash->numEntries; hPtr = Tcl_FirstHashEntry(hash, &search); while (hPtr != NULL) { ensemblePtr->subcommandArrayPtr[i++] = (char *) Tcl_GetHashKey(hash, hPtr); hPtr = Tcl_NextHashEntry(&search); if (hPtr == NULL) { break; } ensemblePtr->subcommandArrayPtr[--j] = (char *) Tcl_GetHashKey(hash, hPtr); hPtr = Tcl_NextHashEntry(&search); } if (hash->numEntries > 1) { qsort(ensemblePtr->subcommandArrayPtr, hash->numEntries, sizeof(char *), NsEnsembleStringOrder); } } /* *---------------------------------------------------------------------- * * NsEnsembleStringOrder -- * * Helper to for use with qsort() that compares two array entries that * contain string pointers. * * Results: * -1 if the first string is smaller, 1 if the second string is smaller, * and 0 if they are equal. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int NsEnsembleStringOrder( const void *strPtr1, /* Points to first array entry */ const void *strPtr2) /* Points to second array entry */ { return strcmp(*(const char **)strPtr1, *(const char **)strPtr2); } /* *---------------------------------------------------------------------- * * FreeEnsembleCmdRep -- * * Destroys the internal representation of a Tcl_Obj that has been * holding information about a command in an ensemble. * * Results: * None. * * Side effects: * Memory is deallocated. If this held the last reference to a * namespace's main structure, that main structure will also be * destroyed. * *---------------------------------------------------------------------- */ static void FreeEnsembleCmdRep( Tcl_Obj *objPtr) { EnsembleCmdRep *ensembleCmd; ECRGetInternalRep(objPtr, ensembleCmd); TclCleanupCommandMacro(ensembleCmd->token); if (ensembleCmd->fix) { Tcl_DecrRefCount(ensembleCmd->fix); } Tcl_Free(ensembleCmd); } /* *---------------------------------------------------------------------- * * DupEnsembleCmdRep -- * * Makes one Tcl_Obj into a copy of another that is a subcommand of an * ensemble. * * Results: * None. * * Side effects: * Memory is allocated, and the namespace that the ensemble is built on * top of gains another reference. * *---------------------------------------------------------------------- */ static void DupEnsembleCmdRep( Tcl_Obj *objPtr, Tcl_Obj *copyPtr) { EnsembleCmdRep *ensembleCmd; EnsembleCmdRep *ensembleCopy = (EnsembleCmdRep *) Tcl_Alloc(sizeof(EnsembleCmdRep)); ECRGetInternalRep(objPtr, ensembleCmd); ECRSetInternalRep(copyPtr, ensembleCopy); ensembleCopy->epoch = ensembleCmd->epoch; ensembleCopy->token = ensembleCmd->token; ensembleCopy->token->refCount++; ensembleCopy->fix = ensembleCmd->fix; if (ensembleCopy->fix) { Tcl_IncrRefCount(ensembleCopy->fix); } ensembleCopy->hPtr = ensembleCmd->hPtr; } /* *---------------------------------------------------------------------- * * TclCompileEnsemble -- * * Procedure called to compile an ensemble command. Note that most * ensembles are not compiled, since modifying a compiled ensemble causes * a invalidation of all existing bytecode (expensive!) which is not * normally warranted. * * Results: * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer * evaluation to runtime. * * Side effects: * Instructions are added to envPtr to execute the subcommands of the * ensemble at runtime if a compile-time mapping is possible. * *---------------------------------------------------------------------- */ int TclCompileEnsemble( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; Tcl_Token *tokenPtr = TokenAfter(parsePtr->tokenPtr); Tcl_Obj *mapObj, *subcmdObj, *targetCmdObj, *listObj, **elems; Tcl_Obj *replaced, *replacement; Tcl_Command ensemble = (Tcl_Command) cmdPtr; Command *oldCmdPtr = cmdPtr, *newCmdPtr; int result, flags = 0, depth = 1, invokeAnyway = 0; int ourResult = TCL_ERROR; Tcl_Size i, len, numBytes; const char *word; TclNewObj(replaced); Tcl_IncrRefCount(replaced); if (parsePtr->numWords <= depth) { goto tryCompileToInv; } if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { /* * Too hard. */ goto tryCompileToInv; } /* * This is where we return to if we are parsing multiple nested compiled * ensembles. [info object] is such a beast. */ checkNextWord: word = tokenPtr[1].start; numBytes = tokenPtr[1].size; /* * There's a sporting chance we'll be able to compile this. But now we * must check properly. To do that, check that we're compiling an ensemble * that has a compilable command as its appropriate subcommand. */ if (Tcl_GetEnsembleMappingDict(NULL, ensemble, &mapObj) != TCL_OK || mapObj == NULL) { /* * Either not an ensemble or a mapping isn't installed. Crud. Too hard * to proceed. */ goto tryCompileToInv; } /* * Also refuse to compile anything that uses a formal parameter list for * now, on the grounds that it is too complex. */ if (Tcl_GetEnsembleParameterList(NULL, ensemble, &listObj) != TCL_OK || listObj != NULL) { /* * Figuring out how to compile this has become too much. Bail out. */ goto tryCompileToInv; } /* * Next, get the flags. We need them on several code paths so that we can * know whether we're to do prefix matching. */ (void) Tcl_GetEnsembleFlags(NULL, ensemble, &flags); /* * Check to see if there's also a subcommand list; must check to see if * the subcommand we are calling is in that list if it exists, since that * list filters the entries in the map. */ (void) Tcl_GetEnsembleSubcommandList(NULL, ensemble, &listObj); if (listObj != NULL) { Tcl_Size sclen; const char *str; Tcl_Obj *matchObj = NULL; if (TclListObjGetElements(NULL, listObj, &len, &elems) != TCL_OK) { goto tryCompileToInv; } for (i=0 ; icompileProc) || newCmdPtr->nsPtr->flags & NS_SUPPRESS_COMPILATION || newCmdPtr->flags & CMD_HAS_EXEC_TRACES || ((Interp *) interp)->flags & DONT_COMPILE_CMDS_INLINE) { /* * Maps to an undefined command or a command without a compiler. * Cannot compile. */ goto cleanup; } cmdPtr = newCmdPtr; depth++; /* * See whether we have a nested ensemble. If we do, we can go round the * mulberry bush again, consuming the next word. */ if (cmdPtr->compileProc == TclCompileEnsemble) { tokenPtr = TokenAfter(tokenPtr); if ((int)parsePtr->numWords < depth + 1 || tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { /* * Too hard because the user has done something unpleasant like * omitting the sub-ensemble's command name or used a non-constant * name for a sub-ensemble's command name; we respond by bailing * out completely (this is a rare case). [Bug 6d2f249a01] */ goto cleanup; } ensemble = (Tcl_Command) cmdPtr; goto checkNextWord; } /* * Now that the mapping process is done we actually try to compile. * If there is a subcommand compiler and that successfully produces code, * we'll use that. Otherwise, we fall back to generating opcodes to do the * invoke at runtime. */ invokeAnyway = 1; if (TCL_OK == TclAttemptCompileProc(interp, parsePtr, depth, cmdPtr, envPtr)) { ourResult = TCL_OK; goto cleanup; } /* * Throw out any line information generated by the failed compile attempt. */ while (mapPtr->nuloc > eclIndex + 1) { mapPtr->nuloc--; Tcl_Free(mapPtr->loc[mapPtr->nuloc].line); mapPtr->loc[mapPtr->nuloc].line = NULL; } /* * Reset the index of next command. Toss out any from failed nested * partial compiles. */ envPtr->numCommands = mapPtr->nuloc; /* * Failed to do a full compile for some reason. Try to do a direct invoke * instead of going through the ensemble lookup process again. */ tryCompileToInv: if (depth < 250) { if (depth > 1) { if (!invokeAnyway) { cmdPtr = oldCmdPtr; depth--; } } /* * The length of the "replaced" list must be depth-1. Trim back * any extra elements that might have been appended by failing * pathways above. */ (void) Tcl_ListObjReplace(NULL, replaced, depth-1, LIST_MAX, 0, NULL); /* * TODO: Reconsider whether we ought to call CompileToInvokedCommand() * when depth==1. In that case we are choosing to emit the * INST_INVOKE_REPLACE bytecode when there is in fact no replacing * to be done. It would be equally functional and presumably more * performant to fall through to cleanup below, return TCL_ERROR, * and let the compiler harness emit the INST_INVOKE_STK * implementation for us. */ CompileToInvokedCommand(interp, parsePtr, replaced, cmdPtr, envPtr); ourResult = TCL_OK; } /* * Release the memory we allocated. If we've got here, we've either done * something useful or we're in a case that we can't compile at all and * we're just giving up. */ cleanup: Tcl_DecrRefCount(replaced); return ourResult; } int TclAttemptCompileProc( Tcl_Interp *interp, Tcl_Parse *parsePtr, Tcl_Size depth, Command *cmdPtr, CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; int result; Tcl_Size i; Tcl_Token *saveTokenPtr = parsePtr->tokenPtr; Tcl_Size savedStackDepth = envPtr->currStackDepth; Tcl_Size savedCodeNext = envPtr->codeNext - envPtr->codeStart; Tcl_Size savedAuxDataArrayNext = envPtr->auxDataArrayNext; Tcl_Size savedExceptArrayNext = envPtr->exceptArrayNext; #ifdef TCL_COMPILE_DEBUG Tcl_Size savedExceptDepth = envPtr->exceptDepth; #endif if (cmdPtr->compileProc == NULL) { return TCL_ERROR; } /* * Advance parsePtr->tokenPtr so that it points at the last subcommand. * This will be wrong but it will not matter, and it will put the * tokens for the arguments in the right place without the need to * allocate a synthetic Tcl_Parse struct or copy tokens around. */ for (i = 0; i < depth - 1; i++) { parsePtr->tokenPtr = TokenAfter(parsePtr->tokenPtr); } parsePtr->numWords -= (depth - 1); /* * Shift the line information arrays to account for different word * index values. */ mapPtr->loc[eclIndex].line += (depth - 1); mapPtr->loc[eclIndex].next += (depth - 1); /* * Hand off compilation to the subcommand compiler. At last! */ result = cmdPtr->compileProc(interp, parsePtr, cmdPtr, envPtr); /* * Undo the shift. */ mapPtr->loc[eclIndex].line -= (depth - 1); mapPtr->loc[eclIndex].next -= (depth - 1); parsePtr->numWords += (depth - 1); parsePtr->tokenPtr = saveTokenPtr; /* * If our target failed to compile, revert any data from failed partial * compiles. Note that envPtr->numCommands need not be checked because * we avoid compiling subcommands that recursively call TclCompileScript(). */ #ifdef TCL_COMPILE_DEBUG if (envPtr->exceptDepth != savedExceptDepth) { Tcl_Panic("ExceptionRange Starts and Ends do not balance"); } #endif if (result != TCL_OK) { ExceptionAux *auxPtr = envPtr->exceptAuxArrayPtr; for (i = 0; i < savedExceptArrayNext; i++) { while (auxPtr->numBreakTargets > 0 && (Tcl_Size) auxPtr->breakTargets[auxPtr->numBreakTargets - 1] >= savedCodeNext) { auxPtr->numBreakTargets--; } while (auxPtr->numContinueTargets > 0 && (Tcl_Size) auxPtr->continueTargets[auxPtr->numContinueTargets - 1] >= savedCodeNext) { auxPtr->numContinueTargets--; } auxPtr++; } envPtr->exceptArrayNext = savedExceptArrayNext; if (savedAuxDataArrayNext != envPtr->auxDataArrayNext) { AuxData *auxDataPtr = envPtr->auxDataArrayPtr; AuxData *auxDataEnd = auxDataPtr; auxDataPtr += savedAuxDataArrayNext; auxDataEnd += envPtr->auxDataArrayNext; while (auxDataPtr < auxDataEnd) { if (auxDataPtr->type->freeProc != NULL) { auxDataPtr->type->freeProc(auxDataPtr->clientData); } auxDataPtr++; } envPtr->auxDataArrayNext = savedAuxDataArrayNext; } envPtr->currStackDepth = savedStackDepth; envPtr->codeNext = envPtr->codeStart + savedCodeNext; #ifdef TCL_COMPILE_DEBUG } else { /* * Confirm that the command compiler generated a single value on * the stack as its result. This is only done in debugging mode, * as it *should* be correct and normal users have no reasonable * way to fix it anyway. */ int diff = envPtr->currStackDepth - savedStackDepth; if (diff != 1) { Tcl_Panic("bad stack adjustment when compiling" " %.*s (was %d instead of 1)", (int)parsePtr->tokenPtr->size, parsePtr->tokenPtr->start, diff); } #endif } return result; } /* * How to compile a subcommand to a _replacing_ invoke of its implementation * command. */ static void CompileToInvokedCommand( Tcl_Interp *interp, Tcl_Parse *parsePtr, Tcl_Obj *replacements, Command *cmdPtr, CompileEnv *envPtr) /* Holds resulting instructions. */ { DefineLineInformation; Tcl_Token *tokPtr; Tcl_Obj *objPtr, **words; const char *bytes; int cmdLit, extraLiteralFlags = LITERAL_CMD_NAME; Tcl_Size i, numWords, length; /* * Push the words of the command. Take care; the command words may be * scripts that have backslashes in them, and [info frame 0] can see the * difference. Hence the call to TclContinuationsEnterDerived... */ TclListObjGetElements(NULL, replacements, &numWords, &words); for (i = 0, tokPtr = parsePtr->tokenPtr; i < parsePtr->numWords; i++, tokPtr = TokenAfter(tokPtr)) { if (i > 0 && i <= numWords) { bytes = TclGetStringFromObj(words[i - 1], &length); PushLiteral(envPtr, bytes, length); continue; } SetLineInformation(i); if (tokPtr->type == TCL_TOKEN_SIMPLE_WORD) { int literal = TclRegisterLiteral(envPtr, tokPtr[1].start, tokPtr[1].size, 0); if (envPtr->clNext) { TclContinuationsEnterDerived( TclFetchLiteral(envPtr, literal), tokPtr[1].start - envPtr->source, envPtr->clNext); } TclEmitPush(literal, envPtr); } else { CompileTokens(envPtr, tokPtr, interp); } } /* * Push the name of the command we're actually dispatching to as part of * the implementation. */ TclNewObj(objPtr); Tcl_GetCommandFullName(interp, (Tcl_Command) cmdPtr, objPtr); bytes = TclGetStringFromObj(objPtr, &length); if ((cmdPtr != NULL) && (cmdPtr->flags & CMD_VIA_RESOLVER)) { extraLiteralFlags |= LITERAL_UNSHARED; } cmdLit = TclRegisterLiteral(envPtr, bytes, length, extraLiteralFlags); TclSetCmdNameObj(interp, TclFetchLiteral(envPtr, cmdLit), cmdPtr); TclEmitPush(cmdLit, envPtr); TclDecrRefCount(objPtr); /* * Do the replacing dispatch. */ TclEmitInvoke(envPtr, INST_INVOKE_REPLACE, parsePtr->numWords, numWords + 1); } /* * Helpers that do issuing of instructions for commands that "don't have * compilers" (well, they do; these). They all work by just generating base * code to invoke the command; they're intended for ensemble subcommands so * that the costs of INST_INVOKE_REPLACE can be avoided where we can work out * that they're not needed. * * Note that these are NOT suitable for commands where there's an argument * that is a script, as an [info level] or [info frame] in the inner context * can see the difference. */ static int CompileBasicNArgCommand( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { Tcl_Obj *objPtr; TclNewObj(objPtr); Tcl_IncrRefCount(objPtr); Tcl_GetCommandFullName(interp, (Tcl_Command) cmdPtr, objPtr); TclCompileInvocation(interp, parsePtr->tokenPtr, objPtr, parsePtr->numWords, envPtr); Tcl_DecrRefCount(objPtr); return TCL_OK; } int TclCompileBasic0ArgCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Verify that the number of arguments is correct; that's the only case * that we know will avoid the call to Tcl_WrongNumArgs() at invoke time, * which is the only code that sees the shenanigans of ensemble dispatch. */ if (parsePtr->numWords != 1) { return TCL_ERROR; } return CompileBasicNArgCommand(interp, parsePtr, cmdPtr, envPtr); } int TclCompileBasic1ArgCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Verify that the number of arguments is correct; that's the only case * that we know will avoid the call to Tcl_WrongNumArgs() at invoke time, * which is the only code that sees the shenanigans of ensemble dispatch. */ if (parsePtr->numWords != 2) { return TCL_ERROR; } return CompileBasicNArgCommand(interp, parsePtr, cmdPtr, envPtr); } int TclCompileBasic2ArgCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Verify that the number of arguments is correct; that's the only case * that we know will avoid the call to Tcl_WrongNumArgs() at invoke time, * which is the only code that sees the shenanigans of ensemble dispatch. */ if (parsePtr->numWords != 3) { return TCL_ERROR; } return CompileBasicNArgCommand(interp, parsePtr, cmdPtr, envPtr); } int TclCompileBasic3ArgCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Verify that the number of arguments is correct; that's the only case * that we know will avoid the call to Tcl_WrongNumArgs() at invoke time, * which is the only code that sees the shenanigans of ensemble dispatch. */ if (parsePtr->numWords != 4) { return TCL_ERROR; } return CompileBasicNArgCommand(interp, parsePtr, cmdPtr, envPtr); } int TclCompileBasic0Or1ArgCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Verify that the number of arguments is correct; that's the only case * that we know will avoid the call to Tcl_WrongNumArgs() at invoke time, * which is the only code that sees the shenanigans of ensemble dispatch. */ if (parsePtr->numWords != 1 && parsePtr->numWords != 2) { return TCL_ERROR; } return CompileBasicNArgCommand(interp, parsePtr, cmdPtr, envPtr); } int TclCompileBasic1Or2ArgCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Verify that the number of arguments is correct; that's the only case * that we know will avoid the call to Tcl_WrongNumArgs() at invoke time, * which is the only code that sees the shenanigans of ensemble dispatch. */ if (parsePtr->numWords != 2 && parsePtr->numWords != 3) { return TCL_ERROR; } return CompileBasicNArgCommand(interp, parsePtr, cmdPtr, envPtr); } int TclCompileBasic2Or3ArgCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Verify that the number of arguments is correct; that's the only case * that we know will avoid the call to Tcl_WrongNumArgs() at invoke time, * which is the only code that sees the shenanigans of ensemble dispatch. */ if (parsePtr->numWords != 3 && parsePtr->numWords != 4) { return TCL_ERROR; } return CompileBasicNArgCommand(interp, parsePtr, cmdPtr, envPtr); } int TclCompileBasic0To2ArgCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Verify that the number of arguments is correct; that's the only case * that we know will avoid the call to Tcl_WrongNumArgs() at invoke time, * which is the only code that sees the shenanigans of ensemble dispatch. */ if (parsePtr->numWords < 1 || parsePtr->numWords > 3) { return TCL_ERROR; } return CompileBasicNArgCommand(interp, parsePtr, cmdPtr, envPtr); } int TclCompileBasic1To3ArgCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Verify that the number of arguments is correct; that's the only case * that we know will avoid the call to Tcl_WrongNumArgs() at invoke time, * which is the only code that sees the shenanigans of ensemble dispatch. */ if (parsePtr->numWords < 2 || parsePtr->numWords > 4) { return TCL_ERROR; } return CompileBasicNArgCommand(interp, parsePtr, cmdPtr, envPtr); } int TclCompileBasicMin0ArgCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Verify that the number of arguments is correct; that's the only case * that we know will avoid the call to Tcl_WrongNumArgs() at invoke time, * which is the only code that sees the shenanigans of ensemble dispatch. */ if ((int)parsePtr->numWords < 1) { return TCL_ERROR; } return CompileBasicNArgCommand(interp, parsePtr, cmdPtr, envPtr); } int TclCompileBasicMin1ArgCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Verify that the number of arguments is correct; that's the only case * that we know will avoid the call to Tcl_WrongNumArgs() at invoke time, * which is the only code that sees the shenanigans of ensemble dispatch. */ if ((int)parsePtr->numWords < 2) { return TCL_ERROR; } return CompileBasicNArgCommand(interp, parsePtr, cmdPtr, envPtr); } int TclCompileBasicMin2ArgCmd( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Parse *parsePtr, /* Points to a parse structure for the command * created by Tcl_ParseCommand. */ Command *cmdPtr, /* Points to definition of command being * compiled. */ CompileEnv *envPtr) /* Holds resulting instructions. */ { /* * Verify that the number of arguments is correct; that's the only case * that we know will avoid the call to Tcl_WrongNumArgs() at invoke time, * which is the only code that sees the shenanigans of ensemble dispatch. */ if ((int)parsePtr->numWords < 3) { return TCL_ERROR; } return CompileBasicNArgCommand(interp, parsePtr, cmdPtr, envPtr); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclEnv.c0000644000175000017500000005442114726623136014577 0ustar sergeisergei/* * tclEnv.c -- * * Tcl support for environment variables, including a setenv function. * This file contains the generic portion of the environment module. It * is primarily responsible for keeping the "env" arrays in sync with the * system environment variables. * * Copyright © 1991-1994 The Regents of the University of California. * Copyright © 1994-1998 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" TCL_DECLARE_MUTEX(envMutex) /* To serialize access to environ. */ #if defined(_WIN32) # define tenviron _wenviron # define tenviron2utfdstr(str, dsPtr) (Tcl_DStringInit(dsPtr), \ (char *)Tcl_Char16ToUtfDString((const unsigned short *)(str), -1, (dsPtr))) # define utf2tenvirondstr(str, dsPtr) (Tcl_DStringInit(dsPtr), \ (const WCHAR *)Tcl_UtfToChar16DString((str), -1, (dsPtr))) # define techar WCHAR # ifdef USE_PUTENV # define putenv(env) _wputenv((const wchar_t *)env) # endif #else # define tenviron environ # define tenviron2utfdstr(str, dsPtr) \ Tcl_ExternalToUtfDString(NULL, str, -1, dsPtr) # define utf2tenvirondstr(str, dsPtr) \ Tcl_UtfToExternalDString(NULL, str, -1, dsPtr) # define techar char #endif /* MODULE_SCOPE */ size_t TclEnvEpoch = 0; /* Epoch of the tcl environment * (if changed with tcl-env). */ static struct { Tcl_Size cacheSize; /* Number of env strings in cache. */ char **cache; /* Array containing all of the environment * strings that Tcl has allocated. */ #ifndef USE_PUTENV techar **ourEnviron; /* Cache of the array that we allocate. We * need to track this in case another * subsystem swaps around the environ array * like we do. */ Tcl_Size ourEnvironSize; /* Non-zero means that the environ array was * malloced and has this many total entries * allocated to it (not all may be in use at * once). Zero means that the environment * array is in its original static state. */ #endif } env; #define tNTL sizeof(techar) /* * Declarations for local functions defined in this file: */ static char * EnvTraceProc(void *clientData, Tcl_Interp *interp, const char *name1, const char *name2, int flags); static void ReplaceString(const char *oldStr, char *newStr); MODULE_SCOPE void TclSetEnv(const char *name, const char *value); MODULE_SCOPE void TclUnsetEnv(const char *name); /* *---------------------------------------------------------------------- * * TclSetupEnv -- * * This function is invoked for an interpreter to make environment * variables accessible from that interpreter via the "env" associative * array. * * Results: * None. * * Side effects: * The interpreter is added to a list of interpreters managed by us, so * that its view of envariables can be kept consistent with the view in * other interpreters. If this is the first call to TclSetupEnv, then * additional initialization happens, such as copying the environment to * dynamically-allocated space for ease of management. * *---------------------------------------------------------------------- */ void TclSetupEnv( Tcl_Interp *interp) /* Interpreter whose "env" array is to be * managed. */ { Var *varPtr, *arrayPtr; Tcl_Obj *varNamePtr; Tcl_DString envString; Tcl_HashTable namesHash; Tcl_HashEntry *hPtr; Tcl_HashSearch search; /* * Synchronize the values in the environ array with the contents of the * Tcl "env" variable. To do this: * 1) Remove the trace that fires when the "env" var is updated. * 2) Find the existing contents of the "env", storing in a hash table. * 3) Create/update elements for each environ variable, removing * elements from the hash table as we go. * 4) Remove the elements for each remaining entry in the hash table, * which must have existed before yet have no analog in the environ * variable. * 5) Add a trace that synchronizes the "env" array. */ Tcl_UntraceVar2(interp, "env", NULL, TCL_GLOBAL_ONLY | TCL_TRACE_WRITES | TCL_TRACE_UNSETS | TCL_TRACE_READS | TCL_TRACE_ARRAY, EnvTraceProc, NULL); /* * Find out what elements are currently in the global env array. */ TclNewLiteralStringObj(varNamePtr, "env"); Tcl_IncrRefCount(varNamePtr); Tcl_InitObjHashTable(&namesHash); varPtr = TclObjLookupVarEx(interp, varNamePtr, NULL, TCL_GLOBAL_ONLY, /*msg*/ 0, /*createPart1*/ 0, /*createPart2*/ 0, &arrayPtr); TclFindArrayPtrElements(varPtr, &namesHash); #if defined(_WIN32) if (tenviron == NULL) { /* * When we are started from main(), the _wenviron array could * be NULL and will be initialized by the first _wgetenv() call. */ (void) _wgetenv(L"WINDIR"); } #endif /* * Go through the environment array and transfer its values into Tcl. At * the same time, remove those elements we add/update from the hash table * of existing elements, so that after this part processes, that table * will hold just the parts to remove. */ if (tenviron[0] != NULL) { int i; Tcl_MutexLock(&envMutex); for (i = 0; tenviron[i] != NULL; i++) { Tcl_Obj *obj1, *obj2; const char *p1; char *p2; p1 = tenviron2utfdstr(tenviron[i], &envString); if (p1 == NULL) { /* Ignore what cannot be decoded (should not happen) */ continue; } p2 = (char *)strchr(p1, '='); if (p2 == NULL) { /* * This condition seem to happen occasionally under some * versions of Solaris, or when encoding accidents swallow the * '='; ignore the entry. */ Tcl_DStringFree(&envString); continue; } p2++; p2[-1] = '\0'; #if defined(_WIN32) /* * Enforce PATH and COMSPEC to be all uppercase. This eliminates * additional trace logic otherwise required in init.tcl. */ if (strcasecmp(p1, "PATH") == 0) { p1 = "PATH"; } else if (strcasecmp(p1, "COMSPEC") == 0) { p1 = "COMSPEC"; } #endif obj1 = Tcl_NewStringObj(p1, -1); obj2 = Tcl_NewStringObj(p2, -1); Tcl_DStringFree(&envString); Tcl_IncrRefCount(obj1); Tcl_IncrRefCount(obj2); Tcl_ObjSetVar2(interp, varNamePtr, obj1, obj2, TCL_GLOBAL_ONLY); hPtr = Tcl_FindHashEntry(&namesHash, obj1); if (hPtr != NULL) { Tcl_DeleteHashEntry(hPtr); } Tcl_DecrRefCount(obj1); Tcl_DecrRefCount(obj2); } Tcl_MutexUnlock(&envMutex); } /* * Delete those elements that existed in the array but which had no * counterparts in the environment array. */ for (hPtr=Tcl_FirstHashEntry(&namesHash, &search); hPtr!=NULL; hPtr=Tcl_NextHashEntry(&search)) { Tcl_Obj *elemName = (Tcl_Obj *)Tcl_GetHashValue(hPtr); TclObjUnsetVar2(interp, varNamePtr, elemName, TCL_GLOBAL_ONLY); } Tcl_DeleteHashTable(&namesHash); Tcl_DecrRefCount(varNamePtr); /* * Re-establish the trace. */ Tcl_TraceVar2(interp, "env", NULL, TCL_GLOBAL_ONLY | TCL_TRACE_WRITES | TCL_TRACE_UNSETS | TCL_TRACE_READS | TCL_TRACE_ARRAY, EnvTraceProc, NULL); } /* *---------------------------------------------------------------------- * * TclSetEnv -- * * Set an environment variable, replacing an existing value or creating a * new variable if there doesn't exist a variable by the given name. This * function is intended to be a stand-in for the UNIX "setenv" function * so that applications using that function will interface properly to * Tcl. To make it a stand-in, the Makefile must define "TclSetEnv" to * "setenv". * * Results: * None. * * Side effects: * The environ array gets updated. * *---------------------------------------------------------------------- */ void TclSetEnv( const char *name, /* Name of variable whose value is to be set * (UTF-8). */ const char *value) /* New value for variable (UTF-8). */ { Tcl_DString envString; Tcl_Size nameLength, valueLength; Tcl_Size index, length; char *p, *oldValue; const techar *p2; /* * Figure out where the entry is going to go. If the name doesn't already * exist, enlarge the array if necessary to make room. If the name exists, * free its old entry. */ Tcl_MutexLock(&envMutex); index = TclpFindVariable(name, &length); if (index == TCL_INDEX_NONE) { #ifndef USE_PUTENV /* * We need to handle the case where the environment may be changed * outside our control. ourEnvironSize is only valid if the current * environment is the one we allocated. [Bug 979640] */ if ((env.ourEnviron != tenviron) || (length+2 > env.ourEnvironSize)) { techar **newEnviron = (techar **)Tcl_Alloc((length + 5) * sizeof(techar *)); memcpy(newEnviron, tenviron, length * sizeof(techar *)); if ((env.ourEnvironSize != 0) && (env.ourEnviron != NULL)) { Tcl_Free(env.ourEnviron); } tenviron = (env.ourEnviron = newEnviron); env.ourEnvironSize = length + 5; } index = length; tenviron[index + 1] = NULL; #endif /* USE_PUTENV */ oldValue = NULL; nameLength = strlen(name); } else { const char *oldEnv; /* * Compare the new value to the existing value. If they're the same * then quit immediately (e.g. don't rewrite the value or propagate it * to other interpreters). Otherwise, when there are N interpreters * there will be N! propagations of the same value among the * interpreters. */ oldEnv = tenviron2utfdstr(tenviron[index], &envString); if (oldEnv == NULL || strcmp(value, oldEnv + (length + 1)) == 0) { Tcl_DStringFree(&envString); /* OK even if oldEnv is NULL */ Tcl_MutexUnlock(&envMutex); return; } Tcl_DStringFree(&envString); oldValue = (char *)tenviron[index]; nameLength = length; } /* * Create a new entry. Build a complete UTF string that contains a * "name=value" pattern. Then convert the string to the native encoding, * and set the environ array value. */ valueLength = strlen(value); p = (char *)Tcl_Alloc(nameLength + valueLength + 2); memcpy(p, name, nameLength); p[nameLength] = '='; memcpy(p+nameLength+1, value, valueLength+1); p2 = utf2tenvirondstr(p, &envString); if (p2 == NULL) { /* No way to signal error from here :-( but should not happen */ Tcl_Free(p); Tcl_MutexUnlock(&envMutex); return; } /* * Copy the native string to heap memory. */ p = (char *)Tcl_Realloc(p, Tcl_DStringLength(&envString) + tNTL); memcpy(p, p2, Tcl_DStringLength(&envString) + tNTL); Tcl_DStringFree(&envString); #ifdef USE_PUTENV /* * Update the system environment. */ putenv(p); index = TclpFindVariable(name, &length); #else tenviron[index] = (techar *)p; #endif /* USE_PUTENV */ /* * Watch out for versions of putenv that copy the string (e.g. VC++). In * this case we need to free the string immediately. Otherwise update the * string in the cache. */ if ((index != TCL_INDEX_NONE) && (tenviron[index] == (techar *)p)) { ReplaceString(oldValue, p); #ifdef HAVE_PUTENV_THAT_COPIES } else { /* * This putenv() copies instead of taking ownership. */ Tcl_Free(p); #endif /* HAVE_PUTENV_THAT_COPIES */ } Tcl_MutexUnlock(&envMutex); } /* *---------------------------------------------------------------------- * * Tcl_PutEnv -- * * Set an environment variable. Similar to setenv except that the * information is passed in a single string of the form NAME=value, * rather than as separate name strings. This function is intended to be * a stand-in for the UNIX "putenv" function so that applications using * that function will interface properly to Tcl. To make it a stand-in, * the Makefile will define "Tcl_PutEnv" to "putenv". * * Results: * None. * * Side effects: * The environ array gets updated, as do all of the interpreters that we * manage. * *---------------------------------------------------------------------- */ int Tcl_PutEnv( const char *assignment) /* Info about environment variable in the form * NAME=value. (native) */ { Tcl_DString nameString; const char *name; char *value; if (assignment == NULL) { return 0; } /* * First convert the native string to Utf. Then separate the string into * name and value parts, and call TclSetEnv to do all of the real work. */ name = Tcl_ExternalToUtfDString(NULL, assignment, TCL_INDEX_NONE, &nameString); value = (char *)strchr(name, '='); if ((value != NULL) && (value != name)) { value[0] = '\0'; #if defined(_WIN32) if (tenviron == NULL) { /* * When we are started from main(), the _wenviron array could * be NULL and will be initialized by the first _wgetenv() call. */ (void) _wgetenv(L"WINDIR"); } #endif TclSetEnv(name, value+1); } TclEnvEpoch++; Tcl_DStringFree(&nameString); return 0; } /* *---------------------------------------------------------------------- * * TclUnsetEnv -- * * Remove an environment variable, updating the "env" arrays in all * interpreters managed by us. This function is intended to replace the * UNIX "unsetenv" function (but to do this the Makefile must be modified * to redefine "TclUnsetEnv" to "unsetenv". * * Results: * None. * * Side effects: * Interpreters are updated, as is environ. * *---------------------------------------------------------------------- */ void TclUnsetEnv( const char *name) /* Name of variable to remove (UTF-8). */ { char *oldValue; Tcl_Size length, index; #ifdef USE_PUTENV_FOR_UNSET Tcl_DString envString; char *string; #else char **envPtr; #endif /* USE_PUTENV_FOR_UNSET */ Tcl_MutexLock(&envMutex); index = TclpFindVariable(name, &length); /* * First make sure that the environment variable exists to avoid doing * needless work and to avoid recursion on the unset. */ if (index == -1) { Tcl_MutexUnlock(&envMutex); return; } /* * Remember the old value so we can free it if Tcl created the string. */ oldValue = (char *)tenviron[index]; /* * Update the system environment. This must be done before we update the * interpreters or we will recurse. */ #ifdef USE_PUTENV_FOR_UNSET /* * For those platforms that support putenv to unset, Linux indicates * that no = should be included, and Windows requires it. */ #if defined(_WIN32) string = (char *)Tcl_Alloc(length + 2); memcpy(string, name, length); string[length] = '='; string[length+1] = '\0'; #else string = (char *)Tcl_Alloc(length + 1); memcpy(string, name, length); string[length] = '\0'; #endif /* _WIN32 */ if (utf2tenvirondstr(string, &envString) == NULL) { /* Should not happen except memory alloc fail. */ Tcl_MutexUnlock(&envMutex); return; } string = (char *)Tcl_Realloc(string, Tcl_DStringLength(&envString) + tNTL); memcpy(string, Tcl_DStringValue(&envString), Tcl_DStringLength(&envString) + tNTL); Tcl_DStringFree(&envString); putenv(string); /* * Watch out for versions of putenv that copy the string (e.g. VC++). In * this case we need to free the string immediately. Otherwise update the * string in the cache. */ if (tenviron[index] == (techar *)string) { ReplaceString(oldValue, string); #ifdef HAVE_PUTENV_THAT_COPIES } else { /* * This putenv() copies instead of taking ownership. */ Tcl_Free(string); #endif /* HAVE_PUTENV_THAT_COPIES */ } #else /* !USE_PUTENV_FOR_UNSET */ for (envPtr = (char **)(tenviron+index+1); ; envPtr++) { envPtr[-1] = *envPtr; if (*envPtr == NULL) { break; } } ReplaceString(oldValue, NULL); #endif /* USE_PUTENV_FOR_UNSET */ Tcl_MutexUnlock(&envMutex); } /* *--------------------------------------------------------------------------- * * TclGetEnv -- * * Retrieve the value of an environment variable. * * Results: * The result is a pointer to a string specifying the value of the * environment variable, or NULL if that environment variable does not * exist. Storage for the result string is allocated in valuePtr; the * caller must call Tcl_DStringFree() when the result is no longer * needed. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * TclGetEnv( const char *name, /* Name of environment variable to find * (UTF-8). */ Tcl_DString *valuePtr) /* Uninitialized or free DString in which the * value of the environment variable is * stored. */ { Tcl_Size length, index; const char *result; Tcl_MutexLock(&envMutex); index = TclpFindVariable(name, &length); result = NULL; if (index != -1) { Tcl_DString envStr; result = tenviron2utfdstr(tenviron[index], &envStr); if (result) { result += length; if (*result == '=') { result++; Tcl_DStringInit(valuePtr); Tcl_DStringAppend(valuePtr, result, -1); result = Tcl_DStringValue(valuePtr); } else { result = NULL; } Tcl_DStringFree(&envStr); } } Tcl_MutexUnlock(&envMutex); return result; } /* *---------------------------------------------------------------------- * * EnvTraceProc -- * * This function is invoked whenever an environment variable is read, * modified or deleted. It propagates the change to the global "environ" * array. * * Results: * Returns NULL to indicate success, or an error-message if the array * element being handled doesn't exist. * * Side effects: * Environment variable changes get propagated. If the whole "env" array * is deleted, then we stop managing things for this interpreter (usually * this happens because the whole interpreter is being deleted). * *---------------------------------------------------------------------- */ static char * EnvTraceProc( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter whose "env" variable is being * modified. */ const char *name1, /* Better be "env". */ const char *name2, /* Name of variable being modified, or NULL if * whole array is being deleted (UTF-8). */ int flags) /* Indicates what's happening. */ { /* * For array traces, let TclSetupEnv do all the work. */ if (flags & TCL_TRACE_ARRAY) { TclSetupEnv(interp); TclEnvEpoch++; return NULL; } /* * If name2 is NULL, then return and do nothing. */ if (name2 == NULL) { return NULL; } /* * If a value is being set, call TclSetEnv to do all of the work. */ if (flags & TCL_TRACE_WRITES) { const char *value; Tcl_DString ds; value = Tcl_GetVar2(interp, "env", name2, TCL_GLOBAL_ONLY); Tcl_DStringInit(&ds); if (Tcl_UtfToExternalDStringEx(NULL, TCLFSENCODING, name2, -1, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return (char *) "encoding error"; } if (Tcl_UtfToExternalDStringEx(NULL, TCLFSENCODING, value, -1, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return (char *) "encoding error"; } Tcl_DStringFree(&ds); TclSetEnv(name2, value); TclEnvEpoch++; } /* * If a value is being read, call TclGetEnv to do all of the work. */ if (flags & TCL_TRACE_READS) { Tcl_DString valueString; const char *value = TclGetEnv(name2, &valueString); if (value == NULL) { return (char *) "no such variable"; } Tcl_SetVar2(interp, name1, name2, value, 0); Tcl_DStringFree(&valueString); } /* * For unset traces, let TclUnsetEnv do all the work. */ if (flags & TCL_TRACE_UNSETS) { TclUnsetEnv(name2); TclEnvEpoch++; } return NULL; } /* *---------------------------------------------------------------------- * * ReplaceString -- * * Replace one string with another in the environment variable cache. The * cache keeps track of all of the environment variables that Tcl has * modified so they can be freed later. * * Results: * None. * * Side effects: * May free the old string. * *---------------------------------------------------------------------- */ static void ReplaceString( const char *oldStr, /* Old environment string. */ char *newStr) /* New environment string. */ { Tcl_Size i; /* * Check to see if the old value was allocated by Tcl. If so, it needs to * be deallocated to avoid memory leaks. Note that this algorithm is O(n), * not O(1). This will result in n-squared behavior if lots of environment * changes are being made. */ for (i = 0; i < env.cacheSize; i++) { if (env.cache[i]==oldStr || env.cache[i]==NULL) { break; } } if (i < env.cacheSize) { /* * Replace or delete the old value. */ if (env.cache[i]) { Tcl_Free(env.cache[i]); } if (newStr) { env.cache[i] = newStr; } else { for (; i < env.cacheSize-1; i++) { env.cache[i] = env.cache[i+1]; } env.cache[env.cacheSize-1] = NULL; } } else { /* * We need to grow the cache in order to hold the new string. */ const int growth = 5; env.cache = (char **)Tcl_Realloc(env.cache, (env.cacheSize + growth) * sizeof(char *)); env.cache[env.cacheSize] = newStr; (void) memset(env.cache+env.cacheSize+1, 0, (growth-1) * sizeof(char *)); env.cacheSize += growth; } } /* *---------------------------------------------------------------------- * * TclFinalizeEnvironment -- * * This function releases any storage allocated by this module that isn't * still in use by the global environment. Any strings that are still in * the environment will be leaked. * * Results: * None. * * Side effects: * May deallocate storage. * *---------------------------------------------------------------------- */ void TclFinalizeEnvironment(void) { /* * For now we just deallocate the cache array and none of the environment * strings. This may leak more memory that strictly necessary, since some * of the strings may no longer be in the environment. However, * determining which ones are ok to delete is n-squared, and is pretty * unlikely, so we don't bother. However, in the case of DPURIFY, just * free all strings in the cache. */ if (env.cache) { #ifdef PURIFY Tcl_Size i; for (i = 0; i < env.cacheSize; i++) { Tcl_Free(env.cache[i]); } #endif Tcl_Free(env.cache); env.cache = NULL; env.cacheSize = 0; #ifndef USE_PUTENV if ((env.ourEnviron != NULL)) { Tcl_Free(env.ourEnviron); env.ourEnviron = NULL; } env.ourEnvironSize = 0; #endif } } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclEvent.c0000644000175000017500000015461414726623136015135 0ustar sergeisergei/* * tclEvent.c -- * * This file implements some general event related interfaces including * background errors, exit handlers, and the "vwait" and "update" command * functions. * * Copyright © 1990-1994 The Regents of the University of California. * Copyright © 1994-1998 Sun Microsystems, Inc. * Copyright © 2004 Zoran Vasiljevic. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclUuid.h" #ifdef TCL_WITH_INTERNAL_ZLIB #include "zlib.h" #endif /* TCL_WITH_INTERNAL_ZLIB */ /* * The data structure below is used to report background errors. One such * structure is allocated for each error; it holds information about the * interpreter and the error until an idle handler command can be invoked. */ typedef struct BgError { Tcl_Obj *errorMsg; /* Copy of the error message (the interp's * result when the error occurred). */ Tcl_Obj *returnOpts; /* Active return options when the error * occurred */ struct BgError *nextPtr; /* Next in list of all pending error reports * for this interpreter, or NULL for end of * list. */ } BgError; /* * One of the structures below is associated with the "tclBgError" assoc data * for each interpreter. It keeps track of the head and tail of the list of * pending background errors for the interpreter. */ typedef struct { Tcl_Interp *interp; /* Interpreter in which error occurred. */ Tcl_Obj *cmdPrefix; /* First word(s) of the handler command */ BgError *firstBgPtr; /* First in list of all background errors * waiting to be processed for this * interpreter (NULL if none). */ BgError *lastBgPtr; /* Last in list of all background errors * waiting to be processed for this * interpreter (NULL if none). */ } ErrAssocData; /* * For each "vwait" event source a structure of the following type * is used: */ typedef struct { int *donePtr; /* Pointer to flag to signal or NULL. */ int sequence; /* Order of occurrence. */ int mask; /* 0, or TCL_READABLE/TCL_WRITABLE. */ Tcl_Obj *sourceObj; /* Name of the event source, either a * variable name or channel name. */ } VwaitItem; /* * For each exit handler created with a call to Tcl_Create(Late)ExitHandler * there is a structure of the following type: */ typedef struct ExitHandler { Tcl_ExitProc *proc; /* Function to call when process exits. */ void *clientData; /* One word of information to pass to proc. */ struct ExitHandler *nextPtr;/* Next in list of all exit handlers for this * application, or NULL for end of list. */ } ExitHandler; /* * There is both per-process and per-thread exit handlers. The first list is * controlled by a mutex. The other is in thread local storage. */ static ExitHandler *firstExitPtr = NULL; /* First in list of all exit handlers for * application. */ static ExitHandler *firstLateExitPtr = NULL; /* First in list of all late exit handlers for * application. */ TCL_DECLARE_MUTEX(exitMutex) /* * This variable is set to 1 when Tcl_Exit is called. The variable is checked * by TclInExit() to allow different behavior for exit-time processing, e.g., * in closing of files and pipes. */ static int inExit = 0; static int subsystemsInitialized = 0; static const char ENCODING_ERROR[] = "\n\t(encoding error in stderr)"; /* * This variable contains the application wide exit handler. It will be called * by Tcl_Exit instead of the C-runtime exit if this variable is set to a * non-NULL value. */ static Tcl_ExitProc *appExitPtr = NULL; typedef struct ThreadSpecificData { ExitHandler *firstExitPtr; /* First in list of all exit handlers for this * thread. */ int inExit; /* True when this thread is exiting. This is * used as a hack to decide to close the * standard channels. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; #if TCL_THREADS typedef struct { Tcl_ThreadCreateProc *proc; /* Main() function of the thread */ void *clientData; /* The one argument to Main() */ } ThreadClientData; static Tcl_ThreadCreateType NewThreadProc(void *clientData); #endif /* TCL_THREADS */ /* * Prototypes for functions referenced only in this file: */ static void BgErrorDeleteProc(void *clientData, Tcl_Interp *interp); static void HandleBgErrors(void *clientData); static void VwaitChannelReadProc(void *clientData, int mask); static void VwaitChannelWriteProc(void *clientData, int mask); static void VwaitTimeoutProc(void *clientData); static char * VwaitVarProc(void *clientData, Tcl_Interp *interp, const char *name1, const char *name2, int flags); static void InvokeExitHandlers(void); static void FinalizeThread(int quick); /* *---------------------------------------------------------------------- * * Tcl_BackgroundException -- * * This function is invoked to handle errors that occur in Tcl commands * that are invoked in "background" (e.g. from event or timer bindings). * * Results: * None. * * Side effects: * A handler command is invoked later as an idle handler to process the * error, passing it the interp result and return options. * *---------------------------------------------------------------------- */ void Tcl_BackgroundException( Tcl_Interp *interp, /* Interpreter in which an exception has * occurred. */ int code) /* The exception code value */ { BgError *errPtr; ErrAssocData *assocPtr; if (code == TCL_OK) { return; } errPtr = (BgError*)Tcl_Alloc(sizeof(BgError)); errPtr->errorMsg = Tcl_GetObjResult(interp); Tcl_IncrRefCount(errPtr->errorMsg); errPtr->returnOpts = Tcl_GetReturnOptions(interp, code); Tcl_IncrRefCount(errPtr->returnOpts); errPtr->nextPtr = NULL; (void) TclGetBgErrorHandler(interp); assocPtr = (ErrAssocData *)Tcl_GetAssocData(interp, "tclBgError", NULL); if (assocPtr->firstBgPtr == NULL) { assocPtr->firstBgPtr = errPtr; Tcl_DoWhenIdle(HandleBgErrors, assocPtr); } else { assocPtr->lastBgPtr->nextPtr = errPtr; } assocPtr->lastBgPtr = errPtr; Tcl_ResetResult(interp); } /* *---------------------------------------------------------------------- * * HandleBgErrors -- * * This function is invoked as an idle handler to process all of the * accumulated background errors. * * Results: * None. * * Side effects: * Depends on what actions the handler command takes for the errors. * *---------------------------------------------------------------------- */ static void HandleBgErrors( void *clientData) /* Pointer to ErrAssocData structure. */ { ErrAssocData *assocPtr = (ErrAssocData *)clientData; Tcl_Interp *interp = assocPtr->interp; BgError *errPtr; /* * Not bothering to save/restore the interp state. Assume that any code * that has interp state it needs to keep will make its own * Tcl_SaveInterpState call before calling something like Tcl_DoOneEvent() * that could lead us here. */ Tcl_Preserve(assocPtr); Tcl_Preserve(interp); while (assocPtr->firstBgPtr != NULL) { int code; Tcl_Size prefixObjc; Tcl_Obj **prefixObjv, **tempObjv; /* * Note we copy the handler command prefix each pass through, so we do * support one handler setting another handler. */ Tcl_Obj *copyObj = TclListObjCopy(NULL, assocPtr->cmdPrefix); errPtr = assocPtr->firstBgPtr; TclListObjGetElements(NULL, copyObj, &prefixObjc, &prefixObjv); tempObjv = (Tcl_Obj**)Tcl_Alloc((prefixObjc+2) * sizeof(Tcl_Obj *)); memcpy(tempObjv, prefixObjv, prefixObjc*sizeof(Tcl_Obj *)); tempObjv[prefixObjc] = errPtr->errorMsg; tempObjv[prefixObjc+1] = errPtr->returnOpts; Tcl_AllowExceptions(interp); code = Tcl_EvalObjv(interp, prefixObjc+2, tempObjv, TCL_EVAL_GLOBAL); /* * Discard the command and the information about the error report. */ Tcl_DecrRefCount(copyObj); Tcl_DecrRefCount(errPtr->errorMsg); Tcl_DecrRefCount(errPtr->returnOpts); assocPtr->firstBgPtr = errPtr->nextPtr; Tcl_Free(errPtr); Tcl_Free(tempObjv); if (code == TCL_BREAK) { /* * Break means cancel any remaining error reports for this * interpreter. */ while (assocPtr->firstBgPtr != NULL) { errPtr = assocPtr->firstBgPtr; assocPtr->firstBgPtr = errPtr->nextPtr; Tcl_DecrRefCount(errPtr->errorMsg); Tcl_DecrRefCount(errPtr->returnOpts); Tcl_Free(errPtr); } } else if ((code == TCL_ERROR) && !Tcl_IsSafe(interp)) { Tcl_Channel errChannel = Tcl_GetStdChannel(TCL_STDERR); if (errChannel != NULL) { Tcl_Obj *options = Tcl_GetReturnOptions(interp, code); Tcl_Obj *valuePtr = NULL; TclDictGet(NULL, options, "-errorinfo", &valuePtr); Tcl_WriteChars(errChannel, "error in background error handler:\n", -1); if (valuePtr) { if (Tcl_WriteObj(errChannel, valuePtr) < 0) { Tcl_WriteChars(errChannel, ENCODING_ERROR, -1); } } else { if (Tcl_WriteObj(errChannel, Tcl_GetObjResult(interp)) < 0) { Tcl_WriteChars(errChannel, ENCODING_ERROR, -1); } } Tcl_WriteChars(errChannel, "\n", 1); Tcl_Flush(errChannel); Tcl_DecrRefCount(options); } } } assocPtr->lastBgPtr = NULL; Tcl_Release(interp); Tcl_Release(assocPtr); } /* *---------------------------------------------------------------------- * * TclDefaultBgErrorHandlerObjCmd -- * * This function is invoked to process the "::tcl::Bgerror" Tcl command. * It is the default handler command registered with [interp bgerror] for * the sake of compatibility with older Tcl releases. * * Results: * A standard Tcl object result. * * Side effects: * Depends on what actions the "bgerror" command takes for the errors. * *---------------------------------------------------------------------- */ int TclDefaultBgErrorHandlerObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *valuePtr; Tcl_Obj *tempObjv[2]; int result, code, level; Tcl_InterpState saved; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "msg options"); return TCL_ERROR; } /* * Check for a valid return options dictionary. */ result = TclDictGet(NULL, objv[2], "-level", &valuePtr); if (result != TCL_OK || valuePtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "missing return option \"-level\"", -1)); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", (char *)NULL); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, valuePtr, &level) == TCL_ERROR) { return TCL_ERROR; } result = TclDictGet(NULL, objv[2], "-code", &valuePtr); if (result != TCL_OK || valuePtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "missing return option \"-code\"", -1)); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", (char *)NULL); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, valuePtr, &code) == TCL_ERROR) { return TCL_ERROR; } if (level != 0) { /* * We're handling a TCL_RETURN exception. */ code = TCL_RETURN; } if (code == TCL_OK) { /* * Somehow we got to exception handling with no exception. (Pass * TCL_OK to Tcl_BackgroundException()?) Just return without doing * anything. */ return TCL_OK; } /* * Construct the bgerror command. */ TclNewLiteralStringObj(tempObjv[0], "bgerror"); Tcl_IncrRefCount(tempObjv[0]); /* * Determine error message argument. Check the return options in case * a non-error exception brought us here. */ switch (code) { case TCL_ERROR: tempObjv[1] = objv[1]; break; case TCL_BREAK: TclNewLiteralStringObj(tempObjv[1], "invoked \"break\" outside of a loop"); break; case TCL_CONTINUE: TclNewLiteralStringObj(tempObjv[1], "invoked \"continue\" outside of a loop"); break; default: tempObjv[1] = Tcl_ObjPrintf("command returned bad code: %d", code); break; } Tcl_IncrRefCount(tempObjv[1]); if (code != TCL_ERROR) { Tcl_SetObjResult(interp, tempObjv[1]); } result = TclDictGet(NULL, objv[2], "-errorcode", &valuePtr); if (result == TCL_OK && valuePtr != NULL) { Tcl_SetObjErrorCode(interp, valuePtr); } result = TclDictGet(NULL, objv[2], "-errorinfo", &valuePtr); if (result == TCL_OK && valuePtr != NULL) { Tcl_AppendObjToErrorInfo(interp, valuePtr); } if (code == TCL_ERROR) { Tcl_SetObjResult(interp, tempObjv[1]); } /* * Save interpreter state so we can restore it if multiple handler * attempts are needed. */ saved = Tcl_SaveInterpState(interp, code); /* * Invoke the bgerror command. */ Tcl_AllowExceptions(interp); code = Tcl_EvalObjv(interp, 2, tempObjv, TCL_EVAL_GLOBAL); if (code == TCL_ERROR) { /* * If the interpreter is safe, we look for a hidden command named * "bgerror" and call that with the error information. Otherwise, * simply ignore the error. The rationale is that this could be an * error caused by a malicious applet trying to cause an infinite * barrage of error messages. The hidden "bgerror" command can be used * by a security policy to interpose on such attacks and e.g. kill the * applet after a few attempts. */ if (Tcl_IsSafe(interp)) { Tcl_RestoreInterpState(interp, saved); TclObjInvoke(interp, 2, tempObjv, TCL_INVOKE_HIDDEN); } else { Tcl_Channel errChannel = Tcl_GetStdChannel(TCL_STDERR); if (errChannel != NULL) { Tcl_Obj *resultPtr = Tcl_GetObjResult(interp); Tcl_IncrRefCount(resultPtr); if (Tcl_FindCommand(interp, "bgerror", NULL, TCL_GLOBAL_ONLY) == NULL) { Tcl_RestoreInterpState(interp, saved); if (Tcl_WriteObj(errChannel, Tcl_GetVar2Ex(interp, "errorInfo", NULL, TCL_GLOBAL_ONLY)) < 0) { Tcl_WriteChars(errChannel, ENCODING_ERROR, -1); } Tcl_WriteChars(errChannel, "\n", -1); } else { Tcl_DiscardInterpState(saved); Tcl_WriteChars(errChannel, "bgerror failed to handle" " background error.\n Original error: ", -1); if (Tcl_WriteObj(errChannel, tempObjv[1]) < 0) { Tcl_WriteChars(errChannel, ENCODING_ERROR, -1); } Tcl_WriteChars(errChannel, "\n Error in bgerror: ", -1); if (Tcl_WriteObj(errChannel, resultPtr) < 0) { Tcl_WriteChars(errChannel, ENCODING_ERROR, -1); } Tcl_WriteChars(errChannel, "\n", -1); } Tcl_DecrRefCount(resultPtr); Tcl_Flush(errChannel); } else { Tcl_DiscardInterpState(saved); } } code = TCL_OK; } else { Tcl_DiscardInterpState(saved); } Tcl_DecrRefCount(tempObjv[0]); Tcl_DecrRefCount(tempObjv[1]); Tcl_ResetResult(interp); return code; } /* *---------------------------------------------------------------------- * * TclSetBgErrorHandler -- * * This function sets the command prefix to be used to handle background * errors in interp. * * Results: * None. * * Side effects: * Error handler is registered. * *---------------------------------------------------------------------- */ void TclSetBgErrorHandler( Tcl_Interp *interp, Tcl_Obj *cmdPrefix) { ErrAssocData *assocPtr = (ErrAssocData *)Tcl_GetAssocData(interp, "tclBgError", NULL); if (cmdPrefix == NULL) { Tcl_Panic("TclSetBgErrorHandler: NULL cmdPrefix argument"); } if (assocPtr == NULL) { /* * First access: initialize. */ assocPtr = (ErrAssocData*)Tcl_Alloc(sizeof(ErrAssocData)); assocPtr->interp = interp; assocPtr->cmdPrefix = NULL; assocPtr->firstBgPtr = NULL; assocPtr->lastBgPtr = NULL; Tcl_SetAssocData(interp, "tclBgError", BgErrorDeleteProc, assocPtr); } if (assocPtr->cmdPrefix) { Tcl_DecrRefCount(assocPtr->cmdPrefix); } assocPtr->cmdPrefix = cmdPrefix; Tcl_IncrRefCount(assocPtr->cmdPrefix); } /* *---------------------------------------------------------------------- * * TclGetBgErrorHandler -- * * This function retrieves the command prefix currently used to handle * background errors in interp. * * Results: * A (Tcl_Obj *) to a list of words (command prefix). * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj * TclGetBgErrorHandler( Tcl_Interp *interp) { ErrAssocData *assocPtr = (ErrAssocData *)Tcl_GetAssocData(interp, "tclBgError", NULL); if (assocPtr == NULL) { Tcl_Obj *bgerrorObj; TclNewLiteralStringObj(bgerrorObj, "::tcl::Bgerror"); TclSetBgErrorHandler(interp, bgerrorObj); assocPtr = (ErrAssocData *)Tcl_GetAssocData(interp, "tclBgError", NULL); } return assocPtr->cmdPrefix; } /* *---------------------------------------------------------------------- * * BgErrorDeleteProc -- * * This function is associated with the "tclBgError" assoc data for an * interpreter; it is invoked when the interpreter is deleted in order to * free the information associated with any pending error reports. * * Results: * None. * * Side effects: * Background error information is freed: if there were any pending error * reports, they are canceled. * *---------------------------------------------------------------------- */ static void BgErrorDeleteProc( void *clientData, /* Pointer to ErrAssocData structure. */ TCL_UNUSED(Tcl_Interp *)) { ErrAssocData *assocPtr = (ErrAssocData *)clientData; BgError *errPtr; while (assocPtr->firstBgPtr != NULL) { errPtr = assocPtr->firstBgPtr; assocPtr->firstBgPtr = errPtr->nextPtr; Tcl_DecrRefCount(errPtr->errorMsg); Tcl_DecrRefCount(errPtr->returnOpts); Tcl_Free(errPtr); } Tcl_CancelIdleCall(HandleBgErrors, assocPtr); Tcl_DecrRefCount(assocPtr->cmdPrefix); Tcl_EventuallyFree(assocPtr, TCL_DYNAMIC); } /* *---------------------------------------------------------------------- * * Tcl_CreateExitHandler -- * * Arrange for a given function to be invoked just before the application * exits. * * Results: * None. * * Side effects: * Proc will be invoked with clientData as argument when the application * exits. * *---------------------------------------------------------------------- */ void Tcl_CreateExitHandler( Tcl_ExitProc *proc, /* Function to invoke. */ void *clientData) /* Arbitrary value to pass to proc. */ { ExitHandler *exitPtr = (ExitHandler*)Tcl_Alloc(sizeof(ExitHandler)); exitPtr->proc = proc; exitPtr->clientData = clientData; Tcl_MutexLock(&exitMutex); exitPtr->nextPtr = firstExitPtr; firstExitPtr = exitPtr; Tcl_MutexUnlock(&exitMutex); } /* *---------------------------------------------------------------------- * * TclCreateLateExitHandler -- * * Arrange for a given function to be invoked after all pre-thread * cleanups. * * Results: * None. * * Side effects: * Proc will be invoked with clientData as argument when the application * exits. * *---------------------------------------------------------------------- */ void TclCreateLateExitHandler( Tcl_ExitProc *proc, /* Function to invoke. */ void *clientData) /* Arbitrary value to pass to proc. */ { ExitHandler *exitPtr = (ExitHandler*)Tcl_Alloc(sizeof(ExitHandler)); exitPtr->proc = proc; exitPtr->clientData = clientData; Tcl_MutexLock(&exitMutex); exitPtr->nextPtr = firstLateExitPtr; firstLateExitPtr = exitPtr; Tcl_MutexUnlock(&exitMutex); } /* *---------------------------------------------------------------------- * * Tcl_DeleteExitHandler -- * * This function cancels an existing exit handler matching proc and * clientData, if such a handler exits. * * Results: * None. * * Side effects: * If there is an exit handler corresponding to proc and clientData then * it is canceled; if no such handler exists then nothing happens. * *---------------------------------------------------------------------- */ void Tcl_DeleteExitHandler( Tcl_ExitProc *proc, /* Function that was previously registered. */ void *clientData) /* Arbitrary value to pass to proc. */ { ExitHandler *exitPtr, *prevPtr; Tcl_MutexLock(&exitMutex); for (prevPtr = NULL, exitPtr = firstExitPtr; exitPtr != NULL; prevPtr = exitPtr, exitPtr = exitPtr->nextPtr) { if ((exitPtr->proc == proc) && (exitPtr->clientData == clientData)) { if (prevPtr == NULL) { firstExitPtr = exitPtr->nextPtr; } else { prevPtr->nextPtr = exitPtr->nextPtr; } Tcl_Free(exitPtr); break; } } Tcl_MutexUnlock(&exitMutex); return; } /* *---------------------------------------------------------------------- * * TclDeleteLateExitHandler -- * * This function cancels an existing late exit handler matching proc and * clientData, if such a handler exits. * * Results: * None. * * Side effects: * If there is a late exit handler corresponding to proc and clientData * then it is canceled; if no such handler exists then nothing happens. * *---------------------------------------------------------------------- */ void TclDeleteLateExitHandler( Tcl_ExitProc *proc, /* Function that was previously registered. */ void *clientData) /* Arbitrary value to pass to proc. */ { ExitHandler *exitPtr, *prevPtr; Tcl_MutexLock(&exitMutex); for (prevPtr = NULL, exitPtr = firstLateExitPtr; exitPtr != NULL; prevPtr = exitPtr, exitPtr = exitPtr->nextPtr) { if ((exitPtr->proc == proc) && (exitPtr->clientData == clientData)) { if (prevPtr == NULL) { firstLateExitPtr = exitPtr->nextPtr; } else { prevPtr->nextPtr = exitPtr->nextPtr; } Tcl_Free(exitPtr); break; } } Tcl_MutexUnlock(&exitMutex); return; } /* *---------------------------------------------------------------------- * * Tcl_CreateThreadExitHandler -- * * Arrange for a given function to be invoked just before the current * thread exits. * * Results: * None. * * Side effects: * Proc will be invoked with clientData as argument when the application * exits. * *---------------------------------------------------------------------- */ void Tcl_CreateThreadExitHandler( Tcl_ExitProc *proc, /* Function to invoke. */ void *clientData) /* Arbitrary value to pass to proc. */ { ExitHandler *exitPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); exitPtr = (ExitHandler*)Tcl_Alloc(sizeof(ExitHandler)); exitPtr->proc = proc; exitPtr->clientData = clientData; exitPtr->nextPtr = tsdPtr->firstExitPtr; tsdPtr->firstExitPtr = exitPtr; } /* *---------------------------------------------------------------------- * * Tcl_DeleteThreadExitHandler -- * * This function cancels an existing exit handler matching proc and * clientData, if such a handler exits. * * Results: * None. * * Side effects: * If there is an exit handler corresponding to proc and clientData then * it is canceled; if no such handler exists then nothing happens. * *---------------------------------------------------------------------- */ void Tcl_DeleteThreadExitHandler( Tcl_ExitProc *proc, /* Function that was previously registered. */ void *clientData) /* Arbitrary value to pass to proc. */ { ExitHandler *exitPtr, *prevPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); for (prevPtr = NULL, exitPtr = tsdPtr->firstExitPtr; exitPtr != NULL; prevPtr = exitPtr, exitPtr = exitPtr->nextPtr) { if ((exitPtr->proc == proc) && (exitPtr->clientData == clientData)) { if (prevPtr == NULL) { tsdPtr->firstExitPtr = exitPtr->nextPtr; } else { prevPtr->nextPtr = exitPtr->nextPtr; } Tcl_Free(exitPtr); return; } } } /* *---------------------------------------------------------------------- * * Tcl_SetExitProc -- * * This function sets the application wide exit handler that will be * called by Tcl_Exit in place of the C-runtime exit. If the application * wide exit handler is NULL, the C-runtime exit will be used instead. * * Results: * The previously set application wide exit handler. * * Side effects: * Sets the application wide exit handler to the specified value. * *---------------------------------------------------------------------- */ Tcl_ExitProc * Tcl_SetExitProc( Tcl_ExitProc *proc) /* New exit handler for app or NULL */ { Tcl_ExitProc *prevExitProc; /* * Swap the old exit proc for the new one, saving the old one for our * return value. */ Tcl_MutexLock(&exitMutex); prevExitProc = appExitPtr; appExitPtr = proc; Tcl_MutexUnlock(&exitMutex); return prevExitProc; } /* *---------------------------------------------------------------------- * * InvokeExitHandlers -- * * Call the registered exit handlers. * * Results: * None. * * Side effects: * The exit handlers are invoked, and the ExitHandler struct is * freed. * *---------------------------------------------------------------------- */ static void InvokeExitHandlers(void) { ExitHandler *exitPtr; Tcl_MutexLock(&exitMutex); inExit = 1; for (exitPtr = firstExitPtr; exitPtr != NULL; exitPtr = firstExitPtr) { /* * Be careful to remove the handler from the list before invoking its * callback. This protects us against double-freeing if the callback * should call Tcl_DeleteExitHandler on itself. */ firstExitPtr = exitPtr->nextPtr; Tcl_MutexUnlock(&exitMutex); exitPtr->proc(exitPtr->clientData); Tcl_Free(exitPtr); Tcl_MutexLock(&exitMutex); } firstExitPtr = NULL; Tcl_MutexUnlock(&exitMutex); } /* *---------------------------------------------------------------------- * * Tcl_Exit -- * * This function is called to terminate the application. * * Results: * None. * * Side effects: * All existing exit handlers are invoked, then the application ends. * *---------------------------------------------------------------------- */ TCL_NORETURN void Tcl_Exit( int status) /* Exit status for application; typically 0 * for normal return, 1 for error return. */ { Tcl_ExitProc *currentAppExitPtr; Tcl_MutexLock(&exitMutex); currentAppExitPtr = appExitPtr; Tcl_MutexUnlock(&exitMutex); /* * Warning: this function SHOULD NOT return, as there is code that depends * on Tcl_Exit never returning. In fact, we will Tcl_Panic if anyone * returns, so critical is this dependency. * * If subsystems are not (yet) initialized, proper Tcl-finalization is * impossible, so fallback to system exit, see bug-[f8a33ce3db5d8cc2]. */ if (currentAppExitPtr) { currentAppExitPtr(INT2PTR(status)); } else if (subsystemsInitialized) { if (TclFullFinalizationRequested()) { /* * Thorough finalization for Valgrind et al. */ Tcl_Finalize(); } else { /* * Fast and deterministic exit (default behavior) */ InvokeExitHandlers(); /* * Ensure the thread-specific data is initialised as it is used in * Tcl_FinalizeThread() */ (void) TCL_TSD_INIT(&dataKey); /* * Now finalize the calling thread only (others are not safely * reachable). Among other things, this triggers a flush of the * Tcl_Channels that may have data enqueued. */ FinalizeThread(/* quick */ 1); } } exit(status); } /* *------------------------------------------------------------------------- * * Tcl_InitSubsystems -- * * Initialize various subsytems in Tcl. This should be called the first * time an interp is created, or before any of the subsystems are used. * This function ensures an order for the initialization of subsystems: * * 1. that cannot be initialized in lazy order because they are mutually * dependent. * * 2. so that they can be finalized in a known order w/o causing the * subsequent re-initialization of a subsystem in the act of shutting * down another. * * Results: * The full Tcl version with build information. * * Side effects: * Varied, see the respective initialization routines. * *------------------------------------------------------------------------- */ MODULE_SCOPE const TclStubs tclStubs; #ifndef STRINGIFY # define STRINGIFY(x) STRINGIFY1(x) # define STRINGIFY1(x) #x #endif static const struct { const TclStubs *stubs; const char version[256]; } stubInfo = { &tclStubs, {TCL_PATCH_LEVEL "+" STRINGIFY(TCL_VERSION_UUID) #if defined(__clang__) && defined(__clang_major__) ".clang-" STRINGIFY(__clang_major__) #if __clang_minor__ < 10 "0" #endif STRINGIFY(__clang_minor__) #endif #ifdef TCL_COMPILE_DEBUG ".compiledebug" #endif #ifdef TCL_COMPILE_STATS ".compilestats" #endif #if defined(__cplusplus) && !defined(__OBJC__) ".cplusplus" #endif #ifndef NDEBUG ".debug" #endif #if !defined(__clang__) && !defined(__INTEL_COMPILER) && defined(__GNUC__) ".gcc-" STRINGIFY(__GNUC__) #if __GNUC_MINOR__ < 10 "0" #endif STRINGIFY(__GNUC_MINOR__) #endif #ifdef __INTEL_COMPILER ".icc-" STRINGIFY(__INTEL_COMPILER) #endif #if (defined(_WIN32) || (ULONG_MAX == 0xffffffffUL)) && !defined(_WIN64) ".ilp32" #endif #ifdef TCL_MEM_DEBUG ".memdebug" #endif #if defined(_MSC_VER) ".msvc-" STRINGIFY(_MSC_VER) #endif #ifdef USE_NMAKE ".nmake" #endif #ifdef TCL_NO_DEPRECATED ".no-deprecate" #endif #if !TCL_THREADS ".no-thread" #endif #ifndef TCL_CFG_OPTIMIZED ".no-optimize" #endif #ifdef __OBJC__ ".objective-c" #if defined(__cplusplus) "plusplus" #endif #endif #ifdef TCL_CFG_PROFILED ".profile" #endif #ifdef PURIFY ".purify" #endif #ifdef STATIC_BUILD ".static" #endif #ifndef TCL_WITH_EXTERNAL_TOMMATH ".tommath-0103" #endif #ifdef TCL_WITH_INTERNAL_ZLIB ".zlib-" #if ZLIB_VER_MAJOR < 10 "0" #endif STRINGIFY(ZLIB_VER_MAJOR) #if ZLIB_VER_MINOR < 10 "0" #endif STRINGIFY(ZLIB_VER_MINOR) #endif // TCL_WITH_INTERNAL_ZLIB }}; const char * Tcl_InitSubsystems(void) { if (inExit != 0) { Tcl_Panic("Tcl_InitSubsystems called while exiting"); } if (subsystemsInitialized == 0) { /* * Double check inside the mutex. There are definitely calls back into * this routine from some of the functions below. */ TclpInitLock(); if (subsystemsInitialized == 0) { /* * Initialize locks used by the memory allocators before anything * interesting happens so we can use the allocators in the * implementation of self-initializing locks. */ TclInitThreadStorage(); /* Creates hash table for * thread local storage */ #if defined(USE_TCLALLOC) && USE_TCLALLOC TclInitAlloc(); /* Process wide mutex init */ #endif #if TCL_THREADS && defined(USE_THREAD_ALLOC) TclInitThreadAlloc(); /* Setup thread allocator caches */ #endif #ifdef TCL_MEM_DEBUG TclInitDbCkalloc(); /* Process wide mutex init */ #endif TclpInitPlatform(); /* Creates signal handler(s) */ TclInitDoubleConversion(); /* Initializes constants for * converting to/from double. */ TclInitObjSubsystem(); /* Register obj types, create * mutexes. */ TclInitIOSubsystem(); /* Inits a tsd key (noop). */ TclInitEncodingSubsystem(); /* Process wide encoding init. */ TclInitNamespaceSubsystem();/* Register ns obj type (mutexed). */ subsystemsInitialized = 1; } TclpInitUnlock(); } TclInitNotifier(); return stubInfo.version; } /* *---------------------------------------------------------------------- * * Tcl_Finalize -- * * Shut down Tcl. First calls registered exit handlers, then carefully * shuts down various subsystems. Should be invoked by user before the * Tcl shared library is being unloaded in an embedded context. * * Results: * None. * * Side effects: * Varied, see the respective finalization routines. * *---------------------------------------------------------------------- */ void Tcl_Finalize(void) { ExitHandler *exitPtr; /* * Invoke exit handlers first. */ InvokeExitHandlers(); TclpInitLock(); if (subsystemsInitialized == 0) { goto alreadyFinalized; } subsystemsInitialized = 0; /* * Ensure the thread-specific data is initialised as it is used in * Tcl_FinalizeThread() */ (void) TCL_TSD_INIT(&dataKey); /* * Clean up after the current thread now, after exit handlers. In * particular, the testexithandler command sets up something that writes * to standard output, which gets closed. Note that there is no * thread-local storage or IO subsystem after this call. */ Tcl_FinalizeThread(); /* * Now invoke late (process-wide) exit handlers. */ Tcl_MutexLock(&exitMutex); for (exitPtr = firstLateExitPtr; exitPtr != NULL; exitPtr = firstLateExitPtr) { /* * Be careful to remove the handler from the list before invoking its * callback. This protects us against double-freeing if the callback * should call Tcl_DeleteLateExitHandler on itself. */ firstLateExitPtr = exitPtr->nextPtr; Tcl_MutexUnlock(&exitMutex); exitPtr->proc(exitPtr->clientData); Tcl_Free(exitPtr); Tcl_MutexLock(&exitMutex); } firstLateExitPtr = NULL; Tcl_MutexUnlock(&exitMutex); /* * Now finalize the Tcl execution environment. Note that this must be done * after the exit handlers, because there are order dependencies. */ TclFinalizeEvaluation(); TclFinalizeExecution(); TclFinalizeEnvironment(); /* * Finalizing the filesystem must come after anything which might * conceivably interact with the 'Tcl_FS' API. */ TclFinalizeFilesystem(); /* * Undo all Tcl_ObjType registrations, and reset the global list of free * Tcl_Obj's. After this returns, no more Tcl_Obj's should be allocated or * freed. * * Note in particular that TclFinalizeObjects() must follow * TclFinalizeFilesystem() because TclFinalizeFilesystem free's the * Tcl_Obj that holds the path of the current working directory. */ TclFinalizeObjects(); /* * We must be sure the encoding finalization doesn't need to examine the * filesystem in any way. Since it only needs to clean up internal data * structures, this is fine. */ TclFinalizeEncodingSubsystem(); /* * Repeat finalization of the thread local storage once more. Although * this step is already done by the Tcl_FinalizeThread call above, series * of events happening afterwards may re-initialize TSD slots. Those need * to be finalized again, otherwise we're leaking memory chunks. Very * important to note is that things happening afterwards should not * reference anything which may re-initialize TSD's. This includes freeing * Tcl_Objs's, among other things. * * This fixes the Tcl Bug #990552. */ TclFinalizeThreadData(/* quick */ 0); /* * Now we can free constants for conversions to/from double. */ TclFinalizeDoubleConversion(); /* * There have been several bugs in the past that cause exit handlers to be * established during Tcl_Finalize processing. Such exit handlers leave * malloc'ed memory, and Tcl_FinalizeMemorySubsystem or * Tcl_FinalizeThreadAlloc will result in a corrupted heap. The result can * be a mysterious crash on process exit. Check here that nobody's done * this. */ if (firstExitPtr != NULL) { Tcl_Panic("exit handlers were created during Tcl_Finalize"); } TclFinalizePreserve(); /* * Free synchronization objects. There really should only be one thread * alive at this moment. */ TclFinalizeSynchronization(); /* * Close down the thread-specific object allocator. */ #if TCL_THREADS && defined(USE_THREAD_ALLOC) TclFinalizeThreadAlloc(); #endif /* * We defer unloading of packages until very late to avoid memory access * issues. Both exit callbacks and synchronization variables may be stored * in packages. * * Note that TclFinalizeLoad unloads packages in the reverse of the order * they were loaded in (i.e. last to be loaded is the first to be * unloaded). This can be important for correct unloading when * dependencies exist. * * Once load has been finalized, we will have deleted any temporary copies * of shared libraries and can therefore reset the filesystem to its * original state. */ TclFinalizeLoad(); TclResetFilesystem(); /* * At this point, there should no longer be any Tcl_Alloc'ed memory. */ TclFinalizeMemorySubsystem(); alreadyFinalized: TclFinalizeLock(); } /* *---------------------------------------------------------------------- * * Tcl_FinalizeThread -- * * Runs the exit handlers to allow Tcl to clean up its state about a * particular thread. * * Results: * None. * * Side effects: * Varied, see the respective finalization routines. * *---------------------------------------------------------------------- */ void Tcl_FinalizeThread(void) { FinalizeThread(/* quick */ 0); } void FinalizeThread( int quick) { ExitHandler *exitPtr; ThreadSpecificData *tsdPtr; /* * We use TclThreadDataKeyGet here, rather than Tcl_GetThreadData, because * we don't want to initialize the data block if it hasn't been * initialized already. */ tsdPtr = (ThreadSpecificData*)TclThreadDataKeyGet(&dataKey); if (tsdPtr != NULL) { tsdPtr->inExit = 1; for (exitPtr = tsdPtr->firstExitPtr; exitPtr != NULL; exitPtr = tsdPtr->firstExitPtr) { /* * Be careful to remove the handler from the list before invoking * its callback. This protects us against double-freeing if the * callback should call Tcl_DeleteThreadExitHandler on itself. */ tsdPtr->firstExitPtr = exitPtr->nextPtr; exitPtr->proc(exitPtr->clientData); Tcl_Free(exitPtr); } TclFinalizeIOSubsystem(); TclFinalizeNotifier(); TclFinalizeAsync(); TclFinalizeThreadObjects(); } /* * Blow away all thread local storage blocks. * * Note that Tcl API allows creation of threads which do not use any Tcl * interp or other Tcl subsytems. Those threads might, however, use thread * local storage, so we must unconditionally finalize it. * * Fix [Bug #571002] */ TclFinalizeThreadData(quick); } /* *---------------------------------------------------------------------- * * TclInExit -- * * Determines if we are in the middle of exit-time cleanup. * * Results: * If we are in the middle of exiting, 1, otherwise 0. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclInExit(void) { return inExit; } /* *---------------------------------------------------------------------- * * TclInThreadExit -- * * Determines if we are in the middle of thread exit-time cleanup. * * Results: * If we are in the middle of exiting this thread, 1, otherwise 0. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclInThreadExit(void) { ThreadSpecificData *tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); if (tsdPtr == NULL) { return 0; } return tsdPtr->inExit; } /* *---------------------------------------------------------------------- * * Tcl_VwaitObjCmd -- * * This function is invoked to process the "vwait" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_VwaitObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int i, done = 0, timedOut = 0, foundEvent, any = 1, timeout = 0; int numItems = 0, extended = 0, result, mode, mask = TCL_ALL_EVENTS; Tcl_InterpState saved = NULL; Tcl_TimerToken timer = NULL; Tcl_Time before, after; Tcl_Channel chan; Tcl_WideInt diff = -1; VwaitItem localItems[32], *vwaitItems = localItems; static const char *const vWaitOptionStrings[] = { "-all", "-extended", "-nofileevents", "-noidleevents", "-notimerevents", "-nowindowevents", "-readable", "-timeout", "-variable", "-writable", "--", NULL }; enum vWaitOptions { OPT_ALL, OPT_EXTD, OPT_NO_FEVTS, OPT_NO_IEVTS, OPT_NO_TEVTS, OPT_NO_WEVTS, OPT_READABLE, OPT_TIMEOUT, OPT_VARIABLE, OPT_WRITABLE, OPT_LAST } index; if ((objc == 2) && (strcmp(Tcl_GetString(objv[1]), "--") != 0)) { /* * Legacy "vwait" syntax, skip option handling. */ i = 1; goto endOfOptionLoop; } if ((unsigned) objc - 1 > sizeof(localItems) / sizeof(localItems[0])) { vwaitItems = (VwaitItem *)Tcl_Alloc(sizeof(VwaitItem) * (objc - 1)); } for (i = 1; i < objc; i++) { const char *name; name = TclGetString(objv[i]); if (name[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, objv[i], vWaitOptionStrings, "option", 0, &index) != TCL_OK) { result = TCL_ERROR; goto done; } switch (index) { case OPT_ALL: any = 0; break; case OPT_EXTD: extended = 1; break; case OPT_NO_FEVTS: mask &= ~TCL_FILE_EVENTS; break; case OPT_NO_IEVTS: mask &= ~TCL_IDLE_EVENTS; break; case OPT_NO_TEVTS: mask &= ~TCL_TIMER_EVENTS; break; case OPT_NO_WEVTS: mask &= ~TCL_WINDOW_EVENTS; break; case OPT_TIMEOUT: if (++i >= objc) { needArg: Tcl_ResetResult(interp); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "argument required for \"%s\"", vWaitOptionStrings[index])); Tcl_SetErrorCode(interp, "TCL", "EVENT", "ARGUMENT", (char *)NULL); result = TCL_ERROR; goto done; } if (Tcl_GetIntFromObj(interp, objv[i], &timeout) != TCL_OK) { result = TCL_ERROR; goto done; } if (timeout < 0) { Tcl_ResetResult(interp); Tcl_SetObjResult(interp, Tcl_NewStringObj( "timeout must be positive", -1)); Tcl_SetErrorCode(interp, "TCL", "EVENT", "NEGTIME", (char *)NULL); result = TCL_ERROR; goto done; } break; case OPT_LAST: i++; goto endOfOptionLoop; case OPT_VARIABLE: if (++i >= objc) { goto needArg; } result = Tcl_TraceVar2(interp, TclGetString(objv[i]), NULL, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, VwaitVarProc, &vwaitItems[numItems]); if (result != TCL_OK) { goto done; } vwaitItems[numItems].donePtr = &done; vwaitItems[numItems].sequence = -1; vwaitItems[numItems].mask = 0; vwaitItems[numItems].sourceObj = objv[i]; numItems++; break; case OPT_READABLE: if (++i >= objc) { goto needArg; } if (TclGetChannelFromObj(interp, objv[i], &chan, &mode, 0) != TCL_OK) { result = TCL_ERROR; goto done; } if (!(mode & TCL_READABLE)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "channel \"%s\" wasn't open for reading", TclGetString(objv[i]))); result = TCL_ERROR; goto done; } Tcl_CreateChannelHandler(chan, TCL_READABLE, VwaitChannelReadProc, &vwaitItems[numItems]); vwaitItems[numItems].donePtr = &done; vwaitItems[numItems].sequence = -1; vwaitItems[numItems].mask = TCL_READABLE; vwaitItems[numItems].sourceObj = objv[i]; numItems++; break; case OPT_WRITABLE: if (++i >= objc) { goto needArg; } if (TclGetChannelFromObj(interp, objv[i], &chan, &mode, 0) != TCL_OK) { result = TCL_ERROR; goto done; } if (!(mode & TCL_WRITABLE)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "channel \"%s\" wasn't open for writing", TclGetString(objv[i]))); result = TCL_ERROR; goto done; } Tcl_CreateChannelHandler(chan, TCL_WRITABLE, VwaitChannelWriteProc, &vwaitItems[numItems]); vwaitItems[numItems].donePtr = &done; vwaitItems[numItems].sequence = -1; vwaitItems[numItems].mask = TCL_WRITABLE; vwaitItems[numItems].sourceObj = objv[i]; numItems++; break; } } endOfOptionLoop: if ((mask & (TCL_FILE_EVENTS | TCL_IDLE_EVENTS | TCL_TIMER_EVENTS | TCL_WINDOW_EVENTS)) == 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "can't wait: would block forever", -1)); Tcl_SetErrorCode(interp, "TCL", "EVENT", "NO_SOURCES", (char *)NULL); result = TCL_ERROR; goto done; } if ((timeout > 0) && ((mask & TCL_TIMER_EVENTS) == 0)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "timer events disabled with timeout specified", -1)); Tcl_SetErrorCode(interp, "TCL", "EVENT", "NO_TIME", (char *)NULL); result = TCL_ERROR; goto done; } for (result = TCL_OK; i < objc; i++) { result = Tcl_TraceVar2(interp, TclGetString(objv[i]), NULL, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, VwaitVarProc, &vwaitItems[numItems]); if (result != TCL_OK) { break; } vwaitItems[numItems].donePtr = &done; vwaitItems[numItems].sequence = -1; vwaitItems[numItems].mask = 0; vwaitItems[numItems].sourceObj = objv[i]; numItems++; } if (result != TCL_OK) { result = TCL_ERROR; goto done; } if (!(mask & TCL_FILE_EVENTS)) { for (i = 0; i < numItems; i++) { if (vwaitItems[i].mask) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "file events disabled with channel(s) specified", -1)); Tcl_SetErrorCode(interp, "TCL", "EVENT", "NO_FILE_EVENT", (char *)NULL); result = TCL_ERROR; goto done; } } } if (timeout > 0) { vwaitItems[numItems].donePtr = &timedOut; vwaitItems[numItems].sequence = -1; vwaitItems[numItems].mask = 0; vwaitItems[numItems].sourceObj = NULL; timer = Tcl_CreateTimerHandler(timeout, VwaitTimeoutProc, &vwaitItems[numItems]); Tcl_GetTime(&before); } else { timeout = 0; } if ((numItems == 0) && (timeout == 0)) { /* * "vwait" is equivalent to "update", * "vwait -nofileevents -notimerevents -nowindowevents" * is equivalent to "update idletasks" */ any = 1; mask |= TCL_DONT_WAIT; } foundEvent = 1; while (!timedOut && foundEvent && ((!any && (done < numItems)) || (any && !done))) { foundEvent = Tcl_DoOneEvent(mask); if (Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG) == TCL_ERROR) { break; } if (Tcl_LimitExceeded(interp)) { Tcl_ResetResult(interp); Tcl_SetObjResult(interp, Tcl_NewStringObj("limit exceeded", -1)); Tcl_SetErrorCode(interp, "TCL", "EVENT", "LIMIT", (char *)NULL); break; } if ((numItems == 0) && (timeout == 0)) { /* * Behavior like "update": clear interpreter's result because * event handlers could have executed commands. */ Tcl_ResetResult(interp); result = TCL_OK; goto done; } } if (!foundEvent) { Tcl_ResetResult(interp); Tcl_SetObjResult(interp, Tcl_NewStringObj((numItems == 0) ? "can't wait: would wait forever" : "can't wait for variable(s)/channel(s): would wait forever", -1)); Tcl_SetErrorCode(interp, "TCL", "EVENT", "NO_SOURCES", (char *)NULL); result = TCL_ERROR; goto done; } if (!done && !timedOut) { /* * The interpreter's result was already set to the right error message * prior to exiting the loop above. */ result = TCL_ERROR; goto done; } result = TCL_OK; if (timeout <= 0) { /* * Clear out the interpreter's result, since it may have been set * by event handlers. */ Tcl_ResetResult(interp); goto done; } /* * When timeout was specified, report milliseconds left or -1 on timeout. */ if (timedOut) { diff = -1; } else { Tcl_GetTime(&after); diff = after.sec * 1000 + after.usec / 1000; diff -= before.sec * 1000 + before.usec / 1000; diff = timeout - diff; if (diff < 0) { diff = 0; } } done: if ((timeout > 0) && (timer != NULL)) { Tcl_DeleteTimerHandler(timer); } if (result != TCL_OK) { saved = Tcl_SaveInterpState(interp, result); } for (i = 0; i < numItems; i++) { if (vwaitItems[i].mask & TCL_READABLE) { if (TclGetChannelFromObj(interp, vwaitItems[i].sourceObj, &chan, &mode, 0) == TCL_OK) { Tcl_DeleteChannelHandler(chan, VwaitChannelReadProc, &vwaitItems[i]); } } else if (vwaitItems[i].mask & TCL_WRITABLE) { if (TclGetChannelFromObj(interp, vwaitItems[i].sourceObj, &chan, &mode, 0) == TCL_OK) { Tcl_DeleteChannelHandler(chan, VwaitChannelWriteProc, &vwaitItems[i]); } } else { Tcl_UntraceVar2(interp, TclGetString(vwaitItems[i].sourceObj), NULL, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, VwaitVarProc, &vwaitItems[i]); } } if (result == TCL_OK) { if (extended) { int k; Tcl_Obj *listObj, *keyObj; TclNewObj(listObj); for (k = 0; k < done; k++) { for (i = 0; i < numItems; i++) { if (vwaitItems[i].sequence != k) { continue; } if (vwaitItems[i].mask & TCL_READABLE) { TclNewLiteralStringObj(keyObj, "readable"); } else if (vwaitItems[i].mask & TCL_WRITABLE) { TclNewLiteralStringObj(keyObj, "writable"); } else { TclNewLiteralStringObj(keyObj, "variable"); } Tcl_ListObjAppendElement(NULL, listObj, keyObj); Tcl_ListObjAppendElement(NULL, listObj, vwaitItems[i].sourceObj); } } if (timeout > 0) { TclNewLiteralStringObj(keyObj, "timeleft"); Tcl_ListObjAppendElement(NULL, listObj, keyObj); Tcl_ListObjAppendElement(NULL, listObj, Tcl_NewWideIntObj(diff)); } Tcl_SetObjResult(interp, listObj); } else if (timeout > 0) { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(diff)); } } else { result = Tcl_RestoreInterpState(interp, saved); } if (vwaitItems != localItems) { Tcl_Free(vwaitItems); } return result; } static void VwaitChannelReadProc( void *clientData, /* Pointer to vwait info record. */ int mask) /* Event mask, must be TCL_READABLE. */ { VwaitItem *itemPtr = (VwaitItem *) clientData; if (!(mask & TCL_READABLE)) { return; } if (itemPtr->donePtr != NULL) { itemPtr->sequence = itemPtr->donePtr[0]; itemPtr->donePtr[0] += 1; itemPtr->donePtr = NULL; } } static void VwaitChannelWriteProc( void *clientData, /* Pointer to vwait info record. */ int mask) /* Event mask, must be TCL_WRITABLE. */ { VwaitItem *itemPtr = (VwaitItem *) clientData; if (!(mask & TCL_WRITABLE)) { return; } if (itemPtr->donePtr != NULL) { itemPtr->sequence = itemPtr->donePtr[0]; itemPtr->donePtr[0] += 1; itemPtr->donePtr = NULL; } } static void VwaitTimeoutProc( void *clientData) /* Pointer to vwait info record. */ { VwaitItem *itemPtr = (VwaitItem *) clientData; if (itemPtr->donePtr != NULL) { itemPtr->donePtr[0] = 1; itemPtr->donePtr = NULL; } } static char * VwaitVarProc( void *clientData, /* Pointer to vwait info record. */ Tcl_Interp *interp, /* Interpreter containing variable. */ const char *name1, /* Name of variable. */ const char *name2, /* Second part of variable name. */ TCL_UNUSED(int) /*flags*/) /* Information about what happened. */ { VwaitItem *itemPtr = (VwaitItem *) clientData; if (itemPtr->donePtr != NULL) { itemPtr->sequence = itemPtr->donePtr[0]; itemPtr->donePtr[0] += 1; itemPtr->donePtr = NULL; } Tcl_UntraceVar2(interp, name1, name2, TCL_TRACE_WRITES|TCL_TRACE_UNSETS, VwaitVarProc, clientData); return NULL; } /* *---------------------------------------------------------------------- * * Tcl_UpdateObjCmd -- * * This function is invoked to process the "update" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_UpdateObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int flags = 0; /* Initialized to avoid compiler warning. */ static const char *const updateOptions[] = {"idletasks", NULL}; enum updateOptionsEnum {OPT_IDLETASKS} optionIndex; if (objc == 1) { flags = TCL_ALL_EVENTS|TCL_DONT_WAIT; } else if (objc == 2) { if (Tcl_GetIndexFromObj(interp, objv[1], updateOptions, "option", 0, &optionIndex) != TCL_OK) { return TCL_ERROR; } switch (optionIndex) { case OPT_IDLETASKS: flags = TCL_IDLE_EVENTS|TCL_DONT_WAIT; break; default: Tcl_Panic("Tcl_UpdateObjCmd: bad option index to UpdateOptions"); } } else { Tcl_WrongNumArgs(interp, 1, objv, "?idletasks?"); return TCL_ERROR; } while (Tcl_DoOneEvent(flags) != 0) { if (Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG) == TCL_ERROR) { return TCL_ERROR; } if (Tcl_LimitExceeded(interp)) { Tcl_ResetResult(interp); Tcl_SetObjResult(interp, Tcl_NewStringObj("limit exceeded", -1)); return TCL_ERROR; } } /* * Must clear the interpreter's result because event handlers could have * executed commands. */ Tcl_ResetResult(interp); return TCL_OK; } #if TCL_THREADS /* *---------------------------------------------------------------------- * * NewThreadProc -- * * Bootstrap function of a new Tcl thread. * * Results: * None. * * Side Effects: * Initializes Tcl notifier for the current thread. * *---------------------------------------------------------------------- */ static Tcl_ThreadCreateType NewThreadProc( void *clientData) { ThreadClientData *cdPtr = (ThreadClientData *)clientData; void *threadClientData; Tcl_ThreadCreateProc *threadProc; threadProc = cdPtr->proc; threadClientData = cdPtr->clientData; Tcl_Free(clientData); /* Allocated in Tcl_CreateThread() */ threadProc(threadClientData); TCL_THREAD_CREATE_RETURN; } #endif /* *---------------------------------------------------------------------- * * Tcl_CreateThread -- * * This function creates a new thread. This actually belongs to the * tclThread.c file but since we use some private data structures local * to this file, it is placed here. * * Results: * TCL_OK if the thread could be created. The thread ID is returned in a * parameter. * * Side effects: * A new thread is created. * *---------------------------------------------------------------------- */ int Tcl_CreateThread( Tcl_ThreadId *idPtr, /* Return, the ID of the thread */ Tcl_ThreadCreateProc *proc, /* Main() function of the thread */ void *clientData, /* The one argument to Main() */ size_t stackSize, /* Size of stack for the new thread */ int flags) /* Flags controlling behaviour of the new * thread. */ { #if TCL_THREADS ThreadClientData *cdPtr = (ThreadClientData *)Tcl_Alloc(sizeof(ThreadClientData)); int result; cdPtr->proc = proc; cdPtr->clientData = clientData; result = TclpThreadCreate(idPtr, NewThreadProc, cdPtr, stackSize, flags); if (result != TCL_OK) { Tcl_Free(cdPtr); } return result; #else (void)idPtr; (void)proc; (void)clientData; (void)stackSize; (void)flags; return TCL_ERROR; #endif /* TCL_THREADS */ } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclExecute.c0000644000175000017500000104460014726623136015450 0ustar sergeisergei/* * tclExecute.c -- * * This file contains procedures that execute byte-compiled Tcl commands. * * Copyright © 1996-1997 Sun Microsystems, Inc. * Copyright © 1998-2000 Scriptics Corporation. * Copyright © 2001 Kevin B. Kenny. All rights reserved. * Copyright © 2002-2010 Miguel Sofer. * Copyright © 2005-2007 Donal K. Fellows. * Copyright © 2007 Daniel A. Steffen * Copyright © 2006-2008 Joe Mistachkin. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" #include "tclOOInt.h" #include "tclTomMath.h" #include #include /* * Hack to determine whether we may expect IEEE floating point. The hack is * formally incorrect in that non-IEEE platforms might have the same precision * and range, but VAX, IBM, and Cray do not; are there any other floating * point units that we might care about? */ #if (FLT_RADIX == 2) && (DBL_MANT_DIG == 53) && (DBL_MAX_EXP == 1024) #define IEEE_FLOATING_POINT #endif /* * A counter that is used to work out when the bytecode engine should call * Tcl_AsyncReady() to see whether there is a signal that needs handling, and * other expensive periodic operations. */ #ifndef ASYNC_CHECK_COUNT # define ASYNC_CHECK_COUNT 64 #endif /* !ASYNC_CHECK_COUNT */ /* * Boolean flag indicating whether the Tcl bytecode interpreter has been * initialized. */ static int execInitialized = 0; TCL_DECLARE_MUTEX(execMutex) static int cachedInExit = 0; #ifdef TCL_COMPILE_DEBUG /* * Variable that controls whether execution tracing is enabled and, if so, * what level of tracing is desired: * 0: no execution tracing * 1: trace invocations of Tcl procs only * 2: trace invocations of all (not compiled away) commands * 3: display each instruction executed * This variable is linked to the Tcl variable "tcl_traceExec". */ int tclTraceExec = 0; #endif /* * Mapping from expression instruction opcodes to strings; used for error * messages. Note that these entries must match the order and number of the * expression opcodes (e.g., INST_LOR) in tclCompile.h. * * Does not include the string for INST_EXPON (and beyond), as that is * disjoint for backward-compatibility reasons. */ static const char *const operatorStrings[] = { "|", "^", "&", "==", "!=", "<", ">", "<=", ">=", "<<", ">>", "+", "-", "*", "/", "%", "+", "-", "~", "!" }; /* * Mapping from Tcl result codes to strings; used for error and debugging * messages. */ #ifdef TCL_COMPILE_DEBUG static const char *const resultStrings[] = { "TCL_OK", "TCL_ERROR", "TCL_RETURN", "TCL_BREAK", "TCL_CONTINUE" }; #endif /* * These are used by evalstats to monitor object usage in Tcl. */ #ifdef TCL_COMPILE_STATS size_t tclObjsAlloced = 0; size_t tclObjsFreed = 0; size_t tclObjsShared[TCL_MAX_SHARED_OBJ_STATS] = { 0, 0, 0, 0, 0 }; #endif /* TCL_COMPILE_STATS */ /* * NR_TEBC * Helpers for NR - non-recursive calls to TEBC * Minimal data required to fully reconstruct the execution state. */ typedef struct { ByteCode *codePtr; /* Constant until the BC returns */ /* -----------------------------------------*/ Tcl_Obj **catchTop; /* These fields are used on return TO this */ Tcl_Obj *auxObjList; /* level: they record the state when a new */ CmdFrame cmdFrame; /* codePtr was received for NR execution. */ Tcl_Obj *stack[1]; /* Start of the actual combined catch and obj * stacks; the struct will be expanded as * necessary */ } TEBCdata; #define TEBC_YIELD() \ do { \ esPtr->tosPtr = tosPtr; \ TclNRAddCallback(interp, TEBCresume, \ TD, pc, INT2PTR(cleanup), NULL); \ } while (0) #define TEBC_DATA_DIG() \ do { \ tosPtr = esPtr->tosPtr; \ } while (0) #define PUSH_TAUX_OBJ(objPtr) \ do { \ if (auxObjList) { \ (objPtr)->length += auxObjList->length; \ } \ (objPtr)->internalRep.twoPtrValue.ptr1 = auxObjList; \ auxObjList = (objPtr); \ } while (0) #define POP_TAUX_OBJ() \ do { \ tmpPtr = auxObjList; \ auxObjList = (Tcl_Obj *)tmpPtr->internalRep.twoPtrValue.ptr1; \ Tcl_DecrRefCount(tmpPtr); \ } while (0) /* * These variable-access macros have to coincide with those in tclVar.c */ #define VarHashGetValue(hPtr) \ ((Var *) ((char *)hPtr - offsetof(VarInHash, entry))) static inline Var * VarHashCreateVar( TclVarHashTable *tablePtr, Tcl_Obj *key, int *newPtr) { Tcl_HashEntry *hPtr = Tcl_CreateHashEntry(&tablePtr->table, key, newPtr); if (!hPtr) { return NULL; } return VarHashGetValue(hPtr); } #define VarHashFindVar(tablePtr, key) \ VarHashCreateVar((tablePtr), (key), NULL) /* * The new macro for ending an instruction; note that a reasonable C-optimiser * will resolve all branches at compile time. (result) is always a constant; * the macro NEXT_INST_F handles constant (nCleanup), NEXT_INST_V is resolved * at runtime for variable (nCleanup). * * ARGUMENTS: * pcAdjustment: how much to increment pc * nCleanup: how many objects to remove from the stack * resultHandling: 0 indicates no object should be pushed on the stack; * otherwise, push objResultPtr. If (result < 0), objResultPtr already * has the correct reference count. * * We use the new compile-time assertions to check that nCleanup is constant * and within range. */ /* Verify the stack depth, only when no expansion is in progress */ #ifdef TCL_COMPILE_DEBUG #define CHECK_STACK() \ do { \ ValidatePcAndStackTop(codePtr, pc, CURR_DEPTH, \ /*checkStack*/ !(starting || auxObjList)); \ starting = 0; \ } while (0) #else #define CHECK_STACK() #endif #define NEXT_INST_F(pcAdjustment, nCleanup, resultHandling) \ do { \ TCL_CT_ASSERT((nCleanup >= 0) && (nCleanup <= 2)); \ CHECK_STACK(); \ if (nCleanup == 0) { \ if (resultHandling != 0) { \ if ((resultHandling) > 0) { \ PUSH_OBJECT(objResultPtr); \ } else { \ *(++tosPtr) = objResultPtr; \ } \ } \ pc += (pcAdjustment); \ goto cleanup0; \ } else if (resultHandling != 0) { \ if ((resultHandling) > 0) { \ Tcl_IncrRefCount(objResultPtr); \ } \ pc += (pcAdjustment); \ switch (nCleanup) { \ case 1: goto cleanup1_pushObjResultPtr; \ case 2: goto cleanup2_pushObjResultPtr; \ case 0: break; \ } \ } else { \ pc += (pcAdjustment); \ switch (nCleanup) { \ case 1: goto cleanup1; \ case 2: goto cleanup2; \ case 0: break; \ } \ } \ } while (0) #define NEXT_INST_V(pcAdjustment, nCleanup, resultHandling) \ CHECK_STACK(); \ do { \ pc += (pcAdjustment); \ cleanup = (nCleanup); \ if (resultHandling) { \ if ((resultHandling) > 0) { \ Tcl_IncrRefCount(objResultPtr); \ } \ goto cleanupV_pushObjResultPtr; \ } else { \ goto cleanupV; \ } \ } while (0) #ifndef TCL_COMPILE_DEBUG #define JUMP_PEEPHOLE_F(condition, pcAdjustment, cleanup) \ do { \ pc += (pcAdjustment); \ switch (*pc) { \ case INST_JUMP_FALSE1: \ NEXT_INST_F(((condition)? 2 : TclGetInt1AtPtr(pc+1)), (cleanup), 0); \ break; \ case INST_JUMP_TRUE1: \ NEXT_INST_F(((condition)? TclGetInt1AtPtr(pc+1) : 2), (cleanup), 0); \ break; \ case INST_JUMP_FALSE4: \ NEXT_INST_F(((condition)? 5 : TclGetInt4AtPtr(pc+1)), (cleanup), 0); \ break; \ case INST_JUMP_TRUE4: \ NEXT_INST_F(((condition)? TclGetInt4AtPtr(pc+1) : 5), (cleanup), 0); \ break; \ default: \ if ((condition) < 0) { \ TclNewIntObj(objResultPtr, -1); \ } else { \ objResultPtr = TCONST((condition) > 0); \ } \ NEXT_INST_F(0, (cleanup), 1); \ break; \ } \ } while (0) #define JUMP_PEEPHOLE_V(condition, pcAdjustment, cleanup) \ do { \ pc += (pcAdjustment); \ switch (*pc) { \ case INST_JUMP_FALSE1: \ NEXT_INST_V(((condition)? 2 : TclGetInt1AtPtr(pc+1)), (cleanup), 0); \ break; \ case INST_JUMP_TRUE1: \ NEXT_INST_V(((condition)? TclGetInt1AtPtr(pc+1) : 2), (cleanup), 0); \ break; \ case INST_JUMP_FALSE4: \ NEXT_INST_V(((condition)? 5 : TclGetInt4AtPtr(pc+1)), (cleanup), 0); \ break; \ case INST_JUMP_TRUE4: \ NEXT_INST_V(((condition)? TclGetInt4AtPtr(pc+1) : 5), (cleanup), 0); \ break; \ default: \ if ((condition) < 0) { \ TclNewIntObj(objResultPtr, -1); \ } else { \ objResultPtr = TCONST((condition) > 0); \ } \ NEXT_INST_V(0, (cleanup), 1); \ break; \ } \ } while (0) #else /* TCL_COMPILE_DEBUG */ #define JUMP_PEEPHOLE_F(condition, pcAdjustment, cleanup) \ do{ \ if ((condition) < 0) { \ TclNewIntObj(objResultPtr, -1); \ } else { \ objResultPtr = TCONST((condition) > 0); \ } \ NEXT_INST_F((pcAdjustment), (cleanup), 1); \ } while (0) #define JUMP_PEEPHOLE_V(condition, pcAdjustment, cleanup) \ do{ \ if ((condition) < 0) { \ TclNewIntObj(objResultPtr, -1); \ } else { \ objResultPtr = TCONST((condition) > 0); \ } \ NEXT_INST_V((pcAdjustment), (cleanup), 1); \ } while (0) #endif /* * Macros used to cache often-referenced Tcl evaluation stack information * in local variables. Note that a DECACHE_STACK_INFO()-CACHE_STACK_INFO() * pair must surround any call inside TclNRExecuteByteCode (and a few other * procedures that use this scheme) that could result in a recursive call * to TclNRExecuteByteCode. */ #define CACHE_STACK_INFO() \ checkInterp = 1 #define DECACHE_STACK_INFO() \ esPtr->tosPtr = tosPtr /* * Macros used to access items on the Tcl evaluation stack. PUSH_OBJECT * increments the object's ref count since it makes the stack have another * reference pointing to the object. However, POP_OBJECT does not decrement * the ref count. This is because the stack may hold the only reference to the * object, so the object would be destroyed if its ref count were decremented * before the caller had a chance to, e.g., store it in a variable. It is the * caller's responsibility to decrement the ref count when it is finished with * an object. * * WARNING! It is essential that objPtr only appear once in the PUSH_OBJECT * macro. The actual parameter might be an expression with side effects, and * this ensures that it will be executed only once. */ #define PUSH_OBJECT(objPtr) \ Tcl_IncrRefCount(*(++tosPtr) = (objPtr)) #define POP_OBJECT() *(tosPtr--) #define OBJ_AT_TOS *tosPtr #define OBJ_UNDER_TOS tosPtr[-1] #define OBJ_AT_DEPTH(n) tosPtr[-(n)] #define CURR_DEPTH (tosPtr - initTosPtr) #define STACK_BASE(esPtr) ((esPtr)->stackWords - 1) /* * Macros used to trace instruction execution. The macros TRACE, * TRACE_WITH_OBJ, and O2S are only used inside TclNRExecuteByteCode. O2S is * only used in TRACE* calls to get a string from an object. */ #ifdef TCL_COMPILE_DEBUG # define TRACE(a) \ while (traceInstructions) { \ fprintf(stdout, "%2" TCL_SIZE_MODIFIER "d: %2" TCL_T_MODIFIER \ "d (%" TCL_T_MODIFIER "d) %s ", iPtr->numLevels, \ CURR_DEPTH, \ (pc - codePtr->codeStart), \ GetOpcodeName(pc)); \ printf a; \ break; \ } # define TRACE_APPEND(a) \ while (traceInstructions) { \ printf a; \ break; \ } # define TRACE_ERROR(interp) \ TRACE_APPEND(("ERROR: %.30s\n", O2S(Tcl_GetObjResult(interp)))); # define TRACE_WITH_OBJ(a, objPtr) \ while (traceInstructions) { \ fprintf(stdout, "%2" TCL_SIZE_MODIFIER "d: %2" TCL_T_MODIFIER \ "d (%" TCL_T_MODIFIER "d) %s ", iPtr->numLevels, \ CURR_DEPTH, \ (pc - codePtr->codeStart), \ GetOpcodeName(pc)); \ printf a; \ TclPrintObject(stdout, objPtr, 30); \ fprintf(stdout, "\n"); \ break; \ } # define O2S(objPtr) \ (objPtr ? TclGetString(objPtr) : "") #else /* !TCL_COMPILE_DEBUG */ # define TRACE(a) # define TRACE_APPEND(a) # define TRACE_ERROR(interp) # define TRACE_WITH_OBJ(a, objPtr) # define O2S(objPtr) #endif /* TCL_COMPILE_DEBUG */ /* * DTrace instruction probe macros. */ #define TCL_DTRACE_INST_NEXT() \ do { \ if (TCL_DTRACE_INST_DONE_ENABLED()) { \ if (curInstName) { \ TCL_DTRACE_INST_DONE(curInstName, (int) CURR_DEPTH, \ tosPtr); \ } \ curInstName = tclInstructionTable[*pc].name; \ if (TCL_DTRACE_INST_START_ENABLED()) { \ TCL_DTRACE_INST_START(curInstName, (int) CURR_DEPTH, \ tosPtr); \ } \ } else if (TCL_DTRACE_INST_START_ENABLED()) { \ TCL_DTRACE_INST_START(tclInstructionTable[*pc].name, \ (int) CURR_DEPTH, tosPtr); \ } \ } while (0) #define TCL_DTRACE_INST_LAST() \ do { \ if (TCL_DTRACE_INST_DONE_ENABLED() && curInstName) { \ TCL_DTRACE_INST_DONE(curInstName, (int) CURR_DEPTH, tosPtr);\ } \ } while (0) /* * Macro used in this file to save a function call for common uses of * Tcl_GetNumberFromObj(). The ANSI C "prototype" is: * * MODULE_SCOPE int GetNumberFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, * void **ptrPtr, int *tPtr); */ #define GetNumberFromObj(interp, objPtr, ptrPtr, tPtr) \ ((TclHasInternalRep((objPtr), &tclIntType)) \ ? (*(tPtr) = TCL_NUMBER_INT, \ *(ptrPtr) = (void *) \ (&((objPtr)->internalRep.wideValue)), TCL_OK) : \ TclHasInternalRep((objPtr), &tclDoubleType) \ ? (((isnan((objPtr)->internalRep.doubleValue)) \ ? (*(tPtr) = TCL_NUMBER_NAN) \ : (*(tPtr) = TCL_NUMBER_DOUBLE)), \ *(ptrPtr) = (void *) \ (&((objPtr)->internalRep.doubleValue)), TCL_OK) : \ (((objPtr)->bytes != NULL) && ((objPtr)->length == 0)) \ ? TCL_ERROR : \ Tcl_GetNumberFromObj((interp), (objPtr), (ptrPtr), (tPtr))) /* * Macro used to make the check for type overflow more mnemonic. This works by * comparing sign bits; the rest of the word is irrelevant. The ANSI C * "prototype" (where inttype_t is any integer type) is: * * MODULE_SCOPE int Overflowing(inttype_t a, inttype_t b, inttype_t sum); * * Check first the condition most likely to fail in usual code (at least for * usage in [incr]: do the first summand and the sum have != signs? */ #define Overflowing(a,b,sum) \ ((((a)^(sum)) < 0) && (((a)^(b)) >= 0)) /* * Macro for checking whether the type is NaN, used when we're thinking about * throwing an error for supplying a non-number number. */ #ifndef ACCEPT_NAN #define IsErroringNaNType(type) ((type) == TCL_NUMBER_NAN) #else #define IsErroringNaNType(type) 0 #endif /* * Auxiliary tables used to compute powers of small integers. */ /* * Maximum base that, when raised to powers 2, 3, ..., 16, fits in a * Tcl_WideInt. */ static const Tcl_WideInt MaxBase64[] = { (Tcl_WideInt)46340*65536+62259, /* 3037000499 == isqrt(2**63-1) */ (Tcl_WideInt)2097151, (Tcl_WideInt)55108, (Tcl_WideInt)6208, (Tcl_WideInt)1448, (Tcl_WideInt)511, (Tcl_WideInt)234, (Tcl_WideInt)127, (Tcl_WideInt)78, (Tcl_WideInt)52, (Tcl_WideInt)38, (Tcl_WideInt)28, (Tcl_WideInt)22, (Tcl_WideInt)18, (Tcl_WideInt)15 }; static const size_t MaxBase64Size = sizeof(MaxBase64)/sizeof(Tcl_WideInt); /* * Table giving 3, 4, ..., 13 raised to powers greater than 16 when the * results fit in a 64-bit signed integer. */ static const unsigned short Exp64Index[] = { 0, 23, 38, 49, 57, 63, 67, 70, 72, 74, 75, 76 }; static const size_t Exp64IndexSize = sizeof(Exp64Index) / sizeof(unsigned short); static const Tcl_WideInt Exp64Value[] = { (Tcl_WideInt)243*243*243*3*3, (Tcl_WideInt)243*243*243*3*3*3, (Tcl_WideInt)243*243*243*3*3*3*3, (Tcl_WideInt)243*243*243*243, (Tcl_WideInt)243*243*243*243*3, (Tcl_WideInt)243*243*243*243*3*3, (Tcl_WideInt)243*243*243*243*3*3*3, (Tcl_WideInt)243*243*243*243*3*3*3*3, (Tcl_WideInt)243*243*243*243*243, (Tcl_WideInt)243*243*243*243*243*3, (Tcl_WideInt)243*243*243*243*243*3*3, (Tcl_WideInt)243*243*243*243*243*3*3*3, (Tcl_WideInt)243*243*243*243*243*3*3*3*3, (Tcl_WideInt)243*243*243*243*243*243, (Tcl_WideInt)243*243*243*243*243*243*3, (Tcl_WideInt)243*243*243*243*243*243*3*3, (Tcl_WideInt)243*243*243*243*243*243*3*3*3, (Tcl_WideInt)243*243*243*243*243*243*3*3*3*3, (Tcl_WideInt)243*243*243*243*243*243*243, (Tcl_WideInt)243*243*243*243*243*243*243*3, (Tcl_WideInt)243*243*243*243*243*243*243*3*3, (Tcl_WideInt)243*243*243*243*243*243*243*3*3*3, (Tcl_WideInt)243*243*243*243*243*243*243*3*3*3*3, (Tcl_WideInt)1024*1024*1024*4*4, (Tcl_WideInt)1024*1024*1024*4*4*4, (Tcl_WideInt)1024*1024*1024*4*4*4*4, (Tcl_WideInt)1024*1024*1024*1024, (Tcl_WideInt)1024*1024*1024*1024*4, (Tcl_WideInt)1024*1024*1024*1024*4*4, (Tcl_WideInt)1024*1024*1024*1024*4*4*4, (Tcl_WideInt)1024*1024*1024*1024*4*4*4*4, (Tcl_WideInt)1024*1024*1024*1024*1024, (Tcl_WideInt)1024*1024*1024*1024*1024*4, (Tcl_WideInt)1024*1024*1024*1024*1024*4*4, (Tcl_WideInt)1024*1024*1024*1024*1024*4*4*4, (Tcl_WideInt)1024*1024*1024*1024*1024*4*4*4*4, (Tcl_WideInt)1024*1024*1024*1024*1024*1024, (Tcl_WideInt)1024*1024*1024*1024*1024*1024*4, (Tcl_WideInt)3125*3125*3125*5*5, (Tcl_WideInt)3125*3125*3125*5*5*5, (Tcl_WideInt)3125*3125*3125*5*5*5*5, (Tcl_WideInt)3125*3125*3125*3125, (Tcl_WideInt)3125*3125*3125*3125*5, (Tcl_WideInt)3125*3125*3125*3125*5*5, (Tcl_WideInt)3125*3125*3125*3125*5*5*5, (Tcl_WideInt)3125*3125*3125*3125*5*5*5*5, (Tcl_WideInt)3125*3125*3125*3125*3125, (Tcl_WideInt)3125*3125*3125*3125*3125*5, (Tcl_WideInt)3125*3125*3125*3125*3125*5*5, (Tcl_WideInt)7776*7776*7776*6*6, (Tcl_WideInt)7776*7776*7776*6*6*6, (Tcl_WideInt)7776*7776*7776*6*6*6*6, (Tcl_WideInt)7776*7776*7776*7776, (Tcl_WideInt)7776*7776*7776*7776*6, (Tcl_WideInt)7776*7776*7776*7776*6*6, (Tcl_WideInt)7776*7776*7776*7776*6*6*6, (Tcl_WideInt)7776*7776*7776*7776*6*6*6*6, (Tcl_WideInt)16807*16807*16807*7*7, (Tcl_WideInt)16807*16807*16807*7*7*7, (Tcl_WideInt)16807*16807*16807*7*7*7*7, (Tcl_WideInt)16807*16807*16807*16807, (Tcl_WideInt)16807*16807*16807*16807*7, (Tcl_WideInt)16807*16807*16807*16807*7*7, (Tcl_WideInt)32768*32768*32768*8*8, (Tcl_WideInt)32768*32768*32768*8*8*8, (Tcl_WideInt)32768*32768*32768*8*8*8*8, (Tcl_WideInt)32768*32768*32768*32768, (Tcl_WideInt)59049*59049*59049*9*9, (Tcl_WideInt)59049*59049*59049*9*9*9, (Tcl_WideInt)59049*59049*59049*9*9*9*9, (Tcl_WideInt)100000*100000*100000*10*10, (Tcl_WideInt)100000*100000*100000*10*10*10, (Tcl_WideInt)161051*161051*161051*11*11, (Tcl_WideInt)161051*161051*161051*11*11*11, (Tcl_WideInt)248832*248832*248832*12*12, (Tcl_WideInt)371293*371293*371293*13*13 }; static const size_t Exp64ValueSize = sizeof(Exp64Value) / sizeof(Tcl_WideInt); /* * Markers for ExecuteExtendedBinaryMathOp. */ #define DIVIDED_BY_ZERO ((Tcl_Obj *) -1) #define EXPONENT_OF_ZERO ((Tcl_Obj *) -2) #define GENERAL_ARITHMETIC_ERROR ((Tcl_Obj *) -3) #define OUT_OF_MEMORY ((Tcl_Obj *) -4) /* * Declarations for local procedures to this file: */ #ifdef TCL_COMPILE_STATS static Tcl_ObjCmdProc EvalStatsCmd; #endif /* TCL_COMPILE_STATS */ #ifdef TCL_COMPILE_DEBUG static const char * GetOpcodeName(const unsigned char *pc); static void PrintByteCodeInfo(ByteCode *codePtr); static const char * StringForResultCode(int result); static void ValidatePcAndStackTop(ByteCode *codePtr, const unsigned char *pc, size_t stackTop, int checkStack); #endif /* TCL_COMPILE_DEBUG */ static ByteCode * CompileExprObj(Tcl_Interp *interp, Tcl_Obj *objPtr); static void DeleteExecStack(ExecStack *esPtr); static void DupExprCodeInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr); static Tcl_Obj * ExecuteExtendedBinaryMathOp(Tcl_Interp *interp, int opcode, Tcl_Obj **constants, Tcl_Obj *valuePtr, Tcl_Obj *value2Ptr); static Tcl_Obj * ExecuteExtendedUnaryMathOp(int opcode, Tcl_Obj *valuePtr); static void FreeExprCodeInternalRep(Tcl_Obj *objPtr); static ExceptionRange * GetExceptRangeForPc(const unsigned char *pc, int searchMode, ByteCode *codePtr); static const char * GetSrcInfoForPc(const unsigned char *pc, ByteCode *codePtr, Tcl_Size *lengthPtr, const unsigned char **pcBeg, Tcl_Size *cmdIdxPtr); static Tcl_Obj ** GrowEvaluationStack(ExecEnv *eePtr, size_t growth, int move); static void IllegalExprOperandType(Tcl_Interp *interp, const char *ord, const unsigned char *pc, Tcl_Obj *opndPtr); static void InitByteCodeExecution(Tcl_Interp *interp); static inline int wordSkip(void *ptr); static void ReleaseDictIterator(Tcl_Obj *objPtr); /* Useful elsewhere, make available in tclInt.h or stubs? */ static Tcl_Obj ** StackAllocWords(Tcl_Interp *interp, size_t numWords); static Tcl_Obj ** StackReallocWords(Tcl_Interp *interp, size_t numWords); static Tcl_NRPostProc CopyCallback; static Tcl_NRPostProc ExprObjCallback; static Tcl_NRPostProc FinalizeOONext; static Tcl_NRPostProc FinalizeOONextFilter; static Tcl_NRPostProc TEBCresume; /* * The structure below defines a bytecode Tcl object type to hold the * compiled bytecode for Tcl expressions. */ const Tcl_ObjType tclExprCodeType = { "exprcode", FreeExprCodeInternalRep, /* freeIntRepProc */ DupExprCodeInternalRep, /* dupIntRepProc */ NULL, /* updateStringProc */ NULL, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; /* * Custom object type only used in this file; values of its type should never * be seen by user scripts. */ static const Tcl_ObjType dictIteratorType = { "dictIterator", ReleaseDictIterator, NULL, NULL, NULL, TCL_OBJTYPE_V0 }; /* *---------------------------------------------------------------------- * * ReleaseDictIterator -- * * This takes apart a dictionary iterator that is stored in the given Tcl * object. * * Results: * None. * * Side effects: * Deallocates memory, marks the object as being untyped. * *---------------------------------------------------------------------- */ static void ReleaseDictIterator( Tcl_Obj *objPtr) { Tcl_DictSearch *searchPtr; Tcl_Obj *dictPtr; const Tcl_ObjInternalRep *irPtr; irPtr = TclFetchInternalRep(objPtr, &dictIteratorType); assert(irPtr != NULL); /* * First kill the search, and then release the reference to the dictionary * that we were holding. */ searchPtr = (Tcl_DictSearch *)irPtr->twoPtrValue.ptr1; Tcl_DictObjDone(searchPtr); Tcl_Free(searchPtr); dictPtr = (Tcl_Obj *)irPtr->twoPtrValue.ptr2; TclDecrRefCount(dictPtr); } /* *---------------------------------------------------------------------- * * InitByteCodeExecution -- * * This procedure is called once to initialize the Tcl bytecode * interpreter. * * Results: * None. * * Side effects: * This procedure initializes the array of instruction names. If * compiling with the TCL_COMPILE_STATS flag, it initializes the array * that counts the executions of each instruction and it creates the * "evalstats" command. It also establishes the link between the Tcl * "tcl_traceExec" and C "tclTraceExec" variables. * *---------------------------------------------------------------------- */ #if defined(TCL_COMPILE_STATS) || defined(TCL_COMPILE_DEBUG) static void InitByteCodeExecution( Tcl_Interp *interp) /* Interpreter for which the Tcl variable * "tcl_traceExec" is linked to control * instruction tracing. */ { #ifdef TCL_COMPILE_DEBUG if (Tcl_LinkVar(interp, "tcl_traceExec", &tclTraceExec, TCL_LINK_INT) != TCL_OK) { Tcl_Panic("InitByteCodeExecution: can't create link for tcl_traceExec variable"); } #endif #ifdef TCL_COMPILE_STATS Tcl_CreateObjCommand(interp, "evalstats", EvalStatsCmd, NULL, NULL); #endif /* TCL_COMPILE_STATS */ } #else static void InitByteCodeExecution( TCL_UNUSED(Tcl_Interp *)) { } #endif /* *---------------------------------------------------------------------- * * TclCreateExecEnv -- * * This procedure creates a new execution environment for Tcl bytecode * execution. An ExecEnv points to a Tcl evaluation stack. An ExecEnv is * typically created once for each Tcl interpreter (Interp structure) and * recursively passed to TclNRExecuteByteCode to execute ByteCode sequences * for nested commands. * * Results: * A newly allocated ExecEnv is returned. This points to an empty * evaluation stack of the standard initial size. * * Side effects: * The bytecode interpreter is also initialized here, as this procedure * will be called before any call to TclNRExecuteByteCode. * *---------------------------------------------------------------------- */ ExecEnv * TclCreateExecEnv( Tcl_Interp *interp, /* Interpreter for which the execution * environment is being created. */ size_t size) /* The initial stack size, in number of words * [sizeof(Tcl_Obj*)] */ { ExecEnv *eePtr = (ExecEnv *)Tcl_Alloc(sizeof(ExecEnv)); ExecStack *esPtr = (ExecStack *)Tcl_Alloc(offsetof(ExecStack, stackWords) + size * sizeof(Tcl_Obj *)); eePtr->execStackPtr = esPtr; TclNewIntObj(eePtr->constants[0], 0); Tcl_IncrRefCount(eePtr->constants[0]); TclNewIntObj(eePtr->constants[1], 1); Tcl_IncrRefCount(eePtr->constants[1]); eePtr->interp = interp; eePtr->callbackPtr = NULL; eePtr->corPtr = NULL; eePtr->rewind = 0; esPtr->prevPtr = NULL; esPtr->nextPtr = NULL; esPtr->markerPtr = NULL; esPtr->endPtr = &esPtr->stackWords[size-1]; esPtr->tosPtr = STACK_BASE(esPtr); Tcl_MutexLock(&execMutex); if (!execInitialized) { InitByteCodeExecution(interp); execInitialized = 1; } Tcl_MutexUnlock(&execMutex); return eePtr; } /* *---------------------------------------------------------------------- * * TclDeleteExecEnv -- * * Frees the storage for an ExecEnv. * * Results: * None. * * Side effects: * Storage for an ExecEnv and its contained storage (e.g. the evaluation * stack) is freed. * *---------------------------------------------------------------------- */ static void DeleteExecStack( ExecStack *esPtr) { if (esPtr->markerPtr && !cachedInExit) { Tcl_Panic("freeing an execStack which is still in use"); } if (esPtr->prevPtr) { esPtr->prevPtr->nextPtr = esPtr->nextPtr; } if (esPtr->nextPtr) { esPtr->nextPtr->prevPtr = esPtr->prevPtr; } Tcl_Free(esPtr); } void TclDeleteExecEnv( ExecEnv *eePtr) /* Execution environment to free. */ { ExecStack *esPtr = eePtr->execStackPtr, *tmpPtr; cachedInExit = TclInExit(); /* * Delete all stacks in this exec env. */ while (esPtr->nextPtr) { esPtr = esPtr->nextPtr; } while (esPtr) { tmpPtr = esPtr; esPtr = tmpPtr->prevPtr; DeleteExecStack(tmpPtr); } TclDecrRefCount(eePtr->constants[0]); TclDecrRefCount(eePtr->constants[1]); if (eePtr->callbackPtr && !cachedInExit) { Tcl_Panic("Deleting execEnv with pending TEOV callbacks!"); } if (eePtr->corPtr && !cachedInExit) { Tcl_Panic("Deleting execEnv with existing coroutine"); } Tcl_Free(eePtr); } /* *---------------------------------------------------------------------- * * TclFinalizeExecution -- * * Finalizes the execution environment setup so that it can be later * reinitialized. * * Results: * None. * * Side effects: * After this call, the next time TclCreateExecEnv will be called it will * call InitByteCodeExecution. * *---------------------------------------------------------------------- */ void TclFinalizeExecution(void) { Tcl_MutexLock(&execMutex); execInitialized = 0; Tcl_MutexUnlock(&execMutex); } /* * Auxiliary code to insure that GrowEvaluationStack always returns correctly * aligned memory. * * WALLOCALIGN represents the alignment reqs in words, just as TCL_ALLOCALIGN * represents the reqs in bytes. This assumes that TCL_ALLOCALIGN is a * multiple of the wordsize 'sizeof(Tcl_Obj *)'. */ #define WALLOCALIGN \ (TCL_ALLOCALIGN/sizeof(Tcl_Obj *)) /* * wordSkip computes how many words have to be skipped until the next aligned * word. Note that we are only interested in the low order bits of ptr, so * that any possible information loss in PTR2INT is of no consequence. */ static inline int wordSkip( void *ptr) { int mask = TCL_ALLOCALIGN-1; int base = PTR2INT(ptr) & mask; return (TCL_ALLOCALIGN - base)/sizeof(Tcl_Obj *); } /* * Given a marker, compute where the following aligned memory starts. */ #define MEMSTART(markerPtr) \ ((markerPtr) + wordSkip(markerPtr)) /* *---------------------------------------------------------------------- * * GrowEvaluationStack -- * * This procedure grows a Tcl evaluation stack stored in an ExecEnv, * copying over the words since the last mark if so requested. A mark is * set at the beginning of the new area when no copying is requested. * * Results: * Returns a pointer to the first usable word in the (possibly) grown * stack. * * Side effects: * The size of the evaluation stack may be grown, a marker is set * *---------------------------------------------------------------------- */ static Tcl_Obj ** GrowEvaluationStack( ExecEnv *eePtr, /* Points to the ExecEnv with an evaluation * stack to enlarge. */ size_t growth1, /* How much larger than the current used * size. */ int move) /* 1 if move words since last marker. */ { ExecStack *esPtr = eePtr->execStackPtr, *oldPtr = NULL; size_t newBytes; Tcl_Size growth = growth1; Tcl_Size newElems, currElems, needed = growth - (esPtr->endPtr - esPtr->tosPtr); Tcl_Obj **markerPtr = esPtr->markerPtr, **memStart; Tcl_Size moveWords = 0; if (move) { if (!markerPtr) { Tcl_Panic("STACK: Reallocating with no previous alloc"); } if (needed <= 0) { return MEMSTART(markerPtr); } } else { #ifndef PURIFY Tcl_Obj **tmpMarkerPtr = esPtr->tosPtr + 1; int offset = wordSkip(tmpMarkerPtr); if (needed + offset < 0) { /* * Put a marker pointing to the previous marker in this stack, and * store it in esPtr as the current marker. Return a pointer to * the start of aligned memory. */ esPtr->markerPtr = tmpMarkerPtr; memStart = tmpMarkerPtr + offset; esPtr->tosPtr = memStart - 1; *esPtr->markerPtr = (Tcl_Obj *) markerPtr; return memStart; } #endif } /* * Reset move to hold the number of words to be moved to new stack (if * any) and growth to hold the complete stack requirements: add one for * the marker, (WALLOCALIGN-1) for the maximal possible offset. */ if (move) { moveWords = esPtr->tosPtr - MEMSTART(markerPtr) + 1; } needed = growth + moveWords + WALLOCALIGN; /* * Check if there is enough room in the next stack (if there is one, it * should be both empty and the last one!) */ if (esPtr->nextPtr) { oldPtr = esPtr; esPtr = oldPtr->nextPtr; currElems = esPtr->endPtr - STACK_BASE(esPtr); if (esPtr->markerPtr || (esPtr->tosPtr != STACK_BASE(esPtr))) { Tcl_Panic("STACK: Stack after current is in use"); } if (esPtr->nextPtr) { Tcl_Panic("STACK: Stack after current is not last"); } if (needed <= currElems) { goto newStackReady; } DeleteExecStack(esPtr); esPtr = oldPtr; } else { currElems = esPtr->endPtr - STACK_BASE(esPtr); } /* * We need to allocate a new stack! It needs to store 'growth' words, * including the elements to be copied over and the new marker. */ #ifndef PURIFY newElems = 2*currElems; while (needed > newElems) { newElems *= 2; } #else newElems = needed; #endif newBytes = offsetof(ExecStack, stackWords) + newElems * sizeof(Tcl_Obj *); oldPtr = esPtr; esPtr = (ExecStack *)Tcl_Alloc(newBytes); oldPtr->nextPtr = esPtr; esPtr->prevPtr = oldPtr; esPtr->nextPtr = NULL; esPtr->endPtr = &esPtr->stackWords[newElems-1]; newStackReady: eePtr->execStackPtr = esPtr; /* * Store a NULL marker at the beginning of the stack, to indicate that * this is the first marker in this stack and that rewinding to here * should actually be a return to the previous stack. */ esPtr->stackWords[0] = NULL; esPtr->markerPtr = &esPtr->stackWords[0]; memStart = MEMSTART(esPtr->markerPtr); esPtr->tosPtr = memStart - 1; if (move) { memcpy(memStart, MEMSTART(markerPtr), moveWords*sizeof(Tcl_Obj *)); esPtr->tosPtr += moveWords; oldPtr->markerPtr = (Tcl_Obj **) *markerPtr; oldPtr->tosPtr = markerPtr-1; } /* * Free the old stack if it is now unused. */ if (!oldPtr->markerPtr) { DeleteExecStack(oldPtr); } return memStart; } /* *-------------------------------------------------------------- * * TclStackAlloc, TclStackRealloc, TclStackFree -- * * Allocate memory from the execution stack; it has to be returned later * with a call to TclStackFree. * * Results: * A pointer to the first byte allocated, or panics if the allocation did * not succeed. * * Side effects: * The execution stack may be grown. * *-------------------------------------------------------------- */ static Tcl_Obj ** StackAllocWords( Tcl_Interp *interp, size_t numWords) { /* * Note that GrowEvaluationStack sets a marker in the stack. This marker * is read when rewinding, e.g., by TclStackFree. */ Interp *iPtr = (Interp *) interp; ExecEnv *eePtr = iPtr->execEnvPtr; Tcl_Obj **resPtr = GrowEvaluationStack(eePtr, numWords, 0); eePtr->execStackPtr->tosPtr += numWords; return resPtr; } static Tcl_Obj ** StackReallocWords( Tcl_Interp *interp, size_t numWords) { Interp *iPtr = (Interp *) interp; ExecEnv *eePtr = iPtr->execEnvPtr; Tcl_Obj **resPtr = GrowEvaluationStack(eePtr, numWords, 1); eePtr->execStackPtr->tosPtr += numWords; return resPtr; } void TclStackFree( Tcl_Interp *interp, void *freePtr) { Interp *iPtr = (Interp *) interp; ExecEnv *eePtr; ExecStack *esPtr; Tcl_Obj **markerPtr, *marker; if (iPtr == NULL || iPtr->execEnvPtr == NULL) { Tcl_Free(freePtr); return; } /* * Rewind the stack to the previous marker position. The current marker, * as set in the last call to GrowEvaluationStack, contains a pointer to * the previous marker. */ eePtr = iPtr->execEnvPtr; esPtr = eePtr->execStackPtr; markerPtr = esPtr->markerPtr; marker = *markerPtr; if ((freePtr != NULL) && (MEMSTART(markerPtr) != (Tcl_Obj **)freePtr)) { Tcl_Panic("TclStackFree: incorrect freePtr (%p != %p). Call out of sequence?", freePtr, MEMSTART(markerPtr)); } esPtr->tosPtr = markerPtr - 1; esPtr->markerPtr = (Tcl_Obj **) marker; if (marker) { return; } /* * Return to previous active stack. Note that repeated expansions or * reallocs could have generated several unused intervening stacks: free * them too. */ while (esPtr->nextPtr) { esPtr = esPtr->nextPtr; } esPtr->tosPtr = STACK_BASE(esPtr); while (esPtr->prevPtr) { ExecStack *tmpPtr = esPtr->prevPtr; if (tmpPtr->tosPtr == STACK_BASE(tmpPtr)) { DeleteExecStack(tmpPtr); } else { break; } } if (esPtr->prevPtr) { eePtr->execStackPtr = esPtr->prevPtr; #ifdef PURIFY eePtr->execStackPtr->nextPtr = NULL; DeleteExecStack(esPtr); #endif } else { eePtr->execStackPtr = esPtr; } } void * TclStackAlloc( Tcl_Interp *interp, size_t numBytes) { Interp *iPtr = (Interp *) interp; size_t numWords; if (iPtr == NULL || iPtr->execEnvPtr == NULL) { return Tcl_Alloc(numBytes); } numWords = (numBytes + (sizeof(Tcl_Obj *) - 1))/sizeof(Tcl_Obj *); return StackAllocWords(interp, numWords); } void * TclStackRealloc( Tcl_Interp *interp, void *ptr, size_t numBytes) { Interp *iPtr = (Interp *) interp; ExecEnv *eePtr; ExecStack *esPtr; Tcl_Obj **markerPtr; size_t numWords; if (iPtr == NULL || iPtr->execEnvPtr == NULL) { return Tcl_Realloc(ptr, numBytes); } eePtr = iPtr->execEnvPtr; esPtr = eePtr->execStackPtr; markerPtr = esPtr->markerPtr; if (MEMSTART(markerPtr) != (Tcl_Obj **)ptr) { Tcl_Panic("TclStackRealloc: incorrect ptr. Call out of sequence?"); } numWords = (numBytes + (sizeof(Tcl_Obj *) - 1))/sizeof(Tcl_Obj *); return (void *) StackReallocWords(interp, numWords); } /* *-------------------------------------------------------------- * * Tcl_ExprObj -- * * Evaluate an expression in a Tcl_Obj. * * Results: * A standard Tcl object result. If the result is other than TCL_OK, then * the interpreter's result contains an error message. If the result is * TCL_OK, then a pointer to the expression's result value object is * stored in resultPtrPtr. In that case, the object's ref count is * incremented to reflect the reference returned to the caller; the * caller is then responsible for the resulting object and must, for * example, decrement the ref count when it is finished with the object. * * Side effects: * Any side effects caused by subcommands in the expression, if any. The * interpreter result is not modified unless there is an error. * *-------------------------------------------------------------- */ int Tcl_ExprObj( Tcl_Interp *interp, /* Context in which to evaluate the * expression. */ Tcl_Obj *objPtr, /* Points to Tcl object containing expression * to evaluate. */ Tcl_Obj **resultPtrPtr) /* Where the Tcl_Obj* that is the expression * result is stored if no errors occur. */ { NRE_callback *rootPtr = TOP_CB(interp); Tcl_Obj *resultPtr; TclNewObj(resultPtr); TclNRAddCallback(interp, CopyCallback, resultPtrPtr, resultPtr, NULL, NULL); Tcl_NRExprObj(interp, objPtr, resultPtr); return TclNRRunCallbacks(interp, TCL_OK, rootPtr); } static int CopyCallback( void *data[], TCL_UNUSED(Tcl_Interp *), int result) { Tcl_Obj **resultPtrPtr = (Tcl_Obj **)data[0]; Tcl_Obj *resultPtr = (Tcl_Obj *)data[1]; if (result == TCL_OK) { *resultPtrPtr = resultPtr; Tcl_IncrRefCount(resultPtr); } else { Tcl_DecrRefCount(resultPtr); } return result; } /* *-------------------------------------------------------------- * * Tcl_NRExprObj -- * * Request evaluation of the expression in a Tcl_Obj by the NR stack. * * Results: * Returns TCL_OK. * * Side effects: * Compiles objPtr as a Tcl expression and places callbacks on the * NR stack to execute the bytecode and store the result in resultPtr. * If bytecode execution raises an exception, nothing is written * to resultPtr, and the exceptional return code flows up the NR * stack. If the exception is TCL_ERROR, an error message is left * in the interp result and the interp's return options dictionary * holds additional error information too. Execution of the bytecode * may have other side effects, depending on the expression. * *-------------------------------------------------------------- */ int Tcl_NRExprObj( Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj *resultPtr) { ByteCode *codePtr; Tcl_InterpState state = Tcl_SaveInterpState(interp, TCL_OK); Tcl_ResetResult(interp); codePtr = CompileExprObj(interp, objPtr); Tcl_NRAddCallback(interp, ExprObjCallback, state, resultPtr, NULL, NULL); return TclNRExecuteByteCode(interp, codePtr); } static int ExprObjCallback( void *data[], Tcl_Interp *interp, int result) { Tcl_InterpState state = (Tcl_InterpState)data[0]; Tcl_Obj *resultPtr = (Tcl_Obj *)data[1]; if (result == TCL_OK) { TclSetDuplicateObj(resultPtr, Tcl_GetObjResult(interp)); (void) Tcl_RestoreInterpState(interp, state); } else { Tcl_DiscardInterpState(state); } return result; } /* *---------------------------------------------------------------------- * * CompileExprObj -- * Compile a Tcl expression value into ByteCode. * * Results: * A (ByteCode *) is returned pointing to the resulting ByteCode. * * Side effects: * The Tcl_ObjType of objPtr is changed to the "exprcode" type, * and the ByteCode is kept in the internal rep (along with context * data for checking validity) for faster operations the next time * CompileExprObj is called on the same value. * *---------------------------------------------------------------------- */ static ByteCode * CompileExprObj( Tcl_Interp *interp, Tcl_Obj *objPtr) { Interp *iPtr = (Interp *) interp; CompileEnv compEnv; /* Compilation environment structure allocated * in frame. */ ByteCode *codePtr = NULL; /* Tcl Internal type of bytecode. Initialized * to avoid compiler warning. */ /* * Get the expression ByteCode from the object. If it exists, make sure it * is valid in the current context. */ ByteCodeGetInternalRep(objPtr, &tclExprCodeType, codePtr); if (codePtr != NULL) { Namespace *namespacePtr = iPtr->varFramePtr->nsPtr; if (((Interp *) *codePtr->interpHandle != iPtr) || (codePtr->compileEpoch != iPtr->compileEpoch) || (codePtr->nsPtr != namespacePtr) || (codePtr->nsEpoch != namespacePtr->resolverEpoch) || (codePtr->localCachePtr != iPtr->varFramePtr->localCachePtr)) { Tcl_StoreInternalRep(objPtr, &tclExprCodeType, NULL); codePtr = NULL; } } if (codePtr == NULL) { /* * TIP #280: No invoker (yet) - Expression compilation. */ Tcl_Size length; const char *string = TclGetStringFromObj(objPtr, &length); TclInitCompileEnv(interp, &compEnv, string, length, NULL, 0); TclCompileExpr(interp, string, length, &compEnv, 0); /* * Successful compilation. If the expression yielded no instructions, * push an zero object as the expression's result. */ if (compEnv.codeNext == compEnv.codeStart) { TclEmitPush(TclRegisterLiteral(&compEnv, "0", 1, 0), &compEnv); } /* * Add a "done" instruction as the last instruction and change the * object into a ByteCode object. Ownership of the literal objects and * aux data items is given to the ByteCode object. */ TclEmitOpcode(INST_DONE, &compEnv); codePtr = TclInitByteCodeObj(objPtr, &tclExprCodeType, &compEnv); TclFreeCompileEnv(&compEnv); if (iPtr->varFramePtr->localCachePtr) { codePtr->localCachePtr = iPtr->varFramePtr->localCachePtr; codePtr->localCachePtr->refCount++; } TclDebugPrintByteCodeObj(objPtr); } return codePtr; } /* *---------------------------------------------------------------------- * * DupExprCodeInternalRep -- * * Part of the Tcl object type implementation for Tcl expression * bytecode. We do not copy the bytecode internalrep. Instead, we return * without setting copyPtr->typePtr, so the copy is a plain string copy * of the expression value, and if it is to be used as a compiled * expression, it will just need a recompile. * * This makes sense, because with Tcl's copy-on-write practices, the * usual (only?) time Tcl_DuplicateObj() will be called is when the copy * is about to be modified, which would invalidate any copied bytecode * anyway. The only reason it might make sense to copy the bytecode is if * we had some modifying routines that operated directly on the internalrep, * like we do for lists and dicts. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void DupExprCodeInternalRep( TCL_UNUSED(Tcl_Obj *), TCL_UNUSED(Tcl_Obj *)) { return; } /* *---------------------------------------------------------------------- * * FreeExprCodeInternalRep -- * * Part of the Tcl object type implementation for Tcl expression * bytecode. Frees the storage allocated to hold the internal rep, unless * ref counts indicate bytecode execution is still in progress. * * Results: * None. * * Side effects: * May free allocated memory. Leaves objPtr untyped. * *---------------------------------------------------------------------- */ static void FreeExprCodeInternalRep( Tcl_Obj *objPtr) { ByteCode *codePtr; ByteCodeGetInternalRep(objPtr, &tclExprCodeType, codePtr); assert(codePtr != NULL); TclReleaseByteCode(codePtr); } /* *---------------------------------------------------------------------- * * TclCompileObj -- * * This procedure compiles the script contained in a Tcl_Obj. * * Results: * A pointer to the corresponding ByteCode, never NULL. * * Side effects: * The object is shimmered to bytecode type. * *---------------------------------------------------------------------- */ ByteCode * TclCompileObj( Tcl_Interp *interp, Tcl_Obj *objPtr, const CmdFrame *invoker, int word) { Interp *iPtr = (Interp *) interp; ByteCode *codePtr; /* Tcl Internal type of bytecode. */ Namespace *namespacePtr = iPtr->varFramePtr->nsPtr; /* * If the object is not already of tclByteCodeType, compile it (and reset * the compilation flags in the interpreter; this should be done after any * compilation). Otherwise, check that it is "fresh" enough. */ ByteCodeGetInternalRep(objPtr, &tclByteCodeType, codePtr); if (codePtr != NULL) { /* * Make sure the Bytecode hasn't been invalidated by, e.g., someone * redefining a command with a compile procedure (this might make the * compiled code wrong). The object needs to be recompiled if it was * compiled in/for a different interpreter, or for a different * namespace, or for the same namespace but with different name * resolution rules. Precompiled objects, however, are immutable and * therefore they are not recompiled, even if the epoch has changed. * * To be pedantically correct, we should also check that the * originating procPtr is the same as the current context procPtr * (assuming one exists at all - none for global level). This code is * #def'ed out because [info body] was changed to never return a * bytecode type object, which should obviate us from the extra checks * here. */ if (((Interp *) *codePtr->interpHandle != iPtr) || (codePtr->compileEpoch != iPtr->compileEpoch) || (codePtr->nsPtr != namespacePtr) || (codePtr->nsEpoch != namespacePtr->resolverEpoch)) { if (!(codePtr->flags & TCL_BYTECODE_PRECOMPILED)) { goto recompileObj; } if ((Interp *) *codePtr->interpHandle != iPtr) { Tcl_Panic("Tcl_EvalObj: compiled script jumped interps"); } codePtr->compileEpoch = iPtr->compileEpoch; } /* * Check that any compiled locals do refer to the current proc * environment! If not, recompile. */ if (!(codePtr->flags & TCL_BYTECODE_PRECOMPILED) && (codePtr->procPtr == NULL) && (codePtr->localCachePtr != iPtr->varFramePtr->localCachePtr)){ goto recompileObj; } /* * #280. * Literal sharing fix. This part of the fix is not required by 8.4 * nor 8.5, because they eval-direct any literals, so just saving the * argument locations per command in bytecode is enough, embedded * 'eval' commands, etc. get the correct information. * * But in 8.6 all the embedded script are compiled, and the resulting * bytecode stored in the literal. Now the shared literal has bytecode * with location data for _one_ particular location this literal is * found at. If we get executed from a different location the bytecode * has to be recompiled to get the correct locations. Not doing this * will execute the saved bytecode with data for a different location, * causing 'info frame' to point to the wrong place in the sources. * * Future optimizations ... * (1) Save the location data (ExtCmdLoc) keyed by start line. In that * case we recompile once per location of the literal, but not * continuously, because the moment we have all locations we do not * need to recompile any longer. * * (2) Alternative: Do not recompile, tell the execution engine the * offset between saved starting line and actual one. Then modify * the users to adjust the locations they have by this offset. * * (3) Alternative 2: Do not fully recompile, adjust just the location * information. */ if (invoker == NULL) { return codePtr; } else { Tcl_HashEntry *hePtr = Tcl_FindHashEntry(iPtr->lineBCPtr, codePtr); ExtCmdLoc *eclPtr; CmdFrame *ctxCopyPtr; int redo; if (!hePtr) { return codePtr; } eclPtr = (ExtCmdLoc *)Tcl_GetHashValue(hePtr); redo = 0; ctxCopyPtr = (CmdFrame *)TclStackAlloc(interp, sizeof(CmdFrame)); *ctxCopyPtr = *invoker; if (invoker->type == TCL_LOCATION_BC) { /* * Note: Type BC => ctx.data.eval.path is not used. * ctx.data.tebc.codePtr used instead */ TclGetSrcInfoForPc(ctxCopyPtr); if (ctxCopyPtr->type == TCL_LOCATION_SOURCE) { /* * The reference made by 'TclGetSrcInfoForPc' is dead. */ Tcl_DecrRefCount(ctxCopyPtr->data.eval.path); ctxCopyPtr->data.eval.path = NULL; } } if (word < ctxCopyPtr->nline) { /* * Note: We do not care if the line[word] is -1. This is a * difference and requires a recompile (location changed from * absolute to relative, literal is used fixed and through * variable) * * Example: * test info-32.0 using literal of info-24.8 * (dict with ... vs set body ...). */ redo = ((eclPtr->type == TCL_LOCATION_SOURCE) && (eclPtr->start != ctxCopyPtr->line[word])) || ((eclPtr->type == TCL_LOCATION_BC) && (ctxCopyPtr->type == TCL_LOCATION_SOURCE)); } TclStackFree(interp, ctxCopyPtr); if (!redo) { return codePtr; } } } recompileObj: iPtr->errorLine = 1; /* * TIP #280. Remember the invoker for a moment in the interpreter * structures so that the byte code compiler can pick it up when * initializing the compilation environment, i.e. the extended location * information. */ iPtr->invokeCmdFramePtr = invoker; iPtr->invokeWord = word; TclSetByteCodeFromAny(interp, objPtr, NULL, NULL); iPtr->invokeCmdFramePtr = NULL; ByteCodeGetInternalRep(objPtr, &tclByteCodeType, codePtr); if (iPtr->varFramePtr->localCachePtr) { codePtr->localCachePtr = iPtr->varFramePtr->localCachePtr; codePtr->localCachePtr->refCount++; } return codePtr; } /* *---------------------------------------------------------------------- * * TclIncrObj -- * * Increment an integral value in a Tcl_Obj by an integral value held * in another Tcl_Obj. Caller is responsible for making sure we can * update the first object. * * Results: * TCL_ERROR if either object is non-integer, and TCL_OK otherwise. On * error, an error message is left in the interpreter (if it is not NULL, * of course). * * Side effects: * valuePtr gets the new incremented value. * *---------------------------------------------------------------------- */ int TclIncrObj( Tcl_Interp *interp, Tcl_Obj *valuePtr, Tcl_Obj *incrPtr) { void *ptr1, *ptr2; int type1, type2; mp_int value, incr; mp_err err; if (Tcl_IsShared(valuePtr)) { Tcl_Panic("%s called with shared object", "TclIncrObj"); } if (GetNumberFromObj(NULL, valuePtr, &ptr1, &type1) != TCL_OK) { /* * Produce error message (reparse?!) */ return TclGetIntFromObj(interp, valuePtr, &type1); } if (GetNumberFromObj(NULL, incrPtr, &ptr2, &type2) != TCL_OK) { /* * Produce error message (reparse?!) */ TclGetIntFromObj(interp, incrPtr, &type1); Tcl_AddErrorInfo(interp, "\n (reading increment)"); return TCL_ERROR; } if ((type1 == TCL_NUMBER_DOUBLE) || (type1 == TCL_NUMBER_NAN)) { /* * Produce error message (reparse?!) */ return TclGetIntFromObj(interp, valuePtr, &type1); } if ((type2 == TCL_NUMBER_DOUBLE) || (type2 == TCL_NUMBER_NAN)) { /* * Produce error message (reparse?!) */ TclGetIntFromObj(interp, incrPtr, &type1); Tcl_AddErrorInfo(interp, "\n (reading increment)"); return TCL_ERROR; } if ((type1 == TCL_NUMBER_INT) && (type2 == TCL_NUMBER_INT)) { Tcl_WideInt w1, w2, sum; w1 = *((const Tcl_WideInt *)ptr1); w2 = *((const Tcl_WideInt *)ptr2); sum = (Tcl_WideInt)((Tcl_WideUInt)w1 + (Tcl_WideUInt)w2); /* * Check for overflow. */ if (!Overflowing(w1, w2, sum)) { TclSetIntObj(valuePtr, sum); return TCL_OK; } } Tcl_TakeBignumFromObj(interp, valuePtr, &value); Tcl_GetBignumFromObj(interp, incrPtr, &incr); err = mp_add(&value, &incr, &value); mp_clear(&incr); if (err != MP_OKAY) { return TCL_ERROR; } Tcl_SetBignumObj(valuePtr, &value); return TCL_OK; } /* *---------------------------------------------------------------------- * * ArgumentBCEnter -- * * This is a helper for TclNRExecuteByteCode/TEBCresume that encapsulates * a code sequence that is fairly common in the code but *not* commonly * called. * * Results: * None * * Side effects: * May register information about the bytecode in the command frame. * *---------------------------------------------------------------------- */ static void ArgumentBCEnter( Tcl_Interp *interp, ByteCode *codePtr, TEBCdata *tdPtr, const unsigned char *pc, Tcl_Size objc, Tcl_Obj **objv) { Tcl_Size cmd; if (GetSrcInfoForPc(pc, codePtr, NULL, NULL, &cmd)) { TclArgumentBCEnter(interp, objv, objc, codePtr, &tdPtr->cmdFrame, cmd, pc - codePtr->codeStart); } } /* *---------------------------------------------------------------------- * * TclNRExecuteByteCode -- * * This procedure executes the instructions of a ByteCode structure. It * returns when a "done" instruction is executed or an error occurs. * * Results: * The return value is one of the return codes defined in tcl.h (such as * TCL_OK), and interp->objResultPtr refers to a Tcl object that either * contains the result of executing the code or an error message. * * Side effects: * Almost certainly, depending on the ByteCode's instructions. * *---------------------------------------------------------------------- */ #define bcFramePtr (&TD->cmdFrame) #define initCatchTop (TD->stack-1) #define initTosPtr (initCatchTop+codePtr->maxExceptDepth) #define esPtr (iPtr->execEnvPtr->execStackPtr) int TclNRExecuteByteCode( Tcl_Interp *interp, /* Token for command interpreter. */ ByteCode *codePtr) /* The bytecode sequence to interpret. */ { Interp *iPtr = (Interp *) interp; TEBCdata *TD; size_t size = sizeof(TEBCdata) - 1 + (codePtr->maxStackDepth + codePtr->maxExceptDepth) * sizeof(void *); size_t numWords = (size + sizeof(Tcl_Obj *) - 1) / sizeof(Tcl_Obj *); TclPreserveByteCode(codePtr); /* * Reserve the stack, setup the TEBCdataPtr (TD) and CallFrame * * The execution uses a unified stack: first a TEBCdata, immediately * above it a CmdFrame, then the catch stack, then the execution stack. * * Make sure the catch stack is large enough to hold the maximum number of * catch commands that could ever be executing at the same time (this will * be no more than the exception range array's depth). Make sure the * execution stack is large enough to execute this ByteCode. */ TD = (TEBCdata *) GrowEvaluationStack(iPtr->execEnvPtr, numWords, 0); esPtr->tosPtr = initTosPtr; TD->codePtr = codePtr; TD->catchTop = initCatchTop; TD->auxObjList = NULL; /* * TIP #280: Initialize the frame. Do not push it yet: it will be pushed * every time that we call out from this TD, popped when we return to it. */ bcFramePtr->type = ((codePtr->flags & TCL_BYTECODE_PRECOMPILED) ? TCL_LOCATION_PREBC : TCL_LOCATION_BC); bcFramePtr->level = (iPtr->cmdFramePtr ? iPtr->cmdFramePtr->level+1 : 1); bcFramePtr->framePtr = iPtr->framePtr; bcFramePtr->nextPtr = iPtr->cmdFramePtr; bcFramePtr->nline = 0; bcFramePtr->line = NULL; bcFramePtr->litarg = NULL; bcFramePtr->data.tebc.codePtr = codePtr; bcFramePtr->data.tebc.pc = NULL; bcFramePtr->cmdObj = NULL; bcFramePtr->cmd = NULL; bcFramePtr->len = 0; #ifdef TCL_COMPILE_STATS iPtr->stats.numExecutions++; #endif /* * Test namespace-50.9 demonstrates the need for this call. * Use a --enable-symbols=mem bug to see. */ TclResetRewriteEnsemble(interp, 1); /* * Push the callback for bytecode execution */ TclNRAddCallback(interp, TEBCresume, TD, /* pc */ NULL, /* cleanup */ NULL, INT2PTR(iPtr->evalFlags)); /* * Reset discard result flag - because it is applicable for this call only, * and should not affect all the nested invocations may return result. */ iPtr->evalFlags &= ~TCL_EVAL_DISCARD_RESULT; return TCL_OK; } static int TEBCresume( void *data[], Tcl_Interp *interp, int result) { /* * Compiler cast directive - not a real variable. * Interp *iPtr = (Interp *) interp; */ #define iPtr ((Interp *) interp) /* * Check just the read-traced/write-traced bit of a variable. */ #define ReadTraced(varPtr) ((varPtr)->flags & VAR_TRACED_READ) #define WriteTraced(varPtr) ((varPtr)->flags & VAR_TRACED_WRITE) #define UnsetTraced(varPtr) ((varPtr)->flags & VAR_TRACED_UNSET) /* * Bottom of allocated stack holds the NR data */ /* * Constants: variables that do not change during the execution, used * sporadically: no special need for speed. */ unsigned interruptCounter = 1; /* Counter that is used to work out when to * call Tcl_AsyncReady(). This must be 1 * initially so that we call the async-check * stanza early, otherwise there are command * sequences that can make the interpreter * busy-loop without an opportunity to * recognise an interrupt. */ const char *curInstName; #ifdef TCL_COMPILE_DEBUG int traceInstructions; /* Whether we are doing instruction-level * tracing or not. */ #endif Var *compiledLocals = iPtr->varFramePtr->compiledLocals; Tcl_Obj **constants = &iPtr->execEnvPtr->constants[0]; #define LOCAL(i) (&compiledLocals[(i)]) #define TCONST(i) (constants[(i)]) /* * These macros are just meant to save some global variables that are not * used too frequently */ TEBCdata *TD = (TEBCdata *)data[0]; #define auxObjList (TD->auxObjList) #define catchTop (TD->catchTop) #define codePtr (TD->codePtr) #define curEvalFlags PTR2INT(data[3]) /* calling iPtr->evalFlags */ /* * Globals: variables that store state, must remain valid at all times. */ Tcl_Obj **tosPtr; /* Cached pointer to top of evaluation * stack. */ const unsigned char *pc = (const unsigned char *)data[1]; /* The current program counter. */ unsigned char inst; /* The currently running instruction */ /* * Transfer variables - needed only between opcodes, but not while * executing an instruction. */ int cleanup = PTR2INT(data[2]); Tcl_Obj *objResultPtr; int checkInterp = 0; /* Indicates when a check of interp readyness * is necessary. Set by CACHE_STACK_INFO() */ /* * Locals - variables that are used within opcodes or bounded sections of * the file (jumps between opcodes within a family). * NOTE: These are now mostly defined locally where needed. */ Tcl_Obj *objPtr, *valuePtr, *value2Ptr, *part1Ptr, *part2Ptr, *tmpPtr; Tcl_Obj **objv = NULL; Tcl_Size length, objc = 0; int opnd, pcAdjustment; Var *varPtr, *arrayPtr; #ifdef TCL_COMPILE_DEBUG char cmdNameBuf[21]; #endif #ifdef TCL_COMPILE_DEBUG int starting = 1; traceInstructions = (tclTraceExec == 3); #endif TEBC_DATA_DIG(); #ifdef TCL_COMPILE_DEBUG if (!pc && (tclTraceExec >= 2)) { PrintByteCodeInfo(codePtr); fprintf(stdout, " Starting stack top=%" TCL_T_MODIFIER "d\n", CURR_DEPTH); fflush(stdout); } #endif if (!pc) { /* bytecode is starting from scratch */ pc = codePtr->codeStart; /* * Reset the interp's result to avoid possible duplications of large * objects [3c6e47363e], [781585], [804681], This can happen by start * also in nested compiled blocks (enclosed in parent cycle). * See else branch below for opposite handling by continuation/resume. */ objPtr = iPtr->objResultPtr; if (objPtr->refCount > 1) { TclDecrRefCount(objPtr); TclNewObj(objPtr); Tcl_IncrRefCount(objPtr); iPtr->objResultPtr = objPtr; } goto cleanup0; } else { /* resume from invocation */ CACHE_STACK_INFO(); NRE_ASSERT(iPtr->cmdFramePtr == bcFramePtr); if (bcFramePtr->cmdObj) { Tcl_DecrRefCount(bcFramePtr->cmdObj); bcFramePtr->cmdObj = NULL; bcFramePtr->cmd = NULL; } iPtr->cmdFramePtr = bcFramePtr->nextPtr; if (iPtr->flags & INTERP_DEBUG_FRAME) { TclArgumentBCRelease(interp, bcFramePtr); } if (iPtr->execEnvPtr->rewind) { result = TCL_ERROR; goto abnormalReturn; } if (codePtr->flags & TCL_BYTECODE_RECOMPILE) { codePtr->flags &= ~TCL_BYTECODE_RECOMPILE; checkInterp = 1; iPtr->flags |= ERR_ALREADY_LOGGED; } if (result != TCL_OK) { pc--; goto processExceptionReturn; } /* * Push the call's object result and continue execution with the next * instruction. */ TRACE_WITH_OBJ(("%" TCL_SIZE_MODIFIER "d => ... after \"%.20s\": TCL_OK, result=", objc, cmdNameBuf), Tcl_GetObjResult(interp)); /* * Obtain and reset interp's result to avoid possible duplications of * objects [Bug 781585]. We do not call Tcl_ResetResult to avoid any * side effects caused by the resetting of errorInfo and errorCode * [Bug 804681], which are not needed here. We chose instead to * manipulate the interp's object result directly. * * Note that the result object is now in objResultPtr, it keeps the * refCount it had in its role of iPtr->objResultPtr. */ objResultPtr = Tcl_GetObjResult(interp); TclNewObj(objPtr); Tcl_IncrRefCount(objPtr); iPtr->objResultPtr = objPtr; #ifndef TCL_COMPILE_DEBUG if (*pc == INST_POP) { TclDecrRefCount(objResultPtr); NEXT_INST_V(1, cleanup, 0); } #endif NEXT_INST_V(0, cleanup, -1); } /* * Targets for standard instruction endings; unrolled for speed in the * most frequent cases (instructions that consume up to two stack * elements). * * This used to be a "for(;;)" loop, with each instruction doing its own * cleanup. */ cleanupV_pushObjResultPtr: switch (cleanup) { case 0: *(++tosPtr) = (objResultPtr); goto cleanup0; default: cleanup -= 2; while (cleanup--) { objPtr = POP_OBJECT(); TclDecrRefCount(objPtr); } /* FALLTHRU */ case 2: cleanup2_pushObjResultPtr: objPtr = POP_OBJECT(); TclDecrRefCount(objPtr); /* FALLTHRU */ case 1: cleanup1_pushObjResultPtr: objPtr = OBJ_AT_TOS; TclDecrRefCount(objPtr); } OBJ_AT_TOS = objResultPtr; goto cleanup0; cleanupV: switch (cleanup) { default: cleanup -= 2; while (cleanup--) { objPtr = POP_OBJECT(); TclDecrRefCount(objPtr); } /* FALLTHRU */ case 2: cleanup2: objPtr = POP_OBJECT(); TclDecrRefCount(objPtr); /* FALLTHRU */ case 1: cleanup1: objPtr = POP_OBJECT(); TclDecrRefCount(objPtr); /* FALLTHRU */ case 0: /* * We really want to do nothing now, but this is needed for some * compilers (SunPro CC). */ break; } cleanup0: /* * Check for asynchronous handlers [Bug 746722]; we do the check every * ASYNC_CHECK_COUNT instructions. */ if ((--interruptCounter) == 0) { interruptCounter = ASYNC_CHECK_COUNT; DECACHE_STACK_INFO(); if (TclAsyncReady(iPtr)) { result = Tcl_AsyncInvoke(interp, result); if (result == TCL_ERROR) { CACHE_STACK_INFO(); goto gotError; } } if (TclCanceled(iPtr)) { if (Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG) == TCL_ERROR) { CACHE_STACK_INFO(); goto gotError; } } if (TclLimitReady(iPtr->limit)) { if (Tcl_LimitCheck(interp) == TCL_ERROR) { CACHE_STACK_INFO(); goto gotError; } } CACHE_STACK_INFO(); } /* * These two instructions account for 26% of all instructions (according * to measurements on tclbench by Ben Vitale * [http://www.cs.toronto.edu/syslab/pubs/tcl2005-vitale-zaleski.pdf] * Resolving them before the switch reduces the cost of branch * mispredictions, seems to improve runtime by 5% to 15%, and (amazingly!) * reduces total obj size. */ inst = *pc; peepholeStart: #ifdef TCL_COMPILE_STATS iPtr->stats.instructionCount[*pc]++; #endif #ifdef TCL_COMPILE_DEBUG /* * Skip the stack depth check if an expansion is in progress. */ CHECK_STACK(); if (traceInstructions) { fprintf(stdout, "%2" TCL_SIZE_MODIFIER "d: %2" TCL_T_MODIFIER "d ", iPtr->numLevels, CURR_DEPTH); TclPrintInstruction(codePtr, pc); fflush(stdout); } #endif /* TCL_COMPILE_DEBUG */ TCL_DTRACE_INST_NEXT(); if (inst == INST_LOAD_SCALAR1) { goto instLoadScalar1; } else if (inst == INST_PUSH1) { PUSH_OBJECT(codePtr->objArrayPtr[TclGetUInt1AtPtr(pc+1)]); TRACE_WITH_OBJ(("%u => ", TclGetUInt1AtPtr(pc + 1)), OBJ_AT_TOS); inst = *(pc += 2); goto peepholeStart; } else if (inst == INST_START_CMD) { /* * Peephole: do not run INST_START_CMD, just skip it */ iPtr->cmdCount += TclGetUInt4AtPtr(pc + 5); if (checkInterp) { if (((codePtr->compileEpoch != iPtr->compileEpoch) || (codePtr->nsEpoch != iPtr->varFramePtr->nsPtr->resolverEpoch)) && !(codePtr->flags & TCL_BYTECODE_PRECOMPILED)) { goto instStartCmdFailed; } checkInterp = 0; } inst = *(pc += 9); goto peepholeStart; } else if (inst == INST_NOP) { #ifndef TCL_COMPILE_DEBUG while (inst == INST_NOP) #endif { inst = *++pc; } goto peepholeStart; } switch (inst) { case INST_SYNTAX: case INST_RETURN_IMM: { int code = TclGetInt4AtPtr(pc+1); int level = TclGetUInt4AtPtr(pc+5); /* * OBJ_AT_TOS is returnOpts, OBJ_UNDER_TOS is resultObjPtr. */ TRACE(("%u %u => ", code, level)); result = TclProcessReturn(interp, code, level, OBJ_AT_TOS); if (result == TCL_OK) { TRACE_APPEND(("continuing to next instruction (result=\"%.30s\")\n", O2S(objResultPtr))); NEXT_INST_F(9, 1, 0); } Tcl_SetObjResult(interp, OBJ_UNDER_TOS); if (*pc == INST_SYNTAX) { iPtr->flags &= ~ERR_ALREADY_LOGGED; } cleanup = 2; TRACE_APPEND(("\n")); goto processExceptionReturn; } case INST_RETURN_STK: TRACE(("=> ")); objResultPtr = POP_OBJECT(); result = Tcl_SetReturnOptions(interp, OBJ_AT_TOS); if (result == TCL_OK) { Tcl_DecrRefCount(OBJ_AT_TOS); OBJ_AT_TOS = objResultPtr; TRACE_APPEND(("continuing to next instruction (result=\"%.30s\")\n", O2S(objResultPtr))); NEXT_INST_F(1, 0, 0); } else if (result == TCL_ERROR) { /* * BEWARE! Must do this in this order, because an error in the * option dictionary overrides the result (and can be verified by * test). */ Tcl_SetObjResult(interp, objResultPtr); Tcl_SetReturnOptions(interp, OBJ_AT_TOS); Tcl_DecrRefCount(OBJ_AT_TOS); OBJ_AT_TOS = objResultPtr; } else { Tcl_DecrRefCount(OBJ_AT_TOS); OBJ_AT_TOS = objResultPtr; Tcl_SetObjResult(interp, objResultPtr); } cleanup = 1; TRACE_APPEND(("\n")); goto processExceptionReturn; { CoroutineData *corPtr; void *yieldParameter; case INST_YIELD: corPtr = iPtr->execEnvPtr->corPtr; TRACE(("%.30s => ", O2S(OBJ_AT_TOS))); if (!corPtr) { TRACE_APPEND(("ERROR: yield outside coroutine\n")); Tcl_SetObjResult(interp, Tcl_NewStringObj( "yield can only be called in a coroutine", -1)); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ILLEGAL_YIELD", (char *)NULL); CACHE_STACK_INFO(); goto gotError; } #ifdef TCL_COMPILE_DEBUG if (tclTraceExec >= 2) { if (traceInstructions) { TRACE_APPEND(("YIELD...\n")); } else { fprintf(stdout, "%" TCL_SIZE_MODIFIER "d: (%" TCL_T_MODIFIER "d) yielding value \"%.30s\"\n", iPtr->numLevels, (pc - codePtr->codeStart), Tcl_GetString(OBJ_AT_TOS)); } fflush(stdout); } #endif yieldParameter = NULL; /*==CORO_ACTIVATE_YIELD*/ Tcl_SetObjResult(interp, OBJ_AT_TOS); goto doYield; case INST_YIELD_TO_INVOKE: corPtr = iPtr->execEnvPtr->corPtr; valuePtr = OBJ_AT_TOS; if (!corPtr) { TRACE(("[%.30s] => ERROR: yield outside coroutine\n", O2S(valuePtr))); Tcl_SetObjResult(interp, Tcl_NewStringObj( "yieldto can only be called in a coroutine", -1)); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ILLEGAL_YIELD", (char *)NULL); CACHE_STACK_INFO(); goto gotError; } if (((Namespace *)TclGetCurrentNamespace(interp))->flags & NS_DYING) { TRACE(("[%.30s] => ERROR: yield in deleted\n", O2S(valuePtr))); Tcl_SetObjResult(interp, Tcl_NewStringObj( "yieldto called in deleted namespace", -1)); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "YIELDTO_IN_DELETED", (char *)NULL); CACHE_STACK_INFO(); goto gotError; } #ifdef TCL_COMPILE_DEBUG if (tclTraceExec >= 2) { if (traceInstructions) { TRACE(("[%.30s] => YIELD...\n", O2S(valuePtr))); } else { /* FIXME: What is the right thing to trace? */ fprintf(stdout, "%" TCL_SIZE_MODIFIER "d: (%" TCL_T_MODIFIER "d) yielding to [%.30s]\n", iPtr->numLevels, (pc - codePtr->codeStart), TclGetString(valuePtr)); } fflush(stdout); } #endif /* * Install a tailcall record in the caller and continue with the * yield. The yield is switched into multi-return mode (via the * 'yieldParameter'). */ iPtr->execEnvPtr = corPtr->callerEEPtr; Tcl_IncrRefCount(valuePtr); TclSetTailcall(interp, valuePtr); corPtr->yieldPtr = valuePtr; iPtr->execEnvPtr = corPtr->eePtr; yieldParameter = INT2PTR(1); /*==CORO_ACTIVATE_YIELDM*/ doYield: /* TIP #280: Record the last piece of info needed by * 'TclGetSrcInfoForPc', and push the frame. */ bcFramePtr->data.tebc.pc = (char *) pc; iPtr->cmdFramePtr = bcFramePtr; if (iPtr->flags & INTERP_DEBUG_FRAME) { ArgumentBCEnter(interp, codePtr, TD, pc, objc, objv); } pc++; cleanup = 1; TEBC_YIELD(); TclNRAddCallback(interp, TclNRCoroutineActivateCallback, corPtr, yieldParameter, NULL, NULL); return TCL_OK; } case INST_TAILCALL: { Tcl_Obj *listPtr; opnd = TclGetUInt1AtPtr(pc+1); if (!(iPtr->varFramePtr->isProcCallFrame & 1)) { TRACE(("%d => ERROR: tailcall in non-proc context\n", opnd)); Tcl_SetObjResult(interp, Tcl_NewStringObj( "tailcall can only be called from a proc or lambda", -1)); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "TAILCALL", "ILLEGAL", (char *)NULL); CACHE_STACK_INFO(); goto gotError; } #ifdef TCL_COMPILE_DEBUG /* FIXME: What is the right thing to trace? */ { int i; TRACE(("%d [", opnd)); for (i=opnd-1 ; i>=0 ; i--) { TRACE_APPEND(("\"%.30s\"", O2S(OBJ_AT_DEPTH(i)))); if (i > 0) { TRACE_APPEND((" ")); } } TRACE_APPEND(("] => RETURN...")); } #endif /* * Push the evaluation of the called command into the NR callback * stack. */ listPtr = Tcl_NewListObj(opnd, &OBJ_AT_DEPTH(opnd-1)); TclListObjSetElement(NULL, listPtr, 0, TclNewNamespaceObj( (Tcl_Namespace *) iPtr->varFramePtr->nsPtr)); if (iPtr->varFramePtr->tailcallPtr) { Tcl_DecrRefCount(iPtr->varFramePtr->tailcallPtr); } iPtr->varFramePtr->tailcallPtr = listPtr; result = TCL_RETURN; cleanup = opnd; goto processExceptionReturn; } case INST_DONE: if (tosPtr > initTosPtr) { if ((curEvalFlags & TCL_EVAL_DISCARD_RESULT) && (result == TCL_OK)) { /* simulate pop & fast done (like it does continue in loop) */ TRACE_WITH_OBJ(("=> discarding "), OBJ_AT_TOS); objPtr = POP_OBJECT(); TclDecrRefCount(objPtr); goto abnormalReturn; } /* * Set the interpreter's object result to point to the topmost * object from the stack, and check for a possible [catch]. The * stackTop's level and refCount will be handled by "processCatch" * or "abnormalReturn". */ Tcl_SetObjResult(interp, OBJ_AT_TOS); #ifdef TCL_COMPILE_DEBUG TRACE_WITH_OBJ(("=> return code=%d, result=", result), iPtr->objResultPtr); if (traceInstructions) { fprintf(stdout, "\n"); } #endif goto checkForCatch; } (void) POP_OBJECT(); goto abnormalReturn; case INST_PUSH4: objResultPtr = codePtr->objArrayPtr[TclGetUInt4AtPtr(pc+1)]; TRACE_WITH_OBJ(("%u => ", TclGetUInt4AtPtr(pc+1)), objResultPtr); NEXT_INST_F(5, 0, 1); break; case INST_POP: TRACE_WITH_OBJ(("=> discarding "), OBJ_AT_TOS); objPtr = POP_OBJECT(); TclDecrRefCount(objPtr); NEXT_INST_F(1, 0, 0); break; case INST_DUP: objResultPtr = OBJ_AT_TOS; TRACE_WITH_OBJ(("=> "), objResultPtr); NEXT_INST_F(1, 0, 1); break; case INST_OVER: opnd = TclGetUInt4AtPtr(pc+1); objResultPtr = OBJ_AT_DEPTH(opnd); TRACE_WITH_OBJ(("%u => ", opnd), objResultPtr); NEXT_INST_F(5, 0, 1); break; case INST_REVERSE: { Tcl_Obj **a, **b; opnd = TclGetUInt4AtPtr(pc + 1); a = tosPtr - (opnd - 1); b = tosPtr; while (a < b) { tmpPtr = *a; *a = *b; *b = tmpPtr; a++; b--; } TRACE(("%u => OK\n", opnd)); NEXT_INST_F(5, 0, 0); } break; case INST_STR_CONCAT1: opnd = TclGetUInt1AtPtr(pc+1); DECACHE_STACK_INFO(); objResultPtr = TclStringCat(interp, opnd, &OBJ_AT_DEPTH(opnd-1), TCL_STRING_IN_PLACE); if (objResultPtr == NULL) { CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } CACHE_STACK_INFO(); TRACE_WITH_OBJ(("%u => ", opnd), objResultPtr); NEXT_INST_V(2, opnd, 1); break; case INST_CONCAT_STK: /* * Pop the opnd (objc) top stack elements, run through Tcl_ConcatObj, * and then decrement their ref counts. */ opnd = TclGetUInt4AtPtr(pc+1); objResultPtr = Tcl_ConcatObj(opnd, &OBJ_AT_DEPTH(opnd - 1)); TRACE_WITH_OBJ(("%u => ", opnd), objResultPtr); NEXT_INST_V(5, opnd, 1); break; case INST_EXPAND_START: /* * Push an element to the auxObjList. This records the current * stack depth - i.e., the point in the stack where the expanded * command starts. * * Use a Tcl_Obj as linked list element; slight mem waste, but faster * allocation than Tcl_Alloc. This also abuses the Tcl_Obj structure, as * we do not define a special tclObjType for it. It is not dangerous * as the obj is never passed anywhere, so that all manipulations are * performed here and in INST_INVOKE_EXPANDED (in case of an expansion * error, also in INST_EXPAND_STKTOP). */ TclNewObj(objPtr); objPtr->internalRep.twoPtrValue.ptr2 = INT2PTR(CURR_DEPTH); objPtr->length = 0; PUSH_TAUX_OBJ(objPtr); TRACE(("=> mark depth as %" TCL_T_MODIFIER "d\n", CURR_DEPTH)); NEXT_INST_F(1, 0, 0); break; case INST_EXPAND_DROP: /* * Drops an element of the auxObjList, popping stack elements to * restore the stack to the state before the point where the aux * element was created. */ CLANG_ASSERT(auxObjList); objc = CURR_DEPTH - PTR2INT(auxObjList->internalRep.twoPtrValue.ptr2); POP_TAUX_OBJ(); #ifdef TCL_COMPILE_DEBUG /* Ugly abuse! */ starting = 1; #endif TRACE(("=> drop %" TCL_SIZE_MODIFIER "d items\n", objc)); NEXT_INST_V(1, objc, 0); case INST_EXPAND_STKTOP: { Tcl_Size i; TEBCdata *newTD; Tcl_Size oldCatchTopOff, oldTosPtrOff; /* * Make sure that the element at stackTop is a list; if not, just * leave with an error. Note that the element from the expand list * will be removed at checkForCatch. */ objPtr = OBJ_AT_TOS; TRACE(("\"%.30s\" => ", O2S(objPtr))); if (TclListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } (void) POP_OBJECT(); /* * Make sure there is enough room in the stack to expand this list * *and* process the rest of the command (at least up to the next * argument expansion or command end). The operand is the current * stack depth, as seen by the compiler. */ auxObjList->length += objc - 1; if ((objc > 1) && (auxObjList->length > 0)) { length = auxObjList->length /* Total expansion room we need */ + codePtr->maxStackDepth /* Beyond the original max */ - CURR_DEPTH; /* Relative to where we are */ DECACHE_STACK_INFO(); oldCatchTopOff = catchTop - initCatchTop; oldTosPtrOff = tosPtr - initTosPtr; newTD = (TEBCdata *) GrowEvaluationStack(iPtr->execEnvPtr, length, 1); if (newTD != TD) { /* * Change the global data to point to the new stack: move the * TEBCdataPtr TD, recompute the position of every other * stack-allocated parameter, update the stack pointers. */ TD = newTD; catchTop = initCatchTop + oldCatchTopOff; tosPtr = initTosPtr + oldTosPtrOff; } } /* * Expand the list at stacktop onto the stack; free the list. Knowing * that it has a freeIntRepProc we use Tcl_DecrRefCount(). */ for (i = 0; i < objc; i++) { PUSH_OBJECT(objv[i]); } TRACE_APPEND(("OK\n")); Tcl_DecrRefCount(objPtr); NEXT_INST_F(5, 0, 0); } break; case INST_EXPR_STK: { ByteCode *newCodePtr; bcFramePtr->data.tebc.pc = (char *) pc; iPtr->cmdFramePtr = bcFramePtr; DECACHE_STACK_INFO(); newCodePtr = CompileExprObj(interp, OBJ_AT_TOS); CACHE_STACK_INFO(); cleanup = 1; pc++; TEBC_YIELD(); return TclNRExecuteByteCode(interp, newCodePtr); } /* * INVOCATION BLOCK */ case INST_EVAL_STK: instEvalStk: bcFramePtr->data.tebc.pc = (char *) pc; iPtr->cmdFramePtr = bcFramePtr; cleanup = 1; pc += 1; /* yield next instruction */ TEBC_YIELD(); /* add TEBCResume for object at top of stack */ return TclNRExecuteByteCode(interp, TclCompileObj(interp, OBJ_AT_TOS, NULL, 0)); case INST_INVOKE_EXPANDED: CLANG_ASSERT(auxObjList); objc = CURR_DEPTH - PTR2INT(auxObjList->internalRep.twoPtrValue.ptr2); POP_TAUX_OBJ(); if (objc) { pcAdjustment = 1; goto doInvocation; } /* * Nothing was expanded, return {}. */ TclNewObj(objResultPtr); NEXT_INST_F(1, 0, 1); break; case INST_INVOKE_STK4: objc = TclGetUInt4AtPtr(pc+1); pcAdjustment = 5; goto doInvocation; case INST_INVOKE_STK1: objc = TclGetUInt1AtPtr(pc+1); pcAdjustment = 2; doInvocation: objv = &OBJ_AT_DEPTH(objc-1); cleanup = objc; #ifdef TCL_COMPILE_DEBUG if (tclTraceExec >= 2) { Tcl_Size i; if (traceInstructions) { strncpy(cmdNameBuf, TclGetString(objv[0]), 20); TRACE(("%" TCL_SIZE_MODIFIER "d => call ", objc)); } else { fprintf(stdout, "%" TCL_SIZE_MODIFIER "d: (%" TCL_T_MODIFIER "d) invoking ", iPtr->numLevels, (pc - codePtr->codeStart)); } for (i = 0; i < objc; i++) { TclPrintObject(stdout, objv[i], 15); fprintf(stdout, " "); } fprintf(stdout, "\n"); fflush(stdout); } #endif /*TCL_COMPILE_DEBUG*/ /* * Finally, let TclEvalObjv handle the command. * * TIP #280: Record the last piece of info needed by * 'TclGetSrcInfoForPc', and push the frame. */ bcFramePtr->data.tebc.pc = (char *) pc; iPtr->cmdFramePtr = bcFramePtr; if (iPtr->flags & INTERP_DEBUG_FRAME) { ArgumentBCEnter(interp, codePtr, TD, pc, objc, objv); } DECACHE_STACK_INFO(); pc += pcAdjustment; TEBC_YIELD(); if (objc > INT_MAX) { return TclCommandWordLimitError(interp, objc); } else { return TclNREvalObjv(interp, objc, objv, TCL_EVAL_NOERR | TCL_EVAL_SOURCE_IN_FRAME, NULL); } case INST_INVOKE_REPLACE: objc = TclGetUInt4AtPtr(pc+1); opnd = TclGetUInt1AtPtr(pc+5); objPtr = POP_OBJECT(); objv = &OBJ_AT_DEPTH(objc-1); cleanup = objc; #ifdef TCL_COMPILE_DEBUG if (tclTraceExec >= 2) { Tcl_Size i; if (traceInstructions) { strncpy(cmdNameBuf, TclGetString(objv[0]), 20); TRACE(("%" TCL_Z_MODIFIER "u => call (implementation %s) ", objc, O2S(objPtr))); } else { fprintf(stdout, "%" TCL_Z_MODIFIER "d: (%" TCL_T_MODIFIER "u) invoking (using implementation %s) ", iPtr->numLevels, (pc - codePtr->codeStart), O2S(objPtr)); } for (i = 0; i < objc; i++) { if (i < opnd) { fprintf(stdout, "<"); TclPrintObject(stdout, objv[i], 15); fprintf(stdout, ">"); } else { TclPrintObject(stdout, objv[i], 15); } fprintf(stdout, " "); } fprintf(stdout, "\n"); fflush(stdout); } #endif /*TCL_COMPILE_DEBUG*/ bcFramePtr->data.tebc.pc = (char *) pc; iPtr->cmdFramePtr = bcFramePtr; if (iPtr->flags & INTERP_DEBUG_FRAME) { ArgumentBCEnter(interp, codePtr, TD, pc, objc, objv); } TclInitRewriteEnsemble(interp, opnd, 1, objv); { Tcl_Obj *copyPtr = Tcl_NewListObj(objc - opnd + 1, NULL); Tcl_ListObjAppendElement(NULL, copyPtr, objPtr); Tcl_ListObjReplace(NULL, copyPtr, LIST_MAX, 0, objc - opnd, objv + opnd); Tcl_DecrRefCount(objPtr); objPtr = copyPtr; } DECACHE_STACK_INFO(); pc += 6; TEBC_YIELD(); TclMarkTailcall(interp); TclNRAddCallback(interp, TclClearRootEnsemble, NULL, NULL, NULL, NULL); TclListObjGetElements(NULL, objPtr, &objc, &objv); TclNRAddCallback(interp, TclNRReleaseValues, objPtr, NULL, NULL, NULL); return TclNREvalObjv(interp, objc, objv, TCL_EVAL_INVOKE, NULL); /* * ----------------------------------------------------------------- * Start of INST_LOAD instructions. * * WARNING: more 'goto' here than your doctor recommended! The different * instructions set the value of some variables and then jump to some * common execution code. */ case INST_LOAD_SCALAR1: instLoadScalar1: opnd = TclGetUInt1AtPtr(pc+1); varPtr = LOCAL(opnd); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } TRACE(("%u => ", opnd)); if (TclIsVarDirectReadable(varPtr)) { /* * No errors, no traces: just get the value. */ objResultPtr = varPtr->value.objPtr; TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_F(2, 0, 1); } pcAdjustment = 2; cleanup = 0; arrayPtr = NULL; part1Ptr = part2Ptr = NULL; goto doCallPtrGetVar; case INST_LOAD_SCALAR4: opnd = TclGetUInt4AtPtr(pc+1); varPtr = LOCAL(opnd); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } TRACE(("%u => ", opnd)); if (TclIsVarDirectReadable(varPtr)) { /* * No errors, no traces: just get the value. */ objResultPtr = varPtr->value.objPtr; TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_F(5, 0, 1); } pcAdjustment = 5; cleanup = 0; arrayPtr = NULL; part1Ptr = part2Ptr = NULL; goto doCallPtrGetVar; case INST_LOAD_ARRAY4: opnd = TclGetUInt4AtPtr(pc+1); pcAdjustment = 5; goto doLoadArray; case INST_LOAD_ARRAY1: opnd = TclGetUInt1AtPtr(pc+1); pcAdjustment = 2; doLoadArray: part1Ptr = NULL; part2Ptr = OBJ_AT_TOS; arrayPtr = LOCAL(opnd); while (TclIsVarLink(arrayPtr)) { arrayPtr = arrayPtr->value.linkPtr; } TRACE(("%u \"%.30s\" => ", opnd, O2S(part2Ptr))); if (TclIsVarArray(arrayPtr) && !ReadTraced(arrayPtr)) { varPtr = VarHashFindVar(arrayPtr->value.tablePtr, part2Ptr); if (varPtr && TclIsVarDirectReadable(varPtr)) { /* * No errors, no traces: just get the value. */ objResultPtr = varPtr->value.objPtr; TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_F(pcAdjustment, 1, 1); } } varPtr = TclLookupArrayElement(interp, part1Ptr, part2Ptr, TCL_LEAVE_ERR_MSG, "read", 0, 1, arrayPtr, opnd); if (varPtr == NULL) { TRACE_ERROR(interp); goto gotError; } cleanup = 1; goto doCallPtrGetVar; case INST_LOAD_ARRAY_STK: cleanup = 2; part2Ptr = OBJ_AT_TOS; /* element name */ objPtr = OBJ_UNDER_TOS; /* array name */ TRACE(("\"%.30s(%.30s)\" => ", O2S(objPtr), O2S(part2Ptr))); goto doLoadStk; case INST_LOAD_STK: case INST_LOAD_SCALAR_STK: cleanup = 1; part2Ptr = NULL; objPtr = OBJ_AT_TOS; /* variable name */ TRACE(("\"%.30s\" => ", O2S(objPtr))); doLoadStk: part1Ptr = objPtr; varPtr = TclObjLookupVarEx(interp, part1Ptr, part2Ptr, TCL_LEAVE_ERR_MSG, "read", /*createPart1*/0, /*createPart2*/1, &arrayPtr); if (!varPtr) { TRACE_ERROR(interp); goto gotError; } if (TclIsVarDirectReadable2(varPtr, arrayPtr)) { /* * No errors, no traces: just get the value. */ objResultPtr = varPtr->value.objPtr; TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_V(1, cleanup, 1); } pcAdjustment = 1; opnd = -1; doCallPtrGetVar: /* * There are either errors or the variable is traced: call * TclPtrGetVar to process fully. */ DECACHE_STACK_INFO(); objResultPtr = TclPtrGetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, TCL_LEAVE_ERR_MSG, opnd); CACHE_STACK_INFO(); if (!objResultPtr) { TRACE_ERROR(interp); goto gotError; } TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_V(pcAdjustment, cleanup, 1); /* * End of INST_LOAD instructions. * ----------------------------------------------------------------- * Start of INST_STORE and related instructions. * * WARNING: more 'goto' here than your doctor recommended! The different * instructions set the value of some variables and then jump to somme * common execution code. */ { int storeFlags; Tcl_Size len; case INST_STORE_ARRAY4: opnd = TclGetUInt4AtPtr(pc+1); pcAdjustment = 5; goto doStoreArrayDirect; case INST_STORE_ARRAY1: opnd = TclGetUInt1AtPtr(pc+1); pcAdjustment = 2; doStoreArrayDirect: valuePtr = OBJ_AT_TOS; part2Ptr = OBJ_UNDER_TOS; arrayPtr = LOCAL(opnd); TRACE(("%u \"%.30s\" <- \"%.30s\" => ", opnd, O2S(part2Ptr), O2S(valuePtr))); while (TclIsVarLink(arrayPtr)) { arrayPtr = arrayPtr->value.linkPtr; } if (TclIsVarArray(arrayPtr) && !WriteTraced(arrayPtr)) { varPtr = VarHashFindVar(arrayPtr->value.tablePtr, part2Ptr); if (varPtr && TclIsVarDirectWritable(varPtr)) { tosPtr--; Tcl_DecrRefCount(OBJ_AT_TOS); OBJ_AT_TOS = valuePtr; goto doStoreVarDirect; } } cleanup = 2; storeFlags = TCL_LEAVE_ERR_MSG; part1Ptr = NULL; goto doStoreArrayDirectFailed; case INST_STORE_SCALAR4: opnd = TclGetUInt4AtPtr(pc+1); pcAdjustment = 5; goto doStoreScalarDirect; case INST_STORE_SCALAR1: opnd = TclGetUInt1AtPtr(pc+1); pcAdjustment = 2; doStoreScalarDirect: valuePtr = OBJ_AT_TOS; varPtr = LOCAL(opnd); TRACE(("%u <- \"%.30s\" => ", opnd, O2S(valuePtr))); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } if (!TclIsVarDirectWritable(varPtr)) { storeFlags = TCL_LEAVE_ERR_MSG; part1Ptr = NULL; goto doStoreScalar; } /* * No traces, no errors, plain 'set': we can safely inline. The value * *will* be set to what's requested, so that the stack top remains * pointing to the same Tcl_Obj. */ doStoreVarDirect: valuePtr = varPtr->value.objPtr; if (valuePtr != NULL) { TclDecrRefCount(valuePtr); } objResultPtr = OBJ_AT_TOS; varPtr->value.objPtr = objResultPtr; #ifndef TCL_COMPILE_DEBUG if (pc[pcAdjustment] == INST_POP) { tosPtr--; NEXT_INST_F((pcAdjustment+1), 0, 0); } #else TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); #endif Tcl_IncrRefCount(objResultPtr); NEXT_INST_F(pcAdjustment, 0, 0); case INST_LAPPEND_STK: valuePtr = OBJ_AT_TOS; /* value to append */ part2Ptr = NULL; storeFlags = (TCL_LEAVE_ERR_MSG | TCL_APPEND_VALUE | TCL_LIST_ELEMENT); goto doStoreStk; case INST_LAPPEND_ARRAY_STK: valuePtr = OBJ_AT_TOS; /* value to append */ part2Ptr = OBJ_UNDER_TOS; storeFlags = (TCL_LEAVE_ERR_MSG | TCL_APPEND_VALUE | TCL_LIST_ELEMENT); goto doStoreStk; case INST_APPEND_STK: valuePtr = OBJ_AT_TOS; /* value to append */ part2Ptr = NULL; storeFlags = (TCL_LEAVE_ERR_MSG | TCL_APPEND_VALUE); goto doStoreStk; case INST_APPEND_ARRAY_STK: valuePtr = OBJ_AT_TOS; /* value to append */ part2Ptr = OBJ_UNDER_TOS; storeFlags = (TCL_LEAVE_ERR_MSG | TCL_APPEND_VALUE); goto doStoreStk; case INST_STORE_ARRAY_STK: valuePtr = OBJ_AT_TOS; part2Ptr = OBJ_UNDER_TOS; storeFlags = TCL_LEAVE_ERR_MSG; goto doStoreStk; case INST_STORE_STK: case INST_STORE_SCALAR_STK: valuePtr = OBJ_AT_TOS; part2Ptr = NULL; storeFlags = TCL_LEAVE_ERR_MSG; doStoreStk: objPtr = OBJ_AT_DEPTH(1 + (part2Ptr != NULL)); /* variable name */ part1Ptr = objPtr; #ifdef TCL_COMPILE_DEBUG if (part2Ptr == NULL) { TRACE(("\"%.30s\" <- \"%.30s\" =>", O2S(part1Ptr),O2S(valuePtr))); } else { TRACE(("\"%.30s(%.30s)\" <- \"%.30s\" => ", O2S(part1Ptr), O2S(part2Ptr), O2S(valuePtr))); } #endif varPtr = TclObjLookupVarEx(interp, objPtr, part2Ptr, TCL_LEAVE_ERR_MSG, "set", /*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr); if (!varPtr) { TRACE_ERROR(interp); goto gotError; } cleanup = ((part2Ptr == NULL)? 2 : 3); pcAdjustment = 1; opnd = -1; goto doCallPtrSetVar; case INST_LAPPEND_ARRAY4: opnd = TclGetUInt4AtPtr(pc+1); pcAdjustment = 5; storeFlags = (TCL_LEAVE_ERR_MSG | TCL_APPEND_VALUE | TCL_LIST_ELEMENT); goto doStoreArray; case INST_LAPPEND_ARRAY1: opnd = TclGetUInt1AtPtr(pc+1); pcAdjustment = 2; storeFlags = (TCL_LEAVE_ERR_MSG | TCL_APPEND_VALUE | TCL_LIST_ELEMENT); goto doStoreArray; case INST_APPEND_ARRAY4: opnd = TclGetUInt4AtPtr(pc+1); pcAdjustment = 5; storeFlags = (TCL_LEAVE_ERR_MSG | TCL_APPEND_VALUE); goto doStoreArray; case INST_APPEND_ARRAY1: opnd = TclGetUInt1AtPtr(pc+1); pcAdjustment = 2; storeFlags = (TCL_LEAVE_ERR_MSG | TCL_APPEND_VALUE); goto doStoreArray; doStoreArray: valuePtr = OBJ_AT_TOS; part2Ptr = OBJ_UNDER_TOS; arrayPtr = LOCAL(opnd); TRACE(("%u \"%.30s\" <- \"%.30s\" => ", opnd, O2S(part2Ptr), O2S(valuePtr))); while (TclIsVarLink(arrayPtr)) { arrayPtr = arrayPtr->value.linkPtr; } cleanup = 2; part1Ptr = NULL; doStoreArrayDirectFailed: varPtr = TclLookupArrayElement(interp, part1Ptr, part2Ptr, TCL_LEAVE_ERR_MSG, "set", 1, 1, arrayPtr, opnd); if (!varPtr) { TRACE_ERROR(interp); goto gotError; } goto doCallPtrSetVar; case INST_LAPPEND_SCALAR4: opnd = TclGetUInt4AtPtr(pc+1); pcAdjustment = 5; storeFlags = (TCL_LEAVE_ERR_MSG | TCL_APPEND_VALUE | TCL_LIST_ELEMENT); goto doStoreScalar; case INST_LAPPEND_SCALAR1: opnd = TclGetUInt1AtPtr(pc+1); pcAdjustment = 2; storeFlags = (TCL_LEAVE_ERR_MSG | TCL_APPEND_VALUE | TCL_LIST_ELEMENT); goto doStoreScalar; case INST_APPEND_SCALAR4: opnd = TclGetUInt4AtPtr(pc+1); pcAdjustment = 5; storeFlags = (TCL_LEAVE_ERR_MSG | TCL_APPEND_VALUE); goto doStoreScalar; case INST_APPEND_SCALAR1: opnd = TclGetUInt1AtPtr(pc+1); pcAdjustment = 2; storeFlags = (TCL_LEAVE_ERR_MSG | TCL_APPEND_VALUE); goto doStoreScalar; doStoreScalar: valuePtr = OBJ_AT_TOS; varPtr = LOCAL(opnd); TRACE(("%u <- \"%.30s\" => ", opnd, O2S(valuePtr))); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } cleanup = 1; arrayPtr = NULL; part1Ptr = part2Ptr = NULL; doCallPtrSetVar: DECACHE_STACK_INFO(); objResultPtr = TclPtrSetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, valuePtr, storeFlags, opnd); CACHE_STACK_INFO(); if (!objResultPtr) { TRACE_ERROR(interp); goto gotError; } #ifndef TCL_COMPILE_DEBUG if (pc[pcAdjustment] == INST_POP) { NEXT_INST_V((pcAdjustment+1), cleanup, 0); } #endif TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_V(pcAdjustment, cleanup, 1); case INST_LAPPEND_LIST: opnd = TclGetUInt4AtPtr(pc+1); valuePtr = OBJ_AT_TOS; varPtr = LOCAL(opnd); cleanup = 1; pcAdjustment = 5; while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } TRACE(("%u <- \"%.30s\" => ", opnd, O2S(valuePtr))); if (TclListObjGetElements(interp, valuePtr, &objc, &objv) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } if (TclIsVarDirectReadable(varPtr) && TclIsVarDirectWritable(varPtr)) { goto lappendListDirect; } arrayPtr = NULL; part1Ptr = part2Ptr = NULL; goto lappendListPtr; case INST_LAPPEND_LIST_ARRAY: opnd = TclGetUInt4AtPtr(pc+1); valuePtr = OBJ_AT_TOS; part1Ptr = NULL; part2Ptr = OBJ_UNDER_TOS; arrayPtr = LOCAL(opnd); cleanup = 2; pcAdjustment = 5; while (TclIsVarLink(arrayPtr)) { arrayPtr = arrayPtr->value.linkPtr; } TRACE(("%u \"%.30s\" \"%.30s\" => ", opnd, O2S(part2Ptr), O2S(valuePtr))); if (TclListObjGetElements(interp, valuePtr, &objc, &objv) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } if (TclIsVarArray(arrayPtr) && !ReadTraced(arrayPtr) && !WriteTraced(arrayPtr)) { varPtr = VarHashFindVar(arrayPtr->value.tablePtr, part2Ptr); if (varPtr && TclIsVarDirectReadable(varPtr) && TclIsVarDirectWritable(varPtr)) { goto lappendListDirect; } } varPtr = TclLookupArrayElement(interp, part1Ptr, part2Ptr, TCL_LEAVE_ERR_MSG, "set", 1, 1, arrayPtr, opnd); if (varPtr == NULL) { TRACE_ERROR(interp); goto gotError; } goto lappendListPtr; case INST_LAPPEND_LIST_ARRAY_STK: pcAdjustment = 1; cleanup = 3; valuePtr = OBJ_AT_TOS; part2Ptr = OBJ_UNDER_TOS; /* element name */ part1Ptr = OBJ_AT_DEPTH(2); /* array name */ TRACE(("\"%.30s(%.30s)\" \"%.30s\" => ", O2S(part1Ptr), O2S(part2Ptr), O2S(valuePtr))); goto lappendList; case INST_LAPPEND_LIST_STK: pcAdjustment = 1; cleanup = 2; valuePtr = OBJ_AT_TOS; part2Ptr = NULL; part1Ptr = OBJ_UNDER_TOS; /* variable name */ TRACE(("\"%.30s\" \"%.30s\" => ", O2S(part1Ptr), O2S(valuePtr))); goto lappendList; lappendListDirect: objResultPtr = varPtr->value.objPtr; if (TclListObjLength(interp, objResultPtr, &len) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } if (Tcl_IsShared(objResultPtr)) { Tcl_Obj *newValue = Tcl_DuplicateObj(objResultPtr); TclDecrRefCount(objResultPtr); varPtr->value.objPtr = objResultPtr = newValue; Tcl_IncrRefCount(newValue); } if (TclListObjAppendElements(interp, objResultPtr, objc, objv) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_V(pcAdjustment, cleanup, 1); lappendList: opnd = -1; if (TclListObjGetElements(interp, valuePtr, &objc, &objv) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } DECACHE_STACK_INFO(); varPtr = TclObjLookupVarEx(interp, part1Ptr, part2Ptr, TCL_LEAVE_ERR_MSG, "set", 1, 1, &arrayPtr); CACHE_STACK_INFO(); if (!varPtr) { TRACE_ERROR(interp); goto gotError; } lappendListPtr: if (TclIsVarInHash(varPtr)) { VarHashRefCount(varPtr)++; } if (arrayPtr && TclIsVarInHash(arrayPtr)) { VarHashRefCount(arrayPtr)++; } DECACHE_STACK_INFO(); objResultPtr = TclPtrGetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, TCL_LEAVE_ERR_MSG, opnd); CACHE_STACK_INFO(); if (TclIsVarInHash(varPtr)) { VarHashRefCount(varPtr)--; } if (arrayPtr && TclIsVarInHash(arrayPtr)) { VarHashRefCount(arrayPtr)--; } { int createdNewObj = 0; Tcl_Obj *valueToAssign; if (!objResultPtr) { valueToAssign = valuePtr; } else if (TclListObjLength(interp, objResultPtr, &len)!=TCL_OK) { TRACE_ERROR(interp); goto gotError; } else { if (Tcl_IsShared(objResultPtr)) { valueToAssign = Tcl_DuplicateObj(objResultPtr); createdNewObj = 1; } else { valueToAssign = objResultPtr; } if (TclListObjAppendElements(interp, valueToAssign, objc, objv) != TCL_OK) { if (createdNewObj) { TclDecrRefCount(valueToAssign); } goto errorInLappendListPtr; } } DECACHE_STACK_INFO(); objResultPtr = TclPtrSetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, valueToAssign, TCL_LEAVE_ERR_MSG, opnd); CACHE_STACK_INFO(); if (!objResultPtr) { errorInLappendListPtr: TRACE_ERROR(interp); goto gotError; } } TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_V(pcAdjustment, cleanup, 1); } /* * End of INST_STORE and related instructions. * ----------------------------------------------------------------- * Start of INST_INCR instructions. * * WARNING: more 'goto' here than your doctor recommended! The different * instructions set the value of some variables and then jump to some * common execution code. */ /*TODO: Consider more untangling here; merge with LOAD and STORE ? */ { Tcl_Obj *incrPtr; Tcl_WideInt w; long increment; case INST_INCR_SCALAR1: case INST_INCR_ARRAY1: case INST_INCR_ARRAY_STK: case INST_INCR_SCALAR_STK: case INST_INCR_STK: opnd = TclGetUInt1AtPtr(pc+1); incrPtr = POP_OBJECT(); switch (*pc) { case INST_INCR_SCALAR1: pcAdjustment = 2; goto doIncrScalar; case INST_INCR_ARRAY1: pcAdjustment = 2; goto doIncrArray; default: pcAdjustment = 1; goto doIncrStk; } case INST_INCR_ARRAY_STK_IMM: case INST_INCR_SCALAR_STK_IMM: case INST_INCR_STK_IMM: increment = TclGetInt1AtPtr(pc+1); TclNewIntObj(incrPtr, increment); Tcl_IncrRefCount(incrPtr); pcAdjustment = 2; doIncrStk: if ((*pc == INST_INCR_ARRAY_STK_IMM) || (*pc == INST_INCR_ARRAY_STK)) { part2Ptr = OBJ_AT_TOS; objPtr = OBJ_UNDER_TOS; TRACE(("\"%.30s(%.30s)\" (by %ld) => ", O2S(objPtr), O2S(part2Ptr), increment)); } else { part2Ptr = NULL; objPtr = OBJ_AT_TOS; TRACE(("\"%.30s\" (by %ld) => ", O2S(objPtr), increment)); } part1Ptr = objPtr; opnd = -1; varPtr = TclObjLookupVarEx(interp, objPtr, part2Ptr, TCL_LEAVE_ERR_MSG, "read", 1, 1, &arrayPtr); if (!varPtr) { DECACHE_STACK_INFO(); Tcl_AddErrorInfo(interp, "\n (reading value of variable to increment)"); CACHE_STACK_INFO(); TRACE_ERROR(interp); Tcl_DecrRefCount(incrPtr); goto gotError; } cleanup = ((part2Ptr == NULL)? 1 : 2); goto doIncrVar; case INST_INCR_ARRAY1_IMM: opnd = TclGetUInt1AtPtr(pc+1); increment = TclGetInt1AtPtr(pc+2); TclNewIntObj(incrPtr, increment); Tcl_IncrRefCount(incrPtr); pcAdjustment = 3; doIncrArray: part1Ptr = NULL; part2Ptr = OBJ_AT_TOS; arrayPtr = LOCAL(opnd); cleanup = 1; while (TclIsVarLink(arrayPtr)) { arrayPtr = arrayPtr->value.linkPtr; } TRACE(("%u \"%.30s\" (by %ld) => ", opnd, O2S(part2Ptr), increment)); varPtr = TclLookupArrayElement(interp, part1Ptr, part2Ptr, TCL_LEAVE_ERR_MSG, "read", 1, 1, arrayPtr, opnd); if (!varPtr) { TRACE_ERROR(interp); Tcl_DecrRefCount(incrPtr); goto gotError; } goto doIncrVar; case INST_INCR_SCALAR1_IMM: opnd = TclGetUInt1AtPtr(pc+1); increment = TclGetInt1AtPtr(pc+2); pcAdjustment = 3; cleanup = 0; varPtr = LOCAL(opnd); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } if (TclIsVarDirectModifyable(varPtr)) { void *ptr; int type; objPtr = varPtr->value.objPtr; if (GetNumberFromObj(NULL, objPtr, &ptr, &type) == TCL_OK) { if (type == TCL_NUMBER_INT) { Tcl_WideInt augend = *((const Tcl_WideInt *)ptr); Tcl_WideInt sum = (Tcl_WideInt)((Tcl_WideUInt)augend + (Tcl_WideUInt)increment); /* * Overflow when (augend and sum have different sign) and * (augend and increment have the same sign). This is * encapsulated in the Overflowing macro. */ if (!Overflowing(augend, increment, sum)) { TRACE(("%u %ld => ", opnd, increment)); if (Tcl_IsShared(objPtr)) { objPtr->refCount--; /* We know it's shared. */ TclNewIntObj(objResultPtr, sum); Tcl_IncrRefCount(objResultPtr); varPtr->value.objPtr = objResultPtr; } else { objResultPtr = objPtr; TclSetIntObj(objPtr, sum); } goto doneIncr; } w = (Tcl_WideInt)augend; TRACE(("%u %ld => ", opnd, increment)); if (Tcl_IsShared(objPtr)) { objPtr->refCount--; /* We know it's shared. */ TclNewIntObj(objResultPtr, w + increment); Tcl_IncrRefCount(objResultPtr); varPtr->value.objPtr = objResultPtr; } else { objResultPtr = objPtr; /* * We know the sum value is outside the Tcl_WideInt range; * use macro form that doesn't range test again. */ TclSetIntObj(objPtr, w+increment); } goto doneIncr; } /* end if (type == TCL_NUMBER_INT) */ } if (Tcl_IsShared(objPtr)) { objPtr->refCount--; /* We know it's shared */ objResultPtr = Tcl_DuplicateObj(objPtr); Tcl_IncrRefCount(objResultPtr); varPtr->value.objPtr = objResultPtr; } else { objResultPtr = objPtr; } TclNewIntObj(incrPtr, increment); if (TclIncrObj(interp, objResultPtr, incrPtr) != TCL_OK) { Tcl_DecrRefCount(incrPtr); TRACE_ERROR(interp); goto gotError; } Tcl_DecrRefCount(incrPtr); goto doneIncr; } /* * All other cases, flow through to generic handling. */ TclNewIntObj(incrPtr, increment); Tcl_IncrRefCount(incrPtr); doIncrScalar: varPtr = LOCAL(opnd); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } arrayPtr = NULL; part1Ptr = part2Ptr = NULL; cleanup = 0; TRACE(("%u %s => ", opnd, TclGetString(incrPtr))); doIncrVar: if (TclIsVarDirectModifyable2(varPtr, arrayPtr)) { objPtr = varPtr->value.objPtr; if (Tcl_IsShared(objPtr)) { objPtr->refCount--; /* We know it's shared */ objResultPtr = Tcl_DuplicateObj(objPtr); Tcl_IncrRefCount(objResultPtr); varPtr->value.objPtr = objResultPtr; } else { objResultPtr = objPtr; } if (TclIncrObj(interp, objResultPtr, incrPtr) != TCL_OK) { Tcl_DecrRefCount(incrPtr); TRACE_ERROR(interp); goto gotError; } Tcl_DecrRefCount(incrPtr); } else { DECACHE_STACK_INFO(); objResultPtr = TclPtrIncrObjVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, incrPtr, TCL_LEAVE_ERR_MSG, opnd); CACHE_STACK_INFO(); Tcl_DecrRefCount(incrPtr); if (objResultPtr == NULL) { TRACE_ERROR(interp); goto gotError; } } doneIncr: TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); #ifndef TCL_COMPILE_DEBUG if (pc[pcAdjustment] == INST_POP) { NEXT_INST_V((pcAdjustment+1), cleanup, 0); } #endif NEXT_INST_V(pcAdjustment, cleanup, 1); } /* * End of INST_INCR instructions. * ----------------------------------------------------------------- * Start of INST_EXIST instructions. */ case INST_EXIST_SCALAR: cleanup = 0; pcAdjustment = 5; opnd = TclGetUInt4AtPtr(pc+1); varPtr = LOCAL(opnd); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } TRACE(("%u => ", opnd)); if (ReadTraced(varPtr)) { DECACHE_STACK_INFO(); TclObjCallVarTraces(iPtr, NULL, varPtr, NULL, NULL, TCL_TRACE_READS, 0, opnd); CACHE_STACK_INFO(); if (TclIsVarUndefined(varPtr)) { TclCleanupVar(varPtr, NULL); varPtr = NULL; } } goto afterExistsPeephole; case INST_EXIST_ARRAY: cleanup = 1; pcAdjustment = 5; opnd = TclGetUInt4AtPtr(pc+1); part2Ptr = OBJ_AT_TOS; arrayPtr = LOCAL(opnd); while (TclIsVarLink(arrayPtr)) { arrayPtr = arrayPtr->value.linkPtr; } TRACE(("%u \"%.30s\" => ", opnd, O2S(part2Ptr))); if (TclIsVarArray(arrayPtr) && !ReadTraced(arrayPtr)) { varPtr = VarHashFindVar(arrayPtr->value.tablePtr, part2Ptr); if (!varPtr || !ReadTraced(varPtr)) { goto afterExistsPeephole; } } varPtr = TclLookupArrayElement(interp, NULL, part2Ptr, 0, "access", 0, 1, arrayPtr, opnd); if (varPtr) { if (ReadTraced(varPtr) || (arrayPtr && ReadTraced(arrayPtr))) { DECACHE_STACK_INFO(); TclObjCallVarTraces(iPtr, arrayPtr, varPtr, NULL, part2Ptr, TCL_TRACE_READS, 0, opnd); CACHE_STACK_INFO(); } if (TclIsVarUndefined(varPtr)) { TclCleanupVar(varPtr, arrayPtr); varPtr = NULL; } } goto afterExistsPeephole; case INST_EXIST_ARRAY_STK: cleanup = 2; pcAdjustment = 1; part2Ptr = OBJ_AT_TOS; /* element name */ part1Ptr = OBJ_UNDER_TOS; /* array name */ TRACE(("\"%.30s(%.30s)\" => ", O2S(part1Ptr), O2S(part2Ptr))); goto doExistStk; case INST_EXIST_STK: cleanup = 1; pcAdjustment = 1; part2Ptr = NULL; part1Ptr = OBJ_AT_TOS; /* variable name */ TRACE(("\"%.30s\" => ", O2S(part1Ptr))); doExistStk: varPtr = TclObjLookupVarEx(interp, part1Ptr, part2Ptr, 0, "access", /*createPart1*/0, /*createPart2*/1, &arrayPtr); if (varPtr) { if (ReadTraced(varPtr) || (arrayPtr && ReadTraced(arrayPtr))) { DECACHE_STACK_INFO(); TclObjCallVarTraces(iPtr, arrayPtr, varPtr, part1Ptr, part2Ptr, TCL_TRACE_READS, 0, -1); CACHE_STACK_INFO(); } if (TclIsVarUndefined(varPtr)) { TclCleanupVar(varPtr, arrayPtr); varPtr = NULL; } } /* * Peep-hole optimisation: if you're about to jump, do jump from here. */ afterExistsPeephole: { int found = (varPtr && !TclIsVarUndefined(varPtr)); TRACE_APPEND(("%d\n", found ? 1 : 0)); JUMP_PEEPHOLE_V(found, pcAdjustment, cleanup); } /* * End of INST_EXIST instructions. * ----------------------------------------------------------------- * Start of INST_UNSET instructions. */ { int flags; case INST_UNSET_SCALAR: flags = TclGetUInt1AtPtr(pc+1) ? TCL_LEAVE_ERR_MSG : 0; opnd = TclGetUInt4AtPtr(pc+2); varPtr = LOCAL(opnd); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } TRACE(("%s %u => ", (flags ? "normal" : "noerr"), opnd)); if (TclIsVarDirectUnsettable(varPtr) && !TclIsVarInHash(varPtr)) { /* * No errors, no traces, no searches: just make the variable cease * to exist. */ if (!TclIsVarUndefined(varPtr)) { TclDecrRefCount(varPtr->value.objPtr); } else if (flags & TCL_LEAVE_ERR_MSG) { goto slowUnsetScalar; } varPtr->value.objPtr = NULL; TRACE_APPEND(("OK\n")); NEXT_INST_F(6, 0, 0); } slowUnsetScalar: DECACHE_STACK_INFO(); if (TclPtrUnsetVarIdx(interp, varPtr, NULL, NULL, NULL, flags, opnd) != TCL_OK && flags) { goto errorInUnset; } CACHE_STACK_INFO(); NEXT_INST_F(6, 0, 0); case INST_UNSET_ARRAY: flags = TclGetUInt1AtPtr(pc+1) ? TCL_LEAVE_ERR_MSG : 0; opnd = TclGetUInt4AtPtr(pc+2); part2Ptr = OBJ_AT_TOS; arrayPtr = LOCAL(opnd); while (TclIsVarLink(arrayPtr)) { arrayPtr = arrayPtr->value.linkPtr; } TRACE(("%s %u \"%.30s\" => ", (flags ? "normal" : "noerr"), opnd, O2S(part2Ptr))); if (TclIsVarArray(arrayPtr) && !UnsetTraced(arrayPtr) && !(arrayPtr->flags & VAR_SEARCH_ACTIVE)) { varPtr = VarHashFindVar(arrayPtr->value.tablePtr, part2Ptr); if (varPtr && TclIsVarDirectUnsettable(varPtr)) { /* * No nasty traces and element exists, so we can proceed to * unset it. Might still not exist though... */ if (!TclIsVarUndefined(varPtr)) { TclDecrRefCount(varPtr->value.objPtr); TclSetVarUndefined(varPtr); TclClearVarNamespaceVar(varPtr); TclCleanupVar(varPtr, arrayPtr); } else if (flags & TCL_LEAVE_ERR_MSG) { goto slowUnsetArray; } TRACE_APPEND(("OK\n")); NEXT_INST_F(6, 1, 0); } else if (!varPtr && !(flags & TCL_LEAVE_ERR_MSG)) { /* * Don't need to do anything here. */ TRACE_APPEND(("OK\n")); NEXT_INST_F(6, 1, 0); } } slowUnsetArray: DECACHE_STACK_INFO(); varPtr = TclLookupArrayElement(interp, NULL, part2Ptr, flags, "unset", 0, 0, arrayPtr, opnd); if (!varPtr) { if (flags & TCL_LEAVE_ERR_MSG) { goto errorInUnset; } } else if (TclPtrUnsetVarIdx(interp, varPtr, arrayPtr, NULL, part2Ptr, flags, opnd) != TCL_OK && (flags & TCL_LEAVE_ERR_MSG)) { goto errorInUnset; } CACHE_STACK_INFO(); NEXT_INST_F(6, 1, 0); case INST_UNSET_ARRAY_STK: flags = TclGetUInt1AtPtr(pc+1) ? TCL_LEAVE_ERR_MSG : 0; cleanup = 2; part2Ptr = OBJ_AT_TOS; /* element name */ part1Ptr = OBJ_UNDER_TOS; /* array name */ TRACE(("%s \"%.30s(%.30s)\" => ", (flags ? "normal" : "noerr"), O2S(part1Ptr), O2S(part2Ptr))); goto doUnsetStk; case INST_UNSET_STK: flags = TclGetUInt1AtPtr(pc+1) ? TCL_LEAVE_ERR_MSG : 0; cleanup = 1; part2Ptr = NULL; part1Ptr = OBJ_AT_TOS; /* variable name */ TRACE(("%s \"%.30s\" => ", (flags ? "normal" : "noerr"), O2S(part1Ptr))); doUnsetStk: DECACHE_STACK_INFO(); if (TclObjUnsetVar2(interp, part1Ptr, part2Ptr, flags) != TCL_OK && (flags & TCL_LEAVE_ERR_MSG)) { goto errorInUnset; } CACHE_STACK_INFO(); TRACE_APPEND(("OK\n")); NEXT_INST_V(2, cleanup, 0); errorInUnset: CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } break; /* * End of INST_UNSET instructions. * ----------------------------------------------------------------- * Start of INST_CONST instructions. */ { const char *msgPart; case INST_CONST_IMM: opnd = TclGetUInt4AtPtr(pc+1); pcAdjustment = 5; cleanup = 1; part1Ptr = NULL; objPtr = OBJ_AT_TOS; TRACE(("%u \"%.30s\" => \n", opnd, O2S(objPtr))); varPtr = LOCAL(opnd); arrayPtr = NULL; while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } goto doConst; case INST_CONST_STK: opnd = -1; pcAdjustment = 1; cleanup = 2; part1Ptr = OBJ_UNDER_TOS; objPtr = OBJ_AT_TOS; TRACE(("\"%.30s\" \"%.30s\" => ", O2S(part1Ptr), O2S(objPtr))); varPtr = TclObjLookupVarEx(interp, part1Ptr, NULL, 0, NULL, /*createPart1*/1, /*createPart2*/0, &arrayPtr); doConst: if (TclIsVarConstant(varPtr)) { TRACE_APPEND(("\n")); NEXT_INST_V(pcAdjustment, cleanup, 0); } if (TclIsVarArray(varPtr)) { msgPart = "variable is array"; goto constError; } else if (TclIsVarArrayElement(varPtr)) { msgPart = "name refers to an element in an array"; goto constError; } else if (!TclIsVarUndefined(varPtr)) { msgPart = "variable already exists"; goto constError; } if (TclIsVarDirectModifyable(varPtr)) { varPtr->value.objPtr = objPtr; Tcl_IncrRefCount(objPtr); } else { Tcl_Obj *resPtr; DECACHE_STACK_INFO(); resPtr = TclPtrSetVarIdx(interp, varPtr, arrayPtr, part1Ptr, NULL, objPtr, TCL_LEAVE_ERR_MSG, opnd); CACHE_STACK_INFO(); if (resPtr == NULL) { TRACE_ERROR(interp); goto gotError; } } TclSetVarConstant(varPtr); TRACE_APPEND(("\n")); NEXT_INST_V(pcAdjustment, cleanup, 0); constError: TclObjVarErrMsg(interp, part1Ptr, NULL, "make constant", msgPart, opnd); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CONST", (char *)NULL); TRACE_ERROR(interp); goto gotError; } /* * End of INST_CONST instructions. * ----------------------------------------------------------------- * Start of INST_ARRAY instructions. */ case INST_ARRAY_EXISTS_IMM: opnd = TclGetUInt4AtPtr(pc+1); pcAdjustment = 5; cleanup = 0; part1Ptr = NULL; arrayPtr = NULL; TRACE(("%u => ", opnd)); varPtr = LOCAL(opnd); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } goto doArrayExists; case INST_ARRAY_EXISTS_STK: opnd = -1; pcAdjustment = 1; cleanup = 1; part1Ptr = OBJ_AT_TOS; TRACE(("\"%.30s\" => ", O2S(part1Ptr))); varPtr = TclObjLookupVarEx(interp, part1Ptr, NULL, 0, NULL, /*createPart1*/0, /*createPart2*/0, &arrayPtr); doArrayExists: DECACHE_STACK_INFO(); result = TclCheckArrayTraces(interp, varPtr, arrayPtr, part1Ptr, opnd); CACHE_STACK_INFO(); if (result == TCL_ERROR) { TRACE_ERROR(interp); goto gotError; } if (varPtr && TclIsVarArray(varPtr) && !TclIsVarUndefined(varPtr)) { objResultPtr = TCONST(1); } else { objResultPtr = TCONST(0); } TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_V(pcAdjustment, cleanup, 1); case INST_ARRAY_MAKE_IMM: opnd = TclGetUInt4AtPtr(pc+1); pcAdjustment = 5; cleanup = 0; part1Ptr = NULL; arrayPtr = NULL; TRACE(("%u => ", opnd)); varPtr = LOCAL(opnd); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } goto doArrayMake; case INST_ARRAY_MAKE_STK: opnd = -1; pcAdjustment = 1; cleanup = 1; part1Ptr = OBJ_AT_TOS; TRACE(("\"%.30s\" => ", O2S(part1Ptr))); varPtr = TclObjLookupVarEx(interp, part1Ptr, NULL, TCL_LEAVE_ERR_MSG, "set", /*createPart1*/1, /*createPart2*/0, &arrayPtr); if (varPtr == NULL) { TRACE_ERROR(interp); goto gotError; } doArrayMake: if (varPtr && !TclIsVarArray(varPtr)) { if (TclIsVarArrayElement(varPtr) || !TclIsVarUndefined(varPtr)) { /* * Either an array element, or a scalar: lose! */ TclObjVarErrMsg(interp, part1Ptr, NULL, "array set", "variable isn't array", opnd); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "WRITE", "ARRAY", (char *)NULL); CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } TclInitArrayVar(varPtr); #ifdef TCL_COMPILE_DEBUG TRACE_APPEND(("done\n")); } else { TRACE_APPEND(("nothing to do\n")); #endif } NEXT_INST_V(pcAdjustment, cleanup, 0); /* * End of INST_ARRAY instructions. * ----------------------------------------------------------------- * Start of variable linking instructions. */ { Var *otherPtr; CallFrame *framePtr, *savedFramePtr; Tcl_Namespace *nsPtr; Namespace *savedNsPtr; case INST_UPVAR: TRACE(("%d %.30s %.30s => ", TclGetInt4AtPtr(pc+1), O2S(OBJ_UNDER_TOS), O2S(OBJ_AT_TOS))); if (TclObjGetFrame(interp, OBJ_UNDER_TOS, &framePtr) == -1) { TRACE_ERROR(interp); goto gotError; } /* * Locate the other variable. */ savedFramePtr = iPtr->varFramePtr; iPtr->varFramePtr = framePtr; otherPtr = TclObjLookupVarEx(interp, OBJ_AT_TOS, NULL, TCL_LEAVE_ERR_MSG, "access", /*createPart1*/ 1, /*createPart2*/ 1, &varPtr); iPtr->varFramePtr = savedFramePtr; if (!otherPtr) { TRACE_ERROR(interp); goto gotError; } goto doLinkVars; case INST_NSUPVAR: TRACE(("%d %.30s %.30s => ", TclGetInt4AtPtr(pc+1), O2S(OBJ_UNDER_TOS), O2S(OBJ_AT_TOS))); if (TclGetNamespaceFromObj(interp, OBJ_UNDER_TOS, &nsPtr) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } /* * Locate the other variable. */ savedNsPtr = iPtr->varFramePtr->nsPtr; iPtr->varFramePtr->nsPtr = (Namespace *) nsPtr; otherPtr = TclObjLookupVarEx(interp, OBJ_AT_TOS, NULL, (TCL_NAMESPACE_ONLY|TCL_LEAVE_ERR_MSG|TCL_AVOID_RESOLVERS), "access", /*createPart1*/ 1, /*createPart2*/ 1, &varPtr); iPtr->varFramePtr->nsPtr = savedNsPtr; if (!otherPtr) { TRACE_ERROR(interp); goto gotError; } goto doLinkVars; case INST_VARIABLE: TRACE(("%d, %.30s => ", TclGetInt4AtPtr(pc+1), O2S(OBJ_AT_TOS))); otherPtr = TclObjLookupVarEx(interp, OBJ_AT_TOS, NULL, (TCL_NAMESPACE_ONLY | TCL_LEAVE_ERR_MSG), "access", /*createPart1*/ 1, /*createPart2*/ 1, &varPtr); if (!otherPtr) { TRACE_ERROR(interp); goto gotError; } /* * Do the [variable] magic. */ TclSetVarNamespaceVar(otherPtr); doLinkVars: /* * If we are here, the local variable has already been created: do the * little work of TclPtrMakeUpvar that remains to be done right here * if there are no errors; otherwise, let it handle the case. */ opnd = TclGetInt4AtPtr(pc+1); varPtr = LOCAL(opnd); if ((varPtr != otherPtr) && !TclIsVarTraced(varPtr) && (TclIsVarUndefined(varPtr) || TclIsVarLink(varPtr))) { if (!TclIsVarUndefined(varPtr)) { /* * Then it is a defined link. */ Var *linkPtr = varPtr->value.linkPtr; if (linkPtr == otherPtr) { TRACE_APPEND(("already linked\n")); NEXT_INST_F(5, 1, 0); } if (TclIsVarInHash(linkPtr)) { VarHashRefCount(linkPtr)--; if (TclIsVarUndefined(linkPtr)) { TclCleanupVar(linkPtr, NULL); } } } TclSetVarLink(varPtr); varPtr->value.linkPtr = otherPtr; if (TclIsVarInHash(otherPtr)) { VarHashRefCount(otherPtr)++; } } else if (TclPtrObjMakeUpvarIdx(interp, otherPtr, NULL, 0, opnd) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } /* * Do not pop the namespace or frame index, it may be needed for other * variables - and [variable] did not push it at all. */ TRACE_APPEND(("link made\n")); NEXT_INST_F(5, 1, 0); } break; /* * End of variable linking instructions. * ----------------------------------------------------------------- */ case INST_JUMP1: opnd = TclGetInt1AtPtr(pc+1); TRACE(("%d => new pc %" TCL_Z_MODIFIER "u\n", opnd, (size_t)(pc + opnd - codePtr->codeStart))); NEXT_INST_F(opnd, 0, 0); break; case INST_JUMP4: opnd = TclGetInt4AtPtr(pc+1); TRACE(("%d => new pc %" TCL_Z_MODIFIER "u\n", opnd, (size_t)(pc + opnd - codePtr->codeStart))); NEXT_INST_F(opnd, 0, 0); { int jmpOffset[2], b; /* TODO: consider rewrite so we don't compute the offset we're not * going to take. */ case INST_JUMP_FALSE4: jmpOffset[0] = TclGetInt4AtPtr(pc+1); /* FALSE offset */ jmpOffset[1] = 5; /* TRUE offset */ goto doCondJump; case INST_JUMP_TRUE4: jmpOffset[0] = 5; jmpOffset[1] = TclGetInt4AtPtr(pc+1); goto doCondJump; case INST_JUMP_FALSE1: jmpOffset[0] = TclGetInt1AtPtr(pc+1); jmpOffset[1] = 2; goto doCondJump; case INST_JUMP_TRUE1: jmpOffset[0] = 2; jmpOffset[1] = TclGetInt1AtPtr(pc+1); doCondJump: valuePtr = OBJ_AT_TOS; TRACE(("%d => ", jmpOffset[ (*pc==INST_JUMP_FALSE1 || *pc==INST_JUMP_FALSE4) ? 0 : 1])); /* TODO - check claim that taking address of b harms performance */ /* TODO - consider optimization search for constants */ if (TclGetBooleanFromObj(interp, valuePtr, &b) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } #ifdef TCL_COMPILE_DEBUG if (b) { if ((*pc == INST_JUMP_TRUE1) || (*pc == INST_JUMP_TRUE4)) { TRACE_APPEND(("%.20s true, new pc %" TCL_Z_MODIFIER "u\n", O2S(valuePtr), (size_t)(pc + jmpOffset[1] - codePtr->codeStart))); } else { TRACE_APPEND(("%.20s true\n", O2S(valuePtr))); } } else { if ((*pc == INST_JUMP_TRUE1) || (*pc == INST_JUMP_TRUE4)) { TRACE_APPEND(("%.20s false\n", O2S(valuePtr))); } else { TRACE_APPEND(("%.20s false, new pc %" TCL_Z_MODIFIER "u\n", O2S(valuePtr), (size_t)(pc + jmpOffset[0] - codePtr->codeStart))); } } #endif NEXT_INST_F(jmpOffset[b], 1, 0); } break; case INST_JUMP_TABLE: { Tcl_HashEntry *hPtr; JumptableInfo *jtPtr; /* * Jump to location looked up in a hashtable; fall through to next * instr if lookup fails. */ opnd = TclGetInt4AtPtr(pc+1); jtPtr = (JumptableInfo *) codePtr->auxDataArrayPtr[opnd].clientData; TRACE(("%d \"%.20s\" => ", opnd, O2S(OBJ_AT_TOS))); hPtr = Tcl_FindHashEntry(&jtPtr->hashTable, TclGetString(OBJ_AT_TOS)); if (hPtr != NULL) { int jumpOffset = PTR2INT(Tcl_GetHashValue(hPtr)); TRACE_APPEND(("found in table, new pc %" TCL_Z_MODIFIER "u\n", (size_t)(pc - codePtr->codeStart + jumpOffset))); NEXT_INST_F(jumpOffset, 1, 0); } else { TRACE_APPEND(("not found in table\n")); NEXT_INST_F(5, 1, 0); } } break; /* * ----------------------------------------------------------------- * Start of general introspector instructions. */ case INST_NS_CURRENT: objResultPtr = TclNewNamespaceObj(TclGetCurrentNamespace(interp)); TRACE_WITH_OBJ(("=> "), objResultPtr); NEXT_INST_F(1, 0, 1); break; case INST_COROUTINE_NAME: { CoroutineData *corPtr = iPtr->execEnvPtr->corPtr; TclNewObj(objResultPtr); if (corPtr && !(corPtr->cmdPtr->flags & CMD_DYING)) { Tcl_GetCommandFullName(interp, (Tcl_Command) corPtr->cmdPtr, objResultPtr); } TRACE_WITH_OBJ(("=> "), objResultPtr); NEXT_INST_F(1, 0, 1); } break; case INST_INFO_LEVEL_NUM: TclNewIntObj(objResultPtr, (int)iPtr->varFramePtr->level); TRACE_WITH_OBJ(("=> "), objResultPtr); NEXT_INST_F(1, 0, 1); break; case INST_INFO_LEVEL_ARGS: { int level; CallFrame *framePtr = iPtr->varFramePtr; CallFrame *rootFramePtr = iPtr->rootFramePtr; TRACE(("\"%.30s\" => ", O2S(OBJ_AT_TOS))); if (TclGetIntFromObj(interp, OBJ_AT_TOS, &level) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } if (level <= 0) { level += framePtr->level; } for (; ((int)framePtr->level!=level) && (framePtr!=rootFramePtr) ; framePtr = framePtr->callerVarPtr) { /* Empty loop body */ } if (framePtr == rootFramePtr) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad level \"%s\"", TclGetString(OBJ_AT_TOS))); TRACE_ERROR(interp); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "STACK_LEVEL", TclGetString(OBJ_AT_TOS), (char *)NULL); CACHE_STACK_INFO(); goto gotError; } objResultPtr = Tcl_NewListObj(framePtr->objc, framePtr->objv); TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_F(1, 1, 1); } { Tcl_Command cmd, origCmd; case INST_RESOLVE_COMMAND: cmd = Tcl_GetCommandFromObj(interp, OBJ_AT_TOS); TclNewObj(objResultPtr); if (cmd != NULL) { Tcl_GetCommandFullName(interp, cmd, objResultPtr); } TRACE_WITH_OBJ(("\"%.20s\" => ", O2S(OBJ_AT_TOS)), objResultPtr); NEXT_INST_F(1, 1, 1); case INST_ORIGIN_COMMAND: TRACE(("\"%.30s\" => ", O2S(OBJ_AT_TOS))); cmd = Tcl_GetCommandFromObj(interp, OBJ_AT_TOS); if (cmd == NULL) { goto instOriginError; } origCmd = TclGetOriginalCommand(cmd); if (origCmd == NULL) { origCmd = cmd; } TclNewObj(objResultPtr); Tcl_GetCommandFullName(interp, origCmd, objResultPtr); if (TclCheckEmptyString(objResultPtr) == TCL_EMPTYSTRING_YES ) { Tcl_DecrRefCount(objResultPtr); instOriginError: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid command name \"%s\"", TclGetString(OBJ_AT_TOS))); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COMMAND", TclGetString(OBJ_AT_TOS), (char *)NULL); CACHE_STACK_INFO(); TRACE_APPEND(("ERROR: not command\n")); goto gotError; } TRACE_APPEND(("\"%.30s\"", O2S(OBJ_AT_TOS))); NEXT_INST_F(1, 1, 1); } /* * ----------------------------------------------------------------- * Start of TclOO support instructions. */ { Object *oPtr; CallFrame *framePtr; CallContext *contextPtr; Tcl_Size skip, newDepth; case INST_TCLOO_SELF: framePtr = iPtr->varFramePtr; if (framePtr == NULL || !(framePtr->isProcCallFrame & FRAME_IS_METHOD)) { TRACE(("=> ERROR: no TclOO call context\n")); Tcl_SetObjResult(interp, Tcl_NewStringObj( "self may only be called from inside a method", -1)); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "OO", "CONTEXT_REQUIRED", (char *)NULL); CACHE_STACK_INFO(); goto gotError; } contextPtr = (CallContext *)framePtr->clientData; /* * Call out to get the name; it's expensive to compute but cached. */ objResultPtr = TclOOObjectName(interp, contextPtr->oPtr); TRACE_WITH_OBJ(("=> "), objResultPtr); NEXT_INST_F(1, 0, 1); case INST_TCLOO_NEXT_CLASS: opnd = TclGetUInt1AtPtr(pc+1); framePtr = iPtr->varFramePtr; valuePtr = OBJ_AT_DEPTH(opnd - 2); objv = &OBJ_AT_DEPTH(opnd - 1); skip = 2; TRACE(("%d => ", opnd)); if (framePtr == NULL || !(framePtr->isProcCallFrame & FRAME_IS_METHOD)) { TRACE_APPEND(("ERROR: no TclOO call context\n")); Tcl_SetObjResult(interp, Tcl_NewStringObj( "nextto may only be called from inside a method", -1)); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "OO", "CONTEXT_REQUIRED", (char *)NULL); CACHE_STACK_INFO(); goto gotError; } contextPtr = (CallContext *)framePtr->clientData; oPtr = (Object *) Tcl_GetObjectFromObj(interp, valuePtr); if (oPtr == NULL) { TRACE_APPEND(("ERROR: \"%.30s\" not object\n", O2S(valuePtr))); goto gotError; } else { Class *classPtr = oPtr->classPtr; struct MInvoke *miPtr; Tcl_Size i; const char *methodType; if (classPtr == NULL) { TRACE_APPEND(("ERROR: \"%.30s\" not class\n", O2S(valuePtr))); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" is not a class", TclGetString(valuePtr))); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "OO", "CLASS_REQUIRED", (char *)NULL); CACHE_STACK_INFO(); goto gotError; } for (i=contextPtr->index+1 ; icallPtr->numChain ; i++) { miPtr = contextPtr->callPtr->chain + i; if (!miPtr->isFilter && miPtr->mPtr->declaringClassPtr == classPtr) { newDepth = i; #ifdef TCL_COMPILE_DEBUG if (tclTraceExec >= 2) { if (traceInstructions) { strncpy(cmdNameBuf, TclGetString(objv[0]), 20); } else { fprintf(stdout, "%" TCL_SIZE_MODIFIER "d: (%" TCL_T_MODIFIER "d) invoking ", iPtr->numLevels, (size_t)(pc - codePtr->codeStart)); } for (i = 0; i < opnd; i++) { TclPrintObject(stdout, objv[i], 15); fprintf(stdout, " "); } fprintf(stdout, "\n"); fflush(stdout); } #endif /*TCL_COMPILE_DEBUG*/ goto doInvokeNext; } } if (contextPtr->callPtr->flags & CONSTRUCTOR) { methodType = "constructor"; } else if (contextPtr->callPtr->flags & DESTRUCTOR) { methodType = "destructor"; } else { methodType = "method"; } TRACE_APPEND(("ERROR: \"%.30s\" not on reachable chain\n", O2S(valuePtr))); for (i = contextPtr->index ; i != TCL_INDEX_NONE ; i--) { miPtr = contextPtr->callPtr->chain + i; if (miPtr->isFilter || miPtr->mPtr->declaringClassPtr != classPtr) { continue; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s implementation by \"%s\" not reachable from here", methodType, TclGetString(valuePtr))); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "OO", "CLASS_NOT_REACHABLE", (char *)NULL); CACHE_STACK_INFO(); goto gotError; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s has no non-filter implementation by \"%s\"", methodType, TclGetString(valuePtr))); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "OO", "CLASS_NOT_THERE", (char *)NULL); CACHE_STACK_INFO(); goto gotError; } case INST_TCLOO_NEXT: opnd = TclGetUInt1AtPtr(pc+1); objv = &OBJ_AT_DEPTH(opnd - 1); framePtr = iPtr->varFramePtr; skip = 1; TRACE(("%d => ", opnd)); if (framePtr == NULL || !(framePtr->isProcCallFrame & FRAME_IS_METHOD)) { TRACE_APPEND(("ERROR: no TclOO call context\n")); Tcl_SetObjResult(interp, Tcl_NewStringObj( "next may only be called from inside a method", -1)); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "OO", "CONTEXT_REQUIRED", (char *)NULL); CACHE_STACK_INFO(); goto gotError; } contextPtr = (CallContext *)framePtr->clientData; newDepth = contextPtr->index + 1; if (newDepth >= contextPtr->callPtr->numChain) { /* * We're at the end of the chain; generate an error message unless * the interpreter is being torn down, in which case we might be * getting here because of methods/destructors doing a [next] (or * equivalent) unexpectedly. */ const char *methodType; if (contextPtr->callPtr->flags & CONSTRUCTOR) { methodType = "constructor"; } else if (contextPtr->callPtr->flags & DESTRUCTOR) { methodType = "destructor"; } else { methodType = "method"; } TRACE_APPEND(("ERROR: no TclOO next impl\n")); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "no next %s implementation", methodType)); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "OO", "NOTHING_NEXT", (char *)NULL); CACHE_STACK_INFO(); goto gotError; #ifdef TCL_COMPILE_DEBUG } else if (tclTraceExec >= 2) { int i; if (traceInstructions) { strncpy(cmdNameBuf, TclGetString(objv[0]), 20); } else { fprintf(stdout, "%" TCL_SIZE_MODIFIER "d: (%" TCL_Z_MODIFIER "u) invoking ", iPtr->numLevels, (pc - codePtr->codeStart)); } for (i = 0; i < opnd; i++) { TclPrintObject(stdout, objv[i], 15); fprintf(stdout, " "); } fprintf(stdout, "\n"); fflush(stdout); #endif /*TCL_COMPILE_DEBUG*/ } doInvokeNext: bcFramePtr->data.tebc.pc = (char *) pc; iPtr->cmdFramePtr = bcFramePtr; if (iPtr->flags & INTERP_DEBUG_FRAME) { ArgumentBCEnter(interp, codePtr, TD, pc, opnd, objv); } pcAdjustment = 2; cleanup = opnd; DECACHE_STACK_INFO(); iPtr->varFramePtr = framePtr->callerVarPtr; pc += pcAdjustment; TEBC_YIELD(); TclPushTailcallPoint(interp); oPtr = contextPtr->oPtr; if (oPtr->flags & FILTER_HANDLING) { TclNRAddCallback(interp, FinalizeOONextFilter, framePtr, contextPtr, INT2PTR(contextPtr->index), INT2PTR(contextPtr->skip)); } else { TclNRAddCallback(interp, FinalizeOONext, framePtr, contextPtr, INT2PTR(contextPtr->index), INT2PTR(contextPtr->skip)); } contextPtr->skip = skip; contextPtr->index = newDepth; if (contextPtr->callPtr->chain[newDepth].isFilter || contextPtr->callPtr->flags & FILTER_HANDLING) { oPtr->flags |= FILTER_HANDLING; } else { oPtr->flags &= ~FILTER_HANDLING; } { Method *const mPtr = contextPtr->callPtr->chain[newDepth].mPtr; if (mPtr->typePtr->version < TCL_OO_METHOD_VERSION_2) { return mPtr->typePtr->callProc(mPtr->clientData, interp, (Tcl_ObjectContext) contextPtr, opnd, objv); } return ((Tcl_MethodCallProc2 *)(void *)(mPtr->typePtr->callProc))(mPtr->clientData, interp, (Tcl_ObjectContext) contextPtr, opnd, objv); } case INST_TCLOO_IS_OBJECT: oPtr = (Object *) Tcl_GetObjectFromObj(interp, OBJ_AT_TOS); objResultPtr = TCONST(oPtr != NULL ? 1 : 0); TRACE_WITH_OBJ(("%.30s => ", O2S(OBJ_AT_TOS)), objResultPtr); NEXT_INST_F(1, 1, 1); case INST_TCLOO_CLASS: oPtr = (Object *) Tcl_GetObjectFromObj(interp, OBJ_AT_TOS); if (oPtr == NULL) { TRACE(("%.30s => ERROR: not object\n", O2S(OBJ_AT_TOS))); goto gotError; } objResultPtr = TclOOObjectName(interp, oPtr->selfCls->thisPtr); TRACE_WITH_OBJ(("%.30s => ", O2S(OBJ_AT_TOS)), objResultPtr); NEXT_INST_F(1, 1, 1); case INST_TCLOO_NS: oPtr = (Object *) Tcl_GetObjectFromObj(interp, OBJ_AT_TOS); if (oPtr == NULL) { TRACE(("%.30s => ERROR: not object\n", O2S(OBJ_AT_TOS))); goto gotError; } objResultPtr = TclNewNamespaceObj(oPtr->namespacePtr); TRACE_WITH_OBJ(("%.30s => ", O2S(OBJ_AT_TOS)), objResultPtr); NEXT_INST_F(1, 1, 1); } /* * End of TclOO support instructions. * ----------------------------------------------------------------- * Start of INST_LIST and related instructions. */ { int numIndices, nocase, match, cflags; Tcl_Size slength, length2, fromIdx, toIdx, index, s1len, s2len; const char *s1, *s2; case INST_LIST: /* * Pop the opnd (objc) top stack elements into a new list obj and then * decrement their ref counts. */ opnd = TclGetUInt4AtPtr(pc+1); objResultPtr = Tcl_NewListObj(opnd, &OBJ_AT_DEPTH(opnd-1)); TRACE_WITH_OBJ(("%u => ", opnd), objResultPtr); NEXT_INST_V(5, opnd, 1); case INST_LIST_LENGTH: TRACE(("\"%.30s\" => ", O2S(OBJ_AT_TOS))); if (TclListObjLength(interp, OBJ_AT_TOS, &length) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } TclNewIntObj(objResultPtr, length); TRACE_APPEND(("%" TCL_SIZE_MODIFIER "d\n", length)); NEXT_INST_F(1, 1, 1); case INST_LIST_INDEX: /* lindex with objc == 3 */ value2Ptr = OBJ_AT_TOS; valuePtr = OBJ_UNDER_TOS; TRACE(("\"%.30s\" \"%.30s\" => ", O2S(valuePtr), O2S(value2Ptr))); /* special case for AbstractList */ if (TclObjTypeHasProc(valuePtr, indexProc)) { DECACHE_STACK_INFO(); length = TclObjTypeLength(valuePtr); if (TclGetIntForIndexM(interp, value2Ptr, length-1, &index)!=TCL_OK) { CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } if (TclObjTypeIndex(interp, valuePtr, index, &objResultPtr)!=TCL_OK) { CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } CACHE_STACK_INFO(); if (objResultPtr == NULL) { /* Index is out of range, return empty result. */ TclNewObj(objResultPtr); } Tcl_IncrRefCount(objResultPtr); // reference held here goto lindexDone; } /* * Extract the desired list element. */ { Tcl_Size value2Length; Tcl_Obj *indexListPtr = value2Ptr; if ((TclListObjGetElements(interp, valuePtr, &objc, &objv) == TCL_OK) && (!TclHasInternalRep(value2Ptr, &tclListType) || (Tcl_ListObjLength(interp, value2Ptr, &value2Length), value2Length == 1 ? (indexListPtr = TclListObjGetElement(value2Ptr, 0), 1) : 0))) { int code; /* increment the refCount of value2Ptr because TclListObjGetElement may * have just extracted it from a list in the condition for this block. */ Tcl_IncrRefCount(indexListPtr); DECACHE_STACK_INFO(); code = TclGetIntForIndexM(interp, indexListPtr, objc-1, &index); TclDecrRefCount(indexListPtr); CACHE_STACK_INFO(); if (code == TCL_OK) { Tcl_DecrRefCount(value2Ptr); tosPtr--; pcAdjustment = 1; goto lindexFastPath; } Tcl_ResetResult(interp); } } DECACHE_STACK_INFO(); objResultPtr = TclLindexList(interp, valuePtr, value2Ptr); CACHE_STACK_INFO(); lindexDone: if (!objResultPtr) { TRACE_ERROR(interp); goto gotError; } /* * Stash the list element on the stack. */ TRACE_APPEND(("\"%.30s\"\n", O2S(objResultPtr))); NEXT_INST_F(1, 2, -1); /* Already has the correct refCount */ case INST_LIST_INDEX_IMM: /* lindex with objc==3 and index in bytecode * stream */ /* * Pop the list and get the index. */ valuePtr = OBJ_AT_TOS; opnd = TclGetInt4AtPtr(pc+1); TRACE(("\"%.30s\" %d => ", O2S(valuePtr), opnd)); /* * Get the contents of the list, making sure that it really is a list * in the process. */ /* special case for AbstractList */ if (TclObjTypeHasProc(valuePtr, indexProc)) { length = TclObjTypeLength(valuePtr); /* Decode end-offset index values. */ index = TclIndexDecode(opnd, length-1); if (index >= 0 && index < length) { /* Compute value @ index */ DECACHE_STACK_INFO(); if (TclObjTypeIndex(interp, valuePtr, index, &objResultPtr)!=TCL_OK) { CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } CACHE_STACK_INFO(); } else { TclNewObj(objResultPtr); } pcAdjustment = 5; goto lindexFastPath2; } /* List case */ if (TclListObjGetElements(interp, valuePtr, &objc, &objv) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } /* Decode end-offset index values. */ index = TclIndexDecode(opnd, objc - 1); pcAdjustment = 5; lindexFastPath: if (index >= 0 && index < objc) { objResultPtr = objv[index]; } else { TclNewObj(objResultPtr); } lindexFastPath2: TRACE_APPEND(("\"%.30s\"\n", O2S(objResultPtr))); NEXT_INST_F(pcAdjustment, 1, 1); case INST_LIST_INDEX_MULTI: /* 'lindex' with multiple index args */ /* * Determine the count of index args. */ opnd = TclGetUInt4AtPtr(pc+1); numIndices = opnd-1; /* * Do the 'lindex' operation. */ TRACE(("%d => ", opnd)); objResultPtr = TclLindexFlat(interp, OBJ_AT_DEPTH(numIndices), numIndices, &OBJ_AT_DEPTH(numIndices - 1)); if (!objResultPtr) { TRACE_ERROR(interp); goto gotError; } /* * Set result. */ TRACE_APPEND(("\"%.30s\"\n", O2S(objResultPtr))); NEXT_INST_V(5, opnd, -1); case INST_LSET_FLAT: /* * Lset with 3, 5, or more args. Get the number of index args. */ opnd = TclGetUInt4AtPtr(pc + 1); numIndices = opnd - 2; TRACE(("%d => ", opnd)); /* * Get the old value of variable, and remove the stack ref. This is * safe because the variable still references the object; the ref * count will never go zero here - we can use the smaller macro * Tcl_DecrRefCount. */ valuePtr = POP_OBJECT(); Tcl_DecrRefCount(valuePtr); /* This one should be done here */ /* * Compute the new variable value. */ DECACHE_STACK_INFO(); if (TclObjTypeHasProc(valuePtr, setElementProc)) { objResultPtr = TclObjTypeSetElement(interp, valuePtr, numIndices, &OBJ_AT_DEPTH(numIndices), OBJ_AT_TOS); } else { objResultPtr = TclLsetFlat(interp, valuePtr, numIndices, &OBJ_AT_DEPTH(numIndices), OBJ_AT_TOS); } if (!objResultPtr) { CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } /* * Set result. */ CACHE_STACK_INFO(); TRACE_APPEND(("\"%.30s\"\n", O2S(objResultPtr))); NEXT_INST_V(5, numIndices+1, -1); case INST_LSET_LIST: /* 'lset' with 4 args */ /* * Get the old value of variable, and remove the stack ref. This is * safe because the variable still references the object; the ref * count will never go zero here - we can use the smaller macro * Tcl_DecrRefCount. */ objPtr = POP_OBJECT(); Tcl_DecrRefCount(objPtr); /* This one should be done here. */ /* * Get the new element value, and the index list. */ valuePtr = OBJ_AT_TOS; value2Ptr = OBJ_UNDER_TOS; TRACE(("\"%.30s\" \"%.30s\" \"%.30s\" => ", O2S(value2Ptr), O2S(valuePtr), O2S(objPtr))); /* * Compute the new variable value. */ objResultPtr = TclLsetList(interp, objPtr, value2Ptr, valuePtr); if (!objResultPtr) { TRACE_ERROR(interp); goto gotError; } /* * Set result. */ TRACE_APPEND(("\"%.30s\"\n", O2S(objResultPtr))); NEXT_INST_F(1, 2, -1); case INST_LIST_RANGE_IMM: /* lrange with objc==4 and both indices in * bytecode stream */ /* * Pop the list and get the indices. */ valuePtr = OBJ_AT_TOS; fromIdx = TclGetInt4AtPtr(pc+1); toIdx = TclGetInt4AtPtr(pc+5); TRACE(("\"%.30s\" %d %d => ", O2S(valuePtr), TclGetInt4AtPtr(pc+1), TclGetInt4AtPtr(pc+5))); /* * Get the length of the list, making sure that it really is a list * in the process. */ if (TclListObjLength(interp, valuePtr, &objc) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } /* * Skip a lot of work if we're about to throw the result away (common * with uses of [lassign]). */ #ifndef TCL_COMPILE_DEBUG if (pc[9] == INST_POP) { NEXT_INST_F(10, 1, 0); } #endif /* Every range of an empty list is an empty list */ if (objc == 0) { /* avoid return of not canonical list (e. g. spaces in string repr.) */ if (!valuePtr->bytes || !valuePtr->length) { TRACE_APPEND(("\n")); NEXT_INST_F(9, 0, 0); } goto emptyList; } /* Decode index value operands. */ if (toIdx == TCL_INDEX_NONE) { emptyList: TclNewObj(objResultPtr); TRACE_APPEND(("\"%.30s\"", O2S(objResultPtr))); NEXT_INST_F(9, 1, 1); } toIdx = TclIndexDecode(toIdx, objc - 1); if (toIdx == TCL_INDEX_NONE) { goto emptyList; } else if (toIdx >= objc) { toIdx = objc - 1; } assert (toIdx >= 0 && toIdx < objc); /* assert ( fromIdx != TCL_INDEX_NONE ); * * Extra safety for legacy bytecodes: */ if (fromIdx == TCL_INDEX_NONE) { fromIdx = TCL_INDEX_START; } fromIdx = TclIndexDecode(fromIdx, objc - 1); DECACHE_STACK_INFO(); if (TclObjTypeHasProc(valuePtr, sliceProc)) { if (TclObjTypeSlice(interp, valuePtr, fromIdx, toIdx, &objResultPtr) != TCL_OK) { objResultPtr = NULL; } } else { objResultPtr = TclListObjRange(interp, valuePtr, fromIdx, toIdx); } if (objResultPtr == NULL) { CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } CACHE_STACK_INFO(); TRACE_APPEND(("\"%.30s\"", O2S(objResultPtr))); NEXT_INST_F(9, 1, 1); case INST_LIST_IN: case INST_LIST_NOT_IN: /* Basic list containment operators. */ value2Ptr = OBJ_AT_TOS; valuePtr = OBJ_UNDER_TOS; s1 = TclGetStringFromObj(valuePtr, &s1len); TRACE(("\"%.30s\" \"%.30s\" => ", O2S(valuePtr), O2S(value2Ptr))); if (TclObjTypeHasProc(value2Ptr, inOperProc) != NULL) { int status = TclObjTypeInOperator(interp, valuePtr, value2Ptr, &match); if (status != TCL_OK) { TRACE_ERROR(interp); goto gotError; } } else { if (TclListObjLength(interp, value2Ptr, &length) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } match = 0; if (length > 0) { Tcl_Size i = 0; Tcl_Obj *o; int isAbstractList = TclObjTypeHasProc(value2Ptr, indexProc) != NULL; /* * An empty list doesn't match anything. */ do { if (isAbstractList) { DECACHE_STACK_INFO(); if (TclObjTypeIndex(interp, value2Ptr, i, &o) != TCL_OK) { CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } CACHE_STACK_INFO(); } else { Tcl_ListObjIndex(NULL, value2Ptr, i, &o); } if (o != NULL) { s2 = TclGetStringFromObj(o, &s2len); } else { s2 = ""; s2len = 0; } if (s1len == s2len) { match = (memcmp(s1, s2, s1len) == 0); } /* Could be an ephemeral abstract obj */ Tcl_BounceRefCount(o); i++; } while (i < length && match == 0); } } if (*pc == INST_LIST_NOT_IN) { match = !match; } TRACE_APPEND(("%d\n", match)); /* * Peep-hole optimisation: if you're about to jump, do jump from here. * We're saving the effort of pushing a boolean value only to pop it * for branching. */ JUMP_PEEPHOLE_F(match, 1, 2); case INST_LIST_CONCAT: value2Ptr = OBJ_AT_TOS; valuePtr = OBJ_UNDER_TOS; TRACE(("\"%.30s\" \"%.30s\" => ", O2S(valuePtr), O2S(value2Ptr))); if (Tcl_IsShared(valuePtr)) { objResultPtr = Tcl_DuplicateObj(valuePtr); if (Tcl_ListObjAppendList(interp, objResultPtr, value2Ptr) != TCL_OK) { TRACE_ERROR(interp); TclDecrRefCount(objResultPtr); goto gotError; } TRACE_APPEND(("\"%.30s\"\n", O2S(objResultPtr))); NEXT_INST_F(1, 2, 1); } else { if (Tcl_ListObjAppendList(interp, valuePtr, value2Ptr) != TCL_OK){ TRACE_ERROR(interp); goto gotError; } TRACE_APPEND(("\"%.30s\"\n", O2S(valuePtr))); NEXT_INST_F(1, 1, 0); } case INST_LREPLACE4: { size_t numToDelete, numNewElems; int end_indicator; int haveSecondIndex, flags; Tcl_Obj *fromIdxObj, *toIdxObj; opnd = TclGetInt4AtPtr(pc + 1); flags = TclGetInt1AtPtr(pc + 5); /* Stack: ... listobj index1 ?index2? new1 ... newN */ valuePtr = OBJ_AT_DEPTH(opnd-1); /* haveSecondIndex==0 => pure insert */ haveSecondIndex = (flags & TCL_LREPLACE4_SINGLE_INDEX) == 0; numNewElems = opnd - 2 - haveSecondIndex; /* end_indicator==1 => "end" is last element's index, 0=>index beyond */ end_indicator = (flags & TCL_LREPLACE4_END_IS_LAST) != 0; fromIdxObj = OBJ_AT_DEPTH(opnd - 2); toIdxObj = haveSecondIndex ? OBJ_AT_DEPTH(opnd - 3) : NULL; if (Tcl_ListObjLength(interp, valuePtr, &length) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } DECACHE_STACK_INFO(); if (TclGetIntForIndexM(interp, fromIdxObj, length - end_indicator, &fromIdx) != TCL_OK) { CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } if (fromIdx == TCL_INDEX_NONE) { fromIdx = 0; } else if (fromIdx > length) { fromIdx = length; } numToDelete = 0; if (toIdxObj) { if (TclGetIntForIndexM(interp, toIdxObj, length - end_indicator, &toIdx) != TCL_OK) { CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } if (toIdx != TCL_INDEX_NONE) { if (toIdx > length) { toIdx = length; } if (toIdx >= fromIdx) { numToDelete = (size_t)toIdx - (size_t)fromIdx + 1; } } } CACHE_STACK_INFO(); if (Tcl_IsShared(valuePtr)) { objResultPtr = Tcl_DuplicateObj(valuePtr); if (Tcl_ListObjReplace(interp, objResultPtr, fromIdx, numToDelete, numNewElems, &OBJ_AT_DEPTH(numNewElems - 1)) != TCL_OK) { TRACE_ERROR(interp); Tcl_DecrRefCount(objResultPtr); goto gotError; } TRACE_APPEND(("\"%.30s\"\n", O2S(objResultPtr))); NEXT_INST_V(6, opnd, 1); } else { if (Tcl_ListObjReplace(interp, valuePtr, fromIdx, numToDelete, numNewElems, &OBJ_AT_DEPTH(numNewElems - 1)) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } TRACE_APPEND(("\"%.30s\"\n", O2S(valuePtr))); NEXT_INST_V(6, opnd - 1, 0); } } /* * End of INST_LIST and related instructions. * ----------------------------------------------------------------- * Start of string-related instructions. */ case INST_STR_EQ: case INST_STR_NEQ: /* String (in)equality check */ case INST_STR_CMP: /* String compare. */ case INST_STR_LT: case INST_STR_GT: case INST_STR_LE: case INST_STR_GE: stringCompare: value2Ptr = OBJ_AT_TOS; valuePtr = OBJ_UNDER_TOS; { int checkEq = ((*pc == INST_EQ) || (*pc == INST_NEQ) || (*pc == INST_STR_EQ) || (*pc == INST_STR_NEQ)); match = TclStringCmp(valuePtr, value2Ptr, checkEq, 0, -1); } /* * Make sure only -1,0,1 is returned * TODO: consider peephole opt. */ if (*pc != INST_STR_CMP) { /* * Take care of the opcodes that goto'ed into here. */ switch (*pc) { case INST_STR_EQ: case INST_EQ: match = (match == 0); break; case INST_STR_NEQ: case INST_NEQ: match = (match != 0); break; case INST_LT: case INST_STR_LT: match = (match < 0); break; case INST_GT: case INST_STR_GT: match = (match > 0); break; case INST_LE: case INST_STR_LE: match = (match <= 0); break; case INST_GE: case INST_STR_GE: match = (match >= 0); break; } } TRACE(("\"%.20s\" \"%.20s\" => %d\n", O2S(valuePtr), O2S(value2Ptr), (match < 0 ? -1 : match > 0 ? 1 : 0))); JUMP_PEEPHOLE_F(match, 1, 2); case INST_STR_LEN: valuePtr = OBJ_AT_TOS; slength = Tcl_GetCharLength(valuePtr); TclNewIntObj(objResultPtr, slength); TRACE(("\"%.20s\" => %" TCL_Z_MODIFIER "u\n", O2S(valuePtr), slength)); NEXT_INST_F(1, 1, 1); case INST_STR_UPPER: valuePtr = OBJ_AT_TOS; TRACE(("\"%.20s\" => ", O2S(valuePtr))); if (Tcl_IsShared(valuePtr)) { s1 = TclGetStringFromObj(valuePtr, &slength); TclNewStringObj(objResultPtr, s1, slength); slength = Tcl_UtfToUpper(TclGetString(objResultPtr)); Tcl_SetObjLength(objResultPtr, slength); TRACE_APPEND(("\"%.20s\"\n", O2S(objResultPtr))); NEXT_INST_F(1, 1, 1); } else { slength = Tcl_UtfToUpper(TclGetString(valuePtr)); Tcl_SetObjLength(valuePtr, slength); TclFreeInternalRep(valuePtr); TRACE_APPEND(("\"%.20s\"\n", O2S(valuePtr))); NEXT_INST_F(1, 0, 0); } case INST_STR_LOWER: valuePtr = OBJ_AT_TOS; TRACE(("\"%.20s\" => ", O2S(valuePtr))); if (Tcl_IsShared(valuePtr)) { s1 = TclGetStringFromObj(valuePtr, &slength); TclNewStringObj(objResultPtr, s1, slength); slength = Tcl_UtfToLower(TclGetString(objResultPtr)); Tcl_SetObjLength(objResultPtr, slength); TRACE_APPEND(("\"%.20s\"\n", O2S(objResultPtr))); NEXT_INST_F(1, 1, 1); } else { slength = Tcl_UtfToLower(TclGetString(valuePtr)); Tcl_SetObjLength(valuePtr, slength); TclFreeInternalRep(valuePtr); TRACE_APPEND(("\"%.20s\"\n", O2S(valuePtr))); NEXT_INST_F(1, 0, 0); } case INST_STR_TITLE: valuePtr = OBJ_AT_TOS; TRACE(("\"%.20s\" => ", O2S(valuePtr))); if (Tcl_IsShared(valuePtr)) { s1 = TclGetStringFromObj(valuePtr, &slength); TclNewStringObj(objResultPtr, s1, slength); slength = Tcl_UtfToTitle(TclGetString(objResultPtr)); Tcl_SetObjLength(objResultPtr, slength); TRACE_APPEND(("\"%.20s\"\n", O2S(objResultPtr))); NEXT_INST_F(1, 1, 1); } else { slength = Tcl_UtfToTitle(TclGetString(valuePtr)); Tcl_SetObjLength(valuePtr, slength); TclFreeInternalRep(valuePtr); TRACE_APPEND(("\"%.20s\"\n", O2S(valuePtr))); NEXT_INST_F(1, 0, 0); } case INST_STR_INDEX: value2Ptr = OBJ_AT_TOS; valuePtr = OBJ_UNDER_TOS; TRACE(("\"%.20s\" %.20s => ", O2S(valuePtr), O2S(value2Ptr))); /* * Get char length to calculate what 'end' means. */ slength = Tcl_GetCharLength(valuePtr); DECACHE_STACK_INFO(); if (TclGetIntForIndexM(interp, value2Ptr, slength-1, &index)!=TCL_OK) { CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } CACHE_STACK_INFO(); if (index < 0 || index >= slength) { TclNewObj(objResultPtr); } else if (TclIsPureByteArray(valuePtr)) { objResultPtr = Tcl_NewByteArrayObj( Tcl_GetBytesFromObj(NULL, valuePtr, (Tcl_Size *)NULL)+index, 1); } else if (valuePtr->bytes && slength == valuePtr->length) { objResultPtr = Tcl_NewStringObj((const char *) valuePtr->bytes+index, 1); } else { char buf[4] = ""; int ch = Tcl_GetUniChar(valuePtr, index); /* * This could be: Tcl_NewUnicodeObj((const Tcl_UniChar *)&ch, 1) * but creating the object as a string seems to be faster in * practical use. */ if (ch == -1) { TclNewObj(objResultPtr); } else { slength = Tcl_UniCharToUtf(ch, buf); objResultPtr = Tcl_NewStringObj(buf, slength); } } TRACE_APPEND(("\"%s\"\n", O2S(objResultPtr))); NEXT_INST_F(1, 2, 1); case INST_STR_RANGE: TRACE(("\"%.20s\" %.20s %.20s =>", O2S(OBJ_AT_DEPTH(2)), O2S(OBJ_UNDER_TOS), O2S(OBJ_AT_TOS))); slength = Tcl_GetCharLength(OBJ_AT_DEPTH(2)) - 1; DECACHE_STACK_INFO(); if (TclGetIntForIndexM(interp, OBJ_UNDER_TOS, slength, &fromIdx) != TCL_OK) { CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } if (TclGetIntForIndexM(interp, OBJ_AT_TOS, slength, &toIdx) != TCL_OK) { CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } CACHE_STACK_INFO(); if (toIdx == TCL_INDEX_NONE) { TclNewObj(objResultPtr); } else { objResultPtr = Tcl_GetRange(OBJ_AT_DEPTH(2), fromIdx, toIdx); } TRACE_APPEND(("\"%.30s\"\n", O2S(objResultPtr))); NEXT_INST_V(1, 3, 1); case INST_STR_RANGE_IMM: valuePtr = OBJ_AT_TOS; fromIdx = TclGetInt4AtPtr(pc+1); toIdx = TclGetInt4AtPtr(pc+5); slength = Tcl_GetCharLength(valuePtr); TRACE(("\"%.20s\" %d %d => ", O2S(valuePtr), (int)(fromIdx), (int)(toIdx))); /* Every range of an empty value is an empty value */ if (slength == 0) { TRACE_APPEND(("\n")); NEXT_INST_F(9, 0, 0); } /* Decode index operands. */ toIdx = TclIndexDecode(toIdx, slength - 1); fromIdx = TclIndexDecode(fromIdx, slength - 1); if (toIdx == TCL_INDEX_NONE) { TclNewObj(objResultPtr); } else { objResultPtr = Tcl_GetRange(valuePtr, fromIdx, toIdx); } TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_F(9, 1, 1); { Tcl_UniChar *ustring1, *ustring2, *ustring3, *end, *p; Tcl_Size length3; Tcl_Obj *value3Ptr; case INST_STR_REPLACE: value3Ptr = POP_OBJECT(); valuePtr = OBJ_AT_DEPTH(2); slength = Tcl_GetCharLength(valuePtr) - 1; TRACE(("\"%.20s\" %s %s \"%.20s\" => ", O2S(valuePtr), O2S(OBJ_UNDER_TOS), O2S(OBJ_AT_TOS), O2S(value3Ptr))); DECACHE_STACK_INFO(); if (TclGetIntForIndexM(interp, OBJ_UNDER_TOS, slength, &fromIdx) != TCL_OK || TclGetIntForIndexM(interp, OBJ_AT_TOS, slength, &toIdx) != TCL_OK) { CACHE_STACK_INFO(); TclDecrRefCount(value3Ptr); TRACE_ERROR(interp); goto gotError; } CACHE_STACK_INFO(); TclDecrRefCount(OBJ_AT_TOS); (void) POP_OBJECT(); TclDecrRefCount(OBJ_AT_TOS); (void) POP_OBJECT(); if ((toIdx < 0) || (fromIdx > slength) || (toIdx < fromIdx)) { TRACE_APPEND(("\"%.30s\"\n", O2S(valuePtr))); TclDecrRefCount(value3Ptr); NEXT_INST_F(1, 0, 0); } if (fromIdx < 0) { fromIdx = 0; } if (toIdx > slength) { toIdx = slength; } if ((fromIdx == 0) && (toIdx == slength)) { TclDecrRefCount(OBJ_AT_TOS); OBJ_AT_TOS = value3Ptr; TRACE_APPEND(("\"%.30s\"\n", O2S(value3Ptr))); NEXT_INST_F(1, 0, 0); } objResultPtr = TclStringReplace(interp, valuePtr, fromIdx, toIdx - fromIdx + 1, value3Ptr, TCL_STRING_IN_PLACE); if (objResultPtr == value3Ptr) { /* See [Bug 82e7f67325] */ TclDecrRefCount(OBJ_AT_TOS); OBJ_AT_TOS = value3Ptr; TRACE_APPEND(("\"%.30s\"\n", O2S(value3Ptr))); NEXT_INST_F(1, 0, 0); } TclDecrRefCount(value3Ptr); TRACE_APPEND(("\"%.30s\"\n", O2S(objResultPtr))); NEXT_INST_F(1, 1, 1); case INST_STR_MAP: valuePtr = OBJ_AT_TOS; /* "Main" string. */ value3Ptr = OBJ_UNDER_TOS; /* "Target" string. */ value2Ptr = OBJ_AT_DEPTH(2); /* "Source" string. */ if (value3Ptr == value2Ptr) { objResultPtr = valuePtr; goto doneStringMap; } else if (valuePtr == value2Ptr) { objResultPtr = value3Ptr; goto doneStringMap; } ustring1 = Tcl_GetUnicodeFromObj(valuePtr, &slength); if (slength == 0) { objResultPtr = valuePtr; goto doneStringMap; } ustring2 = Tcl_GetUnicodeFromObj(value2Ptr, &length2); if (length2 > slength || length2 == 0) { objResultPtr = valuePtr; goto doneStringMap; } else if (length2 == slength) { if (memcmp(ustring1, ustring2, sizeof(Tcl_UniChar) * slength)) { objResultPtr = valuePtr; } else { objResultPtr = value3Ptr; } goto doneStringMap; } ustring3 = Tcl_GetUnicodeFromObj(value3Ptr, &length3); objResultPtr = Tcl_NewUnicodeObj(ustring1, 0); p = ustring1; end = ustring1 + slength; for (; ustring1 < end; ustring1++) { if ((*ustring1 == *ustring2) && /* Fix bug [69218ab7b]: restrict max compare length. */ ((end - ustring1) >= length2) && (length2 == 1 || memcmp(ustring1, ustring2, sizeof(Tcl_UniChar) * length2) == 0)) { if (p != ustring1) { Tcl_AppendUnicodeToObj(objResultPtr, p, ustring1-p); p = ustring1 + length2; } else { p += length2; } ustring1 = p - 1; Tcl_AppendUnicodeToObj(objResultPtr, ustring3, length3); } } if (p != ustring1) { /* * Put the rest of the unmapped chars onto result. */ Tcl_AppendUnicodeToObj(objResultPtr, p, ustring1 - p); } doneStringMap: TRACE_WITH_OBJ(("%.20s %.20s %.20s => ", O2S(value2Ptr), O2S(value3Ptr), O2S(valuePtr)), objResultPtr); NEXT_INST_V(1, 3, 1); case INST_STR_FIND: objResultPtr = TclStringFirst(OBJ_UNDER_TOS, OBJ_AT_TOS, 0); TRACE(("%.20s %.20s => %s\n", O2S(OBJ_UNDER_TOS), O2S(OBJ_AT_TOS), O2S(objResultPtr))); NEXT_INST_F(1, 2, 1); case INST_STR_FIND_LAST: objResultPtr = TclStringLast(OBJ_UNDER_TOS, OBJ_AT_TOS, TCL_SIZE_MAX - 1); TRACE(("%.20s %.20s => %s\n", O2S(OBJ_UNDER_TOS), O2S(OBJ_AT_TOS), O2S(objResultPtr))); NEXT_INST_F(1, 2, 1); case INST_STR_CLASS: opnd = TclGetInt1AtPtr(pc+1); valuePtr = OBJ_AT_TOS; TRACE(("%s \"%.30s\" => ", tclStringClassTable[opnd].name, O2S(valuePtr))); ustring1 = Tcl_GetUnicodeFromObj(valuePtr, &slength); match = 1; if (slength > 0) { int ch; end = ustring1 + slength; for (p=ustring1 ; p %d\n", O2S(valuePtr), O2S(value2Ptr), match)); /* * Peep-hole optimisation: if you're about to jump, do jump from here. */ JUMP_PEEPHOLE_F(match, 2, 2); { const char *string1, *string2; Tcl_Size trim1, trim2; case INST_STR_TRIM_LEFT: valuePtr = OBJ_UNDER_TOS; /* String */ value2Ptr = OBJ_AT_TOS; /* TrimSet */ string2 = TclGetStringFromObj(value2Ptr, &length2); string1 = TclGetStringFromObj(valuePtr, &slength); trim1 = TclTrimLeft(string1, slength, string2, length2); trim2 = 0; goto createTrimmedString; case INST_STR_TRIM_RIGHT: valuePtr = OBJ_UNDER_TOS; /* String */ value2Ptr = OBJ_AT_TOS; /* TrimSet */ string2 = TclGetStringFromObj(value2Ptr, &length2); string1 = TclGetStringFromObj(valuePtr, &slength); trim2 = TclTrimRight(string1, slength, string2, length2); trim1 = 0; goto createTrimmedString; case INST_STR_TRIM: valuePtr = OBJ_UNDER_TOS; /* String */ value2Ptr = OBJ_AT_TOS; /* TrimSet */ string2 = TclGetStringFromObj(value2Ptr, &length2); string1 = TclGetStringFromObj(valuePtr, &slength); trim1 = TclTrim(string1, slength, string2, length2, &trim2); createTrimmedString: /* * Careful here; trim set often contains non-ASCII characters so we * take care when printing. [Bug 971cb4f1db] */ #ifdef TCL_COMPILE_DEBUG if (traceInstructions) { TRACE(("\"%.30s\" ", O2S(valuePtr))); TclPrintObject(stdout, value2Ptr, 30); printf(" => "); } #endif if (trim1 == 0 && trim2 == 0) { #ifdef TCL_COMPILE_DEBUG if (traceInstructions) { TclPrintObject(stdout, valuePtr, 30); printf("\n"); } #endif NEXT_INST_F(1, 1, 0); } else { objResultPtr = Tcl_NewStringObj(string1+trim1, slength-trim1-trim2); #ifdef TCL_COMPILE_DEBUG if (traceInstructions) { TclPrintObject(stdout, objResultPtr, 30); printf("\n"); } #endif NEXT_INST_F(1, 2, 1); } } case INST_REGEXP: cflags = TclGetInt1AtPtr(pc+1); /* RE compile flages like NOCASE */ valuePtr = OBJ_AT_TOS; /* String */ value2Ptr = OBJ_UNDER_TOS; /* Pattern */ TRACE(("\"%.30s\" \"%.30s\" => ", O2S(valuePtr), O2S(value2Ptr))); /* * Compile and match the regular expression. */ { Tcl_RegExp regExpr = Tcl_GetRegExpFromObj(interp, value2Ptr, cflags); if (regExpr == NULL) { TRACE_ERROR(interp); goto gotError; } match = Tcl_RegExpExecObj(interp, regExpr, valuePtr, 0, 0, 0); if (match < 0) { TRACE_ERROR(interp); goto gotError; } } TRACE_APPEND(("%d\n", match)); /* * Peep-hole optimisation: if you're about to jump, do jump from here. * Adjustment is 2 due to the nocase byte. */ JUMP_PEEPHOLE_F(match, 2, 2); } /* * End of string-related instructions. * ----------------------------------------------------------------- * Start of numeric operator instructions. */ { void *ptr1, *ptr2; int type1, type2; Tcl_WideInt w1, w2, wResult; case INST_NUM_TYPE: if (GetNumberFromObj(NULL, OBJ_AT_TOS, &ptr1, &type1) != TCL_OK) { type1 = 0; } TclNewIntObj(objResultPtr, type1); TRACE(("\"%.20s\" => %d\n", O2S(OBJ_AT_TOS), type1)); NEXT_INST_F(1, 1, 1); case INST_EQ: case INST_NEQ: case INST_LT: case INST_GT: case INST_LE: case INST_GE: { int iResult = 0, compare = 0; value2Ptr = OBJ_AT_TOS; valuePtr = OBJ_UNDER_TOS; /* Try to determine, without triggering generation of a string representation, whether one value is not a number. */ if (TclCheckEmptyString(valuePtr) > 0 || TclCheckEmptyString(value2Ptr) > 0) { goto stringCompare; } if (GetNumberFromObj(NULL, valuePtr, &ptr1, &type1) != TCL_OK || GetNumberFromObj(NULL, value2Ptr, &ptr2, &type2) != TCL_OK) { /* * At least one non-numeric argument - compare as strings. */ goto stringCompare; } if (type1 == TCL_NUMBER_NAN || type2 == TCL_NUMBER_NAN) { /* * NaN arg: NaN != to everything, other compares are false. */ iResult = (*pc == INST_NEQ); goto foundResult; } if (valuePtr == value2Ptr) { compare = MP_EQ; goto convertComparison; } if ((type1 == TCL_NUMBER_INT) && (type2 == TCL_NUMBER_INT)) { w1 = *((const Tcl_WideInt *)ptr1); w2 = *((const Tcl_WideInt *)ptr2); compare = (w1 < w2) ? MP_LT : ((w1 > w2) ? MP_GT : MP_EQ); } else { compare = TclCompareTwoNumbers(valuePtr, value2Ptr); } /* * Turn comparison outcome into appropriate result for opcode. */ convertComparison: switch (*pc) { case INST_EQ: iResult = (compare == MP_EQ); break; case INST_NEQ: iResult = (compare != MP_EQ); break; case INST_LT: iResult = (compare == MP_LT); break; case INST_GT: iResult = (compare == MP_GT); break; case INST_LE: iResult = (compare != MP_GT); break; case INST_GE: iResult = (compare != MP_LT); break; } /* * Peep-hole optimisation: if you're about to jump, do jump from here. */ foundResult: TRACE(("\"%.20s\" \"%.20s\" => %d\n", O2S(valuePtr), O2S(value2Ptr), iResult)); JUMP_PEEPHOLE_F(iResult, 1, 2); } case INST_MOD: case INST_LSHIFT: case INST_RSHIFT: case INST_BITOR: case INST_BITXOR: case INST_BITAND: value2Ptr = OBJ_AT_TOS; valuePtr = OBJ_UNDER_TOS; if ((GetNumberFromObj(NULL, valuePtr, &ptr1, &type1) != TCL_OK) || (type1==TCL_NUMBER_DOUBLE) || (type1==TCL_NUMBER_NAN)) { TRACE(("%.20s %.20s => ILLEGAL 1st TYPE %s\n", O2S(valuePtr), O2S(value2Ptr), (valuePtr->typePtr? valuePtr->typePtr->name : "null"))); DECACHE_STACK_INFO(); IllegalExprOperandType(interp, "left ", pc, valuePtr); CACHE_STACK_INFO(); goto gotError; } if ((GetNumberFromObj(NULL, value2Ptr, &ptr2, &type2) != TCL_OK) || (type2==TCL_NUMBER_DOUBLE) || (type2==TCL_NUMBER_NAN)) { TRACE(("%.20s %.20s => ILLEGAL 2nd TYPE %s\n", O2S(valuePtr), O2S(value2Ptr), (value2Ptr->typePtr? value2Ptr->typePtr->name : "null"))); DECACHE_STACK_INFO(); IllegalExprOperandType(interp, "right ", pc, value2Ptr); CACHE_STACK_INFO(); goto gotError; } /* * Check for common, simple case. */ if ((type1 == TCL_NUMBER_INT) && (type2 == TCL_NUMBER_INT)) { w1 = *((const Tcl_WideInt *)ptr1); w2 = *((const Tcl_WideInt *)ptr2); switch (*pc) { case INST_MOD: if (w2 == 0) { TRACE(("%s %s => DIVIDE BY ZERO\n", O2S(valuePtr), O2S(value2Ptr))); goto divideByZero; } else if ((w2 == 1) || (w2 == -1)) { /* * Div. by |1| always yields remainder of 0. */ TRACE(("%s %s => ", O2S(valuePtr), O2S(value2Ptr))); objResultPtr = TCONST(0); TRACE(("%s\n", O2S(objResultPtr))); NEXT_INST_F(1, 2, 1); } else if (w1 == 0) { /* * 0 % (non-zero) always yields remainder of 0. */ TRACE(("%s %s => ", O2S(valuePtr), O2S(value2Ptr))); objResultPtr = TCONST(0); TRACE(("%s\n", O2S(objResultPtr))); NEXT_INST_F(1, 2, 1); } else { wResult = w1 / w2; /* * Force Tcl's integer division rules. * TODO: examine for logic simplification */ if ((wResult < 0 || (wResult == 0 && ((w1 < 0 && w2 > 0) || (w1 > 0 && w2 < 0)))) && (wResult * w2 != w1)) { wResult -= 1; } wResult = (Tcl_WideInt)((Tcl_WideUInt)w1 - (Tcl_WideUInt)w2*(Tcl_WideUInt)wResult); goto wideResultOfArithmetic; } break; case INST_RSHIFT: if (w2 < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "negative shift argument", -1)); #ifdef ERROR_CODE_FOR_EARLY_DETECTED_ARITH_ERROR DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "ARITH", "DOMAIN", "domain error: argument not in valid range", (char *)NULL); CACHE_STACK_INFO(); #endif /* ERROR_CODE_FOR_EARLY_DETECTED_ARITH_ERROR */ goto gotError; } else if (w1 == 0) { TRACE(("%s %s => ", O2S(valuePtr), O2S(value2Ptr))); objResultPtr = TCONST(0); TRACE(("%s\n", O2S(objResultPtr))); NEXT_INST_F(1, 2, 1); } else { /* * Quickly force large right shifts to 0 or -1. */ if (w2 >= (Tcl_WideInt)(CHAR_BIT*sizeof(w1))) { /* * We assume that INT_MAX is much larger than the * number of bits in a Tcl_WideInt. This is a pretty safe * assumption, given that the former is usually around * 4e9 and the latter 64... */ TRACE(("%s %s => ", O2S(valuePtr), O2S(value2Ptr))); if (w1 > 0L) { objResultPtr = TCONST(0); } else { TclNewIntObj(objResultPtr, -1); } TRACE(("%s\n", O2S(objResultPtr))); NEXT_INST_F(1, 2, 1); } /* * Handle shifts within the native Tcl_WideInt range. */ wResult = w1 >> ((int) w2); goto wideResultOfArithmetic; } break; case INST_LSHIFT: if (w2 < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "negative shift argument", -1)); #ifdef ERROR_CODE_FOR_EARLY_DETECTED_ARITH_ERROR DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "ARITH", "DOMAIN", "domain error: argument not in valid range", (char *)NULL); CACHE_STACK_INFO(); #endif /* ERROR_CODE_FOR_EARLY_DETECTED_ARITH_ERROR */ goto gotError; } else if (w1 == 0) { TRACE(("%s %s => ", O2S(valuePtr), O2S(value2Ptr))); objResultPtr = TCONST(0); TRACE(("%s\n", O2S(objResultPtr))); NEXT_INST_F(1, 2, 1); } else if (w2 > INT_MAX) { /* * Technically, we could hold the value (1 << (INT_MAX+1)) * in an mp_int, but since we're using mp_mul_2d() to do * the work, and it takes only an int argument, that's a * good place to draw the line. */ Tcl_SetObjResult(interp, Tcl_NewStringObj( "integer value too large to represent", -1)); #ifdef ERROR_CODE_FOR_EARLY_DETECTED_ARITH_ERROR DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "ARITH", "IOVERFLOW", "integer value too large to represent", (char *)NULL); CACHE_STACK_INFO(); #endif /* ERROR_CODE_FOR_EARLY_DETECTED_ARITH_ERROR */ goto gotError; } else { int shift = (int) w2; /* * Handle shifts within the native Tcl_WideInt range. */ if (((size_t)shift < CHAR_BIT*sizeof(w1)) && !((w1>0 ? w1 : ~w1) & -((Tcl_WideUInt)1<<(CHAR_BIT*sizeof(w1) - 1 - shift)))) { wResult = (Tcl_WideUInt)w1 << shift; goto wideResultOfArithmetic; } } /* * Too large; need to use the broken-out function. */ TRACE(("%s %s => ", O2S(valuePtr), O2S(value2Ptr))); break; case INST_BITAND: wResult = w1 & w2; goto wideResultOfArithmetic; case INST_BITOR: wResult = w1 | w2; goto wideResultOfArithmetic; case INST_BITXOR: wResult = w1 ^ w2; goto wideResultOfArithmetic; } } /* * DO NOT MERGE THIS WITH THE EQUIVALENT SECTION LATER! That would * encourage the compiler to inline ExecuteExtendedBinaryMathOp, which * is highly undesirable due to the overall impact on size. */ TRACE(("%s %s => ", O2S(valuePtr), O2S(value2Ptr))); objResultPtr = ExecuteExtendedBinaryMathOp(interp, *pc, &TCONST(0), valuePtr, value2Ptr); if (objResultPtr == DIVIDED_BY_ZERO) { TRACE_APPEND(("DIVIDE BY ZERO\n")); goto divideByZero; } else if (objResultPtr == GENERAL_ARITHMETIC_ERROR) { TRACE_ERROR(interp); goto gotError; } else if (objResultPtr == NULL) { TRACE_APPEND(("%s\n", O2S(valuePtr))); NEXT_INST_F(1, 1, 0); } else { TRACE_APPEND(("%s\n", O2S(objResultPtr))); NEXT_INST_F(1, 2, 1); } case INST_EXPON: case INST_ADD: case INST_SUB: case INST_DIV: case INST_MULT: value2Ptr = OBJ_AT_TOS; valuePtr = OBJ_UNDER_TOS; if ((GetNumberFromObj(NULL, valuePtr, &ptr1, &type1) != TCL_OK) || IsErroringNaNType(type1)) { TRACE(("%.20s %.20s => ILLEGAL 1st TYPE %s\n", O2S(value2Ptr), O2S(valuePtr), (valuePtr->typePtr? valuePtr->typePtr->name: "null"))); DECACHE_STACK_INFO(); IllegalExprOperandType(interp, "left ", pc, valuePtr); CACHE_STACK_INFO(); goto gotError; } #ifdef ACCEPT_NAN if (type1 == TCL_NUMBER_NAN) { /* * NaN first argument -> result is also NaN. */ NEXT_INST_F(1, 1, 0); } #endif if ((GetNumberFromObj(NULL, value2Ptr, &ptr2, &type2) != TCL_OK) || IsErroringNaNType(type2)) { TRACE(("%.20s %.20s => ILLEGAL 2nd TYPE %s\n", O2S(value2Ptr), O2S(valuePtr), (value2Ptr->typePtr? value2Ptr->typePtr->name: "null"))); DECACHE_STACK_INFO(); IllegalExprOperandType(interp, "right ", pc, value2Ptr); CACHE_STACK_INFO(); goto gotError; } #ifdef ACCEPT_NAN if (type2 == TCL_NUMBER_NAN) { /* * NaN second argument -> result is also NaN. */ objResultPtr = value2Ptr; NEXT_INST_F(1, 2, 1); } #endif /* * Handle Tcl_WideInt arithmetic as best we can without going out to * an external function. */ if ((type1 == TCL_NUMBER_INT) && (type2 == TCL_NUMBER_INT)) { w1 = *((const Tcl_WideInt *)ptr1); w2 = *((const Tcl_WideInt *)ptr2); switch (*pc) { case INST_ADD: wResult = (Tcl_WideInt)((Tcl_WideUInt)w1 + (Tcl_WideUInt)w2); /* * Check for overflow. */ if (Overflowing(w1, w2, wResult)) { goto overflow; } goto wideResultOfArithmetic; case INST_SUB: wResult = (Tcl_WideInt)((Tcl_WideUInt)w1 - (Tcl_WideUInt)w2); /* * Must check for overflow. The macro tests for overflows in * sums by looking at the sign bits. As we have a subtraction * here, we are adding -w2. As -w2 could in turn overflow, we * test with ~w2 instead: it has the opposite sign bit to w2 * so it does the job. Note that the only "bad" case (w2==0) * is irrelevant for this macro, as in that case w1 and * wResult have the same sign and there is no overflow anyway. */ if (Overflowing(w1, ~w2, wResult)) { goto overflow; } wideResultOfArithmetic: TRACE(("%s %s => ", O2S(valuePtr), O2S(value2Ptr))); if (Tcl_IsShared(valuePtr)) { TclNewIntObj(objResultPtr, wResult); TRACE(("%s\n", O2S(objResultPtr))); NEXT_INST_F(1, 2, 1); } TclSetIntObj(valuePtr, wResult); TRACE(("%s\n", O2S(valuePtr))); NEXT_INST_F(1, 1, 0); break; case INST_DIV: if (w2 == 0) { TRACE(("%s %s => DIVIDE BY ZERO\n", O2S(valuePtr), O2S(value2Ptr))); goto divideByZero; } else if ((w1 == WIDE_MIN) && (w2 == -1)) { /* * Can't represent (-WIDE_MIN) as a Tcl_WideInt. */ goto overflow; } wResult = w1 / w2; /* * Force Tcl's integer division rules. * TODO: examine for logic simplification */ if (((wResult < 0) || ((wResult == 0) && ((w1 < 0 && w2 > 0) || (w1 > 0 && w2 < 0)))) && ((wResult * w2) != w1)) { wResult -= 1; } goto wideResultOfArithmetic; case INST_MULT: if (((sizeof(Tcl_WideInt) >= 2*sizeof(int)) && (w1 <= INT_MAX) && (w1 >= INT_MIN) && (w2 <= INT_MAX) && (w2 >= INT_MIN)) || ((sizeof(Tcl_WideInt) >= 2*sizeof(short)) && (w1 <= SHRT_MAX) && (w1 >= SHRT_MIN) && (w2 <= SHRT_MAX) && (w2 >= SHRT_MIN))) { wResult = w1 * w2; goto wideResultOfArithmetic; } } /* * Fall through with INST_EXPON, INST_DIV and large multiplies. */ } overflow: TRACE(("%s %s => ", O2S(valuePtr), O2S(value2Ptr))); objResultPtr = ExecuteExtendedBinaryMathOp(interp, *pc, &TCONST(0), valuePtr, value2Ptr); if (objResultPtr == DIVIDED_BY_ZERO) { TRACE_APPEND(("DIVIDE BY ZERO\n")); goto divideByZero; } else if (objResultPtr == EXPONENT_OF_ZERO) { TRACE_APPEND(("EXPONENT OF ZERO\n")); goto exponOfZero; } else if (objResultPtr == GENERAL_ARITHMETIC_ERROR) { TRACE_ERROR(interp); goto gotError; } else if (objResultPtr == OUT_OF_MEMORY) { TRACE_APPEND(("OUT OF MEMORY\n")); goto outOfMemory; } else if (objResultPtr == NULL) { TRACE_APPEND(("%s\n", O2S(valuePtr))); NEXT_INST_F(1, 1, 0); } else { TRACE_APPEND(("%s\n", O2S(objResultPtr))); NEXT_INST_F(1, 2, 1); } case INST_LNOT: { int b; valuePtr = OBJ_AT_TOS; /* TODO - check claim that taking address of b harms performance */ /* TODO - consider optimization search for constants */ if (TclGetBooleanFromObj(NULL, valuePtr, &b) != TCL_OK) { TRACE(("\"%.20s\" => ERROR: illegal type %s\n", O2S(valuePtr), (valuePtr->typePtr? valuePtr->typePtr->name : "null"))); DECACHE_STACK_INFO(); IllegalExprOperandType(interp, "", pc, valuePtr); CACHE_STACK_INFO(); goto gotError; } /* TODO: Consider peephole opt. */ objResultPtr = TCONST(!b); TRACE_WITH_OBJ(("%s => ", O2S(valuePtr)), objResultPtr); NEXT_INST_F(1, 1, 1); } case INST_BITNOT: valuePtr = OBJ_AT_TOS; TRACE(("\"%.20s\" => ", O2S(valuePtr))); if ((GetNumberFromObj(NULL, valuePtr, &ptr1, &type1) != TCL_OK) || (type1==TCL_NUMBER_NAN) || (type1==TCL_NUMBER_DOUBLE)) { /* * ... ~$NonInteger => raise an error. */ TRACE_APPEND(("ERROR: illegal type %s\n", (valuePtr->typePtr? valuePtr->typePtr->name : "null"))); DECACHE_STACK_INFO(); IllegalExprOperandType(interp, "", pc, valuePtr); CACHE_STACK_INFO(); goto gotError; } if (type1 == TCL_NUMBER_INT) { w1 = *((const Tcl_WideInt *) ptr1); if (Tcl_IsShared(valuePtr)) { TclNewIntObj(objResultPtr, ~w1); TRACE_APPEND(("%s\n", O2S(objResultPtr))); NEXT_INST_F(1, 1, 1); } TclSetIntObj(valuePtr, ~w1); TRACE_APPEND(("%s\n", O2S(valuePtr))); NEXT_INST_F(1, 0, 0); } objResultPtr = ExecuteExtendedUnaryMathOp(*pc, valuePtr); if (objResultPtr != NULL) { TRACE_APPEND(("%s\n", O2S(objResultPtr))); NEXT_INST_F(1, 1, 1); } else { TRACE_APPEND(("%s\n", O2S(valuePtr))); NEXT_INST_F(1, 0, 0); } case INST_UMINUS: valuePtr = OBJ_AT_TOS; TRACE(("\"%.20s\" => ", O2S(valuePtr))); if ((GetNumberFromObj(NULL, valuePtr, &ptr1, &type1) != TCL_OK) || IsErroringNaNType(type1)) { TRACE_APPEND(("ERROR: illegal type %s \n", (valuePtr->typePtr? valuePtr->typePtr->name : "null"))); DECACHE_STACK_INFO(); IllegalExprOperandType(interp, "", pc, valuePtr); CACHE_STACK_INFO(); goto gotError; } switch (type1) { case TCL_NUMBER_NAN: /* -NaN => NaN */ TRACE_APPEND(("%s\n", O2S(valuePtr))); NEXT_INST_F(1, 0, 0); break; case TCL_NUMBER_INT: w1 = *((const Tcl_WideInt *) ptr1); if (w1 != WIDE_MIN) { if (Tcl_IsShared(valuePtr)) { TclNewIntObj(objResultPtr, -w1); TRACE_APPEND(("%s\n", O2S(objResultPtr))); NEXT_INST_F(1, 1, 1); } TclSetIntObj(valuePtr, -w1); TRACE_APPEND(("%s\n", O2S(valuePtr))); NEXT_INST_F(1, 0, 0); } /* FALLTHROUGH */ } objResultPtr = ExecuteExtendedUnaryMathOp(*pc, valuePtr); if (objResultPtr != NULL) { TRACE_APPEND(("%s\n", O2S(objResultPtr))); NEXT_INST_F(1, 1, 1); } else { TRACE_APPEND(("%s\n", O2S(valuePtr))); NEXT_INST_F(1, 0, 0); } case INST_UPLUS: case INST_TRY_CVT_TO_NUMERIC: /* * Try to convert the topmost stack object to numeric object. This is * done in order to support [expr]'s policy of interpreting operands * if at all possible as numbers first, then strings. */ valuePtr = OBJ_AT_TOS; TRACE(("\"%.20s\" => ", O2S(valuePtr))); if (GetNumberFromObj(NULL, valuePtr, &ptr1, &type1) != TCL_OK) { if (*pc == INST_UPLUS) { /* * ... +$NonNumeric => raise an error. */ TRACE_APPEND(("ERROR: illegal type %s\n", (valuePtr->typePtr? valuePtr->typePtr->name:"null"))); DECACHE_STACK_INFO(); IllegalExprOperandType(interp, "", pc, valuePtr); CACHE_STACK_INFO(); goto gotError; } /* ... TryConvertToNumeric($NonNumeric) is acceptable */ TRACE_APPEND(("not numeric\n")); NEXT_INST_F(1, 0, 0); } if (IsErroringNaNType(type1)) { if (*pc == INST_UPLUS) { /* * ... +$NonNumeric => raise an error. */ TRACE_APPEND(("ERROR: illegal type %s\n", (valuePtr->typePtr? valuePtr->typePtr->name:"null"))); DECACHE_STACK_INFO(); IllegalExprOperandType(interp, "", pc, valuePtr); CACHE_STACK_INFO(); } else { /* * Numeric conversion of NaN -> error. */ TRACE_APPEND(("ERROR: IEEE floating pt error\n")); DECACHE_STACK_INFO(); TclExprFloatError(interp, *((const double *) ptr1)); CACHE_STACK_INFO(); } goto gotError; } /* * Ensure that the numeric value has a string rep the same as the * formatted version of its internal rep. This is used, e.g., to make * sure that "expr {0001}" yields "1", not "0001". We implement this * by _discarding_ the string rep since we know it will be * regenerated, if needed later, by formatting the internal rep's * value. */ if (valuePtr->bytes == NULL) { TRACE_APPEND(("numeric, same Tcl_Obj\n")); NEXT_INST_F(1, 0, 0); } if (Tcl_IsShared(valuePtr)) { /* * Here we do some surgery within the Tcl_Obj internals. We want * to copy the internalrep, but not the string, so we temporarily hide * the string so we do not copy it. */ char *savedString = valuePtr->bytes; valuePtr->bytes = NULL; objResultPtr = Tcl_DuplicateObj(valuePtr); valuePtr->bytes = savedString; TRACE_APPEND(("numeric, new Tcl_Obj\n")); NEXT_INST_F(1, 1, 1); } TclInvalidateStringRep(valuePtr); TRACE_APPEND(("numeric, same Tcl_Obj\n")); NEXT_INST_F(1, 0, 0); } break; /* * End of numeric operator instructions. * ----------------------------------------------------------------- */ case INST_TRY_CVT_TO_BOOLEAN: valuePtr = OBJ_AT_TOS; if (TclHasInternalRep(valuePtr, &tclBooleanType)) { objResultPtr = TCONST(1); } else { int res = (TclSetBooleanFromAny(NULL, valuePtr) == TCL_OK); objResultPtr = TCONST(res); } TRACE_WITH_OBJ(("\"%.30s\" => ", O2S(valuePtr)), objResultPtr); NEXT_INST_F(1, 0, 1); break; case INST_BREAK: /* DECACHE_STACK_INFO(); Tcl_ResetResult(interp); CACHE_STACK_INFO(); */ result = TCL_BREAK; cleanup = 0; TRACE(("=> BREAK!\n")); goto processExceptionReturn; case INST_CONTINUE: /* DECACHE_STACK_INFO(); Tcl_ResetResult(interp); CACHE_STACK_INFO(); */ result = TCL_CONTINUE; cleanup = 0; TRACE(("=> CONTINUE!\n")); goto processExceptionReturn; { ForeachInfo *infoPtr; Tcl_Obj *listPtr, **elements; ForeachVarList *varListPtr; Tcl_Size numLists, listLen, numVars, listTmpDepth; Tcl_Size iterNum, iterMax, iterTmp; Tcl_Size varIndex, valIndex, i, j; case INST_FOREACH_START: /* * Initialize the data for the looping construct, pushing the * corresponding Tcl_Objs to the stack. */ opnd = TclGetUInt4AtPtr(pc+1); infoPtr = (ForeachInfo *)codePtr->auxDataArrayPtr[opnd].clientData; numLists = infoPtr->numLists; TRACE(("%u => ", opnd)); /* * Compute the number of iterations that will be run: iterMax */ iterMax = 0; listTmpDepth = numLists-1; for (i = 0; i < numLists; i++) { varListPtr = infoPtr->varLists[i]; numVars = varListPtr->numVars; listPtr = OBJ_AT_DEPTH(listTmpDepth); DECACHE_STACK_INFO(); if (TclListObjLength(interp, listPtr, &listLen) != TCL_OK) { CACHE_STACK_INFO(); TRACE_APPEND(("ERROR converting list %" TCL_Z_MODIFIER "d, \"%s\": %s", i, O2S(listPtr), O2S(Tcl_GetObjResult(interp)))); goto gotError; } if (Tcl_IsShared(listPtr)) { objPtr = TclListObjCopy(NULL, listPtr); Tcl_IncrRefCount(objPtr); Tcl_DecrRefCount(listPtr); OBJ_AT_DEPTH(listTmpDepth) = objPtr; } iterTmp = (listLen + (numVars - 1))/numVars; if (iterTmp > iterMax) { iterMax = iterTmp; } listTmpDepth--; } /* * Store the iterNum and iterMax in a single Tcl_Obj; we keep a * nul-string obj with the pointer stored in the ptrValue so that the * thing is properly garbage collected. THIS OBJ MAKES NO SENSE, but * it will never leave this scope and is read-only. */ TclNewObj(tmpPtr); tmpPtr->internalRep.twoPtrValue.ptr1 = NULL; tmpPtr->internalRep.twoPtrValue.ptr2 = (void *)iterMax; PUSH_OBJECT(tmpPtr); /* iterCounts object */ /* * Store a pointer to the ForeachInfo struct; same dirty trick * as above */ TclNewObj(tmpPtr); tmpPtr->internalRep.twoPtrValue.ptr1 = infoPtr; PUSH_OBJECT(tmpPtr); /* infoPtr object */ TRACE_APPEND(("jump to loop step\n")); /* * Jump directly to the INST_FOREACH_STEP instruction; the C code just * falls through. */ pc += 5 - infoPtr->loopCtTemp; case INST_FOREACH_STEP: /* TODO: address abstract list indexing here! */ /* * "Step" a foreach loop (i.e., begin its next iteration) by assigning * the next value list element to each loop var. */ tmpPtr = OBJ_AT_TOS; infoPtr = (ForeachInfo *)tmpPtr->internalRep.twoPtrValue.ptr1; numLists = infoPtr->numLists; TRACE(("=> ")); tmpPtr = OBJ_AT_DEPTH(1); iterNum = (size_t)tmpPtr->internalRep.twoPtrValue.ptr1; iterMax = (size_t)tmpPtr->internalRep.twoPtrValue.ptr2; /* * If some list still has a remaining list element iterate one more * time. Assign to var the next element from its value list. */ if (iterNum < iterMax) { int status; /* * Set the variables and jump back to run the body */ tmpPtr->internalRep.twoPtrValue.ptr1 =(void *)(iterNum + 1); listTmpDepth = numLists + 1; for (i = 0; i < numLists; i++) { varListPtr = infoPtr->varLists[i]; numVars = varListPtr->numVars; int hasAbstractList; listPtr = OBJ_AT_DEPTH(listTmpDepth); hasAbstractList = TclObjTypeHasProc(listPtr, indexProc) != 0; DECACHE_STACK_INFO(); if (hasAbstractList) { status = Tcl_ListObjLength(interp, listPtr, &listLen); elements = NULL; } else { status = TclListObjGetElements( interp, listPtr, &listLen, &elements); } if (status != TCL_OK) { CACHE_STACK_INFO(); goto gotError; } CACHE_STACK_INFO(); valIndex = (iterNum * numVars); for (j = 0; j < numVars; j++) { if (valIndex >= listLen) { TclNewObj(valuePtr); } else { DECACHE_STACK_INFO(); if (elements) { valuePtr = elements[valIndex]; } else { status = Tcl_ListObjIndex( interp, listPtr, valIndex, &valuePtr); if (status != TCL_OK) { /* Could happen for abstract lists */ CACHE_STACK_INFO(); goto gotError; } if (valuePtr == NULL) { /* Permitted for Tcl_LOI to return NULL */ TclNewObj(valuePtr); } } CACHE_STACK_INFO(); } varIndex = varListPtr->varIndexes[j]; varPtr = LOCAL(varIndex); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } if (TclIsVarDirectWritable(varPtr)) { value2Ptr = varPtr->value.objPtr; if (valuePtr != value2Ptr) { if (value2Ptr != NULL) { TclDecrRefCount(value2Ptr); } varPtr->value.objPtr = valuePtr; Tcl_IncrRefCount(valuePtr); } } else { DECACHE_STACK_INFO(); if (TclPtrSetVarIdx(interp, varPtr, NULL, NULL, NULL, valuePtr, TCL_LEAVE_ERR_MSG, varIndex)==NULL){ CACHE_STACK_INFO(); TRACE_APPEND(("ERROR init. index temp %" TCL_SIZE_MODIFIER "d: %.30s", varIndex, O2S(Tcl_GetObjResult(interp)))); goto gotError; } CACHE_STACK_INFO(); } valIndex++; } listTmpDepth--; } TRACE_APPEND(("jump to loop start\n")); /* loopCtTemp being 'misused' for storing the jump size */ NEXT_INST_F(infoPtr->loopCtTemp, 0, 0); } TRACE_APPEND(("loop has no more iterations\n")); #ifdef TCL_COMPILE_DEBUG NEXT_INST_F(1, 0, 0); #else /* * FALL THROUGH */ pc++; #endif case INST_FOREACH_END: /* THIS INSTRUCTION IS ONLY CALLED AS A BREAK TARGET */ tmpPtr = OBJ_AT_TOS; infoPtr = (ForeachInfo *)tmpPtr->internalRep.twoPtrValue.ptr1; numLists = infoPtr->numLists; TRACE(("=> loop terminated\n")); NEXT_INST_V(1, numLists+2, 0); case INST_LMAP_COLLECT: /* * This instruction is only issued by lmap. The stack is: * - result * - infoPtr * - loop counters * - valLists * - collecting obj (unshared) * The instruction lappends the result to the collecting obj. */ tmpPtr = OBJ_AT_DEPTH(1); infoPtr = (ForeachInfo *)tmpPtr->internalRep.twoPtrValue.ptr1; numLists = infoPtr->numLists; TRACE_APPEND(("=> appending to list at depth %" TCL_SIZE_MODIFIER "d\n", 3 + numLists)); objPtr = OBJ_AT_DEPTH(3 + numLists); Tcl_ListObjAppendElement(NULL, objPtr, OBJ_AT_TOS); NEXT_INST_F(1, 1, 0); } break; case INST_BEGIN_CATCH4: /* * Record start of the catch command with exception range index equal * to the operand. Push the current stack depth onto the special catch * stack. */ *(++catchTop) = (Tcl_Obj *)INT2PTR(CURR_DEPTH); TRACE(("%u => catchTop=%" TCL_T_MODIFIER "d, stackTop=%" TCL_T_MODIFIER "d\n", TclGetUInt4AtPtr(pc+1), (catchTop - initCatchTop - 1), CURR_DEPTH)); NEXT_INST_F(5, 0, 0); break; case INST_END_CATCH: catchTop--; DECACHE_STACK_INFO(); Tcl_ResetResult(interp); CACHE_STACK_INFO(); result = TCL_OK; TRACE(("=> catchTop=%" TCL_Z_MODIFIER "u\n", (size_t)(catchTop - initCatchTop - 1))); NEXT_INST_F(1, 0, 0); break; case INST_PUSH_RESULT: objResultPtr = Tcl_GetObjResult(interp); TRACE_WITH_OBJ(("=> "), objResultPtr); /* * See the comments at INST_INVOKE_STK */ TclNewObj(objPtr); Tcl_IncrRefCount(objPtr); iPtr->objResultPtr = objPtr; NEXT_INST_F(1, 0, -1); break; case INST_PUSH_RETURN_CODE: TclNewIntObj(objResultPtr, result); TRACE(("=> %u\n", result)); NEXT_INST_F(1, 0, 1); break; case INST_PUSH_RETURN_OPTIONS: DECACHE_STACK_INFO(); objResultPtr = Tcl_GetReturnOptions(interp, result); CACHE_STACK_INFO(); TRACE_WITH_OBJ(("=> "), objResultPtr); NEXT_INST_F(1, 0, 1); break; case INST_RETURN_CODE_BRANCH: { int code; if (TclGetIntFromObj(NULL, OBJ_AT_TOS, &code) != TCL_OK) { Tcl_Panic("INST_RETURN_CODE_BRANCH: TOS not a return code!"); } if (code == TCL_OK) { Tcl_Panic("INST_RETURN_CODE_BRANCH: TOS is TCL_OK!"); } if (code < TCL_ERROR || code > TCL_CONTINUE) { code = TCL_CONTINUE + 1; } TRACE(("\"%s\" => jump offset %d\n", O2S(OBJ_AT_TOS), 2*code-1)); NEXT_INST_F(2*code-1, 1, 0); } /* * ----------------------------------------------------------------- * Start of dictionary-related instructions. */ { int opnd2, allocateDict, done, allocdict; Tcl_Size i; Tcl_Obj *dictPtr, *statePtr, *keyPtr, *listPtr, *varNamePtr, *keysPtr; Tcl_Obj *emptyPtr, **keyPtrPtr; Tcl_DictSearch *searchPtr; DictUpdateInfo *duiPtr; case INST_DICT_VERIFY: { Tcl_Size size; dictPtr = OBJ_AT_TOS; TRACE(("\"%.30s\" => ", O2S(dictPtr))); if (Tcl_DictObjSize(interp, dictPtr, &size) != TCL_OK) { TRACE_APPEND(("ERROR verifying dictionary nature of \"%.30s\": %s\n", O2S(dictPtr), O2S(Tcl_GetObjResult(interp)))); goto gotError; } TRACE_APPEND(("OK\n")); NEXT_INST_F(1, 1, 0); } break; case INST_DICT_EXISTS: { int found; opnd = TclGetUInt4AtPtr(pc+1); TRACE(("%u => ", opnd)); dictPtr = OBJ_AT_DEPTH(opnd); if (opnd > 1) { dictPtr = TclTraceDictPath(NULL, dictPtr, opnd-1, &OBJ_AT_DEPTH(opnd-1), DICT_PATH_EXISTS); if (dictPtr == NULL || dictPtr == DICT_PATH_NON_EXISTENT) { found = 0; goto afterDictExists; } } if (Tcl_DictObjGet(NULL, dictPtr, OBJ_AT_TOS, &objResultPtr) == TCL_OK) { found = (objResultPtr ? 1 : 0); } else { found = 0; } afterDictExists: TRACE_APPEND(("%d\n", found)); /* * The INST_DICT_EXISTS instruction is usually followed by a * conditional jump, so we can take advantage of this to do some * peephole optimization (note that we're careful to not close out * someone doing something else). */ JUMP_PEEPHOLE_V(found, 5, opnd+1); } case INST_DICT_GET: opnd = TclGetUInt4AtPtr(pc+1); TRACE(("%u => ", opnd)); dictPtr = OBJ_AT_DEPTH(opnd); if (opnd > 1) { dictPtr = TclTraceDictPath(interp, dictPtr, opnd-1, &OBJ_AT_DEPTH(opnd-1), DICT_PATH_READ); if (dictPtr == NULL) { TRACE_WITH_OBJ(( "ERROR tracing dictionary path into \"%.30s\": ", O2S(OBJ_AT_DEPTH(opnd))), Tcl_GetObjResult(interp)); goto gotError; } } if (Tcl_DictObjGet(interp, dictPtr, OBJ_AT_TOS, &objResultPtr) != TCL_OK) { TRACE_APPEND(("ERROR reading leaf dictionary key \"%.30s\": %s", O2S(dictPtr), O2S(Tcl_GetObjResult(interp)))); goto gotError; } if (!objResultPtr) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "key \"%s\" not known in dictionary", TclGetString(OBJ_AT_TOS))); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "DICT", TclGetString(OBJ_AT_TOS), (char *)NULL); CACHE_STACK_INFO(); TRACE_ERROR(interp); goto gotError; } TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_V(5, opnd+1, 1); case INST_DICT_GET_DEF: opnd = TclGetUInt4AtPtr(pc+1); TRACE(("%u => ", opnd)); dictPtr = OBJ_AT_DEPTH(opnd+1); if (opnd > 1) { dictPtr = TclTraceDictPath(interp, dictPtr, opnd-1, &OBJ_AT_DEPTH(opnd), DICT_PATH_EXISTS); if (dictPtr == NULL) { TRACE_WITH_OBJ(( "ERROR tracing dictionary path into \"%.30s\": ", O2S(OBJ_AT_DEPTH(opnd+1))), Tcl_GetObjResult(interp)); goto gotError; } else if (dictPtr == DICT_PATH_NON_EXISTENT) { goto dictGetDefUseDefault; } } if (Tcl_DictObjGet(interp, dictPtr, OBJ_UNDER_TOS, &objResultPtr) != TCL_OK) { TRACE_APPEND(("ERROR reading leaf dictionary key \"%.30s\": %s", O2S(dictPtr), O2S(Tcl_GetObjResult(interp)))); goto gotError; } else if (!objResultPtr) { dictGetDefUseDefault: objResultPtr = OBJ_AT_TOS; } TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_V(5, opnd+2, 1); case INST_DICT_SET: case INST_DICT_UNSET: case INST_DICT_INCR_IMM: opnd = TclGetUInt4AtPtr(pc+1); opnd2 = TclGetUInt4AtPtr(pc+5); varPtr = LOCAL(opnd2); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } TRACE(("%u %u => ", opnd, opnd2)); if (TclIsVarDirectReadable(varPtr)) { dictPtr = varPtr->value.objPtr; } else { DECACHE_STACK_INFO(); dictPtr = TclPtrGetVarIdx(interp, varPtr, NULL, NULL, NULL, 0, opnd2); CACHE_STACK_INFO(); } if (dictPtr == NULL) { TclNewObj(dictPtr); allocateDict = 1; } else { allocateDict = Tcl_IsShared(dictPtr); if (allocateDict) { dictPtr = Tcl_DuplicateObj(dictPtr); } } switch (*pc) { case INST_DICT_SET: cleanup = opnd + 1; result = Tcl_DictObjPutKeyList(interp, dictPtr, opnd, &OBJ_AT_DEPTH(opnd), OBJ_AT_TOS); break; case INST_DICT_INCR_IMM: cleanup = 1; opnd = TclGetInt4AtPtr(pc+1); result = Tcl_DictObjGet(interp, dictPtr, OBJ_AT_TOS, &valuePtr); if (result != TCL_OK) { break; } if (valuePtr == NULL) { Tcl_DictObjPut(NULL, dictPtr, OBJ_AT_TOS, Tcl_NewWideIntObj(opnd)); } else { TclNewIntObj(value2Ptr, opnd); Tcl_IncrRefCount(value2Ptr); if (Tcl_IsShared(valuePtr)) { valuePtr = Tcl_DuplicateObj(valuePtr); Tcl_DictObjPut(NULL, dictPtr, OBJ_AT_TOS, valuePtr); } result = TclIncrObj(interp, valuePtr, value2Ptr); if (result == TCL_OK) { TclInvalidateStringRep(dictPtr); } TclDecrRefCount(value2Ptr); } break; case INST_DICT_UNSET: cleanup = opnd; result = Tcl_DictObjRemoveKeyList(interp, dictPtr, opnd, &OBJ_AT_DEPTH(opnd-1)); break; default: cleanup = 0; /* stop compiler warning */ Tcl_Panic("Should not happen!"); } if (result != TCL_OK) { if (allocateDict) { TclDecrRefCount(dictPtr); } TRACE_APPEND(("ERROR updating dictionary: %s\n", O2S(Tcl_GetObjResult(interp)))); goto checkForCatch; } if (TclIsVarDirectWritable(varPtr)) { if (allocateDict) { value2Ptr = varPtr->value.objPtr; Tcl_IncrRefCount(dictPtr); if (value2Ptr != NULL) { TclDecrRefCount(value2Ptr); } varPtr->value.objPtr = dictPtr; } objResultPtr = dictPtr; } else { Tcl_IncrRefCount(dictPtr); DECACHE_STACK_INFO(); objResultPtr = TclPtrSetVarIdx(interp, varPtr, NULL, NULL, NULL, dictPtr, TCL_LEAVE_ERR_MSG, opnd2); CACHE_STACK_INFO(); TclDecrRefCount(dictPtr); if (objResultPtr == NULL) { TRACE_ERROR(interp); goto gotError; } } #ifndef TCL_COMPILE_DEBUG if (pc[9] == INST_POP) { NEXT_INST_V(10, cleanup, 0); } #endif TRACE_APPEND(("\"%.30s\"\n", O2S(objResultPtr))); NEXT_INST_V(9, cleanup, 1); case INST_DICT_APPEND: case INST_DICT_LAPPEND: opnd = TclGetUInt4AtPtr(pc+1); varPtr = LOCAL(opnd); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } TRACE(("%u => ", opnd)); if (TclIsVarDirectReadable(varPtr)) { dictPtr = varPtr->value.objPtr; } else { DECACHE_STACK_INFO(); dictPtr = TclPtrGetVarIdx(interp, varPtr, NULL, NULL, NULL, 0, opnd); CACHE_STACK_INFO(); } if (dictPtr == NULL) { TclNewObj(dictPtr); allocateDict = 1; } else { allocateDict = Tcl_IsShared(dictPtr); if (allocateDict) { dictPtr = Tcl_DuplicateObj(dictPtr); } } if (Tcl_DictObjGet(interp, dictPtr, OBJ_UNDER_TOS, &valuePtr) != TCL_OK) { if (allocateDict) { TclDecrRefCount(dictPtr); } TRACE_ERROR(interp); goto gotError; } /* * Note that a non-existent key results in a NULL valuePtr, which is a * case handled separately below. What we *can* say at this point is * that the write-back will always succeed. */ switch (*pc) { case INST_DICT_APPEND: if (valuePtr == NULL) { Tcl_DictObjPut(NULL, dictPtr, OBJ_UNDER_TOS, OBJ_AT_TOS); } else if (Tcl_IsShared(valuePtr)) { valuePtr = Tcl_DuplicateObj(valuePtr); Tcl_AppendObjToObj(valuePtr, OBJ_AT_TOS); Tcl_DictObjPut(NULL, dictPtr, OBJ_UNDER_TOS, valuePtr); } else { Tcl_AppendObjToObj(valuePtr, OBJ_AT_TOS); /* * Must invalidate the string representation of dictionary * here because we have directly updated the internal * representation; if we don't, callers could see the wrong * string rep despite the internal version of the dictionary * having the correct value. [Bug 3079830] */ TclInvalidateStringRep(dictPtr); } break; case INST_DICT_LAPPEND: /* * More complex because list-append can fail. */ if (valuePtr == NULL) { Tcl_DictObjPut(NULL, dictPtr, OBJ_UNDER_TOS, Tcl_NewListObj(1, &OBJ_AT_TOS)); break; } else if (Tcl_IsShared(valuePtr)) { valuePtr = Tcl_DuplicateObj(valuePtr); if (Tcl_ListObjAppendElement(interp, valuePtr, OBJ_AT_TOS) != TCL_OK) { TclDecrRefCount(valuePtr); if (allocateDict) { TclDecrRefCount(dictPtr); } TRACE_ERROR(interp); goto gotError; } Tcl_DictObjPut(NULL, dictPtr, OBJ_UNDER_TOS, valuePtr); } else { if (Tcl_ListObjAppendElement(interp, valuePtr, OBJ_AT_TOS) != TCL_OK) { if (allocateDict) { TclDecrRefCount(dictPtr); } TRACE_ERROR(interp); goto gotError; } /* * Must invalidate the string representation of dictionary * here because we have directly updated the internal * representation; if we don't, callers could see the wrong * string rep despite the internal version of the dictionary * having the correct value. [Bug 3079830] */ TclInvalidateStringRep(dictPtr); } break; default: Tcl_Panic("Should not happen!"); } if (TclIsVarDirectWritable(varPtr)) { if (allocateDict) { value2Ptr = varPtr->value.objPtr; Tcl_IncrRefCount(dictPtr); if (value2Ptr != NULL) { TclDecrRefCount(value2Ptr); } varPtr->value.objPtr = dictPtr; } objResultPtr = dictPtr; } else { Tcl_IncrRefCount(dictPtr); DECACHE_STACK_INFO(); objResultPtr = TclPtrSetVarIdx(interp, varPtr, NULL, NULL, NULL, dictPtr, TCL_LEAVE_ERR_MSG, opnd); CACHE_STACK_INFO(); TclDecrRefCount(dictPtr); if (objResultPtr == NULL) { TRACE_ERROR(interp); goto gotError; } } #ifndef TCL_COMPILE_DEBUG if (pc[5] == INST_POP) { NEXT_INST_F(6, 2, 0); } #endif TRACE_APPEND(("%.30s\n", O2S(objResultPtr))); NEXT_INST_F(5, 2, 1); case INST_DICT_FIRST: opnd = TclGetUInt4AtPtr(pc+1); TRACE(("%u => ", opnd)); dictPtr = POP_OBJECT(); searchPtr = (Tcl_DictSearch *)Tcl_Alloc(sizeof(Tcl_DictSearch)); if (Tcl_DictObjFirst(interp, dictPtr, searchPtr, &keyPtr, &valuePtr, &done) != TCL_OK) { /* * dictPtr is no longer on the stack, and we're not * moving it into the internalrep of an iterator. We need * to drop the refcount [Tcl Bug 9b352768e6]. */ Tcl_DecrRefCount(dictPtr); Tcl_Free(searchPtr); TRACE_ERROR(interp); goto gotError; } { Tcl_ObjInternalRep ir; TclNewObj(statePtr); ir.twoPtrValue.ptr1 = searchPtr; ir.twoPtrValue.ptr2 = dictPtr; Tcl_StoreInternalRep(statePtr, &dictIteratorType, &ir); } varPtr = LOCAL(opnd); if (varPtr->value.objPtr) { if (TclHasInternalRep(varPtr->value.objPtr, &dictIteratorType)) { Tcl_Panic("mis-issued dictFirst!"); } TclDecrRefCount(varPtr->value.objPtr); } varPtr->value.objPtr = statePtr; Tcl_IncrRefCount(statePtr); goto pushDictIteratorResult; case INST_DICT_NEXT: opnd = TclGetUInt4AtPtr(pc+1); TRACE(("%u => ", opnd)); statePtr = (*LOCAL(opnd)).value.objPtr; { const Tcl_ObjInternalRep *irPtr; if (statePtr && (irPtr = TclFetchInternalRep(statePtr, &dictIteratorType))) { searchPtr = (Tcl_DictSearch *)irPtr->twoPtrValue.ptr1; Tcl_DictObjNext(searchPtr, &keyPtr, &valuePtr, &done); } else { Tcl_Panic("mis-issued dictNext!"); } } pushDictIteratorResult: if (done) { TclNewObj(emptyPtr); PUSH_OBJECT(emptyPtr); PUSH_OBJECT(emptyPtr); } else { PUSH_OBJECT(valuePtr); PUSH_OBJECT(keyPtr); } TRACE_APPEND(("\"%.30s\" \"%.30s\" %d\n", O2S(OBJ_UNDER_TOS), O2S(OBJ_AT_TOS), done)); /* * The INST_DICT_FIRST and INST_DICT_NEXT instructions are always * followed by a conditional jump, so we can take advantage of this to * do some peephole optimization (note that we're careful to not close * out someone doing something else). */ JUMP_PEEPHOLE_F(done, 5, 0); case INST_DICT_UPDATE_START: opnd = TclGetUInt4AtPtr(pc+1); opnd2 = TclGetUInt4AtPtr(pc+5); TRACE(("%u => ", opnd)); varPtr = LOCAL(opnd); duiPtr = (DictUpdateInfo *)codePtr->auxDataArrayPtr[opnd2].clientData; while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } if (TclIsVarDirectReadable(varPtr)) { dictPtr = varPtr->value.objPtr; } else { DECACHE_STACK_INFO(); dictPtr = TclPtrGetVarIdx(interp, varPtr, NULL, NULL, NULL, TCL_LEAVE_ERR_MSG, opnd); CACHE_STACK_INFO(); if (dictPtr == NULL) { TRACE_ERROR(interp); goto gotError; } } Tcl_IncrRefCount(dictPtr); if (TclListObjGetElements(interp, OBJ_AT_TOS, &length, &keyPtrPtr) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } if (length != duiPtr->length) { Tcl_Panic("dictUpdateStart argument length mismatch"); } for (i=0 ; ivarIndices[i]); while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } DECACHE_STACK_INFO(); if (valuePtr == NULL) { TclObjUnsetVar2(interp, localName(iPtr->varFramePtr, duiPtr->varIndices[i]), NULL, 0); } else if (TclPtrSetVarIdx(interp, varPtr, NULL, NULL, NULL, valuePtr, TCL_LEAVE_ERR_MSG, duiPtr->varIndices[i]) == NULL) { CACHE_STACK_INFO(); TRACE_ERROR(interp); Tcl_DecrRefCount(dictPtr); goto gotError; } CACHE_STACK_INFO(); } TclDecrRefCount(dictPtr); TRACE_APPEND(("OK\n")); NEXT_INST_F(9, 0, 0); case INST_DICT_UPDATE_END: opnd = TclGetUInt4AtPtr(pc+1); opnd2 = TclGetUInt4AtPtr(pc+5); TRACE(("%u => ", opnd)); varPtr = LOCAL(opnd); duiPtr = (DictUpdateInfo *)codePtr->auxDataArrayPtr[opnd2].clientData; while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } if (TclIsVarDirectReadable(varPtr)) { dictPtr = varPtr->value.objPtr; } else { DECACHE_STACK_INFO(); dictPtr = TclPtrGetVarIdx(interp, varPtr, NULL, NULL, NULL, 0, opnd); CACHE_STACK_INFO(); } if (dictPtr == NULL) { TRACE_APPEND(("storage was unset\n")); NEXT_INST_F(9, 1, 0); } if (Tcl_DictObjSize(interp, dictPtr, &length) != TCL_OK || TclListObjGetElements(interp, OBJ_AT_TOS, &length, &keyPtrPtr) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } allocdict = Tcl_IsShared(dictPtr); if (allocdict) { dictPtr = Tcl_DuplicateObj(dictPtr); } if (length > 0) { TclInvalidateStringRep(dictPtr); } for (i=0 ; ivarIndices[i]); while (TclIsVarLink(var2Ptr)) { var2Ptr = var2Ptr->value.linkPtr; } if (TclIsVarDirectReadable(var2Ptr)) { valuePtr = var2Ptr->value.objPtr; } else { DECACHE_STACK_INFO(); valuePtr = TclPtrGetVarIdx(interp, var2Ptr, NULL, NULL, NULL, 0, duiPtr->varIndices[i]); CACHE_STACK_INFO(); } if (valuePtr == NULL) { Tcl_DictObjRemove(interp, dictPtr, keyPtrPtr[i]); } else if (dictPtr == valuePtr) { Tcl_DictObjPut(interp, dictPtr, keyPtrPtr[i], Tcl_DuplicateObj(valuePtr)); } else { Tcl_DictObjPut(interp, dictPtr, keyPtrPtr[i], valuePtr); } } if (TclIsVarDirectWritable(varPtr)) { Tcl_IncrRefCount(dictPtr); TclDecrRefCount(varPtr->value.objPtr); varPtr->value.objPtr = dictPtr; } else { DECACHE_STACK_INFO(); objResultPtr = TclPtrSetVarIdx(interp, varPtr, NULL, NULL, NULL, dictPtr, TCL_LEAVE_ERR_MSG, opnd); CACHE_STACK_INFO(); if (objResultPtr == NULL) { if (allocdict) { TclDecrRefCount(dictPtr); } TRACE_ERROR(interp); goto gotError; } } TRACE_APPEND(("written back\n")); NEXT_INST_F(9, 1, 0); case INST_DICT_EXPAND: dictPtr = OBJ_UNDER_TOS; listPtr = OBJ_AT_TOS; TRACE(("\"%.30s\" \"%.30s\" =>", O2S(dictPtr), O2S(listPtr))); if (TclListObjGetElements(interp, listPtr, &objc, &objv) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } objResultPtr = TclDictWithInit(interp, dictPtr, objc, objv); if (objResultPtr == NULL) { TRACE_ERROR(interp); goto gotError; } TRACE_APPEND(("\"%.30s\"\n", O2S(objResultPtr))); NEXT_INST_F(1, 2, 1); case INST_DICT_RECOMBINE_STK: keysPtr = POP_OBJECT(); varNamePtr = OBJ_UNDER_TOS; listPtr = OBJ_AT_TOS; TRACE(("\"%.30s\" \"%.30s\" \"%.30s\" => ", O2S(varNamePtr), O2S(valuePtr), O2S(keysPtr))); if (TclListObjGetElements(interp, listPtr, &objc, &objv) != TCL_OK) { TRACE_ERROR(interp); TclDecrRefCount(keysPtr); goto gotError; } varPtr = TclObjLookupVarEx(interp, varNamePtr, NULL, TCL_LEAVE_ERR_MSG, "set", 1, 1, &arrayPtr); if (varPtr == NULL) { TRACE_ERROR(interp); TclDecrRefCount(keysPtr); goto gotError; } DECACHE_STACK_INFO(); result = TclDictWithFinish(interp, varPtr, arrayPtr, varNamePtr, NULL, -1, objc, objv, keysPtr); CACHE_STACK_INFO(); TclDecrRefCount(keysPtr); if (result != TCL_OK) { TRACE_ERROR(interp); goto gotError; } TRACE_APPEND(("OK\n")); NEXT_INST_F(1, 2, 0); case INST_DICT_RECOMBINE_IMM: opnd = TclGetUInt4AtPtr(pc+1); listPtr = OBJ_UNDER_TOS; keysPtr = OBJ_AT_TOS; varPtr = LOCAL(opnd); TRACE(("%u <- \"%.30s\" \"%.30s\" => ", opnd, O2S(valuePtr), O2S(keysPtr))); if (TclListObjGetElements(interp, listPtr, &objc, &objv) != TCL_OK) { TRACE_ERROR(interp); goto gotError; } while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } DECACHE_STACK_INFO(); result = TclDictWithFinish(interp, varPtr, NULL, NULL, NULL, opnd, objc, objv, keysPtr); CACHE_STACK_INFO(); if (result != TCL_OK) { TRACE_ERROR(interp); goto gotError; } TRACE_APPEND(("OK\n")); NEXT_INST_F(5, 2, 0); } break; /* * End of dictionary-related instructions. * ----------------------------------------------------------------- */ case INST_CLOCK_READ: { /* Read the wall clock */ Tcl_WideInt wval; Tcl_Time now; switch (TclGetUInt1AtPtr(pc+1)) { case 0: /* clicks */ #ifdef TCL_WIDE_CLICKS wval = TclpGetWideClicks(); #else wval = (Tcl_WideInt)TclpGetClicks(); #endif break; case 1: /* microseconds */ Tcl_GetTime(&now); wval = (Tcl_WideInt)now.sec * 1000000 + now.usec; break; case 2: /* milliseconds */ Tcl_GetTime(&now); wval = (Tcl_WideInt)now.sec * 1000 + now.usec / 1000; break; case 3: /* seconds */ Tcl_GetTime(&now); wval = (Tcl_WideInt)now.sec; break; default: Tcl_Panic("clockRead instruction with unknown clock#"); break; } TclNewIntObj(objResultPtr, wval); TRACE_WITH_OBJ(("=> "), objResultPtr); NEXT_INST_F(2, 0, 1); } break; default: Tcl_Panic("TclNRExecuteByteCode: unrecognized opCode %u", *pc); } /* end of switch on opCode */ /* * Block for variables needed to process exception returns. */ { ExceptionRange *rangePtr; /* Points to closest loop or catch exception * range enclosing the pc. Used by various * instructions and processCatch to process * break, continue, and errors. */ const char *bytes; /* * An external evaluation (INST_INVOKE or INST_EVAL) returned * something different from TCL_OK, or else INST_BREAK or * INST_CONTINUE were called. */ processExceptionReturn: #ifdef TCL_COMPILE_DEBUG switch (*pc) { case INST_INVOKE_STK1: opnd = TclGetUInt1AtPtr(pc+1); TRACE(("%u => ... after \"%.20s\": ", opnd, cmdNameBuf)); break; case INST_INVOKE_STK4: opnd = TclGetUInt4AtPtr(pc+1); TRACE(("%u => ... after \"%.20s\": ", opnd, cmdNameBuf)); break; case INST_EVAL_STK: /* * Note that the object at stacktop has to be used before doing * the cleanup. */ TRACE(("\"%.30s\" => ", O2S(OBJ_AT_TOS))); break; default: TRACE(("=> ")); } #endif if ((result == TCL_CONTINUE) || (result == TCL_BREAK)) { rangePtr = GetExceptRangeForPc(pc, result, codePtr); if (rangePtr == NULL) { TRACE_APPEND(("no encl. loop or catch, returning %s\n", StringForResultCode(result))); goto abnormalReturn; } if (rangePtr->type == CATCH_EXCEPTION_RANGE) { TRACE_APPEND(("%s ...\n", StringForResultCode(result))); goto processCatch; } while (cleanup--) { valuePtr = POP_OBJECT(); TclDecrRefCount(valuePtr); } if (result == TCL_BREAK) { result = TCL_OK; pc = (codePtr->codeStart + rangePtr->breakOffset); TRACE_APPEND(("%s, range at %" TCL_SIZE_MODIFIER "d, new pc %" TCL_SIZE_MODIFIER "d\n", StringForResultCode(result), rangePtr->codeOffset, rangePtr->breakOffset)); NEXT_INST_F(0, 0, 0); } if (rangePtr->continueOffset == TCL_INDEX_NONE) { TRACE_APPEND(("%s, loop w/o continue, checking for catch\n", StringForResultCode(result))); goto checkForCatch; } result = TCL_OK; pc = (codePtr->codeStart + rangePtr->continueOffset); TRACE_APPEND(("%s, range at %" TCL_SIZE_MODIFIER "d, new pc %" TCL_SIZE_MODIFIER "d\n", StringForResultCode(result), rangePtr->codeOffset, rangePtr->continueOffset)); NEXT_INST_F(0, 0, 0); } #ifdef TCL_COMPILE_DEBUG if (traceInstructions) { objPtr = Tcl_GetObjResult(interp); if ((result != TCL_ERROR) && (result != TCL_RETURN)) { TRACE_APPEND(("OTHER RETURN CODE %d, result=\"%.30s\"\n ", result, O2S(objPtr))); } else { TRACE_APPEND(("%s, result=\"%.30s\"\n", StringForResultCode(result), O2S(objPtr))); } } #endif goto checkForCatch; /* * Division by zero in an expression. Control only reaches this point * by "goto divideByZero". */ divideByZero: Tcl_SetObjResult(interp, Tcl_NewStringObj("divide by zero", -1)); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "ARITH", "DIVZERO", "divide by zero", (char *)NULL); CACHE_STACK_INFO(); goto gotError; outOfMemory: Tcl_SetObjResult(interp, Tcl_NewStringObj("out of memory", -1)); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "ARITH", "OUTOFMEMORY", "out of memory", (char *)NULL); CACHE_STACK_INFO(); goto gotError; /* * Exponentiation of zero by negative number in an expression. Control * only reaches this point by "goto exponOfZero". */ exponOfZero: Tcl_SetObjResult(interp, Tcl_NewStringObj( "exponentiation of zero by negative power", -1)); DECACHE_STACK_INFO(); Tcl_SetErrorCode(interp, "ARITH", "DOMAIN", "exponentiation of zero by negative power", (char *)NULL); CACHE_STACK_INFO(); /* * Almost all error paths feed through here rather than assigning to * result themselves (for a small but consistent saving). */ gotError: result = TCL_ERROR; /* * Execution has generated an "exception" such as TCL_ERROR. If the * exception is an error, record information about what was being * executed when the error occurred. Find the closest enclosing catch * range, if any. If no enclosing catch range is found, stop execution * and return the "exception" code. */ checkForCatch: if (iPtr->execEnvPtr->rewind) { goto abnormalReturn; } if ((result == TCL_ERROR) && !(iPtr->flags & ERR_ALREADY_LOGGED)) { const unsigned char *pcBeg; Tcl_Size xxx1length; bytes = GetSrcInfoForPc(pc, codePtr, &xxx1length, &pcBeg, NULL); DECACHE_STACK_INFO(); TclLogCommandInfo(interp, codePtr->source, bytes, bytes ? xxx1length : 0, pcBeg, tosPtr); CACHE_STACK_INFO(); } iPtr->flags &= ~ERR_ALREADY_LOGGED; /* * Clear all expansions that may have started after the last * INST_BEGIN_CATCH. */ while (auxObjList) { if ((catchTop != initCatchTop) && (PTR2INT(*catchTop) > PTR2INT(auxObjList->internalRep.twoPtrValue.ptr2))) { break; } POP_TAUX_OBJ(); } /* * We must not catch if the script in progress has been canceled with * the TCL_CANCEL_UNWIND flag. Instead, it blows outwards until we * either hit another interpreter (presumably where the script in * progress has not been canceled) or we get to the top-level. We do * NOT modify the interpreter result here because we know it will * already be set prior to vectoring down to this point in the code. */ if (TclCanceled(iPtr) && (Tcl_Canceled(interp, 0) == TCL_ERROR)) { #ifdef TCL_COMPILE_DEBUG if (traceInstructions) { fprintf(stdout, " ... cancel with unwind, returning %s\n", StringForResultCode(result)); } #endif goto abnormalReturn; } /* * We must not catch an exceeded limit. Instead, it blows outwards * until we either hit another interpreter (presumably where the limit * is not exceeded) or we get to the top-level. */ if (TclLimitExceeded(iPtr->limit)) { #ifdef TCL_COMPILE_DEBUG if (traceInstructions) { fprintf(stdout, " ... limit exceeded, returning %s\n", StringForResultCode(result)); } #endif goto abnormalReturn; } if (catchTop == initCatchTop) { #ifdef TCL_COMPILE_DEBUG if (traceInstructions) { fprintf(stdout, " ... no enclosing catch, returning %s\n", StringForResultCode(result)); } #endif goto abnormalReturn; } rangePtr = GetExceptRangeForPc(pc, TCL_ERROR, codePtr); if (rangePtr == NULL) { /* * This is only possible when compiling a [catch] that sends its * script to INST_EVAL. Cannot correct the compiler without * breaking compat with previous .tbc compiled scripts. */ #ifdef TCL_COMPILE_DEBUG if (traceInstructions) { fprintf(stdout, " ... no enclosing catch, returning %s\n", StringForResultCode(result)); } #endif goto abnormalReturn; } /* * A catch exception range (rangePtr) was found to handle an * "exception". It was found either by checkForCatch just above or by * an instruction during break, continue, or error processing. Jump to * its catchOffset after unwinding the operand stack to the depth it * had when starting to execute the range's catch command. */ processCatch: while (CURR_DEPTH > PTR2INT(*catchTop)) { valuePtr = POP_OBJECT(); TclDecrRefCount(valuePtr); } #ifdef TCL_COMPILE_DEBUG if (traceInstructions) { fprintf(stdout, " ... found catch at %" TCL_SIZE_MODIFIER "d, catchTop=%" TCL_T_MODIFIER "d, " "unwound to %" TCL_T_MODIFIER "d, new pc %" TCL_SIZE_MODIFIER "d\n", rangePtr->codeOffset, (catchTop - initCatchTop - 1), PTR2INT(*catchTop), rangePtr->catchOffset); } #endif pc = (codePtr->codeStart + rangePtr->catchOffset); NEXT_INST_F(0, 0, 0); /* Restart the execution loop at pc. */ /* * end of infinite loop dispatching on instructions. */ /* * Done or abnormal return code. Restore the stack to state it had when * starting to execute the ByteCode. Panic if the stack is below the * initial level. */ abnormalReturn: TCL_DTRACE_INST_LAST(); /* * Clear all expansions and same-level NR calls. * * Note that expansion markers have a NULL type; avoid removing other * markers. */ while (auxObjList) { POP_TAUX_OBJ(); } while (tosPtr > initTosPtr) { objPtr = POP_OBJECT(); Tcl_DecrRefCount(objPtr); } if (tosPtr < initTosPtr) { fprintf(stderr, "\nTclNRExecuteByteCode: abnormal return at pc %" TCL_T_MODIFIER "d: " "stack top %" TCL_T_MODIFIER "d < entry stack top %d\n", (pc - codePtr->codeStart), CURR_DEPTH, 0); Tcl_Panic("TclNRExecuteByteCode execution failure: end stack top < start stack top"); } CLANG_ASSERT(bcFramePtr); } iPtr->cmdFramePtr = bcFramePtr->nextPtr; TclReleaseByteCode(codePtr); TclStackFree(interp, TD); /* free my stack */ return result; /* * INST_START_CMD failure case removed where it doesn't bother that much * * Remark that if the interpreter is marked for deletion its * compileEpoch is modified, so that the epoch check also verifies * that the interp is not deleted. If no outside call has been made * since the last check, it is safe to omit the check. * case INST_START_CMD: */ instStartCmdFailed: { const char *bytes; Tcl_Size xxx1length; xxx1length = 0; if (TclInterpReady(interp) == TCL_ERROR) { goto gotError; } /* * We used to switch to direct eval; for NRE-awareness we now * compile and eval the command so that this evaluation does not * add a new TEBC instance. Bug [2910748], bug [fa6bf38d07] * * TODO: recompile, search this command and eval a code starting from, * so that this evaluation does not add a new TEBC instance without * NRE-trampoline. */ codePtr->flags |= TCL_BYTECODE_RECOMPILE; bytes = GetSrcInfoForPc(pc, codePtr, &xxx1length, NULL, NULL); opnd = TclGetUInt4AtPtr(pc+1); pc += (opnd-1); assert(bytes); PUSH_OBJECT(Tcl_NewStringObj(bytes, xxx1length)); goto instEvalStk; } } #undef codePtr #undef iPtr #undef bcFramePtr #undef initCatchTop #undef initTosPtr #undef auxObjList #undef catchTop #undef TCONST #undef esPtr static int FinalizeOONext( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; CallContext *contextPtr = (CallContext *)data[1]; /* * Reset the variable lookup frame. */ iPtr->varFramePtr = (CallFrame *)data[0]; /* * Restore the call chain context index as we've finished the inner invoke * and want to operate in the outer context again. */ contextPtr->index = PTR2INT(data[2]); contextPtr->skip = PTR2INT(data[3]); contextPtr->oPtr->flags &= ~FILTER_HANDLING; return result; } static int FinalizeOONextFilter( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; CallContext *contextPtr = (CallContext *)data[1]; /* * Reset the variable lookup frame. */ iPtr->varFramePtr = (CallFrame *)data[0]; /* * Restore the call chain context index as we've finished the inner invoke * and want to operate in the outer context again. */ contextPtr->index = PTR2INT(data[2]); contextPtr->skip = PTR2INT(data[3]); contextPtr->oPtr->flags |= FILTER_HANDLING; return result; } /* * WidePwrSmallExpon -- * * Helper to calculate small powers of integers whose result is wide. */ static inline Tcl_WideInt WidePwrSmallExpon( Tcl_WideInt w1, long exponent) { Tcl_WideInt wResult; wResult = w1 * w1; /* b**2 */ switch (exponent) { case 2: break; case 3: wResult *= w1; /* b**3 */ break; case 4: wResult *= wResult; /* b**4 */ break; case 5: wResult *= wResult; /* b**4 */ wResult *= w1; /* b**5 */ break; case 6: wResult *= w1; /* b**3 */ wResult *= wResult; /* b**6 */ break; case 7: wResult *= w1; /* b**3 */ wResult *= wResult; /* b**6 */ wResult *= w1; /* b**7 */ break; case 8: wResult *= wResult; /* b**4 */ wResult *= wResult; /* b**8 */ break; case 9: wResult *= wResult; /* b**4 */ wResult *= wResult; /* b**8 */ wResult *= w1; /* b**9 */ break; case 10: wResult *= wResult; /* b**4 */ wResult *= w1; /* b**5 */ wResult *= wResult; /* b**10 */ break; case 11: wResult *= wResult; /* b**4 */ wResult *= w1; /* b**5 */ wResult *= wResult; /* b**10 */ wResult *= w1; /* b**11 */ break; case 12: wResult *= w1; /* b**3 */ wResult *= wResult; /* b**6 */ wResult *= wResult; /* b**12 */ break; case 13: wResult *= w1; /* b**3 */ wResult *= wResult; /* b**6 */ wResult *= wResult; /* b**12 */ wResult *= w1; /* b**13 */ break; case 14: wResult *= w1; /* b**3 */ wResult *= wResult; /* b**6 */ wResult *= w1; /* b**7 */ wResult *= wResult; /* b**14 */ break; case 15: wResult *= w1; /* b**3 */ wResult *= wResult; /* b**6 */ wResult *= w1; /* b**7 */ wResult *= wResult; /* b**14 */ wResult *= w1; /* b**15 */ break; case 16: wResult *= wResult; /* b**4 */ wResult *= wResult; /* b**8 */ wResult *= wResult; /* b**16 */ break; } return wResult; } /* *---------------------------------------------------------------------- * * ExecuteExtendedBinaryMathOp, ExecuteExtendedUnaryMathOp -- * * These functions do advanced math for binary and unary operators * respectively, so that the main TEBC code does not bear the cost of * them. * * Results: * A Tcl_Obj* result, or a NULL (in which case valuePtr is updated to * hold the result value), or one of the special flag values * GENERAL_ARITHMETIC_ERROR, EXPONENT_OF_ZERO or DIVIDED_BY_ZERO. The * latter two signify a zero value raised to a negative power or a value * divided by zero, respectively. With GENERAL_ARITHMETIC_ERROR, all * error information will have already been reported in the interpreter * result. * * Side effects: * May update the Tcl_Obj indicated valuePtr if it is unshared. Will * return a NULL when that happens. * *---------------------------------------------------------------------- */ static Tcl_Obj * ExecuteExtendedBinaryMathOp( Tcl_Interp *interp, /* Where to report errors. */ int opcode, /* What operation to perform. */ Tcl_Obj **constants, /* The execution environment's constants. */ Tcl_Obj *valuePtr, /* The first operand on the stack. */ Tcl_Obj *value2Ptr) /* The second operand on the stack. */ { #define WIDE_RESULT(w) \ if (Tcl_IsShared(valuePtr)) { \ return Tcl_NewWideIntObj(w); \ } else { \ TclSetIntObj(valuePtr, (w)); \ return NULL; \ } #define BIG_RESULT(b) \ if (Tcl_IsShared(valuePtr)) { \ return Tcl_NewBignumObj(b); \ } else { \ Tcl_SetBignumObj(valuePtr, (b)); \ return NULL; \ } #define DOUBLE_RESULT(d) \ if (Tcl_IsShared(valuePtr)) { \ TclNewDoubleObj(objResultPtr, (d)); \ return objResultPtr; \ } else { \ Tcl_SetDoubleObj(valuePtr, (d)); \ return NULL; \ } int type1, type2; void *ptr1, *ptr2; double d1, d2, dResult; Tcl_WideInt w1, w2, wResult; mp_int big1, big2, bigResult, bigRemainder; Tcl_Obj *objResultPtr; int invalid, zero; int shift; mp_err err; (void) GetNumberFromObj(NULL, valuePtr, &ptr1, &type1); (void) GetNumberFromObj(NULL, value2Ptr, &ptr2, &type2); switch (opcode) { case INST_MOD: /* TODO: Attempts to re-use unshared operands on stack */ w2 = 0; /* silence gcc warning */ if (type2 == TCL_NUMBER_INT) { w2 = *((const Tcl_WideInt *)ptr2); if (w2 == 0) { return DIVIDED_BY_ZERO; } if ((w2 == 1) || (w2 == -1)) { /* * Div. by |1| always yields remainder of 0. */ return constants[0]; } } if (type1 == TCL_NUMBER_INT) { w1 = *((const Tcl_WideInt *)ptr1); if (w1 == 0) { /* * 0 % (non-zero) always yields remainder of 0. */ return constants[0]; } if (type2 == TCL_NUMBER_INT) { Tcl_WideInt wQuotient, wRemainder; w2 = *((const Tcl_WideInt *)ptr2); wQuotient = w1 / w2; /* * Force Tcl's integer division rules. * TODO: examine for logic simplification */ if (((wQuotient < 0) || ((wQuotient == 0) && ((w1 < 0 && w2 > 0) || (w1 > 0 && w2 < 0)))) && (wQuotient * w2 != w1)) { wQuotient -= 1; } wRemainder = (Tcl_WideInt)((Tcl_WideUInt)w1 - (Tcl_WideUInt)w2*(Tcl_WideUInt)wQuotient); WIDE_RESULT(wRemainder); } Tcl_TakeBignumFromObj(NULL, value2Ptr, &big2); /* TODO: internals intrusion */ if ((w1 > ((Tcl_WideInt)0)) ^ !mp_isneg(&big2)) { /* * Arguments are opposite sign; remainder is sum. */ err = mp_init_i64(&big1, w1); if (err == MP_OKAY) { err = mp_add(&big2, &big1, &big2); mp_clear(&big1); } if (err != MP_OKAY) { return OUT_OF_MEMORY; } BIG_RESULT(&big2); } /* * Arguments are same sign; remainder is first operand. */ mp_clear(&big2); return NULL; } Tcl_GetBignumFromObj(NULL, valuePtr, &big1); Tcl_GetBignumFromObj(NULL, value2Ptr, &big2); err = mp_init_multi(&bigResult, &bigRemainder, (void *)NULL); if (err == MP_OKAY) { err = mp_div(&big1, &big2, &bigResult, &bigRemainder); } if ((err == MP_OKAY) && !mp_iszero(&bigRemainder) && (bigRemainder.sign != big2.sign)) { /* * Convert to Tcl's integer division rules. */ if ((mp_sub_d(&bigResult, 1, &bigResult) != MP_OKAY) || (mp_add(&bigRemainder, &big2, &bigRemainder) != MP_OKAY)) { return OUT_OF_MEMORY; } } err = mp_copy(&bigRemainder, &bigResult); mp_clear(&bigRemainder); mp_clear(&big1); mp_clear(&big2); if (err != MP_OKAY) { return OUT_OF_MEMORY; } BIG_RESULT(&bigResult); case INST_LSHIFT: case INST_RSHIFT: { /* * Reject negative shift argument. */ switch (type2) { case TCL_NUMBER_INT: invalid = (*((const Tcl_WideInt *)ptr2) < 0); break; case TCL_NUMBER_BIG: Tcl_TakeBignumFromObj(NULL, value2Ptr, &big2); invalid = mp_isneg(&big2); mp_clear(&big2); break; default: /* Unused, here to silence compiler warning */ invalid = 0; } if (invalid) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "negative shift argument", -1)); return GENERAL_ARITHMETIC_ERROR; } /* * Zero shifted any number of bits is still zero. */ if ((type1==TCL_NUMBER_INT) && (*((const Tcl_WideInt *)ptr1) == 0)) { return constants[0]; } if (opcode == INST_LSHIFT) { /* * Large left shifts create integer overflow. * * BEWARE! Can't use Tcl_GetIntFromObj() here because that * converts values in the (unsigned) range to their signed int * counterparts, leading to incorrect results. */ if ((type2 != TCL_NUMBER_INT) || (*((const Tcl_WideInt *)ptr2) > INT_MAX)) { /* * Technically, we could hold the value (1 << (INT_MAX+1)) in * an mp_int, but since we're using mp_mul_2d() to do the * work, and it takes only an int argument, that's a good * place to draw the line. */ Tcl_SetObjResult(interp, Tcl_NewStringObj( "integer value too large to represent", -1)); return GENERAL_ARITHMETIC_ERROR; } shift = (int)(*((const Tcl_WideInt *)ptr2)); /* * Handle shifts within the native wide range. */ if ((type1 == TCL_NUMBER_INT) && ((size_t)shift < CHAR_BIT*sizeof(Tcl_WideInt))) { w1 = *((const Tcl_WideInt *)ptr1); if (!((w1 > 0 ? w1 : ~w1) & -( ((Tcl_WideUInt)1) << (CHAR_BIT*sizeof(Tcl_WideInt) - 1 - shift)))) { WIDE_RESULT((Tcl_WideUInt)w1 << shift); } } } else { /* * Quickly force large right shifts to 0 or -1. */ if ((type2 != TCL_NUMBER_INT) || (*(const Tcl_WideInt *)ptr2 > INT_MAX)) { /* * Again, technically, the value to be shifted could be an * mp_int so huge that a right shift by (INT_MAX+1) bits could * not take us to the result of 0 or -1, but since we're using * mp_div_2d to do the work, and it takes only an int * argument, we draw the line there. */ switch (type1) { case TCL_NUMBER_INT: zero = (*(const Tcl_WideInt *)ptr1 > 0); break; case TCL_NUMBER_BIG: Tcl_TakeBignumFromObj(NULL, valuePtr, &big1); zero = !mp_isneg(&big1); mp_clear(&big1); break; default: /* Unused, here to silence compiler warning. */ zero = 0; } if (zero) { return constants[0]; } WIDE_RESULT(-1); } shift = (int)(*(const Tcl_WideInt *)ptr2); /* * Handle shifts within the native wide range. */ if (type1 == TCL_NUMBER_INT) { w1 = *(const Tcl_WideInt *)ptr1; if ((size_t)shift >= CHAR_BIT*sizeof(Tcl_WideInt)) { if (w1 >= 0) { return constants[0]; } WIDE_RESULT(-1); } WIDE_RESULT(w1 >> shift); } } Tcl_TakeBignumFromObj(NULL, valuePtr, &big1); err = mp_init(&bigResult); if (err == MP_OKAY) { if (opcode == INST_LSHIFT) { err = mp_mul_2d(&big1, shift, &bigResult); } else { err = mp_signed_rsh(&big1, shift, &bigResult); } } if (err != MP_OKAY) { return OUT_OF_MEMORY; } mp_clear(&big1); BIG_RESULT(&bigResult); } case INST_BITOR: case INST_BITXOR: case INST_BITAND: if ((type1 != TCL_NUMBER_INT) || (type2 != TCL_NUMBER_INT)) { Tcl_TakeBignumFromObj(NULL, valuePtr, &big1); Tcl_TakeBignumFromObj(NULL, value2Ptr, &big2); err = mp_init(&bigResult); if (err == MP_OKAY) { switch (opcode) { case INST_BITAND: err = mp_and(&big1, &big2, &bigResult); break; case INST_BITOR: err = mp_or(&big1, &big2, &bigResult); break; case INST_BITXOR: err = mp_xor(&big1, &big2, &bigResult); break; } } if (err != MP_OKAY) { return OUT_OF_MEMORY; } mp_clear(&big1); mp_clear(&big2); BIG_RESULT(&bigResult); } w1 = *((const Tcl_WideInt *)ptr1); w2 = *((const Tcl_WideInt *)ptr2); switch (opcode) { case INST_BITAND: wResult = w1 & w2; break; case INST_BITOR: wResult = w1 | w2; break; case INST_BITXOR: wResult = w1 ^ w2; break; default: /* Unused, here to silence compiler warning. */ wResult = 0; } WIDE_RESULT(wResult); case INST_EXPON: { int oddExponent = 0, negativeExponent = 0; unsigned short base; if ((type1 == TCL_NUMBER_DOUBLE) || (type2 == TCL_NUMBER_DOUBLE)) { Tcl_GetDoubleFromObj(NULL, valuePtr, &d1); Tcl_GetDoubleFromObj(NULL, value2Ptr, &d2); if (d1==0.0 && d2<0.0) { return EXPONENT_OF_ZERO; } dResult = pow(d1, d2); goto doubleResult; } w1 = w2 = 0; /* to silence compiler warning (maybe-uninitialized) */ if (type2 == TCL_NUMBER_INT) { w2 = *((const Tcl_WideInt *) ptr2); if (w2 == 0) { /* * Anything to the zero power is 1. */ return constants[1]; } else if (w2 == 1) { /* * Anything to the first power is itself */ return NULL; } negativeExponent = (w2 < 0); oddExponent = (int)w2 & 1; } else { Tcl_TakeBignumFromObj(NULL, value2Ptr, &big2); negativeExponent = mp_isneg(&big2); err = mp_mod_2d(&big2, 1, &big2); oddExponent = (err == MP_OKAY) && !mp_iszero(&big2); mp_clear(&big2); } if (type1 == TCL_NUMBER_INT) { w1 = *((const Tcl_WideInt *)ptr1); if (negativeExponent) { switch (w1) { case 0: /* * Zero to a negative power is div by zero error. */ return EXPONENT_OF_ZERO; case -1: if (oddExponent) { WIDE_RESULT(-1); } /* fallthrough */ case 1: /* * 1 to any power is 1. */ return constants[1]; } } } if (negativeExponent) { /* * Integers with magnitude greater than 1 raise to a negative * power yield the answer zero (see TIP 123). */ return constants[0]; } if (type1 != TCL_NUMBER_INT) { goto overflowExpon; } switch (w1) { case 0: /* * Zero to a positive power is zero. */ return constants[0]; case 1: /* * 1 to any power is 1. */ return constants[1]; case -1: if (!oddExponent) { return constants[1]; } WIDE_RESULT(-1); } /* * We refuse to accept exponent arguments that exceed one mp_digit * which means the max exponent value is 2**28-1 = 0x0FFFFFFF = * 268435455, which fits into a signed 32 bit int which is within the * range of the Tcl_WideInt type. This means any numeric Tcl_Obj value * not using TCL_NUMBER_INT type must hold a value larger than we * accept. */ if (type2 != TCL_NUMBER_INT) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "exponent too large", -1)); return GENERAL_ARITHMETIC_ERROR; } /* From here (up to overflowExpon) w1 and exponent w2 are wide-int's. */ assert(type1 == TCL_NUMBER_INT && type2 == TCL_NUMBER_INT); if (w1 == 2) { /* * Reduce small powers of 2 to shifts. */ if ((Tcl_WideUInt)w2 < (Tcl_WideUInt)CHAR_BIT*sizeof(Tcl_WideInt) - 1) { WIDE_RESULT(((Tcl_WideInt)1) << (int)w2); } goto overflowExpon; } if (w1 == -2) { int signum = oddExponent ? -1 : 1; /* * Reduce small powers of 2 to shifts. */ if ((Tcl_WideUInt)w2 < CHAR_BIT * sizeof(Tcl_WideInt) - 1) { WIDE_RESULT(signum * (((Tcl_WideInt)1) << (int) w2)); } goto overflowExpon; } if (w2 - 2 < (long)MaxBase64Size && w1 <= MaxBase64[w2 - 2] && w1 >= -MaxBase64[w2 - 2]) { /* * Small powers of integers whose result is wide. */ wResult = WidePwrSmallExpon(w1, (long)w2); WIDE_RESULT(wResult); } /* * Handle cases of powers > 16 that still fit in a 64-bit word by * doing table lookup. */ if (w1 - 3 >= 0 && w1 - 2 < (long)Exp64IndexSize && w2 - 2 < (long)(Exp64ValueSize + MaxBase64Size)) { base = Exp64Index[w1 - 3] + (unsigned short) (w2 - 2 - MaxBase64Size); if (base < Exp64Index[w1 - 2]) { /* * 64-bit number raised to intermediate power, done by * table lookup. */ WIDE_RESULT(Exp64Value[base]); } } if (-w1 - 3 >= 0 && -w1 - 2 < (long)Exp64IndexSize && w2 - 2 < (long)(Exp64ValueSize + MaxBase64Size)) { base = Exp64Index[-w1 - 3] + (unsigned short) (w2 - 2 - MaxBase64Size); if (base < Exp64Index[-w1 - 2]) { /* * 64-bit number raised to intermediate power, done by * table lookup. */ wResult = oddExponent ? -Exp64Value[base] : Exp64Value[base]; WIDE_RESULT(wResult); } } overflowExpon: if ((TclGetWideIntFromObj(NULL, value2Ptr, &w2) != TCL_OK) || !TclHasInternalRep(value2Ptr, &tclIntType) || (Tcl_WideUInt)w2 >= (1<<28)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "exponent too large", -1)); return GENERAL_ARITHMETIC_ERROR; } Tcl_TakeBignumFromObj(NULL, valuePtr, &big1); err = mp_init(&bigResult); if (err == MP_OKAY) { /* Don't use "mp_expt_n" directly here, it doesn't exist in libtommath 1.2 */ err = TclBN_mp_expt_n(&big1, (int)w2, &bigResult); } if (err != MP_OKAY) { return OUT_OF_MEMORY; } mp_clear(&big1); BIG_RESULT(&bigResult); } case INST_ADD: case INST_SUB: case INST_MULT: case INST_DIV: if ((type1 == TCL_NUMBER_DOUBLE) || (type2 == TCL_NUMBER_DOUBLE)) { /* * At least one of the values is floating-point, so perform * floating point calculations. */ Tcl_GetDoubleFromObj(NULL, valuePtr, &d1); Tcl_GetDoubleFromObj(NULL, value2Ptr, &d2); switch (opcode) { case INST_ADD: dResult = d1 + d2; break; case INST_SUB: dResult = d1 - d2; break; case INST_MULT: dResult = d1 * d2; break; case INST_DIV: #ifndef IEEE_FLOATING_POINT if (d2 == 0.0) { return DIVIDED_BY_ZERO; } #endif /* * We presume that we are running with zero-divide unmasked if * we're on an IEEE box. Otherwise, this statement might cause * demons to fly out our noses. */ dResult = d1 / d2; break; default: /* Unused, here to silence compiler warning. */ dResult = 0; } doubleResult: #ifndef ACCEPT_NAN /* * Check now for IEEE floating-point error. */ if (isnan(dResult)) { TclExprFloatError(interp, dResult); return GENERAL_ARITHMETIC_ERROR; } #endif DOUBLE_RESULT(dResult); } if ((type1 == TCL_NUMBER_INT) && (type2 == TCL_NUMBER_INT)) { w1 = *((const Tcl_WideInt *)ptr1); w2 = *((const Tcl_WideInt *)ptr2); switch (opcode) { case INST_ADD: wResult = (Tcl_WideInt)((Tcl_WideUInt)w1 + (Tcl_WideUInt)w2); if ((type1 == TCL_NUMBER_INT) || (type2 == TCL_NUMBER_INT)) { /* * Check for overflow. */ if (Overflowing(w1, w2, wResult)) { goto overflowBasic; } } break; case INST_SUB: wResult = (Tcl_WideInt)((Tcl_WideUInt)w1 - (Tcl_WideUInt)w2); if ((type1 == TCL_NUMBER_INT) || (type2 == TCL_NUMBER_INT)) { /* * Must check for overflow. The macro tests for overflows * in sums by looking at the sign bits. As we have a * subtraction here, we are adding -w2. As -w2 could in * turn overflow, we test with ~w2 instead: it has the * opposite sign bit to w2 so it does the job. Note that * the only "bad" case (w2==0) is irrelevant for this * macro, as in that case w1 and wResult have the same * sign and there is no overflow anyway. */ if (Overflowing(w1, ~w2, wResult)) { goto overflowBasic; } } break; case INST_MULT: if ((w1 < INT_MIN) || (w1 > INT_MAX) || (w2 < INT_MIN) || (w2 > INT_MAX)) { goto overflowBasic; } wResult = w1 * w2; break; case INST_DIV: if (w2 == 0) { return DIVIDED_BY_ZERO; } /* * Need a bignum to represent (WIDE_MIN / -1) */ if ((w1 == WIDE_MIN) && (w2 == -1)) { goto overflowBasic; } wResult = w1 / w2; /* * Force Tcl's integer division rules. * TODO: examine for logic simplification */ if (((wResult < 0) || ((wResult == 0) && ((w1 < 0 && w2 > 0) || (w1 > 0 && w2 < 0)))) && (wResult*w2 != w1)) { wResult -= 1; } break; default: /* * Unused, here to silence compiler warning. */ wResult = 0; } WIDE_RESULT(wResult); } overflowBasic: Tcl_TakeBignumFromObj(NULL, valuePtr, &big1); Tcl_TakeBignumFromObj(NULL, value2Ptr, &big2); err = mp_init(&bigResult); if (err == MP_OKAY) { switch (opcode) { case INST_ADD: err = mp_add(&big1, &big2, &bigResult); break; case INST_SUB: err = mp_sub(&big1, &big2, &bigResult); break; case INST_MULT: err = mp_mul(&big1, &big2, &bigResult); break; case INST_DIV: if (mp_iszero(&big2)) { mp_clear(&big1); mp_clear(&big2); mp_clear(&bigResult); return DIVIDED_BY_ZERO; } err = mp_init(&bigRemainder); if (err == MP_OKAY) { err = mp_div(&big1, &big2, &bigResult, &bigRemainder); } /* TODO: internals intrusion */ if (!mp_iszero(&bigRemainder) && (bigRemainder.sign != big2.sign)) { /* * Convert to Tcl's integer division rules. */ err = mp_sub_d(&bigResult, 1, &bigResult); if (err == MP_OKAY) { err = mp_add(&bigRemainder, &big2, &bigRemainder); } } mp_clear(&bigRemainder); break; } } mp_clear(&big1); mp_clear(&big2); BIG_RESULT(&bigResult); } Tcl_Panic("unexpected opcode"); return NULL; } static Tcl_Obj * ExecuteExtendedUnaryMathOp( int opcode, /* What operation to perform. */ Tcl_Obj *valuePtr) /* The operand on the stack. */ { void *ptr = NULL; int type; Tcl_WideInt w; mp_int big; Tcl_Obj *objResultPtr; mp_err err = MP_OKAY; (void) GetNumberFromObj(NULL, valuePtr, &ptr, &type); switch (opcode) { case INST_BITNOT: if (type == TCL_NUMBER_INT) { w = *((const Tcl_WideInt *) ptr); WIDE_RESULT(~w); } Tcl_TakeBignumFromObj(NULL, valuePtr, &big); /* ~a = - a - 1 */ err = mp_neg(&big, &big); if (err == MP_OKAY) { err = mp_sub_d(&big, 1, &big); } if (err != MP_OKAY) { return OUT_OF_MEMORY; } BIG_RESULT(&big); case INST_UMINUS: switch (type) { case TCL_NUMBER_DOUBLE: DOUBLE_RESULT(-(*((const double *) ptr))); case TCL_NUMBER_INT: w = *((const Tcl_WideInt *) ptr); if (w != WIDE_MIN) { WIDE_RESULT(-w); } err = mp_init_i64(&big, w); if (err != MP_OKAY) { return OUT_OF_MEMORY; } break; default: Tcl_TakeBignumFromObj(NULL, valuePtr, &big); } err = mp_neg(&big, &big); if (err != MP_OKAY) { return OUT_OF_MEMORY; } BIG_RESULT(&big); } Tcl_Panic("unexpected opcode"); return NULL; } #undef WIDE_RESULT #undef BIG_RESULT #undef DOUBLE_RESULT /* *---------------------------------------------------------------------- * * CompareTwoNumbers -- * * This function compares a pair of numbers in Tcl_Objs. Each argument * must already be known to be numeric and not NaN. * * Results: * One of MP_LT, MP_EQ or MP_GT, depending on whether valuePtr is less * than, equal to, or greater than value2Ptr (respectively). * * Side effects: * None, provided both values are numeric. * *---------------------------------------------------------------------- */ int TclCompareTwoNumbers( Tcl_Obj *valuePtr, Tcl_Obj *value2Ptr) { int type1 = TCL_NUMBER_NAN, type2 = TCL_NUMBER_NAN, compare; void *ptr1, *ptr2; mp_int big1, big2; double d1, d2, tmp; Tcl_WideInt w1, w2; (void) GetNumberFromObj(NULL, valuePtr, &ptr1, &type1); (void) GetNumberFromObj(NULL, value2Ptr, &ptr2, &type2); switch (type1) { case TCL_NUMBER_INT: w1 = *((const Tcl_WideInt *)ptr1); switch (type2) { case TCL_NUMBER_INT: w2 = *((const Tcl_WideInt *)ptr2); wideCompare: return (w1 < w2) ? MP_LT : ((w1 > w2) ? MP_GT : MP_EQ); case TCL_NUMBER_DOUBLE: d2 = *((const double *)ptr2); d1 = (double) w1; /* * If the double has a fractional part, or if the Tcl_WideInt can be * converted to double without loss of precision, then compare as * doubles. */ if (DBL_MANT_DIG > CHAR_BIT*sizeof(Tcl_WideInt) || w1 == (Tcl_WideInt)d1 || modf(d2, &tmp) != 0.0) { goto doubleCompare; } /* * Otherwise, to make comparision based on full precision, need to * convert the double to a suitably sized integer. * * Need this to get comparsions like * expr 20000000000000003 < 20000000000000004.0 * right. Converting the first argument to double will yield two * double values that are equivalent within double precision. * Converting the double to an integer gets done exactly, then * integer comparison can tell the difference. */ if (d2 < (double)WIDE_MIN) { return MP_GT; } if (d2 > (double)WIDE_MAX) { return MP_LT; } w2 = (Tcl_WideInt)d2; goto wideCompare; case TCL_NUMBER_BIG: Tcl_TakeBignumFromObj(NULL, value2Ptr, &big2); if (mp_isneg(&big2)) { compare = MP_GT; } else { compare = MP_LT; } mp_clear(&big2); return compare; } break; case TCL_NUMBER_DOUBLE: d1 = *((const double *)ptr1); switch (type2) { case TCL_NUMBER_DOUBLE: d2 = *((const double *)ptr2); doubleCompare: return (d1 < d2) ? MP_LT : ((d1 > d2) ? MP_GT : MP_EQ); case TCL_NUMBER_INT: w2 = *((const Tcl_WideInt *)ptr2); d2 = (double) w2; if (DBL_MANT_DIG > CHAR_BIT*sizeof(Tcl_WideInt) || w2 == (Tcl_WideInt)d2 || modf(d1, &tmp) != 0.0) { goto doubleCompare; } if (d1 < (double)WIDE_MIN) { return MP_LT; } if (d1 > (double)WIDE_MAX) { return MP_GT; } w1 = (Tcl_WideInt)d1; goto wideCompare; case TCL_NUMBER_BIG: if (isinf(d1)) { return (d1 > 0.0) ? MP_GT : MP_LT; } Tcl_TakeBignumFromObj(NULL, value2Ptr, &big2); if ((d1 < (double)WIDE_MAX) && (d1 > (double)WIDE_MIN)) { if (mp_isneg(&big2)) { compare = MP_GT; } else { compare = MP_LT; } mp_clear(&big2); return compare; } if (DBL_MANT_DIG > CHAR_BIT*sizeof(Tcl_WideInt) && modf(d1, &tmp) != 0.0) { d2 = TclBignumToDouble(&big2); mp_clear(&big2); goto doubleCompare; } Tcl_InitBignumFromDouble(NULL, d1, &big1); goto bigCompare; } break; case TCL_NUMBER_BIG: Tcl_TakeBignumFromObj(NULL, valuePtr, &big1); switch (type2) { case TCL_NUMBER_INT: compare = mp_cmp_d(&big1, 0); mp_clear(&big1); return compare; case TCL_NUMBER_DOUBLE: d2 = *((const double *)ptr2); if (isinf(d2)) { compare = (d2 > 0.0) ? MP_LT : MP_GT; mp_clear(&big1); return compare; } if ((d2 < (double)WIDE_MAX) && (d2 > (double)WIDE_MIN)) { compare = mp_cmp_d(&big1, 0); mp_clear(&big1); return compare; } if (DBL_MANT_DIG > CHAR_BIT*sizeof(Tcl_WideInt) && modf(d2, &tmp) != 0.0) { d1 = TclBignumToDouble(&big1); mp_clear(&big1); goto doubleCompare; } Tcl_InitBignumFromDouble(NULL, d2, &big2); goto bigCompare; case TCL_NUMBER_BIG: Tcl_TakeBignumFromObj(NULL, value2Ptr, &big2); bigCompare: compare = mp_cmp(&big1, &big2); mp_clear(&big1); mp_clear(&big2); return compare; } break; default: Tcl_Panic("unexpected number type"); } return TCL_ERROR; } #ifdef TCL_COMPILE_DEBUG /* *---------------------------------------------------------------------- * * PrintByteCodeInfo -- * * This procedure prints a summary about a bytecode object to stdout. It * is called by TclNRExecuteByteCode when starting to execute the bytecode * object if tclTraceExec has the value 2 or more. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void PrintByteCodeInfo( ByteCode *codePtr) /* The bytecode whose summary is printed to * stdout. */ { Proc *procPtr = codePtr->procPtr; Interp *iPtr = (Interp *) *codePtr->interpHandle; fprintf(stdout, "\nExecuting ByteCode 0x%p, refCt %" TCL_Z_MODIFIER "u, epoch %" TCL_Z_MODIFIER "u, interp 0x%p (epoch %" TCL_Z_MODIFIER "u)\n", codePtr, codePtr->refCount, codePtr->compileEpoch, iPtr, iPtr->compileEpoch); fprintf(stdout, " Source: "); TclPrintSource(stdout, codePtr->source, 60); fprintf(stdout, "\n Cmds %" TCL_Z_MODIFIER "u, src %" TCL_Z_MODIFIER "u, inst %" TCL_Z_MODIFIER "u, litObjs %" TCL_Z_MODIFIER "u, aux %" TCL_Z_MODIFIER "u, stkDepth %" TCL_Z_MODIFIER "u, code/src %.2f\n", codePtr->numCommands, codePtr->numSrcBytes, codePtr->numCodeBytes, codePtr->numLitObjects, codePtr->numAuxDataItems, codePtr->maxStackDepth, #ifdef TCL_COMPILE_STATS codePtr->numSrcBytes? ((float)codePtr->structureSize)/codePtr->numSrcBytes : #endif 0.0); #ifdef TCL_COMPILE_STATS fprintf(stdout, " Code %" TCL_Z_MODIFIER "u = header %" TCL_Z_MODIFIER "u+inst %" TCL_Z_MODIFIER "u+litObj %" TCL_Z_MODIFIER "u+exc %" TCL_Z_MODIFIER "u+aux %" TCL_Z_MODIFIER "u+cmdMap %" TCL_Z_MODIFIER "u\n", codePtr->structureSize, offsetof(ByteCode, localCachePtr), codePtr->numCodeBytes, codePtr->numLitObjects * sizeof(Tcl_Obj *), codePtr->numExceptRanges*sizeof(ExceptionRange), codePtr->numAuxDataItems * sizeof(AuxData), codePtr->numCmdLocBytes); #endif /* TCL_COMPILE_STATS */ if (procPtr != NULL) { fprintf(stdout, " Proc 0x%p, refCt %" TCL_Z_MODIFIER "u, args %" TCL_Z_MODIFIER "u, compiled locals %" TCL_Z_MODIFIER "u\n", procPtr, procPtr->refCount, procPtr->numArgs, procPtr->numCompiledLocals); } } #endif /* TCL_COMPILE_DEBUG */ /* *---------------------------------------------------------------------- * * ValidatePcAndStackTop -- * * This procedure is called by TclNRExecuteByteCode when debugging to * verify that the program counter and stack top are valid during * execution. * * Results: * None. * * Side effects: * Prints a message to stderr and panics if either the pc or stack top * are invalid. * *---------------------------------------------------------------------- */ #ifdef TCL_COMPILE_DEBUG static void ValidatePcAndStackTop( ByteCode *codePtr, /* The bytecode whose summary is printed to * stdout. */ const unsigned char *pc, /* Points to first byte of a bytecode * instruction. The program counter. */ size_t stackTop, /* Current stack top. Must be between * stackLowerBound and stackUpperBound * (inclusive). */ int checkStack) /* 0 if the stack depth check should be * skipped. */ { size_t stackUpperBound = codePtr->maxStackDepth; /* Greatest legal value for stackTop. */ size_t relativePc = (size_t)(pc - codePtr->codeStart); size_t codeStart = (size_t)codePtr->codeStart; size_t codeEnd = (size_t) (codePtr->codeStart + codePtr->numCodeBytes); unsigned char opCode = *pc; if ((PTR2UINT(pc) < codeStart) || (PTR2UINT(pc) > codeEnd)) { fprintf(stderr, "\nBad instruction pc 0x%p in TclNRExecuteByteCode\n", pc); Tcl_Panic("TclNRExecuteByteCode execution failure: bad pc"); } if (opCode >= LAST_INST_OPCODE) { fprintf(stderr, "\nBad opcode %u at pc %" TCL_Z_MODIFIER "u in TclNRExecuteByteCode\n", opCode, relativePc); Tcl_Panic("TclNRExecuteByteCode execution failure: bad opcode"); } if (checkStack && (stackTop > stackUpperBound)) { Tcl_Size numChars; const char *cmd = GetSrcInfoForPc(pc, codePtr, &numChars, NULL, NULL); fprintf(stderr, "\nBad stack top %" TCL_Z_MODIFIER "u at pc %" TCL_Z_MODIFIER "u in TclNRExecuteByteCode (min 0, max %" TCL_Z_MODIFIER "u)", stackTop, relativePc, stackUpperBound); if (cmd != NULL) { Tcl_Obj *message; TclNewLiteralStringObj(message, "\n executing "); Tcl_IncrRefCount(message); Tcl_AppendLimitedToObj(message, cmd, numChars, 100, NULL); fprintf(stderr, "%s\n", TclGetString(message)); Tcl_DecrRefCount(message); } else { fprintf(stderr, "\n"); } Tcl_Panic("TclNRExecuteByteCode execution failure: bad stack top"); } } #endif /* TCL_COMPILE_DEBUG */ /* *---------------------------------------------------------------------- * * IllegalExprOperandType -- * * Used by TclNRExecuteByteCode to append an error message to the interp * result when an illegal operand type is detected by an expression * instruction. The argument opndPtr holds the operand object in error. * * Results: * None. * * Side effects: * An error message is appended to the interp result. * *---------------------------------------------------------------------- */ static void IllegalExprOperandType( Tcl_Interp *interp, /* Interpreter to which error information * pertains. */ const char *ord, /* "first ", "second " or "" */ const unsigned char *pc, /* Points to the instruction being executed * when the illegal type was found. */ Tcl_Obj *opndPtr) /* Points to the operand holding the value * with the illegal type. */ { void *ptr; int type; const unsigned char opcode = *pc; const char *description, *op = "unknown"; if (opcode == INST_EXPON) { op = "**"; } else if (opcode <= INST_LNOT) { op = operatorStrings[opcode - INST_BITOR]; } if (GetNumberFromObj(NULL, opndPtr, &ptr, &type) != TCL_OK) { Tcl_Size length; if (TclHasInternalRep(opndPtr, &tclDictType)) { Tcl_DictObjSize(NULL, opndPtr, &length); if (length > 0) { listRep: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot use a list as %soperand of \"%s\"", ord, op)); Tcl_SetErrorCode(interp, "ARITH", "DOMAIN", "list", (char *)NULL); return; } } Tcl_ObjTypeLengthProc *lengthProc = TclObjTypeHasProc(opndPtr, lengthProc); Tcl_Size objcPtr; Tcl_Obj **objvPtr; if ((lengthProc && lengthProc(opndPtr) > 1) || ((TclMaxListLength(TclGetString(opndPtr), TCL_INDEX_NONE, NULL) > 1) && (Tcl_ListObjGetElements(NULL, opndPtr, &objcPtr, &objvPtr) == TCL_OK))) { goto listRep; } description = "non-numeric string"; } else if (type == TCL_NUMBER_NAN) { description = "non-numeric floating-point value"; } else if (type == TCL_NUMBER_DOUBLE) { description = "floating-point value"; } else { /* TODO: No caller needs this. Eliminate? */ description = "(big) integer"; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot use %s \"%s\" as %soperand of \"%s\"", description, TclGetString(opndPtr), ord, op)); Tcl_SetErrorCode(interp, "ARITH", "DOMAIN", description, (char *)NULL); } /* *---------------------------------------------------------------------- * * TclGetSrcInfoForPc, GetSrcInfoForPc, TclGetSourceFromFrame -- * * Given a program counter value, finds the closest command in the * bytecode code unit's CmdLocation array and returns information about * that command's source: a pointer to its first byte and the number of * characters. * * Results: * If a command is found that encloses the program counter value, a * pointer to the command's source is returned and the length of the * source is stored at *lengthPtr. If multiple commands resulted in code * at pc, information about the closest enclosing command is returned. If * no matching command is found, NULL is returned and *lengthPtr is * unchanged. * * Side effects: * The CmdFrame at *cfPtr is updated. * *---------------------------------------------------------------------- */ Tcl_Obj * TclGetSourceFromFrame( CmdFrame *cfPtr, Tcl_Size objc, Tcl_Obj *const objv[]) { if (cfPtr == NULL) { return Tcl_NewListObj(objc, objv); } if (cfPtr->cmdObj == NULL) { if (cfPtr->cmd == NULL) { ByteCode *codePtr = (ByteCode *)cfPtr->data.tebc.codePtr; cfPtr->cmd = GetSrcInfoForPc((unsigned char *) cfPtr->data.tebc.pc, codePtr, &cfPtr->len, NULL, NULL); } if (cfPtr->cmd) { cfPtr->cmdObj = Tcl_NewStringObj(cfPtr->cmd, cfPtr->len); } else { cfPtr->cmdObj = Tcl_NewListObj(objc, objv); } Tcl_IncrRefCount(cfPtr->cmdObj); } return cfPtr->cmdObj; } void TclGetSrcInfoForPc( CmdFrame *cfPtr) { ByteCode *codePtr = (ByteCode *) cfPtr->data.tebc.codePtr; assert(cfPtr->type == TCL_LOCATION_BC); if (cfPtr->cmd == NULL) { cfPtr->cmd = GetSrcInfoForPc( (unsigned char *) cfPtr->data.tebc.pc, codePtr, &cfPtr->len, NULL, NULL); } if (cfPtr->cmd != NULL) { /* * We now have the command. We can get the srcOffset back and from * there find the list of word locations for this command. */ ExtCmdLoc *eclPtr; ECL *locPtr = NULL; Tcl_Size srcOffset; Tcl_Size i; Interp *iPtr = (Interp *) *codePtr->interpHandle; Tcl_HashEntry *hePtr = Tcl_FindHashEntry(iPtr->lineBCPtr, codePtr); if (!hePtr) { return; } srcOffset = cfPtr->cmd - codePtr->source; eclPtr = (ExtCmdLoc *)Tcl_GetHashValue(hePtr); for (i=0; i < eclPtr->nuloc; i++) { if (eclPtr->loc[i].srcOffset == srcOffset) { locPtr = eclPtr->loc+i; break; } } if (locPtr == NULL) { Tcl_Panic("LocSearch failure"); } cfPtr->line = locPtr->line; cfPtr->nline = locPtr->nline; cfPtr->type = eclPtr->type; if (eclPtr->type == TCL_LOCATION_SOURCE) { cfPtr->data.eval.path = eclPtr->path; Tcl_IncrRefCount(cfPtr->data.eval.path); } /* * Do not set cfPtr->data.eval.path NULL for non-SOURCE. Needed for * cfPtr->data.tebc.codePtr. */ } } static const char * GetSrcInfoForPc( const unsigned char *pc, /* The program counter value for which to * return the closest command's source info. * This points within a bytecode instruction * in codePtr's code. */ ByteCode *codePtr, /* The bytecode sequence in which to look up * the command source for the pc. */ Tcl_Size *lengthPtr, /* If non-NULL, the location where the length * of the command's source should be stored. * If NULL, no length is stored. */ const unsigned char **pcBeg,/* If non-NULL, the bytecode location * where the current instruction starts. * If NULL; no pointer is stored. */ Tcl_Size *cmdIdxPtr) /* If non-NULL, the location where the index * of the command containing the pc should * be stored. */ { Tcl_Size pcOffset = pc - codePtr->codeStart; Tcl_Size numCmds = codePtr->numCommands; unsigned char *codeDeltaNext, *codeLengthNext; unsigned char *srcDeltaNext, *srcLengthNext; Tcl_Size codeOffset, codeLen, codeEnd, srcOffset, srcLen, delta, i; Tcl_Size bestDist = TCL_SIZE_MAX; /* Distance of pc to best cmd's start pc. */ Tcl_Size bestSrcOffset = -1; /* Initialized to avoid compiler warning. */ Tcl_Size bestSrcLength = -1; /* Initialized to avoid compiler warning. */ Tcl_Size bestCmdIdx = -1; /* The pc must point within the bytecode */ assert ((pcOffset >= 0) && (pcOffset < codePtr->numCodeBytes)); /* * Decode the code and source offset and length for each command. The * closest enclosing command is the last one whose code started before * pcOffset. */ codeDeltaNext = codePtr->codeDeltaStart; codeLengthNext = codePtr->codeLengthStart; srcDeltaNext = codePtr->srcDeltaStart; srcLengthNext = codePtr->srcLengthStart; codeOffset = srcOffset = 0; for (i = 0; i < numCmds; i++) { if ((unsigned) *codeDeltaNext == (unsigned) 0xFF) { codeDeltaNext++; delta = TclGetInt4AtPtr(codeDeltaNext); codeDeltaNext += 4; } else { delta = TclGetInt1AtPtr(codeDeltaNext); codeDeltaNext++; } codeOffset += delta; if ((unsigned) *codeLengthNext == (unsigned) 0xFF) { codeLengthNext++; codeLen = TclGetInt4AtPtr(codeLengthNext); codeLengthNext += 4; } else { codeLen = TclGetInt1AtPtr(codeLengthNext); codeLengthNext++; } codeEnd = (codeOffset + codeLen - 1); if ((unsigned) *srcDeltaNext == (unsigned) 0xFF) { srcDeltaNext++; delta = TclGetInt4AtPtr(srcDeltaNext); srcDeltaNext += 4; } else { delta = TclGetInt1AtPtr(srcDeltaNext); srcDeltaNext++; } srcOffset += delta; if ((unsigned) *srcLengthNext == (unsigned) 0xFF) { srcLengthNext++; srcLen = TclGetInt4AtPtr(srcLengthNext); srcLengthNext += 4; } else { srcLen = TclGetInt1AtPtr(srcLengthNext); srcLengthNext++; } if (codeOffset > pcOffset) { /* Best cmd already found */ break; } if (pcOffset <= codeEnd) { /* This cmd's code encloses pc */ int dist = (pcOffset - codeOffset); if (dist <= bestDist) { bestDist = dist; bestSrcOffset = srcOffset; bestSrcLength = srcLen; bestCmdIdx = i; } } } if (pcBeg != NULL) { const unsigned char *curr, *prev; /* * Walk from beginning of command or BC to pc, by complete * instructions. Stop when crossing pc; keep previous. */ curr = ((bestDist == TCL_SIZE_MAX) ? codePtr->codeStart : pc - bestDist); prev = curr; while (curr <= pc) { prev = curr; curr += tclInstructionTable[*curr].numBytes; } *pcBeg = prev; } if (bestDist == TCL_SIZE_MAX) { return NULL; } if (lengthPtr != NULL) { *lengthPtr = bestSrcLength; } if (cmdIdxPtr != NULL) { *cmdIdxPtr = bestCmdIdx; } return (codePtr->source + bestSrcOffset); } /* *---------------------------------------------------------------------- * * GetExceptRangeForPc -- * * Given a program counter value, return the closest enclosing * ExceptionRange. * * Results: * If the searchMode is TCL_ERROR, this procedure ignores loop exception * ranges and returns a pointer to the closest catch range. If the * searchMode is TCL_BREAK, this procedure returns a pointer to the most * closely enclosing ExceptionRange regardless of whether it is a loop or * catch exception range. If the searchMode is TCL_CONTINUE, this * procedure returns a pointer to the most closely enclosing * ExceptionRange (of any type) skipping only loop exception ranges if * they don't have a sensible continueOffset defined. If no matching * ExceptionRange is found that encloses pc, a NULL is returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ static ExceptionRange * GetExceptRangeForPc( const unsigned char *pc, /* The program counter value for which to * search for a closest enclosing exception * range. This points to a bytecode * instruction in codePtr's code. */ int searchMode, /* If TCL_BREAK, consider either loop or catch * ExceptionRanges in search. If TCL_ERROR * consider only catch ranges (and ignore any * closer loop ranges). If TCL_CONTINUE, look * for loop ranges that define a continue * point or a catch range. */ ByteCode *codePtr) /* Points to the ByteCode in which to search * for the enclosing ExceptionRange. */ { ExceptionRange *rangeArrayPtr; size_t numRanges = codePtr->numExceptRanges; ExceptionRange *rangePtr; size_t pcOffset = pc - codePtr->codeStart; size_t start; if (numRanges == 0) { return NULL; } /* * This exploits peculiarities of our compiler: nested ranges are always * *after* their containing ranges, so that by scanning backwards we are * sure that the first matching range is indeed the deepest. */ rangeArrayPtr = codePtr->exceptArrayPtr; rangePtr = rangeArrayPtr + numRanges; while (--rangePtr >= rangeArrayPtr) { start = rangePtr->codeOffset; if ((start <= pcOffset) && (pcOffset < (start + rangePtr->numCodeBytes))) { if (rangePtr->type == CATCH_EXCEPTION_RANGE) { return rangePtr; } if (searchMode == TCL_BREAK) { return rangePtr; } if (searchMode == TCL_CONTINUE && rangePtr->continueOffset != TCL_INDEX_NONE){ return rangePtr; } } } return NULL; } /* *---------------------------------------------------------------------- * * GetOpcodeName -- * * This procedure is called by the TRACE and TRACE_WITH_OBJ macros used * in TclNRExecuteByteCode when debugging. It returns the name of the * bytecode instruction at a specified instruction pc. * * Results: * A character string for the instruction. * * Side effects: * None. * *---------------------------------------------------------------------- */ #ifdef TCL_COMPILE_DEBUG static const char * GetOpcodeName( const unsigned char *pc) /* Points to the instruction whose name should * be returned. */ { unsigned char opCode = *pc; return tclInstructionTable[opCode].name; } #endif /* TCL_COMPILE_DEBUG */ /* *---------------------------------------------------------------------- * * TclExprFloatError -- * * This procedure is called when an error occurs during a floating-point * operation. It reads errno and sets interp->objResultPtr accordingly. * * Results: * interp->objResultPtr is set to hold an error message. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclExprFloatError( Tcl_Interp *interp, /* Where to store error message. */ double value) /* Value returned after error; used to * distinguish underflows from overflows. */ { const char *s; if ((errno == EDOM) || isnan(value)) { s = "domain error: argument not in valid range"; Tcl_SetObjResult(interp, Tcl_NewStringObj(s, -1)); Tcl_SetErrorCode(interp, "ARITH", "DOMAIN", s, (char *)NULL); } else if ((errno == ERANGE) || isinf(value)) { if (value == 0.0) { s = "floating-point value too small to represent"; Tcl_SetObjResult(interp, Tcl_NewStringObj(s, -1)); Tcl_SetErrorCode(interp, "ARITH", "UNDERFLOW", s, (char *)NULL); } else { s = "floating-point value too large to represent"; Tcl_SetObjResult(interp, Tcl_NewStringObj(s, -1)); Tcl_SetErrorCode(interp, "ARITH", "OVERFLOW", s, (char *)NULL); } } else { Tcl_Obj *objPtr = Tcl_ObjPrintf( "unknown floating-point error, errno = %d", errno); Tcl_SetErrorCode(interp, "ARITH", "UNKNOWN", TclGetString(objPtr), (char *)NULL); Tcl_SetObjResult(interp, objPtr); } } #ifdef TCL_COMPILE_STATS /* *---------------------------------------------------------------------- * * TclLog2 -- * * Procedure used while collecting compilation statistics to determine * the log base 2 of an integer. * * Results: * Returns the log base 2 of the operand. If the argument is less than or * equal to zero, a zero is returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclLog2( int value) /* The integer for which to compute the log * base 2. */ { int n = value; int result = 0; while (n > 1) { n = n >> 1; result++; } return result; } /* *---------------------------------------------------------------------- * * EvalStatsCmd -- * * Implements the "evalstats" command that prints instruction execution * counts to stdout. * * Results: * Standard Tcl results. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int EvalStatsCmd( TCL_UNUSED(void *), /* Unused. */ Tcl_Interp *interp, /* The current interpreter. */ int objc, /* The number of arguments. */ Tcl_Obj *const objv[]) /* The argument strings. */ { Interp *iPtr = (Interp *) interp; LiteralTable *globalTablePtr = &iPtr->literalTable; ByteCodeStats *statsPtr = &iPtr->stats; double totalCodeBytes, currentCodeBytes; double totalLiteralBytes, currentLiteralBytes; double objBytesIfUnshared, strBytesIfUnshared, sharingBytesSaved; double strBytesSharedMultX, strBytesSharedOnce; double numInstructions, currentHeaderBytes; size_t numCurrentByteCodes, numByteCodeLits; size_t refCountSum, literalMgmtBytes, sum, decadeHigh; size_t numSharedMultX, numSharedOnce, minSizeDecade, maxSizeDecade; Tcl_Size i, length; size_t ui; char *litTableStats; LiteralEntry *entryPtr; Tcl_Obj *objPtr; #define Percent(a,b) ((a) * 100.0 / (b)) TclNewObj(objPtr); Tcl_IncrRefCount(objPtr); numInstructions = 0.0; for (i = 0; i < 256; i++) { if (statsPtr->instructionCount[i] != 0) { numInstructions += statsPtr->instructionCount[i]; } } totalLiteralBytes = sizeof(LiteralTable) + iPtr->literalTable.numBuckets * sizeof(LiteralEntry *) + (statsPtr->numLiteralsCreated * sizeof(LiteralEntry)) + (statsPtr->numLiteralsCreated * sizeof(Tcl_Obj)) + statsPtr->totalLitStringBytes; totalCodeBytes = statsPtr->totalByteCodeBytes + totalLiteralBytes; numCurrentByteCodes = statsPtr->numCompilations - statsPtr->numByteCodesFreed; currentHeaderBytes = numCurrentByteCodes * offsetof(ByteCode, localCachePtr); literalMgmtBytes = sizeof(LiteralTable) + (iPtr->literalTable.numBuckets * sizeof(LiteralEntry *)) + (iPtr->literalTable.numEntries * sizeof(LiteralEntry)); currentLiteralBytes = literalMgmtBytes + iPtr->literalTable.numEntries * sizeof(Tcl_Obj) + statsPtr->currentLitStringBytes; currentCodeBytes = statsPtr->currentByteCodeBytes + currentLiteralBytes; /* * Summary statistics, total and current source and ByteCode sizes. */ Tcl_AppendPrintfToObj(objPtr, "\n----------------------------------------------------------------\n"); Tcl_AppendPrintfToObj(objPtr, "Compilation and execution statistics for interpreter %p\n", iPtr); Tcl_AppendPrintfToObj(objPtr, "\nNumber ByteCodes executed\t%" TCL_Z_MODIFIER "u\n", statsPtr->numExecutions); Tcl_AppendPrintfToObj(objPtr, "Number ByteCodes compiled\t%" TCL_Z_MODIFIER "u\n", statsPtr->numCompilations); Tcl_AppendPrintfToObj(objPtr, " Mean executions/compile\t%.1f\n", statsPtr->numExecutions / (float)statsPtr->numCompilations); Tcl_AppendPrintfToObj(objPtr, "\nInstructions executed\t\t%.0f\n", numInstructions); Tcl_AppendPrintfToObj(objPtr, " Mean inst/compile\t\t%.0f\n", numInstructions / statsPtr->numCompilations); Tcl_AppendPrintfToObj(objPtr, " Mean inst/execution\t\t%.0f\n", numInstructions / statsPtr->numExecutions); Tcl_AppendPrintfToObj(objPtr, "\nTotal ByteCodes\t\t\t%" TCL_Z_MODIFIER "u\n", statsPtr->numCompilations); Tcl_AppendPrintfToObj(objPtr, " Source bytes\t\t\t%.6g\n", statsPtr->totalSrcBytes); Tcl_AppendPrintfToObj(objPtr, " Code bytes\t\t\t%.6g\n", totalCodeBytes); Tcl_AppendPrintfToObj(objPtr, " ByteCode bytes\t\t%.6g\n", statsPtr->totalByteCodeBytes); Tcl_AppendPrintfToObj(objPtr, " Literal bytes\t\t%.6g\n", totalLiteralBytes); Tcl_AppendPrintfToObj(objPtr, " table %" TCL_Z_MODIFIER "u + bkts %" TCL_Z_MODIFIER "u + entries %" TCL_Z_MODIFIER "u + objects %" TCL_Z_MODIFIER "u + strings %.6g\n", sizeof(LiteralTable), iPtr->literalTable.numBuckets * sizeof(LiteralEntry *), statsPtr->numLiteralsCreated * sizeof(LiteralEntry), statsPtr->numLiteralsCreated * sizeof(Tcl_Obj), statsPtr->totalLitStringBytes); Tcl_AppendPrintfToObj(objPtr, " Mean code/compile\t\t%.1f\n", totalCodeBytes / statsPtr->numCompilations); Tcl_AppendPrintfToObj(objPtr, " Mean code/source\t\t%.1f\n", totalCodeBytes / statsPtr->totalSrcBytes); Tcl_AppendPrintfToObj(objPtr, "\nCurrent (active) ByteCodes\t%" TCL_Z_MODIFIER "u\n", numCurrentByteCodes); Tcl_AppendPrintfToObj(objPtr, " Source bytes\t\t\t%.6g\n", statsPtr->currentSrcBytes); Tcl_AppendPrintfToObj(objPtr, " Code bytes\t\t\t%.6g\n", currentCodeBytes); Tcl_AppendPrintfToObj(objPtr, " ByteCode bytes\t\t%.6g\n", statsPtr->currentByteCodeBytes); Tcl_AppendPrintfToObj(objPtr, " Literal bytes\t\t%.6g\n", currentLiteralBytes); Tcl_AppendPrintfToObj(objPtr, " table %" TCL_Z_MODIFIER "u + bkts %" TCL_Z_MODIFIER "u + entries %" TCL_Z_MODIFIER "u + objects %" TCL_Z_MODIFIER "u + strings %.6g\n", sizeof(LiteralTable), iPtr->literalTable.numBuckets * sizeof(LiteralEntry *), iPtr->literalTable.numEntries * sizeof(LiteralEntry), iPtr->literalTable.numEntries * sizeof(Tcl_Obj), statsPtr->currentLitStringBytes); Tcl_AppendPrintfToObj(objPtr, " Mean code/source\t\t%.1f\n", currentCodeBytes / statsPtr->currentSrcBytes); Tcl_AppendPrintfToObj(objPtr, " Code + source bytes\t\t%.6g (%0.1f mean code/src)\n", (currentCodeBytes + statsPtr->currentSrcBytes), (currentCodeBytes / statsPtr->currentSrcBytes) + 1.0); /* * Tcl_IsShared statistics check * * This gives the refcount of each obj as Tcl_IsShared was called for it. * Shared objects must be duplicated before they can be modified. */ numSharedMultX = 0; Tcl_AppendPrintfToObj(objPtr, "\nTcl_IsShared object check (all objects):\n"); Tcl_AppendPrintfToObj(objPtr, " Object had refcount <=1 (not shared)\t%" TCL_Z_MODIFIER "u\n", tclObjsShared[1]); for (i = 2; i < TCL_MAX_SHARED_OBJ_STATS; i++) { Tcl_AppendPrintfToObj(objPtr, " refcount ==%" TCL_Z_MODIFIER "u\t\t%" TCL_Z_MODIFIER "u\n", i, tclObjsShared[i]); numSharedMultX += tclObjsShared[i]; } Tcl_AppendPrintfToObj(objPtr, " refcount >=%" TCL_Z_MODIFIER "u\t\t%" TCL_Z_MODIFIER "u\n", i, tclObjsShared[0]); numSharedMultX += tclObjsShared[0]; Tcl_AppendPrintfToObj(objPtr, " Total shared objects\t\t\t%" TCL_Z_MODIFIER "u\n", numSharedMultX); /* * Literal table statistics. */ numByteCodeLits = 0; refCountSum = 0; numSharedMultX = 0; numSharedOnce = 0; objBytesIfUnshared = 0.0; strBytesIfUnshared = 0.0; strBytesSharedMultX = 0.0; strBytesSharedOnce = 0.0; for (ui = 0; ui < globalTablePtr->numBuckets; ui++) { for (entryPtr = globalTablePtr->buckets[i]; entryPtr != NULL; entryPtr = entryPtr->nextPtr) { if (TclHasInternalRep(entryPtr->objPtr, &tclByteCodeType)) { numByteCodeLits++; } (void) TclGetStringFromObj(entryPtr->objPtr, &length); refCountSum += entryPtr->refCount; objBytesIfUnshared += (entryPtr->refCount * sizeof(Tcl_Obj)); strBytesIfUnshared += (entryPtr->refCount * (length+1)); if (entryPtr->refCount > 1) { numSharedMultX++; strBytesSharedMultX += (length+1); } else { numSharedOnce++; strBytesSharedOnce += (length+1); } } } sharingBytesSaved = (objBytesIfUnshared + strBytesIfUnshared) - currentLiteralBytes; Tcl_AppendPrintfToObj(objPtr, "\nTotal objects (all interps)\t%" TCL_Z_MODIFIER "u\n", tclObjsAlloced); Tcl_AppendPrintfToObj(objPtr, "Current objects\t\t\t%" TCL_Z_MODIFIER "u\n", (tclObjsAlloced - tclObjsFreed)); Tcl_AppendPrintfToObj(objPtr, "Total literal objects\t\t%" TCL_Z_MODIFIER "u\n", statsPtr->numLiteralsCreated); Tcl_AppendPrintfToObj(objPtr, "\nCurrent literal objects\t\t%" TCL_SIZE_MODIFIER "d (%0.1f%% of current objects)\n", globalTablePtr->numEntries, Percent(globalTablePtr->numEntries, tclObjsAlloced-tclObjsFreed)); Tcl_AppendPrintfToObj(objPtr, " ByteCode literals\t\t%" TCL_Z_MODIFIER "u (%0.1f%% of current literals)\n", numByteCodeLits, Percent(numByteCodeLits, globalTablePtr->numEntries)); Tcl_AppendPrintfToObj(objPtr, " Literals reused > 1x\t\t%" TCL_Z_MODIFIER "u\n", numSharedMultX); Tcl_AppendPrintfToObj(objPtr, " Mean reference count\t\t%.2f\n", ((double) refCountSum) / globalTablePtr->numEntries); Tcl_AppendPrintfToObj(objPtr, " Mean len, str reused >1x \t%.2f\n", (numSharedMultX ? strBytesSharedMultX/numSharedMultX : 0.0)); Tcl_AppendPrintfToObj(objPtr, " Mean len, str used 1x\t\t%.2f\n", (numSharedOnce ? strBytesSharedOnce/numSharedOnce : 0.0)); Tcl_AppendPrintfToObj(objPtr, " Total sharing savings\t\t%.6g (%0.1f%% of bytes if no sharing)\n", sharingBytesSaved, Percent(sharingBytesSaved, objBytesIfUnshared+strBytesIfUnshared)); Tcl_AppendPrintfToObj(objPtr, " Bytes with sharing\t\t%.6g\n", currentLiteralBytes); Tcl_AppendPrintfToObj(objPtr, " table %lu + bkts %lu + entries %lu + objects %lu + strings %.6g\n", (unsigned long) sizeof(LiteralTable), (unsigned long) (iPtr->literalTable.numBuckets * sizeof(LiteralEntry *)), (unsigned long) (iPtr->literalTable.numEntries * sizeof(LiteralEntry)), (unsigned long) (iPtr->literalTable.numEntries * sizeof(Tcl_Obj)), statsPtr->currentLitStringBytes); Tcl_AppendPrintfToObj(objPtr, " Bytes if no sharing\t\t%.6g = objects %.6g + strings %.6g\n", (objBytesIfUnshared + strBytesIfUnshared), objBytesIfUnshared, strBytesIfUnshared); Tcl_AppendPrintfToObj(objPtr, " String sharing savings \t%.6g = unshared %.6g - shared %.6g\n", (strBytesIfUnshared - statsPtr->currentLitStringBytes), strBytesIfUnshared, statsPtr->currentLitStringBytes); Tcl_AppendPrintfToObj(objPtr, " Literal mgmt overhead\t\t%" TCL_Z_MODIFIER "u (%0.1f%% of bytes with sharing)\n", literalMgmtBytes, Percent(literalMgmtBytes, currentLiteralBytes)); Tcl_AppendPrintfToObj(objPtr, " table %lu + buckets %lu + entries %lu\n", (unsigned long) sizeof(LiteralTable), (unsigned long) (iPtr->literalTable.numBuckets * sizeof(LiteralEntry *)), (unsigned long) (iPtr->literalTable.numEntries * sizeof(LiteralEntry))); /* * Breakdown of current ByteCode space requirements. */ Tcl_AppendPrintfToObj(objPtr, "\nBreakdown of current ByteCode requirements:\n"); Tcl_AppendPrintfToObj(objPtr, " Bytes Pct of Avg per\n"); Tcl_AppendPrintfToObj(objPtr, " total ByteCode\n"); Tcl_AppendPrintfToObj(objPtr, "Total %12.6g 100.00%% %8.1f\n", statsPtr->currentByteCodeBytes, statsPtr->currentByteCodeBytes / numCurrentByteCodes); Tcl_AppendPrintfToObj(objPtr, "Header %12.6g %8.1f%% %8.1f\n", currentHeaderBytes, Percent(currentHeaderBytes, statsPtr->currentByteCodeBytes), currentHeaderBytes / numCurrentByteCodes); Tcl_AppendPrintfToObj(objPtr, "Instructions %12.6g %8.1f%% %8.1f\n", statsPtr->currentInstBytes, Percent(statsPtr->currentInstBytes, statsPtr->currentByteCodeBytes), statsPtr->currentInstBytes / numCurrentByteCodes); Tcl_AppendPrintfToObj(objPtr, "Literal ptr array %12.6g %8.1f%% %8.1f\n", statsPtr->currentLitBytes, Percent(statsPtr->currentLitBytes, statsPtr->currentByteCodeBytes), statsPtr->currentLitBytes / numCurrentByteCodes); Tcl_AppendPrintfToObj(objPtr, "Exception table %12.6g %8.1f%% %8.1f\n", statsPtr->currentExceptBytes, Percent(statsPtr->currentExceptBytes, statsPtr->currentByteCodeBytes), statsPtr->currentExceptBytes / numCurrentByteCodes); Tcl_AppendPrintfToObj(objPtr, "Auxiliary data %12.6g %8.1f%% %8.1f\n", statsPtr->currentAuxBytes, Percent(statsPtr->currentAuxBytes, statsPtr->currentByteCodeBytes), statsPtr->currentAuxBytes / numCurrentByteCodes); Tcl_AppendPrintfToObj(objPtr, "Command map %12.6g %8.1f%% %8.1f\n", statsPtr->currentCmdMapBytes, Percent(statsPtr->currentCmdMapBytes, statsPtr->currentByteCodeBytes), statsPtr->currentCmdMapBytes / numCurrentByteCodes); /* * Detailed literal statistics. */ Tcl_AppendPrintfToObj(objPtr, "\nLiteral string sizes:\n"); Tcl_AppendPrintfToObj(objPtr, "\t Up to length\t\tPercentage\n"); maxSizeDecade = 0; i = 32; while (i-- > 0) { if (statsPtr->literalCount[i] > 0) { maxSizeDecade = i; break; } } sum = 0; for (ui = 0; ui <= maxSizeDecade; ui++) { decadeHigh = (1 << (ui+1)) - 1; sum += statsPtr->literalCount[ui]; Tcl_AppendPrintfToObj(objPtr, "\t%10" TCL_SIZE_MODIFIER "d\t\t%8.0f%%\n", decadeHigh, Percent(sum, statsPtr->numLiteralsCreated)); } litTableStats = TclLiteralStats(globalTablePtr); Tcl_AppendPrintfToObj(objPtr, "\nCurrent literal table statistics:\n%s\n", litTableStats); Tcl_Free(litTableStats); /* * Source and ByteCode size distributions. */ Tcl_AppendPrintfToObj(objPtr, "\nSource sizes:\n"); Tcl_AppendPrintfToObj(objPtr, "\t Up to size\t\tPercentage\n"); minSizeDecade = maxSizeDecade = 0; for (i = 0; i < 31; i++) { if (statsPtr->srcCount[i] > 0) { minSizeDecade = i; break; } } for (i = 31; i != TCL_INDEX_NONE; i--) { if (statsPtr->srcCount[i] > 0) { break; /* maxSizeDecade to consume 'i' value * below... */ } } maxSizeDecade = i; sum = 0; for (ui = minSizeDecade; ui <= maxSizeDecade; ui++) { decadeHigh = (1 << (ui+1)) - 1; sum += statsPtr->srcCount[ui]; Tcl_AppendPrintfToObj(objPtr, "\t%10" TCL_SIZE_MODIFIER "d\t\t%8.0f%%\n", decadeHigh, Percent(sum, statsPtr->numCompilations)); } Tcl_AppendPrintfToObj(objPtr, "\nByteCode sizes:\n"); Tcl_AppendPrintfToObj(objPtr, "\t Up to size\t\tPercentage\n"); minSizeDecade = maxSizeDecade = 0; for (i = 0; i < 31; i++) { if (statsPtr->byteCodeCount[i] > 0) { minSizeDecade = i; break; } } for (i = 31; i != TCL_INDEX_NONE; i--) { if (statsPtr->byteCodeCount[i] > 0) { break; /* maxSizeDecade to consume 'i' value * below... */ } } maxSizeDecade = i; sum = 0; for (ui = minSizeDecade; ui <= maxSizeDecade; i++) { decadeHigh = (1 << (ui+1)) - 1; sum += statsPtr->byteCodeCount[ui]; Tcl_AppendPrintfToObj(objPtr, "\t%10" TCL_SIZE_MODIFIER "d\t\t%8.0f%%\n", decadeHigh, Percent(sum, statsPtr->numCompilations)); } Tcl_AppendPrintfToObj(objPtr, "\nByteCode longevity (excludes Current ByteCodes):\n"); Tcl_AppendPrintfToObj(objPtr, "\t Up to ms\t\tPercentage\n"); minSizeDecade = maxSizeDecade = 0; for (i = 0; i < 31; i++) { if (statsPtr->lifetimeCount[i] > 0) { minSizeDecade = i; break; } } for (i = 31; i != TCL_INDEX_NONE; i--) { if (statsPtr->lifetimeCount[i] > 0) { break; /* maxSizeDecade to consume 'i' value * below... */ } } maxSizeDecade = i; sum = 0; for (ui = minSizeDecade; ui <= maxSizeDecade; ui++) { decadeHigh = (1 << (ui+1)) - 1; sum += statsPtr->lifetimeCount[ui]; Tcl_AppendPrintfToObj(objPtr, "\t%12.3f\t\t%8.0f%%\n", decadeHigh/1000.0, Percent(sum, statsPtr->numByteCodesFreed)); } /* * Instruction counts. */ Tcl_AppendPrintfToObj(objPtr, "\nInstruction counts:\n"); for (i = 0; i < LAST_INST_OPCODE; i++) { Tcl_AppendPrintfToObj(objPtr, "%20s %8" TCL_Z_MODIFIER "u ", tclInstructionTable[i].name, statsPtr->instructionCount[i]); if (statsPtr->instructionCount[i]) { Tcl_AppendPrintfToObj(objPtr, "%6.1f%%\n", Percent(statsPtr->instructionCount[i], numInstructions)); } else { Tcl_AppendPrintfToObj(objPtr, "0\n"); } } #ifdef TCL_MEM_DEBUG Tcl_AppendPrintfToObj(objPtr, "\nHeap Statistics:\n"); TclDumpMemoryInfo(objPtr, 1); #endif Tcl_AppendPrintfToObj(objPtr, "\n----------------------------------------------------------------\n"); if (objc == 1) { Tcl_SetObjResult(interp, objPtr); } else { Tcl_Channel outChan; char *str = TclGetStringFromObj(objv[1], &length); if (length) { if (strcmp(str, "stdout") == 0) { outChan = Tcl_GetStdChannel(TCL_STDOUT); } else if (strcmp(str, "stderr") == 0) { outChan = Tcl_GetStdChannel(TCL_STDERR); } else { outChan = Tcl_OpenFileChannel(NULL, str, "w", 0664); } } else { outChan = Tcl_GetStdChannel(TCL_STDOUT); } if (outChan != NULL) { Tcl_WriteObj(outChan, objPtr); } } Tcl_DecrRefCount(objPtr); return TCL_OK; } #endif /* TCL_COMPILE_STATS */ #ifdef TCL_COMPILE_DEBUG /* *---------------------------------------------------------------------- * * StringForResultCode -- * * Procedure that returns a human-readable string representing a Tcl * result code such as TCL_ERROR. * * Results: * If the result code is one of the standard Tcl return codes, the result * is a string representing that code such as "TCL_ERROR". Otherwise, the * result string is that code formatted as a sequence of decimal digit * characters. Note that the resulting string must not be modified by the * caller. * * Side effects: * None. * *---------------------------------------------------------------------- */ static const char * StringForResultCode( int result) /* The Tcl result code for which to generate a * string. */ { static char buf[TCL_INTEGER_SPACE]; if ((result >= TCL_OK) && (result <= TCL_CONTINUE)) { return resultStrings[result]; } TclFormatInt(buf, result); return buf; } #endif /* TCL_COMPILE_DEBUG */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclFCmd.c0000644000175000017500000013234314726623136014660 0ustar sergeisergei/* * tclFCmd.c * * This file implements the generic portion of file manipulation * subcommands of the "file" command. * * Copyright © 1996-1998 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclFileSystem.h" /* * Declarations for local functions defined in this file: */ static int CopyRenameOneFile(Tcl_Interp *interp, Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr, int copyFlag, int force); static Tcl_Obj * FileBasename(Tcl_Interp *interp, Tcl_Obj *pathPtr); static int FileCopyRename(Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], int copyFlag); static int FileForceOption(Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], int *forcePtr); /* *--------------------------------------------------------------------------- * * TclFileRenameCmd * * This function implements the "rename" subcommand of the "file" * command. Filename arguments need to be translated to native format * before being passed to platform-specific code that implements rename * functionality. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *--------------------------------------------------------------------------- */ int TclFileRenameCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interp for error reporting or recursive * calls in the case of a tricky rename. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument strings passed to Tcl_FileCmd. */ { return FileCopyRename(interp, objc, objv, 0); } /* *--------------------------------------------------------------------------- * * TclFileCopyCmd * * This function implements the "copy" subcommand of the "file" command. * Filename arguments need to be translated to native format before being * passed to platform-specific code that implements copy functionality. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *--------------------------------------------------------------------------- */ int TclFileCopyCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Used for error reporting or recursive calls * in the case of a tricky copy. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument strings passed to Tcl_FileCmd. */ { return FileCopyRename(interp, objc, objv, 1); } /* *--------------------------------------------------------------------------- * * FileCopyRename -- * * Performs the work of TclFileRenameCmd and TclFileCopyCmd. See * comments for those functions. * * Results: * See above. * * Side effects: * See above. * *--------------------------------------------------------------------------- */ static int FileCopyRename( Tcl_Interp *interp, /* Used for error reporting. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[], /* Argument strings passed to Tcl_FileCmd. */ int copyFlag) /* If non-zero, copy source(s). Otherwise, * rename them. */ { int i, result, force; Tcl_StatBuf statBuf; Tcl_Obj *target; Tcl_DString ds; i = FileForceOption(interp, objc - 1, objv + 1, &force); if (i < 0) { return TCL_ERROR; } i++; if ((objc - i) < 2) { Tcl_WrongNumArgs(interp, 1, objv, "?-option value ...? source ?source ...? target"); return TCL_ERROR; } /* * If target doesn't exist or isn't a directory, try the copy/rename. More * than 2 arguments is only valid if the target is an existing directory. */ target = objv[objc - 1]; if (Tcl_FSConvertToPathType(interp, target) != TCL_OK) { return TCL_ERROR; } if (Tcl_UtfToExternalDStringEx(interp, TCLFSENCODING, TclGetString(target), TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } Tcl_DStringFree(&ds); result = TCL_OK; /* * Call Tcl_FSStat() so that if target is a symlink that points to a * directory we will put the sources in that directory instead of * overwriting the symlink. */ if ((Tcl_FSStat(target, &statBuf) != 0) || !S_ISDIR(statBuf.st_mode)) { if ((objc - i) > 2) { errno = ENOTDIR; Tcl_PosixError(interp); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error %s: target \"%s\" is not a directory", (copyFlag?"copying":"renaming"), TclGetString(target))); result = TCL_ERROR; } else { /* * Even though already have target == translated(objv[i+1]), pass * the original argument down, so if there's an error, the error * message will reflect the original arguments. */ result = CopyRenameOneFile(interp, objv[i], objv[i + 1], copyFlag, force); } return result; } /* * Move each source file into target directory. Extract the basename from * each source, and append it to the end of the target path. */ for ( ; i 0) { goto createDir; } /* Already tried, with delete in-between directly after * creation, so just continue (assume created successful). */ goto nextPart; } /* return with error */ errfile = target; goto done; } nextPart: /* * Forget about this sub-path. */ Tcl_DecrRefCount(target); target = NULL; } Tcl_DecrRefCount(split); split = NULL; } done: if (errfile != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't create directory \"%s\": %s", TclGetString(errfile), Tcl_PosixError(interp))); result = TCL_ERROR; } if (split != NULL) { Tcl_DecrRefCount(split); } if (target != NULL) { Tcl_DecrRefCount(target); } return result; } /* *---------------------------------------------------------------------- * * TclFileDeleteCmd * * This function implements the "delete" subcommand of the "file" * command. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int TclFileDeleteCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Used for error reporting */ int objc, /* Number of arguments */ Tcl_Obj *const objv[]) /* Argument strings passed to Tcl_FileCmd. */ { int i, force, result; Tcl_Obj *errfile; Tcl_Obj *errorBuffer = NULL; Tcl_DString ds; i = FileForceOption(interp, objc - 1, objv + 1, &force); if (i < 0) { return TCL_ERROR; } errfile = NULL; result = TCL_OK; for (i++ ; i < objc; i++) { Tcl_StatBuf statBuf; errfile = objv[i]; if (Tcl_FSConvertToPathType(interp, objv[i]) != TCL_OK) { result = TCL_ERROR; goto done; } if (Tcl_UtfToExternalDStringEx(interp, TCLFSENCODING, TclGetString(objv[i]), TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); result = TCL_ERROR; goto done; } Tcl_DStringFree(&ds); /* * Call lstat() to get info so can delete symbolic link itself. */ if (Tcl_FSLstat(objv[i], &statBuf) != 0) { result = TCL_ERROR; } else if (S_ISDIR(statBuf.st_mode)) { /* * We own a reference count on errorBuffer, if it was set as a * result of this call. */ result = Tcl_FSRemoveDirectory(objv[i], force, &errorBuffer); if (result != TCL_OK) { if ((force == 0) && (errno == EEXIST)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error deleting \"%s\": directory not empty", TclGetString(objv[i]))); Tcl_PosixError(interp); goto done; } /* * If possible, use the untranslated name for the file. */ errfile = errorBuffer; /* * FS supposed to check between translated objv and errfile. */ if (Tcl_FSEqualPaths(objv[i], errfile)) { errfile = objv[i]; } } } else { result = Tcl_FSDeleteFile(objv[i]); } if (result != TCL_OK) { /* * Avoid possible race condition (file/directory deleted after call * of lstat), so bypass ENOENT because not an error, just a no-op */ if (errno == ENOENT) { result = TCL_OK; continue; } /* * It is important that we break on error, otherwise we might end * up owning reference counts on numerous errorBuffers. */ result = TCL_ERROR; break; } } if (result != TCL_OK) { if (errfile == NULL) { /* * We try to accommodate poor error results from our Tcl_FS calls. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error deleting unknown file: %s", Tcl_PosixError(interp))); } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error deleting \"%s\": %s", TclGetString(errfile), Tcl_PosixError(interp))); } } done: if (errorBuffer != NULL) { Tcl_DecrRefCount(errorBuffer); } return result; } /* *--------------------------------------------------------------------------- * * CopyRenameOneFile * * Copies or renames specified source file or directory hierarchy to the * specified target. * * Results: * A standard Tcl result. * * Side effects: * Target is overwritten if the force flag is set. Attempting to * copy/rename a file onto a directory or a directory onto a file will * always result in an error. * *---------------------------------------------------------------------- */ static int CopyRenameOneFile( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Obj *source, /* Pathname of file to copy. May need to be * translated. */ Tcl_Obj *target, /* Pathname of file to create/overwrite. May * need to be translated. */ int copyFlag, /* If non-zero, copy files. Otherwise, rename * them. */ int force) /* If non-zero, overwrite target file if it * exists. Otherwise, error if target already * exists. */ { int result; Tcl_Obj *errfile, *errorBuffer; Tcl_Obj *actualSource=NULL; /* If source is a link, then this is the real * file/directory. */ Tcl_StatBuf sourceStatBuf, targetStatBuf; Tcl_DString ds; if (Tcl_FSConvertToPathType(interp, source) != TCL_OK) { return TCL_ERROR; } if (Tcl_UtfToExternalDStringEx(interp, TCLFSENCODING, TclGetString(source), TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } Tcl_DStringFree(&ds); if (Tcl_FSConvertToPathType(interp, target) != TCL_OK) { return TCL_ERROR; } if (Tcl_UtfToExternalDStringEx(interp, TCLFSENCODING, TclGetString(target), TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } Tcl_DStringFree(&ds); errfile = NULL; errorBuffer = NULL; result = TCL_ERROR; /* * We want to copy/rename links and not the files they point to, so we use * lstat(). If target is a link, we also want to replace the link and not * the file it points to, so we also use lstat() on the target. */ if (Tcl_FSLstat(source, &sourceStatBuf) != 0) { errfile = source; goto done; } if (Tcl_FSLstat(target, &targetStatBuf) != 0) { if (errno != ENOENT) { errfile = target; goto done; } } else { if (force == 0) { errno = EEXIST; errfile = target; goto done; } /* * Prevent copying or renaming a file onto itself. On Windows since * 8.5 we do get an inode number, however the unsigned short field is * insufficient to accept the Win32 API file id so it is truncated to * 16 bits and we get collisions. See bug #2015723. */ #if !defined(_WIN32) && !defined(__CYGWIN__) if ((sourceStatBuf.st_ino != 0) && (targetStatBuf.st_ino != 0)) { if ((sourceStatBuf.st_ino == targetStatBuf.st_ino) && (sourceStatBuf.st_dev == targetStatBuf.st_dev)) { result = TCL_OK; goto done; } } #endif /* * Prevent copying/renaming a file onto a directory and vice-versa. * This is a policy decision based on the fact that existing * implementations of copy and rename on all platforms also prevent * this. */ if (S_ISDIR(sourceStatBuf.st_mode) && !S_ISDIR(targetStatBuf.st_mode)) { errno = EISDIR; Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't overwrite file \"%s\" with directory \"%s\"", TclGetString(target), TclGetString(source))); goto done; } if (!S_ISDIR(sourceStatBuf.st_mode) && S_ISDIR(targetStatBuf.st_mode)) { errno = EISDIR; Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't overwrite directory \"%s\" with file \"%s\"", TclGetString(target), TclGetString(source))); goto done; } /* * The destination exists, but appears to be ok to over-write, and * -force is given. We now try to adjust permissions to ensure the * operation succeeds. If we can't adjust permissions, we'll let the * actual copy/rename return an error later. */ { Tcl_Obj *perm; int index; TclNewLiteralStringObj(perm, "u+w"); Tcl_IncrRefCount(perm); if (TclFSFileAttrIndex(target, "-permissions", &index) == TCL_OK) { Tcl_FSFileAttrsSet(NULL, index, target, perm); } Tcl_DecrRefCount(perm); } } if (copyFlag == 0) { result = Tcl_FSRenameFile(source, target); if (result == TCL_OK) { goto done; } if (errno == EINVAL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error renaming \"%s\" to \"%s\": trying to rename a" " volume or move a directory into itself", TclGetString(source), TclGetString(target))); goto done; } else if (errno != EXDEV) { errfile = target; goto done; } /* * The rename failed because the move was across file systems. Fall * through to copy file and then remove original. Note that the * low-level Tcl_FSRenameFileProc in the filesystem is allowed to * implement cross-filesystem moves itself, if it desires. */ } actualSource = source; Tcl_IncrRefCount(actualSource); /* * Activate the following block to copy files instead of links. However * Tcl's semantics currently say we should copy links, so any such change * should be the subject of careful study on the consequences. * * Perhaps there could be an optional flag to 'file copy' to dictate which * approach to use, with the default being _not_ to have this block * active. */ #if 0 #ifdef S_ISLNK if (copyFlag && S_ISLNK(sourceStatBuf.st_mode)) { /* * We want to copy files not links. Therefore we must follow the link. * There are two purposes to this 'stat' call here. First we want to * know if the linked-file/dir actually exists, and second, in the * block of code which follows, some 20 lines down, we want to check * if the thing is a file or directory. */ if (Tcl_FSStat(source, &sourceStatBuf) != 0) { /* * Actual file doesn't exist. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error copying \"%s\": the target of this link doesn't" " exist", TclGetString(source))); goto done; } else { int counter = 0; while (1) { Tcl_Obj *path = Tcl_FSLink(actualSource, NULL, 0); if (path == NULL) { break; } /* * Now we want to check if this is a relative path, and if so, * to make it absolute. */ if (Tcl_FSGetPathType(path) == TCL_PATH_RELATIVE) { Tcl_Obj *abs = Tcl_FSJoinToPath(actualSource, 1, &path); if (abs == NULL) { break; } Tcl_IncrRefCount(abs); Tcl_DecrRefCount(path); path = abs; } Tcl_DecrRefCount(actualSource); actualSource = path; counter++; /* * Arbitrary limit of 20 links to follow. */ if (counter > 20) { /* * Too many links. */ Tcl_SetErrno(EMLINK); errfile = source; goto done; } } /* Now 'actualSource' is the correct file */ } } #endif /* S_ISLNK */ #endif if (S_ISDIR(sourceStatBuf.st_mode)) { result = Tcl_FSCopyDirectory(actualSource, target, &errorBuffer); if (result != TCL_OK) { if (errno == EXDEV) { /* * The copy failed because we're trying to do a * cross-filesystem copy. We do this through our Tcl library. */ Tcl_Obj *copyCommand, *cmdObj, *opObj; TclNewObj(copyCommand); TclNewLiteralStringObj(cmdObj, "::tcl::CopyDirectory"); Tcl_ListObjAppendElement(interp, copyCommand, cmdObj); if (copyFlag) { TclNewLiteralStringObj(opObj, "copying"); } else { TclNewLiteralStringObj(opObj, "renaming"); } Tcl_ListObjAppendElement(interp, copyCommand, opObj); Tcl_ListObjAppendElement(interp, copyCommand, source); Tcl_ListObjAppendElement(interp, copyCommand, target); Tcl_IncrRefCount(copyCommand); result = Tcl_EvalObjEx(interp, copyCommand, TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT); Tcl_DecrRefCount(copyCommand); if (result != TCL_OK) { /* * There was an error in the Tcl-level copy. We will pass * on the Tcl error message and can ensure this by setting * errfile to NULL */ errfile = NULL; } } else { errfile = errorBuffer; if (Tcl_FSEqualPaths(errfile, source)) { errfile = source; } else if (Tcl_FSEqualPaths(errfile, target)) { errfile = target; } } } } else { result = Tcl_FSCopyFile(actualSource, target); if ((result != TCL_OK) && (errno == EXDEV)) { result = TclCrossFilesystemCopy(interp, source, target); } if (result != TCL_OK) { /* * We could examine 'errno' to double-check if the problem was * with the target, but we checked the source above, so it should * be quite clear */ errfile = target; } /* * We now need to reset the result, because the above call, * may have left set it. (Ideally we would prefer not to pass * an interpreter in above, but the channel IO code used by * TclCrossFilesystemCopy currently requires one) */ Tcl_ResetResult(interp); } if ((copyFlag == 0) && (result == TCL_OK)) { if (S_ISDIR(sourceStatBuf.st_mode)) { result = Tcl_FSRemoveDirectory(source, 1, &errorBuffer); if (result != TCL_OK) { errfile = errorBuffer; if (Tcl_FSEqualPaths(errfile, source) == 0) { errfile = source; } } } else { result = Tcl_FSDeleteFile(source); if (result != TCL_OK) { errfile = source; } } if (result != TCL_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("can't unlink \"%s\": %s", TclGetString(errfile), Tcl_PosixError(interp))); errfile = NULL; } } done: if (errfile != NULL) { Tcl_Obj *errorMsg = Tcl_ObjPrintf("error %s \"%s\"", (copyFlag ? "copying" : "renaming"), TclGetString(source)); if (errfile != source) { Tcl_AppendPrintfToObj(errorMsg, " to \"%s\"", TclGetString(target)); if (errfile != target) { Tcl_AppendPrintfToObj(errorMsg, ": \"%s\"", TclGetString(errfile)); } } Tcl_AppendPrintfToObj(errorMsg, ": %s", Tcl_PosixError(interp)); Tcl_SetObjResult(interp, errorMsg); } if (errorBuffer != NULL) { Tcl_DecrRefCount(errorBuffer); } if (actualSource != NULL) { Tcl_DecrRefCount(actualSource); } return result; } /* *--------------------------------------------------------------------------- * * FileForceOption -- * * Helps parse command line options for file commands that take the * "-force" and "--" options. * * Results: * The return value is how many arguments from argv were consumed by this * function, or -1 if there was an error parsing the options. If an error * occurred, an error message is left in the interp's result. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static int FileForceOption( Tcl_Interp *interp, /* Interp, for error return. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[], /* Argument strings. First command line * option, if it exists, begins at 0. */ int *forcePtr) /* If the "-force" was specified, *forcePtr is * filled with 1, otherwise with 0. */ { int force, i, idx; static const char *const options[] = { "-force", "--", NULL }; force = 0; for (i = 0; i < objc; i++) { if (TclGetString(objv[i])[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", TCL_EXACT, &idx) != TCL_OK) { return -1; } if (idx == 0 /* -force */) { force = 1; } else { /* -- */ i++; break; } } *forcePtr = force; return i; } /* *--------------------------------------------------------------------------- * * FileBasename -- * * Given a path in either tcl format (with / separators), or in the * platform-specific format for the current platform, return all the * characters in the path after the last directory separator. But, if * path is the root directory, returns no characters. * * Results: * Returns the string object that represents the basename. If there is an * error, an error message is left in interp, and NULL is returned. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static Tcl_Obj * FileBasename( TCL_UNUSED(Tcl_Interp *), /* Interp, for error return. */ Tcl_Obj *pathPtr) /* Path whose basename to extract. */ { Tcl_Size objc; Tcl_Obj *splitPtr; Tcl_Obj *resultPtr = NULL; splitPtr = Tcl_FSSplitPath(pathPtr, &objc); Tcl_IncrRefCount(splitPtr); if (objc != 0) { /* * Return the last component, unless it is the only component, and it * is the root of an absolute path. */ if (objc > 0) { Tcl_ListObjIndex(NULL, splitPtr, objc-1, &resultPtr); if ((objc == 1) && (Tcl_FSGetPathType(resultPtr) != TCL_PATH_RELATIVE)) { resultPtr = NULL; } } } if (resultPtr == NULL) { TclNewObj(resultPtr); } Tcl_IncrRefCount(resultPtr); Tcl_DecrRefCount(splitPtr); return resultPtr; } /* *---------------------------------------------------------------------- * * TclFileAttrsCmd -- * * Sets or gets the platform-specific attributes of a file. The objc-objv * points to the file name with the rest of the command line following. * This routine uses platform-specific tables of option strings and * callbacks. The callback to get the attributes take three parameters: * Tcl_Interp *interp; The interp to report errors with. Since * this is an object-based API, the object * form of the result should be used. * const char *fileName; This is extracted using * Tcl_TranslateFileName. * TclObj **attrObjPtrPtr; A new object to hold the attribute is * allocated and put here. * The first two parameters of the callback used to write out the * attributes are the same. The third parameter is: * const *attrObjPtr; A pointer to the object that has the new * attribute. * They both return standard TCL errors; if the routine to get an * attribute fails, no object is allocated and *attrObjPtrPtr is * unchanged. * * Results: * Standard TCL error. * * Side effects: * May set file attributes for the file name. * *---------------------------------------------------------------------- */ int TclFileAttrsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* The interpreter for error reporting. */ int objc, /* Number of command line arguments. */ Tcl_Obj *const objv[]) /* The command line objects. */ { int result; const char *const *attributeStrings; const char **attributeStringsAllocated = NULL; Tcl_Obj *objStrings = NULL; Tcl_Size numObjStrings = TCL_INDEX_NONE; Tcl_Obj *filePtr; Tcl_DString ds; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "name ?-option value ...?"); return TCL_ERROR; } filePtr = objv[1]; if (Tcl_FSConvertToPathType(interp, filePtr) != TCL_OK) { return TCL_ERROR; } if (Tcl_UtfToExternalDStringEx(interp, TCLFSENCODING, TclGetString(filePtr), TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } Tcl_DStringFree(&ds); objc -= 2; objv += 2; result = TCL_ERROR; Tcl_SetErrno(0); /* * Get the set of attribute names from the filesystem. */ attributeStrings = Tcl_FSFileAttrStrings(filePtr, &objStrings); if (attributeStrings == NULL) { Tcl_Size index; Tcl_Obj *objPtr; if (objStrings == NULL) { if (Tcl_GetErrno() != 0) { /* * There was an error, probably that the filePtr is not * accepted by any filesystem */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read \"%s\": %s", TclGetString(filePtr), Tcl_PosixError(interp))); } return TCL_ERROR; } /* * We own the object now. */ Tcl_IncrRefCount(objStrings); /* * Use objStrings as a list object. */ if (TclListObjLength(interp, objStrings, &numObjStrings) != TCL_OK) { goto end; } attributeStringsAllocated = (const char **) TclStackAlloc(interp, (1+numObjStrings) * sizeof(char *)); for (index = 0; index < numObjStrings; index++) { Tcl_ListObjIndex(interp, objStrings, index, &objPtr); attributeStringsAllocated[index] = TclGetString(objPtr); } attributeStringsAllocated[index] = NULL; attributeStrings = attributeStringsAllocated; } else if (objStrings != NULL) { Tcl_Panic("must not update objPtrRef's variable and return non-NULL"); } /* * Process the attributes to produce a list of all of them, the value of a * particular attribute, or to set one or more attributes (depending on * the number of arguments). */ if (objc == 0) { /* * Get all attributes. */ int index, res = TCL_OK, nbAtts = 0; Tcl_Obj *listPtr; listPtr = Tcl_NewListObj(0, NULL); for (index = 0; attributeStrings[index] != NULL; index++) { Tcl_Obj *objPtrAttr; if (res != TCL_OK) { /* * Clear the error from the last iteration. */ Tcl_ResetResult(interp); } res = Tcl_FSFileAttrsGet(interp, index, filePtr, &objPtrAttr); if (res == TCL_OK) { Tcl_Obj *objPtr = Tcl_NewStringObj(attributeStrings[index], -1); Tcl_ListObjAppendElement(interp, listPtr, objPtr); Tcl_ListObjAppendElement(interp, listPtr, objPtrAttr); nbAtts++; } } if (index > 0 && nbAtts == 0) { /* * Error: no valid attributes found. */ Tcl_DecrRefCount(listPtr); goto end; } Tcl_SetObjResult(interp, listPtr); } else if (objc == 1) { /* * Get one attribute. */ int index; Tcl_Obj *objPtr = NULL; if (numObjStrings == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad option \"%s\", there are no file attributes in this" " filesystem", TclGetString(objv[0]))); Tcl_SetErrorCode(interp, "TCL","OPERATION","FATTR","NONE", (char *)NULL); goto end; } if (Tcl_GetIndexFromObj(interp, objv[0], attributeStrings, "option", TCL_INDEX_TEMP_TABLE, &index) != TCL_OK) { goto end; } if (Tcl_FSFileAttrsGet(interp, index, filePtr, &objPtr) != TCL_OK) { goto end; } Tcl_SetObjResult(interp, objPtr); } else { /* * Set option/value pairs. */ int i, index; if (numObjStrings == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad option \"%s\", there are no file attributes in this" " filesystem", TclGetString(objv[0]))); Tcl_SetErrorCode(interp, "TCL","OPERATION","FATTR","NONE", (char *)NULL); goto end; } for (i = 0; i < objc ; i += 2) { if (Tcl_GetIndexFromObj(interp, objv[i], attributeStrings, "option", TCL_INDEX_TEMP_TABLE, &index) != TCL_OK) { goto end; } if (i + 1 == objc) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "value for \"%s\" missing", TclGetString(objv[i]))); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FATTR", "NOVALUE", (char *)NULL); goto end; } if (Tcl_FSFileAttrsSet(interp, index, filePtr, objv[i + 1]) != TCL_OK) { goto end; } } } result = TCL_OK; /* * Free up the array we allocated and drop our reference to any list of * attribute names issued by the filesystem. */ end: if (attributeStringsAllocated != NULL) { TclStackFree(interp, (void *) attributeStringsAllocated); } if (objStrings != NULL) { Tcl_DecrRefCount(objStrings); } return result; } /* *---------------------------------------------------------------------- * * TclFileLinkCmd -- * * This function is invoked to process the "file link" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * May create a new link. * *---------------------------------------------------------------------- */ int TclFileLinkCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *contents; int index; Tcl_DString ds; if (objc < 2 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "?-linktype? linkname ?target?"); return TCL_ERROR; } /* * Index of the 'source' argument. */ if (objc == 4) { index = 2; } else { index = 1; } if (objc > 2) { int linkAction; if (objc == 4) { /* * We have a '-linktype' argument. */ static const char *const linkTypes[] = { "-symbolic", "-hard", NULL }; if (Tcl_GetIndexFromObj(interp, objv[1], linkTypes, "option", 0, &linkAction) != TCL_OK) { return TCL_ERROR; } if (linkAction == 0) { linkAction = TCL_CREATE_SYMBOLIC_LINK; } else { linkAction = TCL_CREATE_HARD_LINK; } } else { linkAction = TCL_CREATE_SYMBOLIC_LINK | TCL_CREATE_HARD_LINK; } if (Tcl_FSConvertToPathType(interp, objv[index]) != TCL_OK) { return TCL_ERROR; } if (Tcl_UtfToExternalDStringEx(interp, TCLFSENCODING, TclGetString(objv[index]), TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } Tcl_DStringFree(&ds); /* * Create link from source to target. */ contents = Tcl_FSLink(objv[index], objv[index+1], linkAction); if (contents == NULL) { /* * We handle three common error cases specially, and for all other * errors, we use the standard Posix error message. */ if (errno == EEXIST) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not create new link \"%s\": that path already" " exists", TclGetString(objv[index]))); Tcl_PosixError(interp); } else if (errno == ENOENT) { /* * There are two cases here: either the target doesn't exist, * or the directory of the src doesn't exist. */ int access; Tcl_Obj *dirPtr = TclPathPart(interp, objv[index], TCL_PATH_DIRNAME); if (dirPtr == NULL) { return TCL_ERROR; } access = Tcl_FSAccess(dirPtr, F_OK); Tcl_DecrRefCount(dirPtr); if (access != 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not create new link \"%s\": no such file" " or directory", TclGetString(objv[index]))); Tcl_PosixError(interp); } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not create new link \"%s\": target \"%s\" " "doesn't exist", TclGetString(objv[index]), TclGetString(objv[index+1]))); errno = ENOENT; Tcl_PosixError(interp); } } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not create new link \"%s\" pointing to \"%s\": %s", TclGetString(objv[index]), TclGetString(objv[index+1]), Tcl_PosixError(interp))); } return TCL_ERROR; } } else { if (Tcl_FSConvertToPathType(interp, objv[index]) != TCL_OK) { return TCL_ERROR; } if (Tcl_UtfToExternalDStringEx(interp, TCLFSENCODING, TclGetString(objv[index]), TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } Tcl_DStringFree(&ds); /* * Read link */ contents = Tcl_FSLink(objv[index], NULL, 0); if (contents == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read link \"%s\": %s", TclGetString(objv[index]), Tcl_PosixError(interp))); return TCL_ERROR; } } Tcl_SetObjResult(interp, contents); if (objc == 2) { /* * If we are reading a link, we need to free this result refCount. If * we are creating a link, this will just be objv[index+1], and so we * don't own it. */ Tcl_DecrRefCount(contents); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclFileReadLinkCmd -- * * This function is invoked to process the "file readlink" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclFileReadLinkCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *contents; Tcl_DString ds; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } if (Tcl_FSConvertToPathType(interp, objv[1]) != TCL_OK) { return TCL_ERROR; } if (Tcl_UtfToExternalDStringEx(interp, TCLFSENCODING, TclGetString(objv[1]), TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } Tcl_DStringFree(&ds); contents = Tcl_FSLink(objv[1], NULL, 0); if (contents == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not read link \"%s\": %s", TclGetString(objv[1]), Tcl_PosixError(interp))); return TCL_ERROR; } Tcl_SetObjResult(interp, contents); Tcl_DecrRefCount(contents); return TCL_OK; } /* *--------------------------------------------------------------------------- * * TclFileTemporaryCmd -- * * This function implements the "tempfile" subcommand of the "file" * command. * * Results: * Returns a standard Tcl result. * * Side effects: * Creates a temporary file. Opens a channel to that file and puts the * name of that channel in the result. *Might* register suitable exit * handlers to ensure that the temporary file gets deleted. Might write * to a variable, so reentrancy is a potential issue. * *--------------------------------------------------------------------------- */ int TclFileTemporaryCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *nameVarObj = NULL; /* Variable to store the name of the temporary * file in. */ Tcl_Obj *nameObj = NULL; /* Object that will contain the filename. */ Tcl_Channel chan; /* The channel opened (RDWR) on the temporary * file, or NULL if there's an error. */ Tcl_Obj *tempDirObj = NULL, *tempBaseObj = NULL, *tempExtObj = NULL; /* Pieces of template. Each piece is NULL if * it is omitted. The platform temporary file * engine might ignore some pieces. */ if (objc < 1 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "?nameVar? ?template?"); return TCL_ERROR; } if (objc > 1) { nameVarObj = objv[1]; TclNewObj(nameObj); } if (objc > 2) { Tcl_Size length; Tcl_Obj *templateObj = objv[2]; const char *string = TclGetStringFromObj(templateObj, &length); /* * Treat an empty string as if it wasn't there. */ if (length == 0) { goto makeTemporary; } /* * The template only gives a directory if there is a directory * separator in it. */ if (strchr(string, '/') != NULL || (tclPlatform == TCL_PLATFORM_WINDOWS && strchr(string, '\\') != NULL)) { tempDirObj = TclPathPart(interp, templateObj, TCL_PATH_DIRNAME); /* * Only allow creation of temporary files in the native filesystem * since they are frequently used for integration with external * tools or system libraries. [Bug 2388866] */ if (tempDirObj != NULL && Tcl_FSGetFileSystemForPath(tempDirObj) != &tclNativeFilesystem) { TclDecrRefCount(tempDirObj); tempDirObj = NULL; } } /* * The template only gives the filename if the last character isn't a * directory separator. */ if (string[length-1] != '/' && (tclPlatform != TCL_PLATFORM_WINDOWS || string[length-1] != '\\')) { Tcl_Obj *tailObj = TclPathPart(interp, templateObj, TCL_PATH_TAIL); if (tailObj != NULL) { tempBaseObj = TclPathPart(interp, tailObj, TCL_PATH_ROOT); tempExtObj = TclPathPart(interp, tailObj, TCL_PATH_EXTENSION); TclDecrRefCount(tailObj); } } } /* * Convert empty parts of the template into unspecified parts. */ if (tempDirObj && !TclGetString(tempDirObj)[0]) { TclDecrRefCount(tempDirObj); tempDirObj = NULL; } if (tempBaseObj && !TclGetString(tempBaseObj)[0]) { TclDecrRefCount(tempBaseObj); tempBaseObj = NULL; } if (tempExtObj && !TclGetString(tempExtObj)[0]) { TclDecrRefCount(tempExtObj); tempExtObj = NULL; } /* * Create and open the temporary file. */ makeTemporary: chan = TclpOpenTemporaryFile(tempDirObj,tempBaseObj,tempExtObj, nameObj); /* * If we created pieces of template, get rid of them now. */ if (tempDirObj) { TclDecrRefCount(tempDirObj); } if (tempBaseObj) { TclDecrRefCount(tempBaseObj); } if (tempExtObj) { TclDecrRefCount(tempExtObj); } /* * Deal with results. */ if (chan == NULL) { if (nameVarObj) { TclDecrRefCount(nameObj); } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't create temporary file: %s", Tcl_PosixError(interp))); return TCL_ERROR; } Tcl_RegisterChannel(interp, chan); if (nameVarObj != NULL) { if (Tcl_ObjSetVar2(interp, nameVarObj, NULL, nameObj, TCL_LEAVE_ERR_MSG) == NULL) { Tcl_UnregisterChannel(interp, chan); return TCL_ERROR; } } Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_GetChannelName(chan), -1)); return TCL_OK; } /* *--------------------------------------------------------------------------- * * TclFileTempDirCmd -- * * This function implements the "tempdir" subcommand of the "file" * command. * * Results: * Returns a standard Tcl result. * * Side effects: * Creates a temporary directory. * *--------------------------------------------------------------------------- */ int TclFileTempDirCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *dirNameObj; /* Object that will contain the directory * name. */ Tcl_Obj *baseDirObj = NULL, *nameBaseObj = NULL; /* Pieces of template. Each piece is NULL if * it is omitted. The platform temporary file * engine might ignore some pieces. */ if (objc < 1 || objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?template?"); return TCL_ERROR; } if (objc > 1) { Tcl_Size length; Tcl_Obj *templateObj = objv[1]; const char *string = TclGetStringFromObj(templateObj, &length); const int onWindows = (tclPlatform == TCL_PLATFORM_WINDOWS); /* * Treat an empty string as if it wasn't there. */ if (length == 0) { goto makeTemporary; } /* * The template only gives a directory if there is a directory * separator in it, and only gives a base name if there's at least one * character after the last directory separator. */ if (strchr(string, '/') == NULL && (!onWindows || strchr(string, '\\') == NULL)) { /* * No directory separator, so just assume we have a file name. * This is a bit wrong on Windows where we could have problems * with disk name prefixes... but those are much less common in * naked form so we just pass through and let the OS figure it out * instead. */ nameBaseObj = templateObj; Tcl_IncrRefCount(nameBaseObj); } else if (string[length-1] != '/' && (!onWindows || string[length-1] != '\\')) { /* * If the template has a non-terminal directory separator, split * into dirname and tail. */ baseDirObj = TclPathPart(interp, templateObj, TCL_PATH_DIRNAME); nameBaseObj = TclPathPart(interp, templateObj, TCL_PATH_TAIL); } else { /* * Otherwise, there must be a terminal directory separator, so * just the directory is given. */ baseDirObj = templateObj; Tcl_IncrRefCount(baseDirObj); } /* * Only allow creation of temporary directories in the native * filesystem since they are frequently used for integration with * external tools or system libraries. */ if (baseDirObj != NULL && Tcl_FSGetFileSystemForPath(baseDirObj) != &tclNativeFilesystem) { TclDecrRefCount(baseDirObj); baseDirObj = NULL; } } /* * Convert empty parts of the template into unspecified parts. */ if (baseDirObj && !TclGetString(baseDirObj)[0]) { TclDecrRefCount(baseDirObj); baseDirObj = NULL; } if (nameBaseObj && !TclGetString(nameBaseObj)[0]) { TclDecrRefCount(nameBaseObj); nameBaseObj = NULL; } /* * Create and open the temporary file. */ makeTemporary: dirNameObj = TclpCreateTemporaryDirectory(baseDirObj, nameBaseObj); /* * If we created pieces of template, get rid of them now. */ if (baseDirObj) { TclDecrRefCount(baseDirObj); } if (nameBaseObj) { TclDecrRefCount(nameBaseObj); } /* * Deal with results. */ if (dirNameObj == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't create temporary directory: %s", Tcl_PosixError(interp))); return TCL_ERROR; } Tcl_SetObjResult(interp, dirNameObj); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclFileHomeCmd -- * * This function is invoked to process the "file home" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclFileHomeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *homeDirObj; if (objc != 1 && objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "?user?"); return TCL_ERROR; } homeDirObj = TclGetHomeDirObj(interp, objc == 1 ? NULL : Tcl_GetString(objv[1])); if (homeDirObj == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, homeDirObj); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclFileTildeExpandCmd -- * * This function is invoked to process the "file tildeexpand" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclFileTildeExpandCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *expandedPathObj; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "path"); return TCL_ERROR; } expandedPathObj = TclResolveTildePath(interp, objv[1]); if (expandedPathObj == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, expandedPathObj); return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclFileName.c0000644000175000017500000017162114726623136015531 0ustar sergeisergei/* * tclFileName.c -- * * This file contains routines for converting file names betwen native * and network form. * * Copyright © 1995-1998 Sun Microsystems, Inc. * Copyright © 1998-1999 Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclRegexp.h" #include "tclFileSystem.h" /* For TclGetPathType() */ /* * The following variable is set in the TclPlatformInit call to one of: * TCL_PLATFORM_UNIX or TCL_PLATFORM_WINDOWS. */ TclPlatformType tclPlatform = TCL_PLATFORM_UNIX; /* * Prototypes for local procedures defined in this file: */ static const char * ExtractWinRoot(const char *path, Tcl_DString *resultPtr, int offset, Tcl_PathType *typePtr); static int SkipToChar(char **stringPtr, int match); static Tcl_Obj * SplitWinPath(const char *path); static Tcl_Obj * SplitUnixPath(const char *path); static int DoGlob(Tcl_Interp *interp, Tcl_Obj *resultPtr, const char *separators, Tcl_Obj *pathPtr, int flags, char *pattern, Tcl_GlobTypeData *types); static int TclGlob(Tcl_Interp *interp, char *pattern, Tcl_Obj *pathPrefix, int globFlags, Tcl_GlobTypeData *types); /* Flag values used by TclGlob() */ #define TCL_GLOBMODE_DIR 4 #define TCL_GLOBMODE_TAILS 8 /* * When there is no support for getting the block size of a file in a stat() * call, use this as a guess. Allow it to be overridden in the platform- * specific files. */ #if (!defined(HAVE_STRUCT_STAT_ST_BLKSIZE) && !defined(GUESSED_BLOCK_SIZE)) #define GUESSED_BLOCK_SIZE 1024 #endif /* *---------------------------------------------------------------------- * * SetResultLength -- * * Resets the result DString for ExtractWinRoot to accommodate * any NT extended path prefixes. * * Results: * None. * * Side effects: * May modify the Tcl_DString. *---------------------------------------------------------------------- */ static void SetResultLength( Tcl_DString *resultPtr, int offset, int extended) { Tcl_DStringSetLength(resultPtr, offset); if (extended == 2) { TclDStringAppendLiteral(resultPtr, "//?/UNC/"); } else if (extended == 1) { TclDStringAppendLiteral(resultPtr, "//?/"); } } /* *---------------------------------------------------------------------- * * ExtractWinRoot -- * * Matches the root portion of a Windows path and appends it to the * specified Tcl_DString. * * Results: * Returns the position in the path immediately after the root including * any trailing slashes. Appends a cleaned up version of the root to the * Tcl_DString at the specified offset. * * Side effects: * Modifies the specified Tcl_DString. * *---------------------------------------------------------------------- */ static const char * ExtractWinRoot( const char *path, /* Path to parse. */ Tcl_DString *resultPtr, /* Buffer to hold result. */ int offset, /* Offset in buffer where result should be * stored. */ Tcl_PathType *typePtr) /* Where to store pathType result */ { int extended = 0; if ( (path[0] == '/' || path[0] == '\\') && (path[1] == '/' || path[1] == '\\') && (path[2] == '?') && (path[3] == '/' || path[3] == '\\')) { extended = 1; path = path + 4; if (path[0] == 'U' && path[1] == 'N' && path[2] == 'C' && (path[3] == '/' || path[3] == '\\')) { extended = 2; path = path + 4; } } if (path[0] == '/' || path[0] == '\\') { /* * Might be a UNC or Vol-Relative path. */ const char *host, *share, *tail; int hlen, slen; if (path[1] != '/' && path[1] != '\\') { SetResultLength(resultPtr, offset, extended); *typePtr = TCL_PATH_VOLUME_RELATIVE; TclDStringAppendLiteral(resultPtr, "/"); return &path[1]; } host = &path[2]; /* * Skip separators. */ while (host[0] == '/' || host[0] == '\\') { host++; } for (hlen = 0; host[hlen];hlen++) { if (host[hlen] == '/' || host[hlen] == '\\') { break; } } if (host[hlen] == 0 || host[hlen+1] == 0) { /* * The path given is simply of the form '/foo', '//foo', * '/////foo' or the same with backslashes. If there is exactly * one leading '/' the path is volume relative (see filename man * page). If there are more than one, we are simply assuming they * are superfluous and we trim them away. (An alternative * interpretation would be that it is a host name, but we have * been documented that that is not the case). */ *typePtr = TCL_PATH_VOLUME_RELATIVE; TclDStringAppendLiteral(resultPtr, "/"); return &path[2]; } SetResultLength(resultPtr, offset, extended); share = &host[hlen]; /* * Skip separators. */ while (share[0] == '/' || share[0] == '\\') { share++; } for (slen=0; share[slen]; slen++) { if (share[slen] == '/' || share[slen] == '\\') { break; } } TclDStringAppendLiteral(resultPtr, "//"); Tcl_DStringAppend(resultPtr, host, hlen); TclDStringAppendLiteral(resultPtr, "/"); Tcl_DStringAppend(resultPtr, share, slen); tail = &share[slen]; /* * Skip separators. */ while (tail[0] == '/' || tail[0] == '\\') { tail++; } *typePtr = TCL_PATH_ABSOLUTE; return tail; } else if (*path && path[1] == ':') { /* * Might be a drive separator. */ SetResultLength(resultPtr, offset, extended); if (path[2] != '/' && path[2] != '\\') { *typePtr = TCL_PATH_VOLUME_RELATIVE; Tcl_DStringAppend(resultPtr, path, 2); return &path[2]; } else { const char *tail = &path[3]; /* * Skip separators. */ while (*tail && (tail[0] == '/' || tail[0] == '\\')) { tail++; } *typePtr = TCL_PATH_ABSOLUTE; Tcl_DStringAppend(resultPtr, path, 2); TclDStringAppendLiteral(resultPtr, "/"); return tail; } } else { int abs = 0; /* * Check for Windows devices. */ if ((path[0] == 'c' || path[0] == 'C') && (path[1] == 'o' || path[1] == 'O')) { if ((path[2] == 'm' || path[2] == 'M') && path[3] >= '1' && path[3] <= '9') { /* * May have match for 'com[1-9]:?', which is a serial port. */ if (path[4] == '\0') { abs = 4; } else if (path[4] == ':' && path[5] == '\0') { abs = 5; } } else if ((path[2] == 'n' || path[2] == 'N') && path[3] == '\0') { /* * Have match for 'con'. */ abs = 3; } } else if ((path[0] == 'l' || path[0] == 'L') && (path[1] == 'p' || path[1] == 'P') && (path[2] == 't' || path[2] == 'T')) { if (path[3] >= '1' && path[3] <= '9') { /* * May have match for 'lpt[1-9]:?' */ if (path[4] == '\0') { abs = 4; } else if (path[4] == ':' && path[5] == '\0') { abs = 5; } } } else if ((path[0] == 'p' || path[0] == 'P') && (path[1] == 'r' || path[1] == 'R') && (path[2] == 'n' || path[2] == 'N') && path[3] == '\0') { /* * Have match for 'prn'. */ abs = 3; } else if ((path[0] == 'n' || path[0] == 'N') && (path[1] == 'u' || path[1] == 'U') && (path[2] == 'l' || path[2] == 'L') && path[3] == '\0') { /* * Have match for 'nul'. */ abs = 3; } else if ((path[0] == 'a' || path[0] == 'A') && (path[1] == 'u' || path[1] == 'U') && (path[2] == 'x' || path[2] == 'X') && path[3] == '\0') { /* * Have match for 'aux'. */ abs = 3; } if (abs != 0) { *typePtr = TCL_PATH_ABSOLUTE; SetResultLength(resultPtr, offset, extended); Tcl_DStringAppend(resultPtr, path, abs); return path + abs; } } /* * Anything else is treated as relative. */ *typePtr = TCL_PATH_RELATIVE; return path; } /* *---------------------------------------------------------------------- * * Tcl_GetPathType -- * * Determines whether a given path is relative to the current directory, * relative to the current volume, or absolute. * * The objectified Tcl_FSGetPathType should be used in preference to this * function (as you can see below, this is just a wrapper around that * other function). * * Results: * Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or * TCL_PATH_VOLUME_RELATIVE. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_PathType Tcl_GetPathType( const char *path) { Tcl_PathType type; Tcl_Obj *tempObj = Tcl_NewStringObj(path,-1); Tcl_IncrRefCount(tempObj); type = Tcl_FSGetPathType(tempObj); Tcl_DecrRefCount(tempObj); return type; } /* *---------------------------------------------------------------------- * * TclpGetNativePathType -- * * Determines whether a given path is relative to the current directory, * relative to the current volume, or absolute, but ONLY FOR THE NATIVE * FILESYSTEM. This function is called from tclIOUtil.c (but needs to be * here due to its dependence on static variables/functions in this * file). The exported function Tcl_FSGetPathType should be used by * extensions. * * Results: * Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or * TCL_PATH_VOLUME_RELATIVE. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_PathType TclpGetNativePathType( Tcl_Obj *pathPtr, /* Native path of interest */ Tcl_Size *driveNameLengthPtr, /* Returns length of drive, if non-NULL and * path was absolute */ Tcl_Obj **driveNameRef) { Tcl_PathType type = TCL_PATH_ABSOLUTE; const char *path = TclGetString(pathPtr); switch (tclPlatform) { case TCL_PLATFORM_UNIX: { const char *origPath = path; /* * Paths that begin with / are absolute. */ if (path[0] == '/') { ++path; /* * Check for "//" network path prefix */ if ((*path == '/') && path[1] && (path[1] != '/')) { path += 2; while (*path && *path != '/') { ++path; } } if (driveNameLengthPtr != NULL) { /* * We need this addition in case the "//" code was used. */ *driveNameLengthPtr = (path - origPath); } } else { type = TCL_PATH_RELATIVE; } break; } case TCL_PLATFORM_WINDOWS: { Tcl_DString ds; const char *rootEnd; Tcl_DStringInit(&ds); rootEnd = ExtractWinRoot(path, &ds, 0, &type); if ((rootEnd != path) && (driveNameLengthPtr != NULL)) { *driveNameLengthPtr = rootEnd - path; if (driveNameRef != NULL) { *driveNameRef = Tcl_DStringToObj(&ds); Tcl_IncrRefCount(*driveNameRef); } } Tcl_DStringFree(&ds); break; } } return type; } /* *--------------------------------------------------------------------------- * * TclpNativeSplitPath -- * * This function takes the given Tcl_Obj, which should be a valid path, * and returns a Tcl List object containing each segment of that path as * an element. * * Note this function currently calls the older Split(Plat)Path * functions, which require more memory allocation than is desirable. * * Results: * Returns list object with refCount of zero. If the passed in lenPtr is * non-NULL, we use it to return the number of elements in the returned * list. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Obj * TclpNativeSplitPath( Tcl_Obj *pathPtr, /* Path to split. */ Tcl_Size *lenPtr) /* int to store number of path elements. */ { Tcl_Obj *resultPtr = NULL; /* Needed only to prevent gcc warnings. */ /* * Perform platform specific splitting. */ switch (tclPlatform) { case TCL_PLATFORM_UNIX: resultPtr = SplitUnixPath(TclGetString(pathPtr)); break; case TCL_PLATFORM_WINDOWS: resultPtr = SplitWinPath(TclGetString(pathPtr)); break; } /* * Compute the number of elements in the result. */ if (lenPtr != NULL) { TclListObjLength(NULL, resultPtr, lenPtr); } return resultPtr; } /* *---------------------------------------------------------------------- * * Tcl_SplitPath -- * * Split a path into a list of path components. The first element of the * list will have the same path type as the original path. * * Results: * Returns a standard Tcl result. The interpreter result contains a list * of path components. *argvPtr will be filled in with the address of an * array whose elements point to the elements of path, in order. * *argcPtr will get filled in with the number of valid elements in the * array. A single block of memory is dynamically allocated to hold both * the argv array and a copy of the path elements. The caller must * eventually free this memory by calling Tcl_Free() on *argvPtr. Note: * *argvPtr and *argcPtr are only modified if the procedure returns * normally. * * Side effects: * Allocates memory. * *---------------------------------------------------------------------- */ #undef Tcl_SplitPath void Tcl_SplitPath( const char *path, /* Pointer to string containing a path. */ Tcl_Size *argcPtr, /* Pointer to location to fill in with the * number of elements in the path. */ const char ***argvPtr) /* Pointer to place to store pointer to array * of pointers to path elements. */ { Tcl_Obj *resultPtr = NULL; /* Needed only to prevent gcc warnings. */ Tcl_Obj *tmpPtr, *eltPtr; Tcl_Size i, size, len; char *p; const char *str; /* * Perform the splitting, using objectified, vfs-aware code. */ tmpPtr = Tcl_NewStringObj(path, -1); Tcl_IncrRefCount(tmpPtr); resultPtr = Tcl_FSSplitPath(tmpPtr, argcPtr); Tcl_IncrRefCount(resultPtr); Tcl_DecrRefCount(tmpPtr); /* * Calculate space required for the result. */ size = 1; for (i = 0; i < *argcPtr; i++) { Tcl_ListObjIndex(NULL, resultPtr, i, &eltPtr); (void)TclGetStringFromObj(eltPtr, &len); size += len + 1; } /* * Allocate a buffer large enough to hold the contents of all of the list * plus the argv pointers and the terminating NULL pointer. */ *argvPtr = (const char **)Tcl_Alloc( ((((*argcPtr) + 1) * sizeof(char *)) + size)); /* * Position p after the last argv pointer and copy the contents of the * list in, piece by piece. */ p = (char *) &(*argvPtr)[(*argcPtr) + 1]; for (i = 0; i < *argcPtr; i++) { Tcl_ListObjIndex(NULL, resultPtr, i, &eltPtr); str = TclGetStringFromObj(eltPtr, &len); memcpy(p, str, len + 1); p += len+1; } /* * Now set up the argv pointers. */ p = (char *) &(*argvPtr)[(*argcPtr) + 1]; for (i = 0; i < *argcPtr; i++) { (*argvPtr)[i] = p; while (*(p++) != '\0'); } (*argvPtr)[i] = NULL; /* * Free the result ptr given to us by Tcl_FSSplitPath */ Tcl_DecrRefCount(resultPtr); } /* *---------------------------------------------------------------------- * * SplitUnixPath -- * * This routine is used by Tcl_(FS)SplitPath to handle splitting Unix * paths. * * Results: * Returns a newly allocated Tcl list object. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_Obj * SplitUnixPath( const char *path) /* Pointer to string containing a path. */ { size_t length; const char *origPath = path, *elementStart; Tcl_Obj *result; /* * Deal with the root directory as a special case. */ TclNewObj(result); if (*path == '/') { Tcl_Obj *rootElt; ++path; /* * Check for "//" network path prefix */ if ((*path == '/') && path[1] && (path[1] != '/')) { path += 2; while (*path && *path != '/') { ++path; } } rootElt = Tcl_NewStringObj(origPath, path - origPath); Tcl_ListObjAppendElement(NULL, result, rootElt); while (*path == '/') { ++path; } } /* * Split on slashes. */ for (;;) { elementStart = path; while ((*path != '\0') && (*path != '/')) { path++; } length = path - elementStart; if (length > 0) { Tcl_Obj *nextElt; nextElt = Tcl_NewStringObj(elementStart, length); Tcl_ListObjAppendElement(NULL, result, nextElt); } if (*path++ == '\0') { break; } } return result; } /* *---------------------------------------------------------------------- * * SplitWinPath -- * * This routine is used by Tcl_(FS)SplitPath to handle splitting Windows * paths. * * Results: * Returns a newly allocated Tcl list object. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_Obj * SplitWinPath( const char *path) /* Pointer to string containing a path. */ { size_t length; const char *p, *elementStart; Tcl_PathType type = TCL_PATH_ABSOLUTE; Tcl_DString buf; Tcl_Obj *result; Tcl_DStringInit(&buf); TclNewObj(result); p = ExtractWinRoot(path, &buf, 0, &type); /* * Terminate the root portion, if we matched something. */ if (p != path) { Tcl_ListObjAppendElement(NULL, result, Tcl_DStringToObj(&buf)); } Tcl_DStringFree(&buf); /* * Split on slashes. */ do { elementStart = p; while ((*p != '\0') && (*p != '/') && (*p != '\\')) { p++; } length = p - elementStart; if (length > 0) { Tcl_Obj *nextElt; if ((elementStart != path) && isalpha(UCHAR(elementStart[0])) && (elementStart[1] == ':')) { TclNewLiteralStringObj(nextElt, "./"); Tcl_AppendToObj(nextElt, elementStart, length); } else { nextElt = Tcl_NewStringObj(elementStart, length); } Tcl_ListObjAppendElement(NULL, result, nextElt); } } while (*p++ != '\0'); return result; } /* *--------------------------------------------------------------------------- * * Tcl_FSJoinToPath -- * * This function takes the given object, which should usually be a valid * path or NULL, and joins onto it the array of paths segments given. * * The objects in the array given will temporarily have their refCount * increased by one, and then decreased by one when this function exits * (which means if they had zero refCount when we were called, they will * be freed). * * Results: * Returns object owned by the caller (which should increment its * refCount) - typically an object with refCount of zero. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSJoinToPath( Tcl_Obj *pathPtr, /* Valid path or NULL. */ Tcl_Size objc, /* Number of array elements to join */ Tcl_Obj *const objv[]) /* Path elements to join. */ { if (pathPtr == NULL) { return TclJoinPath(objc, objv, 0); } if (objc == 0) { return TclJoinPath(1, &pathPtr, 0); } if (objc == 1) { Tcl_Obj *pair[2]; pair[0] = pathPtr; pair[1] = objv[0]; return TclJoinPath(2, pair, 0); } else { Tcl_Size elemc = objc + 1; Tcl_Obj *ret, **elemv = (Tcl_Obj**)Tcl_Alloc(elemc*sizeof(Tcl_Obj *)); elemv[0] = pathPtr; memcpy(elemv+1, objv, objc*sizeof(Tcl_Obj *)); ret = TclJoinPath(elemc, elemv, 0); Tcl_Free(elemv); return ret; } } /* *--------------------------------------------------------------------------- * * TclpNativeJoinPath -- * * 'prefix' is absolute, 'joining' is relative to prefix. * * Results: * modifies prefix * * Side effects: * None. * *--------------------------------------------------------------------------- */ void TclpNativeJoinPath( Tcl_Obj *prefix, const char *joining) { int needsSep; Tcl_Size length; char *dest; const char *p; const char *start; start = TclGetStringFromObj(prefix, &length); /* * Remove the ./ from drive-letter prefixed * elements on Windows, unless it is the first component. */ p = joining; if (length != 0) { if ((p[0] == '.') && (p[1] == '/') && (tclPlatform==TCL_PLATFORM_WINDOWS) && isalpha(UCHAR(p[2])) && (p[3] == ':')) { p += 2; } } if (*p == '\0') { return; } switch (tclPlatform) { case TCL_PLATFORM_UNIX: /* * Append a separator if needed. */ if (length > 0 && (start[length-1] != '/')) { Tcl_AppendToObj(prefix, "/", 1); (void)TclGetStringFromObj(prefix, &length); } needsSep = 0; /* * Append the element, eliminating duplicate and trailing slashes. */ Tcl_SetObjLength(prefix, length + strlen(p)); dest = TclGetString(prefix) + length; for (; *p != '\0'; p++) { if (*p == '/') { while (p[1] == '/') { p++; } if (p[1] != '\0' && needsSep) { *dest++ = '/'; } } else { *dest++ = *p; needsSep = 1; } } length = dest - TclGetString(prefix); Tcl_SetObjLength(prefix, length); break; case TCL_PLATFORM_WINDOWS: /* * Check to see if we need to append a separator. */ if ((length > 0) && (start[length-1] != '/') && (start[length-1] != ':')) { Tcl_AppendToObj(prefix, "/", 1); (void)TclGetStringFromObj(prefix, &length); } needsSep = 0; /* * Append the element, eliminating duplicate and trailing slashes. */ Tcl_SetObjLength(prefix, length + (int) strlen(p)); dest = TclGetString(prefix) + length; for (; *p != '\0'; p++) { if ((*p == '/') || (*p == '\\')) { while ((p[1] == '/') || (p[1] == '\\')) { p++; } if ((p[1] != '\0') && needsSep) { *dest++ = '/'; } } else { *dest++ = *p; needsSep = 1; } } length = dest - TclGetString(prefix); Tcl_SetObjLength(prefix, length); break; } return; } /* *---------------------------------------------------------------------- * * Tcl_JoinPath -- * * Combine a list of paths in a platform specific manner. The function * 'Tcl_FSJoinPath' should be used in preference where possible. * * Results: * Appends the joined path to the end of the specified Tcl_DString * returning a pointer to the resulting string. Note that the * Tcl_DString must already be initialized. * * Side effects: * Modifies the Tcl_DString. * *---------------------------------------------------------------------- */ char * Tcl_JoinPath( Tcl_Size argc, const char *const *argv, Tcl_DString *resultPtr) /* Pointer to previously initialized DString */ { Tcl_Size i, len; Tcl_Obj *listObj; Tcl_Obj *resultObj; const char *resultStr; /* * Build the list of paths. */ TclNewObj(listObj); for (i = 0; i < argc; i++) { Tcl_ListObjAppendElement(NULL, listObj, Tcl_NewStringObj(argv[i], -1)); } /* * Ask the objectified code to join the paths. */ Tcl_IncrRefCount(listObj); resultObj = Tcl_FSJoinPath(listObj, argc); Tcl_IncrRefCount(resultObj); Tcl_DecrRefCount(listObj); /* * Store the result. */ resultStr = TclGetStringFromObj(resultObj, &len); Tcl_DStringAppend(resultPtr, resultStr, len); Tcl_DecrRefCount(resultObj); /* * Return a pointer to the result. */ return Tcl_DStringValue(resultPtr); } /* *--------------------------------------------------------------------------- * * Tcl_TranslateFileName -- * * Converts a file name into a form usable by the native system * interfaces. * * Results: * The return value is a pointer to a string containing the name. * This may either be the name pointer passed in or space allocated in * bufferPtr. In all cases, if the return value is not NULL, the caller * must call Tcl_DStringFree() to free the space. If there was an * error in processing the name, then an error message is left in the * interp's result (if interp was not NULL) and the return value is NULL. * * Side effects: * None. * *---------------------------------------------------------------------- */ char * Tcl_TranslateFileName( Tcl_Interp *interp, /* Interpreter in which to store error message * (if necessary). */ const char *name, /* File name, which may begin with "~" (to * indicate current user's home directory) or * "~" (to indicate any user's home * directory). */ Tcl_DString *bufferPtr) /* Uninitialized or free DString filled with * name. */ { Tcl_Obj *path = Tcl_NewStringObj(name, -1); Tcl_Obj *transPtr; Tcl_IncrRefCount(path); transPtr = Tcl_FSGetTranslatedPath(interp, path); if (transPtr == NULL) { Tcl_DecrRefCount(path); return NULL; } Tcl_DStringInit(bufferPtr); TclDStringAppendObj(bufferPtr, transPtr); Tcl_DecrRefCount(path); Tcl_DecrRefCount(transPtr); /* * Convert forward slashes to backslashes in Windows paths because some * system interfaces don't accept forward slashes. */ if (tclPlatform == TCL_PLATFORM_WINDOWS) { char *p; for (p = Tcl_DStringValue(bufferPtr); *p != '\0'; p++) { if (*p == '/') { *p = '\\'; } } } return Tcl_DStringValue(bufferPtr); } /* *---------------------------------------------------------------------- * * TclGetExtension -- * * This function returns a pointer to the beginning of the extension part * of a file name. * * Results: * Returns a pointer into name which indicates where the extension * starts. If there is no extension, returns NULL. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * TclGetExtension( const char *name) /* File name to parse. */ { const char *p, *lastSep; /* * First find the last directory separator. */ lastSep = NULL; /* Needed only to prevent gcc warnings. */ switch (tclPlatform) { case TCL_PLATFORM_UNIX: lastSep = strrchr(name, '/'); break; case TCL_PLATFORM_WINDOWS: lastSep = NULL; for (p = name; *p != '\0'; p++) { if (strchr("/\\:", *p) != NULL) { lastSep = p; } } break; } p = strrchr(name, '.'); if ((p != NULL) && (lastSep != NULL) && (lastSep > p)) { p = NULL; } /* * In earlier versions, we used to back up to the first period in a series * so that "foo..o" would be split into "foo" and "..o". This is a * confusing and usually incorrect behavior, so now we split at the last * period in the name. */ return p; } /* *---------------------------------------------------------------------- * * Tcl_GlobObjCmd -- * * This procedure is invoked to process the "glob" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_GlobObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int i, globFlags, join, dir, result; Tcl_Size length; char *string; const char *separators; Tcl_Obj *typePtr, *look; Tcl_Obj *pathOrDir = NULL; Tcl_DString prefix; static const char *const options[] = { "-directory", "-join", "-nocomplain", "-path", "-tails", "-types", "--", NULL }; enum globOptionsEnum { GLOB_DIR, GLOB_JOIN, GLOB_NOCOMPLAIN, GLOB_PATH, GLOB_TAILS, GLOB_TYPE, GLOB_LAST } index; enum pathDirOptions {PATH_NONE = -1 , PATH_GENERAL = 0, PATH_DIR = 1}; Tcl_GlobTypeData *globTypes = NULL; globFlags = 0; join = 0; dir = PATH_NONE; typePtr = NULL; for (i = 1; i < objc; i++) { if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", 0, &index) != TCL_OK) { string = TclGetString(objv[i]); if (string[0] == '-') { /* * It looks like the command contains an option so signal an * error. */ return TCL_ERROR; } else { /* * This clearly isn't an option; assume it's the first glob * pattern. We must clear the error. */ Tcl_ResetResult(interp); break; } } switch (index) { case GLOB_NOCOMPLAIN: /* -nocomplain */ /* * Do nothing; This is normal operations in Tcl 9. * Keep accepting as a no-op option to accommodate old scripts. */ break; case GLOB_DIR: /* -dir */ if (i == (objc-1)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "missing argument to \"-directory\"", -1)); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", (char *)NULL); return TCL_ERROR; } if (dir != PATH_NONE) { Tcl_SetObjResult(interp, Tcl_NewStringObj( dir == PATH_DIR ? "\"-directory\" may only be used once" : "\"-directory\" cannot be used with \"-path\"", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "GLOB", "BADOPTIONCOMBINATION", (char *)NULL); return TCL_ERROR; } dir = PATH_DIR; globFlags |= TCL_GLOBMODE_DIR; pathOrDir = objv[i+1]; i++; break; case GLOB_JOIN: /* -join */ join = 1; break; case GLOB_TAILS: /* -tails */ globFlags |= TCL_GLOBMODE_TAILS; break; case GLOB_PATH: /* -path */ if (i == (objc-1)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "missing argument to \"-path\"", -1)); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", (char *)NULL); return TCL_ERROR; } if (dir != PATH_NONE) { Tcl_SetObjResult(interp, Tcl_NewStringObj( dir == PATH_GENERAL ? "\"-path\" may only be used once" : "\"-path\" cannot be used with \"-dictionary\"", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "GLOB", "BADOPTIONCOMBINATION", (char *)NULL); return TCL_ERROR; } dir = PATH_GENERAL; pathOrDir = objv[i+1]; i++; break; case GLOB_TYPE: /* -types */ if (i == (objc-1)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "missing argument to \"-types\"", -1)); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", (char *)NULL); return TCL_ERROR; } typePtr = objv[i+1]; if (TclListObjLength(interp, typePtr, &length) != TCL_OK) { return TCL_ERROR; } i++; break; case GLOB_LAST: /* -- */ i++; goto endOfForLoop; } } endOfForLoop: if ((globFlags & TCL_GLOBMODE_TAILS) && (pathOrDir == NULL)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "\"-tails\" must be used with either " "\"-directory\" or \"-path\"", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "GLOB", "BADOPTIONCOMBINATION", (char *)NULL); return TCL_ERROR; } separators = NULL; switch (tclPlatform) { case TCL_PLATFORM_UNIX: separators = "/"; break; case TCL_PLATFORM_WINDOWS: separators = "/\\:"; break; } if (dir == PATH_GENERAL) { Tcl_Size pathlength; const char *last; const char *first = TclGetStringFromObj(pathOrDir,&pathlength); /* * Find the last path separator in the path */ last = first + pathlength; for (; last != first; last--) { if (strchr(separators, last[-1]) != NULL) { break; } } if (last == first + pathlength) { /* * It's really a directory. */ dir = PATH_DIR; } else { Tcl_DString pref; char *search, *find; Tcl_DStringInit(&pref); if (last == first) { /* * The whole thing is a prefix. This means we must remove any * 'tails' flag too, since it is irrelevant now (the same * effect will happen without it), but in particular its use * in TclGlob requires a non-NULL pathOrDir. */ Tcl_DStringAppend(&pref, first, -1); globFlags &= ~TCL_GLOBMODE_TAILS; pathOrDir = NULL; } else { /* * Have to split off the end. */ Tcl_DStringAppend(&pref, last, first+pathlength-last); pathOrDir = Tcl_NewStringObj(first, last-first-1); /* * We must ensure that we haven't cut off too much, and turned * a valid path like '/' or 'C:/' into an incorrect path like * '' or 'C:'. The way we do this is to add a separator if * there are none presently in the prefix. Similar treatment * for the zipfs volume. */ const char *temp = TclGetString(pathOrDir); if (strpbrk(temp, "\\/") == NULL) { Tcl_AppendToObj(pathOrDir, last-1, 1); } else if (!strcmp(temp, "//zipfs:")) { Tcl_AppendToObj(pathOrDir, "/", 1); } } /* * Need to quote 'prefix'. */ Tcl_DStringInit(&prefix); search = Tcl_DStringValue(&pref); while ((find = (strpbrk(search, "\\[]*?{}"))) != NULL) { Tcl_DStringAppend(&prefix, search, find-search); TclDStringAppendLiteral(&prefix, "\\"); Tcl_DStringAppend(&prefix, find, 1); search = find+1; if (*search == '\0') { break; } } if (*search != '\0') { Tcl_DStringAppend(&prefix, search, -1); } Tcl_DStringFree(&pref); } } if (pathOrDir != NULL) { Tcl_IncrRefCount(pathOrDir); } if (typePtr != NULL) { /* * The rest of the possible type arguments (except 'd') are platform * specific. We don't complain when they are used on an incompatible * platform. */ TclListObjLength(interp, typePtr, &length); if (length == 0) { goto skipTypes; } globTypes = (Tcl_GlobTypeData *)TclStackAlloc(interp, sizeof(Tcl_GlobTypeData)); globTypes->type = 0; globTypes->perm = 0; globTypes->macType = NULL; globTypes->macCreator = NULL; while (length-- > 0) { Tcl_Size len; const char *str; Tcl_ListObjIndex(interp, typePtr, length, &look); str = TclGetStringFromObj(look, &len); if (strcmp("readonly", str) == 0) { globTypes->perm |= TCL_GLOB_PERM_RONLY; } else if (strcmp("hidden", str) == 0) { globTypes->perm |= TCL_GLOB_PERM_HIDDEN; } else if (len == 1) { switch (str[0]) { case 'r': globTypes->perm |= TCL_GLOB_PERM_R; break; case 'w': globTypes->perm |= TCL_GLOB_PERM_W; break; case 'x': globTypes->perm |= TCL_GLOB_PERM_X; break; case 'b': globTypes->type |= TCL_GLOB_TYPE_BLOCK; break; case 'c': globTypes->type |= TCL_GLOB_TYPE_CHAR; break; case 'd': globTypes->type |= TCL_GLOB_TYPE_DIR; break; case 'p': globTypes->type |= TCL_GLOB_TYPE_PIPE; break; case 'f': globTypes->type |= TCL_GLOB_TYPE_FILE; break; case 'l': globTypes->type |= TCL_GLOB_TYPE_LINK; break; case 's': globTypes->type |= TCL_GLOB_TYPE_SOCK; break; default: goto badTypesArg; } } else if (len == 4) { /* * This is assumed to be a MacOS file type. */ if (globTypes->macType != NULL) { goto badMacTypesArg; } globTypes->macType = look; Tcl_IncrRefCount(look); } else { Tcl_Obj *item; Tcl_Size llen; if ((TclListObjLength(NULL, look, &llen) == TCL_OK) && (llen == 3)) { Tcl_ListObjIndex(interp, look, 0, &item); if (!strcmp("macintosh", TclGetString(item))) { Tcl_ListObjIndex(interp, look, 1, &item); if (!strcmp("type", TclGetString(item))) { Tcl_ListObjIndex(interp, look, 2, &item); if (globTypes->macType != NULL) { goto badMacTypesArg; } globTypes->macType = item; Tcl_IncrRefCount(item); continue; } else if (!strcmp("creator", TclGetString(item))) { Tcl_ListObjIndex(interp, look, 2, &item); if (globTypes->macCreator != NULL) { goto badMacTypesArg; } globTypes->macCreator = item; Tcl_IncrRefCount(item); continue; } } } /* * Error cases. We reset the 'join' flag to zero, since we * haven't yet made use of it. */ badTypesArg: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad argument to \"-types\": %s", TclGetString(look))); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "BAD", (char *)NULL); result = TCL_ERROR; join = 0; goto endOfGlob; badMacTypesArg: Tcl_SetObjResult(interp, Tcl_NewStringObj( "only one MacOS type or creator argument" " to \"-types\" allowed", -1)); result = TCL_ERROR; Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "BAD", (char *)NULL); join = 0; goto endOfGlob; } } } skipTypes: /* * Now we perform the actual glob below. This may involve joining together * the pattern arguments, dealing with particular file types etc. We use a * 'goto' to ensure we free any memory allocated along the way. */ objc -= i; objv += i; result = TCL_OK; if (join) { if (dir != PATH_GENERAL) { Tcl_DStringInit(&prefix); } for (i = 0; i < objc; i++) { TclDStringAppendObj(&prefix, objv[i]); if (i != objc -1) { Tcl_DStringAppend(&prefix, separators, 1); } } if (TclGlob(interp, Tcl_DStringValue(&prefix), pathOrDir, globFlags, globTypes) != TCL_OK) { result = TCL_ERROR; goto endOfGlob; } } else if (dir == PATH_GENERAL) { Tcl_DString str; Tcl_DStringInit(&str); for (i = 0; i < objc; i++) { Tcl_DStringSetLength(&str, 0); if (dir == PATH_GENERAL) { TclDStringAppendDString(&str, &prefix); } TclDStringAppendObj(&str, objv[i]); if (TclGlob(interp, Tcl_DStringValue(&str), pathOrDir, globFlags, globTypes) != TCL_OK) { result = TCL_ERROR; Tcl_DStringFree(&str); goto endOfGlob; } } Tcl_DStringFree(&str); } else { for (i = 0; i < objc; i++) { string = TclGetString(objv[i]); if (TclGlob(interp, string, pathOrDir, globFlags, globTypes) != TCL_OK) { result = TCL_ERROR; goto endOfGlob; } } } endOfGlob: if (join || (dir == PATH_GENERAL)) { Tcl_DStringFree(&prefix); } if (pathOrDir != NULL) { Tcl_DecrRefCount(pathOrDir); } if (globTypes != NULL) { if (globTypes->macType != NULL) { Tcl_DecrRefCount(globTypes->macType); } if (globTypes->macCreator != NULL) { Tcl_DecrRefCount(globTypes->macCreator); } TclStackFree(interp, globTypes); } return result; } /* *---------------------------------------------------------------------- * * TclGlob -- * * Sets the separator string based on the platform and calls DoGlob. * * The interpreter's result, on entry to this function, must be a valid * Tcl list (e.g. it could be empty), since we will lappend any new * results to that list. If it is not a valid list, this function will * fail to do anything very meaningful. * * Note that if globFlags contains 'TCL_GLOBMODE_TAILS' then pathPrefix * cannot be NULL (it is only allowed with -dir or -path). * * Results: * The return value is a standard Tcl result indicating whether an error * occurred in globbing. After a normal return the result in interp (set * by DoGlob) holds all of the file names given by the pattern and * pathPrefix arguments. After an error the result in interp will hold * an error message. * * Side effects: * The 'pattern' is written to. * *---------------------------------------------------------------------- */ static int TclGlob( Tcl_Interp *interp, /* Interpreter for returning error message or * appending list of matching file names. */ char *pattern, /* Glob pattern to match. Must not refer to a * static string. */ Tcl_Obj *pathPrefix, /* Path prefix to glob pattern, if non-null, * which is considered literally. */ int globFlags, /* Stores or'ed combination of flags */ Tcl_GlobTypeData *types) /* Struct containing acceptable types. May be * NULL. */ { const char *separators; char *tail; int result; Tcl_Obj *filenamesObj, *savedResultObj; separators = NULL; switch (tclPlatform) { case TCL_PLATFORM_UNIX: separators = "/"; break; case TCL_PLATFORM_WINDOWS: separators = "/\\:"; break; } if (pathPrefix != NULL) { Tcl_IncrRefCount(pathPrefix); } tail = pattern; /* * Handling empty path prefixes with glob patterns like 'C:' or * 'c:////////' is a pain on Windows if we leave it too late, since these * aren't really patterns at all! We therefore check the head of the * pattern now for such cases, if we don't have an unquoted prefix yet. * * Similarly on Unix with '/' at the head of the pattern -- it just * indicates the root volume, so we treat it as such. */ if (tclPlatform == TCL_PLATFORM_WINDOWS) { if (pathPrefix == NULL && tail[0] != '\0' && tail[1] == ':') { char *p = tail + 1; pathPrefix = Tcl_NewStringObj(tail, 1); while (*p != '\0') { char c = p[1]; if (*p == '\\') { if (strchr(separators, c) != NULL) { if (c == '\\') { c = '/'; } Tcl_AppendToObj(pathPrefix, &c, 1); p++; } else { break; } } else if (strchr(separators, *p) != NULL) { Tcl_AppendToObj(pathPrefix, p, 1); } else { break; } p++; } tail = p; Tcl_IncrRefCount(pathPrefix); } else if (pathPrefix == NULL && (tail[0] == '/' || (tail[0] == '\\' && tail[1] == '\\'))) { Tcl_Size driveNameLen; Tcl_Obj *driveName; Tcl_Obj *temp = Tcl_NewStringObj(tail, -1); Tcl_IncrRefCount(temp); switch (TclGetPathType(temp, NULL, &driveNameLen, &driveName)) { case TCL_PATH_VOLUME_RELATIVE: { /* * Volume relative path which is equivalent to a path in the * root of the cwd's volume. We will actually return * non-volume-relative paths here. i.e. 'glob /foo*' will * return 'C:/foobar'. This is much the same as globbing for a * path with '\\' will return one with '/' on Windows. */ Tcl_Obj *cwd = Tcl_FSGetCwd(interp); if (cwd == NULL) { Tcl_DecrRefCount(temp); return TCL_ERROR; } pathPrefix = Tcl_NewStringObj(TclGetString(cwd), 3); Tcl_DecrRefCount(cwd); if (tail[0] == '/') { tail++; } else { tail += 2; } Tcl_IncrRefCount(pathPrefix); break; } case TCL_PATH_ABSOLUTE: /* * Absolute, possibly network path //Machine/Share. Use that * as the path prefix (it already has a refCount). */ pathPrefix = driveName; tail += driveNameLen; break; case TCL_PATH_RELATIVE: /* Do nothing */ break; } Tcl_DecrRefCount(temp); } /* * ':' no longer needed as a separator. It is only relevant to the * beginning of the path. */ separators = "/\\"; } else if (tclPlatform == TCL_PLATFORM_UNIX) { if (pathPrefix == NULL && tail[0] == '/' && tail[1] != '/') { pathPrefix = Tcl_NewStringObj(tail, 1); tail++; Tcl_IncrRefCount(pathPrefix); } } /* * Finally if we still haven't managed to generate a path prefix, check if * the path starts with a current volume. */ if (pathPrefix == NULL) { Tcl_Size driveNameLen; Tcl_Obj *driveName; if (TclFSNonnativePathType(tail, strlen(tail), NULL, &driveNameLen, &driveName) == TCL_PATH_ABSOLUTE) { pathPrefix = driveName; tail += driveNameLen; } } /* * To process a [glob] invocation, this function may be called multiple * times. Each time, the previously discovered filenames are in the * interpreter result. We stash that away here so the result is free for * error messages. */ savedResultObj = Tcl_GetObjResult(interp); Tcl_IncrRefCount(savedResultObj); Tcl_ResetResult(interp); TclNewObj(filenamesObj); Tcl_IncrRefCount(filenamesObj); /* * Now we do the actual globbing, adding filenames as we go to buffer in * filenamesObj */ if (*tail == '\0' && pathPrefix != NULL) { /* * An empty pattern. This means 'pathPrefix' is actually a full path * of a file/directory we want to simply check for existence and type. */ if (types == NULL) { /* * We just want to check for existence. In this case we make it * easy on Tcl_FSMatchInDirectory and its sub-implementations by * not bothering them (even though they should support this * situation) and we just use the simple existence check with * Tcl_FSAccess. */ if (Tcl_FSAccess(pathPrefix, F_OK) == 0) { Tcl_ListObjAppendElement(interp, filenamesObj, pathPrefix); } result = TCL_OK; } else { /* * We want to check for the correct type. Tcl_FSMatchInDirectory * is documented to do this for us, if we give it a NULL pattern. */ result = Tcl_FSMatchInDirectory(interp, filenamesObj, pathPrefix, NULL, types); } } else { result = DoGlob(interp, filenamesObj, separators, pathPrefix, globFlags & TCL_GLOBMODE_DIR, tail, types); } /* * Check for errors... */ if (result != TCL_OK) { TclDecrRefCount(filenamesObj); TclDecrRefCount(savedResultObj); if (pathPrefix != NULL) { Tcl_DecrRefCount(pathPrefix); } return result; } /* * If we only want the tails, we must strip off the prefix now. It may * seem more efficient to pass the tails flag down into DoGlob, * Tcl_FSMatchInDirectory, but those functions are continually adjusting * the prefix as the various pieces of the pattern are assimilated, so * that would add a lot of complexity to the code. This way is a little * slower (when the -tails flag is given), but much simpler to code. * * We do it by rewriting the result list in-place. */ if (globFlags & TCL_GLOBMODE_TAILS) { Tcl_Size objc, i; Tcl_Obj **objv; Tcl_Size prefixLen; const char *pre; /* * If this length has never been set, set it here. */ if (pathPrefix == NULL) { Tcl_Panic("Called TclGlob with TCL_GLOBMODE_TAILS and pathPrefix==NULL"); } pre = TclGetStringFromObj(pathPrefix, &prefixLen); if (prefixLen > 0 && (strchr(separators, pre[prefixLen-1]) == NULL)) { /* * If we're on Windows and the prefix is a volume relative one * like 'C:', then there won't be a path separator in between, so * no need to skip it here. */ if ((tclPlatform != TCL_PLATFORM_WINDOWS) || (prefixLen != 2) || (pre[1] != ':')) { prefixLen++; } } TclListObjGetElements(NULL, filenamesObj, &objc, &objv); for (i = 0; i< objc; i++) { Tcl_Size len; const char *oldStr = TclGetStringFromObj(objv[i], &len); Tcl_Obj *elem; if (len == prefixLen) { if ((pattern[0] == '\0') || (strchr(separators, pattern[0]) == NULL)) { TclNewLiteralStringObj(elem, "."); } else { TclNewLiteralStringObj(elem, "/"); } } else { elem = Tcl_NewStringObj(oldStr+prefixLen, len-prefixLen); } Tcl_ListObjReplace(interp, filenamesObj, i, 1, 1, &elem); } } /* * Now we have a list of discovered filenames in filenamesObj and a list * of previously discovered (saved earlier from the interpreter result) in * savedResultObj. Merge them and put them back in the interpreter result. */ if (Tcl_IsShared(savedResultObj)) { TclDecrRefCount(savedResultObj); savedResultObj = Tcl_DuplicateObj(savedResultObj); Tcl_IncrRefCount(savedResultObj); } if (Tcl_ListObjAppendList(interp, savedResultObj, filenamesObj) != TCL_OK){ result = TCL_ERROR; } else { Tcl_SetObjResult(interp, savedResultObj); } TclDecrRefCount(savedResultObj); TclDecrRefCount(filenamesObj); if (pathPrefix != NULL) { Tcl_DecrRefCount(pathPrefix); } return result; } /* *---------------------------------------------------------------------- * * SkipToChar -- * * This function traverses a glob pattern looking for the next unquoted * occurrence of the specified character at the same braces nesting level. * * Results: * Updates stringPtr to point to the matching character, or to the end of * the string if nothing matched. The return value is 1 if a match was * found at the top level, otherwise it is 0. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int SkipToChar( char **stringPtr, /* Pointer string to check. */ int match) /* Character to find. */ { int quoted, level; char *p; quoted = 0; level = 0; for (p = *stringPtr; *p != '\0'; p++) { if (quoted) { quoted = 0; continue; } if ((level == 0) && (*p == match)) { *stringPtr = p; return 1; } if (*p == '{') { level++; } else if (*p == '}') { level--; } else if (*p == '\\') { quoted = 1; } } *stringPtr = p; return 0; } /* *---------------------------------------------------------------------- * * DoGlob -- * * This recursive procedure forms the heart of the globbing code. It * performs a depth-first traversal of the tree given by the path name to * be globbed and the pattern. The directory and remainder are assumed to * be native format paths. The prefix contained in 'pathPtr' is either a * directory or path from which to start the search (or NULL). If pathPtr * is NULL, then the pattern must not start with an absolute path * specification (that case should be handled by moving the absolute path * prefix into pathPtr before calling DoGlob). * * Results: * The return value is a standard Tcl result indicating whether an error * occurred in globbing. After a normal return the result in interp will * be set to hold all of the file names given by the dir and remaining * arguments. After an error the result in interp will hold an error * message. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int DoGlob( Tcl_Interp *interp, /* Interpreter to use for error reporting * (e.g. unmatched brace). */ Tcl_Obj *matchesObj, /* Unshared list object in which to place all * resulting filenames. Caller allocates and * deallocates; DoGlob must not touch the * refCount of this object. */ const char *separators, /* String containing separator characters that * should be used to identify globbing * boundaries. */ Tcl_Obj *pathPtr, /* Completely expanded prefix. */ int flags, /* If non-zero then pathPtr is a directory */ char *pattern, /* The pattern to match against. Must not be a * pointer to a static string. */ Tcl_GlobTypeData *types) /* List object containing list of acceptable * types. May be NULL. */ { int baseLength, quoted; int result = TCL_OK; char *name, *p, *openBrace, *closeBrace, *firstSpecialChar; Tcl_Obj *joinedPtr; /* * Consume any leading directory separators, leaving pattern pointing just * past the last initial separator. */ name = pattern; for (; *pattern != '\0'; pattern++) { if (*pattern == '\\') { /* * If the first character is escaped, either we have a directory * separator, or we have any other character. In the latter case * the rest is a pattern, and we must break from the loop. This * is particularly important on Windows where '\' is both the * escaping character and a directory separator. */ if (strchr(separators, pattern[1]) != NULL) { pattern++; } else { break; } } else if (strchr(separators, *pattern) == NULL) { break; } } /* * Look for the first matching pair of braces or the first directory * separator that is not inside a pair of braces. */ openBrace = closeBrace = NULL; quoted = 0; for (p = pattern; *p != '\0'; p++) { if (quoted) { quoted = 0; } else if (*p == '\\') { quoted = 1; if (strchr(separators, p[1]) != NULL) { /* * Quoted directory separator. */ break; } } else if (strchr(separators, *p) != NULL) { /* * Unquoted directory separator. */ break; } else if (*p == '{') { openBrace = p; p++; if (SkipToChar(&p, '}')) { /* * Balanced braces. */ closeBrace = p; break; } Tcl_SetObjResult(interp, Tcl_NewStringObj( "unmatched open-brace in file name", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "GLOB", "BALANCE", (char *)NULL); return TCL_ERROR; } else if (*p == '}') { Tcl_SetObjResult(interp, Tcl_NewStringObj( "unmatched close-brace in file name", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "GLOB", "BALANCE", (char *)NULL); return TCL_ERROR; } } /* * Substitute the alternate patterns from the braces and recurse. */ if (openBrace != NULL) { char *element; Tcl_DString newName; Tcl_DStringInit(&newName); /* * For each element within in the outermost pair of braces, append the * element and the remainder to the fixed portion before the first * brace and recursively call DoGlob. */ Tcl_DStringAppend(&newName, pattern, openBrace-pattern); baseLength = Tcl_DStringLength(&newName); *closeBrace = '\0'; for (p = openBrace; p != closeBrace; ) { p++; element = p; SkipToChar(&p, ','); Tcl_DStringSetLength(&newName, baseLength); Tcl_DStringAppend(&newName, element, p-element); Tcl_DStringAppend(&newName, closeBrace+1, -1); result = DoGlob(interp, matchesObj, separators, pathPtr, flags, Tcl_DStringValue(&newName), types); if (result != TCL_OK) { break; } } *closeBrace = '}'; Tcl_DStringFree(&newName); return result; } /* * At this point, there are no more brace substitutions to perform on this * path component. The variable p is pointing at a quoted or unquoted * directory separator or the end of the string. So we need to check for * special globbing characters in the current pattern. We avoid modifying * pattern if p is pointing at the end of the string. * * If we find any globbing characters, then we must call * Tcl_FSMatchInDirectory. If we're at the end of the string, then that's * all we need to do. If we're not at the end of the string, then we must * recurse, so we do that below. * * Alternatively, if there are no globbing characters then again there are * two cases. If we're at the end of the string, we just need to check for * the given path's existence and type. If we're not at the end of the * string, we recurse. */ if (*p != '\0') { char savedChar = *p; /* * Note that we are modifying the string in place. This won't work if * the string is a static. */ *p = '\0'; firstSpecialChar = strpbrk(pattern, "*[]?\\"); *p = savedChar; } else { firstSpecialChar = strpbrk(pattern, "*[]?\\"); } if (firstSpecialChar != NULL) { /* * Look for matching files in the given directory. The implementation * of this function is filesystem specific. For each file that * matches, it will add the match onto the resultPtr given. */ static Tcl_GlobTypeData dirOnly = { TCL_GLOB_TYPE_DIR, 0, NULL, NULL }; char save = *p; Tcl_Obj *subdirsPtr; if (*p == '\0') { return Tcl_FSMatchInDirectory(interp, matchesObj, pathPtr, pattern, types); } /* * We do the recursion ourselves. This makes implementing * Tcl_FSMatchInDirectory for each filesystem much easier. */ *p = '\0'; TclNewObj(subdirsPtr); Tcl_IncrRefCount(subdirsPtr); result = Tcl_FSMatchInDirectory(interp, subdirsPtr, pathPtr, pattern, &dirOnly); *p = save; if (result == TCL_OK) { Tcl_Size i, subdirc, repair = -1; Tcl_Obj **subdirv; result = TclListObjGetElements(interp, subdirsPtr, &subdirc, &subdirv); for (i=0; result==TCL_OK && i 0) && (strchr(separators, joined[len-1]) == NULL)) { Tcl_AppendToObj(joinedPtr, "/", 1); } } Tcl_AppendToObj(joinedPtr, Tcl_DStringValue(&append), Tcl_DStringLength(&append)); } Tcl_IncrRefCount(joinedPtr); Tcl_DStringFree(&append); result = Tcl_FSMatchInDirectory(interp, matchesObj, joinedPtr, NULL, types); Tcl_DecrRefCount(joinedPtr); return result; } /* * If it's not the end of the string, we must recurse */ if (pathPtr == NULL) { joinedPtr = Tcl_NewStringObj(pattern, p-pattern); } else if (flags) { joinedPtr = TclNewFSPathObj(pathPtr, pattern, p-pattern); } else { joinedPtr = Tcl_DuplicateObj(pathPtr); if (strchr(separators, pattern[0]) == NULL) { /* * The current prefix must end in a separator, unless this is a * volume-relative path. In particular globbing in Windows shares, * when not using -dir or -path, e.g. 'glob [file join * //machine/share/subdir *]' requires adding a separator here. * This behaviour is not currently tested for in the test suite. */ Tcl_Size len; const char *joined = TclGetStringFromObj(joinedPtr,&len); if ((len > 0) && (strchr(separators, joined[len-1]) == NULL)) { if (Tcl_FSGetPathType(pathPtr) != TCL_PATH_VOLUME_RELATIVE) { Tcl_AppendToObj(joinedPtr, "/", 1); } } } Tcl_AppendToObj(joinedPtr, pattern, p-pattern); } Tcl_IncrRefCount(joinedPtr); result = DoGlob(interp, matchesObj, separators, joinedPtr, 1, p, types); Tcl_DecrRefCount(joinedPtr); return result; } /* *--------------------------------------------------------------------------- * * Tcl_AllocStatBuf -- * * This procedure allocates a Tcl_StatBuf on the heap. It exists so that * extensions may be used unchanged on systems where largefile support is * optional. * * Results: * A pointer to a Tcl_StatBuf which may be deallocated by being passed to * Tcl_Free(). * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_StatBuf * Tcl_AllocStatBuf(void) { return (Tcl_StatBuf *)Tcl_Alloc(sizeof(Tcl_StatBuf)); } /* *--------------------------------------------------------------------------- * * Access functions for Tcl_StatBuf -- * * These functions provide portable read-only access to the portable * fields of the Tcl_StatBuf structure (really a 'struct stat' * or something else related). [TIP #316] * * Results: * The value from the field being retrieved. * * Side effects: * None. * *--------------------------------------------------------------------------- */ unsigned Tcl_GetFSDeviceFromStat( const Tcl_StatBuf *statPtr) { return statPtr->st_dev; } unsigned Tcl_GetFSInodeFromStat( const Tcl_StatBuf *statPtr) { return statPtr->st_ino; } unsigned Tcl_GetModeFromStat( const Tcl_StatBuf *statPtr) { return statPtr->st_mode; } int Tcl_GetLinkCountFromStat( const Tcl_StatBuf *statPtr) { return (int)statPtr->st_nlink; } int Tcl_GetUserIdFromStat( const Tcl_StatBuf *statPtr) { return (int) statPtr->st_uid; } int Tcl_GetGroupIdFromStat( const Tcl_StatBuf *statPtr) { return (int) statPtr->st_gid; } int Tcl_GetDeviceTypeFromStat( const Tcl_StatBuf *statPtr) { return (int) statPtr->st_rdev; } long long Tcl_GetAccessTimeFromStat( const Tcl_StatBuf *statPtr) { return (long long) statPtr->st_atime; } long long Tcl_GetModificationTimeFromStat( const Tcl_StatBuf *statPtr) { return (long long) statPtr->st_mtime; } long long Tcl_GetChangeTimeFromStat( const Tcl_StatBuf *statPtr) { return (long long) statPtr->st_ctime; } unsigned long long Tcl_GetSizeFromStat( const Tcl_StatBuf *statPtr) { return (unsigned long long) statPtr->st_size; } unsigned long long Tcl_GetBlocksFromStat( const Tcl_StatBuf *statPtr) { #ifdef HAVE_STRUCT_STAT_ST_BLOCKS return (unsigned long long) statPtr->st_blocks; #else unsigned blksize = Tcl_GetBlockSizeFromStat(statPtr); return ((unsigned long long) statPtr->st_size + blksize - 1) / blksize; #endif } #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE unsigned Tcl_GetBlockSizeFromStat( const Tcl_StatBuf *statPtr) { return statPtr->st_blksize; } #else unsigned Tcl_GetBlockSizeFromStat( TCL_UNUSED(const Tcl_StatBuf *)) { /* * Not a great guess, but will do... */ return GUESSED_BLOCK_SIZE; } #endif /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclFileSystem.h0000644000175000017500000000465114726623136016140 0ustar sergeisergei/* * tclFileSystem.h -- * * This file contains the common definitions and prototypes for use by * Tcl's filesystem and path handling layers. * * Copyright (c) 2003 Vince Darley. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef _TCLFILESYSTEM #define _TCLFILESYSTEM #include "tcl.h" /* * The internal TclFS API provides routines for handling and manipulating * paths efficiently, taking direct advantage of the "path" Tcl_Obj type. * * These functions are not exported at all at present. */ MODULE_SCOPE int TclFSCwdPointerEquals(Tcl_Obj **pathPtrPtr); MODULE_SCOPE int TclFSNormalizeToUniquePath(Tcl_Interp *interp, Tcl_Obj *pathPtr, int startAt); MODULE_SCOPE Tcl_Obj * TclFSMakePathRelative(Tcl_Interp *interp, Tcl_Obj *pathPtr, Tcl_Obj *cwdPtr); MODULE_SCOPE int TclFSEnsureEpochOk(Tcl_Obj *pathPtr, const Tcl_Filesystem **fsPtrPtr); MODULE_SCOPE void TclFSSetPathDetails(Tcl_Obj *pathPtr, const Tcl_Filesystem *fsPtr, void *clientData); MODULE_SCOPE Tcl_Obj * TclFSNormalizeAbsolutePath(Tcl_Interp *interp, Tcl_Obj *pathPtr); MODULE_SCOPE size_t TclFSEpoch(void); /* * Private shared variables for use by tclIOUtil.c and tclPathObj.c */ MODULE_SCOPE const Tcl_Filesystem tclNativeFilesystem; /* * Private shared functions for use by tclIOUtil.c, tclPathObj.c and * tclFileName.c, and any platform-specific filesystem code. */ MODULE_SCOPE Tcl_PathType TclFSGetPathType(Tcl_Obj *pathPtr, const Tcl_Filesystem **filesystemPtrPtr, Tcl_Size *driveNameLengthPtr); MODULE_SCOPE Tcl_PathType TclFSNonnativePathType(const char *pathPtr, Tcl_Size pathLen, const Tcl_Filesystem **filesystemPtrPtr, Tcl_Size *driveNameLengthPtr, Tcl_Obj **driveNameRef); MODULE_SCOPE Tcl_PathType TclGetPathType(Tcl_Obj *pathPtr, const Tcl_Filesystem **filesystemPtrPtr, Tcl_Size *driveNameLengthPtr, Tcl_Obj **driveNameRef); MODULE_SCOPE int TclFSEpochOk(size_t filesystemEpoch); MODULE_SCOPE int TclFSCwdIsNative(void); MODULE_SCOPE Tcl_Obj * TclWinVolumeRelativeNormalize(Tcl_Interp *interp, const char *path, Tcl_Obj **useThisCwdPtr); MODULE_SCOPE Tcl_FSPathInFilesystemProc TclNativePathInFilesystem; MODULE_SCOPE Tcl_FSCreateInternalRepProc TclNativeCreateNativeRep; #endif /* _TCLFILESYSTEM */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclGet.c0000644000175000017500000001105414726623136014561 0ustar sergeisergei/* * tclGet.c -- * * This file contains functions to convert strings into other forms, like * integers or floating-point numbers or booleans, doing syntax checking * along the way. * * Copyright © 1990-1993 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* *---------------------------------------------------------------------- * * Tcl_GetInt -- * * Given a string, produce the corresponding integer value. * * Results: * The return value is normally TCL_OK; in this case *intPtr will be set * to the integer value equivalent to src. If src is improperly formed * then TCL_ERROR is returned and an error message will be left in the * interp's result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_GetInt( Tcl_Interp *interp, /* Interpreter to use for error reporting. */ const char *src, /* String containing a (possibly signed) * integer in a form acceptable to * Tcl_GetIntFromObj(). */ int *intPtr) /* Place to store converted result. */ { Tcl_Obj obj; int code; obj.refCount = 1; obj.bytes = (char *) src; obj.length = strlen(src); obj.typePtr = NULL; code = Tcl_GetIntFromObj(interp, &obj, intPtr); if (obj.refCount > 1) { Tcl_Panic("invalid sharing of Tcl_Obj on C stack"); } TclFreeInternalRep(&obj); return code; } /* *---------------------------------------------------------------------- * * Tcl_GetDouble -- * * Given a string, produce the corresponding double-precision * floating-point value. * * Results: * The return value is normally TCL_OK; in this case *doublePtr will be * set to the double-precision value equivalent to src. If src is * improperly formed then TCL_ERROR is returned and an error message will * be left in the interp's result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_GetDouble( Tcl_Interp *interp, /* Interpreter used for error reporting. */ const char *src, /* String containing a floating-point number * in a form acceptable to * Tcl_GetDoubleFromObj(). */ double *doublePtr) /* Place to store converted result. */ { Tcl_Obj obj; int code; obj.refCount = 1; obj.bytes = (char *) src; obj.length = strlen(src); obj.typePtr = NULL; code = Tcl_GetDoubleFromObj(interp, &obj, doublePtr); if (obj.refCount > 1) { Tcl_Panic("invalid sharing of Tcl_Obj on C stack"); } TclFreeInternalRep(&obj); return code; } /* *---------------------------------------------------------------------- * * Tcl_GetBoolean -- * * Given a string, return a 0/1 boolean value corresponding to the * string. * * Results: * The return value is normally TCL_OK; in this case *charPtr will be set * to the 0/1 value equivalent to src. If src is improperly formed then * TCL_ERROR is returned and an error message will be left in the * interp's result. * * Side effects: * None. * *---------------------------------------------------------------------- */ #undef Tcl_GetBool #undef Tcl_GetBoolFromObj int Tcl_GetBool( Tcl_Interp *interp, /* Interpreter used for error reporting. */ const char *src, /* String containing one of the boolean values * 1, 0, true, false, yes, no, on, off. */ int flags, char *charPtr) /* Place to store converted result, which will * be 0 or 1. */ { Tcl_Obj obj; int code; if ((src == NULL) || (*src == '\0')) { return Tcl_GetBoolFromObj(interp, NULL, flags, charPtr); } obj.refCount = 1; obj.bytes = (char *) src; obj.length = strlen(src); obj.typePtr = NULL; code = TclSetBooleanFromAny(interp, &obj); if (obj.refCount > 1) { Tcl_Panic("invalid sharing of Tcl_Obj on C stack"); } if (code == TCL_OK) { Tcl_GetBoolFromObj(NULL, &obj, flags, charPtr); } return code; } #undef Tcl_GetBoolean int Tcl_GetBoolean( Tcl_Interp *interp, /* Interpreter used for error reporting. */ const char *src, /* String containing one of the boolean values * 1, 0, true, false, yes, no, on, off. */ int *intPtr) /* Place to store converted result, which will * be 0 or 1. */ { return Tcl_GetBool(interp, src, (TCL_NULL_OK-2)&(int)sizeof(int), (char *)(void *)intPtr); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclHash.c0000644000175000017500000006640514726623136014737 0ustar sergeisergei/* * tclHash.c -- * * Implementation of in-memory hash tables for Tcl and Tcl-based * applications. * * Copyright © 1991-1993 The Regents of the University of California. * Copyright © 1994 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * When there are this many entries per bucket, on average, rebuild the hash * table to make it larger. */ #define REBUILD_MULTIPLIER 3 /* * The following macro takes a preliminary integer hash value and produces an * index into a hash tables bucket list. The idea is to make it so that * preliminary values that are arbitrarily similar will end up in different * buckets. The hash function was taken from a random-number generator. */ #define RANDOM_INDEX(tablePtr, i) \ ((((i)*(size_t)1103515245) >> (tablePtr)->downShift) & (tablePtr)->mask) /* * Prototypes for the array hash key methods. */ static Tcl_HashEntry * AllocArrayEntry(Tcl_HashTable *tablePtr, void *keyPtr); static int CompareArrayKeys(void *keyPtr, Tcl_HashEntry *hPtr); static size_t HashArrayKey(Tcl_HashTable *tablePtr, void *keyPtr); /* * Prototypes for the string hash key methods. */ static Tcl_HashEntry * AllocStringEntry(Tcl_HashTable *tablePtr, void *keyPtr); /* * Function prototypes for static functions in this file: */ static Tcl_HashEntry * BogusFind(Tcl_HashTable *tablePtr, const char *key); static Tcl_HashEntry * BogusCreate(Tcl_HashTable *tablePtr, const char *key, int *newPtr); static Tcl_HashEntry * CreateHashEntry(Tcl_HashTable *tablePtr, const char *key, int *newPtr); static Tcl_HashEntry * FindHashEntry(Tcl_HashTable *tablePtr, const char *key); static void RebuildTable(Tcl_HashTable *tablePtr); const Tcl_HashKeyType tclArrayHashKeyType = { TCL_HASH_KEY_TYPE_VERSION, /* version */ TCL_HASH_KEY_RANDOMIZE_HASH, /* flags */ HashArrayKey, /* hashKeyProc */ CompareArrayKeys, /* compareKeysProc */ AllocArrayEntry, /* allocEntryProc */ NULL /* freeEntryProc */ }; const Tcl_HashKeyType tclOneWordHashKeyType = { TCL_HASH_KEY_TYPE_VERSION, /* version */ 0, /* flags */ NULL, /* HashOneWordKey, */ /* hashProc */ NULL, /* CompareOneWordKey, */ /* compareProc */ NULL, /* AllocOneWordKey, */ /* allocEntryProc */ NULL /* FreeOneWordKey, */ /* freeEntryProc */ }; const Tcl_HashKeyType tclStringHashKeyType = { TCL_HASH_KEY_TYPE_VERSION, /* version */ 0, /* flags */ TclHashStringKey, /* hashKeyProc */ TclCompareStringKeys, /* compareKeysProc */ AllocStringEntry, /* allocEntryProc */ NULL /* freeEntryProc */ }; /* *---------------------------------------------------------------------- * * Tcl_InitHashTable -- * * Given storage for a hash table, set up the fields to prepare the hash * table for use. * * Results: * None. * * Side effects: * TablePtr is now ready to be passed to Tcl_FindHashEntry and * Tcl_CreateHashEntry. * *---------------------------------------------------------------------- */ void Tcl_InitHashTable( Tcl_HashTable *tablePtr, /* Pointer to table record, which is supplied * by the caller. */ int keyType) /* Type of keys to use in table: * TCL_STRING_KEYS, TCL_ONE_WORD_KEYS, or an * integer >= 2. */ { /* * Use a special value to inform the extended version that it must not * access any of the new fields in the Tcl_HashTable. If an extension is * rebuilt then any calls to this function will be redirected to the * extended version by a macro. */ Tcl_InitCustomHashTable(tablePtr, keyType, (const Tcl_HashKeyType *) -1); } /* *---------------------------------------------------------------------- * * Tcl_InitCustomHashTable -- * * Given storage for a hash table, set up the fields to prepare the hash * table for use. This is an extended version of Tcl_InitHashTable which * supports user defined keys. * * Results: * None. * * Side effects: * TablePtr is now ready to be passed to Tcl_FindHashEntry and * Tcl_CreateHashEntry. * *---------------------------------------------------------------------- */ void Tcl_InitCustomHashTable( Tcl_HashTable *tablePtr, /* Pointer to table record, which is supplied * by the caller. */ int keyType, /* Type of keys to use in table: * TCL_STRING_KEYS, TCL_ONE_WORD_KEYS, * TCL_CUSTOM_TYPE_KEYS, TCL_CUSTOM_PTR_KEYS, * or an integer >= 2. */ const Tcl_HashKeyType *typePtr) /* Pointer to structure which defines the * behaviour of this table. */ { #if (TCL_SMALL_HASH_TABLE != 4) Tcl_Panic("Tcl_InitCustomHashTable: TCL_SMALL_HASH_TABLE is %d, not 4", TCL_SMALL_HASH_TABLE); #endif tablePtr->buckets = tablePtr->staticBuckets; tablePtr->staticBuckets[0] = tablePtr->staticBuckets[1] = 0; tablePtr->staticBuckets[2] = tablePtr->staticBuckets[3] = 0; tablePtr->numBuckets = TCL_SMALL_HASH_TABLE; tablePtr->numEntries = 0; tablePtr->rebuildSize = TCL_SMALL_HASH_TABLE*REBUILD_MULTIPLIER; tablePtr->downShift = 28; tablePtr->mask = 3; tablePtr->keyType = keyType; tablePtr->findProc = FindHashEntry; tablePtr->createProc = CreateHashEntry; if (typePtr == NULL) { /* * The caller has been rebuilt so the hash table is an extended * version. */ } else if (typePtr != (Tcl_HashKeyType *) -1) { /* * The caller is requesting a customized hash table so it must be an * extended version. */ tablePtr->typePtr = typePtr; } else { /* * The caller has not been rebuilt so the hash table is not extended. */ } } /* *---------------------------------------------------------------------- * * FindHashEntry -- * * Given a hash table find the entry with a matching key. * * Results: * The return value is a token for the matching entry in the hash table, * or NULL if there was no matching entry. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_HashEntry * FindHashEntry( Tcl_HashTable *tablePtr, /* Table in which to lookup entry. */ const char *key) /* Key to use to find matching entry. */ { return CreateHashEntry(tablePtr, key, NULL); } /* *---------------------------------------------------------------------- * * CreateHashEntry -- * * Given a hash table with string keys, and a string key, find the entry * with a matching key. If there is no matching entry, then create a new * entry that does match. * * Results: * The return value is a pointer to the matching entry. If this is a * newly-created entry, then *newPtr will be set to a non-zero value; * otherwise *newPtr will be set to 0. If this is a new entry the value * stored in the entry will initially be 0. * * Side effects: * A new entry may be added to the hash table. * *---------------------------------------------------------------------- */ static Tcl_HashEntry * CreateHashEntry( Tcl_HashTable *tablePtr, /* Table in which to lookup entry. */ const char *key, /* Key to use to find or create matching * entry. */ int *newPtr) /* Store info here telling whether a new entry * was created. */ { Tcl_HashEntry *hPtr; const Tcl_HashKeyType *typePtr; size_t hash, index; if (tablePtr->keyType == TCL_STRING_KEYS) { typePtr = &tclStringHashKeyType; } else if (tablePtr->keyType == TCL_ONE_WORD_KEYS) { typePtr = &tclOneWordHashKeyType; } else if (tablePtr->keyType == TCL_CUSTOM_TYPE_KEYS || tablePtr->keyType == TCL_CUSTOM_PTR_KEYS) { typePtr = tablePtr->typePtr; } else { typePtr = &tclArrayHashKeyType; } if (typePtr->hashKeyProc) { hash = typePtr->hashKeyProc(tablePtr, (void *) key); if (typePtr->flags & TCL_HASH_KEY_RANDOMIZE_HASH) { index = RANDOM_INDEX(tablePtr, hash); } else { index = hash & tablePtr->mask; } } else { hash = PTR2UINT(key); index = RANDOM_INDEX(tablePtr, hash); } /* * Search all of the entries in the appropriate bucket. */ if (typePtr->compareKeysProc) { Tcl_CompareHashKeysProc *compareKeysProc = typePtr->compareKeysProc; if (typePtr->flags & TCL_HASH_KEY_DIRECT_COMPARE) { for (hPtr = tablePtr->buckets[index]; hPtr != NULL; hPtr = hPtr->nextPtr) { if (hash != hPtr->hash) { continue; } /* if keys pointers or values are equal */ if ((key == hPtr->key.oneWordValue) || compareKeysProc((void *) key, hPtr)) { if (newPtr) { *newPtr = 0; } return hPtr; } } } else { /* no direct compare - compare key addresses only */ for (hPtr = tablePtr->buckets[index]; hPtr != NULL; hPtr = hPtr->nextPtr) { if (hash != hPtr->hash) { continue; } /* if needle pointer equals content pointer or values equal */ if ((key == hPtr->key.string) || compareKeysProc((void *) key, hPtr)) { if (newPtr) { *newPtr = 0; } return hPtr; } } } } else { for (hPtr = tablePtr->buckets[index]; hPtr != NULL; hPtr = hPtr->nextPtr) { if (hash != hPtr->hash) { continue; } if (key == hPtr->key.oneWordValue) { if (newPtr) { *newPtr = 0; } return hPtr; } } } if (!newPtr) { return NULL; } /* * Entry not found. Add a new one to the bucket. */ *newPtr = 1; if (typePtr->allocEntryProc) { hPtr = typePtr->allocEntryProc(tablePtr, (void *) key); } else { hPtr = (Tcl_HashEntry *)Tcl_Alloc(sizeof(Tcl_HashEntry)); hPtr->key.oneWordValue = (char *) key; Tcl_SetHashValue(hPtr, NULL); } hPtr->tablePtr = tablePtr; hPtr->hash = hash; hPtr->nextPtr = tablePtr->buckets[index]; tablePtr->buckets[index] = hPtr; tablePtr->numEntries++; /* * If the table has exceeded a decent size, rebuild it with many more * buckets. */ if (tablePtr->numEntries >= tablePtr->rebuildSize) { RebuildTable(tablePtr); } return hPtr; } /* *---------------------------------------------------------------------- * * Tcl_DeleteHashEntry -- * * Remove a single entry from a hash table. * * Results: * None. * * Side effects: * The entry given by entryPtr is deleted from its table and should never * again be used by the caller. It is up to the caller to free the * clientData field of the entry, if that is relevant. * *---------------------------------------------------------------------- */ void Tcl_DeleteHashEntry( Tcl_HashEntry *entryPtr) { Tcl_HashEntry *prevPtr; const Tcl_HashKeyType *typePtr; Tcl_HashTable *tablePtr; Tcl_HashEntry **bucketPtr; size_t index; tablePtr = entryPtr->tablePtr; if (tablePtr->keyType == TCL_STRING_KEYS) { typePtr = &tclStringHashKeyType; } else if (tablePtr->keyType == TCL_ONE_WORD_KEYS) { typePtr = &tclOneWordHashKeyType; } else if (tablePtr->keyType == TCL_CUSTOM_TYPE_KEYS || tablePtr->keyType == TCL_CUSTOM_PTR_KEYS) { typePtr = tablePtr->typePtr; } else { typePtr = &tclArrayHashKeyType; } if (typePtr->hashKeyProc == NULL || typePtr->flags & TCL_HASH_KEY_RANDOMIZE_HASH) { index = RANDOM_INDEX(tablePtr, entryPtr->hash); } else { index = entryPtr->hash & tablePtr->mask; } bucketPtr = &tablePtr->buckets[index]; if (*bucketPtr == entryPtr) { *bucketPtr = entryPtr->nextPtr; } else { for (prevPtr = *bucketPtr; ; prevPtr = prevPtr->nextPtr) { if (prevPtr == NULL) { Tcl_Panic("malformed bucket chain in Tcl_DeleteHashEntry"); } if (prevPtr->nextPtr == entryPtr) { prevPtr->nextPtr = entryPtr->nextPtr; break; } } } tablePtr->numEntries--; if (typePtr->freeEntryProc) { typePtr->freeEntryProc(entryPtr); } else { Tcl_Free(entryPtr); } } /* *---------------------------------------------------------------------- * * Tcl_DeleteHashTable -- * * Free up everything associated with a hash table except for the record * for the table itself. * * Results: * None. * * Side effects: * The hash table is no longer useable. * *---------------------------------------------------------------------- */ void Tcl_DeleteHashTable( Tcl_HashTable *tablePtr) /* Table to delete. */ { Tcl_HashEntry *hPtr, *nextPtr; const Tcl_HashKeyType *typePtr; Tcl_Size i; if (tablePtr->keyType == TCL_STRING_KEYS) { typePtr = &tclStringHashKeyType; } else if (tablePtr->keyType == TCL_ONE_WORD_KEYS) { typePtr = &tclOneWordHashKeyType; } else if (tablePtr->keyType == TCL_CUSTOM_TYPE_KEYS || tablePtr->keyType == TCL_CUSTOM_PTR_KEYS) { typePtr = tablePtr->typePtr; } else { typePtr = &tclArrayHashKeyType; } /* * Free up all the entries in the table. */ for (i = 0; i < tablePtr->numBuckets; i++) { hPtr = tablePtr->buckets[i]; while (hPtr != NULL) { nextPtr = hPtr->nextPtr; if (typePtr->freeEntryProc) { typePtr->freeEntryProc(hPtr); } else { Tcl_Free(hPtr); } hPtr = nextPtr; } } /* * Free up the bucket array, if it was dynamically allocated. */ if (tablePtr->buckets != tablePtr->staticBuckets) { if (typePtr->flags & TCL_HASH_KEY_SYSTEM_HASH) { TclpSysFree((char *) tablePtr->buckets); } else { Tcl_Free(tablePtr->buckets); } } /* * Arrange for panics if the table is used again without * re-initialization. */ tablePtr->findProc = BogusFind; tablePtr->createProc = BogusCreate; } /* *---------------------------------------------------------------------- * * Tcl_FirstHashEntry -- * * Locate the first entry in a hash table and set up a record that can be * used to step through all the remaining entries of the table. * * Results: * The return value is a pointer to the first entry in tablePtr, or NULL * if tablePtr has no entries in it. The memory at *searchPtr is * initialized so that subsequent calls to Tcl_NextHashEntry will return * all of the entries in the table, one at a time. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_HashEntry * Tcl_FirstHashEntry( Tcl_HashTable *tablePtr, /* Table to search. */ Tcl_HashSearch *searchPtr) /* Place to store information about progress * through the table. */ { searchPtr->tablePtr = tablePtr; searchPtr->nextIndex = 0; searchPtr->nextEntryPtr = NULL; return Tcl_NextHashEntry(searchPtr); } /* *---------------------------------------------------------------------- * * Tcl_NextHashEntry -- * * Once a hash table enumeration has been initiated by calling * Tcl_FirstHashEntry, this function may be called to return successive * elements of the table. * * Results: * The return value is the next entry in the hash table being enumerated, * or NULL if the end of the table is reached. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_HashEntry * Tcl_NextHashEntry( Tcl_HashSearch *searchPtr) /* Place to store information about progress * through the table. Must have been * initialized by calling * Tcl_FirstHashEntry. */ { Tcl_HashEntry *hPtr; Tcl_HashTable *tablePtr = searchPtr->tablePtr; while (searchPtr->nextEntryPtr == NULL) { if (searchPtr->nextIndex >= tablePtr->numBuckets) { return NULL; } searchPtr->nextEntryPtr = tablePtr->buckets[searchPtr->nextIndex]; searchPtr->nextIndex++; } hPtr = searchPtr->nextEntryPtr; searchPtr->nextEntryPtr = hPtr->nextPtr; return hPtr; } /* *---------------------------------------------------------------------- * * Tcl_HashStats -- * * Return statistics describing the layout of the hash table in its hash * buckets. * * Results: * The return value is a malloc-ed string containing information about * tablePtr. It is the caller's responsibility to free this string. * * Side effects: * None. * *---------------------------------------------------------------------- */ char * Tcl_HashStats( Tcl_HashTable *tablePtr) /* Table for which to produce stats. */ { #define NUM_COUNTERS 10 Tcl_Size i; size_t count[NUM_COUNTERS], overflow, j; double average, tmp; Tcl_HashEntry *hPtr; char *result, *p; /* * Compute a histogram of bucket usage. */ for (i = 0; i < NUM_COUNTERS; i++) { count[i] = 0; } overflow = 0; average = 0.0; for (i = 0; i < tablePtr->numBuckets; i++) { j = 0; for (hPtr = tablePtr->buckets[i]; hPtr != NULL; hPtr = hPtr->nextPtr) { j++; } if (j < NUM_COUNTERS) { count[j]++; } else { overflow++; } tmp = j; if (tablePtr->numEntries != 0) { average += (tmp+1.0)*(tmp/tablePtr->numEntries)/2.0; } } /* * Print out the histogram and a few other pieces of information. */ result = (char *)Tcl_Alloc((NUM_COUNTERS * 60) + 300); snprintf(result, 60, "%" TCL_SIZE_MODIFIER "u entries in table, %" TCL_SIZE_MODIFIER "u buckets\n", tablePtr->numEntries, tablePtr->numBuckets); p = result + strlen(result); for (i = 0; i < NUM_COUNTERS; i++) { snprintf(p, 60, "number of buckets with %" TCL_SIZE_MODIFIER "u entries: %" TCL_SIZE_MODIFIER "u\n", i, count[i]); p += strlen(p); } snprintf(p, 60, "number of buckets with %d or more entries: %" TCL_SIZE_MODIFIER "u\n", NUM_COUNTERS, overflow); p += strlen(p); snprintf(p, 60, "average search distance for entry: %.1f", average); return result; } /* *---------------------------------------------------------------------- * * AllocArrayEntry -- * * Allocate space for a Tcl_HashEntry containing the array key. * * Results: * The return value is a pointer to the created entry. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_HashEntry * AllocArrayEntry( Tcl_HashTable *tablePtr, /* Hash table. */ void *keyPtr) /* Key to store in the hash table entry. */ { Tcl_HashEntry *hPtr; size_t count = tablePtr->keyType * sizeof(int); size_t size = offsetof(Tcl_HashEntry, key) + count; if (size < sizeof(Tcl_HashEntry)) { size = sizeof(Tcl_HashEntry); } hPtr = (Tcl_HashEntry *)Tcl_Alloc(size); memcpy(hPtr->key.string, keyPtr, count); Tcl_SetHashValue(hPtr, NULL); return hPtr; } /* *---------------------------------------------------------------------- * * CompareArrayKeys -- * * Compares two array keys. * * Results: * The return value is 0 if they are different and 1 if they are the * same. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int CompareArrayKeys( void *keyPtr, /* New key to compare. */ Tcl_HashEntry *hPtr) /* Existing key to compare. */ { size_t count = hPtr->tablePtr->keyType * sizeof(int); return !memcmp(keyPtr, hPtr->key.string, count); } /* *---------------------------------------------------------------------- * * HashArrayKey -- * * Compute a one-word summary of an array, which can be used to generate * a hash index. * * Results: * The return value is a one-word summary of the information in * string. * * Side effects: * None. * *---------------------------------------------------------------------- */ static size_t HashArrayKey( Tcl_HashTable *tablePtr, /* Hash table. */ void *keyPtr) /* Key from which to compute hash value. */ { const int *array = (const int *) keyPtr; size_t result; int count; for (result = 0, count = tablePtr->keyType; count > 0; count--, array++) { result += *array; } return result; } /* *---------------------------------------------------------------------- * * AllocStringEntry -- * * Allocate space for a Tcl_HashEntry containing the string key. * * Results: * The return value is a pointer to the created entry. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_HashEntry * AllocStringEntry( TCL_UNUSED(Tcl_HashTable *), void *keyPtr) /* Key to store in the hash table entry. */ { const char *string = (const char *) keyPtr; Tcl_HashEntry *hPtr; size_t size, allocsize; allocsize = size = strlen(string) + 1; if (size < sizeof(hPtr->key)) { allocsize = sizeof(hPtr->key); } hPtr = (Tcl_HashEntry *)Tcl_Alloc(offsetof(Tcl_HashEntry, key) + allocsize); memset(hPtr, 0, offsetof(Tcl_HashEntry, key) + allocsize); memcpy(hPtr->key.string, string, size); Tcl_SetHashValue(hPtr, NULL); return hPtr; } /* *---------------------------------------------------------------------- * * TclCompareStringKeys -- * * Compares two string keys. * * Results: * The return value is 0 if they are different and 1 if they are the * same. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclCompareStringKeys( void *keyPtr, /* New key to compare. */ Tcl_HashEntry *hPtr) /* Existing key to compare. */ { return !strcmp((char *)keyPtr, hPtr->key.string); } /* *---------------------------------------------------------------------- * * TclHashStringKey -- * * Compute a one-word summary of a text string, which can be used to * generate a hash index. * * Results: * The return value is a one-word summary of the information in string. * * Side effects: * None. * *---------------------------------------------------------------------- */ size_t TclHashStringKey( TCL_UNUSED(Tcl_HashTable *), void *keyPtr) /* Key from which to compute hash value. */ { const char *string = (const char *)keyPtr; size_t result; char c; /* * I tried a zillion different hash functions and asked many other people * for advice. Many people had their own favorite functions, all * different, but no-one had much idea why they were good ones. I chose * the one below (multiply by 9 and add new character) because of the * following reasons: * * 1. Multiplying by 10 is perfect for keys that are decimal strings, and * multiplying by 9 is just about as good. * 2. Times-9 is (shift-left-3) plus (old). This means that each * character's bits hang around in the low-order bits of the hash value * for ever, plus they spread fairly rapidly up to the high-order bits * to fill out the hash value. This seems works well both for decimal * and non-decimal strings, but isn't strong against maliciously-chosen * keys. * * Note that this function is very weak against malicious strings; it's * very easy to generate multiple keys that have the same hashcode. On the * other hand, that hardly ever actually occurs and this function *is* * very cheap, even by comparison with industry-standard hashes like FNV. * If real strength of hash is required though, use a custom hash based on * Bob Jenkins's lookup3(), but be aware that it's significantly slower. * Since Tcl command and namespace names are usually reasonably-named (the * main use for string hashes in modern Tcl) speed is far more important * than strength. * * See also HashString in tclLiteral.c. * See also TclObjHashKey in tclObj.c. * * See [tcl-Feature Request #2958832] */ if ((result = UCHAR(*string)) != 0) { while ((c = *++string) != 0) { result += (result << 3) + UCHAR(c); } } return result; } /* *---------------------------------------------------------------------- * * BogusFind -- * * This function is invoked when Tcl_FindHashEntry is called on a * table that has been deleted. * * Results: * If Tcl_Panic returns (which it shouldn't) this function returns NULL. * * Side effects: * Generates a panic. * *---------------------------------------------------------------------- */ static Tcl_HashEntry * BogusFind( TCL_UNUSED(Tcl_HashTable *), TCL_UNUSED(const char *)) { Tcl_Panic("called %s on deleted table", "Tcl_FindHashEntry"); return NULL; } /* *---------------------------------------------------------------------- * * BogusCreate -- * * This function is invoked when Tcl_CreateHashEntry is called on a * table that has been deleted. * * Results: * If panic returns (which it shouldn't) this function returns NULL. * * Side effects: * Generates a panic. * *---------------------------------------------------------------------- */ static Tcl_HashEntry * BogusCreate( TCL_UNUSED(Tcl_HashTable *), TCL_UNUSED(const char *), TCL_UNUSED(int *)) { Tcl_Panic("called %s on deleted table", "Tcl_CreateHashEntry"); return NULL; } /* *---------------------------------------------------------------------- * * RebuildTable -- * * This function is invoked when the ratio of entries to hash buckets * becomes too large. It creates a new table with a larger bucket array * and moves all of the entries into the new table. * * Results: * None. * * Side effects: * Memory gets reallocated and entries get re-hashed to new buckets. * *---------------------------------------------------------------------- */ static void RebuildTable( Tcl_HashTable *tablePtr) /* Table to enlarge. */ { size_t count, index, oldSize = tablePtr->numBuckets; Tcl_HashEntry **oldBuckets = tablePtr->buckets; Tcl_HashEntry **oldChainPtr, **newChainPtr; Tcl_HashEntry *hPtr; const Tcl_HashKeyType *typePtr; /* Avoid outgrowing capability of the memory allocators */ if (oldSize > UINT_MAX / (4 * sizeof(Tcl_HashEntry *))) { tablePtr->rebuildSize = INT_MAX; return; } if (tablePtr->keyType == TCL_STRING_KEYS) { typePtr = &tclStringHashKeyType; } else if (tablePtr->keyType == TCL_ONE_WORD_KEYS) { typePtr = &tclOneWordHashKeyType; } else if (tablePtr->keyType == TCL_CUSTOM_TYPE_KEYS || tablePtr->keyType == TCL_CUSTOM_PTR_KEYS) { typePtr = tablePtr->typePtr; } else { typePtr = &tclArrayHashKeyType; } /* * Allocate and initialize the new bucket array, and set up hashing * constants for new array size. */ tablePtr->numBuckets *= 4; if (typePtr->flags & TCL_HASH_KEY_SYSTEM_HASH) { tablePtr->buckets = (Tcl_HashEntry **)TclpSysAlloc( tablePtr->numBuckets * sizeof(Tcl_HashEntry *)); } else { tablePtr->buckets = (Tcl_HashEntry **)Tcl_Alloc(tablePtr->numBuckets * sizeof(Tcl_HashEntry *)); } for (count = tablePtr->numBuckets, newChainPtr = tablePtr->buckets; count > 0; count--, newChainPtr++) { *newChainPtr = NULL; } tablePtr->rebuildSize *= 4; if (tablePtr->downShift > 1) { tablePtr->downShift -= 2; } tablePtr->mask = (tablePtr->mask << 2) + 3; /* * Rehash all of the existing entries into the new bucket array. */ for (oldChainPtr = oldBuckets; oldSize > 0; oldSize--, oldChainPtr++) { for (hPtr = *oldChainPtr; hPtr != NULL; hPtr = *oldChainPtr) { *oldChainPtr = hPtr->nextPtr; if (typePtr->hashKeyProc == NULL || typePtr->flags & TCL_HASH_KEY_RANDOMIZE_HASH) { index = RANDOM_INDEX(tablePtr, hPtr->hash); } else { index = hPtr->hash & tablePtr->mask; } hPtr->nextPtr = tablePtr->buckets[index]; tablePtr->buckets[index] = hPtr; } } /* * Free up the old bucket array, if it was dynamically allocated. */ if (oldBuckets != tablePtr->staticBuckets) { if (typePtr->flags & TCL_HASH_KEY_SYSTEM_HASH) { TclpSysFree((char *) oldBuckets); } else { Tcl_Free(oldBuckets); } } } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclHistory.c0000644000175000017500000001266214726623136015511 0ustar sergeisergei/* * tclHistory.c -- * * This module and the Tcl library file history.tcl together implement * Tcl command history. Tcl_RecordAndEval(Obj) can be called to record * commands ("events") before they are executed. Commands defined in * history.tcl may be used to perform history substitutions. * * Copyright © 1990-1993 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * Type of the assocData structure used to hold the reference to the [history * add] subcommand, used in Tcl_RecordAndEvalObj. */ typedef struct { Tcl_Obj *historyObj; /* == "::history" */ Tcl_Obj *addObj; /* == "add" */ } HistoryObjs; #define HISTORY_OBJS_KEY "::tcl::HistoryObjs" /* * Static functions in this file. */ static Tcl_InterpDeleteProc DeleteHistoryObjs; /* *---------------------------------------------------------------------- * * Tcl_RecordAndEval -- * * This procedure adds its command argument to the current list of * recorded events and then executes the command by calling Tcl_Eval. * * Results: * The return value is a standard Tcl return value, the result of * executing cmd. * * Side effects: * The command is recorded and executed. * *---------------------------------------------------------------------- */ int Tcl_RecordAndEval( Tcl_Interp *interp, /* Token for interpreter in which command will * be executed. */ const char *cmd, /* Command to record. */ int flags) /* Additional flags. TCL_NO_EVAL means only * record: don't execute command. * TCL_EVAL_GLOBAL means use Tcl_GlobalEval * instead of Tcl_Eval. */ { Tcl_Obj *cmdPtr; int result; if (cmd[0]) { /* * Call Tcl_RecordAndEvalObj to do the actual work. */ cmdPtr = Tcl_NewStringObj(cmd, -1); Tcl_IncrRefCount(cmdPtr); result = Tcl_RecordAndEvalObj(interp, cmdPtr, flags); /* * Discard the Tcl object created to hold the command. */ Tcl_DecrRefCount(cmdPtr); } else { /* * An empty string. Just reset the interpreter's result. */ Tcl_ResetResult(interp); result = TCL_OK; } return result; } /* *---------------------------------------------------------------------- * * Tcl_RecordAndEvalObj -- * * This procedure adds the command held in its argument object to the * current list of recorded events and then executes the command by * calling Tcl_EvalObj. * * Results: * The return value is a standard Tcl return value, the result of * executing the command. * * Side effects: * The command is recorded and executed. * *---------------------------------------------------------------------- */ int Tcl_RecordAndEvalObj( Tcl_Interp *interp, /* Token for interpreter in which command will * be executed. */ Tcl_Obj *cmdPtr, /* Points to object holding the command to * record and execute. */ int flags) /* Additional flags. TCL_NO_EVAL means record * only: don't execute the command. * TCL_EVAL_GLOBAL means evaluate the script * in global variable context instead of the * current procedure. */ { int result, call = 1; Tcl_CmdInfo info; HistoryObjs *histObjsPtr = (HistoryObjs *)Tcl_GetAssocData(interp, HISTORY_OBJS_KEY, NULL); /* * Create the references to the [::history add] command if necessary. */ if (histObjsPtr == NULL) { histObjsPtr = (HistoryObjs *)Tcl_Alloc(sizeof(HistoryObjs)); TclNewLiteralStringObj(histObjsPtr->historyObj, "::history"); TclNewLiteralStringObj(histObjsPtr->addObj, "add"); Tcl_IncrRefCount(histObjsPtr->historyObj); Tcl_IncrRefCount(histObjsPtr->addObj); Tcl_SetAssocData(interp, HISTORY_OBJS_KEY, DeleteHistoryObjs, histObjsPtr); } /* * Do not call [history] if it has been replaced by an empty proc */ result = Tcl_GetCommandInfo(interp, "::history", &info); if (result && (info.deleteProc == TclProcDeleteProc)) { Proc *procPtr = (Proc *) info.objClientData; call = (procPtr->cmdPtr->compileProc != TclCompileNoOp); } if (call) { Tcl_Obj *list[3]; /* * Do recording by eval'ing a tcl history command: history add $cmd. */ list[0] = histObjsPtr->historyObj; list[1] = histObjsPtr->addObj; list[2] = cmdPtr; Tcl_IncrRefCount(cmdPtr); (void) Tcl_EvalObjv(interp, 3, list, TCL_EVAL_GLOBAL); Tcl_DecrRefCount(cmdPtr); /* * One possible failure mode above: exceeding a resource limit. */ if (Tcl_LimitExceeded(interp)) { return TCL_ERROR; } } /* * Execute the command. */ result = TCL_OK; if (!(flags & TCL_NO_EVAL)) { result = Tcl_EvalObjEx(interp, cmdPtr, flags & TCL_EVAL_GLOBAL); } return result; } /* *---------------------------------------------------------------------- * * DeleteHistoryObjs -- * * Called to delete the references to the constant words used when adding * to the history. * * Results: * None. * * Side effects: * The constant words may be deleted. * *---------------------------------------------------------------------- */ static void DeleteHistoryObjs( void *clientData, TCL_UNUSED(Tcl_Interp *)) { HistoryObjs *histObjsPtr = (HistoryObjs *)clientData; TclDecrRefCount(histObjsPtr->historyObj); TclDecrRefCount(histObjsPtr->addObj); Tcl_Free(histObjsPtr); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclIO.c0000644000175000017500000116561114726623136014363 0ustar sergeisergei/* * tclIO.c -- * * This file provides the generic portions (those that are the same on * all platforms and for all channel types) of Tcl's IO facilities. * * Copyright © 1998-2000 Ajuba Solutions * Copyright © 1995-1997 Sun Microsystems, Inc. * Contributions from Don Porter, NIST, 2014. (not subject to US copyright) * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclIO.h" #include /* * For each channel handler registered in a call to Tcl_CreateChannelHandler, * there is one record of the following type. All of records for a specific * channel are chained together in a singly linked list which is stored in * the channel structure. */ typedef struct ChannelHandler { Channel *chanPtr; /* The channel structure for this channel. */ int mask; /* Mask of desired events. */ Tcl_ChannelProc *proc; /* Procedure to call in the type of * Tcl_CreateChannelHandler. */ void *clientData; /* Argument to pass to procedure. */ struct ChannelHandler *nextPtr; /* Next one in list of registered handlers. */ } ChannelHandler; /* * This structure keeps track of the current ChannelHandler being invoked in * the current invocation of Tcl_NotifyChannel. There is a potential * problem if a ChannelHandler is deleted while it is the current one, since * Tcl_NotifyChannel needs to look at the nextPtr field. To handle this * problem, structures of the type below indicate the next handler to be * processed for any (recursively nested) dispatches in progress. The * nextHandlerPtr field is updated if the handler being pointed to is deleted. * The nestedHandlerPtr field is used to chain together all recursive * invocations, so that Tcl_DeleteChannelHandler can find all the recursively * nested invocations of Tcl_NotifyChannel and compare the handler being * deleted against the NEXT handler to be invoked in that invocation; when it * finds such a situation, Tcl_DeleteChannelHandler updates the nextHandlerPtr * field of the structure to the next handler. */ typedef struct NextChannelHandler { ChannelHandler *nextHandlerPtr; /* The next handler to be invoked in * this invocation. */ struct NextChannelHandler *nestedHandlerPtr; /* Next nested invocation of * Tcl_NotifyChannel. */ } NextChannelHandler; /* * The following structure is used by Tcl_GetsObj() to encapsulates the * state for a "gets" operation. */ typedef struct GetsState { Tcl_Obj *objPtr; /* The object to which UTF-8 characters * will be appended. */ char **dstPtr; /* Pointer into objPtr's string rep where * next character should be stored. */ Tcl_Encoding encoding; /* The encoding to use to convert raw bytes * to UTF-8. */ ChannelBuffer *bufPtr; /* The current buffer of raw bytes being * emptied. */ Tcl_EncodingState state; /* The encoding state just before the last * external to UTF-8 conversion in * FilterInputBytes(). */ int rawRead; /* The number of bytes removed from bufPtr * in the last call to FilterInputBytes(). */ int bytesWrote; /* The number of bytes of UTF-8 data * appended to objPtr during the last call to * FilterInputBytes(). */ int charsWrote; /* The corresponding number of UTF-8 * characters appended to objPtr during the * last call to FilterInputBytes(). */ int totalChars; /* The total number of UTF-8 characters * appended to objPtr so far, just before the * last call to FilterInputBytes(). */ } GetsState; /* * The following structure encapsulates the state for a background channel * copy. Note that the data buffer for the copy will be appended to this * structure. */ typedef struct CopyState { struct Channel *readPtr; /* Pointer to input channel. */ struct Channel *writePtr; /* Pointer to output channel. */ int refCount; /* Reference counter. */ int readFlags; /* Original read channel flags. */ int writeFlags; /* Original write channel flags. */ Tcl_WideInt toRead; /* Number of bytes to copy, or -1. */ Tcl_WideInt total; /* Total bytes transferred (written). */ Tcl_Interp *interp; /* Interp that started the copy. */ Tcl_Obj *cmdPtr; /* Command to be invoked at completion. */ Tcl_Size bufSize; /* Size of appended buffer. */ char buffer[TCLFLEXARRAY]; /* Copy buffer, this must be the last * field. */ } CopyState; /* * All static variables used in this file are collected into a single instance * of the following structure. For multi-threaded implementations, there is * one instance of this structure for each thread. * * Notice that different structures with the same name appear in other files. * The structure defined below is used in this file only. */ typedef struct { NextChannelHandler *nestedHandlerPtr; /* This variable holds the list of nested * Tcl_NotifyChannel invocations. */ ChannelState *firstCSPtr; /* List of all channels currently open, * indexed by ChannelState, as only one * ChannelState exists per set of stacked * channels. */ Tcl_Channel stdinChannel; /* Static variable for the stdin channel. */ Tcl_Channel stdoutChannel; /* Static variable for the stdout channel. */ Tcl_Channel stderrChannel; /* Static variable for the stderr channel. */ Tcl_Encoding binaryEncoding; int stdinInitialized; int stdoutInitialized; int stderrInitialized; } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * Structure to record a close callback. One such record exists for * each close callback registered for a channel. */ typedef struct CloseCallback { Tcl_CloseProc *proc; /* The procedure to call. */ void *clientData; /* Arbitrary one-word data to pass * to the callback. */ struct CloseCallback *nextPtr; /* For chaining close callbacks. */ } CloseCallback; /* * Static functions in this file: */ static ChannelBuffer * AllocChannelBuffer(Tcl_Size length); static void PreserveChannelBuffer(ChannelBuffer *bufPtr); static void ReleaseChannelBuffer(ChannelBuffer *bufPtr); static int IsShared(ChannelBuffer *bufPtr); static void ChannelFree(Channel *chanPtr); static void ChannelTimerProc(void *clientData); static int ChanRead(Channel *chanPtr, char *dst, int dstSize); static int CheckChannelErrors(ChannelState *statePtr, int direction); static int CheckForDeadChannel(Tcl_Interp *interp, ChannelState *statePtr); static void CheckForStdChannelsBeingClosed(Tcl_Channel chan); static void CleanupChannelHandlers(Tcl_Interp *interp, Channel *chanPtr); static int CloseChannel(Tcl_Interp *interp, Channel *chanPtr, int errorCode); static int CloseChannelPart(Tcl_Interp *interp, Channel *chanPtr, int errorCode, int flags); static int CloseWrite(Tcl_Interp *interp, Channel *chanPtr); static void CommonGetsCleanup(Channel *chanPtr); static int CopyData(CopyState *csPtr, int mask); static void DeleteTimerHandler(ChannelState *statePtr); static int Lossless(ChannelState *inStatePtr, ChannelState *outStatePtr, long long toRead); static int MoveBytes(CopyState *csPtr); static void MBCallback(CopyState *csPtr, Tcl_Obj *errObj); static void MBError(CopyState *csPtr, int mask, int errorCode); static int MBRead(CopyState *csPtr); static int MBWrite(CopyState *csPtr); static void MBEvent(void *clientData, int mask); static void CopyEventProc(void *clientData, int mask); static void CreateScriptRecord(Tcl_Interp *interp, Channel *chanPtr, int mask, Tcl_Obj *scriptPtr); static void DeleteChannelTable(void *clientData, Tcl_Interp *interp); static void DeleteScriptRecord(Tcl_Interp *interp, Channel *chanPtr, int mask); static int DetachChannel(Tcl_Interp *interp, Tcl_Channel chan); static void DiscardInputQueued(ChannelState *statePtr, int discardSavedBuffers); static void DiscardOutputQueued(ChannelState *chanPtr); static Tcl_Size DoRead(Channel *chanPtr, char *dst, Tcl_Size bytesToRead, int allowShortReads); static Tcl_Size DoReadChars(Channel *chan, Tcl_Obj *objPtr, Tcl_Size toRead, int allowShortReads, int appendFlag); static int FilterInputBytes(Channel *chanPtr, GetsState *statePtr); static int FlushChannel(Tcl_Interp *interp, Channel *chanPtr, int calledFromAsyncFlush); static int TclGetsObjBinary(Tcl_Channel chan, Tcl_Obj *objPtr); static Tcl_Encoding GetBinaryEncoding(void); static void FreeBinaryEncoding(void); static Tcl_HashTable * GetChannelTable(Tcl_Interp *interp); static int GetInput(Channel *chanPtr); static void PeekAhead(Channel *chanPtr, char **dstEndPtr, GetsState *gsPtr); static int ReadBytes(ChannelState *statePtr, Tcl_Obj *objPtr, int charsLeft); static int ReadChars(ChannelState *statePtr, Tcl_Obj *objPtr, int charsLeft, int *factorPtr); static void RecycleBuffer(ChannelState *statePtr, ChannelBuffer *bufPtr, int mustDiscard); static int StackSetBlockMode(Channel *chanPtr, int mode); static int SetBlockMode(Tcl_Interp *interp, Channel *chanPtr, int mode); static void StopCopy(CopyState *csPtr); static void CopyDecrRefCount(CopyState *csPtr); static void TranslateInputEOL(ChannelState *statePtr, char *dst, const char *src, int *dstLenPtr, int *srcLenPtr); static void UpdateInterest(Channel *chanPtr); static Tcl_Size Write(Channel *chanPtr, const char *src, Tcl_Size srcLen, Tcl_Encoding encoding); static Tcl_Obj * FixLevelCode(Tcl_Obj *msg); static void SpliceChannel(Tcl_Channel chan); static void CutChannel(Tcl_Channel chan); static int WillRead(Channel *chanPtr); #define WriteChars(chanPtr, src, srcLen) \ Write(chanPtr, src, srcLen, chanPtr->state->encoding) #define WriteBytes(chanPtr, src, srcLen) \ Write(chanPtr, src, srcLen, tclIdentityEncoding) /* * Simplifying helper macros. All may use their argument(s) multiple times. * The ANSI C "prototypes" for the macros are listed below, together with a * short description of what the macro does. * * -------------------------------------------------------------------------- * Tcl_Size BytesLeft(ChannelBuffer *bufPtr) * * Returns the number of bytes of data remaining in the buffer. * * Tcl_Size SpaceLeft(ChannelBuffer *bufPtr) * * Returns the number of bytes of space remaining at the end of the * buffer. * * int IsBufferReady(ChannelBuffer *bufPtr) * * Returns whether a buffer has bytes available within it. * * int IsBufferEmpty(ChannelBuffer *bufPtr) * * Returns whether a buffer is entirely empty. Note that this is not the * inverse of the above operation; trying to merge the two seems to lead * to occasional crashes... * * int IsBufferFull(ChannelBuffer *bufPtr) * * Returns whether more data can be added to a buffer. * * int IsBufferOverflowing(ChannelBuffer *bufPtr) * * Returns whether a buffer has more data in it than it should. * * char *InsertPoint(ChannelBuffer *bufPtr) * * Returns a pointer to where characters should be added to the buffer. * * char *RemovePoint(ChannelBuffer *bufPtr) * * Returns a pointer to where characters should be removed from the * buffer. * -------------------------------------------------------------------------- */ #define BytesLeft(bufPtr) ((bufPtr)->nextAdded - (bufPtr)->nextRemoved) #define SpaceLeft(bufPtr) ((bufPtr)->bufLength - (bufPtr)->nextAdded) #define IsBufferReady(bufPtr) ((bufPtr)->nextAdded > (bufPtr)->nextRemoved) #define IsBufferEmpty(bufPtr) ((bufPtr)->nextAdded == (bufPtr)->nextRemoved) #define IsBufferFull(bufPtr) ((bufPtr) && (bufPtr)->nextAdded >= (bufPtr)->bufLength) #define IsBufferOverflowing(bufPtr) ((bufPtr)->nextAdded>(bufPtr)->bufLength) #define InsertPoint(bufPtr) (&(bufPtr)->buf[(bufPtr)->nextAdded]) #define RemovePoint(bufPtr) (&(bufPtr)->buf[(bufPtr)->nextRemoved]) /* * For working with channel state flag bits. */ #define SetFlag(statePtr, flag) ((statePtr)->flags |= (flag)) #define ResetFlag(statePtr, flag) ((statePtr)->flags &= ~(flag)) #define GotFlag(statePtr, flag) ((statePtr)->flags & (flag)) /* * Macro for testing whether a string (in optionName, length len) matches a * value (prefix matching rules). Arguments are the minimum length to match * and the value to match against. (Can't use Tcl_GetIndexFromObj as this is * used in a situation where no objects are available.) */ #define HaveOpt(minLength, nameString) \ ((len > (minLength)) && (optionName[1] == (nameString)[1]) \ && (strncmp(optionName, (nameString), len) == 0)) /* * The ChannelObjType type. Used to store the result of looking up * a channel name in the context of an interp. Saves the lookup * result and values needed to check its continued validity. */ typedef struct ResolvedChanName { ChannelState *statePtr; /* The saved lookup result */ Tcl_Interp *interp; /* The interp in which the lookup was done. */ Tcl_Size epoch; /* The epoch of the channel when the lookup * was done. Use to verify validity. */ size_t refCount; /* Share this struct among many Tcl_Obj. */ } ResolvedChanName; static void DupChannelInternalRep(Tcl_Obj *objPtr, Tcl_Obj *copyPtr); static void FreeChannelInternalRep(Tcl_Obj *objPtr); static const Tcl_ObjType chanObjType = { "channel", /* name for this type */ FreeChannelInternalRep, /* freeIntRepProc */ DupChannelInternalRep, /* dupIntRepProc */ NULL, /* updateStringProc */ NULL, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; #define ChanSetInternalRep(objPtr, resPtr) \ do { \ Tcl_ObjInternalRep ir; \ (resPtr)->refCount++; \ ir.twoPtrValue.ptr1 = (resPtr); \ ir.twoPtrValue.ptr2 = NULL; \ Tcl_StoreInternalRep((objPtr), &chanObjType, &ir); \ } while (0) #define ChanGetInternalRep(objPtr, resPtr) \ do { \ const Tcl_ObjInternalRep *irPtr; \ irPtr = TclFetchInternalRep((objPtr), &chanObjType); \ (resPtr) = irPtr ? (ResolvedChanName *)irPtr->twoPtrValue.ptr1 : NULL; \ } while (0) #define BUSY_STATE(st, fl) \ ((((st)->csPtrR) && ((fl) & TCL_READABLE)) || \ (((st)->csPtrW) && ((fl) & TCL_WRITABLE))) #define MAX_CHANNEL_BUFFER_SIZE (1024*1024) /* *--------------------------------------------------------------------------- * * ChanClose, ChanRead, ChanSeek, ChanThreadAction, ChanWatch, ChanWrite -- * * Simplify the access to selected channel driver "methods" that are used * in multiple places in a stereotypical fashion. These are just thin * wrappers around the driver functions. * *--------------------------------------------------------------------------- */ static inline int ChanClose( Channel *chanPtr, Tcl_Interp *interp) { return chanPtr->typePtr->close2Proc(chanPtr->instanceData, interp, 0); } /* *--------------------------------------------------------------------------- * * ChanRead -- * * Read up to dstSize bytes using the inputProc of chanPtr, store them at * dst, and return the number of bytes stored. * * Results: * The return value of the driver inputProc, * - number of bytes stored at dst, ot * - -1 on error, with a Posix error code available to the caller by * calling Tcl_GetErrno(). * * Side effects: * The CHANNEL_ENCODING_ERROR, CHANNEL_BLOCKED and CHANNEL_EOF flags * of the channel state are set as appropriate. On EOF, the * inputEncodingFlags are set to perform ending operations on decoding. * * TODO - Is this really the right place for that? * *--------------------------------------------------------------------------- */ static int ChanRead( Channel *chanPtr, char *dst, int dstSize) { int bytesRead, result; /* * If the caller asked for zero bytes, we'd force the inputProc to return * zero bytes, and then misinterpret that as EOF. */ assert(dstSize > 0); /* * Each read op must set the blocked and eof states anew, not let * the effect of prior reads leak through. */ if (GotFlag(chanPtr->state, CHANNEL_EOF)) { chanPtr->state->inputEncodingFlags |= TCL_ENCODING_START; } ResetFlag(chanPtr->state, CHANNEL_BLOCKED | CHANNEL_EOF); chanPtr->state->inputEncodingFlags &= ~TCL_ENCODING_END; if (WillRead(chanPtr) == -1) { return -1; } bytesRead = chanPtr->typePtr->inputProc(chanPtr->instanceData, dst, dstSize, &result); /* * Stop any flag leakage through stacked channel levels. */ if (GotFlag(chanPtr->state, CHANNEL_EOF)) { chanPtr->state->inputEncodingFlags |= TCL_ENCODING_START; } ResetFlag(chanPtr->state, CHANNEL_BLOCKED | CHANNEL_EOF); chanPtr->state->inputEncodingFlags &= ~TCL_ENCODING_END; if (bytesRead == -1) { if ((result == EWOULDBLOCK) || (result == EAGAIN)) { SetFlag(chanPtr->state, CHANNEL_BLOCKED); result = EAGAIN; } Tcl_SetErrno(result); } else if (bytesRead == 0) { SetFlag(chanPtr->state, CHANNEL_EOF); chanPtr->state->inputEncodingFlags |= TCL_ENCODING_END; } else { /* * If we get a short read, signal up that we may be BLOCKED. We should * avoid calling the driver because on some platforms we will block in * the low level reading code even though the channel is set into * nonblocking mode. */ if (bytesRead < dstSize) { SetFlag(chanPtr->state, CHANNEL_BLOCKED); } } return bytesRead; } static inline Tcl_WideInt ChanSeek( Channel *chanPtr, Tcl_WideInt offset, int mode, int *errnoPtr) { /* * Note that we prefer the wideSeekProc if that field is available in the * type and non-NULL. */ if (Tcl_ChannelWideSeekProc(chanPtr->typePtr) == NULL) { *errnoPtr = EINVAL; return TCL_INDEX_NONE; } return Tcl_ChannelWideSeekProc(chanPtr->typePtr)(chanPtr->instanceData, offset, mode, errnoPtr); } static inline void ChanThreadAction( Channel *chanPtr, int action) { Tcl_DriverThreadActionProc *threadActionProc = Tcl_ChannelThreadActionProc(chanPtr->typePtr); if (threadActionProc != NULL) { threadActionProc(chanPtr->instanceData, action); } } static inline void ChanWatch( Channel *chanPtr, int mask) { chanPtr->typePtr->watchProc(chanPtr->instanceData, mask); } static inline int ChanWrite( Channel *chanPtr, const char *src, int srcLen, int *errnoPtr) { return chanPtr->typePtr->outputProc(chanPtr->instanceData, src, srcLen, errnoPtr); } /* *--------------------------------------------------------------------------- * * TclInitIOSubsystem -- * * Initialize all resources used by this subsystem on a per-process * basis. * * Results: * None. * * Side effects: * Depends on the memory subsystems. * *--------------------------------------------------------------------------- */ void TclInitIOSubsystem(void) { /* * By fetching thread local storage we take care of allocating it for each * thread. */ (void) TCL_TSD_INIT(&dataKey); } /* *------------------------------------------------------------------------- * * TclFinalizeIOSubsystem -- * * Releases all resources used by this subsystem on a per-process basis. * Closes all extant channels that have not already been closed because * they were not owned by any interp. * * Results: * None. * * Side effects: * Depends on encoding and memory subsystems. * *------------------------------------------------------------------------- */ void TclFinalizeIOSubsystem(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Channel *chanPtr = NULL; /* Iterates over open channels. */ ChannelState *statePtr; /* State of channel stack */ int active = 1; /* Flag == 1 while there's still work to do */ int doflushnb; /* * Fetch the pre-TIP#398 compatibility flag. */ { const char *s; Tcl_DString ds; s = TclGetEnv("TCL_FLUSH_NONBLOCKING_ON_EXIT", &ds); doflushnb = ((s != NULL) && strcmp(s, "0")); if (s != NULL) { Tcl_DStringFree(&ds); } } /* * Walk all channel state structures known to this thread and close * corresponding channels. */ while (active) { /* * Iterate through the open channel list, and find the first channel * that isn't dead. We start from the head of the list each time, * because the close action on one channel can close others. */ active = 0; for (statePtr = tsdPtr->firstCSPtr; statePtr != NULL; statePtr = statePtr->nextCSPtr) { chanPtr = statePtr->topChanPtr; if (GotFlag(statePtr, CHANNEL_DEAD)) { continue; } if (!GotFlag(statePtr, CHANNEL_INCLOSE | CHANNEL_CLOSED ) || GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { ResetFlag(statePtr, BG_FLUSH_SCHEDULED); active = 1; break; } } /* * We've found a live (or bg-closing) channel. Close it. */ if (active) { TclChannelPreserve((Tcl_Channel)chanPtr); /* * TIP #398: by default, we no longer set the channel back into * blocking mode. To restore the old blocking behavior, the * environment variable TCL_FLUSH_NONBLOCKING_ON_EXIT must be set * and not be "0". */ if (doflushnb) { /* * Set the channel back into blocking mode to ensure that we * wait for all data to flush out. */ (void) Tcl_SetChannelOption(NULL, (Tcl_Channel) chanPtr, "-blocking", "on"); } if ((chanPtr == (Channel *) tsdPtr->stdinChannel) || (chanPtr == (Channel *) tsdPtr->stdoutChannel) || (chanPtr == (Channel *) tsdPtr->stderrChannel)) { /* * Decrement the refcount which was earlier artificially * bumped up to keep the channel from being closed. */ statePtr->refCount--; } if (statePtr->refCount <= 0) { /* * Close it only if the refcount indicates that the channel is * not referenced from any interpreter. If it is, that * interpreter will close the channel when it gets destroyed. */ (void) Tcl_CloseEx(NULL, (Tcl_Channel) chanPtr, 0); } else { /* * The refcount is greater than zero, so flush the channel. */ Tcl_Flush((Tcl_Channel) chanPtr); /* * Call the device driver to actually close the underlying * device for this channel. */ (void) ChanClose(chanPtr, NULL); /* * Finally, we clean up the fields in the channel data * structure since all of them have been deleted already. We * mark the channel with CHANNEL_DEAD to prevent any further * IO operations on it. */ chanPtr->instanceData = NULL; SetFlag(statePtr, CHANNEL_DEAD); } TclChannelRelease((Tcl_Channel)chanPtr); } } FreeBinaryEncoding(); TclpFinalizeSockets(); TclpFinalizePipes(); } /* *---------------------------------------------------------------------- * * Tcl_SetStdChannel -- * * This function is used to change the channels that are used for * stdin/stdout/stderr in new interpreters. * * Results: * None * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_SetStdChannel( Tcl_Channel channel, int type) /* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); int init = channel ? 1 : -1; switch (type) { case TCL_STDIN: tsdPtr->stdinInitialized = init; tsdPtr->stdinChannel = channel; break; case TCL_STDOUT: tsdPtr->stdoutInitialized = init; tsdPtr->stdoutChannel = channel; break; case TCL_STDERR: tsdPtr->stderrInitialized = init; tsdPtr->stderrChannel = channel; if (channel) { ENCODING_PROFILE_SET(((Channel *)channel)->state->inputEncodingFlags, TCL_ENCODING_PROFILE_REPLACE); ENCODING_PROFILE_SET(((Channel *)channel)->state->outputEncodingFlags, TCL_ENCODING_PROFILE_REPLACE); } break; } } /* *---------------------------------------------------------------------- * * Tcl_GetStdChannel -- * * Returns the specified standard channel. * * Results: * Returns the specified standard channel, or NULL. * * Side effects: * May cause the creation of a standard channel and the underlying file. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_GetStdChannel( int type) /* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR. */ { Tcl_Channel channel = NULL; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* * If the channels were not created yet, create them now and store them in * the static variables. */ switch (type) { case TCL_STDIN: if (!tsdPtr->stdinInitialized) { tsdPtr->stdinInitialized = -1; tsdPtr->stdinChannel = TclpGetDefaultStdChannel(TCL_STDIN); /* * Artificially bump the refcount to ensure that the channel is * only closed on exit. * * NOTE: Must only do this if stdinChannel is not NULL. It can be * NULL in situations where Tcl is unable to connect to the * standard input. */ if (tsdPtr->stdinChannel != NULL) { tsdPtr->stdinInitialized = 1; Tcl_RegisterChannel(NULL, tsdPtr->stdinChannel); } } channel = tsdPtr->stdinChannel; break; case TCL_STDOUT: if (!tsdPtr->stdoutInitialized) { tsdPtr->stdoutInitialized = -1; tsdPtr->stdoutChannel = TclpGetDefaultStdChannel(TCL_STDOUT); if (tsdPtr->stdoutChannel != NULL) { tsdPtr->stdoutInitialized = 1; Tcl_RegisterChannel(NULL, tsdPtr->stdoutChannel); } } channel = tsdPtr->stdoutChannel; break; case TCL_STDERR: if (!tsdPtr->stderrInitialized) { tsdPtr->stderrInitialized = -1; tsdPtr->stderrChannel = TclpGetDefaultStdChannel(TCL_STDERR); if (tsdPtr->stderrChannel != NULL) { ENCODING_PROFILE_SET(((Channel *)tsdPtr->stderrChannel)->state->inputEncodingFlags, TCL_ENCODING_PROFILE_REPLACE); ENCODING_PROFILE_SET(((Channel *)tsdPtr->stderrChannel)->state->outputEncodingFlags, TCL_ENCODING_PROFILE_REPLACE); tsdPtr->stderrInitialized = 1; Tcl_RegisterChannel(NULL, tsdPtr->stderrChannel); } } channel = tsdPtr->stderrChannel; break; } return channel; } /* *---------------------------------------------------------------------- * * Tcl_CreateCloseHandler * * Creates a close callback which will be called when the channel is * closed. * * Results: * None. * * Side effects: * Causes the callback to be called in the future when the channel will * be closed. * *---------------------------------------------------------------------- */ void Tcl_CreateCloseHandler( Tcl_Channel chan, /* The channel for which to create the close * callback. */ Tcl_CloseProc *proc, /* The callback routine to call when the * channel will be closed. */ void *clientData) /* Arbitrary data to pass to the close * callback. */ { ChannelState *statePtr = ((Channel *) chan)->state; CloseCallback *cbPtr; cbPtr = (CloseCallback *)Tcl_Alloc(sizeof(CloseCallback)); cbPtr->proc = proc; cbPtr->clientData = clientData; cbPtr->nextPtr = statePtr->closeCbPtr; statePtr->closeCbPtr = cbPtr; } /* *---------------------------------------------------------------------- * * Tcl_DeleteCloseHandler -- * * Removes a callback that would have been called on closing the channel. * If there is no matching callback then this function has no effect. * * Results: * None. * * Side effects: * The callback will not be called in the future when the channel is * eventually closed. * *---------------------------------------------------------------------- */ void Tcl_DeleteCloseHandler( Tcl_Channel chan, /* The channel for which to cancel the close * callback. */ Tcl_CloseProc *proc, /* The procedure for the callback to * remove. */ void *clientData) /* The callback data for the callback to * remove. */ { ChannelState *statePtr = ((Channel *) chan)->state; CloseCallback *cbPtr, *cbPrevPtr; for (cbPtr = statePtr->closeCbPtr, cbPrevPtr = NULL; cbPtr != NULL; cbPtr = cbPtr->nextPtr) { if ((cbPtr->proc == proc) && (cbPtr->clientData == clientData)) { if (cbPrevPtr == NULL) { statePtr->closeCbPtr = cbPtr->nextPtr; } else { cbPrevPtr->nextPtr = cbPtr->nextPtr; } Tcl_Free(cbPtr); break; } cbPrevPtr = cbPtr; } } /* *---------------------------------------------------------------------- * * GetChannelTable -- * * Gets and potentially initializes the channel table for an interpreter. * If it is initializing the table it also inserts channels for stdin, * stdout and stderr if the interpreter is trusted. * * Results: * A pointer to the hash table created, for use by the caller. * * Side effects: * Initializes the channel table for an interpreter. May create channels * for stdin, stdout and stderr. * *---------------------------------------------------------------------- */ static Tcl_HashTable * GetChannelTable( Tcl_Interp *interp) { Tcl_HashTable *hTblPtr; /* Hash table of channels. */ Tcl_Channel stdinChan, stdoutChan, stderrChan; hTblPtr = (Tcl_HashTable *)Tcl_GetAssocData(interp, "tclIO", NULL); if (hTblPtr == NULL) { hTblPtr = (Tcl_HashTable *)Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(hTblPtr, TCL_STRING_KEYS); Tcl_SetAssocData(interp, "tclIO", (Tcl_InterpDeleteProc *) DeleteChannelTable, hTblPtr); /* * If the interpreter is trusted (not "safe"), insert channels for * stdin, stdout and stderr (possibly creating them in the process). */ if (Tcl_IsSafe(interp) == 0) { stdinChan = Tcl_GetStdChannel(TCL_STDIN); if (stdinChan != NULL) { Tcl_RegisterChannel(interp, stdinChan); } stdoutChan = Tcl_GetStdChannel(TCL_STDOUT); if (stdoutChan != NULL) { Tcl_RegisterChannel(interp, stdoutChan); } stderrChan = Tcl_GetStdChannel(TCL_STDERR); if (stderrChan != NULL) { Tcl_RegisterChannel(interp, stderrChan); } } } return hTblPtr; } /* *---------------------------------------------------------------------- * * DeleteChannelTable -- * * Deletes the channel table for an interpreter, closing any open * channels whose refcount reaches zero. This procedure is invoked when * an interpreter is deleted, via the AssocData cleanup mechanism. * * Results: * None. * * Side effects: * Deletes the hash table of channels. May close channels. May flush * output on closed channels. Removes any channelEvent handlers that were * registered in this interpreter. * *---------------------------------------------------------------------- */ static void DeleteChannelTable( void *clientData, /* The per-interpreter data structure. */ Tcl_Interp *interp) /* The interpreter being deleted. */ { Tcl_HashTable *hTblPtr; /* The hash table. */ Tcl_HashSearch hSearch; /* Search variable. */ Tcl_HashEntry *hPtr; /* Search variable. */ Channel *chanPtr; /* Channel being deleted. */ ChannelState *statePtr; /* State of Channel being deleted. */ EventScriptRecord *sPtr, *prevPtr, *nextPtr; /* Variables to loop over all channel events * registered, to delete the ones that refer * to the interpreter being deleted. */ /* * Delete all the registered channels - this will close channels whose * refcount reaches zero. */ hTblPtr = (Tcl_HashTable *)clientData; for (hPtr = Tcl_FirstHashEntry(hTblPtr, &hSearch); hPtr != NULL; hPtr = Tcl_FirstHashEntry(hTblPtr, &hSearch)) { chanPtr = (Channel *)Tcl_GetHashValue(hPtr); statePtr = chanPtr->state; /* * Remove any file events registered in this interpreter. */ for (sPtr = statePtr->scriptRecordPtr, prevPtr = NULL; sPtr != NULL; sPtr = nextPtr) { nextPtr = sPtr->nextPtr; if (sPtr->interp == interp) { if (prevPtr == NULL) { statePtr->scriptRecordPtr = nextPtr; } else { prevPtr->nextPtr = nextPtr; } Tcl_DeleteChannelHandler((Tcl_Channel) chanPtr, TclChannelEventScriptInvoker, sPtr); TclDecrRefCount(sPtr->scriptPtr); Tcl_Free(sPtr); } else { prevPtr = sPtr; } } /* * Cannot call Tcl_UnregisterChannel because that procedure calls * Tcl_GetAssocData to get the channel table, which might already be * inaccessible from the interpreter structure. Instead, we emulate * the behavior of Tcl_UnregisterChannel directly here. */ Tcl_DeleteHashEntry(hPtr); statePtr->epoch++; if (statePtr->refCount-- <= 1) { if (!GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { (void) Tcl_CloseEx(interp, (Tcl_Channel) chanPtr, 0); } } } Tcl_DeleteHashTable(hTblPtr); Tcl_Free(hTblPtr); } /* *---------------------------------------------------------------------- * * CheckForStdChannelsBeingClosed -- * * Perform special handling for standard channels being closed. When * given a standard channel, if the refcount is now 1, it means that the * last reference to the standard channel is being explicitly closed. Now * bump the refcount artificially down to 0, to ensure the normal * handling of channels being closed will occur. Also reset the static * pointer to the channel to NULL, to avoid dangling references. * * Results: * None. * * Side effects: * Manipulates the refcount on standard channels. May smash the global * static pointer to a standard channel. * *---------------------------------------------------------------------- */ static void CheckForStdChannelsBeingClosed( Tcl_Channel chan) { ChannelState *statePtr = ((Channel *) chan)->state; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (tsdPtr->stdinInitialized == 1 && tsdPtr->stdinChannel != NULL && statePtr == ((Channel *)tsdPtr->stdinChannel)->state) { if (statePtr->refCount < 2) { statePtr->refCount = 0; tsdPtr->stdinChannel = NULL; return; } } else if (tsdPtr->stdoutInitialized == 1 && tsdPtr->stdoutChannel != NULL && statePtr == ((Channel *)tsdPtr->stdoutChannel)->state) { if (statePtr->refCount < 2) { statePtr->refCount = 0; tsdPtr->stdoutChannel = NULL; return; } } else if (tsdPtr->stderrInitialized == 1 && tsdPtr->stderrChannel != NULL && statePtr == ((Channel *)tsdPtr->stderrChannel)->state) { if (statePtr->refCount < 2) { statePtr->refCount = 0; tsdPtr->stderrChannel = NULL; return; } } } /* *---------------------------------------------------------------------- * * Tcl_IsStandardChannel -- * * Test if the given channel is a standard channel. No attempt is made to * check if the channel or the standard channels are initialized or * otherwise valid. * * Results: * Returns 1 if true, 0 if false. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_IsStandardChannel( Tcl_Channel chan) /* Channel to check. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if ((chan == tsdPtr->stdinChannel) || (chan == tsdPtr->stdoutChannel) || (chan == tsdPtr->stderrChannel)) { return 1; } else { return 0; } } /* *---------------------------------------------------------------------- * * Tcl_RegisterChannel -- * * Adds an already-open channel to the channel table of an interpreter. * If the interpreter passed as argument is NULL, it only increments the * channel refCount. * * Results: * None. * * Side effects: * May increment the reference count of a channel. * *---------------------------------------------------------------------- */ void Tcl_RegisterChannel( Tcl_Interp *interp, /* Interpreter in which to add the channel. */ Tcl_Channel chan) /* The channel to add to this interpreter * channel table. */ { Tcl_HashTable *hTblPtr; /* Hash table of channels. */ Tcl_HashEntry *hPtr; /* Search variable. */ int isNew; /* Is the hash entry new or does it exist? */ Channel *chanPtr; /* The actual channel. */ ChannelState *statePtr; /* State of the actual channel. */ /* * Always (un)register bottom-most channel in the stack. This makes * management of the channel list easier because no manipulation is * necessary during (un)stack operation. */ chanPtr = ((Channel *) chan)->state->bottomChanPtr; statePtr = chanPtr->state; if (statePtr->channelName == NULL) { Tcl_Panic("Tcl_RegisterChannel: channel without name"); } if (interp != NULL) { hTblPtr = GetChannelTable(interp); hPtr = Tcl_CreateHashEntry(hTblPtr, statePtr->channelName, &isNew); if (!isNew) { if (chan == Tcl_GetHashValue(hPtr)) { return; } Tcl_Panic("Tcl_RegisterChannel: duplicate channel names"); } Tcl_SetHashValue(hPtr, chanPtr); } statePtr->refCount++; } /* *---------------------------------------------------------------------- * * Tcl_UnregisterChannel -- * * Deletes the hash entry for a channel associated with an interpreter. * If the interpreter given as argument is NULL, it only decrements the * reference count. (This all happens in the Tcl_DetachChannel helper * function). * * Finally, if the reference count of the channel drops to zero, it is * deleted. * * Results: * A standard Tcl result. * * Side effects: * Calls Tcl_DetachChannel which deletes the hash entry for a channel * associated with an interpreter. * * May delete the channel, which can have a variety of consequences, * especially if we are forced to close the channel. * *---------------------------------------------------------------------- */ int Tcl_UnregisterChannel( Tcl_Interp *interp, /* Interpreter in which channel is defined. */ Tcl_Channel chan) /* Channel to delete. */ { ChannelState *statePtr; /* State of the real channel. */ statePtr = ((Channel *) chan)->state->bottomChanPtr->state; if (GotFlag(statePtr, CHANNEL_INCLOSE)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "illegal recursive call to close through close-handler" " of channel", -1)); } return TCL_ERROR; } if (DetachChannel(interp, chan) != TCL_OK) { return TCL_OK; } statePtr = ((Channel *) chan)->state->bottomChanPtr->state; /* * Perform special handling for standard channels being closed. If the * refCount is now 1 it means that the last reference to the standard * channel is being explicitly closed, so bump the refCount down * artificially to 0. This will ensure that the channel is actually * closed, below. Also set the static pointer to NULL for the channel. */ CheckForStdChannelsBeingClosed(chan); /* * If the refCount reached zero, close the actual channel. */ if (statePtr->refCount <= 0) { Tcl_Preserve(statePtr); if (!GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { /* * We don't want to re-enter Tcl_CloseEx(). */ if (!GotFlag(statePtr, CHANNEL_CLOSED)) { if (Tcl_CloseEx(interp, chan, 0) != TCL_OK) { SetFlag(statePtr, CHANNEL_CLOSED); Tcl_Release(statePtr); return TCL_ERROR; } } } SetFlag(statePtr, CHANNEL_CLOSED); Tcl_Release(statePtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_DetachChannel -- * * Deletes the hash entry for a channel associated with an interpreter. * If the interpreter given as argument is NULL, it only decrements the * reference count. Even if the ref count drops to zero, the channel is * NOT closed or cleaned up. This allows a channel to be detached from an * interpreter and left in the same state it was in when it was * originally returned by 'Tcl_OpenFileChannel', for example. * * This function cannot be used on the standard channels, and will return * TCL_ERROR if that is attempted. * * This function should only be necessary for special purposes in which * you need to generate a pristine channel from one that has already been * used. All ordinary purposes will almost always want to use * Tcl_UnregisterChannel instead. * * Provided the channel is not attached to any other interpreter, it can * then be closed with Tcl_Close, rather than with Tcl_UnregisterChannel. * * Results: * A standard Tcl result. If the channel is not currently registered with * the given interpreter, TCL_ERROR is returned, otherwise TCL_OK. * However no error messages are left in the interp's result. * * Side effects: * Deletes the hash entry for a channel associated with an interpreter. * *---------------------------------------------------------------------- */ int Tcl_DetachChannel( Tcl_Interp *interp, /* Interpreter in which channel is defined. */ Tcl_Channel chan) /* Channel to delete. */ { if (Tcl_IsStandardChannel(chan)) { return TCL_ERROR; } return DetachChannel(interp, chan); } /* *---------------------------------------------------------------------- * * DetachChannel -- * * Deletes the hash entry for a channel associated with an interpreter. * If the interpreter given as argument is NULL, it only decrements the * reference count. Even if the ref count drops to zero, the channel is * NOT closed or cleaned up. This allows a channel to be detached from an * interpreter and left in the same state it was in when it was * originally returned by 'Tcl_OpenFileChannel', for example. * * Results: * A standard Tcl result. If the channel is not currently registered with * the given interpreter, TCL_ERROR is returned, otherwise TCL_OK. * However no error messages are left in the interp's result. * * Side effects: * Deletes the hash entry for a channel associated with an interpreter. * *---------------------------------------------------------------------- */ static int DetachChannel( Tcl_Interp *interp, /* Interpreter in which channel is defined. */ Tcl_Channel chan) /* Channel to delete. */ { Tcl_HashTable *hTblPtr; /* Hash table of channels. */ Tcl_HashEntry *hPtr; /* Search variable. */ Channel *chanPtr; /* The real IO channel. */ ChannelState *statePtr; /* State of the real channel. */ /* * Always (un)register bottom-most channel in the stack. This makes * management of the channel list easier because no manipulation is * necessary during (un)stack operation. */ chanPtr = ((Channel *) chan)->state->bottomChanPtr; statePtr = chanPtr->state; if (interp != NULL) { hTblPtr = (Tcl_HashTable *)Tcl_GetAssocData(interp, "tclIO", NULL); if (hTblPtr == NULL) { return TCL_ERROR; } hPtr = Tcl_FindHashEntry(hTblPtr, statePtr->channelName); if (hPtr == NULL) { return TCL_ERROR; } if ((Channel *) Tcl_GetHashValue(hPtr) != chanPtr) { return TCL_ERROR; } Tcl_DeleteHashEntry(hPtr); statePtr->epoch++; /* * Remove channel handlers that refer to this interpreter, so that * they will not be present if the actual close is delayed and more * events happen on the channel. This may occur if the channel is * shared between several interpreters, or if the channel has async * flushing active. */ CleanupChannelHandlers(interp, chanPtr); } statePtr->refCount--; return TCL_OK; } /* *--------------------------------------------------------------------------- * * Tcl_GetChannel -- * * Finds an existing Tcl_Channel structure by name in a given * interpreter. This function is public because it is used by * channel-type-specific functions. * * Results: * A Tcl_Channel or NULL on failure. If failed, interp's result object * contains an error message. *modePtr is filled with the modes in which * the channel was opened. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Channel Tcl_GetChannel( Tcl_Interp *interp, /* Interpreter in which to find or create the * channel. */ const char *chanName, /* The name of the channel. */ int *modePtr) /* Where to store the mode in which the * channel was opened? Will contain an OR'ed * combination of TCL_READABLE and * TCL_WRITABLE, if non-NULL. */ { Channel *chanPtr; /* The actual channel. */ Tcl_HashTable *hTblPtr; /* Hash table of channels. */ Tcl_HashEntry *hPtr; /* Search variable. */ const char *name; /* Translated name. */ /* * Substitute "stdin", etc. Note that even though we immediately find the * channel using Tcl_GetStdChannel, we still need to look it up in the * specified interpreter to ensure that it is present in the channel * table. Otherwise, safe interpreters would always have access to the * standard channels. */ name = chanName; if ((chanName[0] == 's') && (chanName[1] == 't')) { chanPtr = NULL; if (strcmp(chanName, "stdin") == 0) { chanPtr = (Channel *) Tcl_GetStdChannel(TCL_STDIN); } else if (strcmp(chanName, "stdout") == 0) { chanPtr = (Channel *) Tcl_GetStdChannel(TCL_STDOUT); } else if (strcmp(chanName, "stderr") == 0) { chanPtr = (Channel *) Tcl_GetStdChannel(TCL_STDERR); } if (chanPtr != NULL) { name = chanPtr->state->channelName; } } hTblPtr = GetChannelTable(interp); hPtr = Tcl_FindHashEntry(hTblPtr, name); if (hPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can not find channel named \"%s\"", chanName)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CHANNEL", chanName, (char *)NULL); return NULL; } /* * Always return bottom-most channel in the stack. This one lives the * longest - other channels may go away unnoticed. The other APIs * compensate where necessary to retrieve the topmost channel again. */ chanPtr = (Channel *)Tcl_GetHashValue(hPtr); chanPtr = chanPtr->state->bottomChanPtr; if (modePtr != NULL) { *modePtr = GotFlag(chanPtr->state, TCL_READABLE|TCL_WRITABLE); } return (Tcl_Channel) chanPtr; } /* *--------------------------------------------------------------------------- * * TclGetChannelFromObj -- * * Finds an existing Tcl_Channel structure by name in a given * interpreter. This function is public because it is used by * channel-type-specific functions. * * Results: * A Tcl_Channel or NULL on failure. If failed, interp's result object * contains an error message. *modePtr is filled with the modes in which * the channel was opened. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int TclGetChannelFromObj( Tcl_Interp *interp, /* Interpreter in which to find or create the * channel. */ Tcl_Obj *objPtr, Tcl_Channel *channelPtr, int *modePtr, /* Where to store the mode in which the * channel was opened? Will contain an OR'ed * combination of TCL_READABLE and * TCL_WRITABLE, if non-NULL. */ TCL_UNUSED(int) /*flags*/) { ChannelState *statePtr; ResolvedChanName *resPtr = NULL; Tcl_Channel chan; if (interp == NULL) { return TCL_ERROR; } ChanGetInternalRep(objPtr, resPtr); if (resPtr) { /* * Confirm validity of saved lookup results. */ statePtr = resPtr->statePtr; if ((resPtr->interp == interp) /* Same interp context */ /* No epoch change in channel since lookup */ && (resPtr->epoch == statePtr->epoch)) { /* * Have a valid saved lookup. Jump to end to return it. */ goto valid; } } chan = Tcl_GetChannel(interp, TclGetString(objPtr), NULL); if (chan == NULL) { if (resPtr) { Tcl_StoreInternalRep(objPtr, &chanObjType, NULL); } return TCL_ERROR; } if (resPtr && resPtr->refCount == 1) { /* Re-use the ResolvedCmdName struct */ Tcl_Release(resPtr->statePtr); } else { resPtr = (ResolvedChanName *) Tcl_Alloc(sizeof(ResolvedChanName)); resPtr->refCount = 0; ChanSetInternalRep(objPtr, resPtr); /* Overwrites, if needed */ } statePtr = ((Channel *)chan)->state; resPtr->statePtr = statePtr; Tcl_Preserve(statePtr); resPtr->interp = interp; resPtr->epoch = statePtr->epoch; valid: *channelPtr = (Tcl_Channel) statePtr->bottomChanPtr; if (modePtr != NULL) { *modePtr = GotFlag(statePtr, TCL_READABLE|TCL_WRITABLE); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_CreateChannel -- * * Creates a new entry in the hash table for a Tcl_Channel record. * * Results: * Returns the new Tcl_Channel. * * Side effects: * Creates a new Tcl_Channel instance and inserts it into the hash table. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_CreateChannel( const Tcl_ChannelType *typePtr, /* The channel type record. */ const char *chanName, /* Name of channel to record. */ void *instanceData, /* Instance specific data. */ int mask) /* TCL_READABLE & TCL_WRITABLE to indicate if * the channel is readable, writable. */ { Channel *chanPtr; /* The channel structure newly created. */ ChannelState *statePtr; /* The stack-level independent state info for * the channel. */ const char *name; char *tmp; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (typePtr->typeName == NULL) { Tcl_Panic("channel does not have a type name"); } if (Tcl_ChannelVersion(typePtr) != TCL_CHANNEL_VERSION_5) { Tcl_Panic("channel type %s must be version TCL_CHANNEL_VERSION_5", typePtr->typeName); } if (typePtr->close2Proc == NULL) { Tcl_Panic("channel type %s must define close2Proc", typePtr->typeName); } if ((TCL_READABLE & mask) && (NULL == typePtr->inputProc)) { Tcl_Panic("channel type %s must define inputProc when used for reader channel", typePtr->typeName); } if ((TCL_WRITABLE & mask) && (NULL == typePtr->outputProc)) { Tcl_Panic("channel type %s must define outputProc when used for writer channel", typePtr->typeName); } if (NULL == typePtr->watchProc) { Tcl_Panic("channel type %s must define watchProc", typePtr->typeName); } /* * JH: We could subsequently memset these to 0 to avoid the numerous * assignments to 0/NULL below. */ chanPtr = (Channel *)Tcl_Alloc(sizeof(Channel)); statePtr = (ChannelState *)Tcl_Alloc(sizeof(ChannelState)); chanPtr->state = statePtr; chanPtr->instanceData = instanceData; chanPtr->typePtr = typePtr; /* * Set all the bits that are part of the stack-independent state * information for the channel. */ if (chanName != NULL) { unsigned len = strlen(chanName) + 1; /* * Make sure we allocate at least 7 bytes, so it fits for "stdout" * later. */ tmp = (char *)Tcl_Alloc((len < 7) ? 7 : len); strcpy(tmp, chanName); } else { tmp = (char *)Tcl_Alloc(7); tmp[0] = '\0'; } statePtr->channelName = tmp; statePtr->flags = mask; statePtr->maxPerms = mask; /* Save max privileges for close callback */ /* * Set the channel to system default encoding. */ name = Tcl_GetEncodingName(NULL); statePtr->encoding = Tcl_GetEncoding(NULL, name); statePtr->inputEncodingState = NULL; statePtr->inputEncodingFlags = TCL_ENCODING_START; statePtr->outputEncodingState = NULL; statePtr->outputEncodingFlags = TCL_ENCODING_START; /* * Set the channel up initially in AUTO input translation mode to accept * "\n", "\r" and "\r\n". Output translation mode is set to a platform * specific default value. The eofChar is set to 0 for both input and * output, so that Tcl does not look for an in-file EOF indicator (e.g., * ^Z) and does not append an EOF indicator to files. */ statePtr->inputTranslation = TCL_TRANSLATE_AUTO; statePtr->outputTranslation = TCL_PLATFORM_TRANSLATION; statePtr->inEofChar = 0; statePtr->unreportedError = 0; statePtr->refCount = 0; statePtr->closeCbPtr = NULL; statePtr->curOutPtr = NULL; statePtr->outQueueHead = NULL; statePtr->outQueueTail = NULL; statePtr->saveInBufPtr = NULL; statePtr->inQueueHead = NULL; statePtr->inQueueTail = NULL; statePtr->chPtr = NULL; statePtr->interestMask = 0; statePtr->scriptRecordPtr = NULL; statePtr->bufSize = CHANNELBUFFER_DEFAULT_SIZE; statePtr->timer = NULL; statePtr->timerChanPtr = NULL; statePtr->csPtrR = NULL; statePtr->csPtrW = NULL; statePtr->outputStage = NULL; /* * As we are creating the channel, it is obviously the top for now. */ statePtr->topChanPtr = chanPtr; statePtr->bottomChanPtr = chanPtr; chanPtr->downChanPtr = NULL; chanPtr->upChanPtr = NULL; chanPtr->inQueueHead = NULL; chanPtr->inQueueTail = NULL; chanPtr->refCount = 0; /* * TIP #219, Tcl Channel Reflection API */ statePtr->chanMsg = NULL; statePtr->unreportedMsg = NULL; statePtr->epoch = 0; /* * Link the channel into the list of all channels; create an on-exit * handler if there is not one already, to close off all the channels in * the list on exit. * * JH: Could call Tcl_SpliceChannel, but need to avoid NULL check. * * TIP #218. * AK: Just initialize the field to NULL before invoking Tcl_SpliceChannel * We need Tcl_SpliceChannel, for the threadAction calls. There is no * real reason to duplicate all of this. * NOTE: All drivers using thread actions now have to perform their TSD * manipulation only in their thread action proc. Doing it when * creating their instance structures will collide with the thread * action activity and lead to damaged lists. */ statePtr->nextCSPtr = NULL; SpliceChannel((Tcl_Channel) chanPtr); /* * Install this channel in the first empty standard channel slot, if the * channel was previously closed explicitly. */ if ((tsdPtr->stdinChannel == NULL) && (tsdPtr->stdinInitialized == 1)) { strcpy(tmp, "stdin"); Tcl_SetStdChannel((Tcl_Channel) chanPtr, TCL_STDIN); Tcl_RegisterChannel(NULL, (Tcl_Channel) chanPtr); } else if ((tsdPtr->stdoutChannel == NULL) && (tsdPtr->stdoutInitialized == 1)) { strcpy(tmp, "stdout"); Tcl_SetStdChannel((Tcl_Channel) chanPtr, TCL_STDOUT); Tcl_RegisterChannel(NULL, (Tcl_Channel) chanPtr); } else if ((tsdPtr->stderrChannel == NULL) && (tsdPtr->stderrInitialized == 1)) { strcpy(tmp, "stderr"); Tcl_SetStdChannel((Tcl_Channel) chanPtr, TCL_STDERR); Tcl_RegisterChannel(NULL, (Tcl_Channel) chanPtr); } return (Tcl_Channel) chanPtr; } /* *---------------------------------------------------------------------- * * Tcl_StackChannel -- * * Replaces an entry in the hash table for a Tcl_Channel record. The * replacement is a new channel with same name, it supercedes the * replaced channel. Input and output of the superceded channel is now * going through the newly created channel and allows the arbitrary * filtering/manipulation of the dataflow. * * Andreas Kupries , 12/13/1998 "Trf-Patch for * filtering channels" * * Results: * Returns the new Tcl_Channel, which actually contains the saved * information about prevChan. * * Side effects: * A new channel structure is allocated and linked below the existing * channel. The channel operations and client data of the existing * channel are copied down to the newly created channel, and the current * channel has its operations replaced by the new typePtr. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_StackChannel( Tcl_Interp *interp, /* The interpreter we are working in */ const Tcl_ChannelType *typePtr, /* The channel type record for the new * channel. */ void *instanceData, /* Instance specific data for the new * channel. */ int mask, /* TCL_READABLE & TCL_WRITABLE to indicate if * the channel is readable, writable. */ Tcl_Channel prevChan) /* The channel structure to replace */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Channel *chanPtr, *prevChanPtr; ChannelState *statePtr; /* * Find the given channel (prevChan) in the list of all channels. If we do * not find it, then it was never registered correctly. * * This operation should occur at the top of a channel stack. */ statePtr = (ChannelState *) tsdPtr->firstCSPtr; prevChanPtr = ((Channel *) prevChan)->state->topChanPtr; while ((statePtr != NULL) && (statePtr->topChanPtr != prevChanPtr)) { statePtr = statePtr->nextCSPtr; } if (statePtr == NULL) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't find state for channel \"%s\"", Tcl_GetChannelName(prevChan))); } return NULL; } /* * Here we check if the given "mask" matches the "flags" of the already * existing channel. * * | - | R | W | RW | * --+---+---+---+----+ <=> 0 != (chan->mask & prevChan->mask) * - | | | | | * R | | + | | + | The superceding channel is allowed to restrict * W | | | + | + | the capabilities of the superceded one! * RW| | + | + | + | * --+---+---+---+----+ */ if ((mask & GotFlag(statePtr, TCL_READABLE|TCL_WRITABLE)) == 0) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "reading and writing both disallowed for channel \"%s\"", Tcl_GetChannelName(prevChan))); } return NULL; } /* * Flush the buffers. This ensures that any data still in them at this * time is not handled by the new transformation. Restrict this to * writable channels. Take care to hide a possible bg-copy in progress * from Tcl_Flush and the CheckForChannelErrors inside. */ if ((mask & TCL_WRITABLE) != 0) { CopyState *csPtrR = statePtr->csPtrR; CopyState *csPtrW = statePtr->csPtrW; statePtr->csPtrR = NULL; statePtr->csPtrW = NULL; /* * TODO: Examine what can go wrong if Tcl_Flush() call disturbs * the stacking state of this channel during its operations. */ if (Tcl_Flush((Tcl_Channel) prevChanPtr) != TCL_OK) { statePtr->csPtrR = csPtrR; statePtr->csPtrW = csPtrW; if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not flush channel \"%s\"", Tcl_GetChannelName(prevChan))); } return NULL; } statePtr->csPtrR = csPtrR; statePtr->csPtrW = csPtrW; } /* * Discard any input in the buffers. They are not yet read by the user of * the channel, so they have to go through the new transformation before * reading. As the buffers contain the untransformed form their contents * are not only useless but actually distorts our view of the system. * * To preserve the information without having to read them again and to * avoid problems with the location in the channel (seeking might be * impossible) we move the buffers from the common state structure into * the channel itself. We use the buffers in the channel below the new * transformation to hold the data. In the future this allows us to write * transformations which preread data and push the unused part back when * they are going away. */ if (((mask & TCL_READABLE) != 0) && (statePtr->inQueueHead != NULL)) { /* * When statePtr->inQueueHead is not NULL, we know * prevChanPtr->inQueueHead must be NULL. */ assert(prevChanPtr->inQueueHead == NULL); assert(prevChanPtr->inQueueTail == NULL); prevChanPtr->inQueueHead = statePtr->inQueueHead; prevChanPtr->inQueueTail = statePtr->inQueueTail; statePtr->inQueueHead = NULL; statePtr->inQueueTail = NULL; } chanPtr = (Channel *)Tcl_Alloc(sizeof(Channel)); /* * Save some of the current state into the new structure, reinitialize the * parts which will stay with the transformation. * * Remarks: */ chanPtr->state = statePtr; chanPtr->instanceData = instanceData; chanPtr->typePtr = typePtr; chanPtr->downChanPtr = prevChanPtr; chanPtr->upChanPtr = NULL; chanPtr->inQueueHead = NULL; chanPtr->inQueueTail = NULL; chanPtr->refCount = 0; /* * Place new block at the head of a possibly existing list of previously * stacked channels. */ prevChanPtr->upChanPtr = chanPtr; statePtr->topChanPtr = chanPtr; /* * TIP #218, Channel Thread Actions. * * We call the thread actions for the new channel directly. We _cannot_ * use SpliceChannel, because the (thread-)global list of all channels * always contains the _ChannelState_ for a stack of channels, not the * individual channels. And SpliceChannel would not only call the thread * actions, but also add the shared ChannelState to this list a second * time, mangling it. */ ChanThreadAction(chanPtr, TCL_CHANNEL_THREAD_INSERT); return (Tcl_Channel) chanPtr; } void TclChannelPreserve( Tcl_Channel chan) { ((Channel *)chan)->refCount++; } void TclChannelRelease( Tcl_Channel chan) { Channel *chanPtr = (Channel *) chan; if (chanPtr->refCount == 0) { Tcl_Panic("Channel released more than preserved"); } if (--chanPtr->refCount) { return; } if (chanPtr->typePtr == NULL) { Tcl_Free(chanPtr); } } static void ChannelFree( Channel *chanPtr) { if (chanPtr->refCount == 0) { Tcl_Free(chanPtr); return; } chanPtr->typePtr = NULL; } /* *---------------------------------------------------------------------- * * Tcl_UnstackChannel -- * * Unstacks an entry in the hash table for a Tcl_Channel record. This is * the reverse to 'Tcl_StackChannel'. * * Results: * A standard Tcl result. * * Side effects: * If TCL_ERROR is returned, the Posix error code will be set with * Tcl_SetErrno. May leave a message in interp result as well. * *---------------------------------------------------------------------- */ int Tcl_UnstackChannel( Tcl_Interp *interp, /* The interpreter we are working in */ Tcl_Channel chan) /* The channel to unstack */ { Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; int result = 0; /* * This operation should occur at the top of a channel stack. */ chanPtr = statePtr->topChanPtr; if (chanPtr->downChanPtr != NULL) { /* * Instead of manipulating the per-thread / per-interp list/hash table * of registered channels we wind down the state of the * transformation, and then restore the state of underlying channel * into the old structure. * * TODO: Figure out how to handle the situation where the chan * operations called below by this unstacking operation cause * another unstacking recursively. In that case the downChanPtr * value we're holding on to will not be the right thing. */ Channel *downChanPtr = chanPtr->downChanPtr; /* * Flush the buffers. This ensures that any data still in them at this * time _is_ handled by the transformation we are unstacking right * now. Restrict this to writable channels. Take care to hide a * possible bg-copy in progress from Tcl_Flush and the * CheckForChannelErrors inside. */ if (GotFlag(statePtr, TCL_WRITABLE)) { CopyState *csPtrR = statePtr->csPtrR; CopyState *csPtrW = statePtr->csPtrW; statePtr->csPtrR = NULL; statePtr->csPtrW = NULL; if (Tcl_Flush((Tcl_Channel) chanPtr) != TCL_OK) { statePtr->csPtrR = csPtrR; statePtr->csPtrW = csPtrW; /* * TIP #219, Tcl Channel Reflection API. * Move error messages put by the driver into the chan/ip * bypass area into the regular interpreter result. Fall back * to the regular message if nothing was found in the * bypasses. */ if (!TclChanCaughtErrorBypass(interp, chan) && interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not flush channel \"%s\"", Tcl_GetChannelName((Tcl_Channel) chanPtr))); } return TCL_ERROR; } statePtr->csPtrR = csPtrR; statePtr->csPtrW = csPtrW; } /* * Anything in the input queue and the push-back buffers of the * transformation going away is transformed data, but not yet read. As * unstacking means that the caller does not want to see transformed * data any more we have to discard these bytes. To avoid writing an * analogue to 'DiscardInputQueued' we move the information in the * push back buffers to the input queue and then call * 'DiscardInputQueued' on that. */ if (GotFlag(statePtr, TCL_READABLE) && ((statePtr->inQueueHead != NULL) || (chanPtr->inQueueHead != NULL))) { if ((statePtr->inQueueHead != NULL) && (chanPtr->inQueueHead != NULL)) { statePtr->inQueueTail->nextPtr = chanPtr->inQueueHead; statePtr->inQueueTail = chanPtr->inQueueTail; statePtr->inQueueHead = statePtr->inQueueTail; } else if (chanPtr->inQueueHead != NULL) { statePtr->inQueueHead = chanPtr->inQueueHead; statePtr->inQueueTail = chanPtr->inQueueTail; } chanPtr->inQueueHead = NULL; chanPtr->inQueueTail = NULL; DiscardInputQueued(statePtr, 0); } /* * TIP #218, Channel Thread Actions. * * We call the thread actions for the new channel directly. We * _cannot_ use CutChannel, because the (thread-)global list of all * channels always contains the _ChannelState_ for a stack of * channels, not the individual channels. And SpliceChannel would not * only call the thread actions, but also remove the shared * ChannelState from this list despite there being more channels for * the state which are still active. */ ChanThreadAction(chanPtr, TCL_CHANNEL_THREAD_REMOVE); statePtr->topChanPtr = downChanPtr; downChanPtr->upChanPtr = NULL; /* * Leave this link intact for closeproc * chanPtr->downChanPtr = NULL; */ /* * Close and free the channel driver state. * TIP #220: This is done with maximum privileges (as created). */ ResetFlag(statePtr, TCL_READABLE|TCL_WRITABLE); SetFlag(statePtr, statePtr->maxPerms); result = ChanClose(chanPtr, interp); ChannelFree(chanPtr); UpdateInterest(statePtr->topChanPtr); if (result != 0) { Tcl_SetErrno(result); /* * TIP #219, Tcl Channel Reflection API. * Move error messages put by the driver into the chan/ip bypass * area into the regular interpreter result. */ TclChanCaughtErrorBypass(interp, chan); return TCL_ERROR; } } else { /* * This channel does not cover another one. Simply do a close, if * necessary. */ if (statePtr->refCount <= 0) { if (Tcl_CloseEx(interp, chan, 0) != TCL_OK) { /* * TIP #219, Tcl Channel Reflection API. * "TclChanCaughtErrorBypass" is not required here, it was * done already by "Tcl_Close". */ return TCL_ERROR; } } /* * TIP #218, Channel Thread Actions. * Not required in this branch, this is done by Tcl_Close. If * Tcl_Close is not called then the ChannelState is still active in * the thread and no action has to be taken either. */ } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_GetStackedChannel -- * * Determines whether the specified channel is stacked upon another. * * Results: * NULL if the channel is not stacked upon another one, or a reference to * the channel it is stacked upon. This reference can be used in queries, * but modification is not allowed. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_GetStackedChannel( Tcl_Channel chan) { Channel *chanPtr = (Channel *) chan; /* The actual channel. */ return (Tcl_Channel) chanPtr->downChanPtr; } /* *---------------------------------------------------------------------- * * Tcl_GetTopChannel -- * * Returns the top channel of a channel stack. * * Results: * NULL if the channel is not stacked upon another one, or a reference to * the channel it is stacked upon. This reference can be used in queries, * but modification is not allowed. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_GetTopChannel( Tcl_Channel chan) { Channel *chanPtr = (Channel *) chan; /* The actual channel. */ return (Tcl_Channel) chanPtr->state->topChanPtr; } /* *---------------------------------------------------------------------- * * Tcl_GetChannelInstanceData -- * * Returns the client data associated with a channel. * * Results: * The client data. * * Side effects: * None. * *---------------------------------------------------------------------- */ void * Tcl_GetChannelInstanceData( Tcl_Channel chan) /* Channel for which to return client data. */ { Channel *chanPtr = (Channel *) chan; /* The actual channel. */ return chanPtr->instanceData; } /* *---------------------------------------------------------------------- * * Tcl_GetChannelThread -- * * Given a channel structure, returns the thread managing it. TIP #10 * * Results: * Returns the id of the thread managing the channel. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_ThreadId Tcl_GetChannelThread( Tcl_Channel chan) /* The channel to return the managing thread * for. */ { Channel *chanPtr = (Channel *) chan; /* The actual channel. */ return chanPtr->state->managingThread; } /* *---------------------------------------------------------------------- * * Tcl_GetChannelType -- * * Given a channel structure, returns the channel type structure. * * Results: * Returns a pointer to the channel type structure. * * Side effects: * None. * *---------------------------------------------------------------------- */ const Tcl_ChannelType * Tcl_GetChannelType( Tcl_Channel chan) /* The channel to return type for. */ { Channel *chanPtr = (Channel *) chan; /* The actual channel. */ return chanPtr->typePtr; } /* *---------------------------------------------------------------------- * * Tcl_GetChannelMode -- * * Computes a mask indicating whether the channel is open for reading and * writing. * * Results: * An OR-ed combination of TCL_READABLE and TCL_WRITABLE. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_GetChannelMode( Tcl_Channel chan) /* The channel for which the mode is being * computed. */ { ChannelState *statePtr = ((Channel *) chan)->state; /* State of actual channel. */ return GotFlag(statePtr, TCL_READABLE|TCL_WRITABLE); } /* *---------------------------------------------------------------------- * * Tcl_GetChannelName -- * * Returns the string identifying the channel name. * * Results: * The string containing the channel name. This memory is owned by the * generic layer and should not be modified by the caller. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * Tcl_GetChannelName( Tcl_Channel chan) /* The channel for which to return the name. */ { ChannelState *statePtr = ((Channel *) chan)->state; /* State of actual channel. */ return statePtr->channelName; } /* *---------------------------------------------------------------------- * * Tcl_GetChannelHandle -- * * Returns an OS handle associated with a channel. * * Results: * Returns TCL_OK and places the handle in handlePtr, or returns * TCL_ERROR on failure. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_GetChannelHandle( Tcl_Channel chan, /* The channel to get file from. */ int direction, /* TCL_WRITABLE or TCL_READABLE. */ void **handlePtr) /* Where to store handle */ { Channel *chanPtr; /* The actual channel. */ void *handle; int result; chanPtr = ((Channel *) chan)->state->bottomChanPtr; if (!chanPtr->typePtr->getHandleProc) { Tcl_SetChannelError(chan, Tcl_ObjPrintf( "channel \"%s\" does not support OS handles", Tcl_GetChannelName(chan))); return TCL_ERROR; } result = chanPtr->typePtr->getHandleProc(chanPtr->instanceData, direction, &handle); if (handlePtr) { *handlePtr = handle; } return result; } /* *---------------------------------------------------------------------- * * Tcl_RemoveChannelMode -- * * Remove either read or write privileges from the channel. * * Results: * A standard Tcl result code. * * Side effects: * May change the access mode of the channel. * May leave an error message in the interp. * *---------------------------------------------------------------------- */ int Tcl_RemoveChannelMode( Tcl_Interp *interp, /* The interp for an error message. Allowed to be NULL. */ Tcl_Channel chan, /* The channel which is modified. */ int mode) /* The access mode to drop from the channel */ { const char* emsg; ChannelState *statePtr = ((Channel *) chan)->state; /* State of actual channel. */ if ((mode != TCL_READABLE) && (mode != TCL_WRITABLE)) { emsg = "Illegal mode value."; goto error; } if (0 == (GotFlag(statePtr, TCL_READABLE|TCL_WRITABLE) & ~mode)) { emsg = "Bad mode, would make channel inacessible"; goto error; } ResetFlag(statePtr, mode); return TCL_OK; error: if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Tcl_RemoveChannelMode error: %s. Channel: \"%s\"", emsg, Tcl_GetChannelName((Tcl_Channel) chan))); } return TCL_ERROR; } /* *--------------------------------------------------------------------------- * * AllocChannelBuffer -- * * A channel buffer has BUFFER_PADDING bytes extra at beginning to hold * any bytes of a native-encoding character that got split by the end of * the previous buffer and need to be moved to the beginning of the next * buffer to make a contiguous string so it can be converted to UTF-8. * * A channel buffer has BUFFER_PADDING bytes extra at the end to hold any * bytes of a native-encoding character (generated from a UTF-8 * character) that overflow past the end of the buffer and need to be * moved to the next buffer. * * Results: * A newly allocated channel buffer. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static ChannelBuffer * AllocChannelBuffer( Tcl_Size length) /* Desired length of channel buffer. */ { ChannelBuffer *bufPtr; Tcl_Size n; n = length + CHANNELBUFFER_HEADER_SIZE + BUFFER_PADDING + BUFFER_PADDING; bufPtr = (ChannelBuffer *)Tcl_Alloc(n); bufPtr->nextAdded = BUFFER_PADDING; bufPtr->nextRemoved = BUFFER_PADDING; bufPtr->bufLength = length + BUFFER_PADDING; bufPtr->nextPtr = NULL; bufPtr->refCount = 1; return bufPtr; } static void PreserveChannelBuffer( ChannelBuffer *bufPtr) { if (!bufPtr->refCount) { Tcl_Panic("Reuse of ChannelBuffer! %p", bufPtr); } bufPtr->refCount++; } static void ReleaseChannelBuffer( ChannelBuffer *bufPtr) { if (--bufPtr->refCount) { return; } Tcl_Free(bufPtr); } static int IsShared( ChannelBuffer *bufPtr) { return bufPtr->refCount > 1; } /* *---------------------------------------------------------------------- * * RecycleBuffer -- * * Helper function to recycle input and output buffers. Ensures that two * input buffers are saved (one in the input queue and another in the * saveInBufPtr field) and that curOutPtr is set to a buffer. Only if * these conditions are met is the buffer freed to the OS. * * Results: * None. * * Side effects: * May free a buffer to the OS. * *---------------------------------------------------------------------- */ static void RecycleBuffer( ChannelState *statePtr, /* ChannelState in which to recycle buffers. */ ChannelBuffer *bufPtr, /* The buffer to recycle. */ int mustDiscard) /* If nonzero, free the buffer to the OS, * always. */ { /* * Do we have to free the buffer to the OS? */ if (IsShared(bufPtr)) { mustDiscard = 1; } if (mustDiscard) { ReleaseChannelBuffer(bufPtr); return; } /* * Only save buffers which have the requested buffer size for the channel. * This is to honor dynamic changes of the buffe rsize made by the user. */ if ((bufPtr->bufLength) != statePtr->bufSize + BUFFER_PADDING) { ReleaseChannelBuffer(bufPtr); return; } /* * Only save buffers for the input queue if the channel is readable. */ if (GotFlag(statePtr, TCL_READABLE)) { if (statePtr->inQueueHead == NULL) { statePtr->inQueueHead = bufPtr; statePtr->inQueueTail = bufPtr; goto keepBuffer; } if (statePtr->saveInBufPtr == NULL) { statePtr->saveInBufPtr = bufPtr; goto keepBuffer; } } /* * Only save buffers for the output queue if the channel is writable. */ if (GotFlag(statePtr, TCL_WRITABLE)) { if (statePtr->curOutPtr == NULL) { statePtr->curOutPtr = bufPtr; goto keepBuffer; } } /* * If we reached this code we return the buffer to the OS. */ ReleaseChannelBuffer(bufPtr); return; keepBuffer: bufPtr->nextRemoved = BUFFER_PADDING; bufPtr->nextAdded = BUFFER_PADDING; bufPtr->nextPtr = NULL; } /* *---------------------------------------------------------------------- * * DiscardOutputQueued -- * * Discards all output queued in the output queue of a channel. * * Results: * None. * * Side effects: * Recycles buffers. * *---------------------------------------------------------------------- */ static void DiscardOutputQueued( ChannelState *statePtr) /* ChannelState for which to discard output. */ { ChannelBuffer *bufPtr; while (statePtr->outQueueHead != NULL) { bufPtr = statePtr->outQueueHead; statePtr->outQueueHead = bufPtr->nextPtr; RecycleBuffer(statePtr, bufPtr, 0); } statePtr->outQueueHead = NULL; statePtr->outQueueTail = NULL; bufPtr = statePtr->curOutPtr; if (bufPtr && BytesLeft(bufPtr)) { statePtr->curOutPtr = NULL; RecycleBuffer(statePtr, bufPtr, 0); } } /* *---------------------------------------------------------------------- * * CheckForDeadChannel -- * * This function checks is a given channel is Dead (a channel that has * been closed but not yet deallocated.) * * Results: * True (1) if channel is Dead, False (0) if channel is Ok * * Side effects: * None * *---------------------------------------------------------------------- */ static int CheckForDeadChannel( Tcl_Interp *interp, /* For error reporting (can be NULL) */ ChannelState *statePtr) /* The channel state to check. */ { if (!GotFlag(statePtr, CHANNEL_DEAD)) { return 0; } Tcl_SetErrno(EINVAL); if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "unable to access channel: invalid channel", -1)); } return 1; } /* *---------------------------------------------------------------------- * * FlushChannel -- * * This function flushes as much of the queued output as is possible now. * If calledFromAsyncFlush is nonzero, it is being called in an event * handler to flush channel output asynchronously. * * Results: * 0 if successful, else the error code that was returned by the channel * type operation. May leave a message in the interp result. * * Side effects: * May produce output on a channel. May block indefinitely if the channel * is synchronous. May schedule an async flush on the channel. May * recycle memory for buffers in the output queue. * *---------------------------------------------------------------------- */ static int FlushChannel( Tcl_Interp *interp, /* For error reporting during close. */ Channel *chanPtr, /* The channel to flush on. */ int calledFromAsyncFlush) /* If nonzero then we are being called from an * asynchronous flush callback. */ { ChannelState *statePtr = chanPtr->state; /* State of the channel stack. */ ChannelBuffer *bufPtr; /* Iterates over buffered output queue. */ int written; /* Amount of output data actually written in * current round. */ int errorCode = 0; /* Stores POSIX error codes from channel * driver operations. */ int wroteSome = 0; /* Set to one if any data was written to the * driver. */ int bufExists; /* * Prevent writing on a dead channel -- a channel that has been closed but * not yet deallocated. This can occur if the exit handler for the channel * deallocation runs before all channels are unregistered in all * interpreters. */ if (CheckForDeadChannel(interp, statePtr)) { return -1; } /* * Should we shift the current output buffer over to the output queue? * First check that there are bytes in it. If so then... * * If the output queue is empty, then yes, trusting the caller called us * only when written bytes ought to be flushed. * * If the current output buffer is full, then yes, so we can meet the * post-condition that on a successful return to caller we've left space * in the current output buffer for more writing (the flush call was to * make new room). * * If the channel is blocking, then yes, so we guarantee that blocking * flushes actually flush all pending data. * * Otherwise, no. Keep the current output buffer where it is so more * can be written to it, possibly filling it, to promote more efficient * buffer usage. */ bufPtr = statePtr->curOutPtr; if (bufPtr && BytesLeft(bufPtr) && /* Keep empties off queue */ (statePtr->outQueueHead == NULL || IsBufferFull(bufPtr) || !GotFlag(statePtr, CHANNEL_NONBLOCKING))) { if (statePtr->outQueueHead == NULL) { statePtr->outQueueHead = bufPtr; } else { statePtr->outQueueTail->nextPtr = bufPtr; } statePtr->outQueueTail = bufPtr; statePtr->curOutPtr = NULL; } assert(!IsBufferFull(statePtr->curOutPtr)); /* * If we are not being called from an async flush and an async flush * is active, we just return without producing any output. */ if (!calledFromAsyncFlush && GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { return 0; } /* * Loop over the queued buffers and attempt to flush as much as possible * of the queued output to the channel. */ TclChannelPreserve((Tcl_Channel)chanPtr); while (statePtr->outQueueHead) { bufPtr = statePtr->outQueueHead; /* * Produce the output on the channel. */ PreserveChannelBuffer(bufPtr); written = ChanWrite(chanPtr, RemovePoint(bufPtr), BytesLeft(bufPtr), &errorCode); /* * If the write failed completely attempt to start the asynchronous * flush mechanism and break out of this loop - do not attempt to * write any more output at this time. */ if (written < 0) { /* * If the last attempt to write was interrupted, simply retry. */ if (errorCode == EINTR) { errorCode = 0; ReleaseChannelBuffer(bufPtr); continue; } /* * If the channel is non-blocking and we would have blocked, start * a background flushing handler and break out of the loop. */ if ((errorCode == EWOULDBLOCK) || (errorCode == EAGAIN)) { /* * This used to check for CHANNEL_NONBLOCKING, and panic if * the channel was blocking. However, it appears that setting * stdin to -blocking 0 has some effect on the stdout when * it's a tty channel (dup'ed underneath) */ if (!GotFlag(statePtr, BG_FLUSH_SCHEDULED) && !TclInExit()) { SetFlag(statePtr, BG_FLUSH_SCHEDULED); UpdateInterest(chanPtr); } errorCode = 0; ReleaseChannelBuffer(bufPtr); break; } /* * Decide whether to report the error upwards or defer it. */ if (calledFromAsyncFlush) { /* * TIP #219, Tcl Channel Reflection API. * When deferring the error copy a message from the bypass into * the unreported area. Or discard it if the new error is to * be ignored in favor of an earlier deferred error. */ Tcl_Obj *msg = statePtr->chanMsg; if (statePtr->unreportedError == 0) { statePtr->unreportedError = errorCode; statePtr->unreportedMsg = msg; if (msg != NULL) { Tcl_IncrRefCount(msg); } } else { /* * An old unreported error is kept, and this error thrown * away. */ statePtr->chanMsg = NULL; if (msg != NULL) { TclDecrRefCount(msg); } } } else { /* * TIP #219, Tcl Channel Reflection API. * Move error messages put by the driver into the chan bypass * area into the regular interpreter result. Fall back to the * regular message if nothing was found in the bypasses. */ Tcl_SetErrno(errorCode); if (interp != NULL && !TclChanCaughtErrorBypass(interp, (Tcl_Channel) chanPtr)) { Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_PosixError(interp), -1)); } /* * An unreportable bypassed message is kept, for the caller of * Tcl_Seek, Tcl_Write, etc. */ } /* * When we get an error we throw away all the output currently * queued. */ ReleaseChannelBuffer(bufPtr); DiscardOutputQueued(statePtr); break; } else { /* * TODO: Consider detecting and reacting to short writes on * blocking channels. Ought not happen. See iocmd-24.2. */ wroteSome = 1; } bufExists = bufPtr->refCount > 1; ReleaseChannelBuffer(bufPtr); if (bufExists) { /* There is still a reference to this buffer other than the one * this routine just released, meaning that final cleanup of the * buffer hasn't been ordered by, e.g. by a reflected channel * closing the channel from within one of its handler scripts (not * something one would expecte, but it must be considered). Normal * operations on the buffer can proceed. */ bufPtr->nextRemoved += written; /* * If this buffer is now empty, recycle it. */ if (IsBufferEmpty(bufPtr)) { statePtr->outQueueHead = bufPtr->nextPtr; if (statePtr->outQueueHead == NULL) { statePtr->outQueueTail = NULL; } RecycleBuffer(statePtr, bufPtr, 0); } } } /* Closes "while". */ /* * If we wrote some data while flushing in the background, we are done. * We can't finish the background flush until we run out of data and the * channel becomes writable again. This ensures that all of the pending * data has been flushed at the system level. */ if (GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { if (wroteSome) { goto done; } else if (statePtr->outQueueHead == NULL) { ResetFlag(statePtr, BG_FLUSH_SCHEDULED); ChanWatch(chanPtr, statePtr->interestMask); } else { /* * When we are calledFromAsyncFlush, that means a writable * state on the channel triggered the call, so we should be * able to write something. Either we did write something * and wroteSome should be set, or there was nothing left to * write in this call, and we've completed the BG flush. * These are the two cases above. If we get here, that means * there is some kind failure in the writable event machinery. * * The tls extension indeed suffers from flaws in its channel * event mgmt. See https://core.tcl-lang.org/tcl/info/c31ca233ca. * Until that patch is broadly distributed, disable the * assertion checking here, so that programs using Tcl and * tls can be debugged. assert(!calledFromAsyncFlush); */ } } /* * If the channel is flagged as closed, delete it when the refCount drops * to zero, the output queue is empty and there is no output in the * current output buffer. */ if (GotFlag(statePtr, CHANNEL_CLOSED) && (statePtr->refCount <= 0) && (statePtr->outQueueHead == NULL) && ((statePtr->curOutPtr == NULL) || IsBufferEmpty(statePtr->curOutPtr))) { errorCode = CloseChannel(interp, chanPtr, errorCode); goto done; } /* * If the write-side of the channel is flagged as closed, delete it when * the output queue is empty and there is no output in the current output * buffer. */ if (GotFlag(statePtr, CHANNEL_CLOSEDWRITE) && (statePtr->outQueueHead == NULL) && ((statePtr->curOutPtr == NULL) || IsBufferEmpty(statePtr->curOutPtr))) { errorCode = CloseChannelPart(interp, chanPtr, errorCode, TCL_CLOSE_WRITE); goto done; } done: TclChannelRelease((Tcl_Channel)chanPtr); return errorCode; } static void FreeChannelState( void *blockPtr) /* Channel state to free. */ { ChannelState *statePtr = (ChannelState *)blockPtr; /* * Even after close some members can be filled again (in events etc). * Test in bug [79474c588] illustrates one leak (on remaining chanMsg). * Possible other fields need freeing on some constellations. */ DiscardInputQueued(statePtr, 1); if (statePtr->curOutPtr != NULL) { ReleaseChannelBuffer(statePtr->curOutPtr); } DiscardOutputQueued(statePtr); DeleteTimerHandler(statePtr); if (statePtr->chanMsg) { Tcl_DecrRefCount(statePtr->chanMsg); } if (statePtr->unreportedMsg) { Tcl_DecrRefCount(statePtr->unreportedMsg); } Tcl_Free(statePtr); } /* *---------------------------------------------------------------------- * * CloseChannel -- * * Utility procedure to close a channel and free associated resources. * * If the channel was stacked, then the it will copy the necessary * elements of the NEXT channel into the TOP channel, in essence * unstacking the channel. The NEXT channel will then be freed. * * If the channel was not stacked, then we will free all the bits for the * TOP channel, including the data structure itself. * * Results: * Error code from an unreported error or the driver close operation. * * Side effects: * May close the actual channel, may free memory, may change the value of * errno. * *---------------------------------------------------------------------- */ static int CloseChannel( Tcl_Interp *interp, /* For error reporting. */ Channel *chanPtr, /* The channel to close. */ int errorCode) /* Status of operation so far. */ { int result = 0; /* Of calling driver close operation. */ ChannelState *statePtr; /* State of the channel stack. */ ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (chanPtr == NULL) { return result; } statePtr = chanPtr->state; /* * No more input can be consumed so discard any leftover input. */ DiscardInputQueued(statePtr, 1); /* * Discard a leftover buffer in the current output buffer field. */ if (statePtr->curOutPtr != NULL) { ReleaseChannelBuffer(statePtr->curOutPtr); statePtr->curOutPtr = NULL; } /* * The caller guarantees that there are no more buffers queued for output. */ if (statePtr->outQueueHead != NULL) { Tcl_Panic("TclFlush, closed channel: queued output left"); } /* * TIP #219, Tcl Channel Reflection API. * Move a leftover error message in the channel bypass into the * interpreter bypass. Just clear it if there is no interpreter. */ if (statePtr->chanMsg != NULL) { if (interp != NULL) { Tcl_SetChannelErrorInterp(interp, statePtr->chanMsg); } TclDecrRefCount(statePtr->chanMsg); statePtr->chanMsg = NULL; } /* * Remove this channel from of the list of all channels. */ CutChannel((Tcl_Channel) chanPtr); /* * Close and free the channel driver state. * This may leave a TIP #219 error message in the interp. */ result = ChanClose(chanPtr, interp); /* * Some resources can be cleared only if the bottom channel in a stack is * closed. All the other channels in the stack are not allowed to remove. */ if (chanPtr == statePtr->bottomChanPtr) { if (statePtr->channelName != NULL) { Tcl_Free(statePtr->channelName); statePtr->channelName = NULL; } Tcl_FreeEncoding(statePtr->encoding); } /* * If we are being called synchronously, report either any latent error on * the channel or the current error. */ if (statePtr->unreportedError != 0) { errorCode = statePtr->unreportedError; /* * TIP #219, Tcl Channel Reflection API. * Move an error message found in the unreported area into the regular * bypass (interp). This kills any message in the channel bypass area. */ if (statePtr->chanMsg != NULL) { TclDecrRefCount(statePtr->chanMsg); statePtr->chanMsg = NULL; } if (interp) { Tcl_SetChannelErrorInterp(interp, statePtr->unreportedMsg); } } if (errorCode == 0) { errorCode = result; if (errorCode != 0) { Tcl_SetErrno(errorCode); } } /* * Cancel any outstanding timer. */ DeleteTimerHandler(statePtr); /* * Mark the channel as deleted by clearing the type structure. */ if (chanPtr->downChanPtr != NULL) { Channel *downChanPtr = chanPtr->downChanPtr; statePtr->nextCSPtr = tsdPtr->firstCSPtr; tsdPtr->firstCSPtr = statePtr; statePtr->topChanPtr = downChanPtr; downChanPtr->upChanPtr = NULL; ChannelFree(chanPtr); return Tcl_CloseEx(interp, (Tcl_Channel) downChanPtr, 0); } /* * There is only the TOP Channel, so we free the remaining pointers we * have and then ourselves. Since this is the last of the channels in the * stack, make sure to free the ChannelState structure associated with it. */ ChannelFree(chanPtr); Tcl_EventuallyFree(statePtr, FreeChannelState); return errorCode; } /* *---------------------------------------------------------------------- * * Tcl_CutChannel -- * CutChannel -- * * Removes a channel from the (thread-)global list of all channels (in * that thread). This is actually the statePtr for the stack of channel. * * Results: * Nothing. * * Side effects: * Resets the field 'nextCSPtr' of the specified channel state to NULL. * * NOTE: * The channel to cut out of the list must not be referenced in any * interpreter. This is something this procedure cannot check (despite * the refcount) because the caller usually wants fiddle with the channel * (like transferring it to a different thread) and thus keeps the * refcount artificially high to prevent its destruction. * *---------------------------------------------------------------------- */ static void CutChannel( Tcl_Channel chan) /* The channel being removed. Must not be * referenced in any interpreter. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); ChannelState *prevCSPtr; /* Preceding channel state in list of all * states - used to splice a channel out of * the list on close. */ ChannelState *statePtr = ((Channel *) chan)->state; /* State of the channel stack. */ /* * Remove this channel from of the list of all channels (in the current * thread). */ if (tsdPtr->firstCSPtr && (statePtr == tsdPtr->firstCSPtr)) { tsdPtr->firstCSPtr = statePtr->nextCSPtr; } else { for (prevCSPtr = tsdPtr->firstCSPtr; prevCSPtr && (prevCSPtr->nextCSPtr != statePtr); prevCSPtr = prevCSPtr->nextCSPtr) { /* Empty loop body. */ } if (prevCSPtr == NULL) { Tcl_Panic("FlushChannel: damaged channel list"); } prevCSPtr->nextCSPtr = statePtr->nextCSPtr; } statePtr->nextCSPtr = NULL; /* * TIP #218, Channel Thread Actions */ ChanThreadAction((Channel *) chan, TCL_CHANNEL_THREAD_REMOVE); /* Channel is not managed by any thread */ statePtr->managingThread = NULL; } void Tcl_CutChannel( Tcl_Channel chan) /* The channel being added. Must not be * referenced in any interpreter. */ { Channel *chanPtr = ((Channel *) chan)->state->bottomChanPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); ChannelState *prevCSPtr; /* Preceding channel state in list of all * states - used to splice a channel out of * the list on close. */ ChannelState *statePtr = chanPtr->state; /* State of the channel stack. */ /* * Remove this channel from of the list of all channels (in the current * thread). */ if (tsdPtr->firstCSPtr && (statePtr == tsdPtr->firstCSPtr)) { tsdPtr->firstCSPtr = statePtr->nextCSPtr; } else { for (prevCSPtr = tsdPtr->firstCSPtr; prevCSPtr && (prevCSPtr->nextCSPtr != statePtr); prevCSPtr = prevCSPtr->nextCSPtr) { /* Empty loop body. */ } if (prevCSPtr == NULL) { Tcl_Panic("FlushChannel: damaged channel list"); } prevCSPtr->nextCSPtr = statePtr->nextCSPtr; } statePtr->nextCSPtr = NULL; /* * TIP #218, Channel Thread Actions * For all transformations and the base channel. */ for (; chanPtr != NULL ; chanPtr = chanPtr->upChanPtr) { ChanThreadAction(chanPtr, TCL_CHANNEL_THREAD_REMOVE); } /* Channel is not managed by any thread */ statePtr->managingThread = NULL; } /* *---------------------------------------------------------------------- * * Tcl_SpliceChannel -- * SpliceChannel -- * * Adds a channel to the (thread-)global list of all channels (in that * thread). Expects that the field 'nextChanPtr' in the channel is set to * NULL. * * Results: * Nothing. * * Side effects: * Nothing. * * NOTE: * The channel to splice into the list must not be referenced in any * interpreter. This is something this procedure cannot check (despite * the refcount) because the caller usually wants fiddle with the channel * (like transferring it to a different thread) and thus keeps the * refcount artificially high to prevent its destruction. * *---------------------------------------------------------------------- */ static void SpliceChannel( Tcl_Channel chan) /* The channel being added. Must not be * referenced in any interpreter. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); ChannelState *statePtr = ((Channel *) chan)->state; if (statePtr->nextCSPtr != NULL) { Tcl_Panic("SpliceChannel: trying to add channel used in different list"); } statePtr->nextCSPtr = tsdPtr->firstCSPtr; tsdPtr->firstCSPtr = statePtr; /* * TIP #10. Mark the current thread as the new one managing this channel. * Note: 'Tcl_GetCurrentThread' returns sensible values even for * a non-threaded core. */ statePtr->managingThread = Tcl_GetCurrentThread(); /* * TIP #218, Channel Thread Actions */ ChanThreadAction((Channel *) chan, TCL_CHANNEL_THREAD_INSERT); } void Tcl_SpliceChannel( Tcl_Channel chan) /* The channel being added. Must not be * referenced in any interpreter. */ { Channel *chanPtr = ((Channel *) chan)->state->bottomChanPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); ChannelState *statePtr = chanPtr->state; if (statePtr->nextCSPtr != NULL) { Tcl_Panic("SpliceChannel: trying to add channel used in different list"); } statePtr->nextCSPtr = tsdPtr->firstCSPtr; tsdPtr->firstCSPtr = statePtr; /* * TIP #10. Mark the current thread as the new one managing this channel. * Note: 'Tcl_GetCurrentThread' returns sensible values even for * a non-threaded core. */ statePtr->managingThread = Tcl_GetCurrentThread(); /* * TIP #218, Channel Thread Actions * For all transformations and the base channel. */ for (; chanPtr != NULL ; chanPtr = chanPtr->upChanPtr) { ChanThreadAction(chanPtr, TCL_CHANNEL_THREAD_INSERT); } } /* *---------------------------------------------------------------------- * * Tcl_Close -- * * Closes a channel. * * Results: * A standard Tcl result. * * Side effects: * Closes the channel if this is the last reference. * * NOTE: * Tcl_Close removes the channel as far as the user is concerned. * However, it may continue to exist for a while longer if it has a * background flush scheduled. The device itself is eventually closed and * the channel record removed, in CloseChannel, above. * *---------------------------------------------------------------------- */ int TclClose( Tcl_Interp *interp, /* Interpreter for errors. */ Tcl_Channel chan) /* The channel being closed. Must not be * referenced in any interpreter. May be NULL, * in which case this is a no-op. */ { CloseCallback *cbPtr; /* Iterate over close callbacks for this * channel. */ Channel *chanPtr; /* The real IO channel. */ ChannelState *statePtr; /* State of real IO channel. */ int result = 0; /* Of calling FlushChannel. */ int flushcode; int stickyError; if (chan == NULL) { return TCL_OK; } /* * Perform special handling for standard channels being closed. If the * refCount is now 1 it means that the last reference to the standard * channel is being explicitly closed, so bump the refCount down * artificially to 0. This will ensure that the channel is actually * closed, below. Also set the static pointer to NULL for the channel. */ CheckForStdChannelsBeingClosed(chan); /* * This operation should occur at the top of a channel stack. */ chanPtr = (Channel *) chan; statePtr = chanPtr->state; chanPtr = statePtr->topChanPtr; if (statePtr->refCount > 0) { Tcl_Panic("called Tcl_Close on channel with refCount > 0"); } if (GotFlag(statePtr, CHANNEL_INCLOSE)) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "illegal recursive call to close through close-handler" " of channel", -1)); } return TCL_ERROR; } SetFlag(statePtr, CHANNEL_INCLOSE); /* * When the channel has an escape sequence driven encoding such as * iso2022, the terminated escape sequence must write to the buffer. */ stickyError = 0; if (GotFlag(statePtr, TCL_WRITABLE) && (statePtr->encoding != GetBinaryEncoding()) && !(statePtr->outputEncodingFlags & TCL_ENCODING_START)) { int code = CheckChannelErrors(statePtr, TCL_WRITABLE); if (code == 0) { statePtr->outputEncodingFlags |= TCL_ENCODING_END; code = WriteChars(chanPtr, "", 0); statePtr->outputEncodingFlags &= ~TCL_ENCODING_END; statePtr->outputEncodingFlags |= TCL_ENCODING_START; } if (code < 0) { stickyError = Tcl_GetErrno(); } /* * TIP #219, Tcl Channel Reflection API. * Move an error message found in the channel bypass into the * interpreter bypass. Just clear it if there is no interpreter. */ if (statePtr->chanMsg != NULL) { if (interp != NULL) { Tcl_SetChannelErrorInterp(interp, statePtr->chanMsg); } TclDecrRefCount(statePtr->chanMsg); statePtr->chanMsg = NULL; } } Tcl_ClearChannelHandlers(chan); /* * Invoke the registered close callbacks and delete their records. */ while (statePtr->closeCbPtr != NULL) { cbPtr = statePtr->closeCbPtr; statePtr->closeCbPtr = cbPtr->nextPtr; cbPtr->proc(cbPtr->clientData); Tcl_Free(cbPtr); } ResetFlag(statePtr, CHANNEL_INCLOSE); /* * If this channel supports it, close the read side, since we don't need * it anymore and this will help avoid deadlocks on some channel types. */ result = chanPtr->typePtr->close2Proc(chanPtr->instanceData, interp, TCL_CLOSE_READ); if ((result == EINVAL) || result == ENOTCONN) { result = 0; } /* * The call to FlushChannel will flush any queued output and invoke the * close function of the channel driver, or it will set up the channel to * be flushed and closed asynchronously. */ SetFlag(statePtr, CHANNEL_CLOSED); flushcode = FlushChannel(interp, chanPtr, 0); /* * TIP #219. * Capture error messages put by the driver into the bypass area and put * them into the regular interpreter result. * * Notes: Due to the assertion of CHANNEL_CLOSED in the flags * FlushChannel() has called CloseChannel() and thus freed all the channel * structures. We must not try to access "chan" anymore, hence the NULL * argument in the call below. The only place which may still contain a * message is the interpreter itself, and "CloseChannel" made sure to lift * any channel message it generated into it. */ if (TclChanCaughtErrorBypass(interp, NULL)) { result = EINVAL; } if (stickyError != 0) { Tcl_SetErrno(stickyError); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_PosixError(interp), -1)); } return TCL_ERROR; } /* * Bug 97069ea11a: set error message if a flush code is set and no error * message set up to now. */ if (flushcode != 0) { /* flushcode has precedence, if available */ result = flushcode; } if ((result != 0) && (result != TCL_ERROR) && (interp != NULL) && 0 == Tcl_GetCharLength(Tcl_GetObjResult(interp))) { Tcl_SetErrno(result); Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_PosixError(interp), -1)); } if (result != 0) { return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_CloseEx -- * * Closes one side of a channel, read or write, close all. * * Results: * A standard Tcl result. * * Side effects: * Closes one direction of the channel, or do a full close. * * NOTE: * Tcl_CloseEx closes the specified direction of the channel as far as * the user is concerned. If flags = 0, this is equivalent to Tcl_Close. * *---------------------------------------------------------------------- */ int Tcl_CloseEx( Tcl_Interp *interp, /* Interpreter for errors. */ Tcl_Channel chan, /* The channel being closed. May still be used * by some interpreter. */ int flags) /* Flags telling us which side to close. */ { Channel *chanPtr; /* The real IO channel. */ ChannelState *statePtr; /* State of real IO channel. */ if (chan == NULL) { return TCL_OK; } chanPtr = (Channel *) chan; statePtr = chanPtr->state; if ((flags & (TCL_READABLE | TCL_WRITABLE)) == 0) { return TclClose(interp, chan); } if ((flags & (TCL_READABLE | TCL_WRITABLE)) == (TCL_READABLE | TCL_WRITABLE)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "double-close of channels not supported by %ss", chanPtr->typePtr->typeName)); return TCL_ERROR; } /* * Does the channel support half-close anyway? Error if not. */ if (!chanPtr->typePtr->close2Proc) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "half-close of channels not supported by %ss", chanPtr->typePtr->typeName)); return TCL_ERROR; } /* * Is the channel unstacked ? If not we fail. */ if (chanPtr != statePtr->topChanPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "half-close not applicable to stack of transformations", -1)); return TCL_ERROR; } /* * Check direction against channel mode. It is an error if we try to close * a direction not supported by the channel (already closed, or never * opened for that direction). */ if (!(GotFlag(statePtr, TCL_READABLE|TCL_WRITABLE) & flags)) { const char *msg; if (flags & TCL_CLOSE_READ) { msg = "read"; } else { msg = "write"; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Half-close of %s-side not possible, side not opened or" " already closed", msg)); return TCL_ERROR; } /* * A user may try to call half-close from within a channel close handler. * That won't do. */ if (GotFlag(statePtr, CHANNEL_INCLOSE)) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "illegal recursive call to close through close-handler" " of channel", -1)); } return TCL_ERROR; } if (flags & TCL_CLOSE_READ) { /* * Call the finalization code directly. There are no events to handle, * there cannot be for the read-side. */ return CloseChannelPart(interp, chanPtr, 0, flags); } else if (flags & TCL_CLOSE_WRITE) { Tcl_Preserve(statePtr); if (!GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { /* * We don't want to re-enter CloseWrite(). */ if (!GotFlag(statePtr, CHANNEL_CLOSEDWRITE)) { if (CloseWrite(interp, chanPtr) != TCL_OK) { SetFlag(statePtr, CHANNEL_CLOSEDWRITE); Tcl_Release(statePtr); return TCL_ERROR; } } } SetFlag(statePtr, CHANNEL_CLOSEDWRITE); Tcl_Release(statePtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * CloseWrite -- * * Closes the write side a channel. * * Results: * A standard Tcl result. * * Side effects: * Closes the write side of the channel. * * NOTE: * CloseWrite removes the channel as far as the user is concerned. * However, the output data structures may continue to exist for a while * longer if it has a background flush scheduled. The device itself is * eventually closed and the channel structures modified, in * CloseChannelPart, below. * *---------------------------------------------------------------------- */ static int CloseWrite( Tcl_Interp *interp, /* Interpreter for errors. */ Channel *chanPtr) /* The channel whose write side is being * closed. May still be used by some * interpreter */ { /* * Notes: clear-channel-handlers - write side only ? or keep around, just * not called. * * No close callbacks are run - channel is still open (read side) */ ChannelState *statePtr = chanPtr->state; /* State of real IO channel. */ int flushcode; int result = 0; /* * The call to FlushChannel will flush any queued output and invoke the * close function of the channel driver, or it will set up the channel to * be flushed and closed asynchronously. */ SetFlag(statePtr, CHANNEL_CLOSEDWRITE); flushcode = FlushChannel(interp, chanPtr, 0); /* * TIP #219. * Capture error messages put by the driver into the bypass area and put * them into the regular interpreter result. * * Notes: Due to the assertion of CHANNEL_CLOSEDWRITE in the flags * FlushChannel() has called CloseChannelPart(). While we can still access * "chan" (no structures were freed), the only place which may still * contain a message is the interpreter itself, and "CloseChannelPart" * made sure to lift any channel message it generated into it. Hence the * NULL argument in the call below. */ if (TclChanCaughtErrorBypass(interp, NULL)) { result = EINVAL; } if ((flushcode != 0) || (result != 0)) { return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * CloseChannelPart -- * * Utility procedure to close a channel partially and free associated * resources. If the channel was stacked it will never be run (The higher * level forbid this). If the channel was not stacked, then we will free * all the bits of the chosen side (read, or write) for the TOP channel. * * Results: * Error code from an unreported error or the driver close2 operation. * * Side effects: * May free memory, may change the value of errno. * *---------------------------------------------------------------------- */ static int CloseChannelPart( Tcl_Interp *interp, /* Interpreter for errors. */ Channel *chanPtr, /* The channel being closed. May still be used * by some interpreter. */ int errorCode, /* Status of operation so far. */ int flags) /* Flags telling us which side to close. */ { ChannelState *statePtr; /* State of real IO channel. */ int result; /* Of calling the close2proc. */ statePtr = chanPtr->state; if (flags & TCL_CLOSE_READ) { /* * No more input can be consumed so discard any leftover input. */ DiscardInputQueued(statePtr, 1); } else if (flags & TCL_CLOSE_WRITE) { /* * The caller guarantees that there are no more buffers queued for * output. */ if (statePtr->outQueueHead != NULL) { Tcl_Panic("ClosechanHalf, closed write-side of channel: " "queued output left"); } /* * TIP #219, Tcl Channel Reflection API. * Move a leftover error message in the channel bypass into the * interpreter bypass. Just clear it if there is no interpreter. */ if (statePtr->chanMsg != NULL) { if (interp != NULL) { Tcl_SetChannelErrorInterp(interp, statePtr->chanMsg); } TclDecrRefCount(statePtr->chanMsg); statePtr->chanMsg = NULL; } } /* * Finally do what is asked of us. Close and free the channel driver state * for the chosen side of the channel. This may leave a TIP #219 error * message in the interp. */ result = chanPtr->typePtr->close2Proc(chanPtr->instanceData, NULL, flags); /* * If we are being called synchronously, report either any latent error on * the channel or the current error. */ if (statePtr->unreportedError != 0) { errorCode = statePtr->unreportedError; /* * TIP #219, Tcl Channel Reflection API. * Move an error message found in the unreported area into the regular * bypass (interp). This kills any message in the channel bypass area. */ if (statePtr->chanMsg != NULL) { TclDecrRefCount(statePtr->chanMsg); statePtr->chanMsg = NULL; } if (interp) { Tcl_SetChannelErrorInterp(interp, statePtr->unreportedMsg); } } if (errorCode == 0) { errorCode = result; if (errorCode != 0) { Tcl_SetErrno(errorCode); } } /* * TIP #219. * Capture error messages put by the driver into the bypass area and put * them into the regular interpreter result. See also the bottom of * CloseWrite(). */ if (TclChanCaughtErrorBypass(interp, (Tcl_Channel) chanPtr)) { result = EINVAL; } if (result != 0) { return TCL_ERROR; } /* * Remove the closed side from the channel mode/flags. */ ResetFlag(statePtr, flags & (TCL_READABLE | TCL_WRITABLE)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ClearChannelHandlers -- * * Removes all channel handlers and event scripts from the channel, * cancels all background copies involving the channel and any interest * in events. * * Results: * None. * * Side effects: * See above. Deallocates memory. * *---------------------------------------------------------------------- */ void Tcl_ClearChannelHandlers( Tcl_Channel channel) { ChannelHandler *chPtr, *chNext; /* Iterate over channel handlers. */ EventScriptRecord *ePtr, *eNextPtr; /* Iterate over eventscript records. */ Channel *chanPtr; /* The real IO channel. */ ChannelState *statePtr; /* State of real IO channel. */ ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); NextChannelHandler *nhPtr; /* * This operation should occur at the top of a channel stack. */ chanPtr = (Channel *) channel; statePtr = chanPtr->state; chanPtr = statePtr->topChanPtr; /* * Cancel any outstanding timer. */ DeleteTimerHandler(statePtr); /* * Remove any references to channel handlers for this channel that may be * about to be invoked. */ for (nhPtr = tsdPtr->nestedHandlerPtr; nhPtr != NULL; nhPtr = nhPtr->nestedHandlerPtr) { if (nhPtr->nextHandlerPtr && (nhPtr->nextHandlerPtr->chanPtr == chanPtr)) { nhPtr->nextHandlerPtr = NULL; } } /* * Remove all the channel handler records attached to the channel itself. */ for (chPtr = statePtr->chPtr; chPtr != NULL; chPtr = chNext) { chNext = chPtr->nextPtr; Tcl_Free(chPtr); } statePtr->chPtr = NULL; /* * Cancel any pending copy operation. */ if (statePtr->csPtrR) { StopCopy(statePtr->csPtrR); statePtr->csPtrR = NULL; } if (statePtr->csPtrW) { StopCopy(statePtr->csPtrW); statePtr->csPtrW = NULL; } /* * Must set the interest mask now to 0, otherwise infinite loops will * occur if Tcl_DoOneEvent is called before the channel is finally deleted * in FlushChannel. This can happen if the channel has a background flush * active. */ statePtr->interestMask = 0; /* * Remove any EventScript records for this channel. */ for (ePtr = statePtr->scriptRecordPtr; ePtr != NULL; ePtr = eNextPtr) { eNextPtr = ePtr->nextPtr; TclDecrRefCount(ePtr->scriptPtr); Tcl_Free(ePtr); } statePtr->scriptRecordPtr = NULL; } /* *---------------------------------------------------------------------- * * Tcl_Write -- * * Puts a sequence of bytes into an output buffer, may queue the buffer * for output if it gets full, and also remembers whether the current * buffer is ready e.g. if it contains a newline and we are in line * buffering mode. Compensates stacking, i.e. will redirect the data from * the specified channel to the topmost channel in a stack. * * No encoding conversions are applied to the bytes being read. * * Results: * The number of bytes written or TCL_INDEX_NONE in case of error. If * TCL_INDEX_NONE, Tcl_GetErrno will return the error code. * * Side effects: * May buffer up output and may cause output to be produced on the * channel. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_Write( Tcl_Channel chan, /* The channel to buffer output for. */ const char *src, /* Data to queue in output buffer. */ Tcl_Size srcLen) /* Length of data in bytes, or TCL_INDEX_NONE for * strlen(). */ { /* * Always use the topmost channel of the stack */ Channel *chanPtr; ChannelState *statePtr; /* State info for channel */ statePtr = ((Channel *) chan)->state; chanPtr = statePtr->topChanPtr; if (CheckChannelErrors(statePtr, TCL_WRITABLE) != 0) { return TCL_INDEX_NONE; } if (srcLen == TCL_INDEX_NONE) { srcLen = strlen(src); } if (WriteBytes(chanPtr, src, srcLen) == -1) { return TCL_INDEX_NONE; } return srcLen; } /* *---------------------------------------------------------------------- * * Tcl_WriteRaw -- * * Puts a sequence of bytes into an output buffer, may queue the buffer * for output if it gets full, and also remembers whether the current * buffer is ready e.g. if it contains a newline and we are in line * buffering mode. Writes directly to the driver of the channel, does not * compensate for stacking. * * No encoding conversions are applied to the bytes being read. * * Results: * The number of bytes written or TCL_INDEX_NONE in case of error. If * TCL_INDEX_NONE, Tcl_GetErrno will return the error code. * * Side effects: * May buffer up output and may cause output to be produced on the * channel. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_WriteRaw( Tcl_Channel chan, /* The channel to buffer output for. */ const char *src, /* Data to queue in output buffer. */ Tcl_Size srcLen) /* Length of data in bytes, or TCL_INDEX_NONE for * strlen(). */ { Channel *chanPtr = ((Channel *) chan); ChannelState *statePtr = chanPtr->state; /* State info for channel */ int errorCode; Tcl_Size written; if (CheckChannelErrors(statePtr, TCL_WRITABLE | CHANNEL_RAW_MODE) != 0) { return TCL_INDEX_NONE; } if (srcLen == TCL_INDEX_NONE) { srcLen = strlen(src); } /* * Go immediately to the driver, do all the error handling by ourselves. * The code was stolen from 'FlushChannel'. */ written = ChanWrite(chanPtr, src, srcLen, &errorCode); if (written == TCL_INDEX_NONE) { Tcl_SetErrno(errorCode); } return written; } /* *--------------------------------------------------------------------------- * * Tcl_WriteChars -- * * Takes a sequence of UTF-8 characters and converts them for output * using the channel's current encoding, may queue the buffer for output * if it gets full, and also remembers whether the current buffer is * ready e.g. if it contains a newline and we are in line buffering * mode. Compensates stacking, i.e. will redirect the data from the * specified channel to the topmost channel in a stack. * * Results: * The number of bytes written or TCL_INDEX_NONE in case of error. If * TCL_INDEX_NONE, Tcl_GetErrno will return the error code. * * Side effects: * May buffer up output and may cause output to be produced on the * channel. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_WriteChars( Tcl_Channel chan, /* The channel to buffer output for. */ const char *src, /* UTF-8 characters to queue in output * buffer. */ Tcl_Size len) /* Length of string in bytes, or TCL_INDEX_NONE for * strlen(). */ { Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; /* State info for channel */ Tcl_Size result; Tcl_Obj *objPtr; if (CheckChannelErrors(statePtr, TCL_WRITABLE) != 0) { return TCL_INDEX_NONE; } chanPtr = statePtr->topChanPtr; if (len == TCL_INDEX_NONE) { len = strlen(src); } if (statePtr->encoding) { return WriteChars(chanPtr, src, len); } /* * Inefficient way to convert UTF-8 to byte-array, but the code * parallels the way it is done for objects. Special case for 1-byte * (used by e.g. [puts] for the \n) could be extended to more efficient * translation of the src string. */ if ((len == 1) && (UCHAR(*src) < 0xC0)) { return WriteBytes(chanPtr, src, len); } objPtr = Tcl_NewStringObj(src, len); src = (char *) Tcl_GetBytesFromObj(NULL, objPtr, &len); if (src == NULL) { Tcl_SetErrno(EILSEQ); result = TCL_INDEX_NONE; } else { result = WriteBytes(chanPtr, src, len); } TclDecrRefCount(objPtr); return result; } /* *--------------------------------------------------------------------------- * * Tcl_WriteObj -- * * Takes the Tcl object and queues its contents for output. If the * encoding of the channel is NULL, takes the byte-array representation * of the object and queues those bytes for output. Otherwise, takes the * characters in the UTF-8 (string) representation of the object and * converts them for output using the channel's current encoding. May * flush internal buffers to output if one becomes full or is ready for * some other reason, e.g. if it contains a newline and the channel is in * line buffering mode. * * Results: * The number of bytes written or TCL_INDEX_NONE in case of error. If * TCL_INDEX_NONE, Tcl_GetErrno() will return the error code. * * Side effects: * May buffer up output and may cause output to be produced on the * channel. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_WriteObj( Tcl_Channel chan, /* The channel to buffer output for. */ Tcl_Obj *objPtr) /* The object to write. */ { /* * Always use the topmost channel of the stack */ Channel *chanPtr; ChannelState *statePtr; /* State info for channel */ const char *src; Tcl_Size srcLen = 0; statePtr = ((Channel *) chan)->state; chanPtr = statePtr->topChanPtr; if (CheckChannelErrors(statePtr, TCL_WRITABLE) != 0) { return TCL_INDEX_NONE; } if (statePtr->encoding == NULL) { Tcl_Size result; src = (char *) Tcl_GetBytesFromObj(NULL, objPtr, &srcLen); if (src == NULL) { Tcl_SetErrno(EILSEQ); result = TCL_INDEX_NONE; } else { result = WriteBytes(chanPtr, src, srcLen); } return result; } else { src = TclGetStringFromObj(objPtr, &srcLen); return WriteChars(chanPtr, src, srcLen); } } static void WillWrite( Channel *chanPtr) { int inputBuffered; if ((Tcl_ChannelWideSeekProc(chanPtr->typePtr) != NULL) && ((inputBuffered = Tcl_InputBuffered((Tcl_Channel) chanPtr)) > 0)){ int ignore; DiscardInputQueued(chanPtr->state, 0); ChanSeek(chanPtr, -inputBuffered, SEEK_CUR, &ignore); } } static int WillRead( Channel *chanPtr) { if (chanPtr->typePtr == NULL) { /* * Prevent read attempts on a closed channel. */ DiscardInputQueued(chanPtr->state, 0); Tcl_SetErrno(EINVAL); return -1; } if ((Tcl_ChannelWideSeekProc(chanPtr->typePtr) != NULL) && (Tcl_OutputBuffered((Tcl_Channel) chanPtr) > 0)) { /* * CAVEAT - The assumption here is that FlushChannel() will push out * the bytes of any writes that are in progress. Since this is a * seekable channel, we assume it is not one that can block and force * bg flushing. Channels we know that can do that - sockets, pipes - * are not seekable. If the assumption is wrong, more drastic measures * may be required here like temporarily setting the channel into * blocking mode. */ if (FlushChannel(NULL, chanPtr, 0) != 0) { return -1; } } return 0; } /* *---------------------------------------------------------------------- * * Write -- * * Convert srcLen bytes starting at src according to encoding and write * produced bytes into an output buffer, may queue the buffer for output * if it gets full, and also remembers whether the current buffer is * ready e.g. if it contains a newline and we are in line buffering mode. * * Results: * The number of bytes written or TCL_INDEX_NONE in case of error. If TCL_INDEX_NONE, * Tcl_GetErrno will return the error code. * * Side effects: * May buffer up output and may cause output to be produced on the * channel. * *---------------------------------------------------------------------- */ static Tcl_Size Write( Channel *chanPtr, /* The channel to buffer output for. */ const char *src, /* UTF-8 string to write. */ Tcl_Size srcLen, /* Length of UTF-8 string in bytes. */ Tcl_Encoding encoding) { ChannelState *statePtr = chanPtr->state; /* State info for channel */ char *nextNewLine = NULL; int endEncoding, needNlFlush = 0; Tcl_Size saved = 0, total = 0, flushed = 0; char safe[BUFFER_PADDING]; int encodingError = 0; if (srcLen) { WillWrite(chanPtr); } /* * Write the terminated escape sequence even if srcLen is 0. */ endEncoding = ((statePtr->outputEncodingFlags & TCL_ENCODING_END) != 0); if (GotFlag(statePtr, CHANNEL_LINEBUFFERED) || (statePtr->outputTranslation != TCL_TRANSLATE_LF)) { nextNewLine = (char *)memchr(src, '\n', srcLen); } while (srcLen + saved + endEncoding > 0 && !encodingError) { ChannelBuffer *bufPtr; char *dst; int result, srcRead, dstLen, dstWrote; Tcl_Size srcLimit = srcLen; if (nextNewLine) { srcLimit = nextNewLine - src; } /* Get space to write into */ bufPtr = statePtr->curOutPtr; if (bufPtr == NULL) { bufPtr = AllocChannelBuffer(statePtr->bufSize); statePtr->curOutPtr = bufPtr; } if (saved) { /* * Here's some translated bytes left over from the last buffer * that we need to stick at the beginning of this buffer. */ memcpy(InsertPoint(bufPtr), safe, saved); bufPtr->nextAdded += saved; saved = 0; } dst = InsertPoint(bufPtr); dstLen = SpaceLeft(bufPtr); result = Tcl_UtfToExternal(NULL, encoding, src, srcLimit, statePtr->outputEncodingFlags, &statePtr->outputEncodingState, dst, dstLen + BUFFER_PADDING, &srcRead, &dstWrote, NULL); /* * See chan-io-1.[89]. Tcl Bug 506297. */ statePtr->outputEncodingFlags &= ~TCL_ENCODING_START; /* * See io-75.2, TCL bug 6978c01b65. * Check, if an encoding error occured and should be reported to the * script level. * This happens, if a written character may not be represented by the * current output encoding and strict encoding is active. */ if ((result == TCL_CONVERT_UNKNOWN || result == TCL_CONVERT_SYNTAX) || /* * We're reading from invalid/incomplete UTF-8. */ ((result != TCL_OK) && (srcRead + dstWrote == 0))) { encodingError = 1; result = TCL_OK; } bufPtr->nextAdded += dstWrote; src += srcRead; srcLen -= srcRead; total += dstWrote; dst += dstWrote; dstLen -= dstWrote; if (src == nextNewLine && dstLen > 0) { static char crln[3] = "\r\n"; char *nl = NULL; int nlLen = 0; switch (statePtr->outputTranslation) { case TCL_TRANSLATE_LF: nl = crln + 1; nlLen = 1; break; case TCL_TRANSLATE_CR: nl = crln; nlLen = 1; break; case TCL_TRANSLATE_CRLF: nl = crln; nlLen = 2; break; default: Tcl_Panic("unknown output translation requested"); break; } result |= Tcl_UtfToExternal(NULL, encoding, nl, nlLen, statePtr->outputEncodingFlags, &statePtr->outputEncodingState, dst, dstLen + BUFFER_PADDING, &srcRead, &dstWrote, NULL); assert(srcRead == nlLen); bufPtr->nextAdded += dstWrote; src++; srcLen--; total += dstWrote; dst += dstWrote; dstLen -= dstWrote; nextNewLine = (char *)memchr(src, '\n', srcLen); needNlFlush = 1; } if (IsBufferOverflowing(bufPtr)) { /* * When translating from UTF-8 to external encoding, we allowed * the translation to produce a character that crossed the end of * the output buffer, so that we would get a completely full * buffer before flushing it. The extra bytes will be moved to the * beginning of the next buffer. */ saved = -SpaceLeft(bufPtr); memcpy(safe, dst + dstLen, saved); bufPtr->nextAdded = bufPtr->bufLength; } if ((srcLen + saved == 0) && (result == TCL_OK)) { endEncoding = 0; } if (IsBufferFull(bufPtr)) { if (FlushChannel(NULL, chanPtr, 0) != 0) { return -1; } flushed += statePtr->bufSize; /* * We just flushed. So if we have needNlFlush set to record that * we need to flush because there is a (translated) newline in the * buffer, that's likely not true any more. But there is a tricky * exception. If we have saved bytes that did not really get * flushed and those bytes came from a translation of a newline as * the last thing taken from the src array, then needNlFlush needs * to remain set to flag that the next buffer still needs a * newline flush. */ if (needNlFlush && (saved == 0 || src[-1] != '\n')) { needNlFlush = 0; } } } if (((flushed < total) && GotFlag(statePtr, CHANNEL_UNBUFFERED)) || (needNlFlush && GotFlag(statePtr, CHANNEL_LINEBUFFERED))) { if (FlushChannel(NULL, chanPtr, 0) != 0) { return -1; } } UpdateInterest(chanPtr); if (encodingError) { Tcl_SetErrno(EILSEQ); return -1; } return total; } /* *--------------------------------------------------------------------------- * * Tcl_Gets -- * * Reads a complete line of input from the channel into a Tcl_DString. * * Results: * Length of line read (in characters) or TCL_INDEX_NONE if error, EOF, or blocked. * If TCL_INDEX_NONE, use Tcl_GetErrno() to retrieve the POSIX error code for the * error or condition that occurred. * * Side effects: * May flush output on the channel. May cause input to be consumed from * the channel. * *--------------------------------------------------------------------------- */ Tcl_Size Tcl_Gets( Tcl_Channel chan, /* Channel from which to read. */ Tcl_DString *lineRead) /* The line read will be appended to this * DString as UTF-8 characters. The caller * must have initialized it and is responsible * for managing the storage. */ { Tcl_Obj *objPtr; Tcl_Size charsStored; TclNewObj(objPtr); charsStored = Tcl_GetsObj(chan, objPtr); if (charsStored > 0) { TclDStringAppendObj(lineRead, objPtr); } TclDecrRefCount(objPtr); return charsStored; } /* *--------------------------------------------------------------------------- * * Tcl_GetsObj -- * * Accumulate input from the input channel until end-of-line or * end-of-file has been seen. Bytes read from the input channel are * converted to UTF-8 using the encoding specified by the channel. * * Results: * Number of characters accumulated in the object or TCL_INDEX_NONE if error, * blocked, or EOF. If TCL_INDEX_NONE, use Tcl_GetErrno() to retrieve the POSIX error * code for the error or condition that occurred. * * Side effects: * Consumes input from the channel. * * On reading EOF, leave channel pointing at EOF char. On reading EOL, * leave channel pointing after EOL, but don't return EOL in dst buffer. * *--------------------------------------------------------------------------- */ Tcl_Size Tcl_GetsObj( Tcl_Channel chan, /* Channel from which to read. */ Tcl_Obj *objPtr) /* The line read will be appended to this * object as UTF-8 characters. */ { GetsState gs; Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelBuffer *bufPtr; int inEofChar, skip, copiedTotal, oldFlags, oldRemoved; Tcl_Size oldLength; Tcl_Encoding encoding; char *dst, *dstEnd, *eol, *eof; Tcl_EncodingState oldState; if (GotFlag(statePtr, CHANNEL_ENCODING_ERROR)) { UpdateInterest(chanPtr); ResetFlag(statePtr, CHANNEL_EOF|CHANNEL_ENCODING_ERROR); Tcl_SetErrno(EILSEQ); return TCL_INDEX_NONE; } if (CheckChannelErrors(statePtr, TCL_READABLE) != 0) { return TCL_INDEX_NONE; } /* * If we're sitting ready to read the eofchar, there's no need to * do it. */ if (GotFlag(statePtr, CHANNEL_STICKY_EOF)) { SetFlag(statePtr, CHANNEL_EOF); assert(statePtr->inputEncodingFlags & TCL_ENCODING_END); assert(!GotFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR)); /* TODO: Do we need this? */ UpdateInterest(chanPtr); return TCL_INDEX_NONE; } /* * A binary version of Tcl_GetsObj. This could also handle encodings that * are ascii-7 pure (iso8859, utf-8, ...) with a final encoding conversion * done on objPtr. */ if (statePtr->encoding == GetBinaryEncoding() && ((statePtr->inputTranslation == TCL_TRANSLATE_LF) || (statePtr->inputTranslation == TCL_TRANSLATE_CR)) && Tcl_GetBytesFromObj(NULL, objPtr, (Tcl_Size *)NULL) != NULL) { return TclGetsObjBinary(chan, objPtr); } /* * This operation should occur at the top of a channel stack. */ chanPtr = statePtr->topChanPtr; TclChannelPreserve((Tcl_Channel)chanPtr); bufPtr = statePtr->inQueueHead; encoding = statePtr->encoding; /* * Preserved so we can restore the channel's state in case we don't find a * newline in the available input. */ (void)TclGetStringFromObj(objPtr, &oldLength); oldFlags = statePtr->inputEncodingFlags; oldState = statePtr->inputEncodingState; oldRemoved = BUFFER_PADDING; if (bufPtr != NULL) { oldRemoved = bufPtr->nextRemoved; } /* * Object used by FilterInputBytes to keep track of how much data has been * consumed from the channel buffers. */ gs.objPtr = objPtr; gs.dstPtr = &dst; gs.encoding = encoding; gs.bufPtr = bufPtr; gs.state = oldState; gs.rawRead = 0; gs.bytesWrote = 0; gs.charsWrote = 0; gs.totalChars = 0; dst = objPtr->bytes + oldLength; dstEnd = dst; skip = 0; eof = NULL; inEofChar = statePtr->inEofChar; ResetFlag(statePtr, CHANNEL_BLOCKED); while (1) { if (dst >= dstEnd) { /* * In case of encoding errors, state gets flag * CHANNEL_ENCODING_ERROR set in the call below. First, the * EOF/EOL condition is checked, as we may have valid data with * EOF/EOL before the encoding error. */ if (FilterInputBytes(chanPtr, &gs) != 0) { goto restore; } dstEnd = dst + gs.bytesWrote; } /* * Remember if EOF char is seen, then look for EOL anyhow, because the * EOL might be before the EOF char. */ if (inEofChar != '\0') { for (eol = dst; eol < dstEnd; eol++) { if (*eol == inEofChar) { dstEnd = eol; eof = eol; break; } } } /* * On EOL, leave current file position pointing after the EOL, but * don't store the EOL in the output string. */ switch (statePtr->inputTranslation) { case TCL_TRANSLATE_LF: for (eol = dst; eol < dstEnd; eol++) { if (*eol == '\n') { skip = 1; goto gotEOL; } } break; case TCL_TRANSLATE_CR: for (eol = dst; eol < dstEnd; eol++) { if (*eol == '\r') { skip = 1; goto gotEOL; } } break; case TCL_TRANSLATE_CRLF: for (eol = dst; eol < dstEnd; eol++) { if (*eol == '\r') { eol++; /* * If a CR is at the end of the buffer, then check for a * LF at the beginning of the next buffer, unless EOF char * was found already. */ if (eol >= dstEnd) { Tcl_Size offset; if (eol != eof) { offset = eol - objPtr->bytes; dst = dstEnd; if (FilterInputBytes(chanPtr, &gs) != 0) { goto restore; } dstEnd = dst + gs.bytesWrote; eol = objPtr->bytes + offset; } if (eol >= dstEnd) { skip = 0; goto gotEOL; } } if (*eol == '\n') { eol--; skip = 2; goto gotEOL; } } } break; case TCL_TRANSLATE_AUTO: eol = dst; skip = 1; if (GotFlag(statePtr, INPUT_SAW_CR)) { if ((eol < dstEnd) && (*eol == '\n')) { /* * Skip the raw bytes that make up the '\n'. */ int rawRead; char tmp[TCL_UTF_MAX]; bufPtr = gs.bufPtr; Tcl_ExternalToUtf(NULL, gs.encoding, RemovePoint(bufPtr), gs.rawRead, statePtr->inputEncodingFlags | TCL_ENCODING_NO_TERMINATE, &gs.state, tmp, sizeof(tmp), &rawRead, NULL, NULL); bufPtr->nextRemoved += rawRead; gs.rawRead -= rawRead; gs.bytesWrote--; gs.charsWrote--; memmove(dst, dst + 1, dstEnd - dst); dstEnd--; } } for (eol = dst; eol < dstEnd; eol++) { if (*eol == '\r') { eol++; if (eol == dstEnd) { /* * If buffer ended on \r, peek ahead to see if a \n is * available, unless EOF char was found already. */ if (eol != eof) { int offset; offset = eol - objPtr->bytes; dst = dstEnd; PeekAhead(chanPtr, &dstEnd, &gs); eol = objPtr->bytes + offset; } if (eol >= dstEnd) { eol--; SetFlag(statePtr, INPUT_SAW_CR); goto gotEOL; } } if (*eol == '\n') { skip++; } eol--; ResetFlag(statePtr, INPUT_SAW_CR); goto gotEOL; } else if (*eol == '\n') { ResetFlag(statePtr, INPUT_SAW_CR); goto gotEOL; } } } if (eof != NULL) { /* * EOF character was seen. On EOF, leave current file position * pointing at the EOF character, but don't store the EOF * character in the output string. */ dstEnd = eof; SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; ResetFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR); } if (GotFlag(statePtr, CHANNEL_EOF)) { skip = 0; eol = dstEnd; if (eol == objPtr->bytes + oldLength) { /* * If we didn't append any bytes before encountering EOF, * caller needs to see -1. */ Tcl_SetObjLength(objPtr, oldLength); CommonGetsCleanup(chanPtr); copiedTotal = -1; ResetFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR); goto done; } goto gotEOL; } else if (gs.bytesWrote == 0 && GotFlag(statePtr, CHANNEL_ENCODING_ERROR)) { /* Ticket c4eb46a1 Harald Oehlmann 2023-11-12 debugging session. * In non blocking mode we loop indifenitly on a decoding error in * this while-loop. * Removed the following from the upper condition: * "&& !GotFlag(statePtr, CHANNEL_NONBLOCKING)" * In case of an encoding error with leading correct bytes, we pass here * two times, as gs.bytesWrote is not 0 on the first pass. This feels * once to much, as the data is anyway not used. */ /* Set eol to the position that caused the encoding error, and then * continue to gotEOL, which stores the data that was decoded * without error to objPtr. This allows the caller to do something * useful with the data decoded so far, and also results in the * position of the file being the first byte that was not * successfully decoded, allowing further processing at exactly that * point, if desired. */ eol = dstEnd; goto gotEOL; } dst = dstEnd; } /* * Found EOL or EOF, but the output buffer may now contain too many UTF-8 * characters. We need to know how many raw bytes correspond to the number * of UTF-8 characters we want, plus how many raw bytes correspond to the * character(s) making up EOL (if any), so we can remove the correct * number of bytes from the channel buffer. */ gotEOL: /* * Regenerate the top channel, in case it was changed due to * self-modifying reflected transforms. */ if (chanPtr != statePtr->topChanPtr) { TclChannelRelease((Tcl_Channel)chanPtr); chanPtr = statePtr->topChanPtr; TclChannelPreserve((Tcl_Channel)chanPtr); } bufPtr = gs.bufPtr; if (bufPtr == NULL) { Tcl_Panic("Tcl_GetsObj: gotEOL reached with bufPtr==NULL"); } statePtr->inputEncodingState = gs.state; Tcl_ExternalToUtf(NULL, gs.encoding, RemovePoint(bufPtr), gs.rawRead, statePtr->inputEncodingFlags | TCL_ENCODING_NO_TERMINATE, &statePtr->inputEncodingState, dst, eol - dst + skip + TCL_UTF_MAX - 1, &gs.rawRead, NULL, &gs.charsWrote); bufPtr->nextRemoved += gs.rawRead; /* * Recycle all the emptied buffers. */ Tcl_SetObjLength(objPtr, eol - objPtr->bytes); CommonGetsCleanup(chanPtr); ResetFlag(statePtr, CHANNEL_BLOCKED); copiedTotal = gs.totalChars + gs.charsWrote - skip; goto done; /* * Couldn't get a complete line. This only happens if we get a error * reading from the channel or we are non-blocking and there wasn't an EOL * or EOF in the data available. */ restore: /* * Regenerate the top channel, in case it was changed due to * self-modifying reflected transforms. */ if (chanPtr != statePtr->topChanPtr) { TclChannelRelease((Tcl_Channel)chanPtr); chanPtr = statePtr->topChanPtr; TclChannelPreserve((Tcl_Channel)chanPtr); } bufPtr = statePtr->inQueueHead; if (bufPtr != NULL) { bufPtr->nextRemoved = oldRemoved; bufPtr = bufPtr->nextPtr; } for ( ; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { bufPtr->nextRemoved = BUFFER_PADDING; } CommonGetsCleanup(chanPtr); statePtr->inputEncodingState = oldState; statePtr->inputEncodingFlags = oldFlags; Tcl_SetObjLength(objPtr, oldLength); /* * We didn't get a complete line so we need to indicate to UpdateInterest * that the gets blocked. It will wait for more data instead of firing a * timer, avoiding a busy wait. This is where we are assuming that the * next operation is a gets. No more file events will be delivered on this * channel until new data arrives or some operation is performed on the * channel (e.g. gets, read, fconfigure) that changes the blocking state. * Note that this means a file event will not be delivered even though a * read would be able to consume the buffered data. */ SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); copiedTotal = -1; /* * Update the notifier state so we don't block while there is still data * in the buffers. */ done: assert(!GotFlag(statePtr, CHANNEL_EOF) || GotFlag(statePtr, CHANNEL_STICKY_EOF|CHANNEL_ENCODING_ERROR) || Tcl_InputBuffered((Tcl_Channel)chanPtr) == 0); assert(!(GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED) == (CHANNEL_EOF|CHANNEL_BLOCKED))); /* * Regenerate the top channel, in case it was changed due to * self-modifying reflected transforms. */ if (chanPtr != statePtr->topChanPtr) { TclChannelRelease((Tcl_Channel)chanPtr); chanPtr = statePtr->topChanPtr; TclChannelPreserve((Tcl_Channel)chanPtr); } UpdateInterest(chanPtr); TclChannelRelease((Tcl_Channel)chanPtr); if (GotFlag(statePtr, CHANNEL_ENCODING_ERROR) && gs.bytesWrote == 0) { bufPtr->nextRemoved = oldRemoved; Tcl_SetErrno(EILSEQ); copiedTotal = -1; } ResetFlag(statePtr, CHANNEL_ENCODING_ERROR); return copiedTotal; } /* *--------------------------------------------------------------------------- * * TclGetsObjBinary -- * * A variation of Tcl_GetsObj that works directly on the buffers until * end-of-line or end-of-file has been seen. Bytes read from the input * channel return as a ByteArray obj. * * WARNING! The notion of "binary" used here is different from notions * of "binary" used in other places. In particular, this "binary" routine * may be called when an -eofchar is set on the channel. * * Results: * Number of characters accumulated in the object or TCL_INDEX_NONE if error, * blocked, or EOF. If TCL_INDEX_NONE, use Tcl_GetErrno() to retrieve the POSIX error * code for the error or condition that occurred. * * Side effects: * Consumes input from the channel. * * On reading EOF, leave channel pointing at EOF char. On reading EOL, * leave channel pointing after EOL, but don't return EOL in dst buffer. * *--------------------------------------------------------------------------- */ static int TclGetsObjBinary( Tcl_Channel chan, /* Channel from which to read. */ Tcl_Obj *objPtr) /* The line read will be appended to this * object as UTF-8 characters. */ { Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelBuffer *bufPtr; int inEofChar, skip, copiedTotal, oldFlags, oldRemoved; Tcl_Size rawLen, byteLen = 0, oldLength; int eolChar; unsigned char *dst, *dstEnd, *eol, *eof, *byteArray; /* * This operation should occur at the top of a channel stack. */ chanPtr = statePtr->topChanPtr; TclChannelPreserve((Tcl_Channel)chanPtr); bufPtr = statePtr->inQueueHead; /* * Preserved so we can restore the channel's state in case we don't find a * newline in the available input. */ byteArray = Tcl_GetBytesFromObj(NULL, objPtr, &byteLen); if (byteArray == NULL) { Tcl_SetErrno(EILSEQ); return -1; } oldFlags = statePtr->inputEncodingFlags; oldRemoved = BUFFER_PADDING; oldLength = byteLen; if (bufPtr != NULL) { oldRemoved = bufPtr->nextRemoved; } rawLen = 0; skip = 0; eof = NULL; inEofChar = statePtr->inEofChar; /* * Only handle TCL_TRANSLATE_LF and TCL_TRANSLATE_CR. */ eolChar = (statePtr->inputTranslation == TCL_TRANSLATE_LF) ? '\n' : '\r'; ResetFlag(statePtr, CHANNEL_BLOCKED); while (1) { /* * Subtract the number of bytes that were removed from channel buffer * during last call. */ if (bufPtr != NULL) { bufPtr->nextRemoved += rawLen; if (!IsBufferReady(bufPtr)) { bufPtr = bufPtr->nextPtr; } } if ((bufPtr == NULL) || (bufPtr->nextAdded == BUFFER_PADDING)) { /* * All channel buffers were exhausted and the caller still hasn't * seen EOL. Need to read more bytes from the channel device. Side * effect is to allocate another channel buffer. */ if (GetInput(chanPtr) != 0) { goto restore; } bufPtr = statePtr->inQueueTail; if (bufPtr == NULL) { goto restore; } } else { /* * Incoming CHANNEL_STICKY_EOF is filtered out on entry. A new * CHANNEL_STICKY_EOF set in this routine leads to return before * coming back here. When we are not dealing with * CHANNEL_STICKY_EOF, a CHANNEL_EOF implies an empty buffer. * Here the buffer is non-empty so we know we're a non-EOF. */ assert(!GotFlag(statePtr, CHANNEL_STICKY_EOF)); assert(!GotFlag(statePtr, CHANNEL_EOF)); } dst = (unsigned char *) RemovePoint(bufPtr); dstEnd = dst + BytesLeft(bufPtr); /* * Remember if EOF char is seen, then look for EOL anyhow, because the * EOL might be before the EOF char. * XXX - in the binary case, consider coincident search for eol/eof. */ if (inEofChar != '\0') { for (eol = dst; eol < dstEnd; eol++) { if (*eol == inEofChar) { dstEnd = eol; eof = eol; break; } } } /* * On EOL, leave current file position pointing after the EOL, but * don't store the EOL in the output string. */ for (eol = dst; eol < dstEnd; eol++) { if (*eol == eolChar) { skip = 1; goto gotEOL; } } if (eof != NULL) { /* * EOF character was seen. On EOF, leave current file position * pointing at the EOF character, but don't store the EOF * character in the output string. */ SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; ResetFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR); } if (GotFlag(statePtr, CHANNEL_EOF)) { skip = 0; eol = dstEnd; if ((dst == dstEnd) && (byteLen == oldLength)) { /* * If we didn't append any bytes before encountering EOF, * caller needs to see TCL_INDEX_NONE. */ byteArray = Tcl_SetByteArrayLength(objPtr, oldLength); CommonGetsCleanup(chanPtr); copiedTotal = -1; ResetFlag(statePtr, CHANNEL_BLOCKED); goto done; } goto gotEOL; } if (GotFlag(statePtr, CHANNEL_BLOCKED|CHANNEL_NONBLOCKING) == (CHANNEL_BLOCKED|CHANNEL_NONBLOCKING)) { goto restore; } /* * Copy bytes from the channel buffer to the ByteArray. This may * realloc space, so keep track of result. */ rawLen = dstEnd - dst; byteArray = Tcl_SetByteArrayLength(objPtr, byteLen + rawLen); memcpy(byteArray + byteLen, dst, rawLen); byteLen += rawLen; } /* * Found EOL or EOF, but the output buffer may now contain too many bytes. * We need to know how many bytes correspond to the number we want, so we * can remove the correct number of bytes from the channel buffer. */ gotEOL: if (bufPtr == NULL) { Tcl_Panic("TclGetsObjBinary: gotEOL reached with bufPtr==NULL"); } rawLen = eol - dst; byteArray = Tcl_SetByteArrayLength(objPtr, byteLen + rawLen); memcpy(byteArray + byteLen, dst, rawLen); byteLen += rawLen; bufPtr->nextRemoved += rawLen + skip; /* * Convert the buffer if there was an encoding. * XXX - unimplemented. */ if (statePtr->encoding != GetBinaryEncoding()) { } /* * Recycle all the emptied buffers. */ CommonGetsCleanup(chanPtr); ResetFlag(statePtr, CHANNEL_BLOCKED); copiedTotal = byteLen; goto done; /* * Couldn't get a complete line. This only happens if we get a error * reading from the channel or we are non-blocking and there wasn't an EOL * or EOF in the data available. */ restore: bufPtr = statePtr->inQueueHead; if (bufPtr) { bufPtr->nextRemoved = oldRemoved; bufPtr = bufPtr->nextPtr; } for ( ; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { bufPtr->nextRemoved = BUFFER_PADDING; } CommonGetsCleanup(chanPtr); statePtr->inputEncodingFlags = oldFlags; byteArray = Tcl_SetByteArrayLength(objPtr, oldLength); /* * We didn't get a complete line so we need to indicate to UpdateInterest * that the gets blocked. It will wait for more data instead of firing a * timer, avoiding a busy wait. This is where we are assuming that the * next operation is a gets. No more file events will be delivered on this * channel until new data arrives or some operation is performed on the * channel (e.g. gets, read, fconfigure) that changes the blocking state. * Note that this means a file event will not be delivered even though a * read would be able to consume the buffered data. */ SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); copiedTotal = -1; /* * Update the notifier state so we don't block while there is still data * in the buffers. */ done: assert(!GotFlag(statePtr, CHANNEL_EOF) || GotFlag(statePtr, CHANNEL_STICKY_EOF) || Tcl_InputBuffered((Tcl_Channel)chanPtr) == 0); assert(!(GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED) == (CHANNEL_EOF|CHANNEL_BLOCKED))); UpdateInterest(chanPtr); TclChannelRelease((Tcl_Channel)chanPtr); return copiedTotal; } /* *--------------------------------------------------------------------------- * * FreeBinaryEncoding -- * * Frees any "iso8859-1" Tcl_Encoding created by [gets] on a binary * channel in a thread as part of that thread's finalization. * * Results: * None. * *--------------------------------------------------------------------------- */ static void FreeBinaryEncoding(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (tsdPtr->binaryEncoding != NULL) { Tcl_FreeEncoding(tsdPtr->binaryEncoding); tsdPtr->binaryEncoding = NULL; } } static Tcl_Encoding GetBinaryEncoding(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (tsdPtr->binaryEncoding == NULL) { tsdPtr->binaryEncoding = Tcl_GetEncoding(NULL, "iso8859-1"); } if (tsdPtr->binaryEncoding == NULL) { Tcl_Panic("binary encoding is not available"); } return tsdPtr->binaryEncoding; } /* *--------------------------------------------------------------------------- * * FilterInputBytes -- * * Helper function for Tcl_GetsObj. Produces UTF-8 characters from raw * bytes read from the channel. * * Consumes available bytes from channel buffers. When channel buffers * are exhausted, reads more bytes from channel device into a new channel * buffer. It is the caller's responsibility to free the channel buffers * that have been exhausted. * * Results: * The return value is -1 if there was an error reading from the channel, * 0 otherwise. * * Side effects: * Status object keeps track of how much data from channel buffers has * been consumed and where UTF-8 bytes should be stored. * *--------------------------------------------------------------------------- */ static int FilterInputBytes( Channel *chanPtr, /* Channel to read. */ GetsState *gsPtr) /* Current state of gets operation. */ { ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelBuffer *bufPtr; char *raw, *dst; int offset, toRead, dstNeeded, spaceLeft, result, rawLen; Tcl_Obj *objPtr; #define ENCODING_LINESIZE 20 /* Lower bound on how many bytes to convert at * a time. Since we don't know a priori how * many bytes of storage this many source * bytes will use, we actually need at least * ENCODING_LINESIZE * TCL_MAX_UTF bytes of * room. */ objPtr = gsPtr->objPtr; /* * Subtract the number of bytes that were removed from channel buffer * during last call. */ bufPtr = gsPtr->bufPtr; if (bufPtr != NULL) { bufPtr->nextRemoved += gsPtr->rawRead; if (!IsBufferReady(bufPtr)) { bufPtr = bufPtr->nextPtr; } } gsPtr->totalChars += gsPtr->charsWrote; if ((bufPtr == NULL) || (bufPtr->nextAdded == BUFFER_PADDING)) { /* * All channel buffers were exhausted and the caller still hasn't seen * EOL. Need to read more bytes from the channel device. Side effect * is to allocate another channel buffer. */ read: if (GotFlag(statePtr, CHANNEL_NONBLOCKING|CHANNEL_BLOCKED) == (CHANNEL_NONBLOCKING|CHANNEL_BLOCKED)) { gsPtr->charsWrote = 0; gsPtr->rawRead = 0; return -1; } if (GetInput(chanPtr) != 0) { gsPtr->charsWrote = 0; gsPtr->rawRead = 0; return -1; } bufPtr = statePtr->inQueueTail; gsPtr->bufPtr = bufPtr; if (bufPtr == NULL) { gsPtr->charsWrote = 0; gsPtr->rawRead = 0; return -1; } } else { /* * Incoming CHANNEL_STICKY_EOF is filtered out on entry. A new * CHANNEL_STICKY_EOF set in this routine leads to return before * coming back here. When we are not dealing with CHANNEL_STICKY_EOF, * a CHANNEL_EOF implies an empty buffer. Here the buffer is * non-empty so we know we're a non-EOF. */ assert(!GotFlag(statePtr, CHANNEL_STICKY_EOF)); assert(!GotFlag(statePtr, CHANNEL_EOF)); } /* * Convert some of the bytes from the channel buffer to UTF-8. Space in * objPtr's string rep is used to hold the UTF-8 characters. Grow the * string rep if we need more space. */ raw = RemovePoint(bufPtr); rawLen = BytesLeft(bufPtr); dst = *gsPtr->dstPtr; offset = dst - objPtr->bytes; toRead = ENCODING_LINESIZE; if (toRead > rawLen) { toRead = rawLen; } dstNeeded = toRead * TCL_UTF_MAX; spaceLeft = objPtr->length - offset; if (dstNeeded > spaceLeft) { int length = offset + ((offset < dstNeeded) ? dstNeeded : offset); if (Tcl_AttemptSetObjLength(objPtr, length) == 0) { length = offset + dstNeeded; if (Tcl_AttemptSetObjLength(objPtr, length) == 0) { dstNeeded = TCL_UTF_MAX - 1 + toRead; length = offset + dstNeeded; Tcl_SetObjLength(objPtr, length); } } spaceLeft = length - offset; dst = objPtr->bytes + offset; *gsPtr->dstPtr = dst; } gsPtr->state = statePtr->inputEncodingState; result = Tcl_ExternalToUtf(NULL, gsPtr->encoding, raw, rawLen, statePtr->inputEncodingFlags | TCL_ENCODING_NO_TERMINATE, &statePtr->inputEncodingState, dst, spaceLeft, &gsPtr->rawRead, &gsPtr->bytesWrote, &gsPtr->charsWrote); if (result == TCL_CONVERT_UNKNOWN || result == TCL_CONVERT_SYNTAX) { SetFlag(statePtr, CHANNEL_ENCODING_ERROR); ResetFlag(statePtr, CHANNEL_STICKY_EOF); ResetFlag(statePtr, CHANNEL_EOF); result = TCL_OK; } /* * Make sure that if we go through 'gets', that we reset the * TCL_ENCODING_START flag still. [Bug #523988] */ statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; if (result == TCL_CONVERT_MULTIBYTE) { /* * The last few bytes in this channel buffer were the start of a * multibyte sequence. If this buffer was full, then move them to the * next buffer so the bytes will be contiguous. */ ChannelBuffer *nextPtr; int extra; nextPtr = bufPtr->nextPtr; if (!IsBufferFull(bufPtr)) { if (gsPtr->rawRead > 0) { /* * Some raw bytes were converted to UTF-8. Fall through, * returning those UTF-8 characters because a EOL might be * present in them. */ } else if (GotFlag(statePtr, CHANNEL_EOF)) { /* * There was a partial character followed by EOF on the * device. Fall through, returning that nothing was found. */ bufPtr->nextRemoved = bufPtr->nextAdded; } else { /* * There are no more cached raw bytes left. See if we can get * some more, but avoid blocking on a non-blocking channel. */ goto read; } } else { if (nextPtr == NULL) { nextPtr = AllocChannelBuffer(statePtr->bufSize); bufPtr->nextPtr = nextPtr; statePtr->inQueueTail = nextPtr; } extra = rawLen - gsPtr->rawRead; memcpy(nextPtr->buf + (BUFFER_PADDING - extra), raw + gsPtr->rawRead, extra); nextPtr->nextRemoved -= extra; bufPtr->nextAdded -= extra; } } gsPtr->bufPtr = bufPtr; return 0; } /* *--------------------------------------------------------------------------- * * PeekAhead -- * * Helper function used by Tcl_GetsObj(). Called when we've seen a \r at * the end of the UTF-8 string and want to look ahead one character to * see if it is a \n. * * Results: * *gsPtr->dstPtr is filled with a pointer to the start of the range of * UTF-8 characters that were found by peeking and *dstEndPtr is filled * with a pointer to the bytes just after the end of the range. * * Side effects: * If no more raw bytes were available in one of the channel buffers, * tries to perform a non-blocking read to get more bytes from the * channel device. * *--------------------------------------------------------------------------- */ static void PeekAhead( Channel *chanPtr, /* The channel to read. */ char **dstEndPtr, /* Filled with pointer to end of new range of * UTF-8 characters. */ GetsState *gsPtr) /* Current state of gets operation. */ { ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelBuffer *bufPtr; Tcl_DriverBlockModeProc *blockModeProc; int bytesLeft; bufPtr = gsPtr->bufPtr; /* * If there's any more raw input that's still buffered, we'll peek into * that. Otherwise, only get more data from the channel driver if it looks * like there might actually be more data. The assumption is that if the * channel buffer is filled right up to the end, then there might be more * data to read. */ blockModeProc = NULL; if (bufPtr->nextPtr == NULL) { bytesLeft = BytesLeft(bufPtr) - gsPtr->rawRead; if (bytesLeft == 0) { if (!IsBufferFull(bufPtr)) { /* * Don't peek ahead if last read was short read. */ goto cleanup; } if (!GotFlag(statePtr, CHANNEL_NONBLOCKING)) { blockModeProc = Tcl_ChannelBlockModeProc(chanPtr->typePtr); if (blockModeProc == NULL) { /* * Don't peek ahead if cannot set non-blocking mode. */ goto cleanup; } StackSetBlockMode(chanPtr, TCL_MODE_NONBLOCKING); } } } if (FilterInputBytes(chanPtr, gsPtr) == 0) { *dstEndPtr = *gsPtr->dstPtr + gsPtr->bytesWrote; } if (blockModeProc != NULL) { StackSetBlockMode(chanPtr, TCL_MODE_BLOCKING); } return; cleanup: bufPtr->nextRemoved += gsPtr->rawRead; gsPtr->rawRead = 0; gsPtr->totalChars += gsPtr->charsWrote; gsPtr->bytesWrote = 0; gsPtr->charsWrote = 0; } /* *--------------------------------------------------------------------------- * * CommonGetsCleanup -- * * Helper function for Tcl_GetsObj() to restore the channel after a * "gets" operation. * * Results: * None. * * Side effects: * Encoding may be freed. * *--------------------------------------------------------------------------- */ static void CommonGetsCleanup( Channel *chanPtr) { ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelBuffer *bufPtr, *nextPtr; bufPtr = statePtr->inQueueHead; for ( ; bufPtr != NULL; bufPtr = nextPtr) { nextPtr = bufPtr->nextPtr; if (IsBufferReady(bufPtr)) { break; } RecycleBuffer(statePtr, bufPtr, 0); } statePtr->inQueueHead = bufPtr; if (bufPtr == NULL) { statePtr->inQueueTail = NULL; } else { /* * If any multi-byte characters were split across channel buffer * boundaries, the split-up bytes were moved to the next channel * buffer by FilterInputBytes(). Move the bytes back to their original * buffer because the caller could change the channel's encoding which * could change the interpretation of whether those bytes really made * up multi-byte characters after all. */ nextPtr = bufPtr->nextPtr; for ( ; nextPtr != NULL; nextPtr = bufPtr->nextPtr) { Tcl_Size extra; extra = SpaceLeft(bufPtr); if (extra > 0) { memcpy(InsertPoint(bufPtr), nextPtr->buf + (BUFFER_PADDING - extra), extra); bufPtr->nextAdded += extra; nextPtr->nextRemoved = BUFFER_PADDING; } bufPtr = nextPtr; } } } /* *---------------------------------------------------------------------- * * Tcl_Read -- * * Reads a given number of bytes from a channel. EOL and EOF translation * is done on the bytes being read, so the number of bytes consumed from * the channel may not be equal to the number of bytes stored in the * destination buffer. * * No encoding conversions are applied to the bytes being read. * * Results: * The number of bytes read, or TCL_INDEX_NONE on error. Use Tcl_GetErrno() to * retrieve the error code for the error that occurred. * * Side effects: * May cause input to be buffered. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_Read( Tcl_Channel chan, /* The channel from which to read. */ char *dst, /* Where to store input read. */ Tcl_Size bytesToRead) /* Maximum number of bytes to read. */ { Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; /* State info for channel */ /* * This operation should occur at the top of a channel stack. */ chanPtr = statePtr->topChanPtr; if (CheckChannelErrors(statePtr, TCL_READABLE) != 0) { return TCL_INDEX_NONE; } return DoRead(chanPtr, dst, bytesToRead, 0); } /* *---------------------------------------------------------------------- * * Tcl_ReadRaw -- * * Reads a given number of bytes from a channel. EOL and EOF translation * is done on the bytes being read, so the number of bytes consumed from * the channel may not be equal to the number of bytes stored in the * destination buffer. * * No encoding conversions are applied to the bytes being read. * * Results: * The number of bytes read, or TCL_INDEX_NONE on error. Use Tcl_GetErrno() to * retrieve the error code for the error that occurred. * * Side effects: * May cause input to be buffered. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_ReadRaw( Tcl_Channel chan, /* The channel from which to read. */ char *readBuf, /* Where to store input read. */ Tcl_Size bytesToRead) /* Maximum number of bytes to read. */ { Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; /* State info for channel */ int copied = 0; assert(bytesToRead > 0); if (CheckChannelErrors(statePtr, TCL_READABLE | CHANNEL_RAW_MODE) != 0) { return TCL_INDEX_NONE; } /* * First read bytes from the push-back buffers. */ while (chanPtr->inQueueHead && bytesToRead > 0) { ChannelBuffer *bufPtr = chanPtr->inQueueHead; int bytesInBuffer = BytesLeft(bufPtr); int toCopy = (bytesInBuffer < (int)bytesToRead) ? bytesInBuffer : (int)bytesToRead; /* * Copy the current chunk into the read buffer. */ memcpy(readBuf, RemovePoint(bufPtr), toCopy); bufPtr->nextRemoved += toCopy; copied += toCopy; readBuf += toCopy; bytesToRead -= toCopy; /* * If the current buffer is empty recycle it. */ if (IsBufferEmpty(bufPtr)) { chanPtr->inQueueHead = bufPtr->nextPtr; if (chanPtr->inQueueHead == NULL) { chanPtr->inQueueTail = NULL; } RecycleBuffer(chanPtr->state, bufPtr, 0); } } /* * Go to the driver only if we got nothing from pushback. Have to do it * this way to avoid EOF mistimings when we consider the ability that EOF * may not be a permanent condition in the driver, and in that case we * have to synchronize. */ if (copied) { return copied; } /* * This test not needed. */ if (bytesToRead > 0) { int nread = ChanRead(chanPtr, readBuf, bytesToRead); if (nread == -1) { /* * An error signaled. If CHANNEL_BLOCKED, then the error is not * real, but an indication of blocked state. In that case, retain * the flag and let caller receive the short read of copied bytes * from the pushback. HOWEVER, if copied==0 bytes from pushback * then repeat signalling the blocked state as an error to caller * so there is no false report of an EOF. When !CHANNEL_BLOCKED, * the error is real and passes on to caller. */ if (!GotFlag(statePtr, CHANNEL_BLOCKED) || copied == 0) { copied = -1; } } else if (nread > 0) { /* * Successful read (short is OK) - add to bytes copied. */ copied += nread; } else { /* * nread == 0. Driver is at EOF. Let that state filter up. */ } } return copied; } /* *--------------------------------------------------------------------------- * * Tcl_ReadChars -- * * Reads from the channel until the requested number of characters have * been seen, EOF is seen, or the channel would block. EOL and EOF * translation is done. If reading binary data, the raw bytes are wrapped * in a Tcl byte array object. Otherwise, the raw bytes are converted to * UTF-8 using the channel's current encoding and stored in a Tcl string * object. * * Results: * The number of characters read, or TCL_INDEX_NONE on error. Use Tcl_GetErrno() to * retrieve the error code for the error that occurred. * * Side effects: * May cause input to be buffered. * *--------------------------------------------------------------------------- */ Tcl_Size Tcl_ReadChars( Tcl_Channel chan, /* The channel to read. */ Tcl_Obj *objPtr, /* Input data is stored in this object. */ Tcl_Size toRead, /* Maximum number of characters to store, or * TCL_INDEX_NONE to read all available data (up to EOF or * when channel blocks). */ int appendFlag) /* If non-zero, data read from the channel * will be appended to the object. Otherwise, * the data will replace the existing contents * of the object. */ { Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; /* State info for channel */ /* * This operation should occur at the top of a channel stack. */ chanPtr = statePtr->topChanPtr; if (CheckChannelErrors(statePtr, TCL_READABLE) != 0) { /* * Update the notifier state so we don't block while there is still * data in the buffers. */ UpdateInterest(chanPtr); return TCL_INDEX_NONE; } return DoReadChars(chanPtr, objPtr, toRead, 0, appendFlag); } /* *--------------------------------------------------------------------------- * * DoReadChars -- * * Reads from the channel until the requested number of characters have * been seen, EOF is seen, or the channel would block. EOL and EOF * translation is done. If reading binary data, the raw bytes are wrapped * in a Tcl byte array object. Otherwise, the raw bytes are converted to * UTF-8 using the channel's current encoding and stored in a Tcl string * object. * * Results: * The number of characters read, or TCL_INDEX_NONE on error. Use Tcl_GetErrno() to * retrieve the error code for the error that occurred. * * Side effects: * May cause input to be buffered. * *--------------------------------------------------------------------------- */ static Tcl_Size DoReadChars( Channel *chanPtr, /* The channel to read. */ Tcl_Obj *objPtr, /* Input data is stored in this object. */ Tcl_Size toRead, /* Maximum number of characters to store, or * TCL_INDEX_NONE to read all available data (up to EOF or * when channel blocks). */ int allowShortReads, /* Allow half-blocking (pipes,sockets) */ int appendFlag) /* If non-zero, data read from the channel * will be appended to the object. Otherwise, * the data will replace the existing contents * of the object. */ { ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelBuffer *bufPtr; Tcl_Size copied; int result; Tcl_Encoding encoding = statePtr->encoding; int binaryMode; #define UTF_EXPANSION_FACTOR 1024 int factor = UTF_EXPANSION_FACTOR; if (GotFlag(statePtr, CHANNEL_ENCODING_ERROR)) { ResetFlag(statePtr, CHANNEL_EOF|CHANNEL_ENCODING_ERROR); /* TODO: UpdateInterest not needed here? */ UpdateInterest(chanPtr); Tcl_SetErrno(EILSEQ); return -1; } /* * Early out when next read will see eofchar. * * NOTE: See DoRead for argument that it's a bug (one we're keeping) to * have this escape before the one for zero-char read request. */ if (GotFlag(statePtr, CHANNEL_STICKY_EOF)) { SetFlag(statePtr, CHANNEL_EOF); assert(statePtr->inputEncodingFlags & TCL_ENCODING_END); assert(!GotFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR)); /* TODO: UpdateInterest not needed here? */ UpdateInterest(chanPtr); return 0; } /* * Special handling for zero-char read request. */ if (toRead == 0) { if (GotFlag(statePtr, CHANNEL_EOF)) { statePtr->inputEncodingFlags |= TCL_ENCODING_START; } ResetFlag(statePtr, CHANNEL_BLOCKED|CHANNEL_EOF); statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; /* TODO: UpdateInterest not needed here? */ UpdateInterest(chanPtr); return 0; } /* * This operation should occur at the top of a channel stack. */ chanPtr = statePtr->topChanPtr; TclChannelPreserve((Tcl_Channel)chanPtr); binaryMode = (encoding == GetBinaryEncoding()) && (statePtr->inputTranslation == TCL_TRANSLATE_LF) && (statePtr->inEofChar == '\0'); if (appendFlag) { if (binaryMode && (NULL == Tcl_GetBytesFromObj(NULL, objPtr, (Tcl_Size *)NULL))) { binaryMode = 0; } } else { if (binaryMode) { Tcl_SetByteArrayLength(objPtr, 0); } else { Tcl_SetObjLength(objPtr, 0); } } /* * Must clear the BLOCKED|EOF flags here since we check before reading. */ if (GotFlag(statePtr, CHANNEL_EOF)) { statePtr->inputEncodingFlags |= TCL_ENCODING_START; } ResetFlag(statePtr, CHANNEL_BLOCKED|CHANNEL_EOF); statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; for (copied = 0; toRead != 0 ; ) { int copiedNow = -1; if (statePtr->inQueueHead != NULL) { if (binaryMode) { copiedNow = ReadBytes(statePtr, objPtr, toRead); } else { copiedNow = ReadChars(statePtr, objPtr, toRead, &factor); } /* * Recycle current buffer if empty. */ bufPtr = statePtr->inQueueHead; if (IsBufferEmpty(bufPtr)) { ChannelBuffer *nextPtr = bufPtr->nextPtr; RecycleBuffer(statePtr, bufPtr, 0); statePtr->inQueueHead = nextPtr; if (nextPtr == NULL) { statePtr->inQueueTail = NULL; } } /* * If CHANNEL_ENCODING_ERROR and CHANNEL_STICKY_EOF are both set, * then CHANNEL_ENCODING_ERROR was caused by data that occurred * after the EOF character was encountered, so it doesn't count as * a real error. */ if (GotFlag(statePtr, CHANNEL_ENCODING_ERROR) && !GotFlag(statePtr, CHANNEL_STICKY_EOF) && (!GotFlag(statePtr, CHANNEL_NONBLOCKING))) { goto finish; } } if (copiedNow < 0) { if (GotFlag(statePtr, CHANNEL_EOF)) { break; } if ((GotFlag(statePtr, CHANNEL_NONBLOCKING) || allowShortReads) && GotFlag(statePtr, CHANNEL_BLOCKED)) { break; } result = GetInput(chanPtr); if (chanPtr != statePtr->topChanPtr) { TclChannelRelease((Tcl_Channel)chanPtr); chanPtr = statePtr->topChanPtr; TclChannelPreserve((Tcl_Channel)chanPtr); } if (result != 0) { if (!GotFlag(statePtr, CHANNEL_BLOCKED)) { copied = -1; } break; } } else { copied += copiedNow; if (toRead != TCL_INDEX_NONE) { toRead -= copiedNow; /* Only decr if not reading whole file */ } } } finish: /* * Failure to fill a channel buffer may have left channel reporting a * "blocked" state, but so long as we fulfilled the request here, the * caller does not consider us blocked. */ if (toRead == 0) { ResetFlag(statePtr, CHANNEL_BLOCKED); } /* * Regenerate chanPtr in case it was changed due to * self-modifying reflected transforms. */ if (chanPtr != statePtr->topChanPtr) { TclChannelRelease((Tcl_Channel)chanPtr); chanPtr = statePtr->topChanPtr; TclChannelPreserve((Tcl_Channel)chanPtr); } /* * Update the notifier state so we don't block while there is still data * in the buffers. */ assert(!GotFlag(statePtr, CHANNEL_EOF) || GotFlag(statePtr, CHANNEL_STICKY_EOF|CHANNEL_ENCODING_ERROR) || Tcl_InputBuffered((Tcl_Channel)chanPtr) == 0); assert(!(GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED) == (CHANNEL_EOF|CHANNEL_BLOCKED))); UpdateInterest(chanPtr); /* This must comes after UpdateInterest(), which may set errno */ if (GotFlag(statePtr, CHANNEL_ENCODING_ERROR) && (!copied || !GotFlag(statePtr, CHANNEL_NONBLOCKING))) { /* Channel either is blocking or is nonblocking with no data * succesfully red before the error. Return an error so that callers * like [read] can also return an error. */ ResetFlag(statePtr, CHANNEL_EOF|CHANNEL_ENCODING_ERROR); Tcl_SetErrno(EILSEQ); copied = -1; } TclChannelRelease((Tcl_Channel)chanPtr); return copied; } /* *--------------------------------------------------------------------------- * * ReadBytes -- * * Reads from the channel until the requested number of bytes have been * seen, EOF is seen, or the channel would block. Bytes from the channel * are stored in objPtr as a ByteArray object. EOL and EOF translation * are done. * * 'bytesToRead' can safely be a very large number because space is only * allocated to hold data read from the channel as needed. * * Results: * The return value is the number of bytes appended to the object, or * TCL_INDEX_NONE to indicate that zero bytes were read due to an EOF. * * Side effects: * The storage of bytes in objPtr can cause (re-)allocation of memory. * *--------------------------------------------------------------------------- */ static int ReadBytes( ChannelState *statePtr, /* State of the channel to read. */ Tcl_Obj *objPtr, /* Input data is appended to this ByteArray * object. Its length is how much space has * been allocated to hold data, not how many * bytes of data have been stored in the * object. */ int bytesToRead) /* Maximum number of bytes to store, or -1 to * get all available bytes. Bytes are obtained * from the first buffer in the queue - even * if this number is larger than the number of * bytes available in the first buffer, only * the bytes from the first buffer are * returned. */ { ChannelBuffer *bufPtr = statePtr->inQueueHead; int srcLen = BytesLeft(bufPtr); int toRead = bytesToRead>srcLen || bytesToRead<0 ? srcLen : bytesToRead; TclAppendBytesToByteArray(objPtr, (unsigned char *) RemovePoint(bufPtr), toRead); bufPtr->nextRemoved += toRead; return toRead; } /* *--------------------------------------------------------------------------- * * ReadChars -- * * Reads from the channel until the requested number of UTF-8 characters * have been seen, EOF is seen, or the channel would block. Raw bytes * from the channel are converted to UTF-8 and stored in objPtr. EOL and * EOF translation is done. * * 'charsToRead' can safely be a very large number because space is only * allocated to hold data read from the channel as needed. * * 'charsToRead' may *not* be 0. * * Results: * The return value is the number of characters appended to the object, * *offsetPtr is filled with the number of bytes that were appended, and * *factorPtr is filled with the expansion factor used to guess how many * bytes of UTF-8 to allocate to hold N source bytes. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static int ReadChars( ChannelState *statePtr, /* State of channel to read. */ Tcl_Obj *objPtr, /* Input data is appended to this object. * objPtr->length is how much space has been * allocated to hold data, not how many bytes * of data have been stored in the object. */ int charsToRead, /* Maximum number of characters to store, or * TCL_INDEX_NONE to get all available characters. * Characters are obtained from the first * buffer in the queue -- even if this number * is larger than the number of characters * available in the first buffer, only the * characters from the first buffer are * returned. The exception is when there is * not any complete character in the first * buffer. In that case, a recursive call * effectively obtains chars from the * second buffer. */ int *factorPtr) /* On input, contains a guess of how many * bytes need to be allocated to hold the * result of converting N source bytes to * UTF-8. On output, contains another guess * based on the data seen so far. */ { Tcl_Encoding encoding = statePtr->encoding; Tcl_EncodingState savedState = statePtr->inputEncodingState; ChannelBuffer *bufPtr = statePtr->inQueueHead; int savedIEFlags = statePtr->inputEncodingFlags; int savedFlags = statePtr->flags; char *dst, *src = RemovePoint(bufPtr); Tcl_Size numBytes; int srcLen = BytesLeft(bufPtr); /* * One src byte can yield at most one character. So when the number of * src bytes we plan to read is less than the limit on character count to * be read, clearly we will remain within that limit, and we can use the * value of "srcLen" as a tighter limit for sizing receiving buffers. */ int toRead = ((charsToRead<0)||(charsToRead > srcLen)) ? srcLen : charsToRead; /* * 'factor' is how much we guess that the bytes in the source buffer will * expand when converted to UTF-8 chars. This guess comes from analyzing * how many characters were produced by the previous pass. */ int factor = *factorPtr; int dstLimit = TCL_UTF_MAX - 1 + toRead * factor / UTF_EXPANSION_FACTOR; if (dstLimit <= 0) { dstLimit = INT_MAX; /* avoid overflow */ } (void)TclGetStringFromObj(objPtr, &numBytes); TclAppendUtfToUtf(objPtr, NULL, dstLimit); if (toRead == srcLen) { Tcl_Size size; dst = TclGetStringStorage(objPtr, &size) + numBytes; dstLimit = (size - numBytes) > INT_MAX ? INT_MAX : (size - numBytes); } else { dst = TclGetString(objPtr) + numBytes; } /* * This routine is burdened with satisfying several constraints. It cannot * append more than 'charsToRead` chars onto objPtr. This is measured * after encoding and translation transformations are completed. There is * no precise number of src bytes that can be associated with the limit. * Yet, when we are done, we must know precisely the number of src bytes * that were consumed to produce the appended chars, so that all * subsequent bytes are left in the buffers for future read operations. * * The consequence is that we have no choice but to implement a "trial and * error" approach, where in general we may need to perform * transformations and copies multiple times to achieve a consistent set * of results. This takes the shape of a loop. */ while (1) { int dstDecoded, dstRead, dstWrote, srcRead, numChars, code; int flags = statePtr->inputEncodingFlags | TCL_ENCODING_NO_TERMINATE; if (charsToRead > 0) { flags |= TCL_ENCODING_CHAR_LIMIT; numChars = charsToRead; } /* * Perform the encoding transformation. Read no more than srcLen * bytes, write no more than dstLimit bytes. * * Some trickiness with encoding flags here. We do not want the end * of a buffer to be treated as the end of all input when the presence * of bytes in a next buffer are already known to exist. This is * checked with an assert() because so far no test case causing the * assertion to be false has been created. The normal operations of * channel reading appear to cause EOF and TCL_ENCODING_END setting to * appear only in situations where there are no further bytes in any * buffers. */ assert(bufPtr->nextPtr == NULL || BytesLeft(bufPtr->nextPtr) == 0 || (statePtr->inputEncodingFlags & TCL_ENCODING_END) == 0); code = Tcl_ExternalToUtf(NULL, encoding, src, srcLen, flags, &statePtr->inputEncodingState, dst, dstLimit, &srcRead, &dstDecoded, &numChars); if (code == TCL_CONVERT_UNKNOWN || code == TCL_CONVERT_SYNTAX || (code == TCL_CONVERT_MULTIBYTE && GotFlag(statePtr, CHANNEL_EOF))) { SetFlag(statePtr, CHANNEL_ENCODING_ERROR); code = TCL_OK; } /* * Perform the translation transformation in place. Read no more than * the dstDecoded bytes the encoding transformation actually produced. * Capture the number of bytes written in dstWrote. Capture the number * of bytes actually consumed in dstRead. */ dstWrote = dstLimit; dstRead = dstDecoded; TranslateInputEOL(statePtr, dst, dst, &dstWrote, &dstRead); if (dstRead < dstDecoded) { /* * The encoding transformation produced bytes that the translation * transformation did not consume. Why did this happen? */ if (statePtr->inEofChar && dst[dstRead] == statePtr->inEofChar) { /* * 1) There's an eof char set on the channel, and * we saw it and stopped translating at that point. * * NOTE the bizarre spec of TranslateInputEOL in this case. * Clearly the eof char had to be read in order to account for * the stopping, but the value of dstRead does not include it. * * Also rather bizarre, our caller can only notice an EOF * condition if we return the value TCL_INDEX_NONE as the number of chars * read. This forces us to perform a 2-call dance where the * first call can read all the chars up to the eof char, and * the second call is solely for consuming the encoded eof * char then pointed at by src so that we can return that * magic TCL_INDEX_NONE value. This seems really wasteful, especially * since the first decoding pass of each call is likely to * decode many bytes beyond that eof char that's all we care * about. */ if (dstRead == 0) { /* * Curious choice in the eof char handling. We leave the * eof char in the buffer. So, no need to compute a proper * srcRead value. At this point, there are no chars before * the eof char in the buffer. */ Tcl_SetObjLength(objPtr, numBytes); return -1; } { /* * There are chars leading the buffer before the eof char. * Adjust the dstLimit so we go back and read only those * and do not encounter the eof char this time. */ dstLimit = dstRead + (TCL_UTF_MAX - 1); statePtr->flags = savedFlags; statePtr->inputEncodingFlags = savedIEFlags; statePtr->inputEncodingState = savedState; continue; } } /* * 2) The other way to read fewer bytes than are decoded is when * the final byte is \r and we're in a CRLF translation mode so * we cannot decide whether to record \r or \n yet. */ assert(dst[dstRead] == '\r'); assert(statePtr->inputTranslation == TCL_TRANSLATE_CRLF); if (dstWrote > 0) { /* * There are chars we can read before we hit the bare CR. Go * back with a smaller dstLimit so we get them in the next * pass, compute a matching srcRead, and don't end up back * here in this call. */ dstLimit = dstRead + (TCL_UTF_MAX - 1); statePtr->flags = savedFlags; statePtr->inputEncodingFlags = savedIEFlags; statePtr->inputEncodingState = savedState; continue; } assert(dstWrote == 0); assert(dstRead == 0); /* * We decoded only the bare CR, and we cannot read a translated * char from that alone. We have to know what's next. So why do * we only have the one decoded char? */ if (code != TCL_OK) { int read, decoded, count; char buffer[TCL_UTF_MAX + 1]; /* * Didn't get everything the buffer could offer */ statePtr->flags = savedFlags; statePtr->inputEncodingFlags = savedIEFlags; statePtr->inputEncodingState = savedState; assert(bufPtr->nextPtr == NULL || BytesLeft(bufPtr->nextPtr) == 0 || 0 == (statePtr->inputEncodingFlags & TCL_ENCODING_END)); Tcl_ExternalToUtf(NULL, encoding, src, srcLen, (statePtr->inputEncodingFlags | TCL_ENCODING_NO_TERMINATE), &statePtr->inputEncodingState, buffer, sizeof(buffer), &read, &decoded, &count); if (count == 2) { if (buffer[1] == '\n') { /* \r\n translate to \n */ dst[0] = '\n'; bufPtr->nextRemoved += read; } else { dst[0] = '\r'; bufPtr->nextRemoved += srcRead; } statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; Tcl_SetObjLength(objPtr, numBytes + 1); return 1; } } else if (GotFlag(statePtr, CHANNEL_EOF)) { /* * The bare \r is the only char and we will never read a * subsequent char to make the determination. */ dst[0] = '\r'; bufPtr->nextRemoved = bufPtr->nextAdded; Tcl_SetObjLength(objPtr, numBytes + 1); return 1; } /* * Revise the dstRead value so that the numChars calc below * correctly computes zero characters read. */ dstRead = numChars; /* FALL THROUGH - get more data (dstWrote == 0) */ } /* * The translation transformation can only reduce the number of chars * when it converts \r\n into \n. The reduction in the number of chars * is the difference in bytes read and written. */ numChars -= (dstRead - dstWrote); if (charsToRead > 0 && numChars > charsToRead) { /* * TODO: This cannot happen anymore. * * We read more chars than allowed. Reset limits to prevent that * and try again. Don't forget the extra padding of TCL_UTF_MAX * bytes demanded by the Tcl_ExternalToUtf() call! */ dstLimit = Tcl_UtfAtIndex(dst, charsToRead) - dst + (TCL_UTF_MAX - 1); statePtr->flags = savedFlags; statePtr->inputEncodingFlags = savedIEFlags; statePtr->inputEncodingState = savedState; continue; } if (dstWrote == 0) { ChannelBuffer *nextPtr; /* * We were not able to read any chars. */ assert(numChars == 0); /* * There is one situation where this is the correct final result. * If the src buffer contains only a single \n byte, and we are in * TCL_TRANSLATE_AUTO mode, and when the translation pass was made * the INPUT_SAW_CR flag was set on the channel. In that case, the * correct behavior is to consume that \n and produce the empty * string. */ if (dstRead == 1 && dst[0] == '\n') { assert(statePtr->inputTranslation == TCL_TRANSLATE_AUTO); goto consume; } /* * Otherwise, reading zero characters indicates there's something * incomplete at the end of the src buffer. Maybe there were not * enough src bytes to decode into a char. Maybe a lone \r could * not be translated (crlf mode). Need to combine any unused src * bytes we have in the first buffer with subsequent bytes to try * again. */ nextPtr = bufPtr->nextPtr; if (nextPtr == NULL) { if (srcLen > 0) { SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); } Tcl_SetObjLength(objPtr, numBytes); return -1; } /* * Space is made at the beginning of the buffer to copy the * previous unused bytes there. Check first if the buffer we are * using actually has enough space at its beginning for the data * we are copying. Because if not we will write over the buffer * management information, especially the 'nextPtr'. * * Note that the BUFFER_PADDING (See AllocChannelBuffer) is used * to prevent exactly this situation. I.e. it should never happen. * Therefore it is ok to panic should it happen despite the * precautions. */ if (nextPtr->nextRemoved < srcLen) { Tcl_Panic("Buffer Underflow, BUFFER_PADDING not enough"); } nextPtr->nextRemoved -= srcLen; memcpy(RemovePoint(nextPtr), src, srcLen); RecycleBuffer(statePtr, bufPtr, 0); statePtr->inQueueHead = nextPtr; Tcl_SetObjLength(objPtr, numBytes); return ReadChars(statePtr, objPtr, charsToRead, factorPtr); } statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; consume: bufPtr->nextRemoved += srcRead; /* * If this read contained multibyte characters, revise factorPtr so * the next read will allocate bigger buffers. */ if (numChars && numChars < srcRead) { *factorPtr = srcRead * UTF_EXPANSION_FACTOR / numChars; } Tcl_SetObjLength(objPtr, numBytes + dstWrote); return numChars; } } /* *--------------------------------------------------------------------------- * * TranslateInputEOL -- * * Perform input EOL and EOF translation on the source buffer, leaving * the translated result in the destination buffer. * * Results: * The return value is 1 if the EOF character was found when copying * bytes to the destination buffer, 0 otherwise. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static void TranslateInputEOL( ChannelState *statePtr, /* Channel being read, for EOL translation and * EOF character. */ char *dstStart, /* Output buffer filled with chars by applying * appropriate EOL translation to source * characters. */ const char *srcStart, /* Source characters. */ int *dstLenPtr, /* On entry, the maximum length of output * buffer in bytes. On exit, the number of * bytes actually used in output buffer. */ int *srcLenPtr) /* On entry, the length of source buffer. On * exit, the number of bytes read from the * source buffer. */ { const char *eof = NULL; int dstLen = *dstLenPtr; int srcLen = *srcLenPtr; int inEofChar = statePtr->inEofChar; /* * Depending on the translation mode in use, there's no need to scan more * srcLen bytes at srcStart than can possibly transform to dstLen bytes. * This keeps the scan for eof char below from being pointlessly long. */ switch (statePtr->inputTranslation) { case TCL_TRANSLATE_LF: case TCL_TRANSLATE_CR: if (srcLen > dstLen) { /* * In these modes, each src byte become a dst byte. */ srcLen = dstLen; } break; default: /* * In other modes, at most 2 src bytes become a dst byte. */ if (srcLen/2 > dstLen) { srcLen = 2 * dstLen; } break; } if (inEofChar != '\0') { /* * Make sure we do not read past any logical end of channel input * created by the presence of the input eof char. */ if ((eof = (const char *)memchr(srcStart, inEofChar, srcLen))) { srcLen = eof - srcStart; } } switch (statePtr->inputTranslation) { case TCL_TRANSLATE_LF: case TCL_TRANSLATE_CR: if (dstStart != srcStart) { memcpy(dstStart, srcStart, srcLen); } if (statePtr->inputTranslation == TCL_TRANSLATE_CR) { char *dst = dstStart; char *dstEnd = dstStart + srcLen; while ((dst = (char *)memchr(dst, '\r', dstEnd - dst))) { *dst++ = '\n'; } } dstLen = srcLen; break; case TCL_TRANSLATE_CRLF: { const char *crFound, *src = srcStart; char *dst = dstStart; int lesser = (dstLen < srcLen) ? dstLen : srcLen; while ((crFound = (const char *)memchr(src, '\r', lesser))) { int numBytes = crFound - src; memmove(dst, src, numBytes); dst += numBytes; dstLen -= numBytes; src += numBytes; srcLen -= numBytes; if (srcLen == 1) { /* valid src bytes end in \r */ if (eof) { *dst++ = '\r'; src++; srcLen--; } else { lesser = 0; break; } } else if (src[1] == '\n') { *dst++ = '\n'; src += 2; srcLen -= 2; } else { *dst++ = '\r'; src++; srcLen--; } dstLen--; lesser = (dstLen < srcLen) ? dstLen : srcLen; } memmove(dst, src, lesser); srcLen = src + lesser - srcStart; dstLen = dst + lesser - dstStart; break; } case TCL_TRANSLATE_AUTO: { const char *crFound, *src = srcStart; char *dst = dstStart; int lesser; if (GotFlag(statePtr, INPUT_SAW_CR) && srcLen) { if (*src == '\n') { src++; srcLen--; } ResetFlag(statePtr, INPUT_SAW_CR); } lesser = (dstLen < srcLen) ? dstLen : srcLen; while ((crFound = (const char *) memchr(src, '\r', lesser))) { int numBytes = crFound - src; memmove(dst, src, numBytes); dst[numBytes] = '\n'; dst += numBytes + 1; dstLen -= numBytes + 1; src += numBytes + 1; srcLen -= numBytes + 1; if (srcLen == 0) { SetFlag(statePtr, INPUT_SAW_CR); } else if (*src == '\n') { src++; srcLen--; } lesser = (dstLen < srcLen) ? dstLen : srcLen; } memmove(dst, src, lesser); srcLen = src + lesser - srcStart; dstLen = dst + lesser - dstStart; break; } default: Tcl_Panic("unknown input translation %d", statePtr->inputTranslation); } *dstLenPtr = dstLen; *srcLenPtr = srcLen; if (srcStart + srcLen == eof) { /* * EOF character was seen in EOL translated range. Leave current file * position pointing at the EOF character, but don't store the EOF * character in the output string. * * If CHANNEL_ENCODING_ERROR is set, it can only be because of data * encountered after the EOF character, so it is nonsense. Unset it. */ SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; ResetFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR|CHANNEL_ENCODING_ERROR); } } /* *---------------------------------------------------------------------- * * Tcl_Ungets -- * * Causes the supplied string to be added to the input queue of the * channel, at either the head or tail of the queue. * * Results: * The number of bytes stored in the channel, or TCL_INDEX_NONE on error. * * Side effects: * Adds input to the input queue of a channel. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_Ungets( Tcl_Channel chan, /* The channel for which to add the input. */ const char *str, /* The input itself. */ Tcl_Size len, /* The length of the input. */ int atEnd) /* If non-zero, add at end of queue; otherwise * add at head of queue. */ { Channel *chanPtr; /* The real IO channel. */ ChannelState *statePtr; /* State of actual channel. */ ChannelBuffer *bufPtr; /* Buffer to contain the data. */ int flags; chanPtr = (Channel *) chan; statePtr = chanPtr->state; /* * This operation should occur at the top of a channel stack. */ chanPtr = statePtr->topChanPtr; /* * CheckChannelErrors clears too many flag bits in this one case. */ flags = statePtr->flags; if (CheckChannelErrors(statePtr, TCL_READABLE) != 0) { len = TCL_INDEX_NONE; goto done; } statePtr->flags = flags; /* * Clear the EOF flags, and clear the BLOCKED bit. */ if (GotFlag(statePtr, CHANNEL_EOF)) { statePtr->inputEncodingFlags |= TCL_ENCODING_START; } ResetFlag(statePtr, CHANNEL_BLOCKED | CHANNEL_STICKY_EOF | CHANNEL_EOF | INPUT_SAW_CR); statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; bufPtr = AllocChannelBuffer(len); memcpy(InsertPoint(bufPtr), str, len); bufPtr->nextAdded += len; if (statePtr->inQueueHead == NULL) { bufPtr->nextPtr = NULL; statePtr->inQueueHead = bufPtr; statePtr->inQueueTail = bufPtr; } else if (atEnd) { bufPtr->nextPtr = NULL; statePtr->inQueueTail->nextPtr = bufPtr; statePtr->inQueueTail = bufPtr; } else { bufPtr->nextPtr = statePtr->inQueueHead; statePtr->inQueueHead = bufPtr; } /* * Update the notifier state so we don't block while there is still data * in the buffers. */ done: UpdateInterest(chanPtr); return len; } /* *---------------------------------------------------------------------- * * Tcl_Flush -- * * Flushes output data on a channel. * * Results: * A standard Tcl result. * * Side effects: * May flush output queued on this channel. * *---------------------------------------------------------------------- */ int Tcl_Flush( Tcl_Channel chan) /* The Channel to flush. */ { int result; /* Of calling FlushChannel. */ Channel *chanPtr = (Channel *) chan; /* The actual channel. */ ChannelState *statePtr = chanPtr->state; /* State of actual channel. */ /* * This operation should occur at the top of a channel stack. */ chanPtr = statePtr->topChanPtr; if (CheckChannelErrors(statePtr, TCL_WRITABLE) != 0) { return TCL_ERROR; } result = FlushChannel(NULL, chanPtr, 0); if (result != 0) { return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * DiscardInputQueued -- * * Discards any input read from the channel but not yet consumed by Tcl * reading commands. * * Results: * None. * * Side effects: * May discard input from the channel. If discardLastBuffer is zero, * leaves one buffer in place for back-filling. * *---------------------------------------------------------------------- */ static void DiscardInputQueued( ChannelState *statePtr, /* Channel on which to discard the queued * input. */ int discardSavedBuffers) /* If non-zero, discard all buffers including * last one. */ { ChannelBuffer *bufPtr, *nxtPtr; /* Loop variables. */ bufPtr = statePtr->inQueueHead; statePtr->inQueueHead = NULL; statePtr->inQueueTail = NULL; for (; bufPtr != NULL; bufPtr = nxtPtr) { nxtPtr = bufPtr->nextPtr; RecycleBuffer(statePtr, bufPtr, discardSavedBuffers); } /* * If discardSavedBuffers is nonzero, must also discard any previously * saved buffer in the saveInBufPtr field. */ if (discardSavedBuffers && statePtr->saveInBufPtr != NULL) { ReleaseChannelBuffer(statePtr->saveInBufPtr); statePtr->saveInBufPtr = NULL; } } /* *--------------------------------------------------------------------------- * * GetInput -- * * Reads input data from a device into a channel buffer. * * IMPORTANT! This routine is only called on a chanPtr argument * that is the top channel of a stack! * * Results: * The return value is the Posix error code if an error occurred while * reading from the file, or 0 otherwise. * * Side effects: * Reads from the underlying device. * *--------------------------------------------------------------------------- */ static int GetInput( Channel *chanPtr) /* Channel to read input from. */ { int toRead; /* How much to read? */ int result; /* Of calling driver. */ int nread; /* How much was read from channel? */ ChannelBuffer *bufPtr; /* New buffer to add to input queue. */ ChannelState *statePtr = chanPtr->state; /* State info for channel */ /* * Verify that all callers know better than to call us when * it's recorded that the next char waiting to be read is the * eofchar. */ assert(!GotFlag(statePtr, CHANNEL_STICKY_EOF)); /* * Prevent reading from a dead channel -- a channel that has been closed * but not yet deallocated, which can happen if the exit handler for * channel cleanup has run but the channel is still registered in some * interpreter. */ if (CheckForDeadChannel(NULL, statePtr)) { return EINVAL; } /* * WARNING: There was once a comment here claiming that it was a bad idea * to make another call to the inputproc of a channel driver when EOF has * already been detected on the channel. Through much of Tcl's history, * this warning was then completely negated by having all (most?) read * paths clear the EOF setting before reaching here. So we had a guard * that was never triggered. * * Don't be tempted to restore the guard. Even if EOF is set on the * channel, continue through and call the inputproc again. This is the * way to enable the ability to [read] again beyond the EOF, which seems a * strange thing to do, but for which use cases exist [Tcl Bug 5adc350683] * and which may even be essential for channels representing things like * ttys or other devices where the stream might take the logical form of a * series of 'files' separated by an EOF condition. * * First check for more buffers in the pushback area of the topmost * channel in the stack and use them. They can be the result of a * transformation which went away without reading all the information * placed in the area when it was stacked. */ if (chanPtr->inQueueHead != NULL) { /* TODO: Tests to cover this. */ assert(statePtr->inQueueHead == NULL); statePtr->inQueueHead = chanPtr->inQueueHead; statePtr->inQueueTail = chanPtr->inQueueTail; chanPtr->inQueueHead = NULL; chanPtr->inQueueTail = NULL; return 0; } /* * Nothing in the pushback area, fall back to the usual handling (driver, * etc.) */ /* * See if we can fill an existing buffer. If we can, read only as much as * will fit in it. Otherwise allocate a new buffer, add it to the input * queue and attempt to fill it to the max. */ bufPtr = statePtr->inQueueTail; if ((bufPtr == NULL) || IsBufferFull(bufPtr)) { bufPtr = statePtr->saveInBufPtr; statePtr->saveInBufPtr = NULL; /* * Check the actual buffersize against the requested buffersize. * Saved buffers of the wrong size are squashed. This is done to honor * dynamic changes of the buffersize made by the user. * * TODO: Tests to cover this. */ if ((bufPtr != NULL) && (bufPtr->bufLength != statePtr->bufSize + BUFFER_PADDING)) { ReleaseChannelBuffer(bufPtr); bufPtr = NULL; } if (bufPtr == NULL) { bufPtr = AllocChannelBuffer(statePtr->bufSize); } bufPtr->nextPtr = NULL; toRead = SpaceLeft(bufPtr); assert((Tcl_Size)toRead == statePtr->bufSize); if (statePtr->inQueueTail == NULL) { statePtr->inQueueHead = bufPtr; } else { statePtr->inQueueTail->nextPtr = bufPtr; } statePtr->inQueueTail = bufPtr; } else { toRead = SpaceLeft(bufPtr); } PreserveChannelBuffer(bufPtr); nread = ChanRead(chanPtr, InsertPoint(bufPtr), toRead); ReleaseChannelBuffer(bufPtr); if (nread < 0) { result = Tcl_GetErrno(); } else { result = 0; if (statePtr->inQueueTail != NULL) { statePtr->inQueueTail->nextAdded += nread; } } return result; } /* *---------------------------------------------------------------------- * * Tcl_Seek -- * * Implements seeking on Tcl Channels. This is a public function so that * other C facilities may be implemented on top of it. * * Results: * The new access point or -1 on error. If error, use Tcl_GetErrno() to * retrieve the POSIX error code for the error that occurred. * * Side effects: * May flush output on the channel. May discard queued input. * *---------------------------------------------------------------------- */ long long Tcl_Seek( Tcl_Channel chan, /* The channel on which to seek. */ long long offset, /* Offset to seek to. */ int mode) /* Relative to which location to seek? */ { Channel *chanPtr = (Channel *) chan; /* The real IO channel. */ ChannelState *statePtr = chanPtr->state; /* State info for channel */ int inputBuffered, outputBuffered; /* # bytes held in buffers. */ int result; /* Of device driver operations. */ long long curPos; /* Position on the device. */ int wasAsync; /* Was the channel nonblocking before the seek * operation? If so, must restore to * non-blocking mode after the seek. */ if (CheckChannelErrors(statePtr, TCL_WRITABLE | TCL_READABLE) != 0) { return -1; } /* * Disallow seek on dead channels - channels that have been closed but not * yet been deallocated. Such channels can be found if the exit handler * for channel cleanup has run but the channel is still registered in an * interpreter. */ if (CheckForDeadChannel(NULL, statePtr)) { return -1; } /* * This operation should occur at the top of a channel stack. */ chanPtr = statePtr->topChanPtr; /* * Disallow seek on channels whose type does not have a seek procedure * defined. This means that the channel does not support seeking. */ if (Tcl_ChannelWideSeekProc(chanPtr->typePtr) == NULL) { Tcl_SetErrno(EINVAL); return -1; } /* * Compute how much input and output is buffered. If both input and output * is buffered, cannot compute the current position. */ inputBuffered = Tcl_InputBuffered(chan); outputBuffered = Tcl_OutputBuffered(chan); if ((inputBuffered != 0) && (outputBuffered != 0)) { Tcl_SetErrno(EFAULT); return -1; } /* * If we are seeking relative to the current position, compute the * corrected offset taking into account the amount of unread input. */ if (mode == SEEK_CUR) { offset -= inputBuffered; } /* * Discard any queued input - this input should not be read after the * seek. */ DiscardInputQueued(statePtr, 0); /* * Reset EOF and BLOCKED flags. We invalidate them by moving the access * point. Also clear CR related flags. */ if (GotFlag(statePtr, CHANNEL_EOF)) { statePtr->inputEncodingFlags |= TCL_ENCODING_START; } ResetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF | CHANNEL_BLOCKED | INPUT_SAW_CR); statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; /* * If the channel is in asynchronous output mode, switch it back to * synchronous mode and cancel any async flush that may be scheduled. * After the flush, the channel will be put back into asynchronous output * mode. */ wasAsync = 0; if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { wasAsync = 1; result = StackSetBlockMode(chanPtr, TCL_MODE_BLOCKING); if (result != 0) { return -1; } ResetFlag(statePtr, CHANNEL_NONBLOCKING); if (GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { ResetFlag(statePtr, BG_FLUSH_SCHEDULED); } } /* * If the flush fails we cannot recover the original position. In that * case the seek is not attempted because we do not know where the access * position is - instead we return the error. FlushChannel has already * called Tcl_SetErrno() to report the error upwards. If the flush * succeeds we do the seek also. */ if (FlushChannel(NULL, chanPtr, 0) != 0) { curPos = -1; } else { /* * Now seek to the new position in the channel as requested by the * caller. */ curPos = ChanSeek(chanPtr, offset, mode, &result); if (curPos == -1) { Tcl_SetErrno(result); } } /* * Restore to nonblocking mode if that was the previous behavior. * * NOTE: Even if there was an async flush active we do not restore it now * because we already flushed all the queued output, above. */ if (wasAsync) { SetFlag(statePtr, CHANNEL_NONBLOCKING); result = StackSetBlockMode(chanPtr, TCL_MODE_NONBLOCKING); if (result != 0) { return -1; } } return curPos; } /* *---------------------------------------------------------------------- * * Tcl_Tell -- * * Returns the position of the next character to be read/written on this * channel. * * Results: * A nonnegative integer on success, -1 on failure. If failed, use * Tcl_GetErrno() to retrieve the POSIX error code for the error that * occurred. * * Side effects: * None. * *---------------------------------------------------------------------- */ long long Tcl_Tell( Tcl_Channel chan) /* The channel to return pos for. */ { Channel *chanPtr = (Channel *) chan; /* The real IO channel. */ ChannelState *statePtr = chanPtr->state; /* State info for channel */ int inputBuffered, outputBuffered; /* # bytes held in buffers. */ int result; /* Of calling device driver. */ long long curPos; /* Position on device. */ if (CheckChannelErrors(statePtr, TCL_WRITABLE | TCL_READABLE) != 0) { return -1; } /* * Disallow tell on dead channels -- channels that have been closed but * not yet been deallocated. Such channels can be found if the exit * handler for channel cleanup has run but the channel is still registered * in an interpreter. */ if (CheckForDeadChannel(NULL, statePtr)) { return -1; } /* * This operation should occur at the top of a channel stack. */ chanPtr = statePtr->topChanPtr; /* * Disallow tell on channels whose type does not have a seek procedure * defined. This means that the channel does not support seeking. */ if (Tcl_ChannelWideSeekProc(chanPtr->typePtr) == NULL) { Tcl_SetErrno(EINVAL); return -1; } /* * Compute how much input and output is buffered. If both input and output * is buffered, cannot compute the current position. */ inputBuffered = Tcl_InputBuffered(chan); outputBuffered = Tcl_OutputBuffered(chan); /* * Get the current position in the device and compute the position where * the next character will be read or written. Note that we prefer the * wideSeekProc if that is available and non-NULL... */ curPos = ChanSeek(chanPtr, 0, SEEK_CUR, &result); if (curPos == -1) { Tcl_SetErrno(result); return -1; } if (inputBuffered != 0) { return curPos - inputBuffered; } return curPos + outputBuffered; } /* *--------------------------------------------------------------------------- * * Tcl_TruncateChannel -- * * Truncate a channel to the given length. * * Results: * TCL_OK on success, TCL_ERROR if the operation failed (e.g., is not * supported by the type of channel, or the underlying OS operation * failed in some way). * * Side effects: * Seeks the channel to the current location. Sets errno on OS error. * *--------------------------------------------------------------------------- */ int Tcl_TruncateChannel( Tcl_Channel chan, /* Channel to truncate. */ long long length) /* Length to truncate it to. */ { Channel *chanPtr = (Channel *) chan; Tcl_DriverTruncateProc *truncateProc = Tcl_ChannelTruncateProc(chanPtr->typePtr); int result; if (truncateProc == NULL) { /* * Feature not supported and it's not emulatable. Pretend it's * returned an EINVAL, a very generic error! */ Tcl_SetErrno(EINVAL); return TCL_ERROR; } if (!GotFlag(chanPtr->state, TCL_WRITABLE)) { /* * We require that the file was opened of writing. Do that check now * so that we only flush if we think we're going to succeed. */ Tcl_SetErrno(EINVAL); return TCL_ERROR; } /* * Seek first to force a total flush of all pending buffers and ditch any * preread input data. */ WillWrite(chanPtr); if (WillRead(chanPtr) == -1) { return TCL_ERROR; } /* * We're all flushed to disk now and we also don't have any unfortunate * input baggage around either; can truncate with impunity. */ result = truncateProc(chanPtr->instanceData, length); if (result != 0) { Tcl_SetErrno(result); return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * CheckChannelErrors -- * * See if the channel is in an ready state and can perform the desired * operation. * * Results: * The return value is 0 if the channel is OK, otherwise the return value * is -1 and errno is set to indicate the error. * * Side effects: * May clear the EOF and/or BLOCKED bits if reading from channel. * *--------------------------------------------------------------------------- */ static int CheckChannelErrors( ChannelState *statePtr, /* Channel to check. */ int flags) /* Test if channel supports desired operation: * TCL_READABLE, TCL_WRITABLE. Also indicates * Raw read or write for special close * processing */ { int direction = flags & (TCL_READABLE|TCL_WRITABLE); /* * Check for unreported error. */ if (statePtr->unreportedError != 0) { Tcl_SetErrno(statePtr->unreportedError); statePtr->unreportedError = 0; /* * TIP #219, Tcl Channel Reflection API. * Move a deferred error message back into the channel bypass. */ if (statePtr->chanMsg != NULL) { TclDecrRefCount(statePtr->chanMsg); } statePtr->chanMsg = statePtr->unreportedMsg; statePtr->unreportedMsg = NULL; return -1; } /* * Only the raw read and write operations are allowed during close in * order to drain data from stacked channels. */ if (GotFlag(statePtr, CHANNEL_CLOSED) && !(flags & CHANNEL_RAW_MODE)) { Tcl_SetErrno(EACCES); return -1; } /* * Fail if the channel is not opened for desired operation. */ if (GotFlag(statePtr, direction) == 0) { Tcl_SetErrno(EACCES); return -1; } /* * Fail if the channel is in the middle of a background copy. * * Don't do this tests for raw channels here or else the chaining in the * transformation drivers will fail with 'file busy' error instead of * retrieving and transforming the data to copy. */ if (BUSY_STATE(statePtr, flags) && ((flags & CHANNEL_RAW_MODE) == 0)) { Tcl_SetErrno(EBUSY); return -1; } if (direction == TCL_READABLE) { ResetFlag(statePtr, CHANNEL_NEED_MORE_DATA); } return 0; } /* *---------------------------------------------------------------------- * * TclChanIsBinary -- * * Returns 1 if the channel is a binary channel, 0 otherwise. * * Results: * 1 or 0, always. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclChanIsBinary( Tcl_Channel chan) /* Does this channel have EOF? */ { ChannelState *statePtr = ((Channel *) chan)->state; /* State of real channel structure. */ return ((statePtr->encoding == GetBinaryEncoding()) && !statePtr->inEofChar && (!GotFlag(statePtr, TCL_READABLE) || (statePtr->inputTranslation == TCL_TRANSLATE_LF)) && (!GotFlag(statePtr, TCL_WRITABLE) || (statePtr->outputTranslation == TCL_TRANSLATE_LF))); } /* *---------------------------------------------------------------------- * * Tcl_Eof -- * * Returns 1 if the channel is at EOF, 0 otherwise. * * Results: * 1 or 0, always. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_Eof( Tcl_Channel chan) /* Does this channel have EOF? */ { ChannelState *statePtr = ((Channel *) chan)->state; /* State of real channel structure. */ if (GotFlag(statePtr, CHANNEL_ENCODING_ERROR)) { return 0; } return GotFlag(statePtr, CHANNEL_EOF) ? 1 : 0; } /* *---------------------------------------------------------------------- * * TclChannelGetBlockingMode -- * * Returns 1 if the channel is in blocking mode (default), 0 otherwise. * * Results: * 1 or 0, always. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclChannelGetBlockingMode( Tcl_Channel chan) { ChannelState *statePtr = ((Channel *) chan)->state; /* State of real channel structure. */ return GotFlag(statePtr, CHANNEL_NONBLOCKING) ? 0 : 1; } /* *---------------------------------------------------------------------- * * Tcl_InputBlocked -- * * Returns 1 if input is blocked on this channel, 0 otherwise. * * Results: * 0 or 1, always. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_InputBlocked( Tcl_Channel chan) /* Is this channel blocked? */ { ChannelState *statePtr = ((Channel *) chan)->state; /* State of real channel structure. */ return GotFlag(statePtr, CHANNEL_BLOCKED) ? 1 : 0; } /* *---------------------------------------------------------------------- * * Tcl_InputBuffered -- * * Returns the number of bytes of input currently buffered in the common * internal buffer of a channel. * * Results: * The number of input bytes buffered, or zero if the channel is not open * for reading. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_InputBuffered( Tcl_Channel chan) /* The channel to query. */ { ChannelState *statePtr = ((Channel *) chan)->state; /* State of real channel structure. */ ChannelBuffer *bufPtr; int bytesBuffered; for (bytesBuffered = 0, bufPtr = statePtr->inQueueHead; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { bytesBuffered += BytesLeft(bufPtr); } /* * Remember the bytes in the topmost pushback area. */ for (bufPtr = statePtr->topChanPtr->inQueueHead; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { bytesBuffered += BytesLeft(bufPtr); } return bytesBuffered; } /* *---------------------------------------------------------------------- * * Tcl_OutputBuffered -- * * Returns the number of bytes of output currently buffered in the common * internal buffer of a channel. * * Results: * The number of output bytes buffered, or zero if the channel is not open * for writing. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_OutputBuffered( Tcl_Channel chan) /* The channel to query. */ { ChannelState *statePtr = ((Channel *) chan)->state; /* State of real channel structure. */ ChannelBuffer *bufPtr; int bytesBuffered; for (bytesBuffered = 0, bufPtr = statePtr->outQueueHead; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { bytesBuffered += BytesLeft(bufPtr); } if (statePtr->curOutPtr != NULL) { ChannelBuffer *curOutPtr = statePtr->curOutPtr; if (IsBufferReady(curOutPtr)) { bytesBuffered += BytesLeft(curOutPtr); } } return bytesBuffered; } /* *---------------------------------------------------------------------- * * Tcl_ChannelBuffered -- * * Returns the number of bytes of input currently buffered in the * internal buffer (push back area) of a channel. * * Results: * The number of input bytes buffered, or zero if the channel is not open * for reading. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_ChannelBuffered( Tcl_Channel chan) /* The channel to query. */ { Channel *chanPtr = (Channel *) chan; /* Real channel structure. */ ChannelBuffer *bufPtr; int bytesBuffered = 0; for (bufPtr = chanPtr->inQueueHead; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { bytesBuffered += BytesLeft(bufPtr); } return bytesBuffered; } /* *---------------------------------------------------------------------- * * Tcl_SetChannelBufferSize -- * * Sets the size of buffers to allocate to store input or output in the * channel. The size must be between 1 byte and 1 MByte. * * Results: * None. * * Side effects: * Sets the size of buffers subsequently allocated for this channel. * *---------------------------------------------------------------------- */ void Tcl_SetChannelBufferSize( Tcl_Channel chan, /* The channel whose buffer size to set. */ Tcl_Size sz) /* The size to set. */ { ChannelState *statePtr; /* State of real channel structure. */ /* * Clip the buffer size to force it into the [1,1M] range */ if (sz < 1) { sz = 1; } else if (sz > MAX_CHANNEL_BUFFER_SIZE) { sz = MAX_CHANNEL_BUFFER_SIZE; } statePtr = ((Channel *) chan)->state; if (statePtr->bufSize == sz) { return; } statePtr->bufSize = sz; /* * If bufsize changes, need to get rid of old utility buffer. */ if (statePtr->saveInBufPtr != NULL) { RecycleBuffer(statePtr, statePtr->saveInBufPtr, 1); statePtr->saveInBufPtr = NULL; } if ((statePtr->inQueueHead != NULL) && (statePtr->inQueueHead->nextPtr == NULL) && IsBufferEmpty(statePtr->inQueueHead)) { RecycleBuffer(statePtr, statePtr->inQueueHead, 1); statePtr->inQueueHead = NULL; statePtr->inQueueTail = NULL; } } /* *---------------------------------------------------------------------- * * Tcl_GetChannelBufferSize -- * * Retrieves the size of buffers to allocate for this channel. * * Results: * The size. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_GetChannelBufferSize( Tcl_Channel chan) /* The channel for which to find the buffer * size. */ { ChannelState *statePtr = ((Channel *) chan)->state; /* State of real channel structure. */ return statePtr->bufSize; } /* *---------------------------------------------------------------------- * * Tcl_BadChannelOption -- * * This procedure generates a "bad option" error message in an (optional) * interpreter. It is used by channel drivers when a invalid Set/Get * option is requested. Its purpose is to concatenate the generic options * list to the specific ones and factorize the generic options error * message string. * * Results: * TCL_ERROR. * * Side effects: * An error message is generated in interp's result object to indicate * that a command was invoked with a bad option. The message has the * form: * bad option "blah": should be one of * <...generic options...>+<...specific options...> * "blah" is the optionName argument and "" is a space * separated list of specific option words. The function takes good care * of inserting minus signs before each option, commas after, and an "or" * before the last option. * *---------------------------------------------------------------------- */ int Tcl_BadChannelOption( Tcl_Interp *interp, /* Current interpreter (can be NULL).*/ const char *optionName, /* 'bad option' name */ const char *optionList) /* Specific options list to append to the * standard generic options. Can be NULL for * generic options only. */ { if (interp != NULL) { const char *genericopt = "blocking buffering buffersize encoding eofchar profile translation"; const char **argv; Tcl_Size argc, i; Tcl_DString ds; Tcl_Obj *errObj; Tcl_DStringInit(&ds); Tcl_DStringAppend(&ds, genericopt, -1); if (optionList && (*optionList)) { TclDStringAppendLiteral(&ds, " "); Tcl_DStringAppend(&ds, optionList, -1); } if (Tcl_SplitList(interp, Tcl_DStringValue(&ds), &argc, &argv) != TCL_OK) { Tcl_Panic("malformed option list in channel driver"); } Tcl_ResetResult(interp); errObj = Tcl_ObjPrintf("bad option \"%s\": should be one of ", optionName ? optionName : ""); argc--; for (i = 0; i < argc; i++) { Tcl_AppendPrintfToObj(errObj, "-%s, ", argv[i]); } Tcl_AppendPrintfToObj(errObj, "or -%s", argv[i]); Tcl_SetObjResult(interp, errObj); Tcl_DStringFree(&ds); Tcl_Free((void *)argv); } Tcl_SetErrno(EINVAL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_GetChannelOption -- * * Gets a mode associated with an IO channel. If the optionName arg is * non NULL, retrieves the value of that option. If the optionName arg is * NULL, retrieves a list of alternating option names and values for the * given channel. * * Results: * A standard Tcl result. Also sets the supplied DString to the string * value of the option(s) returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_GetChannelOption( Tcl_Interp *interp, /* For error reporting - can be NULL. */ Tcl_Channel chan, /* Channel on which to get option. */ const char *optionName, /* Option to get. */ Tcl_DString *dsPtr) /* Where to store value(s). */ { size_t len; /* Length of optionName string. */ char optionVal[128]; /* Buffer for snprintf. */ Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; /* State info for channel */ int flags; /* * Disallow options on dead channels -- channels that have been closed but * not yet been deallocated. Such channels can be found if the exit * handler for channel cleanup has run but the channel is still registered * in an interpreter. */ if (CheckForDeadChannel(interp, statePtr)) { return TCL_ERROR; } /* * This operation should occur at the top of a channel stack. */ chanPtr = statePtr->topChanPtr; /* * If we are in the middle of a background copy, use the saved flags. */ if (statePtr->csPtrR) { flags = statePtr->csPtrR->readFlags; } else if (statePtr->csPtrW) { flags = statePtr->csPtrW->writeFlags; } else { flags = statePtr->flags; } /* * If the optionName is NULL it means that we want a list of all options * and values. */ if (optionName == NULL) { len = 0; } else { len = strlen(optionName); } if (len == 0 || HaveOpt(2, "-blocking")) { if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-blocking"); } Tcl_DStringAppendElement(dsPtr, (flags & CHANNEL_NONBLOCKING) ? "0" : "1"); if (len > 0) { return TCL_OK; } } if (len == 0 || HaveOpt(7, "-buffering")) { if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-buffering"); } if (flags & CHANNEL_LINEBUFFERED) { Tcl_DStringAppendElement(dsPtr, "line"); } else if (flags & CHANNEL_UNBUFFERED) { Tcl_DStringAppendElement(dsPtr, "none"); } else { Tcl_DStringAppendElement(dsPtr, "full"); } if (len > 0) { return TCL_OK; } } if (len == 0 || HaveOpt(7, "-buffersize")) { if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-buffersize"); } TclFormatInt(optionVal, statePtr->bufSize); Tcl_DStringAppendElement(dsPtr, optionVal); if (len > 0) { return TCL_OK; } } if (len == 0 || HaveOpt(2, "-encoding")) { if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-encoding"); } Tcl_DStringAppendElement(dsPtr, Tcl_GetEncodingName(statePtr->encoding)); if (len > 0) { return TCL_OK; } } if (len == 0 || HaveOpt(2, "-eofchar")) { char buf[4] = ""; if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-eofchar"); } if ((flags & TCL_READABLE) && (statePtr->inEofChar != 0)) { snprintf(buf, sizeof(buf), "%c", statePtr->inEofChar); } if (len > 0) { Tcl_DStringAppend(dsPtr, buf, -1); return TCL_OK; } Tcl_DStringAppendElement(dsPtr, buf); } if (len == 0 || HaveOpt(1, "-profile")) { int profile; const char *profileName; if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-profile"); } /* Note currently input and output profiles are same */ profile = ENCODING_PROFILE_GET(statePtr->inputEncodingFlags); profileName = TclEncodingProfileIdToName(interp, profile); if (profileName == NULL) { return TCL_ERROR; } Tcl_DStringAppendElement(dsPtr, profileName); if (len > 0) { return TCL_OK; } } if (len == 0 || HaveOpt(1, "-translation")) { if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-translation"); } if (((flags & (TCL_READABLE|TCL_WRITABLE)) == (TCL_READABLE|TCL_WRITABLE)) && (len == 0)) { Tcl_DStringStartSublist(dsPtr); } if (flags & TCL_READABLE) { if (statePtr->inputTranslation == TCL_TRANSLATE_AUTO) { Tcl_DStringAppendElement(dsPtr, "auto"); } else if (statePtr->inputTranslation == TCL_TRANSLATE_CR) { Tcl_DStringAppendElement(dsPtr, "cr"); } else if (statePtr->inputTranslation == TCL_TRANSLATE_CRLF) { Tcl_DStringAppendElement(dsPtr, "crlf"); } else { Tcl_DStringAppendElement(dsPtr, "lf"); } } if (flags & TCL_WRITABLE) { if (statePtr->outputTranslation == TCL_TRANSLATE_AUTO) { Tcl_DStringAppendElement(dsPtr, "auto"); } else if (statePtr->outputTranslation == TCL_TRANSLATE_CR) { Tcl_DStringAppendElement(dsPtr, "cr"); } else if (statePtr->outputTranslation == TCL_TRANSLATE_CRLF) { Tcl_DStringAppendElement(dsPtr, "crlf"); } else { Tcl_DStringAppendElement(dsPtr, "lf"); } } if (!(flags & (TCL_READABLE|TCL_WRITABLE))) { /* * Not readable or writable (e.g. server socket) */ Tcl_DStringAppendElement(dsPtr, "auto"); } if (((flags & (TCL_READABLE|TCL_WRITABLE)) == (TCL_READABLE|TCL_WRITABLE)) && (len == 0)) { Tcl_DStringEndSublist(dsPtr); } if (len > 0) { return TCL_OK; } } if (chanPtr->typePtr->getOptionProc != NULL) { /* * Let the driver specific handle additional options and result code * and message. */ return chanPtr->typePtr->getOptionProc(chanPtr->instanceData, interp, optionName, dsPtr); } else { /* * No driver specific options case. */ if (len == 0) { return TCL_OK; } return Tcl_BadChannelOption(interp, optionName, NULL); } } /* *--------------------------------------------------------------------------- * * Tcl_SetChannelOption -- * * Sets an option on a channel. * * Results: * A standard Tcl result. On error, sets interp's result object if * interp is not NULL. * * Side effects: * May modify an option on a device. * *--------------------------------------------------------------------------- */ int Tcl_SetChannelOption( Tcl_Interp *interp, /* For error reporting - can be NULL. */ Tcl_Channel chan, /* Channel on which to set mode. */ const char *optionName, /* Which option to set? */ const char *newValue) /* New value for option. */ { Channel *chanPtr = (Channel *) chan; /* The real IO channel. */ ChannelState *statePtr = chanPtr->state; /* State info for channel */ size_t len; /* Length of optionName string. */ Tcl_Size argc; const char **argv = NULL; /* * If the channel is in the middle of a background copy, fail. */ if (statePtr->csPtrR || statePtr->csPtrW) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "unable to set channel options: background copy in" " progress", -1)); } return TCL_ERROR; } /* * Disallow options on dead channels -- channels that have been closed but * not yet been deallocated. Such channels can be found if the exit * handler for channel cleanup has run but the channel is still registered * in an interpreter. */ if (CheckForDeadChannel(NULL, statePtr)) { return TCL_ERROR; } /* * This operation should occur at the top of a channel stack. */ chanPtr = statePtr->topChanPtr; len = strlen(optionName); if (HaveOpt(2, "-blocking")) { int newMode; if (Tcl_GetBoolean(interp, newValue, &newMode) == TCL_ERROR) { return TCL_ERROR; } if (newMode) { newMode = TCL_MODE_BLOCKING; } else { newMode = TCL_MODE_NONBLOCKING; } return SetBlockMode(interp, chanPtr, newMode); } else if (HaveOpt(7, "-buffering")) { len = strlen(newValue); if ((newValue[0] == 'f') && (strncmp(newValue, "full", len) == 0)) { ResetFlag(statePtr, CHANNEL_UNBUFFERED | CHANNEL_LINEBUFFERED); } else if ((newValue[0] == 'l') && (strncmp(newValue, "line", len) == 0)) { ResetFlag(statePtr, CHANNEL_UNBUFFERED); SetFlag(statePtr, CHANNEL_LINEBUFFERED); } else if ((newValue[0] == 'n') && (strncmp(newValue, "none", len) == 0)) { ResetFlag(statePtr, CHANNEL_LINEBUFFERED); SetFlag(statePtr, CHANNEL_UNBUFFERED); } else if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad value for -buffering: must be one of" " full, line, or none", -1)); return TCL_ERROR; } return TCL_OK; } else if (HaveOpt(7, "-buffersize")) { Tcl_WideInt newBufferSize; Tcl_Obj obj; int code; obj.refCount = 1; obj.bytes = (char *)newValue; obj.length = strlen(newValue); obj.typePtr = NULL; code = Tcl_GetWideIntFromObj(interp, &obj, &newBufferSize); TclFreeInternalRep(&obj); if (code == TCL_ERROR) { return TCL_ERROR; } Tcl_SetChannelBufferSize(chan, newBufferSize); return TCL_OK; } else if (HaveOpt(2, "-encoding")) { Tcl_Encoding encoding; int profile; if ((newValue[0] == '\0') || !strcmp(newValue, "binary")) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown encoding \"%s\": No longer supported.\n" "\tplease use either \"-translation binary\" " "or \"-encoding iso8859-1\"", newValue)); } return TCL_ERROR; } else { encoding = Tcl_GetEncoding(interp, newValue); if (encoding == NULL) { return TCL_ERROR; } } /* * When the channel has an escape sequence driven encoding such as * iso2022, the terminated escape sequence must write to the buffer. */ if ((statePtr->encoding != GetBinaryEncoding()) && !(statePtr->outputEncodingFlags & TCL_ENCODING_START) && (CheckChannelErrors(statePtr, TCL_WRITABLE) == 0)) { statePtr->outputEncodingFlags |= TCL_ENCODING_END; WriteChars(chanPtr, "", 0); } Tcl_FreeEncoding(statePtr->encoding); statePtr->encoding = encoding; statePtr->inputEncodingState = NULL; profile = ENCODING_PROFILE_GET(statePtr->inputEncodingFlags); statePtr->inputEncodingFlags = TCL_ENCODING_START; ENCODING_PROFILE_SET(statePtr->inputEncodingFlags, profile); statePtr->outputEncodingState = NULL; statePtr->outputEncodingFlags = TCL_ENCODING_START; ENCODING_PROFILE_SET(statePtr->outputEncodingFlags, profile); /* Same as input */ ResetFlag(statePtr, CHANNEL_NEED_MORE_DATA|CHANNEL_ENCODING_ERROR); UpdateInterest(chanPtr); return TCL_OK; } else if (HaveOpt(2, "-eofchar")) { if (!newValue[0] || (!(newValue[0] & 0x80) && (!newValue[1] #ifndef TCL_NO_DEPRECATED || !strcmp(newValue+1, " {}") #endif ))) { if (GotFlag(statePtr, TCL_READABLE)) { statePtr->inEofChar = newValue[0]; } } else { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad value for -eofchar: must be non-NUL ASCII" " character", TCL_INDEX_NONE)); } Tcl_Free((void *)argv); return TCL_ERROR; } if (argv != NULL) { Tcl_Free((void *)argv); } /* * [Bug 930851] Reset EOF and BLOCKED flags. Changing the character * which signals eof can transform a current eof condition into a 'go * ahead'. Ditto for blocked. */ if (GotFlag(statePtr, CHANNEL_EOF)) { statePtr->inputEncodingFlags |= TCL_ENCODING_START; } ResetFlag(statePtr, CHANNEL_EOF|CHANNEL_STICKY_EOF|CHANNEL_BLOCKED); statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; return TCL_OK; } else if (HaveOpt(1, "-profile")) { int profile; if (TclEncodingProfileNameToId(interp, newValue, &profile) != TCL_OK) { return TCL_ERROR; } ENCODING_PROFILE_SET(statePtr->inputEncodingFlags, profile); ENCODING_PROFILE_SET(statePtr->outputEncodingFlags, profile); ResetFlag(statePtr, CHANNEL_NEED_MORE_DATA|CHANNEL_ENCODING_ERROR); return TCL_OK; } else if (HaveOpt(1, "-translation")) { const char *readMode, *writeMode; if (Tcl_SplitList(interp, newValue, &argc, &argv) == TCL_ERROR) { return TCL_ERROR; } if (argc == 1) { readMode = GotFlag(statePtr, TCL_READABLE) ? argv[0] : NULL; writeMode = GotFlag(statePtr, TCL_WRITABLE) ? argv[0] : NULL; } else if (argc == 2) { readMode = GotFlag(statePtr, TCL_READABLE) ? argv[0] : NULL; writeMode = GotFlag(statePtr, TCL_WRITABLE) ? argv[1] : NULL; } else { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad value for -translation: must be a one or two" " element list", -1)); } Tcl_Free((void *)argv); return TCL_ERROR; } if (readMode) { TclEolTranslation translation; if (*readMode == '\0') { translation = statePtr->inputTranslation; } else if (strcmp(readMode, "auto") == 0) { translation = TCL_TRANSLATE_AUTO; } else if (strcmp(readMode, "binary") == 0) { translation = TCL_TRANSLATE_LF; statePtr->inEofChar = 0; Tcl_FreeEncoding(statePtr->encoding); statePtr->encoding = Tcl_GetEncoding(NULL, "iso8859-1"); } else if (strcmp(readMode, "lf") == 0) { translation = TCL_TRANSLATE_LF; } else if (strcmp(readMode, "cr") == 0) { translation = TCL_TRANSLATE_CR; } else if (strcmp(readMode, "crlf") == 0) { translation = TCL_TRANSLATE_CRLF; } else if (strcmp(readMode, "platform") == 0) { translation = TCL_PLATFORM_TRANSLATION; } else { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad value for -translation: must be one of " "auto, binary, cr, lf, crlf, or platform", -1)); } Tcl_Free((void *)argv); return TCL_ERROR; } /* * Reset the EOL flags since we need to look at any buffered data * to see if the new translation mode allows us to complete the * line. */ if (translation != statePtr->inputTranslation) { statePtr->inputTranslation = translation; ResetFlag(statePtr, INPUT_SAW_CR | CHANNEL_NEED_MORE_DATA); UpdateInterest(chanPtr); } } if (writeMode) { if (*writeMode == '\0') { /* Do nothing. */ } else if (strcmp(writeMode, "auto") == 0) { /* * This is a hack to get TCP sockets to produce output in CRLF * mode if they are being set into AUTO mode. A better * solution for achieving this effect will be coded later. */ if (strcmp(Tcl_ChannelName(chanPtr->typePtr), "tcp") == 0) { statePtr->outputTranslation = TCL_TRANSLATE_CRLF; } else { statePtr->outputTranslation = TCL_PLATFORM_TRANSLATION; } } else if (strcmp(writeMode, "binary") == 0) { statePtr->outputTranslation = TCL_TRANSLATE_LF; Tcl_FreeEncoding(statePtr->encoding); statePtr->encoding = Tcl_GetEncoding(NULL, "iso8859-1"); } else if (strcmp(writeMode, "lf") == 0) { statePtr->outputTranslation = TCL_TRANSLATE_LF; } else if (strcmp(writeMode, "cr") == 0) { statePtr->outputTranslation = TCL_TRANSLATE_CR; } else if (strcmp(writeMode, "crlf") == 0) { statePtr->outputTranslation = TCL_TRANSLATE_CRLF; } else if (strcmp(writeMode, "platform") == 0) { statePtr->outputTranslation = TCL_PLATFORM_TRANSLATION; } else { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad value for -translation: must be one of " "auto, binary, cr, lf, crlf, or platform", -1)); } Tcl_Free((void *)argv); return TCL_ERROR; } } Tcl_Free((void *)argv); return TCL_OK; } else if (chanPtr->typePtr->setOptionProc != NULL) { return chanPtr->typePtr->setOptionProc(chanPtr->instanceData, interp, optionName, newValue); } else { return Tcl_BadChannelOption(interp, optionName, NULL); } return TCL_OK; } /* *---------------------------------------------------------------------- * * CleanupChannelHandlers -- * * Removes channel handlers that refer to the supplied interpreter, so * that if the actual channel is not closed now, these handlers will not * run on subsequent events on the channel. This would be erroneous, * because the interpreter no longer has a reference to this channel. * * Results: * None. * * Side effects: * Removes channel handlers. * *---------------------------------------------------------------------- */ static void CleanupChannelHandlers( Tcl_Interp *interp, Channel *chanPtr) { ChannelState *statePtr = chanPtr->state; /* State info for channel */ EventScriptRecord *sPtr, *prevPtr, *nextPtr; /* * Remove fileevent records on this channel that refer to the given * interpreter. */ for (sPtr = statePtr->scriptRecordPtr, prevPtr = NULL; sPtr != NULL; sPtr = nextPtr) { nextPtr = sPtr->nextPtr; if (sPtr->interp == interp) { if (prevPtr == NULL) { statePtr->scriptRecordPtr = nextPtr; } else { prevPtr->nextPtr = nextPtr; } Tcl_DeleteChannelHandler((Tcl_Channel) chanPtr, TclChannelEventScriptInvoker, sPtr); TclDecrRefCount(sPtr->scriptPtr); Tcl_Free(sPtr); } else { prevPtr = sPtr; } } } /* *---------------------------------------------------------------------- * * Tcl_NotifyChannel -- * * This procedure is called by a channel driver when a driver detects an * event on a channel. This procedure is responsible for actually * handling the event by invoking any channel handler callbacks. * * Results: * None. * * Side effects: * Whatever the channel handler callback procedure does. * *---------------------------------------------------------------------- */ void Tcl_NotifyChannel( Tcl_Channel channel, /* Channel that detected an event. */ int mask) /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, or TCL_EXCEPTION: indicates * which events were detected. */ { Channel *chanPtr = (Channel *) channel; ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelHandler *chPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); NextChannelHandler nh; Channel *upChanPtr; const Tcl_ChannelType *upTypePtr; /* * In contrast to the other API functions this procedure walks towards the * top of a stack and not down from it. * * The channel calling this procedure is the one who generated the event, * and thus does not take part in handling it. IOW, its HandlerProc is not * called, instead we begin with the channel above it. * * This behaviour also allows the transformation channels to generate * their own events and pass them upward. */ while (mask && (chanPtr->upChanPtr != NULL)) { Tcl_DriverHandlerProc *upHandlerProc; upChanPtr = chanPtr->upChanPtr; upTypePtr = upChanPtr->typePtr; upHandlerProc = Tcl_ChannelHandlerProc(upTypePtr); if (upHandlerProc != NULL) { mask = upHandlerProc(upChanPtr->instanceData, mask); } /* * ELSE: Ignore transformations which are unable to handle the event * coming from below. Assume that they don't change the mask and pass * it on. */ chanPtr = upChanPtr; } channel = (Tcl_Channel) chanPtr; /* * Here we have either reached the top of the stack or the mask is empty. * We break out of the procedure if it is the latter. */ if (!mask) { return; } /* * We are now above the topmost channel in a stack and have events left. * Now call the channel handlers as usual. * * Preserve the channel struct in case the script closes it. */ TclChannelPreserve((Tcl_Channel)channel); Tcl_Preserve(statePtr); /* * Avoid processing if the channel owner has been changed. */ if (statePtr->managingThread != Tcl_GetCurrentThread()) { goto done; } /* * If we are flushing in the background, be sure to call FlushChannel for * writable events. Note that we have to discard the writable event so we * don't call any write handlers before the flush is complete. */ if (GotFlag(statePtr, BG_FLUSH_SCHEDULED) && (mask & TCL_WRITABLE)) { if (0 == FlushChannel(NULL, chanPtr, 1)) { mask &= ~TCL_WRITABLE; } } /* * Add this invocation to the list of recursive invocations of * Tcl_NotifyChannel. */ nh.nextHandlerPtr = NULL; nh.nestedHandlerPtr = tsdPtr->nestedHandlerPtr; tsdPtr->nestedHandlerPtr = &nh; for (chPtr = statePtr->chPtr; chPtr != NULL; ) { /* * If this channel handler is interested in any of the events that * have occurred on the channel, invoke its procedure. */ if ((chPtr->mask & mask) != 0) { nh.nextHandlerPtr = chPtr->nextPtr; chPtr->proc(chPtr->clientData, chPtr->mask & mask); chPtr = nh.nextHandlerPtr; } else { chPtr = chPtr->nextPtr; } /* * Stop if the channel owner has been changed in-between. */ if (chanPtr->state->managingThread != Tcl_GetCurrentThread()) { goto done; } } /* * Update the notifier interest, since it may have changed after invoking * event handlers. Skip that if the channel was deleted in the call to the * channel handler. */ if (chanPtr->typePtr != NULL) { /* * TODO: This call may not be needed. If a handler induced a * change in interest, that handler should have made its own * UpdateInterest() call, one would think. */ UpdateInterest(chanPtr); } done: Tcl_Release(statePtr); TclChannelRelease(channel); tsdPtr->nestedHandlerPtr = nh.nestedHandlerPtr; } /* *---------------------------------------------------------------------- * * UpdateInterest -- * * Arrange for the notifier to call us back at appropriate times based on * the current state of the channel. * * Results: * None. * * Side effects: * May schedule a timer or driver handler. * *---------------------------------------------------------------------- */ static void UpdateInterest( Channel *chanPtr) /* Channel to update. */ { ChannelState *statePtr = chanPtr->state; /* State info for channel */ int mask = statePtr->interestMask; if (chanPtr->typePtr == NULL) { /* Do not update interest on a closed channel */ return; } /* * If there are flushed buffers waiting to be written, then we need to * watch for the channel to become writable. */ if (GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { mask |= TCL_WRITABLE; } /* * If there is data in the input queue, and we aren't waiting for more * data, then we need to schedule a timer so we don't block in the * notifier. Also, cancel the read interest so we don't get duplicate * events. */ if (mask & TCL_READABLE) { if (!GotFlag(statePtr, CHANNEL_NEED_MORE_DATA) && (statePtr->inQueueHead != NULL) && IsBufferReady(statePtr->inQueueHead)) { mask &= ~TCL_READABLE; /* * Andreas Kupries, April 11, 2003 * * Some operating systems (Solaris 2.6 and higher (but not Solaris * 2.5, go figure)) generate READABLE and EXCEPTION events when * select()'ing [*] on a plain file, even if EOF was not yet * reached. This is a problem in the following situation: * * - An extension asks to get both READABLE and EXCEPTION events. * - It reads data into a buffer smaller than the buffer used by * Tcl itself. * - It does not process all events in the event queue, but only * one, at least in some situations. * * In that case we can get into a situation where * * - Tcl drops READABLE here, because it has data in its own * buffers waiting to be read by the extension. * - A READABLE event is synthesized via timer. * - The OS still reports the EXCEPTION condition on the file. * - And the extension gets the EXCEPTION event first, and handles * this as EOF. * * End result ==> Premature end of reading from a file. * * The concrete example is 'Expect', and its [expect] command * (and at the C-level, deep in the bowels of Expect, * 'exp_get_next_event'. See marker 'SunOS' for commentary in * that function too). * * [*] As the Tcl notifier does. See also for marker 'SunOS' in * file 'exp_event.c' of Expect. * * Our solution here is to drop the interest in the EXCEPTION * events too. This compiles on all platforms, and also passes the * testsuite on all of them. */ mask &= ~TCL_EXCEPTION; if (!statePtr->timer) { TclChannelPreserve((Tcl_Channel)chanPtr); statePtr->timerChanPtr = chanPtr; statePtr->timer = Tcl_CreateTimerHandler(SYNTHETIC_EVENT_TIME, ChannelTimerProc, chanPtr); } } } ChanWatch(chanPtr, mask); } /* *---------------------------------------------------------------------- * * ChannelTimerProc -- * * Timer handler scheduled by UpdateInterest to monitor the channel * buffers until they are empty. * * Results: * None. * * Side effects: * May invoke channel handlers. * *---------------------------------------------------------------------- */ static void ChannelTimerProc( void *clientData) { Channel *chanPtr = (Channel *)clientData; /* State info for channel */ ChannelState *statePtr = chanPtr->state; if (chanPtr->typePtr == NULL) { statePtr->timer = NULL; TclChannelRelease((Tcl_Channel)statePtr->timerChanPtr); statePtr->timerChanPtr = NULL; } else { if (!GotFlag(statePtr, CHANNEL_NEED_MORE_DATA) && (statePtr->interestMask & TCL_READABLE) && (statePtr->inQueueHead != NULL) && IsBufferReady(statePtr->inQueueHead)) { /* * Restart the timer in case a channel handler reenters the event loop * before UpdateInterest gets called by Tcl_NotifyChannel. */ statePtr->timer = Tcl_CreateTimerHandler(SYNTHETIC_EVENT_TIME, ChannelTimerProc,chanPtr); Tcl_Preserve(statePtr); Tcl_NotifyChannel((Tcl_Channel) chanPtr, TCL_READABLE); Tcl_Release(statePtr); } else { statePtr->timer = NULL; UpdateInterest(chanPtr); TclChannelRelease((Tcl_Channel)statePtr->timerChanPtr); statePtr->timerChanPtr = NULL; } } } static void DeleteTimerHandler( ChannelState *statePtr) { if (statePtr->timer != NULL) { Tcl_DeleteTimerHandler(statePtr->timer); statePtr->timer = NULL; TclChannelRelease((Tcl_Channel)statePtr->timerChanPtr); statePtr->timerChanPtr = NULL; } } /* *---------------------------------------------------------------------- * * Tcl_CreateChannelHandler -- * * Arrange for a given procedure to be invoked whenever the channel * indicated by the chanPtr arg becomes readable or writable. * * Results: * None. * * Side effects: * From now on, whenever the I/O channel given by chanPtr becomes ready * in the way indicated by mask, proc will be invoked. See the manual * entry for details on the calling sequence to proc. If there is already * an event handler for chan, proc and clientData, then the mask will be * updated. * *---------------------------------------------------------------------- */ void Tcl_CreateChannelHandler( Tcl_Channel chan, /* The channel to create the handler for. */ int mask, /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, and TCL_EXCEPTION: indicates * conditions under which proc should be * called. Use 0 to disable a registered * handler. */ Tcl_ChannelProc *proc, /* Procedure to call for each selected * event. */ void *clientData) /* Arbitrary data to pass to proc. */ { ChannelHandler *chPtr; Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; /* State info for channel */ /* * Check whether this channel handler is not already registered. If it is * not, create a new record, else reuse existing record (smash current * values). */ for (chPtr = statePtr->chPtr; chPtr != NULL; chPtr = chPtr->nextPtr) { if ((chPtr->chanPtr == chanPtr) && (chPtr->proc == proc) && (chPtr->clientData == clientData)) { break; } } if (chPtr == NULL) { chPtr = (ChannelHandler *)Tcl_Alloc(sizeof(ChannelHandler)); chPtr->mask = 0; chPtr->proc = proc; chPtr->clientData = clientData; chPtr->chanPtr = chanPtr; chPtr->nextPtr = statePtr->chPtr; statePtr->chPtr = chPtr; } /* * The remainder of the initialization below is done regardless of whether * this is a new record or a modification of an old one. */ chPtr->mask = mask; /* * Recompute the interest mask for the channel - this call may actually be * disabling an existing handler. */ statePtr->interestMask = 0; for (chPtr = statePtr->chPtr; chPtr != NULL; chPtr = chPtr->nextPtr) { statePtr->interestMask |= chPtr->mask; } UpdateInterest(statePtr->topChanPtr); } /* *---------------------------------------------------------------------- * * Tcl_DeleteChannelHandler -- * * Cancel a previously arranged callback arrangement for an IO channel. * * Results: * None. * * Side effects: * If a callback was previously registered for this chan, proc and * clientData, it is removed and the callback will no longer be called * when the channel becomes ready for IO. * *---------------------------------------------------------------------- */ void Tcl_DeleteChannelHandler( Tcl_Channel chan, /* The channel for which to remove the * callback. */ Tcl_ChannelProc *proc, /* The procedure in the callback to delete. */ void *clientData) /* The client data in the callback to * delete. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); ChannelHandler *chPtr, *prevChPtr; Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; /* State info for channel */ NextChannelHandler *nhPtr; /* * Find the entry and the previous one in the list. */ for (prevChPtr = NULL, chPtr = statePtr->chPtr; chPtr != NULL; chPtr = chPtr->nextPtr) { if ((chPtr->chanPtr == chanPtr) && (chPtr->clientData == clientData) && (chPtr->proc == proc)) { break; } prevChPtr = chPtr; } /* * If not found, return without doing anything. */ if (chPtr == NULL) { return; } /* * If Tcl_NotifyChannel is about to process this handler, tell it to * process the next one instead - we are going to delete *this* one. */ for (nhPtr = tsdPtr->nestedHandlerPtr; nhPtr != NULL; nhPtr = nhPtr->nestedHandlerPtr) { if (nhPtr->nextHandlerPtr == chPtr) { nhPtr->nextHandlerPtr = chPtr->nextPtr; } } /* * Splice it out of the list of channel handlers. */ if (prevChPtr == NULL) { statePtr->chPtr = chPtr->nextPtr; } else { prevChPtr->nextPtr = chPtr->nextPtr; } Tcl_Free(chPtr); /* * Recompute the interest list for the channel, so that infinite loops * will not result if Tcl_DeleteChannelHandler is called inside an event. */ statePtr->interestMask = 0; for (chPtr = statePtr->chPtr; chPtr != NULL; chPtr = chPtr->nextPtr) { statePtr->interestMask |= chPtr->mask; } UpdateInterest(statePtr->topChanPtr); } /* *---------------------------------------------------------------------- * * DeleteScriptRecord -- * * Delete a script record for this combination of channel, interp and * mask. * * Results: * None. * * Side effects: * Deletes a script record and cancels a channel event handler. * *---------------------------------------------------------------------- */ static void DeleteScriptRecord( Tcl_Interp *interp, /* Interpreter in which script was to be * executed. */ Channel *chanPtr, /* The channel for which to delete the script * record (if any). */ int mask) /* Events in mask must exactly match mask of * script to delete. */ { ChannelState *statePtr = chanPtr->state; /* State info for channel */ EventScriptRecord *esPtr, *prevEsPtr; for (esPtr = statePtr->scriptRecordPtr, prevEsPtr = NULL; esPtr != NULL; prevEsPtr = esPtr, esPtr = esPtr->nextPtr) { if ((esPtr->interp == interp) && (esPtr->mask == mask)) { if (esPtr == statePtr->scriptRecordPtr) { statePtr->scriptRecordPtr = esPtr->nextPtr; } else { CLANG_ASSERT(prevEsPtr); prevEsPtr->nextPtr = esPtr->nextPtr; } Tcl_DeleteChannelHandler((Tcl_Channel) chanPtr, TclChannelEventScriptInvoker, esPtr); TclDecrRefCount(esPtr->scriptPtr); Tcl_Free(esPtr); break; } } } /* *---------------------------------------------------------------------- * * CreateScriptRecord -- * * Creates a record to store a script to be executed when a specific * event fires on a specific channel. * * Results: * None. * * Side effects: * Causes the script to be stored for later execution. * *---------------------------------------------------------------------- */ static void CreateScriptRecord( Tcl_Interp *interp, /* Interpreter in which to execute the stored * script. */ Channel *chanPtr, /* Channel for which script is to be stored */ int mask, /* Set of events for which script will be * invoked. */ Tcl_Obj *scriptPtr) /* Pointer to script object. */ { ChannelState *statePtr = chanPtr->state; /* State info for channel */ EventScriptRecord *esPtr; int makeCH; for (esPtr=statePtr->scriptRecordPtr; esPtr!=NULL; esPtr=esPtr->nextPtr) { if ((esPtr->interp == interp) && (esPtr->mask == mask)) { TclDecrRefCount(esPtr->scriptPtr); esPtr->scriptPtr = NULL; break; } } makeCH = (esPtr == NULL); if (makeCH) { esPtr = (EventScriptRecord *)Tcl_Alloc(sizeof(EventScriptRecord)); } /* * Initialize the structure before calling Tcl_CreateChannelHandler, * because a reflected channel calling 'chan postevent' aka * 'Tcl_NotifyChannel' in its 'watch'Proc will invoke * 'TclChannelEventScriptInvoker' immediately, and we do not wish it to * see uninitialized memory and crash. See [Bug 2918110]. */ esPtr->chanPtr = chanPtr; esPtr->interp = interp; esPtr->mask = mask; Tcl_IncrRefCount(scriptPtr); esPtr->scriptPtr = scriptPtr; if (makeCH) { esPtr->nextPtr = statePtr->scriptRecordPtr; statePtr->scriptRecordPtr = esPtr; Tcl_CreateChannelHandler((Tcl_Channel) chanPtr, mask, TclChannelEventScriptInvoker, esPtr); } } /* *---------------------------------------------------------------------- * * TclChannelEventScriptInvoker -- * * Invokes a script scheduled by "fileevent" for when the channel becomes * ready for IO. This function is invoked by the channel handler which * was created by the Tcl "fileevent" command. * * Results: * None. * * Side effects: * Whatever the script does. * *---------------------------------------------------------------------- */ void TclChannelEventScriptInvoker( void *clientData, /* The script+interp record. */ TCL_UNUSED(int) /*mask*/) { EventScriptRecord *esPtr = (EventScriptRecord *)clientData; /* The event script + interpreter to eval it * in. */ Channel *chanPtr = esPtr->chanPtr; /* The channel for which this handler is * registered. */ Tcl_Interp *interp = esPtr->interp; /* Interpreter in which to eval the script. */ int mask = esPtr->mask; int result; /* Result of call to eval script. */ /* * Be sure event executed in managed channel (covering bugs similar [f583715154]). */ assert(chanPtr->state->managingThread == Tcl_GetCurrentThread()); /* * We must preserve the interpreter so we can report errors on it later. * Note that we do not need to preserve the channel because that is done * by Tcl_NotifyChannel before calling channel handlers. */ Tcl_Preserve(interp); TclChannelPreserve((Tcl_Channel)chanPtr); result = Tcl_EvalObjEx(interp, esPtr->scriptPtr, TCL_EVAL_GLOBAL); /* * On error, cause a background error and remove the channel handler and * the script record. * * NOTE: Must delete channel handler before causing the background error * because the background error may want to reinstall the handler. */ if (result != TCL_OK) { if (chanPtr->typePtr != NULL) { DeleteScriptRecord(interp, chanPtr, mask); } Tcl_BackgroundException(interp, result); } TclChannelRelease((Tcl_Channel)chanPtr); Tcl_Release(interp); } /* *---------------------------------------------------------------------- * * Tcl_FileEventObjCmd -- * * This procedure implements the "fileevent" Tcl command. See the user * documentation for details on what it does. This command is based on * the Tk command "fileevent" which in turn is based on work contributed * by Mark Diekhans. * * Results: * A standard Tcl result. * * Side effects: * May create a channel handler for the specified channel. * *---------------------------------------------------------------------- */ int Tcl_FileEventObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter in which the channel for which * to create the handler is found. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Channel *chanPtr; /* The channel to create the handler for. */ ChannelState *statePtr; /* State info for channel */ Tcl_Channel chan; /* The opaque type for the channel. */ const char *chanName; int modeIndex; /* Index of mode argument. */ int mask; static const char *const modeOptions[] = {"readable", "writable", NULL}; static const int maskArray[] = {TCL_READABLE, TCL_WRITABLE}; if ((objc != 3) && (objc != 4)) { Tcl_WrongNumArgs(interp, 1, objv, "channel event ?script?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[2], modeOptions, "event name", 0, &modeIndex) != TCL_OK) { return TCL_ERROR; } mask = maskArray[modeIndex]; chanName = TclGetString(objv[1]); chan = Tcl_GetChannel(interp, chanName, NULL); if (chan == NULL) { return TCL_ERROR; } chanPtr = (Channel *) chan; statePtr = chanPtr->state; if (GotFlag(statePtr, mask) == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("channel is not %s", (mask == TCL_READABLE) ? "readable" : "writable")); return TCL_ERROR; } /* * If we are supposed to return the script, do so. */ if (objc == 3) { EventScriptRecord *esPtr; for (esPtr = statePtr->scriptRecordPtr; esPtr != NULL; esPtr = esPtr->nextPtr) { if ((esPtr->interp == interp) && (esPtr->mask == mask)) { Tcl_SetObjResult(interp, esPtr->scriptPtr); break; } } return TCL_OK; } /* * If we are supposed to delete a stored script, do so. */ if (*(TclGetString(objv[3])) == '\0') { DeleteScriptRecord(interp, chanPtr, mask); return TCL_OK; } /* * Make the script record that will link between the event and the script * to invoke. This also creates a channel event handler which will * evaluate the script in the supplied interpreter. */ CreateScriptRecord(interp, chanPtr, mask, objv[3]); return TCL_OK; } /* *---------------------------------------------------------------------- * * ZeroTransferTimerProc -- * * Timer handler scheduled by TclCopyChannel so that -command is * called asynchronously even when -size is 0. * * Results: * None. * * Side effects: * Calls CopyData for -command invocation. * *---------------------------------------------------------------------- */ static void ZeroTransferTimerProc( void *clientData) { /* calling CopyData with mask==0 still implies immediate invocation of the * -command callback, and completion of the fcopy. */ CopyData((CopyState *)clientData, 0); } /* *---------------------------------------------------------------------- * * TclCopyChannel -- * * This routine copies data from one channel to another, either * synchronously or asynchronously. If a command script is supplied, the * operation runs in the background. The script is invoked when the copy * completes. Otherwise the function waits until the copy is completed * before returning. * * Results: * A standard Tcl result. * * Side effects: * May schedule a background copy operation that causes both channels to * be marked busy. * *---------------------------------------------------------------------- */ int TclCopyChannel( Tcl_Interp *interp, /* Current interpreter. */ Tcl_Channel inChan, /* Channel to read from. */ Tcl_Channel outChan, /* Channel to write to. */ long long toRead, /* Amount of data to copy, or -1 for all. */ Tcl_Obj *cmdPtr) /* Pointer to script to execute or NULL. */ { Channel *inPtr = (Channel *) inChan; Channel *outPtr = (Channel *) outChan; ChannelState *inStatePtr, *outStatePtr; int readFlags, writeFlags; CopyState *csPtr; int nonBlocking = (cmdPtr) ? CHANNEL_NONBLOCKING : 0; int moveBytes; inStatePtr = inPtr->state; outStatePtr = outPtr->state; if (BUSY_STATE(inStatePtr, TCL_READABLE)) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "channel \"%s\" is busy", Tcl_GetChannelName(inChan))); } return TCL_ERROR; } if (BUSY_STATE(outStatePtr, TCL_WRITABLE)) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "channel \"%s\" is busy", Tcl_GetChannelName(outChan))); } return TCL_ERROR; } readFlags = inStatePtr->flags; writeFlags = outStatePtr->flags; /* * Set up the blocking mode appropriately. Background copies need * non-blocking channels. Foreground copies need blocking channels. If * there is an error, restore the old blocking mode. */ if (nonBlocking != (readFlags & CHANNEL_NONBLOCKING)) { if (SetBlockMode(interp, inPtr, nonBlocking ? TCL_MODE_NONBLOCKING : TCL_MODE_BLOCKING) != TCL_OK) { return TCL_ERROR; } } if ((inPtr!=outPtr) && (nonBlocking!=(writeFlags&CHANNEL_NONBLOCKING)) && (SetBlockMode(NULL, outPtr, nonBlocking ? TCL_MODE_NONBLOCKING : TCL_MODE_BLOCKING) != TCL_OK) && (nonBlocking != (readFlags & CHANNEL_NONBLOCKING))) { SetBlockMode(NULL, inPtr, (readFlags & CHANNEL_NONBLOCKING) ? TCL_MODE_NONBLOCKING : TCL_MODE_BLOCKING); return TCL_ERROR; } /* * Make sure the output side is unbuffered. */ ResetFlag(outStatePtr, CHANNEL_LINEBUFFERED); SetFlag(outStatePtr, CHANNEL_UNBUFFERED); moveBytes = Lossless(inStatePtr, outStatePtr, toRead); /* * Allocate a new CopyState to maintain info about the current copy in * progress. This structure will be deallocated when the copy is * completed. */ csPtr = (CopyState *)Tcl_Alloc(offsetof(CopyState, buffer) + 1U + !moveBytes * inStatePtr->bufSize); csPtr->bufSize = !moveBytes * inStatePtr->bufSize; csPtr->readPtr = inPtr; csPtr->writePtr = outPtr; csPtr->refCount = 2; /* two references below (inStatePtr, outStatePtr) */ csPtr->readFlags = readFlags; csPtr->writeFlags = writeFlags; csPtr->toRead = toRead; csPtr->total = 0; csPtr->interp = interp; if (cmdPtr) { Tcl_IncrRefCount(cmdPtr); } csPtr->cmdPtr = cmdPtr; TclChannelPreserve(inChan); TclChannelPreserve(outChan); inStatePtr->csPtrR = csPtr; outStatePtr->csPtrW = csPtr; if (moveBytes) { return MoveBytes(csPtr); } /* * Special handling of -size 0 async transfers, so that the -command is * still called asynchronously. */ if ((nonBlocking == CHANNEL_NONBLOCKING) && (toRead == 0)) { Tcl_CreateTimerHandler(0, ZeroTransferTimerProc, csPtr); return TCL_OK; } /* * Start copying data between the channels. */ return CopyData(csPtr, 0); } /* *---------------------------------------------------------------------- * * CopyData -- * * This function implements the lowest level of the copying mechanism for * TclCopyChannel. * * Results: * Returns TCL_OK on success, else TCL_ERROR. * * Side effects: * Moves data between channels, may create channel handlers. * *---------------------------------------------------------------------- */ static void MBCallback( CopyState *csPtr, Tcl_Obj *errObj) { Tcl_Obj *cmdPtr = Tcl_DuplicateObj(csPtr->cmdPtr); Tcl_WideInt total = csPtr->total; Tcl_Interp *interp = csPtr->interp; int code; Tcl_IncrRefCount(cmdPtr); StopCopy(csPtr); /* TODO: What if cmdPtr is not a list?! */ Tcl_ListObjAppendElement(NULL, cmdPtr, Tcl_NewWideIntObj(total)); if (errObj) { Tcl_ListObjAppendElement(NULL, cmdPtr, errObj); } Tcl_Preserve(interp); code = Tcl_EvalObjEx(interp, cmdPtr, TCL_EVAL_GLOBAL); if (code != TCL_OK) { Tcl_BackgroundException(interp, code); } Tcl_Release(interp); TclDecrRefCount(cmdPtr); } static void MBError( CopyState *csPtr, int mask, int errorCode) { Tcl_Channel inChan = (Tcl_Channel) csPtr->readPtr; Tcl_Channel outChan = (Tcl_Channel) csPtr->writePtr; Tcl_Obj *errObj; Tcl_SetErrno(errorCode); errObj = Tcl_ObjPrintf( "error %sing \"%s\": %s", (mask & TCL_READABLE) ? "read" : "writ", Tcl_GetChannelName((mask & TCL_READABLE) ? inChan : outChan), Tcl_PosixError(csPtr->interp)); if (csPtr->cmdPtr) { MBCallback(csPtr, errObj); } else { Tcl_SetObjResult(csPtr->interp, errObj); StopCopy(csPtr); } } static void MBEvent( void *clientData, int mask) { CopyState *csPtr = (CopyState *) clientData; Tcl_Channel inChan = (Tcl_Channel) csPtr->readPtr; Tcl_Channel outChan = (Tcl_Channel) csPtr->writePtr; ChannelState *inStatePtr = csPtr->readPtr->state; if (mask & TCL_WRITABLE) { Tcl_DeleteChannelHandler(inChan, MBEvent, csPtr); Tcl_DeleteChannelHandler(outChan, MBEvent, csPtr); switch (MBWrite(csPtr)) { case TCL_OK: MBCallback(csPtr, NULL); break; case TCL_CONTINUE: Tcl_CreateChannelHandler(inChan, TCL_READABLE, MBEvent, csPtr); break; } } else if (mask & TCL_READABLE) { if (TCL_OK == MBRead(csPtr)) { /* When at least one full buffer is present, stop reading. */ if (IsBufferFull(inStatePtr->inQueueHead) || !Tcl_InputBlocked(inChan)) { Tcl_DeleteChannelHandler(inChan, MBEvent, csPtr); } /* Successful read -- set up to write the bytes we read */ Tcl_CreateChannelHandler(outChan, TCL_WRITABLE, MBEvent, csPtr); } } } static int MBRead( CopyState *csPtr) { ChannelState *inStatePtr = csPtr->readPtr->state; ChannelBuffer *bufPtr = inStatePtr->inQueueHead; int code; if (bufPtr && BytesLeft(bufPtr) > 0) { return TCL_OK; } code = GetInput(inStatePtr->topChanPtr); if (code == 0 || GotFlag(inStatePtr, CHANNEL_BLOCKED)) { return TCL_OK; } else { MBError(csPtr, TCL_READABLE, code); return TCL_ERROR; } } static int MBWrite( CopyState *csPtr) { ChannelState *inStatePtr = csPtr->readPtr->state; ChannelState *outStatePtr = csPtr->writePtr->state; ChannelBuffer *bufPtr = inStatePtr->inQueueHead; ChannelBuffer *tail = NULL; int code; Tcl_WideInt inBytes = 0; /* Count up number of bytes waiting in the input queue */ while (bufPtr) { inBytes += BytesLeft(bufPtr); tail = bufPtr; if (csPtr->toRead != -1 && csPtr->toRead < inBytes) { /* Queue has enough bytes to complete the copy */ break; } bufPtr = bufPtr->nextPtr; } if (bufPtr) { /* Split the overflowing buffer in two */ int extra = (int) (inBytes - csPtr->toRead); /* Note that going with int for extra assumes that inBytes is not too * much over toRead to require a wide itself. If that gets violated * then the calculations involving extra must be made wide too. * * Noted with Win32/MSVC debug build treating the warning (possible of * data in long long to int conversion) as error. */ bufPtr = AllocChannelBuffer(extra); tail->nextAdded -= extra; memcpy(InsertPoint(bufPtr), InsertPoint(tail), extra); bufPtr->nextAdded += extra; bufPtr->nextPtr = tail->nextPtr; tail->nextPtr = NULL; inBytes = csPtr->toRead; } /* Update the byte counts */ if (csPtr->toRead != -1) { csPtr->toRead -= inBytes; } csPtr->total += inBytes; /* Move buffers from input to output channels */ if (outStatePtr->outQueueTail) { outStatePtr->outQueueTail->nextPtr = inStatePtr->inQueueHead; } else { outStatePtr->outQueueHead = inStatePtr->inQueueHead; } outStatePtr->outQueueTail = tail; inStatePtr->inQueueHead = bufPtr; if (inStatePtr->inQueueTail == tail) { inStatePtr->inQueueTail = bufPtr; } if (bufPtr == NULL) { inStatePtr->inQueueTail = NULL; } code = FlushChannel(csPtr->interp, outStatePtr->topChanPtr, 0); if (code) { MBError(csPtr, TCL_WRITABLE, code); return TCL_ERROR; } if (csPtr->toRead == 0 || GotFlag(inStatePtr, CHANNEL_EOF)) { return TCL_OK; } return TCL_CONTINUE; } static int MoveBytes( CopyState *csPtr) /* State of copy operation. */ { ChannelState *outStatePtr = csPtr->writePtr->state; ChannelBuffer *bufPtr = outStatePtr->curOutPtr; int errorCode; if (bufPtr && BytesLeft(bufPtr)) { /* If we start with unflushed bytes in the destination * channel, flush them out of the way first. */ errorCode = FlushChannel(csPtr->interp, outStatePtr->topChanPtr, 0); if (errorCode != 0) { MBError(csPtr, TCL_WRITABLE, errorCode); return TCL_ERROR; } } if (csPtr->cmdPtr) { Tcl_Channel inChan = (Tcl_Channel) csPtr->readPtr; Tcl_CreateChannelHandler(inChan, TCL_READABLE, MBEvent, csPtr); return TCL_OK; } while (1) { int code; if (TCL_ERROR == MBRead(csPtr)) { return TCL_ERROR; } code = MBWrite(csPtr); if (code == TCL_OK) { Tcl_SetObjResult(csPtr->interp, Tcl_NewWideIntObj(csPtr->total)); StopCopy(csPtr); return TCL_OK; } if (code == TCL_ERROR) { return TCL_ERROR; } /* code == TCL_CONTINUE --> continue the loop */ } return TCL_OK; /* Silence compiler warnings */ } static int CopyData( CopyState *csPtr, /* State of copy operation. */ int mask) /* Current channel event flags. */ { Tcl_Interp *interp; Tcl_Obj *cmdPtr, *errObj = NULL, *bufObj = NULL, *msg = NULL; Tcl_Channel inChan, outChan; ChannelState *inStatePtr, *outStatePtr; int result = TCL_OK; Tcl_Size sizeb; Tcl_Size sizePart; Tcl_WideInt total; Tcl_WideInt size; const char *buffer; int moveBytes; int underflow; /* Input underflow */ csPtr->refCount++; /* avoid freeing during handling */ inChan = (Tcl_Channel) csPtr->readPtr; outChan = (Tcl_Channel) csPtr->writePtr; inStatePtr = csPtr->readPtr->state; outStatePtr = csPtr->writePtr->state; interp = csPtr->interp; cmdPtr = csPtr->cmdPtr; /* * Copy the data the slow way, using the translation mechanism. * * Note: We have make sure that we use the topmost channel in a stack for * the copying. The caller uses Tcl_GetChannel to access it, and thus gets * the bottom of the stack. */ moveBytes = Lossless(inStatePtr, outStatePtr, csPtr->toRead); if (!moveBytes) { TclNewObj(bufObj); Tcl_IncrRefCount(bufObj); } while (csPtr->toRead != 0) { /* * Check for unreported background errors. */ Tcl_GetChannelError(inChan, &msg); if ((inStatePtr->unreportedError != 0) || (msg != NULL)) { Tcl_SetErrno(inStatePtr->unreportedError); inStatePtr->unreportedError = 0; goto readError; } else if (inStatePtr->flags & CHANNEL_ENCODING_ERROR) { Tcl_SetErrno(EILSEQ); inStatePtr->flags &= ~CHANNEL_ENCODING_ERROR; goto readError; } Tcl_GetChannelError(outChan, &msg); if ((outStatePtr->unreportedError != 0) || (msg != NULL)) { Tcl_SetErrno(outStatePtr->unreportedError); outStatePtr->unreportedError = 0; goto writeError; } else if (outStatePtr->flags & CHANNEL_ENCODING_ERROR) { Tcl_SetErrno(EILSEQ); outStatePtr->flags &= ~CHANNEL_ENCODING_ERROR; goto writeError; } if (cmdPtr && (mask == 0)) { /* * In async mode, we skip reading synchronously and fake an * underflow instead to prime the readable fileevent. */ size = 0; underflow = 1; } else { /* * Read up to bufSize characters. */ if ((csPtr->toRead == -1) || (csPtr->toRead > (Tcl_WideInt)csPtr->bufSize)) { sizeb = csPtr->bufSize; } else { sizeb = csPtr->toRead; } if (moveBytes) { size = DoRead(inStatePtr->topChanPtr, csPtr->buffer, sizeb, !GotFlag(inStatePtr, CHANNEL_NONBLOCKING)); } else { size = DoReadChars(inStatePtr->topChanPtr, bufObj, sizeb, !GotFlag(inStatePtr, CHANNEL_NONBLOCKING) ,0 /* No append */); /* * In case of a recoverable encoding error, any data before * the error should be written. This data is in the bufObj. * Program flow for this case: * - Check, if there are any remaining bytes to write * - If yes, simulate a successful read to write them out * - Come back here by the outer loop and read again * - Do not enter in the if below, as there are no pending * writes * - Fail below with a read error */ if (size < 0 && Tcl_GetErrno() == EILSEQ) { TclGetStringFromObj(bufObj, &sizePart); if (sizePart > 0) { size = sizePart; } } } underflow = (size >= 0) && (size < sizeb); /* Input underflow */ } if (size < 0) { readError: if (interp) { TclNewObj(errObj); Tcl_AppendStringsToObj(errObj, "error reading \"", Tcl_GetChannelName(inChan), "\": ", (char *)NULL); if (msg != NULL) { Tcl_AppendObjToObj(errObj, msg); } else { Tcl_AppendStringsToObj(errObj, Tcl_PosixError(interp), (char *)NULL); } } if (msg != NULL) { Tcl_DecrRefCount(msg); } break; } else if (underflow) { /* * We had an underflow on the read side. If we are at EOF, and not * in the synchronous part of an asynchronous fcopy, then the * copying is done, otherwise set up a channel handler to detect * when the channel becomes readable again. */ if ((size == 0) && Tcl_Eof(inChan) && !(cmdPtr && (mask == 0))) { break; } if (cmdPtr && (!Tcl_Eof(inChan) || (mask == 0)) && !(mask & TCL_READABLE)) { if (mask & TCL_WRITABLE) { Tcl_DeleteChannelHandler(outChan, CopyEventProc, csPtr); } Tcl_CreateChannelHandler(inChan, TCL_READABLE, CopyEventProc, csPtr); } if (size == 0) { if (!GotFlag(inStatePtr, CHANNEL_NONBLOCKING)) { /* * We allowed a short read. Keep trying. */ continue; } if (bufObj != NULL) { TclDecrRefCount(bufObj); bufObj = NULL; } goto done; } } /* * Now write the buffer out. */ if (moveBytes) { buffer = csPtr->buffer; sizeb = WriteBytes(outStatePtr->topChanPtr, buffer, size); } else { buffer = TclGetStringFromObj(bufObj, &sizeb); sizeb = WriteChars(outStatePtr->topChanPtr, buffer, sizeb); } /* * [Bug 2895565]. At this point 'size' still contains the number of * characters which have been read. We keep this to later to * update the totals and toRead information, see marker (UP) below. We * must not overwrite it with 'sizeb', which is the number of written * characters, and both EOL translation and encoding * conversion may have changed this number unpredictably in relation * to 'size' (It can be smaller or larger, in the latter case able to * drive toRead below -1, causing infinite looping). Completely * unsuitable for updating totals and toRead. */ if (sizeb < 0) { writeError: if (interp) { TclNewObj(errObj); Tcl_AppendStringsToObj(errObj, "error writing \"", Tcl_GetChannelName(outChan), "\": ", (char *)NULL); if (msg != NULL) { Tcl_AppendObjToObj(errObj, msg); } else { Tcl_AppendStringsToObj(errObj, Tcl_PosixError(interp), (char *)NULL); } } if (msg != NULL) { Tcl_DecrRefCount(msg); } break; } /* * Update the current character count. Do it now so the count is valid * before a return or break takes us out of the loop. The invariant at * the top of the loop should be that csPtr->toRead holds the number * of characters left to copy. */ if (csPtr->toRead != -1) { csPtr->toRead -= size; } csPtr->total += size; /* * Break loop if EOF && (size>0) */ if (Tcl_Eof(inChan)) { break; } /* * Check to see if the write is happening in the background. If so, * stop copying and wait for the channel to become writable again. * After input underflow we already installed a readable handler * therefore we don't need a writable handler. */ if (!underflow && GotFlag(outStatePtr, BG_FLUSH_SCHEDULED)) { if (!(mask & TCL_WRITABLE)) { if (mask & TCL_READABLE) { Tcl_DeleteChannelHandler(inChan, CopyEventProc, csPtr); } Tcl_CreateChannelHandler(outChan, TCL_WRITABLE, CopyEventProc, csPtr); } if (bufObj != NULL) { TclDecrRefCount(bufObj); bufObj = NULL; } goto done; } /* * For background copies, we only do one buffer per invocation so we * don't starve the rest of the system. */ if (cmdPtr && (csPtr->toRead != 0)) { /* * The first time we enter this code, there won't be a channel * handler established yet, so do it here. */ if (mask == 0) { Tcl_CreateChannelHandler(outChan, TCL_WRITABLE, CopyEventProc, csPtr); } if (bufObj != NULL) { TclDecrRefCount(bufObj); bufObj = NULL; } goto done; } } /* while */ if (bufObj != NULL) { TclDecrRefCount(bufObj); bufObj = NULL; } /* * Make the callback or return the number of characters transferred. The * local total is used because StopCopy frees csPtr. */ total = csPtr->total; if (cmdPtr && interp) { int code; /* * Get a private copy of the command so we can mutate it by adding * arguments. Note that StopCopy frees our saved reference to the * original command obj. */ cmdPtr = Tcl_DuplicateObj(cmdPtr); Tcl_IncrRefCount(cmdPtr); StopCopy(csPtr); Tcl_Preserve(interp); Tcl_ListObjAppendElement(interp, cmdPtr, Tcl_NewWideIntObj(total)); if (errObj) { Tcl_ListObjAppendElement(interp, cmdPtr, errObj); } code = Tcl_EvalObjEx(interp, cmdPtr, TCL_EVAL_GLOBAL); if (code != TCL_OK) { Tcl_BackgroundException(interp, code); result = TCL_ERROR; } TclDecrRefCount(cmdPtr); Tcl_Release(interp); } else { StopCopy(csPtr); if (interp) { if (errObj) { Tcl_SetObjResult(interp, errObj); result = TCL_ERROR; } else { Tcl_ResetResult(interp); Tcl_SetObjResult(interp, Tcl_NewWideIntObj(total)); } } } done: CopyDecrRefCount(csPtr); return result; } /* *---------------------------------------------------------------------- * * DoRead -- * * Stores up to "bytesToRead" bytes in memory pointed to by "dst". * These bytes come from reading the channel "chanPtr" and * performing the configured translations. No encoding conversions * are applied to the bytes being read. * * Results: * The number of bytes actually stored (<= bytesToRead), * or TCL_INDEX_NONE if there is an error in reading the channel. Use * Tcl_GetErrno() to retrieve the error code for the error * that occurred. * * The number of bytes stored can be less than the number * requested when * - EOF is reached on the channel; or * - the channel is non-blocking, and we've read all we can * without blocking. * - a channel reading error occurs (and we return TCL_INDEX_NONE) * * Side effects: * May cause input to be buffered. * *---------------------------------------------------------------------- */ static Tcl_Size DoRead( Channel *chanPtr, /* The channel from which to read. */ char *dst, /* Where to store input read. */ Tcl_Size bytesToRead, /* Maximum number of bytes to read. */ int allowShortReads) /* Allow half-blocking (pipes,sockets) */ { ChannelState *statePtr = chanPtr->state; char *p = dst; /* * Early out when we know a read will get the eofchar. * * NOTE: This seems to be a bug. The special handling for * a zero-char read request ought to come first. As coded * the EOF due to eofchar has distinguishing behavior from * the EOF due to reported EOF on the underlying device, and * that seems undesirable. However recent history indicates * that new inconsistent behavior in a patchlevel has problems * too. Keep on keeping on for now. */ if (GotFlag(statePtr, CHANNEL_ENCODING_ERROR)) { UpdateInterest(chanPtr); Tcl_SetErrno(EILSEQ); return -1; } if (GotFlag(statePtr, CHANNEL_STICKY_EOF)) { SetFlag(statePtr, CHANNEL_EOF); assert(statePtr->inputEncodingFlags & TCL_ENCODING_END); assert(!GotFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR)); /* TODO: Don't need this call */ UpdateInterest(chanPtr); return 0; } /* * Special handling for zero-char read request. */ if (bytesToRead == 0) { if (GotFlag(statePtr, CHANNEL_EOF)) { statePtr->inputEncodingFlags |= TCL_ENCODING_START; } ResetFlag(statePtr, CHANNEL_BLOCKED|CHANNEL_EOF); statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; /* TODO: Don't need this call */ UpdateInterest(chanPtr); return 0; } TclChannelPreserve((Tcl_Channel)chanPtr); while (bytesToRead) { /* * Each pass through the loop is intended to process up to one channel * buffer. */ int bytesRead, bytesWritten; ChannelBuffer *bufPtr = statePtr->inQueueHead; /* * Don't read more data if we have what we need. */ while (!bufPtr || /* We got no buffer! OR */ (!IsBufferFull(bufPtr) && /* Our buffer has room AND */ ((Tcl_Size) BytesLeft(bufPtr) < bytesToRead))) { /* Not enough bytes in it yet * to fill the dst */ int code; moreData: code = GetInput(chanPtr); bufPtr = statePtr->inQueueHead; if (GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED)) { /* * Further reads cannot do any more. */ break; } if (code || !bufPtr) { /* Read error (or channel dead/closed) */ goto readErr; } assert(IsBufferFull(bufPtr)); } if (!bufPtr) { readErr: UpdateInterest(chanPtr); TclChannelRelease((Tcl_Channel)chanPtr); return -1; } bytesRead = BytesLeft(bufPtr); bytesWritten = bytesToRead; TranslateInputEOL(statePtr, p, RemovePoint(bufPtr), &bytesWritten, &bytesRead); bufPtr->nextRemoved += bytesRead; p += bytesWritten; bytesToRead -= bytesWritten; if (!IsBufferEmpty(bufPtr)) { /* * Buffer is not empty. How can that be? * * 0) We stopped early because we got all the bytes we were * seeking. That's fine. */ if (bytesToRead == 0) { break; } /* * 1) We're @EOF because we saw eof char, or there was an encoding error. */ if (GotFlag(statePtr, CHANNEL_STICKY_EOF|CHANNEL_ENCODING_ERROR)) { break; } /* * 2) The buffer holds a \r while in CRLF translation, followed by * the end of the buffer. */ assert(statePtr->inputTranslation == TCL_TRANSLATE_CRLF); assert(RemovePoint(bufPtr)[0] == '\r'); assert(BytesLeft(bufPtr) == 1); if (bufPtr->nextPtr == NULL) { /* * There's no more buffered data... */ if (GotFlag(statePtr, CHANNEL_EOF)) { /* * ...and there never will be. */ *p++ = '\r'; bytesToRead--; bufPtr->nextRemoved++; } else if (GotFlag(statePtr, CHANNEL_BLOCKED)) { /* * ...and we cannot get more now. */ SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); break; } else { /* * ...so we need to get some. */ goto moreData; } } if (bufPtr->nextPtr) { /* * There's a next buffer. Shift orphan \r to it. */ ChannelBuffer *nextPtr = bufPtr->nextPtr; nextPtr->nextRemoved -= 1; RemovePoint(nextPtr)[0] = '\r'; bufPtr->nextRemoved++; } } if (IsBufferEmpty(bufPtr)) { statePtr->inQueueHead = bufPtr->nextPtr; if (statePtr->inQueueHead == NULL) { statePtr->inQueueTail = NULL; } RecycleBuffer(statePtr, bufPtr, 0); bufPtr = statePtr->inQueueHead; } if ((GotFlag(statePtr, CHANNEL_NONBLOCKING) || allowShortReads) && GotFlag(statePtr, CHANNEL_BLOCKED)) { break; } /* * When there's no buffered data to read, and we're at EOF, escape to * the caller. */ if (GotFlag(statePtr, CHANNEL_EOF) && (bufPtr == NULL || IsBufferEmpty(bufPtr))) { break; } } if (bytesToRead == 0) { ResetFlag(statePtr, CHANNEL_BLOCKED); } assert(!GotFlag(statePtr, CHANNEL_EOF) || GotFlag(statePtr, CHANNEL_STICKY_EOF|CHANNEL_ENCODING_ERROR) || Tcl_InputBuffered((Tcl_Channel)chanPtr) == 0); assert(!(GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED) == (CHANNEL_EOF|CHANNEL_BLOCKED))); UpdateInterest(chanPtr); TclChannelRelease((Tcl_Channel)chanPtr); return (Tcl_Size)(p - dst); } /* *---------------------------------------------------------------------- * * CopyEventProc -- * * This routine is invoked as a channel event handler for the background * copy operation. It is just a trivial wrapper around the CopyData * routine. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void CopyEventProc( void *clientData, int mask) { (void) CopyData((CopyState *)clientData, mask); } /* *---------------------------------------------------------------------- * * Lossless -- * * Determines whether copying characters between two channel states would * be lossless, i.e. whether one byte corresponds to one character, every * character appears in the Unicode character set, there are no * translations to be performed, and no inline signals to respond to. * * Result: * True if copying would be lossless. * *---------------------------------------------------------------------- */ int Lossless( ChannelState *inStatePtr, ChannelState *outStatePtr, long long toRead) { return inStatePtr->inEofChar == '\0' /* No eofChar to stop input */ && inStatePtr->inputTranslation == TCL_TRANSLATE_LF && outStatePtr->outputTranslation == TCL_TRANSLATE_LF && ((inStatePtr->encoding == GetBinaryEncoding() && outStatePtr->encoding == GetBinaryEncoding()) || (toRead == -1 && inStatePtr->encoding == outStatePtr->encoding && ENCODING_PROFILE_GET(inStatePtr->inputEncodingFlags) == TCL_ENCODING_PROFILE_TCL8 && ENCODING_PROFILE_GET(outStatePtr->inputEncodingFlags) == TCL_ENCODING_PROFILE_TCL8 )); } /* *---------------------------------------------------------------------- * * StopCopy -- * * This routine halts a copy that is in progress. * * Results: * None. * * Side effects: * Removes any pending channel handlers and restores the blocking and * buffering modes of the channels. The CopyState is freed. * *---------------------------------------------------------------------- */ static void StopCopy( CopyState *csPtr) /* State for bg copy to stop . */ { ChannelState *inStatePtr, *outStatePtr; Tcl_Channel inChan, outChan; int nonBlocking; if (!csPtr) { return; } inChan = (Tcl_Channel) csPtr->readPtr; outChan = (Tcl_Channel) csPtr->writePtr; inStatePtr = csPtr->readPtr->state; outStatePtr = csPtr->writePtr->state; /* * Restore the old blocking mode and output buffering mode. */ nonBlocking = csPtr->readFlags & CHANNEL_NONBLOCKING; if (nonBlocking != GotFlag(inStatePtr, CHANNEL_NONBLOCKING)) { SetBlockMode(NULL, csPtr->readPtr, nonBlocking ? TCL_MODE_NONBLOCKING : TCL_MODE_BLOCKING); } if (csPtr->readPtr != csPtr->writePtr) { nonBlocking = csPtr->writeFlags & CHANNEL_NONBLOCKING; if (nonBlocking != GotFlag(outStatePtr, CHANNEL_NONBLOCKING)) { SetBlockMode(NULL, csPtr->writePtr, nonBlocking ? TCL_MODE_NONBLOCKING : TCL_MODE_BLOCKING); } } ResetFlag(outStatePtr, CHANNEL_LINEBUFFERED | CHANNEL_UNBUFFERED); SetFlag(outStatePtr, csPtr->writeFlags & (CHANNEL_LINEBUFFERED | CHANNEL_UNBUFFERED)); if (csPtr->cmdPtr) { Tcl_DeleteChannelHandler(inChan, CopyEventProc, csPtr); if (inChan != outChan) { Tcl_DeleteChannelHandler(outChan, CopyEventProc, csPtr); } Tcl_DeleteChannelHandler(inChan, MBEvent, csPtr); Tcl_DeleteChannelHandler(outChan, MBEvent, csPtr); TclDecrRefCount(csPtr->cmdPtr); csPtr->cmdPtr = NULL; } if (inStatePtr->csPtrR) { assert(inStatePtr->csPtrR == csPtr); inStatePtr->csPtrR = NULL; CopyDecrRefCount(csPtr); } if (outStatePtr->csPtrW) { assert(outStatePtr->csPtrW == csPtr); outStatePtr->csPtrW = NULL; CopyDecrRefCount(csPtr); } } static void CopyDecrRefCount( CopyState *csPtr) { if (csPtr->refCount-- > 1) { return; } TclChannelRelease((Tcl_Channel)csPtr->readPtr); TclChannelRelease((Tcl_Channel)csPtr->writePtr); Tcl_Free(csPtr); } /* *---------------------------------------------------------------------- * * StackSetBlockMode -- * * This function sets the blocking mode for a channel, iterating through * each channel in a stack and updates the state flags. * * Results: * 0 if OK, result code from failed blockModeProc otherwise. * * Side effects: * Modifies the blocking mode of the channel and possibly generates an * error. * *---------------------------------------------------------------------- */ static int StackSetBlockMode( Channel *chanPtr, /* Channel to modify. */ int mode) /* One of TCL_MODE_BLOCKING or * TCL_MODE_NONBLOCKING. */ { int result = 0; Tcl_DriverBlockModeProc *blockModeProc; ChannelState *statePtr = chanPtr->state; /* * Start at the top of the channel stack * TODO: Examine what can go wrong when blockModeProc calls * disturb the stacking state of the channel. */ chanPtr = statePtr->topChanPtr; while (chanPtr != NULL) { blockModeProc = Tcl_ChannelBlockModeProc(chanPtr->typePtr); if (blockModeProc != NULL) { result = blockModeProc(chanPtr->instanceData, mode); if (result != 0) { Tcl_SetErrno(result); return result; } } chanPtr = chanPtr->downChanPtr; } return 0; } /* *---------------------------------------------------------------------- * * SetBlockMode -- * * This function sets the blocking mode for a channel and updates the * state flags. * * Results: * A standard Tcl result. * * Side effects: * Modifies the blocking mode of the channel and possibly generates an * error. * *---------------------------------------------------------------------- */ static int SetBlockMode( Tcl_Interp *interp, /* Interp for error reporting. */ Channel *chanPtr, /* Channel to modify. */ int mode) /* One of TCL_MODE_BLOCKING or * TCL_MODE_NONBLOCKING. */ { int result = 0; ChannelState *statePtr = chanPtr->state; /* State info for channel */ result = StackSetBlockMode(chanPtr, mode); if (result != 0) { if (interp != NULL) { /* * TIP #219. * Move error messages put by the driver into the bypass area and * put them into the regular interpreter result. Fall back to the * regular message if nothing was found in the bypass. * * Note that we cannot have a message in the interpreter bypass * area, StackSetBlockMode is restricted to the channel bypass. * We still need the interp as the destination of the move. */ if (!TclChanCaughtErrorBypass(interp, (Tcl_Channel) chanPtr)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error setting blocking mode: %s", Tcl_PosixError(interp))); } } else { /* * TIP #219. * If we have no interpreter to put a bypass message into we have * to clear it, to prevent its propagation and use in other places * unrelated to the actual occurence of the problem. */ Tcl_SetChannelError((Tcl_Channel) chanPtr, NULL); } return TCL_ERROR; } if (mode == TCL_MODE_BLOCKING) { ResetFlag(statePtr, CHANNEL_NONBLOCKING | BG_FLUSH_SCHEDULED); } else { SetFlag(statePtr, CHANNEL_NONBLOCKING); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_GetChannelNames -- * * Return the names of all open channels in the interp. * * Results: * TCL_OK or TCL_ERROR. * * Side effects: * Interp result modified with list of channel names. * *---------------------------------------------------------------------- */ int Tcl_GetChannelNames( Tcl_Interp *interp) /* Interp for error reporting. */ { return Tcl_GetChannelNamesEx(interp, NULL); } /* *---------------------------------------------------------------------- * * Tcl_GetChannelNamesEx -- * * Return the names of open channels in the interp filtered * through a pattern. If pattern is NULL, it returns all the open * channels. * * Results: * TCL_OK or TCL_ERROR. * * Side effects: * Interp result modified with list of channel names. * *---------------------------------------------------------------------- */ int Tcl_GetChannelNamesEx( Tcl_Interp *interp, /* Interp for error reporting. */ const char *pattern) /* Pattern to filter on. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); ChannelState *statePtr; const char *name; /* Name for channel */ Tcl_Obj *resultPtr; /* Pointer to result object */ Tcl_HashTable *hTblPtr; /* Hash table of channels. */ Tcl_HashEntry *hPtr; /* Search variable. */ Tcl_HashSearch hSearch; /* Search variable. */ if (interp == NULL) { return TCL_OK; } /* * Get the channel table that stores the channels registered for this * interpreter. */ hTblPtr = GetChannelTable(interp); TclNewObj(resultPtr); if ((pattern != NULL) && TclMatchIsTrivial(pattern) && !((pattern[0] == 's') && (pattern[1] == 't') && (pattern[2] == 'd'))) { if ((Tcl_FindHashEntry(hTblPtr, pattern) != NULL) && (Tcl_ListObjAppendElement(interp, resultPtr, Tcl_NewStringObj(pattern, -1)) != TCL_OK)) { goto error; } goto done; } for (hPtr = Tcl_FirstHashEntry(hTblPtr, &hSearch); hPtr != NULL; hPtr = Tcl_NextHashEntry(&hSearch)) { statePtr = ((Channel *) Tcl_GetHashValue(hPtr))->state; if (statePtr->topChanPtr == (Channel *) tsdPtr->stdinChannel) { name = "stdin"; } else if (statePtr->topChanPtr == (Channel *) tsdPtr->stdoutChannel) { name = "stdout"; } else if (statePtr->topChanPtr == (Channel *) tsdPtr->stderrChannel) { name = "stderr"; } else { /* * This is also stored in Tcl_GetHashKey(hTblPtr, hPtr), but it's * simpler to just grab the name from the statePtr. */ name = statePtr->channelName; } if (((pattern == NULL) || Tcl_StringMatch(name, pattern)) && (Tcl_ListObjAppendElement(interp, resultPtr, Tcl_NewStringObj(name, -1)) != TCL_OK)) { error: TclDecrRefCount(resultPtr); return TCL_ERROR; } } done: Tcl_SetObjResult(interp, resultPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_IsChannelRegistered -- * * Checks whether the channel is associated with the interp. See also * Tcl_RegisterChannel and Tcl_UnregisterChannel. * * Results: * 0 if the channel is not registered in the interpreter, 1 else. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_IsChannelRegistered( Tcl_Interp *interp, /* The interp to query of the channel */ Tcl_Channel chan) /* The channel to check */ { Tcl_HashTable *hTblPtr; /* Hash table of channels. */ Tcl_HashEntry *hPtr; /* Search variable. */ Channel *chanPtr; /* The real IO channel. */ ChannelState *statePtr; /* State of the real channel. */ /* * Always check bottom-most channel in the stack. This is the one that * gets registered. */ chanPtr = ((Channel *) chan)->state->bottomChanPtr; statePtr = chanPtr->state; hTblPtr = (Tcl_HashTable *)Tcl_GetAssocData(interp, "tclIO", NULL); if (hTblPtr == NULL) { return 0; } hPtr = Tcl_FindHashEntry(hTblPtr, statePtr->channelName); if (hPtr == NULL) { return 0; } if ((Channel *) Tcl_GetHashValue(hPtr) != chanPtr) { return 0; } return 1; } /* *---------------------------------------------------------------------- * * Tcl_IsChannelShared -- * * Checks whether the channel is shared by multiple interpreters. * * Results: * A boolean value (0 = Not shared, 1 = Shared). * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_IsChannelShared( Tcl_Channel chan) /* The channel to query */ { ChannelState *statePtr = ((Channel *) chan)->state; /* State of real channel structure. */ return ((statePtr->refCount > 1) ? 1 : 0); } /* *---------------------------------------------------------------------- * * Tcl_IsChannelExisting -- * * Checks whether a channel of the given name exists in the * (thread)-global list of all channels. See Tcl_GetChannelNamesEx for * function exposed at the Tcl level. * * Results: * A boolean value (0 = Does not exist, 1 = Does exist). * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_IsChannelExisting( const char *chanName) /* The name of the channel to look for. */ { ChannelState *statePtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); const char *name; int chanNameLen; chanNameLen = strlen(chanName); for (statePtr = tsdPtr->firstCSPtr; statePtr != NULL; statePtr = statePtr->nextCSPtr) { if (statePtr->topChanPtr == (Channel *) tsdPtr->stdinChannel) { name = "stdin"; } else if (statePtr->topChanPtr == (Channel *) tsdPtr->stdoutChannel) { name = "stdout"; } else if (statePtr->topChanPtr == (Channel *) tsdPtr->stderrChannel) { name = "stderr"; } else { name = statePtr->channelName; } if ((*chanName == *name) && (memcmp(name, chanName, chanNameLen + 1) == 0)) { return 1; } } return 0; } /* *---------------------------------------------------------------------- * * Tcl_ChannelName -- * * Return the name of the channel type. * * Results: * A pointer the name of the channel type. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * Tcl_ChannelName( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->typeName; } /* *---------------------------------------------------------------------- * * Tcl_ChannelVersion -- * * Return the of version of the channel type. * * Results: * One of the TCL_CHANNEL_VERSION_* constants from tcl.h * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_ChannelTypeVersion Tcl_ChannelVersion( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->version; } /* *---------------------------------------------------------------------- * * Tcl_ChannelBlockModeProc -- * * Return the Tcl_DriverBlockModeProc of the channel type. * * Results: * A pointer to the proc. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_DriverBlockModeProc * Tcl_ChannelBlockModeProc( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->blockModeProc; } /* *---------------------------------------------------------------------- * * Tcl_ChannelClose2Proc -- * * Return the Tcl_DriverClose2Proc of the channel type. * * Results: * A pointer to the proc. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_DriverClose2Proc * Tcl_ChannelClose2Proc( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->close2Proc; } /* *---------------------------------------------------------------------- * * Tcl_ChannelInputProc -- * * Return the Tcl_DriverInputProc of the channel type. * * Results: * A pointer to the proc. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_DriverInputProc * Tcl_ChannelInputProc( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->inputProc; } /* *---------------------------------------------------------------------- * * Tcl_ChannelOutputProc -- * * Return the Tcl_DriverOutputProc of the channel type. * * Results: * A pointer to the proc. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_DriverOutputProc * Tcl_ChannelOutputProc( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->outputProc; } /* *---------------------------------------------------------------------- * * Tcl_ChannelSetOptionProc -- * * Return the Tcl_DriverSetOptionProc of the channel type. * * Results: * A pointer to the proc. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_DriverSetOptionProc * Tcl_ChannelSetOptionProc( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->setOptionProc; } /* *---------------------------------------------------------------------- * * Tcl_ChannelGetOptionProc -- * * Return the Tcl_DriverGetOptionProc of the channel type. * * Results: * A pointer to the proc. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_DriverGetOptionProc * Tcl_ChannelGetOptionProc( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->getOptionProc; } /* *---------------------------------------------------------------------- * * Tcl_ChannelWatchProc -- * * Return the Tcl_DriverWatchProc of the channel type. * * Results: * A pointer to the proc. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_DriverWatchProc * Tcl_ChannelWatchProc( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->watchProc; } /* *---------------------------------------------------------------------- * * Tcl_ChannelGetHandleProc -- * * Return the Tcl_DriverGetHandleProc of the channel type. * * Results: * A pointer to the proc. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_DriverGetHandleProc * Tcl_ChannelGetHandleProc( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->getHandleProc; } /* *---------------------------------------------------------------------- * * Tcl_ChannelFlushProc -- * * Return the Tcl_DriverFlushProc of the channel type. * * Results: * A pointer to the proc. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_DriverFlushProc * Tcl_ChannelFlushProc( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->flushProc; } /* *---------------------------------------------------------------------- * * Tcl_ChannelHandlerProc -- * * Return the Tcl_DriverHandlerProc of the channel type. * * Results: * A pointer to the proc. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_DriverHandlerProc * Tcl_ChannelHandlerProc( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->handlerProc; } /* *---------------------------------------------------------------------- * * Tcl_ChannelWideSeekProc -- * * Return the Tcl_DriverWideSeekProc of the channel type. * * Results: * A pointer to the proc. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_DriverWideSeekProc * Tcl_ChannelWideSeekProc( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->wideSeekProc; } /* *---------------------------------------------------------------------- * * Tcl_ChannelThreadActionProc -- * * TIP #218, Channel Thread Actions. Return the * Tcl_DriverThreadActionProc of the channel type. * * Results: * A pointer to the proc. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_DriverThreadActionProc * Tcl_ChannelThreadActionProc( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->threadActionProc; } /* *---------------------------------------------------------------------- * * Tcl_SetChannelErrorInterp -- * * TIP #219, Tcl Channel Reflection API. * Store an error message for the I/O system. * * Results: * None. * * Side effects: * Discards a previously stored message. * *---------------------------------------------------------------------- */ void Tcl_SetChannelErrorInterp( Tcl_Interp *interp, /* Interp to store the data into. */ Tcl_Obj *msg) /* Error message to store. */ { Interp *iPtr = (Interp *) interp; Tcl_Obj *disposePtr = iPtr->chanMsg; if (msg != NULL) { iPtr->chanMsg = FixLevelCode(msg); Tcl_IncrRefCount(iPtr->chanMsg); } else { iPtr->chanMsg = NULL; } if (disposePtr != NULL) { TclDecrRefCount(disposePtr); } return; } /* *---------------------------------------------------------------------- * * Tcl_SetChannelError -- * * TIP #219, Tcl Channel Reflection API. * Store an error message for the I/O system. * * Results: * None. * * Side effects: * Discards a previously stored message. * *---------------------------------------------------------------------- */ void Tcl_SetChannelError( Tcl_Channel chan, /* Channel to store the data into. */ Tcl_Obj *msg) /* Error message to store. */ { ChannelState *statePtr = ((Channel *)chan)->state; Tcl_Obj *disposePtr = statePtr->chanMsg; if (msg != NULL) { statePtr->chanMsg = FixLevelCode(msg); Tcl_IncrRefCount(statePtr->chanMsg); } else { statePtr->chanMsg = NULL; } if (disposePtr != NULL) { TclDecrRefCount(disposePtr); } return; } /* *---------------------------------------------------------------------- * * FixLevelCode -- * * TIP #219, Tcl Channel Reflection API. * Scans an error message for bad -code / -level directives. Returns a * modified copy with such directives corrected, and the input if it had * no problems. * * Results: * A Tcl_Obj* * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_Obj * FixLevelCode( Tcl_Obj *msg) { int explicitResult, numOptions, lcn; Tcl_Size lc; Tcl_Obj **lv, **lvn; int res, i, j, val, lignore, cignore; int newlevel = -1, newcode = -1; /* ASSERT msg != NULL */ /* * Process the caught message. * * Syntax = (option value)... ?message? * * Bad message syntax causes a panic, because the other side uses * Tcl_GetReturnOptions and list construction functions to marshal the * information. Hence an error means that we've got serious breakage. */ res = TclListObjGetElements(NULL, msg, &lc, &lv); if (res != TCL_OK) { Tcl_Panic("Tcl_SetChannelError: bad syntax of message"); } explicitResult = (1 == (lc % 2)); numOptions = lc - explicitResult; /* * No options, nothing to do. */ if (numOptions == 0) { return msg; } /* * Check for -code x, x != 1|error, and -level x, x != 0 */ for (i = 0; i < numOptions; i += 2) { if (0 == strcmp(TclGetString(lv[i]), "-code")) { /* * !"error", !integer, integer != 1 (numeric code for error) */ res = TclGetIntFromObj(NULL, lv[i+1], &val); if (((res == TCL_OK) && (val != 1)) || ((res != TCL_OK) && (0 != strcmp(TclGetString(lv[i+1]), "error")))) { newcode = 1; } } else if (0 == strcmp(TclGetString(lv[i]), "-level")) { /* * !integer, integer != 0 */ res = TclGetIntFromObj(NULL, lv [i+1], &val); if ((res != TCL_OK) || (val != 0)) { newlevel = 0; } } } /* * -code, -level are either not present or ok. Nothing to do. */ if ((newlevel < 0) && (newcode < 0)) { return msg; } lcn = numOptions; if (explicitResult) { lcn ++; } if (newlevel >= 0) { lcn += 2; } if (newcode >= 0) { lcn += 2; } lvn = (Tcl_Obj **)Tcl_Alloc(lcn * sizeof(Tcl_Obj *)); /* * New level/code information is spliced into the first occurrence of * -level, -code, further occurrences are ignored. The options cannot be * not present, we would not come here. Options which are ok are simply * copied over. */ lignore = cignore = 0; for (i=0, j=0; i= 0) { lvn[j++] = lv[i]; lvn[j++] = Tcl_NewWideIntObj(newlevel); newlevel = -1; lignore = 1; continue; } else if (lignore) { continue; } } else if (0 == strcmp(TclGetString(lv[i]), "-code")) { if (newcode >= 0) { lvn[j++] = lv[i]; lvn[j++] = Tcl_NewWideIntObj(newcode); newcode = -1; cignore = 1; continue; } else if (cignore) { continue; } } /* * Keep everything else, possibly copied down. */ lvn[j++] = lv[i]; lvn[j++] = lv[i+1]; } if (newlevel >= 0) { Tcl_Panic("Defined newlevel not used in rewrite"); } if (newcode >= 0) { Tcl_Panic("Defined newcode not used in rewrite"); } if (explicitResult) { lvn[j++] = lv[i]; } msg = Tcl_NewListObj(j, lvn); Tcl_Free(lvn); return msg; } /* *---------------------------------------------------------------------- * * Tcl_GetChannelErrorInterp -- * * TIP #219, Tcl Channel Reflection API. * Return the message stored by the channel driver. * * Results: * Tcl error message object. * * Side effects: * Resets the stored data to NULL. * *---------------------------------------------------------------------- */ void Tcl_GetChannelErrorInterp( Tcl_Interp *interp, /* Interp to query. */ Tcl_Obj **msg) /* Place for error message. */ { Interp *iPtr = (Interp *) interp; *msg = iPtr->chanMsg; iPtr->chanMsg = NULL; } /* *---------------------------------------------------------------------- * * Tcl_GetChannelError -- * * TIP #219, Tcl Channel Reflection API. * Return the message stored by the channel driver. * * Results: * Tcl error message object. * * Side effects: * Resets the stored data to NULL. * *---------------------------------------------------------------------- */ void Tcl_GetChannelError( Tcl_Channel chan, /* Channel to query. */ Tcl_Obj **msg) /* Place for error message. */ { ChannelState *statePtr = ((Channel *) chan)->state; *msg = statePtr->chanMsg; statePtr->chanMsg = NULL; } /* *---------------------------------------------------------------------- * * Tcl_ChannelTruncateProc -- * * TIP #208 (subsection relating to truncation, based on TIP #206). * Return the Tcl_DriverTruncateProc of the channel type. * * Results: * A pointer to the proc. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_DriverTruncateProc * Tcl_ChannelTruncateProc( const Tcl_ChannelType *chanTypePtr) /* Pointer to channel type. */ { return chanTypePtr->truncateProc; } /* *---------------------------------------------------------------------- * * DupChannelInternalRep -- * * Initialize the internal representation of a new Tcl_Obj to a copy of * the internal representation of an existing string object. * * Results: * None. * * Side effects: * copyPtr's internal rep is set to a copy of srcPtr's internal * representation. * *---------------------------------------------------------------------- */ static void DupChannelInternalRep( Tcl_Obj *srcPtr, /* Object with internal rep to copy. Must have * an internal rep of type "Channel". */ Tcl_Obj *copyPtr) /* Object with internal rep to set. Must not * currently have an internal rep.*/ { ResolvedChanName *resPtr; ChanGetInternalRep(srcPtr, resPtr); assert(resPtr); ChanSetInternalRep(copyPtr, resPtr); } /* *---------------------------------------------------------------------- * * FreeChannelInternalRep -- * * Release statePtr storage. * * Results: * None. * * Side effects: * May cause state to be freed. * *---------------------------------------------------------------------- */ static void FreeChannelInternalRep( Tcl_Obj *objPtr) /* Object with internal rep to free. */ { ResolvedChanName *resPtr; ChanGetInternalRep(objPtr, resPtr); assert(resPtr); if (resPtr->refCount-- > 1) { return; } Tcl_Release(resPtr->statePtr); Tcl_Free(resPtr); } #if 0 /* * For future debugging work, a simple function to print the flags of a * channel in semi-readable form. */ static int DumpFlags( char *str, int flags) { int i = 0; char buf[24]; #define ChanFlag(chr, bit) (buf[i++] = ((flags & (bit)) ? (chr) : '_')) ChanFlag('r', TCL_READABLE); ChanFlag('w', TCL_WRITABLE); ChanFlag('n', CHANNEL_NONBLOCKING); ChanFlag('l', CHANNEL_LINEBUFFERED); ChanFlag('u', CHANNEL_UNBUFFERED); ChanFlag('F', BG_FLUSH_SCHEDULED); ChanFlag('c', CHANNEL_CLOSED); ChanFlag('E', CHANNEL_EOF); ChanFlag('S', CHANNEL_STICKY_EOF); ChanFlag('U', CHANNEL_ENCODING_ERROR); ChanFlag('B', CHANNEL_BLOCKED); ChanFlag('/', INPUT_SAW_CR); ChanFlag('D', CHANNEL_DEAD); ChanFlag('R', CHANNEL_RAW_MODE); ChanFlag('x', CHANNEL_INCLOSE); buf[i] ='\0'; fprintf(stderr, "%s: %s\n", str, buf); return 0; } #endif /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * indent-tabs-mode: nil * End: */ tcl9.0.1/generic/tclIO.h0000644000175000017500000002754214726623136014367 0ustar sergeisergei/* * tclIO.h -- * * This file provides the generic portions (those that are the same on * all platforms and for all channel types) of Tcl's IO facilities. * * Copyright (c) 1998-2000 Ajuba Solutions * Copyright (c) 1995-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ /* * Make sure that both EAGAIN and EWOULDBLOCK are defined. This does not * compile on systems where neither is defined. We want both defined so that * we can test safely for both. In the code we still have to test for both * because there may be systems on which both are defined and have different * values. */ #if ((!defined(EWOULDBLOCK)) && (defined(EAGAIN))) # define EWOULDBLOCK EAGAIN #endif #if ((!defined(EAGAIN)) && (defined(EWOULDBLOCK))) # define EAGAIN EWOULDBLOCK #endif #if ((!defined(EAGAIN)) && (!defined(EWOULDBLOCK))) #error one of EWOULDBLOCK or EAGAIN must be defined #endif /* * struct ChannelBuffer: * * Buffers data being sent to or from a channel. */ typedef struct ChannelBuffer { Tcl_Size refCount; /* Current uses count */ Tcl_Size nextAdded; /* The next position into which a character * will be put in the buffer. */ Tcl_Size nextRemoved; /* Position of next byte to be removed from * the buffer. */ Tcl_Size bufLength; /* How big is the buffer? */ struct ChannelBuffer *nextPtr; /* Next buffer in chain. */ char buf[TCLFLEXARRAY]; /* Placeholder for real buffer. The real * buffer occupies this space + bufSize-1 * bytes. This must be the last field in the * structure. */ } ChannelBuffer; #define CHANNELBUFFER_HEADER_SIZE offsetof(ChannelBuffer, buf) /* * How much extra space to allocate in buffer to hold bytes from previous * buffer (when converting to UTF-8) or to hold bytes that will go to next * buffer (when converting from UTF-8). */ #define BUFFER_PADDING 16 /* * The following defines the *default* buffer size for channels. */ #define CHANNELBUFFER_DEFAULT_SIZE (1024 * 4) /* * The following structure describes the information saved from a call to * "fileevent". This is used later when the event being waited for to invoke * the saved script in the interpreter designed in this record. */ typedef struct EventScriptRecord { struct Channel *chanPtr; /* The channel for which this script is * registered. This is used only when an error * occurs during evaluation of the script, to * delete the handler. */ Tcl_Obj *scriptPtr; /* Script to invoke. */ Tcl_Interp *interp; /* In what interpreter to invoke script? */ int mask; /* Events must overlap current mask for the * stored script to be invoked. */ struct EventScriptRecord *nextPtr; /* Next in chain of records. */ } EventScriptRecord; /* * struct Channel: * * One of these structures is allocated for each open channel. It contains * data specific to the channel but which belongs to the generic part of the * Tcl channel mechanism, and it points at an instance specific (and type * specific) instance data, and at a channel type structure. */ typedef struct Channel { struct ChannelState *state; /* Split out state information */ void *instanceData; /* Instance-specific data provided by creator * of channel. */ const Tcl_ChannelType *typePtr; /* Pointer to channel type structure. */ struct Channel *downChanPtr;/* Refers to channel this one was stacked * upon. This reference is NULL for normal * channels. See Tcl_StackChannel. */ struct Channel *upChanPtr; /* Refers to the channel above stacked this * one. NULL for the top most channel. */ /* * Intermediate buffers to hold pre-read data for consumption by a newly * stacked transformation. See 'Tcl_StackChannel'. */ ChannelBuffer *inQueueHead; /* Points at first buffer in input queue. */ ChannelBuffer *inQueueTail; /* Points at last buffer in input queue. */ Tcl_Size refCount; } Channel; /* * struct ChannelState: * * One of these structures is allocated for each open channel. It contains * data specific to the channel but which belongs to the generic part of the * Tcl channel mechanism, and it points at an instance specific (and type * specific) instance data, and at a channel type structure. */ typedef struct ChannelState { char *channelName; /* The name of the channel instance in Tcl * commands. Storage is owned by the generic * IO code, is dynamically allocated. */ int flags; /* OR'ed combination of the flags defined * below. */ Tcl_Encoding encoding; /* Encoding to apply when reading or writing * data on this channel. NULL means no * encoding is applied to data. */ Tcl_EncodingState inputEncodingState; /* Current encoding state, used when * converting input data bytes to UTF-8. */ int inputEncodingFlags; /* Encoding flags to pass to conversion * routine when converting input data bytes to * UTF-8. May be TCL_ENCODING_START before * converting first byte and TCL_ENCODING_END * when EOF is seen. */ Tcl_EncodingState outputEncodingState; /* Current encoding state, used when * converting UTF-8 to output data bytes. */ int outputEncodingFlags; /* Encoding flags to pass to conversion * routine when converting UTF-8 to output * data bytes. May be TCL_ENCODING_START * before converting first byte and * TCL_ENCODING_END when EOF is seen. */ TclEolTranslation inputTranslation; /* What translation to apply for end of line * sequences on input? */ TclEolTranslation outputTranslation; /* What translation to use for generating end * of line sequences in output? */ int inEofChar; /* If nonzero, use this as a signal of EOF on * input. */ #if TCL_MAJOR_VERSION < 9 int outEofChar; /* If nonzero, append this to the channel when * it is closed if it is open for writing. * For Tcl 8.x only */ #endif int unreportedError; /* Non-zero if an error report was deferred * because it happened in the background. The * value is the POSIX error code. */ Tcl_Size refCount; /* How many interpreters hold references to * this IO channel? */ struct CloseCallback *closeCbPtr; /* Callbacks registered to be called when the * channel is closed. */ char *outputStage; /* Temporary staging buffer used when * translating EOL before converting from * UTF-8 to external form. */ ChannelBuffer *curOutPtr; /* Current output buffer being filled. */ ChannelBuffer *outQueueHead;/* Points at first buffer in output queue. */ ChannelBuffer *outQueueTail;/* Points at last buffer in output queue. */ ChannelBuffer *saveInBufPtr;/* Buffer saved for input queue - eliminates * need to allocate a new buffer for "gets" * that crosses buffer boundaries. */ ChannelBuffer *inQueueHead; /* Points at first buffer in input queue. */ ChannelBuffer *inQueueTail; /* Points at last buffer in input queue. */ struct ChannelHandler *chPtr;/* List of channel handlers registered for * this channel. */ int interestMask; /* Mask of all events this channel has * handlers for. */ EventScriptRecord *scriptRecordPtr; /* Chain of all scripts registered for event * handlers ("fileevent") on this channel. */ Tcl_Size bufSize; /* What size buffers to allocate? */ Tcl_TimerToken timer; /* Handle to wakeup timer for this channel. */ Channel *timerChanPtr; /* Needed in order to decrement the refCount of * the right channel when the timer is * deleted. */ struct CopyState *csPtrR; /* State of background copy for which channel * is input, or NULL. */ struct CopyState *csPtrW; /* State of background copy for which channel * is output, or NULL. */ Channel *topChanPtr; /* Refers to topmost channel in a stack. Never * NULL. */ Channel *bottomChanPtr; /* Refers to bottommost channel in a stack. * This channel can be relied on to live as * long as the channel state. Never NULL. */ struct ChannelState *nextCSPtr; /* Next in list of channels currently open. */ Tcl_ThreadId managingThread;/* TIP #10: Id of the thread managing this * stack of channels. */ /* * TIP #219 ... Info for the I/O system ... * Error message set by channel drivers, for the propagation of arbitrary * Tcl errors. This information, if present (chanMsg not NULL), takes * precedence over a Posix error code returned by a channel operation. */ Tcl_Obj *chanMsg; Tcl_Obj *unreportedMsg; /* Non-NULL if an error report was deferred * because it happened in the background. The * value is the chanMg, if any. #219's * companion to 'unreportedError'. */ Tcl_Size epoch; /* Used to test validity of stored channelname * lookup results. */ int maxPerms; /* TIP #220: Max access privileges * the channel was created with. */ } ChannelState; /* * Values for the flags field in Channel. Any OR'ed combination of the * following flags can be stored in the field. These flags record various * options and state bits about the channel. In addition to the flags below, * the channel can also have TCL_READABLE (1<<1) and TCL_WRITABLE (1<<2) set. */ #define CHANNEL_NONBLOCKING (1<<6) /* Channel is currently in nonblocking * mode. */ #define BG_FLUSH_SCHEDULED (1<<7) /* A background flush of the queued * output buffers has been * scheduled. */ #define CHANNEL_CLOSED (1<<8) /* Channel has been closed. No further * Tcl-level IO on the channel is * allowed. */ #define CHANNEL_EOF (1<<9) /* EOF occurred on this channel. This * bit is cleared before every input * operation. */ #define CHANNEL_STICKY_EOF (1<<10) /* EOF occurred on this channel * because we saw the input * eofChar. This bit prevents clearing * of the EOF bit before every input * operation. */ #define CHANNEL_BLOCKED (1<<11) /* EWOULDBLOCK or EAGAIN occurred on * this channel. This bit is cleared * before every input or output * operation. */ #define INPUT_SAW_CR (1<<12) /* Channel is in CRLF eol input * translation mode and the last byte * seen was a "\r". */ #define CHANNEL_DEAD (1<<13) /* The channel has been closed by the * exit handler (on exit) but not * deallocated. When any IO operation * sees this flag on a channel, it * does not call driver level * functions to avoid referring to * deallocated data. */ #define CHANNEL_NEED_MORE_DATA (1<<14) /* The last input operation failed * because there was not enough data * to complete the operation. This * flag is set when gets fails to get * a complete line or when read fails * to get a complete character. When * set, file events will not be * delivered for buffered data until * the state of the channel * changes. */ #define CHANNEL_ENCODING_ERROR (1<<15) /* set if channel * encountered an encoding error */ #define CHANNEL_RAW_MODE (1<<16) /* When set, notes that the Raw API is * being used. */ #define CHANNEL_LINEBUFFERED (1<<17) /* Output to the channel must be * flushed after every newline. */ #define CHANNEL_UNBUFFERED (1<<18) /* Output to the channel must always * be flushed immediately. */ #define CHANNEL_INCLOSE (1<<19) /* Channel is currently being closed. * Its structures are still live and * usable, but it may not be closed * again from within the close * handler. */ #define CHANNEL_CLOSEDWRITE (1<<21) /* Channel write side has been closed. * No further Tcl-level write IO on * the channel is allowed. */ /* * The length of time to wait between synthetic timer events. Must be zero or * bad things tend to happen. */ #define SYNTHETIC_EVENT_TIME 0 /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclIOCmd.c0000644000175000017500000015606214726623136015006 0ustar sergeisergei/* * tclIOCmd.c -- * * Contains the definitions of most of the Tcl commands relating to IO. * * Copyright © 1995-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclIO.h" #include "tclTomMath.h" /* * Callback structure for accept callback in a TCP server. */ typedef struct { Tcl_Obj *script; /* Script to invoke. */ Tcl_Interp *interp; /* Interpreter in which to run it. */ } AcceptCallback; /* * Thread local storage used to maintain a per-thread stdout channel obj. * It must be per-thread because of std channel limitations. */ typedef struct { int initialized; /* Set to 1 when the module is initialized. */ Tcl_Obj *stdoutObjPtr; /* Cached stdout channel Tcl_Obj */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * Static functions for this file: */ static Tcl_ExitProc FinalizeIOCmdTSD; static Tcl_TcpAcceptProc AcceptCallbackProc; static Tcl_ObjCmdProc ChanPendingObjCmd; static Tcl_ObjCmdProc ChanTruncateObjCmd; static void RegisterTcpServerInterpCleanup( Tcl_Interp *interp, AcceptCallback *acceptCallbackPtr); static Tcl_InterpDeleteProc TcpAcceptCallbacksDeleteProc; static void TcpServerCloseProc(void *callbackData); static void UnregisterTcpServerInterpCleanupProc( Tcl_Interp *interp, AcceptCallback *acceptCallbackPtr); /* *---------------------------------------------------------------------- * * FinalizeIOCmdTSD -- * * Release the storage associated with the per-thread cache. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void FinalizeIOCmdTSD( TCL_UNUSED(void *)) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (tsdPtr->stdoutObjPtr != NULL) { Tcl_DecrRefCount(tsdPtr->stdoutObjPtr); tsdPtr->stdoutObjPtr = NULL; } tsdPtr->initialized = 0; } /* *---------------------------------------------------------------------- * * Tcl_PutsObjCmd -- * * This function is invoked to process the "puts" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Produces output on a channel. * *---------------------------------------------------------------------- */ int Tcl_PutsObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Channel chan; /* The channel to puts on. */ Tcl_Obj *string; /* String to write. */ Tcl_Obj *chanObjPtr = NULL; /* channel object. */ int newline; /* Add a newline at end? */ Tcl_Size result; /* Result of puts operation. */ int mode; /* Mode in which channel is opened. */ switch (objc) { case 2: /* [puts $x] */ string = objv[1]; newline = 1; break; case 3: /* [puts -nonewline $x] or [puts $chan $x] */ if (strcmp(TclGetString(objv[1]), "-nonewline") == 0) { newline = 0; } else { newline = 1; chanObjPtr = objv[1]; } string = objv[2]; break; case 4: /* [puts -nonewline $chan $x] or * [puts $chan $x nonewline] */ newline = 0; if (strcmp(TclGetString(objv[1]), "-nonewline") == 0) { chanObjPtr = objv[2]; string = objv[3]; break; } /* Fall through */ default: /* [puts] or * [puts some bad number of arguments...] */ Tcl_WrongNumArgs(interp, 1, objv, "?-nonewline? ?channel? string"); return TCL_ERROR; } if (chanObjPtr == NULL) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!tsdPtr->initialized) { tsdPtr->initialized = 1; TclNewLiteralStringObj(tsdPtr->stdoutObjPtr, "stdout"); Tcl_IncrRefCount(tsdPtr->stdoutObjPtr); Tcl_CreateThreadExitHandler(FinalizeIOCmdTSD, NULL); } chanObjPtr = tsdPtr->stdoutObjPtr; } if (TclGetChannelFromObj(interp, chanObjPtr, &chan, &mode, 0) != TCL_OK) { return TCL_ERROR; } if (!(mode & TCL_WRITABLE)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "channel \"%s\" wasn't opened for writing", TclGetString(chanObjPtr))); return TCL_ERROR; } TclChannelPreserve(chan); result = Tcl_WriteObj(chan, string); if (result == TCL_INDEX_NONE) { goto error; } if (newline != 0) { result = Tcl_WriteChars(chan, "\n", 1); if (result == TCL_INDEX_NONE) { goto error; } } TclChannelRelease(chan); return TCL_OK; /* * TIP #219. * Capture error messages put by the driver into the bypass area and put * them into the regular interpreter result. Fall back to the regular * message if nothing was found in the bypass. */ error: if (!TclChanCaughtErrorBypass(interp, chan)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("error writing \"%s\": %s", TclGetString(chanObjPtr), Tcl_PosixError(interp))); } TclChannelRelease(chan); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_FlushObjCmd -- * * This function is called to process the Tcl "flush" command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * May cause output to appear on the specified channel. * *---------------------------------------------------------------------- */ int Tcl_FlushObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *chanObjPtr; Tcl_Channel chan; /* The channel to flush on. */ int mode; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "channel"); return TCL_ERROR; } chanObjPtr = objv[1]; if (TclGetChannelFromObj(interp, chanObjPtr, &chan, &mode, 0) != TCL_OK) { return TCL_ERROR; } if (!(mode & TCL_WRITABLE)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "channel \"%s\" wasn't opened for writing", TclGetString(chanObjPtr))); return TCL_ERROR; } TclChannelPreserve(chan); if (Tcl_Flush(chan) != TCL_OK) { /* * TIP #219. * Capture error messages put by the driver into the bypass area and * put them into the regular interpreter result. Fall back to the * regular message if nothing was found in the bypass. */ if (!TclChanCaughtErrorBypass(interp, chan)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error flushing \"%s\": %s", TclGetString(chanObjPtr), Tcl_PosixError(interp))); } TclChannelRelease(chan); return TCL_ERROR; } TclChannelRelease(chan); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_GetsObjCmd -- * * This function is called to process the Tcl "gets" command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * May consume input from channel. * *---------------------------------------------------------------------- */ int Tcl_GetsObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Channel chan; /* The channel to read from. */ Tcl_Size lineLen; /* Length of line just read. */ int mode; /* Mode in which channel is opened. */ Tcl_Obj *linePtr, *chanObjPtr; int code = TCL_OK; if ((objc != 2) && (objc != 3)) { Tcl_WrongNumArgs(interp, 1, objv, "channel ?varName?"); return TCL_ERROR; } chanObjPtr = objv[1]; if (TclGetChannelFromObj(interp, chanObjPtr, &chan, &mode, 0) != TCL_OK) { return TCL_ERROR; } if (!(mode & TCL_READABLE)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "channel \"%s\" wasn't opened for reading", TclGetString(chanObjPtr))); return TCL_ERROR; } TclChannelPreserve(chan); TclNewObj(linePtr); lineLen = Tcl_GetsObj(chan, linePtr); if (lineLen == TCL_IO_FAILURE) { if (!Tcl_Eof(chan) && !Tcl_InputBlocked(chan)) { Tcl_DecrRefCount(linePtr); /* * TIP #219. * Capture error messages put by the driver into the bypass area * and put them into the regular interpreter result. Fall back to * the regular message if nothing was found in the bypass. */ if (!TclChanCaughtErrorBypass(interp, chan)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error reading \"%s\": %s", TclGetString(chanObjPtr), Tcl_PosixError(interp))); } code = TCL_ERROR; goto done; } lineLen = TCL_IO_FAILURE; } if (objc == 3) { if (Tcl_ObjSetVar2(interp, objv[2], NULL, linePtr, TCL_LEAVE_ERR_MSG) == NULL) { code = TCL_ERROR; goto done; } Tcl_Obj *lineLenObj; TclNewIndexObj(lineLenObj, lineLen); Tcl_SetObjResult(interp, lineLenObj); } else { Tcl_SetObjResult(interp, linePtr); } done: TclChannelRelease(chan); return code; } /* *---------------------------------------------------------------------- * * Tcl_ReadObjCmd -- * * This function is invoked to process the Tcl "read" command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * May consume input from channel. * *---------------------------------------------------------------------- */ int Tcl_ReadObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Channel chan; /* The channel to read from. */ int newline, i; /* Discard newline at end? */ Tcl_WideInt toRead; /* How many bytes to read? */ Tcl_Size charactersRead; /* How many characters were read? */ int mode; /* Mode in which channel is opened. */ Tcl_Obj *resultPtr, *chanObjPtr; if ((objc != 2) && (objc != 3)) { Interp *iPtr; argerror: iPtr = (Interp *) interp; Tcl_WrongNumArgs(interp, 1, objv, "channel ?numChars?"); /* * Do not append directly; that makes ensembles using this command as * a subcommand produce the wrong message. */ iPtr->flags |= INTERP_ALTERNATE_WRONG_ARGS; Tcl_WrongNumArgs(interp, 1, objv, "?-nonewline? channel"); return TCL_ERROR; } i = 1; newline = 0; if (strcmp(TclGetString(objv[1]), "-nonewline") == 0) { newline = 1; i++; } if (i == objc) { goto argerror; } chanObjPtr = objv[i]; if (TclGetChannelFromObj(interp, chanObjPtr, &chan, &mode, 0) != TCL_OK) { return TCL_ERROR; } if (!(mode & TCL_READABLE)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "channel \"%s\" wasn't opened for reading", TclGetString(chanObjPtr))); return TCL_ERROR; } i++; /* Consumed channel name. */ /* * Compute how many bytes to read. */ toRead = -1; if (i < objc) { if ((TclGetWideIntFromObj(NULL, objv[i], &toRead) != TCL_OK) || (toRead < 0)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected non-negative integer but got \"%s\"", TclGetString(objv[i]))); Tcl_SetErrorCode(interp, "TCL", "VALUE", "NUMBER", (char *)NULL); return TCL_ERROR; } } TclNewObj(resultPtr); TclChannelPreserve(chan); charactersRead = Tcl_ReadChars(chan, resultPtr, toRead, 0); if (charactersRead == TCL_IO_FAILURE) { Tcl_Obj *returnOptsPtr = NULL; if (TclChannelGetBlockingMode(chan)) { returnOptsPtr = Tcl_NewDictObj(); Tcl_DictObjPut(NULL, returnOptsPtr, Tcl_NewStringObj("-data", -1), resultPtr); } else { Tcl_DecrRefCount(resultPtr); } /* * TIP #219. * Capture error messages put by the driver into the bypass area and * put them into the regular interpreter result. Fall back to the * regular message if nothing was found in the bypass. */ if (!TclChanCaughtErrorBypass(interp, chan)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error reading \"%s\": %s", TclGetString(chanObjPtr), Tcl_PosixError(interp))); } TclChannelRelease(chan); if (returnOptsPtr) { Tcl_SetReturnOptions(interp, returnOptsPtr); } return TCL_ERROR; } /* * If requested, remove the last newline in the channel if at EOF. */ if ((charactersRead > 0) && (newline != 0)) { const char *result; Tcl_Size length; result = TclGetStringFromObj(resultPtr, &length); if (result[length - 1] == '\n') { Tcl_SetObjLength(resultPtr, length - 1); } } Tcl_SetObjResult(interp, resultPtr); TclChannelRelease(chan); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_SeekObjCmd -- * * This function is invoked to process the Tcl "seek" command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Moves the position of the access point on the specified channel. May * flush queued output. * *---------------------------------------------------------------------- */ int Tcl_SeekObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Channel chan; /* The channel to tell on. */ Tcl_WideInt offset; /* Where to seek? */ int mode; /* How to seek? */ Tcl_WideInt result; /* Of calling Tcl_Seek. */ int optionIndex; static const char *const originOptions[] = { "start", "current", "end", NULL }; static const int modeArray[] = {SEEK_SET, SEEK_CUR, SEEK_END}; if ((objc != 3) && (objc != 4)) { Tcl_WrongNumArgs(interp, 1, objv, "channel offset ?origin?"); return TCL_ERROR; } if (TclGetChannelFromObj(interp, objv[1], &chan, NULL, 0) != TCL_OK) { return TCL_ERROR; } if (TclGetWideIntFromObj(interp, objv[2], &offset) != TCL_OK) { return TCL_ERROR; } mode = SEEK_SET; if (objc == 4) { if (Tcl_GetIndexFromObj(interp, objv[3], originOptions, "origin", 0, &optionIndex) != TCL_OK) { return TCL_ERROR; } mode = modeArray[optionIndex]; } TclChannelPreserve(chan); result = Tcl_Seek(chan, offset, mode); if (result == -1) { /* * TIP #219. * Capture error messages put by the driver into the bypass area and * put them into the regular interpreter result. Fall back to the * regular message if nothing was found in the bypass. */ if (!TclChanCaughtErrorBypass(interp, chan)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error during seek on \"%s\": %s", TclGetString(objv[1]), Tcl_PosixError(interp))); } TclChannelRelease(chan); return TCL_ERROR; } TclChannelRelease(chan); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_TellObjCmd -- * * This function is invoked to process the Tcl "tell" command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_TellObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Channel chan; /* The channel to tell on. */ Tcl_WideInt newLoc; int code; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "channel"); return TCL_ERROR; } /* * Try to find a channel with the right name and permissions in the IO * channel table of this interpreter. */ if (TclGetChannelFromObj(interp, objv[1], &chan, NULL, 0) != TCL_OK) { return TCL_ERROR; } TclChannelPreserve(chan); newLoc = Tcl_Tell(chan); /* * TIP #219. * Capture error messages put by the driver into the bypass area and put * them into the regular interpreter result. */ code = TclChanCaughtErrorBypass(interp, chan); TclChannelRelease(chan); if (code) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(newLoc)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_CloseObjCmd -- * * This function is invoked to process the Tcl "close" command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * May discard queued input; may flush queued output. * *---------------------------------------------------------------------- */ int Tcl_CloseObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Channel chan; /* The channel to close. */ static const char *const dirOptions[] = { "read", "write", NULL }; static const int dirArray[] = {TCL_CLOSE_READ, TCL_CLOSE_WRITE}; if ((objc != 2) && (objc != 3)) { Tcl_WrongNumArgs(interp, 1, objv, "channel ?direction?"); return TCL_ERROR; } if (TclGetChannelFromObj(interp, objv[1], &chan, NULL, 0) != TCL_OK) { return TCL_ERROR; } if (objc == 3) { int index, dir; /* * Get direction requested to close, and check syntax. */ if (Tcl_GetIndexFromObj(interp, objv[2], dirOptions, "direction", 0, &index) != TCL_OK) { return TCL_ERROR; } dir = dirArray[index]; /* * Check direction against channel mode. It is an error if we try to * close a direction not supported by the channel (already closed, or * never opened for that direction). */ if (!(dir & Tcl_GetChannelMode(chan))) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Half-close of %s-side not possible, side not opened" " or already closed", dirOptions[index])); return TCL_ERROR; } /* * Special handling is needed if and only if the channel mode supports * more than the direction to close. Because if the close the last * direction supported we can and will go through the regular * process. */ if ((Tcl_GetChannelMode(chan) & (TCL_CLOSE_READ|TCL_CLOSE_WRITE)) != dir) { return Tcl_CloseEx(interp, chan, dir); } } if (Tcl_UnregisterChannel(interp, chan) != TCL_OK) { /* * If there is an error message and it ends with a newline, remove the * newline. This is done for command pipeline channels where the error * output from the subprocesses is stored in interp's result. * * NOTE: This is likely to not have any effect on regular error * messages produced by drivers during the closing of a channel, * because the Tcl convention is that such error messages do not have * a terminating newline. */ Tcl_Obj *resultPtr = Tcl_GetObjResult(interp); const char *string; Tcl_Size len; if (Tcl_IsShared(resultPtr)) { resultPtr = Tcl_DuplicateObj(resultPtr); Tcl_SetObjResult(interp, resultPtr); } string = TclGetStringFromObj(resultPtr, &len); if ((len > 0) && (string[len - 1] == '\n')) { Tcl_SetObjLength(resultPtr, len - 1); } return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_FconfigureObjCmd -- * * This function is invoked to process the Tcl "fconfigure" command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * May modify the behavior of an IO channel. * *---------------------------------------------------------------------- */ int Tcl_FconfigureObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *optionName, *valueName; Tcl_Channel chan; /* The channel to set a mode on. */ int i; /* Iterate over arg-value pairs. */ if ((objc < 2) || (((objc % 2) == 1) && (objc != 3))) { Tcl_WrongNumArgs(interp, 1, objv, "channel ?-option value ...?"); return TCL_ERROR; } if (TclGetChannelFromObj(interp, objv[1], &chan, NULL, 0) != TCL_OK) { return TCL_ERROR; } if (objc == 2) { Tcl_DString ds; /* DString to hold result of calling * Tcl_GetChannelOption. */ Tcl_DStringInit(&ds); if (Tcl_GetChannelOption(interp, chan, NULL, &ds) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } Tcl_DStringResult(interp, &ds); return TCL_OK; } else if (objc == 3) { Tcl_DString ds; /* DString to hold result of calling * Tcl_GetChannelOption. */ Tcl_DStringInit(&ds); optionName = TclGetString(objv[2]); if (Tcl_GetChannelOption(interp, chan, optionName, &ds) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } Tcl_DStringResult(interp, &ds); return TCL_OK; } for (i = 3; i < objc; i += 2) { optionName = TclGetString(objv[i-1]); valueName = TclGetString(objv[i]); if (Tcl_SetChannelOption(interp, chan, optionName, valueName) != TCL_OK) { return TCL_ERROR; } } return TCL_OK; } /* *--------------------------------------------------------------------------- * * Tcl_EofObjCmd -- * * This function is invoked to process the Tcl "eof" command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Sets interp's result to boolean true or false depending on whether the * specified channel has an EOF condition. * *--------------------------------------------------------------------------- */ int Tcl_EofObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Channel chan; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "channel"); return TCL_ERROR; } if (TclGetChannelFromObj(interp, objv[1], &chan, NULL, 0) != TCL_OK) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(Tcl_Eof(chan))); return TCL_OK; } /* *--------------------------------------------------------------------------- * * ChanIsBinaryCmd -- * * This function is invoked to process the Tcl "chan isbinary" command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Sets interp's result to boolean true or false depending on whether the * specified channel is a binary channel. * *--------------------------------------------------------------------------- */ static int ChanIsBinaryCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Channel chan; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "channel"); return TCL_ERROR; } if (TclGetChannelFromObj(interp, objv[1], &chan, NULL, 0) != TCL_OK) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(TclChanIsBinary(chan))); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ExecObjCmd -- * * This function is invoked to process the "exec" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_ExecObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *resultPtr; const char **argv; /* An array for the string arguments. Stored * on the _Tcl_ stack. */ const char *string; Tcl_Channel chan; int argc, background, i, index, keepNewline, result, skip, ignoreStderr; Tcl_Size length; static const char *const options[] = { "-ignorestderr", "-keepnewline", "--", NULL }; enum execOptionsEnum { EXEC_IGNORESTDERR, EXEC_KEEPNEWLINE, EXEC_LAST }; /* * Check for any leading option arguments. */ keepNewline = 0; ignoreStderr = 0; for (skip = 1; skip < objc; skip++) { string = TclGetString(objv[skip]); if (string[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, objv[skip], options, "option", TCL_EXACT, &index) != TCL_OK) { return TCL_ERROR; } if (index == EXEC_KEEPNEWLINE) { keepNewline = 1; } else if (index == EXEC_IGNORESTDERR) { ignoreStderr = 1; } else { skip++; break; } } if (objc <= skip) { Tcl_WrongNumArgs(interp, 1, objv, "?-option ...? arg ?arg ...?"); return TCL_ERROR; } /* * See if the command is to be run in background. */ background = 0; string = TclGetString(objv[objc - 1]); if ((string[0] == '&') && (string[1] == '\0')) { objc--; background = 1; } /* * Create the string argument array "argv". Make sure argv is large enough * to hold the argc arguments plus 1 extra for the zero end-of-argv word. */ argc = objc - skip; argv = (const char **)TclStackAlloc(interp, (argc + 1) * sizeof(char *)); /* * Copy the string conversions of each (post option) object into the * argument vector. */ for (i = 0; i < argc; i++) { argv[i] = TclGetString(objv[i + skip]); } argv[argc] = NULL; chan = Tcl_OpenCommandChannel(interp, argc, argv, (background ? 0 : ignoreStderr ? TCL_STDOUT : TCL_STDOUT|TCL_STDERR)); /* * Free the argv array. */ TclStackFree(interp, (void *) argv); if (chan == NULL) { return TCL_ERROR; } /* Bug [0f1ddc0df7] - encoding errors - use replace profile */ if (Tcl_SetChannelOption(NULL, chan, "-profile", "replace") != TCL_OK) { return TCL_ERROR; } if (background) { /* * Store the list of PIDs from the pipeline in interp's result and * detach the PIDs (instead of waiting for them). */ TclGetAndDetachPids(interp, chan); if (Tcl_CloseEx(interp, chan, 0) != TCL_OK) { return TCL_ERROR; } return TCL_OK; } TclNewObj(resultPtr); if (Tcl_GetChannelHandle(chan, TCL_READABLE, NULL) == TCL_OK) { if (Tcl_ReadChars(chan, resultPtr, -1, 0) == TCL_IO_FAILURE) { /* * TIP #219. * Capture error messages put by the driver into the bypass area * and put them into the regular interpreter result. Fall back to * the regular message if nothing was found in the bypass. */ if (!TclChanCaughtErrorBypass(interp, chan)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error reading output from command: %s", Tcl_PosixError(interp))); Tcl_DecrRefCount(resultPtr); } return TCL_ERROR; } } /* * If the process produced anything on stderr, it will have been returned * in the interpreter result. It needs to be appended to the result * string. */ result = Tcl_CloseEx(interp, chan, 0); Tcl_AppendObjToObj(resultPtr, Tcl_GetObjResult(interp)); /* * If the last character of the result is a newline, then remove the * newline character. */ if (keepNewline == 0) { string = TclGetStringFromObj(resultPtr, &length); if ((length > 0) && (string[length - 1] == '\n')) { Tcl_SetObjLength(resultPtr, length - 1); } } Tcl_SetObjResult(interp, resultPtr); return result; } /* *--------------------------------------------------------------------------- * * Tcl_FblockedObjCmd -- * * This function is invoked to process the Tcl "fblocked" command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Sets interp's result to boolean true or false depending on whether the * preceding input operation on the channel would have blocked. * *--------------------------------------------------------------------------- */ int Tcl_FblockedObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Channel chan; int mode; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "channel"); return TCL_ERROR; } if (TclGetChannelFromObj(interp, objv[1], &chan, &mode, 0) != TCL_OK) { return TCL_ERROR; } if (!(mode & TCL_READABLE)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "channel \"%s\" wasn't opened for reading", TclGetString(objv[1]))); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(Tcl_InputBlocked(chan))); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_OpenObjCmd -- * * This function is invoked to process the "open" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_OpenObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int pipeline, prot; const char *modeString, *what; Tcl_Channel chan; if ((objc < 2) || (objc > 4)) { Tcl_WrongNumArgs(interp, 1, objv, "fileName ?access? ?permissions?"); return TCL_ERROR; } prot = 0666; if (objc == 2) { modeString = "r"; } else { modeString = TclGetString(objv[2]); if (objc == 4) { const char *permString = TclGetString(objv[3]); int code = TCL_ERROR; int scanned = TclParseAllWhiteSpace(permString, -1); /* * Support legacy octal numbers. */ if ((permString[scanned] == '0') && (permString[scanned+1] >= '0') && (permString[scanned+1] <= '7')) { Tcl_Obj *permObj; TclNewLiteralStringObj(permObj, "0o"); Tcl_AppendToObj(permObj, permString+scanned+1, -1); code = TclGetIntFromObj(NULL, permObj, &prot); Tcl_DecrRefCount(permObj); } if ((code == TCL_ERROR) && TclGetIntFromObj(interp, objv[3], &prot) != TCL_OK) { return TCL_ERROR; } } } pipeline = 0; what = TclGetString(objv[1]); if (what[0] == '|') { pipeline = 1; } /* * Open the file or create a process pipeline. */ if (!pipeline) { chan = Tcl_FSOpenFileChannel(interp, objv[1], modeString, prot); } else { int mode, modeFlags; Tcl_Size cmdObjc; const char **cmdArgv; if (Tcl_SplitList(interp, what+1, &cmdObjc, &cmdArgv) != TCL_OK) { return TCL_ERROR; } mode = TclGetOpenMode(interp, modeString, &modeFlags); if (mode == -1) { chan = NULL; } else { int flags = TCL_STDERR | TCL_ENFORCE_MODE; switch (mode & O_ACCMODE) { case O_RDONLY: flags |= TCL_STDOUT; break; case O_WRONLY: flags |= TCL_STDIN; break; case O_RDWR: flags |= (TCL_STDIN | TCL_STDOUT); break; default: Tcl_Panic("Tcl_OpenCmd: invalid mode value"); break; } chan = Tcl_OpenCommandChannel(interp, cmdObjc, cmdArgv, flags); if ((modeFlags & CHANNEL_RAW_MODE) && chan) { Tcl_SetChannelOption(interp, chan, "-translation", "binary"); } } Tcl_Free((void *)cmdArgv); } if (chan == NULL) { return TCL_ERROR; } Tcl_RegisterChannel(interp, chan); Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_GetChannelName(chan), -1)); return TCL_OK; } /* *---------------------------------------------------------------------- * * TcpAcceptCallbacksDeleteProc -- * * Assocdata cleanup routine called when an interpreter is being deleted * to set the interp field of all the accept callback records registered * with the interpreter to NULL. This will prevent the interpreter from * being used in the future to eval accept scripts. * * Results: * None. * * Side effects: * Deallocates memory and sets the interp field of all the accept * callback records to NULL to prevent this interpreter from being used * subsequently to eval accept scripts. * *---------------------------------------------------------------------- */ static void TcpAcceptCallbacksDeleteProc( void *clientData, /* Data which was passed when the assocdata * was registered. */ TCL_UNUSED(Tcl_Interp *)) { Tcl_HashTable *hTblPtr = (Tcl_HashTable *)clientData; Tcl_HashEntry *hPtr; Tcl_HashSearch hSearch; for (hPtr = Tcl_FirstHashEntry(hTblPtr, &hSearch); hPtr != NULL; hPtr = Tcl_NextHashEntry(&hSearch)) { AcceptCallback *acceptCallbackPtr = (AcceptCallback *)Tcl_GetHashValue(hPtr); acceptCallbackPtr->interp = NULL; } Tcl_DeleteHashTable(hTblPtr); Tcl_Free(hTblPtr); } /* *---------------------------------------------------------------------- * * RegisterTcpServerInterpCleanup -- * * Registers an accept callback record to have its interp field set to * NULL when the interpreter is deleted. * * Results: * None. * * Side effects: * When, in the future, the interpreter is deleted, the interp field of * the accept callback data structure will be set to NULL. This will * prevent attempts to eval the accept script in a deleted interpreter. * *---------------------------------------------------------------------- */ static void RegisterTcpServerInterpCleanup( Tcl_Interp *interp, /* Interpreter for which we want to be * informed of deletion. */ AcceptCallback *acceptCallbackPtr) /* The accept callback record whose interp * field we want set to NULL when the * interpreter is deleted. */ { Tcl_HashTable *hTblPtr; /* Hash table for accept callback records to * smash when the interpreter will be * deleted. */ Tcl_HashEntry *hPtr; /* Entry for this record. */ int isNew; /* Is the entry new? */ hTblPtr = (Tcl_HashTable *)Tcl_GetAssocData(interp, "tclTCPAcceptCallbacks", NULL); if (hTblPtr == NULL) { hTblPtr = (Tcl_HashTable *)Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(hTblPtr, TCL_ONE_WORD_KEYS); Tcl_SetAssocData(interp, "tclTCPAcceptCallbacks", TcpAcceptCallbacksDeleteProc, hTblPtr); } hPtr = Tcl_CreateHashEntry(hTblPtr, acceptCallbackPtr, &isNew); if (!isNew) { Tcl_Panic("RegisterTcpServerCleanup: damaged accept record table"); } Tcl_SetHashValue(hPtr, acceptCallbackPtr); } /* *---------------------------------------------------------------------- * * UnregisterTcpServerInterpCleanupProc -- * * Unregister a previously registered accept callback record. The interp * field of this record will no longer be set to NULL in the future when * the interpreter is deleted. * * Results: * None. * * Side effects: * Prevents the interp field of the accept callback record from being set * to NULL in the future when the interpreter is deleted. * *---------------------------------------------------------------------- */ static void UnregisterTcpServerInterpCleanupProc( Tcl_Interp *interp, /* Interpreter in which the accept callback * record was registered. */ AcceptCallback *acceptCallbackPtr) /* The record for which to delete the * registration. */ { Tcl_HashTable *hTblPtr; Tcl_HashEntry *hPtr; hTblPtr = (Tcl_HashTable *)Tcl_GetAssocData(interp, "tclTCPAcceptCallbacks", NULL); if (hTblPtr == NULL) { return; } hPtr = Tcl_FindHashEntry(hTblPtr, acceptCallbackPtr); if (hPtr != NULL) { Tcl_DeleteHashEntry(hPtr); } } /* *---------------------------------------------------------------------- * * AcceptCallbackProc -- * * This callback is invoked by the TCP channel driver when it accepts a * new connection from a client on a server socket. * * Results: * None. * * Side effects: * Whatever the script does. * *---------------------------------------------------------------------- */ static void AcceptCallbackProc( void *callbackData, /* The data stored when the callback was * created in the call to * Tcl_OpenTcpServer. */ Tcl_Channel chan, /* Channel for the newly accepted * connection. */ char *address, /* Address of client that was accepted. */ int port) /* Port of client that was accepted. */ { AcceptCallback *acceptCallbackPtr = (AcceptCallback *)callbackData; /* * Check if the callback is still valid; the interpreter may have gone * away, this is signalled by setting the interp field of the callback * data to NULL. */ if (acceptCallbackPtr->interp != NULL) { Tcl_Interp *interp = acceptCallbackPtr->interp; Tcl_Obj *script, *objv[2]; int result = TCL_OK; objv[0] = acceptCallbackPtr->script; objv[1] = Tcl_NewListObj(3, NULL); Tcl_ListObjAppendElement(NULL, objv[1], Tcl_NewStringObj( Tcl_GetChannelName(chan), -1)); Tcl_ListObjAppendElement(NULL, objv[1], Tcl_NewStringObj(address, -1)); Tcl_ListObjAppendElement(NULL, objv[1], Tcl_NewWideIntObj(port)); script = Tcl_ConcatObj(2, objv); Tcl_IncrRefCount(script); Tcl_DecrRefCount(objv[1]); Tcl_Preserve(interp); Tcl_RegisterChannel(interp, chan); /* * Artificially bump the refcount to protect the channel from being * deleted while the script is being evaluated. */ Tcl_RegisterChannel(NULL, chan); result = Tcl_EvalObjEx(interp, script, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL); Tcl_DecrRefCount(script); if (result != TCL_OK) { Tcl_BackgroundException(interp, result); Tcl_UnregisterChannel(interp, chan); } /* * Decrement the artificially bumped refcount. After this it is not * safe anymore to use "chan", because it may now be deleted. */ Tcl_UnregisterChannel(NULL, chan); Tcl_Release(interp); } else { /* * The interpreter has been deleted, so there is no useful way to use * the client socket - just close it. */ Tcl_CloseEx(NULL, chan, 0); } } /* *---------------------------------------------------------------------- * * TcpServerCloseProc -- * * This callback is called when the TCP server channel for which it was * registered is being closed. It informs the interpreter in which the * accept script is evaluated (if that interpreter still exists) that * this channel no longer needs to be informed if the interpreter is * deleted. * * Results: * None. * * Side effects: * In the future, if the interpreter is deleted this channel will no * longer be informed. * *---------------------------------------------------------------------- */ static void TcpServerCloseProc( void *callbackData) /* The data passed in the call to * Tcl_CreateCloseHandler. */ { AcceptCallback *acceptCallbackPtr = (AcceptCallback *)callbackData; /* The actual data. */ if (acceptCallbackPtr->interp != NULL) { UnregisterTcpServerInterpCleanupProc(acceptCallbackPtr->interp, acceptCallbackPtr); } Tcl_DecrRefCount(acceptCallbackPtr->script); Tcl_Free(acceptCallbackPtr); } /* *---------------------------------------------------------------------- * * Tcl_SocketObjCmd -- * * This function is invoked to process the "socket" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Creates a socket based channel. * *---------------------------------------------------------------------- */ int Tcl_SocketObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const socketOptions[] = { "-async", "-backlog", "-myaddr", "-myport", "-reuseaddr", "-reuseport", "-server", NULL }; enum socketOptionsEnum { SKT_ASYNC, SKT_BACKLOG, SKT_MYADDR, SKT_MYPORT, SKT_REUSEADDR, SKT_REUSEPORT, SKT_SERVER } optionIndex; int a, server = 0, myport = 0, async = 0, reusep = -1, reusea = -1, backlog = -1; unsigned int flags = 0; const char *host, *port, *myaddr = NULL; Tcl_Obj *script = NULL; Tcl_Channel chan; TclInitSockets(); for (a = 1; a < objc; a++) { const char *arg = TclGetString(objv[a]); if (arg[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, objv[a], socketOptions, "option", TCL_EXACT, &optionIndex) != TCL_OK) { return TCL_ERROR; } switch (optionIndex) { case SKT_ASYNC: if (server == 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot set -async option for server sockets", -1)); return TCL_ERROR; } async = 1; break; case SKT_MYADDR: a++; if (a >= objc) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "no argument given for -myaddr option", -1)); return TCL_ERROR; } myaddr = TclGetString(objv[a]); break; case SKT_MYPORT: { const char *myPortName; a++; if (a >= objc) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "no argument given for -myport option", -1)); return TCL_ERROR; } myPortName = TclGetString(objv[a]); if (TclSockGetPort(interp, myPortName, "tcp", &myport) != TCL_OK) { return TCL_ERROR; } break; } case SKT_SERVER: if (async == 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot set -async option for server sockets", -1)); return TCL_ERROR; } server = 1; a++; if (a >= objc) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "no argument given for -server option", -1)); return TCL_ERROR; } script = objv[a]; break; case SKT_REUSEADDR: a++; if (a >= objc) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "no argument given for -reuseaddr option", -1)); return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[a], &reusea) != TCL_OK) { return TCL_ERROR; } break; case SKT_REUSEPORT: a++; if (a >= objc) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "no argument given for -reuseport option", -1)); return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[a], &reusep) != TCL_OK) { return TCL_ERROR; } break; case SKT_BACKLOG: a++; if (a >= objc) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "no argument given for -backlog option", -1)); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[a], &backlog) != TCL_OK) { return TCL_ERROR; } break; default: Tcl_Panic("Tcl_SocketObjCmd: bad option index to SocketOptions"); } } if (server) { host = myaddr; /* NULL implies INADDR_ANY */ if (myport != 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "option -myport is not valid for servers", -1)); return TCL_ERROR; } } else if (a < objc) { host = TclGetString(objv[a]); a++; } else { Interp *iPtr; wrongNumArgs: iPtr = (Interp *) interp; Tcl_WrongNumArgs(interp, 1, objv, "?-async? ?-myaddr addr? ?-myport myport? host port"); iPtr->flags |= INTERP_ALTERNATE_WRONG_ARGS; Tcl_WrongNumArgs(interp, 1, objv, "-server command ?-backlog count? ?-myaddr addr? " "?-reuseaddr boolean? ?-reuseport boolean? port"); return TCL_ERROR; } if (!server && (reusea != -1 || reusep != -1 || backlog != -1)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "options -backlog, -reuseaddr, and -reuseport are only valid " "for servers", -1)); return TCL_ERROR; } /* * Set the options to their default value if the user didn't override * their value. */ if (reusep == -1) { reusep = 0; } if (reusea == -1) { reusea = 1; } /* * Build the bitset with the flags values. */ if (reusea) { flags |= TCL_TCPSERVER_REUSEADDR; } if (reusep) { flags |= TCL_TCPSERVER_REUSEPORT; } /* * All the arguments should have been parsed by now, 'a' points to the * last one, the port number. */ if (a != objc-1) { goto wrongNumArgs; } port = TclGetString(objv[a]); if (server) { AcceptCallback *acceptCallbackPtr = (AcceptCallback *)Tcl_Alloc(sizeof(AcceptCallback)); Tcl_IncrRefCount(script); acceptCallbackPtr->script = script; acceptCallbackPtr->interp = interp; chan = Tcl_OpenTcpServerEx(interp, port, host, flags, backlog, AcceptCallbackProc, acceptCallbackPtr); if (chan == NULL) { Tcl_DecrRefCount(script); Tcl_Free(acceptCallbackPtr); return TCL_ERROR; } /* * Register with the interpreter to let us know when the interpreter * is deleted (by having the callback set the interp field of the * acceptCallbackPtr's structure to NULL). This is to avoid trying to * eval the script in a deleted interpreter. */ RegisterTcpServerInterpCleanup(interp, acceptCallbackPtr); /* * Register a close callback. This callback will inform the * interpreter (if it still exists) that this channel does not need to * be informed when the interpreter is deleted. */ Tcl_CreateCloseHandler(chan, TcpServerCloseProc, acceptCallbackPtr); } else { int portNum; if (TclSockGetPort(interp, port, "tcp", &portNum) != TCL_OK) { return TCL_ERROR; } chan = Tcl_OpenTcpClient(interp, portNum, host, myaddr, myport, async); if (chan == NULL) { return TCL_ERROR; } } Tcl_RegisterChannel(interp, chan); Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_GetChannelName(chan), -1)); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_FcopyObjCmd -- * * This function is invoked to process the "fcopy" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Moves data between two channels and possibly sets up a background copy * handler. * *---------------------------------------------------------------------- */ int Tcl_FcopyObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Channel inChan, outChan; int mode, i, index; Tcl_WideInt toRead; Tcl_Obj *cmdPtr; static const char *const switches[] = { "-size", "-command", NULL }; enum { FcopySize, FcopyCommand }; if ((objc < 3) || (objc > 7) || (objc == 4) || (objc == 6)) { Tcl_WrongNumArgs(interp, 1, objv, "input output ?-size size? ?-command callback?"); return TCL_ERROR; } /* * Parse the channel arguments and verify that they are readable or * writable, as appropriate. */ if (TclGetChannelFromObj(interp, objv[1], &inChan, &mode, 0) != TCL_OK) { return TCL_ERROR; } if (!(mode & TCL_READABLE)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "channel \"%s\" wasn't opened for reading", TclGetString(objv[1]))); return TCL_ERROR; } if (TclGetChannelFromObj(interp, objv[2], &outChan, &mode, 0) != TCL_OK) { return TCL_ERROR; } if (!(mode & TCL_WRITABLE)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "channel \"%s\" wasn't opened for writing", TclGetString(objv[2]))); return TCL_ERROR; } toRead = -1; cmdPtr = NULL; for (i = 3; i < objc; i += 2) { if (Tcl_GetIndexFromObj(interp, objv[i], switches, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case FcopySize: if (TclGetWideIntFromObj(interp, objv[i+1], &toRead) != TCL_OK) { return TCL_ERROR; } if (toRead < 0) { /* * Handle all negative sizes like -1, meaning 'copy all'. By * resetting toRead we avoid changes in the core copying * functions (which explicitly check for -1 and crash on any * other negative value). */ toRead = -1; } break; case FcopyCommand: cmdPtr = objv[i+1]; break; } } return TclCopyChannel(interp, inChan, outChan, toRead, cmdPtr); } /* *--------------------------------------------------------------------------- * * ChanPendingObjCmd -- * * This function is invoked to process the Tcl "chan pending" command * (TIP #287). See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Sets interp's result to the number of bytes of buffered input or * output (depending on whether the first argument is "input" or * "output"), or -1 if the channel wasn't opened for that mode. * *--------------------------------------------------------------------------- */ static int ChanPendingObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Channel chan; static const char *const options[] = {"input", "output", NULL}; enum pendingOptionsEnum {PENDING_INPUT, PENDING_OUTPUT} index; int mode; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "mode channel"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], options, "mode", 0, &index) != TCL_OK) { return TCL_ERROR; } if (TclGetChannelFromObj(interp, objv[2], &chan, &mode, 0) != TCL_OK) { return TCL_ERROR; } switch (index) { case PENDING_INPUT: if (!(mode & TCL_READABLE)) { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(-1)); } else { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(Tcl_InputBuffered(chan))); } break; case PENDING_OUTPUT: if (!(mode & TCL_WRITABLE)) { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(-1)); } else { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(Tcl_OutputBuffered(chan))); } break; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ChanTruncateObjCmd -- * * This function is invoked to process the "chan truncate" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Truncates a channel (or rather a file underlying a channel). * *---------------------------------------------------------------------- */ static int ChanTruncateObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Channel chan; Tcl_WideInt length; if ((objc < 2) || (objc > 3)) { Tcl_WrongNumArgs(interp, 1, objv, "channel ?length?"); return TCL_ERROR; } if (TclGetChannelFromObj(interp, objv[1], &chan, NULL, 0) != TCL_OK) { return TCL_ERROR; } if (objc == 3) { /* * User is supplying an explicit length. */ if (TclGetWideIntFromObj(interp, objv[2], &length) != TCL_OK) { return TCL_ERROR; } if (length < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot truncate to negative length of file", -1)); return TCL_ERROR; } } else { /* * User wants to truncate to the current file position. */ length = Tcl_Tell(chan); if (length == -1) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not determine current location in \"%s\": %s", TclGetString(objv[1]), Tcl_PosixError(interp))); return TCL_ERROR; } } if (Tcl_TruncateChannel(chan, length) != TCL_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error during truncate on \"%s\": %s", TclGetString(objv[1]), Tcl_PosixError(interp))); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ChanPipeObjCmd -- * * This function is invoked to process the "chan pipe" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Creates a pair of Tcl channels wrapping both ends of a new * anonymous pipe. * *---------------------------------------------------------------------- */ static int ChanPipeObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Channel rchan, wchan; const char *channelNames[2]; Tcl_Obj *resultPtr; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } if (Tcl_CreatePipe(interp, &rchan, &wchan, 0) != TCL_OK) { return TCL_ERROR; } channelNames[0] = Tcl_GetChannelName(rchan); channelNames[1] = Tcl_GetChannelName(wchan); TclNewObj(resultPtr); Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewStringObj(channelNames[0], -1)); Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewStringObj(channelNames[1], -1)); Tcl_SetObjResult(interp, resultPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclChannelNamesCmd -- * * This function is invoked to process the "chan names" and "file * channels" Tcl commands. See the user documentation for details on * what they do. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclChannelNamesCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc < 1 || objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?pattern?"); return TCL_ERROR; } return Tcl_GetChannelNamesEx(interp, ((objc == 1) ? NULL : TclGetString(objv[1]))); } /* *---------------------------------------------------------------------- * * TclInitChanCmd -- * * This function is invoked to create the "chan" Tcl command. See the * user documentation for details on what it does. * * Results: * A Tcl command handle. * * Side effects: * None (since nothing is byte-compiled). * *---------------------------------------------------------------------- */ Tcl_Command TclInitChanCmd( Tcl_Interp *interp) { /* * Most commands are plugged directly together, but some are done via * alias-like rewriting; [chan configure] is this way for security reasons * (want overwriting of [fconfigure] to control that nicely), and [chan * names] because the functionality isn't available as a separate command * function at the moment. */ static const EnsembleImplMap initMap[] = { {"blocked", Tcl_FblockedObjCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"close", Tcl_CloseObjCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, {"copy", Tcl_FcopyObjCmd, NULL, NULL, NULL, 0}, {"create", TclChanCreateObjCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, /* TIP #219 */ {"eof", Tcl_EofObjCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"event", Tcl_FileEventObjCmd, TclCompileBasic2Or3ArgCmd, NULL, NULL, 0}, {"flush", Tcl_FlushObjCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"gets", Tcl_GetsObjCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, {"isbinary", ChanIsBinaryCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"names", TclChannelNamesCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"pending", ChanPendingObjCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, /* TIP #287 */ {"pipe", ChanPipeObjCmd, TclCompileBasic0ArgCmd, NULL, NULL, 0}, /* TIP #304 */ {"pop", TclChanPopObjCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, /* TIP #230 */ {"postevent", TclChanPostEventObjCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, /* TIP #219 */ {"push", TclChanPushObjCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, /* TIP #230 */ {"puts", Tcl_PutsObjCmd, NULL, NULL, NULL, 0}, {"read", Tcl_ReadObjCmd, NULL, NULL, NULL, 0}, {"seek", Tcl_SeekObjCmd, TclCompileBasic2Or3ArgCmd, NULL, NULL, 0}, {"tell", Tcl_TellObjCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"truncate", ChanTruncateObjCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, /* TIP #208 */ {NULL, NULL, NULL, NULL, NULL, 0} }; static const char *const extras[] = { "configure", "::fconfigure", NULL }; Tcl_Command ensemble; Tcl_Obj *mapObj; int i; ensemble = TclMakeEnsemble(interp, "chan", initMap); Tcl_GetEnsembleMappingDict(NULL, ensemble, &mapObj); for (i=0 ; extras[i] ; i+=2) { /* * Can assume that reference counts are all incremented. */ TclDictPutString(NULL, mapObj, extras[i], extras[i + 1]); } Tcl_SetEnsembleMappingDict(interp, ensemble, mapObj); return ensemble; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclIOGT.c0000644000175000017500000010647014726623136014613 0ustar sergeisergei/* * tclIOGT.c -- * * Implements a generic transformation exposing the underlying API at the * script level. Contributed by Andreas Kupries. * * Copyright © 2000 Ajuba Solutions * Copyright © 1999-2000 Andreas Kupries (a.kupries@westend.com) * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclIO.h" /* * Forward declarations of internal procedures. First the driver procedures of * the transformation. */ static int TransformBlockModeProc(void *instanceData, int mode); static int TransformCloseProc(void *instanceData, Tcl_Interp *interp, int flags); static int TransformInputProc(void *instanceData, char *buf, int toRead, int *errorCodePtr); static int TransformOutputProc(void *instanceData, const char *buf, int toWrite, int *errorCodePtr); static int TransformSetOptionProc(void *instanceData, Tcl_Interp *interp, const char *optionName, const char *value); static int TransformGetOptionProc(void *instanceData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr); static void TransformWatchProc(void *instanceData, int mask); static int TransformGetFileHandleProc(void *instanceData, int direction, void **handlePtr); static int TransformNotifyProc(void *instanceData, int mask); static long long TransformWideSeekProc(void *instanceData, long long offset, int mode, int *errorCodePtr); /* * Forward declarations of internal procedures. Secondly the procedures for * handling and generating fileeevents. */ static void TransformChannelHandlerTimer(void *clientData); /* * Forward declarations of internal procedures. Third, helper procedures * encapsulating essential tasks. */ typedef struct TransformChannelData TransformChannelData; static int ExecuteCallback(TransformChannelData *ctrl, Tcl_Interp *interp, unsigned char *op, unsigned char *buf, int bufLen, int transmit, int preserve); /* * Action codes to give to 'ExecuteCallback' (argument 'transmit'), telling * the procedure what to do with the result of the script it calls. */ #define TRANSMIT_DONT 0 /* No transfer to do. */ #define TRANSMIT_DOWN 1 /* Transfer to the underlying channel. */ #define TRANSMIT_SELF 2 /* Transfer into our channel. */ #define TRANSMIT_IBUF 3 /* Transfer to internal input buffer. */ #define TRANSMIT_NUM 4 /* Transfer number to 'maxRead'. */ /* * Codes for 'preserve' of 'ExecuteCallback'. */ #define P_PRESERVE 1 #define P_NO_PRESERVE 0 /* * Strings for the action codes delivered to the script implementing a * transformation. Argument 'op' of 'ExecuteCallback'. */ #define A_CREATE_WRITE (UCHARP("create/write")) #define A_DELETE_WRITE (UCHARP("delete/write")) #define A_FLUSH_WRITE (UCHARP("flush/write")) #define A_WRITE (UCHARP("write")) #define A_CREATE_READ (UCHARP("create/read")) #define A_DELETE_READ (UCHARP("delete/read")) #define A_FLUSH_READ (UCHARP("flush/read")) #define A_READ (UCHARP("read")) #define A_QUERY_MAXREAD (UCHARP("query/maxRead")) #define A_CLEAR_READ (UCHARP("clear/read")) /* * Management of a simple buffer. */ typedef struct ResultBuffer ResultBuffer; static inline void ResultClear(ResultBuffer *r); static inline void ResultInit(ResultBuffer *r); static inline int ResultEmpty(ResultBuffer *r); static inline size_t ResultCopy(ResultBuffer *r, unsigned char *buf, size_t toRead); static inline void ResultAdd(ResultBuffer *r, unsigned char *buf, size_t toWrite); /* * This structure describes the channel type structure for Tcl-based * transformations. */ static const Tcl_ChannelType transformChannelType = { "transform", TCL_CHANNEL_VERSION_5, NULL, /* Deprecated. */ TransformInputProc, TransformOutputProc, NULL, /* Deprecated. */ TransformSetOptionProc, TransformGetOptionProc, TransformWatchProc, TransformGetFileHandleProc, TransformCloseProc, TransformBlockModeProc, NULL, /* Flush proc. */ TransformNotifyProc, TransformWideSeekProc, NULL, /* Thread action proc. */ NULL /* Truncate proc. */ }; /* * Possible values for 'flags' field in control structure, see below. */ #define CHANNEL_ASYNC (1<<0) /* Non-blocking mode. */ /* * Definition of the structure containing the information about the internal * input buffer. */ struct ResultBuffer { unsigned char *buf; /* Reference to the buffer area. */ size_t allocated; /* Allocated size of the buffer area. */ size_t used; /* Number of bytes in the buffer, no more than * number allocated. */ }; /* * Additional bytes to allocate during buffer expansion. */ #define INCREMENT 512 /* * Number of milliseconds to wait before firing an event to flush out * information waiting in buffers (fileevent support). */ #define FLUSH_DELAY 5 /* * Convenience macro to make some casts easier to use. */ #define UCHARP(x) ((unsigned char *) (x)) /* * Definition of a structure used by all transformations generated here to * maintain their local state. */ struct TransformChannelData { /* * General section. Data to integrate the transformation into the channel * system. */ Tcl_Channel self; /* Our own Channel handle. */ int readIsFlushed; /* Flag to note whether in.flushProc was * called or not. */ int eofPending; /* Flag: EOF seen down, not raised up */ int flags; /* Currently CHANNEL_ASYNC or zero. */ int watchMask; /* Current watch/event/interest mask. */ int mode; /* Mode of parent channel, OR'ed combination * of TCL_READABLE, TCL_WRITABLE. */ Tcl_TimerToken timer; /* Timer for automatic flushing of information * sitting in an internal buffer. Required for * full fileevent support. */ /* * Transformation specific data. */ int maxRead; /* Maximum allowed number of bytes to read, as * given to us by the Tcl script implementing * the transformation. */ Tcl_Interp *interp; /* Reference to the interpreter which created * the transformation. Used to execute the * code below. */ Tcl_Obj *command; /* Tcl code to execute for a buffer */ ResultBuffer result; /* Internal buffer used to store the result of * a transformation of incoming data. Also * serves as buffer of all data not yet * consumed by the reader. */ size_t refCount; }; static void PreserveData( TransformChannelData *dataPtr) { dataPtr->refCount++; } static void ReleaseData( TransformChannelData *dataPtr) { if (dataPtr->refCount-- > 1) { return; } ResultClear(&dataPtr->result); Tcl_DecrRefCount(dataPtr->command); Tcl_Free(dataPtr); } /* *---------------------------------------------------------------------- * * TclChannelTransform -- * * Implements the Tcl "testchannel transform" debugging command. This is * part of the testing environment. This sets up a tcl script (cmdObjPtr) * to be used as a transform on the channel. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclChannelTransform( Tcl_Interp *interp, /* Interpreter for result. */ Tcl_Channel chan, /* Channel to transform. */ Tcl_Obj *cmdObjPtr) /* Script to use for transform. */ { Channel *chanPtr; /* The actual channel. */ ChannelState *statePtr; /* State info for channel. */ int mode; /* Read/write mode of the channel. */ Tcl_Size objc; TransformChannelData *dataPtr; Tcl_DString ds; if (chan == NULL) { return TCL_ERROR; } if (TCL_OK != TclListObjLength(interp, cmdObjPtr, &objc)) { Tcl_SetObjResult(interp, Tcl_NewStringObj("-command value is not a list", -1)); return TCL_ERROR; } chanPtr = (Channel *) chan; statePtr = chanPtr->state; chanPtr = statePtr->topChanPtr; chan = (Tcl_Channel) chanPtr; mode = (statePtr->flags & (TCL_READABLE|TCL_WRITABLE)); /* * Now initialize the transformation state and stack it upon the specified * channel. One of the necessary things to do is to retrieve the blocking * regime of the underlying channel and to use the same for us too. */ dataPtr = (TransformChannelData *)Tcl_Alloc(sizeof(TransformChannelData)); dataPtr->refCount = 1; Tcl_DStringInit(&ds); Tcl_GetChannelOption(interp, chan, "-blocking", &ds); dataPtr->readIsFlushed = 0; dataPtr->eofPending = 0; dataPtr->flags = 0; if (ds.string[0] == '0') { dataPtr->flags |= CHANNEL_ASYNC; } Tcl_DStringFree(&ds); dataPtr->watchMask = 0; dataPtr->mode = mode; dataPtr->timer = NULL; dataPtr->maxRead = 4096; /* Initial value not relevant. */ dataPtr->interp = interp; dataPtr->command = cmdObjPtr; Tcl_IncrRefCount(dataPtr->command); ResultInit(&dataPtr->result); dataPtr->self = Tcl_StackChannel(interp, &transformChannelType, dataPtr, mode, chan); if (dataPtr->self == NULL) { Tcl_AppendPrintfToObj(Tcl_GetObjResult(interp), "\nfailed to stack channel \"%s\"", Tcl_GetChannelName(chan)); ReleaseData(dataPtr); return TCL_ERROR; } Tcl_Preserve(dataPtr->self); /* * At last initialize the transformation at the script level. */ PreserveData(dataPtr); if ((dataPtr->mode & TCL_WRITABLE) && ExecuteCallback(dataPtr, NULL, A_CREATE_WRITE, NULL, 0, TRANSMIT_DONT, P_NO_PRESERVE) != TCL_OK){ Tcl_UnstackChannel(interp, chan); ReleaseData(dataPtr); return TCL_ERROR; } if ((dataPtr->mode & TCL_READABLE) && ExecuteCallback(dataPtr, NULL, A_CREATE_READ, NULL, 0, TRANSMIT_DONT, P_NO_PRESERVE) != TCL_OK) { ExecuteCallback(dataPtr, NULL, A_DELETE_WRITE, NULL, 0, TRANSMIT_DONT, P_NO_PRESERVE); Tcl_UnstackChannel(interp, chan); ReleaseData(dataPtr); return TCL_ERROR; } ReleaseData(dataPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * ExecuteCallback -- * * Executes the defined callback for buffer and operation. * * Side effects: * As of the executed tcl script. * * Result: * A standard TCL error code. In case of an error a message is left in * the result area of the specified interpreter. * *---------------------------------------------------------------------- */ static int ExecuteCallback( TransformChannelData *dataPtr, /* Transformation with the callback. */ Tcl_Interp *interp, /* Current interpreter, possibly NULL. */ unsigned char *op, /* Operation invoking the callback. */ unsigned char *buf, /* Buffer to give to the script. */ int bufLen, /* And its length. */ int transmit, /* Flag, determines whether the result of the * callback is sent to the underlying channel * or not. */ int preserve) /* Flag. If true the procedure will preserve * the result state of all accessed * interpreters. */ { Tcl_Obj *resObj; /* See below, switch (transmit). */ Tcl_Size resLen = 0; unsigned char *resBuf; Tcl_InterpState state = NULL; int res = TCL_OK; Tcl_Obj *command = TclListObjCopy(NULL, dataPtr->command); Tcl_Interp *eval = dataPtr->interp; Tcl_Preserve(eval); /* * Step 1, create the complete command to execute. Do this by appending * operation and buffer to operate upon to a copy of the callback * definition. We *cannot* create a list containing 3 objects and then use * 'Tcl_EvalObjv', because the command may contain additional prefixed * arguments. Feather's curried commands would come in handy here. */ if (preserve == P_PRESERVE) { state = Tcl_SaveInterpState(eval, res); } Tcl_IncrRefCount(command); res = Tcl_ListObjAppendElement(NULL, command, Tcl_NewStringObj((char *) op, -1)); if (res != TCL_OK) { Tcl_DecrRefCount(command); Tcl_Release(eval); return res; } /* * Use a byte-array to prevent the misinterpretation of binary data coming * through as Utf while at the tcl level. */ Tcl_ListObjAppendElement(NULL, command, Tcl_NewByteArrayObj(buf, bufLen)); /* * Step 2, execute the command at the global level of the interpreter used * to create the transformation. Destroy the command afterward. If an * error occurred and the current interpreter is defined and not equal to * the interpreter for the callback, then copy the error message into * current interpreter. Don't copy if in preservation mode. */ res = Tcl_EvalObjEx(eval, command, TCL_EVAL_GLOBAL); Tcl_DecrRefCount(command); command = NULL; if ((res != TCL_OK) && (interp != NULL) && (eval != interp) && (preserve == P_NO_PRESERVE)) { Tcl_SetObjResult(interp, Tcl_GetObjResult(eval)); Tcl_Release(eval); return res; } /* * Step 3, transmit a possible conversion result to the underlying * channel, or ourselves. */ switch (transmit) { case TRANSMIT_DONT: /* nothing to do */ break; case TRANSMIT_DOWN: if (dataPtr->self == NULL) { break; } resObj = Tcl_GetObjResult(eval); resBuf = Tcl_GetBytesFromObj(NULL, resObj, &resLen); if (resBuf) { Tcl_WriteRaw(Tcl_GetStackedChannel(dataPtr->self), (char *) resBuf, resLen); break; } goto nonBytes; case TRANSMIT_SELF: if (dataPtr->self == NULL) { break; } resObj = Tcl_GetObjResult(eval); resBuf = Tcl_GetBytesFromObj(NULL, resObj, &resLen); if (resBuf) { Tcl_WriteRaw(dataPtr->self, (char *) resBuf, resLen); break; } goto nonBytes; case TRANSMIT_IBUF: resObj = Tcl_GetObjResult(eval); resBuf = Tcl_GetBytesFromObj(NULL, resObj, &resLen); if (resBuf) { ResultAdd(&dataPtr->result, resBuf, resLen); break; } nonBytes: Tcl_AppendResult(interp, "chan transform callback received non-bytes", (char *)NULL); Tcl_Release(eval); return TCL_ERROR; case TRANSMIT_NUM: /* * Interpret result as integer number. */ resObj = Tcl_GetObjResult(eval); TclGetIntFromObj(eval, resObj, &dataPtr->maxRead); break; } Tcl_ResetResult(eval); if (preserve == P_PRESERVE) { (void) Tcl_RestoreInterpState(eval, state); } Tcl_Release(eval); return res; } /* *---------------------------------------------------------------------- * * TransformBlockModeProc -- * * Trap handler. Called by the generic IO system during option processing * to change the blocking mode of the channel. * * Side effects: * Forwards the request to the underlying channel. * * Result: * 0 if successful, errno when failed. * *---------------------------------------------------------------------- */ static int TransformBlockModeProc( void *instanceData, /* State of transformation. */ int mode) /* New blocking mode. */ { TransformChannelData *dataPtr = (TransformChannelData *)instanceData; if (mode == TCL_MODE_NONBLOCKING) { dataPtr->flags |= CHANNEL_ASYNC; } else { dataPtr->flags &= ~CHANNEL_ASYNC; } return 0; } /* *---------------------------------------------------------------------- * * TransformCloseProc -- * * Trap handler. Called by the generic IO system during destruction of * the transformation channel. * * Side effects: * Releases the memory allocated in 'Tcl_TransformObjCmd'. * * Result: * None. * *---------------------------------------------------------------------- */ static int TransformCloseProc( void *instanceData, Tcl_Interp *interp, int flags) { TransformChannelData *dataPtr = (TransformChannelData *)instanceData; if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) { return EINVAL; } /* * Important: In this procedure 'dataPtr->self' already points to the * underlying channel. * * There is no need to cancel an existing channel handler, this is already * done. Either by 'Tcl_UnstackChannel' or by the general cleanup in * 'Tcl_Close'. * * But we have to cancel an active timer to prevent it from firing on the * removed channel. */ if (dataPtr->timer != NULL) { Tcl_DeleteTimerHandler(dataPtr->timer); dataPtr->timer = NULL; } /* * Now flush data waiting in internal buffers to output and input. The * input must be done despite the fact that there is no real receiver for * it anymore. But the scripts might have sideeffects other parts of the * system rely on (f.e. signalling the close to interested parties). */ PreserveData(dataPtr); if (dataPtr->mode & TCL_WRITABLE) { ExecuteCallback(dataPtr, interp, A_FLUSH_WRITE, NULL, 0, TRANSMIT_DOWN, P_PRESERVE); } if ((dataPtr->mode & TCL_READABLE) && !dataPtr->readIsFlushed) { dataPtr->readIsFlushed = 1; ExecuteCallback(dataPtr, interp, A_FLUSH_READ, NULL, 0, TRANSMIT_IBUF, P_PRESERVE); } if (dataPtr->mode & TCL_WRITABLE) { ExecuteCallback(dataPtr, interp, A_DELETE_WRITE, NULL, 0, TRANSMIT_DONT, P_PRESERVE); } if (dataPtr->mode & TCL_READABLE) { ExecuteCallback(dataPtr, interp, A_DELETE_READ, NULL, 0, TRANSMIT_DONT, P_PRESERVE); } ReleaseData(dataPtr); /* * General cleanup. */ Tcl_Release(dataPtr->self); dataPtr->self = NULL; ReleaseData(dataPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TransformInputProc -- * * Called by the generic IO system to convert read data. * * Side effects: * As defined by the conversion. * * Result: * A transformed buffer. * *---------------------------------------------------------------------- */ static int TransformInputProc( void *instanceData, char *buf, int toRead, int *errorCodePtr) { TransformChannelData *dataPtr = (TransformChannelData *)instanceData; int gotBytes, read, copied; Tcl_Channel downChan; /* * Should assert(dataPtr->mode & TCL_READABLE); */ if (toRead == 0 || dataPtr->self == NULL) { /* * Catch a no-op. TODO: Is this a panic()? */ return 0; } gotBytes = 0; downChan = Tcl_GetStackedChannel(dataPtr->self); PreserveData(dataPtr); while (toRead > 0) { /* * Loop until the request is satisfied (or no data is available from * below, possibly EOF). */ copied = ResultCopy(&dataPtr->result, UCHARP(buf), toRead); toRead -= copied; buf += copied; gotBytes += copied; if (toRead == 0) { /* * The request was completely satisfied from our buffers. We can * break out of the loop and return to the caller. */ break; } /* * Length (dataPtr->result) == 0, toRead > 0 here. Use the incoming * 'buf'! as target to store the intermediary information read from * the underlying channel. * * Ask the tcl level how much data it allows us to read from the * underlying channel. This feature allows the transform to signal EOF * upstream although there is none downstream. Useful to control an * unbounded 'fcopy', either through counting bytes, or by pattern * matching. */ ExecuteCallback(dataPtr, NULL, A_QUERY_MAXREAD, NULL, 0, TRANSMIT_NUM /* -> maxRead */, P_PRESERVE); if (dataPtr->maxRead >= 0) { if (dataPtr->maxRead < toRead) { toRead = dataPtr->maxRead; } } /* else: 'maxRead < 0' == Accept the current value of toRead. */ if (toRead <= 0) { break; } if (dataPtr->eofPending) { /* * Already saw EOF from downChan; don't ask again. * NOTE: Could move this up to avoid the last maxRead * execution. Believe this would still be correct behavior, * but the test suite tests the whole command callback * sequence, so leave it unchanged for now. */ break; } /* * Get bytes from the underlying channel. */ read = Tcl_ReadRaw(downChan, buf, toRead); if (read < 0) { if (Tcl_InputBlocked(downChan) && (gotBytes > 0)) { /* * Zero bytes available from downChan because blocked. * But nonzero bytes already copied, so total is a * valid blocked short read. Return to caller. */ break; } /* * Either downChan is not blocked (there's a real error). * or it is and there are no bytes copied yet. In either * case we want to pass the "error" along to the caller, * either to report an error, or to signal to the caller * that zero bytes are available because blocked. */ *errorCodePtr = Tcl_GetErrno(); gotBytes = -1; break; } else if (read == 0) { /* * Zero returned from Tcl_ReadRaw() always indicates EOF * on the down channel. */ dataPtr->eofPending = 1; dataPtr->readIsFlushed = 1; ExecuteCallback(dataPtr, NULL, A_FLUSH_READ, NULL, 0, TRANSMIT_IBUF, P_PRESERVE); if (ResultEmpty(&dataPtr->result)) { /* * We had nothing to flush. */ break; } continue; /* at: while (toRead > 0) */ } /* read == 0 */ /* * Transform the read chunk and add the result to our read buffer * (dataPtr->result). */ if (ExecuteCallback(dataPtr, NULL, A_READ, UCHARP(buf), read, TRANSMIT_IBUF, P_PRESERVE) != TCL_OK) { *errorCodePtr = EINVAL; gotBytes = -1; break; } } /* while toRead > 0 */ if (gotBytes == 0) { dataPtr->eofPending = 0; } ReleaseData(dataPtr); return gotBytes; } /* *---------------------------------------------------------------------- * * TransformOutputProc -- * * Called by the generic IO system to convert data waiting to be written. * * Side effects: * As defined by the transformation. * * Result: * A transformed buffer. * *---------------------------------------------------------------------- */ static int TransformOutputProc( void *instanceData, const char *buf, int toWrite, int *errorCodePtr) { TransformChannelData *dataPtr = (TransformChannelData *)instanceData; /* * Should assert(dataPtr->mode & TCL_WRITABLE); */ if (toWrite == 0) { /* * Catch a no-op. */ return 0; } PreserveData(dataPtr); if (ExecuteCallback(dataPtr, NULL, A_WRITE, UCHARP(buf), toWrite, TRANSMIT_DOWN, P_NO_PRESERVE) != TCL_OK) { *errorCodePtr = EINVAL; toWrite = -1; } ReleaseData(dataPtr); return toWrite; } /* *---------------------------------------------------------------------- * * TransformWideSeekProc -- * * This procedure is called by the generic IO level to move the access * point in a channel, with a (potentially) 64-bit offset. * * Side effects: * Moves the location at which the channel will be accessed in future * operations. Flushes all transformation buffers, then forwards it to * the underlying channel. * * Result: * -1 if failed, the new position if successful. An output argument * contains the POSIX error code if an error occurred, or zero. * *---------------------------------------------------------------------- */ static long long TransformWideSeekProc( void *instanceData, /* The channel to manipulate. */ long long offset, /* Size of movement. */ int mode, /* How to move. */ int *errorCodePtr) /* Location of error flag. */ { TransformChannelData *dataPtr = (TransformChannelData *)instanceData; Tcl_Channel parent = Tcl_GetStackedChannel(dataPtr->self); const Tcl_ChannelType *parentType = Tcl_GetChannelType(parent); Tcl_DriverWideSeekProc *parentWideSeekProc = Tcl_ChannelWideSeekProc(parentType); void *parentData = Tcl_GetChannelInstanceData(parent); if ((offset == 0) && (mode == SEEK_CUR)) { /* * This is no seek but a request to tell the caller the current * location. Simply pass the request down. */ if (parentWideSeekProc != NULL) { return parentWideSeekProc(parentData, offset, mode, errorCodePtr); } else { *errorCodePtr = EINVAL; return -1; } } /* * It is a real request to change the position. Flush all data waiting for * output and discard everything in the input buffers. Then pass the * request down, unchanged. */ PreserveData(dataPtr); if (dataPtr->mode & TCL_WRITABLE) { ExecuteCallback(dataPtr, NULL, A_FLUSH_WRITE, NULL, 0, TRANSMIT_DOWN, P_NO_PRESERVE); } if (dataPtr->mode & TCL_READABLE) { ExecuteCallback(dataPtr, NULL, A_CLEAR_READ, NULL, 0, TRANSMIT_DONT, P_NO_PRESERVE); ResultClear(&dataPtr->result); dataPtr->readIsFlushed = 0; dataPtr->eofPending = 0; } ReleaseData(dataPtr); /* * If we have a wide seek capability, we should stick with that. */ if (parentWideSeekProc == NULL) { *errorCodePtr = EINVAL; return -1; } return parentWideSeekProc(parentData, offset, mode, errorCodePtr); } /* *---------------------------------------------------------------------- * * TransformSetOptionProc -- * * Called by generic layer to handle the reconfiguration of channel * specific options. As this channel type does not have such, it simply * passes all requests downstream. * * Side effects: * As defined by the channel downstream. * * Result: * A standard TCL error code. * *---------------------------------------------------------------------- */ static int TransformSetOptionProc( void *instanceData, Tcl_Interp *interp, const char *optionName, const char *value) { TransformChannelData *dataPtr = (TransformChannelData *)instanceData; Tcl_Channel downChan = Tcl_GetStackedChannel(dataPtr->self); Tcl_DriverSetOptionProc *setOptionProc; setOptionProc = Tcl_ChannelSetOptionProc(Tcl_GetChannelType(downChan)); if (setOptionProc == NULL) { return TCL_ERROR; } return setOptionProc(Tcl_GetChannelInstanceData(downChan), interp, optionName, value); } /* *---------------------------------------------------------------------- * * TransformGetOptionProc -- * * Called by generic layer to handle requests for the values of channel * specific options. As this channel type does not have such, it simply * passes all requests downstream. * * Side effects: * As defined by the channel downstream. * * Result: * A standard TCL error code. * *---------------------------------------------------------------------- */ static int TransformGetOptionProc( void *instanceData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr) { TransformChannelData *dataPtr = (TransformChannelData *)instanceData; Tcl_Channel downChan = Tcl_GetStackedChannel(dataPtr->self); Tcl_DriverGetOptionProc *getOptionProc; getOptionProc = Tcl_ChannelGetOptionProc(Tcl_GetChannelType(downChan)); if (getOptionProc != NULL) { return getOptionProc(Tcl_GetChannelInstanceData(downChan), interp, optionName, dsPtr); } else if (optionName == NULL) { /* * Request is query for all options, this is ok. */ return TCL_OK; } /* * Request for a specific option has to fail, since we don't have any. */ return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TransformWatchProc -- * * Initialize the notifier to watch for events from this channel. * * Side effects: * Sets up the notifier so that a future event on the channel will be * seen by Tcl. * * Result: * None. * *---------------------------------------------------------------------- */ static void TransformWatchProc( void *instanceData, /* Channel to watch. */ int mask) /* Events of interest. */ { TransformChannelData *dataPtr = (TransformChannelData *)instanceData; Tcl_Channel downChan; /* * The caller expressed interest in events occurring for this channel. We * are forwarding the call to the underlying channel now. */ dataPtr->watchMask = mask; /* * No channel handlers any more. We will be notified automatically about * events on the channel below via a call to our 'TransformNotifyProc'. * But we have to pass the interest down now. We are allowed to add * additional 'interest' to the mask if we want to. But this * transformation has no such interest. It just passes the request down, * unchanged. */ if (dataPtr->self == NULL) { return; } downChan = Tcl_GetStackedChannel(dataPtr->self); Tcl_GetChannelType(downChan)->watchProc( Tcl_GetChannelInstanceData(downChan), mask); /* * Management of the internal timer. */ if ((dataPtr->timer != NULL) && (!(mask & TCL_READABLE) || ResultEmpty(&dataPtr->result))) { /* * A pending timer exists, but either is there no (more) interest in * the events it generates or nothing is available for reading, so * remove it. */ Tcl_DeleteTimerHandler(dataPtr->timer); dataPtr->timer = NULL; } if ((dataPtr->timer == NULL) && (mask & TCL_READABLE) && !ResultEmpty(&dataPtr->result)) { /* * There is no pending timer, but there is interest in readable events * and we actually have data waiting, so generate a timer to flush * that. */ dataPtr->timer = Tcl_CreateTimerHandler(FLUSH_DELAY, TransformChannelHandlerTimer, dataPtr); } } /* *---------------------------------------------------------------------- * * TransformGetFileHandleProc -- * * Called from Tcl_GetChannelHandle to retrieve OS specific file handle * from inside this channel. * * Side effects: * None. * * Result: * The appropriate Tcl_File or NULL if not present. * *---------------------------------------------------------------------- */ static int TransformGetFileHandleProc( void *instanceData, /* Channel to query. */ int direction, /* Direction of interest. */ void **handlePtr) /* Place to store the handle into. */ { TransformChannelData *dataPtr = (TransformChannelData *)instanceData; /* * Return the handle belonging to parent channel. IOW, pass the request * down and the result up. */ return Tcl_GetChannelHandle(Tcl_GetStackedChannel(dataPtr->self), direction, handlePtr); } /* *---------------------------------------------------------------------- * * TransformNotifyProc -- * * Handler called by Tcl to inform us of activity on the underlying * channel. * * Side effects: * May process the incoming event by itself. * * Result: * None. * *---------------------------------------------------------------------- */ static int TransformNotifyProc( void *clientData, /* The state of the notified * transformation. */ int mask) /* The mask of occurring events. */ { TransformChannelData *dataPtr = (TransformChannelData *)clientData; /* * An event occurred in the underlying channel. This transformation doesn't * process such events thus returns the incoming mask unchanged. */ if (dataPtr->timer != NULL) { /* * Delete an existing timer. It was not fired, yet we are here, so the * channel below generated such an event and we don't have to. The * renewal of the interest after the execution of channel handlers * will eventually cause us to recreate the timer (in * TransformWatchProc). */ Tcl_DeleteTimerHandler(dataPtr->timer); dataPtr->timer = NULL; } return mask; } /* *---------------------------------------------------------------------- * * TransformChannelHandlerTimer -- * * Called by the notifier (-> timer) to flush out information waiting in * the input buffer. * * Side effects: * As of 'Tcl_NotifyChannel'. * * Result: * None. * *---------------------------------------------------------------------- */ static void TransformChannelHandlerTimer( void *clientData) /* Transformation to query. */ { TransformChannelData *dataPtr = (TransformChannelData *)clientData; dataPtr->timer = NULL; if (!(dataPtr->watchMask&TCL_READABLE) || ResultEmpty(&dataPtr->result)) { /* * The timer fired, but either is there no (more) interest in the * events it generates or nothing is available for reading, so ignore * it and don't recreate it. */ return; } Tcl_NotifyChannel(dataPtr->self, TCL_READABLE); } /* *---------------------------------------------------------------------- * * ResultClear -- * * Deallocates any memory allocated by 'ResultAdd'. * * Side effects: * See above. * * Result: * None. * *---------------------------------------------------------------------- */ static inline void ResultClear( ResultBuffer *r) /* Reference to the buffer to clear out. */ { r->used = 0; if (r->allocated) { Tcl_Free(r->buf); r->buf = NULL; r->allocated = 0; } } /* *---------------------------------------------------------------------- * * ResultInit -- * * Initializes the specified buffer structure. The structure will contain * valid information for an empty buffer. * * Side effects: * See above. * * Result: * None. * *---------------------------------------------------------------------- */ static inline void ResultInit( ResultBuffer *r) /* Reference to the structure to * initialize. */ { r->used = 0; r->allocated = 0; r->buf = NULL; } /* *---------------------------------------------------------------------- * * ResultEmpty -- * * Returns whether the number of bytes stored in the buffer is zero. * * Side effects: * None. * * Result: * A boolean. * *---------------------------------------------------------------------- */ static inline int ResultEmpty( ResultBuffer *r) /* The structure to query. */ { return r->used == 0; } /* *---------------------------------------------------------------------- * * ResultCopy -- * * Copies the requested number of bytes from the buffer into the * specified array and removes them from the buffer afterward. Copies * less if there is not enough data in the buffer. * * Side effects: * See above. * * Result: * The number of actually copied bytes, possibly less than 'toRead'. * *---------------------------------------------------------------------- */ static inline size_t ResultCopy( ResultBuffer *r, /* The buffer to read from. */ unsigned char *buf, /* The buffer to copy into. */ size_t toRead) /* Number of requested bytes. */ { if (ResultEmpty(r)) { /* * Nothing to copy in the case of an empty buffer. */ return 0; } else if (r->used == toRead) { /* * We have just enough. Copy everything to the caller. */ memcpy(buf, r->buf, toRead); r->used = 0; } else if (r->used > toRead) { /* * The internal buffer contains more than requested. Copy the * requested subset to the caller, and shift the remaining bytes down. */ memcpy(buf, r->buf, toRead); memmove(r->buf, r->buf + toRead, r->used - toRead); r->used -= toRead; } else { /* * There is not enough in the buffer to satisfy the caller, so take * everything. */ memcpy(buf, r->buf, r->used); toRead = r->used; r->used = 0; } return toRead; } /* *---------------------------------------------------------------------- * * ResultAdd -- * * Adds the bytes in the specified array to the buffer, by appending it. * * Side effects: * See above. * * Result: * None. * *---------------------------------------------------------------------- */ static inline void ResultAdd( ResultBuffer *r, /* The buffer to extend. */ unsigned char *buf, /* The buffer to read from. */ size_t toWrite) /* The number of bytes in 'buf'. */ { if ((r->used + toWrite + 1) > r->allocated) { /* * Extension of the internal buffer is required. */ if (r->allocated == 0) { r->allocated = toWrite + INCREMENT; r->buf = (unsigned char *)Tcl_Alloc(r->allocated); } else { r->allocated += toWrite + INCREMENT; r->buf = (unsigned char *)Tcl_Realloc(r->buf, r->allocated); } } /* * Now we may copy the data. */ memcpy(r->buf + r->used, buf, toWrite); r->used += toWrite; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclIORChan.c0000644000175000017500000025755014726623136015302 0ustar sergeisergei/* * tclIORChan.c -- * * This file contains the implementation of Tcl's generic channel * reflection code, which allows the implementation of Tcl channels in * Tcl code. * * Parts of this file are based on code contributed by Jean-Claude * Wippler. * * See TIP #219 for the specification of this functionality. * * Copyright © 2004-2005 ActiveState, a division of Sophos * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclIO.h" #include #ifndef EINVAL #define EINVAL 9 #endif #ifndef EOK #define EOK 0 #endif /* * Signatures of all functions used in the C layer of the reflection. */ static int ReflectClose(void *clientData, Tcl_Interp *interp, int flags); static int ReflectInput(void *clientData, char *buf, int toRead, int *errorCodePtr); static int ReflectOutput(void *clientData, const char *buf, int toWrite, int *errorCodePtr); static void ReflectWatch(void *clientData, int mask); static int ReflectBlock(void *clientData, int mode); #if TCL_THREADS static void ReflectThread(void *clientData, int action); static int ReflectEventRun(Tcl_Event *ev, int flags); static int ReflectEventDelete(Tcl_Event *ev, void *cd); #endif static long long ReflectSeekWide(void *clientData, long long offset, int mode, int *errorCodePtr); static int ReflectGetOption(void *clientData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr); static int ReflectSetOption(void *clientData, Tcl_Interp *interp, const char *optionName, const char *newValue); static int ReflectTruncate(void *clientData, long long length); /* * The C layer channel type/driver definition used by the reflection. */ static const Tcl_ChannelType reflectedChannelType = { "tclrchannel", TCL_CHANNEL_VERSION_5, NULL, /* Deprecated */ ReflectInput, ReflectOutput, NULL, /* Deprecated */ ReflectSetOption, ReflectGetOption, ReflectWatch, NULL, /* Get OS handle from the channel. */ ReflectClose, ReflectBlock, NULL, /* Flush channel. */ NULL, /* Handle bubbled events. */ ReflectSeekWide, #if TCL_THREADS ReflectThread, #else NULL, /* Thread action proc */ #endif ReflectTruncate /* Truncate proc. */ }; /* * Instance data for a reflected channel. =========================== */ typedef struct { Tcl_Channel chan; /* Back reference to generic channel * structure. */ Tcl_Interp *interp; /* Reference to the interpreter containing the * Tcl level part of the channel. NULL here * signals the channel is dead because the * interpreter/thread containing its Tcl * command is gone. */ #if TCL_THREADS Tcl_ThreadId thread; /* Thread the 'interp' belongs to. == Handler thread */ Tcl_ThreadId owner; /* Thread owning the structure. == Channel thread */ #endif Tcl_Obj *cmd; /* Callback command prefix */ Tcl_Obj *methods; /* Methods to append to command prefix */ Tcl_Obj *name; /* Name of the channel as created */ int mode; /* Mask of R/W mode */ int interest; /* Mask of events the channel is interested * in. */ int dead; /* Boolean signal that some operations * should no longer be attempted. */ /* * Note regarding the usage of timers. * * Most channel implementations need a timer in the C level to ensure that * data in buffers is flushed out through the generation of fake file * events. * * See 'refchan', 'memchan', etc. * * Here this is _not_ required. Interest in events is posted to the Tcl * level via 'watch'. And posting of events is possible from the Tcl level * as well, via 'chan postevent'. This means that the generation of all * events, fake or not, timer based or not, is completely in the hands of * the Tcl level. Therefore no timer here. */ } ReflectedChannel; /* * Structure of the table mapping from channel handles to reflected * channels. Each interpreter which has the handler command for one or more * reflected channels records them in such a table, so that 'chan postevent' * is able to find them even if the actual channel was moved to a different * interpreter and/or thread. * * The table is reachable via the standard interpreter AssocData, the key is * defined below. */ typedef struct { Tcl_HashTable map; } ReflectedChannelMap; #define RCMKEY "ReflectedChannelMap" /* * Event literals. ================================================== */ static const char *const eventOptions[] = { "read", "write", NULL }; typedef enum { EVENT_READ, EVENT_WRITE } EventOption; /* * Method literals. ================================================== */ static const char *const methodNames[] = { "blocking", /* OPT */ "cget", /* OPT \/ Together or none */ "cgetall", /* OPT /\ of these two */ "configure", /* OPT */ "finalize", /* */ "initialize", /* */ "read", /* OPT */ "seek", /* OPT */ "truncate", /* OPT */ "watch", /* */ "write", /* OPT */ NULL }; typedef enum { METH_BLOCKING, METH_CGET, METH_CGETALL, METH_CONFIGURE, METH_FINAL, METH_INIT, METH_READ, METH_SEEK, METH_TRUNCATE, METH_WATCH, METH_WRITE } MethodName; #define FLAG(m) (1 << (m)) #define REQUIRED_METHODS \ (FLAG(METH_INIT) | FLAG(METH_FINAL) | FLAG(METH_WATCH)) #define NULLABLE_METHODS \ (FLAG(METH_BLOCKING) | FLAG(METH_SEEK) | \ FLAG(METH_CONFIGURE) | FLAG(METH_CGET) | \ FLAG(METH_CGETALL) | FLAG(METH_TRUNCATE)) #define RANDW \ (TCL_READABLE | TCL_WRITABLE) #define IMPLIES(a,b) ((!(a)) || (b)) #define NEGIMPL(a,b) #define HAS(x,f) ((x) & FLAG(f)) #if TCL_THREADS /* * Thread specific types and structures. * * We are here essentially creating a very specific implementation of 'thread * send'. */ /* * Enumeration of all operations which can be forwarded. */ typedef enum { ForwardedClose, ForwardedInput, ForwardedOutput, ForwardedSeek, ForwardedWatch, ForwardedBlock, ForwardedSetOpt, ForwardedGetOpt, ForwardedGetOptAll, ForwardedTruncate } ForwardedOperation; /* * Event used to forward driver invocations to the thread actually managing * the channel. We cannot construct the command to execute and forward that. * Because then it will contain a mixture of Tcl_Obj's belonging to both the * command handler thread (CT), and the thread managing the channel (MT), * executed in CT. Tcl_Obj's are not allowed to cross thread boundaries. So we * forward an operation code, the argument details, and reference to results. * The command is assembled in the CT and belongs fully to that thread. No * sharing problems. */ typedef struct { int code; /* O: Ok/Fail of the cmd handler */ char *msgStr; /* O: Error message for handler failure */ int mustFree; /* O: True if msgStr is allocated, false if * otherwise (static). */ } ForwardParamBase; /* * Operation specific parameter/result structures. (These are "subtypes" of * ForwardParamBase. Where an operation does not need any special types, it * has no "subtype" and just uses ForwardParamBase, as listed above.) */ struct ForwardParamInput { ForwardParamBase base; /* "Supertype". MUST COME FIRST. */ char *buf; /* O: Where to store the read bytes */ Tcl_Size toRead; /* I: #bytes to read, * O: #bytes actually read */ }; struct ForwardParamOutput { ForwardParamBase base; /* "Supertype". MUST COME FIRST. */ const char *buf; /* I: Where the bytes to write come from */ Tcl_Size toWrite; /* I: #bytes to write, * O: #bytes actually written */ }; struct ForwardParamSeek { ForwardParamBase base; /* "Supertype". MUST COME FIRST. */ int seekMode; /* I: How to seek */ Tcl_WideInt offset; /* I: Where to seek, * O: New location */ }; struct ForwardParamWatch { ForwardParamBase base; /* "Supertype". MUST COME FIRST. */ int mask; /* I: What events to watch for */ }; struct ForwardParamBlock { ForwardParamBase base; /* "Supertype". MUST COME FIRST. */ int nonblocking; /* I: What mode to activate */ }; struct ForwardParamSetOpt { ForwardParamBase base; /* "Supertype". MUST COME FIRST. */ const char *name; /* Name of option to set */ const char *value; /* Value to set */ }; struct ForwardParamGetOpt { ForwardParamBase base; /* "Supertype". MUST COME FIRST. */ const char *name; /* Name of option to get, maybe NULL */ Tcl_DString *value; /* Result */ }; struct ForwardParamTruncate { ForwardParamBase base; /* "Supertype". MUST COME FIRST. */ Tcl_WideInt length; /* I: Length of file. */ }; /* * Now join all these together in a single union for convenience. */ typedef union ForwardParam { ForwardParamBase base; struct ForwardParamInput input; struct ForwardParamOutput output; struct ForwardParamSeek seek; struct ForwardParamWatch watch; struct ForwardParamBlock block; struct ForwardParamSetOpt setOpt; struct ForwardParamGetOpt getOpt; struct ForwardParamTruncate truncate; } ForwardParam; /* * Forward declaration. */ typedef struct ForwardingResult ForwardingResult; /* * General event structure, with reference to operation specific data. */ typedef struct { Tcl_Event event; /* Basic event data, has to be first item */ ForwardingResult *resultPtr; ForwardedOperation op; /* Forwarded driver operation */ ReflectedChannel *rcPtr; /* Channel instance */ ForwardParam *param; /* Packaged arguments and return values, a * ForwardParam pointer. */ } ForwardingEvent; /* * Structure to manage the result of the forwarding. This is not the result of * the operation itself, but about the success of the forward event itself. * The event can be successful, even if the operation which was forwarded * failed. It is also there to manage the synchronization between the involved * threads. */ struct ForwardingResult { Tcl_ThreadId src; /* Originating thread. */ Tcl_ThreadId dst; /* Thread the op was forwarded to. */ Tcl_Interp *dsti; /* Interpreter in the thread the op was * forwarded to. */ /* * Note regarding 'dsti' above: Its information is also available via the * chain evPtr->rcPtr->interp, however, as can be seen, two more * indirections are needed to retrieve it. And the evPtr may be gone, * breaking the chain. */ Tcl_Condition done; /* Condition variable the forwarder blocks * on. */ int result; /* TCL_OK or TCL_ERROR */ ForwardingEvent *evPtr; /* Event the result belongs to. */ ForwardingResult *prevPtr, *nextPtr; /* Links into the list of pending forwarded * results. */ }; typedef struct { /* * Table of all reflected channels owned by this thread. This is the * per-thread version of the per-interpreter map. */ ReflectedChannelMap *rcmPtr; } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * List of forwarded operations which have not completed yet, plus the mutex * to protect the access to this process global list. */ static ForwardingResult *forwardList = NULL; TCL_DECLARE_MUTEX(rcForwardMutex) /* * Function containing the generic code executing a forward, and wrapper * macros for the actual operations we wish to forward. Uses ForwardProc as * the event function executed by the thread receiving a forwarding event * (which executes the appropriate function and collects the result, if any). * * The ExitProc ensures that things do not deadlock when the sending thread * involved in the forwarding exits. It also clean things up so that we don't * leak resources when threads go away. */ static void ForwardOpToHandlerThread(ReflectedChannel *rcPtr, ForwardedOperation op, const void *param); static int ForwardProc(Tcl_Event *evPtr, int mask); static void SrcExitProc(void *clientData); #define FreeReceivedError(p) \ if ((p)->base.mustFree) { \ Tcl_Free((p)->base.msgStr); \ } #define PassReceivedErrorInterp(i,p) \ if ((i) != NULL) { \ Tcl_SetChannelErrorInterp((i), \ Tcl_NewStringObj((p)->base.msgStr, -1)); \ } \ FreeReceivedError(p) #define PassReceivedError(c,p) \ Tcl_SetChannelError((c), Tcl_NewStringObj((p)->base.msgStr, -1)); \ FreeReceivedError(p) #define ForwardSetStaticError(p,emsg) \ (p)->base.code = TCL_ERROR; \ (p)->base.mustFree = 0; \ (p)->base.msgStr = (char *) (emsg) #define ForwardSetDynamicError(p,emsg) \ (p)->base.code = TCL_ERROR; \ (p)->base.mustFree = 1; \ (p)->base.msgStr = (char *) (emsg) static void ForwardSetObjError(ForwardParam *p, Tcl_Obj *objPtr); static ReflectedChannelMap * GetThreadReflectedChannelMap(void); static Tcl_ExitProc DeleteThreadReflectedChannelMap; #endif /* TCL_THREADS */ #define SetChannelErrorStr(c,msgStr) \ Tcl_SetChannelError((c), Tcl_NewStringObj((msgStr), -1)) static Tcl_Obj * MarshallError(Tcl_Interp *interp); static void UnmarshallErrorResult(Tcl_Interp *interp, Tcl_Obj *msgObj); /* * Static functions for this file: */ static int EncodeEventMask(Tcl_Interp *interp, const char *objName, Tcl_Obj *obj, int *mask); static Tcl_Obj * DecodeEventMask(int mask); static ReflectedChannel * NewReflectedChannel(Tcl_Interp *interp, Tcl_Obj *cmdpfxObj, int mode, Tcl_Obj *handleObj); static Tcl_Obj * NextHandle(void); static Tcl_FreeProc FreeReflectedChannel; static int InvokeTclMethod(ReflectedChannel *rcPtr, MethodName method, Tcl_Obj *argOneObj, Tcl_Obj *argTwoObj, Tcl_Obj **resultObjPtr); static ReflectedChannelMap * GetReflectedChannelMap(Tcl_Interp *interp); static Tcl_InterpDeleteProc DeleteReflectedChannelMap; static int ErrnoReturn(ReflectedChannel *rcPtr, Tcl_Obj *resObj); static void MarkDead(ReflectedChannel *rcPtr); /* * Global constant strings (messages). ================== * These string are used directly as bypass errors, thus they have to be valid * Tcl lists where the last element is the message itself. Hence the * list-quoting to keep the words of the message together. See also [x]. */ static const char *msg_read_toomuch = "{read delivered more than requested}"; static const char *msg_read_nonbyte = "{read delivered nonbyte result}"; static const char *msg_write_toomuch = "{write wrote more than requested}"; static const char *msg_write_nothing = "{write wrote nothing}"; static const char *msg_seek_beforestart = "{Tried to seek before origin}"; #if TCL_THREADS static const char *msg_send_originlost = "{Channel thread lost}"; #endif /* TCL_THREADS */ static const char *msg_send_dstlost = "{Owner lost}"; static const char *msg_dstlost = "-code 1 -level 0 -errorcode NONE -errorinfo {} -errorline 1 {Owner lost}"; /* * Main methods to plug into the 'chan' ensemble'. ================== */ /* *---------------------------------------------------------------------- * * TclChanCreateObjCmd -- * * This function is invoked to process the "chan create" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. The handle of the new channel is placed in the * interp result. * * Side effects: * Creates a new channel. * *---------------------------------------------------------------------- */ int TclChanCreateObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { ReflectedChannel *rcPtr; /* Instance data of the new channel */ Tcl_Obj *rcId; /* Handle of the new channel */ int mode; /* R/W mode of new channel. Has to match * abilities of handler commands */ Tcl_Obj *cmdObj; /* Command prefix, list of words */ Tcl_Obj *cmdNameObj; /* Command name */ Tcl_Channel chan; /* Token for the new channel */ Tcl_Obj *modeObj; /* mode in obj form for method call */ Tcl_Size listc; /* Result of 'initialize', and of */ Tcl_Obj **listv; /* its sublist in the 2nd element */ int methIndex; /* Encoded method name */ int result; /* Result code for 'initialize' */ Tcl_Obj *resObj; /* Result data for 'initialize' */ int methods; /* Bitmask for supported methods. */ Channel *chanPtr; /* 'chan' resolved to internal struct. */ Tcl_Obj *err; /* Error message */ ReflectedChannelMap *rcmPtr; /* Map of reflected channels with handlers in * this interp. */ Tcl_HashEntry *hPtr; /* Entry in the above map */ int isNew; /* Placeholder. */ /* * Syntax: chan create MODE CMDPREFIX * [0] [1] [2] [3] * * Actually: rCreate MODE CMDPREFIX * [0] [1] [2] */ enum ArgIndices { MODE = 1, CMD = 2 }; /* * Number of arguments... */ if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "mode cmdprefix"); return TCL_ERROR; } /* * First argument is a list of modes. Allowed entries are "read", "write". * Empty list is uncommon, but allowed. Abbreviations are ok. */ modeObj = objv[MODE]; if (EncodeEventMask(interp, "mode", objv[MODE], &mode) != TCL_OK) { return TCL_ERROR; } /* * Second argument is command prefix, i.e. list of words, first word is * name of handler command, other words are fixed arguments. Run the * 'initialize' method to get the list of supported methods. Validate * this. */ cmdObj = objv[CMD]; /* * Basic check that the command prefix truly is a list. */ if (Tcl_ListObjIndex(interp, cmdObj, 0, &cmdNameObj) != TCL_OK) { return TCL_ERROR; } /* * Now create the channel. */ rcId = NextHandle(); rcPtr = NewReflectedChannel(interp, cmdObj, mode, rcId); if (!rcPtr) { return TCL_ERROR; } /* * Invoke 'initialize' and validate that the handler is present and ok. * Squash the channel if not. * * Note: The conversion of 'mode' back into a Tcl_Obj ensures that * 'initialize' is invoked with canonical mode names, and no * abbreviations. Using modeObj directly could feed abbreviations into the * handler, and the handler is not specified to handle such. */ modeObj = DecodeEventMask(mode); /* assert modeObj.refCount == 1 */ result = InvokeTclMethod(rcPtr, METH_INIT, modeObj, NULL, &resObj); Tcl_DecrRefCount(modeObj); if (result != TCL_OK) { UnmarshallErrorResult(interp, resObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ goto error; } /* * Verify the result. * - List, of method names. Convert to mask. * Check for non-optionals through the mask. * Compare open mode against optional r/w. */ if (TclListObjGetElements(NULL, resObj, &listc, &listv) != TCL_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "chan handler \"%s initialize\" returned non-list: %s", TclGetString(cmdObj), TclGetString(resObj))); Tcl_DecrRefCount(resObj); goto error; } methods = 0; while (listc > 0) { if (Tcl_GetIndexFromObj(interp, listv[listc-1], methodNames, "method", TCL_EXACT, &methIndex) != TCL_OK) { TclNewLiteralStringObj(err, "chan handler \""); Tcl_AppendObjToObj(err, cmdObj); Tcl_AppendToObj(err, " initialize\" returned ", -1); Tcl_AppendObjToObj(err, Tcl_GetObjResult(interp)); Tcl_SetObjResult(interp, err); Tcl_DecrRefCount(resObj); goto error; } methods |= FLAG(methIndex); listc--; } Tcl_DecrRefCount(resObj); if ((REQUIRED_METHODS & methods) != REQUIRED_METHODS) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "chan handler \"%s\" does not support all required methods", TclGetString(cmdObj))); goto error; } if ((mode & TCL_READABLE) && !HAS(methods, METH_READ)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "chan handler \"%s\" lacks a \"read\" method", TclGetString(cmdObj))); goto error; } if ((mode & TCL_WRITABLE) && !HAS(methods, METH_WRITE)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "chan handler \"%s\" lacks a \"write\" method", TclGetString(cmdObj))); goto error; } if (!IMPLIES(HAS(methods, METH_CGET), HAS(methods, METH_CGETALL))) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "chan handler \"%s\" supports \"cget\" but not \"cgetall\"", TclGetString(cmdObj))); goto error; } if (!IMPLIES(HAS(methods, METH_CGETALL), HAS(methods, METH_CGET))) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "chan handler \"%s\" supports \"cgetall\" but not \"cget\"", TclGetString(cmdObj))); goto error; } Tcl_ResetResult(interp); /* * Everything is fine now. */ chan = Tcl_CreateChannel(&reflectedChannelType, TclGetString(rcId), rcPtr, mode); rcPtr->chan = chan; TclChannelPreserve(chan); chanPtr = (Channel *) chan; if ((methods & NULLABLE_METHODS) != NULLABLE_METHODS) { /* * Some of the nullable methods are not supported. We clone the * channel type, null the associated C functions, and use the result * as the actual channel type. */ Tcl_ChannelType *clonePtr = (Tcl_ChannelType *)Tcl_Alloc(sizeof(Tcl_ChannelType)); memcpy(clonePtr, &reflectedChannelType, sizeof(Tcl_ChannelType)); if (!(methods & FLAG(METH_CONFIGURE))) { clonePtr->setOptionProc = NULL; } if (!(methods & FLAG(METH_CGET)) && !(methods & FLAG(METH_CGETALL))) { clonePtr->getOptionProc = NULL; } if (!(methods & FLAG(METH_BLOCKING))) { clonePtr->blockModeProc = NULL; } if (!(methods & FLAG(METH_SEEK))) { clonePtr->wideSeekProc = NULL; } if (!(methods & FLAG(METH_TRUNCATE))) { clonePtr->truncateProc = NULL; } chanPtr->typePtr = clonePtr; } /* * Register the channel in the I/O system, and in our map for 'chan * postevent'. */ Tcl_RegisterChannel(interp, chan); rcmPtr = GetReflectedChannelMap(interp); hPtr = Tcl_CreateHashEntry(&rcmPtr->map, chanPtr->state->channelName, &isNew); if (!isNew && chanPtr != Tcl_GetHashValue(hPtr)) { Tcl_Panic("TclChanCreateObjCmd: duplicate channel names"); } Tcl_SetHashValue(hPtr, chan); #if TCL_THREADS rcmPtr = GetThreadReflectedChannelMap(); hPtr = Tcl_CreateHashEntry(&rcmPtr->map, chanPtr->state->channelName, &isNew); Tcl_SetHashValue(hPtr, chan); #endif /* * Return handle as result of command. */ Tcl_SetObjResult(interp, Tcl_NewStringObj(chanPtr->state->channelName, -1)); return TCL_OK; error: Tcl_DecrRefCount(rcPtr->name); Tcl_DecrRefCount(rcPtr->methods); Tcl_DecrRefCount(rcPtr->cmd); Tcl_Free(rcPtr); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TclChanPostEventObjCmd -- * * This function is invoked to process the "chan postevent" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Posts events to a reflected channel, invokes event handlers. The * latter implies that arbitrary side effects are possible. * *---------------------------------------------------------------------- */ #if TCL_THREADS typedef struct { Tcl_Event header; ReflectedChannel *rcPtr; int events; } ReflectEvent; static int ReflectEventRun( Tcl_Event *ev, TCL_UNUSED(int) /*flags*/) { /* OWNER thread * * Note: When the channel is closed any pending events of this type are * deleted. See ReflectClose() for the Tcl_DeleteEvents() calls * accomplishing that. */ ReflectEvent *e = (ReflectEvent *) ev; Tcl_NotifyChannel(e->rcPtr->chan, e->events); return 1; } static int ReflectEventDelete( Tcl_Event *ev, void *cd) { /* OWNER thread * * Invoked by DeleteThreadReflectedChannelMap() and ReflectClose(). The * latter ensures that no pending events of this type are run on an * invalid channel. */ ReflectEvent *e = (ReflectEvent *) ev; if ((ev->proc != ReflectEventRun) || ((cd != NULL) && (cd != e->rcPtr))) { return 0; } return 1; } #endif int TclChanPostEventObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { /* * Ensure -> HANDLER thread * * Syntax: chan postevent CHANNEL EVENTSPEC * [0] [1] [2] [3] * * Actually: rPostevent CHANNEL EVENTSPEC * [0] [1] [2] * * where EVENTSPEC = {read write ...} (Abbreviations allowed as well). */ enum ArgIndices { CHAN = 1, EVENT = 2 }; const char *chanId; /* Tcl level channel handle */ Tcl_Channel chan; /* Channel associated to the handle */ const Tcl_ChannelType *chanTypePtr; /* Its associated driver structure */ ReflectedChannel *rcPtr; /* Associated instance data */ int events; /* Mask of events to post */ ReflectedChannelMap *rcmPtr;/* Map of reflected channels with handlers in * this interp. */ Tcl_HashEntry *hPtr; /* Entry in the above map */ /* * Number of arguments... */ if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "channel eventspec"); return TCL_ERROR; } /* * First argument is a channel, a reflected channel, and the call of this * command is done from the interp defining the channel handler cmd. */ chanId = TclGetString(objv[CHAN]); rcmPtr = GetReflectedChannelMap(interp); hPtr = Tcl_FindHashEntry(&rcmPtr->map, chanId); if (hPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can not find reflected channel named \"%s\"", chanId)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CHANNEL", chanId, (char *)NULL); return TCL_ERROR; } /* * Note that the search above subsumes several of the older checks, * namely: * * (1) Does the channel handle refer to a reflected channel? * (2) Is the post event issued from the interpreter holding the handler * of the reflected channel? * * A successful search answers yes to both. Because the map holds only * handles of reflected channels, and only of such whose handler is * defined in this interpreter. * * We keep the old checks for both, for paranoia, but abort now instead of * throwing errors, as failure now means that our internal data structures * have gone seriously haywire. */ chan = (Tcl_Channel)Tcl_GetHashValue(hPtr); chanTypePtr = Tcl_GetChannelType(chan); /* * We use a function referenced by the channel type as our cookie to * detect calls to non-reflecting channels. The channel type itself is not * suitable, as it might not be the static definition in this file, but a * clone thereof. And while we have reserved the name of the type nothing * in the core checks against violation, so someone else might have * created a channel type using our name, clashing with ourselves. */ if (chanTypePtr->watchProc != &ReflectWatch) { Tcl_Panic("TclChanPostEventObjCmd: channel is not a reflected channel"); } rcPtr = (ReflectedChannel *)Tcl_GetChannelInstanceData(chan); if (rcPtr->interp != interp) { Tcl_Panic("TclChanPostEventObjCmd: postevent accepted for call from outside interpreter"); } /* * Second argument is a list of events. Allowed entries are "read", * "write". Expect at least one list element. Abbreviations are ok. */ if (EncodeEventMask(interp, "event", objv[EVENT], &events) != TCL_OK) { return TCL_ERROR; } if (events == 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj("bad event list: is empty", -1)); return TCL_ERROR; } /* * Check that the channel is actually interested in the provided events. */ if (events & ~rcPtr->interest) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "tried to post events channel \"%s\" is not interested in", chanId)); return TCL_ERROR; } /* * We have the channel and the events to post. */ #if TCL_THREADS if (rcPtr->owner == rcPtr->thread) { #endif Tcl_NotifyChannel(chan, events); #if TCL_THREADS } else { ReflectEvent *ev = (ReflectEvent *)Tcl_Alloc(sizeof(ReflectEvent)); ev->header.proc = ReflectEventRun; ev->events = events; ev->rcPtr = rcPtr; /* * We are not preserving the structure here. When the channel is * closed any pending events are deleted, see ReflectClose(), and * ReflectEventDelete(). Trying to preserve and later release when the * event is run may generate a situation where the channel structure * is deleted but not our structure, crashing in * FreeReflectedChannel(). * * Force creation of the RCM, for proper cleanup on thread teardown. * The teardown of unprocessed events is currently coupled to the * thread reflected channel map */ (void) GetThreadReflectedChannelMap(); /* * XXX Race condition !! * XXX The destination thread may not exist anymore already. * XXX (Delayed postevent executed after channel got removed). * XXX Can we detect this ? (check the validity of the owner threadid ?) * XXX Actually, in that case the channel should be dead also ! */ Tcl_ThreadQueueEvent(rcPtr->owner, (Tcl_Event *) ev, TCL_QUEUE_TAIL|TCL_QUEUE_ALERT_IF_EMPTY); } #endif /* * Squash interp results left by the event script. */ Tcl_ResetResult(interp); return TCL_OK; } /* * Channel error message marshalling utilities. */ static Tcl_Obj * MarshallError( Tcl_Interp *interp) { /* * Capture the result status of the interpreter into a string. => List of * options and values, followed by the error message. The result has * refCount 0. */ Tcl_Obj *returnOpt = Tcl_GetReturnOptions(interp, TCL_ERROR); /* * => returnOpt.refCount == 0. We can append directly. */ Tcl_ListObjAppendElement(NULL, returnOpt, Tcl_GetObjResult(interp)); return returnOpt; } static void UnmarshallErrorResult( Tcl_Interp *interp, Tcl_Obj *msgObj) { Tcl_Size lc; Tcl_Obj **lv; int explicitResult; Tcl_Size numOptions; /* * Process the caught message. * * Syntax = (option value)... ?message? * * Bad syntax causes a panic. This is OK because the other side uses * Tcl_GetReturnOptions and list construction functions to marshal the * information; if we panic here, something has gone badly wrong already. */ if (TclListObjGetElements(interp, msgObj, &lc, &lv) != TCL_OK) { Tcl_Panic("TclChanCaughtErrorBypass: Bad syntax of caught result"); } if (interp == NULL) { return; } explicitResult = lc & 1; /* Odd number of values? */ numOptions = lc - explicitResult; if (explicitResult) { Tcl_SetObjResult(interp, lv[lc-1]); } (void) Tcl_SetReturnOptions(interp, Tcl_NewListObj(numOptions, lv)); ((Interp *) interp)->flags &= ~ERR_ALREADY_LOGGED; } int TclChanCaughtErrorBypass( Tcl_Interp *interp, Tcl_Channel chan) { Tcl_Obj *chanMsgObj = NULL; Tcl_Obj *interpMsgObj = NULL; Tcl_Obj *msgObj = NULL; /* * Get a bypassed error message from channel and/or interpreter, save the * reference, then kill the returned objects, if there were any. If there * are messages in both the channel has preference. */ if ((chan == NULL) && (interp == NULL)) { return 0; } if (chan != NULL) { Tcl_GetChannelError(chan, &chanMsgObj); } if (interp != NULL) { Tcl_GetChannelErrorInterp(interp, &interpMsgObj); } if (chanMsgObj != NULL) { msgObj = chanMsgObj; } else if (interpMsgObj != NULL) { msgObj = interpMsgObj; } if (msgObj != NULL) { Tcl_IncrRefCount(msgObj); } if (chanMsgObj != NULL) { Tcl_DecrRefCount(chanMsgObj); } if (interpMsgObj != NULL) { Tcl_DecrRefCount(interpMsgObj); } /* * No message returned, nothing caught. */ if (msgObj == NULL) { return 0; } UnmarshallErrorResult(interp, msgObj); Tcl_DecrRefCount(msgObj); return 1; } /* * Driver functions. ================================================ */ /* *---------------------------------------------------------------------- * * ReflectClose -- * * This function is invoked when the channel is closed, to delete the * driver-specific instance data. * * Results: * A Posix error. * * Side effects: * Releases memory. Arbitrary, as it calls upon a script. * *---------------------------------------------------------------------- */ static int ReflectClose( void *clientData, Tcl_Interp *interp, int flags) { ReflectedChannel *rcPtr = (ReflectedChannel *)clientData; int result; /* Result code for 'close' */ Tcl_Obj *resObj; /* Result data for 'close' */ ReflectedChannelMap *rcmPtr;/* Map of reflected channels with handlers in * this interp */ Tcl_HashEntry *hPtr; /* Entry in the above map */ const Tcl_ChannelType *tctPtr; if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) { return EINVAL; } if (TclInThreadExit()) { /* * This call comes from TclFinalizeIOSystem. There are no * interpreters, and therefore we cannot call upon the handler command * anymore. Threading is irrelevant as well. Simply clean up all * the C level data structures and leave the Tcl level to the other * finalization functions. */ /* * THREADED => Forward this to the origin thread * * Note: DeleteThreadReflectedChannelMap() is the thread exit handler * for the origin thread. Use this to clean up the structure? Except * if lost? */ #if TCL_THREADS if (rcPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; ForwardOpToHandlerThread(rcPtr, ForwardedClose, &p); result = p.base.code; /* * Now squash the pending reflection events for this channel. */ Tcl_DeleteEvents(ReflectEventDelete, rcPtr); if (result != TCL_OK) { FreeReceivedError(&p); } } #endif tctPtr = ((Channel *)rcPtr->chan)->typePtr; if (tctPtr && tctPtr != &reflectedChannelType) { Tcl_Free((void *)tctPtr); ((Channel *)rcPtr->chan)->typePtr = NULL; } Tcl_EventuallyFree(rcPtr, FreeReflectedChannel); return EOK; } /* * Are we in the correct thread? */ #if TCL_THREADS if (rcPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; ForwardOpToHandlerThread(rcPtr, ForwardedClose, &p); result = p.base.code; /* * Now squash the pending reflection events for this channel. */ Tcl_DeleteEvents(ReflectEventDelete, rcPtr); if (result != TCL_OK) { PassReceivedErrorInterp(interp, &p); } } else { #endif result = InvokeTclMethod(rcPtr, METH_FINAL, NULL, NULL, &resObj); if ((result != TCL_OK) && (interp != NULL)) { Tcl_SetChannelErrorInterp(interp, resObj); } Tcl_DecrRefCount(resObj); /* Remove reference we held from the * invoke */ /* * Remove the channel from the map before releasing the memory, to * prevent future accesses (like by 'postevent') from finding and * dereferencing a dangling pointer. * * NOTE: The channel may not be in the map. This is ok, that happens * when the channel was created in a different interpreter and/or * thread and then was moved here. * * NOTE: The channel may have been removed from the map already via * the per-interp DeleteReflectedChannelMap exit-handler. */ if (!rcPtr->dead) { rcmPtr = GetReflectedChannelMap(rcPtr->interp); hPtr = Tcl_FindHashEntry(&rcmPtr->map, Tcl_GetChannelName(rcPtr->chan)); if (hPtr) { Tcl_DeleteHashEntry(hPtr); } } #if TCL_THREADS rcmPtr = GetThreadReflectedChannelMap(); hPtr = Tcl_FindHashEntry(&rcmPtr->map, Tcl_GetChannelName(rcPtr->chan)); if (hPtr) { Tcl_DeleteHashEntry(hPtr); } } #endif tctPtr = ((Channel *)rcPtr->chan)->typePtr; if (tctPtr && tctPtr != &reflectedChannelType) { Tcl_Free((void *)tctPtr); ((Channel *)rcPtr->chan)->typePtr = NULL; } Tcl_EventuallyFree(rcPtr, FreeReflectedChannel); return (result == TCL_OK) ? EOK : EINVAL; } /* *---------------------------------------------------------------------- * * ReflectInput -- * * This function is invoked when more data is requested from the channel. * * Results: * The number of bytes read. * * Side effects: * Allocates memory. Arbitrary, as it calls upon a script. * *---------------------------------------------------------------------- */ static int ReflectInput( void *clientData, char *buf, int toRead, int *errorCodePtr) { ReflectedChannel *rcPtr = (ReflectedChannel *)clientData; Tcl_Obj *toReadObj; Tcl_Size bytec = 0; /* Number of returned bytes */ unsigned char *bytev; /* Array of returned bytes */ Tcl_Obj *resObj; /* Result data for 'read' */ /* * Are we in the correct thread? */ #if TCL_THREADS if (rcPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; p.input.buf = buf; p.input.toRead = toRead; ForwardOpToHandlerThread(rcPtr, ForwardedInput, &p); if (p.base.code != TCL_OK) { if (p.base.code < 0) { /* * No error message, this is an errno signal. */ *errorCodePtr = -p.base.code; } else { PassReceivedError(rcPtr->chan, &p); *errorCodePtr = EINVAL; } p.input.toRead = TCL_INDEX_NONE; } else { *errorCodePtr = EOK; } return p.input.toRead; } #endif /* ASSERT: rcPtr->method & FLAG(METH_READ) */ /* ASSERT: rcPtr->mode & TCL_READABLE */ Tcl_Preserve(rcPtr); TclNewIntObj(toReadObj, toRead); Tcl_IncrRefCount(toReadObj); if (InvokeTclMethod(rcPtr, METH_READ, toReadObj, NULL, &resObj)!=TCL_OK) { int code = ErrnoReturn(rcPtr, resObj); if (code < 0) { *errorCodePtr = -code; goto error; } Tcl_SetChannelError(rcPtr->chan, resObj); goto invalid; } bytev = Tcl_GetBytesFromObj(NULL, resObj, &bytec); if (bytev == NULL) { SetChannelErrorStr(rcPtr->chan, msg_read_nonbyte); goto invalid; } else if (toRead < bytec) { SetChannelErrorStr(rcPtr->chan, msg_read_toomuch); goto invalid; } *errorCodePtr = EOK; if (bytec > 0) { memcpy(buf, bytev, bytec); } stop: Tcl_DecrRefCount(toReadObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ Tcl_Release(rcPtr); return bytec; invalid: *errorCodePtr = EINVAL; error: bytec = -1; goto stop; } /* *---------------------------------------------------------------------- * * ReflectOutput -- * * This function is invoked when data is writen to the channel. * * Results: * The number of bytes actually written. * * Side effects: * Allocates memory. Arbitrary, as it calls upon a script. * *---------------------------------------------------------------------- */ static int ReflectOutput( void *clientData, const char *buf, int toWrite, int *errorCodePtr) { ReflectedChannel *rcPtr = (ReflectedChannel *)clientData; Tcl_Obj *bufObj; Tcl_Obj *resObj; /* Result data for 'write' */ int written; /* * Are we in the correct thread? */ #if TCL_THREADS if (rcPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; p.output.buf = buf; p.output.toWrite = toWrite; ForwardOpToHandlerThread(rcPtr, ForwardedOutput, &p); if (p.base.code != TCL_OK) { if (p.base.code < 0) { /* * No error message, this is an errno signal. */ *errorCodePtr = -p.base.code; } else { PassReceivedError(rcPtr->chan, &p); *errorCodePtr = EINVAL; } p.output.toWrite = -1; } else { *errorCodePtr = EOK; } return p.output.toWrite; } #endif /* ASSERT: rcPtr->method & FLAG(METH_WRITE) */ /* ASSERT: rcPtr->mode & TCL_WRITABLE */ Tcl_Preserve(rcPtr); Tcl_Preserve(rcPtr->interp); bufObj = Tcl_NewByteArrayObj((unsigned char *) buf, toWrite); Tcl_IncrRefCount(bufObj); if (InvokeTclMethod(rcPtr, METH_WRITE, bufObj, NULL, &resObj) != TCL_OK) { int code = ErrnoReturn(rcPtr, resObj); if (code < 0) { *errorCodePtr = -code; goto error; } Tcl_SetChannelError(rcPtr->chan, resObj); goto invalid; } if (Tcl_InterpDeleted(rcPtr->interp)) { /* * The interp was destroyed during InvokeTclMethod(). */ SetChannelErrorStr(rcPtr->chan, msg_send_dstlost); goto invalid; } if (Tcl_GetIntFromObj(rcPtr->interp, resObj, &written) != TCL_OK) { Tcl_SetChannelError(rcPtr->chan, MarshallError(rcPtr->interp)); goto invalid; } if ((written == 0) && (toWrite > 0)) { /* * The handler claims to have written nothing of what it was given. * That is bad. */ SetChannelErrorStr(rcPtr->chan, msg_write_nothing); goto invalid; } if (toWrite < written) { /* * The handler claims to have written more than it was given. That is * bad. Note that the I/O core would crash if we were to return this * information, trying to write -nnn bytes in the next iteration. */ SetChannelErrorStr(rcPtr->chan, msg_write_toomuch); goto invalid; } *errorCodePtr = EOK; stop: Tcl_DecrRefCount(bufObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ Tcl_Release(rcPtr->interp); Tcl_Release(rcPtr); return written; invalid: *errorCodePtr = EINVAL; error: written = -1; goto stop; } /* *---------------------------------------------------------------------- * * ReflectSeekWide / ReflectSeek -- * * This function is invoked when the user wishes to seek on the channel. * * Results: * The new location of the access point. * * Side effects: * Allocates memory. Arbitrary, as it calls upon a script. * *---------------------------------------------------------------------- */ static long long ReflectSeekWide( void *clientData, long long offset, int seekMode, int *errorCodePtr) { ReflectedChannel *rcPtr = (ReflectedChannel *)clientData; Tcl_Obj *offObj, *baseObj; Tcl_Obj *resObj; /* Result for 'seek' */ Tcl_WideInt newLoc; /* * Are we in the correct thread? */ #if TCL_THREADS if (rcPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; p.seek.seekMode = seekMode; p.seek.offset = offset; ForwardOpToHandlerThread(rcPtr, ForwardedSeek, &p); if (p.base.code != TCL_OK) { PassReceivedError(rcPtr->chan, &p); *errorCodePtr = EINVAL; p.seek.offset = -1; } else { *errorCodePtr = EOK; } return p.seek.offset; } #endif /* ASSERT: rcPtr->method & FLAG(METH_SEEK) */ Tcl_Preserve(rcPtr); TclNewIntObj(offObj, offset); baseObj = Tcl_NewStringObj( (seekMode == SEEK_SET) ? "start" : (seekMode == SEEK_CUR) ? "current" : "end", -1); Tcl_IncrRefCount(offObj); Tcl_IncrRefCount(baseObj); if (InvokeTclMethod(rcPtr, METH_SEEK, offObj, baseObj, &resObj)!=TCL_OK) { Tcl_SetChannelError(rcPtr->chan, resObj); goto invalid; } if (TclGetWideIntFromObj(rcPtr->interp, resObj, &newLoc) != TCL_OK) { Tcl_SetChannelError(rcPtr->chan, MarshallError(rcPtr->interp)); goto invalid; } if (newLoc < 0) { SetChannelErrorStr(rcPtr->chan, msg_seek_beforestart); goto invalid; } *errorCodePtr = EOK; stop: Tcl_DecrRefCount(offObj); Tcl_DecrRefCount(baseObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ Tcl_Release(rcPtr); return newLoc; invalid: *errorCodePtr = EINVAL; newLoc = -1; goto stop; } /* *---------------------------------------------------------------------- * * ReflectWatch -- * * This function is invoked to tell the channel what events the I/O * system is interested in. * * Results: * None. * * Side effects: * Allocates memory. Arbitrary, as it calls upon a script. * *---------------------------------------------------------------------- */ static void ReflectWatch( void *clientData, int mask) { ReflectedChannel *rcPtr = (ReflectedChannel *)clientData; Tcl_Obj *maskObj; /* * We restrict the interest to what the channel can support. IOW there * will never be write events for a channel which is not writable. * Analoguously for read events and non-readable channels. */ mask &= rcPtr->mode; if (mask == rcPtr->interest) { /* * Same old, same old, why should we do something? */ return; } /* * Are we in the correct thread? */ #if TCL_THREADS if (rcPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; p.watch.mask = mask; ForwardOpToHandlerThread(rcPtr, ForwardedWatch, &p); /* * Any failure from the forward is ignored. We have no place to put * this. */ return; } #endif Tcl_Preserve(rcPtr); rcPtr->interest = mask; maskObj = DecodeEventMask(mask); /* assert maskObj.refCount == 1 */ (void) InvokeTclMethod(rcPtr, METH_WATCH, maskObj, NULL, NULL); Tcl_DecrRefCount(maskObj); Tcl_Release(rcPtr); } /* *---------------------------------------------------------------------- * * ReflectBlock -- * * This function is invoked to tell the channel which blocking behaviour * is required of it. * * Results: * A Posix error number. * * Side effects: * Allocates memory. Arbitrary, as it calls upon a script. * *---------------------------------------------------------------------- */ static int ReflectBlock( void *clientData, int nonblocking) { ReflectedChannel *rcPtr = (ReflectedChannel *)clientData; Tcl_Obj *blockObj; int errorNum; /* EINVAL or EOK (success). */ Tcl_Obj *resObj; /* Result data for 'blocking' */ /* * Are we in the correct thread? */ #if TCL_THREADS if (rcPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; p.block.nonblocking = nonblocking; ForwardOpToHandlerThread(rcPtr, ForwardedBlock, &p); if (p.base.code != TCL_OK) { PassReceivedError(rcPtr->chan, &p); return EINVAL; } return EOK; } #endif blockObj = Tcl_NewBooleanObj(!nonblocking); Tcl_IncrRefCount(blockObj); Tcl_Preserve(rcPtr); if (InvokeTclMethod(rcPtr,METH_BLOCKING,blockObj,NULL,&resObj)!=TCL_OK) { Tcl_SetChannelError(rcPtr->chan, resObj); errorNum = EINVAL; } else { errorNum = EOK; } Tcl_DecrRefCount(blockObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ Tcl_Release(rcPtr); return errorNum; } #if TCL_THREADS /* *---------------------------------------------------------------------- * * ReflectThread -- * * This function is invoked to tell the channel about thread movements. * * Results: * None. * * Side effects: * Allocates memory. Arbitrary, as it calls upon a script. * *---------------------------------------------------------------------- */ static void ReflectThread( void *clientData, int action) { ReflectedChannel *rcPtr = (ReflectedChannel *)clientData; switch (action) { case TCL_CHANNEL_THREAD_INSERT: rcPtr->owner = Tcl_GetCurrentThread(); break; case TCL_CHANNEL_THREAD_REMOVE: rcPtr->owner = NULL; break; default: Tcl_Panic("Unknown thread action code."); break; } } #endif /* *---------------------------------------------------------------------- * * ReflectSetOption -- * * This function is invoked to configure a channel option. * * Results: * A standard Tcl result code. * * Side effects: * Arbitrary, as it calls upon a Tcl script. * *---------------------------------------------------------------------- */ static int ReflectSetOption( void *clientData, /* Channel to query */ Tcl_Interp *interp, /* Interpreter to leave error messages in */ const char *optionName, /* Name of requested option */ const char *newValue) /* The new value */ { ReflectedChannel *rcPtr = (ReflectedChannel *)clientData; Tcl_Obj *optionObj, *valueObj; int result; /* Result code for 'configure' */ Tcl_Obj *resObj; /* Result data for 'configure' */ /* * Are we in the correct thread? */ #if TCL_THREADS if (rcPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; p.setOpt.name = optionName; p.setOpt.value = newValue; ForwardOpToHandlerThread(rcPtr, ForwardedSetOpt, &p); if (p.base.code != TCL_OK) { Tcl_Obj *err = Tcl_NewStringObj(p.base.msgStr, -1); UnmarshallErrorResult(interp, err); Tcl_DecrRefCount(err); FreeReceivedError(&p); } return p.base.code; } #endif Tcl_Preserve(rcPtr); optionObj = Tcl_NewStringObj(optionName, -1); valueObj = Tcl_NewStringObj(newValue, -1); Tcl_IncrRefCount(optionObj); Tcl_IncrRefCount(valueObj); result = InvokeTclMethod(rcPtr, METH_CONFIGURE,optionObj,valueObj, &resObj); if (result != TCL_OK) { UnmarshallErrorResult(interp, resObj); } Tcl_DecrRefCount(optionObj); Tcl_DecrRefCount(valueObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ Tcl_Release(rcPtr); return result; } /* *---------------------------------------------------------------------- * * ReflectGetOption -- * * This function is invoked to retrieve all or a channel option. * * Results: * A standard Tcl result code. * * Side effects: * Arbitrary, as it calls upon a Tcl script. * *---------------------------------------------------------------------- */ static int ReflectGetOption( void *clientData, /* Channel to query */ Tcl_Interp *interp, /* Interpreter to leave error messages in */ const char *optionName, /* Name of reuqested option */ Tcl_DString *dsPtr) /* String to place the result into */ { /* * This code is special. It has regular passing of Tcl result, and errors. * The bypass functions are not required. */ ReflectedChannel *rcPtr = (ReflectedChannel *)clientData; Tcl_Obj *optionObj; Tcl_Obj *resObj; /* Result data for 'configure' */ Tcl_Size listc; int result = TCL_OK; Tcl_Obj **listv; MethodName method; /* * Are we in the correct thread? */ #if TCL_THREADS if (rcPtr->thread != Tcl_GetCurrentThread()) { ForwardedOperation opcode; ForwardParam p; p.getOpt.name = optionName; p.getOpt.value = dsPtr; if (optionName == NULL) { opcode = ForwardedGetOptAll; } else { opcode = ForwardedGetOpt; } ForwardOpToHandlerThread(rcPtr, opcode, &p); if (p.base.code != TCL_OK) { Tcl_Obj *err = Tcl_NewStringObj(p.base.msgStr, -1); UnmarshallErrorResult(interp, err); Tcl_DecrRefCount(err); FreeReceivedError(&p); } return p.base.code; } #endif if (optionName == NULL) { /* * Retrieve all options. */ method = METH_CGETALL; optionObj = NULL; } else { /* * Retrieve the value of one option. */ method = METH_CGET; optionObj = Tcl_NewStringObj(optionName, -1); Tcl_IncrRefCount(optionObj); } Tcl_Preserve(rcPtr); if (InvokeTclMethod(rcPtr, method, optionObj, NULL, &resObj)!=TCL_OK) { UnmarshallErrorResult(interp, resObj); goto error; } /* * The result has to go into the 'dsPtr' for propagation to the caller of * the driver. */ if (optionObj != NULL) { TclDStringAppendObj(dsPtr, resObj); goto ok; } /* * Extract the list and append each item as element. */ /* * NOTE (4): If we extract the string rep we can assume a properly quoted * string. Together with a separating space this way of simply appending * the whole string rep might be faster. It also doesn't check if the * result is a valid list. Nor that the list has an even number elements. */ if (TclListObjGetElements(interp, resObj, &listc, &listv) != TCL_OK) { goto error; } if ((listc % 2) == 1) { /* * Odd number of elements is wrong. */ Tcl_ResetResult(interp); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Expected list with even number of " "elements, got %" TCL_SIZE_MODIFIER "d element%s instead", listc, (listc == 1 ? "" : "s"))); goto error; } else { Tcl_Size len; const char *str = TclGetStringFromObj(resObj, &len); if (len) { TclDStringAppendLiteral(dsPtr, " "); Tcl_DStringAppend(dsPtr, str, len); } goto ok; } ok: result = TCL_OK; stop: if (optionObj) { Tcl_DecrRefCount(optionObj); } Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ Tcl_Release(rcPtr); return result; error: result = TCL_ERROR; goto stop; } /* *---------------------------------------------------------------------- * * ReflectTruncate -- * * This function is invoked to truncate a channel's file size. * * Results: * A standard Tcl result code. * * Side effects: * Arbitrary, as it calls upon a Tcl script. * *---------------------------------------------------------------------- */ static int ReflectTruncate( void *clientData, /* Channel to query */ long long length) /* Length to truncate to. */ { ReflectedChannel *rcPtr = (ReflectedChannel *)clientData; Tcl_Obj *lenObj; int errorNum; /* EINVAL or EOK (success). */ Tcl_Obj *resObj; /* Result for 'truncate' */ /* * Are we in the correct thread? */ #if TCL_THREADS if (rcPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; p.truncate.length = length; ForwardOpToHandlerThread(rcPtr, ForwardedTruncate, &p); if (p.base.code != TCL_OK) { PassReceivedError(rcPtr->chan, &p); return EINVAL; } return EOK; } #endif /* ASSERT: rcPtr->method & FLAG(METH_TRUNCATE) */ Tcl_Preserve(rcPtr); lenObj = Tcl_NewWideIntObj(length); Tcl_IncrRefCount(lenObj); if (InvokeTclMethod(rcPtr,METH_TRUNCATE,lenObj,NULL,&resObj)!=TCL_OK) { Tcl_SetChannelError(rcPtr->chan, resObj); errorNum = EINVAL; } else { errorNum = EOK; } Tcl_DecrRefCount(lenObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ Tcl_Release(rcPtr); return errorNum; } /* * Helpers. ========================================================= */ /* *---------------------------------------------------------------------- * * EncodeEventMask -- * * This function takes a list of event items and constructs the * equivalent internal bitmask. The list may be empty but will usually * contain at least one element. Valid elements are "read", "write", or * any unique abbreviation of them. Note that the bitmask is not changed * if problems are encountered. * * Results: * A standard Tcl error code. A bitmask where TCL_READABLE and/or * TCL_WRITABLE can be set. * * Side effects: * May shimmer 'obj' to a list representation. May place an error message * into the interp result. * *---------------------------------------------------------------------- */ static int EncodeEventMask( Tcl_Interp *interp, const char *objName, Tcl_Obj *obj, int *mask) { int events; /* Mask of events to post */ Tcl_Size listc; /* #elements in eventspec list */ Tcl_Obj **listv; /* Elements of eventspec list */ int evIndex; /* Id of event for an element of the eventspec * list. */ if (TclListObjGetElements(interp, obj, &listc, &listv) != TCL_OK) { return TCL_ERROR; } events = 0; while (listc > 0) { if (Tcl_GetIndexFromObj(interp, listv[listc-1], eventOptions, objName, 0, &evIndex) != TCL_OK) { return TCL_ERROR; } switch (evIndex) { case EVENT_READ: events |= TCL_READABLE; break; case EVENT_WRITE: events |= TCL_WRITABLE; break; } listc --; } *mask = events; return TCL_OK; } /* *---------------------------------------------------------------------- * * DecodeEventMask -- * * This function takes an internal bitmask of events and constructs the * equivalent list of event items. * * Results, Contract: * A Tcl_Obj reference. The object will have a refCount of one. The user * has to decrement it to release the object. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_Obj * DecodeEventMask( int mask) { const char *eventStr; Tcl_Obj *evObj; switch (mask & RANDW) { case RANDW: eventStr = "read write"; break; case TCL_READABLE: eventStr = "read"; break; case TCL_WRITABLE: eventStr = "write"; break; default: eventStr = ""; break; } evObj = Tcl_NewStringObj(eventStr, -1); Tcl_IncrRefCount(evObj); /* assert evObj.refCount == 1 */ return evObj; } /* *---------------------------------------------------------------------- * * NewReflectedChannel -- * * This function is invoked to allocate and initialize the instance data * of a new reflected channel. * * Results: * A heap-allocated channel instance. * * Side effects: * Allocates memory. * *---------------------------------------------------------------------- */ static ReflectedChannel * NewReflectedChannel( Tcl_Interp *interp, Tcl_Obj *cmdpfxObj, int mode, Tcl_Obj *handleObj) { ReflectedChannel *rcPtr; int mn = 0; rcPtr = (ReflectedChannel *)Tcl_Alloc(sizeof(ReflectedChannel)); /* rcPtr->chan: Assigned by caller. Dummy data here. */ rcPtr->chan = NULL; rcPtr->interp = interp; rcPtr->dead = 0; #if TCL_THREADS rcPtr->thread = Tcl_GetCurrentThread(); #endif rcPtr->mode = mode; rcPtr->interest = 0; /* Initially no interest registered */ rcPtr->cmd = TclListObjCopy(NULL, cmdpfxObj); Tcl_IncrRefCount(rcPtr->cmd); rcPtr->methods = Tcl_NewListObj(METH_WRITE + 1, NULL); while (mn <= (int)METH_WRITE) { Tcl_ListObjAppendElement(NULL, rcPtr->methods, Tcl_NewStringObj(methodNames[mn++], -1)); } Tcl_IncrRefCount(rcPtr->methods); rcPtr->name = handleObj; Tcl_IncrRefCount(rcPtr->name); return rcPtr; } /* *---------------------------------------------------------------------- * * NextHandle -- * * This function is invoked to generate a channel handle for a new * reflected channel. * * Results: * A Tcl_Obj containing the string of the new channel handle. The * refcount of the returned object is -- zero --. * * Side effects: * May allocate memory. Mutex-protected critical section locks out other * threads for a short time. * *---------------------------------------------------------------------- */ static Tcl_Obj * NextHandle(void) { /* * Count number of generated reflected channels. Used for id generation. * Ids are never reclaimed and there is no dealing with wrap around. On * the other hand, "unsigned long" should be big enough except for * absolute longrunners (generate a 100 ids per second => overflow will * occur in 1 1/3 years). */ TCL_DECLARE_MUTEX(rcCounterMutex) static unsigned long rcCounter = 0; Tcl_Obj *resObj; Tcl_MutexLock(&rcCounterMutex); resObj = Tcl_ObjPrintf("rc%lu", rcCounter); rcCounter++; Tcl_MutexUnlock(&rcCounterMutex); return resObj; } static inline void CleanRefChannelInstance( ReflectedChannel *rcPtr) { if (rcPtr->name) { /* * Reset obj-type (channel is deleted or dead anyway) to avoid leakage * by cyclic references (see bug [79474c58800cdf94]). */ TclFreeInternalRep(rcPtr->name); Tcl_DecrRefCount(rcPtr->name); rcPtr->name = NULL; } if (rcPtr->methods) { Tcl_DecrRefCount(rcPtr->methods); rcPtr->methods = NULL; } if (rcPtr->cmd) { Tcl_DecrRefCount(rcPtr->cmd); rcPtr->cmd = NULL; } } static void FreeReflectedChannel( void *blockPtr) { ReflectedChannel *rcPtr = (ReflectedChannel *) blockPtr; Channel *chanPtr = (Channel *) rcPtr->chan; TclChannelRelease((Tcl_Channel)chanPtr); CleanRefChannelInstance(rcPtr); Tcl_Free(rcPtr); } /* *---------------------------------------------------------------------- * * InvokeTclMethod -- * * This function is used to invoke the Tcl level of a reflected channel. * It handles all the command assembly, invocation, and generic state and * result mgmt. It does *not* handle thread redirection; that is the * responsibility of clients of this function. * * Results: * Result code and data as returned by the method. * * Side effects: * Arbitrary, as it calls upon a Tcl script. * * Contract: * argOneObj.refCount >= 1 on entry and exit, if argOneObj != NULL * argTwoObj.refCount >= 1 on entry and exit, if argTwoObj != NULL * resObj.refCount in {0, 1, ...} * *---------------------------------------------------------------------- */ static int InvokeTclMethod( ReflectedChannel *rcPtr, MethodName method, Tcl_Obj *argOneObj, /* NULL'able */ Tcl_Obj *argTwoObj, /* NULL'able */ Tcl_Obj **resultObjPtr) /* NULL'able */ { Tcl_Obj *methObj = NULL; /* Method name in object form */ Tcl_InterpState sr; /* State of handler interp */ int result; /* Result code of method invocation */ Tcl_Obj *resObj = NULL; /* Result of method invocation. */ Tcl_Obj *cmd; if (rcPtr->dead) { /* * The channel is marked as dead. Bail out immediately, with an * appropriate error. */ if (resultObjPtr != NULL) { resObj = Tcl_NewStringObj(msg_dstlost,-1); *resultObjPtr = resObj; Tcl_IncrRefCount(resObj); } /* * Not touching argOneObj, argTwoObj, they have not been used. * See the contract as well. */ return TCL_ERROR; } /* * Insert method into the callback command, after the command prefix, * before the channel id. */ cmd = TclListObjCopy(NULL, rcPtr->cmd); Tcl_ListObjIndex(NULL, rcPtr->methods, method, &methObj); Tcl_ListObjAppendElement(NULL, cmd, methObj); Tcl_ListObjAppendElement(NULL, cmd, rcPtr->name); /* * Append the additional argument containing method specific details * behind the channel id. If specified. * * Because of the contract there is no need to increment the refcounts. * The objects will survive the Tcl_EvalObjv without change. */ if (argOneObj) { Tcl_ListObjAppendElement(NULL, cmd, argOneObj); if (argTwoObj) { Tcl_ListObjAppendElement(NULL, cmd, argTwoObj); } } /* * And run the handler... This is done in auch a manner which leaves any * existing state intact. */ Tcl_IncrRefCount(cmd); sr = Tcl_SaveInterpState(rcPtr->interp, 0 /* Dummy */); Tcl_Preserve(rcPtr->interp); result = Tcl_EvalObjEx(rcPtr->interp, cmd, TCL_EVAL_GLOBAL); /* * We do not try to extract the result information if the caller has no * interest in it. I.e. there is no need to put effort into creating * something which is discarded immediately after. */ if (resultObjPtr) { if (result == TCL_OK) { /* * Ok result taken as is, also if the caller requests that there * is no capture. */ resObj = Tcl_GetObjResult(rcPtr->interp); } else { /* * Non-ok result is always treated as an error. We have to capture * the full state of the result, including additional options. * * This is complex and ugly, and would be completely unnecessary * if we only added support for a TCL_FORBID_EXCEPTIONS flag. */ if (result != TCL_ERROR) { Tcl_Size cmdLen; const char *cmdString = TclGetStringFromObj(cmd, &cmdLen); Tcl_IncrRefCount(cmd); Tcl_ResetResult(rcPtr->interp); Tcl_SetObjResult(rcPtr->interp, Tcl_ObjPrintf( "chan handler returned bad code: %d", result)); Tcl_LogCommandInfo(rcPtr->interp, cmdString, cmdString, cmdLen); Tcl_DecrRefCount(cmd); result = TCL_ERROR; } Tcl_AppendObjToErrorInfo(rcPtr->interp, Tcl_ObjPrintf( "\n (chan handler subcommand \"%s\")", methodNames[method])); resObj = MarshallError(rcPtr->interp); } Tcl_IncrRefCount(resObj); } Tcl_DecrRefCount(cmd); Tcl_RestoreInterpState(rcPtr->interp, sr); Tcl_Release(rcPtr->interp); /* * The resObj has a ref count of 1 at this location. This means that the * caller of InvokeTclMethod has to dispose of it (but only if it was * returned to it). */ if (resultObjPtr != NULL) { *resultObjPtr = resObj; } /* * There no need to handle the case where nothing is returned, because for * that case resObj was not set anyway. */ return result; } /* *---------------------------------------------------------------------- * * ErrnoReturn -- * * Checks a method error result if it returned an 'errno'. * * Results: * The negative errno found in the error result, or 0. * * Side effects: * None. * * Users: * ReflectInput/Output(), to enable the signaling of EAGAIN on 0-sized * short reads/writes. * *---------------------------------------------------------------------- */ static int ErrnoReturn( ReflectedChannel *rcPtr, Tcl_Obj *resObj) { int code; Tcl_InterpState sr; /* State of handler interp */ if (rcPtr->dead) { return 0; } sr = Tcl_SaveInterpState(rcPtr->interp, 0 /* Dummy */); UnmarshallErrorResult(rcPtr->interp, resObj); resObj = Tcl_GetObjResult(rcPtr->interp); if (((Tcl_GetIntFromObj(rcPtr->interp, resObj, &code) != TCL_OK) || (code >= 0))) { if (strcmp("EAGAIN", TclGetString(resObj)) == 0) { code = -EAGAIN; } else { code = 0; } } Tcl_RestoreInterpState(rcPtr->interp, sr); return code; } /* *---------------------------------------------------------------------- * * GetReflectedChannelMap -- * * Gets and potentially initializes the reflected channel map for an * interpreter. * * Results: * A pointer to the map created, for use by the caller. * * Side effects: * Initializes the reflected channel map for an interpreter. * *---------------------------------------------------------------------- */ static ReflectedChannelMap * GetReflectedChannelMap( Tcl_Interp *interp) { ReflectedChannelMap *rcmPtr = (ReflectedChannelMap *)Tcl_GetAssocData(interp, RCMKEY, NULL); if (rcmPtr == NULL) { rcmPtr = (ReflectedChannelMap *)Tcl_Alloc(sizeof(ReflectedChannelMap)); Tcl_InitHashTable(&rcmPtr->map, TCL_STRING_KEYS); Tcl_SetAssocData(interp, RCMKEY, DeleteReflectedChannelMap, rcmPtr); } return rcmPtr; } /* *---------------------------------------------------------------------- * * DeleteReflectedChannelMap -- * * Deletes the channel table for an interpreter, closing any open * channels whose refcount reaches zero. This procedure is invoked when * an interpreter is deleted, via the AssocData cleanup mechanism. * * Results: * None. * * Side effects: * Deletes the hash table of channels. May close channels. May flush * output on closed channels. Removes any channelEvent handlers that were * registered in this interpreter. * *---------------------------------------------------------------------- */ static void MarkDead( ReflectedChannel *rcPtr) { if (rcPtr->dead) { return; } CleanRefChannelInstance(rcPtr); rcPtr->dead = 1; } static void DeleteReflectedChannelMap( void *clientData, /* The per-interpreter data structure. */ Tcl_Interp *interp) /* The interpreter being deleted. */ { ReflectedChannelMap *rcmPtr = (ReflectedChannelMap *)clientData; /* The map */ Tcl_HashSearch hSearch; /* Search variable. */ Tcl_HashEntry *hPtr; /* Search variable. */ ReflectedChannel *rcPtr; Tcl_Channel chan; #if TCL_THREADS ForwardingResult *resultPtr; ForwardingEvent *evPtr; ForwardParam *paramPtr; #endif /* * Delete all entries. The channels may have been closed already, or will * be closed later, by the standard IO finalization of an interpreter * under destruction. Except for the channels which were moved to a * different interpreter and/or thread. They do not exist from the IO * systems point of view and will not get closed. Therefore mark all as * dead so that any future access will cause a proper error. For channels * in a different thread we actually do the same as * DeleteThreadReflectedChannelMap(), just restricted to the channels of * this interp. */ for (hPtr = Tcl_FirstHashEntry(&rcmPtr->map, &hSearch); hPtr != NULL; hPtr = Tcl_FirstHashEntry(&rcmPtr->map, &hSearch)) { chan = (Tcl_Channel)Tcl_GetHashValue(hPtr); rcPtr = (ReflectedChannel *)Tcl_GetChannelInstanceData(chan); MarkDead(rcPtr); Tcl_DeleteHashEntry(hPtr); } Tcl_DeleteHashTable(&rcmPtr->map); Tcl_Free(&rcmPtr->map); #if TCL_THREADS /* * The origin interpreter for one or more reflected channels is gone. */ /* * Go through the list of pending results and cancel all whose events were * destined for this interpreter. While this is in progress we block any * other access to the list of pending results. */ Tcl_MutexLock(&rcForwardMutex); for (resultPtr = forwardList; resultPtr != NULL; resultPtr = resultPtr->nextPtr) { if (resultPtr->dsti != interp) { /* * Ignore results/events for other interpreters. */ continue; } /* * The receiver for the event exited, before processing the event. We * detach the result now, wake the originator up and signal failure. * * Attention: Results may have been detached already, by either the * receiver, or this thread, as part of other parts in the thread * teardown. Such results are ignored. See ticket [b47b176adf] for the * identical race condition in Tcl 8.6 IORTrans. */ evPtr = resultPtr->evPtr; /* * Basic crash safety until this routine can get revised [3411310] */ if (evPtr == NULL) { continue; } paramPtr = evPtr->param; if (!evPtr) { continue; } evPtr->resultPtr = NULL; resultPtr->evPtr = NULL; resultPtr->result = TCL_ERROR; ForwardSetStaticError(paramPtr, msg_send_dstlost); Tcl_ConditionNotify(&resultPtr->done); } Tcl_MutexUnlock(&rcForwardMutex); /* * Get the map of all channels handled by the current thread. This is a * ReflectedChannelMap, but on a per-thread basis, not per-interp. Go * through the channels and remove all which were handled by this * interpreter. They have already been marked as dead. */ rcmPtr = GetThreadReflectedChannelMap(); for (hPtr = Tcl_FirstHashEntry(&rcmPtr->map, &hSearch); hPtr != NULL; hPtr = Tcl_NextHashEntry(&hSearch)) { chan = (Tcl_Channel)Tcl_GetHashValue(hPtr); rcPtr = (ReflectedChannel *)Tcl_GetChannelInstanceData(chan); if (rcPtr->interp != interp) { /* * Ignore entries for other interpreters. */ continue; } MarkDead(rcPtr); Tcl_DeleteHashEntry(hPtr); } #else (void)interp; #endif } #if TCL_THREADS /* *---------------------------------------------------------------------- * * GetThreadReflectedChannelMap -- * * Gets and potentially initializes the reflected channel map for a * thread. * * Results: * A pointer to the map created, for use by the caller. * * Side effects: * Initializes the reflected channel map for a thread. * *---------------------------------------------------------------------- */ static ReflectedChannelMap * GetThreadReflectedChannelMap(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!tsdPtr->rcmPtr) { tsdPtr->rcmPtr = (ReflectedChannelMap *)Tcl_Alloc(sizeof(ReflectedChannelMap)); Tcl_InitHashTable(&tsdPtr->rcmPtr->map, TCL_STRING_KEYS); Tcl_CreateThreadExitHandler(DeleteThreadReflectedChannelMap, NULL); } return tsdPtr->rcmPtr; } /* *---------------------------------------------------------------------- * * DeleteThreadReflectedChannelMap -- * * Deletes the channel table for a thread. This procedure is invoked when * a thread is deleted. The channels have already been marked as dead, in * DeleteReflectedChannelMap(). * * Results: * None. * * Side effects: * Deletes the hash table of channels. * *---------------------------------------------------------------------- */ static void DeleteThreadReflectedChannelMap( TCL_UNUSED(void *)) { Tcl_HashSearch hSearch; /* Search variable. */ Tcl_HashEntry *hPtr; /* Search variable. */ Tcl_ThreadId self = Tcl_GetCurrentThread(); ReflectedChannelMap *rcmPtr; /* The map */ ForwardingResult *resultPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* * The origin thread for one or more reflected channels is gone. * NOTE: If this function is called due to a thread getting killed the * per-interp DeleteReflectedChannelMap is apparently not called. */ /* * Go through the list of pending results and cancel all whose events were * destined for this thread. While this is in progress we block any other * access to the list of pending results. */ Tcl_MutexLock(&rcForwardMutex); for (resultPtr = forwardList; resultPtr != NULL; resultPtr = resultPtr->nextPtr) { ForwardingEvent *evPtr; ForwardParam *paramPtr; if (resultPtr->dst != self) { /* * Ignore results/events for other threads. */ continue; } /* * The receiver for the event exited, before processing the event. We * detach the result now, wake the originator up and signal failure. * * Attention: Results may have been detached already, by either the * receiver, or this thread, as part of other parts in the thread * teardown. Such results are ignored. See ticket [b47b176adf] for the * identical race condition in Tcl 8.6 IORTrans. */ evPtr = resultPtr->evPtr; /* * Basic crash safety until this routine can get revised [3411310] */ if (evPtr == NULL ) { continue; } paramPtr = evPtr->param; if (!evPtr) { continue; } evPtr->resultPtr = NULL; resultPtr->evPtr = NULL; resultPtr->result = TCL_ERROR; ForwardSetStaticError(paramPtr, msg_send_dstlost); Tcl_ConditionNotify(&resultPtr->done); } Tcl_MutexUnlock(&rcForwardMutex); /* * Run over the event queue of this thread and remove all ReflectEvent's * still pending. These are inbound events for reflected channels this * thread owns but doesn't handle. The inverse of the channel map * actually. */ Tcl_DeleteEvents(ReflectEventDelete, NULL); /* * Get the map of all channels handled by the current thread. This is a * ReflectedChannelMap, but on a per-thread basis, not per-interp. Go * through the channels, remove all, mark them as dead. */ rcmPtr = GetThreadReflectedChannelMap(); tsdPtr->rcmPtr = NULL; for (hPtr = Tcl_FirstHashEntry(&rcmPtr->map, &hSearch); hPtr != NULL; hPtr = Tcl_FirstHashEntry(&rcmPtr->map, &hSearch)) { Tcl_Channel chan = (Tcl_Channel)Tcl_GetHashValue(hPtr); ReflectedChannel *rcPtr = (ReflectedChannel *)Tcl_GetChannelInstanceData(chan); MarkDead(rcPtr); Tcl_DeleteHashEntry(hPtr); } Tcl_Free(rcmPtr); } static void ForwardOpToHandlerThread( ReflectedChannel *rcPtr, /* Channel instance */ ForwardedOperation op, /* Forwarded driver operation */ const void *param) /* Arguments */ { /* * Core of the communication from OWNER to HANDLER thread. The receiver is * ForwardProc() below. */ Tcl_ThreadId dst = rcPtr->thread; ForwardingEvent *evPtr; ForwardingResult *resultPtr; /* * We gather the lock early. This allows us to check the liveness of the * channel without interference from DeleteThreadReflectedChannelMap(). */ Tcl_MutexLock(&rcForwardMutex); if (rcPtr->dead) { /* * The channel is marked as dead. Bail out immediately, with an * appropriate error. Do not forget to unlock the mutex on this path. */ ForwardSetStaticError((ForwardParam *) param, msg_send_dstlost); Tcl_MutexUnlock(&rcForwardMutex); return; } /* * Create and initialize the event and data structures. */ evPtr = (ForwardingEvent *)Tcl_Alloc(sizeof(ForwardingEvent)); resultPtr = (ForwardingResult *)Tcl_Alloc(sizeof(ForwardingResult)); evPtr->event.proc = ForwardProc; evPtr->resultPtr = resultPtr; evPtr->op = op; evPtr->rcPtr = rcPtr; evPtr->param = (ForwardParam *) param; resultPtr->src = Tcl_GetCurrentThread(); resultPtr->dst = dst; resultPtr->dsti = rcPtr->interp; resultPtr->done = NULL; resultPtr->result = -1; resultPtr->evPtr = evPtr; /* * Now execute the forward. */ TclSpliceIn(resultPtr, forwardList); /* * Do not unlock here. That is done by the ConditionWait. */ /* * Ensure cleanup of the event if the origin thread exits while this event * is pending or in progress. Exit of the destination thread is handled by * DeleteThreadReflectedChannelMap(), this is set up by * GetThreadReflectedChannelMap(). This is what we use the 'forwardList' * (see above) for. */ Tcl_CreateThreadExitHandler(SrcExitProc, evPtr); /* * Queue the event and poke the other thread's notifier. */ Tcl_ThreadQueueEvent(dst, (Tcl_Event *) evPtr, TCL_QUEUE_TAIL|TCL_QUEUE_ALERT_IF_EMPTY); /* * (*) Block until the handler thread has either processed the transfer or * rejected it. */ while (resultPtr->result < 0) { /* * NOTE (1): Is it possible that the current thread goes away while * waiting here? IOW Is it possible that "SrcExitProc" is called while * we are here? See complementary note (2) in "SrcExitProc" * * The ConditionWait unlocks the mutex during the wait and relocks it * immediately after. */ Tcl_ConditionWait(&resultPtr->done, &rcForwardMutex, NULL); } /* * Unlink result from the forwarder list. No need to lock. Either still * locked, or locked by the ConditionWait */ TclSpliceOut(resultPtr, forwardList); resultPtr->nextPtr = NULL; resultPtr->prevPtr = NULL; Tcl_MutexUnlock(&rcForwardMutex); Tcl_ConditionFinalize(&resultPtr->done); /* * Kill the cleanup handler now, and the result structure as well, before * returning the success code. * * Note: The event structure has already been deleted. */ Tcl_DeleteThreadExitHandler(SrcExitProc, evPtr); Tcl_Free(resultPtr); } static int ForwardProc( Tcl_Event *evGPtr, TCL_UNUSED(int) /* mask */) { /* * HANDLER thread. * The receiver part for the operations coming from the OWNER thread. * See ForwardOpToHandlerThread() for the transmitter. * * Notes regarding access to the referenced data. * * In principle the data belongs to the originating thread (see * evPtr->src), however this thread is currently blocked at (*), i.e., * quiescent. Because of this we can treat the data as belonging to us, * without fear of race conditions. I.e. we can read and write as we like. * * The only thing we cannot be sure of is the resultPtr. This can be * NULLed if the originating thread went away while the event is handled * here now. */ ForwardingEvent *evPtr = (ForwardingEvent *) evGPtr; ForwardingResult *resultPtr = evPtr->resultPtr; ReflectedChannel *rcPtr = evPtr->rcPtr; Tcl_Interp *interp = rcPtr->interp; ForwardParam *paramPtr = evPtr->param; Tcl_Obj *resObj = NULL; /* Interp result of InvokeTclMethod */ ReflectedChannelMap *rcmPtr;/* Map of reflected channels with handlers in * this interp. */ Tcl_HashEntry *hPtr; /* Entry in the above map */ /* * Ignore the event if no one is waiting for its result anymore. */ if (!resultPtr) { return 1; } paramPtr->base.code = TCL_OK; paramPtr->base.msgStr = NULL; paramPtr->base.mustFree = 0; switch (evPtr->op) { /* * The destination thread for the following operations is * rcPtr->thread, which contains rcPtr->interp, the interp we have to * call upon for the driver. */ case ForwardedClose: { /* * No parameters/results. */ if (InvokeTclMethod(rcPtr, METH_FINAL, NULL, NULL, &resObj)!=TCL_OK) { ForwardSetObjError(paramPtr, resObj); } /* * Freeing is done here, in the origin thread, callback command * objects belong to this thread. Deallocating them in a different * thread is not allowed * * We remove the channel from both interpreter and thread maps before * releasing the memory, to prevent future accesses (like by * 'postevent') from finding and dereferencing a dangling pointer. */ rcmPtr = GetReflectedChannelMap(interp); hPtr = Tcl_FindHashEntry(&rcmPtr->map, Tcl_GetChannelName(rcPtr->chan)); Tcl_DeleteHashEntry(hPtr); rcmPtr = GetThreadReflectedChannelMap(); hPtr = Tcl_FindHashEntry(&rcmPtr->map, Tcl_GetChannelName(rcPtr->chan)); Tcl_DeleteHashEntry(hPtr); MarkDead(rcPtr); break; } case ForwardedInput: { Tcl_Obj *toReadObj; TclNewIntObj(toReadObj, paramPtr->input.toRead); Tcl_IncrRefCount(toReadObj); Tcl_Preserve(rcPtr); if (InvokeTclMethod(rcPtr, METH_READ, toReadObj, NULL, &resObj)!=TCL_OK){ int code = ErrnoReturn(rcPtr, resObj); if (code < 0) { paramPtr->base.code = code; } else { ForwardSetObjError(paramPtr, resObj); } paramPtr->input.toRead = TCL_IO_FAILURE; } else { /* * Process a regular result. */ Tcl_Size bytec = 0; /* Number of returned bytes */ unsigned char *bytev; /* Array of returned bytes */ bytev = Tcl_GetBytesFromObj(NULL, resObj, &bytec); if (bytev == NULL) { ForwardSetStaticError(paramPtr, msg_read_nonbyte); paramPtr->input.toRead = -1; } else if (paramPtr->input.toRead < bytec) { ForwardSetStaticError(paramPtr, msg_read_toomuch); paramPtr->input.toRead = TCL_IO_FAILURE; } else { if (bytec > 0) { memcpy(paramPtr->input.buf, bytev, bytec); } paramPtr->input.toRead = bytec; } } Tcl_Release(rcPtr); Tcl_DecrRefCount(toReadObj); break; } case ForwardedOutput: { Tcl_Obj *bufObj = Tcl_NewByteArrayObj((unsigned char *) paramPtr->output.buf, paramPtr->output.toWrite); Tcl_IncrRefCount(bufObj); Tcl_Preserve(rcPtr); if (InvokeTclMethod(rcPtr, METH_WRITE, bufObj, NULL, &resObj) != TCL_OK) { int code = ErrnoReturn(rcPtr, resObj); if (code < 0) { paramPtr->base.code = code; } else { ForwardSetObjError(paramPtr, resObj); } paramPtr->output.toWrite = -1; } else { /* * Process a regular result. */ int written; if (Tcl_GetIntFromObj(interp, resObj, &written) != TCL_OK) { Tcl_DecrRefCount(resObj); resObj = MarshallError(interp); ForwardSetObjError(paramPtr, resObj); paramPtr->output.toWrite = -1; } else if (written==0 || paramPtr->output.toWriteoutput.toWrite = -1; } else { paramPtr->output.toWrite = written; } } Tcl_Release(rcPtr); Tcl_DecrRefCount(bufObj); break; } case ForwardedSeek: { Tcl_Obj *offObj; Tcl_Obj *baseObj; TclNewIntObj(offObj, paramPtr->seek.offset); baseObj = Tcl_NewStringObj( (paramPtr->seek.seekMode==SEEK_SET) ? "start" : (paramPtr->seek.seekMode==SEEK_CUR) ? "current" : "end", -1); Tcl_IncrRefCount(offObj); Tcl_IncrRefCount(baseObj); Tcl_Preserve(rcPtr); if (InvokeTclMethod(rcPtr, METH_SEEK, offObj, baseObj, &resObj)!=TCL_OK){ ForwardSetObjError(paramPtr, resObj); paramPtr->seek.offset = -1; } else { /* * Process a regular result. If the type is wrong this may change * into an error. */ Tcl_WideInt newLoc; if (TclGetWideIntFromObj(interp, resObj, &newLoc) == TCL_OK) { if (newLoc < 0) { ForwardSetStaticError(paramPtr, msg_seek_beforestart); paramPtr->seek.offset = -1; } else { paramPtr->seek.offset = newLoc; } } else { Tcl_DecrRefCount(resObj); resObj = MarshallError(interp); ForwardSetObjError(paramPtr, resObj); paramPtr->seek.offset = -1; } } Tcl_Release(rcPtr); Tcl_DecrRefCount(offObj); Tcl_DecrRefCount(baseObj); break; } case ForwardedWatch: { Tcl_Obj *maskObj = DecodeEventMask(paramPtr->watch.mask); /* assert maskObj.refCount == 1 */ Tcl_Preserve(rcPtr); rcPtr->interest = paramPtr->watch.mask; (void) InvokeTclMethod(rcPtr, METH_WATCH, maskObj, NULL, NULL); Tcl_DecrRefCount(maskObj); Tcl_Release(rcPtr); break; } case ForwardedBlock: { Tcl_Obj *blockObj = Tcl_NewBooleanObj(!paramPtr->block.nonblocking); Tcl_IncrRefCount(blockObj); Tcl_Preserve(rcPtr); if (InvokeTclMethod(rcPtr, METH_BLOCKING, blockObj, NULL, &resObj) != TCL_OK) { ForwardSetObjError(paramPtr, resObj); } Tcl_Release(rcPtr); Tcl_DecrRefCount(blockObj); break; } case ForwardedSetOpt: { Tcl_Obj *optionObj = Tcl_NewStringObj(paramPtr->setOpt.name, -1); Tcl_Obj *valueObj = Tcl_NewStringObj(paramPtr->setOpt.value, -1); Tcl_IncrRefCount(optionObj); Tcl_IncrRefCount(valueObj); Tcl_Preserve(rcPtr); if (InvokeTclMethod(rcPtr, METH_CONFIGURE, optionObj, valueObj, &resObj) != TCL_OK) { ForwardSetObjError(paramPtr, resObj); } Tcl_Release(rcPtr); Tcl_DecrRefCount(optionObj); Tcl_DecrRefCount(valueObj); break; } case ForwardedGetOpt: { /* * Retrieve the value of one option. */ Tcl_Obj *optionObj = Tcl_NewStringObj(paramPtr->getOpt.name, -1); Tcl_IncrRefCount(optionObj); Tcl_Preserve(rcPtr); if (InvokeTclMethod(rcPtr, METH_CGET, optionObj, NULL, &resObj)!=TCL_OK){ ForwardSetObjError(paramPtr, resObj); } else { TclDStringAppendObj(paramPtr->getOpt.value, resObj); } Tcl_Release(rcPtr); Tcl_DecrRefCount(optionObj); break; } case ForwardedGetOptAll: /* * Retrieve all options. */ Tcl_Preserve(rcPtr); if (InvokeTclMethod(rcPtr, METH_CGETALL, NULL, NULL, &resObj) != TCL_OK){ ForwardSetObjError(paramPtr, resObj); } else { /* * Extract list, validate that it is a list, and #elements. See * NOTE (4) as well. */ Tcl_Size listc; Tcl_Obj **listv; if (TclListObjGetElements(interp, resObj, &listc, &listv) != TCL_OK) { Tcl_DecrRefCount(resObj); resObj = MarshallError(interp); ForwardSetObjError(paramPtr, resObj); } else if ((listc % 2) == 1) { /* * Odd number of elements is wrong. [x]. */ char *buf = (char *)Tcl_Alloc(200); snprintf(buf, 200, "{Expected list with even number of elements, got %" TCL_SIZE_MODIFIER "d %s instead}", listc, (listc == 1 ? "element" : "elements")); ForwardSetDynamicError(paramPtr, buf); } else { Tcl_Size len; const char *str = TclGetStringFromObj(resObj, &len); if (len) { TclDStringAppendLiteral(paramPtr->getOpt.value, " "); Tcl_DStringAppend(paramPtr->getOpt.value, str, len); } } } Tcl_Release(rcPtr); break; case ForwardedTruncate: { Tcl_Obj *lenObj = Tcl_NewWideIntObj(paramPtr->truncate.length); Tcl_IncrRefCount(lenObj); Tcl_Preserve(rcPtr); if (InvokeTclMethod(rcPtr,METH_TRUNCATE,lenObj,NULL,&resObj)!=TCL_OK) { ForwardSetObjError(paramPtr, resObj); } Tcl_Release(rcPtr); Tcl_DecrRefCount(lenObj); break; } default: /* * Bad operation code. */ Tcl_Panic("Bad operation code in ForwardProc"); break; } /* * Remove the reference we held on the result of the invoke, if we had * such. */ if (resObj != NULL) { Tcl_DecrRefCount(resObj); } if (resultPtr) { /* * Report the forwarding result synchronously to the waiting caller. * This unblocks (*) as well. This is wrapped into a conditional * because the caller may have exited in the mean time. */ Tcl_MutexLock(&rcForwardMutex); resultPtr->result = TCL_OK; Tcl_ConditionNotify(&resultPtr->done); Tcl_MutexUnlock(&rcForwardMutex); } return 1; } static void SrcExitProc( void *clientData) { ForwardingEvent *evPtr = (ForwardingEvent *)clientData; ForwardingResult *resultPtr; ForwardParam *paramPtr; /* * NOTE (2): Can this handler be called with the originator blocked? */ /* * The originator for the event exited. It is not sure if this can happen, * as the originator should be blocked at (*) while the event is in * transit/pending. * * We make sure that the event cannot refer to the result anymore, remove * it from the list of pending results and free the structure. Locking the * access ensures that we cannot get in conflict with "ForwardProc", * should it already execute the event. */ Tcl_MutexLock(&rcForwardMutex); resultPtr = evPtr->resultPtr; paramPtr = evPtr->param; evPtr->resultPtr = NULL; resultPtr->evPtr = NULL; resultPtr->result = TCL_ERROR; ForwardSetStaticError(paramPtr, msg_send_originlost); /* * See below: TclSpliceOut(resultPtr, forwardList); */ Tcl_MutexUnlock(&rcForwardMutex); /* * This unlocks (*). The structure will be spliced out and freed by * "ForwardProc". Maybe. */ Tcl_ConditionNotify(&resultPtr->done); } static void ForwardSetObjError( ForwardParam *paramPtr, Tcl_Obj *obj) { Tcl_Size len; const char *msgStr = TclGetStringFromObj(obj, &len); len++; ForwardSetDynamicError(paramPtr, Tcl_Alloc(len)); memcpy(paramPtr->base.msgStr, msgStr, len); } #endif /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * indent-tabs-mode: nil * End: */ tcl9.0.1/generic/tclIORTrans.c0000644000175000017500000025525614726623136015521 0ustar sergeisergei/* * tclIORTrans.c -- * * This file contains the implementation of Tcl's generic transformation * reflection code, which allows the implementation of Tcl channel * transformations in Tcl code. * * Parts of this file are based on code contributed by Jean-Claude * Wippler. * * See TIP #230 for the specification of this functionality. * * Copyright © 2007-2008 ActiveState. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclIO.h" #include #ifndef EINVAL #define EINVAL 9 #endif #ifndef EOK #define EOK 0 #endif /* * Signatures of all functions used in the C layer of the reflection. */ static int ReflectClose(void *clientData, Tcl_Interp *interp, int flags); static int ReflectInput(void *clientData, char *buf, int toRead, int *errorCodePtr); static int ReflectOutput(void *clientData, const char *buf, int toWrite, int *errorCodePtr); static void ReflectWatch(void *clientData, int mask); static int ReflectBlock(void *clientData, int mode); static long long ReflectSeekWide(void *clientData, long long offset, int mode, int *errorCodePtr); static int ReflectGetOption(void *clientData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr); static int ReflectSetOption(void *clientData, Tcl_Interp *interp, const char *optionName, const char *newValue); static int ReflectHandle(void *clientData, int direction, void **handle); static int ReflectNotify(void *clientData, int mask); /* * The C layer channel type/driver definition used by the reflection. */ static const Tcl_ChannelType reflectedTransformType = { "tclrtransform", TCL_CHANNEL_VERSION_5, NULL, /* Deprecated. */ ReflectInput, ReflectOutput, NULL, /* Deprecated. */ ReflectSetOption, ReflectGetOption, ReflectWatch, ReflectHandle, ReflectClose, ReflectBlock, NULL, /* Flush channel. Not used by core. */ ReflectNotify, ReflectSeekWide, NULL, /* Thread action proc. */ NULL /* Truncate proc. */ }; /* * Structure of the buffer to hold transform results to be consumed by higher * layers upon reading from the channel, plus the functions to manage such. */ typedef struct { unsigned char *buf; /* Reference to the buffer area. */ size_t allocated; /* Allocated size of the buffer area. */ size_t used; /* Number of bytes in the buffer, * <= allocated. */ } ResultBuffer; #define ResultLength(r) ((r)->used) /* static int ResultLength(ResultBuffer *r); */ static inline void ResultClear(ResultBuffer *r); static inline void ResultInit(ResultBuffer *r); static inline void ResultAdd(ResultBuffer *r, unsigned char *buf, size_t toWrite); static inline size_t ResultCopy(ResultBuffer *r, unsigned char *buf, size_t toRead); #define RB_INCREMENT (512) /* * Convenience macro to make some casts easier to use. */ #define UCHARP(x) ((unsigned char *) (x)) /* * Instance data for a reflected transformation. =========================== */ typedef struct { Tcl_Channel chan; /* Back reference to the channel of the * transformation itself. */ Tcl_Channel parent; /* Reference to the channel the transformation * was pushed on. */ Tcl_Interp *interp; /* Reference to the interpreter containing the * Tcl level part of the channel. */ Tcl_Obj *handle; /* Reference to transform handle. Also stored * in the argv, see below. The separate field * gives us direct access, needed when working * with the reflection maps. */ #if TCL_THREADS Tcl_ThreadId thread; /* Thread the 'interp' belongs to. */ #endif Tcl_TimerToken timer; /* See [==] as well. * Storage for the command prefix and the additional words required for * the invocation of methods in the command handler. * * argv [0] ... [.] | [argc-2] [argc-1] | [argc] [argc+2] * cmd ... pfx | method chan | detail1 detail2 * ~~~~ CT ~~~ ~~ CT ~~ * * CT = Belongs to the 'Command handler Thread'. */ int argc; /* Number of preallocated words - 2. */ Tcl_Obj **argv; /* Preallocated array for calling the handler. * args[0] is placeholder for cmd word. * Followed by the arguments in the prefix, * plus 4 placeholders for method, channel, * and at most two varying (method specific) * words. */ int methods; /* Bitmask of supported methods. */ /* * NOTE (9): Should we have predefined shared literals for the method * names? */ int mode; /* Mask of R/W mode */ int nonblocking; /* Flag: Channel is blocking or not. */ int readIsDrained; /* Flag: Read buffers are flushed. */ int eofPending; /* Flag: EOF seen down, but not raised up */ int dead; /* Boolean signal that some operations * should no longer be attempted. */ ResultBuffer result; } ReflectedTransform; /* * Structure of the table mapping from transform handles to reflected * transform (channels). Each interpreter which has the handler command for * one or more reflected transforms records them in such a table, so that we * are able to find them during interpreter/thread cleanup even if the actual * channel they belong to was moved to a different interpreter and/or thread. * * The table is reachable via the standard interpreter AssocData, the key is * defined below. */ typedef struct { Tcl_HashTable map; } ReflectedTransformMap; #define RTMKEY "ReflectedTransformMap" /* * Method literals. ================================================== */ static const char *const methodNames[] = { "clear", /* OPT */ "drain", /* OPT, drain => read */ "finalize", /* */ "flush", /* OPT, flush => write */ "initialize", /* */ "limit?", /* OPT */ "read", /* OPT */ "write", /* OPT */ NULL }; typedef enum { METH_CLEAR, METH_DRAIN, METH_FINAL, METH_FLUSH, METH_INIT, METH_LIMIT, METH_READ, METH_WRITE } MethodName; #define FLAG(m) (1 << (m)) #define REQUIRED_METHODS \ (FLAG(METH_INIT) | FLAG(METH_FINAL)) #define RANDW \ (TCL_READABLE | TCL_WRITABLE) #define IMPLIES(a,b) ((!(a)) || (b)) #define NEGIMPL(a,b) #define HAS(x,f) ((x) & FLAG(f)) #if TCL_THREADS /* * Thread specific types and structures. * * We are here essentially creating a very specific implementation of 'thread * send'. */ /* * Enumeration of all operations which can be forwarded. */ typedef enum { ForwardedClear, ForwardedClose, ForwardedDrain, ForwardedFlush, ForwardedInput, ForwardedLimit, ForwardedOutput } ForwardedOperation; /* * Event used to forward driver invocations to the thread actually managing * the channel. We cannot construct the command to execute and forward that. * Because then it will contain a mixture of Tcl_Obj's belonging to both the * command handler thread (CT), and the thread managing the channel (MT), * executed in CT. Tcl_Obj's are not allowed to cross thread boundaries. So we * forward an operation code, the argument details, and reference to results. * The command is assembled in the CT and belongs fully to that thread. No * sharing problems. */ typedef struct { int code; /* O: Ok/Fail of the cmd handler */ char *msgStr; /* O: Error message for handler failure */ int mustFree; /* O: True if msgStr is allocated, false if * otherwise (static). */ } ForwardParamBase; /* * Operation specific parameter/result structures. (These are "subtypes" of * ForwardParamBase. Where an operation does not need any special types, it * has no "subtype" and just uses ForwardParamBase, as listed above.) */ struct ForwardParamTransform { ForwardParamBase base; /* "Supertype". MUST COME FIRST. */ char *buf; /* I: Bytes to transform, * O: Bytes in transform result */ Tcl_Size size; /* I: #bytes to transform, * O: #bytes in the transform result */ }; struct ForwardParamLimit { ForwardParamBase base; /* "Supertype". MUST COME FIRST. */ int max; /* O: Character read limit */ }; /* * Now join all these together in a single union for convenience. */ typedef union ForwardParam { ForwardParamBase base; struct ForwardParamTransform transform; struct ForwardParamLimit limit; } ForwardParam; /* * Forward declaration. */ typedef struct ForwardingResult ForwardingResult; /* * General event structure, with reference to operation specific data. */ typedef struct { Tcl_Event event; /* Basic event data, has to be first item */ ForwardingResult *resultPtr; ForwardedOperation op; /* Forwarded driver operation */ ReflectedTransform *rtPtr; /* Channel instance */ ForwardParam *param; /* Packaged arguments and return values, a * ForwardParam pointer. */ } ForwardingEvent; /* * Structure to manage the result of the forwarding. This is not the result of * the operation itself, but about the success of the forward event itself. * The event can be successful, even if the operation which was forwarded * failed. It is also there to manage the synchronization between the involved * threads. */ struct ForwardingResult { Tcl_ThreadId src; /* Originating thread. */ Tcl_ThreadId dst; /* Thread the op was forwarded to. */ Tcl_Interp *dsti; /* Interpreter in the thread the op was * forwarded to. */ Tcl_Condition done; /* Condition variable the forwarder blocks * on. */ int result; /* TCL_OK or TCL_ERROR */ ForwardingEvent *evPtr; /* Event the result belongs to. */ ForwardingResult *prevPtr, *nextPtr; /* Links into the list of pending forwarded * results. */ }; typedef struct { /* * Table of all reflected transformations owned by this thread. */ ReflectedTransformMap *rtmPtr; } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * List of forwarded operations which have not completed yet, plus the mutex * to protect the access to this process global list. */ static ForwardingResult *forwardList = NULL; TCL_DECLARE_MUTEX(rtForwardMutex) /* * Function containing the generic code executing a forward, and wrapper * macros for the actual operations we wish to forward. Uses ForwardProc as * the event function executed by the thread receiving a forwarding event * (which executes the appropriate function and collects the result, if any). * * The two ExitProcs are handlers so that things do not deadlock when either * thread involved in the forwarding exits. They also clean things up so that * we don't leak resources when threads go away. */ static void ForwardOpToOwnerThread(ReflectedTransform *rtPtr, ForwardedOperation op, const void *param); static int ForwardProc(Tcl_Event *evPtr, int mask); static void SrcExitProc(void *clientData); #define FreeReceivedError(p) \ do { \ if ((p)->base.mustFree) { \ Tcl_Free((p)->base.msgStr); \ } \ } while (0) #define PassReceivedErrorInterp(i,p) \ do { \ if ((i) != NULL) { \ Tcl_SetChannelErrorInterp((i), \ Tcl_NewStringObj((p)->base.msgStr, -1)); \ } \ FreeReceivedError(p); \ } while (0) #define PassReceivedError(c,p) \ do { \ Tcl_SetChannelError((c), \ Tcl_NewStringObj((p)->base.msgStr, -1)); \ FreeReceivedError(p); \ } while (0) #define ForwardSetStaticError(p,emsg) \ do { \ (p)->base.code = TCL_ERROR; \ (p)->base.mustFree = 0; \ (p)->base.msgStr = (char *) (emsg); \ } while (0) #define ForwardSetDynamicError(p,emsg) \ do { \ (p)->base.code = TCL_ERROR; \ (p)->base.mustFree = 1; \ (p)->base.msgStr = (char *) (emsg); \ } while (0) static void ForwardSetObjError(ForwardParam *p, Tcl_Obj *objPtr); static ReflectedTransformMap * GetThreadReflectedTransformMap(void); static void DeleteThreadReflectedTransformMap( void *clientData); #endif /* TCL_THREADS */ #define SetChannelErrorStr(c,msgStr) \ Tcl_SetChannelError((c), Tcl_NewStringObj((msgStr), -1)) static Tcl_Obj * MarshallError(Tcl_Interp *interp); static void UnmarshallErrorResult(Tcl_Interp *interp, Tcl_Obj *msgObj); /* * Static functions for this file: */ static Tcl_Obj * DecodeEventMask(int mask); static ReflectedTransform * NewReflectedTransform(Tcl_Interp *interp, Tcl_Obj *cmdpfxObj, int mode, Tcl_Obj *handleObj, Tcl_Channel parentChan); static Tcl_Obj * NextHandle(void); static Tcl_FreeProc FreeReflectedTransform; static void FreeReflectedTransformArgs(ReflectedTransform *rtPtr); static int InvokeTclMethod(ReflectedTransform *rtPtr, const char *method, Tcl_Obj *argOneObj, Tcl_Obj *argTwoObj, Tcl_Obj **resultObjPtr); static ReflectedTransformMap * GetReflectedTransformMap(Tcl_Interp *interp); static void DeleteReflectedTransformMap(void *clientData, Tcl_Interp *interp); /* * Global constant strings (messages). ================== * These string are used directly as bypass errors, thus they have to be valid * Tcl lists where the last element is the message itself. Hence the * list-quoting to keep the words of the message together. See also [x]. */ static const char *msg_read_unsup = "{read not supported by Tcl driver}"; static const char *msg_write_unsup = "{write not supported by Tcl driver}"; #if TCL_THREADS static const char *msg_send_originlost = "{Channel thread lost}"; static const char *msg_send_dstlost = "{Owner lost}"; #endif /* TCL_THREADS */ static const char *msg_dstlost = "-code 1 -level 0 -errorcode NONE -errorinfo {} -errorline 1 {Owner lost}"; /* * Timer management (flushing out buffered data via artificial events). */ /* * Helper functions encapsulating some of the thread forwarding to make the * control flow in callers easier. */ static void TimerKill(ReflectedTransform *rtPtr); static void TimerSetup(ReflectedTransform *rtPtr); static void TimerRun(void *clientData); static int TransformRead(ReflectedTransform *rtPtr, int *errorCodePtr, Tcl_Obj *bufObj); static int TransformWrite(ReflectedTransform *rtPtr, int *errorCodePtr, unsigned char *buf, int toWrite); static int TransformDrain(ReflectedTransform *rtPtr, int *errorCodePtr); static int TransformFlush(ReflectedTransform *rtPtr, int *errorCodePtr, int op); static void TransformClear(ReflectedTransform *rtPtr); static int TransformLimit(ReflectedTransform *rtPtr, int *errorCodePtr, int *maxPtr); /* * Operation codes for TransformFlush(). */ #define FLUSH_WRITE 1 #define FLUSH_DISCARD 0 /* * Main methods to plug into the 'chan' ensemble'. ================== */ /* *---------------------------------------------------------------------- * * TclChanPushObjCmd -- * * This function is invoked to process the "chan push" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. The handle of the new channel is placed in the * interp result. * * Side effects: * Creates a new channel. * *---------------------------------------------------------------------- */ int TclChanPushObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { ReflectedTransform *rtPtr; /* Instance data of the new (transform) * channel. */ Tcl_Obj *chanObj; /* Handle of parent channel */ Tcl_Channel parentChan; /* Token of parent channel */ int mode; /* R/W mode of parent, later the new channel. * Has to match the abilities of the handler * commands */ Tcl_Obj *cmdObj; /* Command prefix, list of words */ Tcl_Obj *cmdNameObj; /* Command name */ Tcl_Obj *rtId; /* Handle of the new transform (channel) */ Tcl_Obj *modeObj; /* mode in obj form for method call */ Tcl_Size listc; /* Result of 'initialize', and of */ Tcl_Obj **listv; /* its sublist in the 2nd element */ int methIndex; /* Encoded method name */ int result; /* Result code for 'initialize' */ Tcl_Obj *resObj; /* Result data for 'initialize' */ int methods; /* Bitmask for supported methods. */ ReflectedTransformMap *rtmPtr; /* Map of reflected transforms with handlers * in this interp. */ Tcl_HashEntry *hPtr; /* Entry in the above map */ int isNew; /* Placeholder. */ /* * Syntax: chan push CHANNEL CMDPREFIX * [0] [1] [2] [3] * * Actually: rPush CHANNEL CMDPREFIX * [0] [1] [2] */ enum ArgIndices { CHAN = 1, CMD = 2 }; /* * Number of arguments... */ if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "channel cmdprefix"); return TCL_ERROR; } /* * First argument is a channel handle. */ chanObj = objv[CHAN]; parentChan = Tcl_GetChannel(interp, TclGetString(chanObj), &mode); if (parentChan == NULL) { return TCL_ERROR; } parentChan = Tcl_GetTopChannel(parentChan); /* * Second argument is command prefix, i.e. list of words, first word is * name of handler command, other words are fixed arguments. Run the * 'initialize' method to get the list of supported methods. Validate * this. */ cmdObj = objv[CMD]; /* * Basic check that the command prefix truly is a list. */ if (Tcl_ListObjIndex(interp, cmdObj, 0, &cmdNameObj) != TCL_OK) { return TCL_ERROR; } /* * Now create the transformation (channel). */ rtId = NextHandle(); rtPtr = NewReflectedTransform(interp, cmdObj, mode, rtId, parentChan); /* * Invoke 'initialize' and validate that the handler is present and ok. * Squash the transformation if not. */ modeObj = DecodeEventMask(mode); /* assert modeObj.refCount == 1 */ result = InvokeTclMethod(rtPtr, "initialize", modeObj, NULL, &resObj); Tcl_DecrRefCount(modeObj); if (result != TCL_OK) { UnmarshallErrorResult(interp, resObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ goto error; } /* * Verify the result. * - List, of method names. Convert to mask. Check for non-optionals * through the mask. Compare open mode against optional r/w. */ if (TclListObjGetElements(NULL, resObj, &listc, &listv) != TCL_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "chan handler \"%s initialize\" returned non-list: %s", TclGetString(cmdObj), TclGetString(resObj))); Tcl_DecrRefCount(resObj); goto error; } methods = 0; while (listc > 0) { if (Tcl_GetIndexFromObj(interp, listv[listc-1], methodNames, "method", TCL_EXACT, &methIndex) != TCL_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "chan handler \"%s initialize\" returned %s", TclGetString(cmdObj), Tcl_GetStringResult(interp))); Tcl_DecrRefCount(resObj); goto error; } methods |= FLAG(methIndex); listc--; } Tcl_DecrRefCount(resObj); if ((REQUIRED_METHODS & methods) != REQUIRED_METHODS) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "chan handler \"%s\" does not support all required methods", TclGetString(cmdObj))); goto error; } /* * Mode tell us what the parent channel supports. The methods tell us what * the handler supports. We remove the non-supported bits from the mode * and check that the channel is not completely inaccessible. Afterward the * mode tells us which methods are still required, and these methods will * also be supported by the handler, by design of the check. */ if (!HAS(methods, METH_READ)) { mode &= ~TCL_READABLE; } if (!HAS(methods, METH_WRITE)) { mode &= ~TCL_WRITABLE; } if (!mode) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "chan handler \"%s\" makes the channel inaccessible", TclGetString(cmdObj))); goto error; } /* * The mode and support for it is ok, now check the internal constraints. */ if (!IMPLIES(HAS(methods, METH_DRAIN), HAS(methods, METH_READ))) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "chan handler \"%s\" supports \"drain\" but not \"read\"", TclGetString(cmdObj))); goto error; } if (!IMPLIES(HAS(methods, METH_FLUSH), HAS(methods, METH_WRITE))) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "chan handler \"%s\" supports \"flush\" but not \"write\"", TclGetString(cmdObj))); goto error; } Tcl_ResetResult(interp); /* * Everything is fine now. */ rtPtr->methods = methods; rtPtr->mode = mode; rtPtr->chan = Tcl_StackChannel(interp, &reflectedTransformType, rtPtr, mode, rtPtr->parent); /* * Register the transform in our map for proper handling of deleted * interpreters and/or threads. */ rtmPtr = GetReflectedTransformMap(interp); hPtr = Tcl_CreateHashEntry(&rtmPtr->map, TclGetString(rtId), &isNew); if (!isNew && rtPtr != Tcl_GetHashValue(hPtr)) { Tcl_Panic("TclChanPushObjCmd: duplicate transformation handle"); } Tcl_SetHashValue(hPtr, rtPtr); #if TCL_THREADS rtmPtr = GetThreadReflectedTransformMap(); hPtr = Tcl_CreateHashEntry(&rtmPtr->map, TclGetString(rtId), &isNew); Tcl_SetHashValue(hPtr, rtPtr); #endif /* TCL_THREADS */ /* * Return the channel as the result of the command. */ Tcl_SetObjResult(interp, Tcl_NewStringObj( Tcl_GetChannelName(rtPtr->chan), -1)); return TCL_OK; error: /* * We are not going through ReflectClose as we never had a channel * structure. */ Tcl_EventuallyFree(rtPtr, FreeReflectedTransform); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TclChanPopObjCmd -- * * This function is invoked to process the "chan pop" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Posts events to a reflected channel, invokes event handlers. The * latter implies that arbitrary side effects are possible. * *---------------------------------------------------------------------- */ int TclChanPopObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { /* * Syntax: chan pop CHANNEL * [0] [1] [2] * * Actually: rPop CHANNEL * [0] [1] */ enum ArgIndices { CHAN = 1 }; const char *chanId; /* Tcl level channel handle */ Tcl_Channel chan; /* Channel associated to the handle */ int mode; /* Channel r/w mode */ /* * Number of arguments... */ if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "channel"); return TCL_ERROR; } /* * First argument is a channel, which may have a (reflected) * transformation. */ chanId = TclGetString(objv[CHAN]); chan = Tcl_GetChannel(interp, chanId, &mode); if (chan == NULL) { return TCL_ERROR; } /* * Removing transformations is generic, and not restricted to reflected * transformations. */ Tcl_UnstackChannel(interp, chan); return TCL_OK; } /* * Channel error message marshalling utilities. */ static Tcl_Obj * MarshallError( Tcl_Interp *interp) { /* * Capture the result status of the interpreter into a string. => List of * options and values, followed by the error message. The result has * refCount 0. */ Tcl_Obj *returnOpt = Tcl_GetReturnOptions(interp, TCL_ERROR); /* * => returnOpt.refCount == 0. We can append directly. */ Tcl_ListObjAppendElement(NULL, returnOpt, Tcl_GetObjResult(interp)); return returnOpt; } static void UnmarshallErrorResult( Tcl_Interp *interp, Tcl_Obj *msgObj) { Tcl_Size lc; Tcl_Obj **lv; int explicitResult; Tcl_Size numOptions; /* * Process the caught message. * * Syntax = (option value)... ?message? * * Bad syntax causes a panic. This is OK because the other side uses * Tcl_GetReturnOptions and list construction functions to marshall the * information; if we panic here, something has gone badly wrong already. */ if (TclListObjGetElements(interp, msgObj, &lc, &lv) != TCL_OK) { Tcl_Panic("TclChanCaughtErrorBypass: Bad syntax of caught result"); } if (interp == NULL) { return; } explicitResult = lc & 1; /* Odd number of values? */ numOptions = lc - explicitResult; if (explicitResult) { Tcl_SetObjResult(interp, lv[lc-1]); } Tcl_SetReturnOptions(interp, Tcl_NewListObj(numOptions, lv)); ((Interp *) interp)->flags &= ~ERR_ALREADY_LOGGED; } /* * Driver functions. ================================================ */ /* *---------------------------------------------------------------------- * * ReflectClose -- * * This function is invoked when the channel is closed, to delete the * driver specific instance data. * * Results: * A Posix error. * * Side effects: * Releases memory. Arbitrary, as it calls upon a script. * *---------------------------------------------------------------------- */ static int ReflectClose( void *clientData, Tcl_Interp *interp, int flags) { ReflectedTransform *rtPtr = (ReflectedTransform *)clientData; int errorCode, errorCodeSet = 0; int result = TCL_OK; /* Result code for 'close' */ Tcl_Obj *resObj; /* Result data for 'close' */ ReflectedTransformMap *rtmPtr; /* Map of reflected transforms with handlers * in this interp. */ Tcl_HashEntry *hPtr; /* Entry in the above map */ if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) { return EINVAL; } if (TclInThreadExit()) { /* * This call comes from TclFinalizeIOSystem. There are no * interpreters, and therefore we cannot call upon the handler command * anymore. Threading is irrelevant as well. We simply clean up all * our C level data structures and leave the Tcl level to the other * finalization functions. */ /* * THREADED => Forward this to the origin thread * * Note: DeleteThreadReflectedTransformMap() is the thread exit handler * for the origin thread. Use this to clean up the structure? Except * if lost? */ #if TCL_THREADS if (rtPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; ForwardOpToOwnerThread(rtPtr, ForwardedClose, &p); result = p.base.code; if (result != TCL_OK) { FreeReceivedError(&p); } } #endif /* TCL_THREADS */ Tcl_EventuallyFree(rtPtr, FreeReflectedTransform); return EOK; } /* * In the reflected channel implementation a cleaned method mask here * implies that the channel creation was aborted, and "finalize" must not * be called. for transformations however we are not going through here on * such an abort, but directly through FreeReflectedTransform. So for us * that check is not necessary. We always go through 'finalize'. */ if (HAS(rtPtr->methods, METH_DRAIN) && !rtPtr->readIsDrained) { if (!TransformDrain(rtPtr, &errorCode)) { #if TCL_THREADS if (rtPtr->thread != Tcl_GetCurrentThread()) { Tcl_EventuallyFree(rtPtr, FreeReflectedTransform); return errorCode; } #endif /* TCL_THREADS */ errorCodeSet = 1; goto cleanup; } } if (HAS(rtPtr->methods, METH_FLUSH)) { if (!TransformFlush(rtPtr, &errorCode, FLUSH_WRITE)) { #if TCL_THREADS if (rtPtr->thread != Tcl_GetCurrentThread()) { Tcl_EventuallyFree(rtPtr, FreeReflectedTransform); return errorCode; } #endif /* TCL_THREADS */ errorCodeSet = 1; goto cleanup; } } /* * Are we in the correct thread? */ #if TCL_THREADS if (rtPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; ForwardOpToOwnerThread(rtPtr, ForwardedClose, &p); result = p.base.code; Tcl_EventuallyFree(rtPtr, FreeReflectedTransform); if (result != TCL_OK) { PassReceivedErrorInterp(interp, &p); return EINVAL; } return EOK; } #endif /* TCL_THREADS */ /* * Do the actual invocation of "finalize" now; we're in the right thread. */ result = InvokeTclMethod(rtPtr, "finalize", NULL, NULL, &resObj); if ((result != TCL_OK) && (interp != NULL)) { Tcl_SetChannelErrorInterp(interp, resObj); } Tcl_DecrRefCount(resObj); /* Remove reference we held from the * invoke. */ cleanup: /* * Remove the transform from the map before releasing the memory, to * prevent future accesses from finding and dereferencing a dangling * pointer. * * NOTE: The transform may not be in the map. This is ok, that happens * when the transform was created in a different interpreter and/or thread * and then was moved here. * * NOTE: The channel may have been removed from the map already via * the per-interp DeleteReflectedTransformMap exit-handler. */ if (!rtPtr->dead) { rtmPtr = GetReflectedTransformMap(rtPtr->interp); hPtr = Tcl_FindHashEntry(&rtmPtr->map, TclGetString(rtPtr->handle)); if (hPtr) { Tcl_DeleteHashEntry(hPtr); } /* * In a threaded interpreter we manage a per-thread map as well, * to allow us to survive if the script level pulls the rug out * under a channel by deleting the owning thread. */ #if TCL_THREADS rtmPtr = GetThreadReflectedTransformMap(); hPtr = Tcl_FindHashEntry(&rtmPtr->map, TclGetString(rtPtr->handle)); if (hPtr) { Tcl_DeleteHashEntry(hPtr); } #endif /* TCL_THREADS */ } Tcl_EventuallyFree(rtPtr, FreeReflectedTransform); return errorCodeSet ? errorCode : ((result == TCL_OK) ? EOK : EINVAL); } /* *---------------------------------------------------------------------- * * ReflectInput -- * * This function is invoked when more data is requested from the channel. * * Results: * The number of bytes read. * * Side effects: * Allocates memory. Arbitrary, as it calls upon a script. * *---------------------------------------------------------------------- */ static int ReflectInput( void *clientData, char *buf, int toRead, int *errorCodePtr) { ReflectedTransform *rtPtr = (ReflectedTransform *)clientData; int gotBytes, copied, readBytes; Tcl_Obj *bufObj; /* * The following check can be done before thread redirection, because we * are reading from an item which is readonly, i.e. will never change * during the lifetime of the channel. */ if (!(rtPtr->methods & FLAG(METH_READ))) { SetChannelErrorStr(rtPtr->chan, msg_read_unsup); *errorCodePtr = EINVAL; return -1; } Tcl_Preserve(rtPtr); /* TODO: Consider a more appropriate buffer size. */ bufObj = Tcl_NewByteArrayObj(NULL, toRead); Tcl_IncrRefCount(bufObj); gotBytes = 0; if (rtPtr->eofPending) { goto stop; } rtPtr->readIsDrained = 0; while (toRead > 0) { /* * Loop until the request is satisfied (or no data available from * below, possibly EOF). */ copied = ResultCopy(&rtPtr->result, UCHARP(buf), toRead); toRead -= copied; buf += copied; gotBytes += copied; if (toRead == 0) { goto stop; } if (rtPtr->eofPending) { goto stop; } /* * The buffer is exhausted, but the caller wants even more. We now * have to go to the underlying channel, get more bytes and then * transform them for delivery. We may not get what we want (full EOF * or temporarily out of data). * * Length (rtPtr->result) == 0, toRead > 0 here. Use 'buf'! as target * to store the intermediary information read from the parent channel. * * Ask the transform how much data it allows us to read from the * underlying channel. This feature allows the transform to signal EOF * upstream although there is none downstream. Useful to control an * unbounded 'fcopy' for example, either through counting bytes, or by * pattern matching. */ if ((rtPtr->methods & FLAG(METH_LIMIT))) { int maxRead = -1; if (!TransformLimit(rtPtr, errorCodePtr, &maxRead)) { goto error; } if (maxRead == 0) { goto stop; } else if (maxRead > 0) { if (maxRead < toRead) { toRead = maxRead; } } /* else: 'maxRead < 0' == Accept the current value of toRead */ } if (toRead <= 0) { goto stop; } readBytes = Tcl_ReadRaw(rtPtr->parent, (char *) Tcl_SetByteArrayLength(bufObj, toRead), toRead); if (readBytes < 0) { if (Tcl_InputBlocked(rtPtr->parent) && (gotBytes > 0)) { /* * Down channel is blocked and offers zero additional bytes. * The nonzero gotBytes already returned makes the total * operation a valid short read. Return to caller. */ goto stop; } /* * Either the down channel is not blocked (a real error) * or it is and there are gotBytes==0 byte copied so far. * In either case, pass up the error, so we either report * any real error, or do not mistakenly signal EOF by * returning 0 to the caller. */ *errorCodePtr = Tcl_GetErrno(); goto error; } if (readBytes == 0) { /* * Zero returned from Tcl_ReadRaw() always indicates EOF * on the down channel. */ rtPtr->eofPending = 1; /* * Now this is a bit different. The partial data waiting is * converted and returned. */ if (HAS(rtPtr->methods, METH_DRAIN)) { if (!TransformDrain(rtPtr, errorCodePtr)) { goto error; } } if (ResultLength(&rtPtr->result) == 0) { /* * The drain delivered nothing. */ goto stop; } continue; /* at: while (toRead > 0) */ } /* readBytes == 0 */ /* * Transform the read chunk, which was not empty. Anything we got back * is a transformation result is put into our buffers, and the next * iteration will put it into the result. */ Tcl_SetByteArrayLength(bufObj, readBytes); if (!TransformRead(rtPtr, errorCodePtr, bufObj)) { goto error; } if (Tcl_IsShared(bufObj)) { Tcl_DecrRefCount(bufObj); TclNewObj(bufObj); Tcl_IncrRefCount(bufObj); } Tcl_SetByteArrayLength(bufObj, 0); } /* while toRead > 0 */ stop: if (gotBytes == 0) { rtPtr->eofPending = 0; } Tcl_DecrRefCount(bufObj); Tcl_Release(rtPtr); return gotBytes; error: gotBytes = -1; goto stop; } /* *---------------------------------------------------------------------- * * ReflectOutput -- * * This function is invoked when data is written to the channel. * * Results: * The number of bytes actually written. * * Side effects: * Allocates memory. Arbitrary, as it calls upon a script. * *---------------------------------------------------------------------- */ static int ReflectOutput( void *clientData, const char *buf, int toWrite, int *errorCodePtr) { ReflectedTransform *rtPtr = (ReflectedTransform *)clientData; /* * The following check can be done before thread redirection, because we * are reading from an item which is readonly, i.e. will never change * during the lifetime of the channel. */ if (!(rtPtr->methods & FLAG(METH_WRITE))) { SetChannelErrorStr(rtPtr->chan, msg_write_unsup); *errorCodePtr = EINVAL; return -1; } if (toWrite == 0) { /* * Nothing came in to write, ignore the call */ return 0; } /* * Discard partial data in the input buffers, i.e. on the read side. Like * we do when explicitly seeking as well. */ Tcl_Preserve(rtPtr); if ((rtPtr->methods & FLAG(METH_CLEAR))) { TransformClear(rtPtr); } /* * Hand the data to the transformation itself. Anything it deigned to * return to us is a (partial) transformation result and written to the * parent channel for further processing. */ if (!TransformWrite(rtPtr, errorCodePtr, UCHARP(buf), toWrite)) { Tcl_Release(rtPtr); return -1; } *errorCodePtr = EOK; Tcl_Release(rtPtr); return toWrite; } /* *---------------------------------------------------------------------- * * ReflectSeekWide / ReflectSeek -- * * This function is invoked when the user wishes to seek on the channel. * * Results: * The new location of the access point. * * Side effects: * Allocates memory. Arbitrary, per the parent channel, and the called * scripts. * *---------------------------------------------------------------------- */ static long long ReflectSeekWide( void *clientData, long long offset, int seekMode, int *errorCodePtr) { ReflectedTransform *rtPtr = (ReflectedTransform *)clientData; Channel *parent = (Channel *) rtPtr->parent; Tcl_WideInt curPos; /* Position on the device. */ /* * Check if we can leave out involving the Tcl level, i.e. transformation * handler. This is true for tell requests, and transformations which * support neither flush, nor drain. For these cases we can pass the * request down and the result back up unchanged. */ Tcl_Preserve(rtPtr); if (((seekMode != SEEK_CUR) || (offset != 0)) && (HAS(rtPtr->methods, METH_CLEAR) || HAS(rtPtr->methods, METH_FLUSH))) { /* * Neither a tell request, nor clear/flush both not supported. We have * to go through the Tcl level to clear and/or flush the * transformation. */ if (rtPtr->methods & FLAG(METH_CLEAR)) { TransformClear(rtPtr); } /* * When flushing the transform for seeking the generated results are * irrelevant. We cannot put them into the channel, this would move * the location, throwing it off with regard to where we are and are * seeking to. */ if (HAS(rtPtr->methods, METH_FLUSH)) { if (!TransformFlush(rtPtr, errorCodePtr, FLUSH_DISCARD)) { Tcl_Release(rtPtr); return -1; } } } /* * Now seek to the new position in the channel as requested by the * caller. Note that we prefer the wideSeekProc if that is available and * non-NULL... */ if (Tcl_ChannelWideSeekProc(parent->typePtr) == NULL) { *errorCodePtr = EINVAL; curPos = -1; } else { curPos = Tcl_ChannelWideSeekProc(parent->typePtr)(parent->instanceData, offset, seekMode, errorCodePtr); } if (curPos == -1) { Tcl_SetErrno(*errorCodePtr); } *errorCodePtr = EOK; Tcl_Release(rtPtr); return curPos; } /* *---------------------------------------------------------------------- * * ReflectWatch -- * * This function is invoked to tell the channel what events the I/O * system is interested in. * * Results: * None. * * Side effects: * Allocates memory. Arbitrary, as it calls upon a script. * *---------------------------------------------------------------------- */ static void ReflectWatch( void *clientData, int mask) { ReflectedTransform *rtPtr = (ReflectedTransform *)clientData; Tcl_DriverWatchProc *watchProc; watchProc = Tcl_ChannelWatchProc(Tcl_GetChannelType(rtPtr->parent)); watchProc(Tcl_GetChannelInstanceData(rtPtr->parent), mask); /* * Management of the internal timer. */ if (!(mask & TCL_READABLE) || (ResultLength(&rtPtr->result) == 0)) { /* * A pending timer may exist, but either is there no (more) interest * in the events it generates or nothing is available for reading. * Remove it, if existing. */ TimerKill(rtPtr); } else { /* * There might be no pending timer, but there is interest in readable * events and we actually have data waiting, so generate a timer to * flush that if it does not exist. */ TimerSetup(rtPtr); } } /* *---------------------------------------------------------------------- * * ReflectBlock -- * * This function is invoked to tell the channel which blocking behaviour * is required of it. * * Results: * A Posix error number. * * Side effects: * Allocates memory. Arbitrary, as it calls upon a script. * *---------------------------------------------------------------------- */ static int ReflectBlock( void *clientData, int nonblocking) { ReflectedTransform *rtPtr = (ReflectedTransform *)clientData; /* * Transformations simply record the blocking mode in their C level * structure for use by --> ReflectInput. The Tcl level doesn't see this * information or change. As such thread forwarding is not required. */ rtPtr->nonblocking = nonblocking; return EOK; } /* *---------------------------------------------------------------------- * * ReflectSetOption -- * * This function is invoked to configure a channel option. * * Results: * A standard Tcl result code. * * Side effects: * Arbitrary, per the parent channel. * *---------------------------------------------------------------------- */ static int ReflectSetOption( void *clientData, /* Channel to query */ Tcl_Interp *interp, /* Interpreter to leave error messages in */ const char *optionName, /* Name of requested option */ const char *newValue) /* The new value */ { ReflectedTransform *rtPtr = (ReflectedTransform *)clientData; /* * Transformations have no options. Thus the call is passed down unchanged * to the parent channel for processing. Its results are passed back * unchanged as well. This all happens in the thread we are in. As the Tcl * level is not involved there is no need for thread forwarding. */ Tcl_DriverSetOptionProc *setOptionProc = Tcl_ChannelSetOptionProc(Tcl_GetChannelType(rtPtr->parent)); if (setOptionProc == NULL) { return TCL_ERROR; } return setOptionProc(Tcl_GetChannelInstanceData(rtPtr->parent), interp, optionName, newValue); } /* *---------------------------------------------------------------------- * * ReflectGetOption -- * * This function is invoked to retrieve all or a channel options. * * Results: * A standard Tcl result code. * * Side effects: * Arbitrary, per the parent channel. * *---------------------------------------------------------------------- */ static int ReflectGetOption( void *clientData, /* Channel to query */ Tcl_Interp *interp, /* Interpreter to leave error messages in */ const char *optionName, /* Name of requested option */ Tcl_DString *dsPtr) /* String to place the result into */ { ReflectedTransform *rtPtr = (ReflectedTransform *)clientData; /* * Transformations have no options. Thus the call is passed down unchanged * to the parent channel for processing. Its results are passed back * unchanged as well. This all happens in the thread we are in. As the Tcl * level is not involved there is no need for thread forwarding. * * Note that the parent not having a driver for option retrieval is not an * immediate error. A query for all options is ok. Only a request for a * specific option has to fail. */ Tcl_DriverGetOptionProc *getOptionProc = Tcl_ChannelGetOptionProc(Tcl_GetChannelType(rtPtr->parent)); if (getOptionProc != NULL) { return getOptionProc(Tcl_GetChannelInstanceData(rtPtr->parent), interp, optionName, dsPtr); } else if (optionName == NULL) { return TCL_OK; } else { return TCL_ERROR; } } /* *---------------------------------------------------------------------- * * ReflectHandle -- * * This function is invoked to retrieve the associated file handle. * * Results: * A standard Tcl result code. * * Side effects: * Arbitrary, per the parent channel. * *---------------------------------------------------------------------- */ static int ReflectHandle( void *clientData, int direction, void **handlePtr) { ReflectedTransform *rtPtr = (ReflectedTransform *)clientData; /* * Transformations have no handle of their own. As such we simply query * the parent channel for it. This way the query will ripple down through * all transformations until reaches the base channel. Which then returns * its handle, or fails. The former will then ripple up the stack. * * This all happens in the thread we are in. As the Tcl level is not * involved no forwarding is required. */ return Tcl_GetChannelHandle(rtPtr->parent, direction, handlePtr); } /* *---------------------------------------------------------------------- * * ReflectNotify -- * * This function is invoked to reported incoming events. * * Results: * A standard Tcl result code. * * Side effects: * Arbitrary, per the parent channel. * *---------------------------------------------------------------------- */ static int ReflectNotify( void *clientData, int mask) { ReflectedTransform *rtPtr = (ReflectedTransform *)clientData; /* * An event occurred in the underlying channel. * * We delete our timer. It was not fired, yet we are here, so the channel * below generated such an event and we don't have to. The renewal of the * interest after the execution of channel handlers will eventually cause * us to recreate the timer (in ReflectWatch). */ TimerKill(rtPtr); /* * Pass to higher layers. */ return mask; } /* * Helpers. ========================================================= */ /* *---------------------------------------------------------------------- * * DecodeEventMask -- * * This function takes an internal bitmask of events and constructs the * equivalent list of event items. * * Results: * A Tcl_Obj reference. The object will have a refCount of one. The user * has to decrement it to release the object. * * Side effects: * None. * *---------------------------------------------------------------------- * DUPLICATE of 'DecodeEventMask' in tclIORChan.c */ static Tcl_Obj * DecodeEventMask( int mask) { const char *eventStr; Tcl_Obj *evObj; switch (mask & RANDW) { case RANDW: eventStr = "read write"; break; case TCL_READABLE: eventStr = "read"; break; case TCL_WRITABLE: eventStr = "write"; break; default: eventStr = ""; break; } evObj = Tcl_NewStringObj(eventStr, -1); Tcl_IncrRefCount(evObj); return evObj; } /* *---------------------------------------------------------------------- * * NewReflectedTransform -- * * This function is invoked to allocate and initialize the instance data * of a new reflected channel. * * Results: * A heap-allocated channel instance. * * Side effects: * Allocates memory. * *---------------------------------------------------------------------- */ static ReflectedTransform * NewReflectedTransform( Tcl_Interp *interp, Tcl_Obj *cmdpfxObj, TCL_UNUSED(int) /*mode*/, Tcl_Obj *handleObj, Tcl_Channel parentChan) { ReflectedTransform *rtPtr; Tcl_Size i, listc; Tcl_Obj **listv; rtPtr = (ReflectedTransform *)Tcl_Alloc(sizeof(ReflectedTransform)); /* rtPtr->chan: Assigned by caller. Dummy data here. */ /* rtPtr->methods: Assigned by caller. Dummy data here. */ rtPtr->chan = NULL; rtPtr->methods = 0; #if TCL_THREADS rtPtr->thread = Tcl_GetCurrentThread(); #endif rtPtr->parent = parentChan; rtPtr->interp = interp; rtPtr->handle = handleObj; Tcl_IncrRefCount(handleObj); rtPtr->timer = NULL; rtPtr->mode = 0; rtPtr->readIsDrained = 0; rtPtr->eofPending = 0; rtPtr->nonblocking = (((Channel *) parentChan)->state->flags & CHANNEL_NONBLOCKING); rtPtr->dead = 0; /* * Query parent for current blocking mode. */ ResultInit(&rtPtr->result); /* * Method placeholder. */ /* ASSERT: cmdpfxObj is a Tcl List */ TclListObjGetElements(interp, cmdpfxObj, &listc, &listv); /* * See [==] as well. * Storage for the command prefix and the additional words required for * the invocation of methods in the command handler. * * listv [0] [listc-1] | [listc] [listc+1] | * argv [0] ... [.] | [argc-2] [argc-1] | [argc] [argc+2] * cmd ... pfx | method chan | detail1 detail2 */ rtPtr->argc = listc + 2; rtPtr->argv = (Tcl_Obj **)Tcl_Alloc(sizeof(Tcl_Obj *) * (listc+4)); /* * Duplicate object references. */ for (i=0; iargv[i] = listv[i]; Tcl_IncrRefCount(word); } i++; /* Skip placeholder for method */ /* * See [x] in FreeReflectedTransform for release */ rtPtr->argv[i] = handleObj; Tcl_IncrRefCount(handleObj); /* * The next two objects are kept empty, varying arguments. */ /* * Initialization complete. */ return rtPtr; } /* *---------------------------------------------------------------------- * * NextHandle -- * * This function is invoked to generate a channel handle for a new * reflected channel. * * Results: * A Tcl_Obj containing the string of the new channel handle. The * refcount of the returned object is -- zero --. * * Side effects: * May allocate memory. Mutex protected critical section locks out other * threads for a short time. * *---------------------------------------------------------------------- */ static Tcl_Obj * NextHandle(void) { /* * Count number of generated reflected channels. Used for id generation. * Ids are never reclaimed and there is no dealing with wrap around. On * the other hand, "unsigned long" should be big enough except for * absolute longrunners (generate a 100 ids per second => overflow will * occur in 1 1/3 years). */ TCL_DECLARE_MUTEX(rtCounterMutex) static unsigned long rtCounter = 0; Tcl_Obj *resObj; Tcl_MutexLock(&rtCounterMutex); resObj = Tcl_ObjPrintf("rt%lu", rtCounter); rtCounter++; Tcl_MutexUnlock(&rtCounterMutex); return resObj; } static void FreeReflectedTransformArgs( ReflectedTransform *rtPtr) { int i, n = rtPtr->argc - 2; if (n < 0) { return; } Tcl_DecrRefCount(rtPtr->handle); rtPtr->handle = NULL; for (i=0; iargv[i]); } /* * See [x] in NewReflectedTransform for lock * n+1 = argc-1. */ Tcl_DecrRefCount(rtPtr->argv[n+1]); rtPtr->argc = 1; } static void FreeReflectedTransform( void *blockPtr) { ReflectedTransform *rtPtr = (ReflectedTransform *) blockPtr; TimerKill(rtPtr); ResultClear(&rtPtr->result); FreeReflectedTransformArgs(rtPtr); Tcl_Free(rtPtr->argv); Tcl_Free(rtPtr); } /* *---------------------------------------------------------------------- * * InvokeTclMethod -- * * This function is used to invoke the Tcl level of a reflected channel. * It handles all the command assembly, invocation, and generic state and * result mgmt. It does *not* handle thread redirection; that is the * responsibility of clients of this function. * * Results: * Result code and data as returned by the method. * * Side effects: * Arbitrary, as it calls upon a Tcl script. * * Contract: * argOneObj.refCount >= 1 on entry and exit, if argOneObj != NULL * argTwoObj.refCount >= 1 on entry and exit, if argTwoObj != NULL * resObj.refCount in {0, 1, ...} * *---------------------------------------------------------------------- * Semi-DUPLICATE of 'InvokeTclMethod' in tclIORChan.c * - Semi because different structures are used. * - Still possible to factor out the commonalities into a separate structure. */ static int InvokeTclMethod( ReflectedTransform *rtPtr, const char *method, Tcl_Obj *argOneObj, /* NULL'able */ Tcl_Obj *argTwoObj, /* NULL'able */ Tcl_Obj **resultObjPtr) /* NULL'able */ { int cmdc; /* #words in constructed command */ Tcl_Obj *methObj = NULL; /* Method name in object form */ Tcl_InterpState sr; /* State of handler interp */ int result; /* Result code of method invocation */ Tcl_Obj *resObj = NULL; /* Result of method invocation. */ if (rtPtr->dead) { /* * The transform is marked as dead. Bail out immediately, with an * appropriate error. */ if (resultObjPtr != NULL) { resObj = Tcl_NewStringObj(msg_dstlost,-1); *resultObjPtr = resObj; Tcl_IncrRefCount(resObj); } return TCL_ERROR; } /* * NOTE (5): Decide impl. issue: Cache objects with method names? * Requires TSD data as reflections can be created in many different * threads. * NO: Caching of command resolutions means storage per channel. */ /* * Insert method into the preallocated area, after the command prefix, * before the channel id. */ methObj = Tcl_NewStringObj(method, -1); Tcl_IncrRefCount(methObj); rtPtr->argv[rtPtr->argc - 2] = methObj; /* * Append the additional argument containing method specific details * behind the channel id. If specified. * * Because of the contract there is no need to increment the refcounts. * The objects will survive the Tcl_EvalObjv without change. */ cmdc = rtPtr->argc; if (argOneObj) { rtPtr->argv[cmdc] = argOneObj; cmdc++; if (argTwoObj) { rtPtr->argv[cmdc] = argTwoObj; cmdc++; } } /* * And run the handler... This is done in a manner which leaves any * existing state intact. */ sr = Tcl_SaveInterpState(rtPtr->interp, 0 /* Dummy */); Tcl_Preserve(rtPtr); Tcl_Preserve(rtPtr->interp); result = Tcl_EvalObjv(rtPtr->interp, cmdc, rtPtr->argv, TCL_EVAL_GLOBAL); /* * We do not try to extract the result information if the caller has no * interest in it. I.e. there is no need to put effort into creating * something which is discarded immediately after. */ if (resultObjPtr) { if (result == TCL_OK) { /* * Ok result taken as is, also if the caller requests that there * is no capture. */ resObj = Tcl_GetObjResult(rtPtr->interp); } else { /* * Non-ok result is always treated as an error. We have to capture * the full state of the result, including additional options. * * This is complex and ugly, and would be completely unnecessary * if we only added support for a TCL_FORBID_EXCEPTIONS flag. */ if (result != TCL_ERROR) { Tcl_Obj *cmd = Tcl_NewListObj(cmdc, rtPtr->argv); Tcl_Size cmdLen; const char *cmdString = TclGetStringFromObj(cmd, &cmdLen); Tcl_IncrRefCount(cmd); Tcl_ResetResult(rtPtr->interp); Tcl_SetObjResult(rtPtr->interp, Tcl_ObjPrintf( "chan handler returned bad code: %d", result)); Tcl_LogCommandInfo(rtPtr->interp, cmdString, cmdString, cmdLen); Tcl_DecrRefCount(cmd); result = TCL_ERROR; } Tcl_AppendObjToErrorInfo(rtPtr->interp, Tcl_ObjPrintf( "\n (chan handler subcommand \"%s\")", method)); resObj = MarshallError(rtPtr->interp); } Tcl_IncrRefCount(resObj); } Tcl_RestoreInterpState(rtPtr->interp, sr); Tcl_Release(rtPtr->interp); Tcl_Release(rtPtr); /* * Cleanup of the dynamic parts of the command. * * The detail objects survived the Tcl_EvalObjv without change because of * the contract. Therefore there is no need to decrement the refcounts. Only * the internal method object has to be disposed of. */ Tcl_DecrRefCount(methObj); /* * The resObj has a ref count of 1 at this location. This means that the * caller of InvokeTclMethod has to dispose of it (but only if it was * returned to it). */ if (resultObjPtr != NULL) { *resultObjPtr = resObj; } /* * There no need to handle the case where nothing is returned, because for * that case resObj was not set anyway. */ return result; } /* *---------------------------------------------------------------------- * * GetReflectedTransformMap -- * * Gets and potentially initializes the reflected channel map for an * interpreter. * * Results: * A pointer to the map created, for use by the caller. * * Side effects: * Initializes the reflected channel map for an interpreter. * *---------------------------------------------------------------------- */ static ReflectedTransformMap * GetReflectedTransformMap( Tcl_Interp *interp) { ReflectedTransformMap *rtmPtr = (ReflectedTransformMap *) Tcl_GetAssocData(interp, RTMKEY, NULL); if (rtmPtr == NULL) { rtmPtr = (ReflectedTransformMap *)Tcl_Alloc(sizeof(ReflectedTransformMap)); Tcl_InitHashTable(&rtmPtr->map, TCL_STRING_KEYS); Tcl_SetAssocData(interp, RTMKEY, (Tcl_InterpDeleteProc *) DeleteReflectedTransformMap, rtmPtr); } return rtmPtr; } /* *---------------------------------------------------------------------- * * DeleteReflectedTransformMap -- * * Deletes the channel table for an interpreter, closing any open * channels whose refcount reaches zero. This procedure is invoked when * an interpreter is deleted, via the AssocData cleanup mechanism. * * Results: * None. * * Side effects: * Deletes the hash table of channels. May close channels. May flush * output on closed channels. Removes any channeEvent handlers that were * registered in this interpreter. * *---------------------------------------------------------------------- */ static void DeleteReflectedTransformMap( void *clientData, /* The per-interpreter data structure. */ Tcl_Interp *interp) /* The interpreter being deleted. */ { ReflectedTransformMap *rtmPtr; /* The map */ Tcl_HashSearch hSearch; /* Search variable. */ Tcl_HashEntry *hPtr; /* Search variable. */ ReflectedTransform *rtPtr; #if TCL_THREADS ForwardingResult *resultPtr; ForwardingEvent *evPtr; ForwardParam *paramPtr; #else (void)interp; #endif /* TCL_THREADS */ /* * Delete all entries. The channels may have been closed already, or will * be closed later, by the standard IO finalization of an interpreter * under destruction. Except for the channels which were moved to a * different interpreter and/or thread. They do not exist from the IO * systems point of view and will not get closed. Therefore mark all as * dead so that any future access will cause a proper error. For channels * in a different thread we actually do the same as * DeleteThreadReflectedTransformMap(), just restricted to the channels of * this interp. */ rtmPtr = (ReflectedTransformMap *)clientData; for (hPtr = Tcl_FirstHashEntry(&rtmPtr->map, &hSearch); hPtr != NULL; hPtr = Tcl_FirstHashEntry(&rtmPtr->map, &hSearch)) { rtPtr = (ReflectedTransform *)Tcl_GetHashValue(hPtr); rtPtr->dead = 1; Tcl_DeleteHashEntry(hPtr); } Tcl_DeleteHashTable(&rtmPtr->map); Tcl_Free(&rtmPtr->map); #if TCL_THREADS /* * The origin interpreter for one or more reflected channels is gone. */ /* * Get the map of all channels handled by the current thread. This is a * ReflectedTransformMap, but on a per-thread basis, not per-interp. Go * through the channels and remove all which were handled by this * interpreter. They have already been marked as dead. */ rtmPtr = GetThreadReflectedTransformMap(); for (hPtr = Tcl_FirstHashEntry(&rtmPtr->map, &hSearch); hPtr != NULL; hPtr = Tcl_NextHashEntry(&hSearch)) { rtPtr = (ReflectedTransform *)Tcl_GetHashValue(hPtr); if (rtPtr->interp != interp) { /* * Ignore entries for other interpreters. */ continue; } rtPtr->dead = 1; FreeReflectedTransformArgs(rtPtr); Tcl_DeleteHashEntry(hPtr); } /* * Go through the list of pending results and cancel all whose events were * destined for this interpreter. While this is in progress we block any * other access to the list of pending results. */ Tcl_MutexLock(&rtForwardMutex); for (resultPtr = forwardList; resultPtr != NULL; resultPtr = resultPtr->nextPtr) { if (resultPtr->dsti != interp) { /* * Ignore results/events for other interpreters. */ continue; } /* * The receiver for the event exited, before processing the event. We * detach the result now, wake the originator up and signal failure. */ evPtr = resultPtr->evPtr; if (evPtr == NULL) { continue; } paramPtr = evPtr->param; evPtr->resultPtr = NULL; resultPtr->evPtr = NULL; resultPtr->result = TCL_ERROR; ForwardSetStaticError(paramPtr, msg_send_dstlost); Tcl_ConditionNotify(&resultPtr->done); } Tcl_MutexUnlock(&rtForwardMutex); #endif /* TCL_THREADS */ } #if TCL_THREADS /* *---------------------------------------------------------------------- * * GetThreadReflectedTransformMap -- * * Gets and potentially initializes the reflected channel map for a * thread. * * Results: * A pointer to the map created, for use by the caller. * * Side effects: * Initializes the reflected channel map for a thread. * *---------------------------------------------------------------------- */ static ReflectedTransformMap * GetThreadReflectedTransformMap(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!tsdPtr->rtmPtr) { tsdPtr->rtmPtr = (ReflectedTransformMap *) Tcl_Alloc(sizeof(ReflectedTransformMap)); Tcl_InitHashTable(&tsdPtr->rtmPtr->map, TCL_STRING_KEYS); Tcl_CreateThreadExitHandler(DeleteThreadReflectedTransformMap, NULL); } return tsdPtr->rtmPtr; } /* *---------------------------------------------------------------------- * * DeleteThreadReflectedTransformMap -- * * Deletes the channel table for a thread. This procedure is invoked when * a thread is deleted. The channels have already been marked as dead, in * DeleteReflectedTransformMap(). * * Results: * None. * * Side effects: * Deletes the hash table of channels. * *---------------------------------------------------------------------- */ static void DeleteThreadReflectedTransformMap( TCL_UNUSED(void *)) { Tcl_HashSearch hSearch; /* Search variable. */ Tcl_HashEntry *hPtr; /* Search variable. */ Tcl_ThreadId self = Tcl_GetCurrentThread(); ReflectedTransformMap *rtmPtr; /* The map */ ForwardingResult *resultPtr; /* * The origin thread for one or more reflected channels is gone. * NOTE: If this function is called due to a thread getting killed the * per-interp DeleteReflectedTransformMap is apparently not called. */ /* * Get the map of all channels handled by the current thread. This is a * ReflectedTransformMap, but on a per-thread basis, not per-interp. Go * through the channels, remove all, mark them as dead. */ rtmPtr = GetThreadReflectedTransformMap(); for (hPtr = Tcl_FirstHashEntry(&rtmPtr->map, &hSearch); hPtr != NULL; hPtr = Tcl_FirstHashEntry(&rtmPtr->map, &hSearch)) { ReflectedTransform *rtPtr = (ReflectedTransform *)Tcl_GetHashValue(hPtr); rtPtr->dead = 1; FreeReflectedTransformArgs(rtPtr); Tcl_DeleteHashEntry(hPtr); } Tcl_Free(rtmPtr); /* * Go through the list of pending results and cancel all whose events were * destined for this thread. While this is in progress we block any * other access to the list of pending results. */ Tcl_MutexLock(&rtForwardMutex); for (resultPtr = forwardList; resultPtr != NULL; resultPtr = resultPtr->nextPtr) { ForwardingEvent *evPtr; ForwardParam *paramPtr; if (resultPtr->dst != self) { /* * Ignore results/events for other threads. */ continue; } /* * The receiver for the event exited, before processing the event. We * detach the result now, wake the originator up and signal failure. */ evPtr = resultPtr->evPtr; if (evPtr == NULL) { continue; } paramPtr = evPtr->param; evPtr->resultPtr = NULL; resultPtr->evPtr = NULL; resultPtr->result = TCL_ERROR; ForwardSetStaticError(paramPtr, msg_send_dstlost); Tcl_ConditionNotify(&resultPtr->done); } Tcl_MutexUnlock(&rtForwardMutex); } static void ForwardOpToOwnerThread( ReflectedTransform *rtPtr, /* Channel instance */ ForwardedOperation op, /* Forwarded driver operation */ const void *param) /* Arguments */ { Tcl_ThreadId dst = rtPtr->thread; ForwardingEvent *evPtr; ForwardingResult *resultPtr; /* * We gather the lock early. This allows us to check the liveness of the * channel without interference from DeleteThreadReflectedTransformMap(). */ Tcl_MutexLock(&rtForwardMutex); if (rtPtr->dead) { /* * The channel is marked as dead. Bail out immediately, with an * appropriate error. Do not forget to unlock the mutex on this path. */ ForwardSetStaticError((ForwardParam *) param, msg_send_dstlost); Tcl_MutexUnlock(&rtForwardMutex); return; } /* * Create and initialize the event and data structures. */ evPtr = (ForwardingEvent *)Tcl_Alloc(sizeof(ForwardingEvent)); resultPtr = (ForwardingResult *)Tcl_Alloc(sizeof(ForwardingResult)); evPtr->event.proc = ForwardProc; evPtr->resultPtr = resultPtr; evPtr->op = op; evPtr->rtPtr = rtPtr; evPtr->param = (ForwardParam *) param; resultPtr->src = Tcl_GetCurrentThread(); resultPtr->dst = dst; resultPtr->dsti = rtPtr->interp; resultPtr->done = NULL; resultPtr->result = -1; resultPtr->evPtr = evPtr; /* * Now execute the forward. */ TclSpliceIn(resultPtr, forwardList); /* Do not unlock here. That is done by the ConditionWait */ /* * Ensure cleanup of the event if the origin thread exits while this event * is pending or in progress. Exit of the destination thread is handled by * DeleteThreadReflectionChannelMap(), this is set up by * GetThreadReflectedTransformMap(). This is what we use the 'forwardList' * (see above) for. */ Tcl_CreateThreadExitHandler(SrcExitProc, evPtr); /* * Queue the event and poke the other thread's notifier. */ Tcl_ThreadQueueEvent(dst, (Tcl_Event *) evPtr, TCL_QUEUE_TAIL|TCL_QUEUE_ALERT_IF_EMPTY); /* * (*) Block until the other thread has either processed the transfer or * rejected it. */ while (resultPtr->result < 0) { /* * NOTE (1): Is it possible that the current thread goes away while * waiting here? IOW Is it possible that "SrcExitProc" is called * while we are here? See complementary note (2) in "SrcExitProc" * * The ConditionWait unlocks the mutex during the wait and relocks it * immediately after. */ Tcl_ConditionWait(&resultPtr->done, &rtForwardMutex, NULL); } /* * Unlink result from the forwarder list. No need to lock. Either still * locked, or locked by the ConditionWait */ TclSpliceOut(resultPtr, forwardList); resultPtr->nextPtr = NULL; resultPtr->prevPtr = NULL; Tcl_MutexUnlock(&rtForwardMutex); Tcl_ConditionFinalize(&resultPtr->done); /* * Kill the cleanup handler now, and the result structure as well, before * returning the success code. * * Note: The event structure has already been deleted by the destination * notifier, after it serviced the event. */ Tcl_DeleteThreadExitHandler(SrcExitProc, evPtr); Tcl_Free(resultPtr); } static int ForwardProc( Tcl_Event *evGPtr, TCL_UNUSED(int) /*mask*/) { /* * Notes regarding access to the referenced data. * * In principle the data belongs to the originating thread (see * evPtr->src), however this thread is currently blocked at (*), i.e. * quiescent. Because of this we can treat the data as belonging to us, * without fear of race conditions. I.e. we can read and write as we like. * * The only thing we cannot be sure of is the resultPtr. This can be * NULLed if the originating thread went away while the event is handled * here now. */ ForwardingEvent *evPtr = (ForwardingEvent *) evGPtr; ForwardingResult *resultPtr = evPtr->resultPtr; ReflectedTransform *rtPtr = evPtr->rtPtr; Tcl_Interp *interp = rtPtr->interp; ForwardParam *paramPtr = evPtr->param; Tcl_Obj *resObj = NULL; /* Interp result of InvokeTclMethod */ ReflectedTransformMap *rtmPtr; /* Map of reflected channels with handlers in * this interp. */ Tcl_HashEntry *hPtr; /* Entry in the above map */ /* * Ignore the event if no one is waiting for its result anymore. */ if (!resultPtr) { return 1; } paramPtr->base.code = TCL_OK; paramPtr->base.msgStr = NULL; paramPtr->base.mustFree = 0; switch (evPtr->op) { /* * The destination thread for the following operations is * rtPtr->thread, which contains rtPtr->interp, the interp we have to * call upon for the driver. */ case ForwardedClose: /* * No parameters/results. */ if (InvokeTclMethod(rtPtr, "finalize", NULL, NULL, &resObj) != TCL_OK) { ForwardSetObjError(paramPtr, resObj); } /* * Freeing is done here, in the origin thread, because the argv[] * objects belong to this thread. Deallocating them in a different * thread is not allowed */ /* * Remove the channel from the map before releasing the memory, to * prevent future accesses (like by 'postevent') from finding and * dereferencing a dangling pointer. */ rtmPtr = GetReflectedTransformMap(interp); hPtr = Tcl_FindHashEntry(&rtmPtr->map, TclGetString(rtPtr->handle)); Tcl_DeleteHashEntry(hPtr); /* * In a threaded interpreter we manage a per-thread map as well, to * allow us to survive if the script level pulls the rug out under a * channel by deleting the owning thread. */ rtmPtr = GetThreadReflectedTransformMap(); hPtr = Tcl_FindHashEntry(&rtmPtr->map, TclGetString(rtPtr->handle)); Tcl_DeleteHashEntry(hPtr); FreeReflectedTransformArgs(rtPtr); break; case ForwardedInput: { Tcl_Obj *bufObj = Tcl_NewByteArrayObj((unsigned char *) paramPtr->transform.buf, paramPtr->transform.size); Tcl_IncrRefCount(bufObj); if (InvokeTclMethod(rtPtr, "read", bufObj, NULL, &resObj) != TCL_OK) { ForwardSetObjError(paramPtr, resObj); paramPtr->transform.size = TCL_INDEX_NONE; } else { /* * Process a regular return. Contains the transformation result. * Sent it back to the request originator. */ Tcl_Size bytec = 0; /* Number of returned bytes */ unsigned char *bytev; /* Array of returned bytes */ bytev = Tcl_GetBytesFromObj(NULL, resObj, &bytec); paramPtr->transform.size = bytec; if (bytec > 0) { paramPtr->transform.buf = (char *)Tcl_Alloc(bytec); memcpy(paramPtr->transform.buf, bytev, bytec); } else { paramPtr->transform.buf = NULL; } } Tcl_DecrRefCount(bufObj); break; } case ForwardedOutput: { Tcl_Obj *bufObj = Tcl_NewByteArrayObj((unsigned char *) paramPtr->transform.buf, paramPtr->transform.size); Tcl_IncrRefCount(bufObj); if (InvokeTclMethod(rtPtr, "write", bufObj, NULL, &resObj) != TCL_OK) { ForwardSetObjError(paramPtr, resObj); paramPtr->transform.size = TCL_INDEX_NONE; } else { /* * Process a regular return. Contains the transformation result. * Sent it back to the request originator. */ Tcl_Size bytec = 0; /* Number of returned bytes */ unsigned char *bytev; /* Array of returned bytes */ bytev = Tcl_GetBytesFromObj(NULL, resObj, &bytec); paramPtr->transform.size = bytec; if (bytec > 0) { paramPtr->transform.buf = (char *)Tcl_Alloc(bytec); memcpy(paramPtr->transform.buf, bytev, bytec); } else { paramPtr->transform.buf = NULL; } } Tcl_DecrRefCount(bufObj); break; } case ForwardedDrain: if (InvokeTclMethod(rtPtr, "drain", NULL, NULL, &resObj) != TCL_OK) { ForwardSetObjError(paramPtr, resObj); paramPtr->transform.size = TCL_INDEX_NONE; } else { /* * Process a regular return. Contains the transformation result. * Sent it back to the request originator. */ Tcl_Size bytec = 0; /* Number of returned bytes */ unsigned char *bytev; /* Array of returned bytes */ bytev = Tcl_GetBytesFromObj(NULL, resObj, &bytec); paramPtr->transform.size = bytec; if (bytec > 0) { paramPtr->transform.buf = (char *)Tcl_Alloc(bytec); memcpy(paramPtr->transform.buf, bytev, bytec); } else { paramPtr->transform.buf = NULL; } } break; case ForwardedFlush: if (InvokeTclMethod(rtPtr, "flush", NULL, NULL, &resObj) != TCL_OK) { ForwardSetObjError(paramPtr, resObj); paramPtr->transform.size = TCL_INDEX_NONE; } else { /* * Process a regular return. Contains the transformation result. * Sent it back to the request originator. */ Tcl_Size bytec = 0; /* Number of returned bytes */ unsigned char *bytev; /* Array of returned bytes */ bytev = Tcl_GetBytesFromObj(NULL, resObj, &bytec); paramPtr->transform.size = bytec; if (bytec > 0) { paramPtr->transform.buf = (char *)Tcl_Alloc(bytec); memcpy(paramPtr->transform.buf, bytev, bytec); } else { paramPtr->transform.buf = NULL; } } break; case ForwardedClear: (void) InvokeTclMethod(rtPtr, "clear", NULL, NULL, NULL); break; case ForwardedLimit: if (InvokeTclMethod(rtPtr, "limit?", NULL, NULL, &resObj) != TCL_OK) { ForwardSetObjError(paramPtr, resObj); paramPtr->limit.max = -1; } else if (Tcl_GetIntFromObj(interp, resObj, ¶mPtr->limit.max) != TCL_OK) { ForwardSetObjError(paramPtr, MarshallError(interp)); paramPtr->limit.max = -1; } break; default: /* * Bad operation code. */ Tcl_Panic("Bad operation code in ForwardProc"); break; } /* * Remove the reference we held on the result of the invoke, if we had * such. */ if (resObj != NULL) { Tcl_DecrRefCount(resObj); } if (resultPtr) { /* * Report the forwarding result synchronously to the waiting caller. * This unblocks (*) as well. This is wrapped into a conditional * because the caller may have exited in the mean time. */ Tcl_MutexLock(&rtForwardMutex); resultPtr->result = TCL_OK; Tcl_ConditionNotify(&resultPtr->done); Tcl_MutexUnlock(&rtForwardMutex); } return 1; } static void SrcExitProc( void *clientData) { ForwardingEvent *evPtr = (ForwardingEvent *)clientData; ForwardingResult *resultPtr; ForwardParam *paramPtr; /* * NOTE (2): Can this handler be called with the originator blocked? */ /* * The originator for the event exited. It is not sure if this can happen, * as the originator should be blocked at (*) while the event is in * transit/pending. * * We make sure that the event cannot refer to the result anymore, remove * it from the list of pending results and free the structure. Locking the * access ensures that we cannot get in conflict with "ForwardProc", * should it already execute the event. */ Tcl_MutexLock(&rtForwardMutex); resultPtr = evPtr->resultPtr; paramPtr = evPtr->param; evPtr->resultPtr = NULL; resultPtr->evPtr = NULL; resultPtr->result = TCL_ERROR; ForwardSetStaticError(paramPtr, msg_send_originlost); /* * See below: TclSpliceOut(resultPtr, forwardList); */ Tcl_MutexUnlock(&rtForwardMutex); /* * This unlocks (*). The structure will be spliced out and freed by * "ForwardProc". Maybe. */ Tcl_ConditionNotify(&resultPtr->done); } static void ForwardSetObjError( ForwardParam *paramPtr, Tcl_Obj *obj) { Tcl_Size len; const char *msgStr = TclGetStringFromObj(obj, &len); len++; ForwardSetDynamicError(paramPtr, Tcl_Alloc(len)); memcpy(paramPtr->base.msgStr, msgStr, len); } #endif /* TCL_THREADS */ /* *---------------------------------------------------------------------- * * TimerKill -- * * Timer management. Removes the internal timer if it exists. * * Side effects: * See above. * * Result: * None. * *---------------------------------------------------------------------- */ static void TimerKill( ReflectedTransform *rtPtr) { if (rtPtr->timer == NULL) { return; } /* * Delete an existing flush-out timer, prevent it from firing on a * removed/dead channel. */ Tcl_DeleteTimerHandler(rtPtr->timer); rtPtr->timer = NULL; } /* *---------------------------------------------------------------------- * * TimerSetup -- * * Timer management. Creates the internal timer if it does not exist. * * Side effects: * See above. * * Result: * None. * *---------------------------------------------------------------------- */ static void TimerSetup( ReflectedTransform *rtPtr) { if (rtPtr->timer != NULL) { return; } rtPtr->timer = Tcl_CreateTimerHandler(SYNTHETIC_EVENT_TIME, TimerRun, rtPtr); } /* *---------------------------------------------------------------------- * * TimerRun -- * * Called by the notifier (-> timer) to flush out information waiting in * channel buffers. * * Side effects: * As of 'Tcl_NotifyChannel'. * * Result: * None. * *---------------------------------------------------------------------- */ static void TimerRun( void *clientData) { ReflectedTransform *rtPtr = (ReflectedTransform *)clientData; rtPtr->timer = NULL; Tcl_NotifyChannel(rtPtr->chan, TCL_READABLE); } /* *---------------------------------------------------------------------- * * ResultInit -- * * Initializes the specified buffer structure. The structure will contain * valid information for an empty buffer. * * Side effects: * See above. * * Result: * None. * *---------------------------------------------------------------------- */ static inline void ResultInit( ResultBuffer *rPtr) /* Reference to the structure to * initialize. */ { rPtr->used = 0; rPtr->allocated = 0; rPtr->buf = NULL; } /* *---------------------------------------------------------------------- * * ResultClear -- * * Deallocates any memory allocated by 'ResultAdd'. * * Side effects: * See above. * * Result: * None. * *---------------------------------------------------------------------- */ static inline void ResultClear( ResultBuffer *rPtr) /* Reference to the buffer to clear out */ { rPtr->used = 0; if (!rPtr->allocated) { return; } Tcl_Free(rPtr->buf); rPtr->buf = NULL; rPtr->allocated = 0; } /* *---------------------------------------------------------------------- * * ResultAdd -- * * Adds the bytes in the specified array to the buffer, by appending it. * * Side effects: * See above. * * Result: * None. * *---------------------------------------------------------------------- */ static inline void ResultAdd( ResultBuffer *rPtr, /* The buffer to extend */ unsigned char *buf, /* The buffer to read from */ size_t toWrite) /* The number of bytes in 'buf' */ { if ((rPtr->used + toWrite + 1) > rPtr->allocated) { /* * Extension of the internal buffer is required. * NOTE: Currently linear. Should be doubling to amortize. */ if (rPtr->allocated == 0) { rPtr->allocated = toWrite + RB_INCREMENT; rPtr->buf = UCHARP(Tcl_Alloc(rPtr->allocated)); } else { rPtr->allocated += toWrite + RB_INCREMENT; rPtr->buf = UCHARP(Tcl_Realloc((char *) rPtr->buf, rPtr->allocated)); } } /* * Now copy data. */ memcpy(rPtr->buf + rPtr->used, buf, toWrite); rPtr->used += toWrite; } /* *---------------------------------------------------------------------- * * ResultCopy -- * * Copies the requested number of bytes from the buffer into the * specified array and removes them from the buffer afterward. Copies * less if there is not enough data in the buffer. * * Side effects: * See above. * * Result: * The number of actually copied bytes, possibly less than 'toRead'. * *---------------------------------------------------------------------- */ static inline size_t ResultCopy( ResultBuffer *rPtr, /* The buffer to read from */ unsigned char *buf, /* The buffer to copy into */ size_t toRead) /* Number of requested bytes */ { int copied; if (rPtr->used == 0) { /* * Nothing to copy in the case of an empty buffer. */ copied = 0; } else if (rPtr->used == (size_t)toRead) { /* * We have just enough. Copy everything to the caller. */ memcpy(buf, rPtr->buf, toRead); rPtr->used = 0; copied = toRead; } else if (rPtr->used > (size_t)toRead) { /* * The internal buffer contains more than requested. Copy the * requested subset to the caller, and shift the remaining bytes down. */ memcpy(buf, rPtr->buf, toRead); memmove(rPtr->buf, rPtr->buf + toRead, rPtr->used - toRead); rPtr->used -= toRead; copied = toRead; } else { /* * There is not enough in the buffer to satisfy the caller, so take * everything. */ memcpy(buf, rPtr->buf, rPtr->used); toRead = rPtr->used; rPtr->used = 0; copied = toRead; } /* -- common postwork code ------- */ return copied; } static int TransformRead( ReflectedTransform *rtPtr, int *errorCodePtr, Tcl_Obj *bufObj) { Tcl_Obj *resObj; Tcl_Size bytec = 0; /* Number of returned bytes */ unsigned char *bytev; /* Array of returned bytes */ /* * Are we in the correct thread? */ #if TCL_THREADS if (rtPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; p.transform.buf = (char *) Tcl_GetBytesFromObj(NULL, bufObj, &(p.transform.size)); ForwardOpToOwnerThread(rtPtr, ForwardedInput, &p); if (p.base.code != TCL_OK) { PassReceivedError(rtPtr->chan, &p); *errorCodePtr = EINVAL; return 0; } *errorCodePtr = EOK; ResultAdd(&rtPtr->result, UCHARP(p.transform.buf), p.transform.size); Tcl_Free(p.transform.buf); return 1; } #endif /* TCL_THREADS */ /* ASSERT: rtPtr->method & FLAG(METH_READ) */ /* ASSERT: rtPtr->mode & TCL_READABLE */ if (InvokeTclMethod(rtPtr, "read", bufObj, NULL, &resObj) != TCL_OK) { Tcl_SetChannelError(rtPtr->chan, resObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ *errorCodePtr = EINVAL; return 0; } bytev = Tcl_GetBytesFromObj(NULL, resObj, &bytec); ResultAdd(&rtPtr->result, bytev, bytec); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ return 1; } static int TransformWrite( ReflectedTransform *rtPtr, int *errorCodePtr, unsigned char *buf, int toWrite) { Tcl_Obj *bufObj; Tcl_Obj *resObj; Tcl_Size bytec = 0; /* Number of returned bytes */ unsigned char *bytev; /* Array of returned bytes */ int res; /* * Are we in the correct thread? */ #if TCL_THREADS if (rtPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; p.transform.buf = (char *) buf; p.transform.size = toWrite; ForwardOpToOwnerThread(rtPtr, ForwardedOutput, &p); if (p.base.code != TCL_OK) { PassReceivedError(rtPtr->chan, &p); *errorCodePtr = EINVAL; return 0; } *errorCodePtr = EOK; res = Tcl_WriteRaw(rtPtr->parent, (char *) p.transform.buf, p.transform.size); Tcl_Free(p.transform.buf); } else #endif /* TCL_THREADS */ { /* ASSERT: rtPtr->method & FLAG(METH_WRITE) */ /* ASSERT: rtPtr->mode & TCL_WRITABLE */ bufObj = Tcl_NewByteArrayObj((unsigned char *) buf, toWrite); Tcl_IncrRefCount(bufObj); if (InvokeTclMethod(rtPtr, "write", bufObj, NULL, &resObj) != TCL_OK) { *errorCodePtr = EINVAL; Tcl_SetChannelError(rtPtr->chan, resObj); Tcl_DecrRefCount(bufObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ return 0; } *errorCodePtr = EOK; bytev = Tcl_GetBytesFromObj(NULL, resObj, &bytec); res = Tcl_WriteRaw(rtPtr->parent, (char *) bytev, bytec); Tcl_DecrRefCount(bufObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ } if (res < 0) { *errorCodePtr = Tcl_GetErrno(); return 0; } return 1; } static int TransformDrain( ReflectedTransform *rtPtr, int *errorCodePtr) { Tcl_Obj *resObj; Tcl_Size bytec = 0; /* Number of returned bytes */ unsigned char *bytev; /* Array of returned bytes */ /* * Are we in the correct thread? */ #if TCL_THREADS if (rtPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; ForwardOpToOwnerThread(rtPtr, ForwardedDrain, &p); if (p.base.code != TCL_OK) { PassReceivedError(rtPtr->chan, &p); *errorCodePtr = EINVAL; return 0; } *errorCodePtr = EOK; ResultAdd(&rtPtr->result, UCHARP(p.transform.buf), p.transform.size); Tcl_Free(p.transform.buf); } else #endif /* TCL_THREADS */ { if (InvokeTclMethod(rtPtr, "drain", NULL, NULL, &resObj)!=TCL_OK) { Tcl_SetChannelError(rtPtr->chan, resObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ *errorCodePtr = EINVAL; return 0; } bytev = Tcl_GetBytesFromObj(NULL, resObj, &bytec); ResultAdd(&rtPtr->result, bytev, bytec); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ } rtPtr->readIsDrained = 1; return 1; } static int TransformFlush( ReflectedTransform *rtPtr, int *errorCodePtr, int op) { Tcl_Obj *resObj; Tcl_Size bytec = 0; /* Number of returned bytes */ unsigned char *bytev; /* Array of returned bytes */ int res; /* * Are we in the correct thread? */ #if TCL_THREADS if (rtPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; ForwardOpToOwnerThread(rtPtr, ForwardedFlush, &p); if (p.base.code != TCL_OK) { PassReceivedError(rtPtr->chan, &p); *errorCodePtr = EINVAL; return 0; } *errorCodePtr = EOK; if (op == FLUSH_WRITE) { res = Tcl_WriteRaw(rtPtr->parent, (char *) p.transform.buf, p.transform.size); } else { res = 0; } Tcl_Free(p.transform.buf); } else #endif /* TCL_THREADS */ { if (InvokeTclMethod(rtPtr, "flush", NULL, NULL, &resObj)!=TCL_OK) { Tcl_SetChannelError(rtPtr->chan, resObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ *errorCodePtr = EINVAL; return 0; } if (op == FLUSH_WRITE) { bytev = Tcl_GetBytesFromObj(NULL, resObj, &bytec); res = Tcl_WriteRaw(rtPtr->parent, (char *) bytev, bytec); } else { res = 0; } Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ } if (res < 0) { *errorCodePtr = Tcl_GetErrno(); return 0; } return 1; } static void TransformClear( ReflectedTransform *rtPtr) { /* * Are we in the correct thread? */ #if TCL_THREADS if (rtPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; ForwardOpToOwnerThread(rtPtr, ForwardedClear, &p); return; } #endif /* TCL_THREADS */ /* ASSERT: rtPtr->method & FLAG(METH_READ) */ /* ASSERT: rtPtr->mode & TCL_READABLE */ (void) InvokeTclMethod(rtPtr, "clear", NULL, NULL, NULL); rtPtr->readIsDrained = 0; rtPtr->eofPending = 0; ResultClear(&rtPtr->result); } static int TransformLimit( ReflectedTransform *rtPtr, int *errorCodePtr, int *maxPtr) { Tcl_Obj *resObj; Tcl_InterpState sr; /* State of handler interp */ /* * Are we in the correct thread? */ #if TCL_THREADS if (rtPtr->thread != Tcl_GetCurrentThread()) { ForwardParam p; ForwardOpToOwnerThread(rtPtr, ForwardedLimit, &p); if (p.base.code != TCL_OK) { PassReceivedError(rtPtr->chan, &p); *errorCodePtr = EINVAL; return 0; } *errorCodePtr = EOK; *maxPtr = p.limit.max; return 1; } #endif /* ASSERT: rtPtr->method & FLAG(METH_WRITE) */ /* ASSERT: rtPtr->mode & TCL_WRITABLE */ if (InvokeTclMethod(rtPtr, "limit?", NULL, NULL, &resObj) != TCL_OK) { Tcl_SetChannelError(rtPtr->chan, resObj); Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ *errorCodePtr = EINVAL; return 0; } sr = Tcl_SaveInterpState(rtPtr->interp, 0 /* Dummy */); if (Tcl_GetIntFromObj(rtPtr->interp, resObj, maxPtr) != TCL_OK) { Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ Tcl_SetChannelError(rtPtr->chan, MarshallError(rtPtr->interp)); *errorCodePtr = EINVAL; Tcl_RestoreInterpState(rtPtr->interp, sr); return 0; } Tcl_DecrRefCount(resObj); /* Remove reference held from invoke */ Tcl_RestoreInterpState(rtPtr->interp, sr); return 1; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclIOSock.c0000644000175000017500000002057514726623136015201 0ustar sergeisergei/* * tclIOSock.c -- * * Common routines used by all socket based channel types. * * Copyright © 1995-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #if defined(_WIN32) /* * On Windows, we need to do proper Unicode->UTF-8 conversion. */ typedef struct { int initialized; Tcl_DString errorMsg; /* UTF-8 encoded error-message */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; #undef gai_strerror static const char * gai_strerror( int code) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (tsdPtr->initialized) { Tcl_DStringSetLength(&tsdPtr->errorMsg, 0); } else { Tcl_DStringInit(&tsdPtr->errorMsg); tsdPtr->initialized = 1; } Tcl_WCharToUtfDString(gai_strerrorW(code), -1, &tsdPtr->errorMsg); return Tcl_DStringValue(&tsdPtr->errorMsg); } #endif /* *--------------------------------------------------------------------------- * * TclSockGetPort -- * * Maps from a string, which could be a service name, to a port. Used by * socket creation code to get port numbers and resolve registered * service names to port numbers. * * Results: * A standard Tcl result. On success, the port number is returned in * portPtr. On failure, an error message is left in the interp's result. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int TclSockGetPort( Tcl_Interp *interp, const char *string, /* Integer or service name */ const char *proto, /* "tcp" or "udp", typically */ int *portPtr) /* Return port number */ { struct servent *sp; /* Protocol info for named services */ Tcl_DString ds; const char *native; if (Tcl_GetInt(NULL, string, portPtr) != TCL_OK) { /* * Don't bother translating 'proto' to native. */ if (Tcl_UtfToExternalDStringEx(interp, NULL, string, -1, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return TCL_ERROR; } native = Tcl_DStringValue(&ds); sp = getservbyname(native, proto); /* INTL: Native. */ Tcl_DStringFree(&ds); if (sp != NULL) { *portPtr = ntohs((unsigned short) sp->s_port); return TCL_OK; } } if (Tcl_GetInt(interp, string, portPtr) != TCL_OK) { return TCL_ERROR; } if (*portPtr > 0xFFFF) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "couldn't open socket: port number too high", -1)); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclSockMinimumBuffers -- * * Ensure minimum buffer sizes (non zero). * * Results: * A standard Tcl result. * * Side effects: * Sets SO_SNDBUF and SO_RCVBUF sizes. * *---------------------------------------------------------------------- */ #if !defined(_WIN32) && !defined(__CYGWIN__) # define SOCKET int #endif int TclSockMinimumBuffers( void *sock, /* Socket file descriptor */ Tcl_Size size1) /* Minimum buffer size */ { int current; socklen_t len; int size = size1; if (size != size1) { return TCL_ERROR; } len = sizeof(int); getsockopt((SOCKET)(size_t)sock, SOL_SOCKET, SO_SNDBUF, (char *) ¤t, &len); if (current < size) { len = sizeof(int); setsockopt((SOCKET)(size_t)sock, SOL_SOCKET, SO_SNDBUF, (char *) &size, len); } len = sizeof(int); getsockopt((SOCKET)(size_t)sock, SOL_SOCKET, SO_RCVBUF, (char *) ¤t, &len); if (current < size) { len = sizeof(int); setsockopt((SOCKET)(size_t)sock, SOL_SOCKET, SO_RCVBUF, (char *) &size, len); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCreateSocketAddress -- * * This function initializes a sockaddr structure for a host and port. * * Results: * 1 if the host was valid, 0 if the host could not be converted to an IP * address. * * Side effects: * Fills in the *sockaddrPtr structure. * *---------------------------------------------------------------------- */ int TclCreateSocketAddress( Tcl_Interp *interp, /* Interpreter for querying the desired socket * family */ struct addrinfo **addrlist, /* Socket address list */ const char *host, /* Host. NULL implies INADDR_ANY */ int port, /* Port number */ int willBind, /* Is this an address to bind() to or to * connect() to? */ const char **errorMsgPtr) /* Place to store the error message detail, if * available. */ { struct addrinfo hints; struct addrinfo *p; struct addrinfo *v4head = NULL, *v4ptr = NULL; struct addrinfo *v6head = NULL, *v6ptr = NULL; char *native = NULL, portbuf[TCL_INTEGER_SPACE], *portstring; const char *family = NULL; Tcl_DString ds; int result; if (host != NULL) { if (Tcl_UtfToExternalDStringEx(interp, NULL, host, -1, 0, &ds, NULL) != TCL_OK) { Tcl_DStringFree(&ds); return 0; } native = Tcl_DStringValue(&ds); } /* * Workaround for OSX's apparent inability to resolve "localhost", "0" * when the loopback device is the only available network interface. */ if (host != NULL && port == 0) { portstring = NULL; } else { TclFormatInt(portbuf, port); portstring = portbuf; } (void) memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; /* * Magic variable to enforce a certain address family; to be superseded * by a TIP that adds explicit switches to [socket]. */ if (interp != NULL) { family = Tcl_GetVar2(interp, "::tcl::unsupported::socketAF", NULL, 0); if (family != NULL) { if (strcmp(family, "inet") == 0) { hints.ai_family = AF_INET; } else if (strcmp(family, "inet6") == 0) { hints.ai_family = AF_INET6; } } } hints.ai_socktype = SOCK_STREAM; #if 0 /* * We found some problems when using AI_ADDRCONFIG, e.g. on systems that * have no networking besides the loopback interface and want to resolve * localhost. See [Bugs 3385024, 3382419, 3382431]. As the advantage of * using AI_ADDRCONFIG is probably low even in situations where it works, * we'll leave it out for now. After all, it is just an optimisation. * * Missing on NetBSD. * Causes failure when used on AIX 5.1 and HP-UX */ #if defined(AI_ADDRCONFIG) && !defined(_AIX) && !defined(__hpux) hints.ai_flags |= AI_ADDRCONFIG; #endif /* AI_ADDRCONFIG && !_AIX && !__hpux */ #endif /* 0 */ if (willBind) { hints.ai_flags |= AI_PASSIVE; } result = getaddrinfo(native, portstring, &hints, addrlist); if (host != NULL) { Tcl_DStringFree(&ds); } if (result != 0) { *errorMsgPtr = #ifdef EAI_SYSTEM /* Doesn't exist on Windows */ (result == EAI_SYSTEM) ? Tcl_PosixError(interp) : #endif /* EAI_SYSTEM */ gai_strerror(result); return 0; } /* * Put IPv4 addresses before IPv6 addresses to maximize backwards * compatibility of [fconfigure -sockname] output. * * There might be more elegant/efficient ways to do this. */ if (willBind) { for (p = *addrlist; p != NULL; p = p->ai_next) { if (p->ai_family == AF_INET) { if (v4head == NULL) { v4head = p; } else { v4ptr->ai_next = p; } v4ptr = p; } else { if (v6head == NULL) { v6head = p; } else { v6ptr->ai_next = p; } v6ptr = p; } } *addrlist = NULL; if (v6head != NULL) { *addrlist = v6head; v6ptr->ai_next = NULL; } if (v4head != NULL) { v4ptr->ai_next = *addrlist; *addrlist = v4head; } } return 1; } /* *---------------------------------------------------------------------- * * Tcl_OpenTcpServer -- * * Opens a TCP server socket and creates a channel around it. * * Results: * The channel or NULL if failed. If an error occurred, an error message * is left in the interp's result if interp is not NULL. * * Side effects: * Opens a server socket and creates a new channel. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_OpenTcpServer( Tcl_Interp *interp, int port, const char *host, Tcl_TcpAcceptProc *acceptProc, void *callbackData) { char portbuf[TCL_INTEGER_SPACE]; TclFormatInt(portbuf, port); return Tcl_OpenTcpServerEx(interp, portbuf, host, -1, TCL_TCPSERVER_REUSEADDR, acceptProc, callbackData); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclIOUtil.c0000644000175000017500000036134414726623136015221 0ustar sergeisergei/* * tclIOUtil.c -- * * Provides an interface for managing filesystems in Tcl, and also for * creating a filesystem interface in Tcl arbitrary facilities. All * filesystem operations are performed via this interface. Vince Darley * is the primary author. Other signifiant contributors are Karl * Lehenbauer, Mark Diekhans and Peter da Silva. * * Copyright © 1991-1994 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * Copyright © 2001-2004 Vincent Darley. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclIO.h" #ifdef _WIN32 # include "tclWinInt.h" #endif #include "tclFileSystem.h" #ifdef TCL_TEMPLOAD_NO_UNLINK #ifndef NO_FSTATFS #include #endif #endif /* * struct FilesystemRecord -- * * An item in a linked list of registered filesystems */ typedef struct FilesystemRecord { void *clientData; /* Client-specific data for the filesystem * (can be NULL) */ const Tcl_Filesystem *fsPtr;/* Pointer to filesystem dispatch table. */ struct FilesystemRecord *nextPtr; /* The next registered filesystem, or NULL to * indicate the end of the list. */ struct FilesystemRecord *prevPtr; /* The previous filesystem, or NULL to indicate * the ned of the list */ } FilesystemRecord; /* */ typedef struct { int initialized; size_t cwdPathEpoch; /* Compared with the global cwdPathEpoch to * determine whether cwdPathPtr is stale. */ size_t filesystemEpoch; Tcl_Obj *cwdPathPtr; /* A private copy of cwdPathPtr. Updated when * the value is accessed and cwdPathEpoch has * changed. */ void *cwdClientData; FilesystemRecord *filesystemList; size_t claims; } ThreadSpecificData; /* * Forward declarations. */ static Tcl_NRPostProc EvalFileCallback; static FilesystemRecord*FsGetFirstFilesystem(void); static void FsThrExitProc(void *cd); static Tcl_Obj * FsListMounts(Tcl_Obj *pathPtr, const char *pattern); static void FsAddMountsToGlobResult(Tcl_Obj *resultPtr, Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types); static void FsUpdateCwd(Tcl_Obj *cwdObj, void *clientData); static void FsRecacheFilesystemList(void); static void Claim(void); static void Disclaim(void); static void * DivertFindSymbol(Tcl_Interp *interp, Tcl_LoadHandle loadHandle, const char *symbol); static void DivertUnloadFile(Tcl_LoadHandle loadHandle); /* * Functions that provide native filesystem support. They are private and * should be used only here. They should be called instead of calling Tclp... * native filesystem functions. Others should use the Tcl_FS... functions * which ensure correct and complete virtual filesystem support. */ static Tcl_FSFilesystemSeparatorProc NativeFilesystemSeparator; static Tcl_FSFreeInternalRepProc NativeFreeInternalRep; static Tcl_FSFileAttrStringsProc NativeFileAttrStrings; static Tcl_FSFileAttrsGetProc NativeFileAttrsGet; static Tcl_FSFileAttrsSetProc NativeFileAttrsSet; /* * Functions that support the native filesystem functions listed above. They * are the same for win/unix, and not in tclInt.h because they are and should * be used only here. */ MODULE_SCOPE const char *const tclpFileAttrStrings[]; MODULE_SCOPE const TclFileAttrProcs tclpFileAttrProcs[]; /* * These these functions are not static either because routines in the native * (win/unix) directories call them or they are actually implemented in those * directories. They should be called from outside Tcl's native filesystem * routines. If we ever built the native filesystem support into a separate * code library, this could actually be enforced. */ Tcl_FSFilesystemPathTypeProc TclpFilesystemPathType; Tcl_FSInternalToNormalizedProc TclpNativeToNormalized; Tcl_FSStatProc TclpObjStat; Tcl_FSAccessProc TclpObjAccess; Tcl_FSMatchInDirectoryProc TclpMatchInDirectory; Tcl_FSChdirProc TclpObjChdir; Tcl_FSLstatProc TclpObjLstat; Tcl_FSCopyFileProc TclpObjCopyFile; Tcl_FSDeleteFileProc TclpObjDeleteFile; Tcl_FSRenameFileProc TclpObjRenameFile; Tcl_FSCreateDirectoryProc TclpObjCreateDirectory; Tcl_FSCopyDirectoryProc TclpObjCopyDirectory; Tcl_FSRemoveDirectoryProc TclpObjRemoveDirectory; Tcl_FSLinkProc TclpObjLink; Tcl_FSListVolumesProc TclpObjListVolumes; /* * The native filesystem dispatch table. This could me made public but it * should only be accessed by the functions it points to, or perhaps * subordinate helper functions. */ const Tcl_Filesystem tclNativeFilesystem = { "native", sizeof(Tcl_Filesystem), TCL_FILESYSTEM_VERSION_2, TclNativePathInFilesystem, TclNativeDupInternalRep, NativeFreeInternalRep, TclpNativeToNormalized, TclNativeCreateNativeRep, TclpObjNormalizePath, TclpFilesystemPathType, NativeFilesystemSeparator, TclpObjStat, TclpObjAccess, TclpOpenFileChannel, TclpMatchInDirectory, TclpUtime, #ifndef S_IFLNK NULL, #else TclpObjLink, #endif /* S_IFLNK */ TclpObjListVolumes, NativeFileAttrStrings, NativeFileAttrsGet, NativeFileAttrsSet, TclpObjCreateDirectory, TclpObjRemoveDirectory, TclpObjDeleteFile, TclpObjCopyFile, TclpObjRenameFile, TclpObjCopyDirectory, TclpObjLstat, /* Needs casts since we're using version_2. */ (Tcl_FSLoadFileProc *)(void *) TclpDlopen, (Tcl_FSGetCwdProc *) TclpGetNativeCwd, TclpObjChdir }; /* * An initial record in the linked list for the native filesystem. Remains at * the tail of the list and is never freed. Currently the native filesystem is * hard-coded. It may make sense to modify this to accommodate unconventional * uses of Tcl that provide no native filesystem. */ static FilesystemRecord nativeFilesystemRecord = { NULL, &tclNativeFilesystem, NULL, NULL }; /* * Incremented each time the linked list of filesystems is modified. For * multithreaded builds, invalidates all cached filesystem internal * representations. */ static size_t theFilesystemEpoch = 1; /* * The linked list of filesystems. To minimize locking each thread maintains a * local copy of this list. * */ static FilesystemRecord *filesystemList = &nativeFilesystemRecord; TCL_DECLARE_MUTEX(filesystemMutex) /* * A files-system indepent sense of the current directory. */ static Tcl_Obj *cwdPathPtr = NULL; static size_t cwdPathEpoch = 0; /* The pathname of the current directory */ static void *cwdClientData = NULL; TCL_DECLARE_MUTEX(cwdMutex) static Tcl_ThreadDataKey fsDataKey; /* * When a temporary copy of a file is created on the native filesystem in order * to load the file, an FsDivertLoad structure is created to track both the * actual unloadProc/clientData combination which was used, and the original and * modified filenames. This makes it possible to correctly undo the entire * operation in order to unload the library. */ typedef struct { Tcl_LoadHandle loadHandle; Tcl_FSUnloadFileProc *unloadProcPtr; Tcl_Obj *divertedFile; const Tcl_Filesystem *divertedFilesystem; void *divertedFileNativeRep; } FsDivertLoad; /* * Obsolete string-based APIs that should be removed in a future release, * perhaps in Tcl 9. */ /* Obsolete */ int Tcl_Stat( const char *path, /* Pathname of file to stat (in current system * encoding). */ struct stat *oldStyleBuf) /* Filled with results of stat call. */ { int ret; Tcl_StatBuf buf; Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1); Tcl_IncrRefCount(pathPtr); ret = Tcl_FSStat(pathPtr, &buf); Tcl_DecrRefCount(pathPtr); if (ret != -1) { #ifndef TCL_WIDE_INT_IS_LONG Tcl_WideInt tmp1, tmp2, tmp3 = 0; # define OUT_OF_RANGE(x) \ (((Tcl_WideInt)(x)) < LONG_MIN || \ ((Tcl_WideInt)(x)) > LONG_MAX) # define OUT_OF_URANGE(x) \ (((Tcl_WideUInt)(x)) > ((Tcl_WideUInt)ULONG_MAX)) /* * Perform the result-buffer overflow check manually. * * Note that ino_t/ino64_t is unsigned... * * Workaround gcc warning of "comparison is always false due to * limited range of data type" by assigning to tmp var of type * Tcl_WideInt. */ tmp1 = (Tcl_WideInt) buf.st_ino; tmp2 = (Tcl_WideInt) buf.st_size; #ifdef HAVE_STRUCT_STAT_ST_BLOCKS tmp3 = (Tcl_WideInt) buf.st_blocks; #endif if (OUT_OF_URANGE(tmp1) || OUT_OF_RANGE(tmp2) || OUT_OF_RANGE(tmp3)) { #if defined(EFBIG) errno = EFBIG; #elif defined(EOVERFLOW) errno = EOVERFLOW; #else #error "What status should be returned for file size out of range?" #endif return -1; } # undef OUT_OF_RANGE # undef OUT_OF_URANGE #endif /* !TCL_WIDE_INT_IS_LONG */ /* * Copy across all supported fields, with possible type coercion on * those fields that change between the normal and lf64 versions of * the stat structure (on Solaris at least). This is slow when the * structure sizes coincide, but that's what you get for using an * obsolete interface. */ oldStyleBuf->st_mode = buf.st_mode; oldStyleBuf->st_ino = (ino_t) buf.st_ino; oldStyleBuf->st_dev = buf.st_dev; oldStyleBuf->st_rdev = buf.st_rdev; oldStyleBuf->st_nlink = buf.st_nlink; oldStyleBuf->st_uid = buf.st_uid; oldStyleBuf->st_gid = buf.st_gid; oldStyleBuf->st_size = (off_t) buf.st_size; oldStyleBuf->st_atime = Tcl_GetAccessTimeFromStat(&buf); oldStyleBuf->st_mtime = Tcl_GetModificationTimeFromStat(&buf); oldStyleBuf->st_ctime = Tcl_GetChangeTimeFromStat(&buf); #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE oldStyleBuf->st_blksize = buf.st_blksize; #endif #ifdef HAVE_STRUCT_STAT_ST_BLOCKS #ifdef HAVE_BLKCNT_T oldStyleBuf->st_blocks = (blkcnt_t) buf.st_blocks; #else oldStyleBuf->st_blocks = (unsigned long) buf.st_blocks; #endif #endif } return ret; } /* Obsolete */ int Tcl_Access( const char *path, /* Pathname of file to access (in current * system encoding). */ int mode) /* Permission setting. */ { int ret; Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1); Tcl_IncrRefCount(pathPtr); ret = Tcl_FSAccess(pathPtr,mode); Tcl_DecrRefCount(pathPtr); return ret; } /* Obsolete */ Tcl_Channel Tcl_OpenFileChannel( Tcl_Interp *interp, /* Interpreter for error reporting. May be * NULL. */ const char *path, /* Pathname of file to open. */ const char *modeString, /* A list of POSIX open modes or a string such * as "rw". */ int permissions) /* The modes to use if creating a new file. */ { Tcl_Channel ret; Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1); Tcl_IncrRefCount(pathPtr); ret = Tcl_FSOpenFileChannel(interp, pathPtr, modeString, permissions); Tcl_DecrRefCount(pathPtr); return ret; } /* Obsolete */ int Tcl_Chdir( const char *dirName) { int ret; Tcl_Obj *pathPtr = Tcl_NewStringObj(dirName,-1); Tcl_IncrRefCount(pathPtr); ret = Tcl_FSChdir(pathPtr); Tcl_DecrRefCount(pathPtr); return ret; } /* Obsolete */ char * Tcl_GetCwd( Tcl_Interp *interp, Tcl_DString *cwdPtr) { Tcl_Obj *cwd = Tcl_FSGetCwd(interp); if (cwd == NULL) { return NULL; } Tcl_DStringInit(cwdPtr); TclDStringAppendObj(cwdPtr, cwd); Tcl_DecrRefCount(cwd); return Tcl_DStringValue(cwdPtr); } int Tcl_EvalFile( Tcl_Interp *interp, /* Interpreter in which to evaluate the script. */ const char *fileName) /* Pathname of the file containing the script. * Performs Tilde-substitution on this * pathaname. */ { int ret; Tcl_Obj *pathPtr = Tcl_NewStringObj(fileName,-1); Tcl_IncrRefCount(pathPtr); ret = Tcl_FSEvalFile(interp, pathPtr); Tcl_DecrRefCount(pathPtr); return ret; } /* * The basic filesystem implementation. */ static void FsThrExitProc( void *cd) { ThreadSpecificData *tsdPtr = (ThreadSpecificData *)cd; FilesystemRecord *fsRecPtr = NULL, *tmpFsRecPtr = NULL; /* * Discard the cwd copy. */ if (tsdPtr->cwdPathPtr != NULL) { Tcl_DecrRefCount(tsdPtr->cwdPathPtr); tsdPtr->cwdPathPtr = NULL; } if (tsdPtr->cwdClientData != NULL) { NativeFreeInternalRep(tsdPtr->cwdClientData); } /* * Discard the filesystems cache. */ fsRecPtr = tsdPtr->filesystemList; while (fsRecPtr != NULL) { tmpFsRecPtr = fsRecPtr->nextPtr; fsRecPtr->fsPtr = NULL; Tcl_Free(fsRecPtr); fsRecPtr = tmpFsRecPtr; } tsdPtr->filesystemList = NULL; tsdPtr->initialized = 0; } int TclFSCwdIsNative(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); /* if not yet initialized - ensure we'll once obtain cwd */ if (!tsdPtr->cwdPathEpoch) { Tcl_Obj *temp = Tcl_FSGetCwd(NULL); if (temp) { Tcl_DecrRefCount(temp); } } if (tsdPtr->cwdClientData != NULL) { return 1; } else { return 0; } } /* *---------------------------------------------------------------------- * * TclFSCwdPointerEquals -- * Determine whether the given pathname is equal to the current working * directory. * * Results: * 1 if equal, 0 otherwise. * * Side effects: * Updates TSD if needed. * * Stores a pointer to the current directory in *pathPtrPtr if it is not * already there and the current directory is not NULL. * * If *pathPtrPtr is not null its reference count is decremented * before it is replaced. *---------------------------------------------------------------------- */ int TclFSCwdPointerEquals( Tcl_Obj **pathPtrPtr) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); Tcl_MutexLock(&cwdMutex); if (tsdPtr->cwdPathPtr == NULL || tsdPtr->cwdPathEpoch != cwdPathEpoch) { if (tsdPtr->cwdPathPtr != NULL) { Tcl_DecrRefCount(tsdPtr->cwdPathPtr); } if (tsdPtr->cwdClientData != NULL) { NativeFreeInternalRep(tsdPtr->cwdClientData); } if (cwdPathPtr == NULL) { tsdPtr->cwdPathPtr = NULL; } else { tsdPtr->cwdPathPtr = Tcl_DuplicateObj(cwdPathPtr); Tcl_IncrRefCount(tsdPtr->cwdPathPtr); } if (cwdClientData == NULL) { tsdPtr->cwdClientData = NULL; } else { tsdPtr->cwdClientData = TclNativeDupInternalRep(cwdClientData); } tsdPtr->cwdPathEpoch = cwdPathEpoch; } Tcl_MutexUnlock(&cwdMutex); if (tsdPtr->initialized == 0) { Tcl_CreateThreadExitHandler(FsThrExitProc, tsdPtr); tsdPtr->initialized = 1; } if (pathPtrPtr == NULL) { return (tsdPtr->cwdPathPtr == NULL); } if (tsdPtr->cwdPathPtr == *pathPtrPtr) { return 1; } else { Tcl_Size len1, len2; const char *str1, *str2; str1 = TclGetStringFromObj(tsdPtr->cwdPathPtr, &len1); str2 = TclGetStringFromObj(*pathPtrPtr, &len2); if ((len1 == len2) && !memcmp(str1, str2, len1)) { /* * The values are equal but the objects are different. Cache the * current structure in place of the old one. */ Tcl_DecrRefCount(*pathPtrPtr); *pathPtrPtr = tsdPtr->cwdPathPtr; Tcl_IncrRefCount(*pathPtrPtr); return 1; } else { return 0; } } } static void FsRecacheFilesystemList(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); FilesystemRecord *fsRecPtr, *tmpFsRecPtr = NULL, *toFree = NULL, *list; /* * Trash the current cache. */ fsRecPtr = tsdPtr->filesystemList; while (fsRecPtr != NULL) { tmpFsRecPtr = fsRecPtr->nextPtr; fsRecPtr->nextPtr = toFree; toFree = fsRecPtr; fsRecPtr = tmpFsRecPtr; } /* * Locate tail of the global filesystem list. */ Tcl_MutexLock(&filesystemMutex); fsRecPtr = filesystemList; while (fsRecPtr != NULL) { tmpFsRecPtr = fsRecPtr; fsRecPtr = fsRecPtr->nextPtr; } /* * Refill the cache, honouring the order. */ list = NULL; fsRecPtr = tmpFsRecPtr; while (fsRecPtr != NULL) { tmpFsRecPtr = (FilesystemRecord *)Tcl_Alloc(sizeof(FilesystemRecord)); *tmpFsRecPtr = *fsRecPtr; tmpFsRecPtr->nextPtr = list; tmpFsRecPtr->prevPtr = NULL; list = tmpFsRecPtr; fsRecPtr = fsRecPtr->prevPtr; } tsdPtr->filesystemList = list; tsdPtr->filesystemEpoch = theFilesystemEpoch; Tcl_MutexUnlock(&filesystemMutex); while (toFree) { FilesystemRecord *next = toFree->nextPtr; toFree->fsPtr = NULL; Tcl_Free(toFree); toFree = next; } /* * Make sure the above gets released on thread exit. */ if (tsdPtr->initialized == 0) { Tcl_CreateThreadExitHandler(FsThrExitProc, tsdPtr); tsdPtr->initialized = 1; } } static FilesystemRecord * FsGetFirstFilesystem(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); if (tsdPtr->filesystemList == NULL || ((tsdPtr->claims == 0) && (tsdPtr->filesystemEpoch != theFilesystemEpoch))) { FsRecacheFilesystemList(); } return tsdPtr->filesystemList; } /* * The epoch can is changed when a filesystems is added or removed, when * "system encoding" changes, and when env(HOME) changes. */ int TclFSEpochOk( size_t filesystemEpoch) { return (filesystemEpoch == 0 || filesystemEpoch == theFilesystemEpoch); } static void Claim(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); tsdPtr->claims++; } static void Disclaim(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); tsdPtr->claims--; } size_t TclFSEpoch(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); return tsdPtr->filesystemEpoch; } /* * If non-NULL, take posession of clientData and free it later. */ static void FsUpdateCwd( Tcl_Obj *cwdObj, void *clientData) { Tcl_Size len = 0; const char *str = NULL; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); if (cwdObj != NULL) { str = TclGetStringFromObj(cwdObj, &len); } Tcl_MutexLock(&cwdMutex); if (cwdPathPtr != NULL) { Tcl_DecrRefCount(cwdPathPtr); } if (cwdClientData != NULL) { NativeFreeInternalRep(cwdClientData); } if (cwdObj == NULL) { cwdPathPtr = NULL; cwdClientData = NULL; } else { /* * This must be stored as a string obj! */ cwdPathPtr = Tcl_NewStringObj(str, len); Tcl_IncrRefCount(cwdPathPtr); cwdClientData = TclNativeDupInternalRep(clientData); } if (++cwdPathEpoch == 0) { ++cwdPathEpoch; } tsdPtr->cwdPathEpoch = cwdPathEpoch; Tcl_MutexUnlock(&cwdMutex); if (tsdPtr->cwdPathPtr) { Tcl_DecrRefCount(tsdPtr->cwdPathPtr); } if (tsdPtr->cwdClientData) { NativeFreeInternalRep(tsdPtr->cwdClientData); } if (cwdObj == NULL) { tsdPtr->cwdPathPtr = NULL; tsdPtr->cwdClientData = NULL; } else { tsdPtr->cwdPathPtr = Tcl_NewStringObj(str, len); tsdPtr->cwdClientData = clientData; Tcl_IncrRefCount(tsdPtr->cwdPathPtr); } } /* *---------------------------------------------------------------------- * * TclFinalizeFilesystem -- * * Clean up the filesystem. After this, any call to a Tcl_FS... function * fails. * * If TclResetFilesystem is called later, it restores the filesystem to a * pristine state. * * Results: * None. * * Side effects: * Frees memory allocated for the filesystem. * *---------------------------------------------------------------------- */ void TclFinalizeFilesystem(void) { FilesystemRecord *fsRecPtr; /* * Assume that only one thread is active. Otherwise mutexes would be needed * around this code. * TO DO: This assumption is false, isn't it? */ if (cwdPathPtr != NULL) { Tcl_DecrRefCount(cwdPathPtr); cwdPathPtr = NULL; cwdPathEpoch = 0; } if (cwdClientData != NULL) { NativeFreeInternalRep(cwdClientData); cwdClientData = NULL; } /* * Remove all filesystems, freeing any allocated memory that is no longer * needed. */ TclZipfsFinalize(); fsRecPtr = filesystemList; while (fsRecPtr != NULL) { FilesystemRecord *tmpFsRecPtr = fsRecPtr->nextPtr; /* * The native filesystem is static, so don't free it. */ if (fsRecPtr != &nativeFilesystemRecord) { Tcl_Free(fsRecPtr); } fsRecPtr = tmpFsRecPtr; } if (++theFilesystemEpoch == 0) { ++theFilesystemEpoch; } filesystemList = NULL; /* * filesystemList is now NULL. Any attempt to use the filesystem is likely * to fail. */ #ifdef _WIN32 TclWinEncodingsCleanup(); #endif } /* *---------------------------------------------------------------------- * * TclResetFilesystem -- * * Restore the filesystem to a pristine state. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclResetFilesystem(void) { filesystemList = &nativeFilesystemRecord; if (++theFilesystemEpoch == 0) { ++theFilesystemEpoch; } } /* *---------------------------------------------------------------------- * * Tcl_FSRegister -- * * Prepends to the list of registered fileystems a new FilesystemRecord * for the given Tcl_Filesystem, which is added even if it is already in * the list. To determine whether the filesystem is already in the list, * use Tcl_FSData(). * * Functions that use the list generally process it from head to tail and * use the first filesystem that is suitable. Therefore, when adding a * diagnostic filsystem (one which simply reports all fs activity), it * must be at the head of the list. I.e. it must be the last one * registered. * * Results: * TCL_OK, or TCL_ERROR if memory for a new node in the list could * not be allocated. * * Side effects: * Allocates memory for a filesystem record and modifies the list of * registered filesystems. * *---------------------------------------------------------------------- */ int Tcl_FSRegister( void *clientData, /* Client-specific data for this filesystem. */ const Tcl_Filesystem *fsPtr)/* The filesystem record for the new fs. */ { FilesystemRecord *newFilesystemPtr; if (fsPtr == NULL) { return TCL_ERROR; } newFilesystemPtr = (FilesystemRecord *)Tcl_Alloc(sizeof(FilesystemRecord)); newFilesystemPtr->clientData = clientData; newFilesystemPtr->fsPtr = fsPtr; Tcl_MutexLock(&filesystemMutex); newFilesystemPtr->nextPtr = filesystemList; newFilesystemPtr->prevPtr = NULL; if (filesystemList) { filesystemList->prevPtr = newFilesystemPtr; } filesystemList = newFilesystemPtr; /* * Increment the filesystem epoch counter since existing pathnames might * conceivably now belong to different filesystems. */ if (++theFilesystemEpoch == 0) { ++theFilesystemEpoch; } Tcl_MutexUnlock(&filesystemMutex); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_FSUnregister -- * * Removes the record for given filesystem from the list of registered * filesystems. Refuses to remove the built-in (native) filesystem. This * might be changed in the future to allow a smaller Tcl core in which the * native filesystem is not used at all, e.g. initializing Tcl over a * network connection. * * Results: * TCL_OK if the function pointer was successfully removed, or TCL_ERROR * otherwise. * * Side effects: * The list of registered filesystems is updated. Memory for the * corresponding FilesystemRecord is eventually freed. * *---------------------------------------------------------------------- */ int Tcl_FSUnregister( const Tcl_Filesystem *fsPtr)/* The filesystem record to remove. */ { int retVal = TCL_ERROR; FilesystemRecord *fsRecPtr; Tcl_MutexLock(&filesystemMutex); /* * Traverse filesystemList in search of the record whose * 'fsPtr' member matches 'fsPtr' and remove that record from the list. * Do not revmoe the record for the native filesystem. */ fsRecPtr = filesystemList; while ((retVal == TCL_ERROR) && (fsRecPtr != &nativeFilesystemRecord)) { if (fsRecPtr->fsPtr == fsPtr) { if (fsRecPtr->prevPtr) { fsRecPtr->prevPtr->nextPtr = fsRecPtr->nextPtr; } else { filesystemList = fsRecPtr->nextPtr; } if (fsRecPtr->nextPtr) { fsRecPtr->nextPtr->prevPtr = fsRecPtr->prevPtr; } /* * Each cached pathname could now belong to a different filesystem, * so increment the filesystem epoch counter to ensure that cached * information about the removed filesystem is not used. */ if (++theFilesystemEpoch == 0) { ++theFilesystemEpoch; } Tcl_Free(fsRecPtr); retVal = TCL_OK; } else { fsRecPtr = fsRecPtr->nextPtr; } } Tcl_MutexUnlock(&filesystemMutex); return retVal; } /* *---------------------------------------------------------------------- * * Tcl_FSMatchInDirectory -- * * Search in the given pathname for files matching the given pattern. * Used by [glob]. Processes just one pattern for one directory. Callers * such as TclGlob and DoGlob implement manage the searching of multiple * directories in cases such as * glob -dir $dir -join * pkgIndex.tcl * * Results: * * TCL_OK, or TCL_ERROR * * Side effects: * resultPtr is populated, or in the case of an TCL_ERROR, an error message is * set in the interpreter. * *---------------------------------------------------------------------- */ int Tcl_FSMatchInDirectory( Tcl_Interp *interp, /* Interpreter to receive error messages, or * NULL */ Tcl_Obj *resultPtr, /* List that results are added to. */ Tcl_Obj *pathPtr, /* Pathname of directory to search. If NULL, * the current working directory is used. */ const char *pattern, /* Pattern to match. If NULL, pathPtr must be * a fully-specified pathname of a single * file/directory which already exists and is * of the correct type. */ Tcl_GlobTypeData *types) /* Specifies acceptable types. * May be NULL. The directory flag is * particularly significant. */ { const Tcl_Filesystem *fsPtr; Tcl_Obj *cwd, *tmpResultPtr, **elemsPtr; Tcl_Size resLength, i; int ret = -1; if (types != NULL && (types->type & TCL_GLOB_TYPE_MOUNT)) { /* * Currently external callers may not query mounts, which would be a * valuable future step. This is the only routine that knows about * mounts, so we're being called recursively by ourself. Return no * matches. */ return TCL_OK; } if (pathPtr != NULL) { fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); } else { fsPtr = NULL; } if (fsPtr != NULL) { /* * A corresponding filesystem was found. Search within it. */ if (fsPtr->matchInDirectoryProc == NULL) { Tcl_SetErrno(ENOENT); return -1; } ret = fsPtr->matchInDirectoryProc(interp, resultPtr, pathPtr, pattern, types); if (ret == TCL_OK && pattern != NULL) { FsAddMountsToGlobResult(resultPtr, pathPtr, pattern, types); } return ret; } if (pathPtr != NULL && TclGetString(pathPtr)[0] != '\0') { /* * There is a pathname but it belongs to no known filesystem. Mayday! */ Tcl_SetErrno(ENOENT); return -1; } /* * The pathname is empty or NULL so search in the current working * directory. matchInDirectoryProc prefixes each result with this * directory, so trim it from each result. Deal with this here in the * generic code because otherwise every filesystem implementation of * Tcl_FSMatchInDirectory has to do it. */ cwd = Tcl_FSGetCwd(NULL); if (cwd == NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "glob couldn't determine the current working directory", -1)); } return TCL_ERROR; } fsPtr = Tcl_FSGetFileSystemForPath(cwd); if (fsPtr != NULL && fsPtr->matchInDirectoryProc != NULL) { TclNewObj(tmpResultPtr); Tcl_IncrRefCount(tmpResultPtr); ret = fsPtr->matchInDirectoryProc(interp, tmpResultPtr, cwd, pattern, types); if (ret == TCL_OK) { FsAddMountsToGlobResult(tmpResultPtr, cwd, pattern, types); /* * resultPtr and tmpResultPtr are guaranteed to be distinct. */ ret = TclListObjGetElements(interp, tmpResultPtr, &resLength, &elemsPtr); for (i=0 ; ret==TCL_OK && itype & TCL_GLOB_TYPE_DIR)); Tcl_Obj *mounts = FsListMounts(pathPtr, pattern); if (mounts == NULL) { return; } if (TclListObjLength(NULL, mounts, &mLength) != TCL_OK || mLength == 0) { goto endOfMounts; } if (TclListObjLength(NULL, resultPtr, &gLength) != TCL_OK) { goto endOfMounts; } for (i=0 ; ifsPtr == fsPtr) { retVal = fsRecPtr->clientData; } fsRecPtr = fsRecPtr->nextPtr; } return retVal; } /* *--------------------------------------------------------------------------- * * TclFSNormalizeToUniquePath -- * * Converts the given pathname, containing no ../, ./ components, into a * unique pathname for the given platform. On Unix the resulting pathname * is free of symbolic links/aliases, and on Windows it is the long * case-preserving form. * * * Results: * Stores the resulting pathname in pathPtr and returns the offset of the * last byte processed in pathPtr. * * Side effects: * None (beyond the memory allocation for the result). * * Special notes: * If the filesystem-specific normalizePathProcs can reintroduce ../, ./ * components into the pathname, this function does not return the correct * result. This may be possible with symbolic links on unix. * * *--------------------------------------------------------------------------- */ int TclFSNormalizeToUniquePath( Tcl_Interp *interp, /* Used for error messages. */ Tcl_Obj *pathPtr, /* An Pathname to normalize in-place. Must be * unshared. */ int startAt) /* Offset the string of pathPtr to start at. * Must either be 0 or offset of a directory * separator at the end of a pathname part that * is already normalized, I.e. not the index of * the byte just after the separator. */ { FilesystemRecord *fsRecPtr, *firstFsRecPtr; Tcl_Size i; int isVfsPath = 0; const char *path; /* * Pathnames starting with a UNC prefix and ending with a colon character * are reserved for VFS use. These names can not conflict with real UNC * pathnames per https://msdn.microsoft.com/en-us/library/gg465305.aspx and * rfc3986's definition of reg-name. * * We check these first to avoid useless calls to the native filesystem's * normalizePathProc. */ path = TclGetStringFromObj(pathPtr, &i); if ((i >= 3) && ((path[0] == '/' && path[1] == '/') || (path[0] == '\\' && path[1] == '\\'))) { for (i = 2; ; i++) { if (path[i] == '\0') { break; } if (path[i] == path[0]) { break; } } --i; if (path[i] == ':') { isVfsPath = 1; } } /* * Call the the normalizePathProc routine of each registered filesystem. */ firstFsRecPtr = FsGetFirstFilesystem(); Claim(); if (!isVfsPath) { /* * Find and call the native filesystem handler first if there is one * because the root of Tcl's filesystem is always a native filesystem * (i.e., '/' on unix is native). */ for (fsRecPtr=firstFsRecPtr; fsRecPtr!=NULL; fsRecPtr=fsRecPtr->nextPtr) { if (fsRecPtr->fsPtr != &tclNativeFilesystem) { continue; } /* * TODO: Always call the normalizePathProc here because it should * always exist. */ if (fsRecPtr->fsPtr->normalizePathProc != NULL) { startAt = fsRecPtr->fsPtr->normalizePathProc(interp, pathPtr, startAt); } break; } } for (fsRecPtr=firstFsRecPtr; fsRecPtr!=NULL; fsRecPtr=fsRecPtr->nextPtr) { if (fsRecPtr->fsPtr == &tclNativeFilesystem) { /* * Skip the native system this time through. */ continue; } if (fsRecPtr->fsPtr->normalizePathProc != NULL) { startAt = fsRecPtr->fsPtr->normalizePathProc(interp, pathPtr, startAt); } /* * This efficiency check could be added: * if (retVal == length-of(pathPtr)) {break;} * but there's not much benefit. */ } Disclaim(); return startAt; } /* *--------------------------------------------------------------------------- * * TclGetOpenMode -- * * Computes a POSIX mode mask for opening a file. * * Results: * The mode to pass to "open", or -1 if an error occurs. * * Side effects: * Sets *modeFlagsPtr to 1 to tell the caller to * seek to EOF after opening the file, or to 0 otherwise. * * Adds CHANNEL_RAW_MODE to *modeFlagsPtr to tell the caller * to configure the channel as a binary channel. * * If there is an error and interp is not NULL, sets * interpreter result to an error message. * * Special note: * Based on a prototype implementation contributed by Mark Diekhans. * *--------------------------------------------------------------------------- */ int TclGetOpenMode( Tcl_Interp *interp, /* Interpreter, possibly NULL, to use for * error reporting. */ const char *modeString, /* Mode string, e.g. "r+" or "RDONLY CREAT" */ int *modeFlagsPtr) { int mode, c, gotRW; Tcl_Size modeArgc, i; const char **modeArgv = NULL, *flag; /* * Check for the simpler fopen-like access modes like "r" which are * distinguished from the POSIX access modes by the presence of a * lower-case first letter. */ *modeFlagsPtr = 0; mode = O_RDONLY; /* * Guard against wide characters before using byte-oriented routines. */ if (!(modeString[0] & 0x80) && islower(UCHAR(modeString[0]))) { /* INTL: ISO only. */ switch (modeString[0]) { case 'r': break; case 'w': mode = O_WRONLY|O_CREAT|O_TRUNC; break; case 'a': /* * Add O_APPEND for proper automatic seek-to-end-on-write by the * OS. [Bug 680143] */ mode = O_WRONLY|O_CREAT|O_APPEND; *modeFlagsPtr |= 1; break; default: goto error; } i = 1; while (i<3 && modeString[i]) { if (modeString[i] == modeString[i-1]) { goto error; } switch (modeString[i++]) { case '+': /* * Remove O_APPEND so that the seek command works. [Bug * 1773127] */ mode = (mode & ~(O_ACCMODE|O_APPEND)) | O_RDWR; break; case 'b': *modeFlagsPtr |= CHANNEL_RAW_MODE; break; default: goto error; } } if (modeString[i] != 0) { goto error; } return mode; error: *modeFlagsPtr = 0; if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "illegal access mode \"%s\"", modeString)); Tcl_SetErrorCode(interp, "TCL", "OPENMODE", "INVALID", (char *)NULL); } return -1; } /* * The access modes are specified as a list of POSIX modes like O_CREAT. * * Tcl_SplitList must work correctly when interp is NULL. */ if (Tcl_SplitList(interp, modeString, &modeArgc, &modeArgv) != TCL_OK) { invAccessMode: if (interp != NULL) { Tcl_AddErrorInfo(interp, "\n while processing open access modes \""); Tcl_AddErrorInfo(interp, modeString); Tcl_AddErrorInfo(interp, "\""); Tcl_SetErrorCode(interp, "TCL", "OPENMODE", "INVALID", (char *)NULL); } if (modeArgv) { Tcl_Free((void *)modeArgv); } return -1; } gotRW = 0; for (i = 0; i < modeArgc; i++) { flag = modeArgv[i]; c = flag[0]; if ((c == 'R') && (strcmp(flag, "RDONLY") == 0)) { if (gotRW) { invRW: if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid access mode \"%s\": modes RDONLY, " "RDWR, and WRONLY cannot be combined", flag)); } goto invAccessMode; } mode = (mode & ~O_ACCMODE) | O_RDONLY; gotRW = 1; } else if ((c == 'W') && (strcmp(flag, "WRONLY") == 0)) { if (gotRW) { goto invRW; } mode = (mode & ~O_ACCMODE) | O_WRONLY; gotRW = 1; } else if ((c == 'R') && (strcmp(flag, "RDWR") == 0)) { if (gotRW) { goto invRW; } mode = (mode & ~O_ACCMODE) | O_RDWR; gotRW = 1; } else if ((c == 'A') && (strcmp(flag, "APPEND") == 0)) { if (mode & O_APPEND) { accessFlagRepeated: if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "access mode \"%s\" repeated", flag)); } goto invAccessMode; } mode |= O_APPEND; *modeFlagsPtr |= 1; } else if ((c == 'C') && (strcmp(flag, "CREAT") == 0)) { if (mode & O_CREAT) { goto accessFlagRepeated; } mode |= O_CREAT; } else if ((c == 'E') && (strcmp(flag, "EXCL") == 0)) { if (mode & O_EXCL) { goto accessFlagRepeated; } mode |= O_EXCL; } else if ((c == 'N') && (strcmp(flag, "NOCTTY") == 0)) { #ifdef O_NOCTTY if (mode & O_NOCTTY) { goto accessFlagRepeated; } mode |= O_NOCTTY; #else if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "access mode \"%s\" not supported by this system", flag)); } goto invAccessMode; #endif } else if ((c == 'N') && (strcmp(flag, "NONBLOCK") == 0)) { #ifdef O_NONBLOCK if (mode & O_NONBLOCK) { goto accessFlagRepeated; } mode |= O_NONBLOCK; #else if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "access mode \"%s\" not supported by this system", flag)); } goto invAccessMode; #endif } else if ((c == 'T') && (strcmp(flag, "TRUNC") == 0)) { if (mode & O_TRUNC) { goto accessFlagRepeated; } mode |= O_TRUNC; } else if ((c == 'B') && (strcmp(flag, "BINARY") == 0)) { if (*modeFlagsPtr & CHANNEL_RAW_MODE) { goto accessFlagRepeated; } *modeFlagsPtr |= CHANNEL_RAW_MODE; } else { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid access mode \"%s\": must be APPEND, BINARY, " "CREAT, EXCL, NOCTTY, NONBLOCK, RDONLY, RDWR, " "TRUNC, or WRONLY", flag)); } goto invAccessMode; } } Tcl_Free((void *)modeArgv); if (!gotRW) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "access mode must include either RDONLY, RDWR, or WRONLY", -1)); } return -1; } return mode; } /* *---------------------------------------------------------------------- * * Tcl_FSEvalFile, Tcl_FSEvalFileEx, TclNREvalFile -- * * Reads a file and evaluates it as a script. * * Tcl_FSEvalFile is Tcl_FSEvalFileEx without the encodingName argument. * * TclNREvalFile is an NRE-enabled version of Tcl_FSEvalFileEx. * * Results: * A standard Tcl result, which is either the result of executing the * file or an error indicating why the file couldn't be read. * * Side effects: * Arbitrary, depending on the contents of the script. While the script * is evaluated iPtr->scriptFile is a reference to pathPtr, and after the * evaluation completes, has its original value restored again. * *---------------------------------------------------------------------- */ int Tcl_FSEvalFile( Tcl_Interp *interp, /* Interpreter that evaluates the script. */ Tcl_Obj *pathPtr) /* Pathname of file containing the script. * Tilde-substitution is performed on this * pathname. */ { return Tcl_FSEvalFileEx(interp, pathPtr, NULL); } int Tcl_FSEvalFileEx( Tcl_Interp *interp, /* Interpreter that evaluates the script. */ Tcl_Obj *pathPtr, /* Pathname of the file to process. * Tilde-substitution is performed on this * pathname. */ const char *encodingName) /* Either the name of an encoding or NULL to * use the utf-8 encoding. */ { Tcl_Size length; int result = TCL_ERROR; Tcl_StatBuf statBuf; Tcl_Obj *oldScriptFile; Interp *iPtr; const char *string; Tcl_Channel chan; Tcl_Obj *objPtr; if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) { return result; } if (Tcl_FSStat(pathPtr, &statBuf) == -1) { Tcl_SetErrno(errno); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); return result; } chan = Tcl_FSOpenFileChannel(interp, pathPtr, "r", 0644); if (chan == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); return result; } /* * The eof character is \x1A (^Z). Tcl uses it on every platform to allow * for scripted documents. [Bug: 2040] */ Tcl_SetChannelOption(interp, chan, "-eofchar", "\x1A"); /* * If the encoding is specified, set the channel to that encoding. * Otherwise use utf-8. If the encoding is unknown report an error. */ if (encodingName == NULL) { encodingName = "utf-8"; } if (Tcl_SetChannelOption(interp, chan, "-encoding", encodingName) != TCL_OK) { Tcl_CloseEx(interp,chan,0); return result; } TclNewObj(objPtr); Tcl_IncrRefCount(objPtr); /* * Read first character of stream to check for utf-8 BOM */ if (Tcl_ReadChars(chan, objPtr, 1, 0) == TCL_IO_FAILURE) { Tcl_CloseEx(interp, chan, 0); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); goto end; } string = TclGetString(objPtr); /* * If first character is not a BOM, append the remaining characters. * Otherwise, replace them. [Bug 3466099] */ if (Tcl_ReadChars(chan, objPtr, TCL_INDEX_NONE, memcmp(string, "\xEF\xBB\xBF", 3)) == TCL_IO_FAILURE) { Tcl_CloseEx(interp, chan, 0); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); goto end; } if (Tcl_CloseEx(interp, chan, 0) != TCL_OK) { goto end; } iPtr = (Interp *) interp; oldScriptFile = iPtr->scriptFile; iPtr->scriptFile = pathPtr; Tcl_IncrRefCount(iPtr->scriptFile); string = TclGetStringFromObj(objPtr, &length); /* * TIP #280: Open a frame for the evaluated script. */ iPtr->evalFlags |= TCL_EVAL_FILE; result = TclEvalEx(interp, string, length, 0, 1, NULL, string); /* * Restore the original iPtr->scriptFile value, but because the value may * have hanged during evaluation, don't assume it currently points to * pathPtr. */ if (iPtr->scriptFile != NULL) { Tcl_DecrRefCount(iPtr->scriptFile); } iPtr->scriptFile = oldScriptFile; if (result == TCL_RETURN) { result = TclUpdateReturnInfo(iPtr); } else if (result == TCL_ERROR) { /* * Record information about where the error occurred. */ const char *pathString = TclGetStringFromObj(pathPtr, &length); int limit = 150; int overflow = (length > limit); Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (file \"%.*s%s\" line %d)", (overflow ? limit : (int)length), pathString, (overflow ? "..." : ""), Tcl_GetErrorLine(interp))); } end: Tcl_DecrRefCount(objPtr); return result; } int TclNREvalFile( Tcl_Interp *interp, /* Interpreter in which to evaluate the script. */ Tcl_Obj *pathPtr, /* Pathname of a file containing the script to * evaluate. Tilde-substitution is performed on * this pathname. */ const char *encodingName) /* The name of an encoding to use, or NULL to * use the utf-8 encoding. */ { Tcl_StatBuf statBuf; Tcl_Obj *oldScriptFile, *objPtr; Interp *iPtr; Tcl_Channel chan; const char *string; if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) { return TCL_ERROR; } if (Tcl_FSStat(pathPtr, &statBuf) == -1) { Tcl_SetErrno(errno); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); return TCL_ERROR; } chan = Tcl_FSOpenFileChannel(interp, pathPtr, "r", 0644); if (chan == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); return TCL_ERROR; } TclPkgFileSeen(interp, TclGetString(pathPtr)); /* * The eof character is \x1A (^Z). Tcl uses it on every platform to allow * for scripted documents. [Bug: 2040] */ Tcl_SetChannelOption(interp, chan, "-eofchar", "\x1A"); /* * If the encoding is specified, set the channel to that encoding. * Otherwise use utf-8. If the encoding is unknown report an error. */ if (encodingName == NULL) { encodingName = "utf-8"; } if (Tcl_SetChannelOption(interp, chan, "-encoding", encodingName) != TCL_OK) { Tcl_CloseEx(interp, chan, 0); return TCL_ERROR; } TclNewObj(objPtr); Tcl_IncrRefCount(objPtr); /* * Read first character of stream to check for utf-8 BOM */ if (Tcl_ReadChars(chan, objPtr, 1, 0) == TCL_IO_FAILURE) { Tcl_CloseEx(interp, chan, 0); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); Tcl_DecrRefCount(objPtr); return TCL_ERROR; } string = TclGetString(objPtr); /* * If first character is not a BOM, append the remaining characters. * Otherwise, replace them. [Bug 3466099] */ if (Tcl_ReadChars(chan, objPtr, TCL_INDEX_NONE, memcmp(string, "\xEF\xBB\xBF", 3)) == TCL_IO_FAILURE) { Tcl_CloseEx(interp, chan, 0); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); Tcl_DecrRefCount(objPtr); return TCL_ERROR; } if (Tcl_CloseEx(interp, chan, 0) != TCL_OK) { Tcl_DecrRefCount(objPtr); return TCL_ERROR; } iPtr = (Interp *) interp; oldScriptFile = iPtr->scriptFile; iPtr->scriptFile = pathPtr; Tcl_IncrRefCount(iPtr->scriptFile); /* * TIP #280: Open a frame for the evaluated script. */ iPtr->evalFlags |= TCL_EVAL_FILE; TclNRAddCallback(interp, EvalFileCallback, oldScriptFile, pathPtr, objPtr, NULL); return TclNREvalObjEx(interp, objPtr, 0, NULL, INT_MIN); } static int EvalFileCallback( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; Tcl_Obj *oldScriptFile = (Tcl_Obj *)data[0]; Tcl_Obj *pathPtr = (Tcl_Obj *)data[1]; Tcl_Obj *objPtr = (Tcl_Obj *)data[2]; /* * Restore the original iPtr->scriptFile value, but because the value may * have hanged during evaluation, don't assume it currently points to * pathPtr. */ if (iPtr->scriptFile != NULL) { Tcl_DecrRefCount(iPtr->scriptFile); } iPtr->scriptFile = oldScriptFile; if (result == TCL_RETURN) { result = TclUpdateReturnInfo(iPtr); } else if (result == TCL_ERROR) { /* * Record information about where the error occurred. */ Tcl_Size length; const char *pathString = TclGetStringFromObj(pathPtr, &length); const int limit = 150; int overflow = (length > limit); Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (file \"%.*s%s\" line %d)", (overflow ? limit : (int)length), pathString, (overflow ? "..." : ""), Tcl_GetErrorLine(interp))); } Tcl_DecrRefCount(objPtr); return result; } /* *---------------------------------------------------------------------- * * Tcl_GetErrno -- * * Currently the global variable "errno", but could in the future change * to something else. * * Results: * The current Tcl error number. * * Side effects: * None. The value of the Tcl error code variable is only defined if it * was set by a previous call to Tcl_SetErrno. * *---------------------------------------------------------------------- */ int Tcl_GetErrno(void) { /* * On some platforms errno is thread-local, as implemented by the C * library. */ return errno; } /* *---------------------------------------------------------------------- * * Tcl_SetErrno -- * * Sets the Tcl error code to the given value. On some saner platforms * this is implemented in the C library as a thread-local value , but this * is *really* unsafe to assume! * * Results: * None. * * Side effects: * Modifies the Tcl error code value. * *---------------------------------------------------------------------- */ void Tcl_SetErrno( int err) /* The new value. */ { /* * On some platforms, errno is implemented by the C library as a thread * local value */ errno = err; } /* *---------------------------------------------------------------------- * * Tcl_PosixError -- * * Typically called after a UNIX kernel call returns an error. Sets the * interpreter errorCode to machine-parsable information about the error. * * Results: * A human-readable sring describing the error. * * Side effects: * Sets the errorCode value of the interpreter. * *---------------------------------------------------------------------- */ const char * Tcl_PosixError( Tcl_Interp *interp) /* Interpreter to set the errorCode of */ { const char *id, *msg; msg = Tcl_ErrnoMsg(errno); id = Tcl_ErrnoId(); if (interp) { Tcl_SetErrorCode(interp, "POSIX", id, msg, (char *)NULL); } return msg; } /* *---------------------------------------------------------------------- * * Tcl_FSStat -- * Calls 'statProc' of the filesystem corresponding to pathPtr. * * Replaces the standard library "stat" routine. * * Results: * See stat documentation. * * Side effects: * See stat documentation. * *---------------------------------------------------------------------- */ int Tcl_FSStat( Tcl_Obj *pathPtr, /* Pathname of the file to call stat on (in * current system encoding). */ Tcl_StatBuf *buf) /* A buffer to hold the results of the call to * stat. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->statProc != NULL) { return fsPtr->statProc(pathPtr, buf); } Tcl_SetErrno(ENOENT); return -1; } /* *---------------------------------------------------------------------- * * Tcl_FSLstat -- * Calls the 'lstatProc' of the filesystem corresponding to pathPtr. * * Replaces the library version of lstat. If the filesystem doesn't * provide lstatProc but does provide statProc, Tcl falls back to * statProc. * * Results: * See lstat documentation. * * Side effects: * See lstat documentation. * *---------------------------------------------------------------------- */ int Tcl_FSLstat( Tcl_Obj *pathPtr, /* Pathname of the file to call stat on (in * current system encoding). */ Tcl_StatBuf *buf) /* Filled with results of that call to stat. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL) { if (fsPtr->lstatProc != NULL) { return fsPtr->lstatProc(pathPtr, buf); } if (fsPtr->statProc != NULL) { return fsPtr->statProc(pathPtr, buf); } } Tcl_SetErrno(ENOENT); return -1; } /* *---------------------------------------------------------------------- * * Tcl_FSAccess -- * * Calls 'accessProc' of the filesystem corresponding to pathPtr. * * Replaces the library version of access. * * Results: * See access documentation. * * Side effects: * See access documentation. * *---------------------------------------------------------------------- */ int Tcl_FSAccess( Tcl_Obj *pathPtr, /* Pathname of file to access (in current * system encoding). */ int mode) /* Permission setting. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->accessProc != NULL) { return fsPtr->accessProc(pathPtr, mode); } Tcl_SetErrno(ENOENT); return -1; } /* *---------------------------------------------------------------------- * * Tcl_FSOpenFileChannel -- * * Calls 'openfileChannelProc' of the filesystem corresponding to * pathPtr. * * Results: * The new channel, or NULL if the named file could not be opened. * * Side effects: * Opens a channel, possibly creating the corresponding the file on the * filesystem. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_FSOpenFileChannel( Tcl_Interp *interp, /* Interpreter for error reporting, or NULL */ Tcl_Obj *pathPtr, /* Pathname of file to open. */ const char *modeString, /* A list of POSIX open modes or a string such * as "rw". */ int permissions) /* What modes to use if opening the file * involves creating it. */ { const Tcl_Filesystem *fsPtr; Tcl_Channel retVal = NULL; fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->openFileChannelProc != NULL) { int mode, modeFlags; /* * Parse the mode to determine whether to seek at the outset * and/or set the channel into binary mode. */ mode = TclGetOpenMode(interp, modeString, &modeFlags); if (mode == -1) { return NULL; } /* * Open the file. */ retVal = fsPtr->openFileChannelProc(interp, pathPtr, mode, permissions); if (retVal == NULL) { return NULL; } /* * Seek and/or set binary mode as determined above. */ if ((modeFlags & 1) && Tcl_Seek(retVal, (Tcl_WideInt) 0, SEEK_END) < (Tcl_WideInt) 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not seek to end of file while opening \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); } Tcl_CloseEx(NULL, retVal, 0); return NULL; } if (modeFlags & CHANNEL_RAW_MODE) { Tcl_SetChannelOption(interp, retVal, "-translation", "binary"); } return retVal; } /* * File doesn't belong to any filesystem that can open it. */ Tcl_SetErrno(ENOENT); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't open \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); } return NULL; } /* *---------------------------------------------------------------------- * * Tcl_FSUtime -- * * Calls 'uTimeProc' of the filesystem corresponding to the given * pathname. * * Replaces the library version of utime. * * Results: * See utime documentation. * * Side effects: * See utime documentation. * *---------------------------------------------------------------------- */ int Tcl_FSUtime( Tcl_Obj *pathPtr, /* Pathaname of file to call uTimeProc on */ struct utimbuf *tval) /* Specifies the access/modification * times to use. Should not be modified. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); int err; if (fsPtr == NULL) { err = ENOENT; } else { if (fsPtr->utimeProc != NULL) { return fsPtr->utimeProc(pathPtr, tval); } err = ENOTSUP; } Tcl_SetErrno(err); return -1; } /* *---------------------------------------------------------------------- * * NativeFileAttrStrings -- * * Implements the platform-dependent 'file attributes' subcommand for the * native filesystem, for listing the set of possible attribute strings. * Part of Tcl's native filesystem support. Placed here because it is used * under both Unix and Windows. * * Results: * An array of strings * * Side effects: * None. * *---------------------------------------------------------------------- */ static const char *const * NativeFileAttrStrings( TCL_UNUSED(Tcl_Obj *), TCL_UNUSED(Tcl_Obj **)) { return tclpFileAttrStrings; } /* *---------------------------------------------------------------------- * * NativeFileAttrsGet -- * * Implements the platform-dependent 'file attributes' subcommand for the * native filesystem for 'get' operations. Part of Tcl's native * filesystem support. Defined here because it is used under both Unix * and Windows. * * Results: * Standard Tcl return code. * * If there was no error, stores in objPtrRef a pointer to a new object * having a refCount of zero and holding the result. The caller should * store it somewhere, e.g. as the Tcl result, or decrement its refCount * to free it. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int NativeFileAttrsGet( Tcl_Interp *interp, /* The interpreter for error reporting. */ int index, /* index of the attribute command. */ Tcl_Obj *pathPtr, /* Pathname of the file */ Tcl_Obj **objPtrRef) /* Where to store the a pointer to the result. */ { return tclpFileAttrProcs[index].getProc(interp, index, pathPtr,objPtrRef); } /* *---------------------------------------------------------------------- * * NativeFileAttrsSet -- * * Implements the platform-dependent 'file attributes' subcommand for the * native filesystem for 'set' operations. A part of Tcl's native * filesystem support, it is defined here because it is used under both * Unix and Windows. * * Results: * A standard Tcl return code. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int NativeFileAttrsSet( Tcl_Interp *interp, /* The interpreter for error reporting. */ int index, /* index of the attribute command. */ Tcl_Obj *pathPtr, /* Pathname of the file */ Tcl_Obj *objPtr) /* The value to set. */ { return tclpFileAttrProcs[index].setProc(interp, index, pathPtr, objPtr); } /* *---------------------------------------------------------------------- * * Tcl_FSFileAttrStrings -- * * Implements part of the hookable 'file attributes' * subcommand. * * Calls 'fileAttrStringsProc' of the filesystem corresponding to the * given pathname. * * Results: * Returns an array of strings, or returns NULL and stores in objPtrRef * a pointer to a new Tcl list having a refCount of zero, and containing * the file attribute strings. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char *const * Tcl_FSFileAttrStrings( Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef) { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->fileAttrStringsProc != NULL) { return fsPtr->fileAttrStringsProc(pathPtr, objPtrRef); } Tcl_SetErrno(ENOENT); return NULL; } /* *---------------------------------------------------------------------- * * TclFSFileAttrIndex -- * * Given an attribute name, determines the index of the attribute in the * attribute table. * * Results: * A standard Tcl result code. * * If there is no error, stores the index in *indexPtr. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclFSFileAttrIndex( Tcl_Obj *pathPtr, /* Pathname of the file. */ const char *attributeName, /* The name of the attribute. */ int *indexPtr) /* A place to store the result. */ { Tcl_Obj *listObj = NULL; const char *const *attrTable; /* * Get the attribute table for the file. */ attrTable = Tcl_FSFileAttrStrings(pathPtr, &listObj); if (listObj != NULL) { Tcl_IncrRefCount(listObj); } if (attrTable != NULL) { /* * It's a constant attribute table, so use T_GIFO. */ Tcl_Obj *tmpObj = Tcl_NewStringObj(attributeName, -1); int result; result = Tcl_GetIndexFromObj(NULL, tmpObj, attrTable, NULL, TCL_EXACT, indexPtr); TclDecrRefCount(tmpObj); if (listObj != NULL) { TclDecrRefCount(listObj); } return result; } else if (listObj != NULL) { /* * It's a non-constant attribute list, so do a literal search. */ Tcl_Size i, objc; Tcl_Obj **objv; if (TclListObjGetElements(NULL, listObj, &objc, &objv) != TCL_OK) { TclDecrRefCount(listObj); return TCL_ERROR; } for (i=0 ; ifileAttrsGetProc != NULL) { return fsPtr->fileAttrsGetProc(interp, index, pathPtr, objPtrRef); } Tcl_SetErrno(ENOENT); return -1; } /* *---------------------------------------------------------------------- * * Tcl_FSFileAttrsSet -- * * Implements write access for the hookable 'file * attributes' subcommand. * * Calls 'fileAttrsSetProc' for the filesystem corresponding to the given * pathname. * * Results: * A standard Tcl return code. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_FSFileAttrsSet( Tcl_Interp *interp, /* The interpreter for error reporting. */ int index, /* The index of the attribute command. */ Tcl_Obj *pathPtr, /* The pathname of the file. */ Tcl_Obj *objPtr) /* A place to store the result. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->fileAttrsSetProc != NULL) { return fsPtr->fileAttrsSetProc(interp, index, pathPtr, objPtr); } Tcl_SetErrno(ENOENT); return -1; } /* *---------------------------------------------------------------------- * * Tcl_FSGetCwd -- * * Replaces the library version of getcwd(). * * Most virtual filesystems do not implement cwdProc. Tcl maintains its * own record of the current directory which it keeps synchronized with * the filesystem corresponding to the pathname of the current directory * if the filesystem provides a cwdProc (the native filesystem does). * * If Tcl's current directory is not in the native filesystem, Tcl's * current directory and the current directory of the process are * different. To avoid confusion, extensions should call Tcl_FSGetCwd to * obtain the current directory from Tcl rather than from the operating * system. * * Results: * Returns a pointer to a Tcl_Obj having a refCount of 1 and containing * the current thread's local copy of the global cwdPathPtr value. * * Returns NULL if the current directory could not be determined, and * leaves an error message in the interpreter's result. * * Side effects: * Various objects may be freed and allocated. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSGetCwd( Tcl_Interp *interp) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); if (TclFSCwdPointerEquals(NULL)) { FilesystemRecord *fsRecPtr; Tcl_Obj *retVal = NULL; /* * This is the first time this routine has been called. Call * 'getCwdProc' for each registered filsystems until one returns * something other than NULL, which is a pointer to the pathname of the * current directory. */ fsRecPtr = FsGetFirstFilesystem(); Claim(); for (; (retVal == NULL) && (fsRecPtr != NULL); fsRecPtr = fsRecPtr->nextPtr) { void *retCd; TclFSGetCwdProc2 *proc2; if (fsRecPtr->fsPtr->getCwdProc == NULL) { continue; } if (fsRecPtr->fsPtr->version == TCL_FILESYSTEM_VERSION_1) { retVal = fsRecPtr->fsPtr->getCwdProc(interp); continue; } proc2 = (TclFSGetCwdProc2 *) fsRecPtr->fsPtr->getCwdProc; retCd = proc2(NULL); if (retCd != NULL) { Tcl_Obj *norm; /* * Found the pathname of the current directory. */ retVal = fsRecPtr->fsPtr->internalToNormalizedProc(retCd); Tcl_IncrRefCount(retVal); norm = TclFSNormalizeAbsolutePath(interp,retVal); if (norm != NULL) { /* * Assign to global storage the pathname of the current * directory and copy it into thread-local storage as * well. * * At system startup multiple threads could in principle * call this function simultaneously, which is a little * peculiar, but should be fine given the mutex locks in * FSUPdateCWD. Once some value is assigned to the global * variable the 'else' branch below is always taken, which * is simpler. */ FsUpdateCwd(norm, retCd); Tcl_DecrRefCount(norm); } else { fsRecPtr->fsPtr->freeInternalRepProc(retCd); } Tcl_DecrRefCount(retVal); retVal = NULL; Disclaim(); goto cdDidNotChange; } else if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error getting working directory name: %s", Tcl_PosixError(interp))); } } Disclaim(); if (retVal != NULL) { /* * On some platforms the pathname of the current directory might * not be normalized. For efficiency, ensure that it is * normalized. For the sake of efficiency, we want a completely * normalized current working directory at all times. */ Tcl_Obj *norm = TclFSNormalizeAbsolutePath(interp, retVal); if (norm != NULL) { /* * We found a current working directory, which is now in our * global storage. We must make a copy. Norm already has a * refCount of 1. * * Threading issue: Multiple threads at system startup could in * principle call this function simultaneously. They will * therefore each set the cwdPathPtr independently, which is a * bit peculiar, but should be fine. Once we have a cwd, we'll * always be in the 'else' branch below which is simpler. */ void *cd = (void *) Tcl_FSGetNativePath(norm); FsUpdateCwd(norm, TclNativeDupInternalRep(cd)); Tcl_DecrRefCount(norm); } Tcl_DecrRefCount(retVal); } else { /* * retVal is NULL. There is no current directory, which could be * problematic. */ } } else { /* * There is a thread-local value for the pathname of the current * directory. Give corresponding filesystem a chance update the value * if it is out-of-date. This allows an error to be thrown if, for * example, the permissions on the current working directory have * changed. */ const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr); void *retCd = NULL; Tcl_Obj *retVal, *norm; if (fsPtr == NULL || fsPtr->getCwdProc == NULL) { /* * There is no corresponding filesystem or the filesystem does not * have a getCwd routine. Just assume current local value is ok. */ goto cdDidNotChange; } if (fsPtr->version == TCL_FILESYSTEM_VERSION_1) { retVal = fsPtr->getCwdProc(interp); } else { /* * New API. */ TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2 *) fsPtr->getCwdProc; retCd = proc2(tsdPtr->cwdClientData); if (retCd == NULL && interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error getting working directory name: %s", Tcl_PosixError(interp))); } if (retCd == tsdPtr->cwdClientData) { goto cdDidNotChange; } /* * Looks like a new current directory. */ retVal = fsPtr->internalToNormalizedProc(retCd); Tcl_IncrRefCount(retVal); } if (retVal == NULL) { /* * The current directory could not be determined. Reset the * current direcory to ensure, for example, that 'pwd' does actually * throw the correct error in Tcl. This is tested for in the test * suite on unix. */ FsUpdateCwd(NULL, NULL); goto cdDidNotChange; } norm = TclFSNormalizeAbsolutePath(interp, retVal); if (norm == NULL) { /* * 'norm' shouldn't ever be NULL, but we are careful. */ /* Do nothing */ if (retCd != NULL) { fsPtr->freeInternalRepProc(retCd); } } else if (norm == tsdPtr->cwdPathPtr) { goto cdEqual; } else { /* * Determine whether the filesystem's answer is the same as the * cached local value. Since both 'norm' and 'tsdPtr->cwdPathPtr' * are normalized pathnames, do something more efficient than * calling 'Tcl_FSEqualPaths', and in addition avoid a nasty * infinite loop bug when trying to normalize tsdPtr->cwdPathPtr. */ Tcl_Size len1, len2; const char *str1, *str2; str1 = TclGetStringFromObj(tsdPtr->cwdPathPtr, &len1); str2 = TclGetStringFromObj(norm, &len2); if ((len1 == len2) && (strcmp(str1, str2) == 0)) { /* * The pathname values are equal so retain the old pathname * object which is probably already shared and free the * normalized pathname that was just produced. */ cdEqual: Tcl_DecrRefCount(norm); if (retCd != NULL) { fsPtr->freeInternalRepProc(retCd); } } else { /* * The pathname of the current directory is not the same as * this thread's local cached value. Replace the local value. */ FsUpdateCwd(norm, retCd); Tcl_DecrRefCount(norm); } } Tcl_DecrRefCount(retVal); } cdDidNotChange: if (tsdPtr->cwdPathPtr != NULL) { Tcl_IncrRefCount(tsdPtr->cwdPathPtr); } return tsdPtr->cwdPathPtr; } /* *---------------------------------------------------------------------- * * Tcl_FSChdir -- * * Replaces the library version of chdir(). * * Calls 'chdirProc' of the filesystem that corresponds to the given * pathname. * * Results: * See chdir() documentation. * * Side effects: * See chdir() documentation. * * On success stores in cwdPathPtr the pathname of the new current * directory. * *---------------------------------------------------------------------- */ int Tcl_FSChdir( Tcl_Obj *pathPtr) { const Tcl_Filesystem *fsPtr, *oldFsPtr = NULL; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); int retVal = -1; if (tsdPtr->cwdPathPtr != NULL) { oldFsPtr = Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr); } if (Tcl_FSGetNormalizedPath(NULL, pathPtr) == NULL) { Tcl_SetErrno(ENOENT); return retVal; } fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL) { if (fsPtr->chdirProc != NULL) { /* * If this fails Tcl_SetErrno() has already been called. */ retVal = fsPtr->chdirProc(pathPtr); } else { /* * Fallback to stat-based implementation. */ Tcl_StatBuf buf; if ((Tcl_FSStat(pathPtr, &buf) == 0) && (S_ISDIR(buf.st_mode)) && (Tcl_FSAccess(pathPtr, R_OK) == 0)) { /* * stat was successful, and the file is a directory and is * readable. Can proceed to change the current directory. */ retVal = 0; } else { /* * 'Tcl_SetErrno()' has already been called. */ } } } else { Tcl_SetErrno(ENOENT); } if (retVal == 0) { /* Assume that the cwd was actually changed to the normalized value * just calculated, and cache that information. */ /* * If the filesystem epoch changed recently, the normalized pathname or * its internal handle may be different from what was found above. * This can easily be the case with scripted documents . Therefore get * the normalized pathname again. The correct value will have been * cached as a result of the Tcl_FSGetFileSystemForPath call, above. */ Tcl_Obj *normDirName = Tcl_FSGetNormalizedPath(NULL, pathPtr); if (normDirName == NULL) { /* Not really true, but what else to do? */ Tcl_SetErrno(ENOENT); return -1; } if (fsPtr == &tclNativeFilesystem) { void *cd; void *oldcd = tsdPtr->cwdClientData; /* * Assume that the native filesystem has a getCwdProc and that it * is at version 2. */ TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2 *) fsPtr->getCwdProc; cd = proc2(oldcd); if (cd != oldcd) { /* * Call getCwdProc() and store the resulting internal handle to * compare things with it later. This might not be * exactly the same string as that of the fully normalized * pathname. For example, for the Windows internal handle the * separator is the backslash character. On Unix it might well * be true that the internal handle is the fully normalized * pathname and one could simply use: * cd = Tcl_FSGetNativePath(pathPtr); * but this can't be guaranteed in the general case. In fact, * the internal handle could be any value the filesystem * decides to use to identify a node. */ FsUpdateCwd(normDirName, cd); } } else { /* * Tcl_FSGetCwd() synchronizes the file-global cwdPathPtr if * needed. However, if there is no 'getCwdProc', cwdPathPtr must be * updated right now because there won't be another chance. This * block of code is currently executed whether or not the * filesystem provides a getCwdProc, but it should in principle * work to only call this block if fsPtr->getCwdProc == NULL. */ FsUpdateCwd(normDirName, NULL); } if (oldFsPtr != NULL && fsPtr != oldFsPtr) { /* * The filesystem of the current directory is not the same as the * filesystem of the previous current directory. Invalidate All * FsPath objects. */ Tcl_FSMountsChanged(NULL); } } else { /* * The current directory is now changed or an error occurred and an * error message is now set. Just continue. */ } return retVal; } /* *---------------------------------------------------------------------- * * Tcl_FSLoadFile -- * * Loads a dynamic shared object by passing the given pathname unmodified * to Tcl_LoadFile, and provides pointers to the functions named by 'sym1' * and 'sym2', and another pointer to a function that unloads the object. * * Results: * A standard Tcl completion code. If an error occurs, sets the * interpreter's result to an error message. * * Side effects: * A dynamic shared object is loaded into memory. This may later be * unloaded by passing the handlePtr to *unloadProcPtr. * *---------------------------------------------------------------------- */ int Tcl_FSLoadFile( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Obj *pathPtr, /* Pathname of the file containing the dynamic * shared object. */ const char *sym1, const char *sym2, /* Names of two functions to find in the * dynamic shared object. */ Tcl_LibraryInitProc **proc1Ptr, Tcl_LibraryInitProc **proc2Ptr, /* Places to store pointers to the functions * named by sym1 and sym2. */ Tcl_LoadHandle *handlePtr, /* A place to store the token for the loaded * object. Can be passed to * (*unloadProcPtr)() to unload the file. */ TCL_UNUSED(Tcl_FSUnloadFileProc **)) { const char *symbols[3]; void *procPtrs[2]; int res; symbols[0] = sym1; symbols[1] = sym2; symbols[2] = NULL; res = Tcl_LoadFile(interp, pathPtr, symbols, 0, procPtrs, handlePtr); if (res == TCL_OK) { *proc1Ptr = (Tcl_LibraryInitProc *) procPtrs[0]; *proc2Ptr = (Tcl_LibraryInitProc *) procPtrs[1]; } else { *proc1Ptr = *proc2Ptr = NULL; } return res; } /* *---------------------------------------------------------------------- * * Tcl_LoadFile -- * * Load a dynamic shared object by calling 'loadFileProc' of the * filesystem corresponding to the given pathname, and then finds within * the loaded object the functions named in symbols[]. * * The given pathname is passed unmodified to `loadFileProc`, which * decides how to resolve it. On POSIX systems the native filesystem * passes the given pathname to dlopen(), which resolves the filename * according to its own set of rules. This behaviour is not very * compatible with virtual filesystems, and has other problems as * documented for [load], so it is recommended to use an absolute * pathname. * * Results: * A standard Tcl completion code. If an error occurs, sets the * interpreter result to an error message. * * Side effects: * Memory is allocated for the new object. May be freed by calling * TclFS_UnloadFile. * *---------------------------------------------------------------------- */ /* * Modern HPUX allows the unlink (no ETXTBSY error) yet somehow trashes some * internal data structures, preventing any additional dynamic shared objects * from getting properly loaded. Only the first is ok. Work around the issue * by not unlinking, i.e., emulating the behaviour of the older HPUX which * denied removal. * * Doing the unlink is also an issue within docker containers, whose AUFS * bungles this as well, see * https://github.com/dotcloud/docker/issues/1911 * */ #ifdef _WIN32 #define getenv(x) _wgetenv(L##x) #define atoi(x) _wtoi(x) #else #define WCHAR char #endif static int skipUnlink( Tcl_Obj *shlibFile) { /* * Unlinking is not performed in the following cases: * * 1. The operating system is HPUX. * * 2. If the environment variable TCL_TEMPLOAD_NO_UNLINK is present and * set to true (an integer > 0) * * 3. TCL_TEMPLOAD_NO_UNLINK is not true (an integer > 0) and AUFS * filesystem can be detected (using statfs, if available). */ #ifdef hpux (void)shlibFile; return 1; #else WCHAR *skipstr = getenv("TCL_TEMPLOAD_NO_UNLINK"); if (skipstr && (skipstr[0] != '\0')) { return atoi(skipstr); } #ifndef TCL_TEMPLOAD_NO_UNLINK (void)shlibFile; #else /* At built time TCL_TEMPLOAD_NO_UNLINK can be set manually to control whether * this automatic overriding of unlink is included. */ #ifndef NO_FSTATFS { struct statfs fs; /* * Have fstatfs. May not have the AUFS super magic ... Indeed our build * box is too old to have it directly in the headers. Define taken from * http://mooon.googlecode.com/svn/trunk/linux_include/linux/aufs_type.h * http://aufs.sourceforge.net/ * Better reference will be gladly accepted. */ #ifndef AUFS_SUPER_MAGIC /* AUFS_SUPER_MAGIC can disable/override the AUFS detection, i.e. for * testing if a newer AUFS does not have the bug any more. */ #define AUFS_SUPER_MAGIC ('a' << 24 | 'u' << 16 | 'f' << 8 | 's') #endif /* AUFS_SUPER_MAGIC */ if ((statfs(TclGetString(shlibFile), &fs) == 0) && (fs.f_type == AUFS_SUPER_MAGIC)) { return 1; } } #endif /* ... NO_FSTATFS */ #endif /* ... TCL_TEMPLOAD_NO_UNLINK */ /* * No HPUX, environment variable override, or AUFS detected. Perform * unlink. */ return 0; #endif /* hpux */ } int Tcl_LoadFile( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Obj *pathPtr, /* Pathname of the file containing the dynamic * shared object. */ const char *const symbols[],/* A null-terminated array of names of * functions to find in the loaded object. */ int flags, /* Flags */ void *procVPtrs, /* A place to store pointers to the functions * named by symbols[]. */ Tcl_LoadHandle *handlePtr) /* A place to hold a token for the loaded object. * Can be used by TclpFindSymbol. */ { void **procPtrs = (void **) procVPtrs; const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); const Tcl_Filesystem *copyFsPtr; Tcl_FSUnloadFileProc *unloadProcPtr; Tcl_Obj *copyToPtr; Tcl_LoadHandle newLoadHandle = NULL; Tcl_LoadHandle divertedLoadHandle = NULL; Tcl_FSUnloadFileProc *newUnloadProcPtr = NULL; FsDivertLoad *tvdlPtr; int retVal; int i; if (fsPtr == NULL) { Tcl_SetErrno(ENOENT); return TCL_ERROR; } if (fsPtr->loadFileProc != NULL) { retVal = ((Tcl_FSLoadFileProc2 *)(void *)(fsPtr->loadFileProc)) (interp, pathPtr, handlePtr, &unloadProcPtr, flags); if (retVal == TCL_OK) { if (*handlePtr == NULL) { return TCL_ERROR; } if (interp) { Tcl_ResetResult(interp); } goto resolveSymbols; } if (Tcl_GetErrno() != EXDEV) { return retVal; } } /* * The filesystem doesn't support 'load'. Fall to the following: */ /* * Make sure the file is accessible. */ if (Tcl_FSAccess(pathPtr, R_OK) != 0) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't load library \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); } return TCL_ERROR; } #ifdef TCL_LOAD_FROM_MEMORY /* * The platform supports loading a dynamic shared object from memory. * Create a sufficiently large buffer, read the file into it, and then load * the dynamic shared object from the buffer: */ { Tcl_Size ret; size_t size; void *buffer; Tcl_StatBuf statBuf; Tcl_Channel data; ret = Tcl_FSStat(pathPtr, &statBuf); if (ret < 0) { goto mustCopyToTempAnyway; } size = statBuf.st_size; data = Tcl_FSOpenFileChannel(interp, pathPtr, "rb", 0666); if (!data) { if (interp) { Tcl_ResetResult(interp); } goto mustCopyToTempAnyway; } buffer = TclpLoadMemoryGetBuffer(size); if (!buffer) { Tcl_CloseEx(interp, data, 0); goto mustCopyToTempAnyway; } ret = Tcl_Read(data, (char *)buffer, size); Tcl_CloseEx(interp, data, 0); ret = TclpLoadMemory(buffer, size, ret, TclGetString(pathPtr), handlePtr, &unloadProcPtr, flags); if (ret == TCL_OK && *handlePtr != NULL) { goto resolveSymbols; } } mustCopyToTempAnyway: #endif /* TCL_LOAD_FROM_MEMORY */ /* * Get a temporary filename, first to copy the file into, and then to load. */ copyToPtr = TclpTempFileNameForLibrary(interp, pathPtr); if (copyToPtr == NULL) { return TCL_ERROR; } Tcl_IncrRefCount(copyToPtr); copyFsPtr = Tcl_FSGetFileSystemForPath(copyToPtr); if ((copyFsPtr == NULL) || (copyFsPtr == fsPtr)) { /* * Tcl_FSLoadFile isn't available for the filesystem of the temporary * file. In order to avoid a possible infinite loop, do not attempt to * load further. */ /* * Try to delete the file we probably created and then exit. */ Tcl_FSDeleteFile(copyToPtr); Tcl_DecrRefCount(copyToPtr); if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "couldn't load from current filesystem", -1)); } return TCL_ERROR; } if (TclCrossFilesystemCopy(interp, pathPtr, copyToPtr) != TCL_OK) { Tcl_FSDeleteFile(copyToPtr); Tcl_DecrRefCount(copyToPtr); return TCL_ERROR; } #ifndef _WIN32 /* * It might be necessary on some systems to set the appropriate permissions * on the file. On Unix we could loop over the file attributes and set any * that are called "-permissions" to 0700, but just do it directly instead: */ { int index; Tcl_Obj *perm; TclNewLiteralStringObj(perm, "0700"); Tcl_IncrRefCount(perm); if (TclFSFileAttrIndex(copyToPtr, "-permissions", &index) == TCL_OK) { Tcl_FSFileAttrsSet(NULL, index, copyToPtr, perm); } Tcl_DecrRefCount(perm); } #endif /* * The cross-filesystem copy may have stored the number of bytes in the * result, so reset the result now. */ if (interp) { Tcl_ResetResult(interp); } retVal = Tcl_LoadFile(interp, copyToPtr, symbols, flags, procPtrs, &newLoadHandle); if (retVal != TCL_OK) { Tcl_FSDeleteFile(copyToPtr); Tcl_DecrRefCount(copyToPtr); return retVal; } /* * Try to delete the file immediately. Some operatings systems allow this, * and it avoids leaving the copy laying around after exit. */ if (!skipUnlink(copyToPtr) && (Tcl_FSDeleteFile(copyToPtr) == TCL_OK)) { Tcl_DecrRefCount(copyToPtr); /* * Tell the caller all the details: The package list maintained by * 'load' stores the original (vfs) pathname, the handle of object * loaded from the temporary file, and the unloadProcPtr. */ *handlePtr = newLoadHandle; if (interp) { Tcl_ResetResult(interp); } return TCL_OK; } /* * Divert the unloading in order to unload and cleanup the temporary file. */ tvdlPtr = (FsDivertLoad *)Tcl_Alloc(sizeof(FsDivertLoad)); /* * Remember three pieces of information in order to clean up the diverted * load completely on platforms which allow proper unloading of code. */ tvdlPtr->loadHandle = newLoadHandle; tvdlPtr->unloadProcPtr = newUnloadProcPtr; if (copyFsPtr != &tclNativeFilesystem) { /* refCount of copyToPtr is already incremented. */ tvdlPtr->divertedFile = copyToPtr; /* * This is the filesystem for the temporary file the object was loaded * from. A reference to copyToPtr is already stored in * tvdlPtr->divertedFile, so need need to increment the refCount again. */ tvdlPtr->divertedFilesystem = copyFsPtr; tvdlPtr->divertedFileNativeRep = NULL; } else { /* * Grab the native representation. */ tvdlPtr->divertedFileNativeRep = TclNativeDupInternalRep( Tcl_FSGetInternalRep(copyToPtr, copyFsPtr)); /* * Don't keeep a reference to the Tcl_Obj or the native filesystem. */ tvdlPtr->divertedFile = NULL; tvdlPtr->divertedFilesystem = NULL; Tcl_DecrRefCount(copyToPtr); } copyToPtr = NULL; divertedLoadHandle = (Tcl_LoadHandle)Tcl_Alloc(sizeof(struct Tcl_LoadHandle_)); divertedLoadHandle->clientData = tvdlPtr; divertedLoadHandle->findSymbolProcPtr = DivertFindSymbol; divertedLoadHandle->unloadFileProcPtr = DivertUnloadFile; *handlePtr = divertedLoadHandle; if (interp) { Tcl_ResetResult(interp); } return retVal; resolveSymbols: /* * handlePtr now contains a token for the loaded object. * Resolve the symbols. */ if (symbols != NULL) { for (i=0 ; symbols[i] != NULL; i++) { procPtrs[i] = Tcl_FindSymbol(interp, *handlePtr, symbols[i]); if (procPtrs[i] == NULL) { /* * At least one symbol in the list was not found. Unload the * file and return an error code. Tcl_FindSymbol should have * already left an appropriate error message. */ (*handlePtr)->unloadFileProcPtr(*handlePtr); *handlePtr = NULL; return TCL_ERROR; } } } return TCL_OK; } /* *---------------------------------------------------------------------- * * DivertFindSymbol -- * * Find a symbol in a shared library loaded by making a copying a file * from the virtual filesystem to a native filesystem. * *---------------------------------------------------------------------- */ static void * DivertFindSymbol( Tcl_Interp *interp, /* The relevant interpreter. */ Tcl_LoadHandle loadHandle, /* A handle to the diverted module. */ const char *symbol) /* The name of symbol to resolve. */ { FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle->clientData; Tcl_LoadHandle originalHandle = tvdlPtr->loadHandle; return originalHandle->findSymbolProcPtr(interp, originalHandle, symbol); } /* *---------------------------------------------------------------------- * * DivertUnloadFile -- * * Unloads an object that was loaded from a temporary file copied from the * virtual filesystem the native filesystem. * *---------------------------------------------------------------------- */ static void DivertUnloadFile( Tcl_LoadHandle loadHandle) /* A handle for the loaded object. */ { FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle->clientData; Tcl_LoadHandle originalHandle; if (tvdlPtr == NULL) { /* * tvdlPtr was provided by Tcl_LoadFile so it should not be NULL here. */ return; } originalHandle = tvdlPtr->loadHandle; /* * Call the real 'unloadfile' proc. This must be called first so that the * shared library is actually unloaded by the OS. Otherwise, the following * 'delete' may fail because the shared library is still in use. */ originalHandle->unloadFileProcPtr(originalHandle); /* * Determine which filesystem contains the temporary copy of the file. */ if (tvdlPtr->divertedFilesystem == NULL) { /* * Use the function for the native filsystem, which works even at * this late stage. */ TclpDeleteFile(tvdlPtr->divertedFileNativeRep); NativeFreeInternalRep(tvdlPtr->divertedFileNativeRep); } else { /* * Remove the temporary file. If encodings have been cleaned up * already, this may crash. */ if (tvdlPtr->divertedFilesystem->deleteFileProc(tvdlPtr->divertedFile) != TCL_OK) { /* * This may have happened because Tcl is exiting, and encodings may * have already been deleted or something else the filesystem * depends on may be gone. * * TO DO: Figure out how to delete this file more robustly, or * give the filesystem the information it needs to delete the file * more robustly. One problem might be that the filesystem cannot * extract the information it needs from the above pathname object * because Tcl's entire filesystem apparatus (the code in this * file) has been finalized and there is no way to get the native * handle of the file. */ } /* * This also decrements the refCount of the Tcl_Filesystem * corresponding to this file. which might cause the filesystem to be * deallocated if Tcl is exiting. */ Tcl_DecrRefCount(tvdlPtr->divertedFile); } Tcl_Free(tvdlPtr); Tcl_Free(loadHandle); } /* *---------------------------------------------------------------------- * * Tcl_FindSymbol -- * * Find a symbol in a loaded object. * * Previously filesystem-specific, but has been made portable by having * TclpDlopen return a structure that includes procedure pointers. * * Results: * Returns a pointer to the symbol if found. Otherwise, sets * an error message in the interpreter result and returns NULL. * *---------------------------------------------------------------------- */ void * Tcl_FindSymbol( Tcl_Interp *interp, /* The relevant interpreter. */ Tcl_LoadHandle loadHandle, /* A handle for the loaded object. */ const char *symbol) /* The name of the symbol to resolve. */ { return loadHandle->findSymbolProcPtr(interp, loadHandle, symbol); } /* *---------------------------------------------------------------------- * * Tcl_FSUnloadFile -- * * Unloads a loaded object if unloading is supported for the object. * *---------------------------------------------------------------------- */ int Tcl_FSUnloadFile( Tcl_Interp *interp, /* The relevant interpreter. */ Tcl_LoadHandle handle) /* A handle for the object to unload. */ { if (handle->unloadFileProcPtr == NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot unload: filesystem does not support unloading", -1)); } return TCL_ERROR; } if (handle->unloadFileProcPtr != NULL) { handle->unloadFileProcPtr(handle); } return TCL_OK; } /* *--------------------------------------------------------------------------- * * Tcl_FSLink -- * * Creates or inspects a link by calling 'linkProc' of the filesystem * corresponding to the given pathname. Replaces the library version of * readlink(). * * Results: * If toPtr is NULL, a Tcl_Obj containing the value the symbolic link for * 'pathPtr', or NULL if a symbolic link was not accessible. The caller * should Tcl_DecrRefCount on the result to release it. Otherwise NULL. * * In this case the result has no additional reference count and need not * be freed. The actual action to perform is given by the 'linkAction' * flags, which is a combination of: * * TCL_CREATE_SYMBOLIC_LINK * TCL_CREATE_HARD_LINK * * Most filesystems do not support linking across to different * filesystems, so this function usually fails if the filesystem * corresponding to toPtr is not the same as the filesystem corresponding * to pathPtr. * * Side effects: * Creates or sets a link if toPtr is not NULL. * * See readlink(). * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSLink( Tcl_Obj *pathPtr, /* Pathaname of file. */ Tcl_Obj *toPtr, /* NULL or the pathname of a file to link to. */ int linkAction) /* Action to perform. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr) { if (fsPtr->linkProc == NULL) { Tcl_SetErrno(ENOTSUP); return NULL; } else { return fsPtr->linkProc(pathPtr, toPtr, linkAction); } } /* * If S_IFLNK isn't defined the machine doesn't support symbolic links, so * the file can't possibly be a symbolic link. Generate an EINVAL error, * which is what happens on machines that do support symbolic links when * readlink is called for a file that isn't a symbolic link. */ #ifndef S_IFLNK errno = EINVAL; /* TODO: Change to Tcl_SetErrno()? */ #else Tcl_SetErrno(ENOENT); #endif /* S_IFLNK */ return NULL; } /* *--------------------------------------------------------------------------- * * Tcl_FSListVolumes -- * * Lists the currently mounted volumes by calling `listVolumesProc` of * each registered filesystem, and combining the results to form a list of * volumes. * * Results: * The list of volumes, in an object which has refCount 0. * * Side effects: * None * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSListVolumes(void) { FilesystemRecord *fsRecPtr; Tcl_Obj *resultPtr; /* * Call each "listVolumes" function of each registered filesystem in * succession. A non-NULL return value indicates the particular function * has succeeded. */ TclNewObj(resultPtr); fsRecPtr = FsGetFirstFilesystem(); Claim(); while (fsRecPtr != NULL) { if (fsRecPtr->fsPtr->listVolumesProc != NULL) { Tcl_Obj *thisFsVolumes = fsRecPtr->fsPtr->listVolumesProc(); if (thisFsVolumes != NULL) { Tcl_ListObjAppendList(NULL, resultPtr, thisFsVolumes); /* * The refCount of each list returned by a `listVolumesProc` * is already incremented. Do not hang onto the list, though. * It belongs to the filesystem. Add its contents to the * result we are building, and then decrement the refCount. */ Tcl_DecrRefCount(thisFsVolumes); } } fsRecPtr = fsRecPtr->nextPtr; } Disclaim(); return resultPtr; } /* *--------------------------------------------------------------------------- * * FsListMounts -- * * Lists the mounts mathing the given pattern in the given directory. * * Results: * A list, having a refCount of 0, of the matching mounts, or NULL if no * search was performed because no filesystem provided a search routine. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static Tcl_Obj * FsListMounts( Tcl_Obj *pathPtr, /* Pathname of directory to search. */ const char *pattern) /* Pattern to match against. */ { FilesystemRecord *fsRecPtr; Tcl_GlobTypeData mountsOnly = { TCL_GLOB_TYPE_MOUNT, 0, NULL, NULL }; Tcl_Obj *resultPtr = NULL; /* * Call the matchInDirectory function of each registered filesystem, * passing it 'mountsOnly'. Results accumulate in resultPtr. */ fsRecPtr = FsGetFirstFilesystem(); Claim(); while (fsRecPtr != NULL) { if (fsRecPtr->fsPtr != &tclNativeFilesystem && fsRecPtr->fsPtr->matchInDirectoryProc != NULL) { if (resultPtr == NULL) { TclNewObj(resultPtr); } fsRecPtr->fsPtr->matchInDirectoryProc(NULL, resultPtr, pathPtr, pattern, &mountsOnly); } fsRecPtr = fsRecPtr->nextPtr; } Disclaim(); return resultPtr; } /* *--------------------------------------------------------------------------- * * Tcl_FSSplitPath -- * * Splits a pathname into its components. * * Results: * A list with refCount of zero. * * Side effects: * If lenPtr is not null, sets it to the number of elements in the result. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSSplitPath( Tcl_Obj *pathPtr, /* The pathname to split. */ Tcl_Size *lenPtr) /* A place to hold the number of pathname * elements. */ { Tcl_Obj *result = NULL; /* Just to squelch gcc warnings. */ const Tcl_Filesystem *fsPtr; char separator = '/'; Tcl_Size driveNameLength; const char *p; /* * Perform platform-specific splitting. */ if (TclFSGetPathType(pathPtr, &fsPtr, &driveNameLength) == TCL_PATH_ABSOLUTE) { if (fsPtr == &tclNativeFilesystem) { return TclpNativeSplitPath(pathPtr, lenPtr); } } else { return TclpNativeSplitPath(pathPtr, lenPtr); } /* Assume each separator is a single character. */ if (fsPtr->filesystemSeparatorProc != NULL) { Tcl_Obj *sep = fsPtr->filesystemSeparatorProc(pathPtr); if (sep != NULL) { Tcl_IncrRefCount(sep); separator = TclGetString(sep)[0]; Tcl_DecrRefCount(sep); } } /* * Add the drive name as first element of the result. The drive name may * contain strange characters like colons and sequences of forward slashes * For example, 'ftp://' is a valid drive name. */ TclNewObj(result); p = TclGetString(pathPtr); Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(p, driveNameLength)); p += driveNameLength; /* * Add the remaining pathname elements to the list. */ for (;;) { const char *elementStart = p; Tcl_Size length; while ((*p != '\0') && (*p != separator)) { p++; } length = p - elementStart; if (length > 0) { Tcl_Obj *nextElt; nextElt = Tcl_NewStringObj(elementStart, length); Tcl_ListObjAppendElement(NULL, result, nextElt); } if (*p++ == '\0') { break; } } if (lenPtr != NULL) { TclListObjLength(NULL, result, lenPtr); } return result; } /* *---------------------------------------------------------------------- * * TclGetPathType -- * * Helper function used by TclFSGetPathType and TclJoinPath. * * Results: * One of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or * TCL_PATH_VOLUME_RELATIVE. * * Side effects: * See **filesystemPtrptr, *driveNameLengthPtr and **driveNameRef, * *---------------------------------------------------------------------- */ Tcl_PathType TclGetPathType( Tcl_Obj *pathPtr, /* Pathname to determine type of. */ const Tcl_Filesystem **filesystemPtrPtr, /* If not NULL, a place in which to store a * pointer to the filesystem for this pathname * if it is absolute. */ Tcl_Size *driveNameLengthPtr, /* If not NULL, a place in which to store the * length of the volume name. */ Tcl_Obj **driveNameRef) /* If not NULL, for an absolute pathname, a * place to store a pointer to an object with a * refCount of 1, and whose value is the name * of the volume. */ { Tcl_Size pathLen; const char *path = TclGetStringFromObj(pathPtr, &pathLen); Tcl_PathType type; type = TclFSNonnativePathType(path, pathLen, filesystemPtrPtr, driveNameLengthPtr, driveNameRef); if (type != TCL_PATH_ABSOLUTE) { type = TclpGetNativePathType(pathPtr, driveNameLengthPtr, driveNameRef); if ((type == TCL_PATH_ABSOLUTE) && (filesystemPtrPtr != NULL)) { *filesystemPtrPtr = &tclNativeFilesystem; } } return type; } /* *---------------------------------------------------------------------- * * TclFSNonnativePathType -- * * Helper function used by TclGetPathType. Checks whether the given * pathname starts with a string which corresponds to a file volume in * some registered filesystem other than the native one. For speed and * historical reasons the native filesystem has special hard-coded checks * dotted here and there in the filesystem code. * * Results: * One of TCL_PATH_ABSOLUTE or TCL_PATH_RELATIVE. The filesystem * reference will be set if and only if it is non-NULL and the function's * return value is TCL_PATH_ABSOLUTE. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_PathType TclFSNonnativePathType( const char *path, /* Pathname to determine the type of. */ Tcl_Size pathLen, /* Length of the pathname. */ const Tcl_Filesystem **filesystemPtrPtr, /* If not NULL, a place to store a pointer to * the filesystem for this pathname when it is * an absolute pathname. */ Tcl_Size *driveNameLengthPtr, /* If not NULL, a place to store the length of * the volume name if the pathname is absolute. */ Tcl_Obj **driveNameRef) /* If not NULL, a place to store a pointer to * an object having its refCount already * incremented, and contining the name of the * volume if the pathname is absolute. */ { FilesystemRecord *fsRecPtr; Tcl_PathType type = TCL_PATH_RELATIVE; /* * Determine whether the given pathname is an absolute pathname on some * filesystem other than the native filesystem. */ fsRecPtr = FsGetFirstFilesystem(); Claim(); while (fsRecPtr != NULL) { /* * Skip the native filesystem because otherwise some of the tests * in the Tcl testsuite might fail because some of the tests * artificially change the current platform (between win, unix) but the * list of volumes obtained by calling fsRecPtr->fsPtr->listVolumesProc * reflects the current (real) platform only. In particular, on Unix * '/' matchs the beginning of certain absolute Windows pathnames * starting '//' and those tests go wrong. * * There is another reason to skip the native filesystem: Since the * tclFilename.c code has nice fast 'absolute path' checkers, there is * no reason to waste time doing that in this frequently-called * function. It is better to save the overhead of the native * filesystem continuously returning a list of volumes. */ if ((fsRecPtr->fsPtr != &tclNativeFilesystem) && (fsRecPtr->fsPtr->listVolumesProc != NULL)) { Tcl_Size numVolumes; Tcl_Obj *thisFsVolumes = fsRecPtr->fsPtr->listVolumesProc(); if (thisFsVolumes != NULL) { if (TclListObjLength(NULL, thisFsVolumes, &numVolumes) != TCL_OK) { /* * This is VERY bad; the listVolumesProc didn't return a * valid list. Set numVolumes to -1 to skip the loop below * and just return with the current value of 'type'. * * It would be better to signal an error here, but * Tcl_Panic seems a bit excessive. */ numVolumes = TCL_INDEX_NONE; } while (numVolumes > 0) { Tcl_Obj *vol; Tcl_Size len; const char *strVol; numVolumes--; Tcl_ListObjIndex(NULL, thisFsVolumes, numVolumes, &vol); strVol = TclGetStringFromObj(vol,&len); if (pathLen < len) { continue; } if (strncmp(strVol, path, len) == 0) { type = TCL_PATH_ABSOLUTE; if (filesystemPtrPtr != NULL) { *filesystemPtrPtr = fsRecPtr->fsPtr; } if (driveNameLengthPtr != NULL) { *driveNameLengthPtr = len; } if (driveNameRef != NULL) { *driveNameRef = vol; Tcl_IncrRefCount(vol); } break; } } Tcl_DecrRefCount(thisFsVolumes); if (type == TCL_PATH_ABSOLUTE) { /* * No need to examine additional filesystems. */ break; } } } fsRecPtr = fsRecPtr->nextPtr; } Disclaim(); return type; } /* *--------------------------------------------------------------------------- * * Tcl_FSRenameFile -- * * If the two pathnames correspond to the same filesystem, call * 'renameFileProc' of that filesystem. Otherwise return the POSIX error * 'EXDEV', and -1. * * Results: * A standard Tcl error code if a rename function was called, or -1 * otherwise. * * Side effects: * A file may be renamed. * *--------------------------------------------------------------------------- */ int Tcl_FSRenameFile( Tcl_Obj *srcPathPtr, /* The pathname of a file or directory to be * renamed. */ Tcl_Obj *destPathPtr) /* The new pathname for the file. */ { int retVal = -1; const Tcl_Filesystem *fsPtr, *fsPtr2; fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr); fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr); if ((fsPtr == fsPtr2) && (fsPtr != NULL) && (fsPtr->renameFileProc != NULL)) { retVal = fsPtr->renameFileProc(srcPathPtr, destPathPtr); } if (retVal == -1) { Tcl_SetErrno(EXDEV); } return retVal; } /* *--------------------------------------------------------------------------- * * Tcl_FSCopyFile -- * * If both pathnames correspond to the same filesystem, calls * 'copyFileProc' of that filesystem. * * In the native filesystems, 'copyFileProc' copies a link itself, not the * thing the link points to. * * Results: * A standard Tcl return code if a copyFileProc was called, or -1 * otherwise. * * Side effects: * A file might be copied. The POSIX error 'EXDEV' is set if a copy * function was not called. * *--------------------------------------------------------------------------- */ int Tcl_FSCopyFile( Tcl_Obj *srcPathPtr, /* The pathname of file to be copied. */ Tcl_Obj *destPathPtr) /* The new pathname to copy the file to. */ { int retVal = -1; const Tcl_Filesystem *fsPtr, *fsPtr2; fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr); fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr); if (fsPtr == fsPtr2 && fsPtr != NULL && fsPtr->copyFileProc != NULL) { retVal = fsPtr->copyFileProc(srcPathPtr, destPathPtr); } if (retVal == -1) { Tcl_SetErrno(EXDEV); } return retVal; } /* *--------------------------------------------------------------------------- * * TclCrossFilesystemCopy -- * * Helper for Tcl_FSCopyFile and Tcl_FSLoadFile. Copies a file from one * filesystem to another, overwiting any file that already exists. * * Results: * A standard Tcl return code. * * Side effects: * A file may be copied. * *--------------------------------------------------------------------------- */ int TclCrossFilesystemCopy( Tcl_Interp *interp, /* For error messages. */ Tcl_Obj *source, /* Pathname of file to be copied. */ Tcl_Obj *target) /* Pathname to copy the file to. */ { int result = TCL_ERROR; int prot = 0666; Tcl_Channel in, out; Tcl_StatBuf sourceStatBuf; struct utimbuf tval; out = Tcl_FSOpenFileChannel(interp, target, "wb", prot); if (out == NULL) { /* * Failed to open an output channel. Bail out. */ goto done; } in = Tcl_FSOpenFileChannel(interp, source, "rb", prot); if (in == NULL) { /* * Could not open an input channel. Why didn't the caller check this? */ Tcl_CloseEx(interp, out, 0); goto done; } /* * Copy the file synchronously. TO DO: Maybe add an asynchronous option * to support virtual filesystems that are slow (e.g. network sockets). */ if (TclCopyChannel(interp, in, out, -1, NULL) == TCL_OK) { result = TCL_OK; } /* * If the copy failed, assume that copy channel left an error message. */ Tcl_CloseEx(interp, in, 0); Tcl_CloseEx(interp, out, 0); /* * Set modification date of copied file. */ if (Tcl_FSLstat(source, &sourceStatBuf) == 0) { tval.actime = Tcl_GetAccessTimeFromStat(&sourceStatBuf); tval.modtime = Tcl_GetModificationTimeFromStat(&sourceStatBuf); Tcl_FSUtime(target, &tval); } done: return result; } /* *--------------------------------------------------------------------------- * * Tcl_FSDeleteFile -- * * Calls 'deleteFileProc' of the filesystem corresponding to the given * pathname. * * Results: * A standard Tcl return code. * * Side effects: * A file may be deleted. * *--------------------------------------------------------------------------- */ int Tcl_FSDeleteFile( Tcl_Obj *pathPtr) /* Pathname of file to be removed (UTF-8). */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); int err; if (fsPtr == NULL) { err = ENOENT; } else { if (fsPtr->deleteFileProc != NULL) { return fsPtr->deleteFileProc(pathPtr); } err = ENOTSUP; } Tcl_SetErrno(err); return -1; } /* *--------------------------------------------------------------------------- * * Tcl_FSCreateDirectory -- * * Calls 'createDirectoryProc' of the filesystem corresponding to the * given pathname. * * Results: * A standard Tcl return code, or -1 if no createDirectoryProc is found. * * Side effects: * A directory may be created. POSIX error 'ENOENT' is set if no * createDirectoryProc is found. * *--------------------------------------------------------------------------- */ int Tcl_FSCreateDirectory( Tcl_Obj *pathPtr) /* Pathname of directory to create (UTF-8). */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); int err; if (fsPtr == NULL) { err = ENOENT; } else { if (fsPtr->createDirectoryProc != NULL) { return fsPtr->createDirectoryProc(pathPtr); } err = ENOTSUP; } Tcl_SetErrno(err); return -1; } /* *--------------------------------------------------------------------------- * * Tcl_FSCopyDirectory -- * * If both pathnames correspond to the same filesystem, calls * 'copyDirectoryProc' of that filesystem. * * Results: * A standard Tcl return code, or -1 if no 'copyDirectoryProc' is found. * * Side effects: * A directory may be copied. POSIX error 'EXDEV' is set if no * copyDirectoryProc is found. * *--------------------------------------------------------------------------- */ int Tcl_FSCopyDirectory( Tcl_Obj *srcPathPtr, /* The pathname of the directory to be * copied. */ Tcl_Obj *destPathPtr, /* The pathname of the target directory. */ Tcl_Obj **errorPtr) /* If not NULL, and there is an error, a place * to store a pointer to a new object, with * its refCount already incremented, and * containing the pathname name of file * causing the error. */ { int retVal = -1; const Tcl_Filesystem *fsPtr, *fsPtr2; fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr); fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr); if (fsPtr == fsPtr2 && fsPtr != NULL && fsPtr->copyDirectoryProc != NULL){ retVal = fsPtr->copyDirectoryProc(srcPathPtr, destPathPtr, errorPtr); } if (retVal == -1) { Tcl_SetErrno(EXDEV); } return retVal; } /* *--------------------------------------------------------------------------- * * Tcl_FSRemoveDirectory -- * * Calls 'removeDirectoryProc' of the filesystem corresponding to remove * pathPtr. * * Results: * A standard Tcl return code, or -1 if no removeDirectoryProc is found. * * Side effects: * A directory may be removed. POSIX error 'ENOENT' is set if no * removeDirectoryProc is found. * *--------------------------------------------------------------------------- */ int Tcl_FSRemoveDirectory( Tcl_Obj *pathPtr, /* The pathname of the directory to be removed. */ int recursive, /* If zero, removes only an empty directory. * Otherwise, removes the directory and all its * contents. */ Tcl_Obj **errorPtr) /* If not NULL and an error occurs, stores a * place to store a pointer to a new * object having a refCount of 1 and containing * the name of the file that produced an error. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr == NULL) { Tcl_SetErrno(ENOENT); return -1; } if (fsPtr->removeDirectoryProc == NULL) { Tcl_SetErrno(ENOTSUP); return -1; } if (recursive) { Tcl_Obj *cwdPtr = Tcl_FSGetCwd(NULL); if (cwdPtr != NULL) { const char *cwdStr, *normPathStr; Tcl_Size cwdLen, normLen; Tcl_Obj *normPath = Tcl_FSGetNormalizedPath(NULL, pathPtr); if (normPath != NULL) { normPathStr = TclGetStringFromObj(normPath, &normLen); cwdStr = TclGetStringFromObj(cwdPtr, &cwdLen); if ((cwdLen >= normLen) && (strncmp(normPathStr, cwdStr, normLen) == 0)) { /* * The cwd is inside the directory to be removed. Change * the cwd to [file dirname $path]. */ Tcl_Obj *dirPtr = TclPathPart(NULL, pathPtr, TCL_PATH_DIRNAME); Tcl_FSChdir(dirPtr); Tcl_DecrRefCount(dirPtr); } } Tcl_DecrRefCount(cwdPtr); } } return fsPtr->removeDirectoryProc(pathPtr, recursive, errorPtr); } /* *--------------------------------------------------------------------------- * * Tcl_FSGetFileSystemForPath -- * * Produces the filesystem that corresponds to the given pathname. * * Results: * The corresponding Tcl_Filesystem, or NULL if the pathname is invalid. * * Side effects: * The internal representation of fsPtrPtr is converted to fsPathType if * needed, and that internal representation is updated as needed. * *--------------------------------------------------------------------------- */ const Tcl_Filesystem * Tcl_FSGetFileSystemForPath( Tcl_Obj *pathPtr) { FilesystemRecord *fsRecPtr; const Tcl_Filesystem *retVal = NULL; if (pathPtr == NULL) { Tcl_Panic("Tcl_FSGetFileSystemForPath called with NULL object"); return NULL; } if (pathPtr->refCount == 0) { /* * Avoid possible segfaults or nondeterministic memory leaks where the * reference count has been incorreclty managed. */ Tcl_Panic("Tcl_FSGetFileSystemForPath called with object with refCount == 0"); return NULL; } /* Start with an up-to-date copy of the filesystem. */ fsRecPtr = FsGetFirstFilesystem(); Claim(); /* * Ensure that pathPtr is a valid pathname. */ if (TclFSEnsureEpochOk(pathPtr, &retVal) != TCL_OK) { /* not a valid pathname */ Disclaim(); return NULL; } else if (retVal != NULL) { /* * Found the filesystem in the internal representation of pathPtr. */ Disclaim(); return retVal; } /* * Call each of the "pathInFilesystem" functions in succession until the * corresponding filesystem is found. */ for (; fsRecPtr!=NULL ; fsRecPtr=fsRecPtr->nextPtr) { void *clientData = NULL; if (fsRecPtr->fsPtr->pathInFilesystemProc == NULL) { continue; } if (fsRecPtr->fsPtr->pathInFilesystemProc(pathPtr, &clientData)!=-1) { /* This is the filesystem for pathPtr. Assume the type of pathPtr * hasn't been changed by the above call to the * pathInFilesystemProc, and cache this result in the internal * representation of pathPtr. */ TclFSSetPathDetails(pathPtr, fsRecPtr->fsPtr, clientData); Disclaim(); return fsRecPtr->fsPtr; } } Disclaim(); return NULL; } /* *--------------------------------------------------------------------------- * * Tcl_FSGetNativePath -- * * See Tcl_FSGetInternalRep. * *--------------------------------------------------------------------------- */ const void * Tcl_FSGetNativePath( Tcl_Obj *pathPtr) { return Tcl_FSGetInternalRep(pathPtr, &tclNativeFilesystem); } /* *--------------------------------------------------------------------------- * * NativeFreeInternalRep -- * * Free a native internal representation. * * Results: * None. * * Side effects: * Memory is released. * *--------------------------------------------------------------------------- */ static void NativeFreeInternalRep( void *clientData) { Tcl_Free(clientData); } /* *--------------------------------------------------------------------------- * * Tcl_FSFileSystemInfo -- * Produce the type of a pathname and the type of its filesystem. * * * Results: * A list where the first item is the name of the filesystem (e.g. * "native" or "vfs"), and the second item is the type of the given * pathname within that filesystem. * * Side effects: * The internal representation of pathPtr may be converted to a * fsPathType. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSFileSystemInfo( Tcl_Obj *pathPtr) { Tcl_Obj *resPtr; const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr == NULL) { return NULL; } resPtr = Tcl_NewListObj(0, NULL); Tcl_ListObjAppendElement(NULL, resPtr, Tcl_NewStringObj(fsPtr->typeName, -1)); if (fsPtr->filesystemPathTypeProc != NULL) { Tcl_Obj *typePtr = fsPtr->filesystemPathTypeProc(pathPtr); if (typePtr != NULL) { Tcl_ListObjAppendElement(NULL, resPtr, typePtr); } } return resPtr; } /* *--------------------------------------------------------------------------- * * Tcl_FSPathSeparator -- * * Produces the separator for given pathname. * * Results: * A Tcl object having a refCount of zero. * * Side effects: * The internal representation of pathPtr may be converted to a fsPathType * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSPathSeparator( Tcl_Obj *pathPtr) { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); Tcl_Obj *resultObj; if (fsPtr == NULL) { return NULL; } if (fsPtr->filesystemSeparatorProc != NULL) { return fsPtr->filesystemSeparatorProc(pathPtr); } /* * Use the standard forward slash character if filesystem does not to * provide a filesystemSeparatorProc. */ TclNewLiteralStringObj(resultObj, "/"); return resultObj; } /* *--------------------------------------------------------------------------- * * NativeFilesystemSeparator -- * * This function, part of the native filesystem support, returns the * separator for the given pathname. * * Results: * The separator character. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static Tcl_Obj * NativeFilesystemSeparator( TCL_UNUSED(Tcl_Obj *) /*pathPtr*/) { const char *separator = NULL; switch (tclPlatform) { case TCL_PLATFORM_UNIX: separator = "/"; break; case TCL_PLATFORM_WINDOWS: separator = "\\"; break; } return Tcl_NewStringObj(separator,1); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclIcu.c0000644000175000017500000013103514726623136014564 0ustar sergeisergei/* * tclIcu.c -- * * tclIcu.c implements various Tcl commands that make use of * the ICU library if present on the system. * (Adapted from tkIcu.c) * * Copyright © 2021 Jan Nijtmans * Copyright © 2024 Ashok P. Nadkarni * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" typedef uint16_t UCharx; typedef uint32_t UChar32x; /* * Runtime linking of libicu. */ typedef enum UBreakIteratorTypex { UBRK_CHARACTERX = 0, UBRK_WORDX = 1 } UBreakIteratorTypex; typedef enum UErrorCodex { U_STRING_NOT_TERMINATED_WARNING = -124, U_AMBIGUOUS_ALIAS_WARNING = -122, U_ZERO_ERRORZ = 0, /**< No error, no warning. */ U_BUFFER_OVERFLOW_ERROR = 15, } UErrorCodex; #define U_SUCCESS(x) ((x)<=U_ZERO_ERRORZ) #define U_FAILURE(x) ((x)>U_ZERO_ERRORZ) typedef enum { UCNV_UNASSIGNED = 0, UCNV_ILLEGAL = 1, UCNV_IRREGULAR = 2, UCNV_RESET = 3, UCNV_CLOSE = 4, UCNV_CLONE = 5 } UConverterCallbackReasonx; typedef enum UNormalizationCheckResultx { UNORM_NO, UNORM_YES, UNORM_MAYBE } UNormalizationCheckResultx; typedef struct UEnumeration UEnumeration; typedef struct UCharsetDetector UCharsetDetector; typedef struct UCharsetMatch UCharsetMatch; typedef struct UBreakIterator UBreakIterator; typedef struct UNormalizer2 UNormalizer2; typedef struct UConverter UConverter; typedef struct UConverterFromUnicodeArgs UConverterFromUnicodeArgs; typedef struct UConverterToUnicodeArgs UConverterToUnicodeArgs; typedef void (*UConverterFromUCallback)(const void *context, UConverterFromUnicodeArgs *args, const UCharx *codeUnits, int32_t length, UChar32x codePoint, UConverterCallbackReasonx reason, UErrorCodex *pErrorCode); typedef void (*UConverterToUCallback)(const void *context, UConverterToUnicodeArgs *args, const char *codeUnits, int32_t length, UConverterCallbackReasonx reason, UErrorCodex *pErrorCode); /* * Prototypes for ICU functions sorted by category. */ typedef void (*fn_u_cleanup)(void); typedef const char *(*fn_u_errorName)(UErrorCodex); typedef UCharx *(*fn_u_strFromUTF32)(UCharx *dest, int32_t destCapacity, int32_t *pDestLength, const UChar32x *src, int32_t srcLength, UErrorCodex *pErrorCode); typedef UCharx *(*fn_u_strFromUTF32WithSub)(UCharx *dest, int32_t destCapacity, int32_t *pDestLength, const UChar32x *src, int32_t srcLength, UChar32x subchar, int32_t *pNumSubstitutions, UErrorCodex *pErrorCode); typedef UChar32x *(*fn_u_strToUTF32)(UChar32x *dest, int32_t destCapacity, int32_t *pDestLength, const UCharx *src, int32_t srcLength, UErrorCodex *pErrorCode); typedef UChar32x *(*fn_u_strToUTF32WithSub)(UChar32x *dest, int32_t destCapacity, int32_t *pDestLength, const UCharx *src, int32_t srcLength, UChar32x subchar, int32_t *pNumSubstitutions, UErrorCodex *pErrorCode); typedef void (*fn_ucnv_close)(UConverter *); typedef uint16_t (*fn_ucnv_countAliases)(const char *, UErrorCodex *); typedef int32_t (*fn_ucnv_countAvailable)(void); typedef int32_t (*fn_ucnv_fromUChars)(UConverter *, char *dest, int32_t destCapacity, const UCharx *src, int32_t srcLen, UErrorCodex *); typedef const char *(*fn_ucnv_getAlias)(const char *, uint16_t, UErrorCodex *); typedef const char *(*fn_ucnv_getAvailableName)(int32_t); typedef UConverter *(*fn_ucnv_open)(const char *converterName, UErrorCodex *); typedef void (*fn_ucnv_setFromUCallBack)(UConverter *, UConverterFromUCallback newAction, const void *newContext, UConverterFromUCallback *oldAction, const void **oldContext, UErrorCodex *err); typedef void (*fn_ucnv_setToUCallBack)(UConverter *, UConverterToUCallback newAction, const void *newContext, UConverterToUCallback *oldAction, const void **oldContext, UErrorCodex *err); typedef int32_t (*fn_ucnv_toUChars)(UConverter *, UCharx *dest, int32_t destCapacity, const char *src, int32_t srcLen, UErrorCodex *); typedef UConverterFromUCallback fn_UCNV_FROM_U_CALLBACK_STOP; typedef UConverterToUCallback fn_UCNV_TO_U_CALLBACK_STOP; typedef UBreakIterator *(*fn_ubrk_open)( UBreakIteratorTypex, const char *, const uint16_t *, int32_t, UErrorCodex *); typedef void (*fn_ubrk_close)(UBreakIterator *); typedef int32_t (*fn_ubrk_preceding)(UBreakIterator *, int32_t); typedef int32_t (*fn_ubrk_following)(UBreakIterator *, int32_t); typedef int32_t (*fn_ubrk_previous)(UBreakIterator *); typedef int32_t (*fn_ubrk_next)(UBreakIterator *); typedef void (*fn_ubrk_setText)( UBreakIterator *, const void *, int32_t, UErrorCodex *); typedef UCharsetDetector * (*fn_ucsdet_open)(UErrorCodex *status); typedef void (*fn_ucsdet_close)(UCharsetDetector *ucsd); typedef void (*fn_ucsdet_setText)(UCharsetDetector *ucsd, const char *textIn, int32_t len, UErrorCodex *status); typedef const char * (*fn_ucsdet_getName)( const UCharsetMatch *ucsm, UErrorCodex *status); typedef UEnumeration * (*fn_ucsdet_getAllDetectableCharsets)( UCharsetDetector *ucsd, UErrorCodex *status); typedef const UCharsetMatch * (*fn_ucsdet_detect)( UCharsetDetector *ucsd, UErrorCodex *status); typedef const UCharsetMatch ** (*fn_ucsdet_detectAll)( UCharsetDetector *ucsd, int32_t *matchesFound, UErrorCodex *status); typedef void (*fn_uenum_close)(UEnumeration *); typedef int32_t (*fn_uenum_count)(UEnumeration *, UErrorCodex *); typedef const char *(*fn_uenum_next)(UEnumeration *, int32_t *, UErrorCodex *); typedef UNormalizer2 *(*fn_unorm2_getNFCInstance)(UErrorCodex *); typedef UNormalizer2 *(*fn_unorm2_getNFDInstance)(UErrorCodex *); typedef UNormalizer2 *(*fn_unorm2_getNFKCInstance)(UErrorCodex *); typedef UNormalizer2 *(*fn_unorm2_getNFKDInstance)(UErrorCodex *); typedef int32_t (*fn_unorm2_normalize)(const UNormalizer2 *, const UCharx *, int32_t, UCharx *, int32_t, UErrorCodex *); #define FIELD(name) fn_ ## name _ ## name static struct { size_t nopen; /* Total number of references to ALL libraries */ /* * Depending on platform, ICU symbols may be distributed amongst * multiple libraries. For current functionality at most 2 needed. * Order of library loading is not guaranteed. */ Tcl_LoadHandle libs[2]; FIELD(u_cleanup); FIELD(u_errorName); FIELD(u_strFromUTF32); FIELD(u_strFromUTF32WithSub); FIELD(u_strToUTF32); FIELD(u_strToUTF32WithSub); FIELD(ubrk_open); FIELD(ubrk_close); FIELD(ubrk_preceding); FIELD(ubrk_following); FIELD(ubrk_previous); FIELD(ubrk_next); FIELD(ubrk_setText); FIELD(ucnv_close); FIELD(ucnv_countAliases); FIELD(ucnv_countAvailable); FIELD(ucnv_fromUChars); FIELD(ucnv_getAlias); FIELD(ucnv_getAvailableName); FIELD(ucnv_open); FIELD(ucnv_setFromUCallBack); FIELD(ucnv_setToUCallBack); FIELD(ucnv_toUChars); FIELD(UCNV_FROM_U_CALLBACK_STOP); FIELD(UCNV_TO_U_CALLBACK_STOP); FIELD(ucsdet_close); FIELD(ucsdet_detect); FIELD(ucsdet_detectAll); FIELD(ucsdet_getAllDetectableCharsets); FIELD(ucsdet_getName); FIELD(ucsdet_open); FIELD(ucsdet_setText); FIELD(uenum_close); FIELD(uenum_count); FIELD(uenum_next); FIELD(unorm2_getNFCInstance); FIELD(unorm2_getNFDInstance); FIELD(unorm2_getNFKCInstance); FIELD(unorm2_getNFKDInstance); FIELD(unorm2_normalize); } icu_fns = { 0, {NULL, NULL}, /* Reference count, library handles */ NULL, NULL, NULL, NULL, NULL, NULL, /* u_* */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* ubrk* */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* ucnv_* .. */ NULL, NULL, NULL, /* .. ucnv_ */ NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* ucsdet* */ NULL, NULL, NULL, /* uenum_* */ NULL, NULL, NULL, NULL, NULL, /* unorm2_* */ }; #define u_cleanup icu_fns._u_cleanup #define u_errorName icu_fns._u_errorName #define u_strFromUTF32 icu_fns._u_strFromUTF32 #define u_strFromUTF32WithSub icu_fns._u_strFromUTF32WithSub #define u_strToUTF32 icu_fns._u_strToUTF32 #define u_strToUTF32WithSub icu_fns._u_strToUTF32WithSub #define ubrk_open icu_fns._ubrk_open #define ubrk_close icu_fns._ubrk_close #define ubrk_preceding icu_fns._ubrk_preceding #define ubrk_following icu_fns._ubrk_following #define ubrk_previous icu_fns._ubrk_previous #define ubrk_next icu_fns._ubrk_next #define ubrk_setText icu_fns._ubrk_setText #define ucnv_close icu_fns._ucnv_close #define ucnv_countAliases icu_fns._ucnv_countAliases #define ucnv_countAvailable icu_fns._ucnv_countAvailable #define ucnv_fromUChars icu_fns._ucnv_fromUChars #define ucnv_getAlias icu_fns._ucnv_getAlias #define ucnv_getAvailableName icu_fns._ucnv_getAvailableName #define ucnv_open icu_fns._ucnv_open #define ucnv_setFromUCallBack icu_fns._ucnv_setFromUCallBack #define ucnv_setToUCallBack icu_fns._ucnv_setToUCallBack #define ucnv_toUChars icu_fns._ucnv_toUChars #define UCNV_FROM_U_CALLBACK_STOP icu_fns._UCNV_FROM_U_CALLBACK_STOP #define UCNV_TO_U_CALLBACK_STOP icu_fns._UCNV_TO_U_CALLBACK_STOP #define ucsdet_close icu_fns._ucsdet_close #define ucsdet_detect icu_fns._ucsdet_detect #define ucsdet_detectAll icu_fns._ucsdet_detectAll #define ucsdet_getAllDetectableCharsets icu_fns._ucsdet_getAllDetectableCharsets #define ucsdet_getName icu_fns._ucsdet_getName #define ucsdet_open icu_fns._ucsdet_open #define ucsdet_setText icu_fns._ucsdet_setText #define uenum_next icu_fns._uenum_next #define uenum_close icu_fns._uenum_close #define uenum_count icu_fns._uenum_count #define unorm2_getNFCInstance icu_fns._unorm2_getNFCInstance #define unorm2_getNFDInstance icu_fns._unorm2_getNFDInstance #define unorm2_getNFKCInstance icu_fns._unorm2_getNFKCInstance #define unorm2_getNFKDInstance icu_fns._unorm2_getNFKDInstance #define unorm2_normalize icu_fns._unorm2_normalize TCL_DECLARE_MUTEX(icu_mutex); /* Options used by multiple normalization functions */ static const char *normalizationForms[] = {"nfc", "nfd", "nfkc", "nfkd", NULL}; typedef enum { MODE_NFC, MODE_NFD, MODE_NFKC, MODE_NFKD } NormalizationMode; /* Error handlers. */ static int FunctionNotAvailableError( Tcl_Interp *interp) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "ICU function not available", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ICU", "UNSUPPORTED_OP", NULL); } return TCL_ERROR; } static int IcuError( Tcl_Interp *interp, const char *message, UErrorCodex code) { if (interp) { const char *codeMessage = NULL; if (u_errorName) { codeMessage = u_errorName(code); } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s%sICU error (%d): %s", message ? message : "", message ? ". " : "", code, codeMessage ? codeMessage : "")); Tcl_SetErrorCode(interp, "TCL", "ICU", codeMessage, NULL); } return TCL_ERROR; } /* * Detect the likely encoding of the string encoded in the given byte array. */ static int DetectEncoding( Tcl_Interp *interp, Tcl_Obj *objPtr, int all) { Tcl_Size len; const char *bytes; const UCharsetMatch *match; const UCharsetMatch **matches; int nmatches; int ret; // Confirm we have the profile of functions we need. if (ucsdet_open == NULL || ucsdet_setText == NULL || ucsdet_detect == NULL || ucsdet_detectAll == NULL || ucsdet_getName == NULL || ucsdet_close == NULL) { return FunctionNotAvailableError(interp); } bytes = (char *) Tcl_GetBytesFromObj(interp, objPtr, &len); if (bytes == NULL) { return TCL_ERROR; } UErrorCodex status = U_ZERO_ERRORZ; UCharsetDetector* csd = ucsdet_open(&status); if (U_FAILURE(status)) { return IcuError(interp, "Could not open charset detector", status); } ucsdet_setText(csd, bytes, len, &status); if (U_FAILURE(status)) { IcuError(interp, "Could not set detection text", status); ucsdet_close(csd); return TCL_ERROR; } if (all) { matches = ucsdet_detectAll(csd, &nmatches, &status); } else { match = ucsdet_detect(csd, &status); matches = &match; nmatches = match ? 1 : 0; } if (U_FAILURE(status) || nmatches == 0) { ret = IcuError(interp, "Could not detect character set", status); } else { int i; Tcl_Obj *resultObj = Tcl_NewListObj(nmatches, NULL); for (i = 0; i < nmatches; ++i) { const char *name = ucsdet_getName(matches[i], &status); if (U_FAILURE(status) || name == NULL) { name = "unknown"; status = U_ZERO_ERRORZ; /* Reset on failure */ } Tcl_ListObjAppendElement( NULL, resultObj, Tcl_NewStringObj(name, TCL_AUTO_LENGTH)); } Tcl_SetObjResult(interp, resultObj); ret = TCL_OK; } ucsdet_close(csd); return ret; } static int DetectableEncodings( Tcl_Interp *interp) { // Confirm we have the profile of functions we need. if (ucsdet_open == NULL || ucsdet_getAllDetectableCharsets == NULL || ucsdet_close == NULL || uenum_next == NULL || uenum_count == NULL || uenum_close == NULL) { return FunctionNotAvailableError(interp); } UErrorCodex status = U_ZERO_ERRORZ; UCharsetDetector *csd = ucsdet_open(&status); if (U_FAILURE(status)) { return IcuError(interp, "Could not open charset detector", status); } int ret; UEnumeration *enumerator = ucsdet_getAllDetectableCharsets(csd, &status); if (U_FAILURE(status) || enumerator == NULL) { IcuError(interp, "Could not get list of detectable encodings", status); ret = TCL_ERROR; } else { int32_t count = uenum_count(enumerator, &status); if (U_FAILURE(status)) { IcuError(interp, "Could not get charset enumerator count", status); ret = TCL_ERROR; } else { int i; Tcl_Obj *resultObj = Tcl_NewListObj(0, NULL); for (i = 0; i < count; ++i) { const char *name; int32_t len; name = uenum_next(enumerator, &len, &status); if (name == NULL || U_FAILURE(status)) { name = "unknown"; len = 7; status = U_ZERO_ERRORZ; /* Reset on error */ } Tcl_ListObjAppendElement( NULL, resultObj, Tcl_NewStringObj(name, len)); } Tcl_SetObjResult(interp, resultObj); ret = TCL_OK; } uenum_close(enumerator); } ucsdet_close(csd); return ret; } /* *------------------------------------------------------------------------ * * IcuObjToUCharDString -- * * Encodes a Tcl_Obj value in ICU UChars and stores in dsPtr. * * Results: * Return TCL_OK / TCL_ERROR. * * Side effects: * *dsPtr should be cleared by caller only if return code is TCL_OK. * *------------------------------------------------------------------------ */ static int IcuObjToUCharDString( Tcl_Interp *interp, Tcl_Obj *objPtr, int strict, Tcl_DString *dsPtr) { Tcl_Encoding encoding; /* * TODO - not the most efficient to get an encoding every time. * However, we cannot use Tcl_UtfToChar16DString as that blithely * ignores invalid or ill-formed UTF8 strings. */ encoding = Tcl_GetEncoding(interp, "utf-16"); if (encoding == NULL) { return TCL_ERROR; } int result; char *s; Tcl_Size len; s = Tcl_GetStringFromObj(objPtr, &len); result = Tcl_UtfToExternalDStringEx(interp, encoding, s, len, strict ? TCL_ENCODING_PROFILE_STRICT : TCL_ENCODING_PROFILE_REPLACE, dsPtr, NULL); if (result != TCL_OK) { Tcl_DStringFree(dsPtr); /* Must be done on error */ /* TCL_CONVER_* errors -> TCL_ERROR */ result = TCL_ERROR; } Tcl_FreeEncoding(encoding); return result; } /* *------------------------------------------------------------------------ * * IcuObjFromUCharDString -- * * Stores a Tcl_Obj value by decoding ICU UChars in dsPtr. * * Results: * Return Tcl_Obj or NULL on error. * * Side effects: * None. * *------------------------------------------------------------------------ */ static Tcl_Obj * IcuObjFromUCharDString( Tcl_Interp *interp, Tcl_DString *dsPtr, int strict) { Tcl_Encoding encoding; /* * TODO - not the most efficient to get an encoding every time. * However, we cannot use Tcl_UtfToChar16DString as that blithely * ignores invalid or ill-formed UTF8 strings. */ encoding = Tcl_GetEncoding(interp, "utf-16"); if (encoding == NULL) { return NULL; } Tcl_Obj *objPtr = NULL; char *s = Tcl_DStringValue(dsPtr); Tcl_Size len = Tcl_DStringLength(dsPtr); Tcl_DString dsOut; int result; result = Tcl_ExternalToUtfDStringEx(interp, encoding, s, len, strict ? TCL_ENCODING_PROFILE_STRICT : TCL_ENCODING_PROFILE_REPLACE, &dsOut, NULL); if (result == TCL_OK) { objPtr = Tcl_DStringToObj(&dsOut); /* Clears dsPtr! */ } Tcl_FreeEncoding(encoding); return objPtr; } /* *------------------------------------------------------------------------ * * EncodingDetectObjCmd -- * * Implements the Tcl command EncodingDetect. * encdetect - returns names of all detectable encodings * encdetect BYTES ?-all? - return detected encoding(s) * * Results: * TCL_OK - Success. * TCL_ERROR - Error. * * Side effects: * Interpreter result holds result or error message. * *------------------------------------------------------------------------ */ static int IcuDetectObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc > 3) { Tcl_WrongNumArgs(interp, 1 , objv, "?bytes ?-all??"); return TCL_ERROR; } if (objc == 1) { return DetectableEncodings(interp); } int all = 0; if (objc == 3) { if (strcmp("-all", Tcl_GetString(objv[2]))) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Invalid option %s, must be \"-all\"", Tcl_GetString(objv[2]))); return TCL_ERROR; } all = 1; } return DetectEncoding(interp, objv[1], all); } /* *------------------------------------------------------------------------ * * IcuConverterNamesObjCmd -- * * Sets interp result to list of available ICU converters. * * Results: * TCL_OK - Success. * TCL_ERROR - Error. * * Side effects: * Interpreter result holds list of converter names. * *------------------------------------------------------------------------ */ static int IcuConverterNamesObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc != 1) { Tcl_WrongNumArgs(interp, 1 , objv, ""); return TCL_ERROR; } if (ucnv_countAvailable == NULL || ucnv_getAvailableName == NULL) { return FunctionNotAvailableError(interp); } int32_t count = ucnv_countAvailable(); if (count <= 0) { return TCL_OK; } Tcl_Obj *resultObj = Tcl_NewListObj(count, NULL); int32_t i; for (i = 0; i < count; ++i) { const char *name = ucnv_getAvailableName(i); if (name) { Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(name, TCL_AUTO_LENGTH)); } } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* *------------------------------------------------------------------------ * * IcuConverterAliasesObjCmd -- * * Sets interp result to list of available ICU converters. * * Results: * TCL_OK - Success. * TCL_ERROR - Error. * * Side effects: * Interpreter result holds list of converter names. * *------------------------------------------------------------------------ */ static int IcuConverterAliasesObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc != 2) { Tcl_WrongNumArgs(interp, 1 , objv, "convertername"); return TCL_ERROR; } if (ucnv_countAliases == NULL || ucnv_getAlias == NULL) { return FunctionNotAvailableError(interp); } const char *name = Tcl_GetString(objv[1]); UErrorCodex status = U_ZERO_ERRORZ; uint16_t count = ucnv_countAliases(name, &status); if (status != U_AMBIGUOUS_ALIAS_WARNING && U_FAILURE(status)) { return IcuError(interp, "Could not get aliases", status); } if (count <= 0) { return TCL_OK; } Tcl_Obj *resultObj = Tcl_NewListObj(count, NULL); uint16_t i; for (i = 0; i < count; ++i) { status = U_ZERO_ERRORZ; /* Reset in case U_AMBIGUOUS_ALIAS_WARNING */ const char *aliasName = ucnv_getAlias(name, i, &status); if (status != U_AMBIGUOUS_ALIAS_WARNING && U_FAILURE(status)) { status = U_ZERO_ERRORZ; /* Reset error for next iteration */ continue; } if (aliasName) { Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(aliasName, TCL_AUTO_LENGTH)); } } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* *------------------------------------------------------------------------ * * IcuConverttoDString -- * * Converts a string in ICU default encoding to the specified encoding. * * Results: * TCL_OK / TCL_ERROR * * Side effects: * On success, encoded string is stored in output dsOutPtr * *------------------------------------------------------------------------ */ static int IcuConverttoDString( Tcl_Interp *interp, Tcl_DString *dsInPtr, /* Input UTF16 */ const char *icuEncName, int strict, Tcl_DString *dsOutPtr) /* Output encoded string. */ { if (ucnv_open == NULL || ucnv_close == NULL || ucnv_fromUChars == NULL || UCNV_FROM_U_CALLBACK_STOP == NULL) { return FunctionNotAvailableError(interp); } UErrorCodex status = U_ZERO_ERRORZ; UConverter *ucnvPtr = ucnv_open(icuEncName, &status); if (ucnvPtr == NULL) { return IcuError(interp, "Could not get encoding converter", status); } if (strict) { ucnv_setFromUCallBack(ucnvPtr, UCNV_FROM_U_CALLBACK_STOP, NULL, NULL, NULL, &status); if (U_FAILURE(status)) { /* TODO - use ucnv_getInvalidUChars to retrieve failing chars */ ucnv_close(ucnvPtr); return IcuError(interp, "Could not set conversion callback", status); } } UCharx *utf16 = (UCharx *) Tcl_DStringValue(dsInPtr); Tcl_Size utf16len = Tcl_DStringLength(dsInPtr) / sizeof(UCharx); Tcl_Size dstLen, dstCapacity; if (utf16len > INT_MAX) { Tcl_SetObjResult( interp, Tcl_NewStringObj("Max length supported by ICU exceeded.", TCL_INDEX_NONE)); return TCL_ERROR; } dstCapacity = utf16len; Tcl_DStringInit(dsOutPtr); Tcl_DStringSetLength(dsOutPtr, dstCapacity); dstLen = ucnv_fromUChars(ucnvPtr, Tcl_DStringValue(dsOutPtr), dstCapacity, utf16, utf16len, &status); if (U_FAILURE(status)) { switch (status) { case U_STRING_NOT_TERMINATED_WARNING: break; /* We don't care */ case U_BUFFER_OVERFLOW_ERROR: Tcl_DStringSetLength(dsOutPtr, dstLen); status = U_ZERO_ERRORZ; /* Reset before call */ dstLen = ucnv_fromUChars(ucnvPtr, Tcl_DStringValue(dsOutPtr), dstLen, utf16, utf16len, &status); if (U_SUCCESS(status)) { break; } /* FALLTHRU */ default: Tcl_DStringFree(dsOutPtr); ucnv_close(ucnvPtr); return IcuError(interp, "ICU error while encoding", status); } } Tcl_DStringSetLength(dsOutPtr, dstLen); ucnv_close(ucnvPtr); return TCL_OK; } /* *------------------------------------------------------------------------ * * IcuBytesToUCharDString -- * * Converts encoded bytes to ICU UChars in a Tcl_DString * * Results: * TCL_OK / TCL_ERROR * * Side effects: * On success, encoded string is stored in output dsOutPtr * *------------------------------------------------------------------------ */ static int IcuBytesToUCharDString( Tcl_Interp *interp, const unsigned char *bytes, Tcl_Size nbytes, const char *icuEncName, int strict, Tcl_DString *dsOutPtr) /* Output UChar string. */ { if (ucnv_open == NULL || ucnv_close == NULL || ucnv_toUChars == NULL || UCNV_TO_U_CALLBACK_STOP == NULL) { return FunctionNotAvailableError(interp); } if (nbytes > INT_MAX) { Tcl_SetObjResult(interp, Tcl_NewStringObj("Max length supported by ICU exceeded.", TCL_INDEX_NONE)); return TCL_ERROR; } UErrorCodex status = U_ZERO_ERRORZ; UConverter *ucnvPtr = ucnv_open(icuEncName, &status); if (ucnvPtr == NULL) { return IcuError(interp, "Could not get encoding converter", status); } if (strict) { ucnv_setToUCallBack(ucnvPtr, UCNV_TO_U_CALLBACK_STOP, NULL, NULL, NULL, &status); if (U_FAILURE(status)) { /* TODO - use ucnv_getInvalidUChars to retrieve failing chars */ ucnv_close(ucnvPtr); return IcuError(interp, "Could not set conversion callback", status); } } int dstLen; int dstCapacity = (int) nbytes; /* In UChar's */ Tcl_DStringInit(dsOutPtr); Tcl_DStringSetLength(dsOutPtr, dstCapacity); dstLen = ucnv_toUChars(ucnvPtr, (UCharx *)Tcl_DStringValue(dsOutPtr), dstCapacity, (const char *)bytes, nbytes, &status); if (U_FAILURE(status)) { switch (status) { case U_STRING_NOT_TERMINATED_WARNING: break; /* We don't care */ case U_BUFFER_OVERFLOW_ERROR: dstCapacity = sizeof(UCharx) * dstLen; Tcl_DStringSetLength(dsOutPtr, dstCapacity); status = U_ZERO_ERRORZ; /* Reset before call */ dstLen = ucnv_toUChars(ucnvPtr, (UCharx *)Tcl_DStringValue(dsOutPtr), dstCapacity, (const char *)bytes, nbytes, &status); if (U_SUCCESS(status)) { break; } /* FALLTHRU */ default: Tcl_DStringFree(dsOutPtr); ucnv_close(ucnvPtr); return IcuError(interp, "ICU error while decoding", status); } } Tcl_DStringSetLength(dsOutPtr, sizeof(UCharx)*dstLen); ucnv_close(ucnvPtr); return TCL_OK; } /* *------------------------------------------------------------------------ * * IcuNormalizeUCharDString -- * * Normalizes the UTF-16 encoded data * * Results: * TCL_OK / TCL_ERROR * * Side effects: * Normalized data is stored in dsOutPtr which should only be * Tcl_DStringFree-ed if return code is TCL_OK. * *------------------------------------------------------------------------ */ static int IcuNormalizeUCharDString( Tcl_Interp *interp, Tcl_DString *dsInPtr, /* Input UTF16 */ NormalizationMode mode, Tcl_DString *dsOutPtr) /* Output normalized UTF16. */ { typedef UNormalizer2 *(*normFn)(UErrorCodex *); normFn fn = NULL; switch (mode) { case MODE_NFC: fn = unorm2_getNFCInstance; break; case MODE_NFD: fn = unorm2_getNFDInstance; break; case MODE_NFKC: fn = unorm2_getNFKCInstance; break; case MODE_NFKD: fn = unorm2_getNFKDInstance; break; } if (fn == NULL || unorm2_normalize == NULL) { return FunctionNotAvailableError(interp); } UErrorCodex status = U_ZERO_ERRORZ; UNormalizer2 *normalizer = fn(&status); if (U_FAILURE(status)) { return IcuError(interp, "Could not get ICU normalizer", status); } UCharx *utf16; Tcl_Size utf16len; UCharx *normPtr; int32_t normLen; utf16 = (UCharx *) Tcl_DStringValue(dsInPtr); utf16len = Tcl_DStringLength(dsInPtr) / sizeof(UCharx); if (utf16len > INT_MAX) { Tcl_SetObjResult(interp, Tcl_NewStringObj("Max length supported by ICU exceeded.", TCL_INDEX_NONE)); return TCL_ERROR; } Tcl_DStringInit(dsOutPtr); Tcl_DStringSetLength(dsOutPtr, utf16len * sizeof(UCharx)); normPtr = (UCharx *) Tcl_DStringValue(dsOutPtr); normLen = unorm2_normalize( normalizer, utf16, utf16len, normPtr, utf16len, &status); if (U_FAILURE(status)) { switch (status) { case U_STRING_NOT_TERMINATED_WARNING: /* No problem, don't need it terminated */ break; case U_BUFFER_OVERFLOW_ERROR: /* Expand buffer */ Tcl_DStringSetLength(dsOutPtr, normLen * sizeof(UCharx)); normPtr = (UCharx *) Tcl_DStringValue(dsOutPtr); status = U_ZERO_ERRORZ; /* Need to clear error! */ normLen = unorm2_normalize( normalizer, utf16, utf16len, normPtr, normLen, &status); if (U_SUCCESS(status)) { break; } /* FALLTHRU */ default: Tcl_DStringFree(dsOutPtr); return IcuError(interp, "String normalization failed", status); } } Tcl_DStringSetLength(dsOutPtr, normLen * sizeof(UCharx)); return TCL_OK; } /* * Common function for parsing convert options. */ static int IcuParseConvertOptions( Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], int *strictPtr, Tcl_Obj **failindexVarPtr) { if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "?-profile PROFILE? ICUENCNAME STRING"); return TCL_ERROR; } objc -= 2; /* truncate fixed arguments */ /* Use GetIndexFromObj for option parsing so -failindex can be added later */ static const char *optNames[] = {"-profile", "-failindex", NULL}; enum { OPT_PROFILE, OPT_FAILINDEX } opt; int i; int strict = 1; for (i = 1; i < objc; ++i) { if (Tcl_GetIndexFromObj( interp, objv[i], optNames, "option", 0, &opt) != TCL_OK) { return TCL_ERROR; } ++i; if (i == objc) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("Missing value for option %s.", Tcl_GetString(objv[i - 1]))); return TCL_ERROR; } const char *s = Tcl_GetString(objv[i]); switch (opt) { case OPT_PROFILE: if (!strcmp(s, "replace")) { strict = 0; } else if (strcmp(s, "strict")) { Tcl_SetObjResult( interp, Tcl_ObjPrintf("Invalid value \"%s\" supplied for option" " \"-profile\". Must be \"strict\" or \"replace\".", s)); return TCL_ERROR; } break; case OPT_FAILINDEX: /* TBD */ Tcl_SetObjResult(interp, Tcl_NewStringObj("Option -failindex not implemented.", TCL_INDEX_NONE)); return TCL_ERROR; } } *strictPtr = strict; *failindexVarPtr = NULL; return TCL_OK; } /* *------------------------------------------------------------------------ * * IcuConvertfromObjCmd -- * * Implements the Tcl command "icu convertfrom" * icu convertfrom ?-profile replace|strict? encoding string * * Results: * TCL_OK - Success. * TCL_ERROR - Error. * * Side effects: * Interpreter result holds result or error message. * *------------------------------------------------------------------------ */ static int IcuConvertfromObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int strict; Tcl_Obj *failindexVar; if (IcuParseConvertOptions(interp, objc, objv, &strict, &failindexVar) != TCL_OK) { return TCL_ERROR; } Tcl_Size nbytes; const unsigned char *bytes = Tcl_GetBytesFromObj(interp, objv[objc-1], &nbytes); if (bytes == NULL) { return TCL_ERROR; } Tcl_DString ds; if (IcuBytesToUCharDString(interp, bytes, nbytes, Tcl_GetString(objv[objc-2]), strict, &ds) != TCL_OK) { return TCL_ERROR; } Tcl_Obj *resultObj = IcuObjFromUCharDString(interp, &ds, strict); if (resultObj) { Tcl_SetObjResult(interp, resultObj); return TCL_OK; } else { return TCL_ERROR; } } /* *------------------------------------------------------------------------ * * IcuConverttoObjCmd -- * * Implements the Tcl command "icu convertto" * icu convertto ?-profile replace|strict? encoding string * * Results: * TCL_OK - Success. * TCL_ERROR - Error. * * Side effects: * Interpreter result holds result or error message. * *------------------------------------------------------------------------ */ static int IcuConverttoObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int strict; Tcl_Obj *failindexVar; if (IcuParseConvertOptions(interp, objc, objv, &strict, &failindexVar) != TCL_OK) { return TCL_ERROR; } Tcl_DString dsIn; Tcl_DString dsOut; if (IcuObjToUCharDString(interp, objv[objc - 1], strict, &dsIn) != TCL_OK || IcuConverttoDString(interp, &dsIn, Tcl_GetString(objv[objc-2]), strict, &dsOut) != TCL_OK) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((unsigned char *)Tcl_DStringValue(&dsOut), Tcl_DStringLength(&dsOut))); Tcl_DStringFree(&dsOut); return TCL_OK; } /* *------------------------------------------------------------------------ * * IcuNormalizeObjCmd -- * * Implements the Tcl command "icu normalize" * icu normalize ?-profile replace|strict? ?-mode nfc|nfd|nfkc|nfkd? string * * Results: * TCL_OK - Success. * TCL_ERROR - Error. * * Side effects: * Interpreter result holds result or error message. * *------------------------------------------------------------------------ */ static int IcuNormalizeObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *optNames[] = {"-profile", "-mode", NULL}; enum { OPT_PROFILE, OPT_MODE } opt; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "?-profile PROFILE? ?-mode MODE? STRING"); return TCL_ERROR; } int i; int strict = 1; NormalizationMode mode = MODE_NFC; for (i = 1; i < objc - 1; ++i) { if (Tcl_GetIndexFromObj( interp, objv[i], optNames, "option", 0, &opt) != TCL_OK) { return TCL_ERROR; } ++i; if (i == (objc-1)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("Missing value for option %s.", Tcl_GetString(objv[i - 1]))); return TCL_ERROR; } const char *s = Tcl_GetString(objv[i]); switch (opt) { case OPT_PROFILE: if (!strcmp(s, "replace")) { strict = 0; } else if (strcmp(s, "strict")) { Tcl_SetObjResult( interp, Tcl_ObjPrintf("Invalid value \"%s\" supplied for option \"-profile\". Must be " "\"strict\" or \"replace\".", s)); return TCL_ERROR; } break; case OPT_MODE: if (Tcl_GetIndexFromObj(interp, objv[i], normalizationForms, "normalization mode", 0, &mode) != TCL_OK) { return TCL_ERROR; } break; } } Tcl_DString dsIn; Tcl_DString dsNorm; if (IcuObjToUCharDString(interp, objv[objc - 1], strict, &dsIn) != TCL_OK || IcuNormalizeUCharDString(interp, &dsIn, mode, &dsNorm) != TCL_OK) { return TCL_ERROR; } Tcl_DStringFree(&dsIn); Tcl_Obj *objPtr = IcuObjFromUCharDString(interp, &dsNorm, strict); Tcl_DStringFree(&dsNorm); if (objPtr) { Tcl_SetObjResult(interp, objPtr); return TCL_OK; } else { return TCL_ERROR; } } /* *------------------------------------------------------------------------ * * TclIcuCleanup -- * * Called whenever a command referencing the ICU function table is * deleted. When the reference count drops to zero, the table is released * and the ICU shared libraries are unloaded. * *------------------------------------------------------------------------ */ static void TclIcuCleanup( TCL_UNUSED(void *)) { Tcl_MutexLock(&icu_mutex); if (icu_fns.nopen-- <= 1) { int i; if (u_cleanup != NULL) { u_cleanup(); } for (i = 0; i < (int)(sizeof(icu_fns.libs) / sizeof(icu_fns.libs[0])); ++i) { if (icu_fns.libs[i] != NULL) { Tcl_FSUnloadFile(NULL, icu_fns.libs[i]); } } memset(&icu_fns, 0, sizeof(icu_fns)); } Tcl_MutexUnlock(&icu_mutex); } /* *------------------------------------------------------------------------ * * IcuFindSymbol -- * * Finds an ICU symbol in a shared library and returns its value. * * Caller must be holding icu_mutex lock. * * Results: * Returns the symbol value or NULL if not found. * *------------------------------------------------------------------------ */ static void * IcuFindSymbol( Tcl_LoadHandle loadH, /* Handle to shared library containing symbol */ const char *name, /* Name of function */ const char *suffix /* Suffix that may be present */ ) { /* * ICU symbols may have a version suffix depending on how it was built. * Rather than try both forms every time, suffixConvention remembers if a * suffix is needed (all functions will have it, or none will) * 0 - don't know, 1 - have suffix, -1 - no suffix */ static int suffixConvention = 0; char symbol[256]; void *value = NULL; /* Note we only update suffixConvention on a positive result */ strcpy(symbol, name); if (suffixConvention <= 0) { /* Either don't need suffix or don't know if we do */ value = Tcl_FindSymbol(NULL, loadH, symbol); if (value) { suffixConvention = -1; /* Remember that no suffixes present */ return value; } } if (suffixConvention >= 0) { /* Either need suffix or don't know if we do */ strcat(symbol, suffix); value = Tcl_FindSymbol(NULL, loadH, symbol); if (value) { suffixConvention = 1; } } return value; } /* *------------------------------------------------------------------------ * * TclIcuInit -- * * Load the ICU commands into the given interpreter. If the ICU * commands have never previously been loaded, the ICU libraries are * loaded first. * *------------------------------------------------------------------------ */ static void TclIcuInit( Tcl_Interp *interp) { Tcl_MutexLock(&icu_mutex); char icuversion[4] = "_80"; /* Highest ICU version + 1 */ /* * The initialization below clones the one from Tk. May need revisiting. * ICU shared library names as well as function names *may* be versioned. * See https://unicode-org.github.io/icu/userguide/icu4c/packaging.html * for the gory details. */ if (icu_fns.nopen == 0) { int i = 0; Tcl_Obj *nameobj; static const char *iculibs[] = { #if defined(_WIN32) # define DLLNAME "icu%s%s.dll" "icuuc??.dll", /* Windows, user-provided */ NULL, "cygicuuc??.dll", /* When running under Cygwin */ #elif defined(__CYGWIN__) # define DLLNAME "cygicu%s%s.dll" "cygicuuc??.dll", #elif defined(MAC_OSX_TCL) # define DLLNAME "libicu%s.%s.dylib" "libicuuc.??.dylib", #else # define DLLNAME "libicu%s.so.%s" "libicuuc.so.??", #endif NULL }; /* Going back down to ICU version 60 */ while ((icu_fns.libs[0] == NULL) && (icuversion[1] >= '6')) { if (--icuversion[2] < '0') { icuversion[1]--; icuversion[2] = '9'; } #if defined(__CYGWIN__) i = 2; #else i = 0; #endif while (iculibs[i] != NULL) { Tcl_ResetResult(interp); nameobj = Tcl_NewStringObj(iculibs[i], TCL_AUTO_LENGTH); char *nameStr = Tcl_GetString(nameobj); char *p = strchr(nameStr, '?'); if (p != NULL) { memcpy(p, icuversion+1, 2); } Tcl_IncrRefCount(nameobj); if (Tcl_LoadFile(interp, nameobj, NULL, 0, NULL, &icu_fns.libs[0]) == TCL_OK) { if (p == NULL) { icuversion[0] = '\0'; } Tcl_DecrRefCount(nameobj); break; } Tcl_DecrRefCount(nameobj); ++i; } } if (icu_fns.libs[0] != NULL) { /* Loaded icuuc, load others with the same version */ nameobj = Tcl_ObjPrintf(DLLNAME, "i18n", icuversion+1); Tcl_IncrRefCount(nameobj); /* Ignore errors. Calls to contained functions will fail. */ (void) Tcl_LoadFile(interp, nameobj, NULL, 0, NULL, &icu_fns.libs[1]); Tcl_DecrRefCount(nameobj); } #ifdef _WIN32 /* * On Windows, if no ICU install found, look for the system's * (Win10 1703 or later). There are two cases. Newer systems * have icu.dll containing all functions. Older systems have * icucc.dll and icuin.dll */ if (icu_fns.libs[0] == NULL) { Tcl_ResetResult(interp); nameobj = Tcl_NewStringObj("icu.dll", TCL_AUTO_LENGTH); Tcl_IncrRefCount(nameobj); if (Tcl_LoadFile(interp, nameobj, NULL, 0, NULL, &icu_fns.libs[0]) == TCL_OK) { /* Reload same for second set of functions. */ (void) Tcl_LoadFile(interp, nameobj, NULL, 0, NULL, &icu_fns.libs[1]); /* Functions do NOT have version suffixes */ icuversion[0] = '\0'; } Tcl_DecrRefCount(nameobj); } if (icu_fns.libs[0] == NULL) { /* No icu.dll. Try last fallback */ Tcl_ResetResult(interp); nameobj = Tcl_NewStringObj("icuuc.dll", TCL_AUTO_LENGTH); Tcl_IncrRefCount(nameobj); if (Tcl_LoadFile(interp, nameobj, NULL, 0, NULL, &icu_fns.libs[0]) == TCL_OK) { Tcl_DecrRefCount(nameobj); nameobj = Tcl_NewStringObj("icuin.dll", TCL_AUTO_LENGTH); Tcl_IncrRefCount(nameobj); (void) Tcl_LoadFile(interp, nameobj, NULL, 0, NULL, &icu_fns.libs[1]); /* Functions do NOT have version suffixes */ icuversion[0] = '\0'; } Tcl_DecrRefCount(nameobj); } #endif // _WIN32 /* Symbol may have version (Windows, FreeBSD), or not (Linux) */ #define ICUUC_SYM(name) \ do { \ icu_fns._##name = \ (fn_##name)IcuFindSymbol(icu_fns.libs[0], #name, icuversion); \ } while (0) if (icu_fns.libs[0] != NULL) { ICUUC_SYM(u_cleanup); ICUUC_SYM(u_errorName); ICUUC_SYM(u_strFromUTF32); ICUUC_SYM(u_strFromUTF32WithSub); ICUUC_SYM(u_strToUTF32); ICUUC_SYM(u_strToUTF32WithSub); ICUUC_SYM(ucnv_close); ICUUC_SYM(ucnv_countAliases); ICUUC_SYM(ucnv_countAvailable); ICUUC_SYM(ucnv_fromUChars); ICUUC_SYM(ucnv_getAlias); ICUUC_SYM(ucnv_getAvailableName); ICUUC_SYM(ucnv_open); ICUUC_SYM(ucnv_setFromUCallBack); ICUUC_SYM(ucnv_setToUCallBack); ICUUC_SYM(ucnv_toUChars); ICUUC_SYM(UCNV_FROM_U_CALLBACK_STOP); ICUUC_SYM(UCNV_TO_U_CALLBACK_STOP); ICUUC_SYM(ubrk_open); ICUUC_SYM(ubrk_close); ICUUC_SYM(ubrk_preceding); ICUUC_SYM(ubrk_following); ICUUC_SYM(ubrk_previous); ICUUC_SYM(ubrk_next); ICUUC_SYM(ubrk_setText); ICUUC_SYM(uenum_close); ICUUC_SYM(uenum_count); ICUUC_SYM(uenum_next); ICUUC_SYM(unorm2_getNFCInstance); ICUUC_SYM(unorm2_getNFDInstance); ICUUC_SYM(unorm2_getNFKCInstance); ICUUC_SYM(unorm2_getNFKDInstance); ICUUC_SYM(unorm2_normalize); #undef ICUUC_SYM } #define ICUIN_SYM(name) \ do { \ icu_fns._##name = \ (fn_##name)IcuFindSymbol(icu_fns.libs[1], #name, icuversion); \ } while (0) if (icu_fns.libs[1] != NULL) { ICUIN_SYM(ucsdet_close); ICUIN_SYM(ucsdet_detect); ICUIN_SYM(ucsdet_detectAll); ICUIN_SYM(ucsdet_getName); ICUIN_SYM(ucsdet_getAllDetectableCharsets); ICUIN_SYM(ucsdet_open); ICUIN_SYM(ucsdet_setText); #undef ICUIN_SYM } } if (icu_fns.libs[0] != NULL) { /* * Note refcounts updated BEFORE command definition to protect * against self redefinition. */ if (icu_fns.libs[1] != NULL) { /* Commands needing both libraries */ /* Ref count number of commands */ icu_fns.nopen += 3; Tcl_CreateObjCommand(interp, "::tcl::unsupported::icu::convertto", IcuConverttoObjCmd, 0, TclIcuCleanup); Tcl_CreateObjCommand(interp, "::tcl::unsupported::icu::convertfrom", IcuConvertfromObjCmd, 0, TclIcuCleanup); Tcl_CreateObjCommand(interp, "::tcl::unsupported::icu::detect", IcuDetectObjCmd, 0, TclIcuCleanup); } /* Commands needing only libs[0] (icuuc) */ /* Ref count number of commands */ icu_fns.nopen += 3; /* UPDATE AS CMDS ADDED/DELETED BELOW */ Tcl_CreateObjCommand(interp, "::tcl::unsupported::icu::converters", IcuConverterNamesObjCmd, 0, TclIcuCleanup); Tcl_CreateObjCommand(interp, "::tcl::unsupported::icu::aliases", IcuConverterAliasesObjCmd, 0, TclIcuCleanup); Tcl_CreateObjCommand(interp, "::tcl::unsupported::icu::normalize", IcuNormalizeObjCmd, 0, TclIcuCleanup); } Tcl_MutexUnlock(&icu_mutex); } /* *------------------------------------------------------------------------ * * TclLoadIcuObjCmd -- * * Loads and initializes ICU * * Results: * TCL_OK - Success. * TCL_ERROR - Error. * * Side effects: * Interpreter result holds result or error message. * *------------------------------------------------------------------------ */ int TclLoadIcuObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc != 1) { Tcl_WrongNumArgs(interp, 1 , objv, ""); return TCL_ERROR; } TclIcuInit(interp); return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * coding: utf-8 * End: */ tcl9.0.1/generic/tclIndexObj.c0000644000175000017500000011473114726623136015552 0ustar sergeisergei/* * tclIndexObj.c -- * * This file implements objects of type "index". This object type is used * to lookup a keyword in a table of valid values and cache the index of * the matching entry. Also provides table-based argv/argc processing. * * Copyright © 1990-1994 The Regents of the University of California. * Copyright © 1997 Sun Microsystems, Inc. * Copyright © 2006 Sam Bromley. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * Prototypes for functions defined later in this file: */ static int GetIndexFromObjList(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj *tableObjPtr, const char *msg, int flags, Tcl_Size *indexPtr); static void UpdateStringOfIndex(Tcl_Obj *objPtr); static void DupIndex(Tcl_Obj *srcPtr, Tcl_Obj *dupPtr); static void FreeIndex(Tcl_Obj *objPtr); static Tcl_ObjCmdProc PrefixAllObjCmd; static Tcl_ObjCmdProc PrefixLongestObjCmd; static Tcl_ObjCmdProc PrefixMatchObjCmd; static void PrintUsage(Tcl_Interp *interp, const Tcl_ArgvInfo *argTable); /* * The structure below defines the index Tcl object type by means of functions * that can be invoked by generic object code. */ const Tcl_ObjType tclIndexType = { "index", /* name */ FreeIndex, /* freeIntRepProc */ DupIndex, /* dupIntRepProc */ UpdateStringOfIndex, /* updateStringProc */ NULL, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; /* * The definition of the internal representation of the "index" object; The * internalRep.twoPtrValue.ptr1 field of an object of "index" type will be a * pointer to one of these structures. * * Keep this structure declaration in sync with tclTestObj.c */ typedef struct { void *tablePtr; /* Pointer to the table of strings */ Tcl_Size offset; /* Offset between table entries */ Tcl_Size index; /* Selected index into table. */ } IndexRep; /* * The following macros greatly simplify moving through a table... */ #define STRING_AT(table, offset) \ (*((const char *const *)(((char *)(table)) + (offset)))) #define NEXT_ENTRY(table, offset) \ (&(STRING_AT(table, offset))) #define EXPAND_OF(indexRep) \ (((indexRep)->index != TCL_INDEX_NONE) ? STRING_AT((indexRep)->tablePtr, (indexRep)->offset*(indexRep)->index) : "") /* *---------------------------------------------------------------------- * * GetIndexFromObjList -- * * This procedure looks up an object's value in a table of strings and * returns the index of the matching string, if any. * * Results: * If the value of objPtr is identical to or a unique abbreviation for * one of the entries in tableObjPtr, then the return value is TCL_OK and * the index of the matching entry is stored at *indexPtr. If there isn't * a proper match, then TCL_ERROR is returned and an error message is * left in interp's result (unless interp is NULL). The msg argument is * used in the error message; for example, if msg has the value "option" * then the error message will say something flag 'bad option "foo": must * be ...' * * Side effects: * Removes any internal representation that the object might have. (TODO: * find a way to cache the lookup.) * *---------------------------------------------------------------------- */ int GetIndexFromObjList( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr, /* Object containing the string to lookup. */ Tcl_Obj *tableObjPtr, /* List of strings to compare against the * value of objPtr. */ const char *msg, /* Identifying word to use in error * messages. */ int flags, /* 0 or TCL_EXACT */ Tcl_Size *indexPtr) /* Place to store resulting index. */ { Tcl_Size objc, t; int result; Tcl_Obj **objv; const char **tablePtr; /* * Use Tcl_GetIndexFromObjStruct to do the work to avoid duplicating most * of the code there. This is a bit inefficient but simpler. */ result = TclListObjGetElements(interp, tableObjPtr, &objc, &objv); if (result != TCL_OK) { return result; } /* * Build a string table from the list. */ tablePtr = (const char **)Tcl_Alloc((objc + 1) * sizeof(char *)); for (t = 0; t < objc; t++) { if (objv[t] == objPtr) { /* * An exact match is always chosen, so we can stop here. */ Tcl_Free(tablePtr); *indexPtr = t; return TCL_OK; } tablePtr[t] = TclGetString(objv[t]); } tablePtr[objc] = NULL; result = Tcl_GetIndexFromObjStruct(interp, objPtr, tablePtr, sizeof(char *), msg, flags | TCL_INDEX_TEMP_TABLE, indexPtr); Tcl_Free(tablePtr); return result; } /* *---------------------------------------------------------------------- * * Tcl_GetIndexFromObjStruct -- * * This function looks up an object's value given a starting string and * an offset for the amount of space between strings. This is useful when * the strings are embedded in some other kind of array. * * Results: * If the value of objPtr is identical to or a unique abbreviation for * one of the entries in tablePtr, then the return value is TCL_OK and * the index of the matching entry is stored at *indexPtr * (unless indexPtr is NULL). If there isn't a proper match, then * TCL_ERROR is returned and an error message is left in interp's * result (unless interp is NULL). The msg argument is used in the * error message; for example, if msg has the value "option" then * the error message will say something like 'bad option "foo": must * be ...' * * Side effects: * The result of the lookup is cached as the internal rep of objPtr, so * that repeated lookups can be done quickly. * *---------------------------------------------------------------------- */ #undef Tcl_GetIndexFromObjStruct int Tcl_GetIndexFromObjStruct( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr, /* Object containing the string to lookup. */ const void *tablePtr, /* The first string in the table. The second * string will be at this address plus the * offset, the third plus the offset again, * etc. The last entry must be NULL and there * must not be duplicate entries. */ Tcl_Size offset, /* The number of bytes between entries */ const char *msg, /* Identifying word to use in error * messages. */ int flags, /* 0, TCL_EXACT, TCL_NULL_OK or TCL_INDEX_TEMP_TABLE */ void *indexPtr) /* Place to store resulting index. */ { Tcl_Size index, idx, numAbbrev; const char *key, *p1; const char *p2; const char *const *entryPtr; Tcl_Obj *resultPtr; IndexRep *indexRep; const Tcl_ObjInternalRep *irPtr; if (offset < (Tcl_Size)sizeof(char *)) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Invalid %s value %" TCL_SIZE_MODIFIER "d.", "struct offset", offset)); } return TCL_ERROR; } /* * See if there is a valid cached result from a previous lookup. */ if (objPtr && !(flags & TCL_INDEX_TEMP_TABLE)) { irPtr = TclFetchInternalRep(objPtr, &tclIndexType); if (irPtr) { indexRep = (IndexRep *)irPtr->twoPtrValue.ptr1; if ((indexRep->tablePtr == tablePtr) && (indexRep->offset == offset) && (indexRep->index != TCL_INDEX_NONE)) { index = indexRep->index; goto uncachedDone; } } } /* * Lookup the value of the object in the table. Accept unique * abbreviations unless TCL_EXACT is set in flags. */ key = objPtr ? TclGetString(objPtr) : ""; index = TCL_INDEX_NONE; numAbbrev = 0; if (!*key && (flags & TCL_NULL_OK)) { goto uncachedDone; } /* * Scan the table looking for one of: * - An exact match (always preferred) * - A single abbreviation (allowed depending on flags) * - Several abbreviations (never allowed, but overridden by exact match) */ for (entryPtr = (const char *const *)tablePtr, idx = 0; *entryPtr != NULL; entryPtr = NEXT_ENTRY(entryPtr, offset), idx++) { for (p1 = key, p2 = *entryPtr; *p1 == *p2; p1++, p2++) { if (*p1 == '\0') { index = idx; goto done; } } if (*p1 == '\0') { /* * The value is an abbreviation for this entry. Continue checking * other entries to make sure it's unique. If we get more than one * unique abbreviation, keep searching to see if there is an exact * match, but remember the number of unique abbreviations and * don't allow either. */ numAbbrev++; index = idx; } } /* * Check if we were instructed to disallow abbreviations. */ if ((flags & TCL_EXACT) || (key[0] == '\0') || (numAbbrev != 1)) { goto error; } done: /* * Cache the found representation. Note that we want to avoid allocating a * new internal-rep if at all possible since that is potentially a slow * operation. */ if (objPtr && (index != TCL_INDEX_NONE) && !(flags & TCL_INDEX_TEMP_TABLE)) { irPtr = TclFetchInternalRep(objPtr, &tclIndexType); if (irPtr) { indexRep = (IndexRep *)irPtr->twoPtrValue.ptr1; } else { Tcl_ObjInternalRep ir; indexRep = (IndexRep*)Tcl_Alloc(sizeof(IndexRep)); ir.twoPtrValue.ptr1 = indexRep; Tcl_StoreInternalRep(objPtr, &tclIndexType, &ir); } indexRep->tablePtr = (void *) tablePtr; indexRep->offset = offset; indexRep->index = index; } uncachedDone: if (indexPtr != NULL) { flags &= (30-(int)(sizeof(int)<<1)); if (flags) { if (flags == sizeof(uint16_t)<<1) { *(uint16_t *)indexPtr = index; return TCL_OK; } else if (flags == (int)(sizeof(uint8_t)<<1)) { *(uint8_t *)indexPtr = index; return TCL_OK; } else if (flags == (int)(sizeof(int64_t)<<1)) { *(int64_t *)indexPtr = index; return TCL_OK; } else if (flags == (int)(sizeof(int32_t)<<1)) { *(int32_t *)indexPtr = index; return TCL_OK; } } *(int *)indexPtr = index; } return TCL_OK; error: if (interp != NULL) { /* * Produce a fancy error message. */ int count = 0; TclNewObj(resultPtr); entryPtr = (const char *const *)tablePtr; while ((*entryPtr != NULL) && !**entryPtr) { entryPtr = NEXT_ENTRY(entryPtr, offset); } Tcl_AppendStringsToObj(resultPtr, (numAbbrev>1 && !(flags & TCL_EXACT) ? "ambiguous " : "bad "), msg, " \"", key, (char *)NULL); if (*entryPtr == NULL) { Tcl_AppendStringsToObj(resultPtr, "\": no valid options", (char *)NULL); } else { Tcl_AppendStringsToObj(resultPtr, "\": must be ", *entryPtr, (char *)NULL); entryPtr = NEXT_ENTRY(entryPtr, offset); while (*entryPtr != NULL) { if ((*NEXT_ENTRY(entryPtr, offset) == NULL) && !(flags & TCL_NULL_OK)) { Tcl_AppendStringsToObj(resultPtr, (count > 0 ? "," : ""), " or ", *entryPtr, (char *)NULL); } else if (**entryPtr) { Tcl_AppendStringsToObj(resultPtr, ", ", *entryPtr, (char *)NULL); count++; } entryPtr = NEXT_ENTRY(entryPtr, offset); } if ((flags & TCL_NULL_OK)) { Tcl_AppendStringsToObj(resultPtr, ", or \"\"", (char *)NULL); } } Tcl_SetObjResult(interp, resultPtr); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "INDEX", msg, key, (char *)NULL); } return TCL_ERROR; } /* #define again, needed below */ #define Tcl_GetIndexFromObjStruct(interp, objPtr, tablePtr, offset, msg, flags, indexPtr) \ ((Tcl_GetIndexFromObjStruct)((interp), (objPtr), (tablePtr), (offset), (msg), (flags)|(int)(sizeof(*(indexPtr))<<1), (indexPtr))) /* *---------------------------------------------------------------------- * * UpdateStringOfIndex -- * * This function is called to convert a Tcl object from index internal * form to its string form. No abbreviation is ever generated. * * Results: * None. * * Side effects: * The string representation of the object is updated. * *---------------------------------------------------------------------- */ static void UpdateStringOfIndex( Tcl_Obj *objPtr) { IndexRep *indexRep = (IndexRep *)TclFetchInternalRep(objPtr, &tclIndexType)->twoPtrValue.ptr1; const char *indexStr = EXPAND_OF(indexRep); Tcl_InitStringRep(objPtr, indexStr, strlen(indexStr)); } /* *---------------------------------------------------------------------- * * DupIndex -- * * This function is called to copy the internal rep of an index Tcl * object from to another object. * * Results: * None. * * Side effects: * The internal representation of the target object is updated and the * type is set. * *---------------------------------------------------------------------- */ static void DupIndex( Tcl_Obj *srcPtr, Tcl_Obj *dupPtr) { Tcl_ObjInternalRep ir; IndexRep *dupIndexRep = (IndexRep *)Tcl_Alloc(sizeof(IndexRep)); memcpy(dupIndexRep, TclFetchInternalRep(srcPtr, &tclIndexType)->twoPtrValue.ptr1, sizeof(IndexRep)); ir.twoPtrValue.ptr1 = dupIndexRep; Tcl_StoreInternalRep(dupPtr, &tclIndexType, &ir); } /* *---------------------------------------------------------------------- * * FreeIndex -- * * This function is called to delete the internal rep of an index Tcl * object. * * Results: * None. * * Side effects: * The internal representation of the target object is deleted. * *---------------------------------------------------------------------- */ static void FreeIndex( Tcl_Obj *objPtr) { Tcl_Free(TclFetchInternalRep(objPtr, &tclIndexType)->twoPtrValue.ptr1); objPtr->typePtr = NULL; } /* *---------------------------------------------------------------------- * * TclInitPrefixCmd -- * * This procedure creates the "prefix" Tcl command. See the user * documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ Tcl_Command TclInitPrefixCmd( Tcl_Interp *interp) /* Current interpreter. */ { static const EnsembleImplMap prefixImplMap[] = { {"all", PrefixAllObjCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"longest", PrefixLongestObjCmd,TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"match", PrefixMatchObjCmd, TclCompileBasicMin2ArgCmd, NULL, NULL, 0}, {NULL, NULL, NULL, NULL, NULL, 0} }; Tcl_Command prefixCmd; prefixCmd = TclMakeEnsemble(interp, "::tcl::prefix", prefixImplMap); Tcl_Export(interp, Tcl_FindNamespace(interp, "::tcl", NULL, 0), "prefix", 0); return prefixCmd; } /*---------------------------------------------------------------------- * * PrefixMatchObjCmd -- * * This function implements the 'prefix match' Tcl command. Refer to the * user documentation for details on what it does. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PrefixMatchObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int flags = 0, result; Tcl_Size errorLength, i; Tcl_Obj *errorPtr = NULL; const char *message = "option"; Tcl_Obj *tablePtr, *objPtr, *resultPtr; static const char *const matchOptions[] = { "-error", "-exact", "-message", NULL }; enum matchOptionsEnum { PRFMATCH_ERROR, PRFMATCH_EXACT, PRFMATCH_MESSAGE } index; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "?options? table string"); return TCL_ERROR; } for (i = 1; i < (objc - 2); i++) { if (Tcl_GetIndexFromObjStruct(interp, objv[i], matchOptions, sizeof(char *), "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case PRFMATCH_EXACT: flags |= TCL_EXACT; break; case PRFMATCH_MESSAGE: if (i > objc-4) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "missing value for -message", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "NOARG", (char *)NULL); return TCL_ERROR; } i++; message = TclGetString(objv[i]); break; case PRFMATCH_ERROR: if (i > objc-4) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "missing value for -error", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "NOARG", (char *)NULL); return TCL_ERROR; } i++; result = TclListObjLength(interp, objv[i], &errorLength); if (result != TCL_OK) { return TCL_ERROR; } if ((errorLength % 2) != 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "error options must have an even number of elements", -1)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "DICTIONARY", (char *)NULL); return TCL_ERROR; } errorPtr = objv[i]; break; } } tablePtr = objv[objc - 2]; objPtr = objv[objc - 1]; /* * Check that table is a valid list first, since we want to handle that * error case regardless of level. */ result = TclListObjLength(interp, tablePtr, &i); if (result != TCL_OK) { return result; } result = GetIndexFromObjList(interp, objPtr, tablePtr, message, flags, &i); if (result != TCL_OK) { if (errorPtr != NULL && errorLength == 0) { Tcl_ResetResult(interp); return TCL_OK; } else if (errorPtr == NULL) { return TCL_ERROR; } if (Tcl_IsShared(errorPtr)) { errorPtr = Tcl_DuplicateObj(errorPtr); } Tcl_ListObjAppendElement(interp, errorPtr, Tcl_NewStringObj("-code", 5)); Tcl_ListObjAppendElement(interp, errorPtr, Tcl_NewWideIntObj(result)); return Tcl_SetReturnOptions(interp, errorPtr); } result = Tcl_ListObjIndex(interp, tablePtr, i, &resultPtr); if (result != TCL_OK) { return result; } Tcl_SetObjResult(interp, resultPtr); return TCL_OK; } /*---------------------------------------------------------------------- * * PrefixAllObjCmd -- * * This function implements the 'prefix all' Tcl command. Refer to the * user documentation for details on what it does. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PrefixAllObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int result; Tcl_Size length, elemLength, tableObjc, t; const char *string, *elemString; Tcl_Obj **tableObjv, *resultPtr; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "table string"); return TCL_ERROR; } result = TclListObjGetElements(interp, objv[1], &tableObjc, &tableObjv); if (result != TCL_OK) { return result; } resultPtr = Tcl_NewListObj(0, NULL); string = TclGetStringFromObj(objv[2], &length); for (t = 0; t < tableObjc; t++) { elemString = TclGetStringFromObj(tableObjv[t], &elemLength); /* * A prefix cannot match if it is longest. */ if (length <= elemLength) { if (TclpUtfNcmp2(elemString, string, length) == 0) { Tcl_ListObjAppendElement(interp, resultPtr, tableObjv[t]); } } } Tcl_SetObjResult(interp, resultPtr); return TCL_OK; } /*---------------------------------------------------------------------- * * PrefixLongestObjCmd -- * * This function implements the 'prefix longest' Tcl command. Refer to * the user documentation for details on what it does. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int PrefixLongestObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int result; Tcl_Size i, length, elemLength, resultLength, tableObjc, t; const char *string, *elemString, *resultString; Tcl_Obj **tableObjv; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "table string"); return TCL_ERROR; } result = TclListObjGetElements(interp, objv[1], &tableObjc, &tableObjv); if (result != TCL_OK) { return result; } string = TclGetStringFromObj(objv[2], &length); resultString = NULL; resultLength = 0; for (t = 0; t < tableObjc; t++) { elemString = TclGetStringFromObj(tableObjv[t], &elemLength); /* * First check if the prefix string matches the element. A prefix * cannot match if it is longest. */ if ((length > elemLength) || TclpUtfNcmp2(elemString, string, length) != 0) { continue; } if (resultString == NULL) { /* * If this is the first match, the longest common substring this * far is the complete string. The result is part of this string * so we only need to adjust the length later. */ resultString = elemString; resultLength = elemLength; } else { /* * Longest common substring cannot be longer than shortest string. */ if (elemLength < resultLength) { resultLength = elemLength; } /* * Compare strings. */ for (i = 0; i < resultLength; i++) { if (resultString[i] != elemString[i]) { /* * Adjust in case we stopped in the middle of a UTF char. */ resultLength = Tcl_UtfPrev(&resultString[i+1], resultString) - resultString; break; } } } } if (resultLength > 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj(resultString, resultLength)); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_WrongNumArgs -- * * This function generates a "wrong # args" error message in an * interpreter. It is used as a utility function by many command * functions, including the function that implements procedures. * * Results: * None. * * Side effects: * An error message is generated in interp's result object to indicate * that a command was invoked with the wrong number of arguments. The * message has the form * wrong # args: should be "foo bar additional stuff" * where "foo" and "bar" are the initial objects in objv (objc determines * how many of these are printed) and "additional stuff" is the contents * of the message argument. * * The message printed is modified somewhat if the command is wrapped * inside an ensemble. In that case, the error message generated is * rewritten in such a way that it appears to be generated from the * user-visible command and not how that command is actually implemented, * giving a better overall user experience. * * Internally, the Tcl core may set the flag INTERP_ALTERNATE_WRONG_ARGS * in the interpreter to generate complex multi-part messages by calling * this function repeatedly. This allows the code that knows how to * handle ensemble-related error messages to be kept here while still * generating suitable error messages for commands like [read] and * [socket]. Ideally, this would be done through an extra flags argument, * but that wouldn't be source-compatible with the existing API and it's * a fairly rare requirement anyway. * *---------------------------------------------------------------------- */ void Tcl_WrongNumArgs( Tcl_Interp *interp, /* Current interpreter. */ Tcl_Size objc, /* Number of arguments to print from objv. */ Tcl_Obj *const objv[], /* Initial argument objects, which should be * included in the error message. */ const char *message) /* Error message to print after the leading * objects in objv. The message may be * NULL. */ { Tcl_Obj *objPtr; Tcl_Size i, len, elemLen; char flags; Interp *iPtr = (Interp *)interp; const char *elementStr; TclNewObj(objPtr); if (iPtr->flags & INTERP_ALTERNATE_WRONG_ARGS) { iPtr->flags &= ~INTERP_ALTERNATE_WRONG_ARGS; Tcl_AppendObjToObj(objPtr, Tcl_GetObjResult(interp)); Tcl_AppendToObj(objPtr, " or \"", TCL_INDEX_NONE); } else { Tcl_AppendToObj(objPtr, "wrong # args: should be \"", TCL_INDEX_NONE); } /* * If processing an ensemble implementation, rewrite the results in * terms of how the ensemble was invoked. */ if (iPtr->ensembleRewrite.sourceObjs != NULL) { Tcl_Size toSkip = iPtr->ensembleRewrite.numInsertedObjs; Tcl_Size toPrint = iPtr->ensembleRewrite.numRemovedObjs; Tcl_Obj *const *origObjv = TclEnsembleGetRewriteValues(interp); /* * Only do rewrite the command if all the replaced objects are * actually arguments (in objv) to this function. Otherwise it just * gets too complicated and it's to just give a slightly * confusing error message... */ if (objc < toSkip) { goto addNormalArgumentsToMessage; } /* * Strip out the actual arguments that the ensemble inserted. */ objv += toSkip; objc -= toSkip; /* * Assume no object is of index type. */ for (i=0 ; itwoPtrValue.ptr1; elementStr = EXPAND_OF(indexRep); elemLen = strlen(elementStr); } else { elementStr = TclGetStringFromObj(origObjv[i], &elemLen); } flags = 0; len = TclScanElement(elementStr, elemLen, &flags); if (len != elemLen) { char *quotedElementStr = (char *)TclStackAlloc(interp, len + 1); len = TclConvertElement(elementStr, elemLen, quotedElementStr, flags); Tcl_AppendToObj(objPtr, quotedElementStr, len); TclStackFree(interp, quotedElementStr); } else { Tcl_AppendToObj(objPtr, elementStr, elemLen); } /* * Add a space if the word is not the last one (which has a * moderately complex condition here). */ if (i + 1 < toPrint || objc!=0 || message!=NULL) { Tcl_AppendStringsToObj(objPtr, " ", (char *)NULL); } } } /* * Now add the arguments (other than those rewritten) that the caller took * from its calling context. */ addNormalArgumentsToMessage: for (i = 0; i < objc; i++) { /* * If the object is an index type, use the index table which allows for * the correct error message even if the subcommand was abbreviated. * Otherwise, just use the string rep. */ const Tcl_ObjInternalRep *irPtr; if ((irPtr = TclFetchInternalRep(objv[i], &tclIndexType))) { IndexRep *indexRep = (IndexRep *)irPtr->twoPtrValue.ptr1; Tcl_AppendStringsToObj(objPtr, EXPAND_OF(indexRep), (char *)NULL); } else { /* * Quote the argument if it contains spaces (Bug 942757). */ elementStr = TclGetStringFromObj(objv[i], &elemLen); flags = 0; len = TclScanElement(elementStr, elemLen, &flags); if (len != elemLen) { char *quotedElementStr = (char *)TclStackAlloc(interp, len + 1); len = TclConvertElement(elementStr, elemLen, quotedElementStr, flags); Tcl_AppendToObj(objPtr, quotedElementStr, len); TclStackFree(interp, quotedElementStr); } else { Tcl_AppendToObj(objPtr, elementStr, elemLen); } } /* * Append a space character (" ") if there is more text to follow * (either another element from objv, or the message string). */ if (i + 1 < objc || message!=NULL) { Tcl_AppendStringsToObj(objPtr, " ", (char *)NULL); } } /* * Add any trailing message bits and set the resulting string as the * interpreter result. Caller is responsible for reporting this as an * actual error. */ if (message != NULL) { Tcl_AppendStringsToObj(objPtr, message, (char *)NULL); } Tcl_AppendStringsToObj(objPtr, "\"", (char *)NULL); Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", (char *)NULL); Tcl_SetObjResult(interp, objPtr); } /* *---------------------------------------------------------------------- * * Tcl_ParseArgsObjv -- * * Process an objv array according to a table of expected command-line * options. See the manual page for more details. * * Results: * The return value is a standard Tcl return value. If an error occurs * then an error message is left in the interp's result. Under normal * conditions, both *objcPtr and *objv are modified to return the * arguments that couldn't be processed here (they didn't match the * option table, or followed an TCL_ARGV_REST argument). * * Side effects: * Variables may be modified, or procedures may be called. It all depends * on the arguments and their entries in argTable. See the user * documentation for details. * *---------------------------------------------------------------------- */ #undef Tcl_ParseArgsObjv int Tcl_ParseArgsObjv( Tcl_Interp *interp, /* Place to store error message. */ const Tcl_ArgvInfo *argTable, /* Array of option descriptions. */ Tcl_Size *objcPtr, /* Number of arguments in objv. Modified to * hold # args left in objv at end. */ Tcl_Obj *const *objv, /* Array of arguments to be parsed. */ Tcl_Obj ***remObjv) /* Pointer to array of arguments that were not * processed here. Should be NULL if no return * of arguments is desired. */ { Tcl_Obj **leftovers; /* Array to write back to remObjv on * successful exit. Will include the name of * the command. */ Tcl_Size nrem; /* Size of leftovers.*/ const Tcl_ArgvInfo *infoPtr; /* Pointer to the current entry in the table * of argument descriptions. */ const Tcl_ArgvInfo *matchPtr; /* Descriptor that matches current argument */ Tcl_Obj *curArg; /* Current argument */ const char *str = NULL; char c; /* Second character of current arg (used for * quick check for matching; use 2nd char. * because first char. will almost always be * '-'). */ Tcl_Size srcIndex; /* Location from which to read next argument * from objv. */ Tcl_Size dstIndex; /* Used to keep track of current arguments * being processed, primarily for error * reporting. */ Tcl_Size objc; /* # arguments in objv still to process. */ Tcl_Size length; /* Number of characters in current argument */ Tcl_Size gf_ret; /* Return value from Tcl_ArgvGenFuncProc*/ if (remObjv != NULL) { /* * Then we should copy the name of the command (0th argument). The * upper bound on the number of elements is known, and (undocumented, * but historically true) there should be a NULL argument after the * last result. [Bug 3413857] */ nrem = 1; leftovers = (Tcl_Obj **)Tcl_Alloc((1 + *objcPtr) * sizeof(Tcl_Obj *)); leftovers[0] = objv[0]; } else { nrem = 0; leftovers = NULL; } /* * OK, now start processing from the second element (1st argument). */ srcIndex = dstIndex = 1; objc = *objcPtr-1; while (objc > 0) { curArg = objv[srcIndex]; srcIndex++; objc--; str = TclGetStringFromObj(curArg, &length); if (length > 0) { c = str[1]; } else { c = 0; } /* * Loop through the argument descriptors searching for one with the * matching key string. If found, leave a pointer to it in matchPtr. */ matchPtr = NULL; infoPtr = argTable; for (; infoPtr != NULL && infoPtr->type != TCL_ARGV_END ; infoPtr++) { if (infoPtr->keyStr == NULL) { continue; } if ((infoPtr->keyStr[1] != c) || (strncmp(infoPtr->keyStr, str, length) != 0)) { continue; } if (infoPtr->keyStr[length] == 0) { matchPtr = infoPtr; goto gotMatch; } if (matchPtr != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "ambiguous option \"%s\"", str)); goto error; } matchPtr = infoPtr; } if (matchPtr == NULL) { /* * Unrecognized argument. Just copy it down, unless the caller * prefers an error to be registered. */ if (remObjv == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unrecognized argument \"%s\"", str)); goto error; } dstIndex++; /* This argument is now handled */ leftovers[nrem++] = curArg; continue; } /* * Take the appropriate action based on the option type */ gotMatch: infoPtr = matchPtr; switch (infoPtr->type) { case TCL_ARGV_CONSTANT: *((int *) infoPtr->dstPtr) = PTR2INT(infoPtr->srcPtr); break; case TCL_ARGV_INT: if (objc == 0) { goto missingArg; } if (Tcl_GetIntFromObj(interp, objv[srcIndex], (int *) infoPtr->dstPtr) == TCL_ERROR) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected integer argument for \"%s\" but got \"%s\"", infoPtr->keyStr, TclGetString(objv[srcIndex]))); goto error; } srcIndex++; objc--; break; case TCL_ARGV_STRING: if (objc == 0) { goto missingArg; } *((const char **) infoPtr->dstPtr) = TclGetString(objv[srcIndex]); srcIndex++; objc--; break; case TCL_ARGV_REST: /* * Only store the point where we got to if it's not to be written * to NULL, so that TCL_ARGV_AUTO_REST works. */ if (infoPtr->dstPtr != NULL) { *((int *) infoPtr->dstPtr) = dstIndex; } goto argsDone; case TCL_ARGV_FLOAT: if (objc == 0) { goto missingArg; } if (Tcl_GetDoubleFromObj(interp, objv[srcIndex], (double *) infoPtr->dstPtr) == TCL_ERROR) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected floating-point argument for \"%s\" but got \"%s\"", infoPtr->keyStr, TclGetString(objv[srcIndex]))); goto error; } srcIndex++; objc--; break; case TCL_ARGV_FUNC: { Tcl_ArgvFuncProc *handlerProc = (Tcl_ArgvFuncProc *) infoPtr->srcPtr; Tcl_Obj *argObj; if (objc == 0) { argObj = NULL; } else { argObj = objv[srcIndex]; } if (handlerProc(infoPtr->clientData, argObj, infoPtr->dstPtr)) { srcIndex++; objc--; } break; } case TCL_ARGV_GENFUNC: { if (objc > INT_MAX) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "too many (%" TCL_SIZE_MODIFIER "d) arguments for TCL_ARGV_GENFUNC", objc)); goto error; } Tcl_ArgvGenFuncProc *handlerProc = (Tcl_ArgvGenFuncProc *) infoPtr->srcPtr; gf_ret = handlerProc(infoPtr->clientData, interp, objc, &objv[srcIndex], infoPtr->dstPtr); if (gf_ret < 0) { goto error; } else { srcIndex += gf_ret; objc -= gf_ret; } break; } case TCL_ARGV_HELP: PrintUsage(interp, argTable); goto error; default: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad argument type %d in Tcl_ArgvInfo", infoPtr->type)); goto error; } } /* * If we broke out of the loop because of an OPT_REST argument, copy the * remaining arguments down. Note that there is always at least one * argument left over - the command name - so we always have a result if * our caller is willing to receive it. [Bug 3413857] */ argsDone: if (remObjv == NULL) { /* * Nothing to do. */ return TCL_OK; } if (objc > 0) { memcpy(leftovers+nrem, objv+srcIndex, objc*sizeof(Tcl_Obj *)); nrem += objc; } leftovers[nrem] = NULL; *objcPtr = nrem++; *remObjv = (Tcl_Obj **)Tcl_Realloc(leftovers, nrem * sizeof(Tcl_Obj *)); return TCL_OK; /* * Make sure to handle freeing any temporary space we've allocated on the * way to an error. */ missingArg: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" option requires an additional argument", str)); error: if (leftovers != NULL) { Tcl_Free(leftovers); } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * PrintUsage -- * * Generate a help string describing command-line options. * * Results: * The interp's result will be modified to hold a help string describing * all the options in argTable. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void PrintUsage( Tcl_Interp *interp, /* Place information in this interp's result * area. */ const Tcl_ArgvInfo *argTable) /* Array of command-specific argument * descriptions. */ { const Tcl_ArgvInfo *infoPtr; int width, numSpaces; #define NUM_SPACES 20 static const char spaces[] = " "; Tcl_Obj *msg; /* * First, compute the width of the widest option key, so that we can make * everything line up. */ width = 4; for (infoPtr = argTable; infoPtr->type != TCL_ARGV_END; infoPtr++) { Tcl_Size length; if (infoPtr->keyStr == NULL) { continue; } length = strlen(infoPtr->keyStr); if (length > width) { width = length; } } /* * Now add the option information, with pretty-printing. */ msg = Tcl_NewStringObj("Command-specific options:", TCL_INDEX_NONE); for (infoPtr = argTable; infoPtr->type != TCL_ARGV_END; infoPtr++) { if ((infoPtr->type == TCL_ARGV_HELP) && (infoPtr->keyStr == NULL)) { Tcl_AppendPrintfToObj(msg, "\n%s", infoPtr->helpStr); continue; } Tcl_AppendPrintfToObj(msg, "\n %s:", infoPtr->keyStr); numSpaces = width + 1 - strlen(infoPtr->keyStr); while (numSpaces > 0) { if (numSpaces >= NUM_SPACES) { Tcl_AppendToObj(msg, spaces, NUM_SPACES); } else { Tcl_AppendToObj(msg, spaces, numSpaces); } numSpaces -= NUM_SPACES; } Tcl_AppendToObj(msg, infoPtr->helpStr, TCL_INDEX_NONE); switch (infoPtr->type) { case TCL_ARGV_INT: Tcl_AppendPrintfToObj(msg, "\n\t\tDefault value: %d", *((int *) infoPtr->dstPtr)); break; case TCL_ARGV_FLOAT: Tcl_AppendPrintfToObj(msg, "\n\t\tDefault value: %g", *((double *) infoPtr->dstPtr)); break; case TCL_ARGV_STRING: { char *string = *((char **) infoPtr->dstPtr); if (string != NULL) { Tcl_AppendPrintfToObj(msg, "\n\t\tDefault value: \"%s\"", string); } break; } default: break; } } Tcl_SetObjResult(interp, msg); } /* *---------------------------------------------------------------------- * * TclGetCompletionCodeFromObj -- * * Parses Completion code Code * * Results: * Returns TCL_ERROR if the value is an invalid completion code. * Otherwise, returns TCL_OK, and writes the completion code to the * pointer provided. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclGetCompletionCodeFromObj( Tcl_Interp *interp, /* Current interpreter. */ Tcl_Obj *value, int *codePtr) /* Argument objects. */ { static const char *const returnCodes[] = { "ok", "error", "return", "break", "continue", NULL }; if (!TclHasInternalRep(value, &tclIndexType) && TclGetIntFromObj(NULL, value, codePtr) == TCL_OK) { return TCL_OK; } if (Tcl_GetIndexFromObjStruct(NULL, value, returnCodes, sizeof(char *), NULL, TCL_EXACT, codePtr) == TCL_OK) { return TCL_OK; } /* * Value is not a legal completion code. */ if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad completion code \"%s\": must be" " ok, error, return, break, continue, or an integer", TclGetString(value))); Tcl_SetErrorCode(interp, "TCL", "RESULT", "ILLEGAL_CODE", (char *)NULL); } return TCL_ERROR; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclInt.h0000644000175000017500000057626614726623136014626 0ustar sergeisergei/* * tclInt.h -- * * Declarations of things used internally by the Tcl interpreter. * * Copyright (c) 1987-1993 The Regents of the University of California. * Copyright (c) 1993-1997 Lucent Technologies. * Copyright (c) 1994-1998 Sun Microsystems, Inc. * Copyright (c) 1998-1999 by Scriptics Corporation. * Copyright (c) 2001, 2002 by Kevin B. Kenny. All rights reserved. * Copyright (c) 2007 Daniel A. Steffen * Copyright (c) 2006-2008 by Joe Mistachkin. All rights reserved. * Copyright (c) 2008 by Miguel Sofer. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef _TCLINT #define _TCLINT /* * Some numerics configuration options. */ #undef ACCEPT_NAN /* * Used to tag functions that are only to be visible within the module being * built and not outside it (where this is supported by the linker). * Also used in the platform-specific *Port.h files. */ #ifndef MODULE_SCOPE # ifdef __cplusplus # define MODULE_SCOPE extern "C" # else # define MODULE_SCOPE extern # endif #endif #ifndef JOIN # define JOIN(a,b) JOIN1(a,b) # define JOIN1(a,b) a##b #endif #if defined(__cplusplus) # define TCL_UNUSED(T) T #elif defined(__GNUC__) && (__GNUC__ > 2) # define TCL_UNUSED(T) T JOIN(dummy, __LINE__) __attribute__((unused)) #else # define TCL_UNUSED(T) T JOIN(dummy, __LINE__) #endif /* * Common include files needed by most of the Tcl source files are included * here, so that system-dependent personalizations for the include files only * have to be made in once place. This results in a few extra includes, but * greater modularity. The order of the three groups of #includes is * important. For example, stdio.h is needed by tcl.h. */ #include "tclPort.h" #include #include #include #include #include #include #include /* * Ensure WORDS_BIGENDIAN is defined correctly: * Needs to happen here in addition to configure to work with fat compiles on * Darwin (where configure runs only once for multiple architectures). */ #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_PARAM_H # include #endif #ifdef BYTE_ORDER # ifdef BIG_ENDIAN # if BYTE_ORDER == BIG_ENDIAN # undef WORDS_BIGENDIAN # define WORDS_BIGENDIAN 1 # endif # endif # ifdef LITTLE_ENDIAN # if BYTE_ORDER == LITTLE_ENDIAN # undef WORDS_BIGENDIAN # endif # endif #endif /* * Macros used to cast between pointers and integers (e.g. when storing an int * in ClientData), on 64-bit architectures they avoid gcc warning about "cast * to/from pointer from/to integer of different size". */ #if !defined(INT2PTR) # define INT2PTR(p) ((void *)(ptrdiff_t)(p)) #endif #if !defined(PTR2INT) # define PTR2INT(p) ((ptrdiff_t)(p)) #endif #if !defined(UINT2PTR) # define UINT2PTR(p) ((void *)(size_t)(p)) #endif #if !defined(PTR2UINT) # define PTR2UINT(p) ((size_t)(p)) #endif #if defined(_WIN32) && defined(_MSC_VER) # define vsnprintf _vsnprintf # define snprintf _snprintf #endif #if !defined(TCL_THREADS) # define TCL_THREADS 1 #endif #if !TCL_THREADS # undef TCL_DECLARE_MUTEX # define TCL_DECLARE_MUTEX(name) # undef Tcl_MutexLock # define Tcl_MutexLock(mutexPtr) # undef Tcl_MutexUnlock # define Tcl_MutexUnlock(mutexPtr) # undef Tcl_MutexFinalize # define Tcl_MutexFinalize(mutexPtr) # undef Tcl_ConditionNotify # define Tcl_ConditionNotify(condPtr) # undef Tcl_ConditionWait # define Tcl_ConditionWait(condPtr, mutexPtr, timePtr) # undef Tcl_ConditionFinalize # define Tcl_ConditionFinalize(condPtr) #endif /* * The following procedures allow namespaces to be customized to support * special name resolution rules for commands/variables. */ struct Tcl_ResolvedVarInfo; typedef Tcl_Var (Tcl_ResolveRuntimeVarProc)(Tcl_Interp *interp, struct Tcl_ResolvedVarInfo *vinfoPtr); typedef void (Tcl_ResolveVarDeleteProc)(struct Tcl_ResolvedVarInfo *vinfoPtr); /* * The following structure encapsulates the routines needed to resolve a * variable reference at runtime. Any variable specific state will typically * be appended to this structure. */ typedef struct Tcl_ResolvedVarInfo { Tcl_ResolveRuntimeVarProc *fetchProc; Tcl_ResolveVarDeleteProc *deleteProc; } Tcl_ResolvedVarInfo; typedef int (Tcl_ResolveCompiledVarProc)(Tcl_Interp *interp, const char *name, Tcl_Size length, Tcl_Namespace *context, Tcl_ResolvedVarInfo **rPtr); typedef int (Tcl_ResolveVarProc)(Tcl_Interp *interp, const char *name, Tcl_Namespace *context, int flags, Tcl_Var *rPtr); typedef int (Tcl_ResolveCmdProc)(Tcl_Interp *interp, const char *name, Tcl_Namespace *context, int flags, Tcl_Command *rPtr); typedef struct Tcl_ResolverInfo { Tcl_ResolveCmdProc *cmdResProc; /* Procedure handling command name * resolution. */ Tcl_ResolveVarProc *varResProc; /* Procedure handling variable name resolution * for variables that can only be handled at * runtime. */ Tcl_ResolveCompiledVarProc *compiledVarResProc; /* Procedure handling variable name resolution * at compile time. */ } Tcl_ResolverInfo; /* * This flag bit should not interfere with TCL_GLOBAL_ONLY, * TCL_NAMESPACE_ONLY, or TCL_LEAVE_ERR_MSG; it signals that the variable * lookup is performed for upvar (or similar) purposes, with slightly * different rules: * - Bug #696893 - variable is either proc-local or in the current * namespace; never follow the second (global) resolution path * - Bug #631741 - do not use special namespace or interp resolvers */ #define TCL_AVOID_RESOLVERS 0x40000 /* *---------------------------------------------------------------- * Data structures related to namespaces. *---------------------------------------------------------------- */ typedef struct Tcl_Ensemble Tcl_Ensemble; typedef struct NamespacePathEntry NamespacePathEntry; /* * Special hashtable for variables: This is just a Tcl_HashTable with nsPtr * and arrayPtr fields added at the end so that variables can find their * namespace and possibly containing array without having to copy a pointer in * their struct by accessing them via their hPtr->tablePtr. */ typedef struct TclVarHashTable { Tcl_HashTable table; /* "Inherit" from Tcl_HashTable. */ struct Namespace *nsPtr; /* The namespace containing the variables. */ #if TCL_MAJOR_VERSION > 8 struct Var *arrayPtr; /* The array containing the variables, if they * are variables in an array at all. */ #endif /* TCL_MAJOR_VERSION > 8 */ } TclVarHashTable; /* * This is for itcl - it likes to search our varTables directly :( */ #define TclVarHashFindVar(tablePtr, key) \ TclVarHashCreateVar((tablePtr), (key), NULL) /* * Define this to reduce the amount of space that the average namespace * consumes by only allocating the table of child namespaces when necessary. * Defining it breaks compatibility for Tcl extensions (e.g., itcl) which * reach directly into the Namespace structure. */ #undef BREAK_NAMESPACE_COMPAT /* * The structure below defines a namespace. * Note: the first five fields must match exactly the fields in a * Tcl_Namespace structure (see tcl.h). If you change one, be sure to change * the other. */ typedef struct Namespace { char *name; /* The namespace's simple (unqualified) name. * This contains no ::'s. The name of the * global namespace is "" although "::" is an * synonym. */ char *fullName; /* The namespace's fully qualified name. This * starts with ::. */ void *clientData; /* An arbitrary value associated with this * namespace. */ Tcl_NamespaceDeleteProc *deleteProc; /* Procedure invoked when deleting the * namespace to, e.g., free clientData. */ struct Namespace *parentPtr;/* Points to the namespace that contains this * one. NULL if this is the global * namespace. */ #ifndef BREAK_NAMESPACE_COMPAT Tcl_HashTable childTable; /* Contains any child namespaces. Indexed by * strings; values have type (Namespace *). */ #else Tcl_HashTable *childTablePtr; /* Contains any child namespaces. Indexed by * strings; values have type (Namespace *). If * NULL, there are no children. */ #endif #if TCL_MAJOR_VERSION > 8 size_t nsId; /* Unique id for the namespace. */ #else unsigned long nsId; #endif Tcl_Interp *interp; /* The interpreter containing this * namespace. */ int flags; /* OR-ed combination of the namespace status * flags NS_DYING and NS_DEAD listed below. */ Tcl_Size activationCount; /* Number of "activations" or active call * frames for this namespace that are on the * Tcl call stack. The namespace won't be * freed until activationCount becomes zero. */ Tcl_Size refCount; /* Count of references by namespaceName * objects. The namespace can't be freed until * refCount becomes zero. */ Tcl_HashTable cmdTable; /* Contains all the commands currently * registered in the namespace. Indexed by * strings; values have type (Command *). * Commands imported by Tcl_Import have * Command structures that point (via an * ImportedCmdRef structure) to the Command * structure in the source namespace's command * table. */ TclVarHashTable varTable; /* Contains all the (global) variables * currently in this namespace. Indexed by * strings; values have type (Var *). */ char **exportArrayPtr; /* Points to an array of string patterns * specifying which commands are exported. A * pattern may include "string match" style * wildcard characters to specify multiple * commands; however, no namespace qualifiers * are allowed. NULL if no export patterns are * registered. */ Tcl_Size numExportPatterns; /* Number of export patterns currently * registered using "namespace export". */ Tcl_Size maxExportPatterns; /* Number of export patterns for which space * is currently allocated. */ Tcl_Size cmdRefEpoch; /* Incremented if a newly added command * shadows a command for which this namespace * has already cached a Command* pointer; this * causes all its cached Command* pointers to * be invalidated. */ Tcl_Size resolverEpoch; /* Incremented whenever (a) the name * resolution rules change for this namespace * or (b) a newly added command shadows a * command that is compiled to bytecodes. This * invalidates all byte codes compiled in the * namespace, causing the code to be * recompiled under the new rules.*/ Tcl_ResolveCmdProc *cmdResProc; /* If non-null, this procedure overrides the * usual command resolution mechanism in Tcl. * This procedure is invoked within * Tcl_FindCommand to resolve all command * references within the namespace. */ Tcl_ResolveVarProc *varResProc; /* If non-null, this procedure overrides the * usual variable resolution mechanism in Tcl. * This procedure is invoked within * Tcl_FindNamespaceVar to resolve all * variable references within the namespace at * runtime. */ Tcl_ResolveCompiledVarProc *compiledVarResProc; /* If non-null, this procedure overrides the * usual variable resolution mechanism in Tcl. * This procedure is invoked within * LookupCompiledLocal to resolve variable * references within the namespace at compile * time. */ Tcl_Size exportLookupEpoch; /* Incremented whenever a command is added to * a namespace, removed from a namespace or * the exports of a namespace are changed. * Allows TIP#112-driven command lists to be * validated efficiently. */ Tcl_Ensemble *ensembles; /* List of structures that contain the details * of the ensembles that are implemented on * top of this namespace. */ Tcl_Obj *unknownHandlerPtr; /* A script fragment to be used when command * resolution in this namespace fails. TIP * 181. */ Tcl_Size commandPathLength; /* The length of the explicit path. */ NamespacePathEntry *commandPathArray; /* The explicit path of the namespace as an * array. */ NamespacePathEntry *commandPathSourceList; /* Linked list of path entries that point to * this namespace. */ Tcl_NamespaceDeleteProc *earlyDeleteProc; /* Just like the deleteProc field (and called * with the same clientData) but called at the * start of the deletion process, so there is * a chance for code to do stuff inside the * namespace before deletion completes. */ } Namespace; /* * An entry on a namespace's command resolution path. */ struct NamespacePathEntry { Namespace *nsPtr; /* What does this path entry point to? If it * is NULL, this path entry points is * redundant and should be skipped. */ Namespace *creatorNsPtr; /* Where does this path entry point from? This * allows for efficient invalidation of * references when the path entry's target * updates its current list of defined * commands. */ NamespacePathEntry *prevPtr, *nextPtr; /* Linked list pointers or NULL at either end * of the list that hangs off Namespace's * commandPathSourceList field. */ }; /* * Flags used to represent the status of a namespace: * * NS_DYING - 1 means Tcl_DeleteNamespace has been called to delete the * namespace. There may still be active call frames on the Tcl * stack that refer to the namespace. When the last call frame * referring to it has been popped, its remaining variables and * commands are destroyed and it is marked "dead" (NS_DEAD). * NS_TEARDOWN -1 means that TclTeardownNamespace has already been called on * this namespace and it should not be called again [Bug 1355942]. * NS_DEAD - 1 means Tcl_DeleteNamespace has been called to delete the * namespace and no call frames still refer to it. It is no longer * accessible by name. Its variables and commands have already * been destroyed. When the last namespaceName object in any byte * code unit that refers to the namespace has been freed (i.e., * when the namespace's refCount is 0), the namespace's storage * will be freed. * NS_SUPPRESS_COMPILATION - * Marks the commands in this namespace for not being compiled, * forcing them to be looked up every time. */ #define NS_DYING 0x01 #define NS_DEAD 0x02 #define NS_TEARDOWN 0x04 #define NS_KILLED 0x04 /* Same as NS_TEARDOWN (Deprecated) */ #define NS_SUPPRESS_COMPILATION 0x08 /* * Flags passed to TclGetNamespaceForQualName: * * TCL_GLOBAL_ONLY - (see tcl.h) Look only in the global ns. * TCL_NAMESPACE_ONLY - (see tcl.h) Look only in the context ns. * TCL_CREATE_NS_IF_UNKNOWN - Create unknown namespaces. * TCL_FIND_ONLY_NS - The name sought is a namespace name. * TCL_FIND_IF_NOT_SIMPLE - Retrieve last namespace even if the rest of * name is not simple name (contains ::). */ #define TCL_CREATE_NS_IF_UNKNOWN 0x800 #define TCL_FIND_ONLY_NS 0x1000 #define TCL_FIND_IF_NOT_SIMPLE 0x2000 /* * The client data for an ensemble command. This consists of the table of * commands that are actually exported by the namespace, and an epoch counter * that, combined with the exportLookupEpoch field of the namespace structure, * defines whether the table contains valid data or will need to be recomputed * next time the ensemble command is called. */ typedef struct EnsembleConfig { Namespace *nsPtr; /* The namespace backing this ensemble up. */ Tcl_Command token; /* The token for the command that provides * ensemble support for the namespace, or NULL * if the command has been deleted (or never * existed; the global namespace never has an * ensemble command.) */ Tcl_Size epoch; /* The epoch at which this ensemble's table of * exported commands is valid. */ char **subcommandArrayPtr; /* Array of ensemble subcommand names. At all * consistent points, this will have the same * number of entries as there are entries in * the subcommandTable hash. */ Tcl_HashTable subcommandTable; /* Hash table of ensemble subcommand names, * which are its keys so this also provides * the storage management for those subcommand * names. The contents of the entry values are * object version the prefix lists to use when * substituting for the command/subcommand to * build the ensemble implementation command. * Has to be stored here as well as in * subcommandDict because that field is NULL * when we are deriving the ensemble from the * namespace exports list. FUTURE WORK: use * object hash table here. */ struct EnsembleConfig *next;/* The next ensemble in the linked list of * ensembles associated with a namespace. If * this field points to this ensemble, the * structure has already been unlinked from * all lists, and cannot be found by scanning * the list from the namespace's ensemble * field. */ int flags; /* OR'ed combo of TCL_ENSEMBLE_PREFIX, * ENSEMBLE_DEAD and ENSEMBLE_COMPILE. */ /* OBJECT FIELDS FOR ENSEMBLE CONFIGURATION */ Tcl_Obj *subcommandDict; /* Dictionary providing mapping from * subcommands to their implementing command * prefixes, or NULL if we are to build the * map automatically from the namespace * exports. */ Tcl_Obj *subcmdList; /* List of commands that this ensemble * actually provides, and whose implementation * will be built using the subcommandDict (if * present and defined) and by simple mapping * to the namespace otherwise. If NULL, * indicates that we are using the (dynamic) * list of currently exported commands. */ Tcl_Obj *unknownHandler; /* Script prefix used to handle the case when * no match is found (according to the rule * defined by flag bit TCL_ENSEMBLE_PREFIX) or * NULL to use the default error-generating * behaviour. The script execution gets all * the arguments to the ensemble command * (including objv[0]) and will have the * results passed directly back to the caller * (including the error code) unless the code * is TCL_CONTINUE in which case the * subcommand will be re-parsed by the ensemble * core, presumably because the ensemble * itself has been updated. */ Tcl_Obj *parameterList; /* List of ensemble parameter names. */ Tcl_Size numParameters; /* Cached number of parameters. This is either * 0 (if the parameterList field is NULL) or * the length of the list in the parameterList * field. */ } EnsembleConfig; /* * Various bits for the EnsembleConfig.flags field. */ #define ENSEMBLE_DEAD 0x1 /* Flag value to say that the ensemble is dead * and on its way out. */ #define ENSEMBLE_COMPILE 0x4 /* Flag to enable bytecode compilation of an * ensemble. */ /* *---------------------------------------------------------------- * Data structures related to variables. These are used primarily in tclVar.c *---------------------------------------------------------------- */ /* * The following structure defines a variable trace, which is used to invoke a * specific C procedure whenever certain operations are performed on a * variable. */ typedef struct VarTrace { Tcl_VarTraceProc *traceProc;/* Procedure to call when operations given by * flags are performed on variable. */ void *clientData; /* Argument to pass to proc. */ int flags; /* What events the trace procedure is * interested in: OR-ed combination of * TCL_TRACE_READS, TCL_TRACE_WRITES, * TCL_TRACE_UNSETS and TCL_TRACE_ARRAY. */ struct VarTrace *nextPtr; /* Next in list of traces associated with a * particular variable. */ } VarTrace; /* * The following structure defines a command trace, which is used to invoke a * specific C procedure whenever certain operations are performed on a * command. */ typedef struct CommandTrace { Tcl_CommandTraceProc *traceProc; /* Procedure to call when operations given by * flags are performed on command. */ void *clientData; /* Argument to pass to proc. */ int flags; /* What events the trace procedure is * interested in: OR-ed combination of * TCL_TRACE_RENAME, TCL_TRACE_DELETE. */ struct CommandTrace *nextPtr; /* Next in list of traces associated with a * particular command. */ Tcl_Size refCount; /* Used to ensure this structure is not * deleted too early. Keeps track of how many * pieces of code have a pointer to this * structure. */ } CommandTrace; /* * When a command trace is active (i.e. its associated procedure is executing) * one of the following structures is linked into a list associated with the * command's interpreter. The information in the structure is needed in order * for Tcl to behave reasonably if traces are deleted while traces are active. */ typedef struct ActiveCommandTrace { struct Command *cmdPtr; /* Command that's being traced. */ struct ActiveCommandTrace *nextPtr; /* Next in list of all active command traces * for the interpreter, or NULL if no more. */ CommandTrace *nextTracePtr; /* Next trace to check after current trace * procedure returns; if this trace gets * deleted, must update pointer to avoid using * free'd memory. */ int reverseScan; /* Boolean set true when traces are scanning * in reverse order. */ } ActiveCommandTrace; /* * When a variable trace is active (i.e. its associated procedure is * executing) one of the following structures is linked into a list associated * with the variable's interpreter. The information in the structure is needed * in order for Tcl to behave reasonably if traces are deleted while traces * are active. */ typedef struct ActiveVarTrace { struct Var *varPtr; /* Variable that's being traced. */ struct ActiveVarTrace *nextPtr; /* Next in list of all active variable traces * for the interpreter, or NULL if no more. */ VarTrace *nextTracePtr; /* Next trace to check after current trace * procedure returns; if this trace gets * deleted, must update pointer to avoid using * free'd memory. */ } ActiveVarTrace; /* * The structure below defines a variable, which associates a string name with * a Tcl_Obj value. These structures are kept in procedure call frames (for * local variables recognized by the compiler) or in the heap (for global * variables and any variable not known to the compiler). For each Var * structure in the heap, a hash table entry holds the variable name and a * pointer to the Var structure. */ typedef struct Var { int flags; /* Miscellaneous bits of information about * variable. See below for definitions. */ union { Tcl_Obj *objPtr; /* The variable's object value. Used for * scalar variables and array elements. */ TclVarHashTable *tablePtr;/* For array variables, this points to * information about the hash table used to * implement the associative array. Points to * Tcl_Alloc-ed data. */ struct Var *linkPtr; /* If this is a global variable being referred * to in a procedure, or a variable created by * "upvar", this field points to the * referenced variable's Var struct. */ } value; } Var; typedef struct VarInHash { Var var; /* "Inherit" from Var. */ Tcl_Size refCount; /* Counts number of active uses of this * variable: 1 for the entry in the hash * table, 1 for each additional variable whose * linkPtr points here, 1 for each nested * trace active on variable, and 1 if the * variable is a namespace variable. This * record can't be deleted until refCount * becomes 0. */ Tcl_HashEntry entry; /* The hash table entry that refers to this * variable. This is used to find the name of * the variable and to delete it from its * hash table if it is no longer needed. It * also holds the variable's name. */ } VarInHash; /* * Flag bits for variables. The first two (VAR_ARRAY and VAR_LINK) are * mutually exclusive and give the "type" of the variable. If none is set, * this is a scalar variable. * * VAR_ARRAY - 1 means this is an array variable rather than * a scalar variable or link. The "tablePtr" * field points to the array's hash table for its * elements. * VAR_LINK - 1 means this Var structure contains a pointer * to another Var structure that either has the * real value or is itself another VAR_LINK * pointer. Variables like this come about * through "upvar" and "global" commands, or * through references to variables in enclosing * namespaces. * VAR_CONSTANT - 1 means this is a constant "variable", and * cannot be written to by ordinary commands. * Structurally, it's the same as a scalar when * being read, but writes are rejected. Constants * are not supported inside arrays. * * Flags that indicate the type and status of storage; none is set for * compiled local variables (Var structs). * * VAR_IN_HASHTABLE - 1 means this variable is in a hash table and * the Var structure is malloc'ed. 0 if it is a * local variable that was assigned a slot in a * procedure frame by the compiler so the Var * storage is part of the call frame. * VAR_DEAD_HASH 1 means that this var's entry in the hash table * has already been deleted. * VAR_ARRAY_ELEMENT - 1 means that this variable is an array * element, so it is not legal for it to be an * array itself (the VAR_ARRAY flag had better * not be set). * VAR_NAMESPACE_VAR - 1 means that this variable was declared as a * namespace variable. This flag ensures it * persists until its namespace is destroyed or * until the variable is unset; it will persist * even if it has not been initialized and is * marked undefined. The variable's refCount is * incremented to reflect the "reference" from * its namespace. * * Flag values relating to the variable's trace and search status. * * VAR_TRACED_READ * VAR_TRACED_WRITE * VAR_TRACED_UNSET * VAR_TRACED_ARRAY * VAR_TRACE_ACTIVE - 1 means that trace processing is currently * underway for a read or write access, so new * read or write accesses should not cause trace * procedures to be called and the variable can't * be deleted. * VAR_SEARCH_ACTIVE * * The following additional flags are used with the CompiledLocal type defined * below: * * VAR_ARGUMENT - 1 means that this variable holds a procedure * argument. * VAR_TEMPORARY - 1 if the local variable is an anonymous * temporary variable. Temporaries have a NULL * name. * VAR_RESOLVED - 1 if name resolution has been done for this * variable. * VAR_IS_ARGS 1 if this variable is the last argument and is * named "args". */ /* * FLAGS RENUMBERED: everything breaks already, make things simpler. * * IMPORTANT: skip the values 0x10, 0x20, 0x40, 0x800 corresponding to * TCL_TRACE_(READS/WRITES/UNSETS/ARRAY): makes code simpler in tclTrace.c * * Keep the flag values for VAR_ARGUMENT and VAR_TEMPORARY so that old values * in precompiled scripts keep working. */ /* Type of value (0 is scalar) */ #define VAR_ARRAY 0x1 #define VAR_LINK 0x2 #define VAR_CONSTANT 0x10000 /* Type of storage (0 is compiled local) */ #define VAR_IN_HASHTABLE 0x4 #define VAR_DEAD_HASH 0x8 #define VAR_ARRAY_ELEMENT 0x1000 #define VAR_NAMESPACE_VAR 0x80 /* KEEP OLD VALUE for Itcl */ #define VAR_ALL_HASH \ (VAR_IN_HASHTABLE|VAR_DEAD_HASH|VAR_NAMESPACE_VAR|VAR_ARRAY_ELEMENT) /* Trace and search state. */ #define VAR_TRACED_READ 0x10 /* TCL_TRACE_READS */ #define VAR_TRACED_WRITE 0x20 /* TCL_TRACE_WRITES */ #define VAR_TRACED_UNSET 0x40 /* TCL_TRACE_UNSETS */ #define VAR_TRACED_ARRAY 0x800 /* TCL_TRACE_ARRAY */ #define VAR_TRACE_ACTIVE 0x2000 #define VAR_SEARCH_ACTIVE 0x4000 #define VAR_ALL_TRACES \ (VAR_TRACED_READ|VAR_TRACED_WRITE|VAR_TRACED_ARRAY|VAR_TRACED_UNSET) /* Special handling on initialisation (only CompiledLocal). */ #define VAR_ARGUMENT 0x100 /* KEEP OLD VALUE! See tclProc.c */ #define VAR_TEMPORARY 0x200 /* KEEP OLD VALUE! See tclProc.c */ #define VAR_IS_ARGS 0x400 #define VAR_RESOLVED 0x8000 /* * Macros to ensure that various flag bits are set properly for variables. * The ANSI C "prototypes" for these macros are: * * MODULE_SCOPE void TclSetVarScalar(Var *varPtr); * MODULE_SCOPE void TclSetVarArray(Var *varPtr); * MODULE_SCOPE void TclSetVarLink(Var *varPtr); * MODULE_SCOPE void TclSetVarConstant(Var *varPtr); * MODULE_SCOPE void TclSetVarArrayElement(Var *varPtr); * MODULE_SCOPE void TclSetVarUndefined(Var *varPtr); * MODULE_SCOPE void TclClearVarUndefined(Var *varPtr); */ #define TclSetVarScalar(varPtr) \ (varPtr)->flags &= ~(VAR_ARRAY|VAR_LINK|VAR_CONSTANT) #define TclSetVarArray(varPtr) \ (varPtr)->flags = ((varPtr)->flags & ~VAR_LINK) | VAR_ARRAY #define TclSetVarLink(varPtr) \ (varPtr)->flags = ((varPtr)->flags & ~VAR_ARRAY) | VAR_LINK #define TclSetVarConstant(varPtr) \ (varPtr)->flags = ((varPtr)->flags & ~(VAR_ARRAY|VAR_LINK)) | VAR_CONSTANT #define TclSetVarArrayElement(varPtr) \ (varPtr)->flags = ((varPtr)->flags & ~VAR_ARRAY) | VAR_ARRAY_ELEMENT #define TclSetVarUndefined(varPtr) \ (varPtr)->flags &= ~(VAR_ARRAY|VAR_LINK|VAR_CONSTANT);\ (varPtr)->value.objPtr = NULL #define TclClearVarUndefined(varPtr) #define TclSetVarTraceActive(varPtr) \ (varPtr)->flags |= VAR_TRACE_ACTIVE #define TclClearVarTraceActive(varPtr) \ (varPtr)->flags &= ~VAR_TRACE_ACTIVE #define TclSetVarNamespaceVar(varPtr) \ if (!TclIsVarNamespaceVar(varPtr)) {\ (varPtr)->flags |= VAR_NAMESPACE_VAR;\ if (TclIsVarInHash(varPtr)) {\ ((VarInHash *)(varPtr))->refCount++;\ }\ } #define TclClearVarNamespaceVar(varPtr) \ if (TclIsVarNamespaceVar(varPtr)) {\ (varPtr)->flags &= ~VAR_NAMESPACE_VAR;\ if (TclIsVarInHash(varPtr)) {\ ((VarInHash *)(varPtr))->refCount--;\ }\ } /* * Macros to read various flag bits of variables. * The ANSI C "prototypes" for these macros are: * * MODULE_SCOPE int TclIsVarScalar(Var *varPtr); * MODULE_SCOPE int TclIsVarConstant(Var *varPtr); * MODULE_SCOPE int TclIsVarLink(Var *varPtr); * MODULE_SCOPE int TclIsVarArray(Var *varPtr); * MODULE_SCOPE int TclIsVarUndefined(Var *varPtr); * MODULE_SCOPE int TclIsVarArrayElement(Var *varPtr); * MODULE_SCOPE int TclIsVarTemporary(Var *varPtr); * MODULE_SCOPE int TclIsVarArgument(Var *varPtr); * MODULE_SCOPE int TclIsVarResolved(Var *varPtr); */ #define TclVarFindHiddenArray(varPtr,arrayPtr) \ do { \ if ((arrayPtr == NULL) && TclIsVarInHash(varPtr) && \ (TclVarParentArray(varPtr) != NULL)) { \ arrayPtr = TclVarParentArray(varPtr); \ } \ } while(0) #define TclIsVarScalar(varPtr) \ !((varPtr)->flags & (VAR_ARRAY|VAR_LINK)) #define TclIsVarLink(varPtr) \ ((varPtr)->flags & VAR_LINK) #define TclIsVarArray(varPtr) \ ((varPtr)->flags & VAR_ARRAY) /* Implies scalar as well. */ #define TclIsVarConstant(varPtr) \ ((varPtr)->flags & VAR_CONSTANT) #define TclIsVarUndefined(varPtr) \ ((varPtr)->value.objPtr == NULL) #define TclIsVarArrayElement(varPtr) \ ((varPtr)->flags & VAR_ARRAY_ELEMENT) #define TclIsVarNamespaceVar(varPtr) \ ((varPtr)->flags & VAR_NAMESPACE_VAR) #define TclIsVarTemporary(varPtr) \ ((varPtr)->flags & VAR_TEMPORARY) #define TclIsVarArgument(varPtr) \ ((varPtr)->flags & VAR_ARGUMENT) #define TclIsVarResolved(varPtr) \ ((varPtr)->flags & VAR_RESOLVED) #define TclIsVarTraceActive(varPtr) \ ((varPtr)->flags & VAR_TRACE_ACTIVE) #define TclIsVarTraced(varPtr) \ ((varPtr)->flags & VAR_ALL_TRACES) #define TclIsVarInHash(varPtr) \ ((varPtr)->flags & VAR_IN_HASHTABLE) #define TclIsVarDeadHash(varPtr) \ ((varPtr)->flags & VAR_DEAD_HASH) #define TclGetVarNsPtr(varPtr) \ (TclIsVarInHash(varPtr) \ ? ((TclVarHashTable *) ((((VarInHash *) (varPtr))->entry.tablePtr)))->nsPtr \ : NULL) #define TclVarParentArray(varPtr) \ ((TclVarHashTable *) ((VarInHash *) (varPtr))->entry.tablePtr)->arrayPtr #define VarHashRefCount(varPtr) \ ((VarInHash *) (varPtr))->refCount #define VarHashGetKey(varPtr) \ (((VarInHash *)(varPtr))->entry.key.objPtr) /* * Macros for direct variable access by TEBC. */ #define TclIsVarTricky(varPtr,trickyFlags) \ ( ((varPtr)->flags & (VAR_ARRAY|VAR_LINK|trickyFlags)) \ || (TclIsVarInHash(varPtr) \ && (TclVarParentArray(varPtr) != NULL) \ && (TclVarParentArray(varPtr)->flags & (trickyFlags)))) #define TclIsVarDirectReadable(varPtr) \ ( (!TclIsVarTricky(varPtr,VAR_TRACED_READ)) \ && (varPtr)->value.objPtr) #define TclIsVarDirectWritable(varPtr) \ (!TclIsVarTricky(varPtr,VAR_TRACED_WRITE|VAR_DEAD_HASH|VAR_CONSTANT)) #define TclIsVarDirectUnsettable(varPtr) \ (!TclIsVarTricky(varPtr,VAR_TRACED_READ|VAR_TRACED_WRITE|VAR_TRACED_UNSET|VAR_DEAD_HASH|VAR_CONSTANT)) #define TclIsVarDirectModifyable(varPtr) \ ( (!TclIsVarTricky(varPtr,VAR_TRACED_READ|VAR_TRACED_WRITE|VAR_CONSTANT)) \ && (varPtr)->value.objPtr) #define TclIsVarDirectReadable2(varPtr, arrayPtr) \ (TclIsVarDirectReadable(varPtr) &&\ (!(arrayPtr) || !((arrayPtr)->flags & VAR_TRACED_READ))) #define TclIsVarDirectWritable2(varPtr, arrayPtr) \ (TclIsVarDirectWritable(varPtr) &&\ (!(arrayPtr) || !((arrayPtr)->flags & VAR_TRACED_WRITE))) #define TclIsVarDirectModifyable2(varPtr, arrayPtr) \ (TclIsVarDirectModifyable(varPtr) &&\ (!(arrayPtr) || !((arrayPtr)->flags & (VAR_TRACED_READ|VAR_TRACED_WRITE)))) /* *---------------------------------------------------------------- * Data structures related to procedures. These are used primarily in * tclProc.c, tclCompile.c, and tclExecute.c. *---------------------------------------------------------------- */ #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) # define TCLFLEXARRAY #elif defined(__GNUC__) && (__GNUC__ > 2) # define TCLFLEXARRAY 0 #else # define TCLFLEXARRAY 1 #endif /* * Forward declaration to prevent an error when the forward reference to * Command is encountered in the Proc and ImportRef types declared below. */ struct Command; /* * The variable-length structure below describes a local variable of a * procedure that was recognized by the compiler. These variables have a name, * an element in the array of compiler-assigned local variables in the * procedure's call frame, and various other items of information. If the * local variable is a formal argument, it may also have a default value. The * compiler can't recognize local variables whose names are expressions (these * names are only known at runtime when the expressions are evaluated) or * local variables that are created as a result of an "upvar" or "uplevel" * command. These other local variables are kept separately in a hash table in * the call frame. */ typedef struct CompiledLocal { struct CompiledLocal *nextPtr; /* Next compiler-recognized local variable for * this procedure, or NULL if this is the last * local. */ Tcl_Size nameLength; /* The number of bytes in local variable's name. * Among others used to speed up var lookups. */ Tcl_Size frameIndex; /* Index in the array of compiler-assigned * variables in the procedure call frame. */ #if TCL_MAJOR_VERSION < 9 int flags; #endif Tcl_Obj *defValuePtr; /* Pointer to the default value of an * argument, if any. NULL if not an argument * or, if an argument, no default value. */ Tcl_ResolvedVarInfo *resolveInfo; /* Customized variable resolution info * supplied by the Tcl_ResolveCompiledVarProc * associated with a namespace. Each variable * is marked by a unique tag during * compilation, and that same tag is used to * find the variable at runtime. */ #if TCL_MAJOR_VERSION > 8 int flags; /* Flag bits for the local variable. Same as * the flags for the Var structure above, * although only VAR_ARGUMENT, VAR_TEMPORARY, * and VAR_RESOLVED make sense. */ #endif char name[TCLFLEXARRAY]; /* Name of the local variable starts here. If * the name is NULL, this will just be '\0'. * The actual size of this field will be large * enough to hold the name. MUST BE THE LAST * FIELD IN THE STRUCTURE! */ } CompiledLocal; /* * The structure below defines a command procedure, which consists of a * collection of Tcl commands plus information about arguments and other local * variables recognized at compile time. */ typedef struct Proc { struct Interp *iPtr; /* Interpreter for which this command is * defined. */ Tcl_Size refCount; /* Reference count: 1 if still present in * command table plus 1 for each call to the * procedure that is currently active. This * structure can be freed when refCount * becomes zero. */ struct Command *cmdPtr; /* Points to the Command structure for this * procedure. This is used to get the * namespace in which to execute the * procedure. */ Tcl_Obj *bodyPtr; /* Points to the ByteCode object for * procedure's body command. */ Tcl_Size numArgs; /* Number of formal parameters. */ Tcl_Size numCompiledLocals; /* Count of local variables recognized by the * compiler including arguments and * temporaries. */ CompiledLocal *firstLocalPtr; /* Pointer to first of the procedure's * compiler-allocated local variables, or NULL * if none. The first numArgs entries in this * list describe the procedure's formal * arguments. */ CompiledLocal *lastLocalPtr;/* Pointer to the last allocated local * variable or NULL if none. This has frame * index (numCompiledLocals-1). */ } Proc; /* * The type of functions called to process errors found during the execution * of a procedure (or lambda term or ...). */ typedef void (ProcErrorProc)(Tcl_Interp *interp, Tcl_Obj *procNameObj); /* * The structure below defines a command trace. This is used to allow Tcl * clients to find out whenever a command is about to be executed. */ typedef struct Trace { Tcl_Size level; /* Only trace commands at nesting level less * than or equal to this. */ #if TCL_MAJOR_VERSION > 8 Tcl_CmdObjTraceProc2 *proc; /* Procedure to call to trace command. */ #else Tcl_CmdObjTraceProc *proc; /* Procedure to call to trace command. */ #endif void *clientData; /* Arbitrary value to pass to proc. */ struct Trace *nextPtr; /* Next in list of traces for this interp. */ int flags; /* Flags governing the trace - see * Tcl_CreateObjTrace for details. */ Tcl_CmdObjTraceDeleteProc *delProc; /* Procedure to call when trace is deleted. */ } Trace; /* * When an interpreter trace is active (i.e. its associated procedure is * executing), one of the following structures is linked into a list * associated with the interpreter. The information in the structure is needed * in order for Tcl to behave reasonably if traces are deleted while traces * are active. */ typedef struct ActiveInterpTrace { struct ActiveInterpTrace *nextPtr; /* Next in list of all active command traces * for the interpreter, or NULL if no more. */ Trace *nextTracePtr; /* Next trace to check after current trace * procedure returns; if this trace gets * deleted, must update pointer to avoid using * free'd memory. */ int reverseScan; /* Boolean set true when traces are scanning * in reverse order. */ } ActiveInterpTrace; /* * Flag values designating types of execution traces. See tclTrace.c for * related flag values. * * TCL_TRACE_ENTER_EXEC - triggers enter/enterstep traces. * - passed to Tcl_CreateObjTrace to set up * "enterstep" traces. * TCL_TRACE_LEAVE_EXEC - triggers leave/leavestep traces. * - passed to Tcl_CreateObjTrace to set up * "leavestep" traces. */ #define TCL_TRACE_ENTER_EXEC 1 #define TCL_TRACE_LEAVE_EXEC 2 #if TCL_MAJOR_VERSION > 8 #define TclObjTypeHasProc(objPtr, proc) (((objPtr)->typePtr \ && ((offsetof(Tcl_ObjType, proc) < offsetof(Tcl_ObjType, version)) \ || (offsetof(Tcl_ObjType, proc) < (objPtr)->typePtr->version))) ? \ ((objPtr)->typePtr)->proc : NULL) MODULE_SCOPE Tcl_Size TclLengthOne(Tcl_Obj *); /* * Abstract List * * This structure provides the functions used in List operations to emulate a * List for AbstractList types. */ static inline Tcl_Size TclObjTypeLength( Tcl_Obj *objPtr) { Tcl_ObjTypeLengthProc *proc = TclObjTypeHasProc(objPtr, lengthProc); return proc(objPtr); } static inline int TclObjTypeIndex( Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size index, Tcl_Obj **elemObjPtr) { Tcl_ObjTypeIndexProc *proc = TclObjTypeHasProc(objPtr, indexProc); return proc(interp, objPtr, index, elemObjPtr); } static inline int TclObjTypeSlice( Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size fromIdx, Tcl_Size toIdx, Tcl_Obj **newObjPtr) { Tcl_ObjTypeSliceProc *proc = TclObjTypeHasProc(objPtr, sliceProc); return proc(interp, objPtr, fromIdx, toIdx, newObjPtr); } static inline int TclObjTypeReverse( Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj **newObjPtr) { Tcl_ObjTypeReverseProc *proc = TclObjTypeHasProc(objPtr, reverseProc); return proc(interp, objPtr, newObjPtr); } static inline int TclObjTypeGetElements( Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size *objCPtr, Tcl_Obj ***objVPtr) { Tcl_ObjTypeGetElements *proc = TclObjTypeHasProc(objPtr, getElementsProc); return proc(interp, objPtr, objCPtr, objVPtr); } static inline Tcl_Obj* TclObjTypeSetElement( Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size indexCount, Tcl_Obj *const indexArray[], Tcl_Obj *valueObj) { Tcl_ObjTypeSetElement *proc = TclObjTypeHasProc(objPtr, setElementProc); return proc(interp, objPtr, indexCount, indexArray, valueObj); } static inline int TclObjTypeReplace( Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size first, Tcl_Size numToDelete, Tcl_Size numToInsert, Tcl_Obj *const insertObjs[]) { Tcl_ObjTypeReplaceProc *proc = TclObjTypeHasProc(objPtr, replaceProc); return proc(interp, objPtr, first, numToDelete, numToInsert, insertObjs); } static inline int TclObjTypeInOperator( Tcl_Interp *interp, Tcl_Obj *valueObj, Tcl_Obj *listObj, int *boolResult) { Tcl_ObjTypeInOperatorProc *proc = TclObjTypeHasProc(listObj, inOperProc); return proc(interp, valueObj, listObj, boolResult); } #endif /* TCL_MAJOR_VERSION > 8 */ /* * The structure below defines an entry in the assocData hash table which is * associated with an interpreter. The entry contains a pointer to a function * to call when the interpreter is deleted, and a pointer to a user-defined * piece of data. */ typedef struct AssocData { Tcl_InterpDeleteProc *proc; /* Proc to call when deleting. */ void *clientData; /* Value to pass to proc. */ } AssocData; /* * The structure below defines a call frame. A call frame defines a naming * context for a procedure call: its local naming scope (for local variables) * and its global naming scope (a namespace, perhaps the global :: namespace). * A call frame can also define the naming context for a namespace eval or * namespace inscope command: the namespace in which the command's code should * execute. The Tcl_CallFrame structures exist only while procedures or * namespace eval/inscope's are being executed, and provide a kind of Tcl call * stack. * * WARNING!! The structure definition must be kept consistent with the * Tcl_CallFrame structure in tcl.h. If you change one, change the other. */ /* * Will be grown to contain: pointers to the varnames (allocated at the end), * plus the init values for each variable (suitable to be memcopied on init) */ typedef struct LocalCache { Tcl_Size refCount; /* Reference count. */ Tcl_Size numVars; /* Number of variables. */ Tcl_Obj *varName0; /* First variable name. */ } LocalCache; #define localName(framePtr, i) \ ((&((framePtr)->localCachePtr->varName0))[(i)]) MODULE_SCOPE void TclFreeLocalCache(Tcl_Interp *interp, LocalCache *localCachePtr); typedef struct CallFrame { Namespace *nsPtr; /* Points to the namespace used to resolve * commands and global variables. */ int isProcCallFrame; /* If 0, the frame was pushed to execute a * namespace command and var references are * treated as references to namespace vars; * varTablePtr and compiledLocals are ignored. * If FRAME_IS_PROC is set, the frame was * pushed to execute a Tcl procedure and may * have local vars. */ Tcl_Size objc; /* This and objv below describe the arguments * for this procedure call. */ Tcl_Obj *const *objv; /* Array of argument objects. */ struct CallFrame *callerPtr;/* Value of interp->framePtr when this * procedure was invoked (i.e. next higher in * stack of all active procedures). */ struct CallFrame *callerVarPtr; /* Value of interp->varFramePtr when this * procedure was invoked (i.e. determines * variable scoping within caller). Same as * callerPtr unless an "uplevel" command or * something equivalent was active in the * caller). */ Tcl_Size level; /* Level of this procedure, for "uplevel" * purposes (i.e. corresponds to nesting of * callerVarPtr's, not callerPtr's). 1 for * outermost procedure, 0 for top-level. */ Proc *procPtr; /* Points to the structure defining the called * procedure. Used to get information such as * the number of compiled local variables * (local variables assigned entries ["slots"] * in the compiledLocals array below). */ TclVarHashTable *varTablePtr; /* Hash table containing local variables not * recognized by the compiler, or created at * execution time through, e.g., upvar. * Initially NULL and created if needed. */ Tcl_Size numCompiledLocals; /* Count of local variables recognized * by the compiler including arguments. */ Var *compiledLocals; /* Points to the array of local variables * recognized by the compiler. The compiler * emits code that refers to these variables * using an index into this array. */ void *clientData; /* Pointer to some context that is used by * object systems. The meaning of the contents * of this field is defined by the code that * sets it, and it should only ever be set by * the code that is pushing the frame. In that * case, the code that sets it should also * have some means of discovering what the * meaning of the value is, which we do not * specify. */ LocalCache *localCachePtr; /* Pointer to the start of the cached variable * names and initialisation data for local * variables. */ Tcl_Obj *tailcallPtr; /* NULL if no tailcall is scheduled */ } CallFrame; #define FRAME_IS_PROC 0x1 /* Frame is a procedure body. */ #define FRAME_IS_LAMBDA 0x2 /* Frame is a lambda term body. */ #define FRAME_IS_METHOD 0x4 /* The frame is a method body, and the frame's * clientData field contains a CallContext * reference. Part of TIP#257. */ #define FRAME_IS_OO_DEFINE 0x8 /* The frame is part of the inside workings of * the [oo::define] command; the clientData * field contains an Object reference that has * been confirmed to refer to a class. Part of * TIP#257. */ #define FRAME_IS_PRIVATE_DEFINE 0x10 /* Marks this frame as being used for private * declarations with [oo::define]. Usually * OR'd with FRAME_IS_OO_DEFINE. TIP#500. */ /* * TIP #280 * The structure below defines a command frame. A command frame provides * location information for all commands executing a tcl script (source, eval, * uplevel, procedure bodies, ...). The runtime structure essentially contains * the stack trace as it would be if the currently executing command were to * throw an error. * * For commands where it makes sense it refers to the associated CallFrame as * well. * * The structures are chained in a single list, with the top of the stack * anchored in the Interp structure. * * Instances can be allocated on the C stack, or the heap, the former making * cleanup a bit simpler. */ typedef struct CmdFrame { /* * General data. Always available. */ int type; /* Values see below. */ int level; /* Number of frames in stack, prevent O(n) * scan of list. */ Tcl_Size *line; /* Lines the words of the command start on. */ Tcl_Size nline; /* Number of lines in CmdFrame.line. */ CallFrame *framePtr; /* Procedure activation record, may be * NULL. */ struct CmdFrame *nextPtr; /* Link to calling frame. */ /* * Data needed for Eval vs TEBC * * EXECUTION CONTEXTS and usage of CmdFrame * * Field TEBC EvalEx * ======= ==== ====== * level yes yes * type BC/PREBC SRC/EVAL * line0 yes yes * framePtr yes yes * ======= ==== ====== * * ======= ==== ========= union data * line1 - yes * line3 - yes * path - yes * ------- ---- ------ * codePtr yes - * pc yes - * ======= ==== ====== * * ======= ==== ========= union cmd * str.cmd yes yes * str.len yes yes * ------- ---- ------ */ union { struct { Tcl_Obj *path; /* Path of the sourced file the command is * in. */ } eval; struct { const void *codePtr;/* Byte code currently executed... */ const char *pc; /* ... and instruction pointer. */ } tebc; } data; Tcl_Obj *cmdObj; const char *cmd; /* The executed command, if possible... */ Tcl_Size len; /* ... and its length. */ const struct CFWordBC *litarg; /* Link to set of literal arguments which have * ben pushed on the lineLABCPtr stack by * TclArgumentBCEnter(). These will be removed * by TclArgumentBCRelease. */ } CmdFrame; typedef struct CFWord { CmdFrame *framePtr; /* CmdFrame to access. */ Tcl_Size word; /* Index of the word in the command. */ Tcl_Size refCount; /* Number of times the word is on the * stack. */ } CFWord; typedef struct CFWordBC { CmdFrame *framePtr; /* CmdFrame to access. */ Tcl_Size pc; /* Instruction pointer of a command in * ExtCmdLoc.loc[.] */ Tcl_Size word; /* Index of word in * ExtCmdLoc.loc[cmd]->line[.] */ struct CFWordBC *prevPtr; /* Previous entry in stack for same Tcl_Obj. */ struct CFWordBC *nextPtr; /* Next entry for same command call. See * CmdFrame litarg field for the list start. */ Tcl_Obj *obj; /* Back reference to hash table key */ } CFWordBC; /* * Structure to record the locations of invisible continuation lines in * literal scripts, as character offset from the beginning of the script. Both * compiler and direct evaluator use this information to adjust their line * counters when tracking through the script, because when it is invoked the * continuation line marker as a whole has been removed already, meaning that * the \n which was part of it is gone as well, breaking regular line * tracking. * * These structures are allocated and filled by both the function * TclSubstTokens() in the file "tclParse.c" and its caller TclEvalEx() in the * file "tclBasic.c", and stored in the thread-global hash table "lineCLPtr" in * file "tclObj.c". They are used by the functions TclSetByteCodeFromAny() and * TclCompileScript(), both found in the file "tclCompile.c". Their memory is * released by the function TclFreeObj(), in the file "tclObj.c", and also by * the function TclThreadFinalizeObjects(), in the same file. */ #define CLL_END (-1) typedef struct ContLineLoc { Tcl_Size num; /* Number of entries in loc, not counting the * final -1 marker entry. */ Tcl_Size loc[TCLFLEXARRAY];/* Table of locations, as character offsets. * The table is allocated as part of the * structure, extending behind the nominal end * of the structure. An entry containing the * value -1 is put after the last location, as * end-marker/sentinel. */ } ContLineLoc; /* * The following macros define the allowed values for the type field of the * CmdFrame structure above. Some of the values occur only in the extended * location data referenced via the 'baseLocPtr'. * * TCL_LOCATION_EVAL : Frame is for a script evaluated by EvalEx. * TCL_LOCATION_BC : Frame is for bytecode. * TCL_LOCATION_PREBC : Frame is for precompiled bytecode. * TCL_LOCATION_SOURCE : Frame is for a script evaluated by EvalEx, from a * sourced file. * TCL_LOCATION_PROC : Frame is for bytecode of a procedure. * * A TCL_LOCATION_BC type in a frame can be overridden by _SOURCE and _PROC * types, per the context of the byte code in execution. */ #define TCL_LOCATION_EVAL (0) /* Location in a dynamic eval script. */ #define TCL_LOCATION_BC (2) /* Location in byte code. */ #define TCL_LOCATION_PREBC (3) /* Location in precompiled byte code, no * location. */ #define TCL_LOCATION_SOURCE (4) /* Location in a file. */ #define TCL_LOCATION_PROC (5) /* Location in a dynamic proc. */ #define TCL_LOCATION_LAST (6) /* Number of values in the enum. */ /* * Structure passed to describe procedure-like "procedures" that are not * procedures (e.g. a lambda) so that their details can be reported correctly * by [info frame]. Contains a sub-structure for each extra field. */ typedef Tcl_Obj * (GetFrameInfoValueProc)(void *clientData); typedef struct { const char *name; /* Name of this field. */ GetFrameInfoValueProc *proc;/* Function to generate a Tcl_Obj* from the * clientData, or just use the clientData * directly (after casting) if NULL. */ void *clientData; /* Context for above function, or Tcl_Obj* if * proc field is NULL. */ } ExtraFrameInfoField; typedef struct { Tcl_Size length; /* Length of array. */ ExtraFrameInfoField fields[2]; /* Really as long as necessary, but this is * long enough for nearly anything. */ } ExtraFrameInfo; /* *---------------------------------------------------------------- * Data structures and procedures related to TclHandles, which are a very * lightweight method of preserving enough information to determine if an * arbitrary malloc'd block has been deleted. *---------------------------------------------------------------- */ typedef void **TclHandle; /* *---------------------------------------------------------------- * Experimental flag value passed to Tcl_GetRegExpFromObj. Intended for use * only by Expect. It will probably go away in a later release. *---------------------------------------------------------------- */ #define TCL_REG_BOSONLY 002000 /* Prepend \A to pattern so it only matches at * the beginning of the string. */ /* * These are a thin layer over TclpThreadKeyDataGet and TclpThreadKeyDataSet * when threads are used, or an emulation if there are no threads. These are * really internal and Tcl clients should use Tcl_GetThreadData. */ MODULE_SCOPE void * TclThreadDataKeyGet(Tcl_ThreadDataKey *keyPtr); MODULE_SCOPE void TclThreadDataKeySet(Tcl_ThreadDataKey *keyPtr, void *data); /* * This is a convenience macro used to initialize a thread local storage ptr. */ #define TCL_TSD_INIT(keyPtr) \ (ThreadSpecificData *)Tcl_GetThreadData((keyPtr), sizeof(ThreadSpecificData)) /* *---------------------------------------------------------------- * Data structures related to bytecode compilation and execution. These are * used primarily in tclCompile.c, tclExecute.c, and tclBasic.c. *---------------------------------------------------------------- */ /* * Forward declaration to prevent errors when the forward references to * Tcl_Parse and CompileEnv are encountered in the procedure type CompileProc * declared below. */ struct CompileEnv; /* * The type of procedures called by the Tcl bytecode compiler to compile * commands. Pointers to these procedures are kept in the Command structure * describing each command. The integer value returned by a CompileProc must * be one of the following: * * TCL_OK Compilation completed normally. * TCL_ERROR Compilation could not be completed. This can be just a * judgment by the CompileProc that the command is too * complex to compile effectively, or it can indicate * that in the current state of the interp, the command * would raise an error. The bytecode compiler will not * do any error reporting at compiler time. Error * reporting is deferred until the actual runtime, * because by then changes in the interp state may allow * the command to be successfully evaluated. */ typedef int (CompileProc)(Tcl_Interp *interp, Tcl_Parse *parsePtr, struct Command *cmdPtr, struct CompileEnv *compEnvPtr); /* * The type of procedure called from the compilation hook point in * SetByteCodeFromAny. */ typedef int (CompileHookProc)(Tcl_Interp *interp, struct CompileEnv *compEnvPtr, void *clientData); /* * The data structure for a (linked list of) execution stacks. Note that the * first word on a particular execution stack is NULL, which is used as a * marker to say "go to the previous stack in the list" when unwinding the * stack. */ typedef struct ExecStack { struct ExecStack *prevPtr; /* Previous stack in list. */ struct ExecStack *nextPtr; /* Next stack in list. */ Tcl_Obj **markerPtr; /* The location of the NULL marker. */ Tcl_Obj **endPtr; /* Where the stack end is. */ Tcl_Obj **tosPtr; /* Where the stack top is. */ Tcl_Obj *stackWords[TCLFLEXARRAY]; /* The actual stack space, following this * structure in memory. */ } ExecStack; /* * Saved copies of the stack frame references from the interpreter. Have to be * restored into the interpreter to be used. */ typedef struct CorContext { CallFrame *framePtr; /* See Interp.framePtr */ CallFrame *varFramePtr; /* See Interp.varFramePtr */ CmdFrame *cmdFramePtr; /* See Interp.cmdFramePtr */ Tcl_HashTable *lineLABCPtr; /* See Interp.lineLABCPtr */ } CorContext; /* * The data structure defining the execution environment for ByteCode's. * There is one ExecEnv structure per Tcl interpreter. It holds the evaluation * stack that holds command operands and results. The stack grows towards * increasing addresses. The member stackPtr points to the stackItems of the * currently active execution stack. */ typedef struct CoroutineData { struct Command *cmdPtr; /* The command handle for the coroutine. */ struct ExecEnv *eePtr; /* The special execution environment (stacks, * etc.) for the coroutine. */ struct ExecEnv *callerEEPtr;/* The execution environment for the caller of * the coroutine, which might be the * interpreter global environment or another * coroutine. */ CorContext caller; /* Caller's saved execution context. */ CorContext running; /* This coroutine's saved execution context. */ Tcl_HashTable *lineLABCPtr; /* See Interp.lineLABCPtr */ void *stackLevel; /* C stack frame reference. Used to try to * ensure we don't overflow that stack. */ Tcl_Size auxNumLevels; /* While the coroutine is running the * numLevels of the create/resume command is * stored here; for suspended coroutines it * holds the nesting numLevels at yield. */ Tcl_Size nargs; /* Number of args required for resuming this * coroutine; COROUTINE_ARGUMENTS_SINGLE_OPTIONAL * means "0 or 1" (default), * COROUTINE_ARGUMENTS_ARBITRARY means "any" */ Tcl_Obj *yieldPtr; /* The command to yield to. Stored here in * order to reset splice point in * TclNRCoroutineActivateCallback if the * coroutine is busy. */ } CoroutineData; typedef struct ExecEnv { ExecStack *execStackPtr; /* Points to the first item in the evaluation * stack on the heap. */ Tcl_Obj *constants[2]; /* Pointers to constant "0" and "1" objs. */ struct Tcl_Interp *interp; /* Owning interpreter. */ struct NRE_callback *callbackPtr; /* Top callback in NRE's stack. */ struct CoroutineData *corPtr; /* Current coroutine. */ int rewind; /* Set when exception trapping is disabled * because a context is being deleted (e.g., * the current coroutine has been deleted). */ } ExecEnv; #define COR_IS_SUSPENDED(corPtr) \ ((corPtr)->stackLevel == NULL) /* * The definitions for the LiteralTable and LiteralEntry structures. Each * interpreter contains a LiteralTable. It is used to reduce the storage * needed for all the Tcl objects that hold the literals of scripts compiled * by the interpreter. A literal's object is shared by all the ByteCodes that * refer to the literal. Each distinct literal has one LiteralEntry entry in * the LiteralTable. A literal table is a specialized hash table that is * indexed by the literal's string representation, which may contain null * characters. * * Note that we reduce the space needed for literals by sharing literal * objects both within a ByteCode (each ByteCode contains a local * LiteralTable) and across all an interpreter's ByteCodes (with the * interpreter's global LiteralTable). */ typedef struct LiteralEntry { struct LiteralEntry *nextPtr; /* Points to next entry in this hash bucket or * NULL if end of chain. */ Tcl_Obj *objPtr; /* Points to Tcl object that holds the * literal's bytes and length. */ Tcl_Size refCount; /* If in an interpreter's global literal * table, the number of ByteCode structures * that share the literal object; the literal * entry can be freed when refCount drops to * 0. If in a local literal table, TCL_INDEX_NONE. */ Namespace *nsPtr; /* Namespace in which this literal is used. We * try to avoid sharing literal non-FQ command * names among different namespaces to reduce * shimmering. */ } LiteralEntry; typedef struct LiteralTable { LiteralEntry **buckets; /* Pointer to bucket array. Each element * points to first entry in bucket's hash * chain, or NULL. */ LiteralEntry *staticBuckets[TCL_SMALL_HASH_TABLE]; /* Bucket array used for small tables to avoid * mallocs and frees. */ TCL_HASH_TYPE numBuckets; /* Total number of buckets allocated at * **buckets. */ TCL_HASH_TYPE numEntries; /* Total number of entries present in * table. */ TCL_HASH_TYPE rebuildSize; /* Enlarge table when numEntries gets to be * this large. */ TCL_HASH_TYPE mask; /* Mask value used in hashing function. */ } LiteralTable; /* * The following structure defines for each Tcl interpreter various * statistics-related information about the bytecode compiler and * interpreter's operation in that interpreter. */ #ifdef TCL_COMPILE_STATS typedef struct ByteCodeStats { size_t numExecutions; /* Number of ByteCodes executed. */ size_t numCompilations; /* Number of ByteCodes created. */ size_t numByteCodesFreed; /* Number of ByteCodes destroyed. */ size_t instructionCount[256]; /* Number of times each instruction was * executed. */ double totalSrcBytes; /* Total source bytes ever compiled. */ double totalByteCodeBytes; /* Total bytes for all ByteCodes. */ double currentSrcBytes; /* Src bytes for all current ByteCodes. */ double currentByteCodeBytes;/* Code bytes in all current ByteCodes. */ size_t srcCount[32]; /* Source size distribution: # of srcs of * size [2**(n-1)..2**n), n in [0..32). */ size_t byteCodeCount[32]; /* ByteCode size distribution. */ size_t lifetimeCount[32]; /* ByteCode lifetime distribution (ms). */ double currentInstBytes; /* Instruction bytes-current ByteCodes. */ double currentLitBytes; /* Current literal bytes. */ double currentExceptBytes; /* Current exception table bytes. */ double currentAuxBytes; /* Current auxiliary information bytes. */ double currentCmdMapBytes; /* Current src<->code map bytes. */ size_t numLiteralsCreated; /* Total literal objects ever compiled. */ double totalLitStringBytes; /* Total string bytes in all literals. */ double currentLitStringBytes; /* String bytes in current literals. */ size_t literalCount[32]; /* Distribution of literal string sizes. */ } ByteCodeStats; #endif /* TCL_COMPILE_STATS */ /* * Structure used in implementation of those core ensembles which are * partially compiled. Used as an array of these, with a terminating field * whose 'name' is NULL. */ typedef struct { const char *name; /* The name of the subcommand. */ Tcl_ObjCmdProc *proc; /* The implementation of the subcommand. */ CompileProc *compileProc; /* The compiler for the subcommand. */ Tcl_ObjCmdProc *nreProc; /* NRE implementation of this command. */ void *clientData; /* Any clientData to give the command. */ int unsafe; /* Whether this command is to be hidden by * default in a safe interpreter. */ } EnsembleImplMap; /* *---------------------------------------------------------------- * Data structures related to commands. *---------------------------------------------------------------- */ /* * An imported command is created in an namespace when it imports a "real" * command from another namespace. An imported command has a Command structure * that points (via its ClientData value) to the "real" Command structure in * the source namespace's command table. The real command records all the * imported commands that refer to it in a list of ImportRef structures so * that they can be deleted when the real command is deleted. */ typedef struct ImportRef { struct Command *importedCmdPtr; /* Points to the imported command created in * an importing namespace; this command * redirects its invocations to the "real" * command. */ struct ImportRef *nextPtr; /* Next element on the linked list of imported * commands that refer to the "real" command. * The real command deletes these imported * commands on this list when it is * deleted. */ } ImportRef; /* * Data structure used as the ClientData of imported commands: commands * created in an namespace when it imports a "real" command from another * namespace. */ typedef struct ImportedCmdData { struct Command *realCmdPtr; /* "Real" command that this imported command * refers to. */ struct Command *selfPtr; /* Pointer to this imported command. Needed * only when deleting it in order to remove it * from the real command's linked list of * imported commands that refer to it. */ } ImportedCmdData; /* * A Command structure exists for each command in a namespace. The Tcl_Command * opaque type actually refers to these structures. */ typedef struct Command { Tcl_HashEntry *hPtr; /* Pointer to the hash table entry that refers * to this command. The hash table is either a * namespace's command table or an * interpreter's hidden command table. This * pointer is used to get a command's name * from its Tcl_Command handle. NULL means * that the hash table entry has been removed * already (this can happen if deleteProc * causes the command to be deleted or * recreated). */ Namespace *nsPtr; /* Points to the namespace containing this * command. */ Tcl_Size refCount; /* 1 if in command hashtable plus 1 for each * reference from a CmdName Tcl object * representing a command's name in a ByteCode * instruction sequence. This structure can be * freed when refCount becomes zero. */ Tcl_Size cmdEpoch; /* Incremented to invalidate any references * that point to this command when it is * renamed, deleted, hidden, or exposed. */ CompileProc *compileProc; /* Procedure called to compile command. NULL * if no compile proc exists for command. */ Tcl_ObjCmdProc *objProc; /* Object-based command procedure. */ void *objClientData; /* Arbitrary value passed to object proc. */ Tcl_CmdProc *proc; /* String-based command procedure. */ void *clientData; /* Arbitrary value passed to string proc. */ Tcl_CmdDeleteProc *deleteProc; /* Procedure invoked when deleting command to, * e.g., free all client data. */ void *deleteData; /* Arbitrary value passed to deleteProc. */ int flags; /* Miscellaneous bits of information about * command. See below for definitions. */ ImportRef *importRefPtr; /* List of each imported Command created in * another namespace when this command is * imported. These imported commands redirect * invocations back to this command. The list * is used to remove all those imported * commands when deleting this "real" * command. */ CommandTrace *tracePtr; /* First in list of all traces set for this * command. */ Tcl_ObjCmdProc *nreProc; /* NRE implementation of this command. */ } Command; /* * Flag bits for commands. * * CMD_DYING - If 1 the command is in the process of * being deleted (its deleteProc is currently * executing). Other attempts to delete the * command should be ignored. * CMD_TRACE_ACTIVE - If 1 the trace processing is currently * underway for a rename/delete change. See the * two flags below for which is currently being * processed. * CMD_HAS_EXEC_TRACES - If 1 means that this command has at least one * execution trace (as opposed to simple * delete/rename traces) in its tracePtr list. * CMD_COMPILES_EXPANDED - If 1 this command has a compiler that * can handle expansion (provided it is not the * first word). * TCL_TRACE_RENAME - A rename trace is in progress. Further * recursive renames will not be traced. * TCL_TRACE_DELETE - A delete trace is in progress. Further * recursive deletes will not be traced. * (these last two flags are defined in tcl.h) */ #define CMD_DYING 0x01 #define CMD_TRACE_ACTIVE 0x02 #define CMD_HAS_EXEC_TRACES 0x04 #define CMD_COMPILES_EXPANDED 0x08 #define CMD_REDEF_IN_PROGRESS 0x10 #define CMD_VIA_RESOLVER 0x20 #define CMD_DEAD 0x40 /* *---------------------------------------------------------------- * Data structures related to name resolution procedures. *---------------------------------------------------------------- */ /* * The interpreter keeps a linked list of name resolution schemes. The scheme * for a namespace is consulted first, followed by the list of schemes in an * interpreter, followed by the default name resolution in Tcl. Schemes are * added/removed from the interpreter's list by calling Tcl_AddInterpResolver * and Tcl_RemoveInterpResolver. */ typedef struct ResolverScheme { char *name; /* Name identifying this scheme. */ Tcl_ResolveCmdProc *cmdResProc; /* Procedure handling command name * resolution. */ Tcl_ResolveVarProc *varResProc; /* Procedure handling variable name resolution * for variables that can only be handled at * runtime. */ Tcl_ResolveCompiledVarProc *compiledVarResProc; /* Procedure handling variable name resolution * at compile time. */ struct ResolverScheme *nextPtr; /* Pointer to next record in linked list. */ } ResolverScheme; /* * Forward declaration of the TIP#143 limit handler structure. */ typedef struct LimitHandler LimitHandler; /* * TIP #268. * Values for the selection mode, i.e the package require preferences. */ enum PkgPreferOptions { PKG_PREFER_LATEST, PKG_PREFER_STABLE }; /* *---------------------------------------------------------------- * This structure shadows the first few fields of the memory cache for the * allocator defined in tclThreadAlloc.c; it has to be kept in sync with the * definition there. * Some macros require knowledge of some fields in the struct in order to * avoid hitting the TSD unnecessarily. In order to facilitate this, a pointer * to the relevant fields is kept in the allocCache field in struct Interp. *---------------------------------------------------------------- */ typedef struct AllocCache { struct Cache *nextPtr; /* Linked list of cache entries. */ Tcl_ThreadId owner; /* Which thread's cache is this? */ Tcl_Obj *firstObjPtr; /* List of free objects for thread. */ size_t numObjects; /* Number of objects for thread. */ } AllocCache; /* *---------------------------------------------------------------- * This structure defines an interpreter, which is a collection of commands * plus other state information related to interpreting commands, such as * variable storage. Primary responsibility for this data structure is in * tclBasic.c, but almost every Tcl source file uses something in here. *---------------------------------------------------------------- */ typedef struct Interp { /* * The first two fields were named "result" and "freeProc" in earlier * versions of Tcl. They are no longer used within Tcl, and are no * longer available to be accessed by extensions. However, they cannot * be removed. Why? There is a deployed base of stub-enabled extensions * that query the value of iPtr->stubTable. For them to continue to work, * the location of the field "stubTable" within the Interp struct cannot * change. The most robust way to assure that is to leave all fields up to * that one undisturbed. */ const char *legacyResult; void (*legacyFreeProc) (void); int errorLine; /* When TCL_ERROR is returned, this gives the * line number in the command where the error * occurred (1 means first line). */ const struct TclStubs *stubTable; /* Pointer to the exported Tcl stub table. In * ancient pre-8.1 versions of Tcl this was a * pointer to the objResultPtr or a pointer to a * buckets array in a hash table. Deployed stubs * enabled extensions check for a NULL pointer value * and for a TCL_STUBS_MAGIC value to verify they * are not [load]ing into one of those pre-stubs * interps. */ TclHandle handle; /* Handle used to keep track of when this * interp is deleted. */ Namespace *globalNsPtr; /* The interpreter's global namespace. */ Tcl_HashTable *hiddenCmdTablePtr; /* Hash table used by tclBasic.c to keep track * of hidden commands on a per-interp * basis. */ void *interpInfo; /* Information used by tclInterp.c to keep * track of parent/child interps on a * per-interp basis. */ #if TCL_MAJOR_VERSION > 8 void (*optimizer)(void *envPtr); /* Reference to the bytecode optimizer, if one * is set. */ #else union { void (*optimizer)(void *envPtr); Tcl_HashTable unused2; /* No longer used (was mathFuncTable). The * unused space in interp was repurposed for * pluggable bytecode optimizers. The core * contains one optimizer, which can be * selectively overridden by extensions. */ } extra; #endif /* * Information related to procedures and variables. See tclProc.c and * tclVar.c for usage. */ Tcl_Size numLevels; /* Keeps track of how many nested calls to * Tcl_Eval are in progress for this * interpreter. It's used to delay deletion of * the table until all Tcl_Eval invocations * are completed. */ Tcl_Size maxNestingDepth; /* If numLevels exceeds this value then Tcl * assumes that infinite recursion has * occurred and it generates an error. */ CallFrame *framePtr; /* Points to top-most in stack of all nested * procedure invocations. */ CallFrame *varFramePtr; /* Points to the call frame whose variables * are currently in use (same as framePtr * unless an "uplevel" command is * executing). */ ActiveVarTrace *activeVarTracePtr; /* First in list of active traces for interp, * or NULL if no active traces. */ int returnCode; /* [return -code] parameter. */ CallFrame *rootFramePtr; /* Global frame pointer for this * interpreter. */ Namespace *lookupNsPtr; /* Namespace to use ONLY on the next * TCL_EVAL_INVOKE call to Tcl_EvalObjv. */ #if TCL_MAJOR_VERSION < 9 char *appendResultDontUse; int appendAvlDontUse; int appendUsedDontUse; #endif /* * Information about packages. Used only in tclPkg.c. */ Tcl_HashTable packageTable; /* Describes all of the packages loaded in or * available to this interpreter. Keys are * package names, values are (Package *) * pointers. */ char *packageUnknown; /* Command to invoke during "package require" * commands for packages that aren't described * in packageTable. Ckalloc'ed, may be * NULL. */ /* * Miscellaneous information: */ Tcl_Size cmdCount; /* Total number of times a command procedure * has been called for this interpreter. */ int evalFlags; /* Flags to control next call to Tcl_Eval. * Normally zero, but may be set before * calling Tcl_Eval. See below for valid * values. */ #if TCL_MAJOR_VERSION < 9 int unused1; /* No longer used (was termOffset) */ #endif LiteralTable literalTable; /* Contains LiteralEntry's describing all Tcl * objects holding literals of scripts * compiled by the interpreter. Indexed by the * string representations of literals. Used to * avoid creating duplicate objects. */ Tcl_Size compileEpoch; /* Holds the current "compilation epoch" for * this interpreter. This is incremented to * invalidate existing ByteCodes when, e.g., a * command with a compile procedure is * redefined. */ Proc *compiledProcPtr; /* If a procedure is being compiled, a pointer * to its Proc structure; otherwise, this is * NULL. Set by ObjInterpProc in tclProc.c and * used by tclCompile.c to process local * variables appropriately. */ ResolverScheme *resolverPtr;/* Linked list of name resolution schemes * added to this interpreter. Schemes are * added and removed by calling * Tcl_AddInterpResolvers and * Tcl_RemoveInterpResolver respectively. */ Tcl_Obj *scriptFile; /* NULL means there is no nested source * command active; otherwise this points to * pathPtr of the file being sourced. */ int flags; /* Various flag bits. See below. */ long randSeed; /* Seed used for rand() function. */ Trace *tracePtr; /* List of traces for this interpreter. */ Tcl_HashTable *assocData; /* Hash table for associating data with this * interpreter. Cleaned up when this * interpreter is deleted. */ struct ExecEnv *execEnvPtr; /* Execution environment for Tcl bytecode * execution. Contains a pointer to the Tcl * evaluation stack. */ Tcl_Obj *emptyObjPtr; /* Points to an object holding an empty * string. Returned by Tcl_ObjSetVar2 when * variable traces change a variable in a * gross way. */ #if TCL_MAJOR_VERSION < 9 char resultSpaceDontUse[TCL_DSTRING_STATIC_SIZE+1]; #endif Tcl_Obj *objResultPtr; /* If the last command returned an object * result, this points to it. Should not be * accessed directly; see comment above. */ Tcl_ThreadId threadId; /* ID of thread that owns the interpreter. */ ActiveCommandTrace *activeCmdTracePtr; /* First in list of active command traces for * interp, or NULL if no active traces. */ ActiveInterpTrace *activeInterpTracePtr; /* First in list of active traces for interp, * or NULL if no active traces. */ Tcl_Size tracesForbiddingInline; /* Count of traces (in the list headed by * tracePtr) that forbid inline bytecode * compilation. */ /* * Fields used to manage extensible return options (TIP 90). */ Tcl_Obj *returnOpts; /* A dictionary holding the options to the * last [return] command. */ Tcl_Obj *errorInfo; /* errorInfo value (now as a Tcl_Obj). */ Tcl_Obj *eiVar; /* cached ref to ::errorInfo variable. */ Tcl_Obj *errorCode; /* errorCode value (now as a Tcl_Obj). */ Tcl_Obj *ecVar; /* cached ref to ::errorInfo variable. */ int returnLevel; /* [return -level] parameter. */ /* * Resource limiting framework support (TIP#143). */ struct { int active; /* Flag values defining which limits have been * set. */ int granularityTicker; /* Counter used to determine how often to * check the limits. */ int exceeded; /* Which limits have been exceeded, described * as flag values the same as the 'active' * field. */ Tcl_Size cmdCount; /* Limit for how many commands to execute in * the interpreter. */ LimitHandler *cmdHandlers; /* Handlers to execute when the limit is * reached. */ int cmdGranularity; /* Mod factor used to determine how often to * evaluate the limit check. */ Tcl_Time time; /* Time limit for execution within the * interpreter. */ LimitHandler *timeHandlers; /* Handlers to execute when the limit is * reached. */ int timeGranularity; /* Mod factor used to determine how often to * evaluate the limit check. */ Tcl_TimerToken timeEvent; /* Handle for a timer callback that will occur * when the time-limit is exceeded. */ Tcl_HashTable callbacks;/* Mapping from (interp,type) pair to data * used to install a limit handler callback to * run in _this_ interp when the limit is * exceeded. */ } limit; /* * Information for improved default error generation from ensembles * (TIP#112). */ struct { Tcl_Obj *const *sourceObjs; /* What arguments were actually input into the * *root* ensemble command? (Nested ensembles * don't rewrite this.) NULL if we're not * processing an ensemble. */ Tcl_Size numRemovedObjs;/* How many arguments have been stripped off * because of ensemble processing. */ Tcl_Size numInsertedObjs; /* How many of the current arguments were * inserted by an ensemble. */ } ensembleRewrite; /* * TIP #219: Global info for the I/O system. */ Tcl_Obj *chanMsg; /* Error message set by channel drivers, for * the propagation of arbitrary Tcl errors. * This information, if present (chanMsg not * NULL), takes precedence over a POSIX error * code returned by a channel operation. */ /* * Source code origin information (TIP #280). */ CmdFrame *cmdFramePtr; /* Points to the command frame containing the * location information for the current * command. */ const CmdFrame *invokeCmdFramePtr; /* Points to the command frame which is the * invoking context of the bytecode compiler. * NULL when the byte code compiler is not * active. */ int invokeWord; /* Index of the word in the command which * is getting compiled. */ Tcl_HashTable *linePBodyPtr;/* This table remembers for each statically * defined procedure the location information * for its body. It is keyed by the address of * the Proc structure for a procedure. The * values are "struct CmdFrame*". */ Tcl_HashTable *lineBCPtr; /* This table remembers for each ByteCode * object the location information for its * body. It is keyed by the address of the * Proc structure for a procedure. The values * are "struct ExtCmdLoc*". (See * tclCompile.h) */ Tcl_HashTable *lineLABCPtr; /* Tcl_Obj* (by exact pointer) -> CFWordBC* */ Tcl_HashTable *lineLAPtr; /* This table remembers for each argument of a * command on the execution stack the index of * the argument in the command, and the * location data of the command. It is keyed * by the address of the Tcl_Obj containing * the argument. The values are "struct * CFWord*" (See tclBasic.c). This allows * commands like uplevel, eval, etc. to find * location information for their arguments, * if they are a proper literal argument to an * invoking command. Alt view: An index to the * CmdFrame stack keyed by command argument * holders. */ ContLineLoc *scriptCLLocPtr;/* This table points to the location data for * invisible continuation lines in the script, * if any. This pointer is set by the function * TclEvalObjEx() in file "tclBasic.c", and * used by function ...() in the same file. * It does for the eval/direct path of script * execution what CompileEnv.clLoc does for * the bytecode compiler. */ /* * TIP #268. The currently active selection mode, i.e. the package require * preferences. */ int packagePrefer; /* Current package selection mode. */ /* * Hashtables for variable traces and searches. */ Tcl_HashTable varTraces; /* Hashtable holding the start of a variable's * active trace list; varPtr is the key. */ Tcl_HashTable varSearches; /* Hashtable holding the start of a variable's * active searches list; varPtr is the key. */ /* * The thread-specific data ekeko: cache pointers or values that * (a) do not change during the thread's lifetime * (b) require access to TSD to determine at runtime * (c) are accessed very often (e.g., at each command call) * * Note that these are the same for all interps in the same thread. They * just have to be initialised for the thread's parent interp, children * inherit the value. * * They are used by the macros defined below. */ AllocCache *allocCache; /* Allocator cache for stack frames. */ void *pendingObjDataPtr; /* Pointer to the Cache and PendingObjData * structs for this interp's thread; see * tclObj.c and tclThreadAlloc.c */ int *asyncReadyPtr; /* Pointer to the asyncReady indicator for * this interp's thread; see tclAsync.c */ /* * The pointer to the object system root ekeko. c.f. TIP #257. */ void *objectFoundation; /* Pointer to the Foundation structure of the * object system, which contains things like * references to key namespaces. See * tclOOInt.h and tclOO.c for real definition * and setup. */ struct NRE_callback *deferredCallbacks; /* Callbacks that are set previous to a call * to some Eval function but that actually * belong to the command that is about to be * called - i.e., they should be run *before* * any tailcall is invoked. */ /* * TIP #285, Script cancellation support. */ Tcl_AsyncHandler asyncCancel; /* Async handler token for Tcl_CancelEval. */ Tcl_Obj *asyncCancelMsg; /* Error message set by async cancel handler * for the propagation of arbitrary Tcl * errors. This information, if present * (asyncCancelMsg not NULL), takes precedence * over the default error messages returned by * a script cancellation operation. */ /* * TIP #348 IMPLEMENTATION - Substituted error stack */ Tcl_Obj *errorStack; /* [info errorstack] value (as a Tcl_Obj). */ Tcl_Obj *upLiteral; /* "UP" literal for [info errorstack] */ Tcl_Obj *callLiteral; /* "CALL" literal for [info errorstack] */ Tcl_Obj *innerLiteral; /* "INNER" literal for [info errorstack] */ Tcl_Obj *innerContext; /* cached list for fast reallocation */ int resetErrorStack; /* controls cleaning up of ::errorStack */ #ifdef TCL_COMPILE_STATS /* * Statistical information about the bytecode compiler and interpreter's * operation. This should be the last field of Interp. */ ByteCodeStats stats; /* Holds compilation and execution statistics * for this interpreter. */ #endif /* TCL_COMPILE_STATS */ } Interp; /* * Macros that use the TSD-ekeko. */ #define TclAsyncReady(iPtr) \ *((iPtr)->asyncReadyPtr) /* * Macros for script cancellation support (TIP #285). */ #define TclCanceled(iPtr) \ (((iPtr)->flags & CANCELED) || ((iPtr)->flags & TCL_CANCEL_UNWIND)) #define TclSetCancelFlags(iPtr, cancelFlags) \ (iPtr)->flags |= CANCELED; \ if ((cancelFlags) & TCL_CANCEL_UNWIND) { \ (iPtr)->flags |= TCL_CANCEL_UNWIND; \ } #define TclUnsetCancelFlags(iPtr) \ (iPtr)->flags &= (~(CANCELED | TCL_CANCEL_UNWIND)) /* * Macros for splicing into and out of doubly linked lists. They assume * existence of struct items 'prevPtr' and 'nextPtr'. * * a = element to add or remove. * b = list head. * * TclSpliceIn adds to the head of the list. */ #define TclSpliceIn(a,b) \ (a)->nextPtr = (b); \ if ((b) != NULL) { \ (b)->prevPtr = (a); \ } \ (a)->prevPtr = NULL, (b) = (a); #define TclSpliceOut(a,b) \ if ((a)->prevPtr != NULL) { \ (a)->prevPtr->nextPtr = (a)->nextPtr; \ } else { \ (b) = (a)->nextPtr; \ } \ if ((a)->nextPtr != NULL) { \ (a)->nextPtr->prevPtr = (a)->prevPtr; \ } /* * EvalFlag bits for Interp structures: * * TCL_ALLOW_EXCEPTIONS 1 means it's OK for the script to terminate with a * code other than TCL_OK or TCL_ERROR; 0 means codes * other than these should be turned into errors. */ #define TCL_ALLOW_EXCEPTIONS 0x04 #define TCL_EVAL_FILE 0x02 #define TCL_EVAL_SOURCE_IN_FRAME 0x10 #define TCL_EVAL_NORESOLVE 0x20 #define TCL_EVAL_DISCARD_RESULT 0x40 /* * Flag bits for Interp structures: * * DELETED: Non-zero means the interpreter has been deleted: * don't process any more commands for it, and destroy * the structure as soon as all nested invocations of * Tcl_Eval are done. * ERR_ALREADY_LOGGED: Non-zero means information has already been logged in * iPtr->errorInfo for the current Tcl_Eval instance, so * Tcl_Eval needn't log it (used to implement the "error * message log" command). * DONT_COMPILE_CMDS_INLINE: Non-zero means that the bytecode compiler should * not compile any commands into an inline sequence of * instructions. This is set 1, for example, when command * traces are requested. * RAND_SEED_INITIALIZED: Non-zero means that the randSeed value of the interp * has not be initialized. This is set 1 when we first * use the rand() or srand() functions. * SAFE_INTERP: Non zero means that the current interp is a safe * interp (i.e. it has only the safe commands installed, * less privilege than a regular interp). * INTERP_DEBUG_FRAME: Used for switching on various extra interpreter * debug/info mechanisms (e.g. info frame eval/uplevel * tracing) which are performance intensive. * INTERP_TRACE_IN_PROGRESS: Non-zero means that an interp trace is currently * active; so no further trace callbacks should be * invoked. * INTERP_ALTERNATE_WRONG_ARGS: Used for listing second and subsequent forms * of the wrong-num-args string in Tcl_WrongNumArgs. * Makes it append instead of replacing and uses * different intermediate text. * CANCELED: Non-zero means that the script in progress should be * canceled as soon as possible. This can be checked by * extensions (and the core itself) by calling * Tcl_Canceled and checking if TCL_ERROR is returned. * This is a one-shot flag that is reset immediately upon * being detected; however, if the TCL_CANCEL_UNWIND flag * is set Tcl_Canceled will continue to report that the * script in progress has been canceled thereby allowing * the evaluation stack for the interp to be fully * unwound. */ #define DELETED 1 #define ERR_ALREADY_LOGGED 4 #define INTERP_DEBUG_FRAME 0x10 #define DONT_COMPILE_CMDS_INLINE 0x20 #define RAND_SEED_INITIALIZED 0x40 #define SAFE_INTERP 0x80 #define INTERP_TRACE_IN_PROGRESS 0x200 #define INTERP_ALTERNATE_WRONG_ARGS 0x400 #define ERR_LEGACY_COPY 0x800 #define CANCELED 0x1000 /* * Maximum number of levels of nesting permitted in Tcl commands (used to * catch infinite recursion). */ #define MAX_NESTING_DEPTH 1000 /* * The macro below is used to modify a "char" value (e.g. by casting it to an * unsigned character) so that it can be used safely with macros such as * isspace. */ #define UCHAR(c) ((unsigned char) (c)) /* * This macro is used to properly align the memory allocated by Tcl, giving * the same alignment as the native malloc. */ #if defined(__APPLE__) #define TCL_ALLOCALIGN 16 #else #define TCL_ALLOCALIGN (2*sizeof(void *)) #endif /* * TCL_ALIGN is used to determine the offset needed to safely allocate any * data structure in memory. Given a starting offset or size, it "rounds up" * or "aligns" the offset to the next aligned (typically 8-byte) boundary so * that any data structure can be placed at the resulting offset without fear * of an alignment error. Note this is clamped to a minimum of 8 for API * compatibility. * * WARNING!! DO NOT USE THIS MACRO TO ALIGN POINTERS: it will produce the * wrong result on platforms that allocate addresses that are divisible by a * non-trivial factor of this alignment. Only use it for offsets or sizes. * * This macro is only used by tclCompile.c in the core (Bug 926445). It * however not be made file static, as extensions that touch bytecodes * (notably tbcload) require it. */ struct TclMaxAlignment { char unalign[8]; union { long long maxAlignLongLong; double maxAlignDouble; void *maxAlignPointer; } aligned; }; #define TCL_ALIGN_BYTES \ offsetof(struct TclMaxAlignment, aligned) #define TCL_ALIGN(x) \ (((x) + (TCL_ALIGN_BYTES - 1)) & ~(TCL_ALIGN_BYTES - 1)) /* * A common panic alert when memory allocation fails. */ #define TclOOM(ptr, size) \ ((size) && ((ptr) || (Tcl_Panic( \ "unable to alloc %" TCL_Z_MODIFIER "u bytes", (size_t)(size)), 1))) /* * The following enum values are used to specify the runtime platform setting * of the tclPlatform variable. */ typedef enum { TCL_PLATFORM_UNIX = 0, /* Any Unix-like OS. */ TCL_PLATFORM_WINDOWS = 2 /* Any Microsoft Windows OS. */ } TclPlatformType; /* * The following enum values are used to indicate the translation of a Tcl * channel. Declared here so that each platform can define * TCL_PLATFORM_TRANSLATION to the native translation on that platform. */ typedef enum TclEolTranslation { TCL_TRANSLATE_AUTO, /* Eol == \r, \n and \r\n. */ TCL_TRANSLATE_CR, /* Eol == \r. */ TCL_TRANSLATE_LF, /* Eol == \n. */ TCL_TRANSLATE_CRLF /* Eol == \r\n. */ } TclEolTranslation; /* * Flags for TclInvoke: * * TCL_INVOKE_HIDDEN Invoke a hidden command; if not set, invokes * an exposed command. * TCL_INVOKE_NO_UNKNOWN If set, "unknown" is not invoked if the * command to be invoked is not found. Only has * an effect if invoking an exposed command, * i.e. if TCL_INVOKE_HIDDEN is not also set. * TCL_INVOKE_NO_TRACEBACK Does not record traceback information if the * invoked command returns an error. Used if the * caller plans on recording its own traceback * information. */ #define TCL_INVOKE_HIDDEN (1<<0) #define TCL_INVOKE_NO_UNKNOWN (1<<1) #define TCL_INVOKE_NO_TRACEBACK (1<<2) /* * ListStore -- * * A Tcl list's internal representation is defined through three structures. * * A ListStore struct is a structure that includes a variable size array that * serves as storage for a Tcl list. A contiguous sequence of slots in the * array, the "in-use" area, holds valid pointers to Tcl_Obj values that * belong to one or more Tcl lists. The unused slots before and after these * are free slots that may be used to prepend and append without having to * reallocate the struct. The ListStore may be shared amongst multiple lists * and reference counted. * * A ListSpan struct defines a sequence of slots within a ListStore. This sequence * always lies within the "in-use" area of the ListStore. Like ListStore, the * structure may be shared among multiple lists and is reference counted. * * A ListRep struct holds the internal representation of a Tcl list as stored * in a Tcl_Obj. It is composed of a ListStore and a ListSpan that together * define the content of the list. The ListSpan specifies the range of slots * within the ListStore that hold elements for this list. The ListSpan is * optional in which case the list includes all the "in-use" slots of the * ListStore. * */ typedef struct ListStore { Tcl_Size firstUsed; /* Index of first slot in use within slots[] */ Tcl_Size numUsed; /* Number of slots in use (starting firstUsed) */ Tcl_Size numAllocated; /* Total number of slots[] array slots. */ size_t refCount; /* Number of references to this instance. */ int flags; /* LISTSTORE_* flags */ Tcl_Obj *slots[TCLFLEXARRAY]; /* Variable size array. Grown as needed */ } ListStore; #define LISTSTORE_CANONICAL 0x1 /* All Tcl_Obj's referencing this * store have their string representation * derived from the list representation */ /* Max number of elements that can be contained in a list */ #define LIST_MAX \ ((Tcl_Size)(((size_t)TCL_SIZE_MAX - offsetof(ListStore, slots)) \ / sizeof(Tcl_Obj *))) /* Memory size needed for a ListStore to hold numSlots_ elements */ #define LIST_SIZE(numSlots_) \ ((Tcl_Size)(offsetof(ListStore, slots) \ + ((numSlots_) * sizeof(Tcl_Obj *)))) /* * ListSpan -- * See comments above for ListStore */ typedef struct ListSpan { Tcl_Size spanStart; /* Starting index of the span. */ Tcl_Size spanLength; /* Number of elements in the span. */ size_t refCount; /* Count of references to this span record. */ } ListSpan; #ifndef LIST_SPAN_THRESHOLD /* May be set on build line */ #define LIST_SPAN_THRESHOLD 101 #endif /* * ListRep -- * See comments above for ListStore */ typedef struct ListRep { ListStore *storePtr; /* element array shared amongst different * lists */ ListSpan *spanPtr; /* If not NULL, the span holds the range of * slots within *storePtr that contain this * list elements. */ } ListRep; /* * Macros used to get access list internal representations. * * Naming conventions: * ListRep* - expect a pointer to a valid ListRep * ListObj* - expect a pointer to a Tcl_Obj whose internal type is known to * be a list (tclListType). Will crash otherwise. * TclListObj* - expect a pointer to a Tcl_Obj whose internal type may or may not * be tclListType. These will convert as needed and return error if * conversion not possible. */ /* Returns the starting slot for this listRep in the contained ListStore */ #define ListRepStart(listRepPtr_) \ ((listRepPtr_)->spanPtr \ ? (listRepPtr_)->spanPtr->spanStart \ : (listRepPtr_)->storePtr->firstUsed) /* Returns the number of elements in this listRep */ #define ListRepLength(listRepPtr_) \ ((listRepPtr_)->spanPtr \ ? (listRepPtr_)->spanPtr->spanLength \ : (listRepPtr_)->storePtr->numUsed) /* Returns a pointer to the first slot containing this ListRep elements */ #define ListRepElementsBase(listRepPtr_) \ (&(listRepPtr_)->storePtr->slots[ListRepStart(listRepPtr_)]) /* Stores the number of elements and base address of the element array */ #define ListRepElements(listRepPtr_, objc_, objv_) \ (((objv_) = ListRepElementsBase(listRepPtr_)), \ ((objc_) = ListRepLength(listRepPtr_))) /* Returns 1/0 whether the ListRep's ListStore is shared. */ #define ListRepIsShared(listRepPtr_) ((listRepPtr_)->storePtr->refCount > 1) /* Returns a pointer to the ListStore component */ #define ListObjStorePtr(listObj_) \ ((ListStore *)((listObj_)->internalRep.twoPtrValue.ptr1)) /* Returns a pointer to the ListSpan component */ #define ListObjSpanPtr(listObj_) \ ((ListSpan *)((listObj_)->internalRep.twoPtrValue.ptr2)) /* Returns the ListRep internal representaton in a Tcl_Obj */ #define ListObjGetRep(listObj_, listRepPtr_) \ do { \ (listRepPtr_)->storePtr = ListObjStorePtr(listObj_); \ (listRepPtr_)->spanPtr = ListObjSpanPtr(listObj_); \ } while (0) /* Returns the length of the list */ #define ListObjLength(listObj_, len_) \ ((len_) = ListObjSpanPtr(listObj_) \ ? ListObjSpanPtr(listObj_)->spanLength \ : ListObjStorePtr(listObj_)->numUsed) /* Returns the starting slot index of this list's elements in the ListStore */ #define ListObjStart(listObj_) \ (ListObjSpanPtr(listObj_) \ ? ListObjSpanPtr(listObj_)->spanStart \ : ListObjStorePtr(listObj_)->firstUsed) /* Stores the element count and base address of this list's elements */ #define ListObjGetElements(listObj_, objc_, objv_) \ (((objv_) = &ListObjStorePtr(listObj_)->slots[ListObjStart(listObj_)]), \ (ListObjLength(listObj_, (objc_)))) /* * Returns 1/0 whether the internal representation (not the Tcl_Obj itself) * is shared. Note by intent this only checks for sharing of ListStore, * not spans. */ #define ListObjRepIsShared(listObj_) \ (ListObjStorePtr(listObj_)->refCount > 1) /* * Certain commands like concat are optimized if an existing string * representation of a list object is known to be in canonical format (i.e. * generated from the list representation). There are three conditions when * this will be the case: * (1) No string representation exists which means it will obviously have * to be generated from the list representation when needed * (2) The ListStore flags is marked canonical. This is done at the time * the string representation is generated from the list under certain * conditions (see comments in UpdateStringOfList). * (3) The list representation does not have a span component. This is * because list Tcl_Obj's with spans are always created from existing lists * and never from strings (see SetListFromAny) and thus their string * representation will always be canonical. */ #define ListObjIsCanonical(listObj_) \ (((listObj_)->bytes == NULL) \ || (ListObjStorePtr(listObj_)->flags & LISTSTORE_CANONICAL) \ || ListObjSpanPtr(listObj_) != NULL) /* * Converts the Tcl_Obj to a list if it isn't one and stores the element * count and base address of this list's elements in objcPtr_ and objvPtr_. * Return TCL_OK on success or TCL_ERROR if the Tcl_Obj cannot be * converted to a list. */ #define TclListObjGetElements(interp_, listObj_, objcPtr_, objvPtr_) \ ((TclHasInternalRep((listObj_), &tclListType)) \ ? ((ListObjGetElements((listObj_), *(objcPtr_), *(objvPtr_))), \ TCL_OK) \ : Tcl_ListObjGetElements( \ (interp_), (listObj_), (objcPtr_), (objvPtr_))) /* * Converts the Tcl_Obj to a list if it isn't one and stores the element * count in lenPtr_. Returns TCL_OK on success or TCL_ERROR if the * Tcl_Obj cannot be converted to a list. */ #define TclListObjLength(interp_, listObj_, lenPtr_) \ ((TclHasInternalRep((listObj_), &tclListType)) \ ? ((ListObjLength((listObj_), *(lenPtr_))), TCL_OK) \ : Tcl_ListObjLength((interp_), (listObj_), (lenPtr_))) #define TclListObjIsCanonical(listObj_) \ ((TclHasInternalRep((listObj_), &tclListType)) \ ? ListObjIsCanonical((listObj_)) \ : 0) /* * Modes for collecting (or not) in the implementations of TclNRForeachCmd, * TclNRLmapCmd and their compilations. */ #define TCL_EACH_KEEP_NONE 0 /* Discard iteration result like [foreach] */ #define TCL_EACH_COLLECT 1 /* Collect iteration result like [lmap] */ /* * Macros providing a faster path to booleans and integers: * Tcl_GetBooleanFromObj, Tcl_GetLongFromObj, Tcl_GetIntFromObj * and Tcl_GetIntForIndex. * * WARNING: these macros eval their args more than once. */ #if TCL_MAJOR_VERSION > 8 #define TclGetBooleanFromObj(interp, objPtr, intPtr) \ ((TclHasInternalRep((objPtr), &tclIntType) \ || TclHasInternalRep((objPtr), &tclBooleanType)) \ ? (*(intPtr) = ((objPtr)->internalRep.wideValue!=0), TCL_OK) \ : Tcl_GetBooleanFromObj((interp), (objPtr), (intPtr))) #else #define TclGetBooleanFromObj(interp, objPtr, intPtr) \ ((TclHasInternalRep((objPtr), &tclIntType)) \ ? (*(intPtr) = ((objPtr)->internalRep.wideValue!=0), TCL_OK) \ : (TclHasInternalRep((objPtr), &tclBooleanType)) \ ? (*(intPtr) = ((objPtr)->internalRep.longValue!=0), TCL_OK) \ : Tcl_GetBooleanFromObj((interp), (objPtr), (intPtr))) #endif #ifdef TCL_WIDE_INT_IS_LONG #define TclGetLongFromObj(interp, objPtr, longPtr) \ ((TclHasInternalRep((objPtr), &tclIntType)) \ ? ((*(longPtr) = (objPtr)->internalRep.wideValue), TCL_OK) \ : Tcl_GetLongFromObj((interp), (objPtr), (longPtr))) #else #define TclGetLongFromObj(interp, objPtr, longPtr) \ ((TclHasInternalRep((objPtr), &tclIntType) \ && (objPtr)->internalRep.wideValue >= (Tcl_WideInt)(LONG_MIN) \ && (objPtr)->internalRep.wideValue <= (Tcl_WideInt)(LONG_MAX)) \ ? ((*(longPtr) = (long)(objPtr)->internalRep.wideValue), TCL_OK) \ : Tcl_GetLongFromObj((interp), (objPtr), (longPtr))) #endif #define TclGetIntFromObj(interp, objPtr, intPtr) \ ((TclHasInternalRep((objPtr), &tclIntType) \ && (objPtr)->internalRep.wideValue >= (Tcl_WideInt)(INT_MIN) \ && (objPtr)->internalRep.wideValue <= (Tcl_WideInt)(INT_MAX)) \ ? ((*(intPtr) = (int)(objPtr)->internalRep.wideValue), TCL_OK) \ : Tcl_GetIntFromObj((interp), (objPtr), (intPtr))) #define TclGetIntForIndexM(interp, objPtr, endValue, idxPtr) \ (((TclHasInternalRep((objPtr), &tclIntType)) \ && ((objPtr)->internalRep.wideValue >= 0) \ && ((objPtr)->internalRep.wideValue <= endValue)) \ ? ((*(idxPtr) = (objPtr)->internalRep.wideValue), TCL_OK) \ : Tcl_GetIntForIndex((interp), (objPtr), (endValue), (idxPtr))) /* * Macro used to save a function call for common uses of * Tcl_GetWideIntFromObj(). The ANSI C "prototype" is: * * MODULE_SCOPE int TclGetWideIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, * Tcl_WideInt *wideIntPtr); */ #define TclGetWideIntFromObj(interp, objPtr, wideIntPtr) \ ((TclHasInternalRep((objPtr), &tclIntType)) \ ? (*(wideIntPtr) = ((objPtr)->internalRep.wideValue), TCL_OK) \ : Tcl_GetWideIntFromObj((interp), (objPtr), (wideIntPtr))) /* * Flag values for TclTraceDictPath(). * * DICT_PATH_READ indicates that all entries on the path must exist but no * updates will be needed. * * DICT_PATH_UPDATE indicates that we are going to be doing an update at the * tip of the path, so duplication of shared objects should be done along the * way. * * DICT_PATH_EXISTS indicates that we are performing an existence test and a * lookup failure should therefore not be an error. If (and only if) this flag * is set, TclTraceDictPath() will return the special value * DICT_PATH_NON_EXISTENT if the path is not traceable. * * DICT_PATH_CREATE (which also requires the DICT_PATH_UPDATE bit to be set) * indicates that we are to create non-existent dictionaries on the path. */ #define DICT_PATH_READ 0 #define DICT_PATH_UPDATE 1 #define DICT_PATH_EXISTS 2 #define DICT_PATH_CREATE 5 #define DICT_PATH_NON_EXISTENT ((Tcl_Obj *) (void *) 1) /* *---------------------------------------------------------------- * Data structures related to the filesystem internals *---------------------------------------------------------------- */ /* * The version_2 filesystem is private to Tcl. As and when these changes have * been thoroughly tested and investigated a new public filesystem interface * will be released. The aim is more versatile virtual filesystem interfaces, * more efficiency in 'path' manipulation and usage, and cleaner filesystem * code internally. */ #define TCL_FILESYSTEM_VERSION_2 ((Tcl_FSVersion) 0x2) typedef void *(TclFSGetCwdProc2)(void *clientData); typedef int (Tcl_FSLoadFileProc2) (Tcl_Interp *interp, Tcl_Obj *pathPtr, Tcl_LoadHandle *handlePtr, Tcl_FSUnloadFileProc **unloadProcPtr, int flags); /* * The following types are used for getting and storing platform-specific file * attributes in tclFCmd.c and the various platform-versions of that file. * This is done to have as much common code as possible in the file attributes * code. For more information about the callbacks, see TclFileAttrsCmd in * tclFCmd.c. */ typedef int (TclGetFileAttrProc)(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj **attrObjPtrPtr); typedef int (TclSetFileAttrProc)(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj *attrObjPtr); typedef struct TclFileAttrProcs { TclGetFileAttrProc *getProc;/* The procedure for getting attrs. */ TclSetFileAttrProc *setProc;/* The procedure for setting attrs. */ } TclFileAttrProcs; /* * Opaque handle used in pipeline routines to encapsulate platform-dependent * state. */ typedef struct TclFile_ *TclFile; typedef enum Tcl_PathPart { TCL_PATH_DIRNAME, TCL_PATH_TAIL, TCL_PATH_EXTENSION, TCL_PATH_ROOT } Tcl_PathPart; /* *---------------------------------------------------------------- * Data structures related to obsolete filesystem hooks *---------------------------------------------------------------- */ typedef int (TclStatProc_)(const char *path, struct stat *buf); typedef int (TclAccessProc_)(const char *path, int mode); typedef Tcl_Channel (TclOpenFileChannelProc_)(Tcl_Interp *interp, const char *fileName, const char *modeString, int permissions); /* *---------------------------------------------------------------- * Data structures for process-global values. *---------------------------------------------------------------- */ typedef void (TclInitProcessGlobalValueProc)(char **valuePtr, TCL_HASH_TYPE *lengthPtr, Tcl_Encoding *encodingPtr); #ifdef _WIN32 /* On Windows, all Unicode (except surrogates) are valid. */ # define TCLFSENCODING tclUtf8Encoding #else /* On Non-Windows, use the system encoding for validation checks. */ # define TCLFSENCODING NULL #endif /* * A ProcessGlobalValue struct exists for each internal value in Tcl that is * to be shared among several threads. Each thread sees a (Tcl_Obj) copy of * the value, and the gobal value is kept as a counted string, with epoch and * mutex control. Each ProcessGlobalValue struct should be a static variable in * some file. */ typedef struct ProcessGlobalValue { Tcl_Size epoch; /* Epoch counter to detect changes in the * global value. */ TCL_HASH_TYPE numBytes; /* Length of the global string. */ char *value; /* The global string value. */ Tcl_Encoding encoding; /* system encoding when global string was * initialized. */ TclInitProcessGlobalValueProc *proc; /* A procedure to initialize the global string * copy when a "get" request comes in before * any "set" request has been received. */ Tcl_Mutex mutex; /* Enforce orderly access from multiple * threads. */ Tcl_ThreadDataKey key; /* Key for per-thread data holding the * (Tcl_Obj) copy for each thread. */ } ProcessGlobalValue; /* *---------------------------------------------------------------------- * Flags for TclParseNumber *---------------------------------------------------------------------- */ #define TCL_PARSE_DECIMAL_ONLY 1 /* Leading zero doesn't denote octal or * hex. */ #define TCL_PARSE_OCTAL_ONLY 2 /* Parse octal even without prefix. */ #define TCL_PARSE_HEXADECIMAL_ONLY 4 /* Parse hexadecimal even without prefix. */ #define TCL_PARSE_INTEGER_ONLY 8 /* Disable floating point parsing. */ #define TCL_PARSE_SCAN_PREFIXES 16 /* Use [scan] rules dealing with 0? * prefixes. */ #define TCL_PARSE_NO_WHITESPACE 32 /* Reject leading/trailing whitespace. */ #define TCL_PARSE_BINARY_ONLY 64 /* Parse binary even without prefix. */ #define TCL_PARSE_NO_UNDERSCORE 128 /* Reject underscore digit separator */ /* *---------------------------------------------------------------------- * Internal convenience macros for manipulating encoding flags. See * TCL_ENCODING_PROFILE_* in tcl.h *---------------------------------------------------------------------- */ #define ENCODING_PROFILE_MASK 0xFF000000 #define ENCODING_PROFILE_GET(flags_) \ ((flags_) & ENCODING_PROFILE_MASK) #define ENCODING_PROFILE_SET(flags_, profile_) \ do { \ (flags_) &= ~ENCODING_PROFILE_MASK; \ (flags_) |= ((profile_) & ENCODING_PROFILE_MASK); \ } while (0) /* *---------------------------------------------------------------------- * Common functions for calculating overallocation. Trivial but allows for * experimenting with growth factors without having to change code in * multiple places. See TclAttemptAllocElemsEx and similar for usage * examples. Best to use those functions. Direct use of TclUpsizeAlloc / * TclResizeAlloc is needed in special cases such as when total size of * memory block is limited to less than TCL_SIZE_MAX. *---------------------------------------------------------------------- */ static inline Tcl_Size TclUpsizeAlloc( TCL_UNUSED(Tcl_Size), /* oldSize. For future experiments with * some growth algorithms that use this * information. */ Tcl_Size needed, Tcl_Size limit) { /* assert (oldCapacity < needed <= limit) */ if (needed < (limit - needed/2)) { return needed + needed / 2; } else { return limit; } } static inline Tcl_Size TclUpsizeRetry( Tcl_Size needed, Tcl_Size lastAttempt) { /* assert(needed < lastAttempt); */ if (needed < lastAttempt - 1) { /* (needed+lastAttempt)/2 but that formula may overflow Tcl_Size */ return needed + (lastAttempt - needed) / 2; } else { return needed; } } MODULE_SCOPE void * TclAllocElemsEx(Tcl_Size elemCount, Tcl_Size elemSize, Tcl_Size leadSize, Tcl_Size *capacityPtr); MODULE_SCOPE void * TclReallocElemsEx(void *oldPtr, Tcl_Size elemCount, Tcl_Size elemSize, Tcl_Size leadSize, Tcl_Size *capacityPtr); MODULE_SCOPE void * TclAttemptReallocElemsEx(void *oldPtr, Tcl_Size elemCount, Tcl_Size elemSize, Tcl_Size leadSize, Tcl_Size *capacityPtr); /* Alloc elemCount elements of size elemSize with leadSize header * returning actual capacity (in elements) in *capacityPtr. */ static inline void * TclAttemptAllocElemsEx( Tcl_Size elemCount, Tcl_Size elemSize, Tcl_Size leadSize, Tcl_Size *capacityPtr) { return TclAttemptReallocElemsEx( NULL, elemCount, elemSize, leadSize, capacityPtr); } /* Alloc numByte bytes, returning actual capacity in *capacityPtr. */ static inline void * TclAllocEx( Tcl_Size numBytes, Tcl_Size *capacityPtr) { return TclAllocElemsEx(numBytes, 1, 0, capacityPtr); } /* Alloc numByte bytes, returning actual capacity in *capacityPtr. */ static inline void * TclAttemptAllocEx( Tcl_Size numBytes, Tcl_Size *capacityPtr) { return TclAttemptAllocElemsEx(numBytes, 1, 0, capacityPtr); } /* Realloc numByte bytes, returning actual capacity in *capacityPtr. */ static inline void * TclReallocEx( void *oldPtr, Tcl_Size numBytes, Tcl_Size *capacityPtr) { return TclReallocElemsEx(oldPtr, numBytes, 1, 0, capacityPtr); } /* Realloc numByte bytes, returning actual capacity in *capacityPtr. */ static inline void * TclAttemptReallocEx( void *oldPtr, Tcl_Size numBytes, Tcl_Size *capacityPtr) { return TclAttemptReallocElemsEx(oldPtr, numBytes, 1, 0, capacityPtr); } /* *---------------------------------------------------------------- * Variables shared among Tcl modules but not used by the outside world. *---------------------------------------------------------------- */ MODULE_SCOPE char *tclNativeExecutableName; MODULE_SCOPE int tclFindExecutableSearchDone; MODULE_SCOPE char *tclMemDumpFileName; MODULE_SCOPE TclPlatformType tclPlatform; /* * Declarations related to internal encoding functions. */ MODULE_SCOPE Tcl_Encoding tclIdentityEncoding; MODULE_SCOPE Tcl_Encoding tclUtf8Encoding; MODULE_SCOPE int TclEncodingProfileNameToId(Tcl_Interp *interp, const char *profileName, int *profilePtr); MODULE_SCOPE const char *TclEncodingProfileIdToName(Tcl_Interp *interp, int profileId); MODULE_SCOPE void TclGetEncodingProfiles(Tcl_Interp *interp); /* * TIP #233 (Virtualized Time) * Data for the time hooks, if any. */ MODULE_SCOPE Tcl_GetTimeProc *tclGetTimeProcPtr; MODULE_SCOPE Tcl_ScaleTimeProc *tclScaleTimeProcPtr; MODULE_SCOPE void *tclTimeClientData; /* * Variables denoting the Tcl object types defined in the core. */ MODULE_SCOPE const Tcl_ObjType tclBignumType; MODULE_SCOPE const Tcl_ObjType tclBooleanType; MODULE_SCOPE const Tcl_ObjType tclByteCodeType; MODULE_SCOPE const Tcl_ObjType tclDoubleType; MODULE_SCOPE const Tcl_ObjType tclExprCodeType; MODULE_SCOPE const Tcl_ObjType tclIntType; MODULE_SCOPE const Tcl_ObjType tclIndexType; MODULE_SCOPE const Tcl_ObjType tclListType; MODULE_SCOPE const Tcl_ObjType tclDictType; MODULE_SCOPE const Tcl_ObjType tclProcBodyType; MODULE_SCOPE const Tcl_ObjType tclStringType; MODULE_SCOPE const Tcl_ObjType tclEnsembleCmdType; MODULE_SCOPE const Tcl_ObjType tclRegexpType; MODULE_SCOPE Tcl_ObjType tclCmdNameType; /* * Variables denoting the hash key types defined in the core. */ MODULE_SCOPE const Tcl_HashKeyType tclArrayHashKeyType; MODULE_SCOPE const Tcl_HashKeyType tclOneWordHashKeyType; MODULE_SCOPE const Tcl_HashKeyType tclStringHashKeyType; MODULE_SCOPE const Tcl_HashKeyType tclObjHashKeyType; /* * The head of the list of free Tcl objects, and the total number of Tcl * objects ever allocated and freed. */ MODULE_SCOPE Tcl_Obj * tclFreeObjList; #ifdef TCL_COMPILE_STATS MODULE_SCOPE size_t tclObjsAlloced; MODULE_SCOPE size_t tclObjsFreed; #define TCL_MAX_SHARED_OBJ_STATS 5 MODULE_SCOPE size_t tclObjsShared[TCL_MAX_SHARED_OBJ_STATS]; #endif /* TCL_COMPILE_STATS */ /* * Pointer to a heap-allocated string of length zero that the Tcl core uses as * the value of an empty string representation for an object. This value is * shared by all new objects allocated by Tcl_NewObj. */ MODULE_SCOPE char tclEmptyString; enum CheckEmptyStringResult { TCL_EMPTYSTRING_UNKNOWN = -1, TCL_EMPTYSTRING_NO, TCL_EMPTYSTRING_YES }; /* *---------------------------------------------------------------- * Procedures shared among Tcl modules but not used by the outside world, * introduced by/for NRE. *---------------------------------------------------------------- */ MODULE_SCOPE Tcl_ObjCmdProc TclNRApplyObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNREvalObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRCatchObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRExprObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRForObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRForeachCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRIfObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRLmapCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRPackageObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRSourceObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRSubstObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRSwitchObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRTryObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRUplevelObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRWhileObjCmd; MODULE_SCOPE Tcl_NRPostProc TclNRForIterCallback; MODULE_SCOPE Tcl_NRPostProc TclNRCoroutineActivateCallback; MODULE_SCOPE Tcl_ObjCmdProc TclNRTailcallObjCmd; MODULE_SCOPE Tcl_NRPostProc TclNRTailcallEval; MODULE_SCOPE Tcl_ObjCmdProc TclNRCoroutineObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRYieldObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRYieldmObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRYieldToObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRInvoke; MODULE_SCOPE Tcl_NRPostProc TclNRReleaseValues; MODULE_SCOPE void TclSetTailcall(Tcl_Interp *interp, Tcl_Obj *tailcallPtr); MODULE_SCOPE void TclPushTailcallPoint(Tcl_Interp *interp); /* These two can be considered for the public api */ MODULE_SCOPE void TclMarkTailcall(Tcl_Interp *interp); MODULE_SCOPE void TclSkipTailcall(Tcl_Interp *interp); /* * This structure holds the data for the various iteration callbacks used to * NRE the 'for' and 'while' commands. We need a separate structure because we * have more than the 4 client data entries we can provide directly thorugh * the callback API. It is the 'word' information which puts us over the * limit. It is needed because the loop body is argument 4 of 'for' and * argument 2 of 'while'. Not providing the correct index confuses the #280 * code. We TclSmallAlloc/Free this. */ typedef struct ForIterData { Tcl_Obj *cond; /* Loop condition expression. */ Tcl_Obj *body; /* Loop body. */ Tcl_Obj *next; /* Loop step script, NULL for 'while'. */ const char *msg; /* Error message part. */ Tcl_Size word; /* Index of the body script in the command */ } ForIterData; /* TIP #357 - Structure doing the bookkeeping of handles for Tcl_LoadFile * and Tcl_FindSymbol. This structure corresponds to an opaque * typedef in tcl.h */ typedef void* TclFindSymbolProc(Tcl_Interp* interp, Tcl_LoadHandle loadHandle, const char* symbol); struct Tcl_LoadHandle_ { void *clientData; /* Client data is the load handle in the * native filesystem if a module was loaded * there, or an opaque pointer to a structure * for further bookkeeping on load-from-VFS * and load-from-memory */ TclFindSymbolProc* findSymbolProcPtr; /* Procedure that resolves symbols in a * loaded module */ Tcl_FSUnloadFileProc* unloadFileProcPtr; /* Procedure that unloads a loaded module */ }; /* Flags for conversion of doubles to digit strings */ #define TCL_DD_E_FORMAT 0x2 /* Use a fixed-length string of digits, * suitable for E format*/ #define TCL_DD_F_FORMAT 0x3 /* Use a fixed number of digits after the * decimal point, suitable for F format */ #define TCL_DD_SHORTEST 0x4 /* Use the shortest possible string */ #define TCL_DD_NO_QUICK 0x8 /* Debug flag: forbid quick FP conversion */ #define TCL_DD_CONVERSION_TYPE_MASK 0x3 /* Mask to isolate the conversion type */ /* *---------------------------------------------------------------- * Procedures shared among Tcl modules but not used by the outside world: *---------------------------------------------------------------- */ #if TCL_MAJOR_VERSION > 8 MODULE_SCOPE void TclAdvanceContinuations(Tcl_Size *line, Tcl_Size **next, int loc); MODULE_SCOPE void TclAdvanceLines(Tcl_Size *line, const char *start, const char *end); MODULE_SCOPE void TclAppendBytesToByteArray(Tcl_Obj *objPtr, const unsigned char *bytes, Tcl_Size len); MODULE_SCOPE void TclAppendUtfToUtf(Tcl_Obj *objPtr, const char *bytes, Tcl_Size numBytes); MODULE_SCOPE void TclArgumentEnter(Tcl_Interp *interp, Tcl_Obj *objv[], Tcl_Size objc, CmdFrame *cf); MODULE_SCOPE void TclArgumentRelease(Tcl_Interp *interp, Tcl_Obj *objv[], Tcl_Size objc); MODULE_SCOPE void TclArgumentBCEnter(Tcl_Interp *interp, Tcl_Obj *objv[], Tcl_Size objc, void *codePtr, CmdFrame *cfPtr, Tcl_Size cmd, Tcl_Size pc); MODULE_SCOPE void TclArgumentBCRelease(Tcl_Interp *interp, CmdFrame *cfPtr); MODULE_SCOPE void TclArgumentGet(Tcl_Interp *interp, Tcl_Obj *obj, CmdFrame **cfPtrPtr, int *wordPtr); MODULE_SCOPE int TclAsyncNotifier(int sigNumber, Tcl_ThreadId threadId, void *clientData, int *flagPtr, int value); MODULE_SCOPE void TclAsyncMarkFromNotifier(void); MODULE_SCOPE double TclBignumToDouble(const void *bignum); MODULE_SCOPE int TclByteArrayMatch(const unsigned char *string, Tcl_Size strLen, const unsigned char *pattern, Tcl_Size ptnLen, int flags); MODULE_SCOPE double TclCeil(const void *a); MODULE_SCOPE void TclChannelPreserve(Tcl_Channel chan); MODULE_SCOPE void TclChannelRelease(Tcl_Channel chan); MODULE_SCOPE int TclChannelGetBlockingMode(Tcl_Channel chan); MODULE_SCOPE int TclCheckArrayTraces(Tcl_Interp *interp, Var *varPtr, Var *arrayPtr, Tcl_Obj *name, int index); MODULE_SCOPE int TclCheckEmptyString(Tcl_Obj *objPtr); MODULE_SCOPE int TclChanCaughtErrorBypass(Tcl_Interp *interp, Tcl_Channel chan); MODULE_SCOPE Tcl_ObjCmdProc TclChannelNamesCmd; MODULE_SCOPE int TclChanIsBinary(Tcl_Channel chan); MODULE_SCOPE Tcl_NRPostProc TclClearRootEnsemble; MODULE_SCOPE int TclCompareTwoNumbers(Tcl_Obj *valuePtr, Tcl_Obj *value2Ptr); MODULE_SCOPE ContLineLoc *TclContinuationsEnter(Tcl_Obj *objPtr, Tcl_Size num, Tcl_Size *loc); MODULE_SCOPE void TclContinuationsEnterDerived(Tcl_Obj *objPtr, Tcl_Size start, Tcl_Size *clNext); MODULE_SCOPE ContLineLoc *TclContinuationsGet(Tcl_Obj *objPtr); MODULE_SCOPE void TclContinuationsCopy(Tcl_Obj *objPtr, Tcl_Obj *originObjPtr); MODULE_SCOPE Tcl_Size TclConvertElement(const char *src, Tcl_Size length, char *dst, int flags); MODULE_SCOPE Tcl_Command TclCreateObjCommandInNs(Tcl_Interp *interp, const char *cmdName, Tcl_Namespace *nsPtr, Tcl_ObjCmdProc *proc, void *clientData, Tcl_CmdDeleteProc *deleteProc); MODULE_SCOPE Tcl_Command TclCreateEnsembleInNs(Tcl_Interp *interp, const char *name, Tcl_Namespace *nameNamespacePtr, Tcl_Namespace *ensembleNamespacePtr, int flags); MODULE_SCOPE void TclDeleteNamespaceVars(Namespace *nsPtr); MODULE_SCOPE void TclDeleteNamespaceChildren(Namespace *nsPtr); MODULE_SCOPE Tcl_Size TclDictGetSize(Tcl_Obj *dictPtr); MODULE_SCOPE int TclFindDictElement(Tcl_Interp *interp, const char *dict, Tcl_Size dictLength, const char **elementPtr, const char **nextPtr, Tcl_Size *sizePtr, int *literalPtr); MODULE_SCOPE Tcl_Obj * TclDictObjSmartRef(Tcl_Interp *interp, Tcl_Obj *); MODULE_SCOPE int TclDictGet(Tcl_Interp *interp, Tcl_Obj *dictPtr, const char *key, Tcl_Obj **valuePtrPtr); MODULE_SCOPE int TclDictPut(Tcl_Interp *interp, Tcl_Obj *dictPtr, const char *key, Tcl_Obj *valuePtr); MODULE_SCOPE int TclDictPutString(Tcl_Interp *interp, Tcl_Obj *dictPtr, const char *key, const char *value); MODULE_SCOPE int TclDictRemove(Tcl_Interp *interp, Tcl_Obj *dictPtr, const char *key); /* TIP #280 - Modified token based evaluation, with line information. */ MODULE_SCOPE int TclEvalEx(Tcl_Interp *interp, const char *script, Tcl_Size numBytes, int flags, Tcl_Size line, Tcl_Size *clNextOuter, const char *outerScript); MODULE_SCOPE Tcl_ObjCmdProc TclFileAttrsCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileCopyCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileDeleteCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileLinkCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileMakeDirsCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileReadLinkCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileRenameCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileTempDirCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileTemporaryCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileHomeCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileTildeExpandCmd; MODULE_SCOPE void TclCreateLateExitHandler(Tcl_ExitProc *proc, void *clientData); MODULE_SCOPE void TclDeleteLateExitHandler(Tcl_ExitProc *proc, void *clientData); MODULE_SCOPE char * TclDStringAppendObj(Tcl_DString *dsPtr, Tcl_Obj *objPtr); MODULE_SCOPE char * TclDStringAppendDString(Tcl_DString *dsPtr, Tcl_DString *toAppendPtr); MODULE_SCOPE Tcl_Obj *const *TclFetchEnsembleRoot(Tcl_Interp *interp, Tcl_Obj *const *objv, Tcl_Size objc, Tcl_Size *objcPtr); MODULE_SCOPE Tcl_Obj *const *TclEnsembleGetRewriteValues(Tcl_Interp *interp); MODULE_SCOPE Tcl_Namespace *TclEnsureNamespace(Tcl_Interp *interp, Tcl_Namespace *namespacePtr); MODULE_SCOPE void TclFinalizeAllocSubsystem(void); MODULE_SCOPE void TclFinalizeAsync(void); MODULE_SCOPE void TclFinalizeDoubleConversion(void); MODULE_SCOPE void TclFinalizeEncodingSubsystem(void); MODULE_SCOPE void TclFinalizeEnvironment(void); MODULE_SCOPE void TclFinalizeEvaluation(void); MODULE_SCOPE void TclFinalizeExecution(void); MODULE_SCOPE void TclFinalizeIOSubsystem(void); MODULE_SCOPE void TclFinalizeFilesystem(void); MODULE_SCOPE void TclResetFilesystem(void); MODULE_SCOPE void TclFinalizeLoad(void); MODULE_SCOPE void TclFinalizeLock(void); MODULE_SCOPE void TclFinalizeMemorySubsystem(void); MODULE_SCOPE void TclFinalizeNotifier(void); MODULE_SCOPE void TclFinalizeObjects(void); MODULE_SCOPE void TclFinalizePreserve(void); MODULE_SCOPE void TclFinalizeSynchronization(void); MODULE_SCOPE void TclInitThreadAlloc(void); MODULE_SCOPE void TclFinalizeThreadAlloc(void); MODULE_SCOPE void TclFinalizeThreadAllocThread(void); MODULE_SCOPE void TclFinalizeThreadData(int quick); MODULE_SCOPE void TclFinalizeThreadObjects(void); MODULE_SCOPE double TclFloor(const void *a); MODULE_SCOPE void TclFormatNaN(double value, char *buffer); MODULE_SCOPE int TclFSFileAttrIndex(Tcl_Obj *pathPtr, const char *attributeName, int *indexPtr); MODULE_SCOPE Tcl_Command TclNRCreateCommandInNs(Tcl_Interp *interp, const char *cmdName, Tcl_Namespace *nsPtr, Tcl_ObjCmdProc *proc, Tcl_ObjCmdProc *nreProc, void *clientData, Tcl_CmdDeleteProc *deleteProc); MODULE_SCOPE int TclNREvalFile(Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *encodingName); MODULE_SCOPE int * TclGetAsyncReadyPtr(void); MODULE_SCOPE Tcl_Obj * TclGetBgErrorHandler(Tcl_Interp *interp); MODULE_SCOPE int TclGetChannelFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Channel *chanPtr, int *modePtr, int flags); MODULE_SCOPE CmdFrame * TclGetCmdFrameForProcedure(Proc *procPtr); MODULE_SCOPE int TclGetCompletionCodeFromObj(Tcl_Interp *interp, Tcl_Obj *value, int *code); MODULE_SCOPE Proc * TclGetLambdaFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj **nsObjPtrPtr); MODULE_SCOPE Tcl_Obj * TclGetProcessGlobalValue(ProcessGlobalValue *pgvPtr); MODULE_SCOPE Tcl_Obj * TclGetSourceFromFrame(CmdFrame *cfPtr, Tcl_Size objc, Tcl_Obj *const objv[]); MODULE_SCOPE char * TclGetStringStorage(Tcl_Obj *objPtr, Tcl_Size *sizePtr); MODULE_SCOPE int TclGetLoadedLibraries(Tcl_Interp *interp, const char *targetName, const char *prefix); MODULE_SCOPE int TclGetWideBitsFromObj(Tcl_Interp *, Tcl_Obj *, Tcl_WideInt *); MODULE_SCOPE int TclCompareStringKeys(void *keyPtr, Tcl_HashEntry *hPtr); MODULE_SCOPE size_t TclHashStringKey(Tcl_HashTable *tablePtr, void *keyPtr); MODULE_SCOPE int TclIncrObj(Tcl_Interp *interp, Tcl_Obj *valuePtr, Tcl_Obj *incrPtr); MODULE_SCOPE Tcl_Obj * TclIncrObjVar2(Tcl_Interp *interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *incrPtr, int flags); MODULE_SCOPE Tcl_ObjCmdProc TclInfoExistsCmd; MODULE_SCOPE Tcl_ObjCmdProc TclInfoCoroutineCmd; MODULE_SCOPE Tcl_Obj * TclInfoFrame(Tcl_Interp *interp, CmdFrame *framePtr); MODULE_SCOPE Tcl_ObjCmdProc TclInfoGlobalsCmd; MODULE_SCOPE Tcl_ObjCmdProc TclInfoLocalsCmd; MODULE_SCOPE Tcl_ObjCmdProc TclInfoVarsCmd; MODULE_SCOPE Tcl_ObjCmdProc TclInfoConstsCmd; MODULE_SCOPE Tcl_ObjCmdProc TclInfoConstantCmd; MODULE_SCOPE void TclInitAlloc(void); MODULE_SCOPE void TclInitDbCkalloc(void); MODULE_SCOPE void TclInitDoubleConversion(void); MODULE_SCOPE void TclInitEmbeddedConfigurationInformation( Tcl_Interp *interp); MODULE_SCOPE void TclInitEncodingSubsystem(void); MODULE_SCOPE void TclInitIOSubsystem(void); MODULE_SCOPE void TclInitLimitSupport(Tcl_Interp *interp); MODULE_SCOPE void TclInitNamespaceSubsystem(void); MODULE_SCOPE void TclInitNotifier(void); MODULE_SCOPE void TclInitObjSubsystem(void); MODULE_SCOPE int TclInterpReady(Tcl_Interp *interp); MODULE_SCOPE int TclIsBareword(int byte); MODULE_SCOPE Tcl_Obj * TclJoinPath(Tcl_Size elements, Tcl_Obj * const objv[], int forceRelative); MODULE_SCOPE Tcl_Obj * TclGetHomeDirObj(Tcl_Interp *interp, const char *user); MODULE_SCOPE Tcl_Obj * TclResolveTildePath(Tcl_Interp *interp, Tcl_Obj *pathObj); MODULE_SCOPE Tcl_Obj * TclResolveTildePathList(Tcl_Obj *pathsObj); MODULE_SCOPE int TclJoinThread(Tcl_ThreadId id, int *result); MODULE_SCOPE void TclLimitRemoveAllHandlers(Tcl_Interp *interp); MODULE_SCOPE Tcl_Obj * TclLindexList(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Obj *argPtr); MODULE_SCOPE Tcl_Obj * TclLindexFlat(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size indexCount, Tcl_Obj *const indexArray[]); MODULE_SCOPE Tcl_Obj * TclListObjGetElement(Tcl_Obj *listObj, Tcl_Size index); /* TIP #280 */ MODULE_SCOPE void TclListLines(Tcl_Obj *listObj, Tcl_Size line, Tcl_Size n, Tcl_Size *lines, Tcl_Obj *const *elems); MODULE_SCOPE Tcl_Obj * TclListObjCopy(Tcl_Interp *interp, Tcl_Obj *listPtr); MODULE_SCOPE int TclListObjAppendElements(Tcl_Interp *interp, Tcl_Obj *toObj, Tcl_Size elemCount, Tcl_Obj *const elemObjv[]); MODULE_SCOPE Tcl_Obj * TclListObjRange(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size fromIdx, Tcl_Size toIdx); MODULE_SCOPE Tcl_Obj * TclLsetList(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Obj *indexPtr, Tcl_Obj *valuePtr); MODULE_SCOPE Tcl_Obj * TclLsetFlat(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size indexCount, Tcl_Obj *const indexArray[], Tcl_Obj *valuePtr); MODULE_SCOPE Tcl_Command TclMakeEnsemble(Tcl_Interp *interp, const char *name, const EnsembleImplMap map[]); MODULE_SCOPE Tcl_Size TclMaxListLength(const char *bytes, Tcl_Size numBytes, const char **endPtr); MODULE_SCOPE int TclMergeReturnOptions(Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], Tcl_Obj **optionsPtrPtr, int *codePtr, int *levelPtr); MODULE_SCOPE Tcl_Obj * TclNoErrorStack(Tcl_Interp *interp, Tcl_Obj *options); MODULE_SCOPE int TclNokia770Doubles(void); MODULE_SCOPE void TclNsDecrRefCount(Namespace *nsPtr); MODULE_SCOPE int TclNamespaceDeleted(Namespace *nsPtr); MODULE_SCOPE void TclObjVarErrMsg(Tcl_Interp *interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, const char *operation, const char *reason, int index); MODULE_SCOPE int TclObjInvokeNamespace(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], Tcl_Namespace *nsPtr, int flags); MODULE_SCOPE int TclObjUnsetVar2(Tcl_Interp *interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags); MODULE_SCOPE int TclParseBackslash(const char *src, Tcl_Size numBytes, Tcl_Size *readPtr, char *dst); MODULE_SCOPE int TclParseNumber(Tcl_Interp *interp, Tcl_Obj *objPtr, const char *expected, const char *bytes, Tcl_Size numBytes, const char **endPtrPtr, int flags); MODULE_SCOPE void TclParseInit(Tcl_Interp *interp, const char *string, Tcl_Size numBytes, Tcl_Parse *parsePtr); MODULE_SCOPE Tcl_Size TclParseAllWhiteSpace(const char *src, Tcl_Size numBytes); MODULE_SCOPE int TclProcessReturn(Tcl_Interp *interp, int code, int level, Tcl_Obj *returnOpts); MODULE_SCOPE void TclUndoRefCount(Tcl_Obj *objPtr); MODULE_SCOPE int TclpObjLstat(Tcl_Obj *pathPtr, Tcl_StatBuf *buf); MODULE_SCOPE Tcl_Obj * TclpTempFileName(void); MODULE_SCOPE Tcl_Obj * TclpTempFileNameForLibrary(Tcl_Interp *interp, Tcl_Obj* pathPtr); MODULE_SCOPE Tcl_Obj * TclNewArithSeriesObj(Tcl_Interp *interp, int useDoubles, Tcl_Obj *startObj, Tcl_Obj *endObj, Tcl_Obj *stepObj, Tcl_Obj *lenObj); MODULE_SCOPE Tcl_Obj * TclNewFSPathObj(Tcl_Obj *dirPtr, const char *addStrRep, Tcl_Size len); MODULE_SCOPE Tcl_Obj * TclNewNamespaceObj(Tcl_Namespace *namespacePtr); MODULE_SCOPE void TclpAlertNotifier(void *clientData); MODULE_SCOPE void * TclpNotifierData(void); MODULE_SCOPE void TclpServiceModeHook(int mode); MODULE_SCOPE void TclpSetTimer(const Tcl_Time *timePtr); MODULE_SCOPE int TclpWaitForEvent(const Tcl_Time *timePtr); MODULE_SCOPE void TclpCreateFileHandler(int fd, int mask, Tcl_FileProc *proc, void *clientData); MODULE_SCOPE int TclpDeleteFile(const void *path); MODULE_SCOPE void TclpDeleteFileHandler(int fd); MODULE_SCOPE void TclpFinalizeCondition(Tcl_Condition *condPtr); MODULE_SCOPE void TclpFinalizeMutex(Tcl_Mutex *mutexPtr); MODULE_SCOPE void TclpFinalizeNotifier(void *clientData); MODULE_SCOPE void TclpFinalizePipes(void); MODULE_SCOPE void TclpFinalizeSockets(void); #ifdef _WIN32 MODULE_SCOPE void TclInitSockets(void); #else #define TclInitSockets() /* do nothing */ #endif struct addrinfo; /* forward declaration, needed for TclCreateSocketAddress */ MODULE_SCOPE int TclCreateSocketAddress(Tcl_Interp *interp, struct addrinfo **addrlist, const char *host, int port, int willBind, const char **errorMsgPtr); MODULE_SCOPE int TclpThreadCreate(Tcl_ThreadId *idPtr, Tcl_ThreadCreateProc *proc, void *clientData, TCL_HASH_TYPE stackSize, int flags); MODULE_SCOPE Tcl_Size TclpFindVariable(const char *name, Tcl_Size *lengthPtr); MODULE_SCOPE void TclpInitLibraryPath(char **valuePtr, TCL_HASH_TYPE *lengthPtr, Tcl_Encoding *encodingPtr); MODULE_SCOPE void TclpInitLock(void); MODULE_SCOPE void * TclpInitNotifier(void); MODULE_SCOPE void TclpInitPlatform(void); MODULE_SCOPE void TclpInitUnlock(void); MODULE_SCOPE Tcl_Obj * TclpObjListVolumes(void); MODULE_SCOPE void TclpGlobalLock(void); MODULE_SCOPE void TclpGlobalUnlock(void); MODULE_SCOPE int TclpObjNormalizePath(Tcl_Interp *interp, Tcl_Obj *pathPtr, int nextCheckpoint); MODULE_SCOPE void TclpNativeJoinPath(Tcl_Obj *prefix, const char *joining); MODULE_SCOPE Tcl_Obj * TclpNativeSplitPath(Tcl_Obj *pathPtr, Tcl_Size *lenPtr); MODULE_SCOPE Tcl_PathType TclpGetNativePathType(Tcl_Obj *pathPtr, Tcl_Size *driveNameLengthPtr, Tcl_Obj **driveNameRef); MODULE_SCOPE int TclCrossFilesystemCopy(Tcl_Interp *interp, Tcl_Obj *source, Tcl_Obj *target); MODULE_SCOPE int TclpMatchInDirectory(Tcl_Interp *interp, Tcl_Obj *resultPtr, Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types); MODULE_SCOPE void *TclpGetNativeCwd(void *clientData); MODULE_SCOPE Tcl_FSDupInternalRepProc TclNativeDupInternalRep; MODULE_SCOPE Tcl_Obj * TclpObjLink(Tcl_Obj *pathPtr, Tcl_Obj *toPtr, int linkType); MODULE_SCOPE int TclpObjChdir(Tcl_Obj *pathPtr); MODULE_SCOPE Tcl_Channel TclpOpenTemporaryFile(Tcl_Obj *dirObj, Tcl_Obj *basenameObj, Tcl_Obj *extensionObj, Tcl_Obj *resultingNameObj); MODULE_SCOPE void TclPkgFileSeen(Tcl_Interp *interp, const char *fileName); MODULE_SCOPE void * TclInitPkgFiles(Tcl_Interp *interp); MODULE_SCOPE Tcl_Obj * TclPathPart(Tcl_Interp *interp, Tcl_Obj *pathPtr, Tcl_PathPart portion); MODULE_SCOPE char * TclpReadlink(const char *fileName, Tcl_DString *linkPtr); MODULE_SCOPE void TclpSetVariables(Tcl_Interp *interp); MODULE_SCOPE void * TclThreadStorageKeyGet(Tcl_ThreadDataKey *keyPtr); MODULE_SCOPE void TclThreadStorageKeySet(Tcl_ThreadDataKey *keyPtr, void *data); MODULE_SCOPE TCL_NORETURN void TclpThreadExit(int status); MODULE_SCOPE void TclRememberCondition(Tcl_Condition *mutex); MODULE_SCOPE void TclRememberJoinableThread(Tcl_ThreadId id); MODULE_SCOPE void TclRememberMutex(Tcl_Mutex *mutex); MODULE_SCOPE void TclRemoveScriptLimitCallbacks(Tcl_Interp *interp); MODULE_SCOPE int TclReToGlob(Tcl_Interp *interp, const char *reStr, Tcl_Size reStrLen, Tcl_DString *dsPtr, int *flagsPtr, int *quantifiersFoundPtr); MODULE_SCOPE Tcl_Size TclScanElement(const char *string, Tcl_Size length, char *flagPtr); MODULE_SCOPE void TclSetBgErrorHandler(Tcl_Interp *interp, Tcl_Obj *cmdPrefix); MODULE_SCOPE void TclSetBignumInternalRep(Tcl_Obj *objPtr, void *bignumValue); MODULE_SCOPE int TclSetBooleanFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); MODULE_SCOPE void TclSetCmdNameObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Command *cmdPtr); MODULE_SCOPE void TclSetDuplicateObj(Tcl_Obj *dupPtr, Tcl_Obj *objPtr); MODULE_SCOPE void TclSetProcessGlobalValue(ProcessGlobalValue *pgvPtr, Tcl_Obj *newValue); MODULE_SCOPE void TclSignalExitThread(Tcl_ThreadId id, int result); MODULE_SCOPE void TclSpellFix(Tcl_Interp *interp, Tcl_Obj *const *objv, Tcl_Size objc, Tcl_Size subIdx, Tcl_Obj *bad, Tcl_Obj *fix); MODULE_SCOPE void * TclStackRealloc(Tcl_Interp *interp, void *ptr, TCL_HASH_TYPE numBytes); typedef int (*memCmpFn_t)(const void*, const void*, size_t); MODULE_SCOPE int TclStringCmp(Tcl_Obj *value1Ptr, Tcl_Obj *value2Ptr, int checkEq, int nocase, Tcl_Size reqlength); MODULE_SCOPE int TclStringMatch(const char *str, Tcl_Size strLen, const char *pattern, int ptnLen, int flags); MODULE_SCOPE int TclStringMatchObj(Tcl_Obj *stringObj, Tcl_Obj *patternObj, int flags); MODULE_SCOPE void TclSubstCompile(Tcl_Interp *interp, const char *bytes, Tcl_Size numBytes, int flags, Tcl_Size line, struct CompileEnv *envPtr); MODULE_SCOPE int TclSubstOptions(Tcl_Interp *interp, Tcl_Size numOpts, Tcl_Obj *const opts[], int *flagPtr); MODULE_SCOPE void TclSubstParse(Tcl_Interp *interp, const char *bytes, Tcl_Size numBytes, int flags, Tcl_Parse *parsePtr, Tcl_InterpState *statePtr); MODULE_SCOPE int TclSubstTokens(Tcl_Interp *interp, Tcl_Token *tokenPtr, Tcl_Size count, int *tokensLeftPtr, Tcl_Size line, Tcl_Size *clNextOuter, const char *outerScript); MODULE_SCOPE Tcl_Size TclTrim(const char *bytes, Tcl_Size numBytes, const char *trim, Tcl_Size numTrim, Tcl_Size *trimRight); MODULE_SCOPE Tcl_Size TclTrimLeft(const char *bytes, Tcl_Size numBytes, const char *trim, Tcl_Size numTrim); MODULE_SCOPE Tcl_Size TclTrimRight(const char *bytes, Tcl_Size numBytes, const char *trim, Tcl_Size numTrim); MODULE_SCOPE const char*TclGetCommandTypeName(Tcl_Command command); MODULE_SCOPE int TclObjInterpProc(void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); MODULE_SCOPE void TclRegisterCommandTypeName( Tcl_ObjCmdProc *implementationProc, const char *nameStr); MODULE_SCOPE int TclUtfCmp(const char *cs, const char *ct); MODULE_SCOPE int TclUtfCasecmp(const char *cs, const char *ct); MODULE_SCOPE int TclUtfCount(int ch); MODULE_SCOPE Tcl_Obj * TclpNativeToNormalized(void *clientData); MODULE_SCOPE Tcl_Obj * TclpFilesystemPathType(Tcl_Obj *pathPtr); MODULE_SCOPE int TclpDlopen(Tcl_Interp *interp, Tcl_Obj *pathPtr, Tcl_LoadHandle *loadHandle, Tcl_FSUnloadFileProc **unloadProcPtr, int flags); MODULE_SCOPE int TclpUtime(Tcl_Obj *pathPtr, struct utimbuf *tval); #ifdef TCL_LOAD_FROM_MEMORY MODULE_SCOPE void * TclpLoadMemoryGetBuffer(size_t size); MODULE_SCOPE int TclpLoadMemory(void *buffer, size_t size, Tcl_Size codeSize, const char *path, Tcl_LoadHandle *loadHandle, Tcl_FSUnloadFileProc **unloadProcPtr, int flags); #endif MODULE_SCOPE void TclInitThreadStorage(void); MODULE_SCOPE void TclFinalizeThreadDataThread(void); MODULE_SCOPE void TclFinalizeThreadStorage(void); #ifdef TCL_WIDE_CLICKS MODULE_SCOPE long long TclpGetWideClicks(void); MODULE_SCOPE double TclpWideClicksToNanoseconds(long long clicks); MODULE_SCOPE double TclpWideClickInMicrosec(void); #else # ifdef _WIN32 # define TCL_WIDE_CLICKS 1 MODULE_SCOPE long long TclpGetWideClicks(void); MODULE_SCOPE double TclpWideClickInMicrosec(void); # define TclpWideClicksToNanoseconds(clicks) \ ((double)(clicks) * TclpWideClickInMicrosec() * 1000) # endif #endif MODULE_SCOPE long long TclpGetMicroseconds(void); MODULE_SCOPE int TclZlibInit(Tcl_Interp *interp); MODULE_SCOPE void * TclpThreadCreateKey(void); MODULE_SCOPE void TclpThreadDeleteKey(void *keyPtr); MODULE_SCOPE void TclpThreadSetGlobalTSD(void *tsdKeyPtr, void *ptr); MODULE_SCOPE void * TclpThreadGetGlobalTSD(void *tsdKeyPtr); MODULE_SCOPE void TclErrorStackResetIf(Tcl_Interp *interp, const char *msg, Tcl_Size length); /* Tip 430 */ MODULE_SCOPE int TclZipfs_Init(Tcl_Interp *interp); MODULE_SCOPE int TclIsZipfsPath(const char *path); MODULE_SCOPE void TclZipfsFinalize(void); /* * Many parsing tasks need a common definition of whitespace. * Use this routine and macro to achieve that and place * optimization (fragile on changes) in one place. */ MODULE_SCOPE int TclIsSpaceProc(int byte); #define TclIsSpaceProcM(byte) \ (((byte) > 0x20) ? 0 : TclIsSpaceProc(byte)) /* *---------------------------------------------------------------- * Command procedures in the generic core: *---------------------------------------------------------------- */ MODULE_SCOPE Tcl_ObjCmdProc Tcl_AfterObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_AppendObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ApplyObjCmd; MODULE_SCOPE Tcl_Command TclInitArrayCmd(Tcl_Interp *interp); MODULE_SCOPE Tcl_Command TclInitBinaryCmd(Tcl_Interp *interp); MODULE_SCOPE Tcl_ObjCmdProc Tcl_BreakObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_CatchObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_CdObjCmd; MODULE_SCOPE Tcl_Command TclInitChanCmd(Tcl_Interp *interp); MODULE_SCOPE Tcl_ObjCmdProc TclChanCreateObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclChanPostEventObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclChanPopObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclChanPushObjCmd; MODULE_SCOPE void TclClockInit(Tcl_Interp *interp); MODULE_SCOPE Tcl_ObjCmdProc TclClockOldscanObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_CloseObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ConcatObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ConstObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ContinueObjCmd; MODULE_SCOPE Tcl_TimerToken TclCreateAbsoluteTimerHandler( Tcl_Time *timePtr, Tcl_TimerProc *proc, void *clientData); MODULE_SCOPE Tcl_ObjCmdProc TclDefaultBgErrorHandlerObjCmd; MODULE_SCOPE Tcl_Command TclInitDictCmd(Tcl_Interp *interp); MODULE_SCOPE int TclDictWithFinish(Tcl_Interp *interp, Var *varPtr, Var *arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int index, int pathc, Tcl_Obj *const pathv[], Tcl_Obj *keysPtr); MODULE_SCOPE Tcl_Obj * TclDictWithInit(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size pathc, Tcl_Obj *const pathv[]); MODULE_SCOPE Tcl_ObjCmdProc Tcl_DisassembleObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclLoadIcuObjCmd; /* Assemble command function */ MODULE_SCOPE Tcl_ObjCmdProc Tcl_AssembleObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNRAssembleObjCmd; MODULE_SCOPE Tcl_Command TclInitEncodingCmd(Tcl_Interp *interp); MODULE_SCOPE Tcl_ObjCmdProc Tcl_EofObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ErrorObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_EvalObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ExecObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ExitObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ExprObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_FblockedObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_FconfigureObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_FcopyObjCmd; MODULE_SCOPE Tcl_Command TclInitFileCmd(Tcl_Interp *interp); MODULE_SCOPE Tcl_ObjCmdProc Tcl_FileEventObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_FlushObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ForObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ForeachObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_FormatObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_GetsObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_GlobalObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_GlobObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_IfObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_IncrObjCmd; MODULE_SCOPE Tcl_Command TclInitInfoCmd(Tcl_Interp *interp); MODULE_SCOPE Tcl_ObjCmdProc Tcl_InterpObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_JoinObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LappendObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LassignObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LeditObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LindexObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LinsertObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LlengthObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ListObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LmapObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LoadObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LpopObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LrangeObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LremoveObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LrepeatObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LreplaceObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LreverseObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LsearchObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LseqObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LsetObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_LsortObjCmd; MODULE_SCOPE Tcl_Command TclInitNamespaceCmd(Tcl_Interp *interp); MODULE_SCOPE Tcl_ObjCmdProc TclNamespaceEnsembleCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_OpenObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_PackageObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_PidObjCmd; MODULE_SCOPE Tcl_Command TclInitPrefixCmd(Tcl_Interp *interp); MODULE_SCOPE Tcl_ObjCmdProc Tcl_PutsObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_PwdObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ReadObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_RegexpObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_RegsubObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_RenameObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_RepresentationCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ReturnObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ScanObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_SeekObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_SetObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_SplitObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_SocketObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_SourceObjCmd; MODULE_SCOPE Tcl_Command TclInitStringCmd(Tcl_Interp *interp); MODULE_SCOPE Tcl_ObjCmdProc Tcl_SubstObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_SwitchObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_TellObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_ThrowObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_TimeObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_TimeRateObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_TraceObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_TryObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_UnloadObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_UnsetObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_UpdateObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_UplevelObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_UpvarObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_VariableObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_VwaitObjCmd; MODULE_SCOPE Tcl_ObjCmdProc Tcl_WhileObjCmd; /* *---------------------------------------------------------------- * Compilation procedures for commands in the generic core: *---------------------------------------------------------------- */ MODULE_SCOPE CompileProc TclCompileAppendCmd; MODULE_SCOPE CompileProc TclCompileArrayExistsCmd; MODULE_SCOPE CompileProc TclCompileArraySetCmd; MODULE_SCOPE CompileProc TclCompileArrayUnsetCmd; MODULE_SCOPE CompileProc TclCompileBreakCmd; MODULE_SCOPE CompileProc TclCompileCatchCmd; MODULE_SCOPE CompileProc TclCompileClockClicksCmd; MODULE_SCOPE CompileProc TclCompileClockReadingCmd; MODULE_SCOPE CompileProc TclCompileConcatCmd; MODULE_SCOPE CompileProc TclCompileConstCmd; MODULE_SCOPE CompileProc TclCompileContinueCmd; MODULE_SCOPE CompileProc TclCompileDictAppendCmd; MODULE_SCOPE CompileProc TclCompileDictCreateCmd; MODULE_SCOPE CompileProc TclCompileDictExistsCmd; MODULE_SCOPE CompileProc TclCompileDictForCmd; MODULE_SCOPE CompileProc TclCompileDictGetCmd; MODULE_SCOPE CompileProc TclCompileDictGetWithDefaultCmd; MODULE_SCOPE CompileProc TclCompileDictIncrCmd; MODULE_SCOPE CompileProc TclCompileDictLappendCmd; MODULE_SCOPE CompileProc TclCompileDictMapCmd; MODULE_SCOPE CompileProc TclCompileDictMergeCmd; MODULE_SCOPE CompileProc TclCompileDictSetCmd; MODULE_SCOPE CompileProc TclCompileDictUnsetCmd; MODULE_SCOPE CompileProc TclCompileDictUpdateCmd; MODULE_SCOPE CompileProc TclCompileDictWithCmd; MODULE_SCOPE CompileProc TclCompileEnsemble; MODULE_SCOPE CompileProc TclCompileErrorCmd; MODULE_SCOPE CompileProc TclCompileExprCmd; MODULE_SCOPE CompileProc TclCompileForCmd; MODULE_SCOPE CompileProc TclCompileForeachCmd; MODULE_SCOPE CompileProc TclCompileFormatCmd; MODULE_SCOPE CompileProc TclCompileGlobalCmd; MODULE_SCOPE CompileProc TclCompileIfCmd; MODULE_SCOPE CompileProc TclCompileInfoCommandsCmd; MODULE_SCOPE CompileProc TclCompileInfoCoroutineCmd; MODULE_SCOPE CompileProc TclCompileInfoExistsCmd; MODULE_SCOPE CompileProc TclCompileInfoLevelCmd; MODULE_SCOPE CompileProc TclCompileInfoObjectClassCmd; MODULE_SCOPE CompileProc TclCompileInfoObjectIsACmd; MODULE_SCOPE CompileProc TclCompileInfoObjectNamespaceCmd; MODULE_SCOPE CompileProc TclCompileIncrCmd; MODULE_SCOPE CompileProc TclCompileLappendCmd; MODULE_SCOPE CompileProc TclCompileLassignCmd; MODULE_SCOPE CompileProc TclCompileLindexCmd; MODULE_SCOPE CompileProc TclCompileLinsertCmd; MODULE_SCOPE CompileProc TclCompileListCmd; MODULE_SCOPE CompileProc TclCompileLlengthCmd; MODULE_SCOPE CompileProc TclCompileLmapCmd; MODULE_SCOPE CompileProc TclCompileLrangeCmd; MODULE_SCOPE CompileProc TclCompileLreplaceCmd; MODULE_SCOPE CompileProc TclCompileLsetCmd; MODULE_SCOPE CompileProc TclCompileNamespaceCodeCmd; MODULE_SCOPE CompileProc TclCompileNamespaceCurrentCmd; MODULE_SCOPE CompileProc TclCompileNamespaceOriginCmd; MODULE_SCOPE CompileProc TclCompileNamespaceQualifiersCmd; MODULE_SCOPE CompileProc TclCompileNamespaceTailCmd; MODULE_SCOPE CompileProc TclCompileNamespaceUpvarCmd; MODULE_SCOPE CompileProc TclCompileNamespaceWhichCmd; MODULE_SCOPE CompileProc TclCompileNoOp; MODULE_SCOPE CompileProc TclCompileObjectNextCmd; MODULE_SCOPE CompileProc TclCompileObjectNextToCmd; MODULE_SCOPE CompileProc TclCompileObjectSelfCmd; MODULE_SCOPE CompileProc TclCompileRegexpCmd; MODULE_SCOPE CompileProc TclCompileRegsubCmd; MODULE_SCOPE CompileProc TclCompileReturnCmd; MODULE_SCOPE CompileProc TclCompileSetCmd; MODULE_SCOPE CompileProc TclCompileStringCatCmd; MODULE_SCOPE CompileProc TclCompileStringCmpCmd; MODULE_SCOPE CompileProc TclCompileStringEqualCmd; MODULE_SCOPE CompileProc TclCompileStringFirstCmd; MODULE_SCOPE CompileProc TclCompileStringIndexCmd; MODULE_SCOPE CompileProc TclCompileStringInsertCmd; MODULE_SCOPE CompileProc TclCompileStringIsCmd; MODULE_SCOPE CompileProc TclCompileStringLastCmd; MODULE_SCOPE CompileProc TclCompileStringLenCmd; MODULE_SCOPE CompileProc TclCompileStringMapCmd; MODULE_SCOPE CompileProc TclCompileStringMatchCmd; MODULE_SCOPE CompileProc TclCompileStringRangeCmd; MODULE_SCOPE CompileProc TclCompileStringReplaceCmd; MODULE_SCOPE CompileProc TclCompileStringToLowerCmd; MODULE_SCOPE CompileProc TclCompileStringToTitleCmd; MODULE_SCOPE CompileProc TclCompileStringToUpperCmd; MODULE_SCOPE CompileProc TclCompileStringTrimCmd; MODULE_SCOPE CompileProc TclCompileStringTrimLCmd; MODULE_SCOPE CompileProc TclCompileStringTrimRCmd; MODULE_SCOPE CompileProc TclCompileSubstCmd; MODULE_SCOPE CompileProc TclCompileSwitchCmd; MODULE_SCOPE CompileProc TclCompileTailcallCmd; MODULE_SCOPE CompileProc TclCompileThrowCmd; MODULE_SCOPE CompileProc TclCompileTryCmd; MODULE_SCOPE CompileProc TclCompileUnsetCmd; MODULE_SCOPE CompileProc TclCompileUpvarCmd; MODULE_SCOPE CompileProc TclCompileVariableCmd; MODULE_SCOPE CompileProc TclCompileWhileCmd; MODULE_SCOPE CompileProc TclCompileYieldCmd; MODULE_SCOPE CompileProc TclCompileYieldToCmd; MODULE_SCOPE CompileProc TclCompileBasic0ArgCmd; MODULE_SCOPE CompileProc TclCompileBasic1ArgCmd; MODULE_SCOPE CompileProc TclCompileBasic2ArgCmd; MODULE_SCOPE CompileProc TclCompileBasic3ArgCmd; MODULE_SCOPE CompileProc TclCompileBasic0Or1ArgCmd; MODULE_SCOPE CompileProc TclCompileBasic1Or2ArgCmd; MODULE_SCOPE CompileProc TclCompileBasic2Or3ArgCmd; MODULE_SCOPE CompileProc TclCompileBasic0To2ArgCmd; MODULE_SCOPE CompileProc TclCompileBasic1To3ArgCmd; MODULE_SCOPE CompileProc TclCompileBasicMin0ArgCmd; MODULE_SCOPE CompileProc TclCompileBasicMin1ArgCmd; MODULE_SCOPE CompileProc TclCompileBasicMin2ArgCmd; MODULE_SCOPE Tcl_ObjCmdProc TclInvertOpCmd; MODULE_SCOPE CompileProc TclCompileInvertOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNotOpCmd; MODULE_SCOPE CompileProc TclCompileNotOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclAddOpCmd; MODULE_SCOPE CompileProc TclCompileAddOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclMulOpCmd; MODULE_SCOPE CompileProc TclCompileMulOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclAndOpCmd; MODULE_SCOPE CompileProc TclCompileAndOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOrOpCmd; MODULE_SCOPE CompileProc TclCompileOrOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclXorOpCmd; MODULE_SCOPE CompileProc TclCompileXorOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclPowOpCmd; MODULE_SCOPE CompileProc TclCompilePowOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclLshiftOpCmd; MODULE_SCOPE CompileProc TclCompileLshiftOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclRshiftOpCmd; MODULE_SCOPE CompileProc TclCompileRshiftOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclModOpCmd; MODULE_SCOPE CompileProc TclCompileModOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNeqOpCmd; MODULE_SCOPE CompileProc TclCompileNeqOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclStrneqOpCmd; MODULE_SCOPE CompileProc TclCompileStrneqOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclInOpCmd; MODULE_SCOPE CompileProc TclCompileInOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclNiOpCmd; MODULE_SCOPE CompileProc TclCompileNiOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclMinusOpCmd; MODULE_SCOPE CompileProc TclCompileMinusOpCmd; MODULE_SCOPE Tcl_ObjCmdProc TclDivOpCmd; MODULE_SCOPE CompileProc TclCompileDivOpCmd; MODULE_SCOPE CompileProc TclCompileLessOpCmd; MODULE_SCOPE CompileProc TclCompileLeqOpCmd; MODULE_SCOPE CompileProc TclCompileGreaterOpCmd; MODULE_SCOPE CompileProc TclCompileGeqOpCmd; MODULE_SCOPE CompileProc TclCompileEqOpCmd; MODULE_SCOPE CompileProc TclCompileStreqOpCmd; MODULE_SCOPE CompileProc TclCompileStrLtOpCmd; MODULE_SCOPE CompileProc TclCompileStrLeOpCmd; MODULE_SCOPE CompileProc TclCompileStrGtOpCmd; MODULE_SCOPE CompileProc TclCompileStrGeOpCmd; MODULE_SCOPE CompileProc TclCompileAssembleCmd; /* * Routines that provide the [string] ensemble functionality. Possible * candidates for public interface. */ MODULE_SCOPE Tcl_Obj * TclStringCat(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags); MODULE_SCOPE Tcl_Obj * TclStringFirst(Tcl_Obj *needle, Tcl_Obj *haystack, Tcl_Size start); MODULE_SCOPE Tcl_Obj * TclStringLast(Tcl_Obj *needle, Tcl_Obj *haystack, Tcl_Size last); MODULE_SCOPE Tcl_Obj * TclStringRepeat(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size count, int flags); MODULE_SCOPE Tcl_Obj * TclStringReplace(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size first, Tcl_Size count, Tcl_Obj *insertPtr, int flags); MODULE_SCOPE Tcl_Obj * TclStringReverse(Tcl_Obj *objPtr, int flags); /* Flag values for the [string] ensemble functions. */ #define TCL_STRING_MATCH_NOCASE TCL_MATCH_NOCASE /* (1<<0) in tcl.h */ #define TCL_STRING_IN_PLACE (1<<1) /* * Functions defined in generic/tclVar.c and currently exported only for use * by the bytecode compiler and engine. Some of these could later be placed in * the public interface. */ MODULE_SCOPE Var * TclObjLookupVarEx(Tcl_Interp * interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags, const char *msg, int createPart1, int createPart2, Var **arrayPtrPtr); MODULE_SCOPE Var * TclLookupArrayElement(Tcl_Interp *interp, Tcl_Obj *arrayNamePtr, Tcl_Obj *elNamePtr, int flags, const char *msg, int createPart1, int createPart2, Var *arrayPtr, int index); MODULE_SCOPE Tcl_Obj * TclPtrGetVarIdx(Tcl_Interp *interp, Var *varPtr, Var *arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags, int index); MODULE_SCOPE Tcl_Obj * TclPtrSetVarIdx(Tcl_Interp *interp, Var *varPtr, Var *arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *newValuePtr, int flags, int index); MODULE_SCOPE Tcl_Obj * TclPtrIncrObjVarIdx(Tcl_Interp *interp, Var *varPtr, Var *arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *incrPtr, int flags, int index); MODULE_SCOPE int TclPtrObjMakeUpvarIdx(Tcl_Interp *interp, Var *otherPtr, Tcl_Obj *myNamePtr, int myFlags, int index); MODULE_SCOPE int TclPtrUnsetVarIdx(Tcl_Interp *interp, Var *varPtr, Var *arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags, int index); MODULE_SCOPE void TclInvalidateNsPath(Namespace *nsPtr); MODULE_SCOPE void TclFindArrayPtrElements(Var *arrayPtr, Tcl_HashTable *tablePtr); /* * The new extended interface to the variable traces. */ MODULE_SCOPE int TclObjCallVarTraces(Interp *iPtr, Var *arrayPtr, Var *varPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags, int leaveErrMsg, int index); /* * So tclObj.c and tclDictObj.c can share these implementations. */ MODULE_SCOPE int TclCompareObjKeys(void *keyPtr, Tcl_HashEntry *hPtr); MODULE_SCOPE void TclFreeObjEntry(Tcl_HashEntry *hPtr); MODULE_SCOPE TCL_HASH_TYPE TclHashObjKey(Tcl_HashTable *tablePtr, void *keyPtr); MODULE_SCOPE int TclFullFinalizationRequested(void); /* * TIP #542 */ MODULE_SCOPE size_t TclUniCharLen(const Tcl_UniChar *uniStr); MODULE_SCOPE int TclUniCharNcmp(const Tcl_UniChar *ucs, const Tcl_UniChar *uct, size_t numChars); MODULE_SCOPE int TclUniCharNcasecmp(const Tcl_UniChar *ucs, const Tcl_UniChar *uct, size_t numChars); MODULE_SCOPE int TclUniCharCaseMatch(const Tcl_UniChar *uniStr, const Tcl_UniChar *uniPattern, int nocase); /* * Just for the purposes of command-type registration. */ MODULE_SCOPE Tcl_ObjCmdProc TclEnsembleImplementationCmd; MODULE_SCOPE Tcl_ObjCmdProc TclAliasObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclLocalAliasObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclChildObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclInvokeImportedCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOOPublicObjectCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOOPrivateObjectCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOOMyClassObjCmd; /* * TIP #462. */ /* * The following enum values give the status of a spawned process. */ typedef enum TclProcessWaitStatus { TCL_PROCESS_ERROR = -1, /* Error waiting for process to exit */ TCL_PROCESS_UNCHANGED = 0, /* No change since the last call. */ TCL_PROCESS_EXITED = 1, /* Process has exited. */ TCL_PROCESS_SIGNALED = 2, /* Child killed because of a signal. */ TCL_PROCESS_STOPPED = 3, /* Child suspended because of a signal. */ TCL_PROCESS_UNKNOWN_STATUS = 4 /* Child wait status didn't make sense. */ } TclProcessWaitStatus; MODULE_SCOPE Tcl_Command TclInitProcessCmd(Tcl_Interp *interp); MODULE_SCOPE void TclProcessCreated(Tcl_Pid pid); MODULE_SCOPE TclProcessWaitStatus TclProcessWait(Tcl_Pid pid, int options, int *codePtr, Tcl_Obj **msgObjPtr, Tcl_Obj **errorObjPtr); MODULE_SCOPE int TclClose(Tcl_Interp *, Tcl_Channel chan); /* * TIP #508: [array default] */ MODULE_SCOPE void TclInitArrayVar(Var *arrayPtr); MODULE_SCOPE Tcl_Obj * TclGetArrayDefault(Var *arrayPtr); /* * Utility routines for encoding index values as integers. Used by both * some of the command compilers and by [lsort] and [lsearch]. */ MODULE_SCOPE int TclIndexEncode(Tcl_Interp *interp, Tcl_Obj *objPtr, int before, int after, int *indexPtr); MODULE_SCOPE Tcl_Size TclIndexDecode(int encoded, Tcl_Size endValue); /* * Error message utility functions */ MODULE_SCOPE int TclCommandWordLimitError(Tcl_Interp *interp, Tcl_Size count); #endif /* TCL_MAJOR_VERSION > 8 */ /* Constants used in index value encoding routines. */ #define TCL_INDEX_END ((Tcl_Size)-2) #define TCL_INDEX_START ((Tcl_Size)0) /* *---------------------------------------------------------------------- * * TclScaleTime -- * * TIP #233 (Virtualized Time): Wrapper around the time virutalisation * rescale function to hide the binding of the clientData. * * This is static inline code; it's like a macro, but a function. It's * used because this is a piece of code that ends up in places that are a * bit performance sensitive. * * Results: * None * * Side effects: * Updates the time structure (given as an argument) with what the time * should be after virtualisation. * *---------------------------------------------------------------------- */ static inline void TclScaleTime( Tcl_Time *timePtr) { if (timePtr != NULL) { tclScaleTimeProcPtr(timePtr, tclTimeClientData); } } /* *---------------------------------------------------------------- * Macros used by the Tcl core to create and release Tcl objects. * TclNewObj(objPtr) creates a new object denoting an empty string. * TclDecrRefCount(objPtr) decrements the object's reference count, and frees * the object if its reference count is zero. These macros are inline versions * of Tcl_NewObj() and Tcl_DecrRefCount(). Notice that the names differ in not * having a "_" after the "Tcl". Notice also that these macros reference their * argument more than once, so you should avoid calling them with an * expression that is expensive to compute or has side effects. The ANSI C * "prototypes" for these macros are: * * MODULE_SCOPE void TclNewObj(Tcl_Obj *objPtr); * MODULE_SCOPE void TclDecrRefCount(Tcl_Obj *objPtr); * * These macros are defined in terms of two macros that depend on memory * allocator in use: TclAllocObjStorage, TclFreeObjStorage. They are defined * below. *---------------------------------------------------------------- */ /* * DTrace object allocation probe macros. */ #ifdef USE_DTRACE #ifndef _TCLDTRACE_H #include "tclDTrace.h" #endif #define TCL_DTRACE_OBJ_CREATE(objPtr) TCL_OBJ_CREATE(objPtr) #define TCL_DTRACE_OBJ_FREE(objPtr) TCL_OBJ_FREE(objPtr) #else /* USE_DTRACE */ #define TCL_DTRACE_OBJ_CREATE(objPtr) {} #define TCL_DTRACE_OBJ_FREE(objPtr) {} #endif /* USE_DTRACE */ #ifdef TCL_COMPILE_STATS # define TclIncrObjsAllocated() \ tclObjsAlloced++ # define TclIncrObjsFreed() \ tclObjsFreed++ #else # define TclIncrObjsAllocated() # define TclIncrObjsFreed() #endif /* TCL_COMPILE_STATS */ # define TclAllocObjStorage(objPtr) \ TclAllocObjStorageEx(NULL, (objPtr)) # define TclFreeObjStorage(objPtr) \ TclFreeObjStorageEx(NULL, (objPtr)) #ifndef TCL_MEM_DEBUG # define TclNewObj(objPtr) \ TclIncrObjsAllocated(); \ TclAllocObjStorage(objPtr); \ (objPtr)->refCount = 0; \ (objPtr)->bytes = &tclEmptyString; \ (objPtr)->length = 0; \ (objPtr)->typePtr = NULL; \ TCL_DTRACE_OBJ_CREATE(objPtr) /* * Invalidate the string rep first so we can use the bytes value for our * pointer chain, and signal an obj deletion (as opposed to shimmering) with * 'length == TCL_INDEX_NONE'. * Use empty 'if ; else' to handle use in unbraced outer if/else conditions. */ # define TclDecrRefCount(objPtr) \ if ((objPtr)->refCount-- > 1) ; else { \ if (!(objPtr)->typePtr || !(objPtr)->typePtr->freeIntRepProc) { \ TCL_DTRACE_OBJ_FREE(objPtr); \ if ((objPtr)->bytes \ && ((objPtr)->bytes != &tclEmptyString)) { \ Tcl_Free((objPtr)->bytes); \ } \ (objPtr)->length = TCL_INDEX_NONE; \ TclFreeObjStorage(objPtr); \ TclIncrObjsFreed(); \ } else { \ TclFreeObj(objPtr); \ } \ } #if TCL_THREADS && !defined(USE_THREAD_ALLOC) # define USE_THREAD_ALLOC 1 #endif #if defined(PURIFY) /* * The PURIFY mode is like the regular mode, but instead of doing block * Tcl_Obj allocation and keeping a freed list for efficiency, it always * allocates and frees a single Tcl_Obj so that tools like Purify can better * track memory leaks. */ # define TclAllocObjStorageEx(interp, objPtr) \ (objPtr) = (Tcl_Obj *)Tcl_Alloc(sizeof(Tcl_Obj)) # define TclFreeObjStorageEx(interp, objPtr) \ Tcl_Free(objPtr) #undef USE_THREAD_ALLOC #undef USE_TCLALLOC #elif TCL_THREADS && defined(USE_THREAD_ALLOC) /* * The TCL_THREADS mode is like the regular mode but allocates Tcl_Obj's from * per-thread caches. */ MODULE_SCOPE Tcl_Obj * TclThreadAllocObj(void); MODULE_SCOPE void TclThreadFreeObj(Tcl_Obj *); MODULE_SCOPE Tcl_Mutex *TclpNewAllocMutex(void); MODULE_SCOPE void TclFreeAllocCache(void *); MODULE_SCOPE void * TclpGetAllocCache(void); MODULE_SCOPE void TclpSetAllocCache(void *); MODULE_SCOPE void TclpFreeAllocMutex(Tcl_Mutex *mutex); MODULE_SCOPE void TclpInitAllocCache(void); MODULE_SCOPE void TclpFreeAllocCache(void *); /* * These macros need to be kept in sync with the code of TclThreadAllocObj() * and TclThreadFreeObj(). * * Note that the optimiser should resolve the case (interp==NULL) at compile * time. */ # define ALLOC_NOBJHIGH 1200 # define TclAllocObjStorageEx(interp, objPtr) \ do { \ AllocCache *cachePtr; \ if (((interp) == NULL) || \ ((cachePtr = ((Interp *)(interp))->allocCache), \ (cachePtr->numObjects == 0))) { \ (objPtr) = TclThreadAllocObj(); \ } else { \ (objPtr) = cachePtr->firstObjPtr; \ cachePtr->firstObjPtr = (Tcl_Obj *)(objPtr)->internalRep.twoPtrValue.ptr1; \ --cachePtr->numObjects; \ } \ } while (0) # define TclFreeObjStorageEx(interp, objPtr) \ do { \ AllocCache *cachePtr; \ if (((interp) == NULL) || \ ((cachePtr = ((Interp *)(interp))->allocCache), \ ((cachePtr->numObjects == 0) || \ (cachePtr->numObjects >= ALLOC_NOBJHIGH)))) { \ TclThreadFreeObj(objPtr); \ } else { \ (objPtr)->internalRep.twoPtrValue.ptr1 = cachePtr->firstObjPtr; \ cachePtr->firstObjPtr = objPtr; \ ++cachePtr->numObjects; \ } \ } while (0) #else /* not PURIFY or USE_THREAD_ALLOC */ #if defined(USE_TCLALLOC) && USE_TCLALLOC MODULE_SCOPE void TclFinalizeAllocSubsystem(); MODULE_SCOPE void TclInitAlloc(); #else # define USE_TCLALLOC 0 #endif #if TCL_THREADS /* declared in tclObj.c */ MODULE_SCOPE Tcl_Mutex tclObjMutex; #endif # define TclAllocObjStorageEx(interp, objPtr) \ do { \ Tcl_MutexLock(&tclObjMutex); \ if (tclFreeObjList == NULL) { \ TclAllocateFreeObjects(); \ } \ (objPtr) = tclFreeObjList; \ tclFreeObjList = (Tcl_Obj *) \ tclFreeObjList->internalRep.twoPtrValue.ptr1; \ Tcl_MutexUnlock(&tclObjMutex); \ } while (0) # define TclFreeObjStorageEx(interp, objPtr) \ do { \ Tcl_MutexLock(&tclObjMutex); \ (objPtr)->internalRep.twoPtrValue.ptr1 = (void *) tclFreeObjList; \ tclFreeObjList = (objPtr); \ Tcl_MutexUnlock(&tclObjMutex); \ } while (0) #endif #else /* TCL_MEM_DEBUG */ MODULE_SCOPE void TclDbInitNewObj(Tcl_Obj *objPtr, const char *file, int line); # define TclDbNewObj(objPtr, file, line) \ do { \ TclIncrObjsAllocated(); \ (objPtr) = (Tcl_Obj *) \ Tcl_DbCkalloc(sizeof(Tcl_Obj), (file), (line)); \ TclDbInitNewObj((objPtr), (file), (line)); \ TCL_DTRACE_OBJ_CREATE(objPtr); \ } while (0) # define TclNewObj(objPtr) \ TclDbNewObj(objPtr, __FILE__, __LINE__); # define TclDecrRefCount(objPtr) \ Tcl_DbDecrRefCount(objPtr, __FILE__, __LINE__) #undef USE_THREAD_ALLOC #endif /* TCL_MEM_DEBUG */ /* *---------------------------------------------------------------- * Macros used by the Tcl core to set a Tcl_Obj's string representation to a * copy of the "len" bytes starting at "bytePtr". The value of "len" must * not be negative. When "len" is 0, then it is acceptable to pass * "bytePtr" = NULL. When "len" > 0, "bytePtr" must not be NULL, and it * must point to a location from which "len" bytes may be read. These * constraints are not checked here. The validity of the bytes copied * as a value string representation is also not verififed. This macro * must not be called while "objPtr" is being freed or when "objPtr" * already has a string representation. The caller must use * this macro properly. Improper use can lead to dangerous results. * Because "len" is referenced multiple times, take care that it is an * expression with the same value each use. * * The ANSI C "prototypes" for these macros are: * * MODULE_SCOPE void TclInitEmptyStringRep(Tcl_Obj *objPtr); * MODULE_SCOPE void TclInitStringRep(Tcl_Obj *objPtr, char *bytePtr, size_t len); * MODULE_SCOPE void TclAttemptInitStringRep(Tcl_Obj *objPtr, char *bytePtr, size_t len); * *---------------------------------------------------------------- */ #define TclInitEmptyStringRep(objPtr) \ ((objPtr)->length = (((objPtr)->bytes = &tclEmptyString), 0)) #define TclInitStringRep(objPtr, bytePtr, len) \ if ((len) == 0) { \ TclInitEmptyStringRep(objPtr); \ } else { \ (objPtr)->bytes = (char *)Tcl_Alloc((len) + 1U); \ memcpy((objPtr)->bytes, (bytePtr) ? (bytePtr) : &tclEmptyString, (len)); \ (objPtr)->bytes[len] = '\0'; \ (objPtr)->length = (len); \ } #define TclAttemptInitStringRep(objPtr, bytePtr, len) \ ((((len) == 0) ? ( \ TclInitEmptyStringRep(objPtr) \ ) : ( \ (objPtr)->bytes = (char *)Tcl_AttemptAlloc((len) + 1U), \ (objPtr)->length = ((objPtr)->bytes) ? \ (memcpy((objPtr)->bytes, (bytePtr) ? (bytePtr) : &tclEmptyString, (len)), \ (objPtr)->bytes[len] = '\0', (len)) : (-1) \ )), (objPtr)->bytes) /* *---------------------------------------------------------------- * Macro used by the Tcl core to get the string representation's byte array * pointer from a Tcl_Obj. This is an inline version of Tcl_GetString(). The * macro's expression result is the string rep's byte pointer which might be * NULL. The bytes referenced by this pointer must not be modified by the * caller. The ANSI C "prototype" for this macro is: * * MODULE_SCOPE char * TclGetString(Tcl_Obj *objPtr); *---------------------------------------------------------------- */ #define TclGetString(objPtr) \ ((objPtr)->bytes? (objPtr)->bytes : Tcl_GetString(objPtr)) #define TclGetStringFromObj(objPtr, lenPtr) \ ((objPtr)->bytes \ ? (*(lenPtr) = (objPtr)->length, (objPtr)->bytes) \ : (Tcl_GetStringFromObj)((objPtr), (lenPtr))) /* *---------------------------------------------------------------- * Macro used by the Tcl core to clean out an object's internal * representation. Does not actually reset the rep's bytes. The ANSI C * "prototype" for this macro is: * * MODULE_SCOPE void TclFreeInternalRep(Tcl_Obj *objPtr); *---------------------------------------------------------------- */ #define TclFreeInternalRep(objPtr) \ if ((objPtr)->typePtr != NULL) { \ if ((objPtr)->typePtr->freeIntRepProc != NULL) { \ (objPtr)->typePtr->freeIntRepProc(objPtr); \ } \ (objPtr)->typePtr = NULL; \ } /* *---------------------------------------------------------------- * Macro used by the Tcl core to clean out an object's string representation. * The ANSI C "prototype" for this macro is: * * MODULE_SCOPE void TclInvalidateStringRep(Tcl_Obj *objPtr); *---------------------------------------------------------------- */ #define TclInvalidateStringRep(objPtr) \ do { \ Tcl_Obj *_isobjPtr = (Tcl_Obj *)(objPtr); \ if (_isobjPtr->bytes != NULL) { \ if (_isobjPtr->bytes != &tclEmptyString) { \ Tcl_Free((void *)_isobjPtr->bytes); \ } \ _isobjPtr->bytes = NULL; \ } \ } while (0) /* * These form part of the native filesystem support. They are needed here * because we have a few native filesystem functions (which are the same for * win/unix) in this file. */ #ifdef __cplusplus extern "C" { #endif MODULE_SCOPE const char *const tclpFileAttrStrings[]; MODULE_SCOPE const TclFileAttrProcs tclpFileAttrProcs[]; #ifdef __cplusplus } #endif /* *---------------------------------------------------------------- * Macro used by the Tcl core to test whether an object has a * string representation (or is a 'pure' internal value). * The ANSI C "prototype" for this macro is: * * MODULE_SCOPE int TclHasStringRep(Tcl_Obj *objPtr); *---------------------------------------------------------------- */ #define TclHasStringRep(objPtr) \ ((objPtr)->bytes != NULL) /* *---------------------------------------------------------------- * Macro used by the Tcl core to get the bignum out of the bignum * representation of a Tcl_Obj. * The ANSI C "prototype" for this macro is: * * MODULE_SCOPE void TclUnpackBignum(Tcl_Obj *objPtr, mp_int bignum); *---------------------------------------------------------------- */ #define TclUnpackBignum(objPtr, bignum) \ do { \ Tcl_Obj *bignumObj = (objPtr); \ int bignumPayload = \ PTR2INT(bignumObj->internalRep.twoPtrValue.ptr2); \ if (bignumPayload == -1) { \ (bignum) = *((mp_int *) bignumObj->internalRep.twoPtrValue.ptr1); \ } else { \ (bignum).dp = (mp_digit *)bignumObj->internalRep.twoPtrValue.ptr1; \ (bignum).sign = bignumPayload >> 30; \ (bignum).alloc = (bignumPayload >> 15) & 0x7FFF; \ (bignum).used = bignumPayload & 0x7FFF; \ } \ } while (0) /* *---------------------------------------------------------------- * Macros used by the Tcl core to grow Tcl_Token arrays. They use the same * growth algorithm as used in tclStringObj.c for growing strings. The ANSI C * "prototype" for this macro is: * * MODULE_SCOPE void TclGrowTokenArray(Tcl_Token *tokenPtr, int used, * int available, int append, * Tcl_Token *staticPtr); * MODULE_SCOPE void TclGrowParseTokenArray(Tcl_Parse *parsePtr, * int append); *---------------------------------------------------------------- */ /* General tuning for minimum growth in Tcl growth algorithms */ #ifndef TCL_MIN_GROWTH # ifdef TCL_GROWTH_MIN_ALLOC /* Support for any legacy tuners */ # define TCL_MIN_GROWTH TCL_GROWTH_MIN_ALLOC # else # define TCL_MIN_GROWTH 1024 # endif #endif /* Token growth tuning, default to the general value. */ #ifndef TCL_MIN_TOKEN_GROWTH #define TCL_MIN_TOKEN_GROWTH TCL_MIN_GROWTH/sizeof(Tcl_Token) #endif /* TODO - code below does not check for integer overflow */ #define TclGrowTokenArray(tokenPtr, used, available, append, staticPtr) \ do { \ Tcl_Size _needed = (used) + (append); \ if (_needed > (available)) { \ Tcl_Size allocated = 2 * _needed; \ Tcl_Token *oldPtr = (tokenPtr); \ Tcl_Token *newPtr; \ if (oldPtr == (staticPtr)) { \ oldPtr = NULL; \ } \ newPtr = (Tcl_Token *)Tcl_AttemptRealloc((char *) oldPtr, \ allocated * sizeof(Tcl_Token)); \ if (newPtr == NULL) { \ allocated = _needed + (append) + TCL_MIN_TOKEN_GROWTH; \ newPtr = (Tcl_Token *)Tcl_Realloc((char *) oldPtr, \ allocated * sizeof(Tcl_Token)); \ } \ (available) = allocated; \ if (oldPtr == NULL) { \ memcpy(newPtr, staticPtr, \ (used) * sizeof(Tcl_Token)); \ } \ (tokenPtr) = newPtr; \ } \ } while (0) #define TclGrowParseTokenArray(parsePtr, append) \ TclGrowTokenArray((parsePtr)->tokenPtr, (parsePtr)->numTokens, \ (parsePtr)->tokensAvailable, (append), \ (parsePtr)->staticTokens) /* *---------------------------------------------------------------- * Macro used by the Tcl core get a unicode char from a utf string. It checks * to see if we have a one-byte utf char before calling the real * Tcl_UtfToUniChar, as this will save a lot of time for primarily ASCII * string handling. The macro's expression result is 1 for the 1-byte case or * the result of Tcl_UtfToUniChar. The ANSI C "prototype" for this macro is: * * MODULE_SCOPE int TclUtfToUniChar(const char *string, Tcl_UniChar *ch); *---------------------------------------------------------------- */ #define TclUtfToUniChar(str, chPtr) \ (((UCHAR(*(str))) < 0x80) ? \ ((*(chPtr) = UCHAR(*(str))), 1) \ : Tcl_UtfToUniChar(str, chPtr)) /* *---------------------------------------------------------------- * Macro counterpart of the Tcl_NumUtfChars() function. To be used in speed- * -sensitive points where it pays to avoid a function call in the common case * of counting along a string of all one-byte characters. The ANSI C * "prototype" for this macro is: * * MODULE_SCOPE void TclNumUtfCharsM(Tcl_Size numChars, const char *bytes, * Tcl_Size numBytes); * numBytes must be >= 0 *---------------------------------------------------------------- */ #define TclNumUtfCharsM(numChars, bytes, numBytes) \ do { \ Tcl_Size _count, _i = (numBytes); \ unsigned char *_str = (unsigned char *) (bytes); \ while (_i > 0 && (*_str < 0xC0)) { _i--; _str++; } \ _count = (numBytes) - _i; \ if (_i) { \ _count += Tcl_NumUtfChars((bytes) + _count, _i); \ } \ (numChars) = _count; \ } while (0); /* *---------------------------------------------------------------- * Macro that encapsulates the logic that determines when it is safe to * interpret a string as a byte array directly. In summary, the object must be * a byte array and must not have a string representation (as the operations * that it is used in are defined on strings, not byte arrays). Theoretically * it is possible to also be efficient in the case where the object's bytes * field is filled by generation from the byte array (c.f. list canonicality) * but we don't do that at the moment since this is purely about efficiency. * The ANSI C "prototype" for this macro is: * * MODULE_SCOPE int TclIsPureByteArray(Tcl_Obj *objPtr); *---------------------------------------------------------------- */ MODULE_SCOPE int TclIsPureByteArray(Tcl_Obj *objPtr); #define TclIsPureDict(objPtr) \ (((objPtr)->bytes == NULL) && TclHasInternalRep((objPtr), &tclDictType)) #define TclHasInternalRep(objPtr, type) \ ((objPtr)->typePtr == (type)) #define TclFetchInternalRep(objPtr, type) \ (TclHasInternalRep((objPtr), (type)) ? &(objPtr)->internalRep : NULL) /* *---------------------------------------------------------------- * Macro used by the Tcl core to increment a namespace's export epoch * counter. The ANSI C "prototype" for this macro is: * * MODULE_SCOPE void TclInvalidateNsCmdLookup(Namespace *nsPtr); *---------------------------------------------------------------- */ #define TclInvalidateNsCmdLookup(nsPtr) \ if ((nsPtr)->numExportPatterns) { \ (nsPtr)->exportLookupEpoch++; \ } \ if ((nsPtr)->commandPathLength) { \ (nsPtr)->cmdRefEpoch++; \ } /* *---------------------------------------------------------------------- * * Core procedure added to libtommath for bignum manipulation. * *---------------------------------------------------------------------- */ MODULE_SCOPE Tcl_LibraryInitProc TclTommath_Init; /* *---------------------------------------------------------------------- * * External (platform specific) initialization routine, these declarations * explicitly don't use EXTERN since this code does not get compiled into the * library: * *---------------------------------------------------------------------- */ MODULE_SCOPE Tcl_LibraryInitProc TclplatformtestInit; MODULE_SCOPE Tcl_LibraryInitProc TclObjTest_Init; MODULE_SCOPE Tcl_LibraryInitProc TclThread_Init; MODULE_SCOPE Tcl_LibraryInitProc Procbodytest_Init; MODULE_SCOPE Tcl_LibraryInitProc Procbodytest_SafeInit; MODULE_SCOPE Tcl_LibraryInitProc Tcl_ABSListTest_Init; /* *---------------------------------------------------------------- * Macro used by the Tcl core to check whether a pattern has any characters * special to [string match]. The ANSI C "prototype" for this macro is: * * MODULE_SCOPE int TclMatchIsTrivial(const char *pattern); *---------------------------------------------------------------- */ #define TclMatchIsTrivial(pattern) \ (strpbrk((pattern), "*[?\\") == NULL) /* *---------------------------------------------------------------- * Macros used by the Tcl core to set a Tcl_Obj's numeric representation * avoiding the corresponding function calls in time critical parts of the * core. They should only be called on unshared objects. The ANSI C * "prototypes" for these macros are: * * MODULE_SCOPE void TclSetIntObj(Tcl_Obj *objPtr, Tcl_WideInt w); * MODULE_SCOPE void TclSetDoubleObj(Tcl_Obj *objPtr, double d); *---------------------------------------------------------------- */ #define TclSetIntObj(objPtr, i) \ do { \ Tcl_ObjInternalRep ir; \ ir.wideValue = (Tcl_WideInt) i; \ TclInvalidateStringRep(objPtr); \ Tcl_StoreInternalRep(objPtr, &tclIntType, &ir); \ } while (0) #define TclSetDoubleObj(objPtr, d) \ do { \ Tcl_ObjInternalRep ir; \ ir.doubleValue = (double) d; \ TclInvalidateStringRep(objPtr); \ Tcl_StoreInternalRep(objPtr, &tclDoubleType, &ir); \ } while (0) /* *---------------------------------------------------------------- * Macros used by the Tcl core to create and initialise objects of standard * types, avoiding the corresponding function calls in time critical parts of * the core. The ANSI C "prototypes" for these macros are: * * MODULE_SCOPE void TclNewIntObj(Tcl_Obj *objPtr, Tcl_WideInt w); * MODULE_SCOPE void TclNewDoubleObj(Tcl_Obj *objPtr, double d); * MODULE_SCOPE void TclNewStringObj(Tcl_Obj *objPtr, const char *s, Tcl_Size len); * MODULE_SCOPE void TclNewLiteralStringObj(Tcl_Obj*objPtr, const char *sLiteral); * *---------------------------------------------------------------- */ #ifndef TCL_MEM_DEBUG #define TclNewIntObj(objPtr, w) \ do { \ TclIncrObjsAllocated(); \ TclAllocObjStorage(objPtr); \ (objPtr)->refCount = 0; \ (objPtr)->bytes = NULL; \ (objPtr)->internalRep.wideValue = (Tcl_WideInt)(w); \ (objPtr)->typePtr = &tclIntType; \ TCL_DTRACE_OBJ_CREATE(objPtr); \ } while (0) #define TclNewUIntObj(objPtr, uw) \ do { \ TclIncrObjsAllocated(); \ TclAllocObjStorage(objPtr); \ (objPtr)->refCount = 0; \ (objPtr)->bytes = NULL; \ Tcl_WideUInt uw_ = (uw); \ if (uw_ > WIDE_MAX) { \ mp_int bignumValue_; \ if (mp_init_u64(&bignumValue_, uw_) != MP_OKAY) { \ Tcl_Panic("%s: memory overflow", "TclNewUIntObj"); \ } \ TclSetBignumInternalRep((objPtr), &bignumValue_); \ } else { \ (objPtr)->internalRep.wideValue = (Tcl_WideInt)(uw_); \ (objPtr)->typePtr = &tclIntType; \ } \ TCL_DTRACE_OBJ_CREATE(objPtr); \ } while (0) #define TclNewIndexObj(objPtr, w) \ TclNewIntObj(objPtr, w) #define TclNewDoubleObj(objPtr, d) \ do { \ TclIncrObjsAllocated(); \ TclAllocObjStorage(objPtr); \ (objPtr)->refCount = 0; \ (objPtr)->bytes = NULL; \ (objPtr)->internalRep.doubleValue = (double)(d); \ (objPtr)->typePtr = &tclDoubleType; \ TCL_DTRACE_OBJ_CREATE(objPtr); \ } while (0) #define TclNewStringObj(objPtr, s, len) \ do { \ TclIncrObjsAllocated(); \ TclAllocObjStorage(objPtr); \ (objPtr)->refCount = 0; \ TclInitStringRep((objPtr), (s), (len)); \ (objPtr)->typePtr = NULL; \ TCL_DTRACE_OBJ_CREATE(objPtr); \ } while (0) #else /* TCL_MEM_DEBUG */ #define TclNewIntObj(objPtr, w) \ (objPtr) = Tcl_NewWideIntObj(w) #define TclNewUIntObj(objPtr, uw) \ do { \ Tcl_WideUInt uw_ = (uw); \ if (uw_ > WIDE_MAX) { \ mp_int bignumValue_; \ if (mp_init_u64(&bignumValue_, uw_) == MP_OKAY) { \ (objPtr) = Tcl_NewBignumObj(&bignumValue_); \ } else { \ (objPtr) = NULL; \ } \ } else { \ (objPtr) = Tcl_NewWideIntObj(uw_); \ } \ } while (0) #define TclNewIndexObj(objPtr, w) \ TclNewIntObj(objPtr, w) #define TclNewDoubleObj(objPtr, d) \ (objPtr) = Tcl_NewDoubleObj(d) #define TclNewStringObj(objPtr, s, len) \ (objPtr) = Tcl_NewStringObj((s), (len)) #endif /* TCL_MEM_DEBUG */ /* * The sLiteral argument *must* be a string literal; the incantation with * sizeof(sLiteral "") will fail to compile otherwise. */ #define TclNewLiteralStringObj(objPtr, sLiteral) \ TclNewStringObj((objPtr), (sLiteral), sizeof(sLiteral "") - 1) /* *---------------------------------------------------------------- * Convenience macros for DStrings. * The ANSI C "prototypes" for these macros are: * * MODULE_SCOPE char * TclDStringAppendLiteral(Tcl_DString *dsPtr, * const char *sLiteral); * MODULE_SCOPE void TclDStringClear(Tcl_DString *dsPtr); */ #define TclDStringAppendLiteral(dsPtr, sLiteral) \ Tcl_DStringAppend((dsPtr), (sLiteral), sizeof(sLiteral "") - 1) #define TclDStringClear(dsPtr) \ Tcl_DStringSetLength((dsPtr), 0) /* *---------------------------------------------------------------- * Inline version of Tcl_GetCurrentNamespace and Tcl_GetGlobalNamespace. */ #define TclGetCurrentNamespace(interp) \ (Tcl_Namespace *) ((Interp *)(interp))->varFramePtr->nsPtr #define TclGetGlobalNamespace(interp) \ (Tcl_Namespace *) ((Interp *)(interp))->globalNsPtr /* *---------------------------------------------------------------- * Inline version of TclCleanupCommand; still need the function as it is in * the internal stubs, but the core can use the macro instead. */ #define TclCleanupCommandMacro(cmdPtr) \ do { \ if ((cmdPtr)->refCount-- <= 1) { \ Tcl_Free(cmdPtr); \ } \ } while (0) /* * inside this routine crement refCount first incase cmdPtr is replacing itself */ #define TclRoutineAssign(location, cmdPtr) \ do { \ (cmdPtr)->refCount++; \ if ((location) != NULL \ && (location--) <= 1) { \ Tcl_Free(((location))); \ } \ (location) = (cmdPtr); \ } while (0) #define TclRoutineHasName(cmdPtr) \ ((cmdPtr)->hPtr != NULL) /* *---------------------------------------------------------------- * Inline versions of Tcl_LimitReady() and Tcl_LimitExceeded to limit number * of calls out of the critical path. Note that this code isn't particularly * readable; the non-inline version (in tclInterp.c) is much easier to * understand. Note also that these macros takes different args (iPtr->limit) * to the non-inline version. */ #define TclLimitExceeded(limit) \ ((limit).exceeded != 0) #define TclLimitReady(limit) \ (((limit).active == 0) ? 0 : \ (++(limit).granularityTicker, \ ((((limit).active & TCL_LIMIT_COMMANDS) && \ (((limit).cmdGranularity == 1) || \ ((limit).granularityTicker % (limit).cmdGranularity == 0))) \ ? 1 : \ (((limit).active & TCL_LIMIT_TIME) && \ (((limit).timeGranularity == 1) || \ ((limit).granularityTicker % (limit).timeGranularity == 0)))\ ? 1 : 0))) /* * Compile-time assertions: these produce a compile time error if the * expression is not known to be true at compile time. If the assertion is * known to be false, the compiler (or optimizer?) will error out with * "division by zero". If the assertion cannot be evaluated at compile time, * the compiler will error out with "non-static initializer". * * Adapted with permission from * http://www.pixelbeat.org/programming/gcc/static_assert.html */ #define TCL_CT_ASSERT(e) \ {enum { ct_assert_value = 1/(!!(e)) };} /* *---------------------------------------------------------------- * Allocator for small structs (<=sizeof(Tcl_Obj)) using the Tcl_Obj pool. * Only checked at compile time. * * ONLY USE FOR CONSTANT nBytes. * * DO NOT LET THEM CROSS THREAD BOUNDARIES *---------------------------------------------------------------- */ #define TclSmallAlloc(nbytes, memPtr) \ TclSmallAllocEx(NULL, (nbytes), (memPtr)) #define TclSmallFree(memPtr) \ TclSmallFreeEx(NULL, (memPtr)) #ifndef TCL_MEM_DEBUG #define TclSmallAllocEx(interp, nbytes, memPtr) \ do { \ Tcl_Obj *_objPtr; \ TCL_CT_ASSERT((nbytes)<=sizeof(Tcl_Obj)); \ TclIncrObjsAllocated(); \ TclAllocObjStorageEx((interp), (_objPtr)); \ *(void **)&(memPtr) = (void *) (_objPtr); \ } while (0) #define TclSmallFreeEx(interp, memPtr) \ do { \ TclFreeObjStorageEx((interp), (Tcl_Obj *)(memPtr)); \ TclIncrObjsFreed(); \ } while (0) #else /* TCL_MEM_DEBUG */ #define TclSmallAllocEx(interp, nbytes, memPtr) \ do { \ Tcl_Obj *_objPtr; \ TCL_CT_ASSERT((nbytes)<=sizeof(Tcl_Obj)); \ TclNewObj(_objPtr); \ *(void **)&(memPtr) = (void *)_objPtr; \ } while (0) #define TclSmallFreeEx(interp, memPtr) \ do { \ Tcl_Obj *_objPtr = (Tcl_Obj *)(memPtr); \ _objPtr->bytes = NULL; \ _objPtr->typePtr = NULL; \ _objPtr->refCount = 1; \ TclDecrRefCount(_objPtr); \ } while (0) #endif /* TCL_MEM_DEBUG */ /* * Support for Clang Static Analyzer */ #if defined(PURIFY) && defined(__clang__) #if __has_feature(attribute_analyzer_noreturn) && \ !defined(Tcl_Panic) && defined(Tcl_Panic_TCL_DECLARED) void Tcl_Panic(const char *, ...) __attribute__((analyzer_noreturn)); #endif #if !defined(CLANG_ASSERT) #include #define CLANG_ASSERT(x) assert(x) #endif #elif !defined(CLANG_ASSERT) #define CLANG_ASSERT(x) #endif /* PURIFY && __clang__ */ /* *---------------------------------------------------------------- * Parameters, structs and macros for the non-recursive engine (NRE) *---------------------------------------------------------------- */ #define NRE_USE_SMALL_ALLOC 1 /* Only turn off for debugging purposes. */ #ifndef NRE_ENABLE_ASSERTS #define NRE_ENABLE_ASSERTS 0 #endif /* * This is the main data struct for representing NR commands. It is designed * to fit in sizeof(Tcl_Obj) in order to exploit the fastest memory allocator * available. */ typedef struct NRE_callback { Tcl_NRPostProc *procPtr; void *data[4]; struct NRE_callback *nextPtr; } NRE_callback; #define TOP_CB(iPtr) \ (((Interp *)(iPtr))->execEnvPtr->callbackPtr) /* * Inline version of Tcl_NRAddCallback. */ #define TclNRAddCallback(interp,postProcPtr,data0,data1,data2,data3) \ do { \ NRE_callback *_callbackPtr; \ TCLNR_ALLOC((interp), (_callbackPtr)); \ _callbackPtr->procPtr = (postProcPtr); \ _callbackPtr->data[0] = (void *)(data0); \ _callbackPtr->data[1] = (void *)(data1); \ _callbackPtr->data[2] = (void *)(data2); \ _callbackPtr->data[3] = (void *)(data3); \ _callbackPtr->nextPtr = TOP_CB(interp); \ TOP_CB(interp) = _callbackPtr; \ } while (0) #if NRE_USE_SMALL_ALLOC #define TCLNR_ALLOC(interp, ptr) \ TclSmallAllocEx(interp, sizeof(NRE_callback), (ptr)) #define TCLNR_FREE(interp, ptr) TclSmallFreeEx((interp), (ptr)) #else #define TCLNR_ALLOC(interp, ptr) \ ((ptr) = Tcl_Alloc(sizeof(NRE_callback))) #define TCLNR_FREE(interp, ptr) Tcl_Free(ptr) #endif #if NRE_ENABLE_ASSERTS #define NRE_ASSERT(expr) assert((expr)) #else #define NRE_ASSERT(expr) #endif #include "tclIntDecls.h" #include "tclIntPlatDecls.h" #if !defined(USE_TCL_STUBS) && !defined(TCL_MEM_DEBUG) #define Tcl_AttemptAlloc TclpAlloc #define Tcl_AttemptRealloc TclpRealloc #define Tcl_Free TclpFree #endif /* * Special hack for macOS, where the static linker (technically the 'ar' * command) hates empty object files, and accepts no flags to make it shut up. * * These symbols are otherwise completely useless. * * They can't be written to or written through. They can't be seen by any * other code. They use a separate attribute (supported by all macOS * compilers, which are derivatives of clang or gcc) to stop the compilation * from moaning. They will be excluded during the final linking stage. * * Other platforms get nothing at all. That's good. */ #ifdef MAC_OSX_TCL #define TCL_MAC_EMPTY_FILE(name) \ static __attribute__((used)) const void *const TclUnusedFile_ ## name = NULL; #else #define TCL_MAC_EMPTY_FILE(name) #endif /* MAC_OSX_TCL */ /* * Other externals. */ MODULE_SCOPE size_t TclEnvEpoch; /* Epoch of the tcl environment * (if changed with tcl-env). */ #endif /* _TCLINT */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclIntDecls.h0000644000175000017500000014061614731057471015562 0ustar sergeisergei/* * tclIntDecls.h -- * * This file contains the declarations for all unsupported * functions that are exported by the Tcl library. These * interfaces are not guaranteed to remain the same between * versions. Use at your own risk. * * Copyright (c) 1998-1999 by Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef _TCLINTDECLS #define _TCLINTDECLS #undef TCL_STORAGE_CLASS #ifdef BUILD_tcl # define TCL_STORAGE_CLASS DLLEXPORT #else # ifdef USE_TCL_STUBS # define TCL_STORAGE_CLASS # else # define TCL_STORAGE_CLASS DLLIMPORT # endif #endif /* * WARNING: This file is automatically generated by the tools/genStubs.tcl * script. Any modifications to the function declarations below should be made * in the generic/tclInt.decls script. */ /* !BEGIN!: Do not edit below this line. */ #ifdef __cplusplus extern "C" { #endif /* * Exported function declarations: */ /* Slot 0 is reserved */ /* Slot 1 is reserved */ /* Slot 2 is reserved */ /* 3 */ EXTERN void TclAllocateFreeObjects(void); /* Slot 4 is reserved */ /* 5 */ EXTERN int TclCleanupChildren(Tcl_Interp *interp, Tcl_Size numPids, Tcl_Pid *pidPtr, Tcl_Channel errorChan); /* 6 */ EXTERN void TclCleanupCommand(Command *cmdPtr); /* 7 */ EXTERN Tcl_Size TclCopyAndCollapse(Tcl_Size count, const char *src, char *dst); /* Slot 8 is reserved */ /* 9 */ EXTERN Tcl_Size TclCreatePipeline(Tcl_Interp *interp, Tcl_Size argc, const char **argv, Tcl_Pid **pidArrayPtr, TclFile *inPipePtr, TclFile *outPipePtr, TclFile *errFilePtr); /* 10 */ EXTERN int TclCreateProc(Tcl_Interp *interp, Namespace *nsPtr, const char *procName, Tcl_Obj *argsPtr, Tcl_Obj *bodyPtr, Proc **procPtrPtr); /* 11 */ EXTERN void TclDeleteCompiledLocalVars(Interp *iPtr, CallFrame *framePtr); /* 12 */ EXTERN void TclDeleteVars(Interp *iPtr, TclVarHashTable *tablePtr); /* Slot 13 is reserved */ /* 14 */ EXTERN int TclDumpMemoryInfo(void *clientData, int flags); /* Slot 15 is reserved */ /* 16 */ EXTERN void TclExprFloatError(Tcl_Interp *interp, double value); /* Slot 17 is reserved */ /* Slot 18 is reserved */ /* Slot 19 is reserved */ /* Slot 20 is reserved */ /* Slot 21 is reserved */ /* 22 */ EXTERN int TclFindElement(Tcl_Interp *interp, const char *listStr, Tcl_Size listLength, const char **elementPtr, const char **nextPtr, Tcl_Size *sizePtr, int *bracePtr); /* 23 */ EXTERN Proc * TclFindProc(Interp *iPtr, const char *procName); /* 24 */ EXTERN Tcl_Size TclFormatInt(char *buffer, Tcl_WideInt n); /* 25 */ EXTERN void TclFreePackageInfo(Interp *iPtr); /* Slot 26 is reserved */ /* Slot 27 is reserved */ /* 28 */ EXTERN Tcl_Channel TclpGetDefaultStdChannel(int type); /* Slot 29 is reserved */ /* Slot 30 is reserved */ /* 31 */ EXTERN const char * TclGetExtension(const char *name); /* 32 */ EXTERN int TclGetFrame(Tcl_Interp *interp, const char *str, CallFrame **framePtrPtr); /* Slot 33 is reserved */ /* Slot 34 is reserved */ /* Slot 35 is reserved */ /* Slot 36 is reserved */ /* Slot 37 is reserved */ /* 38 */ EXTERN int TclGetNamespaceForQualName(Tcl_Interp *interp, const char *qualName, Namespace *cxtNsPtr, int flags, Namespace **nsPtrPtr, Namespace **altNsPtrPtr, Namespace **actualCxtPtrPtr, const char **simpleNamePtr); /* 39 */ EXTERN Tcl_ObjCmdProc * TclGetObjInterpProc(void); /* 40 */ EXTERN int TclGetOpenMode(Tcl_Interp *interp, const char *str, int *modeFlagsPtr); /* 41 */ EXTERN Tcl_Command TclGetOriginalCommand(Tcl_Command command); /* 42 */ EXTERN const char * TclpGetUserHome(const char *name, Tcl_DString *bufferPtr); /* 43 */ EXTERN Tcl_ObjCmdProc2 * TclGetObjInterpProc2(void); /* Slot 44 is reserved */ /* 45 */ EXTERN int TclHideUnsafeCommands(Tcl_Interp *interp); /* 46 */ EXTERN int TclInExit(void); /* Slot 47 is reserved */ /* Slot 48 is reserved */ /* Slot 49 is reserved */ /* Slot 50 is reserved */ /* 51 */ EXTERN int TclInterpInit(Tcl_Interp *interp); /* Slot 52 is reserved */ /* Slot 53 is reserved */ /* Slot 54 is reserved */ /* 55 */ EXTERN Proc * TclIsProc(Command *cmdPtr); /* Slot 56 is reserved */ /* Slot 57 is reserved */ /* 58 */ EXTERN Var * TclLookupVar(Tcl_Interp *interp, const char *part1, const char *part2, int flags, const char *msg, int createPart1, int createPart2, Var **arrayPtrPtr); /* Slot 59 is reserved */ /* 60 */ EXTERN int TclNeedSpace(const char *start, const char *end); /* 61 */ EXTERN Tcl_Obj * TclNewProcBodyObj(Proc *procPtr); /* 62 */ EXTERN int TclObjCommandComplete(Tcl_Obj *cmdPtr); /* Slot 63 is reserved */ /* 64 */ EXTERN int TclObjInvoke(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags); /* Slot 65 is reserved */ /* Slot 66 is reserved */ /* Slot 67 is reserved */ /* Slot 68 is reserved */ /* 69 */ EXTERN void * TclpAlloc(TCL_HASH_TYPE size); /* Slot 70 is reserved */ /* Slot 71 is reserved */ /* Slot 72 is reserved */ /* Slot 73 is reserved */ /* 74 */ EXTERN void TclpFree(void *ptr); /* 75 */ EXTERN unsigned long long TclpGetClicks(void); /* 76 */ EXTERN unsigned long long TclpGetSeconds(void); /* Slot 77 is reserved */ /* Slot 78 is reserved */ /* Slot 79 is reserved */ /* Slot 80 is reserved */ /* 81 */ EXTERN void * TclpRealloc(void *ptr, TCL_HASH_TYPE size); /* Slot 82 is reserved */ /* Slot 83 is reserved */ /* Slot 84 is reserved */ /* Slot 85 is reserved */ /* Slot 86 is reserved */ /* Slot 87 is reserved */ /* Slot 88 is reserved */ /* 89 */ EXTERN int TclPreventAliasLoop(Tcl_Interp *interp, Tcl_Interp *cmdInterp, Tcl_Command cmd); /* Slot 90 is reserved */ /* 91 */ EXTERN void TclProcCleanupProc(Proc *procPtr); /* 92 */ EXTERN int TclProcCompileProc(Tcl_Interp *interp, Proc *procPtr, Tcl_Obj *bodyPtr, Namespace *nsPtr, const char *description, const char *procName); /* 93 */ EXTERN void TclProcDeleteProc(void *clientData); /* Slot 94 is reserved */ /* Slot 95 is reserved */ /* 96 */ EXTERN int TclRenameCommand(Tcl_Interp *interp, const char *oldName, const char *newName); /* 97 */ EXTERN void TclResetShadowedCmdRefs(Tcl_Interp *interp, Command *newCmdPtr); /* 98 */ EXTERN int TclServiceIdle(void); /* Slot 99 is reserved */ /* Slot 100 is reserved */ /* Slot 101 is reserved */ /* 102 */ EXTERN void TclSetupEnv(Tcl_Interp *interp); /* 103 */ EXTERN int TclSockGetPort(Tcl_Interp *interp, const char *str, const char *proto, int *portPtr); /* Slot 104 is reserved */ /* Slot 105 is reserved */ /* Slot 106 is reserved */ /* Slot 107 is reserved */ /* 108 */ EXTERN void TclTeardownNamespace(Namespace *nsPtr); /* 109 */ EXTERN int TclUpdateReturnInfo(Interp *iPtr); /* 110 */ EXTERN int TclSockMinimumBuffers(void *sock, Tcl_Size size); /* 111 */ EXTERN void Tcl_AddInterpResolvers(Tcl_Interp *interp, const char *name, Tcl_ResolveCmdProc *cmdProc, Tcl_ResolveVarProc *varProc, Tcl_ResolveCompiledVarProc *compiledVarProc); /* Slot 112 is reserved */ /* Slot 113 is reserved */ /* Slot 114 is reserved */ /* Slot 115 is reserved */ /* Slot 116 is reserved */ /* Slot 117 is reserved */ /* 118 */ EXTERN int Tcl_GetInterpResolvers(Tcl_Interp *interp, const char *name, Tcl_ResolverInfo *resInfo); /* 119 */ EXTERN int Tcl_GetNamespaceResolvers( Tcl_Namespace *namespacePtr, Tcl_ResolverInfo *resInfo); /* 120 */ EXTERN Tcl_Var Tcl_FindNamespaceVar(Tcl_Interp *interp, const char *name, Tcl_Namespace *contextNsPtr, int flags); /* Slot 121 is reserved */ /* Slot 122 is reserved */ /* Slot 123 is reserved */ /* Slot 124 is reserved */ /* Slot 125 is reserved */ /* 126 */ EXTERN void Tcl_GetVariableFullName(Tcl_Interp *interp, Tcl_Var variable, Tcl_Obj *objPtr); /* Slot 127 is reserved */ /* 128 */ EXTERN void Tcl_PopCallFrame(Tcl_Interp *interp); /* 129 */ EXTERN int Tcl_PushCallFrame(Tcl_Interp *interp, Tcl_CallFrame *framePtr, Tcl_Namespace *nsPtr, int isProcCallFrame); /* 130 */ EXTERN int Tcl_RemoveInterpResolvers(Tcl_Interp *interp, const char *name); /* 131 */ EXTERN void Tcl_SetNamespaceResolvers( Tcl_Namespace *namespacePtr, Tcl_ResolveCmdProc *cmdProc, Tcl_ResolveVarProc *varProc, Tcl_ResolveCompiledVarProc *compiledVarProc); /* Slot 132 is reserved */ /* Slot 133 is reserved */ /* Slot 134 is reserved */ /* Slot 135 is reserved */ /* Slot 136 is reserved */ /* Slot 137 is reserved */ /* 138 */ EXTERN const char * TclGetEnv(const char *name, Tcl_DString *valuePtr); /* Slot 139 is reserved */ /* Slot 140 is reserved */ /* 141 */ EXTERN const char * TclpGetCwd(Tcl_Interp *interp, Tcl_DString *cwdPtr); /* 142 */ EXTERN int TclSetByteCodeFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr, CompileHookProc *hookProc, void *clientData); /* 143 */ EXTERN int TclAddLiteralObj(struct CompileEnv *envPtr, Tcl_Obj *objPtr, LiteralEntry **litPtrPtr); /* 144 */ EXTERN void TclHideLiteral(Tcl_Interp *interp, struct CompileEnv *envPtr, int index); /* 145 */ EXTERN const struct AuxDataType * TclGetAuxDataType(const char *typeName); /* 146 */ EXTERN TclHandle TclHandleCreate(void *ptr); /* 147 */ EXTERN void TclHandleFree(TclHandle handle); /* 148 */ EXTERN TclHandle TclHandlePreserve(TclHandle handle); /* 149 */ EXTERN void TclHandleRelease(TclHandle handle); /* 150 */ EXTERN int TclRegAbout(Tcl_Interp *interp, Tcl_RegExp re); /* 151 */ EXTERN void TclRegExpRangeUniChar(Tcl_RegExp re, Tcl_Size index, Tcl_Size *startPtr, Tcl_Size *endPtr); /* Slot 152 is reserved */ /* Slot 153 is reserved */ /* Slot 154 is reserved */ /* Slot 155 is reserved */ /* 156 */ EXTERN void TclRegError(Tcl_Interp *interp, const char *msg, int status); /* 157 */ EXTERN Var * TclVarTraceExists(Tcl_Interp *interp, const char *varName); /* Slot 158 is reserved */ /* Slot 159 is reserved */ /* Slot 160 is reserved */ /* 161 */ EXTERN int TclChannelTransform(Tcl_Interp *interp, Tcl_Channel chan, Tcl_Obj *cmdObjPtr); /* 162 */ EXTERN void TclChannelEventScriptInvoker(void *clientData, int flags); /* 163 */ EXTERN const void * TclGetInstructionTable(void); /* 164 */ EXTERN void TclExpandCodeArray(void *envPtr); /* 165 */ EXTERN void TclpSetInitialEncodings(void); /* 166 */ EXTERN int TclListObjSetElement(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size index, Tcl_Obj *valuePtr); /* Slot 167 is reserved */ /* Slot 168 is reserved */ /* 169 */ EXTERN int TclpUtfNcmp2(const void *s1, const void *s2, size_t n); /* 170 */ EXTERN int TclCheckInterpTraces(Tcl_Interp *interp, const char *command, Tcl_Size numChars, Command *cmdPtr, int result, int traceFlags, Tcl_Size objc, Tcl_Obj *const objv[]); /* 171 */ EXTERN int TclCheckExecutionTraces(Tcl_Interp *interp, const char *command, Tcl_Size numChars, Command *cmdPtr, int result, int traceFlags, Tcl_Size objc, Tcl_Obj *const objv[]); /* 172 */ EXTERN int TclInThreadExit(void); /* 173 */ EXTERN int TclUniCharMatch(const Tcl_UniChar *string, Tcl_Size strLen, const Tcl_UniChar *pattern, Tcl_Size ptnLen, int flags); /* Slot 174 is reserved */ /* 175 */ EXTERN int TclCallVarTraces(Interp *iPtr, Var *arrayPtr, Var *varPtr, const char *part1, const char *part2, int flags, int leaveErrMsg); /* 176 */ EXTERN void TclCleanupVar(Var *varPtr, Var *arrayPtr); /* 177 */ EXTERN void TclVarErrMsg(Tcl_Interp *interp, const char *part1, const char *part2, const char *operation, const char *reason); /* Slot 178 is reserved */ /* Slot 179 is reserved */ /* Slot 180 is reserved */ /* Slot 181 is reserved */ /* Slot 182 is reserved */ /* Slot 183 is reserved */ /* Slot 184 is reserved */ /* Slot 185 is reserved */ /* Slot 186 is reserved */ /* Slot 187 is reserved */ /* Slot 188 is reserved */ /* Slot 189 is reserved */ /* Slot 190 is reserved */ /* Slot 191 is reserved */ /* Slot 192 is reserved */ /* Slot 193 is reserved */ /* Slot 194 is reserved */ /* Slot 195 is reserved */ /* Slot 196 is reserved */ /* Slot 197 is reserved */ /* 198 */ EXTERN int TclObjGetFrame(Tcl_Interp *interp, Tcl_Obj *objPtr, CallFrame **framePtrPtr); /* Slot 199 is reserved */ /* 200 */ EXTERN int TclpObjRemoveDirectory(Tcl_Obj *pathPtr, int recursive, Tcl_Obj **errorPtr); /* 201 */ EXTERN int TclpObjCopyDirectory(Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr); /* 202 */ EXTERN int TclpObjCreateDirectory(Tcl_Obj *pathPtr); /* 203 */ EXTERN int TclpObjDeleteFile(Tcl_Obj *pathPtr); /* 204 */ EXTERN int TclpObjCopyFile(Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); /* 205 */ EXTERN int TclpObjRenameFile(Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); /* 206 */ EXTERN int TclpObjStat(Tcl_Obj *pathPtr, Tcl_StatBuf *buf); /* 207 */ EXTERN int TclpObjAccess(Tcl_Obj *pathPtr, int mode); /* 208 */ EXTERN Tcl_Channel TclpOpenFileChannel(Tcl_Interp *interp, Tcl_Obj *pathPtr, int mode, int permissions); /* Slot 209 is reserved */ /* Slot 210 is reserved */ /* Slot 211 is reserved */ /* 212 */ EXTERN void TclpFindExecutable(const char *argv0); /* 213 */ EXTERN Tcl_Obj * TclGetObjNameOfExecutable(void); /* 214 */ EXTERN void TclSetObjNameOfExecutable(Tcl_Obj *name, Tcl_Encoding encoding); /* 215 */ EXTERN void * TclStackAlloc(Tcl_Interp *interp, TCL_HASH_TYPE numBytes); /* 216 */ EXTERN void TclStackFree(Tcl_Interp *interp, void *freePtr); /* 217 */ EXTERN int TclPushStackFrame(Tcl_Interp *interp, Tcl_CallFrame **framePtrPtr, Tcl_Namespace *namespacePtr, int isProcCallFrame); /* 218 */ EXTERN void TclPopStackFrame(Tcl_Interp *interp); /* 219 */ EXTERN Tcl_Obj * TclpCreateTemporaryDirectory(Tcl_Obj *dirObj, Tcl_Obj *basenameObj); /* Slot 220 is reserved */ /* 221 */ EXTERN Tcl_Obj * TclListTestObj(size_t length, size_t leadingSpace, size_t endSpace); /* 222 */ EXTERN void TclListObjValidate(Tcl_Interp *interp, Tcl_Obj *listObj); /* 223 */ EXTERN void * TclGetCStackPtr(void); /* 224 */ EXTERN TclPlatformType * TclGetPlatform(void); /* 225 */ EXTERN Tcl_Obj * TclTraceDictPath(Tcl_Interp *interp, Tcl_Obj *rootPtr, Tcl_Size keyc, Tcl_Obj *const keyv[], int flags); /* 226 */ EXTERN int TclObjBeingDeleted(Tcl_Obj *objPtr); /* 227 */ EXTERN void TclSetNsPath(Namespace *nsPtr, Tcl_Size pathLength, Tcl_Namespace *pathAry[]); /* Slot 228 is reserved */ /* 229 */ EXTERN int TclPtrMakeUpvar(Tcl_Interp *interp, Var *otherP1Ptr, const char *myName, int myFlags, int index); /* 230 */ EXTERN Var * TclObjLookupVar(Tcl_Interp *interp, Tcl_Obj *part1Ptr, const char *part2, int flags, const char *msg, int createPart1, int createPart2, Var **arrayPtrPtr); /* 231 */ EXTERN int TclGetNamespaceFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Namespace **nsPtrPtr); /* 232 */ EXTERN int TclEvalObjEx(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags, const CmdFrame *invoker, int word); /* 233 */ EXTERN void TclGetSrcInfoForPc(CmdFrame *contextPtr); /* 234 */ EXTERN Var * TclVarHashCreateVar(TclVarHashTable *tablePtr, const char *key, int *newPtr); /* 235 */ EXTERN void TclInitVarHashTable(TclVarHashTable *tablePtr, Namespace *nsPtr); /* Slot 236 is reserved */ /* 237 */ EXTERN int TclResetCancellation(Tcl_Interp *interp, int force); /* 238 */ EXTERN int TclNRInterpProc(void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[]); /* 239 */ EXTERN int TclNRInterpProcCore(Tcl_Interp *interp, Tcl_Obj *procNameObj, Tcl_Size skip, ProcErrorProc *errorProc); /* 240 */ EXTERN int TclNRRunCallbacks(Tcl_Interp *interp, int result, struct NRE_callback *rootPtr); /* 241 */ EXTERN int TclNREvalObjEx(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags, const CmdFrame *invoker, int word); /* 242 */ EXTERN int TclNREvalObjv(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags, Command *cmdPtr); /* 243 */ EXTERN void TclDbDumpActiveObjects(FILE *outFile); /* 244 */ EXTERN Tcl_HashTable * TclGetNamespaceChildTable(Tcl_Namespace *nsPtr); /* 245 */ EXTERN Tcl_HashTable * TclGetNamespaceCommandTable(Tcl_Namespace *nsPtr); /* 246 */ EXTERN int TclInitRewriteEnsemble(Tcl_Interp *interp, Tcl_Size numRemoved, Tcl_Size numInserted, Tcl_Obj *const *objv); /* 247 */ EXTERN void TclResetRewriteEnsemble(Tcl_Interp *interp, int isRootEnsemble); /* 248 */ EXTERN int TclCopyChannel(Tcl_Interp *interp, Tcl_Channel inChan, Tcl_Channel outChan, long long toRead, Tcl_Obj *cmdPtr); /* 249 */ EXTERN char * TclDoubleDigits(double dv, int ndigits, int flags, int *decpt, int *signum, char **endPtr); /* 250 */ EXTERN void TclSetChildCancelFlags(Tcl_Interp *interp, int flags, int force); /* 251 */ EXTERN int TclRegisterLiteral(void *envPtr, const char *bytes, Tcl_Size length, int flags); /* 252 */ EXTERN Tcl_Obj * TclPtrGetVar(Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags); /* 253 */ EXTERN Tcl_Obj * TclPtrSetVar(Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *newValuePtr, int flags); /* 254 */ EXTERN Tcl_Obj * TclPtrIncrObjVar(Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *incrPtr, int flags); /* 255 */ EXTERN int TclPtrObjMakeUpvar(Tcl_Interp *interp, Tcl_Var otherPtr, Tcl_Obj *myNamePtr, int myFlags); /* 256 */ EXTERN int TclPtrUnsetVar(Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags); /* 257 */ EXTERN void TclStaticLibrary(Tcl_Interp *interp, const char *prefix, Tcl_LibraryInitProc *initProc, Tcl_LibraryInitProc *safeInitProc); /* Slot 258 is reserved */ /* Slot 259 is reserved */ /* Slot 260 is reserved */ /* 261 */ EXTERN void TclUnusedStubEntry(void); typedef struct TclIntStubs { int magic; void *hooks; void (*reserved0)(void); void (*reserved1)(void); void (*reserved2)(void); void (*tclAllocateFreeObjects) (void); /* 3 */ void (*reserved4)(void); int (*tclCleanupChildren) (Tcl_Interp *interp, Tcl_Size numPids, Tcl_Pid *pidPtr, Tcl_Channel errorChan); /* 5 */ void (*tclCleanupCommand) (Command *cmdPtr); /* 6 */ Tcl_Size (*tclCopyAndCollapse) (Tcl_Size count, const char *src, char *dst); /* 7 */ void (*reserved8)(void); Tcl_Size (*tclCreatePipeline) (Tcl_Interp *interp, Tcl_Size argc, const char **argv, Tcl_Pid **pidArrayPtr, TclFile *inPipePtr, TclFile *outPipePtr, TclFile *errFilePtr); /* 9 */ int (*tclCreateProc) (Tcl_Interp *interp, Namespace *nsPtr, const char *procName, Tcl_Obj *argsPtr, Tcl_Obj *bodyPtr, Proc **procPtrPtr); /* 10 */ void (*tclDeleteCompiledLocalVars) (Interp *iPtr, CallFrame *framePtr); /* 11 */ void (*tclDeleteVars) (Interp *iPtr, TclVarHashTable *tablePtr); /* 12 */ void (*reserved13)(void); int (*tclDumpMemoryInfo) (void *clientData, int flags); /* 14 */ void (*reserved15)(void); void (*tclExprFloatError) (Tcl_Interp *interp, double value); /* 16 */ void (*reserved17)(void); void (*reserved18)(void); void (*reserved19)(void); void (*reserved20)(void); void (*reserved21)(void); int (*tclFindElement) (Tcl_Interp *interp, const char *listStr, Tcl_Size listLength, const char **elementPtr, const char **nextPtr, Tcl_Size *sizePtr, int *bracePtr); /* 22 */ Proc * (*tclFindProc) (Interp *iPtr, const char *procName); /* 23 */ Tcl_Size (*tclFormatInt) (char *buffer, Tcl_WideInt n); /* 24 */ void (*tclFreePackageInfo) (Interp *iPtr); /* 25 */ void (*reserved26)(void); void (*reserved27)(void); Tcl_Channel (*tclpGetDefaultStdChannel) (int type); /* 28 */ void (*reserved29)(void); void (*reserved30)(void); const char * (*tclGetExtension) (const char *name); /* 31 */ int (*tclGetFrame) (Tcl_Interp *interp, const char *str, CallFrame **framePtrPtr); /* 32 */ void (*reserved33)(void); void (*reserved34)(void); void (*reserved35)(void); void (*reserved36)(void); void (*reserved37)(void); int (*tclGetNamespaceForQualName) (Tcl_Interp *interp, const char *qualName, Namespace *cxtNsPtr, int flags, Namespace **nsPtrPtr, Namespace **altNsPtrPtr, Namespace **actualCxtPtrPtr, const char **simpleNamePtr); /* 38 */ Tcl_ObjCmdProc * (*tclGetObjInterpProc) (void); /* 39 */ int (*tclGetOpenMode) (Tcl_Interp *interp, const char *str, int *modeFlagsPtr); /* 40 */ Tcl_Command (*tclGetOriginalCommand) (Tcl_Command command); /* 41 */ const char * (*tclpGetUserHome) (const char *name, Tcl_DString *bufferPtr); /* 42 */ Tcl_ObjCmdProc2 * (*tclGetObjInterpProc2) (void); /* 43 */ void (*reserved44)(void); int (*tclHideUnsafeCommands) (Tcl_Interp *interp); /* 45 */ int (*tclInExit) (void); /* 46 */ void (*reserved47)(void); void (*reserved48)(void); void (*reserved49)(void); void (*reserved50)(void); int (*tclInterpInit) (Tcl_Interp *interp); /* 51 */ void (*reserved52)(void); void (*reserved53)(void); void (*reserved54)(void); Proc * (*tclIsProc) (Command *cmdPtr); /* 55 */ void (*reserved56)(void); void (*reserved57)(void); Var * (*tclLookupVar) (Tcl_Interp *interp, const char *part1, const char *part2, int flags, const char *msg, int createPart1, int createPart2, Var **arrayPtrPtr); /* 58 */ void (*reserved59)(void); int (*tclNeedSpace) (const char *start, const char *end); /* 60 */ Tcl_Obj * (*tclNewProcBodyObj) (Proc *procPtr); /* 61 */ int (*tclObjCommandComplete) (Tcl_Obj *cmdPtr); /* 62 */ void (*reserved63)(void); int (*tclObjInvoke) (Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags); /* 64 */ void (*reserved65)(void); void (*reserved66)(void); void (*reserved67)(void); void (*reserved68)(void); void * (*tclpAlloc) (TCL_HASH_TYPE size); /* 69 */ void (*reserved70)(void); void (*reserved71)(void); void (*reserved72)(void); void (*reserved73)(void); void (*tclpFree) (void *ptr); /* 74 */ unsigned long long (*tclpGetClicks) (void); /* 75 */ unsigned long long (*tclpGetSeconds) (void); /* 76 */ void (*reserved77)(void); void (*reserved78)(void); void (*reserved79)(void); void (*reserved80)(void); void * (*tclpRealloc) (void *ptr, TCL_HASH_TYPE size); /* 81 */ void (*reserved82)(void); void (*reserved83)(void); void (*reserved84)(void); void (*reserved85)(void); void (*reserved86)(void); void (*reserved87)(void); void (*reserved88)(void); int (*tclPreventAliasLoop) (Tcl_Interp *interp, Tcl_Interp *cmdInterp, Tcl_Command cmd); /* 89 */ void (*reserved90)(void); void (*tclProcCleanupProc) (Proc *procPtr); /* 91 */ int (*tclProcCompileProc) (Tcl_Interp *interp, Proc *procPtr, Tcl_Obj *bodyPtr, Namespace *nsPtr, const char *description, const char *procName); /* 92 */ void (*tclProcDeleteProc) (void *clientData); /* 93 */ void (*reserved94)(void); void (*reserved95)(void); int (*tclRenameCommand) (Tcl_Interp *interp, const char *oldName, const char *newName); /* 96 */ void (*tclResetShadowedCmdRefs) (Tcl_Interp *interp, Command *newCmdPtr); /* 97 */ int (*tclServiceIdle) (void); /* 98 */ void (*reserved99)(void); void (*reserved100)(void); void (*reserved101)(void); void (*tclSetupEnv) (Tcl_Interp *interp); /* 102 */ int (*tclSockGetPort) (Tcl_Interp *interp, const char *str, const char *proto, int *portPtr); /* 103 */ void (*reserved104)(void); void (*reserved105)(void); void (*reserved106)(void); void (*reserved107)(void); void (*tclTeardownNamespace) (Namespace *nsPtr); /* 108 */ int (*tclUpdateReturnInfo) (Interp *iPtr); /* 109 */ int (*tclSockMinimumBuffers) (void *sock, Tcl_Size size); /* 110 */ void (*tcl_AddInterpResolvers) (Tcl_Interp *interp, const char *name, Tcl_ResolveCmdProc *cmdProc, Tcl_ResolveVarProc *varProc, Tcl_ResolveCompiledVarProc *compiledVarProc); /* 111 */ void (*reserved112)(void); void (*reserved113)(void); void (*reserved114)(void); void (*reserved115)(void); void (*reserved116)(void); void (*reserved117)(void); int (*tcl_GetInterpResolvers) (Tcl_Interp *interp, const char *name, Tcl_ResolverInfo *resInfo); /* 118 */ int (*tcl_GetNamespaceResolvers) (Tcl_Namespace *namespacePtr, Tcl_ResolverInfo *resInfo); /* 119 */ Tcl_Var (*tcl_FindNamespaceVar) (Tcl_Interp *interp, const char *name, Tcl_Namespace *contextNsPtr, int flags); /* 120 */ void (*reserved121)(void); void (*reserved122)(void); void (*reserved123)(void); void (*reserved124)(void); void (*reserved125)(void); void (*tcl_GetVariableFullName) (Tcl_Interp *interp, Tcl_Var variable, Tcl_Obj *objPtr); /* 126 */ void (*reserved127)(void); void (*tcl_PopCallFrame) (Tcl_Interp *interp); /* 128 */ int (*tcl_PushCallFrame) (Tcl_Interp *interp, Tcl_CallFrame *framePtr, Tcl_Namespace *nsPtr, int isProcCallFrame); /* 129 */ int (*tcl_RemoveInterpResolvers) (Tcl_Interp *interp, const char *name); /* 130 */ void (*tcl_SetNamespaceResolvers) (Tcl_Namespace *namespacePtr, Tcl_ResolveCmdProc *cmdProc, Tcl_ResolveVarProc *varProc, Tcl_ResolveCompiledVarProc *compiledVarProc); /* 131 */ void (*reserved132)(void); void (*reserved133)(void); void (*reserved134)(void); void (*reserved135)(void); void (*reserved136)(void); void (*reserved137)(void); const char * (*tclGetEnv) (const char *name, Tcl_DString *valuePtr); /* 138 */ void (*reserved139)(void); void (*reserved140)(void); const char * (*tclpGetCwd) (Tcl_Interp *interp, Tcl_DString *cwdPtr); /* 141 */ int (*tclSetByteCodeFromAny) (Tcl_Interp *interp, Tcl_Obj *objPtr, CompileHookProc *hookProc, void *clientData); /* 142 */ int (*tclAddLiteralObj) (struct CompileEnv *envPtr, Tcl_Obj *objPtr, LiteralEntry **litPtrPtr); /* 143 */ void (*tclHideLiteral) (Tcl_Interp *interp, struct CompileEnv *envPtr, int index); /* 144 */ const struct AuxDataType * (*tclGetAuxDataType) (const char *typeName); /* 145 */ TclHandle (*tclHandleCreate) (void *ptr); /* 146 */ void (*tclHandleFree) (TclHandle handle); /* 147 */ TclHandle (*tclHandlePreserve) (TclHandle handle); /* 148 */ void (*tclHandleRelease) (TclHandle handle); /* 149 */ int (*tclRegAbout) (Tcl_Interp *interp, Tcl_RegExp re); /* 150 */ void (*tclRegExpRangeUniChar) (Tcl_RegExp re, Tcl_Size index, Tcl_Size *startPtr, Tcl_Size *endPtr); /* 151 */ void (*reserved152)(void); void (*reserved153)(void); void (*reserved154)(void); void (*reserved155)(void); void (*tclRegError) (Tcl_Interp *interp, const char *msg, int status); /* 156 */ Var * (*tclVarTraceExists) (Tcl_Interp *interp, const char *varName); /* 157 */ void (*reserved158)(void); void (*reserved159)(void); void (*reserved160)(void); int (*tclChannelTransform) (Tcl_Interp *interp, Tcl_Channel chan, Tcl_Obj *cmdObjPtr); /* 161 */ void (*tclChannelEventScriptInvoker) (void *clientData, int flags); /* 162 */ const void * (*tclGetInstructionTable) (void); /* 163 */ void (*tclExpandCodeArray) (void *envPtr); /* 164 */ void (*tclpSetInitialEncodings) (void); /* 165 */ int (*tclListObjSetElement) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size index, Tcl_Obj *valuePtr); /* 166 */ void (*reserved167)(void); void (*reserved168)(void); int (*tclpUtfNcmp2) (const void *s1, const void *s2, size_t n); /* 169 */ int (*tclCheckInterpTraces) (Tcl_Interp *interp, const char *command, Tcl_Size numChars, Command *cmdPtr, int result, int traceFlags, Tcl_Size objc, Tcl_Obj *const objv[]); /* 170 */ int (*tclCheckExecutionTraces) (Tcl_Interp *interp, const char *command, Tcl_Size numChars, Command *cmdPtr, int result, int traceFlags, Tcl_Size objc, Tcl_Obj *const objv[]); /* 171 */ int (*tclInThreadExit) (void); /* 172 */ int (*tclUniCharMatch) (const Tcl_UniChar *string, Tcl_Size strLen, const Tcl_UniChar *pattern, Tcl_Size ptnLen, int flags); /* 173 */ void (*reserved174)(void); int (*tclCallVarTraces) (Interp *iPtr, Var *arrayPtr, Var *varPtr, const char *part1, const char *part2, int flags, int leaveErrMsg); /* 175 */ void (*tclCleanupVar) (Var *varPtr, Var *arrayPtr); /* 176 */ void (*tclVarErrMsg) (Tcl_Interp *interp, const char *part1, const char *part2, const char *operation, const char *reason); /* 177 */ void (*reserved178)(void); void (*reserved179)(void); void (*reserved180)(void); void (*reserved181)(void); void (*reserved182)(void); void (*reserved183)(void); void (*reserved184)(void); void (*reserved185)(void); void (*reserved186)(void); void (*reserved187)(void); void (*reserved188)(void); void (*reserved189)(void); void (*reserved190)(void); void (*reserved191)(void); void (*reserved192)(void); void (*reserved193)(void); void (*reserved194)(void); void (*reserved195)(void); void (*reserved196)(void); void (*reserved197)(void); int (*tclObjGetFrame) (Tcl_Interp *interp, Tcl_Obj *objPtr, CallFrame **framePtrPtr); /* 198 */ void (*reserved199)(void); int (*tclpObjRemoveDirectory) (Tcl_Obj *pathPtr, int recursive, Tcl_Obj **errorPtr); /* 200 */ int (*tclpObjCopyDirectory) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr); /* 201 */ int (*tclpObjCreateDirectory) (Tcl_Obj *pathPtr); /* 202 */ int (*tclpObjDeleteFile) (Tcl_Obj *pathPtr); /* 203 */ int (*tclpObjCopyFile) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); /* 204 */ int (*tclpObjRenameFile) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); /* 205 */ int (*tclpObjStat) (Tcl_Obj *pathPtr, Tcl_StatBuf *buf); /* 206 */ int (*tclpObjAccess) (Tcl_Obj *pathPtr, int mode); /* 207 */ Tcl_Channel (*tclpOpenFileChannel) (Tcl_Interp *interp, Tcl_Obj *pathPtr, int mode, int permissions); /* 208 */ void (*reserved209)(void); void (*reserved210)(void); void (*reserved211)(void); void (*tclpFindExecutable) (const char *argv0); /* 212 */ Tcl_Obj * (*tclGetObjNameOfExecutable) (void); /* 213 */ void (*tclSetObjNameOfExecutable) (Tcl_Obj *name, Tcl_Encoding encoding); /* 214 */ void * (*tclStackAlloc) (Tcl_Interp *interp, TCL_HASH_TYPE numBytes); /* 215 */ void (*tclStackFree) (Tcl_Interp *interp, void *freePtr); /* 216 */ int (*tclPushStackFrame) (Tcl_Interp *interp, Tcl_CallFrame **framePtrPtr, Tcl_Namespace *namespacePtr, int isProcCallFrame); /* 217 */ void (*tclPopStackFrame) (Tcl_Interp *interp); /* 218 */ Tcl_Obj * (*tclpCreateTemporaryDirectory) (Tcl_Obj *dirObj, Tcl_Obj *basenameObj); /* 219 */ void (*reserved220)(void); Tcl_Obj * (*tclListTestObj) (size_t length, size_t leadingSpace, size_t endSpace); /* 221 */ void (*tclListObjValidate) (Tcl_Interp *interp, Tcl_Obj *listObj); /* 222 */ void * (*tclGetCStackPtr) (void); /* 223 */ TclPlatformType * (*tclGetPlatform) (void); /* 224 */ Tcl_Obj * (*tclTraceDictPath) (Tcl_Interp *interp, Tcl_Obj *rootPtr, Tcl_Size keyc, Tcl_Obj *const keyv[], int flags); /* 225 */ int (*tclObjBeingDeleted) (Tcl_Obj *objPtr); /* 226 */ void (*tclSetNsPath) (Namespace *nsPtr, Tcl_Size pathLength, Tcl_Namespace *pathAry[]); /* 227 */ void (*reserved228)(void); int (*tclPtrMakeUpvar) (Tcl_Interp *interp, Var *otherP1Ptr, const char *myName, int myFlags, int index); /* 229 */ Var * (*tclObjLookupVar) (Tcl_Interp *interp, Tcl_Obj *part1Ptr, const char *part2, int flags, const char *msg, int createPart1, int createPart2, Var **arrayPtrPtr); /* 230 */ int (*tclGetNamespaceFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Namespace **nsPtrPtr); /* 231 */ int (*tclEvalObjEx) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags, const CmdFrame *invoker, int word); /* 232 */ void (*tclGetSrcInfoForPc) (CmdFrame *contextPtr); /* 233 */ Var * (*tclVarHashCreateVar) (TclVarHashTable *tablePtr, const char *key, int *newPtr); /* 234 */ void (*tclInitVarHashTable) (TclVarHashTable *tablePtr, Namespace *nsPtr); /* 235 */ void (*reserved236)(void); int (*tclResetCancellation) (Tcl_Interp *interp, int force); /* 237 */ int (*tclNRInterpProc) (void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[]); /* 238 */ int (*tclNRInterpProcCore) (Tcl_Interp *interp, Tcl_Obj *procNameObj, Tcl_Size skip, ProcErrorProc *errorProc); /* 239 */ int (*tclNRRunCallbacks) (Tcl_Interp *interp, int result, struct NRE_callback *rootPtr); /* 240 */ int (*tclNREvalObjEx) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags, const CmdFrame *invoker, int word); /* 241 */ int (*tclNREvalObjv) (Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags, Command *cmdPtr); /* 242 */ void (*tclDbDumpActiveObjects) (FILE *outFile); /* 243 */ Tcl_HashTable * (*tclGetNamespaceChildTable) (Tcl_Namespace *nsPtr); /* 244 */ Tcl_HashTable * (*tclGetNamespaceCommandTable) (Tcl_Namespace *nsPtr); /* 245 */ int (*tclInitRewriteEnsemble) (Tcl_Interp *interp, Tcl_Size numRemoved, Tcl_Size numInserted, Tcl_Obj *const *objv); /* 246 */ void (*tclResetRewriteEnsemble) (Tcl_Interp *interp, int isRootEnsemble); /* 247 */ int (*tclCopyChannel) (Tcl_Interp *interp, Tcl_Channel inChan, Tcl_Channel outChan, long long toRead, Tcl_Obj *cmdPtr); /* 248 */ char * (*tclDoubleDigits) (double dv, int ndigits, int flags, int *decpt, int *signum, char **endPtr); /* 249 */ void (*tclSetChildCancelFlags) (Tcl_Interp *interp, int flags, int force); /* 250 */ int (*tclRegisterLiteral) (void *envPtr, const char *bytes, Tcl_Size length, int flags); /* 251 */ Tcl_Obj * (*tclPtrGetVar) (Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags); /* 252 */ Tcl_Obj * (*tclPtrSetVar) (Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *newValuePtr, int flags); /* 253 */ Tcl_Obj * (*tclPtrIncrObjVar) (Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *incrPtr, int flags); /* 254 */ int (*tclPtrObjMakeUpvar) (Tcl_Interp *interp, Tcl_Var otherPtr, Tcl_Obj *myNamePtr, int myFlags); /* 255 */ int (*tclPtrUnsetVar) (Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags); /* 256 */ void (*tclStaticLibrary) (Tcl_Interp *interp, const char *prefix, Tcl_LibraryInitProc *initProc, Tcl_LibraryInitProc *safeInitProc); /* 257 */ void (*reserved258)(void); void (*reserved259)(void); void (*reserved260)(void); void (*tclUnusedStubEntry) (void); /* 261 */ } TclIntStubs; extern const TclIntStubs *tclIntStubsPtr; #ifdef __cplusplus } #endif #if defined(USE_TCL_STUBS) /* * Inline function declarations: */ /* Slot 0 is reserved */ /* Slot 1 is reserved */ /* Slot 2 is reserved */ #define TclAllocateFreeObjects \ (tclIntStubsPtr->tclAllocateFreeObjects) /* 3 */ /* Slot 4 is reserved */ #define TclCleanupChildren \ (tclIntStubsPtr->tclCleanupChildren) /* 5 */ #define TclCleanupCommand \ (tclIntStubsPtr->tclCleanupCommand) /* 6 */ #define TclCopyAndCollapse \ (tclIntStubsPtr->tclCopyAndCollapse) /* 7 */ /* Slot 8 is reserved */ #define TclCreatePipeline \ (tclIntStubsPtr->tclCreatePipeline) /* 9 */ #define TclCreateProc \ (tclIntStubsPtr->tclCreateProc) /* 10 */ #define TclDeleteCompiledLocalVars \ (tclIntStubsPtr->tclDeleteCompiledLocalVars) /* 11 */ #define TclDeleteVars \ (tclIntStubsPtr->tclDeleteVars) /* 12 */ /* Slot 13 is reserved */ #define TclDumpMemoryInfo \ (tclIntStubsPtr->tclDumpMemoryInfo) /* 14 */ /* Slot 15 is reserved */ #define TclExprFloatError \ (tclIntStubsPtr->tclExprFloatError) /* 16 */ /* Slot 17 is reserved */ /* Slot 18 is reserved */ /* Slot 19 is reserved */ /* Slot 20 is reserved */ /* Slot 21 is reserved */ #define TclFindElement \ (tclIntStubsPtr->tclFindElement) /* 22 */ #define TclFindProc \ (tclIntStubsPtr->tclFindProc) /* 23 */ #define TclFormatInt \ (tclIntStubsPtr->tclFormatInt) /* 24 */ #define TclFreePackageInfo \ (tclIntStubsPtr->tclFreePackageInfo) /* 25 */ /* Slot 26 is reserved */ /* Slot 27 is reserved */ #define TclpGetDefaultStdChannel \ (tclIntStubsPtr->tclpGetDefaultStdChannel) /* 28 */ /* Slot 29 is reserved */ /* Slot 30 is reserved */ #define TclGetExtension \ (tclIntStubsPtr->tclGetExtension) /* 31 */ #define TclGetFrame \ (tclIntStubsPtr->tclGetFrame) /* 32 */ /* Slot 33 is reserved */ /* Slot 34 is reserved */ /* Slot 35 is reserved */ /* Slot 36 is reserved */ /* Slot 37 is reserved */ #define TclGetNamespaceForQualName \ (tclIntStubsPtr->tclGetNamespaceForQualName) /* 38 */ #define TclGetObjInterpProc \ (tclIntStubsPtr->tclGetObjInterpProc) /* 39 */ #define TclGetOpenMode \ (tclIntStubsPtr->tclGetOpenMode) /* 40 */ #define TclGetOriginalCommand \ (tclIntStubsPtr->tclGetOriginalCommand) /* 41 */ #define TclpGetUserHome \ (tclIntStubsPtr->tclpGetUserHome) /* 42 */ #define TclGetObjInterpProc2 \ (tclIntStubsPtr->tclGetObjInterpProc2) /* 43 */ /* Slot 44 is reserved */ #define TclHideUnsafeCommands \ (tclIntStubsPtr->tclHideUnsafeCommands) /* 45 */ #define TclInExit \ (tclIntStubsPtr->tclInExit) /* 46 */ /* Slot 47 is reserved */ /* Slot 48 is reserved */ /* Slot 49 is reserved */ /* Slot 50 is reserved */ #define TclInterpInit \ (tclIntStubsPtr->tclInterpInit) /* 51 */ /* Slot 52 is reserved */ /* Slot 53 is reserved */ /* Slot 54 is reserved */ #define TclIsProc \ (tclIntStubsPtr->tclIsProc) /* 55 */ /* Slot 56 is reserved */ /* Slot 57 is reserved */ #define TclLookupVar \ (tclIntStubsPtr->tclLookupVar) /* 58 */ /* Slot 59 is reserved */ #define TclNeedSpace \ (tclIntStubsPtr->tclNeedSpace) /* 60 */ #define TclNewProcBodyObj \ (tclIntStubsPtr->tclNewProcBodyObj) /* 61 */ #define TclObjCommandComplete \ (tclIntStubsPtr->tclObjCommandComplete) /* 62 */ /* Slot 63 is reserved */ #define TclObjInvoke \ (tclIntStubsPtr->tclObjInvoke) /* 64 */ /* Slot 65 is reserved */ /* Slot 66 is reserved */ /* Slot 67 is reserved */ /* Slot 68 is reserved */ #define TclpAlloc \ (tclIntStubsPtr->tclpAlloc) /* 69 */ /* Slot 70 is reserved */ /* Slot 71 is reserved */ /* Slot 72 is reserved */ /* Slot 73 is reserved */ #define TclpFree \ (tclIntStubsPtr->tclpFree) /* 74 */ #define TclpGetClicks \ (tclIntStubsPtr->tclpGetClicks) /* 75 */ #define TclpGetSeconds \ (tclIntStubsPtr->tclpGetSeconds) /* 76 */ /* Slot 77 is reserved */ /* Slot 78 is reserved */ /* Slot 79 is reserved */ /* Slot 80 is reserved */ #define TclpRealloc \ (tclIntStubsPtr->tclpRealloc) /* 81 */ /* Slot 82 is reserved */ /* Slot 83 is reserved */ /* Slot 84 is reserved */ /* Slot 85 is reserved */ /* Slot 86 is reserved */ /* Slot 87 is reserved */ /* Slot 88 is reserved */ #define TclPreventAliasLoop \ (tclIntStubsPtr->tclPreventAliasLoop) /* 89 */ /* Slot 90 is reserved */ #define TclProcCleanupProc \ (tclIntStubsPtr->tclProcCleanupProc) /* 91 */ #define TclProcCompileProc \ (tclIntStubsPtr->tclProcCompileProc) /* 92 */ #define TclProcDeleteProc \ (tclIntStubsPtr->tclProcDeleteProc) /* 93 */ /* Slot 94 is reserved */ /* Slot 95 is reserved */ #define TclRenameCommand \ (tclIntStubsPtr->tclRenameCommand) /* 96 */ #define TclResetShadowedCmdRefs \ (tclIntStubsPtr->tclResetShadowedCmdRefs) /* 97 */ #define TclServiceIdle \ (tclIntStubsPtr->tclServiceIdle) /* 98 */ /* Slot 99 is reserved */ /* Slot 100 is reserved */ /* Slot 101 is reserved */ #define TclSetupEnv \ (tclIntStubsPtr->tclSetupEnv) /* 102 */ #define TclSockGetPort \ (tclIntStubsPtr->tclSockGetPort) /* 103 */ /* Slot 104 is reserved */ /* Slot 105 is reserved */ /* Slot 106 is reserved */ /* Slot 107 is reserved */ #define TclTeardownNamespace \ (tclIntStubsPtr->tclTeardownNamespace) /* 108 */ #define TclUpdateReturnInfo \ (tclIntStubsPtr->tclUpdateReturnInfo) /* 109 */ #define TclSockMinimumBuffers \ (tclIntStubsPtr->tclSockMinimumBuffers) /* 110 */ #define Tcl_AddInterpResolvers \ (tclIntStubsPtr->tcl_AddInterpResolvers) /* 111 */ /* Slot 112 is reserved */ /* Slot 113 is reserved */ /* Slot 114 is reserved */ /* Slot 115 is reserved */ /* Slot 116 is reserved */ /* Slot 117 is reserved */ #define Tcl_GetInterpResolvers \ (tclIntStubsPtr->tcl_GetInterpResolvers) /* 118 */ #define Tcl_GetNamespaceResolvers \ (tclIntStubsPtr->tcl_GetNamespaceResolvers) /* 119 */ #define Tcl_FindNamespaceVar \ (tclIntStubsPtr->tcl_FindNamespaceVar) /* 120 */ /* Slot 121 is reserved */ /* Slot 122 is reserved */ /* Slot 123 is reserved */ /* Slot 124 is reserved */ /* Slot 125 is reserved */ #define Tcl_GetVariableFullName \ (tclIntStubsPtr->tcl_GetVariableFullName) /* 126 */ /* Slot 127 is reserved */ #define Tcl_PopCallFrame \ (tclIntStubsPtr->tcl_PopCallFrame) /* 128 */ #define Tcl_PushCallFrame \ (tclIntStubsPtr->tcl_PushCallFrame) /* 129 */ #define Tcl_RemoveInterpResolvers \ (tclIntStubsPtr->tcl_RemoveInterpResolvers) /* 130 */ #define Tcl_SetNamespaceResolvers \ (tclIntStubsPtr->tcl_SetNamespaceResolvers) /* 131 */ /* Slot 132 is reserved */ /* Slot 133 is reserved */ /* Slot 134 is reserved */ /* Slot 135 is reserved */ /* Slot 136 is reserved */ /* Slot 137 is reserved */ #define TclGetEnv \ (tclIntStubsPtr->tclGetEnv) /* 138 */ /* Slot 139 is reserved */ /* Slot 140 is reserved */ #define TclpGetCwd \ (tclIntStubsPtr->tclpGetCwd) /* 141 */ #define TclSetByteCodeFromAny \ (tclIntStubsPtr->tclSetByteCodeFromAny) /* 142 */ #define TclAddLiteralObj \ (tclIntStubsPtr->tclAddLiteralObj) /* 143 */ #define TclHideLiteral \ (tclIntStubsPtr->tclHideLiteral) /* 144 */ #define TclGetAuxDataType \ (tclIntStubsPtr->tclGetAuxDataType) /* 145 */ #define TclHandleCreate \ (tclIntStubsPtr->tclHandleCreate) /* 146 */ #define TclHandleFree \ (tclIntStubsPtr->tclHandleFree) /* 147 */ #define TclHandlePreserve \ (tclIntStubsPtr->tclHandlePreserve) /* 148 */ #define TclHandleRelease \ (tclIntStubsPtr->tclHandleRelease) /* 149 */ #define TclRegAbout \ (tclIntStubsPtr->tclRegAbout) /* 150 */ #define TclRegExpRangeUniChar \ (tclIntStubsPtr->tclRegExpRangeUniChar) /* 151 */ /* Slot 152 is reserved */ /* Slot 153 is reserved */ /* Slot 154 is reserved */ /* Slot 155 is reserved */ #define TclRegError \ (tclIntStubsPtr->tclRegError) /* 156 */ #define TclVarTraceExists \ (tclIntStubsPtr->tclVarTraceExists) /* 157 */ /* Slot 158 is reserved */ /* Slot 159 is reserved */ /* Slot 160 is reserved */ #define TclChannelTransform \ (tclIntStubsPtr->tclChannelTransform) /* 161 */ #define TclChannelEventScriptInvoker \ (tclIntStubsPtr->tclChannelEventScriptInvoker) /* 162 */ #define TclGetInstructionTable \ (tclIntStubsPtr->tclGetInstructionTable) /* 163 */ #define TclExpandCodeArray \ (tclIntStubsPtr->tclExpandCodeArray) /* 164 */ #define TclpSetInitialEncodings \ (tclIntStubsPtr->tclpSetInitialEncodings) /* 165 */ #define TclListObjSetElement \ (tclIntStubsPtr->tclListObjSetElement) /* 166 */ /* Slot 167 is reserved */ /* Slot 168 is reserved */ #define TclpUtfNcmp2 \ (tclIntStubsPtr->tclpUtfNcmp2) /* 169 */ #define TclCheckInterpTraces \ (tclIntStubsPtr->tclCheckInterpTraces) /* 170 */ #define TclCheckExecutionTraces \ (tclIntStubsPtr->tclCheckExecutionTraces) /* 171 */ #define TclInThreadExit \ (tclIntStubsPtr->tclInThreadExit) /* 172 */ #define TclUniCharMatch \ (tclIntStubsPtr->tclUniCharMatch) /* 173 */ /* Slot 174 is reserved */ #define TclCallVarTraces \ (tclIntStubsPtr->tclCallVarTraces) /* 175 */ #define TclCleanupVar \ (tclIntStubsPtr->tclCleanupVar) /* 176 */ #define TclVarErrMsg \ (tclIntStubsPtr->tclVarErrMsg) /* 177 */ /* Slot 178 is reserved */ /* Slot 179 is reserved */ /* Slot 180 is reserved */ /* Slot 181 is reserved */ /* Slot 182 is reserved */ /* Slot 183 is reserved */ /* Slot 184 is reserved */ /* Slot 185 is reserved */ /* Slot 186 is reserved */ /* Slot 187 is reserved */ /* Slot 188 is reserved */ /* Slot 189 is reserved */ /* Slot 190 is reserved */ /* Slot 191 is reserved */ /* Slot 192 is reserved */ /* Slot 193 is reserved */ /* Slot 194 is reserved */ /* Slot 195 is reserved */ /* Slot 196 is reserved */ /* Slot 197 is reserved */ #define TclObjGetFrame \ (tclIntStubsPtr->tclObjGetFrame) /* 198 */ /* Slot 199 is reserved */ #define TclpObjRemoveDirectory \ (tclIntStubsPtr->tclpObjRemoveDirectory) /* 200 */ #define TclpObjCopyDirectory \ (tclIntStubsPtr->tclpObjCopyDirectory) /* 201 */ #define TclpObjCreateDirectory \ (tclIntStubsPtr->tclpObjCreateDirectory) /* 202 */ #define TclpObjDeleteFile \ (tclIntStubsPtr->tclpObjDeleteFile) /* 203 */ #define TclpObjCopyFile \ (tclIntStubsPtr->tclpObjCopyFile) /* 204 */ #define TclpObjRenameFile \ (tclIntStubsPtr->tclpObjRenameFile) /* 205 */ #define TclpObjStat \ (tclIntStubsPtr->tclpObjStat) /* 206 */ #define TclpObjAccess \ (tclIntStubsPtr->tclpObjAccess) /* 207 */ #define TclpOpenFileChannel \ (tclIntStubsPtr->tclpOpenFileChannel) /* 208 */ /* Slot 209 is reserved */ /* Slot 210 is reserved */ /* Slot 211 is reserved */ #define TclpFindExecutable \ (tclIntStubsPtr->tclpFindExecutable) /* 212 */ #define TclGetObjNameOfExecutable \ (tclIntStubsPtr->tclGetObjNameOfExecutable) /* 213 */ #define TclSetObjNameOfExecutable \ (tclIntStubsPtr->tclSetObjNameOfExecutable) /* 214 */ #define TclStackAlloc \ (tclIntStubsPtr->tclStackAlloc) /* 215 */ #define TclStackFree \ (tclIntStubsPtr->tclStackFree) /* 216 */ #define TclPushStackFrame \ (tclIntStubsPtr->tclPushStackFrame) /* 217 */ #define TclPopStackFrame \ (tclIntStubsPtr->tclPopStackFrame) /* 218 */ #define TclpCreateTemporaryDirectory \ (tclIntStubsPtr->tclpCreateTemporaryDirectory) /* 219 */ /* Slot 220 is reserved */ #define TclListTestObj \ (tclIntStubsPtr->tclListTestObj) /* 221 */ #define TclListObjValidate \ (tclIntStubsPtr->tclListObjValidate) /* 222 */ #define TclGetCStackPtr \ (tclIntStubsPtr->tclGetCStackPtr) /* 223 */ #define TclGetPlatform \ (tclIntStubsPtr->tclGetPlatform) /* 224 */ #define TclTraceDictPath \ (tclIntStubsPtr->tclTraceDictPath) /* 225 */ #define TclObjBeingDeleted \ (tclIntStubsPtr->tclObjBeingDeleted) /* 226 */ #define TclSetNsPath \ (tclIntStubsPtr->tclSetNsPath) /* 227 */ /* Slot 228 is reserved */ #define TclPtrMakeUpvar \ (tclIntStubsPtr->tclPtrMakeUpvar) /* 229 */ #define TclObjLookupVar \ (tclIntStubsPtr->tclObjLookupVar) /* 230 */ #define TclGetNamespaceFromObj \ (tclIntStubsPtr->tclGetNamespaceFromObj) /* 231 */ #define TclEvalObjEx \ (tclIntStubsPtr->tclEvalObjEx) /* 232 */ #define TclGetSrcInfoForPc \ (tclIntStubsPtr->tclGetSrcInfoForPc) /* 233 */ #define TclVarHashCreateVar \ (tclIntStubsPtr->tclVarHashCreateVar) /* 234 */ #define TclInitVarHashTable \ (tclIntStubsPtr->tclInitVarHashTable) /* 235 */ /* Slot 236 is reserved */ #define TclResetCancellation \ (tclIntStubsPtr->tclResetCancellation) /* 237 */ #define TclNRInterpProc \ (tclIntStubsPtr->tclNRInterpProc) /* 238 */ #define TclNRInterpProcCore \ (tclIntStubsPtr->tclNRInterpProcCore) /* 239 */ #define TclNRRunCallbacks \ (tclIntStubsPtr->tclNRRunCallbacks) /* 240 */ #define TclNREvalObjEx \ (tclIntStubsPtr->tclNREvalObjEx) /* 241 */ #define TclNREvalObjv \ (tclIntStubsPtr->tclNREvalObjv) /* 242 */ #define TclDbDumpActiveObjects \ (tclIntStubsPtr->tclDbDumpActiveObjects) /* 243 */ #define TclGetNamespaceChildTable \ (tclIntStubsPtr->tclGetNamespaceChildTable) /* 244 */ #define TclGetNamespaceCommandTable \ (tclIntStubsPtr->tclGetNamespaceCommandTable) /* 245 */ #define TclInitRewriteEnsemble \ (tclIntStubsPtr->tclInitRewriteEnsemble) /* 246 */ #define TclResetRewriteEnsemble \ (tclIntStubsPtr->tclResetRewriteEnsemble) /* 247 */ #define TclCopyChannel \ (tclIntStubsPtr->tclCopyChannel) /* 248 */ #define TclDoubleDigits \ (tclIntStubsPtr->tclDoubleDigits) /* 249 */ #define TclSetChildCancelFlags \ (tclIntStubsPtr->tclSetChildCancelFlags) /* 250 */ #define TclRegisterLiteral \ (tclIntStubsPtr->tclRegisterLiteral) /* 251 */ #define TclPtrGetVar \ (tclIntStubsPtr->tclPtrGetVar) /* 252 */ #define TclPtrSetVar \ (tclIntStubsPtr->tclPtrSetVar) /* 253 */ #define TclPtrIncrObjVar \ (tclIntStubsPtr->tclPtrIncrObjVar) /* 254 */ #define TclPtrObjMakeUpvar \ (tclIntStubsPtr->tclPtrObjMakeUpvar) /* 255 */ #define TclPtrUnsetVar \ (tclIntStubsPtr->tclPtrUnsetVar) /* 256 */ #define TclStaticLibrary \ (tclIntStubsPtr->tclStaticLibrary) /* 257 */ /* Slot 258 is reserved */ /* Slot 259 is reserved */ /* Slot 260 is reserved */ #define TclUnusedStubEntry \ (tclIntStubsPtr->tclUnusedStubEntry) /* 261 */ #endif /* defined(USE_TCL_STUBS) */ /* !END!: Do not edit above this line. */ #if defined(USE_TCL_STUBS) #undef Tcl_StaticLibrary #define Tcl_StaticLibrary \ (tclIntStubsPtr->tclStaticLibrary) #endif /* defined(USE_TCL_STUBS) */ #if (TCL_MAJOR_VERSION < 9) && defined(USE_TCL_STUBS) #undef TclpGetClicks #define TclpGetClicks() \ ((unsigned long)tclIntStubsPtr->tclpGetClicks()) #undef TclpGetSeconds #define TclpGetSeconds() \ ((unsigned long)tclIntStubsPtr->tclpGetSeconds()) #undef TclGetObjInterpProc2 #define TclGetObjInterpProc2 TclGetObjInterpProc #endif #undef TclUnusedStubEntry #define TclObjInterpProc TclGetObjInterpProc() #define TclObjInterpProc2 TclGetObjInterpProc2() #undef TCL_STORAGE_CLASS #define TCL_STORAGE_CLASS DLLIMPORT #endif /* _TCLINTDECLS */ tcl9.0.1/generic/tclIntPlatDecls.h0000644000175000017500000006435514731057471016410 0ustar sergeisergei/* * tclIntPlatDecls.h -- * * This file contains the declarations for all platform dependent * unsupported functions that are exported by the Tcl library. These * interfaces are not guaranteed to remain the same between * versions. Use at your own risk. * * Copyright (c) 1998-1999 by Scriptics Corporation. * All rights reserved. */ #ifndef _TCLINTPLATDECLS #define _TCLINTPLATDECLS #undef TCL_STORAGE_CLASS #ifdef BUILD_tcl # define TCL_STORAGE_CLASS DLLEXPORT #else # ifdef USE_TCL_STUBS # define TCL_STORAGE_CLASS # else # define TCL_STORAGE_CLASS DLLIMPORT # endif #endif /* * WARNING: This file is automatically generated by the tools/genStubs.tcl * script. Any modifications to the function declarations below should be made * in the generic/tclInt.decls script. */ #if TCL_MAJOR_VERSION < 9 #ifdef __cplusplus extern "C" { #endif /* * Exported function declarations: */ #if !defined(_WIN32) && !defined(__CYGWIN__) && !defined(MAC_OSX_TCL) /* UNIX */ /* 0 */ EXTERN void TclGetAndDetachPids(Tcl_Interp *interp, Tcl_Channel chan); /* 1 */ EXTERN int TclpCloseFile(TclFile file); /* 2 */ EXTERN Tcl_Channel TclpCreateCommandChannel(TclFile readFile, TclFile writeFile, TclFile errorFile, int numPids, Tcl_Pid *pidPtr); /* 3 */ EXTERN int TclpCreatePipe(TclFile *readPipe, TclFile *writePipe); /* 4 */ EXTERN int TclpCreateProcess(Tcl_Interp *interp, int argc, const char **argv, TclFile inputFile, TclFile outputFile, TclFile errorFile, Tcl_Pid *pidPtr); /* Slot 5 is reserved */ /* 6 */ EXTERN TclFile TclpMakeFile(Tcl_Channel channel, int direction); /* 7 */ EXTERN TclFile TclpOpenFile(const char *fname, int mode); /* 8 */ EXTERN int TclUnixWaitForFile(int fd, int mask, int timeout); /* 9 */ EXTERN TclFile TclpCreateTempFile(const char *contents); /* 10 */ EXTERN Tcl_DirEntry * TclpReaddir(TclDIR *dir); /* Slot 11 is reserved */ /* Slot 12 is reserved */ /* Slot 13 is reserved */ /* 14 */ EXTERN int TclUnixCopyFile(const char *src, const char *dst, const Tcl_StatBuf *statBufPtr, int dontCopyAtts); /* 15 */ EXTERN int TclMacOSXGetFileAttribute(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr); /* 16 */ EXTERN int TclMacOSXSetFileAttribute(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj *attributePtr); /* 17 */ EXTERN int TclMacOSXCopyFileAttributes(const char *src, const char *dst, const Tcl_StatBuf *statBufPtr); /* 18 */ EXTERN int TclMacOSXMatchType(Tcl_Interp *interp, const char *pathName, const char *fileName, Tcl_StatBuf *statBufPtr, Tcl_GlobTypeData *types); /* 19 */ EXTERN void TclMacOSXNotifierAddRunLoopMode( const void *runLoopMode); /* Slot 20 is reserved */ /* Slot 21 is reserved */ /* Slot 22 is reserved */ /* Slot 23 is reserved */ /* Slot 24 is reserved */ /* Slot 25 is reserved */ /* Slot 26 is reserved */ /* Slot 27 is reserved */ /* Slot 28 is reserved */ /* 29 */ EXTERN int TclWinCPUID(int index, int *regs); /* 30 */ EXTERN int TclUnixOpenTemporaryFile(Tcl_Obj *dirObj, Tcl_Obj *basenameObj, Tcl_Obj *extensionObj, Tcl_Obj *resultingNameObj); #endif /* UNIX */ #if defined(_WIN32) || defined(__CYGWIN__) /* WIN */ /* Slot 0 is reserved */ /* Slot 1 is reserved */ /* Slot 2 is reserved */ /* Slot 3 is reserved */ /* 4 */ EXTERN void * TclWinGetTclInstance(void); /* 5 */ EXTERN int TclUnixWaitForFile(int fd, int mask, int timeout); /* Slot 6 is reserved */ /* Slot 7 is reserved */ /* 8 */ EXTERN Tcl_Size TclpGetPid(Tcl_Pid pid); /* Slot 9 is reserved */ /* Slot 10 is reserved */ /* 11 */ EXTERN void TclGetAndDetachPids(Tcl_Interp *interp, Tcl_Channel chan); /* 12 */ EXTERN int TclpCloseFile(TclFile file); /* 13 */ EXTERN Tcl_Channel TclpCreateCommandChannel(TclFile readFile, TclFile writeFile, TclFile errorFile, int numPids, Tcl_Pid *pidPtr); /* 14 */ EXTERN int TclpCreatePipe(TclFile *readPipe, TclFile *writePipe); /* 15 */ EXTERN int TclpCreateProcess(Tcl_Interp *interp, int argc, const char **argv, TclFile inputFile, TclFile outputFile, TclFile errorFile, Tcl_Pid *pidPtr); /* 16 */ EXTERN int TclpIsAtty(int fd); /* 17 */ EXTERN int TclUnixCopyFile(const char *src, const char *dst, const Tcl_StatBuf *statBufPtr, int dontCopyAtts); /* 18 */ EXTERN TclFile TclpMakeFile(Tcl_Channel channel, int direction); /* 19 */ EXTERN TclFile TclpOpenFile(const char *fname, int mode); /* 20 */ EXTERN void TclWinAddProcess(void *hProcess, Tcl_Size id); /* Slot 21 is reserved */ /* 22 */ EXTERN TclFile TclpCreateTempFile(const char *contents); /* Slot 23 is reserved */ /* 24 */ EXTERN char * TclWinNoBackslash(char *path); /* Slot 25 is reserved */ /* Slot 26 is reserved */ /* 27 */ EXTERN void TclWinFlushDirtyChannels(void); /* Slot 28 is reserved */ /* 29 */ EXTERN int TclWinCPUID(int index, int *regs); /* 30 */ EXTERN int TclUnixOpenTemporaryFile(Tcl_Obj *dirObj, Tcl_Obj *basenameObj, Tcl_Obj *extensionObj, Tcl_Obj *resultingNameObj); #endif /* WIN */ #ifdef MAC_OSX_TCL /* MACOSX */ /* 0 */ EXTERN void TclGetAndDetachPids(Tcl_Interp *interp, Tcl_Channel chan); /* 1 */ EXTERN int TclpCloseFile(TclFile file); /* 2 */ EXTERN Tcl_Channel TclpCreateCommandChannel(TclFile readFile, TclFile writeFile, TclFile errorFile, int numPids, Tcl_Pid *pidPtr); /* 3 */ EXTERN int TclpCreatePipe(TclFile *readPipe, TclFile *writePipe); /* 4 */ EXTERN int TclpCreateProcess(Tcl_Interp *interp, int argc, const char **argv, TclFile inputFile, TclFile outputFile, TclFile errorFile, Tcl_Pid *pidPtr); /* Slot 5 is reserved */ /* 6 */ EXTERN TclFile TclpMakeFile(Tcl_Channel channel, int direction); /* 7 */ EXTERN TclFile TclpOpenFile(const char *fname, int mode); /* 8 */ EXTERN int TclUnixWaitForFile(int fd, int mask, int timeout); /* 9 */ EXTERN TclFile TclpCreateTempFile(const char *contents); /* 10 */ EXTERN Tcl_DirEntry * TclpReaddir(TclDIR *dir); /* Slot 13 is reserved */ /* 14 */ EXTERN int TclUnixCopyFile(const char *src, const char *dst, const Tcl_StatBuf *statBufPtr, int dontCopyAtts); /* 15 */ EXTERN int TclMacOSXGetFileAttribute(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr); /* 16 */ EXTERN int TclMacOSXSetFileAttribute(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj *attributePtr); /* 17 */ EXTERN int TclMacOSXCopyFileAttributes(const char *src, const char *dst, const Tcl_StatBuf *statBufPtr); /* 18 */ EXTERN int TclMacOSXMatchType(Tcl_Interp *interp, const char *pathName, const char *fileName, Tcl_StatBuf *statBufPtr, Tcl_GlobTypeData *types); /* 19 */ EXTERN void TclMacOSXNotifierAddRunLoopMode( const void *runLoopMode); /* Slot 20 is reserved */ /* Slot 21 is reserved */ /* Slot 22 is reserved */ /* Slot 23 is reserved */ /* Slot 24 is reserved */ /* Slot 25 is reserved */ /* Slot 26 is reserved */ /* Slot 27 is reserved */ /* Slot 28 is reserved */ /* 29 */ EXTERN int TclWinCPUID(int index, int *regs); /* 30 */ EXTERN int TclUnixOpenTemporaryFile(Tcl_Obj *dirObj, Tcl_Obj *basenameObj, Tcl_Obj *extensionObj, Tcl_Obj *resultingNameObj); #endif /* MACOSX */ typedef struct TclIntPlatStubs { int magic; void *hooks; #if !defined(_WIN32) && !defined(__CYGWIN__) && !defined(MAC_OSX_TCL) /* UNIX */ void (*tclGetAndDetachPids) (Tcl_Interp *interp, Tcl_Channel chan); /* 0 */ int (*tclpCloseFile) (TclFile file); /* 1 */ Tcl_Channel (*tclpCreateCommandChannel) (TclFile readFile, TclFile writeFile, TclFile errorFile, int numPids, Tcl_Pid *pidPtr); /* 2 */ int (*tclpCreatePipe) (TclFile *readPipe, TclFile *writePipe); /* 3 */ int (*tclpCreateProcess) (Tcl_Interp *interp, int argc, const char **argv, TclFile inputFile, TclFile outputFile, TclFile errorFile, Tcl_Pid *pidPtr); /* 4 */ int (*tclUnixWaitForFile_) (int fd, int mask, int timeout); /* 5 */ TclFile (*tclpMakeFile) (Tcl_Channel channel, int direction); /* 6 */ TclFile (*tclpOpenFile) (const char *fname, int mode); /* 7 */ int (*tclUnixWaitForFile) (int fd, int mask, int timeout); /* 8 */ TclFile (*tclpCreateTempFile) (const char *contents); /* 9 */ Tcl_DirEntry * (*tclpReaddir) (TclDIR *dir); /* 10 */ void (*reserved11)(void); void (*reserved12)(void); void (*reserved13)(void); int (*tclUnixCopyFile) (const char *src, const char *dst, const Tcl_StatBuf *statBufPtr, int dontCopyAtts); /* 14 */ int (*tclMacOSXGetFileAttribute) (Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr); /* 15 */ int (*tclMacOSXSetFileAttribute) (Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj *attributePtr); /* 16 */ int (*tclMacOSXCopyFileAttributes) (const char *src, const char *dst, const Tcl_StatBuf *statBufPtr); /* 17 */ int (*tclMacOSXMatchType) (Tcl_Interp *interp, const char *pathName, const char *fileName, Tcl_StatBuf *statBufPtr, Tcl_GlobTypeData *types); /* 18 */ void (*tclMacOSXNotifierAddRunLoopMode) (const void *runLoopMode); /* 19 */ void (*reserved20)(void); void (*reserved21)(void); TclFile (*tclpCreateTempFile_) (const char *contents); /* 22 */ void (*reserved23)(void); void (*reserved24)(void); void (*reserved25)(void); void (*reserved26)(void); void (*reserved27)(void); void (*reserved28)(void); int (*tclWinCPUID) (int index, int *regs); /* 29 */ int (*tclUnixOpenTemporaryFile) (Tcl_Obj *dirObj, Tcl_Obj *basenameObj, Tcl_Obj *extensionObj, Tcl_Obj *resultingNameObj); /* 30 */ #endif /* UNIX */ #if defined(_WIN32) || defined(__CYGWIN__) /* WIN */ void (*reserved0)(void); void (*reserved1)(void); void (*reserved2)(void); void (*reserved3)(void); void * (*tclWinGetTclInstance) (void); /* 4 */ int (*tclUnixWaitForFile) (int fd, int mask, int timeout); /* 5 */ void (*reserved6)(void); void (*reserved7)(void); Tcl_Size (*tclpGetPid) (Tcl_Pid pid); /* 8 */ void (*reserved9)(void); void *(*tclpReaddir) (void *dir); /* 10 */ void (*tclGetAndDetachPids) (Tcl_Interp *interp, Tcl_Channel chan); /* 11 */ int (*tclpCloseFile) (TclFile file); /* 12 */ Tcl_Channel (*tclpCreateCommandChannel) (TclFile readFile, TclFile writeFile, TclFile errorFile, int numPids, Tcl_Pid *pidPtr); /* 13 */ int (*tclpCreatePipe) (TclFile *readPipe, TclFile *writePipe); /* 14 */ int (*tclpCreateProcess) (Tcl_Interp *interp, int argc, const char **argv, TclFile inputFile, TclFile outputFile, TclFile errorFile, Tcl_Pid *pidPtr); /* 15 */ int (*tclpIsAtty) (int fd); /* 16 */ int (*tclUnixCopyFile) (const char *src, const char *dst, const Tcl_StatBuf *statBufPtr, int dontCopyAtts); /* 17 */ TclFile (*tclpMakeFile) (Tcl_Channel channel, int direction); /* 18 */ TclFile (*tclpOpenFile) (const char *fname, int mode); /* 19 */ void (*tclWinAddProcess) (void *hProcess, Tcl_Size id); /* 20 */ void (*reserved21)(void); TclFile (*tclpCreateTempFile) (const char *contents); /* 22 */ void (*reserved23)(void); char * (*tclWinNoBackslash) (char *path); /* 24 */ void (*reserved25)(void); void (*reserved26)(void); void (*tclWinFlushDirtyChannels) (void); /* 27 */ void (*reserved28)(void); int (*tclWinCPUID) (int index, int *regs); /* 29 */ int (*tclUnixOpenTemporaryFile) (Tcl_Obj *dirObj, Tcl_Obj *basenameObj, Tcl_Obj *extensionObj, Tcl_Obj *resultingNameObj); /* 30 */ #endif /* WIN */ #ifdef MAC_OSX_TCL /* MACOSX */ void (*tclGetAndDetachPids) (Tcl_Interp *interp, Tcl_Channel chan); /* 0 */ int (*tclpCloseFile) (TclFile file); /* 1 */ Tcl_Channel (*tclpCreateCommandChannel) (TclFile readFile, TclFile writeFile, TclFile errorFile, int numPids, Tcl_Pid *pidPtr); /* 2 */ int (*tclpCreatePipe) (TclFile *readPipe, TclFile *writePipe); /* 3 */ int (*tclpCreateProcess) (Tcl_Interp *interp, int argc, const char **argv, TclFile inputFile, TclFile outputFile, TclFile errorFile, Tcl_Pid *pidPtr); /* 4 */ int (*tclUnixWaitForFile_) (int fd, int mask, int timeout); /* 5 */ TclFile (*tclpMakeFile) (Tcl_Channel channel, int direction); /* 6 */ TclFile (*tclpOpenFile) (const char *fname, int mode); /* 7 */ int (*tclUnixWaitForFile) (int fd, int mask, int timeout); /* 8 */ TclFile (*tclpCreateTempFile) (const char *contents); /* 9 */ Tcl_DirEntry * (*tclpReaddir) (TclDIR *dir); /* 10 */ void (*reserved11)(void); void (*reserved12)(void); void (*reserved13)(void); int (*tclUnixCopyFile) (const char *src, const char *dst, const Tcl_StatBuf *statBufPtr, int dontCopyAtts); /* 14 */ int (*tclMacOSXGetFileAttribute) (Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr); /* 15 */ int (*tclMacOSXSetFileAttribute) (Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj *attributePtr); /* 16 */ int (*tclMacOSXCopyFileAttributes) (const char *src, const char *dst, const Tcl_StatBuf *statBufPtr); /* 17 */ int (*tclMacOSXMatchType) (Tcl_Interp *interp, const char *pathName, const char *fileName, Tcl_StatBuf *statBufPtr, Tcl_GlobTypeData *types); /* 18 */ void (*tclMacOSXNotifierAddRunLoopMode) (const void *runLoopMode); /* 19 */ void (*reserved20)(void); void (*reserved21)(void); TclFile (*tclpCreateTempFile_) (const char *contents); /* 22 */ void (*reserved23)(void); void (*reserved24)(void); void (*reserved25)(void); void (*reserved26)(void); void (*reserved27)(void); void (*reserved28)(void); int (*tclWinCPUID) (int index, int *regs); /* 29 */ int (*tclUnixOpenTemporaryFile) (Tcl_Obj *dirObj, Tcl_Obj *basenameObj, Tcl_Obj *extensionObj, Tcl_Obj *resultingNameObj); /* 30 */ #endif /* MACOSX */ } TclIntPlatStubs; extern const TclIntPlatStubs *tclIntPlatStubsPtr; #ifdef __cplusplus } #endif #if defined(USE_TCL_STUBS) /* * Inline function declarations: */ #if !defined(_WIN32) && !defined(__CYGWIN__) && !defined(MAC_OSX_TCL) /* UNIX */ #define TclGetAndDetachPids \ (tclIntPlatStubsPtr->tclGetAndDetachPids) /* 0 */ #define TclpCloseFile \ (tclIntPlatStubsPtr->tclpCloseFile) /* 1 */ #define TclpCreateCommandChannel \ (tclIntPlatStubsPtr->tclpCreateCommandChannel) /* 2 */ #define TclpCreatePipe \ (tclIntPlatStubsPtr->tclpCreatePipe) /* 3 */ #define TclpCreateProcess \ (tclIntPlatStubsPtr->tclpCreateProcess) /* 4 */ /* Slot 5 is reserved */ #define TclpMakeFile \ (tclIntPlatStubsPtr->tclpMakeFile) /* 6 */ #define TclpOpenFile \ (tclIntPlatStubsPtr->tclpOpenFile) /* 7 */ #define TclUnixWaitForFile \ (tclIntPlatStubsPtr->tclUnixWaitForFile) /* 8 */ #define TclpCreateTempFile \ (tclIntPlatStubsPtr->tclpCreateTempFile) /* 9 */ #define TclpReaddir \ (tclIntPlatStubsPtr->tclpReaddir) /* 10 */ /* Slot 11 is reserved */ /* Slot 12 is reserved */ /* Slot 13 is reserved */ #define TclUnixCopyFile \ (tclIntPlatStubsPtr->tclUnixCopyFile) /* 14 */ #define TclMacOSXGetFileAttribute \ (tclIntPlatStubsPtr->tclMacOSXGetFileAttribute) /* 15 */ #define TclMacOSXSetFileAttribute \ (tclIntPlatStubsPtr->tclMacOSXSetFileAttribute) /* 16 */ #define TclMacOSXCopyFileAttributes \ (tclIntPlatStubsPtr->tclMacOSXCopyFileAttributes) /* 17 */ #define TclMacOSXMatchType \ (tclIntPlatStubsPtr->tclMacOSXMatchType) /* 18 */ #define TclMacOSXNotifierAddRunLoopMode \ (tclIntPlatStubsPtr->tclMacOSXNotifierAddRunLoopMode) /* 19 */ /* Slot 20 is reserved */ /* Slot 21 is reserved */ /* Slot 22 is reserved */ /* Slot 23 is reserved */ /* Slot 24 is reserved */ /* Slot 25 is reserved */ /* Slot 26 is reserved */ /* Slot 27 is reserved */ /* Slot 28 is reserved */ #define TclWinCPUID \ (tclIntPlatStubsPtr->tclWinCPUID) /* 29 */ #define TclUnixOpenTemporaryFile \ (tclIntPlatStubsPtr->tclUnixOpenTemporaryFile) /* 30 */ #endif /* UNIX */ #if defined(_WIN32) || defined(__CYGWIN__) /* WIN */ /* Slot 0 is reserved */ /* Slot 1 is reserved */ /* Slot 2 is reserved */ /* Slot 3 is reserved */ #define TclWinGetTclInstance \ (tclIntPlatStubsPtr->tclWinGetTclInstance) /* 4 */ #define TclUnixWaitForFile \ (tclIntPlatStubsPtr->tclUnixWaitForFile) /* 5 */ /* Slot 6 is reserved */ /* Slot 7 is reserved */ #define TclpGetPid \ (tclIntPlatStubsPtr->tclpGetPid) /* 8 */ /* Slot 9 is reserved */ /* Slot 10 is reserved */ #define TclGetAndDetachPids \ (tclIntPlatStubsPtr->tclGetAndDetachPids) /* 11 */ #define TclpCloseFile \ (tclIntPlatStubsPtr->tclpCloseFile) /* 12 */ #define TclpCreateCommandChannel \ (tclIntPlatStubsPtr->tclpCreateCommandChannel) /* 13 */ #define TclpCreatePipe \ (tclIntPlatStubsPtr->tclpCreatePipe) /* 14 */ #define TclpCreateProcess \ (tclIntPlatStubsPtr->tclpCreateProcess) /* 15 */ #define TclpIsAtty \ (tclIntPlatStubsPtr->tclpIsAtty) /* 16 */ #define TclUnixCopyFile \ (tclIntPlatStubsPtr->tclUnixCopyFile) /* 17 */ #define TclpMakeFile \ (tclIntPlatStubsPtr->tclpMakeFile) /* 18 */ #define TclpOpenFile \ (tclIntPlatStubsPtr->tclpOpenFile) /* 19 */ #define TclWinAddProcess \ (tclIntPlatStubsPtr->tclWinAddProcess) /* 20 */ /* Slot 21 is reserved */ #define TclpCreateTempFile \ (tclIntPlatStubsPtr->tclpCreateTempFile) /* 22 */ /* Slot 23 is reserved */ #define TclWinNoBackslash \ (tclIntPlatStubsPtr->tclWinNoBackslash) /* 24 */ /* Slot 25 is reserved */ /* Slot 26 is reserved */ #define TclWinFlushDirtyChannels \ (tclIntPlatStubsPtr->tclWinFlushDirtyChannels) /* 27 */ /* Slot 28 is reserved */ #define TclWinCPUID \ (tclIntPlatStubsPtr->tclWinCPUID) /* 29 */ #define TclUnixOpenTemporaryFile \ (tclIntPlatStubsPtr->tclUnixOpenTemporaryFile) /* 30 */ #endif /* WIN */ #ifdef MAC_OSX_TCL /* MACOSX */ #define TclGetAndDetachPids \ (tclIntPlatStubsPtr->tclGetAndDetachPids) /* 0 */ #define TclpCloseFile \ (tclIntPlatStubsPtr->tclpCloseFile) /* 1 */ #define TclpCreateCommandChannel \ (tclIntPlatStubsPtr->tclpCreateCommandChannel) /* 2 */ #define TclpCreatePipe \ (tclIntPlatStubsPtr->tclpCreatePipe) /* 3 */ #define TclpCreateProcess \ (tclIntPlatStubsPtr->tclpCreateProcess) /* 4 */ /* Slot 5 is reserved */ #define TclpMakeFile \ (tclIntPlatStubsPtr->tclpMakeFile) /* 6 */ #define TclpOpenFile \ (tclIntPlatStubsPtr->tclpOpenFile) /* 7 */ #define TclUnixWaitForFile \ (tclIntPlatStubsPtr->tclUnixWaitForFile) /* 8 */ #define TclpCreateTempFile \ (tclIntPlatStubsPtr->tclpCreateTempFile) /* 9 */ #define TclpReaddir \ (tclIntPlatStubsPtr->tclpReaddir) /* 10 */ /* Slot 11 is reserved */ /* Slot 12 is reserved */ /* Slot 13 is reserved */ #define TclUnixCopyFile \ (tclIntPlatStubsPtr->tclUnixCopyFile) /* 14 */ #define TclMacOSXGetFileAttribute \ (tclIntPlatStubsPtr->tclMacOSXGetFileAttribute) /* 15 */ #define TclMacOSXSetFileAttribute \ (tclIntPlatStubsPtr->tclMacOSXSetFileAttribute) /* 16 */ #define TclMacOSXCopyFileAttributes \ (tclIntPlatStubsPtr->tclMacOSXCopyFileAttributes) /* 17 */ #define TclMacOSXMatchType \ (tclIntPlatStubsPtr->tclMacOSXMatchType) /* 18 */ #define TclMacOSXNotifierAddRunLoopMode \ (tclIntPlatStubsPtr->tclMacOSXNotifierAddRunLoopMode) /* 19 */ /* Slot 20 is reserved */ /* Slot 21 is reserved */ /* Slot 22 is reserved */ /* Slot 23 is reserved */ /* Slot 24 is reserved */ /* Slot 25 is reserved */ /* Slot 26 is reserved */ /* Slot 27 is reserved */ /* Slot 28 is reserved */ #define TclWinCPUID \ (tclIntPlatStubsPtr->tclWinCPUID) /* 29 */ #define TclUnixOpenTemporaryFile \ (tclIntPlatStubsPtr->tclUnixOpenTemporaryFile) /* 30 */ #endif /* MACOSX */ #endif /* defined(USE_TCL_STUBS) */ #else /* TCL_MAJOR_VERSION > 8 */ /* !BEGIN!: Do not edit below this line. */ #ifdef __cplusplus extern "C" { #endif /* * Exported function declarations: */ /* Slot 0 is reserved */ /* 1 */ EXTERN int TclpCloseFile(TclFile file); /* 2 */ EXTERN Tcl_Channel TclpCreateCommandChannel(TclFile readFile, TclFile writeFile, TclFile errorFile, size_t numPids, Tcl_Pid *pidPtr); /* 3 */ EXTERN int TclpCreatePipe(TclFile *readPipe, TclFile *writePipe); /* 4 */ EXTERN void * TclWinGetTclInstance(void); /* 5 */ EXTERN int TclUnixWaitForFile(int fd, int mask, int timeout); /* 6 */ EXTERN TclFile TclpMakeFile(Tcl_Channel channel, int direction); /* 7 */ EXTERN TclFile TclpOpenFile(const char *fname, int mode); /* 8 */ EXTERN Tcl_Size TclpGetPid(Tcl_Pid pid); /* 9 */ EXTERN TclFile TclpCreateTempFile(const char *contents); /* Slot 10 is reserved */ /* 11 */ EXTERN void TclGetAndDetachPids(Tcl_Interp *interp, Tcl_Channel chan); /* Slot 12 is reserved */ /* Slot 13 is reserved */ /* Slot 14 is reserved */ /* 15 */ EXTERN int TclpCreateProcess(Tcl_Interp *interp, size_t argc, const char **argv, TclFile inputFile, TclFile outputFile, TclFile errorFile, Tcl_Pid *pidPtr); /* 16 */ EXTERN int TclpIsAtty(int fd); /* 17 */ EXTERN int TclUnixCopyFile(const char *src, const char *dst, const Tcl_StatBuf *statBufPtr, int dontCopyAtts); /* Slot 18 is reserved */ /* Slot 19 is reserved */ /* 20 */ EXTERN void TclWinAddProcess(void *hProcess, Tcl_Size id); /* Slot 21 is reserved */ /* Slot 22 is reserved */ /* Slot 23 is reserved */ /* 24 */ EXTERN char * TclWinNoBackslash(char *path); /* Slot 25 is reserved */ /* Slot 26 is reserved */ /* 27 */ EXTERN void TclWinFlushDirtyChannels(void); /* Slot 28 is reserved */ /* 29 */ EXTERN int TclWinCPUID(int index, int *regs); /* 30 */ EXTERN int TclUnixOpenTemporaryFile(Tcl_Obj *dirObj, Tcl_Obj *basenameObj, Tcl_Obj *extensionObj, Tcl_Obj *resultingNameObj); typedef struct TclIntPlatStubs { int magic; void *hooks; void (*reserved0)(void); int (*tclpCloseFile) (TclFile file); /* 1 */ Tcl_Channel (*tclpCreateCommandChannel) (TclFile readFile, TclFile writeFile, TclFile errorFile, size_t numPids, Tcl_Pid *pidPtr); /* 2 */ int (*tclpCreatePipe) (TclFile *readPipe, TclFile *writePipe); /* 3 */ void * (*tclWinGetTclInstance) (void); /* 4 */ int (*tclUnixWaitForFile) (int fd, int mask, int timeout); /* 5 */ TclFile (*tclpMakeFile) (Tcl_Channel channel, int direction); /* 6 */ TclFile (*tclpOpenFile) (const char *fname, int mode); /* 7 */ Tcl_Size (*tclpGetPid) (Tcl_Pid pid); /* 8 */ TclFile (*tclpCreateTempFile) (const char *contents); /* 9 */ void (*reserved10)(void); void (*tclGetAndDetachPids) (Tcl_Interp *interp, Tcl_Channel chan); /* 11 */ void (*reserved12)(void); void (*reserved13)(void); void (*reserved14)(void); int (*tclpCreateProcess) (Tcl_Interp *interp, size_t argc, const char **argv, TclFile inputFile, TclFile outputFile, TclFile errorFile, Tcl_Pid *pidPtr); /* 15 */ int (*tclpIsAtty) (int fd); /* 16 */ int (*tclUnixCopyFile) (const char *src, const char *dst, const Tcl_StatBuf *statBufPtr, int dontCopyAtts); /* 17 */ void (*reserved18)(void); void (*reserved19)(void); void (*tclWinAddProcess) (void *hProcess, Tcl_Size id); /* 20 */ void (*reserved21)(void); void (*reserved22)(void); void (*reserved23)(void); char * (*tclWinNoBackslash) (char *path); /* 24 */ void (*reserved25)(void); void (*reserved26)(void); void (*tclWinFlushDirtyChannels) (void); /* 27 */ void (*reserved28)(void); int (*tclWinCPUID) (int index, int *regs); /* 29 */ int (*tclUnixOpenTemporaryFile) (Tcl_Obj *dirObj, Tcl_Obj *basenameObj, Tcl_Obj *extensionObj, Tcl_Obj *resultingNameObj); /* 30 */ } TclIntPlatStubs; extern const TclIntPlatStubs *tclIntPlatStubsPtr; #ifdef __cplusplus } #endif #if defined(USE_TCL_STUBS) /* * Inline function declarations: */ /* Slot 0 is reserved */ #define TclpCloseFile \ (tclIntPlatStubsPtr->tclpCloseFile) /* 1 */ #define TclpCreateCommandChannel \ (tclIntPlatStubsPtr->tclpCreateCommandChannel) /* 2 */ #define TclpCreatePipe \ (tclIntPlatStubsPtr->tclpCreatePipe) /* 3 */ #define TclWinGetTclInstance \ (tclIntPlatStubsPtr->tclWinGetTclInstance) /* 4 */ #define TclUnixWaitForFile \ (tclIntPlatStubsPtr->tclUnixWaitForFile) /* 5 */ #define TclpMakeFile \ (tclIntPlatStubsPtr->tclpMakeFile) /* 6 */ #define TclpOpenFile \ (tclIntPlatStubsPtr->tclpOpenFile) /* 7 */ #define TclpGetPid \ (tclIntPlatStubsPtr->tclpGetPid) /* 8 */ #define TclpCreateTempFile \ (tclIntPlatStubsPtr->tclpCreateTempFile) /* 9 */ /* Slot 10 is reserved */ #define TclGetAndDetachPids \ (tclIntPlatStubsPtr->tclGetAndDetachPids) /* 11 */ /* Slot 12 is reserved */ /* Slot 13 is reserved */ /* Slot 14 is reserved */ #define TclpCreateProcess \ (tclIntPlatStubsPtr->tclpCreateProcess) /* 15 */ #define TclpIsAtty \ (tclIntPlatStubsPtr->tclpIsAtty) /* 16 */ #define TclUnixCopyFile \ (tclIntPlatStubsPtr->tclUnixCopyFile) /* 17 */ /* Slot 18 is reserved */ /* Slot 19 is reserved */ #define TclWinAddProcess \ (tclIntPlatStubsPtr->tclWinAddProcess) /* 20 */ /* Slot 21 is reserved */ /* Slot 22 is reserved */ /* Slot 23 is reserved */ #define TclWinNoBackslash \ (tclIntPlatStubsPtr->tclWinNoBackslash) /* 24 */ /* Slot 25 is reserved */ /* Slot 26 is reserved */ #define TclWinFlushDirtyChannels \ (tclIntPlatStubsPtr->tclWinFlushDirtyChannels) /* 27 */ /* Slot 28 is reserved */ #define TclWinCPUID \ (tclIntPlatStubsPtr->tclWinCPUID) /* 29 */ #define TclUnixOpenTemporaryFile \ (tclIntPlatStubsPtr->tclUnixOpenTemporaryFile) /* 30 */ #endif /* defined(USE_TCL_STUBS) */ /* !END!: Do not edit above this line. */ #endif /* TCL_MAJOR_VERSION */ #undef TCL_STORAGE_CLASS #define TCL_STORAGE_CLASS DLLIMPORT #ifdef MAC_OSX_TCL /* not accessible on Win32/UNIX */ MODULE_SCOPE int TclMacOSXGetFileAttribute(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr); /* 16 */ MODULE_SCOPE int TclMacOSXSetFileAttribute(Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj *attributePtr); /* 17 */ MODULE_SCOPE int TclMacOSXCopyFileAttributes(const char *src, const char *dst, const Tcl_StatBuf *statBufPtr); /* 18 */ MODULE_SCOPE int TclMacOSXMatchType(Tcl_Interp *interp, const char *pathName, const char *fileName, Tcl_StatBuf *statBufPtr, Tcl_GlobTypeData *types); #else #undef TclMacOSXGetFileAttribute /* 15 */ #undef TclMacOSXSetFileAttribute /* 16 */ #undef TclMacOSXCopyFileAttributes /* 17 */ #undef TclMacOSXMatchType /* 18 */ #undef TclMacOSXNotifierAddRunLoopMode /* 19 */ #endif #if defined(_WIN32) # if !defined(TCL_NO_DEPRECATED) # define TclWinConvertError Tcl_WinConvertError # define TclWinConvertWSAError Tcl_WinConvertError # define TclWinNToHS ntohs # define TclpInetNtoa inet_ntoa # define TclWinGetServByName getservbyname # define TclWinGetSockOpt getsockopt # define TclWinSetSockOpt setsockopt # define TclWinGetPlatformId() (2) /* VER_PLATFORM_WIN32_NT */ # define TclWinResetInterfaces() /* nop */ # define TclWinSetInterfaces(dummy) /* nop */ # endif /* TCL_NO_DEPRECATED */ #else # undef TclpGetPid # define TclpGetPid(pid) ((Tcl_Size)(pid)) #endif #endif /* _TCLINTPLATDECLS */ tcl9.0.1/generic/tclInterp.c0000644000175000017500000040773314726623136015320 0ustar sergeisergei/* * tclInterp.c -- * * This file implements the "interp" command which allows creation and * manipulation of Tcl interpreters from within Tcl scripts. * * Copyright © 1995-1997 Sun Microsystems, Inc. * Copyright © 2004 Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * A pointer to a string that holds an initialization script that if non-NULL * is evaluated in Tcl_Init() prior to the built-in initialization script * above. This variable can be modified by the function below. */ static const char *tclPreInitScript = NULL; /* Forward declaration */ struct Target; /* * Alias: * * Stores information about an alias. Is stored in the child interpreter and * used by the source command to find the target command in the parent when * the source command is invoked. */ typedef struct { Tcl_Obj *token; /* Token for the alias command in the child * interp. This used to be the command name in * the child when the alias was first * created. */ Tcl_Interp *targetInterp; /* Interp in which target command will be * invoked. */ Tcl_Command childCmd; /* Source command in child interpreter, bound * to command that invokes the target command * in the target interpreter. */ Tcl_HashEntry *aliasEntryPtr; /* Entry for the alias hash table in child. * This is used by alias deletion to remove * the alias from the child interpreter alias * table. */ struct Target *targetPtr; /* Entry for target command in parent. This is * used in the parent interpreter to map back * from the target command to aliases * redirecting to it. */ Tcl_Size objc; /* Count of Tcl_Obj in the prefix of the * target command to be invoked in the target * interpreter. Additional arguments specified * when calling the alias in the child interp * will be appended to the prefix before the * command is invoked. */ Tcl_Obj *objPtr; /* The first actual prefix object - the target * command name; this has to be at the end of * the structure, which will be extended to * accommodate the remaining objects in the * prefix. */ } Alias; /* * * Child: * * Used by the "interp" command to record and find information about child * interpreters. Maps from a command name in the parent to information about a * child interpreter, e.g. what aliases are defined in it. */ typedef struct { Tcl_Interp *parentInterp; /* Parent interpreter for this child. */ Tcl_HashEntry *childEntryPtr; /* Hash entry in parents child table for this * child interpreter. Used to find this * record, and used when deleting the child * interpreter to delete it from the parent's * table. */ Tcl_Interp *childInterp; /* The child interpreter. */ Tcl_Command interpCmd; /* Interpreter object command. */ Tcl_HashTable aliasTable; /* Table which maps from names of commands in * child interpreter to struct Alias defined * below. */ } Child; /* * struct Target: * * Maps from parent interpreter commands back to the source commands in child * interpreters. This is needed because aliases can be created between sibling * interpreters and must be deleted when the target interpreter is deleted. In * case they would not be deleted the source interpreter would be left with a * "dangling pointer". One such record is stored in the Parent record of the * parent interpreter with the parent for each alias which directs to a * command in the parent. These records are used to remove the source command * for an from a child if/when the parent is deleted. They are organized in a * doubly-linked list attached to the parent interpreter. */ typedef struct Target { Tcl_Command childCmd; /* Command for alias in child interp. */ Tcl_Interp *childInterp; /* Child Interpreter. */ struct Target *nextPtr; /* Next in list of target records, or NULL if * at the end of the list of targets. */ struct Target *prevPtr; /* Previous in list of target records, or NULL * if at the start of the list of targets. */ } Target; /* * Parent: * * This record is used for two purposes: First, childTable (a hashtable) maps * from names of commands to child interpreters. This hashtable is used to * store information about child interpreters of this interpreter, to map over * all children, etc. The second purpose is to store information about all * aliases in children (or siblings) which direct to target commands in this * interpreter (using the targetsPtr doubly-linked list). * * NB: the flags field in the interp structure, used with SAFE_INTERP mask * denotes whether the interpreter is safe or not. Safe interpreters have * restricted functionality, can only create safe interpreters and can * only load safe extensions. */ typedef struct { Tcl_HashTable childTable; /* Hash table for child interpreters. Maps * from command names to Child records. */ Target *targetsPtr; /* The head of a doubly-linked list of all the * target records which denote aliases from * children or sibling interpreters that direct * to commands in this interpreter. This list * is used to remove dangling pointers from * the child (or sibling) interpreters when * this interpreter is deleted. */ Tcl_Size idIssuer; /* Used to issue a sequence of names for * "unnamed" child interpreters. We keep a * count here to avoid having to scan over IDs * for interpreters that we've already used. */ } Parent; /* * The following structure keeps track of all the Parent and Child information * on a per-interp basis. */ typedef struct { Parent parent; /* Keeps track of all interps for which this * interp is the Parent. */ Child child; /* Information necessary for this interp to * function as a child. */ } InterpInfo; /* * Limit callbacks handled by scripts are modelled as structures which are * stored in hashes indexed by a two-word key. Note that the type of the * 'type' field in the key is not int; this is to make sure that things are * likely to work properly on 64-bit architectures. */ typedef struct { Tcl_Interp *interp; /* The interpreter in which to execute the * callback. */ Tcl_Obj *scriptObj; /* The script to execute to perform the * user-defined part of the callback. */ int type; /* What kind of callback is this. */ Tcl_HashEntry *entryPtr; /* The entry in the hash table maintained by * the target interpreter that refers to this * callback record, or NULL if the entry has * already been deleted from that hash * table. */ } ScriptLimitCallback; typedef struct { Tcl_Interp *interp; /* The interpreter that the limit callback was * attached to. This is not the interpreter * that the callback runs in! */ long type; /* The type of callback that this is. */ } ScriptLimitCallbackKey; /* * TIP#143 limit handler internal representation. */ struct LimitHandler { int flags; /* The state of this particular handler. */ Tcl_LimitHandlerProc *handlerProc; /* The handler callback. */ void *clientData; /* Opaque argument to the handler callback. */ Tcl_LimitHandlerDeleteProc *deleteProc; /* How to delete the clientData. */ LimitHandler *prevPtr; /* Previous item in linked list of * handlers. */ LimitHandler *nextPtr; /* Next item in linked list of handlers. */ }; /* * Values for the LimitHandler flags field. */ enum LimitHandlerFlags { LIMIT_HANDLER_ACTIVE = 1, /* The handler is currently being processed; * handlers are never to be reentered. */ LIMIT_HANDLER_DELETED = 2 /* The handler has been deleted. This should * not normally be observed because when a * handler is deleted it is also spliced out of * the list of handlers, but even so we will be * careful.*/ }; /* * Macro to make looking up child and parent info more convenient. */ #define INTERP_INFO(interp) \ ((InterpInfo *) ((Interp *) (interp))->interpInfo) /* * Prototypes for local static functions: */ static int AliasCreate(Tcl_Interp *interp, Tcl_Interp *childInterp, Tcl_Interp *parentInterp, Tcl_Obj *namePtr, Tcl_Obj *targetPtr, Tcl_Size objc, Tcl_Obj *const objv[]); static int AliasDelete(Tcl_Interp *interp, Tcl_Interp *childInterp, Tcl_Obj *namePtr); static int AliasDescribe(Tcl_Interp *interp, Tcl_Interp *childInterp, Tcl_Obj *objPtr); static int AliasList(Tcl_Interp *interp, Tcl_Interp *childInterp); static Tcl_ObjCmdProc AliasNRCmd; static Tcl_CmdDeleteProc AliasObjCmdDeleteProc; static Tcl_Interp * GetInterp(Tcl_Interp *interp, Tcl_Obj *pathPtr); static Tcl_Interp * GetInterp2(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[]); static Tcl_InterpDeleteProc InterpInfoDeleteProc; static int ChildBgerror(Tcl_Interp *interp, Tcl_Interp *childInterp, Tcl_Size objc, Tcl_Obj *const objv[]); static Tcl_Interp * ChildCreate(Tcl_Interp *interp, Tcl_Obj *pathPtr, int safe); static int ChildDebugCmd(Tcl_Interp *interp, Tcl_Interp *childInterp, Tcl_Size objc, Tcl_Obj *const objv[]); static int ChildEval(Tcl_Interp *interp, Tcl_Interp *childInterp, Tcl_Size objc, Tcl_Obj *const objv[]); static int ChildExpose(Tcl_Interp *interp, Tcl_Interp *childInterp, Tcl_Size objc, Tcl_Obj *const objv[]); static int ChildHide(Tcl_Interp *interp, Tcl_Interp *childInterp, Tcl_Size objc, Tcl_Obj *const objv[]); static int ChildHidden(Tcl_Interp *interp, Tcl_Interp *childInterp); static int ChildInvokeHidden(Tcl_Interp *interp, Tcl_Interp *childInterp, const char *namespaceName, Tcl_Size objc, Tcl_Obj *const objv[]); static int ChildMarkTrusted(Tcl_Interp *interp, Tcl_Interp *childInterp); static Tcl_CmdDeleteProc ChildObjCmdDeleteProc; static int ChildRecursionLimit(Tcl_Interp *interp, Tcl_Interp *childInterp, Tcl_Size objc, Tcl_Obj *const objv[]); static int ChildCommandLimitCmd(Tcl_Interp *interp, Tcl_Interp *childInterp, Tcl_Size consumedObjc, Tcl_Size objc, Tcl_Obj *const objv[]); static int ChildTimeLimitCmd(Tcl_Interp *interp, Tcl_Interp *childInterp, Tcl_Size consumedObjc, Tcl_Size objc, Tcl_Obj *const objv[]); static void InheritLimitsFromParent(Tcl_Interp *childInterp, Tcl_Interp *parentInterp); static void SetScriptLimitCallback(Tcl_Interp *interp, int type, Tcl_Interp *targetInterp, Tcl_Obj *scriptObj); static void CallScriptLimitCallback(void *clientData, Tcl_Interp *interp); static void DeleteScriptLimitCallback(void *clientData); static void MakeSafe(Tcl_Interp *interp); static void RunLimitHandlers(LimitHandler *handlerPtr, Tcl_Interp *interp); static void TimeLimitCallback(void *clientData); /* NRE enabling */ static Tcl_NRPostProc NRPostInvokeHidden; static Tcl_ObjCmdProc NRInterpCmd; static Tcl_ObjCmdProc NRChildCmd; /* *---------------------------------------------------------------------- * * Tcl_SetPreInitScript -- * * This routine is used to change the value of the internal variable, * tclPreInitScript. * * Results: * Returns the current value of tclPreInitScript. * * Side effects: * Changes the way Tcl_Init() routine behaves. * *---------------------------------------------------------------------- */ const char * Tcl_SetPreInitScript( const char *string) /* Pointer to a script. */ { const char *prevString = tclPreInitScript; tclPreInitScript = string; return prevString; } /* *---------------------------------------------------------------------- * * Tcl_Init -- * * This function is typically invoked by Tcl_AppInit functions to find * and source the "init.tcl" script, which should exist somewhere on the * Tcl library path. * * Results: * Returns a standard Tcl completion code and sets the interp's result if * there is an error. * * Side effects: * Depends on what's in the init.tcl script. * *---------------------------------------------------------------------- */ int Tcl_Init( Tcl_Interp *interp) /* Interpreter to initialize. */ { /* * Splice for putting the "tcl" package in the list of packages while the * pre-init and init scripts are running. The real version of this struct * is in tclPkg.c. */ typedef struct PkgName { struct PkgName *nextPtr;/* Next in list of package names being * initialized. */ char name[4]; /* Enough space for "tcl". The *real* version * of this structure uses a flex array. */ } PkgName; PkgName pkgName = {NULL, "tcl"}; PkgName **names = (PkgName **) TclInitPkgFiles(interp); int result = TCL_ERROR; pkgName.nextPtr = *names; *names = &pkgName; if (tclPreInitScript != NULL) { if (Tcl_EvalEx(interp, tclPreInitScript, TCL_INDEX_NONE, 0 /*flags*/) == TCL_ERROR) { goto end; } } /* * In order to find init.tcl during initialization, the following script * is invoked by Tcl_Init(). It looks in several different directories: * * $tcl_library - can specify a primary location, if set, no * other locations will be checked. This is the * recommended way for a program that embeds * Tcl to specifically tell Tcl where to find * an init.tcl file. * * $env(TCL_LIBRARY) - highest priority so user can always override * the search path unless the application has * specified an exact directory above * * $tclDefaultLibrary - INTERNAL: This variable is set by Tcl on * those platforms where it can determine at * runtime the directory where it expects the * init.tcl file to be. After [tclInit] reads * and uses this value, it [unset]s it. * External users of Tcl should not make use of * the variable to customize [tclInit]. * * $tcl_libPath - OBSOLETE: This variable is no longer set by * Tcl itself, but [tclInit] examines it in * case some program that embeds Tcl is * customizing [tclInit] by setting this * variable to a list of directories in which * to search. * * [tcl::pkgconfig get scriptdir,runtime] * - the directory determined by configure to be * the place where Tcl's script library is to * be installed. * * The first directory on this path that contains a valid init.tcl script * will be set as the value of tcl_library. * * Note that this entire search mechanism can be bypassed by defining an * alternate tclInit command before calling Tcl_Init(). */ result = Tcl_EvalEx(interp, "if {[namespace which -command tclInit] eq \"\"} {\n" " proc tclInit {} {\n" " global tcl_libPath tcl_library env tclDefaultLibrary\n" " rename tclInit {}\n" " if {[info exists tcl_library]} {\n" " set scripts {{set tcl_library}}\n" " } else {\n" " set scripts {}\n" " if {[info exists env(TCL_LIBRARY)] && ($env(TCL_LIBRARY) ne {})} {\n" " lappend scripts {set env(TCL_LIBRARY)}\n" " lappend scripts {\n" "if {[regexp ^tcl(.*)$ [file tail $env(TCL_LIBRARY)] -> tail] == 0} continue\n" "if {$tail eq [info tclversion]} continue\n" "file join [file dirname $env(TCL_LIBRARY)] tcl[info tclversion]}\n" " }\n" " lappend scripts {::tcl::zipfs::tcl_library_init}\n" " if {[info exists tclDefaultLibrary]} {\n" " lappend scripts {set tclDefaultLibrary}\n" " } else {\n" " lappend scripts {::tcl::pkgconfig get scriptdir,runtime}\n" " }\n" " lappend scripts {\n" "set parentDir [file dirname [file dirname [info nameofexecutable]]]\n" "set grandParentDir [file dirname $parentDir]\n" "file join $parentDir lib tcl[info tclversion]} \\\n" " {file join $grandParentDir lib tcl[info tclversion]} \\\n" " {file join $parentDir library} \\\n" " {file join $grandParentDir library} \\\n" " {file join $grandParentDir tcl[info tclversion] library} \\\n" " {file join $grandParentDir tcl[info patchlevel] library} \\\n" " {\n" "file join [file dirname $grandParentDir] tcl[info patchlevel] library}\n" " if {[info exists tcl_libPath]\n" " && [catch {llength $tcl_libPath} len] == 0} {\n" " for {set i 0} {$i < $len} {incr i} {\n" " lappend scripts [list lindex \\$tcl_libPath $i]\n" " }\n" " }\n" " }\n" " set dirs {}\n" " set errors {}\n" " foreach script $scripts {\n" " if {[set tcl_library [eval $script]] eq \"\"} continue\n" " set tclfile [file join $tcl_library init.tcl]\n" " if {[file exists $tclfile]} {\n" " if {[catch {uplevel #0 [list source $tclfile]} msg opts]} {\n" " append errors \"$tclfile: $msg\n\"\n" " append errors \"[dict get $opts -errorinfo]\n\"\n" " continue\n" " }\n" " unset -nocomplain tclDefaultLibrary\n" " return\n" " }\n" " lappend dirs $tcl_library\n" " }\n" " unset -nocomplain tclDefaultLibrary\n" " set msg \"Cannot find a usable init.tcl in the following directories: \n\"\n" " append msg \" $dirs\n\n\"\n" " append msg \"$errors\n\n\"\n" " append msg \"This probably means that Tcl wasn't installed properly.\n\"\n" " error $msg\n" " }\n" "}\n" "tclInit", TCL_INDEX_NONE, 0); TclpSetInitialEncodings(); end: *names = (*names)->nextPtr; return result; } /* *--------------------------------------------------------------------------- * * TclInterpInit -- * * Initializes the invoking interpreter for using the parent, child and * safe interp facilities. This is called from inside Tcl_CreateInterp(). * * Results: * Always returns TCL_OK for backwards compatibility. * * Side effects: * Adds the "interp" command to an interpreter and initializes the * interpInfoPtr field of the invoking interpreter. * *--------------------------------------------------------------------------- */ int TclInterpInit( Tcl_Interp *interp) /* Interpreter to initialize. */ { InterpInfo *interpInfoPtr; Parent *parentPtr; Child *childPtr; interpInfoPtr = (InterpInfo *) Tcl_Alloc(sizeof(InterpInfo)); ((Interp *) interp)->interpInfo = interpInfoPtr; parentPtr = &interpInfoPtr->parent; Tcl_InitHashTable(&parentPtr->childTable, TCL_STRING_KEYS); parentPtr->targetsPtr = NULL; parentPtr->idIssuer = 0; childPtr = &interpInfoPtr->child; childPtr->parentInterp = NULL; childPtr->childEntryPtr = NULL; childPtr->childInterp = interp; childPtr->interpCmd = NULL; Tcl_InitHashTable(&childPtr->aliasTable, TCL_STRING_KEYS); Tcl_NRCreateCommand(interp, "interp", Tcl_InterpObjCmd, NRInterpCmd, NULL, NULL); Tcl_CallWhenDeleted(interp, InterpInfoDeleteProc, NULL); return TCL_OK; } /* *--------------------------------------------------------------------------- * * InterpInfoDeleteProc -- * * Invoked when an interpreter is being deleted. It releases all storage * used by the parent/child/safe interpreter facilities. * * Results: * None. * * Side effects: * Cleans up storage. Sets the interpInfoPtr field of the interp to NULL. * *--------------------------------------------------------------------------- */ static void InterpInfoDeleteProc( TCL_UNUSED(void *), Tcl_Interp *interp) /* Interp being deleted. All commands for * child interps should already be deleted. */ { InterpInfo *interpInfoPtr = INTERP_INFO(interp); Child *childPtr; Parent *parentPtr; Target *targetPtr; /* * There shouldn't be any commands left. */ parentPtr = &interpInfoPtr->parent; if (parentPtr->childTable.numEntries != 0) { Tcl_Panic("InterpInfoDeleteProc: still exist commands"); } Tcl_DeleteHashTable(&parentPtr->childTable); /* * Tell any interps that have aliases to this interp that they should * delete those aliases. If the other interp was already dead, it would * have removed the target record already. */ for (targetPtr = parentPtr->targetsPtr; targetPtr != NULL; ) { Target *tmpPtr = targetPtr->nextPtr; Tcl_DeleteCommandFromToken(targetPtr->childInterp, targetPtr->childCmd); targetPtr = tmpPtr; } childPtr = &interpInfoPtr->child; if (childPtr->interpCmd != NULL) { /* * Tcl_DeleteInterp() was called on this interpreter, rather "interp * delete" or the equivalent deletion of the command in the parent. * First ensure that the cleanup callback doesn't try to delete the * interp again. */ childPtr->childInterp = NULL; Tcl_DeleteCommandFromToken(childPtr->parentInterp, childPtr->interpCmd); } /* * There shouldn't be any aliases left. */ if (childPtr->aliasTable.numEntries != 0) { Tcl_Panic("InterpInfoDeleteProc: still exist aliases"); } Tcl_DeleteHashTable(&childPtr->aliasTable); Tcl_Free(interpInfoPtr); } /* *---------------------------------------------------------------------- * * Tcl_InterpObjCmd -- * * This function is invoked to process the "interp" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_InterpObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, NRInterpCmd, clientData, objc, objv); } static int NRInterpCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Interp *childInterp; static const char *const options[] = { "alias", "aliases", "bgerror", "cancel", "children", "create", "debug", "delete", "eval", "exists", "expose", "hide", "hidden", "issafe", "invokehidden", "limit", "marktrusted", "recursionlimit", "share", #ifndef TCL_NO_DEPRECATED "slaves", #endif "target", "transfer", NULL }; static const char *const optionsNoSlaves[] = { "alias", "aliases", "bgerror", "cancel", "children", "create", "debug", "delete", "eval", "exists", "expose", "hide", "hidden", "issafe", "invokehidden", "limit", "marktrusted", "recursionlimit", "share", "target", "transfer", NULL }; enum interpOptionEnum { OPT_ALIAS, OPT_ALIASES, OPT_BGERROR, OPT_CANCEL, OPT_CHILDREN, OPT_CREATE, OPT_DEBUG, OPT_DELETE, OPT_EVAL, OPT_EXISTS, OPT_EXPOSE, OPT_HIDE, OPT_HIDDEN, OPT_ISSAFE, OPT_INVOKEHID, OPT_LIMIT, OPT_MARKTRUSTED, OPT_RECLIMIT, OPT_SHARE, #ifndef TCL_NO_DEPRECATED OPT_SLAVES, #endif OPT_TARGET, OPT_TRANSFER } index; Tcl_Size i; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(NULL, objv[1], options, NULL, 0, &index) != TCL_OK) { /* Don't report the "slaves" option as possibility */ Tcl_GetIndexFromObj(interp, objv[1], optionsNoSlaves, "option", 0, &index); return TCL_ERROR; } switch (index) { case OPT_ALIAS: { Tcl_Interp *parentInterp; if (objc < 4) { goto aliasArgs; } childInterp = GetInterp(interp, objv[2]); if (childInterp == NULL) { return TCL_ERROR; } if (objc == 4) { return AliasDescribe(interp, childInterp, objv[3]); } if ((objc == 5) && (TclGetString(objv[4])[0] == '\0')) { return AliasDelete(interp, childInterp, objv[3]); } if (objc > 5) { parentInterp = GetInterp(interp, objv[4]); if (parentInterp == NULL) { return TCL_ERROR; } return AliasCreate(interp, childInterp, parentInterp, objv[3], objv[5], objc - 6, objv + 6); } aliasArgs: Tcl_WrongNumArgs(interp, 2, objv, "childPath childCmd ?parentPath parentCmd? ?arg ...?"); return TCL_ERROR; } case OPT_ALIASES: childInterp = GetInterp2(interp, objc, objv); if (childInterp == NULL) { return TCL_ERROR; } return AliasList(interp, childInterp); case OPT_BGERROR: if (objc != 3 && objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "path ?cmdPrefix?"); return TCL_ERROR; } childInterp = GetInterp(interp, objv[2]); if (childInterp == NULL) { return TCL_ERROR; } return ChildBgerror(interp, childInterp, objc - 3, objv + 3); case OPT_CANCEL: { int flags = 0; Tcl_Obj *resultObjPtr; static const char *const cancelOptions[] = { "-unwind", "--", NULL }; enum optionCancelEnum { OPT_UNWIND, OPT_LAST } idx; for (i = 2; i < objc; i++) { if (TclGetString(objv[i])[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, objv[i], cancelOptions, "option", 0, &idx) != TCL_OK) { return TCL_ERROR; } switch (idx) { case OPT_UNWIND: /* * The evaluation stack in the target interp is to be unwound. */ flags |= TCL_CANCEL_UNWIND; break; case OPT_LAST: i++; goto endOfForLoop; } } endOfForLoop: if (i < objc - 2) { Tcl_WrongNumArgs(interp, 2, objv, "?-unwind? ?--? ?path? ?result?"); return TCL_ERROR; } /* * Did they specify a child interp to cancel the script in progress * in? If not, use the current interp. */ if (i < objc) { childInterp = GetInterp(interp, objv[i]); if (childInterp == NULL) { return TCL_ERROR; } i++; } else { childInterp = interp; } if (i < objc) { resultObjPtr = objv[i]; /* * Tcl_CancelEval removes this reference. */ Tcl_IncrRefCount(resultObjPtr); i++; } else { resultObjPtr = NULL; } return Tcl_CancelEval(childInterp, resultObjPtr, 0, flags); } case OPT_CREATE: { int last, safe; Tcl_Obj *childPtr; char buf[16 + TCL_INTEGER_SPACE]; static const char *const createOptions[] = { "-safe", "--", NULL }; enum option { OPT_SAFE, OPT_LAST } idx; safe = Tcl_IsSafe(interp); /* * Weird historical rules: "-safe" is accepted at the end, too. */ childPtr = NULL; last = 0; for (i = 2; i < objc; i++) { if ((last == 0) && (TclGetString(objv[i])[0] == '-')) { if (Tcl_GetIndexFromObj(interp, objv[i], createOptions, "option", 0, &idx) != TCL_OK) { return TCL_ERROR; } if (idx == OPT_SAFE) { safe = 1; continue; } i++; last = 1; } if (childPtr != NULL) { Tcl_WrongNumArgs(interp, 2, objv, "?-safe? ?--? ?path?"); return TCL_ERROR; } if (i < objc) { childPtr = objv[i]; } } buf[0] = '\0'; if (childPtr == NULL) { Parent *parentInfo = &INTERP_INFO(interp)->parent; Tcl_CmdInfo cmdInfo; /* * Create an anonymous interpreter -- we choose its name and the * name of the command. We check that the command name that we use * for the interpreter does not collide with an existing command * in the parent interpreter. */ do { snprintf(buf, sizeof(buf), "interp%" TCL_SIZE_MODIFIER "d", parentInfo->idIssuer++); } while (Tcl_GetCommandInfo(interp, buf, &cmdInfo)); childPtr = Tcl_NewStringObj(buf, -1); } if (ChildCreate(interp, childPtr, safe) == NULL) { Tcl_BounceRefCount(childPtr); return TCL_ERROR; } Tcl_SetObjResult(interp, childPtr); return TCL_OK; } case OPT_DEBUG: /* TIP #378 */ /* * Currently only -frame supported, otherwise ?-option ?value?? */ if (objc < 3 || objc > 5) { Tcl_WrongNumArgs(interp, 2, objv, "path ?-frame ?bool??"); return TCL_ERROR; } childInterp = GetInterp(interp, objv[2]); if (childInterp == NULL) { return TCL_ERROR; } return ChildDebugCmd(interp, childInterp, objc - 3, objv + 3); case OPT_DELETE: { InterpInfo *iiPtr; for (i = 2; i < objc; i++) { childInterp = GetInterp(interp, objv[i]); if (childInterp == NULL) { return TCL_ERROR; } else if (childInterp == interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot delete the current interpreter", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "DELETESELF", (char *)NULL); return TCL_ERROR; } iiPtr = INTERP_INFO(childInterp); Tcl_DeleteCommandFromToken(iiPtr->child.parentInterp, iiPtr->child.interpCmd); } return TCL_OK; } case OPT_EVAL: if (objc < 4) { Tcl_WrongNumArgs(interp, 2, objv, "path arg ?arg ...?"); return TCL_ERROR; } childInterp = GetInterp(interp, objv[2]); if (childInterp == NULL) { return TCL_ERROR; } return ChildEval(interp, childInterp, objc - 3, objv + 3); case OPT_EXISTS: { int exists = 1; childInterp = GetInterp2(interp, objc, objv); if (childInterp == NULL) { if (objc > 3) { return TCL_ERROR; } Tcl_ResetResult(interp); exists = 0; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(exists)); return TCL_OK; } case OPT_EXPOSE: if ((objc < 4) || (objc > 5)) { Tcl_WrongNumArgs(interp, 2, objv, "path hiddenCmdName ?cmdName?"); return TCL_ERROR; } childInterp = GetInterp(interp, objv[2]); if (childInterp == NULL) { return TCL_ERROR; } return ChildExpose(interp, childInterp, objc - 3, objv + 3); case OPT_HIDE: if ((objc < 4) || (objc > 5)) { Tcl_WrongNumArgs(interp, 2, objv, "path cmdName ?hiddenCmdName?"); return TCL_ERROR; } childInterp = GetInterp(interp, objv[2]); if (childInterp == NULL) { return TCL_ERROR; } return ChildHide(interp, childInterp, objc - 3, objv + 3); case OPT_HIDDEN: childInterp = GetInterp2(interp, objc, objv); if (childInterp == NULL) { return TCL_ERROR; } return ChildHidden(interp, childInterp); case OPT_ISSAFE: childInterp = GetInterp2(interp, objc, objv); if (childInterp == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(Tcl_IsSafe(childInterp))); return TCL_OK; case OPT_INVOKEHID: { const char *namespaceName; static const char *const hiddenOptions[] = { "-global", "-namespace", "--", NULL }; enum hiddenOption { OPT_GLOBAL, OPT_NAMESPACE, OPT_LAST } idx; namespaceName = NULL; for (i = 3; i < objc; i++) { if (TclGetString(objv[i])[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, objv[i], hiddenOptions, "option", 0, &idx) != TCL_OK) { return TCL_ERROR; } if (idx == OPT_GLOBAL) { namespaceName = "::"; } else if (idx == OPT_NAMESPACE) { if (++i == objc) { /* There must be more arguments. */ break; } else { namespaceName = TclGetString(objv[i]); } } else { i++; break; } } if (objc - i < 1) { Tcl_WrongNumArgs(interp, 2, objv, "path ?-namespace ns? ?-global? ?--? cmd ?arg ..?"); return TCL_ERROR; } childInterp = GetInterp(interp, objv[2]); if (childInterp == NULL) { return TCL_ERROR; } return ChildInvokeHidden(interp, childInterp, namespaceName, objc - i, objv + i); } case OPT_LIMIT: { static const char *const limitTypes[] = { "commands", "time", NULL }; enum LimitTypes { LIMIT_TYPE_COMMANDS, LIMIT_TYPE_TIME } limitType; if (objc < 4) { Tcl_WrongNumArgs(interp, 2, objv, "path limitType ?-option value ...?"); return TCL_ERROR; } childInterp = GetInterp(interp, objv[2]); if (childInterp == NULL) { return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[3], limitTypes, "limit type", 0, &limitType) != TCL_OK) { return TCL_ERROR; } switch (limitType) { case LIMIT_TYPE_COMMANDS: return ChildCommandLimitCmd(interp, childInterp, 4, objc,objv); case LIMIT_TYPE_TIME: return ChildTimeLimitCmd(interp, childInterp, 4, objc, objv); default: Tcl_Panic("unreachable"); return TCL_ERROR; } } case OPT_MARKTRUSTED: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "path"); return TCL_ERROR; } childInterp = GetInterp(interp, objv[2]); if (childInterp == NULL) { return TCL_ERROR; } return ChildMarkTrusted(interp, childInterp); case OPT_RECLIMIT: if (objc != 3 && objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "path ?newlimit?"); return TCL_ERROR; } childInterp = GetInterp(interp, objv[2]); if (childInterp == NULL) { return TCL_ERROR; } return ChildRecursionLimit(interp, childInterp, objc - 3, objv + 3); #ifndef TCL_NO_DEPRECATED case OPT_SLAVES: #endif case OPT_CHILDREN: { InterpInfo *iiPtr; Tcl_Obj *resultPtr; Tcl_HashEntry *hPtr; Tcl_HashSearch hashSearch; char *string; childInterp = GetInterp2(interp, objc, objv); if (childInterp == NULL) { return TCL_ERROR; } iiPtr = INTERP_INFO(childInterp); TclNewObj(resultPtr); hPtr = Tcl_FirstHashEntry(&iiPtr->parent.childTable, &hashSearch); for ( ; hPtr != NULL; hPtr = Tcl_NextHashEntry(&hashSearch)) { string = (char *) Tcl_GetHashKey(&iiPtr->parent.childTable, hPtr); Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewStringObj(string, -1)); } Tcl_SetObjResult(interp, resultPtr); return TCL_OK; } case OPT_TRANSFER: case OPT_SHARE: { Tcl_Interp *parentInterp; /* The parent of the child. */ Tcl_Channel chan; if (objc != 5) { Tcl_WrongNumArgs(interp, 2, objv, "srcPath channel destPath"); return TCL_ERROR; } parentInterp = GetInterp(interp, objv[2]); if (parentInterp == NULL) { return TCL_ERROR; } chan = Tcl_GetChannel(parentInterp, TclGetString(objv[3]), NULL); if (chan == NULL) { Tcl_TransferResult(parentInterp, TCL_OK, interp); return TCL_ERROR; } childInterp = GetInterp(interp, objv[4]); if (childInterp == NULL) { return TCL_ERROR; } Tcl_RegisterChannel(childInterp, chan); if (index == OPT_TRANSFER) { /* * When transferring, as opposed to sharing, we must unhitch the * channel from the interpreter where it started. */ if (Tcl_UnregisterChannel(parentInterp, chan) != TCL_OK) { Tcl_TransferResult(parentInterp, TCL_OK, interp); return TCL_ERROR; } } return TCL_OK; } case OPT_TARGET: { InterpInfo *iiPtr; Tcl_HashEntry *hPtr; Alias *aliasPtr; const char *aliasName; if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "path alias"); return TCL_ERROR; } childInterp = GetInterp(interp, objv[2]); if (childInterp == NULL) { return TCL_ERROR; } aliasName = TclGetString(objv[3]); iiPtr = INTERP_INFO(childInterp); hPtr = Tcl_FindHashEntry(&iiPtr->child.aliasTable, aliasName); if (hPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "alias \"%s\" in path \"%s\" not found", aliasName, TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "ALIAS", aliasName, (char *)NULL); return TCL_ERROR; } aliasPtr = (Alias *) Tcl_GetHashValue(hPtr); if (Tcl_GetInterpPath(interp, aliasPtr->targetInterp) != TCL_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "target interpreter for alias \"%s\" in path \"%s\" is " "not my descendant", aliasName, TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "TARGETSHROUDED", (char *)NULL); return TCL_ERROR; } return TCL_OK; } default: Tcl_Panic("unreachable"); return TCL_ERROR; } } /* *--------------------------------------------------------------------------- * * GetInterp2 -- * * Helper function for Tcl_InterpObjCmd() to convert the interp name * potentially specified on the command line to an Tcl_Interp. * * Results: * The return value is the interp specified on the command line, or the * interp argument itself if no interp was specified on the command line. * If the interp could not be found or the wrong number of arguments was * specified on the command line, the return value is NULL and an error * message is left in the interp's result. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static Tcl_Interp * GetInterp2( Tcl_Interp *interp, /* Default interp if no interp was specified * on the command line. */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc == 2) { return interp; } else if (objc == 3) { return GetInterp(interp, objv[2]); } else { Tcl_WrongNumArgs(interp, 2, objv, "?path?"); return NULL; } } /* *---------------------------------------------------------------------- * * Tcl_CreateAlias -- * * Creates an alias between two interpreters. * * Results: * A standard Tcl result. * * Side effects: * Creates a new alias, manipulates the result field of childInterp. * *---------------------------------------------------------------------- */ int Tcl_CreateAlias( Tcl_Interp *childInterp, /* Interpreter for source command. */ const char *childCmd, /* Command to install in child. */ Tcl_Interp *targetInterp, /* Interpreter for target command. */ const char *targetCmd, /* Name of target command. */ Tcl_Size argc, /* How many additional arguments? */ const char *const *argv) /* These are the additional args. */ { Tcl_Obj *childObjPtr, *targetObjPtr; Tcl_Obj **objv; Tcl_Size i; int result; objv = (Tcl_Obj **) TclStackAlloc(childInterp, sizeof(Tcl_Obj *) * argc); for (i = 0; i < argc; i++) { objv[i] = Tcl_NewStringObj(argv[i], -1); Tcl_IncrRefCount(objv[i]); } childObjPtr = Tcl_NewStringObj(childCmd, -1); Tcl_IncrRefCount(childObjPtr); targetObjPtr = Tcl_NewStringObj(targetCmd, -1); Tcl_IncrRefCount(targetObjPtr); result = AliasCreate(childInterp, childInterp, targetInterp, childObjPtr, targetObjPtr, argc, objv); for (i = 0; i < argc; i++) { Tcl_DecrRefCount(objv[i]); } TclStackFree(childInterp, objv); Tcl_DecrRefCount(targetObjPtr); Tcl_DecrRefCount(childObjPtr); return result; } /* *---------------------------------------------------------------------- * * Tcl_CreateAliasObj -- * * Object version: Creates an alias between two interpreters. * * Results: * A standard Tcl result. * * Side effects: * Creates a new alias. * *---------------------------------------------------------------------- */ int Tcl_CreateAliasObj( Tcl_Interp *childInterp, /* Interpreter for source command. */ const char *childCmd, /* Command to install in child. */ Tcl_Interp *targetInterp, /* Interpreter for target command. */ const char *targetCmd, /* Name of target command. */ Tcl_Size objc, /* How many additional arguments? */ Tcl_Obj *const objv[]) /* Argument vector. */ { Tcl_Obj *childObjPtr, *targetObjPtr; int result; childObjPtr = Tcl_NewStringObj(childCmd, -1); Tcl_IncrRefCount(childObjPtr); targetObjPtr = Tcl_NewStringObj(targetCmd, -1); Tcl_IncrRefCount(targetObjPtr); result = AliasCreate(childInterp, childInterp, targetInterp, childObjPtr, targetObjPtr, objc, objv); Tcl_DecrRefCount(childObjPtr); Tcl_DecrRefCount(targetObjPtr); return result; } /* *---------------------------------------------------------------------- * * Tcl_GetAliasObj -- * * Object version: Gets information about an alias. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_GetAliasObj( Tcl_Interp *interp, /* Interp to start search from. */ const char *aliasName, /* Name of alias to find. */ Tcl_Interp **targetInterpPtr, /* (Return) target interpreter. */ const char **targetCmdPtr, /* (Return) name of target command. */ Tcl_Size *objcPtr, /* (Return) count of addnl args. */ Tcl_Obj ***objvPtr) /* (Return) additional args. */ { InterpInfo *iiPtr = INTERP_INFO(interp); Tcl_HashEntry *hPtr; Alias *aliasPtr; Tcl_Size objc; Tcl_Obj **objv; hPtr = Tcl_FindHashEntry(&iiPtr->child.aliasTable, aliasName); if (hPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "alias \"%s\" not found", aliasName)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "ALIAS", aliasName, (char *)NULL); return TCL_ERROR; } aliasPtr = (Alias *) Tcl_GetHashValue(hPtr); objc = aliasPtr->objc; objv = &aliasPtr->objPtr; if (targetInterpPtr != NULL) { *targetInterpPtr = aliasPtr->targetInterp; } if (targetCmdPtr != NULL) { *targetCmdPtr = TclGetString(objv[0]); } if (objcPtr != NULL) { *objcPtr = objc - 1; } if (objvPtr != NULL) { *objvPtr = objv + 1; } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclPreventAliasLoop -- * * When defining an alias or renaming a command, prevent an alias loop * from being formed. * * Results: * A standard Tcl object result. * * Side effects: * If TCL_ERROR is returned, the function also stores an error message in * the interpreter's result object. * * NOTE: * This function is public internal (instead of being static to this * file) because it is also used from TclRenameCommand. * *---------------------------------------------------------------------- */ int TclPreventAliasLoop( Tcl_Interp *interp, /* Interp in which to report errors. */ Tcl_Interp *cmdInterp, /* Interp in which the command is being * defined. */ Tcl_Command cmd) /* Tcl command we are attempting to define. */ { Command *cmdPtr = (Command *) cmd; Alias *aliasPtr, *nextAliasPtr; Tcl_Command aliasCmd; Command *aliasCmdPtr; /* * If we are not creating or renaming an alias, then it is always OK to * create or rename the command. */ if (cmdPtr->objProc != TclAliasObjCmd && cmdPtr->objProc != TclLocalAliasObjCmd) { return TCL_OK; } /* * OK, we are dealing with an alias, so traverse the chain of aliases. If * we encounter the alias we are defining (or renaming to) any in the * chain then we have a loop. */ aliasPtr = (Alias *) cmdPtr->objClientData; nextAliasPtr = aliasPtr; while (1) { Tcl_Obj *cmdNamePtr; /* * If the target of the next alias in the chain is the same as the * source alias, we have a loop. */ if (Tcl_InterpDeleted(nextAliasPtr->targetInterp)) { /* * The child interpreter can be deleted while creating the alias. * [Bug #641195] */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot define or rename alias \"%s\": interpreter deleted", Tcl_GetCommandName(cmdInterp, cmd))); return TCL_ERROR; } cmdNamePtr = nextAliasPtr->objPtr; aliasCmd = Tcl_FindCommand(nextAliasPtr->targetInterp, TclGetString(cmdNamePtr), Tcl_GetGlobalNamespace(nextAliasPtr->targetInterp), /*flags*/ 0); if (aliasCmd == NULL) { return TCL_OK; } aliasCmdPtr = (Command *) aliasCmd; if (aliasCmdPtr == cmdPtr) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot define or rename alias \"%s\": would create a loop", Tcl_GetCommandName(cmdInterp, cmd))); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "ALIASLOOP", (char *)NULL); return TCL_ERROR; } /* * Otherwise, follow the chain one step further. See if the target * command is an alias - if so, follow the loop to its target command. * Otherwise we do not have a loop. */ if (aliasCmdPtr->objProc != TclAliasObjCmd && aliasCmdPtr->objProc != TclLocalAliasObjCmd) { return TCL_OK; } nextAliasPtr = (Alias *) aliasCmdPtr->objClientData; } } /* *---------------------------------------------------------------------- * * AliasCreate -- * * Helper function to do the work to actually create an alias. * * Results: * A standard Tcl result. * * Side effects: * An alias command is created and entered into the alias table for the * child interpreter. * *---------------------------------------------------------------------- */ static int AliasCreate( Tcl_Interp *interp, /* Interp for error reporting. */ Tcl_Interp *childInterp, /* Interp where alias cmd will live or from * which alias will be deleted. */ Tcl_Interp *parentInterp, /* Interp in which target command will be * invoked. */ Tcl_Obj *namePtr, /* Name of alias cmd. */ Tcl_Obj *targetCmdPtr, /* Name of target cmd. */ Tcl_Size objc, /* Additional arguments to store */ Tcl_Obj *const objv[]) /* with alias. */ { Alias *aliasPtr; Tcl_HashEntry *hPtr; Target *targetPtr; Child *childPtr; Parent *parentPtr; Tcl_Obj **prefv; int isNew; Tcl_Size i; aliasPtr = (Alias *) Tcl_Alloc(sizeof(Alias) + objc * sizeof(Tcl_Obj *)); aliasPtr->token = namePtr; Tcl_IncrRefCount(aliasPtr->token); aliasPtr->targetInterp = parentInterp; aliasPtr->objc = objc + 1; prefv = &aliasPtr->objPtr; *prefv = targetCmdPtr; Tcl_IncrRefCount(targetCmdPtr); for (i = 0; i < objc; i++) { *(++prefv) = objv[i]; Tcl_IncrRefCount(objv[i]); } Tcl_Preserve(childInterp); Tcl_Preserve(parentInterp); if (childInterp == parentInterp) { aliasPtr->childCmd = Tcl_NRCreateCommand(childInterp, TclGetString(namePtr), TclLocalAliasObjCmd, AliasNRCmd, aliasPtr, AliasObjCmdDeleteProc); } else { aliasPtr->childCmd = Tcl_CreateObjCommand(childInterp, TclGetString(namePtr), TclAliasObjCmd, aliasPtr, AliasObjCmdDeleteProc); } if (TclPreventAliasLoop(interp, childInterp, aliasPtr->childCmd) != TCL_OK) { /* * Found an alias loop! The last call to Tcl_CreateObjCommand made the * alias point to itself. Delete the command and its alias record. Be * careful to wipe out its client data first, so the command doesn't * try to delete itself. */ Command *cmdPtr; Tcl_DecrRefCount(aliasPtr->token); Tcl_DecrRefCount(targetCmdPtr); for (i = 0; i < objc; i++) { Tcl_DecrRefCount(objv[i]); } cmdPtr = (Command *) aliasPtr->childCmd; cmdPtr->clientData = NULL; cmdPtr->deleteProc = NULL; cmdPtr->deleteData = NULL; Tcl_DeleteCommandFromToken(childInterp, aliasPtr->childCmd); Tcl_Free(aliasPtr); /* * The result was already set by TclPreventAliasLoop. */ Tcl_Release(childInterp); Tcl_Release(parentInterp); return TCL_ERROR; } /* * Make an entry in the alias table. If it already exists, retry. */ childPtr = &INTERP_INFO(childInterp)->child; while (1) { Tcl_Obj *newToken; const char *string; string = TclGetString(aliasPtr->token); hPtr = Tcl_CreateHashEntry(&childPtr->aliasTable, string, &isNew); if (isNew != 0) { break; } /* * The alias name cannot be used as unique token, it is already taken. * We can produce a unique token by prepending "::" repeatedly. This * algorithm is a stop-gap to try to maintain the command name as * token for most use cases, fearful of possible backwards compat * problems. A better algorithm would produce unique tokens that need * not be related to the command name. * * ATTENTION: the tests in interp.test and possibly safe.test depend * on the precise definition of these tokens. */ TclNewLiteralStringObj(newToken, "::"); Tcl_AppendObjToObj(newToken, aliasPtr->token); Tcl_DecrRefCount(aliasPtr->token); aliasPtr->token = newToken; Tcl_IncrRefCount(aliasPtr->token); } aliasPtr->aliasEntryPtr = hPtr; Tcl_SetHashValue(hPtr, aliasPtr); /* * Create the new command. We must do it after deleting any old command, * because the alias may be pointing at a renamed alias, as in: * * interp alias {} foo {} bar # Create an alias "foo" * rename foo zop # Now rename the alias * interp alias {} foo {} zop # Now recreate "foo"... */ targetPtr = (Target *) Tcl_Alloc(sizeof(Target)); targetPtr->childCmd = aliasPtr->childCmd; targetPtr->childInterp = childInterp; parentPtr = &INTERP_INFO(parentInterp)->parent; targetPtr->nextPtr = parentPtr->targetsPtr; targetPtr->prevPtr = NULL; if (parentPtr->targetsPtr != NULL) { parentPtr->targetsPtr->prevPtr = targetPtr; } parentPtr->targetsPtr = targetPtr; aliasPtr->targetPtr = targetPtr; Tcl_SetObjResult(interp, aliasPtr->token); Tcl_Release(childInterp); Tcl_Release(parentInterp); return TCL_OK; } /* *---------------------------------------------------------------------- * * AliasDelete -- * * Deletes the given alias from the child interpreter given. * * Results: * A standard Tcl result. * * Side effects: * Deletes the alias from the child interpreter. * *---------------------------------------------------------------------- */ static int AliasDelete( Tcl_Interp *interp, /* Interpreter for result & errors. */ Tcl_Interp *childInterp, /* Interpreter containing alias. */ Tcl_Obj *namePtr) /* Name of alias to delete. */ { Child *childPtr; Alias *aliasPtr; Tcl_HashEntry *hPtr; /* * If the alias has been renamed in the child, the parent can still use * the original name (with which it was created) to find the alias to * delete it. */ childPtr = &INTERP_INFO(childInterp)->child; hPtr = Tcl_FindHashEntry(&childPtr->aliasTable, TclGetString(namePtr)); if (hPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "alias \"%s\" not found", TclGetString(namePtr))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "ALIAS", TclGetString(namePtr), (char *)NULL); return TCL_ERROR; } aliasPtr = (Alias *) Tcl_GetHashValue(hPtr); Tcl_DeleteCommandFromToken(childInterp, aliasPtr->childCmd); return TCL_OK; } /* *---------------------------------------------------------------------- * * AliasDescribe -- * * Sets the interpreter's result object to a Tcl list describing the * given alias in the given interpreter: its target command and the * additional arguments to prepend to any invocation of the alias. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int AliasDescribe( Tcl_Interp *interp, /* Interpreter for result & errors. */ Tcl_Interp *childInterp, /* Interpreter containing alias. */ Tcl_Obj *namePtr) /* Name of alias to describe. */ { Child *childPtr; Tcl_HashEntry *hPtr; Alias *aliasPtr; Tcl_Obj *prefixPtr; /* * If the alias has been renamed in the child, the parent can still use * the original name (with which it was created) to find the alias to * describe it. */ childPtr = &INTERP_INFO(childInterp)->child; hPtr = Tcl_FindHashEntry(&childPtr->aliasTable, TclGetString(namePtr)); if (hPtr == NULL) { return TCL_OK; } aliasPtr = (Alias *) Tcl_GetHashValue(hPtr); prefixPtr = Tcl_NewListObj(aliasPtr->objc, &aliasPtr->objPtr); Tcl_SetObjResult(interp, prefixPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * AliasList -- * * Computes a list of aliases defined in a child interpreter. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int AliasList( Tcl_Interp *interp, /* Interp for data return. */ Tcl_Interp *childInterp) /* Interp whose aliases to compute. */ { Tcl_HashEntry *entryPtr; Tcl_HashSearch hashSearch; Tcl_Obj *resultPtr; Alias *aliasPtr; Child *childPtr; TclNewObj(resultPtr); childPtr = &INTERP_INFO(childInterp)->child; entryPtr = Tcl_FirstHashEntry(&childPtr->aliasTable, &hashSearch); for ( ; entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&hashSearch)) { aliasPtr = (Alias *) Tcl_GetHashValue(entryPtr); Tcl_ListObjAppendElement(NULL, resultPtr, aliasPtr->token); } Tcl_SetObjResult(interp, resultPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclAliasObjCmd, TclLocalAliasObjCmd -- * * This is the function that services invocations of aliases in a child * interpreter. One such command exists for each alias. When invoked, * this function redirects the invocation to the target command in the * parent interpreter as designated by the Alias record associated with * this command. * * TclLocalAliasObjCmd is a stripped down version used when the source * and target interpreters of the alias are the same. That lets a number * of safety precautions be avoided: the state is much more precisely * known. * * Results: * A standard Tcl result. * * Side effects: * Causes forwarding of the invocation; all possible side effects may * occur as a result of invoking the command to which the invocation is * forwarded. * *---------------------------------------------------------------------- */ static int AliasNRCmd( void *clientData, /* Alias record. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument vector. */ { Alias *aliasPtr = (Alias *) clientData; Tcl_Size prefc, cmdc, i; Tcl_Obj **prefv, **cmdv; Tcl_Obj *listPtr; ListRep listRep; int flags = TCL_EVAL_INVOKE; /* * Append the arguments to the command prefix and invoke the command in * the target interp's global namespace. */ prefc = aliasPtr->objc; prefv = &aliasPtr->objPtr; cmdc = prefc + objc - 1; /* TODO - encapsulate this into tclListObj.c */ listPtr = Tcl_NewListObj(cmdc, NULL); ListObjGetRep(listPtr, &listRep); cmdv = ListRepElementsBase(&listRep); listRep.storePtr->numUsed = cmdc; if (listRep.spanPtr) { listRep.spanPtr->spanStart = listRep.storePtr->firstUsed; listRep.spanPtr->spanLength = listRep.storePtr->numUsed; } prefv = &aliasPtr->objPtr; memcpy(cmdv, prefv, prefc * sizeof(Tcl_Obj *)); memcpy(cmdv+prefc, objv+1, (objc-1) * sizeof(Tcl_Obj *)); for (i=0; itargetInterp; int result; Tcl_Size prefc, cmdc, i; Tcl_Obj **prefv, **cmdv; Tcl_Obj *cmdArr[ALIAS_CMDV_PREALLOC]; int isRootEnsemble; /* * Append the arguments to the command prefix and invoke the command in * the target interp's global namespace. */ prefc = aliasPtr->objc; prefv = &aliasPtr->objPtr; cmdc = prefc + objc - 1; if (cmdc <= ALIAS_CMDV_PREALLOC) { cmdv = cmdArr; } else { cmdv = (Tcl_Obj **) TclStackAlloc(interp, cmdc * sizeof(Tcl_Obj *)); } memcpy(cmdv, prefv, prefc * sizeof(Tcl_Obj *)); memcpy(cmdv+prefc, objv+1, (objc-1) * sizeof(Tcl_Obj *)); Tcl_ResetResult(targetInterp); for (i=0; iobjc; prefv = &aliasPtr->objPtr; cmdc = prefc + objc - 1; if (cmdc <= ALIAS_CMDV_PREALLOC) { cmdv = cmdArr; } else { cmdv = (Tcl_Obj **) TclStackAlloc(interp, cmdc * sizeof(Tcl_Obj *)); } memcpy(cmdv, prefv, prefc * sizeof(Tcl_Obj *)); memcpy(cmdv+prefc, objv+1, (objc-1) * sizeof(Tcl_Obj *)); for (i=0; itoken); objv = &aliasPtr->objPtr; for (i = 0; i < aliasPtr->objc; i++) { Tcl_DecrRefCount(objv[i]); } Tcl_DeleteHashEntry(aliasPtr->aliasEntryPtr); /* * Splice the target record out of the target interpreter's parent list. */ targetPtr = aliasPtr->targetPtr; if (targetPtr->prevPtr != NULL) { targetPtr->prevPtr->nextPtr = targetPtr->nextPtr; } else { Parent *parentPtr = &INTERP_INFO(aliasPtr->targetInterp)->parent; parentPtr->targetsPtr = targetPtr->nextPtr; } if (targetPtr->nextPtr != NULL) { targetPtr->nextPtr->prevPtr = targetPtr->prevPtr; } Tcl_Free(targetPtr); Tcl_Free(aliasPtr); } /* *---------------------------------------------------------------------- * * Tcl_CreateChild -- * * Creates a child interpreter. The childPath argument denotes the name * of the new child relative to the current interpreter; the child is a * direct descendant of the one-before-last component of the path, * e.g. it is a descendant of the current interpreter if the childPath * argument contains only one component. Optionally makes the child * interpreter safe. * * Results: * Returns the interpreter structure created, or NULL if an error * occurred. * * Side effects: * Creates a new interpreter and a new interpreter object command in the * interpreter indicated by the childPath argument. * *---------------------------------------------------------------------- */ Tcl_Interp * Tcl_CreateChild( Tcl_Interp *interp, /* Interpreter to start search at. */ const char *childPath, /* Name of child to create. */ int isSafe) /* Should new child be "safe" ? */ { Tcl_Obj *pathPtr; Tcl_Interp *childInterp; pathPtr = Tcl_NewStringObj(childPath, -1); childInterp = ChildCreate(interp, pathPtr, isSafe); Tcl_DecrRefCount(pathPtr); return childInterp; } /* *---------------------------------------------------------------------- * * Tcl_GetChild -- * * Finds a child interpreter by its path name. * * Results: * Returns a Tcl_Interp * for the named interpreter or NULL if not found. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Interp * Tcl_GetChild( Tcl_Interp *interp, /* Interpreter to start search from. */ const char *childPath) /* Path of child to find. */ { Tcl_Obj *pathPtr; Tcl_Interp *childInterp; pathPtr = Tcl_NewStringObj(childPath, -1); childInterp = GetInterp(interp, pathPtr); Tcl_DecrRefCount(pathPtr); return childInterp; } /* *---------------------------------------------------------------------- * * Tcl_GetParent -- * * Finds the parent interpreter of a child interpreter. * * Results: * Returns a Tcl_Interp * for the parent interpreter or NULL if none. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Interp * Tcl_GetParent( Tcl_Interp *interp) /* Get the parent of this interpreter. */ { Child *childPtr; /* Child record of this interpreter. */ if (interp == NULL) { return NULL; } childPtr = &INTERP_INFO(interp)->child; return childPtr->parentInterp; } /* *---------------------------------------------------------------------- * * TclSetChildCancelFlags -- * * This function marks all child interpreters belonging to a given * interpreter as being canceled or not canceled, depending on the * provided flags. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclSetChildCancelFlags( Tcl_Interp *interp, /* Set cancel flags of this interpreter. */ int flags, /* Collection of OR-ed bits that control * the cancellation of the script. Only * TCL_CANCEL_UNWIND is currently * supported. */ int force) /* Non-zero to ignore numLevels for the purpose * of resetting the cancellation flags. */ { Parent *parentPtr; /* Parent record of given interpreter. */ Tcl_HashEntry *hPtr; /* Search element. */ Tcl_HashSearch hashSearch; /* Search variable. */ Child *childPtr; /* Child record of interpreter. */ Interp *iPtr; if (interp == NULL) { return; } flags &= (CANCELED | TCL_CANCEL_UNWIND); parentPtr = &INTERP_INFO(interp)->parent; hPtr = Tcl_FirstHashEntry(&parentPtr->childTable, &hashSearch); for ( ; hPtr != NULL; hPtr = Tcl_NextHashEntry(&hashSearch)) { childPtr = (Child *) Tcl_GetHashValue(hPtr); iPtr = (Interp *) childPtr->childInterp; if (iPtr == NULL) { continue; } if (flags == 0) { TclResetCancellation((Tcl_Interp *) iPtr, force); } else { TclSetCancelFlags(iPtr, flags); } /* * Now, recursively handle this for the children of this child * interpreter. */ TclSetChildCancelFlags((Tcl_Interp *) iPtr, flags, force); } } /* *---------------------------------------------------------------------- * * Tcl_GetInterpPath -- * * Sets the result of the asking interpreter to a proper Tcl list * containing the names of interpreters between the asking and target * interpreters. The target interpreter must be either the same as the * asking interpreter or one of its children (including recursively). * * Results: * TCL_OK if the target interpreter is the same as, or a descendant of, * the asking interpreter; TCL_ERROR else. This way one can distinguish * between the case where the asking and target interps are the same (an * empty list is the result, and TCL_OK is returned) and when the target * is not a descendant of the asking interpreter (in which case the Tcl * result is an error message and the function returns TCL_ERROR). * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_GetInterpPath( Tcl_Interp *interp, /* Interpreter to start search from. */ Tcl_Interp *targetInterp) /* Interpreter to find. */ { InterpInfo *iiPtr; if (targetInterp == interp) { Tcl_SetObjResult(interp, Tcl_NewObj()); return TCL_OK; } if (targetInterp == NULL) { return TCL_ERROR; } iiPtr = INTERP_INFO(targetInterp); if (Tcl_GetInterpPath(interp, iiPtr->child.parentInterp) != TCL_OK) { return TCL_ERROR; } Tcl_ListObjAppendElement(NULL, Tcl_GetObjResult(interp), Tcl_NewStringObj((const char *) Tcl_GetHashKey(&iiPtr->parent.childTable, iiPtr->child.childEntryPtr), -1)); return TCL_OK; } /* *---------------------------------------------------------------------- * * GetInterp -- * * Helper function to find a child interpreter given a pathname. * * Results: * Returns the child interpreter known by that name in the calling * interpreter, or NULL if no interpreter known by that name exists. * * Side effects: * Assigns to the pointer variable passed in, if not NULL. * *---------------------------------------------------------------------- */ static Tcl_Interp * GetInterp( Tcl_Interp *interp, /* Interp. to start search from. */ Tcl_Obj *pathPtr) /* List object containing name of interp. to * be found. */ { Tcl_HashEntry *hPtr; /* Search element. */ Child *childPtr; /* Interim child record. */ Tcl_Obj **objv; Tcl_Size objc, i; Tcl_Interp *searchInterp; /* Interim storage for interp. to find. */ InterpInfo *parentInfoPtr; if (TclListObjGetElements(interp, pathPtr, &objc, &objv) != TCL_OK) { return NULL; } searchInterp = interp; for (i = 0; i < objc; i++) { parentInfoPtr = INTERP_INFO(searchInterp); hPtr = Tcl_FindHashEntry(&parentInfoPtr->parent.childTable, TclGetString(objv[i])); if (hPtr == NULL) { searchInterp = NULL; break; } childPtr = (Child *) Tcl_GetHashValue(hPtr); searchInterp = childPtr->childInterp; if (searchInterp == NULL) { break; } } if (searchInterp == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not find interpreter \"%s\"", TclGetString(pathPtr))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "INTERP", TclGetString(pathPtr), (char *)NULL); } return searchInterp; } /* *---------------------------------------------------------------------- * * ChildBgerror -- * * Helper function to set/query the background error handling command * prefix of an interp * * Results: * A standard Tcl result. * * Side effects: * When (objc == 1), childInterp will be set to a new background handler * of objv[0]. * *---------------------------------------------------------------------- */ static int ChildBgerror( Tcl_Interp *interp, /* Interp for error return. */ Tcl_Interp *childInterp, /* Interp in which limit is set/queried. */ Tcl_Size objc, /* Set or Query. */ Tcl_Obj *const objv[]) /* Argument strings. */ { if (objc) { Tcl_Size length; if (TCL_ERROR == TclListObjLength(NULL, objv[0], &length) || (length < 1)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cmdPrefix must be list of length >= 1", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "BGERRORFORMAT", (char *)NULL); return TCL_ERROR; } TclSetBgErrorHandler(childInterp, objv[0]); } Tcl_SetObjResult(interp, TclGetBgErrorHandler(childInterp)); return TCL_OK; } /* *---------------------------------------------------------------------- * * ChildCreate -- * * Helper function to do the actual work of creating a child interp and * new object command. Also optionally makes the new child interpreter * "safe". * * Results: * Returns the new Tcl_Interp * if successful or NULL if not. If failed, * the result of the invoking interpreter contains an error message. * * Side effects: * Creates a new child interpreter and a new object command. * *---------------------------------------------------------------------- */ static Tcl_Interp * ChildCreate( Tcl_Interp *interp, /* Interp. to start search from. */ Tcl_Obj *pathPtr, /* Path (name) of child to create. */ int safe) /* Should we make it "safe"? */ { Tcl_Interp *parentInterp, *childInterp; Child *childPtr; InterpInfo *parentInfoPtr; Tcl_HashEntry *hPtr; const char *path; int isNew; Tcl_Size objc; Tcl_Obj **objv; if (TclListObjGetElements(interp, pathPtr, &objc, &objv) != TCL_OK) { return NULL; } if (objc < 2) { parentInterp = interp; path = TclGetString(pathPtr); } else { Tcl_Obj *objPtr; objPtr = Tcl_NewListObj(objc - 1, objv); parentInterp = GetInterp(interp, objPtr); Tcl_DecrRefCount(objPtr); if (parentInterp == NULL) { return NULL; } path = TclGetString(objv[objc - 1]); } if (safe == 0) { safe = Tcl_IsSafe(parentInterp); } parentInfoPtr = INTERP_INFO(parentInterp); hPtr = Tcl_CreateHashEntry(&parentInfoPtr->parent.childTable, path, &isNew); if (isNew == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "interpreter named \"%s\" already exists, cannot create", path)); return NULL; } childInterp = Tcl_CreateInterp(); childPtr = &INTERP_INFO(childInterp)->child; childPtr->parentInterp = parentInterp; childPtr->childEntryPtr = hPtr; childPtr->childInterp = childInterp; childPtr->interpCmd = Tcl_NRCreateCommand(parentInterp, path, TclChildObjCmd, NRChildCmd, childInterp, ChildObjCmdDeleteProc); Tcl_InitHashTable(&childPtr->aliasTable, TCL_STRING_KEYS); Tcl_SetHashValue(hPtr, childPtr); Tcl_SetVar2(childInterp, "tcl_interactive", NULL, "0", TCL_GLOBAL_ONLY); /* * Inherit the recursion limit. */ ((Interp *) childInterp)->maxNestingDepth = ((Interp *) parentInterp)->maxNestingDepth; if (safe) { MakeSafe(childInterp); } else { if (Tcl_Init(childInterp) == TCL_ERROR) { goto error; } /* * This will create the "memory" command in child interpreters if we * compiled with TCL_MEM_DEBUG, otherwise it does nothing. */ Tcl_InitMemory(childInterp); } /* * Inherit the TIP#143 limits. */ InheritLimitsFromParent(childInterp, parentInterp); /* * The [clock] command presents a safe API, but uses unsafe features in * its implementation. This means it has to be implemented in safe interps * as an alias to a version in the (trusted) parent. */ if (safe) { Tcl_Obj *clockObj; int status; TclNewLiteralStringObj(clockObj, "clock"); Tcl_IncrRefCount(clockObj); status = AliasCreate(interp, childInterp, parentInterp, clockObj, clockObj, 0, NULL); Tcl_DecrRefCount(clockObj); if (status != TCL_OK) { goto error2; } } return childInterp; error: Tcl_TransferResult(childInterp, TCL_ERROR, interp); error2: Tcl_DeleteInterp(childInterp); return NULL; } /* *---------------------------------------------------------------------- * * TclChildObjCmd -- * * Command to manipulate an interpreter, e.g. to send commands to it to * be evaluated. One such command exists for each child interpreter. * * Results: * A standard Tcl result. * * Side effects: * See user documentation for details. * *---------------------------------------------------------------------- */ int TclChildObjCmd( void *clientData, /* Child interpreter. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, NRChildCmd, clientData, objc, objv); } static int NRChildCmd( void *clientData, /* Child interpreter. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Interp *childInterp = (Tcl_Interp *) clientData; static const char *const options[] = { "alias", "aliases", "bgerror", "debug", "eval", "expose", "hide", "hidden", "issafe", "invokehidden", "limit", "marktrusted", "recursionlimit", NULL }; enum childCmdOptionsEnum { OPT_ALIAS, OPT_ALIASES, OPT_BGERROR, OPT_DEBUG, OPT_EVAL, OPT_EXPOSE, OPT_HIDE, OPT_HIDDEN, OPT_ISSAFE, OPT_INVOKEHIDDEN, OPT_LIMIT, OPT_MARKTRUSTED, OPT_RECLIMIT } index; if (childInterp == NULL) { Tcl_Panic("TclChildObjCmd: interpreter has been deleted"); } if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], options, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case OPT_ALIAS: if (objc > 2) { if (objc == 3) { return AliasDescribe(interp, childInterp, objv[2]); } if (TclGetString(objv[3])[0] == '\0') { if (objc == 4) { return AliasDelete(interp, childInterp, objv[2]); } } else { return AliasCreate(interp, childInterp, interp, objv[2], objv[3], objc - 4, objv + 4); } } Tcl_WrongNumArgs(interp, 2, objv, "aliasName ?targetName? ?arg ...?"); return TCL_ERROR; case OPT_ALIASES: if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } return AliasList(interp, childInterp); case OPT_BGERROR: if (objc != 2 && objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "?cmdPrefix?"); return TCL_ERROR; } return ChildBgerror(interp, childInterp, objc - 2, objv + 2); case OPT_DEBUG: /* * TIP #378 * Currently only -frame supported, otherwise ?-option ?value? ...? */ if (objc > 4) { Tcl_WrongNumArgs(interp, 2, objv, "?-frame ?bool??"); return TCL_ERROR; } return ChildDebugCmd(interp, childInterp, objc - 2, objv + 2); case OPT_EVAL: if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "arg ?arg ...?"); return TCL_ERROR; } return ChildEval(interp, childInterp, objc - 2, objv + 2); case OPT_EXPOSE: if ((objc < 3) || (objc > 4)) { Tcl_WrongNumArgs(interp, 2, objv, "hiddenCmdName ?cmdName?"); return TCL_ERROR; } return ChildExpose(interp, childInterp, objc - 2, objv + 2); case OPT_HIDE: if ((objc < 3) || (objc > 4)) { Tcl_WrongNumArgs(interp, 2, objv, "cmdName ?hiddenCmdName?"); return TCL_ERROR; } return ChildHide(interp, childInterp, objc - 2, objv + 2); case OPT_HIDDEN: if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } return ChildHidden(interp, childInterp); case OPT_ISSAFE: if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(Tcl_IsSafe(childInterp))); return TCL_OK; case OPT_INVOKEHIDDEN: { Tcl_Size i; const char *namespaceName; static const char *const hiddenOptions[] = { "-global", "-namespace", "--", NULL }; enum hiddenOption { OPT_GLOBAL, OPT_NAMESPACE, OPT_LAST } idx; namespaceName = NULL; for (i = 2; i < objc; i++) { if (TclGetString(objv[i])[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, objv[i], hiddenOptions, "option", 0, &idx) != TCL_OK) { return TCL_ERROR; } if (idx == OPT_GLOBAL) { namespaceName = "::"; } else if (idx == OPT_NAMESPACE) { if (++i == objc) { /* There must be more arguments. */ break; } namespaceName = TclGetString(objv[i]); } else { i++; break; } } if (objc - i < 1) { Tcl_WrongNumArgs(interp, 2, objv, "?-namespace ns? ?-global? ?--? cmd ?arg ..?"); return TCL_ERROR; } return ChildInvokeHidden(interp, childInterp, namespaceName, objc - i, objv + i); } case OPT_LIMIT: { static const char *const limitTypes[] = { "commands", "time", NULL }; enum LimitTypes { LIMIT_TYPE_COMMANDS, LIMIT_TYPE_TIME } limitType; if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "limitType ?-option value ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[2], limitTypes, "limit type", 0, &limitType) != TCL_OK) { return TCL_ERROR; } switch (limitType) { case LIMIT_TYPE_COMMANDS: return ChildCommandLimitCmd(interp, childInterp, 3, objc,objv); case LIMIT_TYPE_TIME: return ChildTimeLimitCmd(interp, childInterp, 3, objc, objv); } } break; case OPT_MARKTRUSTED: if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } return ChildMarkTrusted(interp, childInterp); case OPT_RECLIMIT: if (objc != 2 && objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "?newlimit?"); return TCL_ERROR; } return ChildRecursionLimit(interp, childInterp, objc - 2, objv + 2); } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * ChildObjCmdDeleteProc -- * * Invoked when an object command for a child interpreter is deleted; * cleans up all state associated with the child interpreter and destroys * the child interpreter. * * Results: * None. * * Side effects: * Cleans up all state associated with the child interpreter and destroys * the child interpreter. * *---------------------------------------------------------------------- */ static void ChildObjCmdDeleteProc( void *clientData) /* The ChildRecord for the command. */ { Child *childPtr; /* Interim storage for Child record. */ Tcl_Interp *childInterp = (Tcl_Interp *) clientData; /* And for a child interp. */ childPtr = &INTERP_INFO(childInterp)->child; /* * Unlink the child from its parent interpreter. */ Tcl_DeleteHashEntry(childPtr->childEntryPtr); /* * Set to NULL so that when the InterpInfo is cleaned up in the child it * does not try to delete the command causing all sorts of grief. See * ChildRecordDeleteProc(). */ childPtr->interpCmd = NULL; if (childPtr->childInterp != NULL) { Tcl_DeleteInterp(childPtr->childInterp); } } /* *---------------------------------------------------------------------- * * ChildDebugCmd -- TIP #378 * * Helper function to handle 'debug' command in a child interpreter. * * Results: * A standard Tcl result. * * Side effects: * May modify INTERP_DEBUG_FRAME flag in the child. * *---------------------------------------------------------------------- */ static int ChildDebugCmd( Tcl_Interp *interp, /* Interp for error return. */ Tcl_Interp *childInterp, /* The child interpreter in which command * will be evaluated. */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const debugTypes[] = { "-frame", NULL }; enum DebugTypes { DEBUG_TYPE_FRAME }; int debugType; Interp *iPtr; Tcl_Obj *resultPtr; iPtr = (Interp *) childInterp; if (objc == 0) { TclNewObj(resultPtr); Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewStringObj("-frame", -1)); Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewBooleanObj(iPtr->flags & INTERP_DEBUG_FRAME)); Tcl_SetObjResult(interp, resultPtr); } else { if (Tcl_GetIndexFromObj(interp, objv[0], debugTypes, "debug option", 0, &debugType) != TCL_OK) { return TCL_ERROR; } if (debugType == DEBUG_TYPE_FRAME) { if (objc == 2) { /* set */ if (Tcl_GetBooleanFromObj(interp, objv[1], &debugType) != TCL_OK) { return TCL_ERROR; } /* * Quietly ignore attempts to disable interp debugging. This * is a one-way switch as frame debug info is maintained in a * stack that must be consistent once turned on. */ if (debugType) { iPtr->flags |= INTERP_DEBUG_FRAME; } } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(iPtr->flags & INTERP_DEBUG_FRAME)); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * ChildEval -- * * Helper function to evaluate a command in a child interpreter. * * Results: * A standard Tcl result. * * Side effects: * Whatever the command does. * *---------------------------------------------------------------------- */ static int ChildEval( Tcl_Interp *interp, /* Interp for error return. */ Tcl_Interp *childInterp, /* The child interpreter in which command * will be evaluated. */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int result; /* * TIP #285: If necessary, reset the cancellation flags for the child * interpreter now; otherwise, canceling a script in a parent interpreter * can result in a situation where a child interpreter can no longer * evaluate any scripts unless somebody calls the TclResetCancellation * function for that particular Tcl_Interp. */ TclSetChildCancelFlags(childInterp, 0, 0); Tcl_Preserve(childInterp); Tcl_AllowExceptions(childInterp); if (objc == 1) { /* * TIP #280: Make actual argument location available to eval'd script. */ Interp *iPtr = (Interp *) interp; CmdFrame *invoker = iPtr->cmdFramePtr; int word = 0; TclArgumentGet(interp, objv[0], &invoker, &word); result = TclEvalObjEx(childInterp, objv[0], 0, invoker, word); } else { Tcl_Obj *objPtr = Tcl_ConcatObj(objc, objv); Tcl_IncrRefCount(objPtr); result = Tcl_EvalObjEx(childInterp, objPtr, 0); Tcl_DecrRefCount(objPtr); } Tcl_TransferResult(childInterp, result, interp); Tcl_Release(childInterp); return result; } /* *---------------------------------------------------------------------- * * ChildExpose -- * * Helper function to expose a command in a child interpreter. * * Results: * A standard Tcl result. * * Side effects: * After this call scripts in the child will be able to invoke the newly * exposed command. * *---------------------------------------------------------------------- */ static int ChildExpose( Tcl_Interp *interp, /* Interp for error return. */ Tcl_Interp *childInterp, /* Interp in which command will be exposed. */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument strings. */ { const char *name; if (Tcl_IsSafe(interp)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "permission denied: safe interpreter cannot expose commands", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "UNSAFE", (char *)NULL); return TCL_ERROR; } name = TclGetString(objv[(objc == 1) ? 0 : 1]); if (Tcl_ExposeCommand(childInterp, TclGetString(objv[0]), name) != TCL_OK) { Tcl_TransferResult(childInterp, TCL_ERROR, interp); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ChildRecursionLimit -- * * Helper function to set/query the Recursion limit of an interp * * Results: * A standard Tcl result. * * Side effects: * When (objc == 1), childInterp will be set to a new recursion limit of * objv[0]. * *---------------------------------------------------------------------- */ static int ChildRecursionLimit( Tcl_Interp *interp, /* Interp for error return. */ Tcl_Interp *childInterp, /* Interp in which limit is set/queried. */ Tcl_Size objc, /* Set or Query. */ Tcl_Obj *const objv[]) /* Argument strings. */ { Interp *iPtr; Tcl_WideInt limit; if (objc) { if (Tcl_IsSafe(interp)) { Tcl_SetObjResult(interp, Tcl_NewStringObj("permission denied: " "safe interpreters cannot change recursion limit", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "UNSAFE", (char *)NULL); return TCL_ERROR; } if (TclGetWideIntFromObj(interp, objv[0], &limit) == TCL_ERROR) { return TCL_ERROR; } if (limit <= 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "recursion limit must be > 0", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "BADLIMIT", (char *)NULL); return TCL_ERROR; } Tcl_SetRecursionLimit(childInterp, limit); iPtr = (Interp *) childInterp; if (interp == childInterp && iPtr->numLevels > limit) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "falling back due to new recursion limit", -1)); Tcl_SetErrorCode(interp, "TCL", "RECURSION", (char *)NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, objv[0]); return TCL_OK; } else { limit = Tcl_SetRecursionLimit(childInterp, 0); Tcl_SetObjResult(interp, Tcl_NewWideIntObj(limit)); return TCL_OK; } } /* *---------------------------------------------------------------------- * * ChildHide -- * * Helper function to hide a command in a child interpreter. * * Results: * A standard Tcl result. * * Side effects: * After this call scripts in the child will no longer be able to invoke * the named command. * *---------------------------------------------------------------------- */ static int ChildHide( Tcl_Interp *interp, /* Interp for error return. */ Tcl_Interp *childInterp, /* Interp in which command will be exposed. */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument strings. */ { const char *name; if (Tcl_IsSafe(interp)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "permission denied: safe interpreter cannot hide commands", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "UNSAFE", (char *)NULL); return TCL_ERROR; } name = TclGetString(objv[(objc == 1) ? 0 : 1]); if (Tcl_HideCommand(childInterp, TclGetString(objv[0]), name) != TCL_OK) { Tcl_TransferResult(childInterp, TCL_ERROR, interp); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ChildHidden -- * * Helper function to compute list of hidden commands in a child * interpreter. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int ChildHidden( Tcl_Interp *interp, /* Interp for data return. */ Tcl_Interp *childInterp) /* Interp whose hidden commands to query. */ { Tcl_Obj *listObjPtr; /* Local object pointer. */ Tcl_HashTable *hTblPtr; /* For local searches. */ Tcl_HashEntry *hPtr; /* For local searches. */ Tcl_HashSearch hSearch; /* For local searches. */ TclNewObj(listObjPtr); hTblPtr = ((Interp *) childInterp)->hiddenCmdTablePtr; if (hTblPtr != NULL) { for (hPtr = Tcl_FirstHashEntry(hTblPtr, &hSearch); hPtr != NULL; hPtr = Tcl_NextHashEntry(&hSearch)) { Tcl_ListObjAppendElement(NULL, listObjPtr, Tcl_NewStringObj((const char *) Tcl_GetHashKey(hTblPtr, hPtr), -1)); } } Tcl_SetObjResult(interp, listObjPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * ChildInvokeHidden -- * * Helper function to invoke a hidden command in a child interpreter. * * Results: * A standard Tcl result. * * Side effects: * Whatever the hidden command does. * *---------------------------------------------------------------------- */ static int ChildInvokeHidden( Tcl_Interp *interp, /* Interp for error return. */ Tcl_Interp *childInterp, /* The child interpreter in which command will * be invoked. */ const char *namespaceName, /* The namespace to use, if any. */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int result; if (Tcl_IsSafe(interp)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "not allowed to invoke hidden commands from safe interpreter", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "UNSAFE", (char *)NULL); return TCL_ERROR; } Tcl_Preserve(childInterp); Tcl_AllowExceptions(childInterp); if (namespaceName == NULL) { NRE_callback *rootPtr = TOP_CB(childInterp); Tcl_NRAddCallback(interp, NRPostInvokeHidden, childInterp, rootPtr, NULL, NULL); return TclNRInvoke(NULL, childInterp, objc, objv); } else { Namespace *nsPtr, *dummy1, *dummy2; const char *tail; result = TclGetNamespaceForQualName(childInterp, namespaceName, NULL, TCL_FIND_ONLY_NS | TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG | TCL_CREATE_NS_IF_UNKNOWN, &nsPtr, &dummy1, &dummy2, &tail); if (result == TCL_OK) { result = TclObjInvokeNamespace(childInterp, objc, objv, (Tcl_Namespace *) nsPtr, TCL_INVOKE_HIDDEN); } } Tcl_TransferResult(childInterp, result, interp); Tcl_Release(childInterp); return result; } static int NRPostInvokeHidden( void *data[], Tcl_Interp *interp, int result) { Tcl_Interp *childInterp = (Tcl_Interp *) data[0]; NRE_callback *rootPtr = (NRE_callback *) data[1]; if (interp != childInterp) { result = TclNRRunCallbacks(childInterp, result, rootPtr); Tcl_TransferResult(childInterp, result, interp); } Tcl_Release(childInterp); return result; } /* *---------------------------------------------------------------------- * * ChildMarkTrusted -- * * Helper function to mark a child interpreter as trusted (unsafe). * * Results: * A standard Tcl result. * * Side effects: * After this call the hard-wired security checks in the core no longer * prevent the child from performing certain operations. * *---------------------------------------------------------------------- */ static int ChildMarkTrusted( Tcl_Interp *interp, /* Interp for error return. */ Tcl_Interp *childInterp) /* The child interpreter which will be marked * trusted. */ { if (Tcl_IsSafe(interp)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "permission denied: safe interpreter cannot mark trusted", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "UNSAFE", (char *)NULL); return TCL_ERROR; } ((Interp *) childInterp)->flags &= ~SAFE_INTERP; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_IsSafe -- * * Determines whether an interpreter is safe * * Results: * 1 if it is safe, 0 if it is not. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_IsSafe( Tcl_Interp *interp) /* Is this interpreter "safe" ? */ { Interp *iPtr = (Interp *) interp; if (iPtr == NULL) { return 0; } return (iPtr->flags & SAFE_INTERP) ? 1 : 0; } /* *---------------------------------------------------------------------- * * MakeSafe -- * * Makes its argument interpreter contain only functionality that is * defined to be part of Safe Tcl. Unsafe commands are hidden, the env * array is unset, and the standard channels are removed. * * Results: * None. * * Side effects: * Hides commands in its argument interpreter, and removes settings and * channels. * *---------------------------------------------------------------------- */ void MakeSafe( Tcl_Interp *interp) /* Interpreter to be made safe. */ { Tcl_Channel chan; /* Channel to remove from safe interpreter. */ Interp *iPtr = (Interp *) interp; Tcl_Interp *parent = INTERP_INFO(iPtr)->child.parentInterp; TclHideUnsafeCommands(interp); if (parent != NULL) { /* * Alias these function implementations in the child to those in the * parent; the overall implementations are safe, but they're normally * defined by init.tcl which is not sourced by safe interpreters. * Assume these functions all work. [Bug 2895741] */ (void) Tcl_EvalEx(interp, "namespace eval ::tcl {namespace eval mathfunc {}}", TCL_INDEX_NONE, 0); } iPtr->flags |= SAFE_INTERP; /* * Unsetting variables : (which should not have been set in the first * place, but...) */ /* * No env array in a safe interpreter. */ Tcl_UnsetVar2(interp, "env", NULL, TCL_GLOBAL_ONLY); /* * Remove unsafe parts of tcl_platform */ Tcl_UnsetVar2(interp, "tcl_platform", "os", TCL_GLOBAL_ONLY); Tcl_UnsetVar2(interp, "tcl_platform", "osVersion", TCL_GLOBAL_ONLY); Tcl_UnsetVar2(interp, "tcl_platform", "machine", TCL_GLOBAL_ONLY); Tcl_UnsetVar2(interp, "tcl_platform", "user", TCL_GLOBAL_ONLY); /* * Unset path information variables (the only one remaining is [info * nameofexecutable]) */ Tcl_UnsetVar2(interp, "tclDefaultLibrary", NULL, TCL_GLOBAL_ONLY); Tcl_UnsetVar2(interp, "tcl_library", NULL, TCL_GLOBAL_ONLY); Tcl_UnsetVar2(interp, "tcl_pkgPath", NULL, TCL_GLOBAL_ONLY); /* * Remove the standard channels from the interpreter; safe interpreters do * not ordinarily have access to stdin, stdout and stderr. * * NOTE: These channels are not added to the interpreter by the * Tcl_CreateInterp call, but may be added later, by another I/O * operation. We want to ensure that the interpreter does not have these * channels even if it is being made safe after being used for some time.. */ chan = Tcl_GetStdChannel(TCL_STDIN); if (chan != NULL) { Tcl_UnregisterChannel(interp, chan); } chan = Tcl_GetStdChannel(TCL_STDOUT); if (chan != NULL) { Tcl_UnregisterChannel(interp, chan); } chan = Tcl_GetStdChannel(TCL_STDERR); if (chan != NULL) { Tcl_UnregisterChannel(interp, chan); } } /* *---------------------------------------------------------------------- * * Tcl_LimitExceeded -- * * Tests whether any limit has been exceeded in the given interpreter * (i.e. whether the interpreter is currently unable to process further * scripts). * * Results: * A boolean value. * * Side effects: * None. * * Notes: * If you change this function, you MUST also update TclLimitExceeded() in * tclInt.h. *---------------------------------------------------------------------- */ int Tcl_LimitExceeded( Tcl_Interp *interp) { Interp *iPtr = (Interp *) interp; return iPtr->limit.exceeded != 0; } /* *---------------------------------------------------------------------- * * Tcl_LimitReady -- * * Find out whether any limit has been set on the interpreter, and if so * check whether the granularity of that limit is such that the full * limit check should be carried out. * * Results: * A boolean value that indicates whether to call Tcl_LimitCheck. * * Side effects: * Increments the limit granularity counter. * * Notes: * If you change this function, you MUST also update TclLimitReady() in * tclInt.h. * *---------------------------------------------------------------------- */ int Tcl_LimitReady( Tcl_Interp *interp) { Interp *iPtr = (Interp *) interp; if (iPtr->limit.active != 0) { int ticker = ++iPtr->limit.granularityTicker; if ((iPtr->limit.active & TCL_LIMIT_COMMANDS) && ((iPtr->limit.cmdGranularity == 1) || (ticker % iPtr->limit.cmdGranularity == 0))) { return 1; } if ((iPtr->limit.active & TCL_LIMIT_TIME) && ((iPtr->limit.timeGranularity == 1) || (ticker % iPtr->limit.timeGranularity == 0))) { return 1; } } return 0; } /* *---------------------------------------------------------------------- * * Tcl_LimitCheck -- * * Check all currently set limits in the interpreter (where permitted by * granularity). If a limit is exceeded, call its callbacks and, if the * limit is still exceeded after the callbacks have run, make the * interpreter generate an error that cannot be caught within the limited * interpreter. * * Results: * A Tcl result value (TCL_OK if no limit is exceeded, and TCL_ERROR if a * limit has been exceeded). * * Side effects: * May invoke system calls. May invoke other interpreters. May be * reentrant. May put the interpreter into a state where it can no longer * execute commands without outside intervention. * *---------------------------------------------------------------------- */ int Tcl_LimitCheck( Tcl_Interp *interp) { Interp *iPtr = (Interp *) interp; int ticker = iPtr->limit.granularityTicker; if (Tcl_InterpDeleted(interp)) { return TCL_OK; } if ((iPtr->limit.active & TCL_LIMIT_COMMANDS) && ((iPtr->limit.cmdGranularity == 1) || (ticker % iPtr->limit.cmdGranularity == 0)) && (iPtr->limit.cmdCount < iPtr->cmdCount)) { iPtr->limit.exceeded |= TCL_LIMIT_COMMANDS; Tcl_Preserve(interp); RunLimitHandlers(iPtr->limit.cmdHandlers, interp); if (iPtr->limit.cmdCount >= iPtr->cmdCount) { iPtr->limit.exceeded &= ~TCL_LIMIT_COMMANDS; } else if (iPtr->limit.exceeded & TCL_LIMIT_COMMANDS) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "command count limit exceeded", -1)); Tcl_SetErrorCode(interp, "TCL", "LIMIT", "COMMANDS", (char *)NULL); Tcl_Release(interp); return TCL_ERROR; } Tcl_Release(interp); } if ((iPtr->limit.active & TCL_LIMIT_TIME) && ((iPtr->limit.timeGranularity == 1) || (ticker % iPtr->limit.timeGranularity == 0))) { Tcl_Time now; Tcl_GetTime(&now); if (iPtr->limit.time.sec < now.sec || (iPtr->limit.time.sec == now.sec && iPtr->limit.time.usec < now.usec)) { iPtr->limit.exceeded |= TCL_LIMIT_TIME; Tcl_Preserve(interp); RunLimitHandlers(iPtr->limit.timeHandlers, interp); if (iPtr->limit.time.sec > now.sec || (iPtr->limit.time.sec == now.sec && iPtr->limit.time.usec >= now.usec)) { iPtr->limit.exceeded &= ~TCL_LIMIT_TIME; } else if (iPtr->limit.exceeded & TCL_LIMIT_TIME) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "time limit exceeded", -1)); Tcl_SetErrorCode(interp, "TCL", "LIMIT", "TIME", (char *)NULL); Tcl_Release(interp); return TCL_ERROR; } Tcl_Release(interp); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * RunLimitHandlers -- * * Invoke all the limit handlers in a list (for a particular limit). * Note that no particular limit handler callback will be invoked * reentrantly. * * Results: * None. * * Side effects: * Depends on the limit handlers. * *---------------------------------------------------------------------- */ static void RunLimitHandlers( LimitHandler *handlerPtr, Tcl_Interp *interp) { LimitHandler *nextPtr; for (; handlerPtr!=NULL ; handlerPtr=nextPtr) { if (handlerPtr->flags & (LIMIT_HANDLER_DELETED|LIMIT_HANDLER_ACTIVE)) { /* * Reentrant call or something seriously strange in the delete * code. */ nextPtr = handlerPtr->nextPtr; continue; } /* * Set the ACTIVE flag while running the limit handler itself so we * cannot reentrantly call this handler and know to use the alternate * method of deletion if necessary. */ handlerPtr->flags |= LIMIT_HANDLER_ACTIVE; handlerPtr->handlerProc(handlerPtr->clientData, interp); handlerPtr->flags &= ~LIMIT_HANDLER_ACTIVE; /* * Rediscover this value; it might have changed during the processing * of a limit handler. We have to record it here because we might * delete the structure below, and reading a value out of a deleted * structure is unsafe (even if actually legal with some * malloc()/free() implementations.) */ nextPtr = handlerPtr->nextPtr; /* * If we deleted the current handler while we were executing it, we * will have spliced it out of the list and set the * LIMIT_HANDLER_DELETED flag. */ if (handlerPtr->flags & LIMIT_HANDLER_DELETED) { if (handlerPtr->deleteProc != NULL) { handlerPtr->deleteProc(handlerPtr->clientData); } Tcl_Free(handlerPtr); } } } /* *---------------------------------------------------------------------- * * Tcl_LimitAddHandler -- * * Add a callback handler for a particular resource limit. * * Results: * None. * * Side effects: * Extends the internal linked list of handlers for a limit. * *---------------------------------------------------------------------- */ void Tcl_LimitAddHandler( Tcl_Interp *interp, int type, Tcl_LimitHandlerProc *handlerProc, void *clientData, Tcl_LimitHandlerDeleteProc *deleteProc) { Interp *iPtr = (Interp *) interp; LimitHandler *handlerPtr; /* * Convert everything into a real deletion callback. */ if (deleteProc == TCL_DYNAMIC) { deleteProc = TclpFree; } /* * Allocate a handler record. */ handlerPtr = (LimitHandler *) Tcl_Alloc(sizeof(LimitHandler)); handlerPtr->flags = 0; handlerPtr->handlerProc = handlerProc; handlerPtr->clientData = clientData; handlerPtr->deleteProc = deleteProc; handlerPtr->prevPtr = NULL; /* * Prepend onto the front of the correct linked list. */ switch (type) { case TCL_LIMIT_COMMANDS: handlerPtr->nextPtr = iPtr->limit.cmdHandlers; if (handlerPtr->nextPtr != NULL) { handlerPtr->nextPtr->prevPtr = handlerPtr; } iPtr->limit.cmdHandlers = handlerPtr; return; case TCL_LIMIT_TIME: handlerPtr->nextPtr = iPtr->limit.timeHandlers; if (handlerPtr->nextPtr != NULL) { handlerPtr->nextPtr->prevPtr = handlerPtr; } iPtr->limit.timeHandlers = handlerPtr; return; } Tcl_Panic("unknown type of resource limit"); } /* *---------------------------------------------------------------------- * * Tcl_LimitRemoveHandler -- * * Remove a callback handler for a particular resource limit. * * Results: * None. * * Side effects: * The handler is spliced out of the internal linked list for the limit, * and if not currently being invoked, deleted. Otherwise it is just * marked for deletion and removed when the limit handler has finished * executing. * *---------------------------------------------------------------------- */ void Tcl_LimitRemoveHandler( Tcl_Interp *interp, int type, Tcl_LimitHandlerProc *handlerProc, void *clientData) { Interp *iPtr = (Interp *) interp; LimitHandler *handlerPtr; switch (type) { case TCL_LIMIT_COMMANDS: handlerPtr = iPtr->limit.cmdHandlers; break; case TCL_LIMIT_TIME: handlerPtr = iPtr->limit.timeHandlers; break; default: Tcl_Panic("unknown type of resource limit"); return; } for (; handlerPtr!=NULL ; handlerPtr=handlerPtr->nextPtr) { if ((handlerPtr->handlerProc != handlerProc) || (handlerPtr->clientData != clientData)) { continue; } /* * We've found the handler to delete; mark it as doomed if not already * so marked (which shouldn't actually happen). */ if (handlerPtr->flags & LIMIT_HANDLER_DELETED) { return; } handlerPtr->flags |= LIMIT_HANDLER_DELETED; /* * Splice the handler out of the doubly-linked list. */ if (handlerPtr->prevPtr == NULL) { switch (type) { case TCL_LIMIT_COMMANDS: iPtr->limit.cmdHandlers = handlerPtr->nextPtr; break; case TCL_LIMIT_TIME: iPtr->limit.timeHandlers = handlerPtr->nextPtr; break; } } else { handlerPtr->prevPtr->nextPtr = handlerPtr->nextPtr; } if (handlerPtr->nextPtr != NULL) { handlerPtr->nextPtr->prevPtr = handlerPtr->prevPtr; } /* * If nothing is currently executing the handler, delete its client * data and the overall handler structure now. Otherwise it will all * go away when the handler returns. */ if (!(handlerPtr->flags & LIMIT_HANDLER_ACTIVE)) { if (handlerPtr->deleteProc != NULL) { handlerPtr->deleteProc(handlerPtr->clientData); } Tcl_Free(handlerPtr); } return; } } /* *---------------------------------------------------------------------- * * TclLimitRemoveAllHandlers -- * * Remove all limit callback handlers for an interpreter. This is invoked * as part of deleting the interpreter. * * Results: * None. * * Side effects: * Limit handlers are deleted or marked for deletion (as with * Tcl_LimitRemoveHandler). * *---------------------------------------------------------------------- */ void TclLimitRemoveAllHandlers( Tcl_Interp *interp) { Interp *iPtr = (Interp *) interp; LimitHandler *handlerPtr, *nextHandlerPtr; /* * Delete all command-limit handlers. */ for (handlerPtr=iPtr->limit.cmdHandlers, iPtr->limit.cmdHandlers=NULL; handlerPtr!=NULL; handlerPtr=nextHandlerPtr) { nextHandlerPtr = handlerPtr->nextPtr; /* * Do not delete here if it has already been marked for deletion. */ if (handlerPtr->flags & LIMIT_HANDLER_DELETED) { continue; } handlerPtr->flags |= LIMIT_HANDLER_DELETED; handlerPtr->prevPtr = NULL; handlerPtr->nextPtr = NULL; /* * If nothing is currently executing the handler, delete its client * data and the overall handler structure now. Otherwise it will all * go away when the handler returns. */ if (!(handlerPtr->flags & LIMIT_HANDLER_ACTIVE)) { if (handlerPtr->deleteProc != NULL) { handlerPtr->deleteProc(handlerPtr->clientData); } Tcl_Free(handlerPtr); } } /* * Delete all time-limit handlers. */ for (handlerPtr=iPtr->limit.timeHandlers, iPtr->limit.timeHandlers=NULL; handlerPtr!=NULL; handlerPtr=nextHandlerPtr) { nextHandlerPtr = handlerPtr->nextPtr; /* * Do not delete here if it has already been marked for deletion. */ if (handlerPtr->flags & LIMIT_HANDLER_DELETED) { continue; } handlerPtr->flags |= LIMIT_HANDLER_DELETED; handlerPtr->prevPtr = NULL; handlerPtr->nextPtr = NULL; /* * If nothing is currently executing the handler, delete its client * data and the overall handler structure now. Otherwise it will all * go away when the handler returns. */ if (!(handlerPtr->flags & LIMIT_HANDLER_ACTIVE)) { if (handlerPtr->deleteProc != NULL) { handlerPtr->deleteProc(handlerPtr->clientData); } Tcl_Free(handlerPtr); } } /* * Delete the timer callback that is used to trap limits that occur in * [vwait]s... */ if (iPtr->limit.timeEvent != NULL) { Tcl_DeleteTimerHandler(iPtr->limit.timeEvent); iPtr->limit.timeEvent = NULL; } } /* *---------------------------------------------------------------------- * * Tcl_LimitTypeEnabled -- * * Check whether a particular limit has been enabled for an interpreter. * * Results: * A boolean value. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_LimitTypeEnabled( Tcl_Interp *interp, int type) { Interp *iPtr = (Interp *) interp; return (iPtr->limit.active & type) != 0; } /* *---------------------------------------------------------------------- * * Tcl_LimitTypeExceeded -- * * Check whether a particular limit has been exceeded for an interpreter. * * Results: * A boolean value (note that Tcl_LimitExceeded will always return * non-zero when this function returns non-zero). * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_LimitTypeExceeded( Tcl_Interp *interp, int type) { Interp *iPtr = (Interp *) interp; return (iPtr->limit.exceeded & type) != 0; } /* *---------------------------------------------------------------------- * * Tcl_LimitTypeSet -- * * Enable a particular limit for an interpreter. * * Results: * None. * * Side effects: * The limit is turned on and will be checked in future at an interval * determined by the frequency of calling of Tcl_LimitReady and the * granularity of the limit in question. * *---------------------------------------------------------------------- */ void Tcl_LimitTypeSet( Tcl_Interp *interp, int type) { Interp *iPtr = (Interp *) interp; iPtr->limit.active |= type; } /* *---------------------------------------------------------------------- * * Tcl_LimitTypeReset -- * * Disable a particular limit for an interpreter. * * Results: * None. * * Side effects: * The limit is disabled. If the limit was exceeded when this function * was called, the limit will no longer be exceeded afterwards and the * interpreter will be free to execute further scripts (assuming it isn't * also deleted, of course). * *---------------------------------------------------------------------- */ void Tcl_LimitTypeReset( Tcl_Interp *interp, int type) { Interp *iPtr = (Interp *) interp; iPtr->limit.active &= ~type; iPtr->limit.exceeded &= ~type; } /* *---------------------------------------------------------------------- * * Tcl_LimitSetCommands -- * * Set the command limit for an interpreter. * * Results: * None. * * Side effects: * Also resets whether the command limit was exceeded. This might permit * a small amount of further execution in the interpreter even if the * limit itself is theoretically exceeded. * *---------------------------------------------------------------------- */ void Tcl_LimitSetCommands( Tcl_Interp *interp, Tcl_Size commandLimit) { Interp *iPtr = (Interp *) interp; iPtr->limit.cmdCount = commandLimit; iPtr->limit.exceeded &= ~TCL_LIMIT_COMMANDS; } /* *---------------------------------------------------------------------- * * Tcl_LimitGetCommands -- * * Get the number of commands that may be executed in the interpreter * before the command-limit is reached. * * Results: * An upper bound on the number of commands. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_LimitGetCommands( Tcl_Interp *interp) { Interp *iPtr = (Interp *) interp; return iPtr->limit.cmdCount; } /* *---------------------------------------------------------------------- * * Tcl_LimitSetTime -- * * Set the time limit for an interpreter by copying it from the value * pointed to by the timeLimitPtr argument. * * Results: * None. * * Side effects: * Also resets whether the time limit was exceeded. This might permit a * small amount of further execution in the interpreter even if the limit * itself is theoretically exceeded. * *---------------------------------------------------------------------- */ void Tcl_LimitSetTime( Tcl_Interp *interp, Tcl_Time *timeLimitPtr) { Interp *iPtr = (Interp *) interp; Tcl_Time nextMoment; memcpy(&iPtr->limit.time, timeLimitPtr, sizeof(Tcl_Time)); if (iPtr->limit.timeEvent != NULL) { Tcl_DeleteTimerHandler(iPtr->limit.timeEvent); } nextMoment.sec = timeLimitPtr->sec; nextMoment.usec = timeLimitPtr->usec+10; if (nextMoment.usec >= 1000000) { nextMoment.sec++; nextMoment.usec -= 1000000; } iPtr->limit.timeEvent = TclCreateAbsoluteTimerHandler(&nextMoment, TimeLimitCallback, interp); iPtr->limit.exceeded &= ~TCL_LIMIT_TIME; } /* *---------------------------------------------------------------------- * * TimeLimitCallback -- * * Callback that allows time limits to be enforced even when doing a * blocking wait for events. * * Results: * None. * * Side effects: * May put the interpreter into a state where it can no longer execute * commands. May make callbacks into other interpreters. * *---------------------------------------------------------------------- */ static void TimeLimitCallback( void *clientData) { Tcl_Interp *interp = (Tcl_Interp *) clientData; Interp *iPtr = (Interp *) clientData; int code; Tcl_Preserve(interp); iPtr->limit.timeEvent = NULL; /* * Must reset the granularity ticker here to force an immediate full * check. This is OK because we're swallowing the cost in the overall cost * of the event loop. [Bug 2891362] */ iPtr->limit.granularityTicker = 0; code = Tcl_LimitCheck(interp); if (code != TCL_OK) { Tcl_AddErrorInfo(interp, "\n (while waiting for event)"); Tcl_BackgroundException(interp, code); } Tcl_Release(interp); } /* *---------------------------------------------------------------------- * * Tcl_LimitGetTime -- * * Get the current time limit. * * Results: * The time limit (by it being copied into the variable pointed to by the * timeLimitPtr). * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_LimitGetTime( Tcl_Interp *interp, Tcl_Time *timeLimitPtr) { Interp *iPtr = (Interp *) interp; memcpy(timeLimitPtr, &iPtr->limit.time, sizeof(Tcl_Time)); } /* *---------------------------------------------------------------------- * * Tcl_LimitSetGranularity -- * * Set the granularity divisor (which must be positive) for a particular * limit. * * Results: * None. * * Side effects: * The granularity is updated. * *---------------------------------------------------------------------- */ void Tcl_LimitSetGranularity( Tcl_Interp *interp, int type, int granularity) { Interp *iPtr = (Interp *) interp; if (granularity < 1) { Tcl_Panic("limit granularity must be positive"); } switch (type) { case TCL_LIMIT_COMMANDS: iPtr->limit.cmdGranularity = granularity; return; case TCL_LIMIT_TIME: iPtr->limit.timeGranularity = granularity; return; } Tcl_Panic("unknown type of resource limit"); } /* *---------------------------------------------------------------------- * * Tcl_LimitGetGranularity -- * * Get the granularity divisor for a particular limit. * * Results: * The granularity divisor for the given limit. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_LimitGetGranularity( Tcl_Interp *interp, int type) { Interp *iPtr = (Interp *) interp; switch (type) { case TCL_LIMIT_COMMANDS: return iPtr->limit.cmdGranularity; case TCL_LIMIT_TIME: return iPtr->limit.timeGranularity; } Tcl_Panic("unknown type of resource limit"); return -1; /* NOT REACHED */ } /* *---------------------------------------------------------------------- * * DeleteScriptLimitCallback -- * * Callback for when a script limit (a limit callback implemented as a * Tcl script in a parent interpreter, as set up from Tcl) is deleted. * * Results: * None. * * Side effects: * The reference to the script callback from the controlling interpreter * is removed. * *---------------------------------------------------------------------- */ static void DeleteScriptLimitCallback( void *clientData) { ScriptLimitCallback *limitCBPtr = (ScriptLimitCallback *) clientData; Tcl_DecrRefCount(limitCBPtr->scriptObj); if (limitCBPtr->entryPtr != NULL) { Tcl_DeleteHashEntry(limitCBPtr->entryPtr); } Tcl_Free(limitCBPtr); } /* *---------------------------------------------------------------------- * * CallScriptLimitCallback -- * * Invoke a script limit callback. Used to implement limit callbacks set * at the Tcl level on child interpreters. * * Results: * None. * * Side effects: * Depends on the callback script. Errors are reported as background * errors. * *---------------------------------------------------------------------- */ static void CallScriptLimitCallback( void *clientData, TCL_UNUSED(Tcl_Interp *)) { ScriptLimitCallback *limitCBPtr = (ScriptLimitCallback *) clientData; int code; if (Tcl_InterpDeleted(limitCBPtr->interp)) { return; } Tcl_Preserve(limitCBPtr->interp); code = Tcl_EvalObjEx(limitCBPtr->interp, limitCBPtr->scriptObj, TCL_EVAL_GLOBAL); if (code != TCL_OK && !Tcl_InterpDeleted(limitCBPtr->interp)) { Tcl_BackgroundException(limitCBPtr->interp, code); } Tcl_Release(limitCBPtr->interp); } /* *---------------------------------------------------------------------- * * SetScriptLimitCallback -- * * Install (or remove, if scriptObj is NULL) a limit callback script that * is called when the target interpreter exceeds the type of limit * specified. Each interpreter may only have one callback set on another * interpreter through this mechanism (though as many interpreters may be * limited as the programmer chooses overall). * * Results: * None. * * Side effects: * A limit callback implemented as an invocation of a Tcl script in * another interpreter is either installed or removed. * *---------------------------------------------------------------------- */ static void SetScriptLimitCallback( Tcl_Interp *interp, int type, Tcl_Interp *targetInterp, Tcl_Obj *scriptObj) { ScriptLimitCallback *limitCBPtr; Tcl_HashEntry *hashPtr; int isNew; ScriptLimitCallbackKey key; Interp *iPtr = (Interp *) interp; if (interp == targetInterp) { Tcl_Panic("installing limit callback to the limited interpreter"); } key.interp = targetInterp; key.type = type; if (scriptObj == NULL) { hashPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, &key); if (hashPtr != NULL) { Tcl_LimitRemoveHandler(targetInterp, type, CallScriptLimitCallback, Tcl_GetHashValue(hashPtr)); } return; } hashPtr = Tcl_CreateHashEntry(&iPtr->limit.callbacks, &key, &isNew); if (!isNew) { limitCBPtr = (ScriptLimitCallback *) Tcl_GetHashValue(hashPtr); limitCBPtr->entryPtr = NULL; Tcl_LimitRemoveHandler(targetInterp, type, CallScriptLimitCallback, limitCBPtr); } limitCBPtr = (ScriptLimitCallback *) Tcl_Alloc(sizeof(ScriptLimitCallback)); limitCBPtr->interp = interp; limitCBPtr->scriptObj = scriptObj; limitCBPtr->entryPtr = hashPtr; limitCBPtr->type = type; Tcl_IncrRefCount(scriptObj); Tcl_LimitAddHandler(targetInterp, type, CallScriptLimitCallback, limitCBPtr, DeleteScriptLimitCallback); Tcl_SetHashValue(hashPtr, limitCBPtr); } /* *---------------------------------------------------------------------- * * TclRemoveScriptLimitCallbacks -- * * Remove all script-implemented limit callbacks that make calls back * into the given interpreter. This invoked as part of deleting an * interpreter. * * Results: * None. * * Side effects: * The script limit callbacks are removed or marked for later removal. * *---------------------------------------------------------------------- */ void TclRemoveScriptLimitCallbacks( Tcl_Interp *interp) { Interp *iPtr = (Interp *) interp; Tcl_HashEntry *hashPtr; Tcl_HashSearch search; ScriptLimitCallbackKey *keyPtr; hashPtr = Tcl_FirstHashEntry(&iPtr->limit.callbacks, &search); while (hashPtr != NULL) { keyPtr = (ScriptLimitCallbackKey *) Tcl_GetHashKey(&iPtr->limit.callbacks, hashPtr); Tcl_LimitRemoveHandler(keyPtr->interp, keyPtr->type, CallScriptLimitCallback, Tcl_GetHashValue(hashPtr)); hashPtr = Tcl_NextHashEntry(&search); } Tcl_DeleteHashTable(&iPtr->limit.callbacks); } /* *---------------------------------------------------------------------- * * TclInitLimitSupport -- * * Initialise all the parts of the interpreter relating to resource limit * management. This allows an interpreter to both have limits set upon * itself and set limits upon other interpreters. * * Results: * None. * * Side effects: * The resource limit subsystem is initialised for the interpreter. * *---------------------------------------------------------------------- */ void TclInitLimitSupport( Tcl_Interp *interp) { Interp *iPtr = (Interp *) interp; iPtr->limit.active = 0; iPtr->limit.granularityTicker = 0; iPtr->limit.exceeded = 0; iPtr->limit.cmdCount = 0; iPtr->limit.cmdHandlers = NULL; iPtr->limit.cmdGranularity = 1; memset(&iPtr->limit.time, 0, sizeof(Tcl_Time)); iPtr->limit.timeHandlers = NULL; iPtr->limit.timeEvent = NULL; iPtr->limit.timeGranularity = 10; Tcl_InitHashTable(&iPtr->limit.callbacks, sizeof(ScriptLimitCallbackKey) / sizeof(int)); } /* *---------------------------------------------------------------------- * * InheritLimitsFromParent -- * * Derive the interpreter limit configuration for a child interpreter * from the limit config for the parent. * * Results: * None. * * Side effects: * The child interpreter limits are set so that if the parent has a * limit, it may not exceed it by handing off work to child interpreters. * Note that this does not transfer limit callbacks from the parent to * the child. * *---------------------------------------------------------------------- */ static void InheritLimitsFromParent( Tcl_Interp *childInterp, Tcl_Interp *parentInterp) { Interp *childPtr = (Interp *) childInterp; Interp *parentPtr = (Interp *) parentInterp; if (parentPtr->limit.active & TCL_LIMIT_COMMANDS) { childPtr->limit.active |= TCL_LIMIT_COMMANDS; childPtr->limit.cmdCount = 0; childPtr->limit.cmdGranularity = parentPtr->limit.cmdGranularity; } if (parentPtr->limit.active & TCL_LIMIT_TIME) { childPtr->limit.active |= TCL_LIMIT_TIME; memcpy(&childPtr->limit.time, &parentPtr->limit.time, sizeof(Tcl_Time)); childPtr->limit.timeGranularity = parentPtr->limit.timeGranularity; } } /* *---------------------------------------------------------------------- * * ChildCommandLimitCmd -- * * Implementation of the [interp limit $i commands] and [$i limit * commands] subcommands. See the interp manual page for a full * description. * * Results: * A standard Tcl result. * * Side effects: * Depends on the arguments. * *---------------------------------------------------------------------- */ static int ChildCommandLimitCmd( Tcl_Interp *interp, /* Current interpreter. */ Tcl_Interp *childInterp, /* Interpreter being adjusted. */ Tcl_Size consumedObjc, /* Number of args already parsed. */ Tcl_Size objc, /* Total number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const options[] = { "-command", "-granularity", "-value", NULL }; enum Options { OPT_CMD, OPT_GRAN, OPT_VAL } index; Interp *iPtr = (Interp *) interp; ScriptLimitCallbackKey key; ScriptLimitCallback *limitCBPtr; Tcl_HashEntry *hPtr; /* * First, ensure that we are not reading or writing the calling * interpreter's limits; it may only manipulate its children. Note that * the low level API enforces this with Tcl_Panic, which we want to * avoid. [Bug 3398794] */ if (interp == childInterp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "limits on current interpreter inaccessible", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "SELF", (char *)NULL); return TCL_ERROR; } if (objc == consumedObjc) { Tcl_Obj *dictPtr; TclNewObj(dictPtr); key.interp = childInterp; key.type = TCL_LIMIT_COMMANDS; hPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, &key); if (hPtr != NULL) { limitCBPtr = (ScriptLimitCallback *) Tcl_GetHashValue(hPtr); if (limitCBPtr != NULL && limitCBPtr->scriptObj != NULL) { TclDictPut(NULL, dictPtr, options[0], limitCBPtr->scriptObj); } else { goto putEmptyCommandInDict; } } else { Tcl_Obj *empty; putEmptyCommandInDict: TclNewObj(empty); TclDictPut(NULL, dictPtr, options[0], empty); } TclDictPut(NULL, dictPtr, options[1], Tcl_NewWideIntObj( Tcl_LimitGetGranularity(childInterp, TCL_LIMIT_COMMANDS))); if (Tcl_LimitTypeEnabled(childInterp, TCL_LIMIT_COMMANDS)) { TclDictPut(NULL, dictPtr, options[2], Tcl_NewWideIntObj( Tcl_LimitGetCommands(childInterp))); } else { Tcl_Obj *empty; TclNewObj(empty); TclDictPut(NULL, dictPtr, options[2], empty); } Tcl_SetObjResult(interp, dictPtr); return TCL_OK; } else if (objc == consumedObjc+1) { if (Tcl_GetIndexFromObj(interp, objv[consumedObjc], options, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case OPT_CMD: key.interp = childInterp; key.type = TCL_LIMIT_COMMANDS; hPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, &key); if (hPtr != NULL) { limitCBPtr = (ScriptLimitCallback *) Tcl_GetHashValue(hPtr); if (limitCBPtr != NULL && limitCBPtr->scriptObj != NULL) { Tcl_SetObjResult(interp, limitCBPtr->scriptObj); } } break; case OPT_GRAN: Tcl_SetObjResult(interp, Tcl_NewWideIntObj( Tcl_LimitGetGranularity(childInterp, TCL_LIMIT_COMMANDS))); break; case OPT_VAL: if (Tcl_LimitTypeEnabled(childInterp, TCL_LIMIT_COMMANDS)) { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(Tcl_LimitGetCommands(childInterp))); } break; } return TCL_OK; } else if ((objc-consumedObjc) & 1 /* isOdd(objc-consumedObjc) */) { Tcl_WrongNumArgs(interp, consumedObjc, objv, "?-option value ...?"); return TCL_ERROR; } else { Tcl_Size i, scriptLen = 0, limitLen = 0; Tcl_Obj *scriptObj = NULL, *granObj = NULL, *limitObj = NULL; int gran = 0, limit = 0; for (i=consumedObjc ; i 0 ? scriptObj : NULL)); } if (granObj != NULL) { Tcl_LimitSetGranularity(childInterp, TCL_LIMIT_COMMANDS, gran); } if (limitObj != NULL) { if (limitLen > 0) { Tcl_LimitSetCommands(childInterp, limit); Tcl_LimitTypeSet(childInterp, TCL_LIMIT_COMMANDS); } else { Tcl_LimitTypeReset(childInterp, TCL_LIMIT_COMMANDS); } } return TCL_OK; } } /* *---------------------------------------------------------------------- * * ChildTimeLimitCmd -- * * Implementation of the [interp limit $i time] and [$i limit time] * subcommands. See the interp manual page for a full description. * * Results: * A standard Tcl result. * * Side effects: * Depends on the arguments. * *---------------------------------------------------------------------- */ static int ChildTimeLimitCmd( Tcl_Interp *interp, /* Current interpreter. */ Tcl_Interp *childInterp, /* Interpreter being adjusted. */ Tcl_Size consumedObjc, /* Number of args already parsed. */ Tcl_Size objc, /* Total number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const options[] = { "-command", "-granularity", "-milliseconds", "-seconds", NULL }; enum Options { OPT_CMD, OPT_GRAN, OPT_MILLI, OPT_SEC } index; Interp *iPtr = (Interp *) interp; ScriptLimitCallbackKey key; ScriptLimitCallback *limitCBPtr; Tcl_HashEntry *hPtr; /* * First, ensure that we are not reading or writing the calling * interpreter's limits; it may only manipulate its children. Note that * the low level API enforces this with Tcl_Panic, which we want to * avoid. [Bug 3398794] */ if (interp == childInterp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "limits on current interpreter inaccessible", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "SELF", (char *)NULL); return TCL_ERROR; } if (objc == consumedObjc) { Tcl_Obj *dictPtr; TclNewObj(dictPtr); key.interp = childInterp; key.type = TCL_LIMIT_TIME; hPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, &key); if (hPtr != NULL) { limitCBPtr = (ScriptLimitCallback *) Tcl_GetHashValue(hPtr); if (limitCBPtr != NULL && limitCBPtr->scriptObj != NULL) { TclDictPut(NULL, dictPtr, options[0], limitCBPtr->scriptObj); } else { goto putEmptyCommandInDict; } } else { Tcl_Obj *empty; putEmptyCommandInDict: TclNewObj(empty); TclDictPut(NULL, dictPtr, options[0], empty); } TclDictPut(NULL, dictPtr, options[1], Tcl_NewWideIntObj( Tcl_LimitGetGranularity(childInterp, TCL_LIMIT_TIME))); if (Tcl_LimitTypeEnabled(childInterp, TCL_LIMIT_TIME)) { Tcl_Time limitMoment; Tcl_LimitGetTime(childInterp, &limitMoment); TclDictPut(NULL, dictPtr, options[2], Tcl_NewWideIntObj(limitMoment.usec / 1000)); TclDictPut(NULL, dictPtr, options[3], Tcl_NewWideIntObj(limitMoment.sec)); } else { Tcl_Obj *empty; TclNewObj(empty); TclDictPut(NULL, dictPtr, options[2], empty); TclDictPut(NULL, dictPtr, options[3], empty); } Tcl_SetObjResult(interp, dictPtr); return TCL_OK; } else if (objc == consumedObjc+1) { if (Tcl_GetIndexFromObj(interp, objv[consumedObjc], options, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case OPT_CMD: key.interp = childInterp; key.type = TCL_LIMIT_TIME; hPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, &key); if (hPtr != NULL) { limitCBPtr = (ScriptLimitCallback *) Tcl_GetHashValue(hPtr); if (limitCBPtr != NULL && limitCBPtr->scriptObj != NULL) { Tcl_SetObjResult(interp, limitCBPtr->scriptObj); } } break; case OPT_GRAN: Tcl_SetObjResult(interp, Tcl_NewWideIntObj( Tcl_LimitGetGranularity(childInterp, TCL_LIMIT_TIME))); break; case OPT_MILLI: if (Tcl_LimitTypeEnabled(childInterp, TCL_LIMIT_TIME)) { Tcl_Time limitMoment; Tcl_LimitGetTime(childInterp, &limitMoment); Tcl_SetObjResult(interp, Tcl_NewWideIntObj(limitMoment.usec/1000)); } break; case OPT_SEC: if (Tcl_LimitTypeEnabled(childInterp, TCL_LIMIT_TIME)) { Tcl_Time limitMoment; Tcl_LimitGetTime(childInterp, &limitMoment); Tcl_SetObjResult(interp, Tcl_NewWideIntObj(limitMoment.sec)); } break; } return TCL_OK; } else if ((objc-consumedObjc) & 1 /* isOdd(objc-consumedObjc) */) { Tcl_WrongNumArgs(interp, consumedObjc, objv, "?-option value ...?"); return TCL_ERROR; } else { Tcl_Size i, scriptLen = 0, milliLen = 0, secLen = 0; Tcl_Obj *scriptObj = NULL, *granObj = NULL; Tcl_Obj *milliObj = NULL, *secObj = NULL; int gran = 0; Tcl_Time limitMoment; Tcl_WideInt tmp; Tcl_LimitGetTime(childInterp, &limitMoment); for (i=consumedObjc ; i 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "may only set -milliseconds if -seconds is not " "also being reset", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "BADUSAGE", (char *)NULL); return TCL_ERROR; } if (milliLen == 0 && (secObj == NULL || secLen > 0)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "may only reset -milliseconds if -seconds is " "also being reset", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "INTERP", "BADUSAGE", (char *)NULL); return TCL_ERROR; } } if (milliLen > 0 || secLen > 0) { /* * Force usec to be in range [0..1000000), possibly * incrementing sec in the process. This makes it much easier * for people to write scripts that do small time increments. */ limitMoment.sec += limitMoment.usec / 1000000; limitMoment.usec %= 1000000; Tcl_LimitSetTime(childInterp, &limitMoment); Tcl_LimitTypeSet(childInterp, TCL_LIMIT_TIME); } else { Tcl_LimitTypeReset(childInterp, TCL_LIMIT_TIME); } } if (scriptObj != NULL) { SetScriptLimitCallback(interp, TCL_LIMIT_TIME, childInterp, (scriptLen > 0 ? scriptObj : NULL)); } if (granObj != NULL) { Tcl_LimitSetGranularity(childInterp, TCL_LIMIT_TIME, gran); } return TCL_OK; } } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclLink.c0000644000175000017500000012002714726623136014740 0ustar sergeisergei/* * tclLink.c -- * * This file implements linked variables (a C variable that is tied to a * Tcl variable). The idea of linked variables was first suggested by * Andreas Stolcke and this implementation is based heavily on a * prototype implementation provided by him. * * Copyright © 1993 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * Copyright © 2008 Rene Zaumseil * Copyright © 2019 Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclTomMath.h" #include /* * For each linked variable there is a data structure of the following type, * which describes the link and is the clientData for the trace set on the Tcl * variable. */ typedef struct { Tcl_Interp *interp; /* Interpreter containing Tcl variable. */ Namespace *nsPtr; /* Namespace containing Tcl variable */ Tcl_Obj *varName; /* Name of variable (must be global). This is * needed during trace callbacks, since the * actual variable may be aliased at that time * via upvar. */ void *addr; /* Location of C variable. */ Tcl_Size bytes; /* Size of C variable array. This is 0 when * single variables, and >0 used for array * variables. */ Tcl_Size numElems; /* Number of elements in C variable array. * Zero for single variables. */ int type; /* Type of link (TCL_LINK_INT, etc.). */ union { char c; unsigned char uc; int i; unsigned int ui; short s; unsigned short us; #if !defined(TCL_WIDE_INT_IS_LONG) && !defined(_WIN32) && !defined(__CYGWIN__) long l; unsigned long ul; #endif Tcl_WideInt w; Tcl_WideUInt uw; float f; double d; void *aryPtr; /* Generic array. */ char *cPtr; /* char array */ unsigned char *ucPtr; /* unsigned char array */ short *sPtr; /* short array */ unsigned short *usPtr; /* unsigned short array */ int *iPtr; /* int array */ unsigned int *uiPtr; /* unsigned int array */ long *lPtr; /* long array */ unsigned long *ulPtr; /* unsigned long array */ Tcl_WideInt *wPtr; /* wide (long long) array */ Tcl_WideUInt *uwPtr; /* unsigned wide (long long) array */ float *fPtr; /* float array */ double *dPtr; /* double array */ } lastValue; /* Last known value of C variable; used to * avoid string conversions. */ int flags; /* Miscellaneous one-bit values; see below for * definitions. */ } Link; /* * Definitions for flag bits: * LINK_READ_ONLY - 1 means errors should be generated if Tcl * script attempts to write variable. * LINK_BEING_UPDATED - 1 means that a call to Tcl_UpdateLinkedVar is * in progress for this variable, so trace * callbacks on the variable should be ignored. * LINK_ALLOC_ADDR - 1 means linkPtr->addr was allocated on the * heap. * LINK_ALLOC_LAST - 1 means linkPtr->valueLast.p was allocated on * the heap. */ #define LINK_READ_ONLY 1 #define LINK_BEING_UPDATED 2 #define LINK_ALLOC_ADDR 4 #define LINK_ALLOC_LAST 8 /* * Forward references to functions defined later in this file: */ static char * LinkTraceProc(void *clientData,Tcl_Interp *interp, const char *name1, const char *name2, int flags); static Tcl_Obj * ObjValue(Link *linkPtr); static void LinkFree(Link *linkPtr); static int GetInvalidIntFromObj(Tcl_Obj *objPtr, int *intPtr); static int GetInvalidDoubleFromObj(Tcl_Obj *objPtr, double *doublePtr); static int SetInvalidRealFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); /* * A marker type used to flag weirdnesses so we can pass them around right. */ static const Tcl_ObjType invalidRealType = { "invalidReal", /* name */ NULL, /* freeIntRepProc */ NULL, /* dupIntRepProc */ NULL, /* updateStringProc */ NULL, /* setFromAnyProc */ TCL_OBJTYPE_V1(TclLengthOne) }; /* * Convenience macro for accessing the value of the C variable pointed to by a * link. Note that this macro produces something that may be regarded as an * lvalue or rvalue; it may be assigned to as well as read. Also note that * this macro assumes the name of the variable being accessed (linkPtr); this * is not strictly a good thing, but it keeps the code much shorter and * cleaner. */ #define LinkedVar(type) (*(type *) linkPtr->addr) /* *---------------------------------------------------------------------- * * Tcl_LinkVar -- * * Link a C variable to a Tcl variable so that changes to either one * causes the other to change. * * Results: * The return value is TCL_OK if everything went well or TCL_ERROR if an * error occurred (the interp's result is also set after errors). * * Side effects: * The value at *addr is linked to the Tcl variable "varName", using * "type" to convert between string values for Tcl and binary values for * *addr. * *---------------------------------------------------------------------- */ int Tcl_LinkVar( Tcl_Interp *interp, /* Interpreter in which varName exists. */ const char *varName, /* Name of a global variable in interp. */ void *addr, /* Address of a C variable to be linked to * varName. */ int type) /* Type of C variable: TCL_LINK_INT, etc. Also * may have TCL_LINK_READ_ONLY OR'ed in. */ { Tcl_Obj *objPtr; Link *linkPtr; Namespace *dummy; const char *name; int code; linkPtr = (Link *) Tcl_VarTraceInfo2(interp, varName, NULL, TCL_GLOBAL_ONLY, LinkTraceProc, NULL); if (linkPtr != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "variable '%s' is already linked", varName)); return TCL_ERROR; } linkPtr = (Link *)Tcl_Alloc(sizeof(Link)); linkPtr->interp = interp; linkPtr->nsPtr = NULL; linkPtr->varName = Tcl_NewStringObj(varName, -1); Tcl_IncrRefCount(linkPtr->varName); linkPtr->addr = addr; linkPtr->type = type & ~TCL_LINK_READ_ONLY; if (type & TCL_LINK_READ_ONLY) { linkPtr->flags = LINK_READ_ONLY; } else { linkPtr->flags = 0; } linkPtr->bytes = 0; linkPtr->numElems = 0; objPtr = ObjValue(linkPtr); if (Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, objPtr, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { Tcl_DecrRefCount(linkPtr->varName); LinkFree(linkPtr); return TCL_ERROR; } TclGetNamespaceForQualName(interp, varName, NULL, TCL_GLOBAL_ONLY, &(linkPtr->nsPtr), &dummy, &dummy, &name); linkPtr->nsPtr->refCount++; code = Tcl_TraceVar2(interp, varName, NULL, TCL_GLOBAL_ONLY|TCL_TRACE_READS|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, LinkTraceProc, linkPtr); if (code != TCL_OK) { Tcl_DecrRefCount(linkPtr->varName); LinkFree(linkPtr); } return code; } /* *---------------------------------------------------------------------- * * Tcl_LinkArray -- * * Link a C variable array to a Tcl variable so that changes to either * one causes the other to change. * * Results: * The return value is TCL_OK if everything went well or TCL_ERROR if an * error occurred (the interp's result is also set after errors). * * Side effects: * The value at *addr is linked to the Tcl variable "varName", using * "type" to convert between string values for Tcl and binary values for * *addr. * *---------------------------------------------------------------------- */ int Tcl_LinkArray( Tcl_Interp *interp, /* Interpreter in which varName exists. */ const char *varName, /* Name of a global variable in interp. */ void *addr, /* Address of a C variable to be linked to * varName. If NULL then the necessary space * will be allocated and returned as the * interpreter result. */ int type, /* Type of C variable: TCL_LINK_INT, etc. Also * may have TCL_LINK_READ_ONLY OR'ed in. */ Tcl_Size size) /* Size of C variable array, >1 if array */ { Tcl_Obj *objPtr; Link *linkPtr; Namespace *dummy; const char *name; int code; if (size < 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "wrong array size given", -1)); return TCL_ERROR; } linkPtr = (Link *)Tcl_Alloc(sizeof(Link)); linkPtr->type = type & ~TCL_LINK_READ_ONLY; linkPtr->numElems = size; if (type & TCL_LINK_READ_ONLY) { linkPtr->flags = LINK_READ_ONLY; } else { linkPtr->flags = 0; } switch (linkPtr->type) { case TCL_LINK_INT: case TCL_LINK_BOOLEAN: linkPtr->bytes = size * sizeof(int); break; case TCL_LINK_DOUBLE: linkPtr->bytes = size * sizeof(double); break; case TCL_LINK_WIDE_INT: linkPtr->bytes = size * sizeof(Tcl_WideInt); break; case TCL_LINK_WIDE_UINT: linkPtr->bytes = size * sizeof(Tcl_WideUInt); break; case TCL_LINK_CHAR: linkPtr->bytes = size * sizeof(char); break; case TCL_LINK_UCHAR: linkPtr->bytes = size * sizeof(unsigned char); break; case TCL_LINK_SHORT: linkPtr->bytes = size * sizeof(short); break; case TCL_LINK_USHORT: linkPtr->bytes = size * sizeof(unsigned short); break; case TCL_LINK_UINT: linkPtr->bytes = size * sizeof(unsigned int); break; case TCL_LINK_FLOAT: linkPtr->bytes = size * sizeof(float); break; case TCL_LINK_STRING: linkPtr->bytes = size * sizeof(char); size = 1; /* This is a variable length string, no need * to check last value. */ /* * If no address is given create one and use as address the * not needed linkPtr->lastValue */ if (addr == NULL) { linkPtr->lastValue.aryPtr = Tcl_Alloc(linkPtr->bytes); linkPtr->flags |= LINK_ALLOC_LAST; addr = (char *) &linkPtr->lastValue.cPtr; } break; case TCL_LINK_CHARS: case TCL_LINK_BINARY: linkPtr->bytes = size * sizeof(char); break; default: LinkFree(linkPtr); Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad linked array variable type", -1)); return TCL_ERROR; } /* * Allocate C variable space in case no address is given */ if (addr == NULL) { linkPtr->addr = Tcl_Alloc(linkPtr->bytes); linkPtr->flags |= LINK_ALLOC_ADDR; } else { linkPtr->addr = addr; } /* * If necessary create space for last used value. */ if (size > 1) { linkPtr->lastValue.aryPtr = Tcl_Alloc(linkPtr->bytes); linkPtr->flags |= LINK_ALLOC_LAST; } /* * Initialize allocated space. */ if (linkPtr->flags & LINK_ALLOC_ADDR) { memset(linkPtr->addr, 0, linkPtr->bytes); } if (linkPtr->flags & LINK_ALLOC_LAST) { memset(linkPtr->lastValue.aryPtr, 0, linkPtr->bytes); } /* * Set common structure values. */ linkPtr->interp = interp; linkPtr->varName = Tcl_NewStringObj(varName, -1); Tcl_IncrRefCount(linkPtr->varName); TclGetNamespaceForQualName(interp, varName, NULL, TCL_GLOBAL_ONLY, &(linkPtr->nsPtr), &dummy, &dummy, &name); linkPtr->nsPtr->refCount++; objPtr = ObjValue(linkPtr); if (Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, objPtr, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { Tcl_DecrRefCount(linkPtr->varName); LinkFree(linkPtr); return TCL_ERROR; } code = Tcl_TraceVar2(interp, varName, NULL, TCL_GLOBAL_ONLY|TCL_TRACE_READS|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, LinkTraceProc, linkPtr); if (code != TCL_OK) { Tcl_DecrRefCount(linkPtr->varName); LinkFree(linkPtr); } return code; } /* *---------------------------------------------------------------------- * * Tcl_UnlinkVar -- * * Destroy the link between a Tcl variable and a C variable. * * Results: * None. * * Side effects: * If "varName" was previously linked to a C variable, the link is broken * to make the variable independent. If there was no previous link for * "varName" then nothing happens. * *---------------------------------------------------------------------- */ void Tcl_UnlinkVar( Tcl_Interp *interp, /* Interpreter containing variable to unlink */ const char *varName) /* Global variable in interp to unlink. */ { Link *linkPtr = (Link *) Tcl_VarTraceInfo2(interp, varName, NULL, TCL_GLOBAL_ONLY, LinkTraceProc, NULL); if (linkPtr == NULL) { return; } Tcl_UntraceVar2(interp, varName, NULL, TCL_GLOBAL_ONLY|TCL_TRACE_READS|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, LinkTraceProc, linkPtr); Tcl_DecrRefCount(linkPtr->varName); LinkFree(linkPtr); } /* *---------------------------------------------------------------------- * * Tcl_UpdateLinkedVar -- * * This function is invoked after a linked variable has been changed by C * code. It updates the Tcl variable so that traces on the variable will * trigger. * * Results: * None. * * Side effects: * The Tcl variable "varName" is updated from its C value, causing traces * on the variable to trigger. * *---------------------------------------------------------------------- */ void Tcl_UpdateLinkedVar( Tcl_Interp *interp, /* Interpreter containing variable. */ const char *varName) /* Name of global variable that is linked. */ { Link *linkPtr = (Link *) Tcl_VarTraceInfo2(interp, varName, NULL, TCL_GLOBAL_ONLY, LinkTraceProc, NULL); int savedFlag; if (linkPtr == NULL) { return; } savedFlag = linkPtr->flags & LINK_BEING_UPDATED; linkPtr->flags |= LINK_BEING_UPDATED; Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); /* * Callback may have unlinked the variable. [Bug 1740631] */ linkPtr = (Link *) Tcl_VarTraceInfo2(interp, varName, NULL, TCL_GLOBAL_ONLY, LinkTraceProc, NULL); if (linkPtr != NULL) { linkPtr->flags = (linkPtr->flags & ~LINK_BEING_UPDATED) | savedFlag; } } /* *---------------------------------------------------------------------- * * GetInt, GetWide, GetUWide, GetDouble, EqualDouble, IsSpecial -- * * Helper functions for LinkTraceProc and ObjValue. These are all * factored out here to make those functions simpler. * *---------------------------------------------------------------------- */ static inline int GetInt( Tcl_Obj *objPtr, int *intPtr) { return (Tcl_GetIntFromObj(NULL, objPtr, intPtr) != TCL_OK && GetInvalidIntFromObj(objPtr, intPtr) != TCL_OK); } static inline int GetWide( Tcl_Obj *objPtr, Tcl_WideInt *widePtr) { if (TclGetWideIntFromObj(NULL, objPtr, widePtr) != TCL_OK) { int intValue; if (GetInvalidIntFromObj(objPtr, &intValue) != TCL_OK) { return 1; } *widePtr = intValue; } return 0; } static inline int GetUWide( Tcl_Obj *objPtr, Tcl_WideUInt *uwidePtr) { if (Tcl_GetWideUIntFromObj(NULL, objPtr, uwidePtr) != TCL_OK) { int intValue; if (GetInvalidIntFromObj(objPtr, &intValue) != TCL_OK) { return 1; } *uwidePtr = intValue; } return 0; } static inline int GetDouble( Tcl_Obj *objPtr, double *dblPtr) { if (Tcl_GetDoubleFromObj(NULL, objPtr, dblPtr) == TCL_OK) { return 0; } else { #ifdef ACCEPT_NAN Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(objPtr, &tclDoubleType); if (irPtr != NULL) { *dblPtr = irPtr->doubleValue; return 0; } #endif /* ACCEPT_NAN */ return GetInvalidDoubleFromObj(objPtr, dblPtr) != TCL_OK; } } static inline int EqualDouble( double a, double b) { return (a == b) #ifdef ACCEPT_NAN || (isnan(a) && isnan(b)) #endif /* ACCEPT_NAN */ ; } static inline int IsSpecial( double a) { return isinf(a) #ifdef ACCEPT_NAN || isnan(a) #endif /* ACCEPT_NAN */ ; } /* * Mark an object as holding a weird double. */ static int SetInvalidRealFromAny( TCL_UNUSED(Tcl_Interp *), Tcl_Obj *objPtr) { const char *str; const char *endPtr; Tcl_Size length; str = TclGetStringFromObj(objPtr, &length); if ((length == 1) && (str[0] == '.')) { objPtr->typePtr = &invalidRealType; objPtr->internalRep.doubleValue = 0.0; return TCL_OK; } if (TclParseNumber(NULL, objPtr, NULL, str, length, &endPtr, TCL_PARSE_DECIMAL_ONLY) == TCL_OK) { /* * If number is followed by [eE][+-]?, then it is an invalid * double, but it could be the start of a valid double. */ if (*endPtr == 'e' || *endPtr == 'E') { ++endPtr; if (*endPtr == '+' || *endPtr == '-') { ++endPtr; } if (*endPtr == 0) { double doubleValue = 0.0; Tcl_GetDoubleFromObj(NULL, objPtr, &doubleValue); TclFreeInternalRep(objPtr); objPtr->typePtr = &invalidRealType; objPtr->internalRep.doubleValue = doubleValue; return TCL_OK; } } } return TCL_ERROR; } /* * This function checks for integer representations, which are valid * when linking with C variables, but which are invalid in other * contexts in Tcl. Handled are "+", "-", "", "0x", "0b", "0d" and "0o" * (upperand lowercase). See bug [39f6304c2e]. */ static int GetInvalidIntFromObj( Tcl_Obj *objPtr, int *intPtr) { Tcl_Size length; const char *str = TclGetStringFromObj(objPtr, &length); if ((length == 0) || ((length == 2) && (str[0] == '0') && strchr("xXbBoOdD", str[1]))) { *intPtr = 0; return TCL_OK; } else if ((length == 1) && strchr("+-", str[0])) { *intPtr = (str[0] == '+'); return TCL_OK; } return TCL_ERROR; } /* * This function checks for double representations, which are valid * when linking with C variables, but which are invalid in other * contexts in Tcl. Handled are "+", "-", "", ".", "0x", "0b" and "0o" * (upper- and lowercase) and sequences like "1e-". See bug [39f6304c2e]. */ static int GetInvalidDoubleFromObj( Tcl_Obj *objPtr, double *doublePtr) { int intValue; if (TclHasInternalRep(objPtr, &invalidRealType)) { goto gotdouble; } if (GetInvalidIntFromObj(objPtr, &intValue) == TCL_OK) { *doublePtr = (double) intValue; return TCL_OK; } if (SetInvalidRealFromAny(NULL, objPtr) == TCL_OK) { gotdouble: *doublePtr = objPtr->internalRep.doubleValue; return TCL_OK; } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * LinkTraceProc -- * * This function is invoked when a linked Tcl variable is read, written, * or unset from Tcl. It's responsible for keeping the C variable in sync * with the Tcl variable. * * Results: * If all goes well, NULL is returned; otherwise an error message is * returned. * * Side effects: * The C variable may be updated to make it consistent with the Tcl * variable, or the Tcl variable may be overwritten to reject a * modification. * *---------------------------------------------------------------------- */ static char * LinkTraceProc( void *clientData, /* Contains information about the link. */ Tcl_Interp *interp, /* Interpreter containing Tcl variable. */ TCL_UNUSED(const char *) /*name1*/, TCL_UNUSED(const char *) /*name2*/, /* Links can only be made to global variables, * so we can find them with need to resolve * caller-supplied name in caller context. */ int flags) /* Miscellaneous additional information. */ { Link *linkPtr = (Link *)clientData; int changed; Tcl_Size valueLength = 0; const char *value; char **pp; Tcl_Obj *valueObj; int valueInt; Tcl_WideInt valueWide; Tcl_WideUInt valueUWide; double valueDouble; Tcl_Size objc, i; Tcl_Obj **objv; /* * If the variable is being unset, then just re-create it (with a trace) * unless the whole interpreter is going away. */ if (flags & TCL_TRACE_UNSETS) { if (Tcl_InterpDeleted(interp) || TclNamespaceDeleted(linkPtr->nsPtr)) { Tcl_DecrRefCount(linkPtr->varName); LinkFree(linkPtr); } else if (flags & TCL_TRACE_DESTROYED) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); Tcl_TraceVar2(interp, TclGetString(linkPtr->varName), NULL, TCL_GLOBAL_ONLY|TCL_TRACE_READS|TCL_TRACE_WRITES |TCL_TRACE_UNSETS, LinkTraceProc, linkPtr); } return NULL; } /* * If we were invoked because of a call to Tcl_UpdateLinkedVar, then don't * do anything at all. In particular, we don't want to get upset that the * variable is being modified, even if it is supposed to be read-only. */ if (linkPtr->flags & LINK_BEING_UPDATED) { return NULL; } /* * For read accesses, update the Tcl variable if the C variable has * changed since the last time we updated the Tcl variable. */ if (flags & TCL_TRACE_READS) { /* * Variable arrays */ if (linkPtr->flags & LINK_ALLOC_LAST) { changed = memcmp(linkPtr->addr, linkPtr->lastValue.aryPtr, linkPtr->bytes); } else { /* single variables */ switch (linkPtr->type) { case TCL_LINK_INT: case TCL_LINK_BOOLEAN: changed = (LinkedVar(int) != linkPtr->lastValue.i); break; case TCL_LINK_DOUBLE: changed = !EqualDouble(LinkedVar(double), linkPtr->lastValue.d); break; case TCL_LINK_WIDE_INT: changed = (LinkedVar(Tcl_WideInt) != linkPtr->lastValue.w); break; case TCL_LINK_WIDE_UINT: changed = (LinkedVar(Tcl_WideUInt) != linkPtr->lastValue.uw); break; case TCL_LINK_CHAR: changed = (LinkedVar(char) != linkPtr->lastValue.c); break; case TCL_LINK_UCHAR: changed = (LinkedVar(unsigned char) != linkPtr->lastValue.uc); break; case TCL_LINK_SHORT: changed = (LinkedVar(short) != linkPtr->lastValue.s); break; case TCL_LINK_USHORT: changed = (LinkedVar(unsigned short) != linkPtr->lastValue.us); break; case TCL_LINK_UINT: changed = (LinkedVar(unsigned int) != linkPtr->lastValue.ui); break; case TCL_LINK_FLOAT: changed = !EqualDouble(LinkedVar(float), linkPtr->lastValue.f); break; case TCL_LINK_STRING: case TCL_LINK_CHARS: case TCL_LINK_BINARY: changed = 1; break; default: changed = 0; /* return (char *) "internal error: bad linked variable type"; */ } } if (changed) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); } return NULL; } /* * For writes, first make sure that the variable is writable. Then convert * the Tcl value to C if possible. If the variable isn't writable or can't * be converted, then restore the variable's old value and return an * error. Another tricky thing: we have to save and restore the interp's * result, since the variable access could occur when the result has been * partially set. */ if (linkPtr->flags & LINK_READ_ONLY) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "linked variable is read-only"; } valueObj = Tcl_ObjGetVar2(interp, linkPtr->varName,NULL, TCL_GLOBAL_ONLY); if (valueObj == NULL) { /* * This shouldn't ever happen. */ return (char *) "internal error: linked variable couldn't be read"; } /* * Special cases. */ switch (linkPtr->type) { case TCL_LINK_STRING: value = TclGetStringFromObj(valueObj, &valueLength); pp = (char **) linkPtr->addr; *pp = (char *)Tcl_Realloc(*pp, ++valueLength); memcpy(*pp, value, valueLength); return NULL; case TCL_LINK_CHARS: value = (char *) TclGetStringFromObj(valueObj, &valueLength); valueLength++; /* include end of string char */ if (valueLength > linkPtr->bytes) { return (char *) "wrong size of char* value"; } if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, value, valueLength); memcpy(linkPtr->addr, value, valueLength); } else { linkPtr->lastValue.c = '\0'; LinkedVar(char) = linkPtr->lastValue.c; } return NULL; case TCL_LINK_BINARY: value = (char *) Tcl_GetBytesFromObj(NULL, valueObj, &valueLength); if (value == NULL) { return (char *) "invalid binary value"; } else if (valueLength != linkPtr->bytes) { return (char *) "wrong size of binary value"; } if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, value, valueLength); memcpy(linkPtr->addr, value, valueLength); } else { linkPtr->lastValue.uc = (unsigned char) *value; LinkedVar(unsigned char) = linkPtr->lastValue.uc; } return NULL; } /* * A helper macro. Writing this as a function is messy because of type * variance. */ #define InRange(lowerLimit, value, upperLimit) \ ((value) >= (lowerLimit) && (value) <= (upperLimit)) /* * If we're working with an array of numbers, extract the Tcl list. */ if (linkPtr->flags & LINK_ALLOC_LAST) { if (TclListObjGetElements(NULL, (valueObj), &objc, &objv) == TCL_ERROR || objc != linkPtr->numElems) { return (char *) "wrong dimension"; } } switch (linkPtr->type) { case TCL_LINK_INT: if (linkPtr->flags & LINK_ALLOC_LAST) { for (i = 0; i < objc; i++) { int *varPtr = &linkPtr->lastValue.iPtr[i]; if (GetInt(objv[i], varPtr)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable array must have integer values"; } } } else { int *varPtr = &linkPtr->lastValue.i; if (GetInt(valueObj, varPtr)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable must have integer value"; } LinkedVar(int) = *varPtr; } break; case TCL_LINK_WIDE_INT: if (linkPtr->flags & LINK_ALLOC_LAST) { for (i=0; i < objc; i++) { Tcl_WideInt *varPtr = &linkPtr->lastValue.wPtr[i]; if (GetWide(objv[i], varPtr)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable array must have wide integer value"; } } } else { Tcl_WideInt *varPtr = &linkPtr->lastValue.w; if (GetWide(valueObj, varPtr)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable must have wide integer value"; } LinkedVar(Tcl_WideInt) = *varPtr; } break; case TCL_LINK_DOUBLE: if (linkPtr->flags & LINK_ALLOC_LAST) { for (i=0; i < objc; i++) { if (GetDouble(objv[i], &linkPtr->lastValue.dPtr[i])) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable array must have real value"; } } } else { double *varPtr = &linkPtr->lastValue.d; if (GetDouble(valueObj, varPtr)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable must have real value"; } LinkedVar(double) = *varPtr; } break; case TCL_LINK_BOOLEAN: if (linkPtr->flags & LINK_ALLOC_LAST) { for (i=0; i < objc; i++) { int *varPtr = &linkPtr->lastValue.iPtr[i]; if (Tcl_GetBooleanFromObj(NULL, objv[i], varPtr) != TCL_OK) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable array must have boolean value"; } } } else { int *varPtr = &linkPtr->lastValue.i; if (Tcl_GetBooleanFromObj(NULL, valueObj, varPtr) != TCL_OK) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable must have boolean value"; } LinkedVar(int) = *varPtr; } break; case TCL_LINK_CHAR: if (linkPtr->flags & LINK_ALLOC_LAST) { for (i=0; i < objc; i++) { if (GetInt(objv[i], &valueInt) || !InRange(SCHAR_MIN, valueInt, SCHAR_MAX)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable array must have char value"; } linkPtr->lastValue.cPtr[i] = (char) valueInt; } } else { if (GetInt(valueObj, &valueInt) || !InRange(SCHAR_MIN, valueInt, SCHAR_MAX)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable must have char value"; } LinkedVar(char) = linkPtr->lastValue.c = (char) valueInt; } break; case TCL_LINK_UCHAR: if (linkPtr->flags & LINK_ALLOC_LAST) { for (i=0; i < objc; i++) { if (GetInt(objv[i], &valueInt) || !InRange(0, valueInt, (int)UCHAR_MAX)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable array must have unsigned char value"; } linkPtr->lastValue.ucPtr[i] = (unsigned char) valueInt; } } else { if (GetInt(valueObj, &valueInt) || !InRange(0, valueInt, (int)UCHAR_MAX)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable must have unsigned char value"; } LinkedVar(unsigned char) = linkPtr->lastValue.uc = (unsigned char) valueInt; } break; case TCL_LINK_SHORT: if (linkPtr->flags & LINK_ALLOC_LAST) { for (i=0; i < objc; i++) { if (GetInt(objv[i], &valueInt) || !InRange(SHRT_MIN, valueInt, SHRT_MAX)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable array must have short value"; } linkPtr->lastValue.sPtr[i] = (short) valueInt; } } else { if (GetInt(valueObj, &valueInt) || !InRange(SHRT_MIN, valueInt, SHRT_MAX)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable must have short value"; } LinkedVar(short) = linkPtr->lastValue.s = (short) valueInt; } break; case TCL_LINK_USHORT: if (linkPtr->flags & LINK_ALLOC_LAST) { for (i=0; i < objc; i++) { if (GetInt(objv[i], &valueInt) || !InRange(0, valueInt, (int)USHRT_MAX)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable array must have unsigned short value"; } linkPtr->lastValue.usPtr[i] = (unsigned short) valueInt; } } else { if (GetInt(valueObj, &valueInt) || !InRange(0, valueInt, (int)USHRT_MAX)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable must have unsigned short value"; } LinkedVar(unsigned short) = linkPtr->lastValue.us = (unsigned short) valueInt; } break; case TCL_LINK_UINT: if (linkPtr->flags & LINK_ALLOC_LAST) { for (i=0; i < objc; i++) { if (GetWide(objv[i], &valueWide) || !InRange(0, valueWide, (Tcl_WideInt)UINT_MAX)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable array must have unsigned int value"; } linkPtr->lastValue.uiPtr[i] = (unsigned int) valueWide; } } else { if (GetWide(valueObj, &valueWide) || !InRange(0, valueWide, (Tcl_WideInt)UINT_MAX)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable must have unsigned int value"; } LinkedVar(unsigned int) = linkPtr->lastValue.ui = (unsigned int) valueWide; } break; case TCL_LINK_WIDE_UINT: if (linkPtr->flags & LINK_ALLOC_LAST) { for (i=0; i < objc; i++) { if (GetUWide(objv[i], &valueUWide)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable array must have unsigned wide int value"; } linkPtr->lastValue.uwPtr[i] = valueUWide; } } else { if (GetUWide(valueObj, &valueUWide)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *) "variable must have unsigned wide int value"; } LinkedVar(Tcl_WideUInt) = linkPtr->lastValue.uw = valueUWide; } break; case TCL_LINK_FLOAT: if (linkPtr->flags & LINK_ALLOC_LAST) { for (i=0; i < objc; i++) { if (GetDouble(objv[i], &valueDouble) && !InRange(FLT_MIN, fabs(valueDouble), FLT_MAX) && !IsSpecial(valueDouble)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *)"variable array must have float value"; } linkPtr->lastValue.fPtr[i] = (float) valueDouble; } } else { if (GetDouble(valueObj, &valueDouble) && !InRange(FLT_MIN, fabs(valueDouble), FLT_MAX) && !IsSpecial(valueDouble)) { Tcl_ObjSetVar2(interp, linkPtr->varName, NULL, ObjValue(linkPtr), TCL_GLOBAL_ONLY); return (char *)"variable must have float value"; } LinkedVar(float) = linkPtr->lastValue.f = (float) valueDouble; } break; default: return (char *) "internal error: bad linked variable type"; } if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->addr, linkPtr->lastValue.aryPtr, linkPtr->bytes); } return NULL; } /* *---------------------------------------------------------------------- * * ObjValue -- * * Converts the value of a C variable to a Tcl_Obj* for use in a Tcl * variable to which it is linked. * * Results: * The return value is a pointer to a Tcl_Obj that represents the value * of the C variable given by linkPtr. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_Obj * ObjValue( Link *linkPtr) /* Structure describing linked variable. */ { char *p; Tcl_Obj *resultObj, **objv; Tcl_Size i; switch (linkPtr->type) { case TCL_LINK_INT: if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, linkPtr->addr, linkPtr->bytes); objv = (Tcl_Obj **)Tcl_Alloc(linkPtr->numElems * sizeof(Tcl_Obj *)); for (i=0; i < linkPtr->numElems; i++) { TclNewIntObj(objv[i], linkPtr->lastValue.iPtr[i]); } resultObj = Tcl_NewListObj(linkPtr->numElems, objv); Tcl_Free(objv); return resultObj; } linkPtr->lastValue.i = LinkedVar(int); return Tcl_NewWideIntObj(linkPtr->lastValue.i); case TCL_LINK_WIDE_INT: if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, linkPtr->addr, linkPtr->bytes); objv = (Tcl_Obj **)Tcl_Alloc(linkPtr->numElems * sizeof(Tcl_Obj *)); for (i=0; i < linkPtr->numElems; i++) { TclNewIntObj(objv[i], linkPtr->lastValue.wPtr[i]); } resultObj = Tcl_NewListObj(linkPtr->numElems, objv); Tcl_Free(objv); return resultObj; } linkPtr->lastValue.w = LinkedVar(Tcl_WideInt); return Tcl_NewWideIntObj(linkPtr->lastValue.w); case TCL_LINK_DOUBLE: if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, linkPtr->addr, linkPtr->bytes); objv = (Tcl_Obj **)Tcl_Alloc(linkPtr->numElems * sizeof(Tcl_Obj *)); for (i=0; i < linkPtr->numElems; i++) { TclNewDoubleObj(objv[i], linkPtr->lastValue.dPtr[i]); } resultObj = Tcl_NewListObj(linkPtr->numElems, objv); Tcl_Free(objv); return resultObj; } linkPtr->lastValue.d = LinkedVar(double); return Tcl_NewDoubleObj(linkPtr->lastValue.d); case TCL_LINK_BOOLEAN: if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, linkPtr->addr, linkPtr->bytes); objv = (Tcl_Obj **)Tcl_Alloc(linkPtr->numElems * sizeof(Tcl_Obj *)); for (i=0; i < linkPtr->numElems; i++) { objv[i] = Tcl_NewBooleanObj(linkPtr->lastValue.iPtr[i] != 0); } resultObj = Tcl_NewListObj(linkPtr->numElems, objv); Tcl_Free(objv); return resultObj; } linkPtr->lastValue.i = LinkedVar(int); return Tcl_NewBooleanObj(linkPtr->lastValue.i); case TCL_LINK_CHAR: if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, linkPtr->addr, linkPtr->bytes); objv = (Tcl_Obj **)Tcl_Alloc(linkPtr->numElems * sizeof(Tcl_Obj *)); for (i=0; i < linkPtr->numElems; i++) { TclNewIntObj(objv[i], linkPtr->lastValue.cPtr[i]); } resultObj = Tcl_NewListObj(linkPtr->numElems, objv); Tcl_Free(objv); return resultObj; } linkPtr->lastValue.c = LinkedVar(char); return Tcl_NewWideIntObj(linkPtr->lastValue.c); case TCL_LINK_UCHAR: if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, linkPtr->addr, linkPtr->bytes); objv = (Tcl_Obj **)Tcl_Alloc(linkPtr->numElems * sizeof(Tcl_Obj *)); for (i=0; i < linkPtr->numElems; i++) { TclNewIntObj(objv[i], linkPtr->lastValue.ucPtr[i]); } resultObj = Tcl_NewListObj(linkPtr->numElems, objv); Tcl_Free(objv); return resultObj; } linkPtr->lastValue.uc = LinkedVar(unsigned char); return Tcl_NewWideIntObj(linkPtr->lastValue.uc); case TCL_LINK_SHORT: if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, linkPtr->addr, linkPtr->bytes); objv = (Tcl_Obj **)Tcl_Alloc(linkPtr->numElems * sizeof(Tcl_Obj *)); for (i=0; i < linkPtr->numElems; i++) { TclNewIntObj(objv[i], linkPtr->lastValue.sPtr[i]); } resultObj = Tcl_NewListObj(linkPtr->numElems, objv); Tcl_Free(objv); return resultObj; } linkPtr->lastValue.s = LinkedVar(short); return Tcl_NewWideIntObj(linkPtr->lastValue.s); case TCL_LINK_USHORT: if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, linkPtr->addr, linkPtr->bytes); objv = (Tcl_Obj **)Tcl_Alloc(linkPtr->numElems * sizeof(Tcl_Obj *)); for (i=0; i < linkPtr->numElems; i++) { TclNewIntObj(objv[i], linkPtr->lastValue.usPtr[i]); } resultObj = Tcl_NewListObj(linkPtr->numElems, objv); Tcl_Free(objv); return resultObj; } linkPtr->lastValue.us = LinkedVar(unsigned short); return Tcl_NewWideIntObj(linkPtr->lastValue.us); case TCL_LINK_UINT: if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, linkPtr->addr, linkPtr->bytes); objv = (Tcl_Obj **)Tcl_Alloc(linkPtr->numElems * sizeof(Tcl_Obj *)); for (i=0; i < linkPtr->numElems; i++) { TclNewIntObj(objv[i], linkPtr->lastValue.uiPtr[i]); } resultObj = Tcl_NewListObj(linkPtr->numElems, objv); Tcl_Free(objv); return resultObj; } linkPtr->lastValue.ui = LinkedVar(unsigned int); return Tcl_NewWideIntObj((Tcl_WideInt) linkPtr->lastValue.ui); case TCL_LINK_FLOAT: if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, linkPtr->addr, linkPtr->bytes); objv = (Tcl_Obj **)Tcl_Alloc(linkPtr->numElems * sizeof(Tcl_Obj *)); for (i=0; i < linkPtr->numElems; i++) { TclNewDoubleObj(objv[i], linkPtr->lastValue.fPtr[i]); } resultObj = Tcl_NewListObj(linkPtr->numElems, objv); Tcl_Free(objv); return resultObj; } linkPtr->lastValue.f = LinkedVar(float); return Tcl_NewDoubleObj(linkPtr->lastValue.f); case TCL_LINK_WIDE_UINT: { if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, linkPtr->addr, linkPtr->bytes); objv = (Tcl_Obj **)Tcl_Alloc(linkPtr->numElems * sizeof(Tcl_Obj *)); for (i=0; i < linkPtr->numElems; i++) { TclNewUIntObj(objv[i], linkPtr->lastValue.uwPtr[i]); } resultObj = Tcl_NewListObj(linkPtr->numElems, objv); Tcl_Free(objv); return resultObj; } linkPtr->lastValue.uw = LinkedVar(Tcl_WideUInt); Tcl_Obj *uwObj; TclNewUIntObj(uwObj, linkPtr->lastValue.uw); return uwObj; } case TCL_LINK_STRING: p = LinkedVar(char *); if (p == NULL) { TclNewLiteralStringObj(resultObj, "NULL"); return resultObj; } return Tcl_NewStringObj(p, -1); case TCL_LINK_CHARS: if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, linkPtr->addr, linkPtr->bytes); linkPtr->lastValue.cPtr[linkPtr->bytes-1] = '\0'; /* take care of proper string end */ return Tcl_NewStringObj(linkPtr->lastValue.cPtr, linkPtr->bytes); } linkPtr->lastValue.c = '\0'; return Tcl_NewStringObj(&linkPtr->lastValue.c, 1); case TCL_LINK_BINARY: if (linkPtr->flags & LINK_ALLOC_LAST) { memcpy(linkPtr->lastValue.aryPtr, linkPtr->addr, linkPtr->bytes); return Tcl_NewByteArrayObj((unsigned char *) linkPtr->addr, linkPtr->bytes); } linkPtr->lastValue.uc = LinkedVar(unsigned char); return Tcl_NewByteArrayObj(&linkPtr->lastValue.uc, 1); /* * This code only gets executed if the link type is unknown (shouldn't * ever happen). */ default: TclNewLiteralStringObj(resultObj, "??"); return resultObj; } } /* *---------------------------------------------------------------------- * * LinkFree -- * * Free's allocated space of given link and link structure. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void LinkFree( Link *linkPtr) /* Structure describing linked variable. */ { if (linkPtr->nsPtr) { TclNsDecrRefCount(linkPtr->nsPtr); } if (linkPtr->flags & LINK_ALLOC_ADDR) { Tcl_Free(linkPtr->addr); } if (linkPtr->flags & LINK_ALLOC_LAST) { Tcl_Free(linkPtr->lastValue.aryPtr); } Tcl_Free(linkPtr); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclListObj.c0000644000175000017500000034541714726623136015425 0ustar sergeisergei/* * tclListObj.c -- * * This file contains functions that implement the Tcl list object type. * * Copyright © 2022 Ashok P. Nadkarni. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include #include "tclInt.h" #include "tclTomMath.h" /* * TODO - memmove is fast. Measure at what size we should prefer memmove * (for unshared objects only) in lieu of range operations. On the other * hand, more cache dirtied? */ /* * Macros for validation and bug checking. */ /* * Control whether asserts are enabled. Always enable in debug builds. In non-debug * builds, can be set with cdebug="-DENABLE_LIST_ASSERTS" on the nmake command line. */ #ifdef ENABLE_LIST_ASSERTS # ifdef NDEBUG # undef NDEBUG /* Activate assert() macro */ # endif #else # ifndef NDEBUG # define ENABLE_LIST_ASSERTS /* Always activate list asserts in debug mode */ # endif #endif #ifdef ENABLE_LIST_ASSERTS #define LIST_ASSERT(cond_) assert(cond_) /* * LIST_INDEX_ASSERT is to catch errors with negative indices and counts * being passed AFTER validation. On Tcl9 length types are unsigned hence * the checks against LIST_MAX. On Tcl8 length types are signed hence the * also checks against 0. */ #define LIST_INDEX_ASSERT(idxarg_) \ do { \ Tcl_Size idx_ = (idxarg_); /* To guard against ++ etc. */ \ LIST_ASSERT(idx_ >= 0 && idx_ < LIST_MAX); \ } while (0) /* Ditto for counts except upper limit is different */ #define LIST_COUNT_ASSERT(countarg_) \ do { \ Tcl_Size count_ = (countarg_); /* To guard against ++ etc. */ \ LIST_ASSERT(count_ >= 0 && count_ <= LIST_MAX); \ } while (0) #else #define LIST_ASSERT(cond_) ((void) 0) #define LIST_INDEX_ASSERT(idx_) ((void) 0) #define LIST_COUNT_ASSERT(count_) ((void) 0) #endif /* Checks for when caller should have already converted to internal list type */ #define LIST_ASSERT_TYPE(listObj_) \ LIST_ASSERT(TclHasInternalRep((listObj_), &tclListType)) /* * If ENABLE_LIST_INVARIANTS is enabled (-DENABLE_LIST_INVARIANTS from the * command line), the entire list internal representation is checked for * inconsistencies. This has a non-trivial cost so has to be separately * enabled and not part of assertions checking. However, the test suite does * invoke ListRepValidate directly even without ENABLE_LIST_INVARIANTS. */ #ifdef ENABLE_LIST_INVARIANTS #define LISTREP_CHECK(listRepPtr_) ListRepValidate(listRepPtr_, __FILE__, __LINE__) #else #define LISTREP_CHECK(listRepPtr_) (void) 0 #endif /* * Flags used for controlling behavior of allocation of list * internal representations. * * If the LISTREP_PANIC_ON_FAIL bit is set, the function will panic if * list is too large or memory cannot be allocated. Without the flag * a NULL pointer is returned. * * The LISTREP_SPACE_FAVOR_NONE, LISTREP_SPACE_FAVOR_FRONT, * LISTREP_SPACE_FAVOR_BACK, LISTREP_SPACE_ONLY_BACK flags are used to * control additional space when allocating. * - If none of these flags is present, the exact space requested is * allocated, nothing more. * - Otherwise, if only LISTREP_FAVOR_FRONT is present, extra space is * allocated with more towards the front. * - Conversely, if only LISTREP_FAVOR_BACK is present extra space is allocated * with more to the back. * - If both flags are present (LISTREP_SPACE_FAVOR_NONE), the extra space * is equally apportioned. * - Finally if LISTREP_SPACE_ONLY_BACK is present, ALL extra space is at * the back. */ #define LISTREP_PANIC_ON_FAIL 0x00000001 #define LISTREP_SPACE_FAVOR_FRONT 0x00000002 #define LISTREP_SPACE_FAVOR_BACK 0x00000004 #define LISTREP_SPACE_ONLY_BACK 0x00000008 #define LISTREP_SPACE_FAVOR_NONE \ (LISTREP_SPACE_FAVOR_FRONT | LISTREP_SPACE_FAVOR_BACK) #define LISTREP_SPACE_FLAGS \ (LISTREP_SPACE_FAVOR_FRONT | LISTREP_SPACE_FAVOR_BACK \ | LISTREP_SPACE_ONLY_BACK) /* * Prototypes for non-inline static functions defined later in this file: */ static int MemoryAllocationError(Tcl_Interp *, size_t size); static int ListLimitExceededError(Tcl_Interp *); static ListStore *ListStoreNew(Tcl_Size objc, Tcl_Obj *const objv[], int flags); static int ListRepInit(Tcl_Size objc, Tcl_Obj *const objv[], int flags, ListRep *); static int ListRepInitAttempt(Tcl_Interp *, Tcl_Size objc, Tcl_Obj *const objv[], ListRep *); static void ListRepClone(ListRep *fromRepPtr, ListRep *toRepPtr, int flags); static void ListRepUnsharedFreeUnreferenced(const ListRep *repPtr); static int TclListObjGetRep(Tcl_Interp *, Tcl_Obj *listPtr, ListRep *repPtr); static void ListRepRange(ListRep *srcRepPtr, Tcl_Size rangeStart, Tcl_Size rangeEnd, int preserveSrcRep, ListRep *rangeRepPtr); static ListStore *ListStoreReallocate(ListStore *storePtr, Tcl_Size numSlots); static void ListRepValidate(const ListRep *repPtr, const char *file, int lineNum); static void DupListInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr); static void FreeListInternalRep(Tcl_Obj *listPtr); static int SetListFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); static void UpdateStringOfList(Tcl_Obj *listPtr); static Tcl_Size ListLength(Tcl_Obj *listPtr); /* * The structure below defines the list Tcl object type by means of functions * that can be invoked by generic object code. * * The internal representation of a list object is ListRep defined in tcl.h. */ const Tcl_ObjType tclListType = { "list", /* name */ FreeListInternalRep, /* freeIntRepProc */ DupListInternalRep, /* dupIntRepProc */ UpdateStringOfList, /* updateStringProc */ SetListFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V1(ListLength) }; /* Macros to manipulate the List internal rep */ #define ListRepIncrRefs(repPtr_) \ do { \ (repPtr_)->storePtr->refCount++; \ if ((repPtr_)->spanPtr) { \ (repPtr_)->spanPtr->refCount++; \ } \ } while (0) /* Returns number of free unused slots at the back of the ListRep's ListStore */ #define ListRepNumFreeTail(repPtr_) \ ((repPtr_)->storePtr->numAllocated \ - ((repPtr_)->storePtr->firstUsed + (repPtr_)->storePtr->numUsed)) /* Returns number of free unused slots at the front of the ListRep's ListStore */ #define ListRepNumFreeHead(repPtr_) ((repPtr_)->storePtr->firstUsed) /* Returns a pointer to the slot corresponding to list index listIdx_ */ #define ListRepSlotPtr(repPtr_, listIdx_) \ (&(repPtr_)->storePtr->slots[ListRepStart(repPtr_) + (listIdx_)]) /* * Macros to replace the internal representation in a Tcl_Obj. There are * subtle differences in each so make sure to use the right one to avoid * memory leaks, access to freed memory and the like. * * ListObjStompRep - assumes the Tcl_Obj internal representation can be * overwritten AND that the passed ListRep already has reference counts that * include the reference from the Tcl_Obj. Basically just copies the pointers * and sets the internal Tcl_Obj type to list * * ListObjOverwriteRep - like ListObjOverwriteRep but additionally * increments reference counts on the passed ListRep. Generally used when * the string representation of the Tcl_Obj is not to be modified. * * ListObjReplaceRepAndInvalidate - Like ListObjOverwriteRep but additionally * assumes the Tcl_Obj internal rep is valid (and possibly even same as * passed ListRep) and frees it first. Additionally invalidates the string * representation. Generally used when modifying a Tcl_Obj value. */ #define ListObjStompRep(objPtr_, repPtr_) \ do { \ (objPtr_)->internalRep.twoPtrValue.ptr1 = (repPtr_)->storePtr; \ (objPtr_)->internalRep.twoPtrValue.ptr2 = (repPtr_)->spanPtr; \ (objPtr_)->typePtr = &tclListType; \ } while (0) #define ListObjOverwriteRep(objPtr_, repPtr_) \ do { \ ListRepIncrRefs(repPtr_); \ ListObjStompRep(objPtr_, repPtr_); \ } while (0) #define ListObjReplaceRepAndInvalidate(objPtr_, repPtr_) \ do { \ /* Note order important, don't use ListObjOverwriteRep! */ \ ListRepIncrRefs(repPtr_); \ TclFreeInternalRep(objPtr_); \ TclInvalidateStringRep(objPtr_); \ ListObjStompRep(objPtr_, repPtr_); \ } while (0) /* *------------------------------------------------------------------------ * * ListSpanNew -- * * Allocates and initializes memory for a new ListSpan. The reference * count on the returned struct is 0. * * Results: * Non-NULL pointer to the allocated ListSpan. * * Side effects: * The function will panic on memory allocation failure. * *------------------------------------------------------------------------ */ static inline ListSpan * ListSpanNew( Tcl_Size firstSlot, /* Starting slot index of the span */ Tcl_Size numSlots) /* Number of slots covered by the span */ { ListSpan *spanPtr = (ListSpan *) Tcl_Alloc(sizeof(*spanPtr)); spanPtr->refCount = 0; spanPtr->spanStart = firstSlot; spanPtr->spanLength = numSlots; return spanPtr; } /* *------------------------------------------------------------------------ * * ListSpanDecrRefs -- * * Decrements the reference count on a span, freeing the memory if * it drops to zero or less. * * Results: * None. * * Side effects: * The memory may be freed. * *------------------------------------------------------------------------ */ static inline void ListSpanDecrRefs( ListSpan *spanPtr) { if (spanPtr->refCount <= 1) { Tcl_Free(spanPtr); } else { spanPtr->refCount -= 1; } } /* *------------------------------------------------------------------------ * * ListSpanMerited -- * * Creation of a new list may sometimes be done as a span on existing * storage instead of allocating new. The tradeoff is that if the * original list is released, the new span-based list may hold on to * more memory than desired. This function implements heuristics for * deciding which option is better. * * Results: * Returns non-0 if a span-based list is likely to be more optimal * and 0 if not. * * Side effects: * None. * *------------------------------------------------------------------------ */ static inline int ListSpanMerited( Tcl_Size length, /* Length of the proposed span */ Tcl_Size usedStorageLength, /* Number of slots currently in used */ Tcl_Size allocatedStorageLength) /* Length of the currently allocation */ { /* * Possible optimizations for future consideration * - heuristic LIST_SPAN_THRESHOLD * - currently, information about the sharing (ref count) of existing * storage is not passed. Perhaps it should be. For example if the * existing storage has a "large" ref count, then it might make sense * to do even a small span. */ if (length < LIST_SPAN_THRESHOLD) { return 0;/* No span for small lists */ } if (length < (allocatedStorageLength / 2 - allocatedStorageLength / 8)) { return 0; /* No span if less than 3/8 of allocation */ } if (length < usedStorageLength / 2) { return 0; /* No span if less than half current storage */ } return 1; } /* *------------------------------------------------------------------------ * * ListRepFreeUnreferenced -- * * Inline wrapper for ListRepUnsharedFreeUnreferenced that does quick checks * before calling it. * * IMPORTANT: this function must not be called on an internal * representation of a Tcl_Obj that is itself shared. * * Results: * None. * * Side effects: * See comments for ListRepUnsharedFreeUnreferenced. * *------------------------------------------------------------------------ */ static inline void ListRepFreeUnreferenced( const ListRep *repPtr) { if (! ListRepIsShared(repPtr) && repPtr->spanPtr) { /* T:listrep-1.5.1 */ ListRepUnsharedFreeUnreferenced(repPtr); } } /* *------------------------------------------------------------------------ * * ObjArrayIncrRefs -- * * Increments the reference counts for Tcl_Obj's in a subarray. * * Results: * None. * * Side effects: * As above. * *------------------------------------------------------------------------ */ static inline void ObjArrayIncrRefs( Tcl_Obj * const *objv, /* Pointer to the array */ Tcl_Size startIdx, /* Starting index of subarray within objv */ Tcl_Size count) /* Number of elements in the subarray */ { Tcl_Obj *const *end; LIST_INDEX_ASSERT(startIdx); LIST_COUNT_ASSERT(count); objv += startIdx; end = objv + count; while (objv < end) { Tcl_IncrRefCount(*objv); ++objv; } } /* *------------------------------------------------------------------------ * * ObjArrayDecrRefs -- * * Decrements the reference counts for Tcl_Obj's in a subarray. * * Results: * None. * * Side effects: * As above. * *------------------------------------------------------------------------ */ static inline void ObjArrayDecrRefs( Tcl_Obj * const *objv, /* Pointer to the array */ Tcl_Size startIdx, /* Starting index of subarray within objv */ Tcl_Size count) /* Number of elements in the subarray */ { Tcl_Obj * const *end; LIST_INDEX_ASSERT(startIdx); LIST_COUNT_ASSERT(count); objv += startIdx; end = objv + count; while (objv < end) { Tcl_DecrRefCount(*objv); ++objv; } } /* *------------------------------------------------------------------------ * * ObjArrayCopy -- * * Copies an array of Tcl_Obj* pointers. * * Results: * None. * * Side effects: * Reference counts on copied Tcl_Obj's are incremented. * *------------------------------------------------------------------------ */ static inline void ObjArrayCopy( Tcl_Obj **to, /* Destination */ Tcl_Size count, /* Number of pointers to copy */ Tcl_Obj *const from[]) /* Source array of Tcl_Obj* */ { Tcl_Obj **end; LIST_COUNT_ASSERT(count); end = to + count; /* TODO - would memmove followed by separate IncrRef loop be faster? */ while (to < end) { Tcl_IncrRefCount(*from); *to++ = *from++; } } /* *------------------------------------------------------------------------ * * MemoryAllocationError -- * * Generates a memory allocation failure error. * * Results: * Always TCL_ERROR. * * Side effects: * Error message and code are stored in the interpreter if not NULL. * *------------------------------------------------------------------------ */ static int MemoryAllocationError( Tcl_Interp *interp, /* Interpreter for error message. May be NULL */ size_t size) /* Size of attempted allocation that failed */ { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "list construction failed: unable to alloc %" TCL_Z_MODIFIER "u bytes", size)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); } return TCL_ERROR; } /* *------------------------------------------------------------------------ * * ListLimitExceeded -- * * Generates an error for exceeding maximum list size. * * Results: * Always TCL_ERROR. * * Side effects: * Error message and code are stored in the interpreter if not NULL. * *------------------------------------------------------------------------ */ static int ListLimitExceededError( Tcl_Interp *interp) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "max length of a Tcl list exceeded", -1)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); } return TCL_ERROR; } /* *------------------------------------------------------------------------ * * ListRepUnsharedShiftDown -- * * Shifts the "in-use" contents in the ListStore for a ListRep down * by the given number of slots. The ListStore must be unshared and * the free space at the front of the storage area must be big enough. * It is the caller's responsibility to check. * * Results: * None. * * Side effects: * The contents of the ListRep's ListStore area are shifted down in the * storage area. The ListRep's ListSpan is updated accordingly. * *------------------------------------------------------------------------ */ static inline void ListRepUnsharedShiftDown( ListRep *repPtr, Tcl_Size shiftCount) { ListStore *storePtr; LISTREP_CHECK(repPtr); LIST_ASSERT(!ListRepIsShared(repPtr)); storePtr = repPtr->storePtr; LIST_COUNT_ASSERT(shiftCount); LIST_ASSERT(storePtr->firstUsed >= shiftCount); memmove(&storePtr->slots[storePtr->firstUsed - shiftCount], &storePtr->slots[storePtr->firstUsed], storePtr->numUsed * sizeof(Tcl_Obj *)); storePtr->firstUsed -= shiftCount; if (repPtr->spanPtr) { repPtr->spanPtr->spanStart -= shiftCount; LIST_ASSERT(repPtr->spanPtr->spanLength == storePtr->numUsed); } else { /* * If there was no span, firstUsed must have been 0 (Invariant) * AND shiftCount must have been 0 (<= firstUsed on call) * In other words, this would have been a no-op */ LIST_ASSERT(storePtr->firstUsed == 0); LIST_ASSERT(shiftCount == 0); } LISTREP_CHECK(repPtr); } /* *------------------------------------------------------------------------ * * ListRepUnsharedShiftUp -- * * Shifts the "in-use" contents in the ListStore for a ListRep up * by the given number of slots. The ListStore must be unshared and * the free space at the back of the storage area must be big enough. * It is the caller's responsibility to check. * TODO - this function is not currently used. * * Results: * None. * * Side effects: * The contents of the ListRep's ListStore area are shifted up in the * storage area. The ListRep's ListSpan is updated accordingly. * *------------------------------------------------------------------------ */ #if 0 static inline void ListRepUnsharedShiftUp( ListRep *repPtr, Tcl_Size shiftCount) { ListStore *storePtr; LISTREP_CHECK(repPtr); LIST_ASSERT(!ListRepIsShared(repPtr)); LIST_COUNT_ASSERT(shiftCount); storePtr = repPtr->storePtr; LIST_ASSERT((storePtr->firstUsed + storePtr->numUsed + shiftCount) <= storePtr->numAllocated); memmove(&storePtr->slots[storePtr->firstUsed + shiftCount], &storePtr->slots[storePtr->firstUsed], storePtr->numUsed * sizeof(Tcl_Obj *)); storePtr->firstUsed += shiftCount; if (repPtr->spanPtr) { repPtr->spanPtr->spanStart += shiftCount; } else { /* No span means entire original list is span */ /* Should have been zero before shift - Invariant TBD */ LIST_ASSERT(storePtr->firstUsed == shiftCount); repPtr->spanPtr = ListSpanNew(shiftCount, storePtr->numUsed); } LISTREP_CHECK(repPtr); } #endif /* *------------------------------------------------------------------------ * * ListRepValidate -- * * Checks all invariants for a ListRep and panics on failure. * Note this is independent of NDEBUG, assert etc. * * Results: * None. * * Side effects: * Panics if any invariant is not met. * *------------------------------------------------------------------------ */ static void ListRepValidate( const ListRep *repPtr, const char *file, int lineNum) { ListStore *storePtr = repPtr->storePtr; const char *condition; (void)storePtr; /* To stop gcc from whining about unused vars */ #define INVARIANT(cond_) \ do { \ if (!(cond_)) { \ condition = #cond_; \ goto failure; \ } \ } while (0) /* Separate each condition so line number gives exact reason for failure */ INVARIANT(storePtr != NULL); INVARIANT(storePtr->numAllocated >= 0); INVARIANT(storePtr->numAllocated <= LIST_MAX); INVARIANT(storePtr->firstUsed >= 0); INVARIANT(storePtr->firstUsed < storePtr->numAllocated); INVARIANT(storePtr->numUsed >= 0); INVARIANT(storePtr->numUsed <= storePtr->numAllocated); INVARIANT(storePtr->firstUsed <= (storePtr->numAllocated - storePtr->numUsed)); if (! ListRepIsShared(repPtr)) { /* * If this is the only reference and there is no span, then store * occupancy must begin at 0 */ INVARIANT(repPtr->spanPtr || repPtr->storePtr->firstUsed == 0); } INVARIANT(ListRepStart(repPtr) >= storePtr->firstUsed); INVARIANT(ListRepLength(repPtr) <= storePtr->numUsed); INVARIANT(ListRepStart(repPtr) <= (storePtr->firstUsed + storePtr->numUsed - ListRepLength(repPtr))); #undef INVARIANT return; failure: Tcl_Panic("List internal failure in %s line %d. Condition: %s", file, lineNum, condition); } /* *------------------------------------------------------------------------ * * TclListObjValidate -- * * Wrapper around ListRepValidate. Primarily used from test suite. * * Results: * None. * * Side effects: * Will panic if internal structure is not consistent or if object * cannot be converted to a list object. * *------------------------------------------------------------------------ */ void TclListObjValidate( Tcl_Interp *interp, Tcl_Obj *listObj) { ListRep listRep; if (TclListObjGetRep(interp, listObj, &listRep) != TCL_OK) { Tcl_Panic("Object passed to TclListObjValidate cannot be converted to " "a list object."); } ListRepValidate(&listRep, __FILE__, __LINE__); } /* *---------------------------------------------------------------------- * * ListStoreNew -- * * Allocates a new ListStore with space for at least objc elements. objc * must be > 0. If objv!=NULL, initializes with the first objc values * in that array. If objv==NULL, initalize 0 elements, with space * to add objc more. * * Normally the function allocates the exact space requested unless * the flags arguments has any LISTREP_SPACE_* * bits set. See the comments for those #defines. * * Results: * On success, a pointer to the allocated ListStore is returned. * On allocation failure, panics if LISTREP_PANIC_ON_FAIL is set in * flags; otherwise returns NULL. * * Side effects: * The ref counts of the elements in objv are incremented on success * since the returned ListStore references them. * *---------------------------------------------------------------------- */ static ListStore * ListStoreNew( Tcl_Size objc, Tcl_Obj *const objv[], int flags) { ListStore *storePtr; Tcl_Size capacity; /* * First check to see if we'd overflow and try to allocate an object * larger than our memory allocator allows. */ if (objc > LIST_MAX) { if (flags & LISTREP_PANIC_ON_FAIL) { Tcl_Panic("max length of a Tcl list exceeded"); } return NULL; } storePtr = NULL; if (flags & LISTREP_SPACE_FLAGS) { /* Caller requests extra space front, back or both */ storePtr = (ListStore *)TclAttemptAllocElemsEx( objc, sizeof(Tcl_Obj *), offsetof(ListStore, slots), &capacity); } else { /* Exact allocation */ capacity = objc; storePtr = (ListStore *)Tcl_AttemptAlloc(LIST_SIZE(capacity)); } if (storePtr == NULL) { if (flags & LISTREP_PANIC_ON_FAIL) { Tcl_Panic("list creation failed: unable to alloc %" TCL_SIZE_MODIFIER "d bytes", LIST_SIZE(objc)); } return NULL; } storePtr->refCount = 0; storePtr->flags = 0; storePtr->numAllocated = capacity; if (capacity == objc) { storePtr->firstUsed = 0; } else { Tcl_Size extra = capacity - objc; int spaceFlags = flags & LISTREP_SPACE_FLAGS; if (spaceFlags == LISTREP_SPACE_ONLY_BACK) { storePtr->firstUsed = 0; } else if (spaceFlags == LISTREP_SPACE_FAVOR_FRONT) { /* Leave more space in the front */ storePtr->firstUsed = extra - (extra / 4); /* NOT same as 3*extra/4 */ } else if (spaceFlags == LISTREP_SPACE_FAVOR_BACK) { /* Leave more space in the back */ storePtr->firstUsed = extra / 4; } else { /* Apportion equally */ storePtr->firstUsed = extra / 2; } } if (objv) { storePtr->numUsed = objc; ObjArrayCopy(&storePtr->slots[storePtr->firstUsed], objc, objv); } else { storePtr->numUsed = 0; } return storePtr; } /* *------------------------------------------------------------------------ * * ListStoreReallocate -- * * Reallocates the memory for a ListStore allocating extra for * possible future growth. * * Results: * Pointer to the ListStore which may be the same as storePtr or pointer * to a new block of memory. On reallocation failure, NULL is returned. * * * Side effects: * The memory pointed to by storePtr is freed if it a new block has to * be returned. * * *------------------------------------------------------------------------ */ static ListStore * ListStoreReallocate( ListStore *storePtr, Tcl_Size needed) { Tcl_Size capacity; if (needed > LIST_MAX) { return NULL; } storePtr = (ListStore *) TclAttemptReallocElemsEx(storePtr, needed, sizeof(Tcl_Obj *), offsetof(ListStore, slots), &capacity); /* Only the capacity has changed, fix it in the header */ if (storePtr) { storePtr->numAllocated = capacity; } return storePtr; } /* *---------------------------------------------------------------------- * * ListRepInit -- * * Initializes a ListRep to hold a list internal representation * with space for objc elements. * * objc must be > 0. If objv!=NULL, initializes with the first objc * values in that array. If objv==NULL, initalize list internal rep to * have 0 elements, with space to add objc more. * * Normally the function allocates the exact space requested unless * the flags arguments has one of the LISTREP_SPACE_* bits set. * See the comments for those #defines. * * The reference counts of the ListStore and ListSpan (if present) * pointed to by the initialized repPtr are set to zero. * Caller has to manage them as necessary. * * Results: * On success, TCL_OK is returned with *listRepPtr initialized. * On failure, panics if LISTREP_PANIC_ON_FAIL is set in flags; otherwise * returns TCL_ERROR with *listRepPtr fields set to NULL. * * Side effects: * The ref counts of the elements in objv are incremented since the * resulting list now refers to them. * *---------------------------------------------------------------------- */ static int ListRepInit( Tcl_Size objc, Tcl_Obj *const objv[], int flags, ListRep *repPtr) { ListStore *storePtr; storePtr = ListStoreNew(objc, objv, flags); if (storePtr) { repPtr->storePtr = storePtr; if (storePtr->firstUsed == 0) { repPtr->spanPtr = NULL; } else { repPtr->spanPtr = ListSpanNew(storePtr->firstUsed, storePtr->numUsed); } return TCL_OK; } /* * Initialize to keep gcc happy at the call site. Else it complains * about possibly uninitialized use. */ repPtr->storePtr = NULL; repPtr->spanPtr = NULL; return TCL_ERROR; } /* *---------------------------------------------------------------------- * * ListRepInitAttempt -- * * Creates a list internal rep with space for objc elements. See * ListRepInit for requirements for parameters (in particular objc must * be > 0). This function only adds error messages to the interpreter if * not NULL. * * The reference counts of the ListStore and ListSpan (if present) * pointed to by the initialized repPtr are set to zero. * Caller has to manage them as necessary. * * Results: * On success, TCL_OK is returned with *listRepPtr initialized. * On allocation failure, returnes TCL_ERROR with an error message * in the interpreter if non-NULL. * * Side effects: * The ref counts of the elements in objv are incremented since the * resulting list now refers to them. * *---------------------------------------------------------------------- */ static int ListRepInitAttempt( Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], ListRep *repPtr) { int result = ListRepInit(objc, objv, 0, repPtr); if (result != TCL_OK && interp != NULL) { if (objc > LIST_MAX) { ListLimitExceededError(interp); } else { MemoryAllocationError(interp, LIST_SIZE(objc)); } } return result; } /* *------------------------------------------------------------------------ * * ListRepClone -- * * Does a deep clone of an existing ListRep. * * Normally the function allocates the exact space needed unless * the flags arguments has one of the LISTREP_SPACE_* bits set. * See the comments for those #defines. * * Results: * None. * * Side effects: * The toRepPtr location is initialized with the ListStore and ListSpan * (if needed) containing a copy of the list elements in fromRepPtr. * The function will panic if memory cannot be allocated. * *------------------------------------------------------------------------ */ static void ListRepClone( ListRep *fromRepPtr, ListRep *toRepPtr, int flags) { Tcl_Obj **fromObjs; Tcl_Size numFrom; ListRepElements(fromRepPtr, numFrom, fromObjs); ListRepInit(numFrom, fromObjs, flags | LISTREP_PANIC_ON_FAIL, toRepPtr); } /* *------------------------------------------------------------------------ * * ListRepUnsharedFreeUnreferenced -- * * Frees any Tcl_Obj's from the "in-use" area of the ListStore for a * ListRep that are not actually references from any lists. * * IMPORTANT: this function must not be called on a shared internal * representation or the internal representation of a shared Tcl_Obj. * * Results: * None. * * Side effects: * The firstUsed and numUsed fields of the ListStore are updated to * reflect the new "in-use" extent. * *------------------------------------------------------------------------ */ static void ListRepUnsharedFreeUnreferenced( const ListRep *repPtr) { Tcl_Size count; ListStore *storePtr; ListSpan *spanPtr; LIST_ASSERT(!ListRepIsShared(repPtr)); LISTREP_CHECK(repPtr); storePtr = repPtr->storePtr; spanPtr = repPtr->spanPtr; if (spanPtr == NULL) { LIST_ASSERT(storePtr->firstUsed == 0); /* Invariant TBD */ return; } /* Collect garbage at front */ count = spanPtr->spanStart - storePtr->firstUsed; LIST_COUNT_ASSERT(count); if (count > 0) { /* T:listrep-1.5.1,6.{1:8} */ ObjArrayDecrRefs(storePtr->slots, storePtr->firstUsed, count); storePtr->firstUsed = spanPtr->spanStart; LIST_ASSERT(storePtr->numUsed >= count); storePtr->numUsed -= count; } /* Collect garbage at back */ count = (storePtr->firstUsed + storePtr->numUsed) - (spanPtr->spanStart + spanPtr->spanLength); LIST_COUNT_ASSERT(count); if (count > 0) { /* T:listrep-6.{1:8} */ ObjArrayDecrRefs( storePtr->slots, spanPtr->spanStart + spanPtr->spanLength, count); LIST_ASSERT(storePtr->numUsed >= count); storePtr->numUsed -= count; } LIST_ASSERT(ListRepStart(repPtr) == storePtr->firstUsed); LIST_ASSERT(ListRepLength(repPtr) == storePtr->numUsed); LISTREP_CHECK(repPtr); } /* *---------------------------------------------------------------------- * * Tcl_NewListObj -- * * This function is normally called when not debugging: i.e., when * TCL_MEM_DEBUG is not defined. It creates a new list object from an * (objc,objv) array: that is, each of the objc elements of the array * referenced by objv is inserted as an element into a new Tcl object. * * When TCL_MEM_DEBUG is defined, this function just returns the result * of calling the debugging version Tcl_DbNewListObj. * * Results: * A new list object is returned that is initialized from the object * pointers in objv. If objc is less than or equal to zero, an empty * object is returned. The new object's string representation is left * NULL. The resulting new list object has ref count 0. * * Side effects: * The ref counts of the elements in objv are incremented since the * resulting list now refers to them. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG #undef Tcl_NewListObj Tcl_Obj * Tcl_NewListObj( Tcl_Size objc, /* Count of objects referenced by objv. */ Tcl_Obj *const objv[]) /* An array of pointers to Tcl objects. */ { return Tcl_DbNewListObj(objc, objv, "unknown", 0); } #else /* if not TCL_MEM_DEBUG */ Tcl_Obj * Tcl_NewListObj( Tcl_Size objc, /* Count of objects referenced by objv. */ Tcl_Obj *const objv[]) /* An array of pointers to Tcl objects. */ { ListRep listRep; Tcl_Obj *listObj; TclNewObj(listObj); if (objc <= 0) { return listObj; } ListRepInit(objc, objv, LISTREP_PANIC_ON_FAIL, &listRep); ListObjReplaceRepAndInvalidate(listObj, &listRep); return listObj; } #endif /* if TCL_MEM_DEBUG */ /* *---------------------------------------------------------------------- * * Tcl_DbNewListObj -- * * This function is normally called when debugging: i.e., when * TCL_MEM_DEBUG is defined. It creates new list objects. It is the same * as the Tcl_NewListObj function above except that it calls * Tcl_DbCkalloc directly with the file name and line number from its * caller. This simplifies debugging since then the [memory active] * command will report the correct file name and line number when * reporting objects that haven't been freed. * * When TCL_MEM_DEBUG is not defined, this function just returns the * result of calling Tcl_NewListObj. * * Results: * A new list object is returned that is initialized from the object * pointers in objv. If objc is less than or equal to zero, an empty * object is returned. The new object's string representation is left * NULL. The new list object has ref count 0. * * Side effects: * The ref counts of the elements in objv are incremented since the * resulting list now refers to them. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG Tcl_Obj * Tcl_DbNewListObj( Tcl_Size objc, /* Count of objects referenced by objv. */ Tcl_Obj *const objv[], /* An array of pointers to Tcl objects. */ const char *file, /* The name of the source file calling this * function; used for debugging. */ int line) /* Line number in the source file; used for * debugging. */ { Tcl_Obj *listObj; ListRep listRep; TclDbNewObj(listObj, file, line); if (objc <= 0) { return listObj; } ListRepInit(objc, objv, LISTREP_PANIC_ON_FAIL, &listRep); ListObjReplaceRepAndInvalidate(listObj, &listRep); return listObj; } #else /* if not TCL_MEM_DEBUG */ Tcl_Obj * Tcl_DbNewListObj( Tcl_Size objc, /* Count of objects referenced by objv. */ Tcl_Obj *const objv[], /* An array of pointers to Tcl objects. */ TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { return Tcl_NewListObj(objc, objv); } #endif /* TCL_MEM_DEBUG */ /* *------------------------------------------------------------------------ * * TclNewListObj2 -- * * Create a new Tcl_Obj list comprising of the concatenation of two * Tcl_Obj* arrays. * TODO - currently this function is not used within tclListObj but * need to see if it would be useful in other files that preallocate * lists and then append. * * Results: * Non-NULL pointer to the allocate Tcl_Obj. * * Side effects: * None. * *------------------------------------------------------------------------ */ Tcl_Obj * TclNewListObj2( Tcl_Size objc1, /* Count of objects referenced by objv1. */ Tcl_Obj *const objv1[], /* First array of pointers to Tcl objects. */ Tcl_Size objc2, /* Count of objects referenced by objv2. */ Tcl_Obj *const objv2[]) /* Second array of pointers to Tcl objects. */ { Tcl_Obj *listObj; ListStore *storePtr; Tcl_Size objc = objc1 + objc2; listObj = Tcl_NewListObj(objc, NULL); if (objc == 0) { return listObj; /* An empty object */ } LIST_ASSERT_TYPE(listObj); storePtr = ListObjStorePtr(listObj); LIST_ASSERT(ListObjSpanPtr(listObj) == NULL); LIST_ASSERT(storePtr->firstUsed == 0); LIST_ASSERT(storePtr->numUsed == 0); LIST_ASSERT(storePtr->numAllocated >= objc); if (objc1) { ObjArrayCopy(storePtr->slots, objc1, objv1); } if (objc2) { ObjArrayCopy(&storePtr->slots[objc1], objc2, objv2); } storePtr->numUsed = objc; return listObj; } /* *---------------------------------------------------------------------- * * TclListObjGetRep -- * * This function returns a copy of the ListRep stored * as the internal representation of an object. The reference * counts of the (ListStore, ListSpan) contained in the representation * are NOT incremented. * * Results: * The return value is normally TCL_OK; in this case *listRepP * is set to a copy of the descriptor stored as the internal * representation of the Tcl_Obj containing a list. if listPtr does not * refer to a list object and the object can not be converted to one, * TCL_ERROR is returned and an error message will be left in the * interpreter's result if interp is not NULL. * * Side effects: * The possible conversion of the object referenced by listPtr * to a list object. *repPtr is initialized to the internal rep * if result is TCL_OK, or set to NULL on error. *---------------------------------------------------------------------- */ static int TclListObjGetRep( Tcl_Interp *interp, /* Used to report errors if not NULL. */ Tcl_Obj *listObj, /* List object for which an element array is * to be returned. */ ListRep *repPtr) /* Location to store descriptor */ { if (!TclHasInternalRep(listObj, &tclListType)) { int result; result = SetListFromAny(interp, listObj); if (result != TCL_OK) { /* Init to keep gcc happy wrt uninitialized fields at call site */ repPtr->storePtr = NULL; repPtr->spanPtr = NULL; return result; } } ListObjGetRep(listObj, repPtr); LISTREP_CHECK(repPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_SetListObj -- * * Modify an object to be a list containing each of the objc elements of * the object array referenced by objv. * * Results: * None. * * Side effects: * The object is made a list object and is initialized from the object * pointers in objv. If objc is less than or equal to zero, an empty * object is returned. The new object's string representation is left * NULL. The ref counts of the elements in objv are incremented since the * list now refers to them. The object's old string and internal * representations are freed and its type is set NULL. * *---------------------------------------------------------------------- */ void Tcl_SetListObj( Tcl_Obj *objPtr, /* Object whose internal rep to init. */ Tcl_Size objc, /* Count of objects referenced by objv. */ Tcl_Obj *const objv[]) /* An array of pointers to Tcl objects. */ { if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_SetListObj"); } /* * Set the object's type to "list" and initialize the internal rep. * However, if there are no elements to put in the list, just give the * object an empty string rep and a NULL type. NOTE ListRepInit must * not be called with objc == 0! */ if (objc > 0) { ListRep listRep; /* TODO - perhaps ask for extra space? */ ListRepInit(objc, objv, LISTREP_PANIC_ON_FAIL, &listRep); ListObjReplaceRepAndInvalidate(objPtr, &listRep); } else { TclFreeInternalRep(objPtr); TclInvalidateStringRep(objPtr); Tcl_InitStringRep(objPtr, NULL, 0); } } /* *---------------------------------------------------------------------- * * TclListObjCopy -- * * Makes a "pure list" copy of a list value. This provides for the C * level a counterpart of the [lrange $list 0 end] command, while using * internals details to be as efficient as possible. * * Results: * Normally returns a pointer to a new Tcl_Obj, that contains the same * list value as *listPtr does. The returned Tcl_Obj has a refCount of * zero. If *listPtr does not hold a list, NULL is returned, and if * interp is non-NULL, an error message is recorded there. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj * TclListObjCopy( Tcl_Interp *interp, /* Used to report errors if not NULL. */ Tcl_Obj *listObj) /* List object for which an element array is * to be returned. */ { Tcl_Obj *copyObj; if (!TclHasInternalRep(listObj, &tclListType)) { if (TclObjTypeHasProc(listObj, lengthProc)) { return Tcl_DuplicateObj(listObj); } if (SetListFromAny(interp, listObj) != TCL_OK) { return NULL; } } TclNewObj(copyObj); TclInvalidateStringRep(copyObj); DupListInternalRep(listObj, copyObj); return copyObj; } /* *------------------------------------------------------------------------ * * ListRepRange -- * * Initializes a ListRep as a range within the passed ListRep. * The range limits are clamped to the list boundaries. * * Results: * None. * * Side effects: * The ListStore and ListSpan referenced by in the returned ListRep * may or may not be the same as those passed in. For example, the * ListStore may differ because the range is small enough that a new * ListStore is more memory-optimal. The ListSpan may differ because * it is NULL or shared. Regardless, reference counts on the returned * values are not incremented. Generally, ListObjReplaceRepAndInvalidate * may be used to store the new ListRep back into an object or a * ListRepIncrRefs followed by ListRepDecrRefs to free in case of errors. * Any other use should be carefully reconsidered. * TODO WARNING:- this is an awkward interface and easy for caller * to get wrong. Mostly due to refcount combinations. Perhaps passing * in the source listObj instead of source listRep might simplify. * *------------------------------------------------------------------------ */ static void ListRepRange( ListRep *srcRepPtr, /* Contains source of the range */ Tcl_Size rangeStart, /* Index of first element to include */ Tcl_Size rangeEnd, /* Index of last element to include */ int preserveSrcRep, /* If true, srcRepPtr contents must not be * modified (generally because a shared Tcl_Obj * references it) */ ListRep *rangeRepPtr) /* Output. Must NOT be == srcRepPtr */ { Tcl_Obj **srcElems; Tcl_Size numSrcElems = ListRepLength(srcRepPtr); Tcl_Size rangeLen; Tcl_Size numAfterRangeEnd; LISTREP_CHECK(srcRepPtr); /* Take the opportunity to garbage collect */ /* TODO - we probably do not need the preserveSrcRep here unlike later */ if (!preserveSrcRep) { /* T:listrep-1.{4,5,8,9},2.{4:7},3.{15:18},4.{7,8} */ ListRepFreeUnreferenced(srcRepPtr); } /* else T:listrep-2.{4.2,4.3,5.2,5.3,6.2,7.2,8.1} */ if (rangeStart < 0) { rangeStart = 0; } if (rangeEnd >= numSrcElems) { rangeEnd = numSrcElems - 1; } if (rangeStart > rangeEnd) { /* Empty list of capacity 1. */ ListRepInit(1, NULL, LISTREP_PANIC_ON_FAIL, rangeRepPtr); return; } rangeLen = rangeEnd - rangeStart + 1; /* * We can create a range one of four ways: * (0) Range encapsulates entire list * (1) Special case: deleting in-place from end of an unshared object * (2) Use a ListSpan referencing the current ListStore * (3) Creating a new ListStore * (4) Removing all elements outside the range in the current ListStore * Option (4) may only be done if caller has not disallowed it AND * the ListStore is not shared. * * The choice depends on heuristics related to speed and memory. * TODO - heuristics below need to be measured and tuned. * * Note: Even if nothing below cause any changes, we still want the * string-canonizing effect of [lrange 0 end] so the Tcl_Obj should not * be returned as is even if the range encompasses the whole list. */ if (rangeStart == 0 && rangeEnd == (numSrcElems-1)) { /* Option 0 - entire list. This may be used to canonicalize */ /* T:listrep-1.10.1,2.8.1 */ *rangeRepPtr = *srcRepPtr; /* Not ref counts not incremented */ } else if (rangeStart == 0 && (!preserveSrcRep) && (!ListRepIsShared(srcRepPtr) && srcRepPtr->spanPtr == NULL)) { /* Option 1 - Special case unshared, exclude end elements, no span */ LIST_ASSERT(srcRepPtr->storePtr->firstUsed == 0); /* If no span */ ListRepElements(srcRepPtr, numSrcElems, srcElems); numAfterRangeEnd = numSrcElems - (rangeEnd + 1); /* Assert: Because numSrcElems > rangeEnd earlier */ if (numAfterRangeEnd != 0) { /* T:listrep-1.{8,9} */ ObjArrayDecrRefs(srcElems, rangeEnd + 1, numAfterRangeEnd); } /* srcRepPtr->storePtr->firstUsed,numAllocated unchanged */ srcRepPtr->storePtr->numUsed = rangeLen; srcRepPtr->storePtr->flags = 0; rangeRepPtr->storePtr = srcRepPtr->storePtr; /* Note no incr ref */ rangeRepPtr->spanPtr = NULL; } else if (ListSpanMerited(rangeLen, srcRepPtr->storePtr->numUsed, srcRepPtr->storePtr->numAllocated)) { /* Option 2 - because span would be most efficient */ Tcl_Size spanStart = ListRepStart(srcRepPtr) + rangeStart; if (!preserveSrcRep && srcRepPtr->spanPtr && srcRepPtr->spanPtr->refCount <= 1) { /* If span is not shared reuse it */ /* T:listrep-2.7.3,3.{16,18} */ srcRepPtr->spanPtr->spanStart = spanStart; srcRepPtr->spanPtr->spanLength = rangeLen; *rangeRepPtr = *srcRepPtr; } else { /* Span not present or is shared. */ /* T:listrep-1.5,2.{5,7},4.{7,8} */ rangeRepPtr->storePtr = srcRepPtr->storePtr; rangeRepPtr->spanPtr = ListSpanNew(spanStart, rangeLen); } /* * We have potentially created a new internal representation that * references the same storage as srcRep but not yet incremented its * reference count. So do NOT call freezombies if preserveSrcRep * is mandated. */ if (!preserveSrcRep) { /* T:listrep-1.{5.1,5.2,5.4},2.{5,7},3.{16,18},4.{7,8} */ ListRepFreeUnreferenced(rangeRepPtr); } } else if (preserveSrcRep || ListRepIsShared(srcRepPtr)) { /* Option 3 - span or modification in place not allowed/desired */ /* T:listrep-2.{4,6} */ ListRepElements(srcRepPtr, numSrcElems, srcElems); /* TODO - allocate extra space? */ ListRepInit(rangeLen, &srcElems[rangeStart], LISTREP_PANIC_ON_FAIL, rangeRepPtr); } else { /* * Option 4 - modify in place. Note that because of the invariant * that spanless list stores must start at 0, we have to move * everything to the front. * TODO - perhaps if a span already exists, no need to move to front? * or maybe no need to move all the way to the front? * TODO - if range is small relative to allocation, allocate new? */ /* Asserts follow from call to ListRepFreeUnreferenced earlier */ LIST_ASSERT(!preserveSrcRep); LIST_ASSERT(!ListRepIsShared(srcRepPtr)); LIST_ASSERT(ListRepStart(srcRepPtr) == srcRepPtr->storePtr->firstUsed); LIST_ASSERT(ListRepLength(srcRepPtr) == srcRepPtr->storePtr->numUsed); ListRepElements(srcRepPtr, numSrcElems, srcElems); /* Free leading elements outside range */ if (rangeStart != 0) { /* T:listrep-1.4,3.15 */ ObjArrayDecrRefs(srcElems, 0, rangeStart); } /* Ditto for trailing */ numAfterRangeEnd = numSrcElems - (rangeEnd + 1); /* Assert: Because numSrcElems > rangeEnd earlier */ if (numAfterRangeEnd != 0) { /* T:listrep-3.17 */ ObjArrayDecrRefs(srcElems, rangeEnd + 1, numAfterRangeEnd); } memmove(&srcRepPtr->storePtr->slots[0], &srcRepPtr->storePtr ->slots[srcRepPtr->storePtr->firstUsed + rangeStart], rangeLen * sizeof(Tcl_Obj *)); srcRepPtr->storePtr->firstUsed = 0; srcRepPtr->storePtr->numUsed = rangeLen; srcRepPtr->storePtr->flags = 0; if (srcRepPtr->spanPtr) { /* In case the source has a span, update it for consistency */ /* T:listrep-3.{15,17} */ srcRepPtr->spanPtr->spanStart = srcRepPtr->storePtr->firstUsed; srcRepPtr->spanPtr->spanLength = srcRepPtr->storePtr->numUsed; } rangeRepPtr->storePtr = srcRepPtr->storePtr; rangeRepPtr->spanPtr = NULL; } /* TODO - call freezombies here if !preserveSrcRep? */ /* Note ref counts intentionally not incremented */ LISTREP_CHECK(rangeRepPtr); return; } /* *---------------------------------------------------------------------- * * TclListObjRange -- * * Makes a slice of a list value. * *listObj must be known to be a valid list. * * Results: * Returns a pointer to the sliced list. * This may be a new object or the same object if not shared. * Returns NULL if passed listObj was not a list and could not be * converted to one. * * Side effects: * The possible conversion of the object referenced by listPtr * to a list object. * *---------------------------------------------------------------------- */ Tcl_Obj * TclListObjRange( Tcl_Interp *interp, /* May be NULL. Used for error messages */ Tcl_Obj *listObj, /* List object to take a range from. */ Tcl_Size rangeStart, /* Index of first element to include. */ Tcl_Size rangeEnd) /* Index of last element to include. */ { ListRep listRep; ListRep resultRep; int isShared; if (TclListObjGetRep(interp, listObj, &listRep) != TCL_OK) { return NULL; } isShared = Tcl_IsShared(listObj); ListRepRange(&listRep, rangeStart, rangeEnd, isShared, &resultRep); if (isShared) { /* T:listrep-1.10.1,2.{4.2,4.3,5.2,5.3,6.2,7.2,8.1} */ TclNewObj(listObj); } /* T:listrep-1.{4.3,5.1,5.2} */ ListObjReplaceRepAndInvalidate(listObj, &resultRep); return listObj; } /* *---------------------------------------------------------------------- * * TclListObjGetElement -- * * Returns a single element from the array of the elements in a list * object, without doing any bounds checking. Caller must ensure * that ObjPtr of type 'tclListType' and that index is valid for the * list. * *---------------------------------------------------------------------- */ Tcl_Obj * TclListObjGetElement( Tcl_Obj *objPtr, /* List object for which an element array is * to be returned. */ Tcl_Size index) { return ListObjStorePtr(objPtr)->slots[ListObjStart(objPtr) + index]; } /* *---------------------------------------------------------------------- * * Tcl_ListObjGetElements -- * * This function returns an (objc,objv) array of the elements in a list * object. * * Results: * The return value is normally TCL_OK; in this case *objcPtr is set to * the count of list elements and *objvPtr is set to a pointer to an * array of (*objcPtr) pointers to each list element. If listPtr does not * refer to a list object and the object can not be converted to one, * TCL_ERROR is returned and an error message will be left in the * interpreter's result if interp is not NULL. * * The objects referenced by the returned array should be treated as * readonly and their ref counts are _not_ incremented; the caller must * do that if it holds on to a reference. Furthermore, the pointer and * length returned by this function may change as soon as any function is * called on the list object; be careful about retaining the pointer in a * local data structure. * * Side effects: * The possible conversion of the object referenced by listPtr * to a list object. * *---------------------------------------------------------------------- */ #undef Tcl_ListObjGetElements int Tcl_ListObjGetElements( Tcl_Interp *interp, /* Used to report errors if not NULL. */ Tcl_Obj *objPtr, /* List object for which an element array is * to be returned. */ Tcl_Size *objcPtr, /* Where to store the count of objects * referenced by objv. */ Tcl_Obj ***objvPtr) /* Where to store the pointer to an array of * pointers to the list's objects. */ { ListRep listRep; if (TclObjTypeHasProc(objPtr, getElementsProc)) { return TclObjTypeGetElements(interp, objPtr, objcPtr, objvPtr); } if (TclListObjGetRep(interp, objPtr, &listRep) != TCL_OK) { return TCL_ERROR; } ListRepElements(&listRep, *objcPtr, *objvPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ListObjAppendList -- * * This function appends the elements in the list fromObj * to toObj. toObj must not be shared else the function will panic. * * Results: * The return value is normally TCL_OK. If fromObj or toObj do not * refer to list values, TCL_ERROR is returned and an error message is * left in the interpreter's result if interp is not NULL. * * Side effects: * The reference counts of the elements in fromObj are incremented * since the list now refers to them. toObj and fromObj are * converted, if necessary, to list objects. Also, appending the new * elements may cause toObj's array of element pointers to grow. * toObj's old string representation, if any, is invalidated. * *---------------------------------------------------------------------- */ int Tcl_ListObjAppendList( Tcl_Interp *interp, /* Used to report errors if not NULL. */ Tcl_Obj *toObj, /* List object to append elements to. */ Tcl_Obj *fromObj) /* List obj with elements to append. */ { Tcl_Size objc; Tcl_Obj **objv; if (Tcl_IsShared(toObj)) { Tcl_Panic("%s called with shared object", "Tcl_ListObjAppendList"); } if (TclListObjGetElements(interp, fromObj, &objc, &objv) != TCL_OK) { return TCL_ERROR; } /* * Insert the new elements starting after the lists's last element. * Delete zero existing elements. */ return TclListObjAppendElements(interp, toObj, objc, objv); } /* *------------------------------------------------------------------------ * * TclListObjAppendElements -- * * Appends multiple elements to a Tcl_Obj list object. If * the passed Tcl_Obj is not a list object, it will be converted to one * and an error raised if the conversion fails. * * The Tcl_Obj must not be shared though the internal representation * may be. * * Results: * On success, TCL_OK is returned with the specified elements appended. * On failure, TCL_ERROR is returned with an error message in the * interpreter if not NULL. * * Side effects: * None. * *------------------------------------------------------------------------ */ int TclListObjAppendElements ( Tcl_Interp *interp, /* Used to report errors if not NULL. */ Tcl_Obj *toObj, /* List object to append */ Tcl_Size elemCount, /* Number of elements in elemObjs[] */ Tcl_Obj * const elemObjv[]) /* Objects to append to toObj's list. */ { ListRep listRep; Tcl_Obj **toObjv; Tcl_Size toLen; Tcl_Size finalLen; if (Tcl_IsShared(toObj)) { Tcl_Panic("%s called with shared object", "TclListObjAppendElements"); } if (TclListObjGetRep(interp, toObj, &listRep) != TCL_OK) { /* Cannot be converted to a list */ return TCL_ERROR; } if (elemCount <= 0) { /* * Note that when elemCount <= 0, this routine is logically a * no-op, removing and adding no elements to the list. However, by removing * the string representation, we get the important side effect that the * resulting listPtr is a list in canonical form. This is important. * Resist any temptation to optimize this case further. See bug [e38dce74e2]. */ if (!ListObjIsCanonical(toObj)) { TclInvalidateStringRep(toObj); } /* Nothing to do. Note AFTER check for list above */ return TCL_OK; } ListRepElements(&listRep, toLen, toObjv); if (elemCount > LIST_MAX || toLen > (LIST_MAX - elemCount)) { return ListLimitExceededError(interp); } finalLen = toLen + elemCount; if (!ListRepIsShared(&listRep)) { /* * Reuse storage if possible. Even if too small, realloc-ing instead * of creating a new ListStore will save us on manipulating Tcl_Obj * reference counts on the elements which is a substantial cost * if the list is not small. */ Tcl_Size numTailFree; ListRepFreeUnreferenced(&listRep); /* Collect garbage before checking room */ LIST_ASSERT(ListRepStart(&listRep) == listRep.storePtr->firstUsed); LIST_ASSERT(ListRepLength(&listRep) == listRep.storePtr->numUsed); LIST_ASSERT(toLen == listRep.storePtr->numUsed); if (finalLen > listRep.storePtr->numAllocated) { /* T:listrep-1.{2,11},3.6 */ ListStore *newStorePtr = ListStoreReallocate( listRep.storePtr, finalLen); if (newStorePtr == NULL) { return MemoryAllocationError(interp, LIST_SIZE(finalLen)); } LIST_ASSERT(newStorePtr->numAllocated >= finalLen); listRep.storePtr = newStorePtr; /* * WARNING: at this point the Tcl_Obj internal rep potentially * points to freed storage if the reallocation returned a * different location. Overwrite it to bring it back in sync. */ ListObjStompRep(toObj, &listRep); } /* else T:listrep-3.{4,5} */ LIST_ASSERT(listRep.storePtr->numAllocated >= finalLen); /* Current store big enough */ numTailFree = ListRepNumFreeTail(&listRep); LIST_ASSERT((numTailFree + listRep.storePtr->firstUsed) >= elemCount); /* Total free */ if (numTailFree < elemCount) { /* Not enough room at back. Move some to front */ /* T:listrep-3.5 */ Tcl_Size shiftCount = elemCount - numTailFree; /* Divide remaining space between front and back */ shiftCount += (listRep.storePtr->numAllocated - finalLen) / 2; LIST_ASSERT(shiftCount <= listRep.storePtr->firstUsed); if (shiftCount) { /* T:listrep-3.5 */ ListRepUnsharedShiftDown(&listRep, shiftCount); } } /* else T:listrep-3.{4,6} */ ObjArrayCopy( &listRep.storePtr->slots[ ListRepStart(&listRep) + ListRepLength(&listRep)], elemCount, elemObjv); listRep.storePtr->numUsed = finalLen; if (listRep.spanPtr) { /* T:listrep-3.{4,5,6} */ LIST_ASSERT(listRep.spanPtr->spanStart == listRep.storePtr->firstUsed); listRep.spanPtr->spanLength = finalLen; } /* else T:listrep-3.6.3 */ LIST_ASSERT(ListRepStart(&listRep) == listRep.storePtr->firstUsed); LIST_ASSERT(ListRepLength(&listRep) == finalLen); LISTREP_CHECK(&listRep); ListObjReplaceRepAndInvalidate(toObj, &listRep); return TCL_OK; } /* * Have to make a new list rep, either shared or no room in old one. * If the old list did not have a span (all elements at front), do * not leave space in the front either, assuming all appends and no * prepends. */ if (ListRepInit(finalLen, NULL, listRep.spanPtr ? LISTREP_SPACE_FAVOR_BACK : LISTREP_SPACE_ONLY_BACK, &listRep) != TCL_OK) { return MemoryAllocationError(interp, finalLen); } LIST_ASSERT(listRep.storePtr->numAllocated >= finalLen); if (toLen) { /* T:listrep-2.{2,9},4.5 */ ObjArrayCopy(ListRepSlotPtr(&listRep, 0), toLen, toObjv); } ObjArrayCopy(ListRepSlotPtr(&listRep, toLen), elemCount, elemObjv); listRep.storePtr->numUsed = finalLen; if (listRep.spanPtr) { /* T:listrep-4.5 */ LIST_ASSERT(listRep.spanPtr->spanStart == listRep.storePtr->firstUsed); listRep.spanPtr->spanLength = finalLen; } LISTREP_CHECK(&listRep); ListObjReplaceRepAndInvalidate(toObj, &listRep); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ListObjAppendElement -- * * Like 'Tcl_ListObjAppendList', but Appends a single value to a list. * * Value * * TCL_OK * * 'objPtr' is appended to the elements of 'listPtr'. * * TCL_ERROR * * listPtr does not refer to a list object and the object can not be * converted to one. An error message will be left in the * interpreter's result if interp is not NULL. * * Effect * * If 'listPtr' is not already of type 'tclListType', it is converted. * The 'refCount' of 'objPtr' is incremented as it is added to 'listPtr'. * Appending the new element may cause the array of element pointers * in 'listObj' to grow. Any preexisting string representation of * 'listPtr' is invalidated. * *---------------------------------------------------------------------- */ int Tcl_ListObjAppendElement( Tcl_Interp *interp, /* Used to report errors if not NULL. */ Tcl_Obj *toObj, /* List object to append elemObj to. */ Tcl_Obj *elemObj) /* Object to append to toObj's list. */ { /* * TODO - compare perf with 8.6 to see if worth optimizing single * element case */ return TclListObjAppendElements(interp, toObj, 1, &elemObj); } /* *---------------------------------------------------------------------- * * Tcl_ListObjIndex -- * * Retrieve a pointer to the element of 'listPtr' at 'index'. The index * of the first element is 0. * * Returns: * TCL_OK * A pointer to the element at 'index' is stored in 'objPtrPtr'. If * 'index' is out of range, NULL is stored in 'objPtrPtr'. This * object should be treated as readonly and its 'refCount' is _not_ * incremented. The caller must do that if it holds on to the * reference. * * TCL_ERROR * 'listPtr' is not a valid list. An error message is left in the * interpreter's result if 'interp' is not NULL. * * Effect: * If 'listPtr' is not already of type 'tclListType', it is converted. * *---------------------------------------------------------------------- */ int Tcl_ListObjIndex( Tcl_Interp *interp, /* Used to report errors if not NULL. */ Tcl_Obj *listObj, /* List object to index into. */ Tcl_Size index, /* Index of element to return. */ Tcl_Obj **objPtrPtr) /* The resulting Tcl_Obj* is stored here. */ { Tcl_Obj **elemObjs; Tcl_Size numElems; /* Empty string => empty list. Avoid unnecessary shimmering */ if (listObj->bytes == &tclEmptyString) { *objPtrPtr = NULL; return TCL_OK; } int hasAbstractList = TclObjTypeHasProc(listObj,indexProc) != 0; if (hasAbstractList) { return TclObjTypeIndex(interp, listObj, index, objPtrPtr); } if (TclListObjGetElements(interp, listObj, &numElems, &elemObjs) != TCL_OK) { return TCL_ERROR; } if ((index < 0) || (index >= numElems)) { *objPtrPtr = NULL; } else { *objPtrPtr = elemObjs[index]; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ListObjLength -- * * This function returns the number of elements in a list object. If the * object is not already a list object, an attempt will be made to * convert it to one. * * Results: * The return value is normally TCL_OK; in this case *lenPtr will be set * to the integer count of list elements. If listPtr does not refer to a * list object and the object can not be converted to one, TCL_ERROR is * returned and an error message will be left in the interpreter's result * if interp is not NULL. * * Side effects: * The possible conversion of the argument object to a list object. * *---------------------------------------------------------------------- */ #undef Tcl_ListObjLength int Tcl_ListObjLength( Tcl_Interp *interp, /* Used to report errors if not NULL. */ Tcl_Obj *listObj, /* List object whose #elements to return. */ Tcl_Size *lenPtr) /* The resulting length is stored here. */ { ListRep listRep; /* Empty string => empty list. Avoid unnecessary shimmering */ if (listObj->bytes == &tclEmptyString) { *lenPtr = 0; return TCL_OK; } if (TclObjTypeHasProc(listObj, lengthProc)) { *lenPtr = TclObjTypeLength(listObj); return TCL_OK; } if (TclListObjGetRep(interp, listObj, &listRep) != TCL_OK) { return TCL_ERROR; } *lenPtr = ListRepLength(&listRep); return TCL_OK; } static Tcl_Size ListLength( Tcl_Obj *listPtr) { ListRep listRep; ListObjGetRep(listPtr, &listRep); return ListRepLength(&listRep); } /* *---------------------------------------------------------------------- * * Tcl_ListObjReplace -- * * This function replaces zero or more elements of the list referenced by * listObj with the objects from an (objc,objv) array. The objc elements * of the array referenced by objv replace the count elements in listPtr * starting at first. * * If the argument first is zero or negative, it refers to the first * element. If first is greater than or equal to the number of elements * in the list, then no elements are deleted; the new elements are * appended to the list. Count gives the number of elements to replace. * If count is zero or negative then no elements are deleted; the new * elements are simply inserted before first. * * The argument objv refers to an array of objc pointers to the new * elements to be added to listPtr in place of those that were deleted. * If objv is NULL, no new elements are added. If listPtr is not a list * object, an attempt will be made to convert it to one. * * Results: * The return value is normally TCL_OK. If listPtr does not refer to a * list object and can not be converted to one, TCL_ERROR is returned and * an error message will be left in the interpreter's result if interp is * not NULL. * * Side effects: * The ref counts of the objc elements in objv are incremented since the * resulting list now refers to them. Similarly, the ref counts for * replaced objects are decremented. listObj is converted, if necessary, * to a list object. listObj's old string representation, if any, is * freed. * *---------------------------------------------------------------------- */ int Tcl_ListObjReplace( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *listObj, /* List object whose elements to replace. */ Tcl_Size first, /* Index of first element to replace. */ Tcl_Size numToDelete, /* Number of elements to replace. */ Tcl_Size numToInsert, /* Number of objects to insert. */ Tcl_Obj *const insertObjs[])/* Tcl objects to insert */ { ListRep listRep; Tcl_Size origListLen; Tcl_Size lenChange; Tcl_Size leadSegmentLen; Tcl_Size tailSegmentLen; Tcl_Size numFreeSlots; Tcl_Size leadShift; Tcl_Size tailShift; Tcl_Obj **listObjs; int favor; if (Tcl_IsShared(listObj)) { Tcl_Panic("%s called with shared object", "Tcl_ListObjReplace"); } if (TclObjTypeHasProc(listObj, replaceProc)) { return TclObjTypeReplace(interp, listObj, first, numToDelete, numToInsert, insertObjs); } if (TclListObjGetRep(interp, listObj, &listRep) != TCL_OK) { /* Cannot be converted to a list */ return TCL_ERROR; } /* Make limits sane */ origListLen = ListRepLength(&listRep); if (first < 0) { first = 0; } if (first > origListLen) { first = origListLen; /* So we'll insert after last element. */ } if (numToDelete < 0) { numToDelete = 0; } else if (first > LIST_MAX - numToDelete /* Handle integer overflow */ || origListLen < first + numToDelete) { numToDelete = origListLen - first; } if (numToInsert > LIST_MAX - (origListLen - numToDelete)) { return ListLimitExceededError(interp); } if ((first+numToDelete) >= origListLen) { /* Operating at back of list. Favor leaving space at back */ favor = LISTREP_SPACE_FAVOR_BACK; } else if (first == 0) { /* Operating on front of list. Favor leaving space in front */ favor = LISTREP_SPACE_FAVOR_FRONT; } else { /* Operating on middle of list. */ favor = LISTREP_SPACE_FAVOR_NONE; } /* * There are a number of special cases to consider from an optimization * point of view. * (1) Pure deletes (numToInsert==0) from the front or back can be treated * as a range op irrespective of whether the ListStore is shared or not * (2) Pure inserts (numToDelete == 0) * (2a) Pure inserts at the back can be treated as appends * (2b) Pure inserts from the *front* can be optimized under certain * conditions by inserting before first ListStore slot in use if there * is room, again irrespective of sharing * (3) If the ListStore is shared OR there is insufficient free space * OR existing allocation is too large compared to new size, create * a new ListStore * (4) Unshared ListStore with sufficient free space. Delete, shift and * insert within the ListStore. */ /* Note: do not do TclInvalidateStringRep as yet in case there are errors */ /* Check Case (1) - Treat pure deletes from front or back as range ops */ if (numToInsert == 0) { if (numToDelete == 0) { /* * Should force canonical even for no-op. Remember Tcl_Obj unshared * so OK to invalidate string rep */ /* T:listrep-1.10,2.8 */ TclInvalidateStringRep(listObj); return TCL_OK; } if (first == 0) { /* Delete from front, so return tail. */ /* T:listrep-1.{4,5},2.{4,5},3.{15,16},4.7 */ ListRep tailRep; ListRepRange(&listRep, numToDelete, origListLen-1, 0, &tailRep); ListObjReplaceRepAndInvalidate(listObj, &tailRep); return TCL_OK; } else if ((first+numToDelete) >= origListLen) { /* Delete from tail, so return head */ /* T:listrep-1.{8,9},2.{6,7},3.{17,18},4.8 */ ListRep headRep; ListRepRange(&listRep, 0, first-1, 0, &headRep); ListObjReplaceRepAndInvalidate(listObj, &headRep); return TCL_OK; } /* Deletion from middle. Fall through to general case */ } /* Garbage collect before checking the pure insert optimization */ ListRepFreeUnreferenced(&listRep); /* * Check Case (2) - pure inserts under certain conditions: */ if (numToDelete == 0) { /* Case (2a) - Append to list. */ if (first == origListLen) { /* T:listrep-1.11,2.9,3.{5,6},2.2.1 */ return TclListObjAppendElements( interp, listObj, numToInsert, insertObjs); } /* * Case (2b) - pure inserts at front under some circumstances * (i) Insertion must be at head of list * (ii) The list's span must be at head of the in-use slots in the store * (iii) There must be unused room at front of the store * NOTE THIS IS TRUE EVEN IF THE ListStore IS SHARED as it will not * affect the other Tcl_Obj's referencing this ListStore. */ if (first == 0 && /* (i) */ ListRepStart(&listRep) == listRep.storePtr->firstUsed && /* (ii) */ numToInsert <= listRep.storePtr->firstUsed) { /* (iii) */ Tcl_Size newLen; LIST_ASSERT(numToInsert); /* Else would have returned above */ listRep.storePtr->firstUsed -= numToInsert; ObjArrayCopy(&listRep.storePtr->slots[listRep.storePtr->firstUsed], numToInsert, insertObjs); listRep.storePtr->numUsed += numToInsert; newLen = listRep.spanPtr->spanLength + numToInsert; if (listRep.spanPtr && listRep.spanPtr->refCount <= 1) { /* An unshared span record, re-use it */ /* T:listrep-3.1 */ listRep.spanPtr->spanStart = listRep.storePtr->firstUsed; listRep.spanPtr->spanLength = newLen; } else { /* Need a new span record */ if (listRep.storePtr->firstUsed == 0) { listRep.spanPtr = NULL; } else { /* T:listrep-4.3 */ listRep.spanPtr = ListSpanNew(listRep.storePtr->firstUsed, newLen); } } ListObjReplaceRepAndInvalidate(listObj, &listRep); return TCL_OK; } } /* Just for readability of the code */ lenChange = numToInsert - numToDelete; leadSegmentLen = first; tailSegmentLen = origListLen - (first + numToDelete); numFreeSlots = listRep.storePtr->numAllocated - listRep.storePtr->numUsed; /* * Before further processing, if unshared, try and reallocate to avoid * new allocation below. This avoids expensive ref count manipulation * later by not having to go through the ListRepInit and * ListObjReplaceAndInvalidate below. * TODO - we could be smarter about the reallocate. Use of realloc * means all new free space is at the back. Instead, the realloc could * be an explicit alloc and memmove which would let us redistribute * free space. */ if (numFreeSlots < lenChange && !ListRepIsShared(&listRep)) { /* T:listrep-1.{1,3,14,18,21},3.{3,10,11,14,27,32,41} */ ListStore *newStorePtr = ListStoreReallocate(listRep.storePtr, origListLen + lenChange); if (newStorePtr == NULL) { return MemoryAllocationError(interp, LIST_SIZE(origListLen + lenChange)); } listRep.storePtr = newStorePtr; numFreeSlots = listRep.storePtr->numAllocated - listRep.storePtr->numUsed; /* * WARNING: at this point the Tcl_Obj internal rep potentially * points to freed storage if the reallocation returned a * different location. Overwrite it to bring it back in sync. */ ListObjStompRep(listObj, &listRep); } /* * Case (3) a new ListStore is required * (a) The passed-in ListStore is shared * (b) There is not enough free space in the unshared passed-in ListStore * (c) The new unshared size is much "smaller" (TODO) than the allocated space * TODO - for unshared case ONLY, consider a "move" based implementation */ if (ListRepIsShared(&listRep) || /* 3a */ numFreeSlots < lenChange || /* 3b */ (origListLen + lenChange) < (listRep.storePtr->numAllocated / 4)) { /* 3c */ ListRep newRep; Tcl_Obj **toObjs; listObjs = &listRep.storePtr->slots[ListRepStart(&listRep)]; ListRepInit(origListLen + lenChange, NULL, LISTREP_PANIC_ON_FAIL | favor, &newRep); toObjs = ListRepSlotPtr(&newRep, 0); if (leadSegmentLen > 0) { /* T:listrep-2.{2,3,13:18},4.{6,9,13:18} */ ObjArrayCopy(toObjs, leadSegmentLen, listObjs); } if (numToInsert > 0) { /* T:listrep-2.{1,2,3,10:18},4.{1,2,4,6,10:18} */ ObjArrayCopy(&toObjs[leadSegmentLen], numToInsert, insertObjs); } if (tailSegmentLen > 0) { /* T:listrep-2.{1,2,3,10:15},4.{1,2,4,6,9:12,16:18} */ ObjArrayCopy(&toObjs[leadSegmentLen + numToInsert], tailSegmentLen, &listObjs[leadSegmentLen+numToDelete]); } newRep.storePtr->numUsed = origListLen + lenChange; if (newRep.spanPtr) { /* T:listrep-2.{1,2,3,10:18},4.{1,2,4,6,9:18} */ newRep.spanPtr->spanLength = newRep.storePtr->numUsed; } LISTREP_CHECK(&newRep); ListObjReplaceRepAndInvalidate(listObj, &newRep); return TCL_OK; } /* * Case (4) - unshared ListStore with sufficient room. * After deleting elements, there will be a corresponding gap. If this * gap does not match number of insertions, either the lead segment, * or the tail segment, or both will have to be moved. * The general strategy is to move the fewest number of elements. If * * TODO - what about appends to unshared ? Is below sufficiently optimal? */ /* Following must hold for unshared listreps after ListRepFreeUnreferenced above */ LIST_ASSERT(origListLen == listRep.storePtr->numUsed); LIST_ASSERT(origListLen == ListRepLength(&listRep)); LIST_ASSERT(ListRepStart(&listRep) == listRep.storePtr->firstUsed); LIST_ASSERT((numToDelete + numToInsert) > 0); /* Base of slot array holding the list elements */ listObjs = &listRep.storePtr->slots[ListRepStart(&listRep)]; /* * Free up elements to be deleted. Before that, increment the ref counts * for objects to be inserted in case there is overlap. T:listobj-11.1 */ if (numToInsert) { /* T:listrep-1.{1,3,12:21},3.{2,3,7:14,23:41} */ ObjArrayIncrRefs(insertObjs, 0, numToInsert); } if (numToDelete) { /* T:listrep-1.{6,7,12:21},3.{19:41} */ ObjArrayDecrRefs(listObjs, first, numToDelete); } /* * TODO - below the moves are optimized but this may result in needing a * span allocation. Perhaps for small lists, it may be more efficient to * just move everything up front and save on allocating a span. */ /* * Calculate shifts if necessary to accommodate insertions. * NOTE: all indices are relative to listObjs which is not necessarily the * start of the ListStore storage area. * * leadShift - how much to shift the lead segment * tailShift - how much to shift the tail segment * insertTarget - index where to insert. */ if (lenChange == 0) { /* T:listrep-1.{12,15,19},3.{23,28,33}. Exact fit */ leadShift = 0; tailShift = 0; } else if (lenChange < 0) { /* * More deletions than insertions. The gap after deletions is large * enough for insertions. Move a segment depending on size. */ if (leadSegmentLen > tailSegmentLen) { /* Tail segment smaller. Insert after lead, move tail down */ /* T:listrep-1.{7,17,20},3.{21,2229,35} */ leadShift = 0; tailShift = lenChange; } else { /* Lead segment smaller. Insert before tail, move lead up */ /* T:listrep-1.{6,13,16},3.{19,20,24,34} */ leadShift = -lenChange; tailShift = 0; } } else { LIST_ASSERT(lenChange > 0); /* Reminder */ /* * We need to make room for the insertions. Again we have multiple * possibilities. We may be able to get by just shifting one segment * or need to shift both. In the former case, favor shifting the * smaller segment. */ Tcl_Size leadSpace = ListRepNumFreeHead(&listRep); Tcl_Size tailSpace = ListRepNumFreeTail(&listRep); Tcl_Size finalFreeSpace = leadSpace + tailSpace - lenChange; LIST_ASSERT((leadSpace + tailSpace) >= lenChange); if (leadSpace >= lenChange && (leadSegmentLen < tailSegmentLen || tailSpace < lenChange)) { /* Move only lead to the front to make more room */ /* T:listrep-3.25,36,38, */ leadShift = -lenChange; tailShift = 0; /* * Redistribute the remaining free space between the front and * back if either there is no tail space left or if the * entire list is the head anyways. This is an important * optimization for further operations like further asymmetric * insertions. */ if (finalFreeSpace > 1 && (tailSpace == 0 || tailSegmentLen == 0)) { Tcl_Size postShiftLeadSpace = leadSpace - lenChange; if (postShiftLeadSpace > (finalFreeSpace/2)) { Tcl_Size extraShift = postShiftLeadSpace - (finalFreeSpace / 2); leadShift -= extraShift; tailShift = -extraShift; /* Move tail to the front as well */ } } /* else T:listrep-3.{7,12,25,38} */ LIST_ASSERT(leadShift >= 0 || leadSpace >= -leadShift); } else if (tailSpace >= lenChange) { /* Move only tail segment to the back to make more room. */ /* T:listrep-3.{8,10,11,14,26,27,30,32,37,39,41} */ leadShift = 0; tailShift = lenChange; /* * See comments above. This is analogous. */ if (finalFreeSpace > 1 && (leadSpace == 0 || leadSegmentLen == 0)) { Tcl_Size postShiftTailSpace = tailSpace - lenChange; if (postShiftTailSpace > (finalFreeSpace/2)) { /* T:listrep-1.{1,3,14,18,21},3.{2,3,26,27} */ Tcl_Size extraShift = postShiftTailSpace - (finalFreeSpace / 2); tailShift += extraShift; leadShift = extraShift; /* Move head to the back as well */ } } LIST_ASSERT(tailShift <= tailSpace); } else { /* * Both lead and tail need to be shifted to make room. * Divide remaining free space equally between front and back. */ /* T:listrep-3.{9,13,31,40} */ LIST_ASSERT(leadSpace < lenChange); LIST_ASSERT(tailSpace < lenChange); /* * leadShift = leadSpace - (finalFreeSpace/2) * Thus leadShift <= leadSpace * Also, * = leadSpace - (leadSpace + tailSpace - lenChange)/2 * = leadSpace/2 - tailSpace/2 + lenChange/2 * >= 0 because lenChange > tailSpace */ leadShift = leadSpace - (finalFreeSpace / 2); tailShift = lenChange - leadShift; if (tailShift > tailSpace) { /* Account for integer division errors */ leadShift += 1; tailShift -= 1; } /* * Following must be true because otherwise one of the previous * if clauses would have been taken. */ LIST_ASSERT(leadShift > 0 && leadShift < lenChange); LIST_ASSERT(tailShift > 0 && tailShift < lenChange); leadShift = -leadShift; /* Lead is actually shifted downward */ } } /* Careful about order of moves! */ if (leadShift > 0) { /* Will happen when we have to make room at bottom */ if (tailShift != 0 && tailSegmentLen != 0) { /* T:listrep-1.{1,3,14,18},3.{2,3,26,27} */ Tcl_Size tailStart = leadSegmentLen + numToDelete; memmove(&listObjs[tailStart + tailShift], &listObjs[tailStart], tailSegmentLen * sizeof(Tcl_Obj *)); } if (leadSegmentLen != 0) { /* T:listrep-1.{3,6,16,18,21},3.{19,20,34} */ memmove(&listObjs[leadShift], &listObjs[0], leadSegmentLen * sizeof(Tcl_Obj *)); } } else { if (leadShift != 0 && leadSegmentLen != 0) { /* T:listrep-3.{7,9,12,13,31,36,38,40} */ memmove(&listObjs[leadShift], &listObjs[0], leadSegmentLen * sizeof(Tcl_Obj *)); } if (tailShift != 0 && tailSegmentLen != 0) { /* T:listrep-1.{7,17},3.{8:11,13,14,21,22,35,37,39:41} */ Tcl_Size tailStart = leadSegmentLen + numToDelete; memmove(&listObjs[tailStart + tailShift], &listObjs[tailStart], tailSegmentLen * sizeof(Tcl_Obj *)); } } if (numToInsert) { /* Do NOT use ObjArrayCopy here since we have already incr'ed ref counts */ /* T:listrep-1.{1,3,12:21},3.{2,3,7:14,23:41} */ memmove(&listObjs[leadSegmentLen + leadShift], insertObjs, numToInsert * sizeof(Tcl_Obj *)); } listRep.storePtr->firstUsed += leadShift; listRep.storePtr->numUsed = origListLen + lenChange; listRep.storePtr->flags = 0; if (listRep.spanPtr && listRep.spanPtr->refCount <= 1) { /* An unshared span record, re-use it, even if not required */ /* T:listrep-3.{2,3,7:14},3.{19:41} */ listRep.spanPtr->spanStart = listRep.storePtr->firstUsed; listRep.spanPtr->spanLength = listRep.storePtr->numUsed; } else { /* Need a new span record */ if (listRep.storePtr->firstUsed == 0) { /* T:listrep-1.{7,12,15,17,19,20} */ listRep.spanPtr = NULL; } else { /* T:listrep-1.{1,3,6.1,13,14,16,18,21} */ listRep.spanPtr = ListSpanNew(listRep.storePtr->firstUsed, listRep.storePtr->numUsed); } } LISTREP_CHECK(&listRep); ListObjReplaceRepAndInvalidate(listObj, &listRep); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclLindexList -- * * This procedure handles the 'lindex' command when objc==3. * * Results: * Returns a pointer to the object extracted, or NULL if an error * occurred. The returned object already includes one reference count for * the pointer returned. * * Side effects: * None. * * Notes: * This procedure is implemented entirely as a wrapper around * TclLindexFlat. All it does is reconfigure the argument format into the * form required by TclLindexFlat, while taking care to manage shimmering * in such a way that we tend to keep the most useful internalreps and/or * avoid the most expensive conversions. * *---------------------------------------------------------------------- */ Tcl_Obj * TclLindexList( Tcl_Interp *interp, /* Tcl interpreter. */ Tcl_Obj *listObj, /* List being unpacked. */ Tcl_Obj *argObj) /* Index or index list. */ { Tcl_Size index; /* Index into the list. */ Tcl_Obj *indexListCopy; Tcl_Obj **indexObjs; Tcl_Size numIndexObjs; /* * Determine whether argPtr designates a list or a single index. We have * to be careful about the order of the checks to avoid repeated * shimmering; if internal rep is already a list do not shimmer it. * see TIP#22 and TIP#33 for the details. */ if (!TclHasInternalRep(argObj, &tclListType) && TclGetIntForIndexM(NULL, argObj, TCL_SIZE_MAX - 1, &index) == TCL_OK) { /* * argPtr designates a single index. */ return TclLindexFlat(interp, listObj, 1, &argObj); } /* * Here we make a private copy of the index list argument to avoid any * shimmering issues that might invalidate the indices array below while * we are still using it. This is probably unnecessary. It does not appear * that any damaging shimmering is possible, and no test has been devised * to show any error when this private copy is not made. But it's cheap, * and it offers some future-proofing insurance in case the TclLindexFlat * implementation changes in some unexpected way, or some new form of * trace or callback permits things to happen that the current * implementation does not. */ indexListCopy = TclListObjCopy(NULL, argObj); if (indexListCopy == NULL) { /* * The argument is neither an index nor a well-formed list. * Report the error via TclLindexFlat. * TODO - This is as original code. why not directly return an error? */ return TclLindexFlat(interp, listObj, 1, &argObj); } TclListObjGetElements(interp, indexListCopy, &numIndexObjs, &indexObjs); listObj = TclLindexFlat(interp, listObj, numIndexObjs, indexObjs); Tcl_DecrRefCount(indexListCopy); return listObj; } /* *---------------------------------------------------------------------- * * TclLindexFlat -- * * This procedure is the core of the 'lindex' command, with all index * arguments presented as a flat list. * * Results: * Returns a pointer to the object extracted, or NULL if an error * occurred. The returned object already includes one reference count for * the pointer returned. * * Side effects: * None. * * Notes: * The reference count of the returned object includes one reference * corresponding to the pointer returned. Thus, the calling code will * usually do something like: * Tcl_SetObjResult(interp, result); * Tcl_DecrRefCount(result); * *---------------------------------------------------------------------- */ Tcl_Obj * TclLindexFlat( Tcl_Interp *interp, /* Tcl interpreter. */ Tcl_Obj *listObj, /* Tcl object representing the list. */ Tcl_Size indexCount, /* Count of indices. */ Tcl_Obj *const indexArray[])/* Array of pointers to Tcl objects that * represent the indices in the list. */ { int status; Tcl_Size i; /* Handle AbstractList as special case */ if (TclObjTypeHasProc(listObj,indexProc)) { Tcl_Size listLen = TclObjTypeLength(listObj); Tcl_Size index; Tcl_Obj *elemObj = listObj; /* for lindex without indices return list */ for (i=0 ; i 0) { // TODO: support nested lists Tcl_Obj *e2Obj = TclLindexFlat(interp, elemObj, 1, &indexArray[i]); Tcl_DecrRefCount(elemObj); elemObj = e2Obj; } } Tcl_IncrRefCount(elemObj); return elemObj; } Tcl_IncrRefCount(listObj); for (i=0 ; i= listLen) { /* * Index is out of range. Break out of loop with empty result. * First check remaining indices for validity */ while (++i < indexCount) { if (TclGetIntForIndexM(interp, indexArray[i], TCL_SIZE_MAX - 1, &index) != TCL_OK) { Tcl_DecrRefCount(listObj); return NULL; } } Tcl_DecrRefCount(listObj); TclNewObj(listObj); Tcl_IncrRefCount(listObj); } else { Tcl_Obj *itemObj; /* * Must set the internal rep again because it may have been * changed by TclGetIntForIndexM. See test lindex-8.4. */ if (!TclHasInternalRep(listObj, &tclListType)) { status = SetListFromAny(interp, listObj); if (status != TCL_OK) { /* The list is not a list at all => error. */ Tcl_DecrRefCount(listObj); return NULL; } } ListObjGetElements(listObj, listLen, elemPtrs); /* increment this reference count first before decrementing * just in case they are the same Tcl_Obj */ itemObj = elemPtrs[index]; Tcl_IncrRefCount(itemObj); Tcl_DecrRefCount(listObj); /* Extract the pointer to the appropriate element. */ listObj = itemObj; } } else { Tcl_DecrRefCount(listObj); listObj = NULL; } } return listObj; } /* *---------------------------------------------------------------------- * * TclLsetList -- * * Core of the 'lset' command when objc == 4. Objv[2] may be either a * scalar index or a list of indices. * It also handles 'lpop' when given a NULL value. * * Results: * Returns the new value of the list variable, or NULL if there was an * error. The returned object includes one reference count for the * pointer returned. * * Side effects: * None. * * Notes: * This procedure is implemented entirely as a wrapper around * TclLsetFlat. All it does is reconfigure the argument format into the * form required by TclLsetFlat, while taking care to manage shimmering * in such a way that we tend to keep the most useful internalreps and/or * avoid the most expensive conversions. * *---------------------------------------------------------------------- */ Tcl_Obj * TclLsetList( Tcl_Interp *interp, /* Tcl interpreter. */ Tcl_Obj *listObj, /* Pointer to the list being modified. */ Tcl_Obj *indexArgObj, /* Index or index-list arg to 'lset'. */ Tcl_Obj *valueObj) /* Value arg to 'lset' or NULL to 'lpop'. */ { Tcl_Size indexCount = 0; /* Number of indices in the index list. */ Tcl_Obj **indices = NULL; /* Vector of indices in the index list. */ Tcl_Obj *retValueObj; /* Pointer to the list to be returned. */ Tcl_Size index; /* Current index in the list - discarded. */ Tcl_Obj *indexListCopy; /* * Determine whether the index arg designates a list or a single index. * We have to be careful about the order of the checks to avoid repeated * shimmering; see TIP #22 and #23 for details. */ if (!TclHasInternalRep(indexArgObj, &tclListType) && TclGetIntForIndexM(NULL, indexArgObj, TCL_SIZE_MAX - 1, &index) == TCL_OK) { if (TclObjTypeHasProc(listObj, setElementProc)) { indices = &indexArgObj; retValueObj = TclObjTypeSetElement( interp, listObj, 1, indices, valueObj); if (retValueObj) { Tcl_IncrRefCount(retValueObj); } } else { /* indexArgPtr designates a single index. */ /* T:listrep-1.{2.1,12.1,15.1,19.1},2.{2.3,9.3,10.1,13.1,16.1}, 3.{4,5,6}.3 */ retValueObj = TclLsetFlat(interp, listObj, 1, &indexArgObj, valueObj); } } else { indexListCopy = TclListObjCopy(NULL,indexArgObj); if (!indexListCopy) { /* * indexArgPtr designates something that is neither an index nor a * well formed list. Report the error via TclLsetFlat. */ retValueObj = TclLsetFlat(interp, listObj, 1, &indexArgObj, valueObj); } else { if (TCL_OK != TclListObjGetElements( interp, indexListCopy, &indexCount, &indices)) { Tcl_DecrRefCount(indexListCopy); /* * indexArgPtr designates something that is neither an index nor a * well formed list. Report the error via TclLsetFlat. */ retValueObj = TclLsetFlat(interp, listObj, 1, &indexArgObj, valueObj); } else { /* * Let TclLsetFlat perform the actual lset operation. */ retValueObj = TclLsetFlat(interp, listObj, indexCount, indices, valueObj); if (indexListCopy) { Tcl_DecrRefCount(indexListCopy); } } } } assert (retValueObj==NULL || retValueObj->typePtr || retValueObj->bytes); return retValueObj; } /* *---------------------------------------------------------------------- * * TclLsetFlat -- * * Core engine of the 'lset' command. * It also handles 'lpop' when given a NULL value. * * Results: * Returns the new value of the list variable, or NULL if an error * occurred. The returned object includes one reference count for the * pointer returned. * * Side effects: * On entry, the reference count of the variable value does not reflect * any references held on the stack. The first action of this function is * to determine whether the object is shared, and to duplicate it if it * is. The reference count of the duplicate is incremented. At this * point, the reference count will be 1 for either case, so that the * object will appear to be unshared. * * If an error occurs, and the object has been duplicated, the reference * count on the duplicate is decremented so that it is now 0: this * dismisses any memory that was allocated by this function. * * If no error occurs, the reference count of the original object is * incremented if the object has not been duplicated, and nothing is done * to a reference count of the duplicate. Now the reference count of an * unduplicated object is 2 (the returned pointer, plus the one stored in * the variable). The reference count of a duplicate object is 1, * reflecting that the returned pointer is the only active reference. The * caller is expected to store the returned value back in the variable * and decrement its reference count. (INST_STORE_* does exactly this.) * *---------------------------------------------------------------------- */ Tcl_Obj * TclLsetFlat( Tcl_Interp *interp, /* Tcl interpreter. */ Tcl_Obj *listObj, /* Pointer to the list being modified. */ Tcl_Size indexCount, /* Number of index args. */ Tcl_Obj *const indexArray[], /* Index args. */ Tcl_Obj *valueObj) /* Value arg to 'lset' or NULL to 'lpop'. */ { Tcl_Size index, len; int result; Tcl_Obj *subListObj, *retValueObj; Tcl_Obj *pendingInvalidates[10]; Tcl_Obj **pendingInvalidatesPtr = pendingInvalidates; Tcl_Size numPendingInvalidates = 0; /* * If there are no indices, simply return the new value. (Without * indices, [lset] is a synonym for [set]. * [lpop] does not use this but protect for NULL valueObj just in case. */ if (indexCount == 0) { if (valueObj != NULL) { Tcl_IncrRefCount(valueObj); } return valueObj; } /* * If the list is shared, make a copy we can modify (copy-on-write). We * use Tcl_DuplicateObj() instead of TclListObjCopy() for a few reasons: * 1) we have not yet confirmed listObj is actually a list; 2) We make a * verbatim copy of any existing string rep, and when we combine that with * the delayed invalidation of string reps of modified Tcl_Obj's * implemented below, the outcome is that any error condition that causes * this routine to return NULL, will leave the string rep of listObj and * all elements to be unchanged. */ subListObj = Tcl_IsShared(listObj) ? Tcl_DuplicateObj(listObj) : listObj; /* * Anchor the linked list of Tcl_Obj's whose string reps must be * invalidated if the operation succeeds. */ retValueObj = subListObj; result = TCL_OK; /* Allocate if static array for pending invalidations is too small */ if (indexCount > (Tcl_Size) (sizeof(pendingInvalidates) / sizeof(pendingInvalidates[0]))) { pendingInvalidatesPtr = (Tcl_Obj **) Tcl_Alloc(indexCount * sizeof(*pendingInvalidatesPtr)); } /* * Loop through all the index arguments, and for each one dive into the * appropriate sublist. */ do { Tcl_Size elemCount; Tcl_Obj *parentList, **elemPtrs; /* * Check for the possible error conditions... */ if (TclListObjGetElements(interp, subListObj, &elemCount, &elemPtrs) != TCL_OK) { /* ...the sublist we're indexing into isn't a list at all. */ result = TCL_ERROR; break; } /* * WARNING: the macro TclGetIntForIndexM is not safe for * post-increments, avoid '*indexArray++' here. */ if (TclGetIntForIndexM(interp, *indexArray, elemCount - 1, &index) != TCL_OK) { /* ...the index we're trying to use isn't an index at all. */ result = TCL_ERROR; indexArray++; /* Why bother with this increment? TBD */ break; } indexArray++; /* * Special case 0-length lists. The Tcl indexing function treat * will return any value beyond length as TCL_SIZE_MAX for this * case. */ if ((index == TCL_SIZE_MAX) && (elemCount == 0)) { index = 0; } if (index < 0 || index > elemCount || (valueObj == NULL && index >= elemCount)) { /* ...the index points outside the sublist. */ if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "index \"%s\" out of range", TclGetString(indexArray[-1]))); Tcl_SetErrorCode(interp, "TCL", "VALUE", "INDEX", "OUTOFRANGE", (char *)NULL); } result = TCL_ERROR; break; } /* * No error conditions. As long as we're not yet on the last index, * determine the next sublist for the next pass through the loop, * and take steps to make sure it is an unshared copy, as we intend * to modify it. */ if (--indexCount) { parentList = subListObj; if (index == elemCount) { TclNewObj(subListObj); } else { subListObj = elemPtrs[index]; } if (Tcl_IsShared(subListObj)) { subListObj = Tcl_DuplicateObj(subListObj); } /* * Replace the original elemPtr[index] in parentList with a copy * we know to be unshared. This call will also deal with the * situation where parentList shares its internalrep with other * Tcl_Obj's. Dealing with the shared internalrep case can * cause subListObj to become shared again, so detect that case * and make and store another copy. */ if (index == elemCount) { Tcl_ListObjAppendElement(NULL, parentList, subListObj); } else { TclListObjSetElement(NULL, parentList, index, subListObj); } if (Tcl_IsShared(subListObj)) { subListObj = Tcl_DuplicateObj(subListObj); TclListObjSetElement(NULL, parentList, index, subListObj); } /* * The TclListObjSetElement() calls do not spoil the string rep * of parentList, and that's fine for now, since all we've done * so far is replace a list element with an unshared copy. The * list value remains the same, so the string rep. is still * valid, and unchanged, which is good because if this whole * routine returns NULL, we'd like to leave no change to the * value of the lset variable. Later on, when we set valueObj * in its proper place, then all containing lists will have * their values changed, and will need their string reps * spoiled. We maintain a list of all those Tcl_Obj's * pendingInvalidatesPtr[] so we can spoil them at that time. */ pendingInvalidatesPtr[numPendingInvalidates] = parentList; ++numPendingInvalidates; } } while (indexCount > 0); /* * Either we've detected and error condition, and exited the loop with * result == TCL_ERROR, or we've successfully reached the last index, and * we're ready to store valueObj. On success, we need to invalidate * the string representations of intermediate lists whose contained * list element would have changed. */ if (result == TCL_OK) { while (numPendingInvalidates > 0) { Tcl_Obj *objPtr; --numPendingInvalidates; objPtr = pendingInvalidatesPtr[numPendingInvalidates]; if (result == TCL_OK) { /* * We're going to store valueObj, so spoil string reps of all * containing lists. * TODO - historically, the storing of the internal rep was done * because the ptr2 field of the internal rep was used to chain * objects whose string rep needed to be invalidated. Now this * is no longer the case, so replacing of the internal rep * should not be needed. The TclInvalidateStringRep should * suffice. Formulate a test case before changing. */ ListRep objInternalRep; TclListObjGetRep(NULL, objPtr, &objInternalRep); ListObjReplaceRepAndInvalidate(objPtr, &objInternalRep); } } } if (pendingInvalidatesPtr != pendingInvalidates) { Tcl_Free(pendingInvalidatesPtr); } if (result != TCL_OK) { /* * Error return; message is already in interp. Clean up any excess * memory. */ if (retValueObj != listObj) { Tcl_DecrRefCount(retValueObj); } return NULL; } /* * Store valueObj in proper sublist and return. The -1 is to avoid a * compiler warning (not a problem because we checked that we have a * proper list - or something convertible to one - above). */ len = -1; TclListObjLength(NULL, subListObj, &len); if (valueObj == NULL) { /* T:listrep-1.{4.2,5.4,6.1,7.1,8.3},2.{4,5}.4 */ Tcl_ListObjReplace(NULL, subListObj, index, 1, 0, NULL); } else if (index == len) { /* T:listrep-1.2.1,2.{2.3,9.3},3.{4,5,6}.3 */ Tcl_ListObjAppendElement(NULL, subListObj, valueObj); } else { /* T:listrep-1.{12.1,15.1,19.1},2.{10,13,16}.1 */ TclListObjSetElement(NULL, subListObj, index, valueObj); TclInvalidateStringRep(subListObj); } Tcl_IncrRefCount(retValueObj); return retValueObj; } /* *---------------------------------------------------------------------- * * TclListObjSetElement -- * * Set a single element of a list to a specified value * * Results: * The return value is normally TCL_OK. If listObj does not refer to a * list object and cannot be converted to one, TCL_ERROR is returned and * an error message will be left in the interpreter result if interp is * not NULL. Similarly, if index designates an element outside the range * [0..listLength-1], where listLength is the count of elements in the * list object designated by listObj, TCL_ERROR is returned and an error * message is left in the interpreter result. * * Side effects: * Tcl_Panic if listObj designates a shared object. Otherwise, attempts * to convert it to a list with a non-shared internal rep. Decrements the * ref count of the object at the specified index within the list, * replaces with the object designated by valueObj, and increments the * ref count of the replacement object. * *---------------------------------------------------------------------- */ int TclListObjSetElement( Tcl_Interp *interp, /* Tcl interpreter; used for error reporting * if not NULL. */ Tcl_Obj *listObj, /* List object in which element should be * stored. */ Tcl_Size index, /* Index of element to store. */ Tcl_Obj *valueObj) /* Tcl object to store in the designated list * element. */ { ListRep listRep; Tcl_Obj **elemPtrs; /* Pointers to elements of the list. */ Tcl_Size elemCount; /* Number of elements in the list. */ /* Ensure that the listObj parameter designates an unshared list. */ if (Tcl_IsShared(listObj)) { Tcl_Panic("%s called with shared object", "TclListObjSetElement"); } if (TclListObjGetRep(interp, listObj, &listRep) != TCL_OK) { return TCL_ERROR; } elemCount = ListRepLength(&listRep); /* Ensure that the index is in bounds. */ if ((index < 0) || (index >= elemCount)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "index \"%" TCL_SIZE_MODIFIER "d\" out of range", index)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "INDEX", "OUTOFRANGE", (char *)NULL); } return TCL_ERROR; } /* * Note - garbage collect this only AFTER checking indices above. * Do not want to modify listrep and then not store it back in listObj. */ ListRepFreeUnreferenced(&listRep); /* Replace a shared internal rep with an unshared copy */ if (listRep.storePtr->refCount > 1) { ListRep newInternalRep; /* T:listrep-2.{10,13,16}.1 */ /* TODO - leave extra space? */ ListRepClone(&listRep, &newInternalRep, LISTREP_PANIC_ON_FAIL); listRep = newInternalRep; } /* else T:listrep-1.{12.1,15.1,19.1} */ /* Retrieve element array AFTER potential cloning above */ ListRepElements(&listRep, elemCount, elemPtrs); /* * Add a reference to the new list element and remove from old before * replacing it. Order is important! */ Tcl_IncrRefCount(valueObj); Tcl_DecrRefCount(elemPtrs[index]); elemPtrs[index] = valueObj; /* Internal rep may be cloned so replace */ ListObjReplaceRepAndInvalidate(listObj, &listRep); return TCL_OK; } /* *---------------------------------------------------------------------- * * FreeListInternalRep -- * * Deallocate the storage associated with a list object's internal * representation. * * Results: * None. * * Side effects: * Frees listPtr's List* internal representation, if no longer shared. * May decrement the ref counts of element objects, which may free them. * *---------------------------------------------------------------------- */ static void FreeListInternalRep( Tcl_Obj *listObj) /* List object with internal rep to free. */ { ListRep listRep; ListObjGetRep(listObj, &listRep); if (listRep.storePtr->refCount-- <= 1) { ObjArrayDecrRefs( listRep.storePtr->slots, listRep.storePtr->firstUsed, listRep.storePtr->numUsed); Tcl_Free(listRep.storePtr); } if (listRep.spanPtr) { ListSpanDecrRefs(listRep.spanPtr); } } /* *---------------------------------------------------------------------- * * DupListInternalRep -- * * Initialize the internal representation of a list Tcl_Obj to share the * internal representation of an existing list object. * * Results: * None. * * Side effects: * The reference count of the List internal rep is incremented. * *---------------------------------------------------------------------- */ static void DupListInternalRep( Tcl_Obj *srcObj, /* Object with internal rep to copy. */ Tcl_Obj *copyObj) /* Object with internal rep to set. */ { ListRep listRep; ListObjGetRep(srcObj, &listRep); ListObjOverwriteRep(copyObj, &listRep); } /* *---------------------------------------------------------------------- * * SetListFromAny -- * * Attempt to generate a list internal form for the Tcl object "objPtr". * * Results: * The return value is TCL_OK or TCL_ERROR. If an error occurs during * conversion, an error message is left in the interpreter's result * unless "interp" is NULL. * * Side effects: * If no error occurs, a list is stored as "objPtr"s internal * representation. * *---------------------------------------------------------------------- */ static int SetListFromAny( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr) /* The object to convert. */ { Tcl_Obj **elemPtrs; ListRep listRep; /* * Dictionaries are a special case; they have a string representation such * that *all* valid dictionaries are valid lists. Hence we can convert * more directly. Only do this when there's no existing string rep; if * there is, it is the string rep that's authoritative (because it could * describe duplicate keys). */ if (!TclHasStringRep(objPtr) && TclHasInternalRep(objPtr, &tclDictType)) { Tcl_Obj *keyPtr, *valuePtr; Tcl_DictSearch search; int done; Tcl_Size size; /* * Create the new list representation. Note that we do not need to do * anything with the string representation as the transformation (and * the reverse back to a dictionary) are both order-preserving. Also * note that since we know we've got a valid dictionary (by * representation) we also know that fetching the size of the * dictionary or iterating over it will not fail. */ Tcl_DictObjSize(NULL, objPtr, &size); /* TODO - leave space in front and/or back? */ if (ListRepInitAttempt(interp, size > 0 ? 2 * size : 1, NULL, &listRep) != TCL_OK) { return TCL_ERROR; } LIST_ASSERT(listRep.spanPtr == NULL); /* Guard against future changes */ LIST_ASSERT(listRep.storePtr->firstUsed == 0); LIST_ASSERT((listRep.storePtr->flags & LISTSTORE_CANONICAL) == 0); listRep.storePtr->numUsed = 2 * size; /* Populate the list representation. */ elemPtrs = listRep.storePtr->slots; Tcl_DictObjFirst(NULL, objPtr, &search, &keyPtr, &valuePtr, &done); while (!done) { *elemPtrs++ = keyPtr; *elemPtrs++ = valuePtr; Tcl_IncrRefCount(keyPtr); Tcl_IncrRefCount(valuePtr); Tcl_DictObjNext(&search, &keyPtr, &valuePtr, &done); } } else if (TclObjTypeHasProc(objPtr,indexProc)) { Tcl_Size elemCount, i; elemCount = TclObjTypeLength(objPtr); if (ListRepInitAttempt(interp, elemCount, NULL, &listRep) != TCL_OK) { return TCL_ERROR; } LIST_ASSERT(listRep.spanPtr == NULL); /* Guard against future changes */ LIST_ASSERT(listRep.storePtr->firstUsed == 0); elemPtrs = listRep.storePtr->slots; /* Each iteration, store a list element */ for (i = 0; i < elemCount; i++) { if (TclObjTypeIndex(interp, objPtr, i, elemPtrs) != TCL_OK) { return TCL_ERROR; } Tcl_IncrRefCount(*elemPtrs++);/* Since list now holds ref to it. */ } LIST_ASSERT((Tcl_Size)(elemPtrs - listRep.storePtr->slots) == elemCount); listRep.storePtr->numUsed = elemCount; } else { Tcl_Size estCount, length; const char *limit, *nextElem = TclGetStringFromObj(objPtr, &length); /* * Allocate enough space to hold a (Tcl_Obj *) for each * (possible) list element. */ estCount = TclMaxListLength(nextElem, length, &limit); estCount += (estCount == 0); /* Smallest list struct holds 1 * element. */ /* TODO - allocate additional space? */ if (ListRepInitAttempt(interp, estCount, NULL, &listRep) != TCL_OK) { return TCL_ERROR; } LIST_ASSERT(listRep.spanPtr == NULL); /* Guard against future changes */ LIST_ASSERT(listRep.storePtr->firstUsed == 0); elemPtrs = listRep.storePtr->slots; /* Each iteration, parse and store a list element. */ while (nextElem < limit) { const char *elemStart; char *check; Tcl_Size elemSize; int literal; if (TCL_OK != TclFindElement(interp, nextElem, limit - nextElem, &elemStart, &nextElem, &elemSize, &literal)) { fail: while (--elemPtrs >= listRep.storePtr->slots) { Tcl_DecrRefCount(*elemPtrs); } Tcl_Free(listRep.storePtr); return TCL_ERROR; } if (elemStart == limit) { break; } TclNewObj(*elemPtrs); TclInvalidateStringRep(*elemPtrs); check = Tcl_InitStringRep(*elemPtrs, literal ? elemStart : NULL, elemSize); if (elemSize && check == NULL) { MemoryAllocationError(interp, elemSize); goto fail; } if (!literal) { Tcl_InitStringRep(*elemPtrs, NULL, TclCopyAndCollapse(elemSize, elemStart, check)); } Tcl_IncrRefCount(*elemPtrs++);/* Since list now holds ref to it. */ } listRep.storePtr->numUsed = elemPtrs - listRep.storePtr->slots; } LISTREP_CHECK(&listRep); /* * Store the new internalRep. We do this as late * as possible to allow the conversion code, in particular * Tcl_GetStringFromObj, to use the old internalRep. */ /* * Note old string representation NOT to be invalidated. * So do NOT use ListObjReplaceRepAndInvalidate. InternalRep to be freed AFTER * IncrRefs so do not use ListObjOverwriteRep */ ListRepIncrRefs(&listRep); TclFreeInternalRep(objPtr); objPtr->internalRep.twoPtrValue.ptr1 = listRep.storePtr; objPtr->internalRep.twoPtrValue.ptr2 = listRep.spanPtr; objPtr->typePtr = &tclListType; return TCL_OK; } /* *---------------------------------------------------------------------- * * UpdateStringOfList -- * * Update the string representation for a list object. * * Any previously-existing string representation is not invalidated, so * storage is lost if this has not been taken care of. * * Effect * * The string representation of 'listPtr' is set to the resulting string. * This string will be empty if the list has no elements. It is assumed * that the list internal representation is not NULL. * *---------------------------------------------------------------------- */ static void UpdateStringOfList( Tcl_Obj *listObj) /* List object with string rep to update. */ { # define LOCAL_SIZE 64 char localFlags[LOCAL_SIZE], *flagPtr = NULL; Tcl_Size numElems, i, length; size_t bytesNeeded = 0; const char *elem, *start; char *dst; Tcl_Obj **elemPtrs; ListRep listRep; ListObjGetRep(listObj, &listRep); LISTREP_CHECK(&listRep); ListRepElements(&listRep, numElems, elemPtrs); /* * Mark the list as being canonical; although it will now have a string * rep, it is one we derived through proper "canonical" quoting and so * it's known to be free from nasties relating to [concat] and [eval]. * However, we only do this if * * (a) the store is not shared as a shared store may be referenced by * multiple lists with different string reps. (see [a366c6efee]), AND * * (b) list does not have a span. Consider a list generated from a * string and then this function called for a spanned list generated * from the original list. We cannot mark the list store as canonical as * that would also make the originating list canonical, which it may not * be. On the other hand, the spanned list itself is always canonical * (never generated from a string) so it does not have to be explicitly * marked as such. The ListObjIsCanonical macro takes this into account. * See the comments there. */ if (listRep.storePtr->refCount < 2 && listRep.spanPtr == NULL) { LIST_ASSERT(listRep.storePtr->firstUsed == 0);/* Invariant */ listRep.storePtr->flags |= LISTSTORE_CANONICAL; } /* Handle empty list case first, so rest of the routine is simpler. */ if (numElems == 0) { Tcl_InitStringRep(listObj, NULL, 0); return; } /* Pass 1: estimate space, gather flags. */ if (numElems <= LOCAL_SIZE) { flagPtr = localFlags; } else { /* We know numElems <= LIST_MAX, so this is safe. */ flagPtr = (char *)Tcl_Alloc(numElems); } for (i = 0; i < numElems; i++) { flagPtr[i] = (i ? TCL_DONT_QUOTE_HASH : 0); elem = TclGetStringFromObj(elemPtrs[i], &length); bytesNeeded += TclScanElement(elem, length, flagPtr+i); if (bytesNeeded > SIZE_MAX - numElems) { Tcl_Panic("max size for a Tcl value (%" TCL_Z_MODIFIER "u bytes) exceeded", SIZE_MAX); } } bytesNeeded += numElems - 1; /* * Pass 2: copy into string rep buffer. */ start = dst = Tcl_InitStringRep(listObj, NULL, bytesNeeded); TclOOM(dst, bytesNeeded); for (i = 0; i < numElems; i++) { flagPtr[i] |= (i ? TCL_DONT_QUOTE_HASH : 0); elem = TclGetStringFromObj(elemPtrs[i], &length); dst += TclConvertElement(elem, length, dst, flagPtr[i]); *dst++ = ' '; } /* Set the string length to what was actually written, the safe choice */ (void) Tcl_InitStringRep(listObj, NULL, dst - 1 - start); if (flagPtr != localFlags) { Tcl_Free(flagPtr); } } /* *------------------------------------------------------------------------ * * TclListTestObj -- * * Returns a list object with a specific internal rep and content. * Used specifically for testing so span can be controlled explicitly. * * Results: * Pointer to the Tcl_Obj containing the list. * * Side effects: * None. * *------------------------------------------------------------------------ */ Tcl_Obj * TclListTestObj( size_t length, size_t leadingSpace, size_t endSpace) { ListRep listRep; size_t capacity; Tcl_Obj *listObj; TclNewObj(listObj); /* Only a test object so ignoring overflow checks */ capacity = length + leadingSpace + endSpace; if (capacity == 0) { return listObj; } if (capacity > LIST_MAX) { return NULL; } ListRepInit(capacity, NULL, LISTREP_PANIC_ON_FAIL, &listRep); ListStore *storePtr = listRep.storePtr; size_t i; for (i = 0; i < length; ++i) { TclNewUIntObj(storePtr->slots[i + leadingSpace], i); Tcl_IncrRefCount(storePtr->slots[i + leadingSpace]); } storePtr->firstUsed = leadingSpace; storePtr->numUsed = length; if (leadingSpace != 0) { listRep.spanPtr = ListSpanNew(leadingSpace, length); } ListObjReplaceRepAndInvalidate(listObj, &listRep); return listObj; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclLiteral.c0000644000175000017500000011065014726623136015440 0ustar sergeisergei/* * tclLiteral.c -- * * Implementation of the global and ByteCode-local literal tables used to * manage the Tcl objects created for literal values during compilation * of Tcl scripts. This implementation borrows heavily from the more * general hashtable implementation of Tcl hash tables that appears in * tclHash.c. * * Copyright © 1997-1998 Sun Microsystems, Inc. * Copyright © 2004 Kevin B. Kenny. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" /* * When there are this many entries per bucket, on average, rebuild a * literal's hash table to make it larger. */ #define REBUILD_MULTIPLIER 3 /* * Function prototypes for static functions in this file: */ static size_t AddLocalLiteralEntry(CompileEnv *envPtr, Tcl_Obj *objPtr, int localHash); static void ExpandLocalLiteralArray(CompileEnv *envPtr); static size_t HashString(const char *string, size_t length); #ifdef TCL_COMPILE_DEBUG static LiteralEntry * LookupLiteralEntry(Tcl_Interp *interp, Tcl_Obj *objPtr); #endif static void RebuildLiteralTable(LiteralTable *tablePtr); /* *---------------------------------------------------------------------- * * TclInitLiteralTable -- * * This function is called to initialize the fields of a literal table * structure for either an interpreter or a compilation's CompileEnv * structure. * * Results: * None. * * Side effects: * The literal table is made ready for use. * *---------------------------------------------------------------------- */ void TclInitLiteralTable( LiteralTable *tablePtr) /* Pointer to table structure, which is * supplied by the caller. */ { #if (TCL_SMALL_HASH_TABLE != 4) Tcl_Panic("%s: TCL_SMALL_HASH_TABLE is %d, not 4", "TclInitLiteralTable", TCL_SMALL_HASH_TABLE); #endif tablePtr->buckets = tablePtr->staticBuckets; tablePtr->staticBuckets[0] = tablePtr->staticBuckets[1] = 0; tablePtr->staticBuckets[2] = tablePtr->staticBuckets[3] = 0; tablePtr->numBuckets = TCL_SMALL_HASH_TABLE; tablePtr->numEntries = 0; tablePtr->rebuildSize = TCL_SMALL_HASH_TABLE * REBUILD_MULTIPLIER; tablePtr->mask = 3; } /* *---------------------------------------------------------------------- * * TclDeleteLiteralTable -- * * This function frees up everything associated with a literal table * except for the table's structure itself. It is called when the * interpreter is deleted. * * Results: * None. * * Side effects: * Each literal in the table is released: i.e., its reference count in * the global literal table is decremented and, if it becomes zero, the * literal is freed. In addition, the table's bucket array is freed. * *---------------------------------------------------------------------- */ void TclDeleteLiteralTable( Tcl_Interp *interp, /* Interpreter containing shared literals * referenced by the table to delete. */ LiteralTable *tablePtr) /* Points to the literal table to delete. */ { LiteralEntry *entryPtr, *nextPtr; Tcl_Obj *objPtr; size_t i; /* * Release remaining literals in the table. Note that releasing a literal * might release other literals, modifying the table, so we restart the * search from the bucket chain we last found an entry. */ #ifdef TCL_COMPILE_DEBUG TclVerifyGlobalLiteralTable((Interp *) interp); #else (void)interp; #endif /*TCL_COMPILE_DEBUG*/ /* * We used to call TclReleaseLiteral for each literal in the table, which * is rather inefficient as it causes one lookup-by-hash for each * reference to the literal. We now rely at interp-deletion on each * bytecode object to release its references to the literal Tcl_Obj * without requiring that it updates the global table itself, and deal * here only with the table. */ for (i=0 ; inumBuckets ; i++) { entryPtr = tablePtr->buckets[i]; while (entryPtr != NULL) { objPtr = entryPtr->objPtr; TclDecrRefCount(objPtr); nextPtr = entryPtr->nextPtr; Tcl_Free(entryPtr); entryPtr = nextPtr; } } /* * Free up the table's bucket array if it was dynamically allocated. */ if (tablePtr->buckets != tablePtr->staticBuckets) { Tcl_Free(tablePtr->buckets); } } /* *---------------------------------------------------------------------- * * TclCreateLiteral -- * * Find, or if necessary create, an object in the interpreter's literal * table that has a string representation matching the argument * string. If nsPtr!=NULL then only literals stored for the namespace are * considered. * * Results: * The literal object. If it was created in this call *newPtr is set to * 1, else 0. NULL is returned if newPtr==NULL and no literal is found. * * Side effects: * Increments the ref count of the global LiteralEntry since the caller * now holds a reference. If LITERAL_ON_HEAP is set in flags, this * function is given ownership of the string: if an object is created * then its string representation is set directly from string, otherwise * the string is freed. Typically, a caller sets LITERAL_ON_HEAP if * "string" is an already heap-allocated buffer holding the result of * backslash substitutions. * *---------------------------------------------------------------------- */ Tcl_Obj * TclCreateLiteral( Interp *iPtr, const char *bytes, /* The start of the string. Note that this is * not a NUL-terminated string. */ Tcl_Size length, /* Number of bytes in the string. */ size_t hash, /* The string's hash. If the value is * TCL_INDEX_NONE, it will be computed here. */ int *newPtr, Namespace *nsPtr, int flags, LiteralEntry **globalPtrPtr) { LiteralTable *globalTablePtr = &iPtr->literalTable; LiteralEntry *globalPtr; size_t globalHash; Tcl_Obj *objPtr; /* * Is it in the interpreter's global literal table? */ if (hash == (size_t) TCL_INDEX_NONE) { hash = HashString(bytes, length); } globalHash = (hash & globalTablePtr->mask); for (globalPtr=globalTablePtr->buckets[globalHash] ; globalPtr!=NULL; globalPtr = globalPtr->nextPtr) { objPtr = globalPtr->objPtr; if (globalPtr->nsPtr == nsPtr) { /* * Literals should always have UTF-8 representations... but this * is not guaranteed so we need to be careful anyway. * * https://stackoverflow.com/q/54337750/301832 */ Tcl_Size objLength; const char *objBytes = TclGetStringFromObj(objPtr, &objLength); if ((objLength == length) && ((length == 0) || ((objBytes[0] == bytes[0]) && (memcmp(objBytes, bytes, length) == 0)))) { /* * A literal was found: return it */ if (newPtr) { *newPtr = 0; } if (globalPtrPtr) { *globalPtrPtr = globalPtr; } if (flags & LITERAL_ON_HEAP) { Tcl_Free((void *)bytes); } if (globalPtr->refCount != TCL_INDEX_NONE) { globalPtr->refCount++; } return objPtr; } } } if (!newPtr) { if ((flags & LITERAL_ON_HEAP)) { Tcl_Free((void *)bytes); } return NULL; } /* * The literal is new to the interpreter. */ TclNewObj(objPtr); if ((flags & LITERAL_ON_HEAP)) { objPtr->bytes = (char *) bytes; objPtr->length = length; } else { TclInitStringRep(objPtr, bytes, length); } /* Should the new literal be shared globally? */ if ((flags & LITERAL_UNSHARED)) { /* * No, do *not* add it the global literal table * Make clear, that no global value is returned */ if (globalPtrPtr != NULL) { *globalPtrPtr = NULL; } return objPtr; } /* * Yes, add it to the global literal table. */ #ifdef TCL_COMPILE_DEBUG if (LookupLiteralEntry((Tcl_Interp *) iPtr, objPtr) != NULL) { Tcl_Panic("%s: literal \"%.*s\" found globally but shouldn't be", "TclRegisterLiteral", (length>60? 60 : (int)length), bytes); } #endif globalPtr = (LiteralEntry *)Tcl_Alloc(sizeof(LiteralEntry)); globalPtr->objPtr = objPtr; Tcl_IncrRefCount(objPtr); globalPtr->refCount = 1; globalPtr->nsPtr = nsPtr; globalPtr->nextPtr = globalTablePtr->buckets[globalHash]; globalTablePtr->buckets[globalHash] = globalPtr; globalTablePtr->numEntries++; /* * If the global literal table has exceeded a decent size, rebuild it with * more buckets. */ if (globalTablePtr->numEntries >= globalTablePtr->rebuildSize) { RebuildLiteralTable(globalTablePtr); } #ifdef TCL_COMPILE_DEBUG TclVerifyGlobalLiteralTable(iPtr); { LiteralEntry *entryPtr; int found; size_t i; found = 0; for (i=0 ; inumBuckets ; i++) { for (entryPtr=globalTablePtr->buckets[i]; entryPtr!=NULL ; entryPtr=entryPtr->nextPtr) { if ((entryPtr == globalPtr) && (entryPtr->objPtr == objPtr)) { found = 1; } } } if (!found) { Tcl_Panic("%s: literal \"%.*s\" wasn't global", "TclRegisterLiteral", (length>60? 60 : (int)length), bytes); } } #endif /*TCL_COMPILE_DEBUG*/ #ifdef TCL_COMPILE_STATS iPtr->stats.numLiteralsCreated++; iPtr->stats.totalLitStringBytes += (double) (length + 1); iPtr->stats.currentLitStringBytes += (double) (length + 1); iPtr->stats.literalCount[TclLog2(length)]++; #endif /*TCL_COMPILE_STATS*/ if (globalPtrPtr) { *globalPtrPtr = globalPtr; } *newPtr = 1; return objPtr; } /* *---------------------------------------------------------------------- * * TclFetchLiteral -- * * Fetch from a CompileEnv the literal value identified by an index * value, as returned by a prior call to TclRegisterLiteral(). * * Results: * The literal value, or NULL if the index is out of range. * *---------------------------------------------------------------------- */ Tcl_Obj * TclFetchLiteral( CompileEnv *envPtr, /* Points to the CompileEnv from which to * fetch the registered literal value. */ Tcl_Size index) /* Index of the desired literal, as returned * by prior call to TclRegisterLiteral() */ { if (index >= envPtr->literalArrayNext) { return NULL; } return envPtr->literalArrayPtr[index].objPtr; } /* *---------------------------------------------------------------------- * * TclRegisterLiteral -- * * Find, or if necessary create, an object in a CompileEnv literal array * that has a string representation matching the argument string. * * Results: * The index in the CompileEnv's literal array that references a shared * literal matching the string. The object is created if necessary. * * Side effects: * To maximize sharing, we look up the string in the interpreter's global * literal table. If not found, we create a new shared literal in the * global table. We then add a reference to the shared literal in the * CompileEnv's literal array. * * If LITERAL_ON_HEAP is set in flags, this function is given ownership * of the string: if an object is created then its string representation * is set directly from string, otherwise the string is freed. Typically, * a caller sets LITERAL_ON_HEAP if "string" is an already heap-allocated * buffer holding the result of backslash substitutions. * *---------------------------------------------------------------------- */ int /* Do NOT change this type. Should not be wider than TclEmitPush operand*/ TclRegisterLiteral( void *ePtr, /* Points to the CompileEnv in whose object * array an object is found or created. */ const char *bytes, /* Points to string for which to find or * create an object in CompileEnv's object * array. */ Tcl_Size length, /* Number of bytes in the string. If -1, the * string consists of all bytes up to the * first null character. */ int flags) /* If LITERAL_ON_HEAP then the caller already * malloc'd bytes and ownership is passed to * this function. If LITERAL_CMD_NAME then * the literal should not be shared across * namespaces. */ { CompileEnv *envPtr = (CompileEnv *)ePtr; Interp *iPtr = envPtr->iPtr; LiteralTable *localTablePtr = &envPtr->localLitTable; LiteralEntry *globalPtr, *localPtr; Tcl_Obj *objPtr; size_t hash, localHash, objIndex; int isNew; Namespace *nsPtr; if (length < 0) { length = (bytes ? strlen(bytes) : 0); } hash = HashString(bytes, length); /* * Is the literal already in the CompileEnv's local literal array? If so, * just return its index. */ localHash = (hash & localTablePtr->mask); for (localPtr=localTablePtr->buckets[localHash] ; localPtr!=NULL; localPtr = localPtr->nextPtr) { objPtr = localPtr->objPtr; if ((objPtr->length == length) && ((length == 0) || ((objPtr->bytes[0] == bytes[0]) && (memcmp(objPtr->bytes, bytes, length) == 0)))) { if ((flags & LITERAL_ON_HEAP)) { Tcl_Free((void *)bytes); } objIndex = (localPtr - envPtr->literalArrayPtr); #ifdef TCL_COMPILE_DEBUG TclVerifyLocalLiteralTable(envPtr); #endif /*TCL_COMPILE_DEBUG*/ if (objIndex > INT_MAX) { Tcl_Panic("Literal table index too large. Cannot be handled by TclEmitPush"); } return objIndex; } } /* * The literal is new to this CompileEnv. If it is a command name, avoid * sharing it across namespaces, and try not to share it with non-cmd * literals. Note that FQ command names can be shared, so that we register * the namespace as the interp's global NS. */ if ((flags & LITERAL_CMD_NAME)) { if ((length >= 2) && (bytes[0] == ':') && (bytes[1] == ':')) { nsPtr = iPtr->globalNsPtr; } else { nsPtr = iPtr->varFramePtr->nsPtr; } } else { nsPtr = NULL; } /* * Is it in the interpreter's global literal table? If not, create it. */ globalPtr = NULL; objPtr = TclCreateLiteral(iPtr, bytes, length, hash, &isNew, nsPtr, flags, &globalPtr); objIndex = AddLocalLiteralEntry(envPtr, objPtr, localHash); #ifdef TCL_COMPILE_DEBUG if (globalPtr != NULL && (globalPtr->refCount + 1 < 2)) { Tcl_Panic("%s: global literal \"%.*s\" had bad refCount %" TCL_Z_MODIFIER "u", "TclRegisterLiteral", (length>60? 60 : (int)length), bytes, globalPtr->refCount); } TclVerifyLocalLiteralTable(envPtr); #endif /*TCL_COMPILE_DEBUG*/ if (objIndex > INT_MAX) { Tcl_Panic( "Literal table index too large. Cannot be handled by TclEmitPush"); } return objIndex; } #ifdef TCL_COMPILE_DEBUG /* *---------------------------------------------------------------------- * * LookupLiteralEntry -- * * Finds the LiteralEntry that corresponds to a literal Tcl object * holding a literal. * * Results: * Returns the matching LiteralEntry if found, otherwise NULL. * * Side effects: * None. * *---------------------------------------------------------------------- */ static LiteralEntry * LookupLiteralEntry( Tcl_Interp *interp, /* Interpreter for which objPtr was created to * hold a literal. */ Tcl_Obj *objPtr) /* Points to a Tcl object holding a literal * that was previously created by a call to * TclRegisterLiteral. */ { Interp *iPtr = (Interp *) interp; LiteralTable *globalTablePtr = &iPtr->literalTable; LiteralEntry *entryPtr; const char *bytes; Tcl_Size globalHash, length; bytes = TclGetStringFromObj(objPtr, &length); globalHash = (HashString(bytes, length) & globalTablePtr->mask); for (entryPtr=globalTablePtr->buckets[globalHash] ; entryPtr!=NULL; entryPtr=entryPtr->nextPtr) { if (entryPtr->objPtr == objPtr) { return entryPtr; } } return NULL; } #endif /* *---------------------------------------------------------------------- * * TclHideLiteral -- * * Remove a literal entry from the literal hash tables, leaving it in the * literal array so existing references continue to function. This makes * it possible to turn a shared literal into a private literal that * cannot be shared. * * Results: * None. * * Side effects: * Removes the literal from the local hash table and decrements the * global hash entry's reference count. * *---------------------------------------------------------------------- */ void TclHideLiteral( Tcl_Interp *interp, /* Interpreter for which objPtr was created to * hold a literal. */ CompileEnv *envPtr,/* Points to CompileEnv whose literal array * contains the entry being hidden. */ int index) /* The index of the entry in the literal * array. */ { LiteralEntry **nextPtrPtr, *entryPtr, *lPtr; LiteralTable *localTablePtr = &envPtr->localLitTable; size_t localHash; Tcl_Size length; const char *bytes; Tcl_Obj *newObjPtr; lPtr = &envPtr->literalArrayPtr[index]; /* * To avoid unwanted sharing we need to copy the object and remove it from * the local and global literal tables. It still has a slot in the literal * array so it can be referred to by byte codes, but it will not be * matched by literal searches. */ newObjPtr = Tcl_DuplicateObj(lPtr->objPtr); Tcl_IncrRefCount(newObjPtr); TclReleaseLiteral(interp, lPtr->objPtr); lPtr->objPtr = newObjPtr; bytes = TclGetStringFromObj(newObjPtr, &length); localHash = HashString(bytes, length) & localTablePtr->mask; nextPtrPtr = &localTablePtr->buckets[localHash]; for (entryPtr=*nextPtrPtr ; entryPtr!=NULL ; entryPtr=*nextPtrPtr) { if (entryPtr == lPtr) { *nextPtrPtr = lPtr->nextPtr; lPtr->nextPtr = NULL; localTablePtr->numEntries--; break; } nextPtrPtr = &entryPtr->nextPtr; } } /* *---------------------------------------------------------------------- * * TclAddLiteralObj -- * * Add a single literal object to the literal array. This function does * not add the literal to the local or global literal tables. The caller * is expected to add the entry to whatever tables are appropriate. * * Results: * The index in the CompileEnv's literal array that references the * literal. Stores the pointer to the new literal entry in the location * referenced by the localPtrPtr argument. * * Side effects: * Expands the literal array if necessary. Increments the refcount on the * literal object. * *---------------------------------------------------------------------- */ int TclAddLiteralObj( CompileEnv *envPtr,/* Points to CompileEnv in whose literal array * the object is to be inserted. */ Tcl_Obj *objPtr, /* The object to insert into the array. */ LiteralEntry **litPtrPtr) /* The location where the pointer to the new * literal entry should be stored. May be * NULL. */ { LiteralEntry *lPtr; size_t objIndex; if (envPtr->literalArrayNext >= envPtr->literalArrayEnd) { ExpandLocalLiteralArray(envPtr); } objIndex = envPtr->literalArrayNext; envPtr->literalArrayNext++; if (objIndex > INT_MAX) { Tcl_Panic( "Literal table index too large. Cannot be handled by TclEmitPush"); } lPtr = &envPtr->literalArrayPtr[objIndex]; lPtr->objPtr = objPtr; Tcl_IncrRefCount(objPtr); lPtr->refCount = TCL_INDEX_NONE; /* i.e., unused */ lPtr->nextPtr = NULL; if (litPtrPtr) { *litPtrPtr = lPtr; } return objIndex; } /* *---------------------------------------------------------------------- * * AddLocalLiteralEntry -- * * Insert a new literal into a CompileEnv's local literal array. * * Results: * The index in the CompileEnv's literal array that references the * literal. * * Side effects: * Expands the literal array if necessary. May rebuild the hash bucket * array of the CompileEnv's literal array if it becomes too large. * *---------------------------------------------------------------------- */ static size_t AddLocalLiteralEntry( CompileEnv *envPtr,/* Points to CompileEnv in whose literal array * the object is to be inserted. */ Tcl_Obj *objPtr, /* The literal to add to the CompileEnv. */ int localHash) /* Hash value for the literal's string. */ { LiteralTable *localTablePtr = &envPtr->localLitTable; LiteralEntry *localPtr; size_t objIndex; objIndex = TclAddLiteralObj(envPtr, objPtr, &localPtr); /* * Add the literal to the local table. */ localPtr->nextPtr = localTablePtr->buckets[localHash]; localTablePtr->buckets[localHash] = localPtr; localTablePtr->numEntries++; /* * If the CompileEnv's local literal table has exceeded a decent size, * rebuild it with more buckets. */ if (localTablePtr->numEntries >= localTablePtr->rebuildSize) { RebuildLiteralTable(localTablePtr); } #ifdef TCL_COMPILE_DEBUG TclVerifyLocalLiteralTable(envPtr); { char *bytes; int found; size_t i; Tcl_Size length; found = 0; for (i=0 ; inumBuckets ; i++) { for (localPtr=localTablePtr->buckets[i] ; localPtr!=NULL ; localPtr=localPtr->nextPtr) { if (localPtr->objPtr == objPtr) { found = 1; } } } if (!found) { bytes = TclGetStringFromObj(objPtr, &length); Tcl_Panic("%s: literal \"%.*s\" wasn't found locally", "AddLocalLiteralEntry", (length>60? 60 : (int)length), bytes); } } #endif /*TCL_COMPILE_DEBUG*/ return objIndex; } /* *---------------------------------------------------------------------- * * ExpandLocalLiteralArray -- * * Function that uses malloc to allocate more storage for a CompileEnv's * local literal array. * * Results: * None. * * Side effects: * The literal array in *envPtr is reallocated to a new array of double * the size, and if envPtr->mallocedLiteralArray is non-zero the old * array is freed. Entries are copied from the old array to the new one. * The local literal table is updated to refer to the new entries. * *---------------------------------------------------------------------- */ static void ExpandLocalLiteralArray( CompileEnv *envPtr)/* Points to the CompileEnv whose object array * must be enlarged. */ { /* * The current allocated local literal entries are stored between elements * 0 and (envPtr->literalArrayNext - 1) [inclusive]. */ LiteralTable *localTablePtr = &envPtr->localLitTable; size_t currElems = envPtr->literalArrayNext; size_t currBytes = (currElems * sizeof(LiteralEntry)); LiteralEntry *currArrayPtr = envPtr->literalArrayPtr; LiteralEntry *newArrayPtr; size_t i; size_t newSize = (currBytes <= UINT_MAX / 2) ? 2*currBytes : UINT_MAX; if (currBytes == newSize) { Tcl_Panic("max size of Tcl literal array (%" TCL_Z_MODIFIER "u literals) exceeded", currElems); } if (envPtr->mallocedLiteralArray) { newArrayPtr = (LiteralEntry *)Tcl_Realloc(currArrayPtr, newSize); } else { /* * envPtr->literalArrayPtr isn't a Tcl_Alloc'd pointer, so we must * code a Tcl_Realloc equivalent for ourselves. */ newArrayPtr = (LiteralEntry *)Tcl_Alloc(newSize); memcpy(newArrayPtr, currArrayPtr, currBytes); envPtr->mallocedLiteralArray = 1; } /* * Update the local literal table's bucket array. */ if (currArrayPtr != newArrayPtr) { for (i=0 ; inumBuckets ; i++) { if (localTablePtr->buckets[i] != NULL) { localTablePtr->buckets[i] = newArrayPtr + (localTablePtr->buckets[i] - currArrayPtr); } } } envPtr->literalArrayPtr = newArrayPtr; envPtr->literalArrayEnd = newSize / sizeof(LiteralEntry); } /* *---------------------------------------------------------------------- * * TclReleaseLiteral -- * * This function releases a reference to one of the shared Tcl objects * that hold literals. It is called to release the literals referenced by * a ByteCode that is being destroyed, and it is also called by * TclDeleteLiteralTable. * * Results: * None. * * Side effects: * The reference count for the global LiteralTable entry that corresponds * to the literal is decremented. If no other reference to a global * literal object remains, it is freed. * *---------------------------------------------------------------------- */ void TclReleaseLiteral( Tcl_Interp *interp, /* Interpreter for which objPtr was created to * hold a literal. */ Tcl_Obj *objPtr) /* Points to a literal object that was * previously created by a call to * TclRegisterLiteral. */ { Interp *iPtr = (Interp *) interp; LiteralTable *globalTablePtr; LiteralEntry *entryPtr, *prevPtr; const char *bytes; size_t index; Tcl_Size length; if (iPtr == NULL) { goto done; } globalTablePtr = &iPtr->literalTable; bytes = TclGetStringFromObj(objPtr, &length); index = HashString(bytes, length) & globalTablePtr->mask; /* * Check to see if the object is in the global literal table and remove * this reference. The object may not be in the table if it is a hidden * local literal. */ for (prevPtr=NULL, entryPtr=globalTablePtr->buckets[index]; entryPtr!=NULL ; prevPtr=entryPtr, entryPtr=entryPtr->nextPtr) { if (entryPtr->objPtr == objPtr) { /* * If the literal is no longer being used by any ByteCode, delete * the entry then remove the reference corresponding to the global * literal table entry (decrement the ref count of the object). */ if ((entryPtr->refCount != TCL_INDEX_NONE) && (entryPtr->refCount-- <= 1)) { if (prevPtr == NULL) { globalTablePtr->buckets[index] = entryPtr->nextPtr; } else { prevPtr->nextPtr = entryPtr->nextPtr; } Tcl_Free(entryPtr); globalTablePtr->numEntries--; TclDecrRefCount(objPtr); #ifdef TCL_COMPILE_STATS iPtr->stats.currentLitStringBytes -= (double) (length + 1); #endif /*TCL_COMPILE_STATS*/ } break; } } /* * Remove the reference corresponding to the local literal table entry. */ done: Tcl_DecrRefCount(objPtr); } /* *---------------------------------------------------------------------- * * HashString -- * * Compute a one-word summary of a text string, which can be used to * generate a hash index. * * Results: * The return value is a one-word summary of the information in string. * * Side effects: * None. * *---------------------------------------------------------------------- */ static size_t HashString( const char *string, /* String for which to compute hash value. */ size_t length) /* Number of bytes in the string. */ { size_t result = 0; /* * I tried a zillion different hash functions and asked many other people * for advice. Many people had their own favorite functions, all * different, but no-one had much idea why they were good ones. I chose * the one below (multiply by 9 and add new character) because of the * following reasons: * * 1. Multiplying by 10 is perfect for keys that are decimal strings, and * multiplying by 9 is just about as good. * 2. Times-9 is (shift-left-3) plus (old). This means that each * character's bits hang around in the low-order bits of the hash value * for ever, plus they spread fairly rapidly up to the high-order bits * to fill out the hash value. This seems works well both for decimal * and non-decimal strings. * * Note that this function is very weak against malicious strings; it's * very easy to generate multiple keys that have the same hashcode. On the * other hand, that hardly ever actually occurs and this function *is* * very cheap, even by comparison with industry-standard hashes like FNV. * If real strength of hash is required though, use a custom hash based on * Bob Jenkins's lookup3(), but be aware that it's significantly slower. * Tcl scripts tend to not have a big issue in this area, and literals * mostly aren't looked up by name anyway. * * See also HashStringKey in tclHash.c. * See also TclObjHashKey in tclObj.c. * * See [tcl-Feature Request #2958832] */ if (length > 0) { result = UCHAR(*string); while (--length) { result += (result << 3) + UCHAR(*++string); } } return result; } /* *---------------------------------------------------------------------- * * RebuildLiteralTable -- * * This function is invoked when the ratio of entries to hash buckets * becomes too large in a local or global literal table. It allocates a * larger bucket array and moves the entries into the new buckets. * * Results: * None. * * Side effects: * Memory gets reallocated and entries get rehashed into new buckets. * *---------------------------------------------------------------------- */ static void RebuildLiteralTable( LiteralTable *tablePtr) /* Local or global table to enlarge. */ { LiteralEntry **oldBuckets; LiteralEntry **oldChainPtr, **newChainPtr; LiteralEntry *entryPtr; LiteralEntry **bucketPtr; const char *bytes; size_t oldSize, count, index; Tcl_Size length; oldSize = tablePtr->numBuckets; oldBuckets = tablePtr->buckets; /* * Allocate and initialize the new bucket array, and set up hashing * constants for new array size. */ if (oldSize > UINT_MAX/(4 * sizeof(LiteralEntry *))) { /* * Memory allocator limitations will not let us create the * next larger table size. Best option is to limp along * with what we have. */ return; } tablePtr->numBuckets *= 4; tablePtr->buckets = (LiteralEntry **)Tcl_Alloc( tablePtr->numBuckets * sizeof(LiteralEntry*)); for (count=tablePtr->numBuckets, newChainPtr=tablePtr->buckets; count>0 ; count--, newChainPtr++) { *newChainPtr = NULL; } tablePtr->rebuildSize *= 4; tablePtr->mask = (tablePtr->mask << 2) + 3; /* * Rehash all of the existing entries into the new bucket array. */ for (oldChainPtr=oldBuckets ; oldSize>0 ; oldSize--,oldChainPtr++) { for (entryPtr=*oldChainPtr ; entryPtr!=NULL ; entryPtr=*oldChainPtr) { bytes = TclGetStringFromObj(entryPtr->objPtr, &length); index = (HashString(bytes, length) & tablePtr->mask); *oldChainPtr = entryPtr->nextPtr; bucketPtr = &tablePtr->buckets[index]; entryPtr->nextPtr = *bucketPtr; *bucketPtr = entryPtr; } } /* * Free up the old bucket array, if it was dynamically allocated. */ if (oldBuckets != tablePtr->staticBuckets) { Tcl_Free(oldBuckets); } } /* *---------------------------------------------------------------------- * * TclInvalidateCmdLiteral -- * * Invalidate a command literal entry, if present in the literal hash * tables, by resetting its internal representation. This invalidation * leaves it in the literal tables and in existing literal arrays. As a * result, existing references continue to work but we force a fresh * command look-up upon the next use (see, in particular, * TclSetCmdNameObj()). * * Results: * None. * * Side effects: * Resets the internal representation of the CmdName Tcl_Obj * using TclFreeInternalRep(). * *---------------------------------------------------------------------- */ void TclInvalidateCmdLiteral( Tcl_Interp *interp, /* Interpreter for which to invalidate a * command literal. */ const char *name, /* Points to the start of the cmd literal * name. */ Namespace *nsPtr) /* The namespace for which to lookup and * invalidate a cmd literal. */ { Interp *iPtr = (Interp *) interp; Tcl_Obj *literalObjPtr = TclCreateLiteral(iPtr, name, strlen(name), TCL_INDEX_NONE, NULL, nsPtr, 0, NULL); if (literalObjPtr != NULL) { if (TclHasInternalRep(literalObjPtr, &tclCmdNameType)) { TclFreeInternalRep(literalObjPtr); } /* Balance the refcount effects of TclCreateLiteral() above */ Tcl_IncrRefCount(literalObjPtr); TclReleaseLiteral(interp, literalObjPtr); } } #ifdef TCL_COMPILE_STATS /* *---------------------------------------------------------------------- * * TclLiteralStats -- * * Return statistics describing the layout of the hash table in its hash * buckets. * * Results: * The return value is a malloc-ed string containing information about * tablePtr. It is the caller's responsibility to free this string. * * Side effects: * None. * *---------------------------------------------------------------------- */ char * TclLiteralStats( LiteralTable *tablePtr) /* Table for which to produce stats. */ { #define NUM_COUNTERS 10 size_t count[NUM_COUNTERS], overflow, i, j; double average, tmp; LiteralEntry *entryPtr; char *result, *p; /* * Compute a histogram of bucket usage. For each bucket chain i, j is the * number of entries in the chain. */ for (i=0 ; inumBuckets ; i++) { j = 0; for (entryPtr=tablePtr->buckets[i] ; entryPtr!=NULL; entryPtr=entryPtr->nextPtr) { j++; } if (j < NUM_COUNTERS) { count[j]++; } else { overflow++; } tmp = j; average += (tmp+1.0)*(tmp/tablePtr->numEntries)/2.0; } /* * Print out the histogram and a few other pieces of information. */ result = (char *)Tcl_Alloc(NUM_COUNTERS*60 + 300); snprintf(result, 60, "%" TCL_Z_MODIFIER "u entries in table, %" TCL_Z_MODIFIER "u buckets\n", tablePtr->numEntries, tablePtr->numBuckets); p = result + strlen(result); for (i=0 ; ilocalLitTable; LiteralEntry *localPtr; char *bytes; size_t i, count = 0; Tcl_Size length; for (i=0 ; inumBuckets ; i++) { for (localPtr=localTablePtr->buckets[i] ; localPtr!=NULL; localPtr=localPtr->nextPtr) { count++; if (localPtr->refCount != TCL_INDEX_NONE) { bytes = TclGetStringFromObj(localPtr->objPtr, &length); Tcl_Panic("%s: local literal \"%.*s\" had bad refCount %" TCL_Z_MODIFIER "u", "TclVerifyLocalLiteralTable", (length>60? 60 : (int) length), bytes, localPtr->refCount); } if (localPtr->objPtr->bytes == NULL) { Tcl_Panic("%s: literal has NULL string rep", "TclVerifyLocalLiteralTable"); } } } if (count != localTablePtr->numEntries) { Tcl_Panic("%s: local literal table had %" TCL_Z_MODIFIER "u entries, should be %" TCL_Z_MODIFIER "u", "TclVerifyLocalLiteralTable", count, localTablePtr->numEntries); } } /* *---------------------------------------------------------------------- * * TclVerifyGlobalLiteralTable -- * * Check an interpreter's global literal table literal for consistency. * * Results: * None. * * Side effects: * Tcl_Panic if problems are found. * *---------------------------------------------------------------------- */ void TclVerifyGlobalLiteralTable( Interp *iPtr) /* Points to interpreter whose global literal * table is to be validated. */ { LiteralTable *globalTablePtr = &iPtr->literalTable; LiteralEntry *globalPtr; char *bytes; size_t i, count = 0; Tcl_Size length; for (i=0 ; inumBuckets ; i++) { for (globalPtr=globalTablePtr->buckets[i] ; globalPtr!=NULL; globalPtr=globalPtr->nextPtr) { count++; if (globalPtr->refCount + 1 < 2) { bytes = TclGetStringFromObj(globalPtr->objPtr, &length); Tcl_Panic("%s: global literal \"%.*s\" had bad refCount %" TCL_Z_MODIFIER "u", "TclVerifyGlobalLiteralTable", (length>60? 60 : (int)length), bytes, globalPtr->refCount); } if (globalPtr->objPtr->bytes == NULL) { Tcl_Panic("%s: literal has NULL string rep", "TclVerifyGlobalLiteralTable"); } } } if (count != globalTablePtr->numEntries) { Tcl_Panic("%s: global literal table had %" TCL_Z_MODIFIER "u entries, should be %" TCL_Z_MODIFIER "u", "TclVerifyGlobalLiteralTable", count, globalTablePtr->numEntries); } } #endif /*TCL_COMPILE_DEBUG*/ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclLoad.c0000644000175000017500000010540514726623136014725 0ustar sergeisergei/* * tclLoad.c -- * * This file provides the generic portion (those that are the same on all * platforms) of Tcl's dynamic loading facilities. * * Copyright © 1995-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * The following structure describes a library that has been loaded either * dynamically (with the "load" command) or statically (as indicated by a call * to Tcl_StaticLibrary). All such libraries are linked together into a * single list for the process. */ typedef struct LoadedLibrary { char *fileName; /* Name of the file from which the library was * loaded. An empty string means the library * is loaded statically. Malloc-ed. */ char *prefix; /* Prefix for the library. * Malloc-ed. */ Tcl_LoadHandle loadHandle; /* Token for the loaded file which should be * passed to (*unLoadProcPtr)() when the file * is no longer needed. If fileName is NULL, * then this field is irrelevant. */ Tcl_LibraryInitProc *initProc; /* Initialization function to call to * incorporate this library into a trusted * interpreter. */ Tcl_LibraryInitProc *safeInitProc; /* Initialization function to call to * incorporate this library into a safe * interpreter (one that will execute * untrusted scripts). NULL means the library * can't be used in unsafe interpreters. */ Tcl_LibraryUnloadProc *unloadProc; /* Finalization function to unload a library * from a trusted interpreter. NULL means that * the library cannot be unloaded. */ Tcl_LibraryUnloadProc *safeUnloadProc; /* Finalization function to unload a library * from a safe interpreter. NULL means that * the library cannot be unloaded. */ int interpRefCount; /* How many times the library has been loaded * in trusted interpreters. */ int safeInterpRefCount; /* How many times the library has been loaded * in safe interpreters. */ struct LoadedLibrary *nextPtr; /* Next in list of all libraries loaded into * this application process. NULL means end of * list. */ } LoadedLibrary; /* * TCL_THREADS * There is a global list of libraries that is anchored at firstLibraryPtr. * Access to this list is governed by a mutex. */ static LoadedLibrary *firstLibraryPtr = NULL; /* First in list of all libraries loaded into * this process. */ TCL_DECLARE_MUTEX(libraryMutex) /* * The following structure represents a particular library that has been * incorporated into a particular interpreter (by calling its initialization * function). There is a list of these structures for each interpreter, with * an AssocData value (key "load") for the interpreter that points to the * first library (if any). */ typedef struct InterpLibrary { LoadedLibrary *libraryPtr; /* Points to detailed information about * library. */ struct InterpLibrary *nextPtr; /* Next library in this interpreter, or NULL * for end of list. */ } InterpLibrary; /* * Prototypes for functions that are private to this file: */ static void LoadCleanupProc(void *clientData, Tcl_Interp *interp); static int IsStatic(LoadedLibrary *libraryPtr); static int UnloadLibrary(Tcl_Interp *interp, Tcl_Interp *target, LoadedLibrary *library, int keepLibrary, const char *fullFileName, int interpExiting); static int IsStatic( LoadedLibrary *libraryPtr) { return (libraryPtr->fileName[0] == '\0'); } /* *---------------------------------------------------------------------- * * Tcl_LoadObjCmd -- * * This function is invoked to process the "load" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_LoadObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Interp *target; LoadedLibrary *libraryPtr, *defaultPtr; Tcl_DString pfx, tmp, initName, safeInitName; Tcl_DString unloadName, safeUnloadName; InterpLibrary *ipFirstPtr, *ipPtr; int code, namesMatch, filesMatch, offset; const char *symbols[2]; Tcl_LibraryInitProc *initProc; const char *p, *fullFileName, *prefix; Tcl_LoadHandle loadHandle; Tcl_UniChar ch = 0; size_t len; int flags = 0; Tcl_Obj *const *savedobjv = objv; static const char *const options[] = { "-global", "-lazy", "--", NULL }; enum loadOptionsEnum { LOAD_GLOBAL, LOAD_LAZY, LOAD_LAST } index; while (objc > 2) { if (TclGetString(objv[1])[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, objv[1], options, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } ++objv; --objc; if (LOAD_GLOBAL == index) { flags |= TCL_LOAD_GLOBAL; } else if (LOAD_LAZY == index) { flags |= TCL_LOAD_LAZY; } else { break; } } if ((objc < 2) || (objc > 4)) { Tcl_WrongNumArgs(interp, 1, savedobjv, "?-global? ?-lazy? ?--? fileName ?prefix? ?interp?"); return TCL_ERROR; } if (Tcl_FSConvertToPathType(interp, objv[1]) != TCL_OK) { return TCL_ERROR; } fullFileName = TclGetString(objv[1]); Tcl_DStringInit(&pfx); Tcl_DStringInit(&initName); Tcl_DStringInit(&safeInitName); Tcl_DStringInit(&unloadName); Tcl_DStringInit(&safeUnloadName); Tcl_DStringInit(&tmp); prefix = NULL; if (objc >= 3) { prefix = TclGetString(objv[2]); if (prefix[0] == '\0') { prefix = NULL; } } if ((fullFileName[0] == 0) && (prefix == NULL)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "must specify either file name or prefix", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LOAD", "NOLIBRARY", (char *)NULL); code = TCL_ERROR; goto done; } /* * Figure out which interpreter we're going to load the library into. */ target = interp; if (objc == 4) { const char *childIntName = TclGetString(objv[3]); target = Tcl_GetChild(interp, childIntName); if (target == NULL) { code = TCL_ERROR; goto done; } } /* * Scan through the libraries that are currently loaded to see if the * library we want is already loaded. We'll use a loaded library if it * meets any of the following conditions: * - Its name and file match the once we're looking for. * - Its file matches, and we weren't given a name. * - Its name matches, the file name was specified as empty, and there is * only no statically loaded library with the same prefix. */ Tcl_MutexLock(&libraryMutex); defaultPtr = NULL; for (libraryPtr = firstLibraryPtr; libraryPtr != NULL; libraryPtr = libraryPtr->nextPtr) { if (prefix == NULL) { namesMatch = 0; } else { TclDStringClear(&pfx); Tcl_DStringAppend(&pfx, prefix, -1); TclDStringClear(&tmp); Tcl_DStringAppend(&tmp, libraryPtr->prefix, -1); if (strcmp(Tcl_DStringValue(&tmp), Tcl_DStringValue(&pfx)) == 0) { namesMatch = 1; } else { namesMatch = 0; } } TclDStringClear(&pfx); filesMatch = (strcmp(libraryPtr->fileName, fullFileName) == 0); if (filesMatch && (namesMatch || (prefix == NULL))) { break; } if (namesMatch && (fullFileName[0] == 0)) { defaultPtr = libraryPtr; } if (filesMatch && !namesMatch && (fullFileName[0] != 0)) { /* * Can't have two different libraries loaded from the same file. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "file \"%s\" is already loaded for prefix \"%s\"", fullFileName, libraryPtr->prefix)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LOAD", "SPLITPERSONALITY", (char *)NULL); code = TCL_ERROR; Tcl_MutexUnlock(&libraryMutex); goto done; } } Tcl_MutexUnlock(&libraryMutex); if (libraryPtr == NULL) { libraryPtr = defaultPtr; } /* * Scan through the list of libraries already loaded in the target * interpreter. If the library we want is already loaded there, then * there's nothing for us to do. */ if (libraryPtr != NULL) { ipFirstPtr = (InterpLibrary *)Tcl_GetAssocData(target, "tclLoad", NULL); for (ipPtr = ipFirstPtr; ipPtr != NULL; ipPtr = ipPtr->nextPtr) { if (ipPtr->libraryPtr == libraryPtr) { code = TCL_OK; goto done; } } } if (libraryPtr == NULL) { /* * The desired file isn't currently loaded, so load it. It's an error * if the desired library is a static one. */ if (fullFileName[0] == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "no library with prefix \"%s\" is loaded statically", prefix)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LOAD", "NOTSTATIC", (char *)NULL); code = TCL_ERROR; goto done; } /* * Figure out the prefix if it wasn't provided explicitly. */ if (prefix != NULL) { Tcl_DStringAppend(&pfx, prefix, -1); } else { Tcl_Obj *splitPtr, *pkgGuessPtr; Tcl_Size pElements; const char *pkgGuess; /* * Threading note - this call used to be protected by a mutex. */ /* * The platform-specific code couldn't figure out the prefix. * Make a guess by taking the last element of the file * name, stripping off any leading "lib" and/or "tcl9", and * then using all of the alphabetic and underline characters * that follow that. */ splitPtr = Tcl_FSSplitPath(objv[1], &pElements); Tcl_ListObjIndex(NULL, splitPtr, pElements -1, &pkgGuessPtr); pkgGuess = TclGetString(pkgGuessPtr); if ((pkgGuess[0] == 'l') && (pkgGuess[1] == 'i') && (pkgGuess[2] == 'b')) { pkgGuess += 3; } #ifdef __CYGWIN__ else if ((pkgGuess[0] == 'c') && (pkgGuess[1] == 'y') && (pkgGuess[2] == 'g')) { pkgGuess += 3; } #endif /* __CYGWIN__ */ if (((pkgGuess[0] == 't') #ifdef MAC_OSX_TCL || (pkgGuess[0] == 'T') #endif ) && (pkgGuess[1] == 'c') && (pkgGuess[2] == 'l') && (pkgGuess[3] == '9')) { pkgGuess += 4; } for (p = pkgGuess; *p != 0; p += offset) { offset = TclUtfToUniChar(p, &ch); if (!Tcl_UniCharIsWordChar(UCHAR(ch)) || Tcl_UniCharIsDigit(UCHAR(ch))) { break; } } if (p == pkgGuess) { Tcl_DecrRefCount(splitPtr); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot figure out prefix for %s", fullFileName)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LOAD", "WHATLIBRARY", (char *)NULL); code = TCL_ERROR; goto done; } Tcl_DStringAppend(&pfx, pkgGuess, p - pkgGuess); Tcl_DecrRefCount(splitPtr); /* * Fix the capitalization in the prefix so that the first * character is in caps (or title case) but the others are all * lower-case. */ Tcl_DStringSetLength(&pfx, Tcl_UtfToTitle(Tcl_DStringValue(&pfx))); } /* * Compute the names of the two initialization functions, based on the * prefix. */ TclDStringAppendDString(&initName, &pfx); TclDStringAppendLiteral(&initName, "_Init"); TclDStringAppendDString(&safeInitName, &pfx); TclDStringAppendLiteral(&safeInitName, "_SafeInit"); TclDStringAppendDString(&unloadName, &pfx); TclDStringAppendLiteral(&unloadName, "_Unload"); TclDStringAppendDString(&safeUnloadName, &pfx); TclDStringAppendLiteral(&safeUnloadName, "_SafeUnload"); /* * Call platform-specific code to load the library and find the two * initialization functions. */ symbols[0] = Tcl_DStringValue(&initName); symbols[1] = NULL; Tcl_MutexLock(&libraryMutex); code = Tcl_LoadFile(interp, objv[1], symbols, flags, &initProc, &loadHandle); Tcl_MutexUnlock(&libraryMutex); if (code != TCL_OK) { goto done; } /* * Create a new record to describe this library. */ libraryPtr = (LoadedLibrary *)Tcl_Alloc(sizeof(LoadedLibrary)); len = strlen(fullFileName) + 1; libraryPtr->fileName = (char *)Tcl_Alloc(len); memcpy(libraryPtr->fileName, fullFileName, len); len = Tcl_DStringLength(&pfx) + 1; libraryPtr->prefix = (char *)Tcl_Alloc(len); memcpy(libraryPtr->prefix, Tcl_DStringValue(&pfx), len); libraryPtr->loadHandle = loadHandle; libraryPtr->initProc = initProc; libraryPtr->safeInitProc = (Tcl_LibraryInitProc *) Tcl_FindSymbol(interp, loadHandle, Tcl_DStringValue(&safeInitName)); libraryPtr->unloadProc = (Tcl_LibraryUnloadProc *) Tcl_FindSymbol(interp, loadHandle, Tcl_DStringValue(&unloadName)); libraryPtr->safeUnloadProc = (Tcl_LibraryUnloadProc *) Tcl_FindSymbol(interp, loadHandle, Tcl_DStringValue(&safeUnloadName)); libraryPtr->interpRefCount = 0; libraryPtr->safeInterpRefCount = 0; Tcl_MutexLock(&libraryMutex); libraryPtr->nextPtr = firstLibraryPtr; firstLibraryPtr = libraryPtr; Tcl_MutexUnlock(&libraryMutex); /* * The Tcl_FindSymbol calls may have left a spurious error message in * the interpreter result. */ Tcl_ResetResult(interp); } /* * Invoke the library's initialization function (either the normal one or * the safe one, depending on whether or not the interpreter is safe). */ if (Tcl_IsSafe(target)) { if (libraryPtr->safeInitProc == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot use library in a safe interpreter: no" " %s_SafeInit procedure", libraryPtr->prefix)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LOAD", "UNSAFE", (char *)NULL); code = TCL_ERROR; goto done; } code = libraryPtr->safeInitProc(target); } else { if (libraryPtr->initProc == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot attach library to interpreter: no %s_Init procedure", libraryPtr->prefix)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "LOAD", "ENTRYPOINT", (char *)NULL); code = TCL_ERROR; goto done; } code = libraryPtr->initProc(target); } /* * Test for whether the initialization failed. If so, transfer the error * from the target interpreter to the originating one. */ if (code != TCL_OK) { Interp *iPtr = (Interp *) target; if (iPtr->legacyResult && *(iPtr->legacyResult) && !iPtr->legacyFreeProc) { /* * A call to Tcl_InitStubs() determined the caller extension and * this interp are incompatible in their stubs mechanisms, and * recorded the error in the oldest legacy place we have to do so. */ Tcl_SetObjResult(target, Tcl_NewStringObj(iPtr->legacyResult, -1)); iPtr->legacyResult = NULL; iPtr->legacyFreeProc = (void (*) (void))-1; } Tcl_TransferResult(target, code, interp); goto done; } /* * Record the fact that the library has been loaded in the target * interpreter. * * Update the proper reference count. */ Tcl_MutexLock(&libraryMutex); if (Tcl_IsSafe(target)) { libraryPtr->safeInterpRefCount++; } else { libraryPtr->interpRefCount++; } Tcl_MutexUnlock(&libraryMutex); /* * Refetch ipFirstPtr: loading the library may have introduced additional * static libraries at the head of the linked list! */ ipFirstPtr = (InterpLibrary *)Tcl_GetAssocData(target, "tclLoad", NULL); ipPtr = (InterpLibrary *)Tcl_Alloc(sizeof(InterpLibrary)); ipPtr->libraryPtr = libraryPtr; ipPtr->nextPtr = ipFirstPtr; Tcl_SetAssocData(target, "tclLoad", LoadCleanupProc, ipPtr); done: Tcl_DStringFree(&pfx); Tcl_DStringFree(&initName); Tcl_DStringFree(&safeInitName); Tcl_DStringFree(&unloadName); Tcl_DStringFree(&safeUnloadName); Tcl_DStringFree(&tmp); return code; } /* *---------------------------------------------------------------------- * * Tcl_UnloadObjCmd -- * * Implements the "unload" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_UnloadObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Interp *target; /* Which interpreter to unload from. */ LoadedLibrary *libraryPtr; Tcl_DString pfx, tmp; InterpLibrary *ipFirstPtr, *ipPtr; int i, code, complain = 1, keepLibrary = 0; const char *fullFileName = ""; const char *prefix; static const char *const options[] = { "-nocomplain", "-keeplibrary", "--", NULL }; enum unloadOptionsEnum { UNLOAD_NOCOMPLAIN, UNLOAD_KEEPLIB, UNLOAD_LAST } index; for (i = 1; i < objc; i++) { if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", 0, &index) != TCL_OK) { fullFileName = TclGetString(objv[i]); if (fullFileName[0] == '-') { /* * It looks like the command contains an option so signal an * error */ return TCL_ERROR; } else { /* * This clearly isn't an option; assume it's the filename. We * must clear the error. */ Tcl_ResetResult(interp); break; } } switch (index) { case UNLOAD_NOCOMPLAIN: /* -nocomplain */ complain = 0; break; case UNLOAD_KEEPLIB: /* -keeplibrary */ keepLibrary = 1; break; case UNLOAD_LAST: /* -- */ i++; goto endOfForLoop; } } endOfForLoop: if ((objc-i < 1) || (objc-i > 3)) { Tcl_WrongNumArgs(interp, 1, objv, "?-switch ...? fileName ?prefix? ?interp?"); return TCL_ERROR; } if (Tcl_FSConvertToPathType(interp, objv[i]) != TCL_OK) { return TCL_ERROR; } fullFileName = TclGetString(objv[i]); Tcl_DStringInit(&pfx); Tcl_DStringInit(&tmp); prefix = NULL; if (objc - i >= 2) { prefix = TclGetString(objv[i+1]); if (prefix[0] == '\0') { prefix = NULL; } } if ((fullFileName[0] == 0) && (prefix == NULL)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "must specify either file name or prefix", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "UNLOAD", "NOLIBRARY", (char *)NULL); code = TCL_ERROR; goto done; } /* * Figure out which interpreter we're going to load the library into. */ target = interp; if (objc - i == 3) { const char *childIntName = TclGetString(objv[i + 2]); target = Tcl_GetChild(interp, childIntName); if (target == NULL) { return TCL_ERROR; } } /* * Scan through the libraries that are currently loaded to see if the * library we want is already loaded. We'll use a loaded library if it * meets any of the following conditions: * - Its prefix and file match the once we're looking for. * - Its file matches, and we weren't given a prefix. * - Its prefix matches, the file name was specified as empty, and there is * no statically loaded library with the same prefix. */ Tcl_MutexLock(&libraryMutex); for (libraryPtr = firstLibraryPtr; libraryPtr != NULL; libraryPtr = libraryPtr->nextPtr) { int namesMatch, filesMatch; if (prefix == NULL) { namesMatch = 0; } else { TclDStringClear(&pfx); Tcl_DStringAppend(&pfx, prefix, -1); TclDStringClear(&tmp); Tcl_DStringAppend(&tmp, libraryPtr->prefix, -1); if (strcmp(Tcl_DStringValue(&tmp), Tcl_DStringValue(&pfx)) == 0) { namesMatch = 1; } else { namesMatch = 0; } } TclDStringClear(&pfx); filesMatch = (strcmp(libraryPtr->fileName, fullFileName) == 0); if (filesMatch && (namesMatch || (prefix == NULL))) { break; } if (filesMatch && !namesMatch && (fullFileName[0] != 0)) { break; } } Tcl_MutexUnlock(&libraryMutex); if (fullFileName[0] == 0) { /* * It's an error to try unload a static library. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "library with prefix \"%s\" is loaded statically and cannot be unloaded", prefix)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "UNLOAD", "STATIC", (char *)NULL); code = TCL_ERROR; goto done; } if (libraryPtr == NULL) { /* * The DLL pointed by the provided filename has never been loaded. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "file \"%s\" has never been loaded", fullFileName)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "UNLOAD", "NEVERLOADED", (char *)NULL); code = TCL_ERROR; goto done; } /* * Scan through the list of libraries already loaded in the target * interpreter. If the library we want is already loaded there, then we * should proceed with unloading. */ code = TCL_ERROR; if (libraryPtr != NULL) { ipFirstPtr = (InterpLibrary *)Tcl_GetAssocData(target, "tclLoad", NULL); for (ipPtr = ipFirstPtr; ipPtr != NULL; ipPtr = ipPtr->nextPtr) { if (ipPtr->libraryPtr == libraryPtr) { code = TCL_OK; break; } } } if (code != TCL_OK) { /* * The library has not been loaded in this interpreter. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "file \"%s\" has never been loaded in this interpreter", fullFileName)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "UNLOAD", "NEVERLOADED", (char *)NULL); code = TCL_ERROR; goto done; } code = UnloadLibrary(interp, target, libraryPtr, keepLibrary, fullFileName, 0); done: Tcl_DStringFree(&pfx); Tcl_DStringFree(&tmp); if (!complain && (code != TCL_OK)) { code = TCL_OK; Tcl_ResetResult(interp); } return code; } /* *---------------------------------------------------------------------- * * UnloadLibrary -- * * Unloads a library from an interpreter, and also from the process if it * is unloadable, i.e. if it provides an "unload" function. * * Results: * A standard Tcl result. * * Side effects: * See description. * *---------------------------------------------------------------------- */ static int UnloadLibrary( Tcl_Interp *interp, Tcl_Interp *target, LoadedLibrary *libraryPtr, int keepLibrary, const char *fullFileName, int interpExiting) { int code; InterpLibrary *ipFirstPtr, *ipPtr; LoadedLibrary *iterLibraryPtr; int trustedRefCount = -1, safeRefCount = -1; Tcl_LibraryUnloadProc *unloadProc = NULL; /* * Ensure that the DLL can be unloaded. If it is a trusted interpreter, * libraryPtr->unloadProc must not be NULL for the DLL to be unloadable. If * the interpreter is a safe one, libraryPtr->safeUnloadProc must be non-NULL. */ if (Tcl_IsSafe(target)) { if (libraryPtr->safeUnloadProc == NULL) { if (!interpExiting) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "file \"%s\" cannot be unloaded under a safe interpreter", fullFileName)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "UNLOAD", "CANNOT", (char *)NULL); code = TCL_ERROR; goto done; } } unloadProc = libraryPtr->safeUnloadProc; } else { if (libraryPtr->unloadProc == NULL) { if (!interpExiting) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "file \"%s\" cannot be unloaded under a trusted interpreter", fullFileName)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "UNLOAD", "CANNOT", (char *)NULL); code = TCL_ERROR; goto done; } } unloadProc = libraryPtr->unloadProc; } /* * We are ready to unload the library. First, evaluate the unload * function. If this fails, we cannot proceed with unload. Also, we must * specify the proper flag to pass to the unload callback. * TCL_UNLOAD_DETACH_FROM_INTERPRETER is defined when the callback should * only remove itself from the interpreter; the library will be unloaded * in a future call of unload. In case the library will be unloaded just * after the callback returns, TCL_UNLOAD_DETACH_FROM_PROCESS is passed. */ if (unloadProc == NULL) { code = TCL_OK; } else { code = TCL_UNLOAD_DETACH_FROM_INTERPRETER; if (!keepLibrary) { Tcl_MutexLock(&libraryMutex); trustedRefCount = libraryPtr->interpRefCount; safeRefCount = libraryPtr->safeInterpRefCount; Tcl_MutexUnlock(&libraryMutex); if (Tcl_IsSafe(target)) { safeRefCount--; } else { trustedRefCount--; } if (safeRefCount <= 0 && trustedRefCount <= 0) { code = TCL_UNLOAD_DETACH_FROM_PROCESS; } } code = unloadProc(target, code); } if (code != TCL_OK) { Tcl_TransferResult(target, code, interp); goto done; } /* * Remove this library from the interpreter's library cache. */ if (!interpExiting) { ipFirstPtr = (InterpLibrary *)Tcl_GetAssocData(target, "tclLoad", NULL); if (ipFirstPtr) { ipPtr = ipFirstPtr; if (ipPtr->libraryPtr == libraryPtr) { ipFirstPtr = ipFirstPtr->nextPtr; } else { InterpLibrary *ipPrevPtr; for (ipPrevPtr = ipPtr; ipPtr != NULL; ipPrevPtr = ipPtr, ipPtr = ipPtr->nextPtr) { if (ipPtr->libraryPtr == libraryPtr) { ipPrevPtr->nextPtr = ipPtr->nextPtr; break; } } } Tcl_Free(ipPtr); Tcl_SetAssocData(target, "tclLoad", LoadCleanupProc, ipFirstPtr); } } if (IsStatic(libraryPtr)) { goto done; } /* * The unload function was called succesfully. */ Tcl_MutexLock(&libraryMutex); if (Tcl_IsSafe(target)) { libraryPtr->safeInterpRefCount--; /* * Do not let counter get negative. */ if (libraryPtr->safeInterpRefCount < 0) { libraryPtr->safeInterpRefCount = 0; } } else { libraryPtr->interpRefCount--; /* * Do not let counter get negative. */ if (libraryPtr->interpRefCount < 0) { libraryPtr->interpRefCount = 0; } } trustedRefCount = libraryPtr->interpRefCount; safeRefCount = libraryPtr->safeInterpRefCount; Tcl_MutexUnlock(&libraryMutex); code = TCL_OK; if (libraryPtr->safeInterpRefCount <= 0 && libraryPtr->interpRefCount <= 0 && (unloadProc != NULL) && !keepLibrary) { /* * Unload the shared library from the application memory... */ #if defined(TCL_UNLOAD_DLLS) || defined(_WIN32) /* * Some Unix dlls are poorly behaved - registering things like atexit * calls that can't be unregistered. If you unload such dlls, you get * a core on exit because it wants to call a function in the dll after * it's been unloaded. */ if (!IsStatic(libraryPtr)) { Tcl_MutexLock(&libraryMutex); if (Tcl_FSUnloadFile(interp, libraryPtr->loadHandle) == TCL_OK) { /* * Remove this library from the loaded library cache. */ iterLibraryPtr = libraryPtr; if (iterLibraryPtr == firstLibraryPtr) { firstLibraryPtr = libraryPtr->nextPtr; } else { for (libraryPtr = firstLibraryPtr; libraryPtr != NULL; libraryPtr = libraryPtr->nextPtr) { if (libraryPtr->nextPtr == iterLibraryPtr) { libraryPtr->nextPtr = iterLibraryPtr->nextPtr; break; } } } Tcl_Free(iterLibraryPtr->fileName); Tcl_Free(iterLibraryPtr->prefix); Tcl_Free(iterLibraryPtr); Tcl_MutexUnlock(&libraryMutex); } else { code = TCL_ERROR; } } #else Tcl_SetObjResult(interp, Tcl_ObjPrintf( "file \"%s\" cannot be unloaded: unloading disabled", fullFileName)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "UNLOAD", "DISABLED", NULL); code = TCL_ERROR; #endif } done: return code; } /* *---------------------------------------------------------------------- * * Tcl_StaticLibrary -- * * This function is invoked to indicate that a particular library has * been linked statically with an application. * * Results: * None. * * Side effects: * Once this function completes, the library becomes loadable via the * "load" command with an empty file name. * *---------------------------------------------------------------------- */ void Tcl_StaticLibrary( Tcl_Interp *interp, /* If not NULL, it means that the library has * already been loaded into the given * interpreter by calling the appropriate init * proc. */ const char *prefix, /* Prefix. */ Tcl_LibraryInitProc *initProc, /* Function to call to incorporate this * library into a trusted interpreter. */ Tcl_LibraryInitProc *safeInitProc) /* Function to call to incorporate this * library into a safe interpreter (one that * will execute untrusted scripts). NULL means * the library can't be used in safe * interpreters. */ { LoadedLibrary *libraryPtr; InterpLibrary *ipPtr, *ipFirstPtr; /* * Check to see if someone else has already reported this library as * statically loaded in the process. */ Tcl_MutexLock(&libraryMutex); for (libraryPtr = firstLibraryPtr; libraryPtr != NULL; libraryPtr = libraryPtr->nextPtr) { if ((libraryPtr->initProc == initProc) && (libraryPtr->safeInitProc == safeInitProc) && (strcmp(libraryPtr->prefix, prefix) == 0)) { break; } } Tcl_MutexUnlock(&libraryMutex); /* * If the library is not yet recorded as being loaded statically, add it * to the list now. */ if (libraryPtr == NULL) { libraryPtr = (LoadedLibrary *)Tcl_Alloc(sizeof(LoadedLibrary)); libraryPtr->fileName = (char *)Tcl_Alloc(1); libraryPtr->fileName[0] = 0; libraryPtr->prefix = (char *)Tcl_Alloc(strlen(prefix) + 1); strcpy(libraryPtr->prefix, prefix); libraryPtr->loadHandle = NULL; libraryPtr->initProc = initProc; libraryPtr->safeInitProc = safeInitProc; libraryPtr->unloadProc = NULL; libraryPtr->safeUnloadProc = NULL; Tcl_MutexLock(&libraryMutex); libraryPtr->nextPtr = firstLibraryPtr; firstLibraryPtr = libraryPtr; Tcl_MutexUnlock(&libraryMutex); } if (interp != NULL) { /* * If we're loading the library into an interpreter, determine whether * it's already loaded. */ ipFirstPtr = (InterpLibrary *)Tcl_GetAssocData(interp, "tclLoad", NULL); for (ipPtr = ipFirstPtr; ipPtr != NULL; ipPtr = ipPtr->nextPtr) { if (ipPtr->libraryPtr == libraryPtr) { return; } } /* * Library isn't loaded in the current interp yet. Mark it as now being * loaded. */ ipPtr = (InterpLibrary *)Tcl_Alloc(sizeof(InterpLibrary)); ipPtr->libraryPtr = libraryPtr; ipPtr->nextPtr = ipFirstPtr; Tcl_SetAssocData(interp, "tclLoad", LoadCleanupProc, ipPtr); } } /* *---------------------------------------------------------------------- * * TclGetLoadedLibraries -- * * This function returns information about all of the files that are * loaded (either in a particular interpreter, or for all interpreters). * * Results: * The return value is a standard Tcl completion code. If successful, a * list of lists is placed in the interp's result. Each sublist * corresponds to one loaded file; its first element is the name of the * file (or an empty string for something that's statically loaded) and * the second element is the prefix of the library in that file. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclGetLoadedLibraries( Tcl_Interp *interp, /* Interpreter in which to return information * or error message. */ const char *targetName, /* Name of target interpreter or NULL. If * NULL, return info about all interps; * otherwise, just return info about this * interpreter. */ const char *prefix) /* Prefix or NULL. If NULL, return info * for all prefixes. */ { Tcl_Interp *target; LoadedLibrary *libraryPtr; InterpLibrary *ipPtr; Tcl_Obj *resultObj, *pkgDesc[2]; if (targetName == NULL) { TclNewObj(resultObj); Tcl_MutexLock(&libraryMutex); for (libraryPtr = firstLibraryPtr; libraryPtr != NULL; libraryPtr = libraryPtr->nextPtr) { pkgDesc[0] = Tcl_NewStringObj(libraryPtr->fileName, -1); pkgDesc[1] = Tcl_NewStringObj(libraryPtr->prefix, -1); Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewListObj(2, pkgDesc)); } Tcl_MutexUnlock(&libraryMutex); Tcl_SetObjResult(interp, resultObj); return TCL_OK; } target = Tcl_GetChild(interp, targetName); if (target == NULL) { return TCL_ERROR; } ipPtr = (InterpLibrary *)Tcl_GetAssocData(target, "tclLoad", NULL); /* * Return information about all of the available libraries. */ if (prefix) { resultObj = NULL; for (; ipPtr != NULL; ipPtr = ipPtr->nextPtr) { libraryPtr = ipPtr->libraryPtr; if (!strcmp(prefix, libraryPtr->prefix)) { resultObj = Tcl_NewStringObj(libraryPtr->fileName, -1); break; } } if (resultObj) { Tcl_SetObjResult(interp, resultObj); } return TCL_OK; } /* * Return information about only the libraries that are loaded in a given * interpreter. */ TclNewObj(resultObj); for (; ipPtr != NULL; ipPtr = ipPtr->nextPtr) { libraryPtr = ipPtr->libraryPtr; pkgDesc[0] = Tcl_NewStringObj(libraryPtr->fileName, -1); pkgDesc[1] = Tcl_NewStringObj(libraryPtr->prefix, -1); Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewListObj(2, pkgDesc)); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* *---------------------------------------------------------------------- * * LoadCleanupProc -- * * This function is called to delete all of the InterpLibrary structures * for an interpreter when the interpreter is deleted. It gets invoked * via the Tcl AssocData mechanism. * * Results: * None. * * Side effects: * Storage for all of the InterpLibrary functions for interp get deleted. * *---------------------------------------------------------------------- */ static void LoadCleanupProc( void *clientData, /* Pointer to first InterpLibrary structure * for interp. */ Tcl_Interp *interp) { InterpLibrary *ipPtr = (InterpLibrary *)clientData, *nextPtr; LoadedLibrary *libraryPtr; while (ipPtr) { libraryPtr = ipPtr->libraryPtr; UnloadLibrary(interp, interp, libraryPtr, 0, "", 1); /* UnloadLibrary doesn't free it by interp delete, so do it here and * repeat for next. */ nextPtr = ipPtr->nextPtr; Tcl_Free(ipPtr); ipPtr = nextPtr; } } /* *---------------------------------------------------------------------- * * TclFinalizeLoad -- * * This function is invoked just before the application exits. It frees * all of the LoadedLibrary structures. * * Results: * None. * * Side effects: * Memory is freed. * *---------------------------------------------------------------------- */ void TclFinalizeLoad(void) { LoadedLibrary *libraryPtr; /* * No synchronization here because there should just be one thread alive * at this point. Logically, libraryMutex should be grabbed at this point, * but the Mutexes get finalized before the call to this routine. The only * subsystem left alive at this point is the memory allocator. */ while (firstLibraryPtr != NULL) { libraryPtr = firstLibraryPtr; firstLibraryPtr = libraryPtr->nextPtr; #if defined(TCL_UNLOAD_DLLS) || defined(_WIN32) /* * Some Unix dlls are poorly behaved - registering things like atexit * calls that can't be unregistered. If you unload such dlls, you get * a core on exit because it wants to call a function in the dll after * it has been unloaded. */ if (!IsStatic(libraryPtr)) { Tcl_FSUnloadFile(NULL, libraryPtr->loadHandle); } #endif Tcl_Free(libraryPtr->fileName); Tcl_Free(libraryPtr->prefix); Tcl_Free(libraryPtr); } } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclLoadNone.c0000644000175000017500000000426514726623136015547 0ustar sergeisergei/* * tclLoadNone.c -- * * This procedure provides a version of the TclpDlopen for use in * systems that don't support dynamic loading; it just returns an error. * * Copyright © 1995-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* *---------------------------------------------------------------------- * * TclpDlopen -- * * This procedure is called to carry out dynamic loading of binary code; * it is intended for use only on systems that don't support dynamic * loading (it returns an error). * * Results: * The result is TCL_ERROR, and an error message is left in the interp's * result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclpDlopen( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Obj *pathPtr, /* Name of the file containing the desired * code (UTF-8). */ Tcl_LoadHandle *loadHandle, /* Filled with token for dynamically loaded * file which will be passed back to * (*unloadProcPtr)() to unload the file. */ Tcl_FSUnloadFileProc **unloadProcPtr, /* Filled with address of Tcl_FSUnloadFileProc * function which should be used for this * file. */ int flags) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "dynamic loading is not currently available on this system", -1)); } return TCL_ERROR; } /* * These functions are fallbacks if we somehow determine that the platform can * do loading from memory but the user wishes to disable it. They just report * (gracefully) that they fail. */ #ifdef TCL_LOAD_FROM_MEMORY MODULE_SCOPE void * TclpLoadMemoryGetBuffer( TCL_UNUSED(size_t)) { return NULL; } MODULE_SCOPE int TclpLoadMemory( TCL_UNUSED(void *), TCL_UNUSED(size_t), TCL_UNUSED(Tcl_Size), TCL_UNUSED(const char *), TCL_UNUSED(Tcl_LoadHandle *), TCL_UNUSED(Tcl_FSUnloadFileProc **), TCL_UNUSED(int)) { return TCL_ERROR; } #endif /* TCL_LOAD_FROM_MEMORY */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclMain.c0000644000175000017500000006111714726623136014733 0ustar sergeisergei/* * tclMain.c -- * * Main program for Tcl shells and other Tcl-based applications. * This file contains a generic main program for Tcl shells and other * Tcl-based applications. It can be used as-is for many applications, * just by supplying a different appInitProc function for each specific * application. Or, it can be used as a template for creating new main * programs for Tcl applications. * * Copyright © 1988-1994 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * Copyright © 2000 Ajuba Solutions. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ /* * On Windows, this file needs to be compiled twice, once with UNICODE and * _UNICODE defined. This way both Tcl_Main and Tcl_MainExW can be * implemented, sharing the same source code. */ #include "tclInt.h" /* * The default prompt used when the user has not overridden it. */ static const char DEFAULT_PRIMARY_PROMPT[] = "% "; static const char ENCODING_ERROR[] = "\n\t(encoding error in stderr)"; /* * This file can be compiled on Windows in UNICODE mode, as well as on all * other platforms using the native encoding. This is done by using the normal * Windows functions like _tcscmp, but on platforms which don't have * we have to translate that to strcmp here. */ #ifndef _WIN32 # define TCHAR char # define TEXT(arg) arg # define _tcscmp strcmp #endif static inline Tcl_Obj * NewNativeObj( TCHAR *string) { Tcl_DString ds; #ifdef UNICODE Tcl_DStringInit(&ds); Tcl_WCharToUtfDString(string, -1, &ds); #else (void)Tcl_ExternalToUtfDString(NULL, (char *)string, -1, &ds); #endif return Tcl_DStringToObj(&ds); } /* * Declarations for various library functions and variables (don't want to * include tclPort.h here, because people might copy this file out of the Tcl * source directory to make their own modified versions). */ /* * The thread-local variables for this file's functions. */ typedef struct { Tcl_Obj *path; /* The filename of the script for *_Main() * routines to [source] as a startup script, * or NULL for none set, meaning enter * interactive mode. */ Tcl_Obj *encoding; /* The encoding of the startup script file. */ Tcl_MainLoopProc *mainLoopProc; /* Any installed main loop handler. The main * extension that installs these is Tk. */ } ThreadSpecificData; /* * Structure definition for information used to keep the state of an * interactive command processor that reads lines from standard input and * writes prompts and results to standard output. */ typedef enum { PROMPT_NONE, /* Print no prompt */ PROMPT_START, /* Print prompt for command start */ PROMPT_CONTINUE /* Print prompt for command continuation */ } PromptType; typedef struct { Tcl_Channel input; /* The standard input channel from which lines * are read. */ int tty; /* Non-zero means standard input is a * terminal-like device. Zero means it's a * file. */ Tcl_Obj *commandPtr; /* Used to assemble lines of input into Tcl * commands. */ PromptType prompt; /* Next prompt to print */ Tcl_Interp *interp; /* Interpreter that evaluates interactive * commands. */ } InteractiveState; /* * Forward declarations for functions defined later in this file. */ MODULE_SCOPE Tcl_MainLoopProc *TclGetMainLoop(void); static void Prompt(Tcl_Interp *interp, InteractiveState *isPtr); static void StdinProc(void *clientData, int mask); static void FreeMainInterp(void *clientData); #if !defined(_WIN32) || defined(UNICODE) && !defined(TCL_ASCII_MAIN) static Tcl_ThreadDataKey dataKey; /* *---------------------------------------------------------------------- * * Tcl_SetStartupScript -- * * Sets the path and encoding of the startup script to be evaluated by * Tcl_Main, used to override the command line processing. * * Results: * None. * * Side effects: * *---------------------------------------------------------------------- */ void Tcl_SetStartupScript( Tcl_Obj *path, /* Filesystem path of startup script file */ const char *encodingName) /* Encoding of the data in that file */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Tcl_Obj *encodingObj = NULL; if (encodingName != NULL) { encodingObj = Tcl_NewStringObj(encodingName, -1); Tcl_IncrRefCount(encodingObj); } if (path != NULL) { Tcl_IncrRefCount(path); } if (tsdPtr->path != NULL) { Tcl_DecrRefCount(tsdPtr->path); } tsdPtr->path = path; if (tsdPtr->encoding != NULL) { Tcl_DecrRefCount(tsdPtr->encoding); } tsdPtr->encoding = encodingObj; } /* *---------------------------------------------------------------------- * * Tcl_GetStartupScript -- * * Gets the path and encoding of the startup script to be evaluated by * Tcl_Main. * * Results: * The path of the startup script; NULL if none has been set. * * Side effects: * If encodingPtr is not NULL, stores a (const char *) in it pointing to * the encoding name registered for the startup script. Tcl retains * ownership of the string, and may free it. Caller should make a copy * for long-term use. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_GetStartupScript( const char **encodingPtr) /* When not NULL, points to storage for the * (const char *) that points to the * registered encoding name for the startup * script. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (encodingPtr != NULL) { if (tsdPtr->encoding != NULL) { *encodingPtr = Tcl_GetString(tsdPtr->encoding); } else { *encodingPtr = NULL; } } return tsdPtr->path; } /*---------------------------------------------------------------------- * * Tcl_SourceRCFile -- * * This function is typically invoked by Tcl_Main of Tk_Main function to * source an application specific rc file into the interpreter at startup * time. If the filename cannot be translated (e.g. it referred to a bogus * user or there was no HOME environment variable). Just do nothing. * * Results: * None. * * Side effects: * Depends on what's in the rc script. * *---------------------------------------------------------------------- */ void Tcl_SourceRCFile( Tcl_Interp *interp) /* Interpreter to source rc file into. */ { Tcl_DString temp; const char *fileName; Tcl_Channel chan; fileName = Tcl_GetVar2(interp, "tcl_rcFileName", NULL, TCL_GLOBAL_ONLY); if (fileName != NULL) { Tcl_Channel c; const char *fullName; Tcl_DStringInit(&temp); fullName = Tcl_TranslateFileName(interp, fileName, &temp); if (fullName != NULL) { /* * Test for the existence of the rc file before trying to read it. */ c = Tcl_OpenFileChannel(NULL, fullName, "r", 0); if (c != NULL) { Tcl_CloseEx(NULL, c, 0); if (Tcl_EvalFile(interp, fullName) != TCL_OK) { chan = Tcl_GetStdChannel(TCL_STDERR); if (chan) { if (Tcl_WriteObj(chan, Tcl_GetObjResult(interp)) < 0) { Tcl_WriteChars(chan, ENCODING_ERROR, -1); } Tcl_WriteChars(chan, "\n", 1); } } } } Tcl_DStringFree(&temp); } } #endif /* !UNICODE */ /*---------------------------------------------------------------------- * * Tcl_MainEx -- * * Main program for tclsh and most other Tcl-based applications. * * Results: * None. This function never returns (it exits the process when it's * done). * * Side effects: * This function initializes the Tcl world and then starts interpreting * commands; almost anything could happen, depending on the script being * interpreted. * *---------------------------------------------------------------------- */ TCL_NORETURN void Tcl_MainEx( Tcl_Size argc, /* Number of arguments. */ TCHAR **argv, /* Array of argument strings. */ Tcl_AppInitProc *appInitProc, /* Application-specific initialization * function to call after most initialization * but before starting to execute commands. */ Tcl_Interp *interp) { Tcl_Size i=0; /* argv[i] index */ Tcl_Obj *path, *resultPtr, *argvPtr, *appName; const char *encodingName = NULL; int code, exitCode = 0; Tcl_MainLoopProc *mainLoopProc; Tcl_Channel chan; InteractiveState is; TclpSetInitialEncodings(); if (argc > 0) { --argc; /* consume argv[0] */ ++i; } TclpFindExecutable((const char *)argv[0]); /* nb: this could be NULL * w/ (eg) an empty argv supplied to execve() */ Tcl_InitMemory(interp); is.interp = interp; is.prompt = PROMPT_START; TclNewObj(is.commandPtr); /* * If the application has not already set a startup script, parse the * first few command line arguments to determine the script path and * encoding. */ if (NULL == Tcl_GetStartupScript(NULL)) { /* * Check whether first 3 args (argv[1] - argv[3]) look like * -encoding ENCODING FILENAME * or like * FILENAME */ /* mind argc is being adjusted as we proceed */ if ((argc >= 3) && (0 == _tcscmp(TEXT("-encoding"), argv[1])) && ('-' != argv[3][0])) { Tcl_Obj *value = NewNativeObj(argv[2]); Tcl_SetStartupScript(NewNativeObj(argv[3]), Tcl_GetString(value)); Tcl_DecrRefCount(value); argc -= 3; i += 3; } else if ((argc >= 1) && ('-' != argv[1][0])) { Tcl_SetStartupScript(NewNativeObj(argv[1]), NULL); argc--; i++; } } path = Tcl_GetStartupScript(&encodingName); if (path != NULL) { appName = path; } else if (argv[0]) { appName = NewNativeObj(argv[0]); } else { appName = Tcl_NewStringObj("tclsh", -1); } Tcl_SetVar2Ex(interp, "argv0", NULL, appName, TCL_GLOBAL_ONLY); Tcl_SetVar2Ex(interp, "argc", NULL, Tcl_NewWideIntObj(argc), TCL_GLOBAL_ONLY); argvPtr = Tcl_NewListObj(0, NULL); while (argc--) { Tcl_ListObjAppendElement(NULL, argvPtr, NewNativeObj(argv[i++])); } Tcl_SetVar2Ex(interp, "argv", NULL, argvPtr, TCL_GLOBAL_ONLY); /* * Set the "tcl_interactive" variable. */ is.tty = isatty(0); Tcl_SetVar2Ex(interp, "tcl_interactive", NULL, Tcl_NewBooleanObj(!path && is.tty), TCL_GLOBAL_ONLY); /* * Invoke application-specific initialization. */ Tcl_Preserve(interp); if (appInitProc(interp) != TCL_OK) { chan = Tcl_GetStdChannel(TCL_STDERR); if (chan) { Tcl_WriteChars(chan, "application-specific initialization failed: ", -1); if (Tcl_WriteObj(chan, Tcl_GetObjResult(interp)) < 0) { Tcl_WriteChars(chan, ENCODING_ERROR, -1); } Tcl_WriteChars(chan, "\n", 1); } } if (Tcl_InterpDeleted(interp)) { goto done; } if (Tcl_LimitExceeded(interp)) { goto done; } if (TclFullFinalizationRequested()) { /* * Arrange for final deletion of the main interp */ /* ARGH Munchhausen effect */ Tcl_CreateExitHandler(FreeMainInterp, interp); } /* * Invoke the script specified on the command line, if any. Must fetch it * again, as the appInitProc might have reset it. */ path = Tcl_GetStartupScript(&encodingName); if (path != NULL) { Tcl_ResetResult(interp); code = Tcl_FSEvalFileEx(interp, path, encodingName); if (code != TCL_OK) { chan = Tcl_GetStdChannel(TCL_STDERR); if (chan) { Tcl_Obj *options = Tcl_GetReturnOptions(interp, code); Tcl_Obj *valuePtr = NULL; TclDictGet(NULL, options, "-errorinfo", &valuePtr); if (valuePtr) { if (Tcl_WriteObj(chan, valuePtr) < 0) { Tcl_WriteChars(chan, ENCODING_ERROR, -1); } } Tcl_WriteChars(chan, "\n", 1); Tcl_DecrRefCount(options); } exitCode = 1; } goto done; } /* * We're running interactively. Source a user-specific startup file if the * application specified one and if the file exists. */ Tcl_SourceRCFile(interp); if (Tcl_LimitExceeded(interp)) { goto done; } /* * Process commands from stdin until there's an end-of-file. Note that we * need to fetch the standard channels again after every eval, since they * may have been changed. */ Tcl_IncrRefCount(is.commandPtr); /* * Get a new value for tty if anyone writes to ::tcl_interactive */ Tcl_LinkVar(interp, "tcl_interactive", &is.tty, TCL_LINK_BOOLEAN); is.input = Tcl_GetStdChannel(TCL_STDIN); while ((is.input != NULL) && !Tcl_InterpDeleted(interp)) { mainLoopProc = TclGetMainLoop(); if (mainLoopProc == NULL) { Tcl_Size length; if (is.tty) { Prompt(interp, &is); if (Tcl_InterpDeleted(interp)) { break; } if (Tcl_LimitExceeded(interp)) { break; } is.input = Tcl_GetStdChannel(TCL_STDIN); if (is.input == NULL) { break; } } if (Tcl_IsShared(is.commandPtr)) { Tcl_DecrRefCount(is.commandPtr); is.commandPtr = Tcl_DuplicateObj(is.commandPtr); Tcl_IncrRefCount(is.commandPtr); } length = Tcl_GetsObj(is.input, is.commandPtr); if (length < 0) { if (Tcl_InputBlocked(is.input)) { /* * This can only happen if stdin has been set to * non-blocking. In that case cycle back and try again. * This sets up a tight polling loop (since we have no * event loop running). If this causes bad CPU hogging, we * might try toggling the blocking on stdin instead. */ continue; } /* * Either EOF, or an error on stdin; we're done */ break; } /* * Add the newline removed by Tcl_GetsObj back to the string. Have * to add it back before testing completeness, because it can make * a difference. [Bug 1775878] */ if (Tcl_IsShared(is.commandPtr)) { Tcl_DecrRefCount(is.commandPtr); is.commandPtr = Tcl_DuplicateObj(is.commandPtr); Tcl_IncrRefCount(is.commandPtr); } Tcl_AppendToObj(is.commandPtr, "\n", 1); if (!TclObjCommandComplete(is.commandPtr)) { is.prompt = PROMPT_CONTINUE; continue; } is.prompt = PROMPT_START; /* * The final newline is syntactically redundant, and causes some * error messages troubles deeper in, so lop it back off. */ (void)Tcl_GetStringFromObj(is.commandPtr, &length); Tcl_SetObjLength(is.commandPtr, --length); code = Tcl_RecordAndEvalObj(interp, is.commandPtr, TCL_EVAL_GLOBAL); is.input = Tcl_GetStdChannel(TCL_STDIN); Tcl_DecrRefCount(is.commandPtr); TclNewObj(is.commandPtr); Tcl_IncrRefCount(is.commandPtr); if (code != TCL_OK) { chan = Tcl_GetStdChannel(TCL_STDERR); if (chan) { if (Tcl_WriteObj(chan, Tcl_GetObjResult(interp)) < 0) { Tcl_WriteChars(chan, ENCODING_ERROR, -1); } Tcl_WriteChars(chan, "\n", 1); } } else if (is.tty) { resultPtr = Tcl_GetObjResult(interp); Tcl_IncrRefCount(resultPtr); (void)Tcl_GetStringFromObj(resultPtr, &length); chan = Tcl_GetStdChannel(TCL_STDOUT); if ((length > 0) && chan) { if (Tcl_WriteObj(chan, resultPtr) < 0) { Tcl_WriteChars(chan, ENCODING_ERROR, -1); } Tcl_WriteChars(chan, "\n", 1); } Tcl_DecrRefCount(resultPtr); } } else { /* (mainLoopProc != NULL) */ /* * If a main loop has been defined while running interactively, we * want to start a fileevent based prompt by establishing a * channel handler for stdin. */ if (is.input) { if (is.tty) { Prompt(interp, &is); } Tcl_CreateChannelHandler(is.input, TCL_READABLE, StdinProc, &is); } mainLoopProc(); Tcl_SetMainLoop(NULL); if (is.input) { Tcl_DeleteChannelHandler(is.input, StdinProc, &is); } is.input = Tcl_GetStdChannel(TCL_STDIN); } /* * This code here only for the (unsupported and deprecated) [checkmem] * command. */ #ifdef TCL_MEM_DEBUG if (tclMemDumpFileName != NULL) { Tcl_SetMainLoop(NULL); Tcl_DeleteInterp(interp); } #endif /* TCL_MEM_DEBUG */ } done: mainLoopProc = TclGetMainLoop(); if ((exitCode == 0) && mainLoopProc && !Tcl_LimitExceeded(interp)) { /* * If everything has gone OK so far, call the main loop proc, if it * exists. Packages (like Tk) can set it to start processing events at * this point. */ mainLoopProc(); Tcl_SetMainLoop(NULL); } if (is.commandPtr != NULL) { Tcl_DecrRefCount(is.commandPtr); } /* * Rather than calling exit, invoke the "exit" command so that users can * replace "exit" with some other command to do additional cleanup on * exit. The Tcl_EvalObjEx call should never return. */ if (!Tcl_InterpDeleted(interp) && !Tcl_LimitExceeded(interp)) { Tcl_Obj *cmd = Tcl_ObjPrintf("exit %d", exitCode); Tcl_IncrRefCount(cmd); Tcl_EvalObjEx(interp, cmd, TCL_EVAL_GLOBAL); Tcl_DecrRefCount(cmd); } /* * If Tcl_EvalObjEx returns, trying to eval [exit], something unusual is * happening. Maybe interp has been deleted; maybe [exit] was redefined, * maybe we've blown up because of an exceeded limit. We still want to * cleanup and exit. */ Tcl_Exit(exitCode); } #if !defined(_WIN32) || defined(UNICODE) /* *--------------------------------------------------------------- * * Tcl_SetMainLoop -- * * Sets an alternative main loop function. * * Results: * None. * * Side effects: * This function will be called before Tcl exits, allowing for the * creation of an event loop. * *--------------------------------------------------------------- */ void Tcl_SetMainLoop( Tcl_MainLoopProc *proc) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); tsdPtr->mainLoopProc = proc; } /* *--------------------------------------------------------------- * * TclGetMainLoop -- * * Returns the current alternative main loop function. * * Results: * Returns the previously defined main loop function, or NULL to indicate * that no such function has been installed and standard tclsh behaviour * (i.e., exit once the script is evaluated if not interactive) is * requested.. * * Side effects: * None (other than possible creation of this file's TSD block). * *--------------------------------------------------------------- */ Tcl_MainLoopProc * TclGetMainLoop(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); return tsdPtr->mainLoopProc; } /* *---------------------------------------------------------------------- * * TclFullFinalizationRequested -- * * This function returns true when either -DPURIFY is specified, or the * environment variable TCL_FINALIZE_ON_EXIT is set and not "0". This * predicate is called at places affecting the exit sequence, so that the * default behavior is a fast and deadlock-free exit, and the modified * behavior is a more thorough finalization for debugging purposes (leak * hunting etc). * * Results: * A boolean. * *---------------------------------------------------------------------- */ MODULE_SCOPE int TclFullFinalizationRequested(void) { #ifdef PURIFY return 1; #else const char *fin; Tcl_DString ds; int finalize = 0; fin = TclGetEnv("TCL_FINALIZE_ON_EXIT", &ds); finalize = ((fin != NULL) && strcmp(fin, "0")); if (fin != NULL) { Tcl_DStringFree(&ds); } return finalize; #endif /* PURIFY */ } #endif /* UNICODE */ /* *---------------------------------------------------------------------- * * StdinProc -- * * This function is invoked by the event dispatcher whenever standard * input becomes readable. It grabs the next line of input characters, * adds them to a command being assembled, and executes the command if * it's complete. * * Results: * None. * * Side effects: * Could be almost arbitrary, depending on the command that's typed. * *---------------------------------------------------------------------- */ static void StdinProc( void *clientData, /* The state of interactive cmd line */ TCL_UNUSED(int) /*mask*/) { int code; Tcl_Size length; InteractiveState *isPtr = (InteractiveState *)clientData; Tcl_Channel chan = isPtr->input; Tcl_Obj *commandPtr = isPtr->commandPtr; Tcl_Interp *interp = isPtr->interp; if (Tcl_IsShared(commandPtr)) { Tcl_DecrRefCount(commandPtr); commandPtr = Tcl_DuplicateObj(commandPtr); Tcl_IncrRefCount(commandPtr); } length = Tcl_GetsObj(chan, commandPtr); if (length < 0) { if (Tcl_InputBlocked(chan)) { return; } if (isPtr->tty) { /* * Would be better to find a way to exit the mainLoop? Or perhaps * evaluate [exit]? Leaving as is for now due to compatibility * concerns. */ Tcl_Exit(0); } Tcl_DeleteChannelHandler(chan, StdinProc, isPtr); return; } if (Tcl_IsShared(commandPtr)) { Tcl_DecrRefCount(commandPtr); commandPtr = Tcl_DuplicateObj(commandPtr); Tcl_IncrRefCount(commandPtr); } Tcl_AppendToObj(commandPtr, "\n", 1); if (!TclObjCommandComplete(commandPtr)) { isPtr->prompt = PROMPT_CONTINUE; goto prompt; } isPtr->prompt = PROMPT_START; (void)Tcl_GetStringFromObj(commandPtr, &length); Tcl_SetObjLength(commandPtr, --length); /* * Disable the stdin channel handler while evaluating the command; * otherwise if the command re-enters the event loop we might process * commands from stdin before the current command is finished. Among other * things, this will trash the text of the command being evaluated. */ Tcl_CreateChannelHandler(chan, 0, StdinProc, isPtr); code = Tcl_RecordAndEvalObj(interp, commandPtr, TCL_EVAL_GLOBAL); isPtr->input = chan = Tcl_GetStdChannel(TCL_STDIN); Tcl_DecrRefCount(commandPtr); TclNewObj(commandPtr); isPtr->commandPtr = commandPtr; Tcl_IncrRefCount(commandPtr); if (chan != NULL) { Tcl_CreateChannelHandler(chan, TCL_READABLE, StdinProc, isPtr); } if (code != TCL_OK) { chan = Tcl_GetStdChannel(TCL_STDERR); if (chan != NULL) { if (Tcl_WriteObj(chan, Tcl_GetObjResult(interp)) < 0) { Tcl_WriteChars(chan, ENCODING_ERROR, -1); } Tcl_WriteChars(chan, "\n", 1); } } else if (isPtr->tty) { Tcl_Obj *resultPtr = Tcl_GetObjResult(interp); chan = Tcl_GetStdChannel(TCL_STDOUT); Tcl_IncrRefCount(resultPtr); (void)Tcl_GetStringFromObj(resultPtr, &length); if ((length > 0) && (chan != NULL)) { if (Tcl_WriteObj(chan, resultPtr) < 0) { Tcl_WriteChars(chan, ENCODING_ERROR, -1); } Tcl_WriteChars(chan, "\n", 1); } Tcl_DecrRefCount(resultPtr); } /* * If a tty stdin is still around, output a prompt. */ prompt: if (isPtr->tty && (isPtr->input != NULL)) { Prompt(interp, isPtr); isPtr->input = Tcl_GetStdChannel(TCL_STDIN); } } /* *---------------------------------------------------------------------- * * Prompt -- * * Issue a prompt on standard output, or invoke a script to issue the * prompt. * * Results: * None. * * Side effects: * A prompt gets output, and a Tcl script may be evaluated in interp. * *---------------------------------------------------------------------- */ static void Prompt( Tcl_Interp *interp, /* Interpreter to use for prompting. */ InteractiveState *isPtr) /* InteractiveState. Filled with PROMPT_NONE * after a prompt is printed. */ { Tcl_Obj *promptCmdPtr; int code; Tcl_Channel chan; if (isPtr->prompt == PROMPT_NONE) { return; } promptCmdPtr = Tcl_GetVar2Ex(interp, (isPtr->prompt==PROMPT_CONTINUE ? "tcl_prompt2" : "tcl_prompt1"), NULL, TCL_GLOBAL_ONLY); if (Tcl_InterpDeleted(interp)) { return; } if (promptCmdPtr == NULL) { defaultPrompt: if (isPtr->prompt == PROMPT_START) { chan = Tcl_GetStdChannel(TCL_STDOUT); if (chan != NULL) { Tcl_WriteChars(chan, DEFAULT_PRIMARY_PROMPT, sizeof(DEFAULT_PRIMARY_PROMPT) - 1); } } } else { code = Tcl_EvalObjEx(interp, promptCmdPtr, TCL_EVAL_GLOBAL); if (code != TCL_OK) { Tcl_AddErrorInfo(interp, "\n (script that generates prompt)"); chan = Tcl_GetStdChannel(TCL_STDERR); if (chan != NULL) { if (Tcl_WriteObj(chan, Tcl_GetObjResult(interp)) < 0) { Tcl_WriteChars(chan, ENCODING_ERROR, -1); } Tcl_WriteChars(chan, "\n", 1); } goto defaultPrompt; } } chan = Tcl_GetStdChannel(TCL_STDOUT); if (chan != NULL) { Tcl_Flush(chan); } isPtr->prompt = PROMPT_NONE; } /* *---------------------------------------------------------------------- * * FreeMainInterp -- * * Exit handler used to cleanup the main interpreter and ancillary * startup script storage at exit. * *---------------------------------------------------------------------- */ static void FreeMainInterp( void *clientData) { Tcl_Interp *interp = (Tcl_Interp *)clientData; /*if (TclInExit()) return;*/ if (!Tcl_InterpDeleted(interp)) { Tcl_DeleteInterp(interp); } Tcl_SetStartupScript(NULL, NULL); Tcl_Release(interp); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclNamesp.c0000644000175000017500000046350614731032403015265 0ustar sergeisergei/* * tclNamesp.c -- * * Contains support for namespaces, which provide a separate context of * commands and global variables. The global :: namespace is the * traditional Tcl "global" scope. Other namespaces are created as * children of the global namespace. These other namespaces contain * special-purpose commands and variables for packages. * * Copyright © 1993-1997 Lucent Technologies. * Copyright © 1997 Sun Microsystems, Inc. * Copyright © 1998-1999 Scriptics Corporation. * Copyright © 2002-2005 Donal K. Fellows. * Copyright © 2006 Neil Madden. * Contributions from Don Porter, NIST, 2007. (not subject to US copyright) * * Originally implemented by * Michael J. McLennan * Bell Labs Innovations for Lucent Technologies * mmclennan@lucent.com * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" /* for TclLogCommandInfo visibility */ #include /* * Thread-local storage used to avoid having a global lock on data that is not * limited to a single interpreter. */ typedef struct { size_t numNsCreated; /* Count of the number of namespaces created * within the thread. This value is used as a * unique id for each namespace. Cannot be * per-interp because the nsId is used to * distinguish objects which can be passed * around between interps in the same thread, * but does not need to be global because * object internal reps are always per-thread * anyway. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * This structure contains a cached pointer to a namespace that is the result * of resolving the namespace's name in some other namespace. It is the * internal representation for a nsName object. It contains the pointer along * with some information that is used to check the cached pointer's validity. */ typedef struct { Namespace *nsPtr; /* A cached pointer to the Namespace that the * name resolved to. */ Namespace *refNsPtr; /* Points to the namespace context in which * the name was resolved. NULL if the name is * fully qualified and thus the resolution * does not depend on the context. */ size_t refCount; /* Reference count: 1 for each nsName object * that has a pointer to this ResolvedNsName * structure as its internal rep. This * structure can be freed when refCount * becomes zero. */ } ResolvedNsName; /* * Declarations for functions local to this file: */ static void DeleteImportedCmd(void *clientData); static int DoImport(Tcl_Interp *interp, Namespace *nsPtr, Tcl_HashEntry *hPtr, const char *cmdName, const char *pattern, Namespace *importNsPtr, int allowOverwrite); static void DupNsNameInternalRep(Tcl_Obj *objPtr, Tcl_Obj *copyPtr); static char * ErrorCodeRead(void *clientData, Tcl_Interp *interp, const char *name1, const char *name2, int flags); static char * ErrorInfoRead(void *clientData, Tcl_Interp *interp, const char *name1, const char *name2, int flags); static char * EstablishErrorCodeTraces(void *clientData, Tcl_Interp *interp, const char *name1, const char *name2, int flags); static char * EstablishErrorInfoTraces(void *clientData, Tcl_Interp *interp, const char *name1, const char *name2, int flags); static void FreeNsNameInternalRep(Tcl_Obj *objPtr); static int GetNamespaceFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Namespace **nsPtrPtr); static int InvokeImportedNRCmd(void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static Tcl_ObjCmdProc NamespaceChildrenCmd; static Tcl_ObjCmdProc NamespaceCodeCmd; static Tcl_ObjCmdProc NamespaceCurrentCmd; static Tcl_ObjCmdProc NamespaceDeleteCmd; static Tcl_ObjCmdProc NamespaceEvalCmd; static Tcl_ObjCmdProc NRNamespaceEvalCmd; static Tcl_ObjCmdProc NamespaceExistsCmd; static Tcl_ObjCmdProc NamespaceExportCmd; static Tcl_ObjCmdProc NamespaceForgetCmd; static void NamespaceFree(Namespace *nsPtr); static Tcl_ObjCmdProc NamespaceImportCmd; static Tcl_ObjCmdProc NamespaceInscopeCmd; static Tcl_ObjCmdProc NRNamespaceInscopeCmd; static Tcl_ObjCmdProc NamespaceOriginCmd; static Tcl_ObjCmdProc NamespaceParentCmd; static Tcl_ObjCmdProc NamespacePathCmd; static Tcl_ObjCmdProc NamespaceQualifiersCmd; static Tcl_ObjCmdProc NamespaceTailCmd; static Tcl_ObjCmdProc NamespaceUpvarCmd; static Tcl_ObjCmdProc NamespaceUnknownCmd; static Tcl_ObjCmdProc NamespaceWhichCmd; static int SetNsNameFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); static void UnlinkNsPath(Namespace *nsPtr); static Tcl_NRPostProc NsEval_Callback; /* * This structure defines a Tcl object type that contains a namespace * reference. It is used in commands that take the name of a namespace as an * argument. The namespace reference is resolved, and the result in cached in * the object. */ static const Tcl_ObjType nsNameType = { "nsName", /* the type's name */ FreeNsNameInternalRep, /* freeIntRepProc */ DupNsNameInternalRep, /* dupIntRepProc */ NULL, /* updateStringProc */ SetNsNameFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; #define NsNameSetInternalRep(objPtr, nnPtr) \ do { \ Tcl_ObjInternalRep ir; \ (nnPtr)->refCount++; \ ir.twoPtrValue.ptr1 = (nnPtr); \ ir.twoPtrValue.ptr2 = NULL; \ Tcl_StoreInternalRep((objPtr), &nsNameType, &ir); \ } while (0) #define NsNameGetInternalRep(objPtr, nnPtr) \ do { \ const Tcl_ObjInternalRep *irPtr; \ irPtr = TclFetchInternalRep((objPtr), &nsNameType); \ (nnPtr) = irPtr ? (ResolvedNsName *) irPtr->twoPtrValue.ptr1 : NULL; \ } while (0) /* * Array of values describing how to implement each standard subcommand of the * "namespace" command. */ static const EnsembleImplMap defaultNamespaceMap[] = { {"children", NamespaceChildrenCmd, TclCompileBasic0To2ArgCmd, NULL, NULL, 0}, {"code", NamespaceCodeCmd, TclCompileNamespaceCodeCmd, NULL, NULL, 0}, {"current", NamespaceCurrentCmd, TclCompileNamespaceCurrentCmd, NULL, NULL, 0}, {"delete", NamespaceDeleteCmd, TclCompileBasicMin0ArgCmd, NULL, NULL, 0}, {"ensemble", TclNamespaceEnsembleCmd, NULL, NULL, NULL, 0}, {"eval", NamespaceEvalCmd, NULL, NRNamespaceEvalCmd, NULL, 0}, {"exists", NamespaceExistsCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"export", NamespaceExportCmd, TclCompileBasicMin0ArgCmd, NULL, NULL, 0}, {"forget", NamespaceForgetCmd, TclCompileBasicMin0ArgCmd, NULL, NULL, 0}, {"import", NamespaceImportCmd, TclCompileBasicMin0ArgCmd, NULL, NULL, 0}, {"inscope", NamespaceInscopeCmd, NULL, NRNamespaceInscopeCmd, NULL, 0}, {"origin", NamespaceOriginCmd, TclCompileNamespaceOriginCmd, NULL, NULL, 0}, {"parent", NamespaceParentCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"path", NamespacePathCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"qualifiers", NamespaceQualifiersCmd, TclCompileNamespaceQualifiersCmd, NULL, NULL, 0}, {"tail", NamespaceTailCmd, TclCompileNamespaceTailCmd, NULL, NULL, 0}, {"unknown", NamespaceUnknownCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"upvar", NamespaceUpvarCmd, TclCompileNamespaceUpvarCmd, NULL, NULL, 0}, {"which", NamespaceWhichCmd, TclCompileNamespaceWhichCmd, NULL, NULL, 0}, {NULL, NULL, NULL, NULL, NULL, 0} }; /* *---------------------------------------------------------------------- * * CreateChildEntry -- * * Create a child namespace hash table entry. * * Results: * Handle to hash table entry for a child namespace with the given name. * Caller should handle filling in the namespace value. * * Side effects: * None. * *---------------------------------------------------------------------- */ static inline Tcl_HashEntry * CreateChildEntry( Namespace *nsPtr, /* Parent namespace. */ const char *name, /* Simple name to look for. */ int *isNewPtr) /* Pointer to var with whether this is new. */ { #ifndef BREAK_NAMESPACE_COMPAT return Tcl_CreateHashEntry(&nsPtr->childTable, name, isNewPtr); #else if )nsPtr->childTablePtr == NULL) { nsPtr->childTablePtr = (Tcl_HashTable *) Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(nsPtr->childTablePtr, TCL_STRING_KEYS); } return Tcl_CreateHashEntry(nsPtr->childTablePtr, name, isNewPtr); #endif } /* *---------------------------------------------------------------------- * * FindChildEntry -- * * Look up a child namespace hash table entry. * * Results: * Handle to hash table entry if a child namespace with the given name * exists, otherwise NULL. * * Side effects: * None. * *---------------------------------------------------------------------- */ static inline Tcl_HashEntry * FindChildEntry( Namespace *nsPtr, /* Parent namespace. */ const char *name) /* Simple name to look for. */ { #ifndef BREAK_NAMESPACE_COMPAT return Tcl_FindHashEntry(&nsPtr->childTable, name); #else return nsPtr->childTablePtr ? Tcl_FindHashEntry(nsPtr->childTablePtr, name) : NULL; #endif } /* *---------------------------------------------------------------------- * * FirstChildEntry -- * * Start an iteration through the collection of child namespaces. * * Results: * Handle to hash table entry if a child namespace exists, otherwise NULL. * * Side effects: * Updates the search handle. * *---------------------------------------------------------------------- */ static inline Tcl_HashEntry * FirstChildEntry( Namespace *nsPtr, /* Parent namespace. */ Tcl_HashSearch *searchPtr) /* Iteration handle reference. */ { #ifndef BREAK_NAMESPACE_COMPAT return Tcl_FirstHashEntry(&nsPtr->childTable, searchPtr); #else return nsPtr->childTablePtr ? Tcl_FirstHashEntry(nsPtr->childTablePtr, searchPtr) : NULL; #endif } /* *---------------------------------------------------------------------- * * NumChildEntries -- * * Get the count of child namespaces. * * Results: * Number of child entries. * * Side effects: * None. * *---------------------------------------------------------------------- */ static inline Tcl_Size NumChildEntries( Namespace *nsPtr) { #ifndef BREAK_NAMESPACE_COMPAT return nsPtr->childTable.numEntries; #else return nsPtr->childTablePtr ? nsPtr->childTablePtr->numEntries : 0; #endif } /* *---------------------------------------------------------------------- * * TclInitNamespaceSubsystem -- * * This function is called to initialize all the structures that are used * by namespaces on a per-process basis. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclInitNamespaceSubsystem(void) { /* * Does nothing for now. */ } /* *---------------------------------------------------------------------- * * Tcl_GetCurrentNamespace -- * * Returns a pointer to an interpreter's currently active namespace. * * Results: * Returns a pointer to the interpreter's current namespace. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Namespace * Tcl_GetCurrentNamespace( Tcl_Interp *interp) /* Interpreter whose current namespace is * being queried. */ { return TclGetCurrentNamespace(interp); } /* *---------------------------------------------------------------------- * * Tcl_GetGlobalNamespace -- * * Returns a pointer to an interpreter's global :: namespace. * * Results: * Returns a pointer to the specified interpreter's global namespace. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Namespace * Tcl_GetGlobalNamespace( Tcl_Interp *interp) /* Interpreter whose global namespace should * be returned. */ { return TclGetGlobalNamespace(interp); } /* *---------------------------------------------------------------------- * * Tcl_PushCallFrame -- * * Pushes a new call frame onto the interpreter's Tcl call stack. Called * when executing a Tcl procedure or a "namespace eval" or "namespace * inscope" command. * * Results: * Returns TCL_OK if successful, or TCL_ERROR (along with an error * message in the interpreter's result object) if something goes wrong. * * Side effects: * Modifies the interpreter's Tcl call stack. * *---------------------------------------------------------------------- */ int Tcl_PushCallFrame( Tcl_Interp *interp, /* Interpreter in which the new call frame is * to be pushed. */ Tcl_CallFrame *callFramePtr,/* Points to a call frame structure to push. * Storage for this has already been allocated * by the caller; typically this is the * address of a CallFrame structure allocated * on the caller's C stack. The call frame * will be initialized by this function. The * caller can pop the frame later with * Tcl_PopCallFrame, and it is responsible for * freeing the frame's storage. */ Tcl_Namespace *namespacePtr,/* Points to the namespace in which the frame * will execute. If NULL, the interpreter's * current namespace will be used. */ int isProcCallFrame) /* If nonzero, the frame represents a called * Tcl procedure and may have local vars. Vars * will ordinarily be looked up in the frame. * If new variables are created, they will be * created in the frame. If 0, the frame is * for a "namespace eval" or "namespace * inscope" command and var references are * treated as references to namespace * variables. */ { Interp *iPtr = (Interp *) interp; CallFrame *framePtr = (CallFrame *) callFramePtr; Namespace *nsPtr; if (namespacePtr == NULL) { nsPtr = (Namespace *) TclGetCurrentNamespace(interp); } else { nsPtr = (Namespace *) namespacePtr; /* * TODO: Examine whether it would be better to guard based on NS_DYING * or NS_TEARDOWN. It appears that these are not tested because they can * be set in a global interp that has been [namespace delete]d, but * which never really completely goes away because of lingering global * things like ::errorInfo and [::unknown] and hidden commands. * Review of those designs might permit stricter checking here. */ if (nsPtr->flags & NS_DEAD) { Tcl_Panic("Trying to push call frame for dead namespace"); } } nsPtr->activationCount++; framePtr->nsPtr = nsPtr; framePtr->isProcCallFrame = isProcCallFrame; framePtr->objc = 0; framePtr->objv = NULL; framePtr->callerPtr = iPtr->framePtr; framePtr->callerVarPtr = iPtr->varFramePtr; if (iPtr->varFramePtr != NULL) { framePtr->level = iPtr->varFramePtr->level + 1U; } else { framePtr->level = 0; } framePtr->procPtr = NULL; /* no called procedure */ framePtr->varTablePtr = NULL; /* and no local variables */ framePtr->numCompiledLocals = 0; framePtr->compiledLocals = NULL; framePtr->clientData = NULL; framePtr->localCachePtr = NULL; framePtr->tailcallPtr = NULL; /* * Push the new call frame onto the interpreter's stack of procedure call * frames making it the current frame. */ iPtr->framePtr = framePtr; iPtr->varFramePtr = framePtr; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_PopCallFrame -- * * Removes a call frame from the Tcl call stack for the interpreter. * Called to remove a frame previously pushed by Tcl_PushCallFrame. * * Results: * None. * * Side effects: * Modifies the call stack of the interpreter. Resets various fields of * the popped call frame. If a namespace has been deleted and has no more * activations on the call stack, the namespace is destroyed. * *---------------------------------------------------------------------- */ void Tcl_PopCallFrame( Tcl_Interp *interp) /* Interpreter with call frame to pop. */ { Interp *iPtr = (Interp *) interp; CallFrame *framePtr = iPtr->framePtr; Namespace *nsPtr; /* * It's important to remove the call frame from the interpreter's stack of * call frames before deleting local variables, so that traces invoked by * the variable deletion don't see the partially-deleted frame. */ if (framePtr->callerPtr) { iPtr->framePtr = framePtr->callerPtr; iPtr->varFramePtr = framePtr->callerVarPtr; } else { /* Tcl_PopCallFrame: trying to pop rootCallFrame! */ } if (framePtr->varTablePtr != NULL) { TclDeleteVars(iPtr, framePtr->varTablePtr); Tcl_Free(framePtr->varTablePtr); framePtr->varTablePtr = NULL; } if (framePtr->numCompiledLocals > 0) { TclDeleteCompiledLocalVars(iPtr, framePtr); if (framePtr->localCachePtr->refCount-- <= 1) { TclFreeLocalCache(interp, framePtr->localCachePtr); } framePtr->localCachePtr = NULL; } /* * Decrement the namespace's count of active call frames. If the namespace * is "dying" and there are no more active call frames, call * Tcl_DeleteNamespace to destroy it. */ nsPtr = framePtr->nsPtr; if ((--nsPtr->activationCount <= (nsPtr == iPtr->globalNsPtr)) && (nsPtr->flags & NS_DYING)) { Tcl_DeleteNamespace((Tcl_Namespace *) nsPtr); } framePtr->nsPtr = NULL; if (framePtr->tailcallPtr) { /* Reusing the existing reference count from framePtr->tailcallPtr, so * no need to Tcl_IncrRefCount(framePtr->tailcallPtr)*/ TclSetTailcall(interp, framePtr->tailcallPtr); } } /* *---------------------------------------------------------------------- * * TclPushStackFrame -- * * Allocates a new call frame in the interpreter's execution stack, then * pushes it onto the interpreter's Tcl call stack. Called when executing * a Tcl procedure or a "namespace eval" or "namespace inscope" command. * * Results: * Returns TCL_OK if successful, or TCL_ERROR (along with an error * message in the interpreter's result object) if something goes wrong. * * Side effects: * Modifies the interpreter's Tcl call stack. * *---------------------------------------------------------------------- */ int TclPushStackFrame( Tcl_Interp *interp, /* Interpreter in which the new call frame is * to be pushed. */ Tcl_CallFrame **framePtrPtr,/* Place to store a pointer to the stack * allocated call frame. */ Tcl_Namespace *namespacePtr,/* Points to the namespace in which the frame * will execute. If NULL, the interpreter's * current namespace will be used. */ int isProcCallFrame) /* If nonzero, the frame represents a called * Tcl procedure and may have local vars. Vars * will ordinarily be looked up in the frame. * If new variables are created, they will be * created in the frame. If 0, the frame is * for a "namespace eval" or "namespace * inscope" command and var references are * treated as references to namespace * variables. */ { *framePtrPtr = (Tcl_CallFrame *) TclStackAlloc(interp, sizeof(CallFrame)); return Tcl_PushCallFrame(interp, *framePtrPtr, namespacePtr, isProcCallFrame); } void TclPopStackFrame( Tcl_Interp *interp) /* Interpreter with call frame to pop. */ { CallFrame *freePtr = ((Interp *) interp)->framePtr; Tcl_PopCallFrame(interp); TclStackFree(interp, freePtr); } /* *---------------------------------------------------------------------- * * EstablishErrorCodeTraces -- * * Creates traces on the ::errorCode variable to keep its value * consistent with the expectations of legacy code. * * Results: * None. * * Side effects: * Read and unset traces are established on ::errorCode. * *---------------------------------------------------------------------- */ static char * EstablishErrorCodeTraces( TCL_UNUSED(void *), Tcl_Interp *interp, TCL_UNUSED(const char *) /*name1*/, TCL_UNUSED(const char *) /*name2*/, TCL_UNUSED(int) /*flags*/) { Tcl_TraceVar2(interp, "errorCode", NULL, TCL_GLOBAL_ONLY|TCL_TRACE_READS, ErrorCodeRead, NULL); Tcl_TraceVar2(interp, "errorCode", NULL, TCL_GLOBAL_ONLY|TCL_TRACE_UNSETS, EstablishErrorCodeTraces, NULL); return NULL; } /* *---------------------------------------------------------------------- * * ErrorCodeRead -- * * Called when the ::errorCode variable is read. Copies the current value * of the interp's errorCode field into ::errorCode. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static char * ErrorCodeRead( TCL_UNUSED(void *), Tcl_Interp *interp, TCL_UNUSED(const char *) /*name1*/, TCL_UNUSED(const char *) /*name2*/, TCL_UNUSED(int) /*flags*/) { Interp *iPtr = (Interp *) interp; if (Tcl_InterpDeleted(interp) || !(iPtr->flags & ERR_LEGACY_COPY)) { return NULL; } if (iPtr->errorCode) { Tcl_ObjSetVar2(interp, iPtr->ecVar, NULL, iPtr->errorCode, TCL_GLOBAL_ONLY); return NULL; } if (NULL == Tcl_ObjGetVar2(interp, iPtr->ecVar, NULL, TCL_GLOBAL_ONLY)) { Tcl_ObjSetVar2(interp, iPtr->ecVar, NULL, Tcl_NewObj(), TCL_GLOBAL_ONLY); } return NULL; } /* *---------------------------------------------------------------------- * * EstablishErrorInfoTraces -- * * Creates traces on the ::errorInfo variable to keep its value * consistent with the expectations of legacy code. * * Results: * None. * * Side effects: * Read and unset traces are established on ::errorInfo. * *---------------------------------------------------------------------- */ static char * EstablishErrorInfoTraces( TCL_UNUSED(void *), Tcl_Interp *interp, TCL_UNUSED(const char *) /*name1*/, TCL_UNUSED(const char *) /*name2*/, TCL_UNUSED(int) /*flags*/) { Tcl_TraceVar2(interp, "errorInfo", NULL, TCL_GLOBAL_ONLY|TCL_TRACE_READS, ErrorInfoRead, NULL); Tcl_TraceVar2(interp, "errorInfo", NULL, TCL_GLOBAL_ONLY|TCL_TRACE_UNSETS, EstablishErrorInfoTraces, NULL); return NULL; } /* *---------------------------------------------------------------------- * * ErrorInfoRead -- * * Called when the ::errorInfo variable is read. Copies the current value * of the interp's errorInfo field into ::errorInfo. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static char * ErrorInfoRead( TCL_UNUSED(void *), Tcl_Interp *interp, TCL_UNUSED(const char *) /*name1*/, TCL_UNUSED(const char *) /*name2*/, TCL_UNUSED(int) /*flags*/) { Interp *iPtr = (Interp *) interp; if (Tcl_InterpDeleted(interp) || !(iPtr->flags & ERR_LEGACY_COPY)) { return NULL; } if (iPtr->errorInfo) { Tcl_ObjSetVar2(interp, iPtr->eiVar, NULL, iPtr->errorInfo, TCL_GLOBAL_ONLY); return NULL; } if (NULL == Tcl_ObjGetVar2(interp, iPtr->eiVar, NULL, TCL_GLOBAL_ONLY)) { Tcl_ObjSetVar2(interp, iPtr->eiVar, NULL, Tcl_NewObj(), TCL_GLOBAL_ONLY); } return NULL; } /* *---------------------------------------------------------------------- * * Tcl_CreateNamespace -- * * Creates a new namespace with the given name. If there is no active * namespace (i.e., the interpreter is being initialized), the global :: * namespace is created and returned. * * Results: * Returns a pointer to the new namespace if successful. If the namespace * already exists or if another error occurs, this routine returns NULL, * along with an error message in the interpreter's result object. * * Side effects: * If the name contains "::" qualifiers and a parent namespace does not * already exist, it is automatically created. * *---------------------------------------------------------------------- */ Tcl_Namespace * Tcl_CreateNamespace( Tcl_Interp *interp, /* Interpreter in which a new namespace is * being created. Also used for error * reporting. */ const char *name, /* Name for the new namespace. May be a * qualified name with names of ancestor * namespaces separated by "::"s. */ void *clientData, /* One-word value to store with namespace. */ Tcl_NamespaceDeleteProc *deleteProc) /* Function called to delete client data when * the namespace is deleted. NULL if no * function should be called. */ { Interp *iPtr = (Interp *) interp; Namespace *nsPtr, *ancestorPtr; Namespace *parentPtr, *dummy1Ptr, *dummy2Ptr; Namespace *globalNsPtr = iPtr->globalNsPtr; const char *simpleName; Tcl_HashEntry *entryPtr; Tcl_DString buffer1, buffer2; Tcl_DString *namePtr, *buffPtr; int newEntry; size_t nameLen; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); const char *nameStr; Tcl_DString tmpBuffer; Tcl_DStringInit(&tmpBuffer); /* * If there is no active namespace, the interpreter is being initialized. */ if ((globalNsPtr == NULL) && (iPtr->varFramePtr == NULL)) { /* * Treat this namespace as the global namespace, and avoid looking for * a parent. */ parentPtr = NULL; simpleName = ""; goto doCreate; } /* * Ensure that there are no trailing colons as that causes chaos when a * deleteProc is specified. [Bug d614d63989] */ if (deleteProc != NULL) { nameStr = name + strlen(name) - 2; if (nameStr >= name && nameStr[1] == ':' && nameStr[0] == ':') { Tcl_DStringAppend(&tmpBuffer, name, -1); while ((nameLen = Tcl_DStringLength(&tmpBuffer)) > 0 && Tcl_DStringValue(&tmpBuffer)[nameLen-1] == ':') { Tcl_DStringSetLength(&tmpBuffer, nameLen-1); } name = Tcl_DStringValue(&tmpBuffer); } } /* * If we've ended up with an empty string now, we're attempting to create * the global namespace despite the global namespace existing. That's * naughty! */ if (*name == '\0') { Tcl_SetObjResult(interp, Tcl_NewStringObj("can't create namespace" " \"\": only global namespace can have empty name", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "NAMESPACE", "CREATEGLOBAL", (char *)NULL); Tcl_DStringFree(&tmpBuffer); return NULL; } /* * Find the parent for the new namespace. */ TclGetNamespaceForQualName(interp, name, NULL, TCL_CREATE_NS_IF_UNKNOWN, &parentPtr, &dummy1Ptr, &dummy2Ptr, &simpleName); /* * If the unqualified name at the end is empty, there were trailing "::"s * after the namespace's name which we ignore. The new namespace was * already (recursively) created and is pointed to by parentPtr. */ if (*simpleName == '\0') { Tcl_DStringFree(&tmpBuffer); return (Tcl_Namespace *) parentPtr; } /* * Check for a bad namespace name and make sure that the name does not * already exist in the parent namespace. */ if (FindChildEntry(parentPtr, simpleName) != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't create namespace \"%s\": already exists", name)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "NAMESPACE", "CREATEEXISTING", (char *)NULL); Tcl_DStringFree(&tmpBuffer); return NULL; } /* * Create the new namespace and root it in its parent. Increment the count * of namespaces created. */ doCreate: nsPtr = (Namespace *) Tcl_Alloc(sizeof(Namespace)); nameLen = strlen(simpleName) + 1; nsPtr->name = (char *) Tcl_Alloc(nameLen); memcpy(nsPtr->name, simpleName, nameLen); nsPtr->fullName = NULL; /* Set below. */ nsPtr->clientData = clientData; nsPtr->deleteProc = deleteProc; nsPtr->parentPtr = parentPtr; #ifndef BREAK_NAMESPACE_COMPAT Tcl_InitHashTable(&nsPtr->childTable, TCL_STRING_KEYS); #else nsPtr->childTablePtr = NULL; #endif nsPtr->nsId = ++(tsdPtr->numNsCreated); nsPtr->interp = interp; nsPtr->flags = 0; nsPtr->activationCount = 0; nsPtr->refCount = 0; Tcl_InitHashTable(&nsPtr->cmdTable, TCL_STRING_KEYS); TclInitVarHashTable(&nsPtr->varTable, nsPtr); nsPtr->exportArrayPtr = NULL; nsPtr->numExportPatterns = 0; nsPtr->maxExportPatterns = 0; nsPtr->cmdRefEpoch = 0; nsPtr->resolverEpoch = 0; nsPtr->cmdResProc = NULL; nsPtr->varResProc = NULL; nsPtr->compiledVarResProc = NULL; nsPtr->exportLookupEpoch = 0; nsPtr->ensembles = NULL; nsPtr->unknownHandlerPtr = NULL; nsPtr->commandPathLength = 0; nsPtr->commandPathArray = NULL; nsPtr->commandPathSourceList = NULL; nsPtr->earlyDeleteProc = NULL; if (parentPtr != NULL) { entryPtr = CreateChildEntry(parentPtr, simpleName, &newEntry); Tcl_SetHashValue(entryPtr, nsPtr); } else { /* * In the global namespace create traces to maintain the ::errorInfo * and ::errorCode variables. */ iPtr->globalNsPtr = nsPtr; EstablishErrorInfoTraces(NULL, interp, NULL, NULL, 0); EstablishErrorCodeTraces(NULL, interp, NULL, NULL, 0); } /* * Build the fully qualified name for this namespace. */ Tcl_DStringInit(&buffer1); Tcl_DStringInit(&buffer2); namePtr = &buffer1; buffPtr = &buffer2; for (ancestorPtr = nsPtr; ancestorPtr != NULL; ancestorPtr = ancestorPtr->parentPtr) { if (ancestorPtr != globalNsPtr) { Tcl_DString *tempPtr = namePtr; TclDStringAppendLiteral(buffPtr, "::"); Tcl_DStringAppend(buffPtr, ancestorPtr->name, -1); TclDStringAppendDString(buffPtr, namePtr); /* * Clear the unwanted buffer or we end up appending to previous * results, making the namespace fullNames of nested namespaces * very wrong (and strange). */ TclDStringClear(namePtr); /* * Now swap the buffer pointers so that we build in the other * buffer. This is faster than repeated copying back and forth * between buffers. */ namePtr = buffPtr; buffPtr = tempPtr; } } name = Tcl_DStringValue(namePtr); nameLen = Tcl_DStringLength(namePtr); nsPtr->fullName = (char *) Tcl_Alloc(nameLen + 1); memcpy(nsPtr->fullName, name, nameLen + 1); Tcl_DStringFree(&buffer1); Tcl_DStringFree(&buffer2); Tcl_DStringFree(&tmpBuffer); /* * If compilation of commands originating from the parent NS is * suppressed, suppress it for commands originating in this one too. */ if (nsPtr->parentPtr != NULL && nsPtr->parentPtr->flags & NS_SUPPRESS_COMPILATION) { nsPtr->flags |= NS_SUPPRESS_COMPILATION; } /* * Return a pointer to the new namespace. */ return (Tcl_Namespace *) nsPtr; } /* *---------------------------------------------------------------------- * * Tcl_DeleteNamespace -- * * Deletes a namespace and all of the commands, variables, and other * namespaces within it. * * Results: * None. * * Side effects: * When a namespace is deleted, it is automatically removed as a child of * its parent namespace. Also, all its commands, variables and child * namespaces are deleted. * *---------------------------------------------------------------------- */ void Tcl_DeleteNamespace( Tcl_Namespace *namespacePtr)/* Points to the namespace to delete. */ { Namespace *nsPtr = (Namespace *) namespacePtr; Tcl_Interp *interp = nsPtr->interp; Namespace *globalNsPtr = (Namespace *) TclGetGlobalNamespace(interp); Tcl_HashEntry *entryPtr; Tcl_HashSearch search; Command *cmdPtr; /* * Ensure that this namespace doesn't get deallocated in the meantime. */ nsPtr->refCount++; /* * Give anyone interested - notably TclOO - a chance to use this namespace * normally despite the fact that the namespace is going to go. Allows the * calling of destructors. Will only be called once (unless re-established * by the called function). [Bug 2950259] * * Note that setting this field requires access to the internal definition * of namespaces, so it should only be accessed by code that knows about * being careful with reentrancy. */ if (nsPtr->earlyDeleteProc != NULL) { Tcl_NamespaceDeleteProc *earlyDeleteProc = nsPtr->earlyDeleteProc; nsPtr->earlyDeleteProc = NULL; nsPtr->activationCount++; earlyDeleteProc(nsPtr->clientData); nsPtr->activationCount--; } /* * Delete all coroutine commands now: break the circular ref cycle between * the namespace and the coroutine command [Bug 2724403]. This code is * essentially duplicated in TclTeardownNamespace() for all other * commands. Don't optimize to Tcl_NextHashEntry() because of traces. * * NOTE: we could avoid traversing the ns's command list by keeping a * separate list of coros. */ for (entryPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search); entryPtr != NULL;) { cmdPtr = (Command *) Tcl_GetHashValue(entryPtr); if (cmdPtr->nreProc == TclNRInterpCoroutine) { Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmdPtr); entryPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search); } else { entryPtr = Tcl_NextHashEntry(&search); } } /* * If the namespace has associated ensemble commands, delete them first. * This leaves the actual contents of the namespace alone (unless they are * linked ensemble commands, of course). Note that this code is actually * reentrant so command delete traces won't purturb things badly. */ while (nsPtr->ensembles != NULL) { EnsembleConfig *ensemblePtr = (EnsembleConfig *) nsPtr->ensembles; /* * Splice out and link to indicate that we've already been killed. */ nsPtr->ensembles = (Tcl_Ensemble *) ensemblePtr->next; ensemblePtr->next = ensemblePtr; Tcl_DeleteCommandFromToken(interp, ensemblePtr->token); } /* * If the namespace has a registered unknown handler (TIP 181), then free * it here. */ if (nsPtr->unknownHandlerPtr != NULL) { Tcl_DecrRefCount(nsPtr->unknownHandlerPtr); nsPtr->unknownHandlerPtr = NULL; } /* * If the namespace is on the call frame stack, it is marked as "dying" * (NS_DYING is OR'd into its flags): Contents of the namespace are * still available and visible until the namespace is later marked as * NS_DEAD, and its commands and variables are still usable by any * active call frames referring to th namespace. When all active call * frames referring to the namespace have been popped from the Tcl * stack, Tcl_PopCallFrame calls Tcl_DeleteNamespace again. If no * nsName objects refer to the namespace (i.e., if its refCount is * zero), its commands and variables are deleted and the storage for * its namespace structure is freed. Otherwise, if its refCount is * nonzero, the namespace's commands and variables are deleted but the * structure isn't freed. Instead, NS_DEAD is OR'd into the structure's * flags to allow the namespace resolution code to recognize that the * namespace is "deleted". The structure's storage is freed by * FreeNsNameInternalRep when its refCount reaches 0. */ if (nsPtr->activationCount > (nsPtr == globalNsPtr)) { nsPtr->flags |= NS_DYING; if (nsPtr->parentPtr != NULL) { entryPtr = FindChildEntry(nsPtr->parentPtr, nsPtr->name); if (entryPtr != NULL) { Tcl_DeleteHashEntry(entryPtr); } } nsPtr->parentPtr = NULL; } else if (!(nsPtr->flags & NS_TEARDOWN)) { /* * Delete the namespace and everything in it. If this is the global * namespace, then clear it but don't free its storage unless the * interpreter is being torn down. Set the NS_TEARDOWN flag to avoid * recursive calls here - if the namespace is really in the process of * being deleted, ignore any second call. */ nsPtr->flags |= NS_DYING | NS_TEARDOWN; TclTeardownNamespace(nsPtr); if ((nsPtr != globalNsPtr) || (((Interp *) interp)->flags & DELETED)) { /* * If this is the global namespace, then it may have residual * "errorInfo" and "errorCode" variables for errors that occurred * while it was being torn down. Try to clear the variable list * one last time. */ TclDeleteNamespaceVars(nsPtr); #ifndef BREAK_NAMESPACE_COMPAT Tcl_DeleteHashTable(&nsPtr->childTable); #else if (nsPtr->childTablePtr != NULL) { Tcl_DeleteHashTable(nsPtr->childTablePtr); Tcl_Free(nsPtr->childTablePtr); } #endif Tcl_DeleteHashTable(&nsPtr->cmdTable); nsPtr ->flags |= NS_DEAD; } else { /* * Restore the ::errorInfo and ::errorCode traces. */ EstablishErrorInfoTraces(NULL, interp, NULL, NULL, 0); EstablishErrorCodeTraces(NULL, interp, NULL, NULL, 0); /* * We didn't really kill it, so remove the KILLED marks, so it can * get killed later, avoiding mem leaks. */ nsPtr->flags &= ~(NS_DYING|NS_TEARDOWN); } } TclNsDecrRefCount(nsPtr); } int TclNamespaceDeleted( Namespace *nsPtr) { return (nsPtr->flags & NS_DYING) ? 1 : 0; } void TclDeleteNamespaceChildren( Namespace *nsPtr) /* Namespace whose children to delete */ { Tcl_Interp *interp = nsPtr->interp; Tcl_HashEntry *entryPtr; size_t i; int unchecked; Tcl_HashSearch search; /* * Delete all the child namespaces. * * BE CAREFUL: When each child is deleted, it divorces itself from its * parent. The hash table can't be properly traversed if its elements are * being deleted. Because of traces (and the desire to avoid the * quadratic problems of just using Tcl_FirstHashEntry over and over, [Bug * f97d4ee020]) copy to a temporary array and then delete all those * namespaces. * * Important: leave the hash table itself still live. */ unchecked = (NumChildEntries(nsPtr) > 0); while (NumChildEntries(nsPtr) > 0 && unchecked) { size_t length = NumChildEntries(nsPtr); Namespace **children = (Namespace **) TclStackAlloc(interp, sizeof(Namespace *) * length); i = 0; for (entryPtr = FirstChildEntry(nsPtr, &search); entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { children[i] = (Namespace *) Tcl_GetHashValue(entryPtr); children[i]->refCount++; i++; } unchecked = 0; for (i = 0 ; i < length ; i++) { if (!(children[i]->flags & NS_DYING)) { unchecked = 1; Tcl_DeleteNamespace((Tcl_Namespace *) children[i]); TclNsDecrRefCount(children[i]); } } TclStackFree(interp, children); } } /* *---------------------------------------------------------------------- * * TclTeardownNamespace -- * * Used internally to dismantle and unlink a namespace when it is * deleted. Divorces the namespace from its parent, and deletes all * commands, variables, and child namespaces. * * This is kept separate from Tcl_DeleteNamespace so that the global * namespace can be handled specially. * * Results: * None. * * Side effects: * Removes this namespace from its parent's child namespace hashtable. * Deletes all commands, variables and namespaces in this namespace. * *---------------------------------------------------------------------- */ void TclTeardownNamespace( Namespace *nsPtr) /* Points to the namespace to be dismantled * and unlinked from its parent. */ { Tcl_Interp *interp = nsPtr->interp; Tcl_HashEntry *entryPtr; Tcl_HashSearch search; Tcl_Size i; /* * Start by destroying the namespace's variable table, since variables * might trigger traces. Variable table should be cleared but not freed! * TclDeleteNamespaceVars frees it, so we reinitialize it afterwards. */ TclDeleteNamespaceVars(nsPtr); TclInitVarHashTable(&nsPtr->varTable, nsPtr); /* * Delete all commands in this namespace. Be careful when traversing the * hash table: when each command is deleted, it removes itself from the * command table. Because of traces (and the desire to avoid the quadratic * problems of just using Tcl_FirstHashEntry over and over, [Bug * f97d4ee020]) we copy to a temporary array and then delete all those * commands. */ while (nsPtr->cmdTable.numEntries > 0) { Tcl_Size length = nsPtr->cmdTable.numEntries; Command **cmds = (Command **)TclStackAlloc(interp, sizeof(Command *) * length); i = 0; for (entryPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search); entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { cmds[i] = (Command *) Tcl_GetHashValue(entryPtr); cmds[i]->refCount++; i++; } for (i = 0 ; i < length ; i++) { Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmds[i]); TclCleanupCommandMacro(cmds[i]); } TclStackFree(interp, cmds); } Tcl_DeleteHashTable(&nsPtr->cmdTable); Tcl_InitHashTable(&nsPtr->cmdTable, TCL_STRING_KEYS); /* * Remove the namespace from its parent's child hashtable. */ if (nsPtr->parentPtr != NULL) { entryPtr = FindChildEntry(nsPtr->parentPtr, nsPtr->name); if (entryPtr != NULL) { Tcl_DeleteHashEntry(entryPtr); } } nsPtr->parentPtr = NULL; /* * Delete the namespace path if one is installed. */ if (nsPtr->commandPathLength != 0) { UnlinkNsPath(nsPtr); nsPtr->commandPathLength = 0; } if (nsPtr->commandPathSourceList != NULL) { NamespacePathEntry *nsPathPtr = nsPtr->commandPathSourceList; do { if (nsPathPtr->nsPtr != NULL && nsPathPtr->creatorNsPtr != NULL) { nsPathPtr->creatorNsPtr->cmdRefEpoch++; } nsPathPtr->nsPtr = NULL; nsPathPtr = nsPathPtr->nextPtr; } while (nsPathPtr != NULL); nsPtr->commandPathSourceList = NULL; } TclDeleteNamespaceChildren(nsPtr); /* * Free the namespace's export pattern array. */ if (nsPtr->exportArrayPtr != NULL) { for (i = 0; i < nsPtr->numExportPatterns; i++) { Tcl_Free(nsPtr->exportArrayPtr[i]); } Tcl_Free(nsPtr->exportArrayPtr); nsPtr->exportArrayPtr = NULL; nsPtr->numExportPatterns = 0; nsPtr->maxExportPatterns = 0; } /* * Free any client data associated with the namespace. */ if (nsPtr->deleteProc != NULL) { nsPtr->deleteProc(nsPtr->clientData); } nsPtr->deleteProc = NULL; nsPtr->clientData = NULL; /* * Reset the namespace's id field to ensure that this namespace won't be * interpreted as valid by, e.g., the cache validation code for cached * command references in Tcl_GetCommandFromObj. */ nsPtr->nsId = 0; } /* *---------------------------------------------------------------------- * * NamespaceFree -- * * Called after a namespace has been deleted, when its reference count * reaches 0. Frees the data structure representing the namespace. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void NamespaceFree( Namespace *nsPtr) /* Points to the namespace to free. */ { /* * Most of the namespace's contents are freed when the namespace is * deleted by Tcl_DeleteNamespace. All that remains is to free its names * (for error messages), and the structure itself. */ Tcl_Free(nsPtr->name); Tcl_Free(nsPtr->fullName); Tcl_Free(nsPtr); } /* *---------------------------------------------------------------------- * * TclNsDecrRefCount -- * * Drops a reference to a namespace and frees it if the namespace has * been deleted and the last reference has just been dropped. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclNsDecrRefCount( Namespace *nsPtr) { if ((nsPtr->refCount-- <= 1) && (nsPtr->flags & NS_DEAD)) { NamespaceFree(nsPtr); } } /* *---------------------------------------------------------------------- * * Tcl_Export -- * * Makes all the commands matching a pattern available to later be * imported from the namespace specified by namespacePtr (or the current * namespace if namespacePtr is NULL). The specified pattern is appended * onto the namespace's export pattern list, which is optionally cleared * beforehand. * * Results: * Returns TCL_OK if successful, or TCL_ERROR (along with an error * message in the interpreter's result) if something goes wrong. * * Side effects: * Appends the export pattern onto the namespace's export list. * Optionally reset the namespace's export pattern list. * *---------------------------------------------------------------------- */ int Tcl_Export( Tcl_Interp *interp, /* Current interpreter. */ Tcl_Namespace *namespacePtr,/* Points to the namespace from which commands * are to be exported. NULL for the current * namespace. */ const char *pattern, /* String pattern indicating which commands to * export. This pattern may not include any * namespace qualifiers; only commands in the * specified namespace may be exported. */ int resetListFirst) /* If nonzero, resets the namespace's export * list before appending. */ { #define INIT_EXPORT_PATTERNS 5 Namespace *nsPtr, *exportNsPtr, *dummyPtr; Namespace *currNsPtr = (Namespace *) TclGetCurrentNamespace(interp); const char *simplePattern; char *patternCpy; Tcl_Size neededElems, len, i; /* * If the specified namespace is NULL, use the current namespace. */ if (namespacePtr == NULL) { nsPtr = (Namespace *) currNsPtr; } else { nsPtr = (Namespace *) namespacePtr; } /* * If resetListFirst is true (nonzero), clear the namespace's export * pattern list. */ if (resetListFirst) { if (nsPtr->exportArrayPtr != NULL) { for (i = 0; i < nsPtr->numExportPatterns; i++) { Tcl_Free(nsPtr->exportArrayPtr[i]); } Tcl_Free(nsPtr->exportArrayPtr); nsPtr->exportArrayPtr = NULL; TclInvalidateNsCmdLookup(nsPtr); nsPtr->numExportPatterns = 0; nsPtr->maxExportPatterns = 0; } } /* * Check that the pattern doesn't have namespace qualifiers. */ TclGetNamespaceForQualName(interp, pattern, nsPtr, TCL_NAMESPACE_ONLY, &exportNsPtr, &dummyPtr, &dummyPtr, &simplePattern); if ((exportNsPtr != nsPtr) || (strcmp(pattern, simplePattern) != 0)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("invalid export pattern" " \"%s\": pattern can't specify a namespace", pattern)); Tcl_SetErrorCode(interp, "TCL", "EXPORT", "INVALID", (char *)NULL); return TCL_ERROR; } /* * Make sure that we don't already have the pattern in the array */ if (nsPtr->exportArrayPtr != NULL) { for (i = 0; i < nsPtr->numExportPatterns; i++) { if (strcmp(pattern, nsPtr->exportArrayPtr[i]) == 0) { /* * The pattern already exists in the list. */ return TCL_OK; } } } /* * Make sure there is room in the namespace's pattern array for the new * pattern. */ neededElems = nsPtr->numExportPatterns + 1; if (neededElems > nsPtr->maxExportPatterns) { nsPtr->maxExportPatterns = nsPtr->maxExportPatterns ? 2 * nsPtr->maxExportPatterns : INIT_EXPORT_PATTERNS; nsPtr->exportArrayPtr = (char **) Tcl_Realloc(nsPtr->exportArrayPtr, sizeof(char *) * nsPtr->maxExportPatterns); } /* * Add the pattern to the namespace's array of export patterns. */ len = strlen(pattern); patternCpy = (char *) Tcl_Alloc(len + 1); memcpy(patternCpy, pattern, len + 1); nsPtr->exportArrayPtr[nsPtr->numExportPatterns] = patternCpy; nsPtr->numExportPatterns++; /* * The list of commands actually exported from the namespace might have * changed (probably will have!) However, we do not need to recompute this * just yet; next time we need the info will be soon enough. */ TclInvalidateNsCmdLookup(nsPtr); return TCL_OK; #undef INIT_EXPORT_PATTERNS } /* *---------------------------------------------------------------------- * * Tcl_AppendExportList -- * * Appends onto the argument object the list of export patterns for the * specified namespace. * * Results: * The return value is normally TCL_OK; in this case the object * referenced by objPtr has each export pattern appended to it. If an * error occurs, TCL_ERROR is returned and the interpreter's result holds * an error message. * * Side effects: * If necessary, the object referenced by objPtr is converted into a list * object. * *---------------------------------------------------------------------- */ int Tcl_AppendExportList( Tcl_Interp *interp, /* Interpreter used for global NS and error * reporting. */ Tcl_Namespace *namespacePtr,/* Points to the namespace whose export * pattern list is appended onto objPtr. NULL * for the current namespace. */ Tcl_Obj *objPtr) /* Points to the Tcl object onto which the * export pattern list is appended. */ { Namespace *nsPtr; Tcl_Size i; int result; /* * If the specified namespace is NULL, use the current namespace. */ if (namespacePtr == NULL) { nsPtr = (Namespace *) TclGetCurrentNamespace(interp); } else { nsPtr = (Namespace *) namespacePtr; } /* * Append the export pattern list onto objPtr. */ for (i = 0; i < nsPtr->numExportPatterns; i++) { result = Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewStringObj(nsPtr->exportArrayPtr[i], -1)); if (result != TCL_OK) { return result; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_Import -- * * Imports all of the commands matching a pattern into the namespace * specified by namespacePtr (or the current namespace if contextNsPtr is * NULL). This is done by creating a new command (the "imported command") * that points to the real command in its original namespace. * * If matching commands are on the autoload path but haven't been loaded * yet, this command forces them to be loaded, then creates the links to * them. * * Results: * Returns TCL_OK if successful, or TCL_ERROR (along with an error * message in the interpreter's result) if something goes wrong. * * Side effects: * Creates new commands in the importing namespace. These indirect calls * back to the real command and are deleted if the real commands are * deleted. * *---------------------------------------------------------------------- */ int Tcl_Import( Tcl_Interp *interp, /* Current interpreter. */ Tcl_Namespace *namespacePtr,/* Points to the namespace into which the * commands are to be imported. NULL for the * current namespace. */ const char *pattern, /* String pattern indicating which commands to * import. This pattern should be qualified by * the name of the namespace from which to * import the command(s). */ int allowOverwrite) /* If nonzero, allow existing commands to be * overwritten by imported commands. If 0, * return an error if an imported cmd * conflicts with an existing one. */ { Namespace *nsPtr, *importNsPtr, *dummyPtr; const char *simplePattern; Tcl_HashEntry *hPtr; Tcl_HashSearch search; /* * If the specified namespace is NULL, use the current namespace. */ if (namespacePtr == NULL) { nsPtr = (Namespace *) TclGetCurrentNamespace(interp); } else { nsPtr = (Namespace *) namespacePtr; } /* * First, invoke the "auto_import" command with the pattern being * imported. This command is part of the Tcl library. It looks for * imported commands in autoloaded libraries and loads them in. That way, * they will be found when we try to create links below. * * Note that we don't just call Tcl_EvalObjv() directly because we do not * want absence of the command to be a failure case. */ if (Tcl_FindCommand(interp, "auto_import", NULL, TCL_GLOBAL_ONLY) != NULL) { Tcl_Obj *objv[2]; int result; TclNewLiteralStringObj(objv[0], "auto_import"); objv[1] = Tcl_NewStringObj(pattern, -1); Tcl_IncrRefCount(objv[0]); Tcl_IncrRefCount(objv[1]); result = Tcl_EvalObjv(interp, 2, objv, TCL_GLOBAL_ONLY); Tcl_DecrRefCount(objv[0]); Tcl_DecrRefCount(objv[1]); if (result != TCL_OK) { return TCL_ERROR; } Tcl_ResetResult(interp); } /* * From the pattern, find the namespace from which we are importing and * get the simple pattern (no namespace qualifiers or ::'s) at the end. */ if (strlen(pattern) == 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj("empty import pattern",-1)); Tcl_SetErrorCode(interp, "TCL", "IMPORT", "EMPTY", (char *)NULL); return TCL_ERROR; } TclGetNamespaceForQualName(interp, pattern, nsPtr, TCL_NAMESPACE_ONLY, &importNsPtr, &dummyPtr, &dummyPtr, &simplePattern); if (importNsPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown namespace in import pattern \"%s\"", pattern)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "NAMESPACE", pattern, (char *)NULL); return TCL_ERROR; } if (importNsPtr == nsPtr) { if (pattern == simplePattern) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "no namespace specified in import pattern \"%s\"", pattern)); Tcl_SetErrorCode(interp, "TCL", "IMPORT", "ORIGIN", (char *)NULL); } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "import pattern \"%s\" tries to import from namespace" " \"%s\" into itself", pattern, importNsPtr->name)); Tcl_SetErrorCode(interp, "TCL", "IMPORT", "SELF", (char *)NULL); } return TCL_ERROR; } /* * Scan through the command table in the source namespace and look for * exported commands that match the string pattern. Create an "imported * command" in the current namespace for each imported command; these * commands redirect their invocations to the "real" command. */ if ((simplePattern != NULL) && TclMatchIsTrivial(simplePattern)) { hPtr = Tcl_FindHashEntry(&importNsPtr->cmdTable, simplePattern); if (hPtr == NULL) { return TCL_OK; } return DoImport(interp, nsPtr, hPtr, simplePattern, pattern, importNsPtr, allowOverwrite); } for (hPtr = Tcl_FirstHashEntry(&importNsPtr->cmdTable, &search); (hPtr != NULL); hPtr = Tcl_NextHashEntry(&search)) { char *cmdName = (char *) Tcl_GetHashKey(&importNsPtr->cmdTable, hPtr); if (Tcl_StringMatch(cmdName, simplePattern) && DoImport(interp, nsPtr, hPtr, cmdName, pattern, importNsPtr, allowOverwrite) == TCL_ERROR) { return TCL_ERROR; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * DoImport -- * * Import a particular command from one namespace into another. Helper * for Tcl_Import(). * * Results: * Standard Tcl result code. If TCL_ERROR, appends an error message to * the interpreter result. * * Side effects: * A new command is created in the target namespace unless this is a * reimport of exactly the same command as before. * *---------------------------------------------------------------------- */ static int DoImport( Tcl_Interp *interp, Namespace *nsPtr, Tcl_HashEntry *hPtr, const char *cmdName, const char *pattern, Namespace *importNsPtr, int allowOverwrite) { Tcl_Size i = 0, exported = 0; Tcl_HashEntry *found; /* * The command cmdName in the source namespace matches the pattern. Check * whether it was exported. If it wasn't, we ignore it. */ while (!exported && (i < importNsPtr->numExportPatterns)) { exported |= Tcl_StringMatch(cmdName, importNsPtr->exportArrayPtr[i++]); } if (!exported) { return TCL_OK; } /* * Unless there is a name clash, create an imported command in the current * namespace that refers to cmdPtr. */ found = Tcl_FindHashEntry(&nsPtr->cmdTable, cmdName); if ((found == NULL) || allowOverwrite) { /* * Create the imported command and its client data. To create the new * command in the current namespace, generate a fully qualified name * for it. */ Tcl_DString ds; Tcl_Command importedCmd; ImportedCmdData *dataPtr; Command *cmdPtr; ImportRef *refPtr; Tcl_DStringInit(&ds); Tcl_DStringAppend(&ds, nsPtr->fullName, -1); if (nsPtr != ((Interp *) interp)->globalNsPtr) { TclDStringAppendLiteral(&ds, "::"); } Tcl_DStringAppend(&ds, cmdName, -1); /* * Check whether creating the new imported command in the current * namespace would create a cycle of imported command references. */ cmdPtr = (Command *) Tcl_GetHashValue(hPtr); if (found != NULL && cmdPtr->deleteProc == DeleteImportedCmd) { Command *overwrite = (Command *) Tcl_GetHashValue(found); Command *linkCmd = cmdPtr; while (linkCmd->deleteProc == DeleteImportedCmd) { dataPtr = (ImportedCmdData *) linkCmd->objClientData; linkCmd = dataPtr->realCmdPtr; if (overwrite == linkCmd) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "import pattern \"%s\" would create a loop" " containing command \"%s\"", pattern, Tcl_DStringValue(&ds))); Tcl_DStringFree(&ds); Tcl_SetErrorCode(interp, "TCL", "IMPORT", "LOOP", (char *)NULL); return TCL_ERROR; } } } dataPtr = (ImportedCmdData *) Tcl_Alloc(sizeof(ImportedCmdData)); importedCmd = Tcl_NRCreateCommand(interp, Tcl_DStringValue(&ds), TclInvokeImportedCmd, InvokeImportedNRCmd, dataPtr, DeleteImportedCmd); dataPtr->realCmdPtr = cmdPtr; /* corresponding decrement is in DeleteImportedCmd */ cmdPtr->refCount++; dataPtr->selfPtr = (Command *) importedCmd; dataPtr->selfPtr->compileProc = cmdPtr->compileProc; Tcl_DStringFree(&ds); /* * Create an ImportRef structure describing this new import command * and add it to the import ref list in the "real" command. */ refPtr = (ImportRef *) Tcl_Alloc(sizeof(ImportRef)); refPtr->importedCmdPtr = (Command *) importedCmd; refPtr->nextPtr = cmdPtr->importRefPtr; cmdPtr->importRefPtr = refPtr; } else { Command *overwrite = (Command *) Tcl_GetHashValue(found); if (overwrite->deleteProc == DeleteImportedCmd) { ImportedCmdData *dataPtr = (ImportedCmdData *) overwrite->objClientData; if (dataPtr->realCmdPtr == Tcl_GetHashValue(hPtr)) { /* * Repeated import of same command is acceptable. */ return TCL_OK; } } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't import command \"%s\": already exists", cmdName)); Tcl_SetErrorCode(interp, "TCL", "IMPORT", "OVERWRITE", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ForgetImport -- * * Deletes commands previously imported into the namespace indicated. * The by namespacePtr, or the current namespace of interp, when * namespacePtr is NULL. The pattern controls which imported commands are * deleted. A simple pattern, one without namespace separators, matches * the current command names of imported commands in the namespace. * Matching imported commands are deleted. A qualified pattern is * interpreted as deletion selection on the basis of where the command is * imported from. The original command and "first link" command for each * imported command are determined, and they are matched against the * pattern. A match leads to deletion of the imported command. * * Results: * Returns TCL_ERROR and records an error message in the interp result if * a namespace qualified pattern refers to a namespace that does not * exist. Otherwise, returns TCL_OK. * * Side effects: * May delete commands. * *---------------------------------------------------------------------- */ int Tcl_ForgetImport( Tcl_Interp *interp, /* Current interpreter. */ Tcl_Namespace *namespacePtr,/* Points to the namespace from which * previously imported commands should be * removed. NULL for current namespace. */ const char *pattern) /* String pattern indicating which imported * commands to remove. */ { Namespace *nsPtr, *sourceNsPtr, *dummyPtr; const char *simplePattern; char *cmdName; Tcl_HashEntry *hPtr; Tcl_HashSearch search; /* * If the specified namespace is NULL, use the current namespace. */ if (namespacePtr == NULL) { nsPtr = (Namespace *) TclGetCurrentNamespace(interp); } else { nsPtr = (Namespace *) namespacePtr; } /* * Parse the pattern into its namespace-qualification (if any) and the * simple pattern. */ TclGetNamespaceForQualName(interp, pattern, nsPtr, TCL_NAMESPACE_ONLY, &sourceNsPtr, &dummyPtr, &dummyPtr, &simplePattern); if (sourceNsPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown namespace in namespace forget pattern \"%s\"", pattern)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "NAMESPACE", pattern, (char *)NULL); return TCL_ERROR; } if (strcmp(pattern, simplePattern) == 0) { /* * The pattern is simple. Delete any imported commands that match it. */ if (TclMatchIsTrivial(simplePattern)) { hPtr = Tcl_FindHashEntry(&nsPtr->cmdTable, simplePattern); if (hPtr != NULL) { Command *cmdPtr = (Command *) Tcl_GetHashValue(hPtr); if (cmdPtr && (cmdPtr->deleteProc == DeleteImportedCmd)) { Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmdPtr); } } return TCL_OK; } for (hPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search); (hPtr != NULL); hPtr = Tcl_NextHashEntry(&search)) { Command *cmdPtr = (Command *) Tcl_GetHashValue(hPtr); if (cmdPtr->deleteProc != DeleteImportedCmd) { continue; } cmdName = (char *) Tcl_GetHashKey(&nsPtr->cmdTable, hPtr); if (Tcl_StringMatch(cmdName, simplePattern)) { Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmdPtr); } } return TCL_OK; } /* * The pattern was namespace-qualified. */ for (hPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search); (hPtr != NULL); hPtr = Tcl_NextHashEntry(&search)) { Tcl_CmdInfo info; Tcl_Command token = (Tcl_Command) Tcl_GetHashValue(hPtr); Tcl_Command origin = TclGetOriginalCommand(token); if (Tcl_GetCommandInfoFromToken(origin, &info) == 0) { continue; /* Not an imported command. */ } if (info.namespacePtr != (Tcl_Namespace *) sourceNsPtr) { /* * Original not in namespace we're matching. Check the first link * in the import chain. */ Command *cmdPtr = (Command *) token; ImportedCmdData *dataPtr = (ImportedCmdData *) cmdPtr->objClientData; Tcl_Command firstToken = (Tcl_Command) dataPtr->realCmdPtr; if (firstToken == origin) { continue; } Tcl_GetCommandInfoFromToken(firstToken, &info); if (info.namespacePtr != (Tcl_Namespace *) sourceNsPtr) { continue; } origin = firstToken; } if (Tcl_StringMatch(Tcl_GetCommandName(NULL, origin), simplePattern)) { Tcl_DeleteCommandFromToken(interp, token); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclGetOriginalCommand -- * * An imported command is created in an namespace when a "real" command * is imported from another namespace. If the specified command is an * imported command, this function returns the original command it refers * to. * * Results: * If the command was imported into a sequence of namespaces a, b,...,n * where each successive namespace just imports the command from the * previous namespace, this function returns the Tcl_Command token in the * first namespace, a. Otherwise, if the specified command is not an * imported command, the function returns NULL. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Command TclGetOriginalCommand( Tcl_Command command) /* The imported command for which the original * command should be returned. */ { Command *cmdPtr = (Command *) command; if (cmdPtr->deleteProc != DeleteImportedCmd) { return NULL; } while (cmdPtr->deleteProc == DeleteImportedCmd) { ImportedCmdData *dataPtr = (ImportedCmdData *) cmdPtr->objClientData; cmdPtr = dataPtr->realCmdPtr; } return (Tcl_Command) cmdPtr; } /* *---------------------------------------------------------------------- * * TclInvokeImportedCmd -- * * Invoked by Tcl whenever the user calls an imported command that was * created by Tcl_Import. Finds the "real" command (in another * namespace), and passes control to it. * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Returns a result in the interpreter's result object. If anything goes * wrong, the result object is set to an error message. * *---------------------------------------------------------------------- */ static int InvokeImportedNRCmd( void *clientData, /* Points to the imported command's * ImportedCmdData structure. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { ImportedCmdData *dataPtr = (ImportedCmdData *) clientData; Command *realCmdPtr = dataPtr->realCmdPtr; TclSkipTailcall(interp); return TclNREvalObjv(interp, objc, objv, TCL_EVAL_NOERR, realCmdPtr); } int TclInvokeImportedCmd( void *clientData, /* Points to the imported command's * ImportedCmdData structure. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { return Tcl_NRCallObjProc(interp, InvokeImportedNRCmd, clientData, objc, objv); } /* *---------------------------------------------------------------------- * * DeleteImportedCmd -- * * Invoked by Tcl whenever an imported command is deleted. The "real" * command keeps a list of all the imported commands that refer to it, so * those imported commands can be deleted when the real command is * deleted. This function removes the imported command reference from the * real command's list, and frees up the memory associated with the * imported command. * * Results: * None. * * Side effects: * Removes the imported command from the real command's import list. * *---------------------------------------------------------------------- */ static void DeleteImportedCmd( void *clientData) /* Points to the imported command's * ImportedCmdData structure. */ { ImportedCmdData *dataPtr = (ImportedCmdData *) clientData; Command *realCmdPtr = dataPtr->realCmdPtr; Command *selfPtr = dataPtr->selfPtr; ImportRef *refPtr, *prevPtr; prevPtr = NULL; for (refPtr = realCmdPtr->importRefPtr; refPtr != NULL; refPtr = refPtr->nextPtr) { if (refPtr->importedCmdPtr == selfPtr) { /* * Remove *refPtr from real command's list of imported commands * that refer to it. */ if (prevPtr == NULL) { /* refPtr is first in list. */ realCmdPtr->importRefPtr = refPtr->nextPtr; } else { prevPtr->nextPtr = refPtr->nextPtr; } Tcl_Free(refPtr); TclCleanupCommandMacro(realCmdPtr); Tcl_Free(dataPtr); return; } prevPtr = refPtr; } Tcl_Panic("DeleteImportedCmd: did not find cmd in real cmd's list of import references"); } /* *---------------------------------------------------------------------- * * TclGetNamespaceForQualName -- * * Given a qualified name specifying a command, variable, or namespace, * and a namespace in which to resolve the name, this function returns a * pointer to the namespace that contains the item. A qualified name * consists of the "simple" name of an item qualified by the names of an * arbitrary number of containing namespace separated by "::"s. If the * qualified name starts with "::", it is interpreted absolutely from the * global namespace. Otherwise, it is interpreted relative to the * namespace specified by cxtNsPtr if it is non-NULL. If cxtNsPtr is * NULL, the name is interpreted relative to the current namespace. * * A relative name like "foo::bar::x" can be found starting in either the * current namespace or in the global namespace. So each search usually * follows two tracks, and two possible namespaces are returned. If the * function sets either *nsPtrPtr or *altNsPtrPtr to NULL, then that path * failed. * * If "flags" contains TCL_GLOBAL_ONLY, the relative qualified name is * sought only in the global :: namespace. The alternate search (also) * starting from the global namespace is ignored and *altNsPtrPtr is set * NULL. * * If "flags" contains TCL_NAMESPACE_ONLY, the relative qualified name is * sought only in the namespace specified by cxtNsPtr. The alternate * search starting from the global namespace is ignored and *altNsPtrPtr * is set NULL. If both TCL_GLOBAL_ONLY and TCL_NAMESPACE_ONLY are * specified, TCL_GLOBAL_ONLY is ignored and the search starts from the * namespace specified by cxtNsPtr. * * If "flags" contains TCL_CREATE_NS_IF_UNKNOWN, all namespace components * of the qualified name that cannot be found are automatically created * within their specified parent. This makes sure that functions like * Tcl_CreateObjCommand always succeed. There is no alternate search path, * so *altNsPtrPtr is set NULL. * * If "flags" contains TCL_FIND_ONLY_NS, the qualified name is treated as * a reference to a namespace, and the entire qualified name is followed. * If the name is relative, the namespace is looked up only in the * current namespace. A pointer to the namespace is stored in *nsPtrPtr * and NULL is stored in *simpleNamePtr. Otherwise, if TCL_FIND_ONLY_NS * is not specified, only the leading components are treated as namespace * names, and a pointer to the simple name of the final component is * stored in *simpleNamePtr. * * Results: * It sets *nsPtrPtr and *altNsPtrPtr to point to the two possible * namespaces which represent the last (containing) namespace in the * qualified name. If the function sets either *nsPtrPtr or *altNsPtrPtr * to NULL, then the search along that path failed. The function also * stores a pointer to the simple name of the final component in * *simpleNamePtr. If the qualified name is "::" or was treated as a * namespace reference (TCL_FIND_ONLY_NS), the function stores a pointer * to the namespace in *nsPtrPtr, NULL in *altNsPtrPtr, and sets * *simpleNamePtr to point to an empty string. * * If there is an error, this function returns TCL_ERROR. If "flags" * contains TCL_LEAVE_ERR_MSG, an error message is returned in the * interpreter's result object. Otherwise, the interpreter's result * object is left unchanged. * * *actualCxtPtrPtr is set to the actual context namespace. It is set to * the input context namespace pointer in cxtNsPtr. If cxtNsPtr is NULL, * it is set to the current namespace context. * * For backwards compatibility with the TclPro byte code loader, this * function always returns TCL_OK. * * Side effects: * If "flags" contains TCL_CREATE_NS_IF_UNKNOWN, new namespaces may be * created. * *---------------------------------------------------------------------- */ int TclGetNamespaceForQualName( Tcl_Interp *interp, /* Interpreter in which to find the namespace * containing qualName. */ const char *qualName, /* A namespace-qualified name of an command, * variable, or namespace. */ Namespace *cxtNsPtr, /* The namespace in which to start the search * for qualName's namespace. If NULL start * from the current namespace. Ignored if * TCL_GLOBAL_ONLY is set. */ int flags, /* Flags controlling the search: an OR'd * combination of TCL_GLOBAL_ONLY, * TCL_NAMESPACE_ONLY, TCL_FIND_ONLY_NS, and * TCL_CREATE_NS_IF_UNKNOWN. */ Namespace **nsPtrPtr, /* Address where function stores a pointer to * containing namespace if qualName is found * starting from *cxtNsPtr or, if * TCL_GLOBAL_ONLY is set, if qualName is * found in the global :: namespace. NULL is * stored otherwise. */ Namespace **altNsPtrPtr, /* Address where function stores a pointer to * containing namespace if qualName is found * starting from the global :: namespace. * NULL is stored if qualName isn't found * starting from :: or if the TCL_GLOBAL_ONLY, * TCL_NAMESPACE_ONLY, TCL_FIND_ONLY_NS, * TCL_CREATE_NS_IF_UNKNOWN flag is set. */ Namespace **actualCxtPtrPtr,/* Address where function stores a pointer to * the actual namespace from which the search * started. This is either cxtNsPtr, the :: * namespace if TCL_GLOBAL_ONLY was specified, * or the current namespace if cxtNsPtr was * NULL. */ const char **simpleNamePtr) /* Address where function stores the simple * name at end of the qualName, or NULL if * qualName is "::" or the flag * TCL_FIND_ONLY_NS was specified. */ { Interp *iPtr = (Interp *) interp; Namespace *nsPtr = cxtNsPtr, *lastNsPtr = NULL, *lastAltNsPtr = NULL; Namespace *altNsPtr; Namespace *globalNsPtr = iPtr->globalNsPtr; const char *start, *end; const char *nsName; Tcl_HashEntry *entryPtr; Tcl_DString buffer; int len; /* * Determine the context namespace nsPtr in which to start the primary * search. If the qualName name starts with a "::" or TCL_GLOBAL_ONLY was * specified, search from the global namespace. Otherwise, use the * namespace given in cxtNsPtr, or if that is NULL, use the current * namespace context. Note that we always treat two or more adjacent ":"s * as a namespace separator. */ if (flags & TCL_GLOBAL_ONLY) { nsPtr = globalNsPtr; } else if (nsPtr == NULL) { nsPtr = iPtr->varFramePtr->nsPtr; } start = qualName; /* Points to start of qualifying * namespace. */ if ((qualName[0] == ':') && (qualName[1] == ':')) { start = qualName + 2; /* Skip over the initial :: */ while (start[0] == ':') { start++; /* Skip over a subsequent : */ } nsPtr = globalNsPtr; if (start[0] == '\0') { /* qualName is just two or more * ":"s. */ *nsPtrPtr = globalNsPtr; *altNsPtrPtr = NULL; *actualCxtPtrPtr = globalNsPtr; *simpleNamePtr = start; /* Points to empty string. */ return TCL_OK; } } *actualCxtPtrPtr = nsPtr; /* * Start an alternate search path starting with the global namespace. * However, if the starting context is the global namespace, or if the * flag is set to search only the namespace *cxtNsPtr, ignore the * alternate search path. */ altNsPtr = globalNsPtr; if ((nsPtr == globalNsPtr) || (flags & (TCL_NAMESPACE_ONLY | TCL_FIND_ONLY_NS))) { altNsPtr = NULL; } /* * Loop to resolve each namespace qualifier in qualName. */ Tcl_DStringInit(&buffer); end = start; while (*start != '\0') { /* * Find the next namespace qualifier (i.e., a name ending in "::") or * the end of the qualified name (i.e., a name ending in "\0"). Set * len to the number of characters, starting from start, in the name; * set end to point after the "::"s or at the "\0". */ len = 0; for (end = start; *end != '\0'; end++) { if ((end[0] == ':') && (end[1] == ':')) { end += 2; /* Skip over the initial :: */ while (*end == ':') { end++; /* Skip over the subsequent : */ } break; /* Exit for loop; end is after ::'s */ } len++; } if (end[0]=='\0' && !(end-start>=2 && end[-1]==':' && end[-2]==':')) { /* * qualName ended with a simple name at start. If TCL_FIND_ONLY_NS * was specified, look this up as a namespace. Otherwise, start is * the name of a cmd or var and we are done. */ if (flags & TCL_FIND_ONLY_NS) { nsName = start; } else { *simpleNamePtr = start; goto done; } } else { /* * start points to the beginning of a namespace qualifier ending * in "::". end points to the start of a name in that namespace * that might be empty. Copy the namespace qualifier to a buffer * so it can be null terminated. We can't modify the incoming * qualName since it may be a string constant. */ TclDStringClear(&buffer); Tcl_DStringAppend(&buffer, start, len); nsName = Tcl_DStringValue(&buffer); } /* * Look up the namespace qualifier nsName in the current namespace * context. If it isn't found but TCL_CREATE_NS_IF_UNKNOWN is set, * create that qualifying namespace. This is needed for functions like * Tcl_CreateObjCommand that cannot fail. */ if (nsPtr != NULL) { entryPtr = FindChildEntry(nsPtr, nsName); if (entryPtr != NULL) { nsPtr = (Namespace *) Tcl_GetHashValue(entryPtr); } else if (flags & TCL_CREATE_NS_IF_UNKNOWN) { Tcl_CallFrame *framePtr; (void) TclPushStackFrame(interp, &framePtr, (Tcl_Namespace *) nsPtr, /*isProcCallFrame*/ 0); nsPtr = (Namespace *) Tcl_CreateNamespace(interp, nsName, NULL, NULL); TclPopStackFrame(interp); if (nsPtr == NULL) { Tcl_Panic("Could not create namespace '%s'", nsName); } } else { /* * Namespace not found and was not created. * Remember last found namespace for TCL_FIND_IF_NOT_SIMPLE. */ lastNsPtr = nsPtr; nsPtr = NULL; } } /* * Look up the namespace qualifier in the alternate search path too. */ if (altNsPtr != NULL) { entryPtr = FindChildEntry(altNsPtr, nsName); if (entryPtr != NULL) { altNsPtr = (Namespace *) Tcl_GetHashValue(entryPtr); } else { /* Remember last found in alternate path */ lastAltNsPtr = altNsPtr; altNsPtr = NULL; } } /* * If both search paths have failed, return NULL results. */ if ((nsPtr == NULL) && (altNsPtr == NULL)) { if (flags & TCL_FIND_IF_NOT_SIMPLE) { /* * return last found NS, regardless simple name or not, * e. g. ::A::B::C::D -> ::A::B and C::D, if namespace C * cannot be found in ::A::B */ nsPtr = lastNsPtr; altNsPtr = lastAltNsPtr; *simpleNamePtr = start; goto done; } *simpleNamePtr = NULL; goto done; } start = end; } /* * We ignore trailing "::"s in a namespace name, but in a command or * variable name, trailing "::"s refer to the cmd or var named {}. */ if ((flags & TCL_FIND_ONLY_NS) || (end>start && end[-1]!=':')) { *simpleNamePtr = NULL; /* Found namespace name. */ } else { *simpleNamePtr = end; /* Found cmd/var: points to empty * string. */ } /* * As a special case, if we are looking for a namespace and qualName is "" * and the current active namespace (nsPtr) is not the global namespace, * return NULL (no namespace was found). This is because namespaces can * not have empty names except for the global namespace. */ if ((flags & TCL_FIND_ONLY_NS) && (*qualName == '\0') && (nsPtr != globalNsPtr)) { nsPtr = NULL; } done: *nsPtrPtr = nsPtr; *altNsPtrPtr = altNsPtr; Tcl_DStringFree(&buffer); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclEnsureNamespace -- * * Provide a namespace that is not deleted. * * Value * * namespacePtr, if it is not scheduled for deletion, or a pointer to a * new namespace with the same name otherwise. * * Effect * None. * *---------------------------------------------------------------------- */ Tcl_Namespace * TclEnsureNamespace( Tcl_Interp *interp, Tcl_Namespace *namespacePtr) { Namespace *nsPtr = (Namespace *) namespacePtr; if (!(nsPtr->flags & NS_DYING)) { return namespacePtr; } return Tcl_CreateNamespace(interp, nsPtr->fullName, NULL, NULL); } /* *---------------------------------------------------------------------- * * Tcl_FindNamespace -- * * Searches for a namespace. * * Results: * Returns a pointer to the namespace if it is found. Otherwise, returns * NULL and leaves an error message in the interpreter's result object if * "flags" contains TCL_LEAVE_ERR_MSG. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Namespace * Tcl_FindNamespace( Tcl_Interp *interp, /* The interpreter in which to find the * namespace. */ const char *name, /* Namespace name. If it starts with "::", * will be looked up in global namespace. * Else, looked up first in contextNsPtr * (current namespace if contextNsPtr is * NULL), then in global namespace. */ Tcl_Namespace *contextNsPtr,/* Ignored if TCL_GLOBAL_ONLY flag is set or * if the name starts with "::". Otherwise, * points to namespace in which to resolve * name; if NULL, look up name in the current * namespace. */ int flags) /* Flags controlling namespace lookup: an OR'd * combination of TCL_GLOBAL_ONLY and * TCL_LEAVE_ERR_MSG flags. */ { Namespace *nsPtr, *dummy1Ptr, *dummy2Ptr; const char *dummy; /* * Find the namespace(s) that contain the specified namespace name. Add * the TCL_FIND_ONLY_NS flag to resolve the name all the way down to its * last component, a namespace. */ TclGetNamespaceForQualName(interp, name, (Namespace *) contextNsPtr, flags|TCL_FIND_ONLY_NS, &nsPtr, &dummy1Ptr, &dummy2Ptr, &dummy); if (nsPtr != NULL) { return (Tcl_Namespace *) nsPtr; } if (flags & TCL_LEAVE_ERR_MSG) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown namespace \"%s\"", name)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "NAMESPACE", name, (char *)NULL); } return NULL; } /* *---------------------------------------------------------------------- * * Tcl_FindCommand -- * * Searches for a command. * * Results: * Returns a token for the command if it is found. Otherwise, if it can't * be found or there is an error, returns NULL and leaves an error * message in the interpreter's result object if "flags" contains * TCL_LEAVE_ERR_MSG. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Command Tcl_FindCommand( Tcl_Interp *interp, /* The interpreter in which to find the * command and to report errors. */ const char *name, /* Command's name. If it starts with "::", * will be looked up in global namespace. * Else, looked up first in contextNsPtr * (current namespace if contextNsPtr is * NULL), then in global namespace. */ Tcl_Namespace *contextNsPtr,/* Ignored if TCL_GLOBAL_ONLY flag set. * Otherwise, points to namespace in which to * resolve name. If NULL, look up name in the * current namespace. */ int flags) /* An OR'd combination of flags: * TCL_GLOBAL_ONLY (look up name only in * global namespace), TCL_NAMESPACE_ONLY (look * up only in contextNsPtr, or the current * namespace if contextNsPtr is NULL), and * TCL_LEAVE_ERR_MSG. If both TCL_GLOBAL_ONLY * and TCL_NAMESPACE_ONLY are given, * TCL_GLOBAL_ONLY is ignored. */ { Interp *iPtr = (Interp *) interp; Namespace *cxtNsPtr; Tcl_HashEntry *entryPtr; Command *cmdPtr; const char *simpleName; int result; /* * If this namespace has a command resolver, then give it first crack at * the command resolution. If the interpreter has any command resolvers, * consult them next. The command resolver functions may return a * Tcl_Command value, they may signal to continue onward, or they may * signal an error. */ if ((flags & TCL_GLOBAL_ONLY) || !strncmp(name, "::", 2)) { cxtNsPtr = (Namespace *) TclGetGlobalNamespace(interp); } else if (contextNsPtr != NULL) { cxtNsPtr = (Namespace *) contextNsPtr; } else { cxtNsPtr = (Namespace *) TclGetCurrentNamespace(interp); } if (cxtNsPtr->cmdResProc != NULL || iPtr->resolverPtr != NULL) { ResolverScheme *resPtr = iPtr->resolverPtr; Tcl_Command cmd; if (cxtNsPtr->cmdResProc) { result = cxtNsPtr->cmdResProc(interp, name, (Tcl_Namespace *) cxtNsPtr, flags, &cmd); } else { result = TCL_CONTINUE; } while (result == TCL_CONTINUE && resPtr) { if (resPtr->cmdResProc) { result = resPtr->cmdResProc(interp, name, (Tcl_Namespace *) cxtNsPtr, flags, &cmd); } resPtr = resPtr->nextPtr; } if (result == TCL_OK) { ((Command *) cmd)->flags |= CMD_VIA_RESOLVER; return cmd; } else if (result != TCL_CONTINUE) { return NULL; } } /* * Find the namespace(s) that contain the command. */ cmdPtr = NULL; if (cxtNsPtr->commandPathLength!=0 && strncmp(name, "::", 2) && !(flags & TCL_NAMESPACE_ONLY)) { Tcl_Size i; Namespace *pathNsPtr, *realNsPtr, *dummyNsPtr; (void) TclGetNamespaceForQualName(interp, name, cxtNsPtr, TCL_NAMESPACE_ONLY, &realNsPtr, &dummyNsPtr, &dummyNsPtr, &simpleName); if ((realNsPtr != NULL) && (simpleName != NULL)) { if ((cxtNsPtr == realNsPtr) || !(realNsPtr->flags & NS_DEAD)) { entryPtr = Tcl_FindHashEntry(&realNsPtr->cmdTable, simpleName); if (entryPtr != NULL) { cmdPtr = (Command *) Tcl_GetHashValue(entryPtr); } } } /* * Next, check along the path. */ for (i=0 ; (cmdPtr == NULL) && icommandPathLength ; i++) { pathNsPtr = cxtNsPtr->commandPathArray[i].nsPtr; if (pathNsPtr == NULL) { continue; } (void) TclGetNamespaceForQualName(interp, name, pathNsPtr, TCL_NAMESPACE_ONLY, &realNsPtr, &dummyNsPtr, &dummyNsPtr, &simpleName); if ((realNsPtr != NULL) && (simpleName != NULL) && !(realNsPtr->flags & NS_DEAD)) { entryPtr = Tcl_FindHashEntry(&realNsPtr->cmdTable, simpleName); if (entryPtr != NULL) { cmdPtr = (Command *) Tcl_GetHashValue(entryPtr); } } } /* * If we've still not found the command, look in the global namespace * as a last resort. */ if (cmdPtr == NULL) { (void) TclGetNamespaceForQualName(interp, name, NULL, TCL_GLOBAL_ONLY, &realNsPtr, &dummyNsPtr, &dummyNsPtr, &simpleName); if ((realNsPtr != NULL) && (simpleName != NULL) && !(realNsPtr->flags & NS_DEAD)) { entryPtr = Tcl_FindHashEntry(&realNsPtr->cmdTable, simpleName); if (entryPtr != NULL) { cmdPtr = (Command *) Tcl_GetHashValue(entryPtr); } } } } else { Namespace *nsPtr[2]; int search; TclGetNamespaceForQualName(interp, name, cxtNsPtr, flags, &nsPtr[0], &nsPtr[1], &cxtNsPtr, &simpleName); /* * Look for the command in the command table of its namespace. Be sure * to check both possible search paths: from the specified namespace * context and from the global namespace. */ for (search = 0; (search < 2) && (cmdPtr == NULL); search++) { if ((nsPtr[search] != NULL) && (simpleName != NULL)) { entryPtr = Tcl_FindHashEntry(&nsPtr[search]->cmdTable, simpleName); if (entryPtr != NULL) { cmdPtr = (Command *) Tcl_GetHashValue(entryPtr); } } } } if (cmdPtr != NULL) { cmdPtr->flags &= ~CMD_VIA_RESOLVER; return (Tcl_Command) cmdPtr; } if (flags & TCL_LEAVE_ERR_MSG) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown command \"%s\"", name)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COMMAND", name, (char *)NULL); } return NULL; } /* *---------------------------------------------------------------------- * * TclResetShadowedCmdRefs -- * * Called when a command is added to a namespace to check for existing * command references that the new command may invalidate. Consider the * following cases that could happen when you add a command "foo" to a * namespace "b": * 1. It could shadow a command named "foo" at the global scope. If * it does, all command references in the namespace "b" are * suspect. * 2. Suppose the namespace "b" resides in a namespace "a". Then to * "a" the new command "b::foo" could shadow another command * "b::foo" in the global namespace. If so, then all command * references in "a" * are suspect. * The same checks are applied to all parent namespaces, until we reach * the global :: namespace. * * Results: * None. * * Side effects: * If the new command shadows an existing command, the cmdRefEpoch * counter is incremented in each namespace that sees the shadow. This * invalidates all command references that were previously cached in that * namespace. The next time the commands are used, they are resolved from * scratch. * *---------------------------------------------------------------------- */ void TclResetShadowedCmdRefs( Tcl_Interp *interp, /* Interpreter containing the new command. */ Command *newCmdPtr) /* Points to the new command. */ { char *cmdName; Tcl_HashEntry *hPtr; Namespace *nsPtr; Namespace *trailNsPtr, *shadowNsPtr; Namespace *globalNsPtr = (Namespace *) TclGetGlobalNamespace(interp); int found, i; int trailFront = -1; int trailSize = 5; /* Formerly NUM_TRAIL_ELEMS. */ Namespace **trailPtr = (Namespace **) TclStackAlloc(interp, trailSize * sizeof(Namespace *)); /* * Start at the namespace containing the new command, and work up through * the list of parents. Stop just before the global namespace, since the * global namespace can't "shadow" its own entries. * * The namespace "trail" list we build consists of the names of each * namespace that encloses the new command, in order from outermost to * innermost: for example, "a" then "b". Each iteration of this loop * eventually extends the trail upwards by one namespace, nsPtr. We use * this trail list to see if nsPtr (e.g. "a" in 2. above) could have * now-invalid cached command references. This will happen if nsPtr * (e.g. "a") contains a sequence of child namespaces (e.g. "b") such that * there is a identically-named sequence of child namespaces starting from * :: (e.g. "::b") whose tail namespace contains a command also named * cmdName. */ cmdName = (char *) Tcl_GetHashKey(newCmdPtr->hPtr->tablePtr, newCmdPtr->hPtr); for (nsPtr=newCmdPtr->nsPtr ; (nsPtr!=NULL) && (nsPtr!=globalNsPtr) ; nsPtr=nsPtr->parentPtr) { /* * Find the maximal sequence of child namespaces contained in nsPtr * such that there is a identically-named sequence of child namespaces * starting from ::. shadowNsPtr will be the tail of this sequence, or * the deepest namespace under :: that might contain a command now * shadowed by cmdName. We check below if shadowNsPtr actually * contains a command cmdName. */ found = 1; shadowNsPtr = globalNsPtr; for (i = trailFront; i >= 0; i--) { trailNsPtr = trailPtr[i]; hPtr = FindChildEntry(shadowNsPtr, trailNsPtr->name); if (hPtr != NULL) { shadowNsPtr = (Namespace *) Tcl_GetHashValue(hPtr); } else { found = 0; break; } } /* * If shadowNsPtr contains a command named cmdName, we invalidate all * of the command refs cached in nsPtr. As a boundary case, * shadowNsPtr is initially :: and we check for case 1. above. */ if (found) { hPtr = Tcl_FindHashEntry(&shadowNsPtr->cmdTable, cmdName); if (hPtr != NULL) { nsPtr->cmdRefEpoch++; TclInvalidateNsPath(nsPtr); /* * If the shadowed command was compiled to bytecodes, we * invalidate all the bytecodes in nsPtr, to force a new * compilation. We use the resolverEpoch to signal the need * for a fresh compilation of every bytecode. */ if (((Command *) Tcl_GetHashValue(hPtr))->compileProc != NULL) { nsPtr->resolverEpoch++; } } } /* * Insert nsPtr at the front of the trail list: i.e., at the end of * the trailPtr array. */ trailFront++; if (trailFront == trailSize) { int newSize = 2 * trailSize; trailPtr = (Namespace **) TclStackRealloc(interp, trailPtr, newSize * sizeof(Namespace *)); trailSize = newSize; } trailPtr[trailFront] = nsPtr; } TclStackFree(interp, trailPtr); } /* *---------------------------------------------------------------------- * * TclGetNamespaceFromObj, GetNamespaceFromObj -- * * Gets the namespace specified by the name in a Tcl_Obj. * * Results: * Returns TCL_OK if the namespace was resolved successfully, and stores * a pointer to the namespace in the location specified by nsPtrPtr. If * the namespace can't be found, or anything else goes wrong, this * function returns TCL_ERROR and writes an error message to interp, * if non-NULL. * * Side effects: * May update the internal representation for the object, caching the * namespace reference. The next time this function is called, the * namespace value can be found quickly. * *---------------------------------------------------------------------- */ int TclGetNamespaceFromObj( Tcl_Interp *interp, /* The current interpreter. */ Tcl_Obj *objPtr, /* The object to be resolved as the name of a * namespace. */ Tcl_Namespace **nsPtrPtr) /* Result namespace pointer goes here. */ { if (GetNamespaceFromObj(interp, objPtr, nsPtrPtr) == TCL_ERROR) { const char *name = TclGetString(objPtr); if ((name[0] == ':') && (name[1] == ':')) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "namespace \"%s\" not found", name)); } else { /* * Get the current namespace name. */ NamespaceCurrentCmd(NULL, interp, 1, NULL); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "namespace \"%s\" not found in \"%s\"", name, Tcl_GetStringResult(interp))); } Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "NAMESPACE", name, (char *)NULL); return TCL_ERROR; } return TCL_OK; } static int GetNamespaceFromObj( Tcl_Interp *interp, /* The current interpreter. */ Tcl_Obj *objPtr, /* The object to be resolved as the name of a * namespace. */ Tcl_Namespace **nsPtrPtr) /* Result namespace pointer goes here. */ { ResolvedNsName *resNamePtr; NsNameGetInternalRep(objPtr, resNamePtr); if (resNamePtr) { Namespace *nsPtr, *refNsPtr; /* * Check that the ResolvedNsName is still valid; avoid letting the ref * cross interps. */ nsPtr = resNamePtr->nsPtr; refNsPtr = resNamePtr->refNsPtr; if (!(nsPtr->flags & NS_DYING) && (interp == nsPtr->interp) && (!refNsPtr || (refNsPtr == (Namespace *) TclGetCurrentNamespace(interp)))) { *nsPtrPtr = (Tcl_Namespace *) nsPtr; return TCL_OK; } Tcl_StoreInternalRep(objPtr, &nsNameType, NULL); } if (SetNsNameFromAny(interp, objPtr) == TCL_OK) { NsNameGetInternalRep(objPtr, resNamePtr); assert(resNamePtr != NULL); *nsPtrPtr = (Tcl_Namespace *) resNamePtr->nsPtr; return TCL_OK; } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TclNewNamespaceObj -- * * Gets an object that contains a reference to a given namespace. * * Note that this gets the name of the namespace immediately; this means * that the name is guaranteed to persist even if the namespace is * deleted. (This is checked by test namespace-7.1.) * * Results: * Returns a newly-allocated Tcl_Obj. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj * TclNewNamespaceObj( Tcl_Namespace *namespacePtr) { Namespace *nsPtr = (Namespace *) namespacePtr; Tcl_Size len; Tcl_Obj *objPtr; /* * If NS_DEAD set, we have no name any more; the fullName field may have * been deallocated. */ assert(!(nsPtr->flags & NS_DEAD)); /* * Need to get the name pro-actively; the name must persist after the * namespace is deleted. This is the easiest way. */ len = strlen(nsPtr->fullName); TclNewStringObj(objPtr, nsPtr->fullName, len); /* * But we know exactly which namespace this resolves to. Remember that * unless things are already being taken apart. */ if (!(nsPtr->flags & (NS_DYING | NS_TEARDOWN))) { ResolvedNsName *resNamePtr = (ResolvedNsName *) Tcl_Alloc(sizeof(ResolvedNsName)); resNamePtr->nsPtr = nsPtr; resNamePtr->refNsPtr = NULL; resNamePtr->refCount = 0; nsPtr->refCount++; NsNameSetInternalRep(objPtr, resNamePtr); } return objPtr; } /* *---------------------------------------------------------------------- * * TclInitNamespaceCmd -- * * This function is called to create the "namespace" Tcl command. See the * user documentation for details on what it does. * * Results: * Handle for the namespace command, or NULL on failure. * * Side effects: * none * *---------------------------------------------------------------------- */ Tcl_Command TclInitNamespaceCmd( Tcl_Interp *interp) /* Current interpreter. */ { return TclMakeEnsemble(interp, "namespace", defaultNamespaceMap); } /* *---------------------------------------------------------------------- * * NamespaceChildrenCmd -- * * Invoked to implement the "namespace children" command that returns a * list containing the fully-qualified names of the child namespaces of a * given namespace. Handles the following syntax: * * namespace children ?name? ?pattern? * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Returns a result in the interpreter's result object. If anything goes * wrong, the result is an error message. * *---------------------------------------------------------------------- */ static int NamespaceChildrenCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Namespace *namespacePtr; Namespace *nsPtr, *childNsPtr; Namespace *globalNsPtr = (Namespace *) TclGetGlobalNamespace(interp); const char *pattern = NULL; Tcl_DString buffer; Tcl_HashEntry *entryPtr; Tcl_HashSearch search; Tcl_Obj *listPtr; /* * Get a pointer to the specified namespace, or the current namespace. */ if (objc == 1) { nsPtr = (Namespace *) TclGetCurrentNamespace(interp); } else if ((objc == 2) || (objc == 3)) { if (TclGetNamespaceFromObj(interp, objv[1], &namespacePtr) != TCL_OK) { return TCL_ERROR; } nsPtr = (Namespace *) namespacePtr; } else { Tcl_WrongNumArgs(interp, 1, objv, "?name? ?pattern?"); return TCL_ERROR; } /* * Get the glob-style pattern, if any, used to narrow the search. */ Tcl_DStringInit(&buffer); if (objc == 3) { const char *name = TclGetString(objv[2]); if ((name[0] == ':') && (name[1] == ':')) { pattern = name; } else { Tcl_DStringAppend(&buffer, nsPtr->fullName, -1); if (nsPtr != globalNsPtr) { TclDStringAppendLiteral(&buffer, "::"); } Tcl_DStringAppend(&buffer, name, -1); pattern = Tcl_DStringValue(&buffer); } } /* * Create a list containing the full names of all child namespaces whose * names match the specified pattern, if any. */ listPtr = Tcl_NewListObj(0, NULL); if ((pattern != NULL) && TclMatchIsTrivial(pattern)) { size_t length = strlen(nsPtr->fullName); if (strncmp(pattern, nsPtr->fullName, length) != 0) { goto searchDone; } /* * Global namespace members are prefixed with "::", others not. Ticket [63449c0514] */ if (FindChildEntry(nsPtr, (nsPtr != globalNsPtr ? 2 : 0) + pattern+length) != NULL) { Tcl_ListObjAppendElement(NULL, listPtr, Tcl_NewStringObj(pattern, -1)); } goto searchDone; } entryPtr = FirstChildEntry(nsPtr, &search); while (entryPtr != NULL) { childNsPtr = (Namespace *) Tcl_GetHashValue(entryPtr); if ((pattern == NULL) || Tcl_StringMatch(childNsPtr->fullName, pattern)) { Tcl_ListObjAppendElement(NULL, listPtr, TclNewNamespaceObj((Tcl_Namespace *) childNsPtr)); } entryPtr = Tcl_NextHashEntry(&search); } searchDone: Tcl_SetObjResult(interp, listPtr); Tcl_DStringFree(&buffer); return TCL_OK; } /* *---------------------------------------------------------------------- * * NamespaceCodeCmd -- * * Invoked to implement the "namespace code" command to capture the * namespace context of a command. Handles the following syntax: * * namespace code arg * * Here "arg" can be a list. "namespace code arg" produces a result * equivalent to that produced by the command * * list ::namespace inscope [namespace current] $arg * * However, if "arg" is itself a scoped value starting with "::namespace * inscope", then the result is just "arg". * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * If anything goes wrong, this function returns an error message as the * result in the interpreter's result object. * *---------------------------------------------------------------------- */ static int NamespaceCodeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *listPtr, *objPtr; const char *arg; Tcl_Size length; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "arg"); return TCL_ERROR; } /* * If "arg" is already a scoped value, then return it directly. * Take care to only check for scoping in precisely the style that * [::namespace code] generates it. Anything more forgiving can have * the effect of failing in namespaces that contain their own custom " "namespace" command. [Bug 3202171]. */ arg = TclGetStringFromObj(objv[1], &length); if (*arg==':' && length > 20 && strncmp(arg, "::namespace inscope ", 20) == 0) { Tcl_SetObjResult(interp, objv[1]); return TCL_OK; } /* * Otherwise, construct a scoped command by building a list with * "namespace inscope", the full name of the current namespace, and the * argument "arg". By constructing a list, we ensure that scoped commands * are interpreted properly when they are executed later, by the * "namespace inscope" command. */ TclNewObj(listPtr); TclNewLiteralStringObj(objPtr, "::namespace"); Tcl_ListObjAppendElement(NULL, listPtr, objPtr); TclNewLiteralStringObj(objPtr, "inscope"); Tcl_ListObjAppendElement(NULL, listPtr, objPtr); Tcl_ListObjAppendElement(NULL, listPtr, TclNewNamespaceObj(TclGetCurrentNamespace(interp))); Tcl_ListObjAppendElement(NULL, listPtr, objv[1]); Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * NamespaceCurrentCmd -- * * Invoked to implement the "namespace current" command which returns the * fully-qualified name of the current namespace. Handles the following * syntax: * * namespace current * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Returns a result in the interpreter's result object. If anything goes * wrong, the result is an error message. * *---------------------------------------------------------------------- */ static int NamespaceCurrentCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } /* * The "real" name of the global namespace ("::") is the null string, but * we return "::" for it as a convenience to programmers. Note that "" and * "::" are treated as synonyms by the namespace code so that it is still * easy to do things like: * * namespace [namespace current]::bar { ... } * * This behavior is encoded into TclNewNamespaceObj(). */ Tcl_SetObjResult(interp, TclNewNamespaceObj(TclGetCurrentNamespace(interp))); return TCL_OK; } /* *---------------------------------------------------------------------- * * NamespaceDeleteCmd -- * * Invoked to implement the "namespace delete" command to delete * namespace(s). Handles the following syntax: * * namespace delete ?name name...? * * Each name identifies a namespace. It may include a sequence of * namespace qualifiers separated by "::"s. If a namespace is found, it * is deleted: all variables and procedures contained in that namespace * are deleted. If that namespace is being used on the call stack, it is * kept alive (but logically deleted) until it is removed from the call * stack: that is, it can no longer be referenced by name but any * currently executing procedure that refers to it is allowed to do so * until the procedure returns. If the namespace can't be found, this * function returns an error. If no namespaces are specified, this * command does nothing. * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Deletes the specified namespaces. If anything goes wrong, this * function returns an error message in the interpreter's result object. * *---------------------------------------------------------------------- */ static int NamespaceDeleteCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Namespace *namespacePtr; const char *name; int i; if (objc < 1) { Tcl_WrongNumArgs(interp, 1, objv, "?name name...?"); return TCL_ERROR; } /* * Destroying one namespace may cause another to be destroyed. Break this * into two passes: first check to make sure that all namespaces on the * command line are valid, and report any errors. */ for (i = 1; i < objc; i++) { name = TclGetString(objv[i]); namespacePtr = Tcl_FindNamespace(interp, name, NULL, /*flags*/ 0); if ((namespacePtr == NULL) || (((Namespace *) namespacePtr)->flags & NS_TEARDOWN)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown namespace \"%s\" in namespace delete command", TclGetString(objv[i]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "NAMESPACE", TclGetString(objv[i]), (char *)NULL); return TCL_ERROR; } } /* * Okay, now delete each namespace. */ for (i = 1; i < objc; i++) { name = TclGetString(objv[i]); namespacePtr = Tcl_FindNamespace(interp, name, NULL, /* flags */ 0); if (namespacePtr) { Tcl_DeleteNamespace(namespacePtr); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * NamespaceEvalCmd -- * * Invoked to implement the "namespace eval" command. Executes commands * in a namespace. If the namespace does not already exist, it is * created. Handles the following syntax: * * namespace eval name arg ?arg...? * * If more than one arg argument is specified, the command that is * executed is the result of concatenating the arguments together with a * space between each argument. * * Results: * Returns TCL_OK if the namespace is found and the commands are executed * successfully. Returns TCL_ERROR if anything goes wrong. * * Side effects: * Returns the result of the command in the interpreter's result object. * If anything goes wrong, this function returns an error message as the * result. * *---------------------------------------------------------------------- */ static int NamespaceEvalCmd( void *clientData, /* Arbitrary value passed to cmd. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, NRNamespaceEvalCmd, clientData, objc, objv); } static int NRNamespaceEvalCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; CmdFrame *invoker; int word; Tcl_Namespace *namespacePtr; CallFrame *framePtr, **framePtrPtr; Tcl_Obj *objPtr; int result; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "name arg ?arg...?"); return TCL_ERROR; } /* * Try to resolve the namespace reference, caching the result in the * namespace object along the way. */ result = GetNamespaceFromObj(interp, objv[1], &namespacePtr); /* * If the namespace wasn't found, try to create it. */ if (result == TCL_ERROR) { const char *name = TclGetString(objv[1]); namespacePtr = Tcl_CreateNamespace(interp, name, NULL, NULL); if (namespacePtr == NULL) { return TCL_ERROR; } } /* * Make the specified namespace the current namespace and evaluate the * command(s). */ /* This is needed to satisfy GCC 3.3's strict aliasing rules */ framePtrPtr = &framePtr; (void) TclPushStackFrame(interp, (Tcl_CallFrame **) framePtrPtr, namespacePtr, /*isProcCallFrame*/ 0); framePtr->objv = TclFetchEnsembleRoot(interp, objv, objc, &framePtr->objc); if (objc == 3) { /* * TIP #280: Make actual argument location available to eval'd script. */ objPtr = objv[2]; invoker = iPtr->cmdFramePtr; word = 3; TclArgumentGet(interp, objPtr, &invoker, &word); } else { /* * More than one argument: concatenate them together with spaces * between, then evaluate the result. Tcl_EvalObjEx will delete the * object when it decrements its refcount after eval'ing it. */ objPtr = Tcl_ConcatObj(objc-2, objv+2); invoker = NULL; word = 0; } /* * TIP #280: Make invoking context available to eval'd script. */ TclNRAddCallback(interp, NsEval_Callback, namespacePtr, "eval", NULL, NULL); return TclNREvalObjEx(interp, objPtr, 0, invoker, word); } static int NsEval_Callback( void *data[], Tcl_Interp *interp, int result) { Tcl_Namespace *namespacePtr = (Tcl_Namespace *) data[0]; if (result == TCL_ERROR) { size_t length = strlen(namespacePtr->fullName); unsigned limit = 200; int overflow = (length > limit); char *cmd = (char *) data[1]; Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (in namespace %s \"%.*s%s\" script line %d)", cmd, (overflow ? limit : (unsigned)length), namespacePtr->fullName, (overflow ? "..." : ""), Tcl_GetErrorLine(interp))); } /* * Restore the previous "current" namespace. */ TclPopStackFrame(interp); return result; } /* *---------------------------------------------------------------------- * * NamespaceExistsCmd -- * * Invoked to implement the "namespace exists" command that returns true * if the given namespace currently exists, and false otherwise. Handles * the following syntax: * * namespace exists name * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Returns a result in the interpreter's result object. If anything goes * wrong, the result is an error message. * *---------------------------------------------------------------------- */ static int NamespaceExistsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Namespace *namespacePtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj( GetNamespaceFromObj(interp, objv[1], &namespacePtr) == TCL_OK)); return TCL_OK; } /* *---------------------------------------------------------------------- * * NamespaceExportCmd -- * * Invoked to implement the "namespace export" command that specifies * which commands are exported from a namespace. The exported commands * are those that can be imported into another namespace using "namespace * import". Both commands defined in a namespace and commands the * namespace has imported can be exported by a namespace. This command * has the following syntax: * * namespace export ?-clear? ?pattern pattern...? * * Each pattern may contain "string match"-style pattern matching special * characters, but the pattern may not include any namespace qualifiers: * that is, the pattern must specify commands in the current (exporting) * namespace. The specified patterns are appended onto the namespace's * list of export patterns. * * To reset the namespace's export pattern list, specify the "-clear" * flag. * * If there are no export patterns and the "-clear" flag isn't given, * this command returns the namespace's current export list. * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Returns a result in the interpreter's result object. If anything goes * wrong, the result is an error message. * *---------------------------------------------------------------------- */ static int NamespaceExportCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int firstArg, i; if (objc < 1) { Tcl_WrongNumArgs(interp, 1, objv, "?-clear? ?pattern pattern...?"); return TCL_ERROR; } /* * If no pattern arguments are given, and "-clear" isn't specified, return * the namespace's current export pattern list. */ if (objc == 1) { Tcl_Obj *listPtr; TclNewObj(listPtr); (void) Tcl_AppendExportList(interp, NULL, listPtr); Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* * Process the optional "-clear" argument. */ firstArg = 1; if (strcmp("-clear", TclGetString(objv[firstArg])) == 0) { Tcl_Export(interp, NULL, "::", 1); Tcl_ResetResult(interp); firstArg++; } /* * Add each pattern to the namespace's export pattern list. */ for (i = firstArg; i < objc; i++) { int result = Tcl_Export(interp, NULL, TclGetString(objv[i]), 0); if (result != TCL_OK) { return result; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * NamespaceForgetCmd -- * * Invoked to implement the "namespace forget" command to remove imported * commands from a namespace. Handles the following syntax: * * namespace forget ?pattern pattern...? * * Each pattern is a name like "foo::*" or "a::b::x*". That is, the * pattern may include the special pattern matching characters recognized * by the "string match" command, but only in the command name at the end * of the qualified name; the special pattern characters may not appear * in a namespace name. All of the commands that match that pattern are * checked to see if they have an imported command in the current * namespace that refers to the matched command. If there is an alias, it * is removed. * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Imported commands are removed from the current namespace. If anything * goes wrong, this function returns an error message in the * interpreter's result object. * *---------------------------------------------------------------------- */ static int NamespaceForgetCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *pattern; int i, result; if (objc < 1) { Tcl_WrongNumArgs(interp, 1, objv, "?pattern pattern...?"); return TCL_ERROR; } for (i = 1; i < objc; i++) { pattern = TclGetString(objv[i]); result = Tcl_ForgetImport(interp, NULL, pattern); if (result != TCL_OK) { return result; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * NamespaceImportCmd -- * * Invoked to implement the "namespace import" command that imports * commands into a namespace. Handles the following syntax: * * namespace import ?-force? ?pattern pattern...? * * Each pattern is a namespace-qualified name like "foo::*", "a::b::x*", * or "bar::p". That is, the pattern may include the special pattern * matching characters recognized by the "string match" command, but only * in the command name at the end of the qualified name; the special * pattern characters may not appear in a namespace name. All of the * commands that match the pattern and which are exported from their * namespace are made accessible from the current namespace context. This * is done by creating a new "imported command" in the current namespace * that points to the real command in its original namespace; when the * imported command is called, it invokes the real command. * * If an imported command conflicts with an existing command, it is * treated as an error. But if the "-force" option is included, then * existing commands are overwritten by the imported commands. * * If there are no pattern arguments and the "-force" flag isn't given, * this command returns the list of commands currently imported in * the current namespace. * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Adds imported commands to the current namespace. If anything goes * wrong, this function returns an error message in the interpreter's * result object. * *---------------------------------------------------------------------- */ static int NamespaceImportCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int allowOverwrite = 0; const char *string, *pattern; int i, result; int firstArg; if (objc < 1) { Tcl_WrongNumArgs(interp, 1, objv, "?-force? ?pattern pattern...?"); return TCL_ERROR; } /* * Skip over the optional "-force" as the first argument. */ firstArg = 1; if (firstArg < objc) { string = TclGetString(objv[firstArg]); if ((*string == '-') && (strcmp(string, "-force") == 0)) { allowOverwrite = 1; firstArg++; } } else { /* * When objc == 1, command is just [namespace import]. Introspection * form to return list of imported commands. */ Tcl_HashEntry *hPtr; Tcl_HashSearch search; Namespace *nsPtr = (Namespace *) TclGetCurrentNamespace(interp); Tcl_Obj *listPtr; TclNewObj(listPtr); for (hPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { Command *cmdPtr = (Command *) Tcl_GetHashValue(hPtr); if (cmdPtr->deleteProc == DeleteImportedCmd) { Tcl_ListObjAppendElement(NULL, listPtr, Tcl_NewStringObj( (char *) Tcl_GetHashKey(&nsPtr->cmdTable, hPtr), -1)); } } Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* * Handle the imports for each of the patterns. */ for (i = firstArg; i < objc; i++) { pattern = TclGetString(objv[i]); result = Tcl_Import(interp, NULL, pattern, allowOverwrite); if (result != TCL_OK) { return result; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * NamespaceInscopeCmd -- * * Invoked to implement the "namespace inscope" command that executes a * script in the context of a particular namespace. This command is not * expected to be used directly by programmers; calls to it are generated * implicitly when programs use "namespace code" commands to register * callback scripts. Handles the following syntax: * * namespace inscope name arg ?arg...? * * The "namespace inscope" command is much like the "namespace eval" * command except that it has lappend semantics and the namespace must * already exist. It treats the first argument as a list, and appends any * arguments after the first onto the end as proper list elements. For * example, * * namespace inscope ::foo {a b} c d e * * is equivalent to * * namespace eval ::foo [concat {a b} [list c d e]] * * This lappend semantics is important because many callback scripts are * actually prefixes. * * Results: * Returns TCL_OK to indicate success, or TCL_ERROR to indicate failure. * * Side effects: * Returns a result in the Tcl interpreter's result object. * *---------------------------------------------------------------------- */ static int NamespaceInscopeCmd( void *clientData, /* Arbitrary value passed to cmd. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, NRNamespaceInscopeCmd, clientData, objc, objv); } static int NRNamespaceInscopeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Namespace *namespacePtr; CallFrame *framePtr, **framePtrPtr; Tcl_Obj *cmdObjPtr; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "name arg ?arg...?"); return TCL_ERROR; } /* * Resolve the namespace reference. */ if (TclGetNamespaceFromObj(interp, objv[1], &namespacePtr) != TCL_OK) { return TCL_ERROR; } /* * Make the specified namespace the current namespace. */ framePtrPtr = &framePtr; /* This is needed to satisfy GCC's * strict aliasing rules. */ (void) TclPushStackFrame(interp, (Tcl_CallFrame **) framePtrPtr, namespacePtr, /*isProcCallFrame*/ 0); framePtr->objv = TclFetchEnsembleRoot(interp, objv, objc, &framePtr->objc); /* * Execute the command. If there is just one argument, just treat it as a * script and evaluate it. Otherwise, create a list from the arguments * after the first one, then concatenate the first argument and the list * of extra arguments to form the command to evaluate. */ if (objc == 3) { cmdObjPtr = objv[2]; } else { Tcl_Obj *concatObjv[2]; concatObjv[0] = objv[2]; concatObjv[1] = Tcl_NewListObj(objc - 3, objv + 3); cmdObjPtr = Tcl_ConcatObj(2, concatObjv); Tcl_DecrRefCount(concatObjv[1]); /* We're done with the list object. */ } TclNRAddCallback(interp, NsEval_Callback, namespacePtr, "inscope", NULL, NULL); return TclNREvalObjEx(interp, cmdObjPtr, 0, NULL, 0); } /* *---------------------------------------------------------------------- * * NamespaceOriginCmd -- * * Invoked to implement the "namespace origin" command to return the * fully-qualified name of the "real" command to which the specified * "imported command" refers. Handles the following syntax: * * namespace origin name * * Results: * An imported command is created in an namespace when that namespace * imports a command from another namespace. If a command is imported * into a sequence of namespaces a, b,...,n where each successive * namespace just imports the command from the previous namespace, this * command returns the fully-qualified name of the original command in * the first namespace, a. If "name" does not refer to an alias, its * fully-qualified name is returned. The returned name is stored in the * interpreter's result object. This function returns TCL_OK if * successful, and TCL_ERROR if anything goes wrong. * * Side effects: * If anything goes wrong, this function returns an error message in the * interpreter's result object. * *---------------------------------------------------------------------- */ static int NamespaceOriginCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Command cmd, origCmd; Tcl_Obj *resultPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "name"); return TCL_ERROR; } cmd = Tcl_GetCommandFromObj(interp, objv[1]); if (cmd == NULL) { goto namespaceOriginError; } origCmd = TclGetOriginalCommand(cmd); if (origCmd == NULL) { origCmd = cmd; } TclNewObj(resultPtr); Tcl_GetCommandFullName(interp, origCmd, resultPtr); if (TclCheckEmptyString(resultPtr) == TCL_EMPTYSTRING_YES) { Tcl_DecrRefCount(resultPtr); goto namespaceOriginError; } Tcl_SetObjResult(interp, resultPtr); return TCL_OK; namespaceOriginError: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid command name \"%s\"", TclGetString(objv[1]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COMMAND", TclGetString(objv[1]), (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * NamespaceParentCmd -- * * Invoked to implement the "namespace parent" command that returns the * fully-qualified name of the parent namespace for a specified * namespace. Handles the following syntax: * * namespace parent ?name? * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Returns a result in the interpreter's result object. If anything goes * wrong, the result is an error message. * *---------------------------------------------------------------------- */ static int NamespaceParentCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Namespace *nsPtr; if (objc == 1) { nsPtr = TclGetCurrentNamespace(interp); } else if (objc == 2) { if (TclGetNamespaceFromObj(interp, objv[1], &nsPtr) != TCL_OK) { return TCL_ERROR; } } else { Tcl_WrongNumArgs(interp, 1, objv, "?name?"); return TCL_ERROR; } /* * Report the parent of the specified namespace. */ if (nsPtr->parentPtr != NULL) { Tcl_SetObjResult(interp, TclNewNamespaceObj(nsPtr->parentPtr)); } return TCL_OK; } /* *---------------------------------------------------------------------- * * NamespacePathCmd -- * * Invoked to implement the "namespace path" command that reads and * writes the current namespace's command resolution path. Has one * optional argument: if present, it is a list of named namespaces to set * the path to, and if absent, the current path should be returned. * Handles the following syntax: * * namespace path ?nsList? * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong * (most notably if the namespace list contains the name of something * other than a namespace). In the successful-exit case, may set the * interpreter result to the list of names of the namespaces on the * current namespace's path. * * Side effects: * May update the namespace path (triggering a recomputing of all command * names that depend on the namespace for resolution). * *---------------------------------------------------------------------- */ static int NamespacePathCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Namespace *nsPtr = (Namespace *) TclGetCurrentNamespace(interp); Tcl_Size nsObjc, i; int result = TCL_ERROR; Tcl_Obj **nsObjv; Tcl_Namespace **namespaceList = NULL; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?pathList?"); return TCL_ERROR; } /* * If no path is given, return the current path. */ if (objc == 1) { Tcl_Obj *resultObj; TclNewObj(resultObj); for (i=0 ; icommandPathLength ; i++) { if (nsPtr->commandPathArray[i].nsPtr != NULL) { Tcl_ListObjAppendElement(NULL, resultObj, TclNewNamespaceObj( (Tcl_Namespace *) nsPtr->commandPathArray[i].nsPtr)); } } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * There is a path given, so parse it into an array of namespace pointers. */ if (TclListObjGetElements(interp, objv[1], &nsObjc, &nsObjv) != TCL_OK) { goto badNamespace; } if (nsObjc != 0) { namespaceList = (Tcl_Namespace **) TclStackAlloc(interp, sizeof(Tcl_Namespace *) * nsObjc); for (i = 0; i < nsObjc; i++) { if (TclGetNamespaceFromObj(interp, nsObjv[i], &namespaceList[i]) != TCL_OK) { goto badNamespace; } } } /* * Now we have the list of valid namespaces, install it as the path. */ TclSetNsPath(nsPtr, nsObjc, namespaceList); result = TCL_OK; badNamespace: if (namespaceList != NULL) { TclStackFree(interp, namespaceList); } return result; } /* *---------------------------------------------------------------------- * * TclSetNsPath -- * * Sets the namespace command name resolution path to the given list of * namespaces. If the list is empty (of zero length) the path is set to * empty and the default old-style behaviour of command name resolution * is used. * * Results: * nothing * * Side effects: * Invalidates the command name resolution caches for any command * resolved in the given namespace. * *---------------------------------------------------------------------- */ void TclSetNsPath( Namespace *nsPtr, /* Namespace whose path is to be set. */ Tcl_Size pathLength, /* Length of pathAry. */ Tcl_Namespace *pathAry[]) /* Array of namespaces that are the path. */ { if (pathLength != 0) { NamespacePathEntry *tmpPathArray = (NamespacePathEntry *) Tcl_Alloc(sizeof(NamespacePathEntry) * pathLength); Tcl_Size i; for (i=0 ; icommandPathSourceList; if (tmpPathArray[i].nextPtr != NULL) { tmpPathArray[i].nextPtr->prevPtr = &tmpPathArray[i]; } tmpPathArray[i].nsPtr->commandPathSourceList = &tmpPathArray[i]; } if (nsPtr->commandPathLength != 0) { UnlinkNsPath(nsPtr); } nsPtr->commandPathArray = tmpPathArray; } else { if (nsPtr->commandPathLength != 0) { UnlinkNsPath(nsPtr); } } nsPtr->commandPathLength = pathLength; nsPtr->cmdRefEpoch++; nsPtr->resolverEpoch++; } /* *---------------------------------------------------------------------- * * UnlinkNsPath -- * * Delete the given namespace's command name resolution path. Only call * if the path is non-empty. Caller must reset the counter containing the * path size. * * Results: * nothing * * Side effects: * Deletes the array of path entries and unlinks those path entries from * the target namespace's list of interested namespaces. * *---------------------------------------------------------------------- */ static void UnlinkNsPath( Namespace *nsPtr) { Tcl_Size i; for (i=0 ; icommandPathLength ; i++) { NamespacePathEntry *nsPathPtr = &nsPtr->commandPathArray[i]; if (nsPathPtr->prevPtr != NULL) { nsPathPtr->prevPtr->nextPtr = nsPathPtr->nextPtr; } if (nsPathPtr->nextPtr != NULL) { nsPathPtr->nextPtr->prevPtr = nsPathPtr->prevPtr; } if (nsPathPtr->nsPtr != NULL) { if (nsPathPtr->nsPtr->commandPathSourceList == nsPathPtr) { nsPathPtr->nsPtr->commandPathSourceList = nsPathPtr->nextPtr; } } } Tcl_Free(nsPtr->commandPathArray); } /* *---------------------------------------------------------------------- * * TclInvalidateNsPath -- * * Invalidate the name resolution caches for all names looked up in * namespaces whose name path includes the given namespace. * * Results: * nothing * * Side effects: * Increments the command reference epoch in each namespace whose path * includes the given namespace. This causes any cached resolved names * whose root caching context starts at that namespace to be recomputed * the next time they are used. * *---------------------------------------------------------------------- */ void TclInvalidateNsPath( Namespace *nsPtr) { NamespacePathEntry *nsPathPtr = nsPtr->commandPathSourceList; while (nsPathPtr != NULL) { if (nsPathPtr->nsPtr != NULL) { nsPathPtr->creatorNsPtr->cmdRefEpoch++; } nsPathPtr = nsPathPtr->nextPtr; } } /* *---------------------------------------------------------------------- * * NamespaceQualifiersCmd -- * * Invoked to implement the "namespace qualifiers" command that returns * any leading namespace qualifiers in a string. These qualifiers are * namespace names separated by "::"s. For example, for "::foo::p" this * command returns "::foo", and for "::" it returns "". This command is * the complement of the "namespace tail" command. Note that this command * does not check whether the "namespace" names are, in fact, the names * of currently defined namespaces. Handles the following syntax: * * namespace qualifiers string * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Returns a result in the interpreter's result object. If anything goes * wrong, the result is an error message. * *---------------------------------------------------------------------- */ static int NamespaceQualifiersCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *name, *p; size_t length; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "string"); return TCL_ERROR; } /* * Find the end of the string, then work backward and find the start of * the last "::" qualifier. */ name = TclGetString(objv[1]); for (p = name; p[0] != '\0'; p++) { /* empty body */ } while (--p >= name) { if ((p[0] == ':') && (p > name) && (p[-1] == ':')) { p -= 2; /* Back up over the :: */ while ((p >= name) && (p[0] == ':')) { p--; /* Back up over the preceding : */ } break; } } if (p >= name) { length = p-name+1; Tcl_SetObjResult(interp, Tcl_NewStringObj(name, length)); } return TCL_OK; } /* *---------------------------------------------------------------------- * * NamespaceUnknownCmd -- * * Invoked to implement the "namespace unknown" command (TIP 181) that * sets or queries a per-namespace unknown command handler. This handler * is called when command lookup fails (current and global ns). The * default handler for the global namespace is ::unknown. The default * handler for other namespaces is to call the global namespace unknown * handler. Passing an empty list results in resetting the handler to its * default. * * namespace unknown ?handler? * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * If no handler is specified, returns a result in the interpreter's * result object, otherwise it sets the unknown handler pointer in the * current namespace to the script fragment provided. If anything goes * wrong, the result is an error message. * *---------------------------------------------------------------------- */ static int NamespaceUnknownCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Namespace *currNsPtr; Tcl_Obj *resultPtr; int rc; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?script?"); return TCL_ERROR; } currNsPtr = TclGetCurrentNamespace(interp); if (objc == 1) { /* * Introspection - return the current namespace handler. */ resultPtr = Tcl_GetNamespaceUnknownHandler(interp, currNsPtr); if (resultPtr == NULL) { TclNewObj(resultPtr); } Tcl_SetObjResult(interp, resultPtr); } else { rc = Tcl_SetNamespaceUnknownHandler(interp, currNsPtr, objv[1]); if (rc == TCL_OK) { Tcl_SetObjResult(interp, objv[1]); } return rc; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_GetNamespaceUnknownHandler -- * * Returns the unknown command handler registered for the given * namespace. * * Results: * Returns the current unknown command handler, or NULL if none exists * for the namespace. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_GetNamespaceUnknownHandler( Tcl_Interp *interp, /* The interpreter in which the namespace * exists. */ Tcl_Namespace *nsPtr) /* The namespace. */ { Namespace *currNsPtr = (Namespace *) nsPtr; if (currNsPtr->unknownHandlerPtr == NULL && currNsPtr == ((Interp *) interp)->globalNsPtr) { /* * Default handler for global namespace is "::unknown". For all other * namespaces, it is NULL (which falls back on the global unknown * handler). */ TclNewLiteralStringObj(currNsPtr->unknownHandlerPtr, "::unknown"); Tcl_IncrRefCount(currNsPtr->unknownHandlerPtr); } return currNsPtr->unknownHandlerPtr; } /* *---------------------------------------------------------------------- * * Tcl_SetNamespaceUnknownHandler -- * * Sets the unknown command handler for the given namespace to the * command prefix passed. * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Sets the namespace unknown command handler. If the passed in handler * is NULL or an empty list, then the handler is reset to its default. If * an error occurs, then an error message is left in the interpreter * result. * *---------------------------------------------------------------------- */ int Tcl_SetNamespaceUnknownHandler( Tcl_Interp *interp, /* Interpreter in which the namespace * exists. */ Tcl_Namespace *nsPtr, /* Namespace which is being updated. */ Tcl_Obj *handlerPtr) /* The new handler, or NULL to reset. */ { Tcl_Size lstlen = 0; Namespace *currNsPtr = (Namespace *) nsPtr; /* * Ensure that we check for errors *first* before we change anything. */ if (handlerPtr != NULL) { if (TclListObjLength(interp, handlerPtr, &lstlen) != TCL_OK) { /* * Not a list. */ return TCL_ERROR; } if (lstlen > 0) { /* * We are going to be saving this handler. Increment the reference * count before decrementing the refcount on the previous handler, * so that nothing strange can happen if we are told to set the * handler to the previous value. */ Tcl_IncrRefCount(handlerPtr); } } /* * Remove old handler next. */ if (currNsPtr->unknownHandlerPtr != NULL) { Tcl_DecrRefCount(currNsPtr->unknownHandlerPtr); } /* * Install the new handler. */ if (lstlen > 0) { /* * Just store the handler. It already has the correct reference count. */ currNsPtr->unknownHandlerPtr = handlerPtr; } else { /* * If NULL or an empty list is passed, this resets to the default * handler. */ currNsPtr->unknownHandlerPtr = NULL; } return TCL_OK; } /* *---------------------------------------------------------------------- * * NamespaceTailCmd -- * * Invoked to implement the "namespace tail" command that returns the * trailing name at the end of a string with "::" namespace qualifiers. * These qualifiers are namespace names separated by "::"s. For example, * for "::foo::p" this command returns "p", and for "::" it returns "". * This command is the complement of the "namespace qualifiers" command. * Note that this command does not check whether the "namespace" names * are, in fact, the names of currently defined namespaces. Handles the * following syntax: * * namespace tail string * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Returns a result in the interpreter's result object. If anything goes * wrong, the result is an error message. * *---------------------------------------------------------------------- */ static int NamespaceTailCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *name, *p; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "string"); return TCL_ERROR; } /* * Find the end of the string, then work backward and find the last "::" * qualifier. */ name = TclGetString(objv[1]); for (p = name; *p != '\0'; p++) { /* empty body */ } while (--p > name) { if ((p[0] == ':') && (p[-1] == ':')) { p++; /* Just after the last "::" */ break; } } if (p >= name) { Tcl_SetObjResult(interp, Tcl_NewStringObj(p, -1)); } return TCL_OK; } /* *---------------------------------------------------------------------- * * NamespaceUpvarCmd -- * * Invoked to implement the "namespace upvar" command, that creates * variables in the current scope linked to variables in another * namespace. Handles the following syntax: * * namespace upvar ns otherVar myVar ?otherVar myVar ...? * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Creates new variables in the current scope, linked to the * corresponding variables in the stipulated namespace. If anything goes * wrong, the result is an error message. * *---------------------------------------------------------------------- */ static int NamespaceUpvarCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; Tcl_Namespace *nsPtr, *savedNsPtr; Var *otherPtr, *arrayPtr; const char *myName; if (objc < 2 || (objc & 1)) { Tcl_WrongNumArgs(interp, 1, objv, "ns ?otherVar myVar ...?"); return TCL_ERROR; } if (TclGetNamespaceFromObj(interp, objv[1], &nsPtr) != TCL_OK) { return TCL_ERROR; } objc -= 2; objv += 2; for (; objc>0 ; objc-=2, objv+=2) { /* * Locate the other variable. */ savedNsPtr = (Tcl_Namespace *) iPtr->varFramePtr->nsPtr; iPtr->varFramePtr->nsPtr = (Namespace *) nsPtr; otherPtr = TclObjLookupVarEx(interp, objv[0], NULL, (TCL_NAMESPACE_ONLY|TCL_LEAVE_ERR_MSG|TCL_AVOID_RESOLVERS), "access", /*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr); iPtr->varFramePtr->nsPtr = (Namespace *) savedNsPtr; if (otherPtr == NULL) { return TCL_ERROR; } /* * Create the new variable and link it to otherPtr. */ myName = TclGetString(objv[1]); if (TclPtrMakeUpvar(interp, otherPtr, myName, 0, -1) != TCL_OK) { return TCL_ERROR; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * NamespaceWhichCmd -- * * Invoked to implement the "namespace which" command that returns the * fully-qualified name of a command or variable. If the specified * command or variable does not exist, it returns "". Handles the * following syntax: * * namespace which ?-command? ?-variable? name * * Results: * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong. * * Side effects: * Returns a result in the interpreter's result object. If anything goes * wrong, the result is an error message. * *---------------------------------------------------------------------- */ static int NamespaceWhichCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const opts[] = { "-command", "-variable", NULL }; int lookupType = 0; Tcl_Obj *resultPtr; if (objc < 2 || objc > 3) { badArgs: Tcl_WrongNumArgs(interp, 1, objv, "?-command? ?-variable? name"); return TCL_ERROR; } else if (objc == 3) { /* * Look for a flag controlling the lookup. */ if (Tcl_GetIndexFromObj(interp, objv[1], opts, "option", 0, &lookupType) != TCL_OK) { /* * Preserve old style of error message! */ Tcl_ResetResult(interp); goto badArgs; } } TclNewObj(resultPtr); switch (lookupType) { case 0: { /* -command */ Tcl_Command cmd = Tcl_GetCommandFromObj(interp, objv[objc-1]); if (cmd != NULL) { Tcl_GetCommandFullName(interp, cmd, resultPtr); } break; } case 1: { /* -variable */ Tcl_Var var = Tcl_FindNamespaceVar(interp, TclGetString(objv[objc-1]), NULL, /*flags*/ 0); if (var != NULL) { Tcl_GetVariableFullName(interp, var, resultPtr); } break; } } Tcl_SetObjResult(interp, resultPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * FreeNsNameInternalRep -- * * Frees the resources associated with a nsName object's internal * representation. * * Results: * None. * * Side effects: * Decrements the ref count of any Namespace structure pointed to by the * nsName's internal representation. If there are no more references to * the namespace, it's structure will be freed. * *---------------------------------------------------------------------- */ static void FreeNsNameInternalRep( Tcl_Obj *objPtr) /* nsName object with internal representation * to free. */ { ResolvedNsName *resNamePtr; NsNameGetInternalRep(objPtr, resNamePtr); assert(resNamePtr != NULL); /* * Decrement the reference count of the namespace. If there are no more * references, free it up. */ if (resNamePtr->refCount-- <= 1) { /* * Decrement the reference count for the cached namespace. If the * namespace is dead, and there are no more references to it, free * it. */ TclNsDecrRefCount(resNamePtr->nsPtr); Tcl_Free(resNamePtr); } } /* *---------------------------------------------------------------------- * * DupNsNameInternalRep -- * * Initializes the internal representation of a nsName object to a copy * of the internal representation of another nsName object. * * Results: * None. * * Side effects: * copyPtr's internal rep is set to refer to the same namespace * referenced by srcPtr's internal rep. Increments the ref count of the * ResolvedNsName structure used to hold the namespace reference. * *---------------------------------------------------------------------- */ static void DupNsNameInternalRep( Tcl_Obj *srcPtr, /* Object with internal rep to copy. */ Tcl_Obj *copyPtr) /* Object with internal rep to set. */ { ResolvedNsName *resNamePtr; NsNameGetInternalRep(srcPtr, resNamePtr); assert(resNamePtr != NULL); NsNameSetInternalRep(copyPtr, resNamePtr); } /* *---------------------------------------------------------------------- * * SetNsNameFromAny -- * * Attempt to generate a nsName internal representation for a Tcl object. * * Results: * Returns TCL_OK if the value could be converted to a proper namespace * reference. Otherwise, it returns TCL_ERROR, along with an error * message in the interpreter's result object. * * Side effects: * If successful, the object is made a nsName object. Its internal rep is * set to point to a ResolvedNsName, which contains a cached pointer to * the Namespace. Reference counts are kept on both the ResolvedNsName * and the Namespace, so we can keep track of their usage and free them * when appropriate. * *---------------------------------------------------------------------- */ static int SetNsNameFromAny( Tcl_Interp *interp, /* Points to the namespace in which to resolve * name. Also used for error reporting if not * NULL. */ Tcl_Obj *objPtr) /* The object to convert. */ { const char *dummy; Namespace *nsPtr, *dummy1Ptr, *dummy2Ptr; ResolvedNsName *resNamePtr; const char *name; if (interp == NULL) { return TCL_ERROR; } name = TclGetString(objPtr); TclGetNamespaceForQualName(interp, name, NULL, TCL_FIND_ONLY_NS, &nsPtr, &dummy1Ptr, &dummy2Ptr, &dummy); if ((nsPtr == NULL) || (nsPtr->flags & NS_DYING)) { return TCL_ERROR; } /* * If we found a namespace, then create a new ResolvedNsName structure * that holds a reference to it. */ nsPtr->refCount++; resNamePtr = (ResolvedNsName *) Tcl_Alloc(sizeof(ResolvedNsName)); resNamePtr->nsPtr = nsPtr; if ((name[0] == ':') && (name[1] == ':')) { resNamePtr->refNsPtr = NULL; } else { resNamePtr->refNsPtr = (Namespace *) TclGetCurrentNamespace(interp); } resNamePtr->refCount = 0; NsNameSetInternalRep(objPtr, resNamePtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclGetNamespaceCommandTable -- * * Returns the hash table of commands. * * Results: * Pointer to the hash table. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_HashTable * TclGetNamespaceCommandTable( Tcl_Namespace *nsPtr) { return &((Namespace *) nsPtr)->cmdTable; } /* *---------------------------------------------------------------------- * * TclGetNamespaceChildTable -- * * Returns the hash table of child namespaces. * * Results: * Pointer to the hash table. * * Side effects: * Might allocate memory. * *---------------------------------------------------------------------- */ Tcl_HashTable * TclGetNamespaceChildTable( Tcl_Namespace *nsPtr) { Namespace *nPtr = (Namespace *) nsPtr; #ifndef BREAK_NAMESPACE_COMPAT return &nPtr->childTable; #else if (nPtr->childTablePtr == NULL) { nPtr->childTablePtr = (Tcl_HashTable *) Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(nPtr->childTablePtr, TCL_STRING_KEYS); } return nPtr->childTablePtr; #endif } /* *---------------------------------------------------------------------- * * TclLogCommandInfo -- * * Invoked after an error occurs in an interpreter. * Adds information to iPtr->errorInfo/errorStack fields to describe the * command that was being executed when the error occurred. When pc and * tosPtr are non-NULL, conveying a bytecode execution "inner context", * and the offending instruction is suitable, and that inner context is * recorded in errorStack. * * Results: * None. * * Side effects: * Information about the command is added to errorInfo/errorStack and the * line number stored internally in the interpreter is set. * *---------------------------------------------------------------------- */ void TclLogCommandInfo( Tcl_Interp *interp, /* Interpreter in which to log information. */ const char *script, /* First character in script containing * command (must be <= command). */ const char *command, /* First character in command that generated * the error. */ Tcl_Size length, /* Number of bytes in command (< 0 means use * all bytes up to first null byte). */ const unsigned char *pc, /* Current pc of bytecode execution context */ Tcl_Obj **tosPtr) /* Current stack of bytecode execution * context */ { const char *p; Interp *iPtr = (Interp *) interp; int overflow, limit = 150; Var *varPtr, *arrayPtr; if (iPtr->flags & ERR_ALREADY_LOGGED) { /* * Someone else has already logged error information for this command. * Don't add anything more. */ return; } if (command != NULL) { /* * Compute the line number where the error occurred. */ iPtr->errorLine = 1; for (p = script; p != command; p++) { if (*p == '\n') { iPtr->errorLine++; } } if (length < 0) { length = strlen(command); } overflow = (length > limit); Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n %s\n\"%.*s%s\"", ((iPtr->errorInfo == NULL) ? "while executing" : "invoked from within"), (overflow ? limit : (int)length), command, (overflow ? "..." : ""))); varPtr = TclObjLookupVarEx(interp, iPtr->eiVar, NULL, TCL_GLOBAL_ONLY, NULL, 0, 0, &arrayPtr); if ((varPtr == NULL) || !TclIsVarTraced(varPtr)) { /* * Should not happen. */ return; } else { Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&iPtr->varTraces, varPtr); VarTrace *tracePtr = (VarTrace *) Tcl_GetHashValue(hPtr); if (tracePtr->traceProc != EstablishErrorInfoTraces) { /* * The most recent trace set on ::errorInfo is not the one the * core itself puts on last. This means some other code is * tracing the variable, and the additional trace(s) might be * write traces that expect the timing of writes to * ::errorInfo that existed Tcl releases before 8.5. To * satisfy that compatibility need, we write the current * -errorinfo value to the ::errorInfo variable. */ Tcl_ObjSetVar2(interp, iPtr->eiVar, NULL, iPtr->errorInfo, TCL_GLOBAL_ONLY); } } } /* * TIP #348 */ if (Tcl_IsShared(iPtr->errorStack)) { Tcl_Obj *newObj; newObj = Tcl_DuplicateObj(iPtr->errorStack); Tcl_DecrRefCount(iPtr->errorStack); Tcl_IncrRefCount(newObj); iPtr->errorStack = newObj; } if (iPtr->resetErrorStack) { Tcl_Size len; iPtr->resetErrorStack = 0; TclListObjLength(interp, iPtr->errorStack, &len); /* * Reset while keeping the list internalrep as much as possible. */ Tcl_ListObjReplace(interp, iPtr->errorStack, 0, len, 0, NULL); if (pc != NULL) { Tcl_Obj *innerContext; innerContext = TclGetInnerContext(interp, pc, tosPtr); if (innerContext != NULL) { Tcl_ListObjAppendElement(NULL, iPtr->errorStack, iPtr->innerLiteral); Tcl_ListObjAppendElement(NULL, iPtr->errorStack, innerContext); } } else if (command != NULL) { Tcl_ListObjAppendElement(NULL, iPtr->errorStack, iPtr->innerLiteral); Tcl_ListObjAppendElement(NULL, iPtr->errorStack, Tcl_NewStringObj(command, length)); } } if (!iPtr->framePtr->objc) { /* * Special frame, nothing to report. */ } else if (iPtr->varFramePtr != iPtr->framePtr) { /* * uplevel case, [lappend errorstack UP $relativelevel] */ Tcl_ListObjAppendElement(NULL, iPtr->errorStack, iPtr->upLiteral); Tcl_ListObjAppendElement(NULL, iPtr->errorStack, Tcl_NewWideIntObj( (int)(iPtr->framePtr->level - iPtr->varFramePtr->level))); } else if (iPtr->framePtr != iPtr->rootFramePtr) { /* * normal case, [lappend errorstack CALL [info level 0]] */ Tcl_ListObjAppendElement(NULL, iPtr->errorStack, iPtr->callLiteral); Tcl_ListObjAppendElement(NULL, iPtr->errorStack, Tcl_NewListObj( iPtr->framePtr->objc, iPtr->framePtr->objv)); } } /* *---------------------------------------------------------------------- * * TclErrorStackResetIf -- * * The TIP 348 reset/no-bc part of TLCI, for specific use by * TclCompileSyntaxError. * * Results: * None. * * Side effects: * Reset errorstack if it needs be, and in that case remember the * passed-in error message as inner context. * *---------------------------------------------------------------------- */ void TclErrorStackResetIf( Tcl_Interp *interp, const char *msg, Tcl_Size length) { Interp *iPtr = (Interp *) interp; if (Tcl_IsShared(iPtr->errorStack)) { Tcl_Obj *newObj; newObj = Tcl_DuplicateObj(iPtr->errorStack); Tcl_DecrRefCount(iPtr->errorStack); Tcl_IncrRefCount(newObj); iPtr->errorStack = newObj; } if (iPtr->resetErrorStack) { Tcl_Size len; iPtr->resetErrorStack = 0; TclListObjLength(interp, iPtr->errorStack, &len); /* * Reset while keeping the list internalrep as much as possible. */ Tcl_ListObjReplace(interp, iPtr->errorStack, 0, len, 0, NULL); Tcl_ListObjAppendElement(NULL, iPtr->errorStack, iPtr->innerLiteral); Tcl_ListObjAppendElement(NULL, iPtr->errorStack, Tcl_NewStringObj(msg, length)); } } /* *---------------------------------------------------------------------- * * Tcl_LogCommandInfo -- * * This function is invoked after an error occurs in an interpreter. It * adds information to iPtr->errorInfo/errorStack fields to describe the * command that was being executed when the error occurred. * * Results: * None. * * Side effects: * Information about the command is added to errorInfo/errorStack and the * line number stored internally in the interpreter is set. * *---------------------------------------------------------------------- */ void Tcl_LogCommandInfo( Tcl_Interp *interp, /* Interpreter in which to log information. */ const char *script, /* First character in script containing * command (must be <= command). */ const char *command, /* First character in command that generated * the error. */ Tcl_Size length) /* Number of bytes in command (-1 means use * all bytes up to first null byte). */ { TclLogCommandInfo(interp, script, command, length, NULL, NULL); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * End: */ tcl9.0.1/generic/tclNotify.c0000644000175000017500000011527214726623136015321 0ustar sergeisergei/* * tclNotify.c -- * * This file implements the generic portion of the Tcl notifier. The * notifier is lowest-level part of the event system. It manages an event * queue that holds Tcl_Event structures. The platform specific portion * of the notifier is defined in the tcl*Notify.c files in each platform * directory. * * Copyright © 1995-1997 Sun Microsystems, Inc. * Copyright © 1998 Scriptics Corporation. * Copyright © 2003 Kevin B. Kenny. All rights reserved. * Copyright © 2021 Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * Notifier hooks that are checked in the public wrappers for the default * notifier functions (for overriding via Tcl_SetNotifier). */ static Tcl_NotifierProcs tclNotifierHooks = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; /* * For each event source (created with Tcl_CreateEventSource) there is a * structure of the following type: */ typedef struct EventSource { Tcl_EventSetupProc *setupProc; Tcl_EventCheckProc *checkProc; void *clientData; struct EventSource *nextPtr; } EventSource; /* * The following structure keeps track of the state of the notifier on a * per-thread basis. The first three elements keep track of the event queue. * In addition to the first (next to be serviced) and last events in the * queue, we keep track of a "marker" event. This provides a simple priority * mechanism whereby events can be inserted at the front of the queue but * behind all other high-priority events already in the queue (this is used * for things like a sequence of Enter and Leave events generated during a * grab in Tk). These elements are protected by the queueMutex so that any * thread can queue an event on any notifier. Note that all of the values in * this structure will be initialized to 0. */ typedef struct ThreadSpecificData { Tcl_Event *firstEventPtr; /* First pending event, or NULL if none. */ Tcl_Event *lastEventPtr; /* Last pending event, or NULL if none. */ Tcl_Event *markerEventPtr; /* Last high-priority event in queue, or NULL * if none. */ Tcl_Size eventCount; /* Number of entries, but refer to comments in * Tcl_ServiceEvent(). */ Tcl_Mutex queueMutex; /* Mutex to protect access to the previous * four fields. */ int serviceMode; /* One of TCL_SERVICE_NONE or * TCL_SERVICE_ALL. */ int blockTimeSet; /* 0 means there is no maximum block time: * block forever. */ Tcl_Time blockTime; /* If blockTimeSet is 1, gives the maximum * elapsed time for the next block. */ int inTraversal; /* 1 if Tcl_SetMaxBlockTime is being called * during an event source traversal. */ int initialized; /* 1 if notifier has been initialized. */ EventSource *firstEventSourcePtr; /* Pointer to first event source in list of * event sources for this thread. */ Tcl_ThreadId threadId; /* Thread that owns this notifier instance. */ void *clientData; /* Opaque handle for platform specific * notifier. */ struct ThreadSpecificData *nextPtr; /* Next notifier in global list of notifiers. * Access is controlled by the listLock global * mutex. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * Global list of notifiers. Access to this list is controlled by the listLock * mutex. If this becomes a performance bottleneck, this could be replaced * with a hashtable. */ static ThreadSpecificData *firstNotifierPtr = NULL; TCL_DECLARE_MUTEX(listLock) /* * Declarations for routines used only in this file. */ static int QueueEvent(ThreadSpecificData *tsdPtr, Tcl_Event *evPtr, int position); /* *---------------------------------------------------------------------- * * TclInitNotifier -- * * Initialize the thread local data structures for the notifier * subsystem. * * Results: * None. * * Side effects: * Adds the current thread to the global list of notifiers. * *---------------------------------------------------------------------- */ void TclInitNotifier(void) { ThreadSpecificData *tsdPtr; Tcl_ThreadId threadId = Tcl_GetCurrentThread(); Tcl_MutexLock(&listLock); for (tsdPtr = firstNotifierPtr; tsdPtr && tsdPtr->threadId != threadId; tsdPtr = tsdPtr->nextPtr) { /* Empty loop body. */ } if (NULL == tsdPtr) { /* * Notifier not yet initialized in this thread. */ tsdPtr = TCL_TSD_INIT(&dataKey); tsdPtr->threadId = threadId; tsdPtr->clientData = Tcl_InitNotifier(); tsdPtr->initialized = 1; tsdPtr->nextPtr = firstNotifierPtr; firstNotifierPtr = tsdPtr; } Tcl_MutexUnlock(&listLock); } /* *---------------------------------------------------------------------- * * TclFinalizeNotifier -- * * Finalize the thread local data structures for the notifier subsystem. * * Results: * None. * * Side effects: * Removes the notifier associated with the current thread from the * global notifier list. This is done only if the notifier was * initialized for this thread by call to TclInitNotifier(). This is * always true for threads which have been seeded with an Tcl * interpreter, since the call to Tcl_CreateInterp will, among other * things, call TclInitializeSubsystems() and this one will, in turn, * call the TclInitNotifier() for the thread. For threads created without * the Tcl interpreter, though, nobody is explicitly nor implicitly * calling the TclInitNotifier hence, TclFinalizeNotifier should not be * performed at all. * *---------------------------------------------------------------------- */ void TclFinalizeNotifier(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); ThreadSpecificData **prevPtrPtr; Tcl_Event *evPtr, *hold; if (!tsdPtr->initialized) { return; /* Notifier not initialized for the current thread */ } Tcl_MutexLock(&(tsdPtr->queueMutex)); for (evPtr = tsdPtr->firstEventPtr; evPtr != NULL; ) { hold = evPtr; evPtr = evPtr->nextPtr; Tcl_Free(hold); } tsdPtr->firstEventPtr = NULL; tsdPtr->lastEventPtr = NULL; tsdPtr->eventCount = 0; Tcl_MutexUnlock(&(tsdPtr->queueMutex)); Tcl_MutexLock(&listLock); Tcl_FinalizeNotifier(tsdPtr->clientData); Tcl_MutexFinalize(&(tsdPtr->queueMutex)); for (prevPtrPtr = &firstNotifierPtr; *prevPtrPtr != NULL; prevPtrPtr = &((*prevPtrPtr)->nextPtr)) { if (*prevPtrPtr == tsdPtr) { *prevPtrPtr = tsdPtr->nextPtr; break; } } tsdPtr->initialized = 0; Tcl_MutexUnlock(&listLock); } /* *---------------------------------------------------------------------- * * Tcl_SetNotifier -- * * Install a set of alternate functions for use with the notifier. In * particular, this can be used to install the Xt-based notifier for use * with the Browser plugin. * * Results: * None. * * Side effects: * Set the tclNotifierHooks global, which is checked in the default * notifier functions. * *---------------------------------------------------------------------- */ void Tcl_SetNotifier( const Tcl_NotifierProcs *notifierProcPtr) { tclNotifierHooks = *notifierProcPtr; /* * Don't allow hooks to refer to the hook point functions; avoids infinite * loop. */ if (tclNotifierHooks.setTimerProc == Tcl_SetTimer) { tclNotifierHooks.setTimerProc = NULL; } if (tclNotifierHooks.waitForEventProc == Tcl_WaitForEvent) { tclNotifierHooks.waitForEventProc = NULL; } if (tclNotifierHooks.initNotifierProc == Tcl_InitNotifier) { tclNotifierHooks.initNotifierProc = NULL; } if (tclNotifierHooks.finalizeNotifierProc == Tcl_FinalizeNotifier) { tclNotifierHooks.finalizeNotifierProc = NULL; } if (tclNotifierHooks.alertNotifierProc == Tcl_AlertNotifier) { tclNotifierHooks.alertNotifierProc = NULL; } if (tclNotifierHooks.serviceModeHookProc == Tcl_ServiceModeHook) { tclNotifierHooks.serviceModeHookProc = NULL; } #ifndef _WIN32 if (tclNotifierHooks.createFileHandlerProc == Tcl_CreateFileHandler) { tclNotifierHooks.createFileHandlerProc = NULL; } if (tclNotifierHooks.deleteFileHandlerProc == Tcl_DeleteFileHandler) { tclNotifierHooks.deleteFileHandlerProc = NULL; } #endif /* !_WIN32 */ } /* *---------------------------------------------------------------------- * * Tcl_CreateEventSource -- * * This function is invoked to create a new source of events. The source * is identified by a function that gets invoked during Tcl_DoOneEvent to * check for events on that source and queue them. * * * Results: * None. * * Side effects: * SetupProc and checkProc will be invoked each time that Tcl_DoOneEvent * runs out of things to do. SetupProc will be invoked before * Tcl_DoOneEvent calls select or whatever else it uses to wait for * events. SetupProc typically calls functions like Tcl_SetMaxBlockTime * to indicate what to wait for. * * CheckProc is called after select or whatever operation was actually * used to wait. It figures out whether anything interesting actually * happened (e.g. by calling Tcl_AsyncReady), and then calls * Tcl_QueueEvent to queue any events that are ready. * * Each of these functions is passed two arguments, e.g. * (*checkProc)(void *clientData, int flags)); * ClientData is the same as the clientData argument here, and flags is a * combination of things like TCL_FILE_EVENTS that indicates what events * are of interest: setupProc and checkProc use flags to figure out * whether their events are relevant or not. * *---------------------------------------------------------------------- */ void Tcl_CreateEventSource( Tcl_EventSetupProc *setupProc, /* Function to invoke to figure out what to * wait for. */ Tcl_EventCheckProc *checkProc, /* Function to call after waiting to see what * happened. */ void *clientData) /* One-word argument to pass to setupProc and * checkProc. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); EventSource *sourcePtr = (EventSource *)Tcl_Alloc(sizeof(EventSource)); sourcePtr->setupProc = setupProc; sourcePtr->checkProc = checkProc; sourcePtr->clientData = clientData; sourcePtr->nextPtr = tsdPtr->firstEventSourcePtr; tsdPtr->firstEventSourcePtr = sourcePtr; } /* *---------------------------------------------------------------------- * * Tcl_DeleteEventSource -- * * This function is invoked to delete the source of events given by proc * and clientData. * * Results: * None. * * Side effects: * The given event source is canceled, so its function will never again * be called. If no such source exists, nothing happens. * *---------------------------------------------------------------------- */ void Tcl_DeleteEventSource( Tcl_EventSetupProc *setupProc, /* Function to invoke to figure out what to * wait for. */ Tcl_EventCheckProc *checkProc, /* Function to call after waiting to see what * happened. */ void *clientData) /* One-word argument to pass to setupProc and * checkProc. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); EventSource *sourcePtr, *prevPtr; for (sourcePtr = tsdPtr->firstEventSourcePtr, prevPtr = NULL; sourcePtr != NULL; prevPtr = sourcePtr, sourcePtr = sourcePtr->nextPtr) { if ((sourcePtr->setupProc != setupProc) || (sourcePtr->checkProc != checkProc) || (sourcePtr->clientData != clientData)) { continue; } if (prevPtr == NULL) { tsdPtr->firstEventSourcePtr = sourcePtr->nextPtr; } else { prevPtr->nextPtr = sourcePtr->nextPtr; } Tcl_Free(sourcePtr); return; } } /* *---------------------------------------------------------------------- * * Tcl_QueueEvent -- * * Queue an event on the event queue associated with the current thread. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_QueueEvent( Tcl_Event *evPtr, /* Event to add to queue. The storage space * must have been allocated the caller with * malloc (Tcl_Alloc), and it becomes the * property of the event queue. It will be * freed after the event has been handled. */ int position) /* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD, TCL_QUEUE_MARK, * possibly combined with TCL_QUEUE_ALERT_IF_EMPTY. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); QueueEvent(tsdPtr, evPtr, position); } /* *---------------------------------------------------------------------- * * Tcl_ThreadQueueEvent -- * * Queue an event on the specified thread's event queue. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_ThreadQueueEvent( Tcl_ThreadId threadId, /* Identifier for thread to use. */ Tcl_Event *evPtr, /* Event to add to queue. The storage space * must have been allocated the caller with * malloc (Tcl_Alloc), and it becomes the * property of the event queue. It will be * freed after the event has been handled. */ int position) /* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD, TCL_QUEUE_MARK, * possibly combined with TCL_QUEUE_ALERT_IF_EMPTY. */ { ThreadSpecificData *tsdPtr; /* * Find the notifier associated with the specified thread. */ Tcl_MutexLock(&listLock); for (tsdPtr = firstNotifierPtr; tsdPtr && tsdPtr->threadId != threadId; tsdPtr = tsdPtr->nextPtr) { /* Empty loop body. */ } /* * Queue the event if there was a notifier associated with the thread. */ if (tsdPtr) { if (QueueEvent(tsdPtr, evPtr, position)) { Tcl_AlertNotifier(tsdPtr->clientData); } } else { Tcl_Free(evPtr); } Tcl_MutexUnlock(&listLock); } /* *---------------------------------------------------------------------- * * QueueEvent -- * * Insert an event into the specified thread's event queue at one of * three positions: the head, the tail, or before a floating marker. * Events inserted before the marker will be processed in first-in- * first-out order, but before any events inserted at the tail of the * queue. Events inserted at the head of the queue will be processed in * last-in-first-out order. * * Results: * For TCL_QUEUE_ALERT_IF_EMPTY the empty state before the * operation is returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int QueueEvent( ThreadSpecificData *tsdPtr, /* Handle to thread local data that indicates * which event queue to use. */ Tcl_Event *evPtr, /* Event to add to queue. The storage space * must have been allocated the caller with * malloc (Tcl_Alloc), and it becomes the * property of the event queue. It will be * freed after the event has been handled. */ int position) /* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD, TCL_QUEUE_MARK, * possibly combined with TCL_QUEUE_ALERT_IF_EMPTY */ { int wasEmpty = 0; Tcl_MutexLock(&(tsdPtr->queueMutex)); if ((position & 3) == TCL_QUEUE_TAIL) { /* * Append the event on the end of the queue. */ evPtr->nextPtr = NULL; if (tsdPtr->firstEventPtr == NULL) { tsdPtr->firstEventPtr = evPtr; } else { tsdPtr->lastEventPtr->nextPtr = evPtr; } tsdPtr->lastEventPtr = evPtr; } else if ((position & 3) == TCL_QUEUE_HEAD) { /* * Push the event on the head of the queue. */ evPtr->nextPtr = tsdPtr->firstEventPtr; if (tsdPtr->firstEventPtr == NULL) { tsdPtr->lastEventPtr = evPtr; } tsdPtr->firstEventPtr = evPtr; } else if ((position & 3) == TCL_QUEUE_MARK) { /* * Insert the event after the current marker event and advance the * marker to the new event. */ if (tsdPtr->markerEventPtr == NULL) { evPtr->nextPtr = tsdPtr->firstEventPtr; tsdPtr->firstEventPtr = evPtr; } else { evPtr->nextPtr = tsdPtr->markerEventPtr->nextPtr; tsdPtr->markerEventPtr->nextPtr = evPtr; } tsdPtr->markerEventPtr = evPtr; if (evPtr->nextPtr == NULL) { tsdPtr->lastEventPtr = evPtr; } } if (position & TCL_QUEUE_ALERT_IF_EMPTY) { wasEmpty = (tsdPtr->eventCount <= 0); } tsdPtr->eventCount++; Tcl_MutexUnlock(&(tsdPtr->queueMutex)); return wasEmpty; } /* *---------------------------------------------------------------------- * * Tcl_DeleteEvents -- * * Calls a function for each event in the queue and deletes those for * which the function returns 1. Events for which the function returns 0 * are left in the queue. Operates on the queue associated with the * current thread. * * Results: * None. * * Side effects: * Potentially removes one or more events from the event queue. * *---------------------------------------------------------------------- */ void Tcl_DeleteEvents( Tcl_EventDeleteProc *proc, /* The function to call. */ void *clientData) /* The type-specific data. */ { Tcl_Event *evPtr; /* Pointer to the event being examined */ Tcl_Event *prevPtr; /* Pointer to evPtr's predecessor, or NULL if * evPtr designates the first event in the * queue for the thread. */ Tcl_Event *hold; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Tcl_MutexLock(&(tsdPtr->queueMutex)); /* * Walk the queue of events for the thread, applying 'proc' to each to * decide whether to eliminate the event. */ prevPtr = NULL; evPtr = tsdPtr->firstEventPtr; while (evPtr != NULL) { if (proc(evPtr, clientData) == 1) { /* * This event should be deleted. Unlink it. */ if (prevPtr == NULL) { tsdPtr->firstEventPtr = evPtr->nextPtr; } else { prevPtr->nextPtr = evPtr->nextPtr; } /* * Update 'last' and 'marker' events if either has been deleted. */ if (evPtr->nextPtr == NULL) { tsdPtr->lastEventPtr = prevPtr; } if (tsdPtr->markerEventPtr == evPtr) { tsdPtr->markerEventPtr = prevPtr; } /* * Delete the event data structure. */ hold = evPtr; evPtr = evPtr->nextPtr; Tcl_Free(hold); tsdPtr->eventCount--; } else { /* * Event is to be retained. */ prevPtr = evPtr; evPtr = evPtr->nextPtr; } } Tcl_MutexUnlock(&(tsdPtr->queueMutex)); } /* *---------------------------------------------------------------------- * * Tcl_ServiceEvent -- * * Process one event from the event queue, or invoke an asynchronous * event handler. Operates on event queue for current thread. * * Results: * The return value is 1 if the function actually found an event to * process. If no processing occurred, then 0 is returned. * * Side effects: * Invokes all of the event handlers for the highest priority event in * the event queue. May collapse some events into a single event or * discard stale events. * *---------------------------------------------------------------------- */ int Tcl_ServiceEvent( int flags) /* Indicates what events should be processed. * May be any combination of TCL_WINDOW_EVENTS * TCL_FILE_EVENTS, TCL_TIMER_EVENTS, or other * flags defined elsewhere. Events not * matching this will be skipped for * processing later. */ { Tcl_Event *evPtr, *prevPtr; Tcl_EventProc *proc; Tcl_Size eventCount; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); int result; /* * Asynchronous event handlers are considered to be the highest priority * events, and so must be invoked before we process events on the event * queue. */ if (Tcl_AsyncReady()) { (void) Tcl_AsyncInvoke(NULL, 0); return 1; } /* * No event flags is equivalent to TCL_ALL_EVENTS. */ if ((flags & TCL_ALL_EVENTS) == 0) { flags |= TCL_ALL_EVENTS; } /* * Loop through all the events in the queue until we find one that can * actually be handled. */ Tcl_MutexLock(&(tsdPtr->queueMutex)); for (evPtr = tsdPtr->firstEventPtr; evPtr != NULL; evPtr = evPtr->nextPtr) { /* * Call the handler for the event. If it actually handles the event * then free the storage for the event. There are two tricky things * here, both stemming from the fact that the event code may be * re-entered while servicing the event: * * 1. Set the "proc" field to NULL. This is a signal to ourselves * that we shouldn't reexecute the handler if the event loop is * re-entered. * 2. When freeing the event, must search the queue again from the * front to find it. This is because the event queue could change * almost arbitrarily while handling the event, so we can't depend * on pointers found now still being valid when the handler * returns. */ proc = evPtr->proc; if (proc == NULL) { continue; } evPtr->proc = NULL; /* * Release the lock before calling the event function. This allows * other threads to post events if we enter a recursive event loop in * this thread. Note that we are making the assumption that if the * proc returns 0, the event is still in the list. * * The eventCount is remembered and set to zero that the next * level of Tcl_ServiceEvent() gets an empty condition for the * Tcl_ThreadQueueEvent() to perform optional wakeups. * On exit of the next level, the eventCount is readjusted. */ eventCount = tsdPtr->eventCount; tsdPtr->eventCount = 0; Tcl_MutexUnlock(&(tsdPtr->queueMutex)); result = proc(evPtr, flags); Tcl_MutexLock(&(tsdPtr->queueMutex)); tsdPtr->eventCount += eventCount; if (result) { /* * The event was processed, so remove it from the queue. */ if (tsdPtr->firstEventPtr == evPtr) { tsdPtr->firstEventPtr = evPtr->nextPtr; if (evPtr->nextPtr == NULL) { tsdPtr->lastEventPtr = NULL; } if (tsdPtr->markerEventPtr == evPtr) { tsdPtr->markerEventPtr = NULL; } } else { for (prevPtr = tsdPtr->firstEventPtr; prevPtr && prevPtr->nextPtr != evPtr; prevPtr = prevPtr->nextPtr) { /* Empty loop body. */ } if (prevPtr) { prevPtr->nextPtr = evPtr->nextPtr; if (evPtr->nextPtr == NULL) { tsdPtr->lastEventPtr = prevPtr; } if (tsdPtr->markerEventPtr == evPtr) { tsdPtr->markerEventPtr = prevPtr; } } else { evPtr = NULL; } } if (evPtr) { Tcl_Free(evPtr); tsdPtr->eventCount--; } Tcl_MutexUnlock(&(tsdPtr->queueMutex)); return 1; } else { /* * The event wasn't actually handled, so we have to restore the * proc field to allow the event to be attempted again. */ evPtr->proc = proc; } } Tcl_MutexUnlock(&(tsdPtr->queueMutex)); return 0; } /* *---------------------------------------------------------------------- * * Tcl_GetServiceMode -- * * This routine returns the current service mode of the notifier. * * Results: * Returns either TCL_SERVICE_ALL or TCL_SERVICE_NONE. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_GetServiceMode(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); return tsdPtr->serviceMode; } /* *---------------------------------------------------------------------- * * Tcl_SetServiceMode -- * * This routine sets the current service mode of the tsdPtr-> * * Results: * Returns the previous service mode. * * Side effects: * Invokes the notifier service mode hook function. * *---------------------------------------------------------------------- */ int Tcl_SetServiceMode( int mode) /* New service mode: TCL_SERVICE_ALL or * TCL_SERVICE_NONE */ { int oldMode; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); oldMode = tsdPtr->serviceMode; tsdPtr->serviceMode = mode; Tcl_ServiceModeHook(mode); return oldMode; } /* *---------------------------------------------------------------------- * * Tcl_SetMaxBlockTime -- * * This function is invoked by event sources to tell the notifier how * long it may block the next time it blocks. The timePtr argument gives * a maximum time; the actual time may be less if some other event source * requested a smaller time. * * Results: * None. * * Side effects: * May reduce the length of the next sleep in the tsdPtr-> * *---------------------------------------------------------------------- */ void Tcl_SetMaxBlockTime( const Tcl_Time *timePtr) /* Specifies a maximum elapsed time for the * next blocking operation in the event * tsdPtr-> */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!tsdPtr->blockTimeSet || (timePtr->sec < tsdPtr->blockTime.sec) || ((timePtr->sec == tsdPtr->blockTime.sec) && (timePtr->usec < tsdPtr->blockTime.usec))) { tsdPtr->blockTime = *timePtr; tsdPtr->blockTimeSet = 1; } /* * If we are called outside an event source traversal, set the timeout * immediately. */ if (!tsdPtr->inTraversal) { Tcl_SetTimer(&tsdPtr->blockTime); } } /* *---------------------------------------------------------------------- * * Tcl_DoOneEvent -- * * Process a single event of some sort. If there's no work to do, wait * for an event to occur, then process it. * * Results: * The return value is 1 if the function actually found an event to * process. If no processing occurred, then 0 is returned (this can * happen if the TCL_DONT_WAIT flag is set or if there are no event * handlers to wait for in the set specified by flags). * * Side effects: * May delay execution of process while waiting for an event, unless * TCL_DONT_WAIT is set in the flags argument. Event sources are invoked * to check for and queue events. Event handlers may produce arbitrary * side effects. * *---------------------------------------------------------------------- */ int Tcl_DoOneEvent( int flags) /* Miscellaneous flag values: may be any * combination of TCL_DONT_WAIT, * TCL_WINDOW_EVENTS, TCL_FILE_EVENTS, * TCL_TIMER_EVENTS, TCL_IDLE_EVENTS, or * others defined by event sources. */ { int result = 0, oldMode; EventSource *sourcePtr; Tcl_Time *timePtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* * The first thing we do is to service any asynchronous event handlers. */ if (Tcl_AsyncReady()) { (void) Tcl_AsyncInvoke(NULL, 0); return 1; } /* * No event flags is equivalent to TCL_ALL_EVENTS. */ if ((flags & TCL_ALL_EVENTS) == 0) { flags |= TCL_ALL_EVENTS; } /* * Set the service mode to none so notifier event routines won't try to * service events recursively. */ oldMode = tsdPtr->serviceMode; tsdPtr->serviceMode = TCL_SERVICE_NONE; /* * The core of this function is an infinite loop, even though we only * service one event. The reason for this is that we may be processing * events that don't do anything inside of Tcl. */ while (1) { /* * If idle events are the only things to service, skip the main part * of the loop and go directly to handle idle events (i.e. don't wait * even if TCL_DONT_WAIT isn't set). */ if ((flags & TCL_ALL_EVENTS) == TCL_IDLE_EVENTS) { flags = TCL_IDLE_EVENTS | TCL_DONT_WAIT; goto idleEvents; } /* * Ask Tcl to service a queued event, if there are any. */ if (Tcl_ServiceEvent(flags)) { result = 1; break; } /* * If TCL_DONT_WAIT is set, be sure to poll rather than blocking, * otherwise reset the block time to infinity. */ if (flags & TCL_DONT_WAIT) { tsdPtr->blockTime.sec = 0; tsdPtr->blockTime.usec = 0; tsdPtr->blockTimeSet = 1; } else { tsdPtr->blockTimeSet = 0; } /* * Set up all the event sources for new events. This will cause the * block time to be updated if necessary. */ tsdPtr->inTraversal = 1; for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; sourcePtr = sourcePtr->nextPtr) { if (sourcePtr->setupProc) { sourcePtr->setupProc(sourcePtr->clientData, flags); } } tsdPtr->inTraversal = 0; if ((flags & TCL_DONT_WAIT) || tsdPtr->blockTimeSet) { timePtr = &tsdPtr->blockTime; } else { timePtr = NULL; } /* * Wait for a new event or a timeout. If Tcl_WaitForEvent returns -1, * we should abort Tcl_DoOneEvent. */ result = Tcl_WaitForEvent(timePtr); if (result < 0) { result = 0; break; } /* * Check all the event sources for new events. */ for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; sourcePtr = sourcePtr->nextPtr) { if (sourcePtr->checkProc) { sourcePtr->checkProc(sourcePtr->clientData, flags); } } /* * Check for events queued by the notifier or event sources. */ if (Tcl_ServiceEvent(flags)) { result = 1; break; } /* * We've tried everything at this point, but nobody we know about had * anything to do. Check for idle events. If none, either quit or go * back to the top and try again. */ idleEvents: if (flags & TCL_IDLE_EVENTS) { if (TclServiceIdle()) { result = 1; break; } } if (flags & TCL_DONT_WAIT) { break; } /* * If Tcl_WaitForEvent has returned 1, indicating that one system event * has been dispatched (and thus that some Tcl code might have been * indirectly executed), we break out of the loop in order, e.g. to * give vwait a chance to determine whether that system event had the * side effect of changing the variable (so the vwait can return and * unwind properly). * * NB: We will process idle events if any first, because otherwise we * might never do the idle events if the notifier always gets * system events. */ if (result) { break; } } tsdPtr->serviceMode = oldMode; return result; } /* *---------------------------------------------------------------------- * * Tcl_ServiceAll -- * * This routine checks all of the event sources, processes events that * are on the Tcl event queue, and then calls the any idle handlers. * Platform specific notifier callbacks that generate events should call * this routine before returning to the system in order to ensure that * Tcl gets a chance to process the new events. * * Results: * Returns 1 if an event or idle handler was invoked, else 0. * * Side effects: * Anything that an event or idle handler may do. * *---------------------------------------------------------------------- */ int Tcl_ServiceAll(void) { int result = 0; EventSource *sourcePtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (tsdPtr->serviceMode == TCL_SERVICE_NONE) { return result; } /* * We need to turn off event servicing like we do in Tcl_DoOneEvent, to * avoid recursive calls. */ tsdPtr->serviceMode = TCL_SERVICE_NONE; /* * Check async handlers first. */ if (Tcl_AsyncReady()) { (void) Tcl_AsyncInvoke(NULL, 0); } /* * Make a single pass through all event sources, queued events, and idle * handlers. Note that we wait to update the notifier timer until the end * so we can avoid multiple changes. */ tsdPtr->inTraversal = 1; tsdPtr->blockTimeSet = 0; for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; sourcePtr = sourcePtr->nextPtr) { if (sourcePtr->setupProc) { sourcePtr->setupProc(sourcePtr->clientData, TCL_ALL_EVENTS); } } for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; sourcePtr = sourcePtr->nextPtr) { if (sourcePtr->checkProc) { sourcePtr->checkProc(sourcePtr->clientData, TCL_ALL_EVENTS); } } while (Tcl_ServiceEvent(0)) { result = 1; } if (TclServiceIdle()) { result = 1; } if (!tsdPtr->blockTimeSet) { Tcl_SetTimer(NULL); } else { Tcl_SetTimer(&tsdPtr->blockTime); } tsdPtr->inTraversal = 0; tsdPtr->serviceMode = TCL_SERVICE_ALL; return result; } /* *---------------------------------------------------------------------- * * Tcl_ThreadAlert -- * * This function wakes up the notifier associated with the specified * thread (if there is one). * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_ThreadAlert( Tcl_ThreadId threadId) /* Identifier for thread to use. */ { ThreadSpecificData *tsdPtr; /* * Find the notifier associated with the specified thread. Note that we * need to hold the listLock while calling Tcl_AlertNotifier to avoid a * race condition where the specified thread might destroy its notifier. */ Tcl_MutexLock(&listLock); for (tsdPtr = firstNotifierPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { if (tsdPtr->threadId == threadId) { Tcl_AlertNotifier(tsdPtr->clientData); break; } } Tcl_MutexUnlock(&listLock); } /* *---------------------------------------------------------------------- * * Tcl_InitNotifier -- * * Initializes the platform specific notifier state. Forwards to the * platform implementation when the hook is not enabled. * * Results: * Returns a handle to the notifier state for this thread.. * * Side effects: * None. * *---------------------------------------------------------------------- */ void * Tcl_InitNotifier(void) { if (tclNotifierHooks.initNotifierProc) { return tclNotifierHooks.initNotifierProc(); } else { return TclpInitNotifier(); } } /* *---------------------------------------------------------------------- * * Tcl_FinalizeNotifier -- * * This function is called to cleanup the notifier state before a thread * is terminated. Forwards to the platform implementation when the hook * is not enabled. * * Results: * None. * * Side effects: * If no finalizeNotifierProc notifier hook exists, TclpFinalizeNotifier * is called. * *---------------------------------------------------------------------- */ void Tcl_FinalizeNotifier( void *clientData) { if (tclNotifierHooks.finalizeNotifierProc) { tclNotifierHooks.finalizeNotifierProc(clientData); } else { TclpFinalizeNotifier(clientData); } } /* *---------------------------------------------------------------------- * * Tcl_AlertNotifier -- * * Wake up the specified notifier from any thread. This routine is called * by the platform independent notifier code whenever the Tcl_ThreadAlert * routine is called. This routine is guaranteed not to be called by Tcl * on a given notifier after Tcl_FinalizeNotifier is called for that * notifier. This routine is typically called from a thread other than * the notifier's thread. Forwards to the platform implementation when * the hook is not enabled. * * Results: * None. * * Side effects: * See the platform-specific implementations. * *---------------------------------------------------------------------- */ void Tcl_AlertNotifier( void *clientData) /* Pointer to thread data. */ { if (tclNotifierHooks.alertNotifierProc) { tclNotifierHooks.alertNotifierProc(clientData); } else { TclpAlertNotifier(clientData); } } /* *---------------------------------------------------------------------- * * Tcl_ServiceModeHook -- * * This function is invoked whenever the service mode changes. Forwards * to the platform implementation when the hook is not enabled. * * Results: * None. * * Side effects: * See the platform-specific implementations. * *---------------------------------------------------------------------- */ void Tcl_ServiceModeHook( int mode) /* Either TCL_SERVICE_ALL, or * TCL_SERVICE_NONE. */ { if (tclNotifierHooks.serviceModeHookProc) { tclNotifierHooks.serviceModeHookProc(mode); } else { TclpServiceModeHook(mode); } } /* *---------------------------------------------------------------------- * * Tcl_SetTimer -- * * This function sets the current notifier timer value. Forwards to the * platform implementation when the hook is not enabled. * * Results: * None. * * Side effects: * See the platform-specific implementations. * *---------------------------------------------------------------------- */ void Tcl_SetTimer( const Tcl_Time *timePtr) /* Timeout value, may be NULL. */ { if (tclNotifierHooks.setTimerProc) { tclNotifierHooks.setTimerProc(timePtr); } else { TclpSetTimer(timePtr); } } /* *---------------------------------------------------------------------- * * Tcl_WaitForEvent -- * * This function is called by Tcl_DoOneEvent to wait for new events on * the notifier's message queue. If the block time is 0, then * Tcl_WaitForEvent just polls without blocking. Forwards to the * platform implementation when the hook is not enabled. * * Results: * Returns -1 if the wait would block forever, 1 if an out-of-loop source * was processed (see platform-specific notes) and otherwise returns 0. * * Side effects: * Queues file events that are detected by the notifier. * *---------------------------------------------------------------------- */ int Tcl_WaitForEvent( const Tcl_Time *timePtr) /* Maximum block time, or NULL. */ { if (tclNotifierHooks.waitForEventProc) { return tclNotifierHooks.waitForEventProc(timePtr); } else { return TclpWaitForEvent(timePtr); } } /* *---------------------------------------------------------------------- * * Tcl_CreateFileHandler -- * * This function registers a file descriptor handler with the notifier. * Forwards to the platform implementation when the hook is not enabled. * * This function is not defined on Windows. The OS API there is too * different. * * Results: * None. * * Side effects: * Creates a new file handler structure. * *---------------------------------------------------------------------- */ #ifndef _WIN32 void Tcl_CreateFileHandler( int fd, /* Handle of stream to watch. */ int mask, /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, and TCL_EXCEPTION: indicates * conditions under which proc should be * called. */ Tcl_FileProc *proc, /* Function to call for each selected * event. */ void *clientData) /* Arbitrary data to pass to proc. */ { if (tclNotifierHooks.createFileHandlerProc) { tclNotifierHooks.createFileHandlerProc(fd, mask, proc, clientData); } else { TclpCreateFileHandler(fd, mask, proc, clientData); } } #endif /* !_WIN32 */ /* *---------------------------------------------------------------------- * * Tcl_DeleteFileHandler -- * * Cancel a previously-arranged callback arrangement for a file * descriptor. Forwards to the platform implementation when the hook is * not enabled. * * This function is not defined on Windows. The OS API there is too * different. * * Results: * None. * * Side effects: * If a callback was previously registered on the file descriptor, remove * it. * *---------------------------------------------------------------------- */ #ifndef _WIN32 void Tcl_DeleteFileHandler( int fd) /* Stream id for which to remove callback * function. */ { if (tclNotifierHooks.deleteFileHandlerProc) { tclNotifierHooks.deleteFileHandlerProc(fd); } else { TclpDeleteFileHandler(fd); } } #endif /* !_WIN32 */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclOO.c0000644000175000017500000025727414726623136014377 0ustar sergeisergei/* * tclOO.c -- * * This file contains the object-system core (NB: not Tcl_Obj, but ::oo) * * Copyright © 2005-2019 Donal K. Fellows * Copyright © 2017 Nathan Coulter * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "tclInt.h" #include "tclOOInt.h" /* * Commands in oo::define and oo::objdefine. */ static const struct { const char *name; Tcl_ObjCmdProc *objProc; int flag; } defineCmds[] = { {"constructor", TclOODefineConstructorObjCmd, 0}, {"definitionnamespace", TclOODefineDefnNsObjCmd, 0}, {"deletemethod", TclOODefineDeleteMethodObjCmd, 0}, {"destructor", TclOODefineDestructorObjCmd, 0}, {"export", TclOODefineExportObjCmd, 0}, {"forward", TclOODefineForwardObjCmd, 0}, {"method", TclOODefineMethodObjCmd, 0}, {"private", TclOODefinePrivateObjCmd, 0}, {"renamemethod", TclOODefineRenameMethodObjCmd, 0}, {"self", TclOODefineSelfObjCmd, 0}, {"unexport", TclOODefineUnexportObjCmd, 0}, {NULL, NULL, 0} }, objdefCmds[] = { {"class", TclOODefineClassObjCmd, 1}, {"deletemethod", TclOODefineDeleteMethodObjCmd, 1}, {"export", TclOODefineExportObjCmd, 1}, {"forward", TclOODefineForwardObjCmd, 1}, {"method", TclOODefineMethodObjCmd, 1}, {"private", TclOODefinePrivateObjCmd, 1}, {"renamemethod", TclOODefineRenameMethodObjCmd, 1}, {"self", TclOODefineObjSelfObjCmd, 0}, {"unexport", TclOODefineUnexportObjCmd, 1}, {NULL, NULL, 0} }; /* * What sort of size of things we like to allocate. */ #define ALLOC_CHUNK 8 /* * Function declarations for things defined in this file. */ static Object * AllocObject(Tcl_Interp *interp, const char *nameStr, Namespace *nsPtr, const char *nsNameStr); static int CloneClassMethod(Tcl_Interp *interp, Class *clsPtr, Method *mPtr, Tcl_Obj *namePtr, Method **newMPtrPtr); static int CloneObjectMethod(Tcl_Interp *interp, Object *oPtr, Method *mPtr, Tcl_Obj *namePtr); static void DeletedHelpersNamespace(void *clientData); static Tcl_NRPostProc FinalizeAlloc; static Tcl_NRPostProc FinalizeNext; static Tcl_NRPostProc FinalizeObjectCall; static inline void InitClassPath(Tcl_Interp * interp, Class *clsPtr); static void InitClassSystemRoots(Tcl_Interp *interp, Foundation *fPtr); static int InitFoundation(Tcl_Interp *interp); static Tcl_InterpDeleteProc KillFoundation; static void MyDeleted(void *clientData); static void ObjectNamespaceDeleted(void *clientData); static Tcl_CommandTraceProc ObjectRenamedTrace; static inline void RemoveClass(Class **list, size_t num, size_t idx); static inline void RemoveObject(Object **list, size_t num, size_t idx); static inline void SquelchCachedName(Object *oPtr); static int PublicNRObjectCmd(void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv); static int PrivateNRObjectCmd(void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv); static int MyClassNRObjCmd(void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv); static void MyClassDeleted(void *clientData); /* * Methods in the oo::object and oo::class classes. First, we define a helper * macro that makes building the method type declaration structure a lot * easier. No point in making life harder than it has to be! * * Note that the core methods don't need clone or free proc callbacks. */ #define DCM(name,visibility,proc) \ {name,visibility,\ {TCL_OO_METHOD_VERSION_CURRENT,"core method: "#name,proc,NULL,NULL}} static const DeclaredClassMethod objMethods[] = { DCM("destroy", 1, TclOO_Object_Destroy), DCM("eval", 0, TclOO_Object_Eval), DCM("unknown", 0, TclOO_Object_Unknown), DCM("variable", 0, TclOO_Object_LinkVar), DCM("varname", 0, TclOO_Object_VarName), {NULL, 0, {0, NULL, NULL, NULL, NULL}} }, clsMethods[] = { DCM("create", 1, TclOO_Class_Create), DCM("new", 1, TclOO_Class_New), DCM("createWithNamespace", 0, TclOO_Class_CreateNs), {NULL, 0, {0, NULL, NULL, NULL, NULL}} }, cfgMethods[] = { DCM("configure", 1, TclOO_Configurable_Configure), {NULL, 0, {0, NULL, NULL, NULL, NULL}} }; /* * And for the oo::class constructor... */ static const Tcl_MethodType classConstructor = { TCL_OO_METHOD_VERSION_CURRENT, "oo::class constructor", TclOO_Class_Constructor, NULL, NULL }; /* * Scripted parts of TclOO. First, the main script (cannot be outside this * file). */ static const char initScript[] = #ifndef TCL_NO_DEPRECATED "package ifneeded TclOO " TCLOO_PATCHLEVEL " {# Already present, OK?};" #endif "package ifneeded tcl::oo " TCLOO_PATCHLEVEL " {# Already present, OK?};" "namespace eval ::oo { variable version " TCLOO_VERSION " };" "namespace eval ::oo { variable patchlevel " TCLOO_PATCHLEVEL " };"; /* "tcl_findLibrary tcloo $oo::version $oo::version" */ /* " tcloo.tcl OO_LIBRARY oo::library;"; */ /* * The scripted part of the definitions of TclOO. */ #include "tclOOScript.h" /* * The actual definition of the variable holding the TclOO stub table. */ MODULE_SCOPE const TclOOStubs tclOOStubs; /* * Convenience macro for getting the foundation from an interpreter. */ #define GetFoundation(interp) \ ((Foundation *)((Interp *)(interp))->objectFoundation) /* * Macros to make inspecting into the guts of an object cleaner. * * The ocPtr parameter (only in these macros) is assumed to work fine with * either an oPtr or a classPtr. Note that the roots oo::object and oo::class * have _both_ their object and class flags tagged with ROOT_OBJECT and * ROOT_CLASS respectively. */ #define Destructing(oPtr) ((oPtr)->flags & OBJECT_DESTRUCTING) #define IsRootObject(ocPtr) ((ocPtr)->flags & ROOT_OBJECT) #define IsRootClass(ocPtr) ((ocPtr)->flags & ROOT_CLASS) #define IsRoot(ocPtr) ((ocPtr)->flags & (ROOT_OBJECT|ROOT_CLASS)) #define RemoveItem(type, lst, i) \ do { \ Remove ## type ((lst).list, (lst).num, i); \ (lst).num--; \ } while (0) /* * ---------------------------------------------------------------------- * * RemoveClass, RemoveObject -- * * Helpers for the RemoveItem macro for deleting a class or object from a * list. Setting the "empty" location to NULL makes debugging a little * easier. * * ---------------------------------------------------------------------- */ static inline void RemoveClass( Class **list, size_t num, size_t idx) { for (; idx + 1 < num; idx++) { list[idx] = list[idx + 1]; } list[idx] = NULL; } static inline void RemoveObject( Object **list, size_t num, size_t idx) { for (; idx + 1 < num; idx++) { list[idx] = list[idx + 1]; } list[idx] = NULL; } /* * ---------------------------------------------------------------------- * * TclOOInit -- * * Called to initialise the OO system within an interpreter. * * Result: * TCL_OK if the setup succeeded. Currently assumed to always work. * * Side effects: * Creates namespaces, commands, several classes and a number of * callbacks. Upon return, the OO system is ready for use. * * ---------------------------------------------------------------------- */ int TclOOInit( Tcl_Interp *interp) /* The interpreter to install into. */ { /* * Build the core of the OO system. */ if (InitFoundation(interp) != TCL_OK) { return TCL_ERROR; } /* * Run our initialization script and, if that works, declare the package * to be fully provided. */ if (Tcl_EvalEx(interp, initScript, TCL_INDEX_NONE, 0) != TCL_OK) { return TCL_ERROR; } #ifndef TCL_NO_DEPRECATED Tcl_PkgProvideEx(interp, "TclOO", TCLOO_PATCHLEVEL, &tclOOStubs); #endif return Tcl_PkgProvideEx(interp, "tcl::oo", TCLOO_PATCHLEVEL, &tclOOStubs); } /* * ---------------------------------------------------------------------- * * TclOOGetFoundation -- * * Get a reference to the OO core class system. * * ---------------------------------------------------------------------- */ Foundation * TclOOGetFoundation( Tcl_Interp *interp) { return GetFoundation(interp); } /* * ---------------------------------------------------------------------- * * CreateCmdInNS -- * * Create a command in a namespace. Supports setting various * implementation functions, but not a deletion callback or a clientData; * it's suitable for use-cases in this file, no more. * * ---------------------------------------------------------------------- */ static inline void CreateCmdInNS( Tcl_Interp *interp, Tcl_Namespace *namespacePtr, const char *name, Tcl_ObjCmdProc *cmdProc, Tcl_ObjCmdProc *nreProc, CompileProc *compileProc) { Command *cmdPtr; if (cmdProc == NULL && nreProc == NULL) { Tcl_Panic("must supply at least one implementation function"); } cmdPtr = (Command *) TclCreateObjCommandInNs(interp, name, namespacePtr, cmdProc, NULL, NULL); cmdPtr->nreProc = nreProc; cmdPtr->compileProc = compileProc; } /* * ---------------------------------------------------------------------- * * InitFoundation -- * * Set up the core of the OO core class system. This is a structure * holding references to the magical bits that need to be known about in * other places, plus the oo::object and oo::class classes. * * ---------------------------------------------------------------------- */ static int InitFoundation( Tcl_Interp *interp) { static Tcl_ThreadDataKey tsdKey; ThreadLocalData *tsdPtr = (ThreadLocalData *) Tcl_GetThreadData(&tsdKey, sizeof(ThreadLocalData)); Foundation *fPtr = (Foundation *) Tcl_Alloc(sizeof(Foundation)); Tcl_Namespace *define, *objdef; Tcl_Obj *namePtr; size_t i; /* * Initialize the structure that holds the OO system core. This is * attached to the interpreter via an assocData entry; not very efficient, * but the best we can do without hacking the core more. */ memset(fPtr, 0, sizeof(Foundation)); ((Interp *) interp)->objectFoundation = fPtr; fPtr->interp = interp; fPtr->ooNs = Tcl_CreateNamespace(interp, "::oo", fPtr, NULL); Tcl_Export(interp, fPtr->ooNs, "[a-z]*", 1); define = Tcl_CreateNamespace(interp, "::oo::define", fPtr, NULL); objdef = Tcl_CreateNamespace(interp, "::oo::objdefine", fPtr, NULL); fPtr->helpersNs = Tcl_CreateNamespace(interp, "::oo::Helpers", fPtr, DeletedHelpersNamespace); Tcl_CreateNamespace(interp, "::oo::configuresupport", NULL, NULL); fPtr->epoch = 1; fPtr->tsdPtr = tsdPtr; TclNewLiteralStringObj(fPtr->unknownMethodNameObj, "unknown"); TclNewLiteralStringObj(fPtr->constructorName, ""); TclNewLiteralStringObj(fPtr->destructorName, ""); TclNewLiteralStringObj(fPtr->clonedName, ""); TclNewLiteralStringObj(fPtr->defineName, "::oo::define"); TclNewLiteralStringObj(fPtr->myName, "my"); TclNewLiteralStringObj(fPtr->mcdName, "::oo::MixinClassDelegates"); Tcl_IncrRefCount(fPtr->unknownMethodNameObj); Tcl_IncrRefCount(fPtr->constructorName); Tcl_IncrRefCount(fPtr->destructorName); Tcl_IncrRefCount(fPtr->clonedName); Tcl_IncrRefCount(fPtr->defineName); Tcl_IncrRefCount(fPtr->myName); Tcl_IncrRefCount(fPtr->mcdName); TclCreateObjCommandInNs(interp, "UnknownDefinition", fPtr->ooNs, TclOOUnknownDefinition, NULL, NULL); TclNewLiteralStringObj(namePtr, "::oo::UnknownDefinition"); Tcl_SetNamespaceUnknownHandler(interp, define, namePtr); Tcl_SetNamespaceUnknownHandler(interp, objdef, namePtr); Tcl_BounceRefCount(namePtr); /* * Create the subcommands in the oo::define and oo::objdefine spaces. */ for (i = 0 ; defineCmds[i].name ; i++) { TclCreateObjCommandInNs(interp, defineCmds[i].name, define, defineCmds[i].objProc, INT2PTR(defineCmds[i].flag), NULL); } for (i = 0 ; objdefCmds[i].name ; i++) { TclCreateObjCommandInNs(interp, objdefCmds[i].name, objdef, objdefCmds[i].objProc, INT2PTR(objdefCmds[i].flag), NULL); } Tcl_CallWhenDeleted(interp, KillFoundation, NULL); /* * Create the special objects at the core of the object system. */ InitClassSystemRoots(interp, fPtr); /* * Basic method declarations for the core classes. */ TclOODefineBasicMethods(fPtr->objectCls, objMethods); TclOODefineBasicMethods(fPtr->classCls, clsMethods); /* * Finish setting up the class of classes by marking the 'new' method as * private; classes, unlike general objects, must have explicit names. We * also need to create the constructor for classes. */ TclNewLiteralStringObj(namePtr, "new"); TclNewInstanceMethod(interp, (Tcl_Object) fPtr->classCls->thisPtr, namePtr /* keeps ref */, 0 /* private */, NULL, NULL); Tcl_BounceRefCount(namePtr); fPtr->classCls->constructorPtr = (Method *) TclNewMethod( (Tcl_Class) fPtr->classCls, NULL, 0, &classConstructor, NULL); /* * Create non-object commands and plug ourselves into the Tcl [info] * ensemble. */ CreateCmdInNS(interp, fPtr->helpersNs, "next", NULL, TclOONextObjCmd, TclCompileObjectNextCmd); CreateCmdInNS(interp, fPtr->helpersNs, "nextto", NULL, TclOONextToObjCmd, TclCompileObjectNextToCmd); CreateCmdInNS(interp, fPtr->helpersNs, "self", TclOOSelfObjCmd, NULL, TclCompileObjectSelfCmd); CreateCmdInNS(interp, fPtr->ooNs, "define", TclOODefineObjCmd, NULL, NULL); CreateCmdInNS(interp, fPtr->ooNs, "objdefine", TclOOObjDefObjCmd, NULL, NULL); CreateCmdInNS(interp, fPtr->ooNs, "copy", TclOOCopyObjectCmd, NULL, NULL); TclOOInitInfo(interp); /* * Now make the class of slots. */ if (TclOODefineSlots(fPtr) != TCL_OK) { return TCL_ERROR; } /* * Make the configurable class and install its standard defined method. */ Tcl_Object cfgCls = Tcl_NewObjectInstance(interp, (Tcl_Class) fPtr->classCls, "::oo::configuresupport::configurable", NULL, TCL_INDEX_NONE, NULL, 0); TclOODefineBasicMethods(((Object *) cfgCls)->classPtr, cfgMethods); /* * Don't have handles to these namespaces, so use Tcl_CreateObjCommand. */ Tcl_CreateObjCommand(interp, "::oo::configuresupport::configurableobject::property", TclOODefinePropertyCmd, (void *) 1, NULL); Tcl_CreateObjCommand(interp, "::oo::configuresupport::configurableclass::property", TclOODefinePropertyCmd, (void *) 0, NULL); /* * Evaluate the remaining definitions, which are a compiled-in Tcl script. */ return Tcl_EvalEx(interp, tclOOSetupScript, TCL_INDEX_NONE, 0); } /* * ---------------------------------------------------------------------- * * InitClassSystemRoots -- * * Creates the objects at the core of the object system. These need to be * spliced manually. * * ---------------------------------------------------------------------- */ static void InitClassSystemRoots( Tcl_Interp *interp, Foundation *fPtr) { Class fakeCls; Object fakeObject; Tcl_Obj *defNsName; /* Stand up a phony class for bootstrapping. */ fPtr->objectCls = &fakeCls; /* referenced in TclOOAllocClass to increment the refCount. */ fakeCls.thisPtr = &fakeObject; fakeObject.refCount = 0; // Do not increment an uninitialized value. fPtr->objectCls = TclOOAllocClass(interp, AllocObject(interp, "object", (Namespace *) fPtr->ooNs, NULL)); // Corresponding TclOODecrRefCount in KillFoundation AddRef(fPtr->objectCls->thisPtr); /* * This is why it is unnecessary in this routine to replace the * incremented reference count of fPtr->objectCls that was swallowed by * fakeObject. */ fPtr->objectCls->superclasses.num = 0; Tcl_Free(fPtr->objectCls->superclasses.list); fPtr->objectCls->superclasses.list = NULL; /* * Special initialization for the primordial objects. */ fPtr->objectCls->thisPtr->flags |= ROOT_OBJECT; fPtr->objectCls->flags |= ROOT_OBJECT; TclNewLiteralStringObj(defNsName, "::oo::objdefine"); fPtr->objectCls->objDefinitionNs = defNsName; Tcl_IncrRefCount(defNsName); fPtr->classCls = TclOOAllocClass(interp, AllocObject(interp, "class", (Namespace *) fPtr->ooNs, NULL)); // Corresponding TclOODecrRefCount in KillFoundation AddRef(fPtr->classCls->thisPtr); /* * Increment reference counts for each reference because these * relationships can be dynamically changed. * * Corresponding TclOODecrRefCount for all incremented refcounts is in * KillFoundation. */ /* * Rewire bootstrapped objects. */ fPtr->objectCls->thisPtr->selfCls = fPtr->classCls; AddRef(fPtr->classCls->thisPtr); TclOOAddToInstances(fPtr->objectCls->thisPtr, fPtr->classCls); fPtr->classCls->thisPtr->selfCls = fPtr->classCls; AddRef(fPtr->classCls->thisPtr); TclOOAddToInstances(fPtr->classCls->thisPtr, fPtr->classCls); fPtr->classCls->thisPtr->flags |= ROOT_CLASS; fPtr->classCls->flags |= ROOT_CLASS; TclNewLiteralStringObj(defNsName, "::oo::define"); fPtr->classCls->clsDefinitionNs = defNsName; Tcl_IncrRefCount(defNsName); /* Standard initialization for new Objects */ TclOOAddToSubclasses(fPtr->classCls, fPtr->objectCls); /* * THIS IS THE ONLY FUNCTION THAT DOES NON-STANDARD CLASS SPLICING. * Everything else is careful to prohibit looping. */ } /* * ---------------------------------------------------------------------- * * DeletedHelpersNamespace -- * * Simple helper used to clear fields of the foundation when they no * longer hold useful information. * * ---------------------------------------------------------------------- */ static void DeletedHelpersNamespace( void *clientData) { Foundation *fPtr = (Foundation *) clientData; fPtr->helpersNs = NULL; } /* * ---------------------------------------------------------------------- * * KillFoundation -- * * Delete those parts of the OO core that are not deleted automatically * when the objects and classes themselves are destroyed. * * ---------------------------------------------------------------------- */ static void KillFoundation( TCL_UNUSED(void *), Tcl_Interp *interp) /* The interpreter containing the OO system * foundation. */ { Foundation *fPtr = GetFoundation(interp); TclDecrRefCount(fPtr->unknownMethodNameObj); TclDecrRefCount(fPtr->constructorName); TclDecrRefCount(fPtr->destructorName); TclDecrRefCount(fPtr->clonedName); TclDecrRefCount(fPtr->defineName); TclDecrRefCount(fPtr->myName); TclDecrRefCount(fPtr->mcdName); TclOODecrRefCount(fPtr->objectCls->thisPtr); TclOODecrRefCount(fPtr->classCls->thisPtr); Tcl_Free(fPtr); /* * Don't leave the interpreter field pointing to freed data. */ ((Interp *) interp)->objectFoundation = NULL; } /* * ---------------------------------------------------------------------- * * AllocObject -- * * Allocate an object of basic type. Does not splice the object into its * class's instance list. The caller must set the classPtr on the object * to either a class or NULL, call TclOOAddToInstances to add the object * to the class's instance list, and if the object itself is a class, use * call TclOOAddToSubclasses() to add it to the right class's list of * subclasses. * * Returns: * Pointer to the object structure created, or NULL if a specific * namespace was asked for but couldn't be created. * * ---------------------------------------------------------------------- */ static Object * AllocObject( Tcl_Interp *interp, /* Interpreter within which to create the * object. */ const char *nameStr, /* The name of the object to create, or NULL * if the OO system should pick the object * name itself (equal to the namespace * name). */ Namespace *nsPtr, /* The namespace to create the object in, or * NULL if *nameStr is NULL */ const char *nsNameStr) /* The name of the namespace to create, or * NULL if the OO system should pick a unique * name itself. If this is non-NULL but names * a namespace that already exists, the effect * will be the same as if this was NULL. */ { Foundation *fPtr = GetFoundation(interp); Object *oPtr; Command *cmdPtr; CommandTrace *tracePtr; size_t creationEpoch; oPtr = (Object *) Tcl_Alloc(sizeof(Object)); memset(oPtr, 0, sizeof(Object)); /* * Every object has a namespace; make one. Note that this also normally * computes the creation epoch value for the object, a sequence number * that is unique to the object (and which allows us to manage method * caching without comparing pointers). * * When creating a namespace, we first check to see if the caller * specified the name for the namespace. If not, we generate namespace * names using the epoch until such time as a new namespace is actually * created. */ if (nsNameStr != NULL) { oPtr->namespacePtr = Tcl_CreateNamespace(interp, nsNameStr, oPtr, NULL); if (oPtr->namespacePtr == NULL) { /* * Couldn't make the specific namespace. Report as an error. * [Bug 154f0982f2] */ Tcl_Free(oPtr); return NULL; } creationEpoch = ++fPtr->tsdPtr->nsCount; goto configNamespace; } while (1) { char objName[10 + TCL_INTEGER_SPACE]; snprintf(objName, sizeof(objName), "::oo::Obj%" TCL_Z_MODIFIER "u", ++fPtr->tsdPtr->nsCount); oPtr->namespacePtr = Tcl_CreateNamespace(interp, objName, oPtr, NULL); if (oPtr->namespacePtr != NULL) { creationEpoch = fPtr->tsdPtr->nsCount; break; } /* * Could not make that namespace, so we make another. But first we * have to get rid of the error message from Tcl_CreateNamespace, * since that's something that should not be exposed to the user. */ Tcl_ResetResult(interp); } configNamespace: ((Namespace *) oPtr->namespacePtr)->refCount++; /* * Make the namespace know about the helper commands. This grants access * to the [self] and [next] commands. */ if (fPtr->helpersNs != NULL) { TclSetNsPath((Namespace *) oPtr->namespacePtr, 1, &fPtr->helpersNs); } TclOOSetupVariableResolver(oPtr->namespacePtr); /* * Suppress use of compiled versions of the commands in this object's * namespace and its children; causes wrong behaviour without expensive * recompilation. [Bug 2037727] */ ((Namespace *) oPtr->namespacePtr)->flags |= NS_SUPPRESS_COMPILATION; /* * Set up a callback to get notification of the deletion of a namespace * when enough of the namespace still remains to execute commands and * access variables in it. [Bug 2950259] */ ((Namespace *) oPtr->namespacePtr)->earlyDeleteProc = ObjectNamespaceDeleted; /* * Fill in the rest of the non-zero/NULL parts of the structure. */ oPtr->fPtr = fPtr; oPtr->creationEpoch = creationEpoch; /* * An object starts life with a refCount of 2 to mark the two stages of * destruction it occur: A call to ObjectRenamedTrace(), and a call to * ObjectNamespaceDeleted(). */ oPtr->refCount = 2; oPtr->flags = USE_CLASS_CACHE; /* * Finally, create the object commands and initialize the trace on the * public command (so that the object structures are deleted when the * command is deleted). */ if (!nameStr) { nameStr = oPtr->namespacePtr->name; nsPtr = (Namespace *) oPtr->namespacePtr; if (nsPtr->parentPtr != NULL) { nsPtr = nsPtr->parentPtr; } } oPtr->command = TclCreateObjCommandInNs(interp, nameStr, (Tcl_Namespace *) nsPtr, TclOOPublicObjectCmd, oPtr, NULL); /* * Add the NRE command and trace directly. While this breaks a number of * abstractions, it is faster and we're inside Tcl here so we're allowed. */ cmdPtr = (Command *) oPtr->command; cmdPtr->nreProc = PublicNRObjectCmd; cmdPtr->tracePtr = tracePtr = (CommandTrace *) Tcl_Alloc(sizeof(CommandTrace)); tracePtr->traceProc = ObjectRenamedTrace; tracePtr->clientData = oPtr; tracePtr->flags = TCL_TRACE_RENAME|TCL_TRACE_DELETE; tracePtr->nextPtr = NULL; tracePtr->refCount = 1; oPtr->myCommand = TclNRCreateCommandInNs(interp, "my", oPtr->namespacePtr, TclOOPrivateObjectCmd, PrivateNRObjectCmd, oPtr, MyDeleted); oPtr->myclassCommand = TclNRCreateCommandInNs(interp, "myclass", oPtr->namespacePtr, TclOOMyClassObjCmd, MyClassNRObjCmd, oPtr, MyClassDeleted); return oPtr; } /* * ---------------------------------------------------------------------- * * SquelchCachedName -- * * Encapsulates how to throw away a cached object name. Called from * object rename traces and at object destruction. * * ---------------------------------------------------------------------- */ static inline void SquelchCachedName( Object *oPtr) { if (oPtr->cachedNameObj) { Tcl_DecrRefCount(oPtr->cachedNameObj); oPtr->cachedNameObj = NULL; } } /* * ---------------------------------------------------------------------- * * MyDeleted, MyClassDeleted -- * * These callbacks are triggered when the object's [my] or [myclass] * commands are deleted by any mechanism. They just mark the object as * not having a [my] command or [myclass] command, and so prevent cleanup * of those commands when the object itself is deleted. * * ---------------------------------------------------------------------- */ static void MyDeleted( void *clientData) /* Reference to the object whose [my] has been * squelched. */ { Object *oPtr = (Object *) clientData; oPtr->myCommand = NULL; } static void MyClassDeleted( void *clientData) { Object *oPtr = (Object *) clientData; oPtr->myclassCommand = NULL; } /* * ---------------------------------------------------------------------- * * ObjectRenamedTrace -- * * This callback is triggered when the object is deleted by any * mechanism. It runs the destructors and arranges for the actual cleanup * of the object's namespace, which in turn triggers cleansing of the * object data structures. * * ---------------------------------------------------------------------- */ static void ObjectRenamedTrace( void *clientData, /* The object being deleted. */ TCL_UNUSED(Tcl_Interp *), TCL_UNUSED(const char *) /*oldName*/, TCL_UNUSED(const char *) /*newName*/, int flags) /* Why was the object deleted? */ { Object *oPtr = (Object *) clientData; /* * If this is a rename and not a delete of the object, we just flush the * cache of the object name. */ if (flags & TCL_TRACE_RENAME) { SquelchCachedName(oPtr); return; } /* * The namespace is only deleted if it hasn't already been deleted. [Bug * 2950259]. */ if (!Destructing(oPtr)) { Tcl_DeleteNamespace(oPtr->namespacePtr); } oPtr->command = NULL; TclOODecrRefCount(oPtr); return; } /* * ---------------------------------------------------------------------- * * TclOODeleteDescendants -- * * Delete all descendants of a particular class. * * ---------------------------------------------------------------------- */ void TclOODeleteDescendants( Tcl_Interp *interp, /* The interpreter containing the class. */ Object *oPtr) /* The object representing the class. */ { Class *clsPtr = oPtr->classPtr, *subclassPtr, *mixinSubclassPtr; Object *instancePtr; /* * Squelch classes that this class has been mixed into. */ if (clsPtr->mixinSubs.num > 0) { while (clsPtr->mixinSubs.num > 0) { mixinSubclassPtr = clsPtr->mixinSubs.list[clsPtr->mixinSubs.num - 1]; /* * This condition also covers the case where mixinSubclassPtr == * clsPtr */ if (!Destructing(mixinSubclassPtr->thisPtr) && !(mixinSubclassPtr->thisPtr->flags & DONT_DELETE)) { Tcl_DeleteCommandFromToken(interp, mixinSubclassPtr->thisPtr->command); } TclOORemoveFromMixinSubs(mixinSubclassPtr, clsPtr); } } if (clsPtr->mixinSubs.size > 0) { Tcl_Free(clsPtr->mixinSubs.list); clsPtr->mixinSubs.size = 0; } /* * Squelch subclasses of this class. */ if (clsPtr->subclasses.num > 0) { while (clsPtr->subclasses.num > 0) { subclassPtr = clsPtr->subclasses.list[clsPtr->subclasses.num - 1]; if (!Destructing(subclassPtr->thisPtr) && !IsRoot(subclassPtr) && !(subclassPtr->thisPtr->flags & DONT_DELETE)) { Tcl_DeleteCommandFromToken(interp, subclassPtr->thisPtr->command); } TclOORemoveFromSubclasses(subclassPtr, clsPtr); } } if (clsPtr->subclasses.size > 0) { Tcl_Free(clsPtr->subclasses.list); clsPtr->subclasses.list = NULL; clsPtr->subclasses.size = 0; } /* * Squelch instances of this class (includes objects we're mixed into). */ if (clsPtr->instances.num > 0) { while (clsPtr->instances.num > 0) { instancePtr = clsPtr->instances.list[clsPtr->instances.num - 1]; /* * This condition also covers the case where instancePtr == oPtr */ if (!Destructing(instancePtr) && !IsRoot(instancePtr) && !(instancePtr->flags & DONT_DELETE)) { Tcl_DeleteCommandFromToken(interp, instancePtr->command); } TclOORemoveFromInstances(instancePtr, clsPtr); } } if (clsPtr->instances.size > 0) { Tcl_Free(clsPtr->instances.list); clsPtr->instances.list = NULL; clsPtr->instances.size = 0; } } /* * ---------------------------------------------------------------------- * * TclOOReleaseClassContents -- * * Tear down the special class data structure, including deleting all * dependent classes and objects. * * ---------------------------------------------------------------------- */ void TclOOReleaseClassContents( Tcl_Interp *interp, /* The interpreter containing the class. */ Object *oPtr) /* The object representing the class. */ { FOREACH_HASH_DECLS; Tcl_Size i; Class *clsPtr = oPtr->classPtr, *tmpClsPtr; Method *mPtr; Foundation *fPtr = oPtr->fPtr; Tcl_Obj *variableObj; PrivateVariableMapping *privateVariable; /* * Sanity check! */ if (!Destructing(oPtr)) { if (IsRootClass(oPtr)) { Tcl_Panic("deleting class structure for non-deleted %s", "::oo::class"); } else if (IsRootObject(oPtr)) { Tcl_Panic("deleting class structure for non-deleted %s", "::oo::object"); } } /* * Stop using the class for definition information. */ if (clsPtr->clsDefinitionNs) { Tcl_DecrRefCount(clsPtr->clsDefinitionNs); clsPtr->clsDefinitionNs = NULL; } if (clsPtr->objDefinitionNs) { Tcl_DecrRefCount(clsPtr->objDefinitionNs); clsPtr->objDefinitionNs = NULL; } /* * Squelch method implementation chain caches. */ if (clsPtr->constructorChainPtr) { TclOODeleteChain(clsPtr->constructorChainPtr); clsPtr->constructorChainPtr = NULL; } if (clsPtr->destructorChainPtr) { TclOODeleteChain(clsPtr->destructorChainPtr); clsPtr->destructorChainPtr = NULL; } if (clsPtr->classChainCache) { CallChain *callPtr; FOREACH_HASH_VALUE(callPtr, clsPtr->classChainCache) { TclOODeleteChain(callPtr); } Tcl_DeleteHashTable(clsPtr->classChainCache); Tcl_Free(clsPtr->classChainCache); clsPtr->classChainCache = NULL; } /* * Squelch the property lists. */ TclOOReleasePropertyStorage(&clsPtr->properties); /* * Squelch our filter list. */ if (clsPtr->filters.num) { Tcl_Obj *filterObj; FOREACH(filterObj, clsPtr->filters) { TclDecrRefCount(filterObj); } Tcl_Free(clsPtr->filters.list); clsPtr->filters.list = NULL; clsPtr->filters.num = 0; } /* * Squelch our metadata. */ if (clsPtr->metadataPtr != NULL) { Tcl_ObjectMetadataType *metadataTypePtr; void *value; FOREACH_HASH(metadataTypePtr, value, clsPtr->metadataPtr) { metadataTypePtr->deleteProc(value); } Tcl_DeleteHashTable(clsPtr->metadataPtr); Tcl_Free(clsPtr->metadataPtr); clsPtr->metadataPtr = NULL; } if (clsPtr->mixins.num) { FOREACH(tmpClsPtr, clsPtr->mixins) { TclOORemoveFromMixinSubs(clsPtr, tmpClsPtr); TclOODecrRefCount(tmpClsPtr->thisPtr); } Tcl_Free(clsPtr->mixins.list); clsPtr->mixins.list = NULL; clsPtr->mixins.num = 0; } if (clsPtr->superclasses.num > 0) { FOREACH(tmpClsPtr, clsPtr->superclasses) { TclOORemoveFromSubclasses(clsPtr, tmpClsPtr); TclOODecrRefCount(tmpClsPtr->thisPtr); } Tcl_Free(clsPtr->superclasses.list); clsPtr->superclasses.num = 0; clsPtr->superclasses.list = NULL; } FOREACH_HASH_VALUE(mPtr, &clsPtr->classMethods) { /* instance gets deleted, so if method remains, reset it there */ if (mPtr->refCount > 1 && mPtr->declaringClassPtr == clsPtr) { mPtr->declaringClassPtr = NULL; } TclOODelMethodRef(mPtr); } Tcl_DeleteHashTable(&clsPtr->classMethods); TclOODelMethodRef(clsPtr->constructorPtr); TclOODelMethodRef(clsPtr->destructorPtr); FOREACH(variableObj, clsPtr->variables) { TclDecrRefCount(variableObj); } if (i) { Tcl_Free(clsPtr->variables.list); } FOREACH_STRUCT(privateVariable, clsPtr->privateVariables) { TclDecrRefCount(privateVariable->variableObj); TclDecrRefCount(privateVariable->fullNameObj); } if (i) { Tcl_Free(clsPtr->privateVariables.list); } if (IsRootClass(oPtr) && !Destructing(fPtr->objectCls->thisPtr)) { Tcl_DeleteCommandFromToken(interp, fPtr->objectCls->thisPtr->command); } } /* * ---------------------------------------------------------------------- * * ObjectNamespaceDeleted -- * * Callback when the object's namespace is deleted. Used to clean up the * data structures associated with the object. The complicated bit is * that this can sometimes happen before the object's command is deleted * (interpreter teardown is complex!) * * ---------------------------------------------------------------------- */ static void ObjectNamespaceDeleted( void *clientData) /* Pointer to the class whose namespace is * being deleted. */ { Object *oPtr = (Object *) clientData; Foundation *fPtr = oPtr->fPtr; FOREACH_HASH_DECLS; Class *mixinPtr; Method *mPtr; Tcl_Obj *filterObj, *variableObj; PrivateVariableMapping *privateVariable; Tcl_Interp *interp = fPtr->interp; Tcl_Size i; if (Destructing(oPtr)) { /* * TODO: Can ObjectNamespaceDeleted ever be called twice? If not, * this guard could be removed. */ return; } /* * One rule for the teardown routines is that if an object is in the * process of being deleted, nothing else may modify its bookkeeping * records. This is the flag that */ oPtr->flags |= OBJECT_DESTRUCTING; /* * Let the dominoes fall! */ if (oPtr->classPtr) { TclOODeleteDescendants(interp, oPtr); } /* * We do not run destructors on the core class objects when the * interpreter is being deleted; their incestuous nature causes problems * in that case when the destructor is partially deleted before the uses * of it have gone. [Bug 2949397] */ if (!Tcl_InterpDeleted(interp) && !(oPtr->flags & DESTRUCTOR_CALLED)) { CallContext *contextPtr = TclOOGetCallContext(oPtr, NULL, DESTRUCTOR, NULL, NULL, NULL); oPtr->flags |= DESTRUCTOR_CALLED; if (contextPtr != NULL) { int result; Tcl_InterpState state; contextPtr->callPtr->flags |= DESTRUCTOR; contextPtr->skip = 0; state = Tcl_SaveInterpState(interp, TCL_OK); result = Tcl_NRCallObjProc(interp, TclOOInvokeContext, contextPtr, 0, NULL); if (result != TCL_OK) { Tcl_BackgroundException(interp, result); } Tcl_RestoreInterpState(interp, state); TclOODeleteContext(contextPtr); } } /* * Instruct everyone to no longer use any allocated fields of the object. * Also delete the command that refers to the object at this point (if it * still exists) because otherwise its pointer to the object points into * freed memory. */ if (((Command *) oPtr->command)->flags && CMD_DYING) { /* * Something has already started the command deletion process. We can * go ahead and clean up the namespace, */ } else { /* * The namespace must have been deleted directly. Delete the command * as well. */ Tcl_DeleteCommandFromToken(interp, oPtr->command); } if (oPtr->myclassCommand) { Tcl_DeleteCommandFromToken(interp, oPtr->myclassCommand); } if (oPtr->myCommand) { Tcl_DeleteCommandFromToken(interp, oPtr->myCommand); } /* * Splice the object out of its context. After this, we must *not* call * methods on the object. */ // TODO: Should this be protected with a !IsRoot() condition? TclOORemoveFromInstances(oPtr, oPtr->selfCls); if (oPtr->mixins.num > 0) { FOREACH(mixinPtr, oPtr->mixins) { TclOORemoveFromInstances(oPtr, mixinPtr); TclOODecrRefCount(mixinPtr->thisPtr); } if (oPtr->mixins.list != NULL) { Tcl_Free(oPtr->mixins.list); } } FOREACH(filterObj, oPtr->filters) { TclDecrRefCount(filterObj); } if (i) { Tcl_Free(oPtr->filters.list); } if (oPtr->methodsPtr) { FOREACH_HASH_VALUE(mPtr, oPtr->methodsPtr) { /* instance gets deleted, so if method remains, reset it there */ if (mPtr->refCount > 1 && mPtr->declaringObjectPtr == oPtr) { mPtr->declaringObjectPtr = NULL; } TclOODelMethodRef(mPtr); } Tcl_DeleteHashTable(oPtr->methodsPtr); Tcl_Free(oPtr->methodsPtr); } FOREACH(variableObj, oPtr->variables) { TclDecrRefCount(variableObj); } if (i) { Tcl_Free(oPtr->variables.list); } FOREACH_STRUCT(privateVariable, oPtr->privateVariables) { TclDecrRefCount(privateVariable->variableObj); TclDecrRefCount(privateVariable->fullNameObj); } if (i) { Tcl_Free(oPtr->privateVariables.list); } if (oPtr->chainCache) { TclOODeleteChainCache(oPtr->chainCache); } SquelchCachedName(oPtr); if (oPtr->metadataPtr != NULL) { Tcl_ObjectMetadataType *metadataTypePtr; void *value; FOREACH_HASH(metadataTypePtr, value, oPtr->metadataPtr) { metadataTypePtr->deleteProc(value); } Tcl_DeleteHashTable(oPtr->metadataPtr); Tcl_Free(oPtr->metadataPtr); oPtr->metadataPtr = NULL; } /* * Squelch the property lists. */ TclOOReleasePropertyStorage(&oPtr->properties); /* * Because an object can be a class that is an instance of itself, the * class object's class structure should only be cleaned after most of * the cleanup on the object is done. * * The class of objects needs some special care; if it is deleted (and * we're not killing the whole interpreter) we force the delete of the * class of classes now as well. Due to the incestuous nature of those two * classes, if one goes the other must too and yet the tangle can * sometimes not go away automatically; we force it here. [Bug 2962664] */ if (IsRootObject(oPtr) && !Destructing(fPtr->classCls->thisPtr) && !Tcl_InterpDeleted(interp)) { Tcl_DeleteCommandFromToken(interp, fPtr->classCls->thisPtr->command); } if (oPtr->classPtr != NULL) { TclOOReleaseClassContents(interp, oPtr); } /* * Delete the object structure itself. */ TclNsDecrRefCount((Namespace *) oPtr->namespacePtr); oPtr->namespacePtr = NULL; TclOODecrRefCount(oPtr->selfCls->thisPtr); oPtr->selfCls = NULL; TclOODecrRefCount(oPtr); return; } /* * ---------------------------------------------------------------------- * * TclOODecrRefCount -- * * Decrement the refcount of an object and deallocate storage then object * is no longer referenced. Returns 1 if storage was deallocated, and 0 * otherwise. * * ---------------------------------------------------------------------- */ int TclOODecrRefCount( Object *oPtr) { if (oPtr->refCount-- <= 1) { if (oPtr->classPtr != NULL) { Tcl_Free(oPtr->classPtr); } Tcl_Free(oPtr); return 1; } return 0; } /* * ---------------------------------------------------------------------- * * TclOOObjectDestroyed -- * * Returns TCL_OK if an object is entirely deleted, i.e. the destruction * sequence has completed. * * ---------------------------------------------------------------------- */ int TclOOObjectDestroyed( Object *oPtr) { return (oPtr->namespacePtr == NULL); } /* * ---------------------------------------------------------------------- * * TclOORemoveFromInstances -- * * Utility function to remove an object from the list of instances within * a class. * * ---------------------------------------------------------------------- */ int TclOORemoveFromInstances( Object *oPtr, /* The instance to remove. */ Class *clsPtr) /* The class (possibly) containing the * reference to the instance. */ { Tcl_Size i; int res = 0; Object *instPtr; FOREACH(instPtr, clsPtr->instances) { if (oPtr == instPtr) { RemoveItem(Object, clsPtr->instances, i); TclOODecrRefCount(oPtr); res++; break; } } return res; } /* * ---------------------------------------------------------------------- * * TclOOAddToInstances -- * * Utility function to add an object to the list of instances within a * class. * * ---------------------------------------------------------------------- */ void TclOOAddToInstances( Object *oPtr, /* The instance to add. */ Class *clsPtr) /* The class to add the instance to. It is * assumed that the class is not already * present as an instance in the class. */ { if (clsPtr->instances.num >= clsPtr->instances.size) { clsPtr->instances.size += ALLOC_CHUNK; if (clsPtr->instances.size == ALLOC_CHUNK) { clsPtr->instances.list = (Object **) Tcl_Alloc(sizeof(Object *) * ALLOC_CHUNK); } else { clsPtr->instances.list = (Object **) Tcl_Realloc(clsPtr->instances.list, sizeof(Object *) * clsPtr->instances.size); } } clsPtr->instances.list[clsPtr->instances.num++] = oPtr; AddRef(oPtr); } /* * ---------------------------------------------------------------------- * * TclOORemoveFromMixins -- * * Utility function to remove a class from the list of mixins within an * object. * * ---------------------------------------------------------------------- */ int TclOORemoveFromMixins( Class *mixinPtr, /* The mixin to remove. */ Object *oPtr) /* The object (possibly) containing the * reference to the mixin. */ { Tcl_Size i; int res = 0; Class *mixPtr; FOREACH(mixPtr, oPtr->mixins) { if (mixinPtr == mixPtr) { RemoveItem(Class, oPtr->mixins, i); TclOODecrRefCount(mixPtr->thisPtr); res++; break; } } if (oPtr->mixins.num == 0) { Tcl_Free(oPtr->mixins.list); oPtr->mixins.list = NULL; } return res; } /* * ---------------------------------------------------------------------- * * TclOORemoveFromSubclasses -- * * Utility function to remove a class from the list of subclasses within * another class. Returns the number of removals performed. * * ---------------------------------------------------------------------- */ int TclOORemoveFromSubclasses( Class *subPtr, /* The subclass to remove. */ Class *superPtr) /* The superclass to possibly remove the * subclass reference from. */ { Tcl_Size i; int res = 0; Class *subclsPtr; FOREACH(subclsPtr, superPtr->subclasses) { if (subPtr == subclsPtr) { RemoveItem(Class, superPtr->subclasses, i); TclOODecrRefCount(subPtr->thisPtr); res++; } } return res; } /* * ---------------------------------------------------------------------- * * TclOOAddToSubclasses -- * * Utility function to add a class to the list of subclasses within * another class. * * ---------------------------------------------------------------------- */ void TclOOAddToSubclasses( Class *subPtr, /* The subclass to add. */ Class *superPtr) /* The superclass to add the subclass to. It * is assumed that the class is not already * present as a subclass in the superclass. */ { if (Destructing(superPtr->thisPtr)) { return; } if (superPtr->subclasses.num >= superPtr->subclasses.size) { superPtr->subclasses.size += ALLOC_CHUNK; if (superPtr->subclasses.size == ALLOC_CHUNK) { superPtr->subclasses.list = (Class **) Tcl_Alloc(sizeof(Class *) * ALLOC_CHUNK); } else { superPtr->subclasses.list = (Class **) Tcl_Realloc(superPtr->subclasses.list, sizeof(Class *) * superPtr->subclasses.size); } } superPtr->subclasses.list[superPtr->subclasses.num++] = subPtr; AddRef(subPtr->thisPtr); } /* * ---------------------------------------------------------------------- * * TclOORemoveFromMixinSubs -- * * Utility function to remove a class from the list of mixinSubs within * another class. * * ---------------------------------------------------------------------- */ int TclOORemoveFromMixinSubs( Class *subPtr, /* The subclass to remove. */ Class *superPtr) /* The superclass to possibly remove the * subclass reference from. */ { Tcl_Size i; int res = 0; Class *subclsPtr; FOREACH(subclsPtr, superPtr->mixinSubs) { if (subPtr == subclsPtr) { RemoveItem(Class, superPtr->mixinSubs, i); TclOODecrRefCount(subPtr->thisPtr); res++; break; } } return res; } /* * ---------------------------------------------------------------------- * * TclOOAddToMixinSubs -- * * Utility function to add a class to the list of mixinSubs within * another class. * * ---------------------------------------------------------------------- */ void TclOOAddToMixinSubs( Class *subPtr, /* The subclass to add. */ Class *superPtr) /* The superclass to add the subclass to. It * is assumed that the class is not already * present as a subclass in the superclass. */ { if (Destructing(superPtr->thisPtr)) { return; } if (superPtr->mixinSubs.num >= superPtr->mixinSubs.size) { superPtr->mixinSubs.size += ALLOC_CHUNK; if (superPtr->mixinSubs.size == ALLOC_CHUNK) { superPtr->mixinSubs.list = (Class **) Tcl_Alloc(sizeof(Class *) * ALLOC_CHUNK); } else { superPtr->mixinSubs.list = (Class **) Tcl_Realloc(superPtr->mixinSubs.list, sizeof(Class *) * superPtr->mixinSubs.size); } } superPtr->mixinSubs.list[superPtr->mixinSubs.num++] = subPtr; AddRef(subPtr->thisPtr); } /* * ---------------------------------------------------------------------- * * TclOOAllocClass -- * * Allocate a basic class. Does not add class to its class's instance * list. * * ---------------------------------------------------------------------- */ static inline void InitClassPath( Tcl_Interp *interp, Class *clsPtr) { Foundation *fPtr = GetFoundation(interp); if (fPtr->helpersNs != NULL) { Tcl_Namespace *path[2]; path[0] = fPtr->helpersNs; path[1] = fPtr->ooNs; TclSetNsPath((Namespace *) clsPtr->thisPtr->namespacePtr, 2, path); } else { TclSetNsPath((Namespace *) clsPtr->thisPtr->namespacePtr, 1, &fPtr->ooNs); } } Class * TclOOAllocClass( Tcl_Interp *interp, /* Interpreter within which to allocate the * class. */ Object *useThisObj) /* Object that is to act as the class * representation. */ { Foundation *fPtr = GetFoundation(interp); Class *clsPtr = (Class *) Tcl_Alloc(sizeof(Class)); memset(clsPtr, 0, sizeof(Class)); clsPtr->thisPtr = useThisObj; /* * Configure the namespace path for the class's object. */ InitClassPath(interp, clsPtr); /* * Classes are subclasses of oo::object, i.e. the objects they create are * objects. */ clsPtr->superclasses.num = 1; clsPtr->superclasses.list = (Class **) Tcl_Alloc(sizeof(Class *)); clsPtr->superclasses.list[0] = fPtr->objectCls; AddRef(fPtr->objectCls->thisPtr); /* * Finish connecting the class structure to the object structure. */ clsPtr->thisPtr->classPtr = clsPtr; /* * That's the complicated bit. Now fill in the rest of the non-zero/NULL * fields. */ Tcl_InitObjHashTable(&clsPtr->classMethods); return clsPtr; } /* * ---------------------------------------------------------------------- * * Tcl_NewObjectInstance -- * * Allocate a new instance of an object. * * ---------------------------------------------------------------------- */ Tcl_Object Tcl_NewObjectInstance( Tcl_Interp *interp, /* Interpreter context. */ Tcl_Class cls, /* Class to create an instance of. */ const char *nameStr, /* Name of object to create, or NULL to ask * the code to pick its own unique name. */ const char *nsNameStr, /* Name of namespace to create inside object, * or NULL to ask the code to pick its own * unique name. */ Tcl_Size objc, /* Number of arguments. Negative value means * do not call constructor. */ Tcl_Obj *const *objv, /* Argument list. */ Tcl_Size skip) /* Number of arguments to _not_ pass to the * constructor. */ { Class *classPtr = (Class *) cls; Object *oPtr; void *clientData[4]; oPtr = TclNewObjectInstanceCommon(interp, classPtr, nameStr, nsNameStr); if (oPtr == NULL) { return NULL; } /* * Run constructors, except when objc < 0, which is a special flag case * used for object cloning only. */ if (objc != TCL_INDEX_NONE) { CallContext *contextPtr = TclOOGetCallContext(oPtr, NULL, CONSTRUCTOR, NULL, NULL, NULL); if (contextPtr != NULL) { int isRoot, result; Tcl_InterpState state; state = Tcl_SaveInterpState(interp, TCL_OK); contextPtr->callPtr->flags |= CONSTRUCTOR; contextPtr->skip = skip; /* * Adjust the ensemble tracking record if necessary. [Bug 3514761] */ isRoot = TclInitRewriteEnsemble(interp, skip, skip, objv); result = Tcl_NRCallObjProc(interp, TclOOInvokeContext, contextPtr, objc, objv); if (isRoot) { TclResetRewriteEnsemble(interp, 1); } clientData[0] = contextPtr; clientData[1] = oPtr; clientData[2] = state; clientData[3] = &oPtr; result = FinalizeAlloc(clientData, interp, result); if (result != TCL_OK) { return NULL; } } } return (Tcl_Object) oPtr; } int TclNRNewObjectInstance( Tcl_Interp *interp, /* Interpreter context. */ Tcl_Class cls, /* Class to create an instance of. */ const char *nameStr, /* Name of object to create, or NULL to ask * the code to pick its own unique name. */ const char *nsNameStr, /* Name of namespace to create inside object, * or NULL to ask the code to pick its own * unique name. */ Tcl_Size objc, /* Number of arguments. Negative value means * do not call constructor. */ Tcl_Obj *const *objv, /* Argument list. */ Tcl_Size skip, /* Number of arguments to _not_ pass to the * constructor. */ Tcl_Object *objectPtr) /* Place to write the object reference upon * successful allocation. */ { Class *classPtr = (Class *) cls; CallContext *contextPtr; Tcl_InterpState state; Object *oPtr; oPtr = TclNewObjectInstanceCommon(interp, classPtr, nameStr, nsNameStr); if (oPtr == NULL) { return TCL_ERROR; } /* * Run constructors, except when objc == TCL_INDEX_NONE (a special flag case used for * object cloning only). If there aren't any constructors, we do nothing. */ if (objc < 0) { *objectPtr = (Tcl_Object) oPtr; return TCL_OK; } contextPtr = TclOOGetCallContext(oPtr, NULL, CONSTRUCTOR, NULL, NULL, NULL); if (contextPtr == NULL) { *objectPtr = (Tcl_Object) oPtr; return TCL_OK; } state = Tcl_SaveInterpState(interp, TCL_OK); contextPtr->callPtr->flags |= CONSTRUCTOR; contextPtr->skip = skip; /* * Adjust the ensemble tracking record if necessary. [Bug 3514761] */ if (TclInitRewriteEnsemble(interp, skip, skip, objv)) { TclNRAddCallback(interp, TclClearRootEnsemble, NULL, NULL, NULL, NULL); } /* * Fire off the constructors non-recursively. */ TclNRAddCallback(interp, FinalizeAlloc, contextPtr, oPtr, state, objectPtr); TclPushTailcallPoint(interp); return TclOOInvokeContext(contextPtr, interp, objc, objv); } Object * TclNewObjectInstanceCommon( Tcl_Interp *interp, Class *classPtr, const char *nameStr, const char *nsNameStr) { Tcl_HashEntry *hPtr; Foundation *fPtr = GetFoundation(interp); Object *oPtr; const char *simpleName = NULL; Namespace *nsPtr = NULL, *dummy; Namespace *inNsPtr = (Namespace *) TclGetCurrentNamespace(interp); if (nameStr) { TclGetNamespaceForQualName(interp, nameStr, inNsPtr, TCL_CREATE_NS_IF_UNKNOWN, &nsPtr, &dummy, &dummy, &simpleName); /* * Disallow creation of an object over an existing command. */ hPtr = Tcl_FindHashEntry(&nsPtr->cmdTable, simpleName); if (hPtr) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't create object \"%s\": command already exists with" " that name", nameStr)); Tcl_SetErrorCode(interp, "TCL", "OO", "OVERWRITE_OBJECT", (char *)NULL); return NULL; } } /* * Create the object. */ oPtr = AllocObject(interp, simpleName, nsPtr, nsNameStr); if (oPtr == NULL) { return NULL; } oPtr->selfCls = classPtr; AddRef(classPtr->thisPtr); TclOOAddToInstances(oPtr, classPtr); /* * Check to see if we're really creating a class. If so, allocate the * class structure as well. */ if (TclOOIsReachable(fPtr->classCls, classPtr)) { /* * Is a class, so attach a class structure. Note that the * TclOOAllocClass function splices the structure into the object, so * we don't have to. Once that's done, we need to repatch the object * to have the right class since TclOOAllocClass interferes with that. */ TclOOAllocClass(interp, oPtr); TclOOAddToSubclasses(oPtr->classPtr, fPtr->objectCls); } else { oPtr->classPtr = NULL; } return oPtr; } static int FinalizeAlloc( void *data[], Tcl_Interp *interp, int result) { CallContext *contextPtr = (CallContext *) data[0]; Object *oPtr = (Object *) data[1]; Tcl_InterpState state = (Tcl_InterpState) data[2]; Tcl_Object *objectPtr = (Tcl_Object *) data[3]; /* * Ensure an error if the object was deleted in the constructor. Don't * want to lose errors by accident. [Bug 2903011] */ if (result != TCL_ERROR && Destructing(oPtr)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "object deleted in constructor", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "STILLBORN", (char *)NULL); result = TCL_ERROR; } if (result != TCL_OK) { Tcl_DiscardInterpState(state); /* * Take care to not delete a deleted object; that would be bad. [Bug * 2903011] Also take care to make sure that we have the name of the * command before we delete it. [Bug 9dd1bd7a74] */ if (!Destructing(oPtr)) { (void) TclOOObjectName(interp, oPtr); Tcl_DeleteCommandFromToken(interp, oPtr->command); } /* * This decrements the refcount of oPtr. */ TclOODeleteContext(contextPtr); return TCL_ERROR; } Tcl_RestoreInterpState(interp, state); *objectPtr = (Tcl_Object) oPtr; /* * This decrements the refcount of oPtr. */ TclOODeleteContext(contextPtr); return TCL_OK; } /* * ---------------------------------------------------------------------- * * Tcl_CopyObjectInstance -- * * Creates a copy of an object. Does not copy the backing namespace, * since the correct way to do that (e.g., shallow/deep) depends on the * object/class's own policies. * * ---------------------------------------------------------------------- */ Tcl_Object Tcl_CopyObjectInstance( Tcl_Interp *interp, Tcl_Object sourceObject, const char *targetName, const char *targetNamespaceName) { Object *oPtr = (Object *) sourceObject, *o2Ptr; FOREACH_HASH_DECLS; Method *mPtr; Class *mixinPtr; CallContext *contextPtr; Tcl_Obj *keyPtr, *filterObj, *variableObj, *args[3]; PrivateVariableMapping *privateVariable; Tcl_Size i; int result; /* * Sanity check. */ if (IsRootClass(oPtr)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "may not clone the class of classes", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "CLONING_CLASS", (char *)NULL); return NULL; } /* * Build the instance. Note that this does not run any constructors. */ o2Ptr = (Object *) Tcl_NewObjectInstance(interp, (Tcl_Class) oPtr->selfCls, targetName, targetNamespaceName, TCL_INDEX_NONE, NULL, 0); if (o2Ptr == NULL) { return NULL; } /* * Copy the object-local methods to the new object. */ if (oPtr->methodsPtr) { FOREACH_HASH(keyPtr, mPtr, oPtr->methodsPtr) { if (CloneObjectMethod(interp, o2Ptr, mPtr, keyPtr) != TCL_OK) { Tcl_DeleteCommandFromToken(interp, o2Ptr->command); return NULL; } } } /* * Copy the object's mixin references to the new object. */ if (o2Ptr->mixins.num != 0) { FOREACH(mixinPtr, o2Ptr->mixins) { if (mixinPtr && mixinPtr != o2Ptr->selfCls) { TclOORemoveFromInstances(o2Ptr, mixinPtr); } TclOODecrRefCount(mixinPtr->thisPtr); } Tcl_Free(o2Ptr->mixins.list); } DUPLICATE(o2Ptr->mixins, oPtr->mixins, Class *); FOREACH(mixinPtr, o2Ptr->mixins) { if (mixinPtr && mixinPtr != o2Ptr->selfCls) { TclOOAddToInstances(o2Ptr, mixinPtr); } /* * For the reference just created in DUPLICATE. */ AddRef(mixinPtr->thisPtr); } /* * Copy the object's filter list to the new object. */ DUPLICATE(o2Ptr->filters, oPtr->filters, Tcl_Obj *); FOREACH(filterObj, o2Ptr->filters) { Tcl_IncrRefCount(filterObj); } /* * Copy the object's variable resolution lists to the new object. */ DUPLICATE(o2Ptr->variables, oPtr->variables, Tcl_Obj *); FOREACH(variableObj, o2Ptr->variables) { Tcl_IncrRefCount(variableObj); } DUPLICATE(o2Ptr->privateVariables, oPtr->privateVariables, PrivateVariableMapping); FOREACH_STRUCT(privateVariable, o2Ptr->privateVariables) { Tcl_IncrRefCount(privateVariable->variableObj); Tcl_IncrRefCount(privateVariable->fullNameObj); } /* * Copy the object's flags to the new object, clearing those that must be * kept object-local. The duplicate is never deleted at this point, nor is * it the root of the object system or in the midst of processing a filter * call. */ o2Ptr->flags = oPtr->flags & ~( OBJECT_DESTRUCTING | ROOT_OBJECT | ROOT_CLASS | FILTER_HANDLING); /* * Copy the object's metadata. */ if (oPtr->metadataPtr != NULL) { Tcl_ObjectMetadataType *metadataTypePtr; void *value, *duplicate; FOREACH_HASH(metadataTypePtr, value, oPtr->metadataPtr) { if (metadataTypePtr->cloneProc == NULL) { duplicate = value; } else { if (metadataTypePtr->cloneProc(interp, value, &duplicate) != TCL_OK) { Tcl_DeleteCommandFromToken(interp, o2Ptr->command); return NULL; } } if (duplicate != NULL) { Tcl_ObjectSetMetadata((Tcl_Object) o2Ptr, metadataTypePtr, duplicate); } } } /* * Copy the class, if present. Note that if there is a class present in * the source object, there must also be one in the copy. */ if (oPtr->classPtr != NULL) { Class *clsPtr = oPtr->classPtr; Class *cls2Ptr = o2Ptr->classPtr; Class *superPtr; /* * Copy the class flags across. */ cls2Ptr->flags = clsPtr->flags; /* * Ensure that the new class's superclass structure is the same as the * old class's. */ FOREACH(superPtr, cls2Ptr->superclasses) { TclOORemoveFromSubclasses(cls2Ptr, superPtr); TclOODecrRefCount(superPtr->thisPtr); } if (cls2Ptr->superclasses.num) { cls2Ptr->superclasses.list = (Class **) Tcl_Realloc(cls2Ptr->superclasses.list, sizeof(Class *) * clsPtr->superclasses.num); } else { cls2Ptr->superclasses.list = (Class **) Tcl_Alloc(sizeof(Class *) * clsPtr->superclasses.num); } memcpy(cls2Ptr->superclasses.list, clsPtr->superclasses.list, sizeof(Class *) * clsPtr->superclasses.num); cls2Ptr->superclasses.num = clsPtr->superclasses.num; FOREACH(superPtr, cls2Ptr->superclasses) { TclOOAddToSubclasses(cls2Ptr, superPtr); /* * For the new item in cls2Ptr->superclasses that memcpy just * created. */ AddRef(superPtr->thisPtr); } /* * Duplicate the source class's filters. */ DUPLICATE(cls2Ptr->filters, clsPtr->filters, Tcl_Obj *); FOREACH(filterObj, cls2Ptr->filters) { Tcl_IncrRefCount(filterObj); } /* * Copy the source class's variable resolution lists. */ DUPLICATE(cls2Ptr->variables, clsPtr->variables, Tcl_Obj *); FOREACH(variableObj, cls2Ptr->variables) { Tcl_IncrRefCount(variableObj); } DUPLICATE(cls2Ptr->privateVariables, clsPtr->privateVariables, PrivateVariableMapping); FOREACH_STRUCT(privateVariable, cls2Ptr->privateVariables) { Tcl_IncrRefCount(privateVariable->variableObj); Tcl_IncrRefCount(privateVariable->fullNameObj); } /* * Duplicate the source class's mixins (which cannot be circular * references to the duplicate). */ if (cls2Ptr->mixins.num != 0) { FOREACH(mixinPtr, cls2Ptr->mixins) { TclOORemoveFromMixinSubs(cls2Ptr, mixinPtr); TclOODecrRefCount(mixinPtr->thisPtr); } Tcl_Free(clsPtr->mixins.list); } DUPLICATE(cls2Ptr->mixins, clsPtr->mixins, Class *); FOREACH(mixinPtr, cls2Ptr->mixins) { TclOOAddToMixinSubs(cls2Ptr, mixinPtr); /* * For the copy just created in DUPLICATE. */ AddRef(mixinPtr->thisPtr); } /* * Duplicate the source class's methods, constructor and destructor. */ FOREACH_HASH(keyPtr, mPtr, &clsPtr->classMethods) { if (CloneClassMethod(interp, cls2Ptr, mPtr, keyPtr, NULL) != TCL_OK) { Tcl_DeleteCommandFromToken(interp, o2Ptr->command); return NULL; } } if (clsPtr->constructorPtr) { if (CloneClassMethod(interp, cls2Ptr, clsPtr->constructorPtr, NULL, &cls2Ptr->constructorPtr) != TCL_OK) { Tcl_DeleteCommandFromToken(interp, o2Ptr->command); return NULL; } } if (clsPtr->destructorPtr) { if (CloneClassMethod(interp, cls2Ptr, clsPtr->destructorPtr, NULL, &cls2Ptr->destructorPtr) != TCL_OK) { Tcl_DeleteCommandFromToken(interp, o2Ptr->command); return NULL; } } /* * Duplicate the class's metadata. */ if (clsPtr->metadataPtr != NULL) { Tcl_ObjectMetadataType *metadataTypePtr; void *value, *duplicate; FOREACH_HASH(metadataTypePtr, value, clsPtr->metadataPtr) { if (metadataTypePtr->cloneProc == NULL) { duplicate = value; } else { if (metadataTypePtr->cloneProc(interp, value, &duplicate) != TCL_OK) { Tcl_DeleteCommandFromToken(interp, o2Ptr->command); return NULL; } } if (duplicate != NULL) { Tcl_ClassSetMetadata((Tcl_Class) cls2Ptr, metadataTypePtr, duplicate); } } } } TclResetRewriteEnsemble(interp, 1); contextPtr = TclOOGetCallContext(o2Ptr, oPtr->fPtr->clonedName, 0, NULL, NULL, NULL); if (contextPtr) { args[0] = TclOOObjectName(interp, o2Ptr); args[1] = oPtr->fPtr->clonedName; args[2] = TclOOObjectName(interp, oPtr); Tcl_IncrRefCount(args[0]); Tcl_IncrRefCount(args[1]); Tcl_IncrRefCount(args[2]); result = Tcl_NRCallObjProc(interp, TclOOInvokeContext, contextPtr, 3, args); TclDecrRefCount(args[0]); TclDecrRefCount(args[1]); TclDecrRefCount(args[2]); TclOODeleteContext(contextPtr); if (result == TCL_ERROR) { Tcl_AddErrorInfo(interp, "\n (while performing post-copy callback)"); } if (result != TCL_OK) { Tcl_DeleteCommandFromToken(interp, o2Ptr->command); return NULL; } } return (Tcl_Object) o2Ptr; } /* * ---------------------------------------------------------------------- * * CloneObjectMethod, CloneClassMethod -- * * Helper functions used for cloning methods. They work identically to * each other, except for the difference between them in how they * register the cloned method on a successful clone. * * ---------------------------------------------------------------------- */ static int CloneObjectMethod( Tcl_Interp *interp, Object *oPtr, Method *mPtr, Tcl_Obj *namePtr) { if (mPtr->typePtr == NULL) { TclNewInstanceMethod(interp, (Tcl_Object) oPtr, namePtr, mPtr->flags & PUBLIC_METHOD, NULL, NULL); } else if (mPtr->typePtr->cloneProc) { void *newClientData; if (mPtr->typePtr->cloneProc(interp, mPtr->clientData, &newClientData) != TCL_OK) { return TCL_ERROR; } TclNewInstanceMethod(interp, (Tcl_Object) oPtr, namePtr, mPtr->flags & PUBLIC_METHOD, mPtr->typePtr, newClientData); } else { TclNewInstanceMethod(interp, (Tcl_Object) oPtr, namePtr, mPtr->flags & PUBLIC_METHOD, mPtr->typePtr, mPtr->clientData); } return TCL_OK; } static int CloneClassMethod( Tcl_Interp *interp, Class *clsPtr, Method *mPtr, Tcl_Obj *namePtr, Method **m2PtrPtr) { Method *m2Ptr; if (mPtr->typePtr == NULL) { m2Ptr = (Method *) TclNewMethod((Tcl_Class) clsPtr, namePtr, mPtr->flags & PUBLIC_METHOD, NULL, NULL); } else if (mPtr->typePtr->cloneProc) { void *newClientData; if (mPtr->typePtr->cloneProc(interp, mPtr->clientData, &newClientData) != TCL_OK) { return TCL_ERROR; } m2Ptr = (Method *) TclNewMethod((Tcl_Class) clsPtr, namePtr, mPtr->flags & PUBLIC_METHOD, mPtr->typePtr, newClientData); } else { m2Ptr = (Method *) TclNewMethod((Tcl_Class) clsPtr, namePtr, mPtr->flags & PUBLIC_METHOD, mPtr->typePtr, mPtr->clientData); } if (m2PtrPtr != NULL) { *m2PtrPtr = m2Ptr; } return TCL_OK; } /* * ---------------------------------------------------------------------- * * Tcl_ClassGetMetadata, Tcl_ClassSetMetadata, Tcl_ObjectGetMetadata, * Tcl_ObjectSetMetadata -- * * Metadata management API. The metadata system allows code in extensions * to attach arbitrary non-NULL pointers to objects and classes without * the different things that might be interested being able to interfere * with each other. Apart from non-NULL-ness, these routines attach no * interpretation to the meaning of the metadata pointers. * * The Tcl_*GetMetadata routines get the metadata pointer attached that * has been related with a particular type, or NULL if no metadata * associated with the given type has been attached. * * The Tcl_*SetMetadata routines set or delete the metadata pointer that * is related to a particular type. The value associated with the type is * deleted (if present; no-op otherwise) if the value is NULL, and * attached (replacing the previous value, which is deleted if present) * otherwise. This means it is impossible to attach a NULL value for any * metadata type. * * ---------------------------------------------------------------------- */ void * Tcl_ClassGetMetadata( Tcl_Class clazz, const Tcl_ObjectMetadataType *typePtr) { Class *clsPtr = (Class *) clazz; Tcl_HashEntry *hPtr; /* * If there's no metadata store attached, the type in question has * definitely not been attached either! */ if (clsPtr->metadataPtr == NULL) { return NULL; } /* * There is a metadata store, so look in it for the given type. */ hPtr = Tcl_FindHashEntry(clsPtr->metadataPtr, typePtr); /* * Return the metadata value if we found it, otherwise NULL. */ if (hPtr == NULL) { return NULL; } return Tcl_GetHashValue(hPtr); } void Tcl_ClassSetMetadata( Tcl_Class clazz, const Tcl_ObjectMetadataType *typePtr, void *metadata) { Class *clsPtr = (Class *) clazz; Tcl_HashEntry *hPtr; int isNew; /* * Attach the metadata store if not done already. */ if (clsPtr->metadataPtr == NULL) { if (metadata == NULL) { return; } clsPtr->metadataPtr = (Tcl_HashTable *) Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(clsPtr->metadataPtr, TCL_ONE_WORD_KEYS); } /* * If the metadata is NULL, we're deleting the metadata for the type. */ if (metadata == NULL) { hPtr = Tcl_FindHashEntry(clsPtr->metadataPtr, typePtr); if (hPtr != NULL) { typePtr->deleteProc(Tcl_GetHashValue(hPtr)); Tcl_DeleteHashEntry(hPtr); } return; } /* * Otherwise we're attaching the metadata. Note that if there was already * some metadata attached of this type, we delete that first. */ hPtr = Tcl_CreateHashEntry(clsPtr->metadataPtr, typePtr, &isNew); if (!isNew) { typePtr->deleteProc(Tcl_GetHashValue(hPtr)); } Tcl_SetHashValue(hPtr, metadata); } void * Tcl_ObjectGetMetadata( Tcl_Object object, const Tcl_ObjectMetadataType *typePtr) { Object *oPtr = (Object *) object; Tcl_HashEntry *hPtr; /* * If there's no metadata store attached, the type in question has * definitely not been attached either! */ if (oPtr->metadataPtr == NULL) { return NULL; } /* * There is a metadata store, so look in it for the given type. */ hPtr = Tcl_FindHashEntry(oPtr->metadataPtr, typePtr); /* * Return the metadata value if we found it, otherwise NULL. */ if (hPtr == NULL) { return NULL; } return Tcl_GetHashValue(hPtr); } void Tcl_ObjectSetMetadata( Tcl_Object object, const Tcl_ObjectMetadataType *typePtr, void *metadata) { Object *oPtr = (Object *) object; Tcl_HashEntry *hPtr; int isNew; /* * Attach the metadata store if not done already. */ if (oPtr->metadataPtr == NULL) { if (metadata == NULL) { return; } oPtr->metadataPtr = (Tcl_HashTable *) Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(oPtr->metadataPtr, TCL_ONE_WORD_KEYS); } /* * If the metadata is NULL, we're deleting the metadata for the type. */ if (metadata == NULL) { hPtr = Tcl_FindHashEntry(oPtr->metadataPtr, typePtr); if (hPtr != NULL) { typePtr->deleteProc(Tcl_GetHashValue(hPtr)); Tcl_DeleteHashEntry(hPtr); } return; } /* * Otherwise we're attaching the metadata. Note that if there was already * some metadata attached of this type, we delete that first. */ hPtr = Tcl_CreateHashEntry(oPtr->metadataPtr, typePtr, &isNew); if (!isNew) { typePtr->deleteProc(Tcl_GetHashValue(hPtr)); } Tcl_SetHashValue(hPtr, metadata); } /* * ---------------------------------------------------------------------- * * TclOOPublicObjectCmd, TclOOPrivateObjectCmd, TclOOInvokeObject -- * * Main entry point for object invocations. The Public* and Private* * wrapper functions (implementations of both object instance commands * and [my]) are just thin wrappers round the main TclOOObjectCmdCore * function. Note that the core is function is NRE-aware. * * ---------------------------------------------------------------------- */ int TclOOPublicObjectCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { return Tcl_NRCallObjProc(interp, PublicNRObjectCmd, clientData, objc, objv); } static int PublicNRObjectCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { return TclOOObjectCmdCore((Object *) clientData, interp, objc, objv, PUBLIC_METHOD, NULL); } int TclOOPrivateObjectCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { return Tcl_NRCallObjProc(interp, PrivateNRObjectCmd, clientData, objc, objv); } static int PrivateNRObjectCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { return TclOOObjectCmdCore((Object *) clientData, interp, objc, objv, 0, NULL); } int TclOOInvokeObject( Tcl_Interp *interp, /* Interpreter for commands, variables, * results, error reporting, etc. */ Tcl_Object object, /* The object to invoke. */ Tcl_Class startCls, /* Where in the class chain to start the * invoke from, or NULL to traverse the whole * chain including filters. */ int publicPrivate, /* Whether this is an invoke from a public * context (PUBLIC_METHOD), a private context * (PRIVATE_METHOD), or a *really* private * context (any other value; conventionally * 0). */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Array of argument objects. It is assumed * that the name of the method to invoke will * be at index 1. */ { switch (publicPrivate) { case PUBLIC_METHOD: return TclOOObjectCmdCore((Object *) object, interp, objc, objv, PUBLIC_METHOD, (Class *) startCls); case PRIVATE_METHOD: return TclOOObjectCmdCore((Object *) object, interp, objc, objv, PRIVATE_METHOD, (Class *) startCls); default: return TclOOObjectCmdCore((Object *) object, interp, objc, objv, 0, (Class *) startCls); } } /* * ---------------------------------------------------------------------- * * TclOOMyClassObjCmd, MyClassNRObjCmd -- * * Special trap door to allow an object to delegate simply to its class. * * ---------------------------------------------------------------------- */ int TclOOMyClassObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { return Tcl_NRCallObjProc(interp, MyClassNRObjCmd, clientData, objc, objv); } static int MyClassNRObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Object *oPtr = (Object *) clientData; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "methodName ?arg ...?"); return TCL_ERROR; } return TclOOObjectCmdCore(oPtr->selfCls->thisPtr, interp, objc, objv, 0, NULL); } /* * ---------------------------------------------------------------------- * * TclOOObjectCmdCore, FinalizeObjectCall -- * * Main function for object invocations. Does call chain creation, * management and invocation. The function FinalizeObjectCall exists to * clean up after the non-recursive processing of TclOOObjectCmdCore. * * ---------------------------------------------------------------------- */ int TclOOObjectCmdCore( Object *oPtr, /* The object being invoked. */ Tcl_Interp *interp, /* The interpreter containing the object. */ Tcl_Size objc, /* How many arguments are being passed in. */ Tcl_Obj *const *objv, /* The array of arguments. */ int flags, /* Whether this is an invocation through the * public or the private command interface. */ Class *startCls) /* Where to start in the call chain, or NULL * if we are to start at the front with * filters and the object's methods (which is * the normal case). */ { CallContext *contextPtr; Tcl_Obj *methodNamePtr; CallFrame *framePtr = ((Interp *) interp)->varFramePtr; Object *callerObjPtr = NULL; Class *callerClsPtr = NULL; int result; /* * If we've no method name, throw this directly into the unknown * processing. */ if (objc < 2) { flags |= FORCE_UNKNOWN; methodNamePtr = NULL; goto noMapping; } /* * Determine if we're in a context that can see the extra, private methods * in this class. */ if (framePtr->isProcCallFrame & FRAME_IS_METHOD) { CallContext *callerContextPtr = (CallContext *) framePtr->clientData; Method *callerMethodPtr = callerContextPtr->callPtr->chain[callerContextPtr->index].mPtr; if (callerMethodPtr->declaringObjectPtr) { callerObjPtr = callerMethodPtr->declaringObjectPtr; } if (callerMethodPtr->declaringClassPtr) { callerClsPtr = callerMethodPtr->declaringClassPtr; } } /* * Give plugged in code a chance to remap the method name. */ methodNamePtr = objv[1]; if (oPtr->mapMethodNameProc != NULL) { Class **startClsPtr = &startCls; Tcl_Obj *mappedMethodName = Tcl_DuplicateObj(methodNamePtr); result = oPtr->mapMethodNameProc(interp, (Tcl_Object) oPtr, (Tcl_Class *) startClsPtr, mappedMethodName); if (result != TCL_OK) { TclDecrRefCount(mappedMethodName); if (result == TCL_BREAK) { goto noMapping; } else if (result == TCL_ERROR) { Tcl_AddErrorInfo(interp, "\n (while mapping method name)"); } return result; } /* * Get the call chain for the remapped name. */ Tcl_IncrRefCount(mappedMethodName); contextPtr = TclOOGetCallContext(oPtr, mappedMethodName, flags | (oPtr->flags & FILTER_HANDLING), callerObjPtr, callerClsPtr, methodNamePtr); TclDecrRefCount(mappedMethodName); if (contextPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "impossible to invoke method \"%s\": no defined method or" " unknown method", TclGetString(methodNamePtr))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD_MAPPED", TclGetString(methodNamePtr), (char *)NULL); return TCL_ERROR; } } else { /* * Get the call chain. */ noMapping: contextPtr = TclOOGetCallContext(oPtr, methodNamePtr, flags | (oPtr->flags & FILTER_HANDLING), callerObjPtr, callerClsPtr, NULL); if (contextPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "impossible to invoke method \"%s\": no defined method or" " unknown method", TclGetString(methodNamePtr))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(methodNamePtr), (char *)NULL); return TCL_ERROR; } } /* * Check to see if we need to apply magical tricks to start part way * through the call chain. */ if (startCls != NULL) { for (; contextPtr->index < contextPtr->callPtr->numChain; contextPtr->index++) { MInvoke *miPtr = &contextPtr->callPtr->chain[contextPtr->index]; if (miPtr->isFilter) { continue; } if (miPtr->mPtr->declaringClassPtr == startCls) { break; } } if (contextPtr->index >= contextPtr->callPtr->numChain) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "no valid method implementation", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(methodNamePtr), (char *)NULL); TclOODeleteContext(contextPtr); return TCL_ERROR; } } /* * Invoke the call chain, locking the object structure against deletion * for the duration. */ TclNRAddCallback(interp, FinalizeObjectCall, contextPtr, NULL,NULL,NULL); return TclOOInvokeContext(contextPtr, interp, objc, objv); } static int FinalizeObjectCall( void *data[], TCL_UNUSED(Tcl_Interp *), int result) { /* * Dispose of the call chain, which drops the lock on the object's * structure. */ TclOODeleteContext((CallContext *) data[0]); return result; } /* * ---------------------------------------------------------------------- * * Tcl_ObjectContextInvokeNext, TclNRObjectContextInvokeNext, FinalizeNext -- * * Invokes the next stage of the call chain described in an object * context. This is the core of the implementation of the [next] command. * Does not do management of the call-frame stack. Available in public * (standard API) and private (NRE-aware) forms. FinalizeNext is a * private function used to clean up in the NRE case. * * ---------------------------------------------------------------------- */ int Tcl_ObjectContextInvokeNext( Tcl_Interp *interp, Tcl_ObjectContext context, Tcl_Size objc, Tcl_Obj *const *objv, Tcl_Size skip) { CallContext *contextPtr = (CallContext *) context; size_t savedIndex = contextPtr->index; size_t savedSkip = contextPtr->skip; int result; if (contextPtr->index + 1 >= contextPtr->callPtr->numChain) { /* * We're at the end of the chain; generate an error message unless the * interpreter is being torn down, in which case we might be getting * here because of methods/destructors doing a [next] (or equivalent) * unexpectedly. */ const char *methodType; if (Tcl_InterpDeleted(interp)) { return TCL_OK; } if (contextPtr->callPtr->flags & CONSTRUCTOR) { methodType = "constructor"; } else if (contextPtr->callPtr->flags & DESTRUCTOR) { methodType = "destructor"; } else { methodType = "method"; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "no next %s implementation", methodType)); Tcl_SetErrorCode(interp, "TCL", "OO", "NOTHING_NEXT", (char *)NULL); return TCL_ERROR; } /* * Advance to the next method implementation in the chain in the method * call context while we process the body. However, need to adjust the * argument-skip control because we're guaranteed to have a single prefix * arg (i.e., 'next') and not the variable amount that can happen because * method invocations (i.e., '$obj meth' and 'my meth'), constructors * (i.e., '$cls new' and '$cls create obj') and destructors (no args at * all) come through the same code. */ contextPtr->index++; contextPtr->skip = skip; /* * Invoke the (advanced) method call context in the caller context. */ result = Tcl_NRCallObjProc(interp, TclOOInvokeContext, contextPtr, objc, objv); /* * Restore the call chain context index as we've finished the inner invoke * and want to operate in the outer context again. */ contextPtr->index = savedIndex; contextPtr->skip = savedSkip; return result; } int TclNRObjectContextInvokeNext( Tcl_Interp *interp, Tcl_ObjectContext context, Tcl_Size objc, Tcl_Obj *const *objv, Tcl_Size skip) { CallContext *contextPtr = (CallContext *) context; if (contextPtr->index + 1 >= contextPtr->callPtr->numChain) { /* * We're at the end of the chain; generate an error message unless the * interpreter is being torn down, in which case we might be getting * here because of methods/destructors doing a [next] (or equivalent) * unexpectedly. */ const char *methodType; if (Tcl_InterpDeleted(interp)) { return TCL_OK; } if (contextPtr->callPtr->flags & CONSTRUCTOR) { methodType = "constructor"; } else if (contextPtr->callPtr->flags & DESTRUCTOR) { methodType = "destructor"; } else { methodType = "method"; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "no next %s implementation", methodType)); Tcl_SetErrorCode(interp, "TCL", "OO", "NOTHING_NEXT", (char *)NULL); return TCL_ERROR; } /* * Advance to the next method implementation in the chain in the method * call context while we process the body. However, need to adjust the * argument-skip control because we're guaranteed to have a single prefix * arg (i.e., 'next') and not the variable amount that can happen because * method invocations (i.e., '$obj meth' and 'my meth'), constructors * (i.e., '$cls new' and '$cls create obj') and destructors (no args at * all) come through the same code. */ TclNRAddCallback(interp, FinalizeNext, contextPtr, INT2PTR(contextPtr->index), INT2PTR(contextPtr->skip), NULL); contextPtr->index++; contextPtr->skip = skip; /* * Invoke the (advanced) method call context in the caller context. */ return TclOOInvokeContext(contextPtr, interp, objc, objv); } static int FinalizeNext( void *data[], TCL_UNUSED(Tcl_Interp *), int result) { CallContext *contextPtr = (CallContext *) data[0]; /* * Restore the call chain context index as we've finished the inner invoke * and want to operate in the outer context again. */ contextPtr->index = PTR2INT(data[1]); contextPtr->skip = PTR2INT(data[2]); return result; } /* * ---------------------------------------------------------------------- * * Tcl_GetObjectFromObj -- * * Utility function to get an object from a Tcl_Obj containing its name. * * ---------------------------------------------------------------------- */ Tcl_Object Tcl_GetObjectFromObj( Tcl_Interp *interp, /* Interpreter in which to locate the object. * Will have an error message placed in it if * the name does not refer to an object. */ Tcl_Obj *objPtr) /* The name of the object to look up, which is * exactly the name of its public command. */ { Command *cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objPtr); if (cmdPtr == NULL) { goto notAnObject; } if (cmdPtr->objProc != TclOOPublicObjectCmd) { cmdPtr = (Command *) TclGetOriginalCommand((Tcl_Command) cmdPtr); if (cmdPtr == NULL || cmdPtr->objProc != TclOOPublicObjectCmd) { goto notAnObject; } } return (Tcl_Object) cmdPtr->objClientData; notAnObject: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s does not refer to an object", TclGetString(objPtr))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "OBJECT", TclGetString(objPtr), (char *)NULL); return NULL; } /* * ---------------------------------------------------------------------- * * TclOOIsReachable -- * * Utility function that tests whether a class is a subclass (whether * directly or indirectly) of another class. * * ---------------------------------------------------------------------- */ int TclOOIsReachable( Class *targetPtr, Class *startPtr) { Tcl_Size i; Class *superPtr; tailRecurse: if (startPtr == targetPtr) { return 1; } if (startPtr->superclasses.num == 1 && startPtr->mixins.num == 0) { startPtr = startPtr->superclasses.list[0]; goto tailRecurse; } FOREACH(superPtr, startPtr->superclasses) { if (TclOOIsReachable(targetPtr, superPtr)) { return 1; } } FOREACH(superPtr, startPtr->mixins) { if (TclOOIsReachable(targetPtr, superPtr)) { return 1; } } return 0; } /* * ---------------------------------------------------------------------- * * TclOOObjectName, Tcl_GetObjectName -- * * Utility function that returns the name of the object. Note that this * simplifies cache management by keeping the code to do it in one place * and not sprayed all over. The value returned always has a reference * count of at least one. * * ---------------------------------------------------------------------- */ Tcl_Obj * TclOOObjectName( Tcl_Interp *interp, Object *oPtr) { Tcl_Obj *namePtr; if (oPtr->cachedNameObj) { return oPtr->cachedNameObj; } TclNewObj(namePtr); Tcl_GetCommandFullName(interp, oPtr->command, namePtr); Tcl_IncrRefCount(namePtr); oPtr->cachedNameObj = namePtr; return namePtr; } Tcl_Obj * Tcl_GetObjectName( Tcl_Interp *interp, Tcl_Object object) { return TclOOObjectName(interp, (Object *) object); } /* * ---------------------------------------------------------------------- * * assorted trivial 'getter' functions * * ---------------------------------------------------------------------- */ Tcl_Method Tcl_ObjectContextMethod( Tcl_ObjectContext context) { CallContext *contextPtr = (CallContext *) context; return (Tcl_Method) contextPtr->callPtr->chain[contextPtr->index].mPtr; } int Tcl_ObjectContextIsFiltering( Tcl_ObjectContext context) { CallContext *contextPtr = (CallContext *) context; return contextPtr->callPtr->chain[contextPtr->index].isFilter; } Tcl_Object Tcl_ObjectContextObject( Tcl_ObjectContext context) { return (Tcl_Object) ((CallContext *) context)->oPtr; } Tcl_Size Tcl_ObjectContextSkippedArgs( Tcl_ObjectContext context) { return ((CallContext *) context)->skip; } Tcl_Namespace * Tcl_GetObjectNamespace( Tcl_Object object) { return ((Object *) object)->namespacePtr; } Tcl_Command Tcl_GetObjectCommand( Tcl_Object object) { return ((Object *) object)->command; } Tcl_Class Tcl_GetObjectAsClass( Tcl_Object object) { return (Tcl_Class) ((Object *) object)->classPtr; } int Tcl_ObjectDeleted( Tcl_Object object) { return ((Object *) object)->command == NULL; } Tcl_Object Tcl_GetClassAsObject( Tcl_Class clazz) { return (Tcl_Object) ((Class *) clazz)->thisPtr; } Tcl_ObjectMapMethodNameProc * Tcl_ObjectGetMethodNameMapper( Tcl_Object object) { return ((Object *) object)->mapMethodNameProc; } void Tcl_ObjectSetMethodNameMapper( Tcl_Object object, Tcl_ObjectMapMethodNameProc *mapMethodNameProc) { ((Object *) object)->mapMethodNameProc = mapMethodNameProc; } Tcl_Class Tcl_GetClassOfObject( Tcl_Object object) { return (Tcl_Class) ((Object *) object)->selfCls; } Tcl_Obj * Tcl_GetObjectClassName( Tcl_Interp *interp, Tcl_Object object) { Tcl_Object classObj = (Tcl_Object) (((Object *) object)->selfCls)->thisPtr; if (classObj == NULL) { return NULL; } return Tcl_GetObjectName(interp, classObj); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclOO.h0000644000175000017500000001313314726623136014364 0ustar sergeisergei/* * tclOO.h -- * * This file contains the public API definitions and some of the function * declarations for the object-system (NB: not Tcl_Obj, but ::oo). * * Copyright (c) 2006-2010 by Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef TCLOO_H_INCLUDED #define TCLOO_H_INCLUDED /* * Be careful when it comes to versioning; need to make sure that the * standalone TclOO version matches. Also make sure that this matches the * version in the files: * * tests/oo.test * tests/ooNext2.test * unix/tclooConfig.sh * win/tclooConfig.sh */ #define TCLOO_VERSION "1.3" #define TCLOO_PATCHLEVEL TCLOO_VERSION ".0" #include "tcl.h" /* * For C++ compilers, use extern "C" */ #ifdef __cplusplus extern "C" { #endif extern const char *TclOOInitializeStubs( Tcl_Interp *, const char *version); #define Tcl_OOInitStubs(interp) \ TclOOInitializeStubs((interp), TCLOO_PATCHLEVEL) #ifndef USE_TCL_STUBS # define TclOOInitializeStubs(interp, version) (TCLOO_PATCHLEVEL) #endif /* * These are opaque types. */ typedef struct Tcl_Class_ *Tcl_Class; typedef struct Tcl_Method_ *Tcl_Method; typedef struct Tcl_Object_ *Tcl_Object; typedef struct Tcl_ObjectContext_ *Tcl_ObjectContext; /* * Public datatypes for callbacks and structures used in the TIP#257 (OO) * implementation. These are used to implement custom types of method calls * and to allow the attachment of arbitrary data to objects and classes. */ typedef int (Tcl_MethodCallProc)(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext objectContext, int objc, Tcl_Obj *const *objv); #if TCL_MAJOR_VERSION > 8 typedef int (Tcl_MethodCallProc2)(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext objectContext, Tcl_Size objc, Tcl_Obj *const *objv); #else #define Tcl_MethodCallProc2 Tcl_MethodCallProc #endif typedef void (Tcl_MethodDeleteProc)(void *clientData); typedef int (Tcl_CloneProc)(Tcl_Interp *interp, void *oldClientData, void **newClientData); typedef void (Tcl_ObjectMetadataDeleteProc)(void *clientData); typedef int (Tcl_ObjectMapMethodNameProc)(Tcl_Interp *interp, Tcl_Object object, Tcl_Class *startClsPtr, Tcl_Obj *methodNameObj); /* * The type of a method implementation. This describes how to call the method * implementation, how to delete it (when the object or class is deleted) and * how to create a clone of it (when the object or class is copied). */ typedef struct Tcl_MethodType { int version; /* Structure version field. Always to be equal * to TCL_OO_METHOD_VERSION_(1|CURRENT) in * declarations. */ const char *name; /* Name of this type of method, mostly for * debugging purposes. */ Tcl_MethodCallProc *callProc; /* How to invoke this method. */ Tcl_MethodDeleteProc *deleteProc; /* How to delete this method's type-specific * data, or NULL if the type-specific data * does not need deleting. */ Tcl_CloneProc *cloneProc; /* How to copy this method's type-specific * data, or NULL if the type-specific data can * be copied directly. */ } Tcl_MethodType; #if TCL_MAJOR_VERSION > 8 typedef struct Tcl_MethodType2 { int version; /* Structure version field. Always to be equal * to TCL_OO_METHOD_VERSION_2 in * declarations. */ const char *name; /* Name of this type of method, mostly for * debugging purposes. */ Tcl_MethodCallProc2 *callProc; /* How to invoke this method. */ Tcl_MethodDeleteProc *deleteProc; /* How to delete this method's type-specific * data, or NULL if the type-specific data * does not need deleting. */ Tcl_CloneProc *cloneProc; /* How to copy this method's type-specific * data, or NULL if the type-specific data can * be copied directly. */ } Tcl_MethodType2; #else #define Tcl_MethodType2 Tcl_MethodType #endif /* * The correct value for the version field of the Tcl_MethodType structure. * This allows new versions of the structure to be introduced without breaking * binary compatibility. */ enum TclOOMethodVersion { TCL_OO_METHOD_VERSION_1 = 1, TCL_OO_METHOD_VERSION_2 = 2 }; #define TCL_OO_METHOD_VERSION_CURRENT TCL_OO_METHOD_VERSION_1 /* * Visibility constants for the flags parameter to Tcl_NewMethod and * Tcl_NewInstanceMethod. */ enum TclOOMethodVisibilityFlags { TCL_OO_METHOD_PUBLIC = 1, TCL_OO_METHOD_UNEXPORTED = 0, TCL_OO_METHOD_PRIVATE = 0x20 }; /* * The type of some object (or class) metadata. This describes how to delete * the metadata (when the object or class is deleted) and how to create a * clone of it (when the object or class is copied). */ typedef struct Tcl_ObjectMetadataType { int version; /* Structure version field. Always to be equal * to TCL_OO_METADATA_VERSION_CURRENT in * declarations. */ const char *name; Tcl_ObjectMetadataDeleteProc *deleteProc; /* How to delete the metadata. This must not * be NULL. */ Tcl_CloneProc *cloneProc; /* How to copy the metadata, or NULL if the * type-specific data can be copied * directly. */ } Tcl_ObjectMetadataType; /* * The correct value for the version field of the Tcl_ObjectMetadataType * structure. This allows new versions of the structure to be introduced * without breaking binary compatibility. */ enum TclOOMetadataVersion { TCL_OO_METADATA_VERSION_1 = 1 }; #define TCL_OO_METADATA_VERSION_CURRENT TCL_OO_METADATA_VERSION_1 /* * Include all the public API, generated from tclOO.decls. */ #include "tclOODecls.h" #ifdef __cplusplus } #endif #endif /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclOOBasic.c0000644000175000017500000012002614726623136015321 0ustar sergeisergei/* * tclOOBasic.c -- * * This file contains implementations of the "simple" commands and * methods from the object-system core. * * Copyright © 2005-2013 Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "tclInt.h" #include "tclOOInt.h" #include "tclTomMath.h" static inline Tcl_Object *AddConstructionFinalizer(Tcl_Interp *interp); static Tcl_NRPostProc AfterNRDestructor; static Tcl_NRPostProc DecrRefsPostClassConstructor; static Tcl_NRPostProc FinalizeConstruction; static Tcl_NRPostProc FinalizeEval; static Tcl_NRPostProc NextRestoreFrame; /* * ---------------------------------------------------------------------- * * AddCreateCallback, FinalizeConstruction -- * * Special version of TclNRAddCallback that allows the caller to splice * the object created later on. Always calls FinalizeConstruction, which * converts the object into its name and stores that in the interpreter * result. This is shared by all the construction methods (create, * createWithNamespace, new). * * Note that this is the only code in this file (or, indeed, the whole of * TclOO) that uses NRE internals; it is the only code that does * non-standard poking in the NRE guts. * * ---------------------------------------------------------------------- */ static inline Tcl_Object * AddConstructionFinalizer( Tcl_Interp *interp) { TclNRAddCallback(interp, FinalizeConstruction, NULL, NULL, NULL, NULL); return (Tcl_Object *) &(TOP_CB(interp)->data[0]); } static int FinalizeConstruction( void *data[], Tcl_Interp *interp, int result) { Object *oPtr = (Object *) data[0]; if (result != TCL_OK) { return result; } Tcl_SetObjResult(interp, TclOOObjectName(interp, oPtr)); return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOO_Class_Constructor -- * * Implementation for oo::class constructor. * * ---------------------------------------------------------------------- */ int TclOO_Class_Constructor( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Object *oPtr = (Object *) Tcl_ObjectContextObject(context); Tcl_Obj **invoke, *nameObj; size_t skip = Tcl_ObjectContextSkippedArgs(context); if ((size_t) objc > skip + 1) { Tcl_WrongNumArgs(interp, skip, objv, "?definitionScript?"); return TCL_ERROR; } /* * Make the class definition delegate. This is special; it doesn't reenter * here (and the class definition delegate doesn't run any constructors). * * This needs to be done before consideration of whether to pass the script * argument to [oo::define]. [Bug 680503] */ nameObj = Tcl_ObjPrintf("%s:: oo ::delegate", oPtr->namespacePtr->fullName); Tcl_NewObjectInstance(interp, (Tcl_Class) oPtr->fPtr->classCls, TclGetString(nameObj), NULL, TCL_INDEX_NONE, NULL, 0); Tcl_BounceRefCount(nameObj); /* * If there's nothing else to do, we're done. */ if ((size_t) objc == skip) { return TCL_OK; } /* * Delegate to [oo::define] to do the work. */ invoke = (Tcl_Obj **) TclStackAlloc(interp, 3 * sizeof(Tcl_Obj *)); invoke[0] = oPtr->fPtr->defineName; invoke[1] = TclOOObjectName(interp, oPtr); invoke[2] = objv[objc - 1]; /* * Must add references or errors in configuration script will cause * trouble. */ Tcl_IncrRefCount(invoke[0]); Tcl_IncrRefCount(invoke[1]); Tcl_IncrRefCount(invoke[2]); TclNRAddCallback(interp, DecrRefsPostClassConstructor, invoke, oPtr, NULL, NULL); /* * Tricky point: do not want the extra reported level in the Tcl stack * trace, so use TCL_EVAL_NOERR. */ return TclNREvalObjv(interp, 3, invoke, TCL_EVAL_NOERR, NULL); } static int DecrRefsPostClassConstructor( void *data[], Tcl_Interp *interp, int result) { Tcl_Obj **invoke = (Tcl_Obj **) data[0]; Object *oPtr = (Object *) data[1]; Tcl_InterpState saved; int code; TclDecrRefCount(invoke[0]); TclDecrRefCount(invoke[1]); TclDecrRefCount(invoke[2]); invoke[0] = oPtr->fPtr->mcdName; invoke[1] = TclOOObjectName(interp, oPtr); Tcl_IncrRefCount(invoke[0]); Tcl_IncrRefCount(invoke[1]); saved = Tcl_SaveInterpState(interp, result); code = Tcl_EvalObjv(interp, 2, invoke, 0); TclDecrRefCount(invoke[0]); TclDecrRefCount(invoke[1]); TclStackFree(interp, invoke); if (code != TCL_OK) { Tcl_DiscardInterpState(saved); return code; } return Tcl_RestoreInterpState(interp, saved); } /* * ---------------------------------------------------------------------- * * TclOO_Class_Create -- * * Implementation for oo::class->create method. * * ---------------------------------------------------------------------- */ int TclOO_Class_Create( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter in which to create the object; * also used for error reporting. */ Tcl_ObjectContext context, /* The object/call context. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* The actual arguments. */ { Object *oPtr = (Object *) Tcl_ObjectContextObject(context); const char *objName; Tcl_Size len; /* * Sanity check; should not be possible to invoke this method on a * non-class. */ if (oPtr->classPtr == NULL) { Tcl_Obj *cmdnameObj = TclOOObjectName(interp, oPtr); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "object \"%s\" is not a class", TclGetString(cmdnameObj))); Tcl_SetErrorCode(interp, "TCL", "OO", "INSTANTIATE_NONCLASS", (char *)NULL); return TCL_ERROR; } /* * Check we have the right number of (sensible) arguments. */ if (objc < 1 + Tcl_ObjectContextSkippedArgs(context)) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "objectName ?arg ...?"); return TCL_ERROR; } objName = Tcl_GetStringFromObj( objv[Tcl_ObjectContextSkippedArgs(context)], &len); if (len == 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "object name must not be empty", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "EMPTY_NAME", (char *)NULL); return TCL_ERROR; } /* * Make the object and return its name. */ return TclNRNewObjectInstance(interp, (Tcl_Class) oPtr->classPtr, objName, NULL, objc, objv, Tcl_ObjectContextSkippedArgs(context)+1, AddConstructionFinalizer(interp)); } /* * ---------------------------------------------------------------------- * * TclOO_Class_CreateNs -- * * Implementation for oo::class->createWithNamespace method. * * ---------------------------------------------------------------------- */ int TclOO_Class_CreateNs( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter in which to create the object; * also used for error reporting. */ Tcl_ObjectContext context, /* The object/call context. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* The actual arguments. */ { Object *oPtr = (Object *) Tcl_ObjectContextObject(context); const char *objName, *nsName; Tcl_Size len; /* * Sanity check; should not be possible to invoke this method on a * non-class. */ if (oPtr->classPtr == NULL) { Tcl_Obj *cmdnameObj = TclOOObjectName(interp, oPtr); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "object \"%s\" is not a class", TclGetString(cmdnameObj))); Tcl_SetErrorCode(interp, "TCL", "OO", "INSTANTIATE_NONCLASS", (char *)NULL); return TCL_ERROR; } /* * Check we have the right number of (sensible) arguments. */ if (objc + 1 < Tcl_ObjectContextSkippedArgs(context) + 3) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "objectName namespaceName ?arg ...?"); return TCL_ERROR; } objName = Tcl_GetStringFromObj( objv[Tcl_ObjectContextSkippedArgs(context)], &len); if (len == 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "object name must not be empty", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "EMPTY_NAME", (char *)NULL); return TCL_ERROR; } nsName = Tcl_GetStringFromObj( objv[Tcl_ObjectContextSkippedArgs(context) + 1], &len); if (len == 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "namespace name must not be empty", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "EMPTY_NAME", (char *)NULL); return TCL_ERROR; } /* * Make the object and return its name. */ return TclNRNewObjectInstance(interp, (Tcl_Class) oPtr->classPtr, objName, nsName, objc, objv, Tcl_ObjectContextSkippedArgs(context) + 2, AddConstructionFinalizer(interp)); } /* * ---------------------------------------------------------------------- * * TclOO_Class_New -- * * Implementation for oo::class->new method. * * ---------------------------------------------------------------------- */ int TclOO_Class_New( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter in which to create the object; * also used for error reporting. */ Tcl_ObjectContext context, /* The object/call context. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* The actual arguments. */ { Object *oPtr = (Object *) Tcl_ObjectContextObject(context); /* * Sanity check; should not be possible to invoke this method on a * non-class. */ if (oPtr->classPtr == NULL) { Tcl_Obj *cmdnameObj = TclOOObjectName(interp, oPtr); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "object \"%s\" is not a class", TclGetString(cmdnameObj))); Tcl_SetErrorCode(interp, "TCL", "OO", "INSTANTIATE_NONCLASS", (char *)NULL); return TCL_ERROR; } /* * Make the object and return its name. */ return TclNRNewObjectInstance(interp, (Tcl_Class) oPtr->classPtr, NULL, NULL, objc, objv, Tcl_ObjectContextSkippedArgs(context), AddConstructionFinalizer(interp)); } /* * ---------------------------------------------------------------------- * * TclOO_Object_Destroy -- * * Implementation for oo::object->destroy method. * * ---------------------------------------------------------------------- */ int TclOO_Object_Destroy( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter in which to create the object; * also used for error reporting. */ Tcl_ObjectContext context, /* The object/call context. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* The actual arguments. */ { Object *oPtr = (Object *) Tcl_ObjectContextObject(context); CallContext *contextPtr; if (objc != (int) Tcl_ObjectContextSkippedArgs(context)) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, NULL); return TCL_ERROR; } if (!(oPtr->flags & DESTRUCTOR_CALLED)) { oPtr->flags |= DESTRUCTOR_CALLED; contextPtr = TclOOGetCallContext(oPtr, NULL, DESTRUCTOR, NULL, NULL, NULL); if (contextPtr != NULL) { contextPtr->callPtr->flags |= DESTRUCTOR; contextPtr->skip = 0; TclNRAddCallback(interp, AfterNRDestructor, contextPtr, NULL, NULL, NULL); TclPushTailcallPoint(interp); return TclOOInvokeContext(contextPtr, interp, 0, NULL); } } if (oPtr->command) { Tcl_DeleteCommandFromToken(interp, oPtr->command); } return TCL_OK; } static int AfterNRDestructor( void *data[], Tcl_Interp *interp, int result) { CallContext *contextPtr = (CallContext *) data[0]; if (contextPtr->oPtr->command) { Tcl_DeleteCommandFromToken(interp, contextPtr->oPtr->command); } TclOODeleteContext(contextPtr); return result; } /* * ---------------------------------------------------------------------- * * TclOO_Object_Eval -- * * Implementation for oo::object->eval method. * * ---------------------------------------------------------------------- */ int TclOO_Object_Eval( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter in which to create the object; * also used for error reporting. */ Tcl_ObjectContext context, /* The object/call context. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* The actual arguments. */ { CallContext *contextPtr = (CallContext *) context; Tcl_Object object = Tcl_ObjectContextObject(context); size_t skip = Tcl_ObjectContextSkippedArgs(context); CallFrame *framePtr, **framePtrPtr = &framePtr; Tcl_Obj *scriptPtr; CmdFrame *invoker; if ((size_t) objc < skip + 1) { Tcl_WrongNumArgs(interp, skip, objv, "arg ?arg ...?"); return TCL_ERROR; } /* * Make the object's namespace the current namespace and evaluate the * command(s). */ (void) TclPushStackFrame(interp, (Tcl_CallFrame **) framePtrPtr, Tcl_GetObjectNamespace(object), FRAME_IS_METHOD); framePtr->clientData = context; framePtr->objc = objc; framePtr->objv = objv; /* Reference counts do not need to be * incremented here. */ if (!(contextPtr->callPtr->flags & PUBLIC_METHOD)) { object = NULL; /* Now just for error mesage printing. */ } /* * Work out what script we are actually going to evaluate. * * When there's more than one argument, we concatenate them together with * spaces between, then evaluate the result. Tcl_EvalObjEx will delete the * object when it decrements its refcount after eval'ing it. */ if ((size_t) objc != skip+1) { scriptPtr = Tcl_ConcatObj(objc-skip, objv+skip); invoker = NULL; } else { scriptPtr = objv[skip]; invoker = ((Interp *) interp)->cmdFramePtr; } /* * Evaluate the script now, with FinalizeEval to do the processing after * the script completes. */ TclNRAddCallback(interp, FinalizeEval, object, NULL, NULL, NULL); return TclNREvalObjEx(interp, scriptPtr, 0, invoker, skip); } static int FinalizeEval( void *data[], Tcl_Interp *interp, int result) { if (result == TCL_ERROR) { Object *oPtr = (Object *) data[0]; const char *namePtr; if (oPtr) { namePtr = TclGetString(TclOOObjectName(interp, oPtr)); } else { namePtr = "my"; } Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (in \"%s eval\" script line %d)", namePtr, Tcl_GetErrorLine(interp))); } /* * Restore the previous "current" namespace. */ TclPopStackFrame(interp); return result; } /* * ---------------------------------------------------------------------- * * TclOO_Object_Unknown -- * * Default unknown method handler method (defined in oo::object). This * just creates a suitable error message. * * ---------------------------------------------------------------------- */ int TclOO_Object_Unknown( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter in which to create the object; * also used for error reporting. */ Tcl_ObjectContext context, /* The object/call context. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* The actual arguments. */ { CallContext *contextPtr = (CallContext *) context; Object *callerObj = NULL; Class *callerCls = NULL; Object *oPtr = contextPtr->oPtr; const char **methodNames; int numMethodNames, i; size_t skip = Tcl_ObjectContextSkippedArgs(context); CallFrame *framePtr = ((Interp *) interp)->varFramePtr; Tcl_Obj *errorMsg; /* * If no method name, generate an error asking for a method name. (Only by * overriding *this* method can an object handle the absence of a method * name without an error). */ if ((size_t) objc < skip + 1) { Tcl_WrongNumArgs(interp, skip, objv, "method ?arg ...?"); return TCL_ERROR; } /* * Determine if the calling context should know about extra private * methods, and if so, which. */ if (framePtr->isProcCallFrame & FRAME_IS_METHOD) { CallContext *callerContext = (CallContext *) framePtr->clientData; Method *mPtr = callerContext->callPtr->chain[ callerContext->index].mPtr; if (mPtr->declaringObjectPtr) { if (oPtr == mPtr->declaringObjectPtr) { callerObj = mPtr->declaringObjectPtr; } } else { if (TclOOIsReachable(mPtr->declaringClassPtr, oPtr->selfCls)) { callerCls = mPtr->declaringClassPtr; } } } /* * Get the list of methods that we want to know about. */ numMethodNames = TclOOGetSortedMethodList(oPtr, callerObj, callerCls, contextPtr->callPtr->flags & PUBLIC_METHOD, &methodNames); /* * Special message when there are no visible methods at all. */ if (numMethodNames == 0) { Tcl_Obj *tmpBuf = TclOOObjectName(interp, oPtr); const char *piece; if (contextPtr->callPtr->flags & PUBLIC_METHOD) { piece = "visible methods"; } else { piece = "methods"; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "object \"%s\" has no %s", TclGetString(tmpBuf), piece)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(objv[skip]), (char *)NULL); return TCL_ERROR; } errorMsg = Tcl_ObjPrintf("unknown method \"%s\": must be ", TclGetString(objv[skip])); for (i=0 ; ivariable method. * * ---------------------------------------------------------------------- */ int TclOO_Object_LinkVar( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter in which to create the object; * also used for error reporting. */ Tcl_ObjectContext context, /* The object/call context. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* The actual arguments. */ { Interp *iPtr = (Interp *) interp; Tcl_Object object = Tcl_ObjectContextObject(context); Namespace *savedNsPtr; Tcl_Size i; if (objc < Tcl_ObjectContextSkippedArgs(context)) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "?varName ...?"); return TCL_ERROR; } /* * A sanity check. Shouldn't ever happen. (This is all that remains of a * more complex check inherited from [global] after we have applied the * fix for [Bug 2903811]; note that the fix involved *removing* code.) */ if (iPtr->varFramePtr == NULL) { return TCL_OK; } for (i = Tcl_ObjectContextSkippedArgs(context) ; i < objc ; i++) { Var *varPtr, *aryPtr; const char *varName = TclGetString(objv[i]); /* * The variable name must not contain a '::' since that's illegal in * local names. */ if (strstr(varName, "::") != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "variable name \"%s\" illegal: must not contain namespace" " separator", varName)); Tcl_SetErrorCode(interp, "TCL", "UPVAR", "INVERTED", (char *)NULL); return TCL_ERROR; } /* * Switch to the object's namespace for the duration of this call. * Like this, the variable is looked up in the namespace of the * object, and not in the namespace of the caller. Otherwise this * would only work if the caller was a method of the object itself, * which might not be true if the method was exported. This is a bit * of a hack, but the simplest way to do this (pushing a stack frame * would be horribly expensive by comparison). */ savedNsPtr = iPtr->varFramePtr->nsPtr; iPtr->varFramePtr->nsPtr = (Namespace *) Tcl_GetObjectNamespace(object); varPtr = TclObjLookupVar(interp, objv[i], NULL, TCL_NAMESPACE_ONLY, "define", 1, 0, &aryPtr); iPtr->varFramePtr->nsPtr = savedNsPtr; if (varPtr == NULL || aryPtr != NULL) { /* * Variable cannot be an element in an array. If aryPtr is not * NULL, it is an element, so throw up an error and return. */ TclVarErrMsg(interp, varName, NULL, "define", "name refers to an element in an array"); Tcl_SetErrorCode(interp, "TCL", "UPVAR", "LOCAL_ELEMENT", (char *)NULL); return TCL_ERROR; } /* * Arrange for the lifetime of the variable to be correctly managed. * This is copied out of Tcl_VariableObjCmd... */ if (!TclIsVarNamespaceVar(varPtr)) { TclSetVarNamespaceVar(varPtr); } if (TclPtrMakeUpvar(interp, varPtr, varName, 0, -1) != TCL_OK) { return TCL_ERROR; } } return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOOLookupObjectVar -- * * Look up a variable in an object. Tricky because of private variables. * * Returns: * Handle to the variable if it can be found, or NULL if there's an error. * * ---------------------------------------------------------------------- */ Tcl_Var TclOOLookupObjectVar( Tcl_Interp *interp, Tcl_Object object, /* Object we're looking up within. */ Tcl_Obj *varName, /* User-visible name we're looking up. */ Tcl_Var *aryPtr) /* Where to write the handle to the array * containing the element; if not an element, * then the variable this points to is set to * NULL. */ { const char *arg = TclGetString(varName); Tcl_Obj *varNamePtr; /* * Convert the variable name to fully-qualified form if it wasn't already. * This has to be done prior to lookup because we can run into problems * with resolvers otherwise. [Bug 3603695] * * We still need to do the lookup; the variable could be linked to another * variable and we want the target's name. */ if (arg[0] == ':' && arg[1] == ':') { varNamePtr = varName; } else { Tcl_Namespace *namespacePtr = Tcl_GetObjectNamespace(object); CallFrame *framePtr = ((Interp *) interp)->varFramePtr; /* * Private method handling. [TIP 500] * * If we're in a context that can see some private methods of an * object, we may need to precede a variable name with its prefix. * This is a little tricky as we need to check through the inheritance * hierarchy when the method was declared by a class to see if the * current object is an instance of that class. */ if (framePtr->isProcCallFrame & FRAME_IS_METHOD) { Object *oPtr = (Object *) object; CallContext *callerContext = (CallContext *) framePtr->clientData; Method *mPtr = callerContext->callPtr->chain[ callerContext->index].mPtr; PrivateVariableMapping *pvPtr; Tcl_Size i; if (mPtr->declaringObjectPtr == oPtr) { FOREACH_STRUCT(pvPtr, oPtr->privateVariables) { if (!TclStringCmp(pvPtr->variableObj, varName, 1, 0, TCL_INDEX_NONE)) { varName = pvPtr->fullNameObj; break; } } } else if (mPtr->declaringClassPtr && mPtr->declaringClassPtr->privateVariables.num) { Class *clsPtr = mPtr->declaringClassPtr; int isInstance = TclOOIsReachable(clsPtr, oPtr->selfCls); Class *mixinCls; if (!isInstance) { FOREACH(mixinCls, oPtr->mixins) { if (TclOOIsReachable(clsPtr, mixinCls)) { isInstance = 1; break; } } } if (isInstance) { FOREACH_STRUCT(pvPtr, clsPtr->privateVariables) { if (!TclStringCmp(pvPtr->variableObj, varName, 1, 0, TCL_INDEX_NONE)) { varName = pvPtr->fullNameObj; break; } } } } } // The namespace isn't the global one; necessarily true for any object! varNamePtr = Tcl_ObjPrintf("%s::%s", namespacePtr->fullName, TclGetString(varName)); } Tcl_IncrRefCount(varNamePtr); Tcl_Var var = (Tcl_Var) TclObjLookupVar(interp, varNamePtr, NULL, TCL_NAMESPACE_ONLY|TCL_LEAVE_ERR_MSG, "refer to", 1, 1, (Var **) aryPtr); Tcl_DecrRefCount(varNamePtr); if (var == NULL) { Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "VARIABLE", arg, (void *) NULL); } else if (*aryPtr == NULL && TclIsVarArrayElement((Var *) var)) { /* * If the varPtr points to an element of an array but we don't already * have the array, find it now. Note that this can't be easily * backported; the arrayPtr field is new in Tcl 9.0. [Bug 2da1cb0c80] */ *aryPtr = (Tcl_Var) TclVarParentArray(var); } return var; } /* * ---------------------------------------------------------------------- * * TclOO_Object_VarName -- * * Implementation of the oo::object->varname method. * * ---------------------------------------------------------------------- */ int TclOO_Object_VarName( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter in which to create the object; * also used for error reporting. */ Tcl_ObjectContext context, /* The object/call context. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* The actual arguments. */ { Tcl_Var varPtr, aryVar; Tcl_Obj *varNamePtr; if ((int) Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "varName"); return TCL_ERROR; } varPtr = TclOOLookupObjectVar(interp, Tcl_ObjectContextObject(context), objv[objc - 1], &aryVar); if (varPtr == NULL) { return TCL_ERROR; } /* * The variable reference must not disappear too soon. [Bug 74b6110204] */ if (!TclIsVarArrayElement((Var *) varPtr)) { TclSetVarNamespaceVar((Var *) varPtr); } /* * Now that we've pinned down what variable we're really talking about * (including traversing variable links), convert back to a name. */ TclNewObj(varNamePtr); if (aryVar != NULL) { Tcl_GetVariableFullName(interp, aryVar, varNamePtr); Tcl_AppendPrintfToObj(varNamePtr, "(%s)", Tcl_GetString( VarHashGetKey(varPtr))); } else { Tcl_GetVariableFullName(interp, varPtr, varNamePtr); } Tcl_SetObjResult(interp, varNamePtr); return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOONextObjCmd, TclOONextToObjCmd -- * * Implementation of the [next] and [nextto] commands. Note that these * commands are only ever to be used inside the body of a procedure-like * method. * * ---------------------------------------------------------------------- */ int TclOONextObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Interp *iPtr = (Interp *) interp; CallFrame *framePtr = iPtr->varFramePtr; Tcl_ObjectContext context; /* * Start with sanity checks on the calling context to make sure that we * are invoked from a suitable method context. If so, we can safely * retrieve the handle to the object call context. */ if (framePtr == NULL || !(framePtr->isProcCallFrame & FRAME_IS_METHOD)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s may only be called from inside a method", TclGetString(objv[0]))); Tcl_SetErrorCode(interp, "TCL", "OO", "CONTEXT_REQUIRED", (char *)NULL); return TCL_ERROR; } context = (Tcl_ObjectContext) framePtr->clientData; /* * Invoke the (advanced) method call context in the caller context. Note * that this is like [uplevel 1] and not [eval]. */ TclNRAddCallback(interp, NextRestoreFrame, framePtr, NULL,NULL,NULL); iPtr->varFramePtr = framePtr->callerVarPtr; return TclNRObjectContextInvokeNext(interp, context, objc, objv, 1); } int TclOONextToObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Interp *iPtr = (Interp *) interp; CallFrame *framePtr = iPtr->varFramePtr; Class *classPtr; CallContext *contextPtr; Tcl_Size i; Tcl_Object object; const char *methodType; /* * Start with sanity checks on the calling context to make sure that we * are invoked from a suitable method context. If so, we can safely * retrieve the handle to the object call context. */ if (framePtr == NULL || !(framePtr->isProcCallFrame & FRAME_IS_METHOD)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s may only be called from inside a method", TclGetString(objv[0]))); Tcl_SetErrorCode(interp, "TCL", "OO", "CONTEXT_REQUIRED", (char *)NULL); return TCL_ERROR; } contextPtr = (CallContext *) framePtr->clientData; /* * Sanity check the arguments; we need the first one to refer to a class. */ if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "class ?arg...?"); return TCL_ERROR; } object = Tcl_GetObjectFromObj(interp, objv[1]); if (object == NULL) { return TCL_ERROR; } classPtr = ((Object *) object)->classPtr; if (classPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" is not a class", TclGetString(objv[1]))); Tcl_SetErrorCode(interp, "TCL", "OO", "CLASS_REQUIRED", (char *)NULL); return TCL_ERROR; } /* * Search for an implementation of a method associated with the current * call on the call chain past the point where we currently are. Do not * allow jumping backwards! */ for (i=contextPtr->index+1 ; icallPtr->numChain ; i++) { MInvoke *miPtr = &contextPtr->callPtr->chain[i]; if (!miPtr->isFilter && miPtr->mPtr->declaringClassPtr == classPtr) { /* * Invoke the (advanced) method call context in the caller * context. Note that this is like [uplevel 1] and not [eval]. */ TclNRAddCallback(interp, NextRestoreFrame, framePtr, contextPtr, INT2PTR(contextPtr->index), NULL); contextPtr->index = i - 1; iPtr->varFramePtr = framePtr->callerVarPtr; return TclNRObjectContextInvokeNext(interp, (Tcl_ObjectContext) contextPtr, objc, objv, 2); } } /* * Generate an appropriate error message, depending on whether the value * is on the chain but unreachable, or not on the chain at all. */ if (contextPtr->callPtr->flags & CONSTRUCTOR) { methodType = "constructor"; } else if (contextPtr->callPtr->flags & DESTRUCTOR) { methodType = "destructor"; } else { methodType = "method"; } for (i=contextPtr->index ; i != TCL_INDEX_NONE ; i--) { MInvoke *miPtr = &contextPtr->callPtr->chain[i]; if (!miPtr->isFilter && miPtr->mPtr->declaringClassPtr == classPtr) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s implementation by \"%s\" not reachable from here", methodType, TclGetString(objv[1]))); Tcl_SetErrorCode(interp, "TCL", "OO", "CLASS_NOT_REACHABLE", (char *)NULL); return TCL_ERROR; } } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s has no non-filter implementation by \"%s\"", methodType, TclGetString(objv[1]))); Tcl_SetErrorCode(interp, "TCL", "OO", "CLASS_NOT_THERE", (char *)NULL); return TCL_ERROR; } static int NextRestoreFrame( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; CallContext *contextPtr = (CallContext *) data[1]; iPtr->varFramePtr = (CallFrame *) data[0]; if (contextPtr != NULL) { contextPtr->index = PTR2UINT(data[2]); } return result; } /* * ---------------------------------------------------------------------- * * TclOOSelfObjCmd -- * * Implementation of the [self] command, which provides introspection of * the call context. * * ---------------------------------------------------------------------- */ int TclOOSelfObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { static const char *const subcmds[] = { "call", "caller", "class", "filter", "method", "namespace", "next", "object", "target", NULL }; enum SelfCmds { SELF_CALL, SELF_CALLER, SELF_CLASS, SELF_FILTER, SELF_METHOD, SELF_NS, SELF_NEXT, SELF_OBJECT, SELF_TARGET } index; Interp *iPtr = (Interp *) interp; CallFrame *framePtr = iPtr->varFramePtr; CallContext *contextPtr; Tcl_Obj *result[3]; #define CurrentlyInvoked(contextPtr) \ ((contextPtr)->callPtr->chain[(contextPtr)->index]) /* * Start with sanity checks on the calling context and the method context. */ if (framePtr == NULL || !(framePtr->isProcCallFrame & FRAME_IS_METHOD)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s may only be called from inside a method", TclGetString(objv[0]))); Tcl_SetErrorCode(interp, "TCL", "OO", "CONTEXT_REQUIRED", (char *)NULL); return TCL_ERROR; } contextPtr = (CallContext *) framePtr->clientData; /* * Now we do "conventional" argument parsing for a while. Note that no * subcommand takes arguments. */ if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "subcommand"); return TCL_ERROR; } else if (objc == 1) { index = SELF_OBJECT; } else if (Tcl_GetIndexFromObj(interp, objv[1], subcmds, "subcommand", 0, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case SELF_OBJECT: Tcl_SetObjResult(interp, TclOOObjectName(interp, contextPtr->oPtr)); return TCL_OK; case SELF_NS: Tcl_SetObjResult(interp, TclNewNamespaceObj(contextPtr->oPtr->namespacePtr)); return TCL_OK; case SELF_CLASS: { Class *clsPtr = CurrentlyInvoked(contextPtr).mPtr->declaringClassPtr; if (clsPtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "method not defined by a class", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "UNMATCHED_CONTEXT", (char *)NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, TclOOObjectName(interp, clsPtr->thisPtr)); return TCL_OK; } case SELF_METHOD: if (contextPtr->callPtr->flags & CONSTRUCTOR) { Tcl_SetObjResult(interp, contextPtr->oPtr->fPtr->constructorName); } else if (contextPtr->callPtr->flags & DESTRUCTOR) { Tcl_SetObjResult(interp, contextPtr->oPtr->fPtr->destructorName); } else { Tcl_SetObjResult(interp, CurrentlyInvoked(contextPtr).mPtr->namePtr); } return TCL_OK; case SELF_FILTER: if (!CurrentlyInvoked(contextPtr).isFilter) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "not inside a filtering context", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "UNMATCHED_CONTEXT", (char *)NULL); return TCL_ERROR; } else { MInvoke *miPtr = &CurrentlyInvoked(contextPtr); Object *oPtr; const char *type; if (miPtr->filterDeclarer != NULL) { oPtr = miPtr->filterDeclarer->thisPtr; type = "class"; } else { oPtr = contextPtr->oPtr; type = "object"; } result[0] = TclOOObjectName(interp, oPtr); result[1] = Tcl_NewStringObj(type, TCL_AUTO_LENGTH); result[2] = miPtr->mPtr->namePtr; Tcl_SetObjResult(interp, Tcl_NewListObj(3, result)); return TCL_OK; } case SELF_CALLER: if ((framePtr->callerVarPtr == NULL) || !(framePtr->callerVarPtr->isProcCallFrame & FRAME_IS_METHOD)){ Tcl_SetObjResult(interp, Tcl_NewStringObj( "caller is not an object", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "CONTEXT_REQUIRED", (char *)NULL); return TCL_ERROR; } else { CallContext *callerPtr = (CallContext *) framePtr->callerVarPtr->clientData; Method *mPtr = callerPtr->callPtr->chain[callerPtr->index].mPtr; Object *declarerPtr; if (mPtr->declaringClassPtr != NULL) { declarerPtr = mPtr->declaringClassPtr->thisPtr; } else if (mPtr->declaringObjectPtr != NULL) { declarerPtr = mPtr->declaringObjectPtr; } else { /* * This should be unreachable code. */ Tcl_SetObjResult(interp, Tcl_NewStringObj( "method without declarer!", TCL_AUTO_LENGTH)); return TCL_ERROR; } result[0] = TclOOObjectName(interp, declarerPtr); result[1] = TclOOObjectName(interp, callerPtr->oPtr); if (callerPtr->callPtr->flags & CONSTRUCTOR) { result[2] = declarerPtr->fPtr->constructorName; } else if (callerPtr->callPtr->flags & DESTRUCTOR) { result[2] = declarerPtr->fPtr->destructorName; } else { result[2] = mPtr->namePtr; } Tcl_SetObjResult(interp, Tcl_NewListObj(3, result)); return TCL_OK; } case SELF_NEXT: if (contextPtr->index < contextPtr->callPtr->numChain - 1) { Method *mPtr = contextPtr->callPtr->chain[contextPtr->index + 1].mPtr; Object *declarerPtr; if (mPtr->declaringClassPtr != NULL) { declarerPtr = mPtr->declaringClassPtr->thisPtr; } else if (mPtr->declaringObjectPtr != NULL) { declarerPtr = mPtr->declaringObjectPtr; } else { /* * This should be unreachable code. */ Tcl_SetObjResult(interp, Tcl_NewStringObj( "method without declarer!", TCL_AUTO_LENGTH)); return TCL_ERROR; } result[0] = TclOOObjectName(interp, declarerPtr); if (contextPtr->callPtr->flags & CONSTRUCTOR) { result[1] = declarerPtr->fPtr->constructorName; } else if (contextPtr->callPtr->flags & DESTRUCTOR) { result[1] = declarerPtr->fPtr->destructorName; } else { result[1] = mPtr->namePtr; } Tcl_SetObjResult(interp, Tcl_NewListObj(2, result)); } return TCL_OK; case SELF_TARGET: if (!CurrentlyInvoked(contextPtr).isFilter) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "not inside a filtering context", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "UNMATCHED_CONTEXT", (char *)NULL); return TCL_ERROR; } else { Method *mPtr; Object *declarerPtr; Tcl_Size i; for (i=contextPtr->index ; icallPtr->numChain ; i++) { if (!contextPtr->callPtr->chain[i].isFilter) { break; } } if (i == contextPtr->callPtr->numChain) { Tcl_Panic("filtering call chain without terminal non-filter"); } mPtr = contextPtr->callPtr->chain[i].mPtr; if (mPtr->declaringClassPtr != NULL) { declarerPtr = mPtr->declaringClassPtr->thisPtr; } else if (mPtr->declaringObjectPtr != NULL) { declarerPtr = mPtr->declaringObjectPtr; } else { /* * This should be unreachable code. */ Tcl_SetObjResult(interp, Tcl_NewStringObj( "method without declarer!", TCL_AUTO_LENGTH)); return TCL_ERROR; } result[0] = TclOOObjectName(interp, declarerPtr); result[1] = mPtr->namePtr; Tcl_SetObjResult(interp, Tcl_NewListObj(2, result)); return TCL_OK; } case SELF_CALL: result[0] = TclOORenderCallChain(interp, contextPtr->callPtr); TclNewIndexObj(result[1], contextPtr->index); Tcl_SetObjResult(interp, Tcl_NewListObj(2, result)); return TCL_OK; } return TCL_ERROR; } /* * ---------------------------------------------------------------------- * * CopyObjectCmd -- * * Implementation of the [oo::copy] command, which clones an object (but * not its namespace). Note that no constructors are called during this * process. * * ---------------------------------------------------------------------- */ int TclOOCopyObjectCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Tcl_Object oPtr, o2Ptr; if (objc < 2 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "sourceName ?targetName? ?targetNamespace?"); return TCL_ERROR; } oPtr = Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } /* * Create a cloned object of the correct class. Note that constructors are * not called. Also note that we must resolve the object name ourselves * because we do not want to create the object in the current namespace, * but rather in the context of the namespace of the caller of the overall * [oo::define] command. */ if (objc == 2) { o2Ptr = Tcl_CopyObjectInstance(interp, oPtr, NULL, NULL); } else { const char *name, *namespaceName; name = TclGetString(objv[2]); if (name[0] == '\0') { name = NULL; } /* * Choose a unique namespace name if the user didn't supply one. */ namespaceName = NULL; if (objc == 4) { namespaceName = TclGetString(objv[3]); if (namespaceName[0] == '\0') { namespaceName = NULL; } else if (Tcl_FindNamespace(interp, namespaceName, NULL, 0) != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s refers to an existing namespace", namespaceName)); return TCL_ERROR; } } o2Ptr = Tcl_CopyObjectInstance(interp, oPtr, name, namespaceName); } if (o2Ptr == NULL) { return TCL_ERROR; } /* * Return the name of the cloned object. */ Tcl_SetObjResult(interp, TclOOObjectName(interp, (Object *) o2Ptr)); return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclOOCall.c0000644000175000017500000017454414726623136015171 0ustar sergeisergei/* * tclOOCall.c -- * * This file contains the method call chain management code for the * object-system core. It also contains everything else that does * inheritance hierarchy traversal. * * Copyright © 2005-2019 Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "tclInt.h" #include "tclOOInt.h" #include /* * Structure containing a CallChain and any other values needed only during * the construction of the CallChain. */ typedef struct ChainBuilder { CallChain *callChainPtr; /* The call chain being built. */ size_t filterLength; /* Number of entries in the call chain that * are due to processing filters and not the * main call chain. */ Object *oPtr; /* The object that we are building the chain * for. */ } ChainBuilder; /* * Structures used for traversing the class hierarchy to find out where * definitions are supposed to be done. */ typedef struct DefineEntry { Class *definerCls; Tcl_Obj *namespaceName; } DefineEntry; typedef struct DefineChain { DefineEntry *list; int num; int size; } DefineChain; /* * Extra flags used for call chain management. */ enum CallChainFlags { DEFINITE_PROTECTED = 0x100000, DEFINITE_PUBLIC = 0x200000, KNOWN_STATE = (DEFINITE_PROTECTED | DEFINITE_PUBLIC), SPECIAL = (CONSTRUCTOR | DESTRUCTOR | FORCE_UNKNOWN), BUILDING_MIXINS = 0x400000, TRAVERSED_MIXIN = 0x800000, OBJECT_MIXIN = 0x1000000, DEFINE_FOR_CLASS = 0x2000000 }; #define MIXIN_CONSISTENT(flags) \ (((flags) & OBJECT_MIXIN) || \ !((flags) & BUILDING_MIXINS) == !((flags) & TRAVERSED_MIXIN)) /* * Note that the flag bit PRIVATE_METHOD has a confusing name; it's just for * Itcl's special type of private. */ #define IS_PUBLIC(mPtr) \ (((mPtr)->flags & PUBLIC_METHOD) != 0) #define IS_UNEXPORTED(mPtr) \ (((mPtr)->flags & SCOPE_FLAGS) == 0) #define IS_ITCLPRIVATE(mPtr) \ (((mPtr)->flags & PRIVATE_METHOD) != 0) #define IS_PRIVATE(mPtr) \ (((mPtr)->flags & TRUE_PRIVATE_METHOD) != 0) #define WANT_PUBLIC(flags) \ (((flags) & PUBLIC_METHOD) != 0) #define WANT_UNEXPORTED(flags) \ (((flags) & (PRIVATE_METHOD | TRUE_PRIVATE_METHOD)) == 0) #define WANT_ITCLPRIVATE(flags) \ (((flags) & PRIVATE_METHOD) != 0) #define WANT_PRIVATE(flags) \ (((flags) & TRUE_PRIVATE_METHOD) != 0) /* * Name the bits used in the names table values. */ enum NameTableValues { IN_LIST = 1, /* Seen an implementation. */ NO_IMPLEMENTATION = 2 /* Seen, but not implemented yet. */ }; /* * Function declarations for things defined in this file. */ static void AddClassFiltersToCallContext(Object *const oPtr, Class *clsPtr, ChainBuilder *const cbPtr, Tcl_HashTable *const doneFilters, int flags); static void AddClassMethodNames(Class *clsPtr, int flags, Tcl_HashTable *const namesPtr, Tcl_HashTable *const examinedClassesPtr); static inline void AddDefinitionNamespaceToChain(Class *const definerCls, Tcl_Obj *const namespaceName, DefineChain *const definePtr, int flags); static inline void AddMethodToCallChain(Method *const mPtr, ChainBuilder *const cbPtr, Tcl_HashTable *const doneFilters, Class *const filterDecl, int flags); static inline int AddInstancePrivateToCallContext(Object *const oPtr, Tcl_Obj *const methodNameObj, ChainBuilder *const cbPtr, int flags); static inline void AddStandardMethodName(int flags, Tcl_Obj *namePtr, Method *mPtr, Tcl_HashTable *namesPtr); static inline void AddPrivateMethodNames(Tcl_HashTable *methodsTablePtr, Tcl_HashTable *namesPtr); static inline int AddSimpleChainToCallContext(Object *const oPtr, Class *const contextCls, Tcl_Obj *const methodNameObj, ChainBuilder *const cbPtr, Tcl_HashTable *const doneFilters, int flags, Class *const filterDecl); static int AddPrivatesFromClassChainToCallContext(Class *classPtr, Class *const contextCls, Tcl_Obj *const methodNameObj, ChainBuilder *const cbPtr, Tcl_HashTable *const doneFilters, int flags, Class *const filterDecl); static int AddSimpleClassChainToCallContext(Class *classPtr, Tcl_Obj *const methodNameObj, ChainBuilder *const cbPtr, Tcl_HashTable *const doneFilters, int flags, Class *const filterDecl); static void AddSimpleClassDefineNamespaces(Class *classPtr, DefineChain *const definePtr, int flags); static inline void AddSimpleDefineNamespaces(Object *const oPtr, DefineChain *const definePtr, int flags); static int CmpStr(const void *ptr1, const void *ptr2); static void DupMethodNameRep(Tcl_Obj *srcPtr, Tcl_Obj *dstPtr); static Tcl_NRPostProc FinalizeMethodRefs; static void FreeMethodNameRep(Tcl_Obj *objPtr); static inline int IsStillValid(CallChain *callPtr, Object *oPtr, int flags, int reuseMask); static Tcl_NRPostProc ResetFilterFlags; static Tcl_NRPostProc SetFilterFlags; static size_t SortMethodNames(Tcl_HashTable *namesPtr, int flags, const char ***stringsPtr); static inline void StashCallChain(Tcl_Obj *objPtr, CallChain *callPtr); /* * Object type used to manage type caches attached to method names. */ static const Tcl_ObjType methodNameType = { "TclOO method name", FreeMethodNameRep, DupMethodNameRep, NULL, NULL, TCL_OBJTYPE_V0 }; /* * ---------------------------------------------------------------------- * * TclOODeleteContext -- * * Destroys a method call-chain context, which should not be in use. * * ---------------------------------------------------------------------- */ void TclOODeleteContext( CallContext *contextPtr) { Object *oPtr = contextPtr->oPtr; TclOODeleteChain(contextPtr->callPtr); if (oPtr != NULL) { TclStackFree(oPtr->fPtr->interp, contextPtr); /* * Corresponding AddRef() in TclOO.c/TclOOObjectCmdCore */ TclOODecrRefCount(oPtr); } } /* * ---------------------------------------------------------------------- * * TclOODeleteChainCache -- * * Destroy the cache of method call-chains. * * ---------------------------------------------------------------------- */ void TclOODeleteChainCache( Tcl_HashTable *tablePtr) { FOREACH_HASH_DECLS; CallChain *callPtr; FOREACH_HASH_VALUE(callPtr, tablePtr) { if (callPtr) { TclOODeleteChain(callPtr); } } Tcl_DeleteHashTable(tablePtr); Tcl_Free(tablePtr); } /* * ---------------------------------------------------------------------- * * TclOODeleteChain -- * * Destroys a method call-chain. * * ---------------------------------------------------------------------- */ void TclOODeleteChain( CallChain *callPtr) { if (callPtr == NULL || callPtr->refCount-- > 1) { return; } if (callPtr->chain != callPtr->staticChain) { Tcl_Free(callPtr->chain); } Tcl_Free(callPtr); } /* * ---------------------------------------------------------------------- * * TclOOStashContext -- * * Saves a reference to a method call context in a Tcl_Obj's internal * representation. * * ---------------------------------------------------------------------- */ static inline void StashCallChain( Tcl_Obj *objPtr, CallChain *callPtr) { Tcl_ObjInternalRep ir; callPtr->refCount++; TclGetString(objPtr); ir.twoPtrValue.ptr1 = callPtr; Tcl_StoreInternalRep(objPtr, &methodNameType, &ir); } void TclOOStashContext( Tcl_Obj *objPtr, CallContext *contextPtr) { StashCallChain(objPtr, contextPtr->callPtr); } /* * ---------------------------------------------------------------------- * * DupMethodNameRep, FreeMethodNameRep -- * * Functions to implement the required parts of the Tcl_Obj guts needed * for caching of method contexts in Tcl_Objs. * * ---------------------------------------------------------------------- */ static void DupMethodNameRep( Tcl_Obj *srcPtr, Tcl_Obj *dstPtr) { StashCallChain(dstPtr, (CallChain *) TclFetchInternalRep(srcPtr, &methodNameType)->twoPtrValue.ptr1); } static void FreeMethodNameRep( Tcl_Obj *objPtr) { TclOODeleteChain((CallChain *) TclFetchInternalRep(objPtr, &methodNameType)->twoPtrValue.ptr1); } /* * ---------------------------------------------------------------------- * * TclOOInvokeContext -- * * Invokes a single step along a method call-chain context. Note that the * invocation of a step along the chain can cause further steps along the * chain to be invoked. Note that this function is written to be as light * in stack usage as possible. * * ---------------------------------------------------------------------- */ int TclOOInvokeContext( void *clientData, /* The method call context. */ Tcl_Interp *interp, /* Interpreter for error reporting, and many * other sorts of context handling (e.g., * commands, variables) depending on method * implementation. */ int objc, /* The number of arguments. */ Tcl_Obj *const objv[]) /* The arguments as actually seen. */ { CallContext *const contextPtr = (CallContext *) clientData; Method *const mPtr = contextPtr->callPtr->chain[contextPtr->index].mPtr; const int isFilter = contextPtr->callPtr->chain[contextPtr->index].isFilter; /* * If this is the first step along the chain, we preserve the method * entries in the chain so that they do not get deleted out from under our * feet. */ if (contextPtr->index == 0) { Tcl_Size i; for (i = 0 ; i < contextPtr->callPtr->numChain ; i++) { AddRef(contextPtr->callPtr->chain[i].mPtr); } /* * Ensure that the method name itself is part of the arguments when * we're doing unknown processing. */ if (contextPtr->callPtr->flags & OO_UNKNOWN_METHOD) { contextPtr->skip--; } /* * Add a callback to ensure that method references are dropped once * this call is finished. */ TclNRAddCallback(interp, FinalizeMethodRefs, contextPtr, NULL, NULL, NULL); } /* * Save whether we were in a filter and set up whether we are now. */ if (contextPtr->oPtr->flags & FILTER_HANDLING) { TclNRAddCallback(interp, SetFilterFlags, contextPtr, NULL,NULL,NULL); } else { TclNRAddCallback(interp, ResetFilterFlags,contextPtr,NULL,NULL,NULL); } if (isFilter || contextPtr->callPtr->flags & FILTER_HANDLING) { contextPtr->oPtr->flags |= FILTER_HANDLING; } else { contextPtr->oPtr->flags &= ~FILTER_HANDLING; } /* * Run the method implementation. */ if (mPtr->typePtr->version < TCL_OO_METHOD_VERSION_2) { return (mPtr->typePtr->callProc)(mPtr->clientData, interp, (Tcl_ObjectContext) contextPtr, objc, objv); } return (mPtr->type2Ptr->callProc)(mPtr->clientData, interp, (Tcl_ObjectContext) contextPtr, objc, objv); } static int SetFilterFlags( void *data[], TCL_UNUSED(Tcl_Interp *), int result) { CallContext *contextPtr = (CallContext *) data[0]; contextPtr->oPtr->flags |= FILTER_HANDLING; return result; } static int ResetFilterFlags( void *data[], TCL_UNUSED(Tcl_Interp *), int result) { CallContext *contextPtr = (CallContext *) data[0]; contextPtr->oPtr->flags &= ~FILTER_HANDLING; return result; } static int FinalizeMethodRefs( void *data[], TCL_UNUSED(Tcl_Interp *), int result) { CallContext *contextPtr = (CallContext *) data[0]; Tcl_Size i; for (i = 0 ; i < contextPtr->callPtr->numChain ; i++) { TclOODelMethodRef(contextPtr->callPtr->chain[i].mPtr); } return result; } /* * ---------------------------------------------------------------------- * * TclOOGetSortedMethodList, TclOOGetSortedClassMethodList -- * * Discovers the list of method names supported by an object or class. * * ---------------------------------------------------------------------- */ int TclOOGetSortedMethodList( Object *oPtr, /* The object to get the method names for. */ Object *contextObj, /* From what context object we are inquiring. * NULL when the context shouldn't see * object-level private methods. Note that * flags can override this. */ Class *contextCls, /* From what context class we are inquiring. * NULL when the context shouldn't see * class-level private methods. Note that * flags can override this. */ int flags, /* Whether we just want the public method * names. */ const char ***stringsPtr) /* Where to write a pointer to the array of * strings to. */ { Tcl_HashTable names; /* Tcl_Obj* method name to "wanted in list" * mapping. */ Tcl_HashTable examinedClasses; /* Used to track what classes have been looked * at. Is set-like in nature and keyed by * pointer to class. */ FOREACH_HASH_DECLS; Tcl_Size i, numStrings; Class *mixinPtr; Tcl_Obj *namePtr; Method *mPtr; Tcl_InitObjHashTable(&names); Tcl_InitHashTable(&examinedClasses, TCL_ONE_WORD_KEYS); /* * Process method names due to the object. */ if (oPtr->methodsPtr) { FOREACH_HASH(namePtr, mPtr, oPtr->methodsPtr) { if (IS_PRIVATE(mPtr)) { continue; } if (IS_UNEXPORTED(mPtr) && !WANT_UNEXPORTED(flags)) { continue; } AddStandardMethodName(flags, namePtr, mPtr, &names); } } /* * Process method names due to private methods on the object's class. */ if (WANT_UNEXPORTED(flags)) { FOREACH_HASH(namePtr, mPtr, &oPtr->selfCls->classMethods) { if (IS_UNEXPORTED(mPtr)) { AddStandardMethodName(flags, namePtr, mPtr, &names); } } } /* * Process method names due to private methods on the context's object or * class. Which must be correct if either are not NULL. */ if (contextObj && contextObj->methodsPtr) { AddPrivateMethodNames(contextObj->methodsPtr, &names); } if (contextCls) { AddPrivateMethodNames(&contextCls->classMethods, &names); } /* * Process (normal) method names from the class hierarchy and the mixin * hierarchy. */ AddClassMethodNames(oPtr->selfCls, flags, &names, &examinedClasses); FOREACH(mixinPtr, oPtr->mixins) { AddClassMethodNames(mixinPtr, flags | TRAVERSED_MIXIN, &names, &examinedClasses); } /* * Tidy up, sort the names and resolve finally whether we really want * them (processing export layering). */ Tcl_DeleteHashTable(&examinedClasses); numStrings = SortMethodNames(&names, flags, stringsPtr); Tcl_DeleteHashTable(&names); return numStrings; } size_t TclOOGetSortedClassMethodList( Class *clsPtr, /* The class to get the method names for. */ int flags, /* Whether we just want the public method * names. */ const char ***stringsPtr) /* Where to write a pointer to the array of * strings to. */ { Tcl_HashTable names; /* Tcl_Obj* method name to "wanted in list" * mapping. */ Tcl_HashTable examinedClasses; /* Used to track what classes have been looked * at. Is set-like in nature and keyed by * pointer to class. */ size_t numStrings; Tcl_InitObjHashTable(&names); Tcl_InitHashTable(&examinedClasses, TCL_ONE_WORD_KEYS); /* * Process method names from the class hierarchy and the mixin hierarchy. */ AddClassMethodNames(clsPtr, flags, &names, &examinedClasses); Tcl_DeleteHashTable(&examinedClasses); /* * Process private method names if we should. [TIP 500] */ if (WANT_PRIVATE(flags)) { AddPrivateMethodNames(&clsPtr->classMethods, &names); flags &= ~TRUE_PRIVATE_METHOD; } /* * Tidy up, sort the names and resolve finally whether we really want * them (processing export layering). */ numStrings = SortMethodNames(&names, flags, stringsPtr); Tcl_DeleteHashTable(&names); return numStrings; } /* * ---------------------------------------------------------------------- * * SortMethodNames -- * * Shared helper for TclOOGetSortedMethodList etc. that knows the method * sorting rules. * * Returns: * The length of the sorted list. * * ---------------------------------------------------------------------- */ static size_t SortMethodNames( Tcl_HashTable *namesPtr, /* The table of names; unsorted, but contains * whether the names are wanted and under what * circumstances. */ int flags, /* Whether we are looking for unexported * methods. Full private methods are handled * on insertion to the table. */ const char ***stringsPtr) /* Where to store the sorted list of strings * that we produce. Tcl_Alloced() */ { const char **strings; FOREACH_HASH_DECLS; Tcl_Obj *namePtr; void *isWanted; size_t i = 0; /* * See how many (visible) method names there are. If none, we do not (and * should not) try to sort the list of them. */ if (namesPtr->numEntries == 0) { *stringsPtr = NULL; return 0; } /* * We need to build the list of methods to sort. We will be using qsort() * for this, because it is very unlikely that the list will be heavily * sorted when it is long enough to matter. */ strings = (const char **) Tcl_Alloc(sizeof(char *) * namesPtr->numEntries); FOREACH_HASH(namePtr, isWanted, namesPtr) { if (!WANT_PUBLIC(flags) || (PTR2INT(isWanted) & IN_LIST)) { if (PTR2INT(isWanted) & NO_IMPLEMENTATION) { continue; } strings[i++] = TclGetString(namePtr); } } /* * Note that 'i' may well be less than names.numEntries when we are * dealing with public method names. We don't sort unless there's at least * two method names. */ if (i > 0) { if (i > 1) { qsort((void *) strings, i, sizeof(char *), CmpStr); } *stringsPtr = strings; } else { Tcl_Free((void *)strings); *stringsPtr = NULL; } return i; } /* * Comparator for SortMethodNames */ static int CmpStr( const void *ptr1, const void *ptr2) { const char **strPtr1 = (const char **) ptr1; const char **strPtr2 = (const char **) ptr2; return TclpUtfNcmp2(*strPtr1, *strPtr2, strlen(*strPtr1) + 1); } /* * ---------------------------------------------------------------------- * * AddClassMethodNames -- * * Adds the method names defined by a class (or its superclasses) to the * collection being built. The collection is built in a hash table to * ensure that duplicates are excluded. Helper for GetSortedMethodList(). * * ---------------------------------------------------------------------- */ static void AddClassMethodNames( Class *clsPtr, /* Class to get method names from. */ int flags, /* Whether we are interested in just the * public method names. */ Tcl_HashTable *const namesPtr, /* Reference to the hash table to put the * information in. The hash table maps the * Tcl_Obj * method name to an integral value * describing whether the method is wanted. * This ensures that public/private override * semantics are handled correctly. */ Tcl_HashTable *const examinedClassesPtr) /* Hash table that tracks what classes have * already been looked at. The keys are the * pointers to the classes, and the values are * immaterial. */ { Tcl_Size i; /* * If we've already started looking at this class, stop working on it now * to prevent repeated work. */ if (Tcl_FindHashEntry(examinedClassesPtr, clsPtr)) { return; } /* * Scope all declarations so that the compiler can stand a good chance of * making the recursive step highly efficient. We also hand-implement the * tail-recursive case using a while loop; C compilers typically cannot do * tail-recursion optimization usefully. */ while (1) { FOREACH_HASH_DECLS; Tcl_Obj *namePtr; Method *mPtr; int isNew; (void) Tcl_CreateHashEntry(examinedClassesPtr, clsPtr, &isNew); if (!isNew) { break; } if (clsPtr->mixins.num != 0) { Class *mixinPtr; FOREACH(mixinPtr, clsPtr->mixins) { if (mixinPtr != clsPtr) { AddClassMethodNames(mixinPtr, flags|TRAVERSED_MIXIN, namesPtr, examinedClassesPtr); } } } FOREACH_HASH(namePtr, mPtr, &clsPtr->classMethods) { AddStandardMethodName(flags, namePtr, mPtr, namesPtr); } if (clsPtr->superclasses.num != 1) { break; } clsPtr = clsPtr->superclasses.list[0]; } if (clsPtr->superclasses.num != 0) { Class *superPtr; FOREACH(superPtr, clsPtr->superclasses) { AddClassMethodNames(superPtr, flags, namesPtr, examinedClassesPtr); } } } /* * ---------------------------------------------------------------------- * * AddPrivateMethodNames, AddStandardMethodName -- * * Factored-out helpers for the sorted name list production functions. * * ---------------------------------------------------------------------- */ static inline void AddPrivateMethodNames( Tcl_HashTable *methodsTablePtr, Tcl_HashTable *namesPtr) { FOREACH_HASH_DECLS; Method *mPtr; Tcl_Obj *namePtr; FOREACH_HASH(namePtr, mPtr, methodsTablePtr) { if (IS_PRIVATE(mPtr)) { int isNew; hPtr = Tcl_CreateHashEntry(namesPtr, namePtr, &isNew); Tcl_SetHashValue(hPtr, INT2PTR(IN_LIST)); } } } static inline void AddStandardMethodName( int flags, Tcl_Obj *namePtr, Method *mPtr, Tcl_HashTable *namesPtr) { if (!IS_PRIVATE(mPtr)) { int isNew; Tcl_HashEntry *hPtr = Tcl_CreateHashEntry(namesPtr, namePtr, &isNew); if (isNew) { int isWanted = (!WANT_PUBLIC(flags) || IS_PUBLIC(mPtr)) ? IN_LIST : 0; isWanted |= (mPtr->typePtr == NULL ? NO_IMPLEMENTATION : 0); Tcl_SetHashValue(hPtr, INT2PTR(isWanted)); } else if ((PTR2INT(Tcl_GetHashValue(hPtr)) & NO_IMPLEMENTATION) && mPtr->typePtr != NULL) { int isWanted = PTR2INT(Tcl_GetHashValue(hPtr)); isWanted &= ~NO_IMPLEMENTATION; Tcl_SetHashValue(hPtr, INT2PTR(isWanted)); } } } /* * ---------------------------------------------------------------------- * * AddInstancePrivateToCallContext -- * * Add private methods from the instance. Called when the calling Tcl * context is a TclOO method declared by an object that is the same as * the current object. Returns true iff a private method was actually * found and added to the call chain (as this suppresses caching). * * ---------------------------------------------------------------------- */ static inline int AddInstancePrivateToCallContext( Object *const oPtr, /* Object to add call chain entries for. */ Tcl_Obj *const methodName, /* Name of method to add the call chain * entries for. */ ChainBuilder *const cbPtr, /* Where to add the call chain entries. */ int flags) /* What sort of call chain are we building. */ { Tcl_HashEntry *hPtr; Method *mPtr; int donePrivate = 0; if (oPtr->methodsPtr) { hPtr = Tcl_FindHashEntry(oPtr->methodsPtr, methodName); if (hPtr != NULL) { mPtr = (Method *) Tcl_GetHashValue(hPtr); if (IS_PRIVATE(mPtr)) { AddMethodToCallChain(mPtr, cbPtr, NULL, NULL, flags); donePrivate = 1; } } } return donePrivate; } /* * ---------------------------------------------------------------------- * * AddSimpleChainToCallContext -- * * The core of the call-chain construction engine, this handles calling a * particular method on a particular object. Note that filters and * unknown handling are already handled by the logic that uses this * function. Returns true if a private method was one of those found. * * ---------------------------------------------------------------------- */ static inline int AddSimpleChainToCallContext( Object *const oPtr, /* Object to add call chain entries for. */ Class *const contextCls, /* Context class; the currently considered * class is equal to this, private methods may * also be added. [TIP 500] */ Tcl_Obj *const methodNameObj, /* Name of method to add the call chain * entries for. */ ChainBuilder *const cbPtr, /* Where to add the call chain entries. */ Tcl_HashTable *const doneFilters, /* Where to record what call chain entries * have been processed. */ int flags, /* What sort of call chain are we building. */ Class *const filterDecl) /* The class that declared the filter. If * NULL, either the filter was declared by the * object or this isn't a filter. */ { Tcl_Size i; int foundPrivate = 0, blockedUnexported = 0; Tcl_HashEntry *hPtr; Method *mPtr; if (!(flags & (KNOWN_STATE | SPECIAL)) && oPtr->methodsPtr) { hPtr = Tcl_FindHashEntry(oPtr->methodsPtr, methodNameObj); if (hPtr != NULL) { mPtr = (Method *) Tcl_GetHashValue(hPtr); if (!IS_PRIVATE(mPtr)) { if (WANT_PUBLIC(flags)) { if (!IS_PUBLIC(mPtr)) { blockedUnexported = 1; } else { flags |= DEFINITE_PUBLIC; } } else { flags |= DEFINITE_PROTECTED; } } } } if (!(flags & SPECIAL)) { Class *mixinPtr; FOREACH(mixinPtr, oPtr->mixins) { if (contextCls) { foundPrivate |= AddPrivatesFromClassChainToCallContext( mixinPtr, contextCls, methodNameObj, cbPtr, doneFilters, flags|TRAVERSED_MIXIN, filterDecl); } foundPrivate |= AddSimpleClassChainToCallContext(mixinPtr, methodNameObj, cbPtr, doneFilters, flags | TRAVERSED_MIXIN, filterDecl); } if (oPtr->methodsPtr && !blockedUnexported) { hPtr = Tcl_FindHashEntry(oPtr->methodsPtr, methodNameObj); if (hPtr != NULL) { mPtr = (Method *) Tcl_GetHashValue(hPtr); if (!IS_PRIVATE(mPtr)) { AddMethodToCallChain(mPtr, cbPtr, doneFilters, filterDecl, flags); } } } } if (!oPtr->selfCls) { return foundPrivate; } if (contextCls) { foundPrivate |= AddPrivatesFromClassChainToCallContext(oPtr->selfCls, contextCls, methodNameObj, cbPtr, doneFilters, flags, filterDecl); } if (!blockedUnexported) { foundPrivate |= AddSimpleClassChainToCallContext(oPtr->selfCls, methodNameObj, cbPtr, doneFilters, flags, filterDecl); } return foundPrivate; } /* * ---------------------------------------------------------------------- * * AddMethodToCallChain -- * * Utility method that manages the adding of a particular method * implementation to a call-chain. * * ---------------------------------------------------------------------- */ static inline void AddMethodToCallChain( Method *const mPtr, /* Actual method implementation to add to call * chain (or NULL, a no-op). */ ChainBuilder *const cbPtr, /* The call chain to add the method * implementation to. */ Tcl_HashTable *const doneFilters, /* Where to record what filters have been * processed. If NULL, not processing filters. * Note that this function does not update * this hashtable. */ Class *const filterDecl, /* The class that declared the filter. If * NULL, either the filter was declared by the * object or this isn't a filter. */ int flags) /* Used to check if we're mixin-consistent * only. Mixin-consistent means that either * we're looking to add things from a mixin * and we have passed a mixin, or we're not * looking to add things from a mixin and have * not passed a mixin. */ { CallChain *callPtr = cbPtr->callChainPtr; Tcl_Size i; /* * Return if this is just an entry used to record whether this is a public * method. If so, there's nothing real to call and so nothing to add to * the call chain. * * This is also where we enforce mixin-consistency. */ if (mPtr == NULL || mPtr->typePtr == NULL || !MIXIN_CONSISTENT(flags)) { return; } /* * Enforce real private method handling here. We will skip adding this * method IF * 1) we are not allowing private methods, AND * 2) this is a private method, AND * 3) this is a class method, AND * 4) this method was not declared by the class of the current object. * * This does mean that only classes really handle private methods. This * should be sufficient for [incr Tcl] support though. */ if (!WANT_UNEXPORTED(callPtr->flags) && IS_UNEXPORTED(mPtr) && (mPtr->declaringClassPtr != NULL) && (mPtr->declaringClassPtr != cbPtr->oPtr->selfCls)) { return; } /* * First test whether the method is already in the call chain. Skip over * any leading filters. */ for (i = cbPtr->filterLength ; i < callPtr->numChain ; i++) { if (callPtr->chain[i].mPtr == mPtr && callPtr->chain[i].isFilter == (doneFilters != NULL)) { /* * Call chain semantics states that methods come as *late* in the * call chain as possible. This is done by copying down the * following methods. Note that this does not change the number of * method invocations in the call chain; it just rearranges them. */ Class *declCls = callPtr->chain[i].filterDeclarer; for (; i + 1 < callPtr->numChain ; i++) { callPtr->chain[i] = callPtr->chain[i + 1]; } callPtr->chain[i].mPtr = mPtr; callPtr->chain[i].isFilter = (doneFilters != NULL); callPtr->chain[i].filterDeclarer = declCls; return; } } /* * Need to really add the method. This is made a bit more complex by the * fact that we are using some "static" space initially, and only start * realloc-ing if the chain gets long. */ if (callPtr->numChain == CALL_CHAIN_STATIC_SIZE) { callPtr->chain = (MInvoke *) Tcl_Alloc(sizeof(MInvoke) * (callPtr->numChain + 1)); memcpy(callPtr->chain, callPtr->staticChain, sizeof(MInvoke) * callPtr->numChain); } else if (callPtr->numChain > CALL_CHAIN_STATIC_SIZE) { callPtr->chain = (MInvoke *) Tcl_Realloc(callPtr->chain, sizeof(MInvoke) * (callPtr->numChain + 1)); } callPtr->chain[i].mPtr = mPtr; callPtr->chain[i].isFilter = (doneFilters != NULL); callPtr->chain[i].filterDeclarer = filterDecl; callPtr->numChain++; } /* * ---------------------------------------------------------------------- * * InitCallChain -- * Encoding of the policy of how to set up a call chain. Doesn't populate * the chain with the method implementation data. * * ---------------------------------------------------------------------- */ static inline void InitCallChain( CallChain *callPtr, Object *oPtr, int flags) { /* * Note that it's possible to end up with a NULL oPtr->selfCls here if * there is a call into stereotypical object after it has finished running * its destructor phase. Such things can't be cached for a long time so the * epoch can be bogus. [Bug 7842f33a5c] */ callPtr->flags = flags & (PUBLIC_METHOD | PRIVATE_METHOD | SPECIAL | FILTER_HANDLING); if (oPtr->flags & USE_CLASS_CACHE) { oPtr = (oPtr->selfCls ? oPtr->selfCls->thisPtr : NULL); callPtr->flags |= USE_CLASS_CACHE; } if (oPtr) { callPtr->epoch = oPtr->fPtr->epoch; callPtr->objectCreationEpoch = oPtr->creationEpoch; callPtr->objectEpoch = oPtr->epoch; } else { callPtr->epoch = 0; callPtr->objectCreationEpoch = 0; callPtr->objectEpoch = 0; } callPtr->refCount = 1; callPtr->numChain = 0; callPtr->chain = callPtr->staticChain; } /* * ---------------------------------------------------------------------- * * IsStillValid -- * * Calculates whether the given call chain can be used for executing a * method for the given object. The condition on a chain from a cached * location being reusable is: * - Refers to the same object (same creation epoch), and * - Still across the same class structure (same global epoch), and * - Still across the same object structure (same local epoch), and * - No public/private/filter magic leakage (same flags, modulo the fact * that a public chain will satisfy a non-public call). * * ---------------------------------------------------------------------- */ static inline int IsStillValid( CallChain *callPtr, Object *oPtr, int flags, int mask) { if ((oPtr->flags & USE_CLASS_CACHE)) { /* * If the object is in a weird state (due to stereotype tricks) then * just declare the cache invalid. [Bug 7842f33a5c] */ if (!oPtr->selfCls) { return 0; } oPtr = oPtr->selfCls->thisPtr; flags |= USE_CLASS_CACHE; } return ((callPtr->objectCreationEpoch == oPtr->creationEpoch) && (callPtr->epoch == oPtr->fPtr->epoch) && (callPtr->objectEpoch == oPtr->epoch) && ((callPtr->flags & mask) == (flags & mask))); } /* * ---------------------------------------------------------------------- * * TclOOGetCallContext -- * * Responsible for constructing the call context, an ordered list of all * method implementations to be called as part of a method invocation. * This method is central to the whole operation of the OO system. * * ---------------------------------------------------------------------- */ CallContext * TclOOGetCallContext( Object *oPtr, /* The object to get the context for. */ Tcl_Obj *methodNameObj, /* The name of the method to get the context * for. NULL when getting a constructor or * destructor chain. */ int flags, /* What sort of context are we looking for. * Only the bits PUBLIC_METHOD, CONSTRUCTOR, * PRIVATE_METHOD, DESTRUCTOR and * FILTER_HANDLING are useful. */ Object *contextObj, /* Context object; when equal to oPtr, it * means that private methods may also be * added. [TIP 500] */ Class *contextCls, /* Context class; the currently considered * class is equal to this, private methods may * also be added. [TIP 500] */ Tcl_Obj *cacheInThisObj) /* What object to cache in, or NULL if it is * to be in the same object as the * methodNameObj. */ { CallContext *contextPtr; CallChain *callPtr; ChainBuilder cb; Tcl_Size i, count; int doFilters, donePrivate = 0; Tcl_HashEntry *hPtr; Tcl_HashTable doneFilters; if (cacheInThisObj == NULL) { cacheInThisObj = methodNameObj; } if (flags&(SPECIAL|FILTER_HANDLING) || (oPtr->flags&FILTER_HANDLING)) { hPtr = NULL; doFilters = 0; /* * Check if we have a cached valid constructor or destructor. */ if (flags & CONSTRUCTOR) { callPtr = oPtr->selfCls->constructorChainPtr; if ((callPtr != NULL) && (callPtr->objectEpoch == oPtr->selfCls->thisPtr->epoch) && (callPtr->epoch == oPtr->fPtr->epoch)) { callPtr->refCount++; goto returnContext; } } else if (flags & DESTRUCTOR) { callPtr = oPtr->selfCls->destructorChainPtr; if ((oPtr->mixins.num == 0) && (callPtr != NULL) && (callPtr->objectEpoch == oPtr->selfCls->thisPtr->epoch) && (callPtr->epoch == oPtr->fPtr->epoch)) { callPtr->refCount++; goto returnContext; } } } else { /* * Check if we can get the chain out of the Tcl_Obj method name or out * of the cache. This is made a bit more complex by the fact that * there are multiple different layers of cache (in the Tcl_Obj, in * the object, and in the class). */ const Tcl_ObjInternalRep *irPtr; const int reuseMask = (WANT_PUBLIC(flags) ? ~0 : ~PUBLIC_METHOD); if ((irPtr = TclFetchInternalRep(cacheInThisObj, &methodNameType))) { callPtr = (CallChain *) irPtr->twoPtrValue.ptr1; if (IsStillValid(callPtr, oPtr, flags, reuseMask)) { callPtr->refCount++; goto returnContext; } Tcl_StoreInternalRep(cacheInThisObj, &methodNameType, NULL); } /* * Note that it's possible to end up with a NULL oPtr->selfCls here if * there is a call into stereotypical object after it has finished * running its destructor phase. It's quite a tangle, but at that * point, we simply can't get stereotypes from the cache. * [Bug 7842f33a5c] */ if (oPtr->flags & USE_CLASS_CACHE && oPtr->selfCls) { if (oPtr->selfCls->classChainCache) { hPtr = Tcl_FindHashEntry(oPtr->selfCls->classChainCache, methodNameObj); } else { hPtr = NULL; } } else { if (oPtr->chainCache != NULL) { hPtr = Tcl_FindHashEntry(oPtr->chainCache, methodNameObj); } else { hPtr = NULL; } } if (hPtr != NULL && Tcl_GetHashValue(hPtr) != NULL) { callPtr = (CallChain *) Tcl_GetHashValue(hPtr); if (IsStillValid(callPtr, oPtr, flags, reuseMask)) { callPtr->refCount++; goto returnContext; } Tcl_SetHashValue(hPtr, NULL); TclOODeleteChain(callPtr); } doFilters = 1; } callPtr = (CallChain *) Tcl_Alloc(sizeof(CallChain)); InitCallChain(callPtr, oPtr, flags); cb.callChainPtr = callPtr; cb.filterLength = 0; cb.oPtr = oPtr; /* * If we're working with a forced use of unknown, do that now. */ if (flags & FORCE_UNKNOWN) { AddSimpleChainToCallContext(oPtr, NULL, oPtr->fPtr->unknownMethodNameObj, &cb, NULL, BUILDING_MIXINS, NULL); AddSimpleChainToCallContext(oPtr, NULL, oPtr->fPtr->unknownMethodNameObj, &cb, NULL, 0, NULL); callPtr->flags |= OO_UNKNOWN_METHOD; callPtr->epoch = 0; if (callPtr->numChain == 0) { TclOODeleteChain(callPtr); return NULL; } goto returnContext; } /* * Add all defined filters (if any, and if we're going to be processing * them; they're not processed for constructors, destructors or when we're * in the middle of processing a filter). */ if (doFilters) { Tcl_Obj *filterObj; Class *mixinPtr; doFilters = 1; Tcl_InitObjHashTable(&doneFilters); FOREACH(mixinPtr, oPtr->mixins) { AddClassFiltersToCallContext(oPtr, mixinPtr, &cb, &doneFilters, TRAVERSED_MIXIN|BUILDING_MIXINS|OBJECT_MIXIN); AddClassFiltersToCallContext(oPtr, mixinPtr, &cb, &doneFilters, OBJECT_MIXIN); } FOREACH(filterObj, oPtr->filters) { donePrivate |= AddSimpleChainToCallContext(oPtr, contextCls, filterObj, &cb, &doneFilters, BUILDING_MIXINS, NULL); donePrivate |= AddSimpleChainToCallContext(oPtr, contextCls, filterObj, &cb, &doneFilters, 0, NULL); } AddClassFiltersToCallContext(oPtr, oPtr->selfCls, &cb, &doneFilters, BUILDING_MIXINS); AddClassFiltersToCallContext(oPtr, oPtr->selfCls, &cb, &doneFilters, 0); Tcl_DeleteHashTable(&doneFilters); } count = cb.filterLength = callPtr->numChain; /* * Add the actual method implementations. We have to do this twice to * handle class mixins right. */ if (oPtr == contextObj) { donePrivate |= AddInstancePrivateToCallContext(oPtr, methodNameObj, &cb, flags); donePrivate |= (contextObj->flags & HAS_PRIVATE_METHODS); } donePrivate |= AddSimpleChainToCallContext(oPtr, contextCls, methodNameObj, &cb, NULL, flags|BUILDING_MIXINS, NULL); donePrivate |= AddSimpleChainToCallContext(oPtr, contextCls, methodNameObj, &cb, NULL, flags, NULL); /* * Check to see if the method has no implementation. If so, we probably * need to add in a call to the unknown method. Otherwise, set up the * cacheing of the method implementation (if relevant). */ if (count == callPtr->numChain) { /* * Method does not actually exist. If we're dealing with constructors * or destructors, this isn't a problem. */ if (flags & SPECIAL) { TclOODeleteChain(callPtr); return NULL; } AddSimpleChainToCallContext(oPtr, NULL, oPtr->fPtr->unknownMethodNameObj, &cb, NULL, BUILDING_MIXINS, NULL); AddSimpleChainToCallContext(oPtr, NULL, oPtr->fPtr->unknownMethodNameObj, &cb, NULL, 0, NULL); callPtr->flags |= OO_UNKNOWN_METHOD; callPtr->epoch = 0; if (count == callPtr->numChain) { TclOODeleteChain(callPtr); return NULL; } } else if (doFilters && !donePrivate) { if (hPtr == NULL) { int isNew; if (oPtr->flags & USE_CLASS_CACHE) { if (oPtr->selfCls->classChainCache == NULL) { oPtr->selfCls->classChainCache = (Tcl_HashTable *) Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitObjHashTable(oPtr->selfCls->classChainCache); } hPtr = Tcl_CreateHashEntry(oPtr->selfCls->classChainCache, methodNameObj, &isNew); } else { if (oPtr->chainCache == NULL) { oPtr->chainCache = (Tcl_HashTable *) Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitObjHashTable(oPtr->chainCache); } hPtr = Tcl_CreateHashEntry(oPtr->chainCache, methodNameObj, &isNew); } } callPtr->refCount++; Tcl_SetHashValue(hPtr, callPtr); StashCallChain(cacheInThisObj, callPtr); } else if (flags & CONSTRUCTOR) { if (oPtr->selfCls->constructorChainPtr) { TclOODeleteChain(oPtr->selfCls->constructorChainPtr); } oPtr->selfCls->constructorChainPtr = callPtr; callPtr->refCount++; } else if ((flags & DESTRUCTOR) && oPtr->mixins.num == 0) { if (oPtr->selfCls->destructorChainPtr) { TclOODeleteChain(oPtr->selfCls->destructorChainPtr); } oPtr->selfCls->destructorChainPtr = callPtr; callPtr->refCount++; } returnContext: contextPtr = (CallContext *) TclStackAlloc(oPtr->fPtr->interp, sizeof(CallContext)); contextPtr->oPtr = oPtr; /* * Corresponding TclOODecrRefCount() in TclOODeleteContext */ AddRef(oPtr); contextPtr->callPtr = callPtr; contextPtr->skip = 2; contextPtr->index = 0; return contextPtr; } /* * ---------------------------------------------------------------------- * * TclOOGetStereotypeCallChain -- * * Construct a call-chain for a method that would be used by a * stereotypical instance of the given class (i.e., where the object has * no definitions special to itself). * * ---------------------------------------------------------------------- */ CallChain * TclOOGetStereotypeCallChain( Class *clsPtr, /* The object to get the context for. */ Tcl_Obj *methodNameObj, /* The name of the method to get the context * for. NULL when getting a constructor or * destructor chain. */ int flags) /* What sort of context are we looking for. * Only the bits PUBLIC_METHOD, CONSTRUCTOR, * PRIVATE_METHOD, DESTRUCTOR and * FILTER_HANDLING are useful. */ { CallChain *callPtr; ChainBuilder cb; Tcl_Size count; Foundation *fPtr = clsPtr->thisPtr->fPtr; Tcl_HashEntry *hPtr; Tcl_HashTable doneFilters; Object obj; /* * Note that it's possible to end up with a NULL clsPtr here if there is * a call into stereotypical object after it has finished running its * destructor phase. It's quite a tangle, but at that point, we simply * can't get stereotypes. [Bug 7842f33a5c] */ if (clsPtr == NULL) { return NULL; } /* * Synthesize a temporary stereotypical object so that we can use existing * machinery to produce the stereotypical call chain. */ memset(&obj, 0, sizeof(Object)); obj.fPtr = fPtr; obj.selfCls = clsPtr; obj.refCount = 1; obj.flags = USE_CLASS_CACHE; /* * Check if we can get the chain out of the Tcl_Obj method name or out of * the cache. This is made a bit more complex by the fact that there are * multiple different layers of cache (in the Tcl_Obj, in the object, and * in the class). */ if (clsPtr->classChainCache != NULL) { hPtr = Tcl_FindHashEntry(clsPtr->classChainCache, methodNameObj); if (hPtr != NULL && Tcl_GetHashValue(hPtr) != NULL) { const int reuseMask = (WANT_PUBLIC(flags) ? ~0 : ~PUBLIC_METHOD); callPtr = (CallChain *) Tcl_GetHashValue(hPtr); if (IsStillValid(callPtr, &obj, flags, reuseMask)) { callPtr->refCount++; return callPtr; } Tcl_SetHashValue(hPtr, NULL); TclOODeleteChain(callPtr); } } else { hPtr = NULL; } callPtr = (CallChain *) Tcl_Alloc(sizeof(CallChain)); memset(callPtr, 0, sizeof(CallChain)); callPtr->flags = flags & (PUBLIC_METHOD|PRIVATE_METHOD|FILTER_HANDLING); callPtr->epoch = fPtr->epoch; callPtr->objectCreationEpoch = fPtr->tsdPtr->nsCount; callPtr->objectEpoch = clsPtr->thisPtr->epoch; callPtr->refCount = 1; callPtr->chain = callPtr->staticChain; cb.callChainPtr = callPtr; cb.filterLength = 0; cb.oPtr = &obj; /* * Add all defined filters (if any, and if we're going to be processing * them; they're not processed for constructors, destructors or when we're * in the middle of processing a filter). */ Tcl_InitObjHashTable(&doneFilters); AddClassFiltersToCallContext(&obj, clsPtr, &cb, &doneFilters, BUILDING_MIXINS); AddClassFiltersToCallContext(&obj, clsPtr, &cb, &doneFilters, 0); Tcl_DeleteHashTable(&doneFilters); count = cb.filterLength = callPtr->numChain; /* * Add the actual method implementations. */ AddSimpleChainToCallContext(&obj, NULL, methodNameObj, &cb, NULL, flags|BUILDING_MIXINS, NULL); AddSimpleChainToCallContext(&obj, NULL, methodNameObj, &cb, NULL, flags, NULL); /* * Check to see if the method has no implementation. If so, we probably * need to add in a call to the unknown method. Otherwise, set up the * caching of the method implementation (if relevant). */ if (count == callPtr->numChain) { AddSimpleChainToCallContext(&obj, NULL, fPtr->unknownMethodNameObj, &cb, NULL, BUILDING_MIXINS, NULL); AddSimpleChainToCallContext(&obj, NULL, fPtr->unknownMethodNameObj, &cb, NULL, 0, NULL); callPtr->flags |= OO_UNKNOWN_METHOD; callPtr->epoch = 0; if (count == callPtr->numChain) { TclOODeleteChain(callPtr); return NULL; } } else { if (hPtr == NULL) { int isNew; if (clsPtr->classChainCache == NULL) { clsPtr->classChainCache = (Tcl_HashTable *) Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitObjHashTable(clsPtr->classChainCache); } hPtr = Tcl_CreateHashEntry(clsPtr->classChainCache, methodNameObj, &isNew); } callPtr->refCount++; Tcl_SetHashValue(hPtr, callPtr); StashCallChain(methodNameObj, callPtr); } return callPtr; } /* * ---------------------------------------------------------------------- * * AddClassFiltersToCallContext -- * * Logic to make extracting all the filters from the class context much * easier. * * ---------------------------------------------------------------------- */ static void AddClassFiltersToCallContext( Object *const oPtr, /* Object that the filters operate on. */ Class *clsPtr, /* Class to get the filters from. */ ChainBuilder *const cbPtr, /* Context to fill with call chain entries. */ Tcl_HashTable *const doneFilters, /* Where to record what filters have been * processed. Keys are objects, values are * ignored. */ int flags) /* Whether we've gone along a mixin link * yet. */ { Tcl_Size i; int clearedFlags = flags & ~(TRAVERSED_MIXIN|OBJECT_MIXIN|BUILDING_MIXINS); Class *superPtr, *mixinPtr; Tcl_Obj *filterObj; tailRecurse: if (clsPtr == NULL) { return; } /* * Add all the filters defined by classes mixed into the main class * hierarchy. */ FOREACH(mixinPtr, clsPtr->mixins) { AddClassFiltersToCallContext(oPtr, mixinPtr, cbPtr, doneFilters, flags|TRAVERSED_MIXIN); } /* * Add all the class filters from the current class. Note that the filters * are added starting at the object root, as this allows the object to * override how filters work to extend their behaviour. */ if (MIXIN_CONSISTENT(flags)) { FOREACH(filterObj, clsPtr->filters) { int isNew; (void) Tcl_CreateHashEntry(doneFilters, filterObj, &isNew); if (isNew) { AddSimpleChainToCallContext(oPtr, NULL, filterObj, cbPtr, doneFilters, clearedFlags|BUILDING_MIXINS, clsPtr); AddSimpleChainToCallContext(oPtr, NULL, filterObj, cbPtr, doneFilters, clearedFlags, clsPtr); } } } /* * Now process the recursive case. Notice the tail-call optimization. */ switch (clsPtr->superclasses.num) { case 1: clsPtr = clsPtr->superclasses.list[0]; goto tailRecurse; default: FOREACH(superPtr, clsPtr->superclasses) { AddClassFiltersToCallContext(oPtr, superPtr, cbPtr, doneFilters, flags); } case 0: return; } } /* * ---------------------------------------------------------------------- * * AddPrivatesFromClassChainToCallContext -- * * Helper for AddSimpleChainToCallContext that is used to find private * methds and add them to the call chain. Returns true when a private * method is found and added. [TIP 500] * * ---------------------------------------------------------------------- */ static int AddPrivatesFromClassChainToCallContext( Class *classPtr, /* Class to add the call chain entries for. */ Class *const contextCls, /* Context class; the currently considered * class is equal to this, private methods may * also be added. */ Tcl_Obj *const methodName, /* Name of method to add the call chain * entries for. */ ChainBuilder *const cbPtr, /* Where to add the call chain entries. */ Tcl_HashTable *const doneFilters, /* Where to record what call chain entries * have been processed. */ int flags, /* What sort of call chain are we building. */ Class *const filterDecl) /* The class that declared the filter. If * NULL, either the filter was declared by the * object or this isn't a filter. */ { Tcl_Size i; Class *superPtr; /* * We hard-code the tail-recursive form. It's by far the most common case * *and* it is much more gentle on the stack. * * Note that mixins must be processed before the main class hierarchy. * [Bug 1998221] * * Note also that it's possible to end up with a null classPtr here if * there is a call into stereotypical object after it has finished running * its destructor phase. [Bug 7842f33a5c] */ tailRecurse: if (classPtr == NULL) { return 0; } FOREACH(superPtr, classPtr->mixins) { if (AddPrivatesFromClassChainToCallContext(superPtr, contextCls, methodName, cbPtr, doneFilters, flags|TRAVERSED_MIXIN, filterDecl)) { return 1; } } if (classPtr == contextCls) { Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&classPtr->classMethods, methodName); if (hPtr != NULL) { Method *mPtr = (Method *) Tcl_GetHashValue(hPtr); if (IS_PRIVATE(mPtr)) { AddMethodToCallChain(mPtr, cbPtr, doneFilters, filterDecl, flags); return 1; } } } switch (classPtr->superclasses.num) { case 1: classPtr = classPtr->superclasses.list[0]; goto tailRecurse; default: FOREACH(superPtr, classPtr->superclasses) { if (AddPrivatesFromClassChainToCallContext(superPtr, contextCls, methodName, cbPtr, doneFilters, flags, filterDecl)) { return 1; } } /* FALLTHRU */ case 0: return 0; } } /* * ---------------------------------------------------------------------- * * AddSimpleClassChainToCallContext -- * * Construct a call-chain from a class hierarchy. * * ---------------------------------------------------------------------- */ static int AddSimpleClassChainToCallContext( Class *classPtr, /* Class to add the call chain entries for. */ Tcl_Obj *const methodNameObj, /* Name of method to add the call chain * entries for. */ ChainBuilder *const cbPtr, /* Where to add the call chain entries. */ Tcl_HashTable *const doneFilters, /* Where to record what call chain entries * have been processed. */ int flags, /* What sort of call chain are we building. */ Class *const filterDecl) /* The class that declared the filter. If * NULL, either the filter was declared by the * object or this isn't a filter. */ { Tcl_Size i; int privateDanger = 0; Class *superPtr; /* * We hard-code the tail-recursive form. It's by far the most common case * *and* it is much more gentle on the stack. * * Note that mixins must be processed before the main class hierarchy. * [Bug 1998221] */ tailRecurse: if (classPtr == NULL) { return privateDanger; } FOREACH(superPtr, classPtr->mixins) { privateDanger |= AddSimpleClassChainToCallContext(superPtr, methodNameObj, cbPtr, doneFilters, flags | TRAVERSED_MIXIN, filterDecl); } if (flags & CONSTRUCTOR) { AddMethodToCallChain(classPtr->constructorPtr, cbPtr, doneFilters, filterDecl, flags); } else if (flags & DESTRUCTOR) { AddMethodToCallChain(classPtr->destructorPtr, cbPtr, doneFilters, filterDecl, flags); } else { Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&classPtr->classMethods, methodNameObj); if (classPtr->flags & HAS_PRIVATE_METHODS) { privateDanger |= 1; } if (hPtr != NULL) { Method *mPtr = (Method *) Tcl_GetHashValue(hPtr); if (!IS_PRIVATE(mPtr)) { if (!(flags & KNOWN_STATE)) { if (flags & PUBLIC_METHOD) { if (!IS_PUBLIC(mPtr)) { return privateDanger; } flags |= DEFINITE_PUBLIC; } else { flags |= DEFINITE_PROTECTED; } } AddMethodToCallChain(mPtr, cbPtr, doneFilters, filterDecl, flags); } } } switch (classPtr->superclasses.num) { case 1: classPtr = classPtr->superclasses.list[0]; goto tailRecurse; default: FOREACH(superPtr, classPtr->superclasses) { privateDanger |= AddSimpleClassChainToCallContext(superPtr, methodNameObj, cbPtr, doneFilters, flags, filterDecl); } /* FALLTHRU */ case 0: return privateDanger; } } /* * ---------------------------------------------------------------------- * * TclOORenderCallChain -- * * Create a description of a call chain. Used in [info object call], * [info class call], and [self call]. * * ---------------------------------------------------------------------- */ Tcl_Obj * TclOORenderCallChain( Tcl_Interp *interp, CallChain *callPtr) { Tcl_Obj *filterLiteral, *methodLiteral, *objectLiteral, *privateLiteral; Tcl_Obj *resultObj, *descObjs[4], **objv; Foundation *fPtr = TclOOGetFoundation(interp); Tcl_Size i; /* * Allocate the literals (potentially) used in our description. */ TclNewLiteralStringObj(filterLiteral, "filter"); TclNewLiteralStringObj(methodLiteral, "method"); TclNewLiteralStringObj(objectLiteral, "object"); TclNewLiteralStringObj(privateLiteral, "private"); /* * Do the actual construction of the descriptions. They consist of a list * of triples that describe the details of how a method is understood. For * each triple, the first word is the type of invocation ("method" is * normal, "unknown" is special because it adds the method name as an * extra argument when handled by some method types, and "filter" is * special because it's a filter method). The second word is the name of * the method in question (which differs for "unknown" and "filter" types) * and the third word is the full name of the class that declares the * method (or "object" if it is declared on the instance). */ objv = (Tcl_Obj **) TclStackAlloc(interp, callPtr->numChain * sizeof(Tcl_Obj *)); for (i = 0 ; i < callPtr->numChain ; i++) { MInvoke *miPtr = &callPtr->chain[i]; descObjs[0] = miPtr->isFilter ? filterLiteral : callPtr->flags & OO_UNKNOWN_METHOD ? fPtr->unknownMethodNameObj : IS_PRIVATE(miPtr->mPtr) ? privateLiteral : methodLiteral; descObjs[1] = callPtr->flags & CONSTRUCTOR ? fPtr->constructorName : callPtr->flags & DESTRUCTOR ? fPtr->destructorName : miPtr->mPtr->namePtr; descObjs[2] = miPtr->mPtr->declaringClassPtr ? Tcl_GetObjectName(interp, (Tcl_Object) miPtr->mPtr->declaringClassPtr->thisPtr) : objectLiteral; descObjs[3] = Tcl_NewStringObj(miPtr->mPtr->typePtr->name, TCL_AUTO_LENGTH); objv[i] = Tcl_NewListObj(4, descObjs); } /* * Drop the local references to the literals; if they're actually used, * they'll live on the description itself. */ Tcl_BounceRefCount(filterLiteral); Tcl_BounceRefCount(methodLiteral); Tcl_BounceRefCount(objectLiteral); Tcl_BounceRefCount(privateLiteral); /* * Finish building the description and return it. */ resultObj = Tcl_NewListObj(callPtr->numChain, objv); TclStackFree(interp, objv); return resultObj; } /* * ---------------------------------------------------------------------- * * TclOOGetDefineContextNamespace -- * * Responsible for determining which namespace to use for definitions. * This is done by building a define chain, which models (strongly!) the * way that a call chain works but with a different internal model. * * Then it walks the chain to find the first namespace name that actually * resolves to an existing namespace. * * Returns: * Name of namespace, or NULL if none can be found. Note that this * function does *not* set an error message in the interpreter on failure. * * ---------------------------------------------------------------------- */ #define DEFINE_CHAIN_STATIC_SIZE 4 /* Enough space to store most cases. */ Tcl_Namespace * TclOOGetDefineContextNamespace( Tcl_Interp *interp, /* In what interpreter should namespace names * actually be resolved. */ Object *oPtr, /* The object to get the context for. */ int forClass) /* What sort of context are we looking for. * If true, we are going to use this for * [oo::define], otherwise, we are going to * use this for [oo::objdefine]. */ { DefineChain define; DefineEntry staticSpace[DEFINE_CHAIN_STATIC_SIZE]; DefineEntry *entryPtr; Tcl_Namespace *nsPtr = NULL; int i, flags = (forClass ? DEFINE_FOR_CLASS : 0); define.list = staticSpace; define.num = 0; define.size = DEFINE_CHAIN_STATIC_SIZE; /* * Add the actual define locations. We have to do this twice to handle * class mixins right. */ AddSimpleDefineNamespaces(oPtr, &define, flags | BUILDING_MIXINS); AddSimpleDefineNamespaces(oPtr, &define, flags); /* * Go through the list until we find a namespace whose name we can * resolve. */ FOREACH_STRUCT(entryPtr, define) { if (TclGetNamespaceFromObj(interp, entryPtr->namespaceName, &nsPtr) == TCL_OK) { break; } Tcl_ResetResult(interp); } if (define.list != staticSpace) { Tcl_Free(define.list); } return nsPtr; } /* * ---------------------------------------------------------------------- * * AddSimpleDefineNamespaces -- * * Adds to the definition chain all the definitions provided by an * object's class and its mixins, taking into account everything they * inherit from. * * ---------------------------------------------------------------------- */ static inline void AddSimpleDefineNamespaces( Object *const oPtr, /* Object to add define chain entries for. */ DefineChain *const definePtr, /* Where to add the define chain entries. */ int flags) /* What sort of define chain are we * building. */ { Class *mixinPtr; Tcl_Size i; FOREACH(mixinPtr, oPtr->mixins) { AddSimpleClassDefineNamespaces(mixinPtr, definePtr, flags | TRAVERSED_MIXIN); } AddSimpleClassDefineNamespaces(oPtr->selfCls, definePtr, flags); } /* * ---------------------------------------------------------------------- * * AddSimpleClassDefineNamespaces -- * * Adds to the definition chain all the definitions provided by a class * and its superclasses and its class mixins. * * ---------------------------------------------------------------------- */ static void AddSimpleClassDefineNamespaces( Class *classPtr, /* Class to add the define chain entries for. */ DefineChain *const definePtr, /* Where to add the define chain entries. */ int flags) /* What sort of define chain are we * building. */ { Tcl_Size i; Class *superPtr; /* * We hard-code the tail-recursive form. It's by far the most common case * *and* it is much more gentle on the stack. */ tailRecurse: FOREACH(superPtr, classPtr->mixins) { AddSimpleClassDefineNamespaces(superPtr, definePtr, flags | TRAVERSED_MIXIN); } if (flags & DEFINE_FOR_CLASS) { AddDefinitionNamespaceToChain(classPtr, classPtr->clsDefinitionNs, definePtr, flags); } else { AddDefinitionNamespaceToChain(classPtr, classPtr->objDefinitionNs, definePtr, flags); } switch (classPtr->superclasses.num) { case 1: classPtr = classPtr->superclasses.list[0]; goto tailRecurse; default: FOREACH(superPtr, classPtr->superclasses) { AddSimpleClassDefineNamespaces(superPtr, definePtr, flags); } case 0: return; } } /* * ---------------------------------------------------------------------- * * AddDefinitionNamespaceToChain -- * * Adds a single item to the definition chain (if it is meaningful), * reallocating the space for the chain if necessary. * * ---------------------------------------------------------------------- */ static inline void AddDefinitionNamespaceToChain( Class *const definerCls, /* What class defines this entry. */ Tcl_Obj *const namespaceName, /* The name for this entry (or NULL, a * no-op). */ DefineChain *const definePtr, /* The define chain to add the method * implementation to. */ int flags) /* Used to check if we're mixin-consistent * only. Mixin-consistent means that either * we're looking to add things from a mixin * and we have passed a mixin, or we're not * looking to add things from a mixin and have * not passed a mixin. */ { int i; /* * Return if this entry is blank. This is also where we enforce * mixin-consistency. */ if (namespaceName == NULL || !MIXIN_CONSISTENT(flags)) { return; } /* * First test whether the method is already in the call chain. */ for (i=0 ; inum ; i++) { if (definePtr->list[i].definerCls == definerCls) { /* * Call chain semantics states that methods come as *late* in the * call chain as possible. This is done by copying down the * following methods. Note that this does not change the number of * method invocations in the call chain; it just rearranges them. * * We skip changing anything if the place we found was already at * the end of the list. */ if (i < definePtr->num - 1) { memmove(&definePtr->list[i], &definePtr->list[i + 1], sizeof(DefineEntry) * (definePtr->num - i - 1)); definePtr->list[i].definerCls = definerCls; definePtr->list[i].namespaceName = namespaceName; } return; } } /* * Need to really add the define. This is made a bit more complex by the * fact that we are using some "static" space initially, and only start * realloc-ing if the chain gets long. */ if (definePtr->num == definePtr->size) { definePtr->size *= 2; if (definePtr->num == DEFINE_CHAIN_STATIC_SIZE) { DefineEntry *staticList = definePtr->list; definePtr->list = (DefineEntry *) Tcl_Alloc(sizeof(DefineEntry) * definePtr->size); memcpy(definePtr->list, staticList, sizeof(DefineEntry) * definePtr->num); } else { definePtr->list = (DefineEntry *) Tcl_Realloc(definePtr->list, sizeof(DefineEntry) * definePtr->size); } } definePtr->list[i].definerCls = definerCls; definePtr->list[i].namespaceName = namespaceName; definePtr->num++; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclOODecls.h0000644000175000017500000002570014731057471015341 0ustar sergeisergei/* * This file is (mostly) automatically generated from tclOO.decls. */ #ifndef _TCLOODECLS #define _TCLOODECLS #ifndef TCLAPI # ifdef BUILD_tcl # define TCLAPI extern DLLEXPORT # else # define TCLAPI extern DLLIMPORT # endif #endif #ifdef USE_TCL_STUBS # undef USE_TCLOO_STUBS # define USE_TCLOO_STUBS #endif /* !BEGIN!: Do not edit below this line. */ #ifdef __cplusplus extern "C" { #endif /* * Exported function declarations: */ /* 0 */ TCLAPI Tcl_Object Tcl_CopyObjectInstance(Tcl_Interp *interp, Tcl_Object sourceObject, const char *targetName, const char *targetNamespaceName); /* 1 */ TCLAPI Tcl_Object Tcl_GetClassAsObject(Tcl_Class clazz); /* 2 */ TCLAPI Tcl_Class Tcl_GetObjectAsClass(Tcl_Object object); /* 3 */ TCLAPI Tcl_Command Tcl_GetObjectCommand(Tcl_Object object); /* 4 */ TCLAPI Tcl_Object Tcl_GetObjectFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr); /* 5 */ TCLAPI Tcl_Namespace * Tcl_GetObjectNamespace(Tcl_Object object); /* 6 */ TCLAPI Tcl_Class Tcl_MethodDeclarerClass(Tcl_Method method); /* 7 */ TCLAPI Tcl_Object Tcl_MethodDeclarerObject(Tcl_Method method); /* 8 */ TCLAPI int Tcl_MethodIsPublic(Tcl_Method method); /* 9 */ TCLAPI int Tcl_MethodIsType(Tcl_Method method, const Tcl_MethodType *typePtr, void **clientDataPtr); /* 10 */ TCLAPI Tcl_Obj * Tcl_MethodName(Tcl_Method method); /* 11 */ TCLAPI Tcl_Method Tcl_NewInstanceMethod(Tcl_Interp *interp, Tcl_Object object, Tcl_Obj *nameObj, int flags, const Tcl_MethodType *typePtr, void *clientData); /* 12 */ TCLAPI Tcl_Method Tcl_NewMethod(Tcl_Interp *interp, Tcl_Class cls, Tcl_Obj *nameObj, int flags, const Tcl_MethodType *typePtr, void *clientData); /* 13 */ TCLAPI Tcl_Object Tcl_NewObjectInstance(Tcl_Interp *interp, Tcl_Class cls, const char *nameStr, const char *nsNameStr, Tcl_Size objc, Tcl_Obj *const *objv, Tcl_Size skip); /* 14 */ TCLAPI int Tcl_ObjectDeleted(Tcl_Object object); /* 15 */ TCLAPI int Tcl_ObjectContextIsFiltering( Tcl_ObjectContext context); /* 16 */ TCLAPI Tcl_Method Tcl_ObjectContextMethod(Tcl_ObjectContext context); /* 17 */ TCLAPI Tcl_Object Tcl_ObjectContextObject(Tcl_ObjectContext context); /* 18 */ TCLAPI Tcl_Size Tcl_ObjectContextSkippedArgs( Tcl_ObjectContext context); /* 19 */ TCLAPI void * Tcl_ClassGetMetadata(Tcl_Class clazz, const Tcl_ObjectMetadataType *typePtr); /* 20 */ TCLAPI void Tcl_ClassSetMetadata(Tcl_Class clazz, const Tcl_ObjectMetadataType *typePtr, void *metadata); /* 21 */ TCLAPI void * Tcl_ObjectGetMetadata(Tcl_Object object, const Tcl_ObjectMetadataType *typePtr); /* 22 */ TCLAPI void Tcl_ObjectSetMetadata(Tcl_Object object, const Tcl_ObjectMetadataType *typePtr, void *metadata); /* 23 */ TCLAPI int Tcl_ObjectContextInvokeNext(Tcl_Interp *interp, Tcl_ObjectContext context, Tcl_Size objc, Tcl_Obj *const *objv, Tcl_Size skip); /* 24 */ TCLAPI Tcl_ObjectMapMethodNameProc * Tcl_ObjectGetMethodNameMapper( Tcl_Object object); /* 25 */ TCLAPI void Tcl_ObjectSetMethodNameMapper(Tcl_Object object, Tcl_ObjectMapMethodNameProc *mapMethodNameProc); /* 26 */ TCLAPI void Tcl_ClassSetConstructor(Tcl_Interp *interp, Tcl_Class clazz, Tcl_Method method); /* 27 */ TCLAPI void Tcl_ClassSetDestructor(Tcl_Interp *interp, Tcl_Class clazz, Tcl_Method method); /* 28 */ TCLAPI Tcl_Obj * Tcl_GetObjectName(Tcl_Interp *interp, Tcl_Object object); /* 29 */ TCLAPI int Tcl_MethodIsPrivate(Tcl_Method method); /* 30 */ TCLAPI Tcl_Class Tcl_GetClassOfObject(Tcl_Object object); /* 31 */ TCLAPI Tcl_Obj * Tcl_GetObjectClassName(Tcl_Interp *interp, Tcl_Object object); /* 32 */ TCLAPI int Tcl_MethodIsType2(Tcl_Method method, const Tcl_MethodType2 *typePtr, void **clientDataPtr); /* 33 */ TCLAPI Tcl_Method Tcl_NewInstanceMethod2(Tcl_Interp *interp, Tcl_Object object, Tcl_Obj *nameObj, int flags, const Tcl_MethodType2 *typePtr, void *clientData); /* 34 */ TCLAPI Tcl_Method Tcl_NewMethod2(Tcl_Interp *interp, Tcl_Class cls, Tcl_Obj *nameObj, int flags, const Tcl_MethodType2 *typePtr, void *clientData); typedef struct { const struct TclOOIntStubs *tclOOIntStubs; } TclOOStubHooks; typedef struct TclOOStubs { int magic; const TclOOStubHooks *hooks; Tcl_Object (*tcl_CopyObjectInstance) (Tcl_Interp *interp, Tcl_Object sourceObject, const char *targetName, const char *targetNamespaceName); /* 0 */ Tcl_Object (*tcl_GetClassAsObject) (Tcl_Class clazz); /* 1 */ Tcl_Class (*tcl_GetObjectAsClass) (Tcl_Object object); /* 2 */ Tcl_Command (*tcl_GetObjectCommand) (Tcl_Object object); /* 3 */ Tcl_Object (*tcl_GetObjectFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr); /* 4 */ Tcl_Namespace * (*tcl_GetObjectNamespace) (Tcl_Object object); /* 5 */ Tcl_Class (*tcl_MethodDeclarerClass) (Tcl_Method method); /* 6 */ Tcl_Object (*tcl_MethodDeclarerObject) (Tcl_Method method); /* 7 */ int (*tcl_MethodIsPublic) (Tcl_Method method); /* 8 */ int (*tcl_MethodIsType) (Tcl_Method method, const Tcl_MethodType *typePtr, void **clientDataPtr); /* 9 */ Tcl_Obj * (*tcl_MethodName) (Tcl_Method method); /* 10 */ Tcl_Method (*tcl_NewInstanceMethod) (Tcl_Interp *interp, Tcl_Object object, Tcl_Obj *nameObj, int flags, const Tcl_MethodType *typePtr, void *clientData); /* 11 */ Tcl_Method (*tcl_NewMethod) (Tcl_Interp *interp, Tcl_Class cls, Tcl_Obj *nameObj, int flags, const Tcl_MethodType *typePtr, void *clientData); /* 12 */ Tcl_Object (*tcl_NewObjectInstance) (Tcl_Interp *interp, Tcl_Class cls, const char *nameStr, const char *nsNameStr, Tcl_Size objc, Tcl_Obj *const *objv, Tcl_Size skip); /* 13 */ int (*tcl_ObjectDeleted) (Tcl_Object object); /* 14 */ int (*tcl_ObjectContextIsFiltering) (Tcl_ObjectContext context); /* 15 */ Tcl_Method (*tcl_ObjectContextMethod) (Tcl_ObjectContext context); /* 16 */ Tcl_Object (*tcl_ObjectContextObject) (Tcl_ObjectContext context); /* 17 */ Tcl_Size (*tcl_ObjectContextSkippedArgs) (Tcl_ObjectContext context); /* 18 */ void * (*tcl_ClassGetMetadata) (Tcl_Class clazz, const Tcl_ObjectMetadataType *typePtr); /* 19 */ void (*tcl_ClassSetMetadata) (Tcl_Class clazz, const Tcl_ObjectMetadataType *typePtr, void *metadata); /* 20 */ void * (*tcl_ObjectGetMetadata) (Tcl_Object object, const Tcl_ObjectMetadataType *typePtr); /* 21 */ void (*tcl_ObjectSetMetadata) (Tcl_Object object, const Tcl_ObjectMetadataType *typePtr, void *metadata); /* 22 */ int (*tcl_ObjectContextInvokeNext) (Tcl_Interp *interp, Tcl_ObjectContext context, Tcl_Size objc, Tcl_Obj *const *objv, Tcl_Size skip); /* 23 */ Tcl_ObjectMapMethodNameProc * (*tcl_ObjectGetMethodNameMapper) (Tcl_Object object); /* 24 */ void (*tcl_ObjectSetMethodNameMapper) (Tcl_Object object, Tcl_ObjectMapMethodNameProc *mapMethodNameProc); /* 25 */ void (*tcl_ClassSetConstructor) (Tcl_Interp *interp, Tcl_Class clazz, Tcl_Method method); /* 26 */ void (*tcl_ClassSetDestructor) (Tcl_Interp *interp, Tcl_Class clazz, Tcl_Method method); /* 27 */ Tcl_Obj * (*tcl_GetObjectName) (Tcl_Interp *interp, Tcl_Object object); /* 28 */ int (*tcl_MethodIsPrivate) (Tcl_Method method); /* 29 */ Tcl_Class (*tcl_GetClassOfObject) (Tcl_Object object); /* 30 */ Tcl_Obj * (*tcl_GetObjectClassName) (Tcl_Interp *interp, Tcl_Object object); /* 31 */ int (*tcl_MethodIsType2) (Tcl_Method method, const Tcl_MethodType2 *typePtr, void **clientDataPtr); /* 32 */ Tcl_Method (*tcl_NewInstanceMethod2) (Tcl_Interp *interp, Tcl_Object object, Tcl_Obj *nameObj, int flags, const Tcl_MethodType2 *typePtr, void *clientData); /* 33 */ Tcl_Method (*tcl_NewMethod2) (Tcl_Interp *interp, Tcl_Class cls, Tcl_Obj *nameObj, int flags, const Tcl_MethodType2 *typePtr, void *clientData); /* 34 */ } TclOOStubs; extern const TclOOStubs *tclOOStubsPtr; #ifdef __cplusplus } #endif #if defined(USE_TCLOO_STUBS) /* * Inline function declarations: */ #define Tcl_CopyObjectInstance \ (tclOOStubsPtr->tcl_CopyObjectInstance) /* 0 */ #define Tcl_GetClassAsObject \ (tclOOStubsPtr->tcl_GetClassAsObject) /* 1 */ #define Tcl_GetObjectAsClass \ (tclOOStubsPtr->tcl_GetObjectAsClass) /* 2 */ #define Tcl_GetObjectCommand \ (tclOOStubsPtr->tcl_GetObjectCommand) /* 3 */ #define Tcl_GetObjectFromObj \ (tclOOStubsPtr->tcl_GetObjectFromObj) /* 4 */ #define Tcl_GetObjectNamespace \ (tclOOStubsPtr->tcl_GetObjectNamespace) /* 5 */ #define Tcl_MethodDeclarerClass \ (tclOOStubsPtr->tcl_MethodDeclarerClass) /* 6 */ #define Tcl_MethodDeclarerObject \ (tclOOStubsPtr->tcl_MethodDeclarerObject) /* 7 */ #define Tcl_MethodIsPublic \ (tclOOStubsPtr->tcl_MethodIsPublic) /* 8 */ #define Tcl_MethodIsType \ (tclOOStubsPtr->tcl_MethodIsType) /* 9 */ #define Tcl_MethodName \ (tclOOStubsPtr->tcl_MethodName) /* 10 */ #define Tcl_NewInstanceMethod \ (tclOOStubsPtr->tcl_NewInstanceMethod) /* 11 */ #define Tcl_NewMethod \ (tclOOStubsPtr->tcl_NewMethod) /* 12 */ #define Tcl_NewObjectInstance \ (tclOOStubsPtr->tcl_NewObjectInstance) /* 13 */ #define Tcl_ObjectDeleted \ (tclOOStubsPtr->tcl_ObjectDeleted) /* 14 */ #define Tcl_ObjectContextIsFiltering \ (tclOOStubsPtr->tcl_ObjectContextIsFiltering) /* 15 */ #define Tcl_ObjectContextMethod \ (tclOOStubsPtr->tcl_ObjectContextMethod) /* 16 */ #define Tcl_ObjectContextObject \ (tclOOStubsPtr->tcl_ObjectContextObject) /* 17 */ #define Tcl_ObjectContextSkippedArgs \ (tclOOStubsPtr->tcl_ObjectContextSkippedArgs) /* 18 */ #define Tcl_ClassGetMetadata \ (tclOOStubsPtr->tcl_ClassGetMetadata) /* 19 */ #define Tcl_ClassSetMetadata \ (tclOOStubsPtr->tcl_ClassSetMetadata) /* 20 */ #define Tcl_ObjectGetMetadata \ (tclOOStubsPtr->tcl_ObjectGetMetadata) /* 21 */ #define Tcl_ObjectSetMetadata \ (tclOOStubsPtr->tcl_ObjectSetMetadata) /* 22 */ #define Tcl_ObjectContextInvokeNext \ (tclOOStubsPtr->tcl_ObjectContextInvokeNext) /* 23 */ #define Tcl_ObjectGetMethodNameMapper \ (tclOOStubsPtr->tcl_ObjectGetMethodNameMapper) /* 24 */ #define Tcl_ObjectSetMethodNameMapper \ (tclOOStubsPtr->tcl_ObjectSetMethodNameMapper) /* 25 */ #define Tcl_ClassSetConstructor \ (tclOOStubsPtr->tcl_ClassSetConstructor) /* 26 */ #define Tcl_ClassSetDestructor \ (tclOOStubsPtr->tcl_ClassSetDestructor) /* 27 */ #define Tcl_GetObjectName \ (tclOOStubsPtr->tcl_GetObjectName) /* 28 */ #define Tcl_MethodIsPrivate \ (tclOOStubsPtr->tcl_MethodIsPrivate) /* 29 */ #define Tcl_GetClassOfObject \ (tclOOStubsPtr->tcl_GetClassOfObject) /* 30 */ #define Tcl_GetObjectClassName \ (tclOOStubsPtr->tcl_GetObjectClassName) /* 31 */ #define Tcl_MethodIsType2 \ (tclOOStubsPtr->tcl_MethodIsType2) /* 32 */ #define Tcl_NewInstanceMethod2 \ (tclOOStubsPtr->tcl_NewInstanceMethod2) /* 33 */ #define Tcl_NewMethod2 \ (tclOOStubsPtr->tcl_NewMethod2) /* 34 */ #endif /* defined(USE_TCLOO_STUBS) */ /* !END!: Do not edit above this line. */ #if TCL_MAJOR_VERSION < 9 /* TIP #630 for 8.7 */ # undef Tcl_MethodIsType2 # define Tcl_MethodIsType2 Tcl_MethodIsType # undef Tcl_NewInstanceMethod2 # define Tcl_NewInstanceMethod2 Tcl_NewInstanceMethod # undef Tcl_NewMethod2 # define Tcl_NewMethod2 Tcl_NewMethod #endif #endif /* _TCLOODECLS */ tcl9.0.1/generic/tclOODefineCmds.c0000644000175000017500000027054714726623136016317 0ustar sergeisergei/* * tclOODefineCmds.c -- * * This file contains the implementation of the ::oo::define command, * part of the object-system core (NB: not Tcl_Obj, but ::oo). * * Copyright © 2006-2019 Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "tclInt.h" #include "tclOOInt.h" /* * The actual value used to mark private declaration frames. */ #define PRIVATE_FRAME (FRAME_IS_OO_DEFINE | FRAME_IS_PRIVATE_DEFINE) /* * The maximum length of fully-qualified object name to use in an errorinfo * message. Longer than this will be curtailed. */ #define OBJNAME_LENGTH_IN_ERRORINFO_LIMIT 30 /* * Some things that make it easier to declare a slot. */ typedef struct DeclaredSlot { const char *name; const Tcl_MethodType getterType; const Tcl_MethodType setterType; const Tcl_MethodType resolverType; } DeclaredSlot; #define SLOT(name,getter,setter,resolver) \ {"::oo::" name, \ {TCL_OO_METHOD_VERSION_CURRENT, "core method: " name " Getter", \ getter, NULL, NULL}, \ {TCL_OO_METHOD_VERSION_CURRENT, "core method: " name " Setter", \ setter, NULL, NULL}, \ {TCL_OO_METHOD_VERSION_CURRENT, "core method: " name " Resolver", \ resolver, NULL, NULL}} /* * A [string match] pattern used to determine if a method should be exported. */ #define PUBLIC_PATTERN "[a-z]*" /* * Forward declarations. */ static inline void BumpGlobalEpoch(Tcl_Interp *interp, Class *classPtr); static inline void BumpInstanceEpoch(Object *oPtr); static Tcl_Command FindCommand(Tcl_Interp *interp, Tcl_Obj *stringObj, Tcl_Namespace *const namespacePtr); static inline void GenerateErrorInfo(Tcl_Interp *interp, Object *oPtr, Tcl_Obj *savedNameObj, const char *typeOfSubject); static inline int MagicDefinitionInvoke(Tcl_Interp *interp, Tcl_Namespace *nsPtr, int cmdIndex, int objc, Tcl_Obj *const *objv); static inline Class * GetClassInOuterContext(Tcl_Interp *interp, Tcl_Obj *className, const char *errMsg); static inline Tcl_Namespace *GetNamespaceInOuterContext(Tcl_Interp *interp, Tcl_Obj *namespaceName); static inline int InitDefineContext(Tcl_Interp *interp, Tcl_Namespace *namespacePtr, Object *oPtr, int objc, Tcl_Obj *const objv[]); static inline void RecomputeClassCacheFlag(Object *oPtr); static int RenameDeleteMethod(Tcl_Interp *interp, Object *oPtr, int useClass, Tcl_Obj *const fromPtr, Tcl_Obj *const toPtr); static int ClassFilter_Get(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ClassFilter_Set(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ClassMixin_Get(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ClassMixin_Set(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ClassSuper_Get(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ClassSuper_Set(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ClassVars_Get(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ClassVars_Set(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ObjFilter_Get(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ObjFilter_Set(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ObjMixin_Get(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ObjMixin_Set(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ObjVars_Get(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ObjVars_Set(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int Configurable_ClassReadableProps_Get(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int Configurable_ClassReadableProps_Set(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int Configurable_ClassWritableProps_Get(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int Configurable_ClassWritableProps_Set(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int Configurable_ObjectReadableProps_Get(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int Configurable_ObjectReadableProps_Set(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int Configurable_ObjectWritableProps_Get(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int Configurable_ObjectWritableProps_Set(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int ResolveClass(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); /* * Now define the slots used in declarations. */ static const DeclaredSlot slots[] = { SLOT("define::filter", ClassFilter_Get, ClassFilter_Set, NULL), SLOT("define::mixin", ClassMixin_Get, ClassMixin_Set, ResolveClass), SLOT("define::superclass", ClassSuper_Get, ClassSuper_Set, ResolveClass), SLOT("define::variable", ClassVars_Get, ClassVars_Set, NULL), SLOT("objdefine::filter", ObjFilter_Get, ObjFilter_Set, NULL), SLOT("objdefine::mixin", ObjMixin_Get, ObjMixin_Set, ResolveClass), SLOT("objdefine::variable", ObjVars_Get, ObjVars_Set, NULL), SLOT("configuresupport::readableproperties", Configurable_ClassReadableProps_Get, Configurable_ClassReadableProps_Set, NULL), SLOT("configuresupport::writableproperties", Configurable_ClassWritableProps_Get, Configurable_ClassWritableProps_Set, NULL), SLOT("configuresupport::objreadableproperties", Configurable_ObjectReadableProps_Get, Configurable_ObjectReadableProps_Set, NULL), SLOT("configuresupport::objwritableproperties", Configurable_ObjectWritableProps_Get, Configurable_ObjectWritableProps_Set, NULL), {NULL, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}} }; /* * How to build the in-namespace name of a private variable. This is a pattern * used with Tcl_ObjPrintf(). */ #define PRIVATE_VARIABLE_PATTERN "%d : %s" /* * ---------------------------------------------------------------------- * * IsPrivateDefine -- * * Extracts whether the current context is handling private definitions. * * ---------------------------------------------------------------------- */ static inline int IsPrivateDefine( Tcl_Interp *interp) { Interp *iPtr = (Interp *) interp; if (!iPtr->varFramePtr) { return 0; } return iPtr->varFramePtr->isProcCallFrame == PRIVATE_FRAME; } /* * ---------------------------------------------------------------------- * * BumpGlobalEpoch -- * * Utility that ensures that call chains that are invalid will get thrown * away at an appropriate time. Note that exactly which epoch gets * advanced will depend on exactly what the class is tangled up in; in * the worst case, the simplest option is to advance the global epoch, * causing *everything* to be thrown away on next usage. * * ---------------------------------------------------------------------- */ static inline void BumpGlobalEpoch( Tcl_Interp *interp, Class *classPtr) { if (classPtr != NULL && classPtr->subclasses.num == 0 && classPtr->instances.num == 0 && classPtr->mixinSubs.num == 0) { /* * If a class has no subclasses or instances, and is not mixed into * anything, a change to its structure does not require us to * invalidate any call chains. Note that we still bump our object's * epoch if it has any mixins; the relation between a class and its * representative object is special. But it won't hurt. */ if (classPtr->thisPtr->mixins.num > 0) { classPtr->thisPtr->epoch++; /* * Invalidate the property caches directly. */ if (classPtr->properties.allReadableCache) { Tcl_DecrRefCount(classPtr->properties.allReadableCache); classPtr->properties.allReadableCache = NULL; } if (classPtr->properties.allWritableCache) { Tcl_DecrRefCount(classPtr->properties.allWritableCache); classPtr->properties.allWritableCache = NULL; } } return; } /* * Either there's no class (?!) or we're reconfiguring something that is * in use. Force regeneration of call chains and properties. */ TclOOGetFoundation(interp)->epoch++; } /* * ---------------------------------------------------------------------- * * BumpInstanceEpoch -- * * Advances the epoch and clears the property cache of an object. The * equivalent for classes is BumpGlobalEpoch(), as classes have a more * complex set of relationships to other entities. * * ---------------------------------------------------------------------- */ static inline void BumpInstanceEpoch( Object *oPtr) { oPtr->epoch++; if (oPtr->properties.allReadableCache) { Tcl_DecrRefCount(oPtr->properties.allReadableCache); oPtr->properties.allReadableCache = NULL; } if (oPtr->properties.allWritableCache) { Tcl_DecrRefCount(oPtr->properties.allWritableCache); oPtr->properties.allWritableCache = NULL; } } /* * ---------------------------------------------------------------------- * * RecomputeClassCacheFlag -- * * Determine whether the object is prototypical of its class, and hence * able to use the class's method chain cache. * * ---------------------------------------------------------------------- */ static inline void RecomputeClassCacheFlag( Object *oPtr) { if ((oPtr->methodsPtr == NULL || oPtr->methodsPtr->numEntries == 0) && (oPtr->mixins.num == 0) && (oPtr->filters.num == 0)) { oPtr->flags |= USE_CLASS_CACHE; } else { oPtr->flags &= ~USE_CLASS_CACHE; } } /* * ---------------------------------------------------------------------- * * TclOOObjectSetFilters -- * * Install a list of filter method names into an object. * * ---------------------------------------------------------------------- */ void TclOOObjectSetFilters( Object *oPtr, Tcl_Size numFilters, Tcl_Obj *const *filters) { Tcl_Size i; if (oPtr->filters.num) { Tcl_Obj *filterObj; FOREACH(filterObj, oPtr->filters) { Tcl_DecrRefCount(filterObj); } } if (numFilters == 0) { /* * No list of filters was supplied, so we're deleting filters. */ Tcl_Free(oPtr->filters.list); oPtr->filters.list = NULL; oPtr->filters.num = 0; RecomputeClassCacheFlag(oPtr); } else { /* * We've got a list of filters, so we're creating filters. */ Tcl_Obj **filtersList; size_t size = sizeof(Tcl_Obj *) * numFilters; if (oPtr->filters.num == 0) { filtersList = (Tcl_Obj **) Tcl_Alloc(size); } else { filtersList = (Tcl_Obj **) Tcl_Realloc(oPtr->filters.list, size); } for (i = 0 ; i < numFilters ; i++) { filtersList[i] = filters[i]; Tcl_IncrRefCount(filters[i]); } oPtr->filters.list = filtersList; oPtr->filters.num = numFilters; oPtr->flags &= ~USE_CLASS_CACHE; } BumpInstanceEpoch(oPtr); // Only this object can be affected. } /* * ---------------------------------------------------------------------- * * TclOOClassSetFilters -- * * Install a list of filter method names into a class. * * ---------------------------------------------------------------------- */ void TclOOClassSetFilters( Tcl_Interp *interp, Class *classPtr, Tcl_Size numFilters, Tcl_Obj *const *filters) { Tcl_Size i; if (classPtr->filters.num) { Tcl_Obj *filterObj; FOREACH(filterObj, classPtr->filters) { Tcl_DecrRefCount(filterObj); } } if (numFilters == 0) { /* * No list of filters was supplied, so we're deleting filters. */ Tcl_Free(classPtr->filters.list); classPtr->filters.list = NULL; classPtr->filters.num = 0; } else { /* * We've got a list of filters, so we're creating filters. */ Tcl_Obj **filtersList; size_t size = sizeof(Tcl_Obj *) * numFilters; if (classPtr->filters.num == 0) { filtersList = (Tcl_Obj **) Tcl_Alloc(size); } else { filtersList = (Tcl_Obj **) Tcl_Realloc(classPtr->filters.list, size); } for (i = 0 ; i < numFilters ; i++) { filtersList[i] = filters[i]; Tcl_IncrRefCount(filters[i]); } classPtr->filters.list = filtersList; classPtr->filters.num = numFilters; } /* * There may be many objects affected, so bump the global epoch. */ BumpGlobalEpoch(interp, classPtr); } /* * ---------------------------------------------------------------------- * * TclOOObjectSetMixins -- * * Install a list of mixin classes into an object. * * ---------------------------------------------------------------------- */ void TclOOObjectSetMixins( Object *oPtr, Tcl_Size numMixins, Class *const *mixins) { Class *mixinPtr; Tcl_Size i; if (numMixins == 0) { if (oPtr->mixins.num != 0) { FOREACH(mixinPtr, oPtr->mixins) { TclOORemoveFromInstances(oPtr, mixinPtr); TclOODecrRefCount(mixinPtr->thisPtr); } Tcl_Free(oPtr->mixins.list); oPtr->mixins.num = 0; } RecomputeClassCacheFlag(oPtr); } else { if (oPtr->mixins.num != 0) { FOREACH(mixinPtr, oPtr->mixins) { if (mixinPtr && mixinPtr != oPtr->selfCls) { TclOORemoveFromInstances(oPtr, mixinPtr); } TclOODecrRefCount(mixinPtr->thisPtr); } oPtr->mixins.list = (Class **) Tcl_Realloc(oPtr->mixins.list, sizeof(Class *) * numMixins); } else { oPtr->mixins.list = (Class **) Tcl_Alloc(sizeof(Class *) * numMixins); oPtr->flags &= ~USE_CLASS_CACHE; } oPtr->mixins.num = numMixins; memcpy(oPtr->mixins.list, mixins, sizeof(Class *) * numMixins); FOREACH(mixinPtr, oPtr->mixins) { if (mixinPtr != oPtr->selfCls) { TclOOAddToInstances(oPtr, mixinPtr); /* * For the new copy created by memcpy(). */ AddRef(mixinPtr->thisPtr); } } } BumpInstanceEpoch(oPtr); } /* * ---------------------------------------------------------------------- * * TclOOClassSetMixins -- * * Install a list of mixin classes into a class. * * ---------------------------------------------------------------------- */ void TclOOClassSetMixins( Tcl_Interp *interp, Class *classPtr, Tcl_Size numMixins, Class *const *mixins) { Class *mixinPtr; Tcl_Size i; if (numMixins == 0) { if (classPtr->mixins.num != 0) { FOREACH(mixinPtr, classPtr->mixins) { TclOORemoveFromMixinSubs(classPtr, mixinPtr); TclOODecrRefCount(mixinPtr->thisPtr); } Tcl_Free(classPtr->mixins.list); classPtr->mixins.num = 0; } } else { if (classPtr->mixins.num != 0) { FOREACH(mixinPtr, classPtr->mixins) { TclOORemoveFromMixinSubs(classPtr, mixinPtr); TclOODecrRefCount(mixinPtr->thisPtr); } classPtr->mixins.list = (Class **) Tcl_Realloc(classPtr->mixins.list, sizeof(Class *) * numMixins); } else { classPtr->mixins.list = (Class **) Tcl_Alloc(sizeof(Class *) * numMixins); } classPtr->mixins.num = numMixins; memcpy(classPtr->mixins.list, mixins, sizeof(Class *) * numMixins); FOREACH(mixinPtr, classPtr->mixins) { TclOOAddToMixinSubs(classPtr, mixinPtr); /* * For the new copy created by memcpy. */ AddRef(mixinPtr->thisPtr); } } BumpGlobalEpoch(interp, classPtr); } /* * ---------------------------------------------------------------------- * * InstallStandardVariableMapping, InstallPrivateVariableMapping -- * * Helpers for installing standard and private variable maps. * * ---------------------------------------------------------------------- */ static inline void InstallStandardVariableMapping( VariableNameList *vnlPtr, Tcl_Size varc, Tcl_Obj *const *varv) { Tcl_Obj *variableObj; Tcl_Size i, n; int created; Tcl_HashTable uniqueTable; for (i=0 ; ilist); } else if (i) { vnlPtr->list = (Tcl_Obj **) Tcl_Realloc(vnlPtr->list, sizeof(Tcl_Obj *) * varc); } else { vnlPtr->list = (Tcl_Obj **) Tcl_Alloc(sizeof(Tcl_Obj *) * varc); } } vnlPtr->num = 0; if (varc > 0) { Tcl_InitObjHashTable(&uniqueTable); for (i=n=0 ; ilist[n++] = varv[i]; } else { Tcl_DecrRefCount(varv[i]); } } vnlPtr->num = n; /* * Shouldn't be necessary, but maintain num/list invariant. */ if (n != varc) { vnlPtr->list = (Tcl_Obj **) Tcl_Realloc(vnlPtr->list, sizeof(Tcl_Obj *) * n); } Tcl_DeleteHashTable(&uniqueTable); } } static inline void InstallPrivateVariableMapping( PrivateVariableList *pvlPtr, Tcl_Size varc, Tcl_Obj *const *varv, int creationEpoch) { PrivateVariableMapping *privatePtr; Tcl_Size i, n; int created; Tcl_HashTable uniqueTable; for (i=0 ; ivariableObj); Tcl_DecrRefCount(privatePtr->fullNameObj); } if (i != varc) { if (varc == 0) { Tcl_Free(pvlPtr->list); } else if (i) { pvlPtr->list = (PrivateVariableMapping *) Tcl_Realloc(pvlPtr->list, sizeof(PrivateVariableMapping) * varc); } else { pvlPtr->list = (PrivateVariableMapping *) Tcl_Alloc(sizeof(PrivateVariableMapping) * varc); } } pvlPtr->num = 0; if (varc > 0) { Tcl_InitObjHashTable(&uniqueTable); for (i=n=0 ; ilist[n++]); privatePtr->variableObj = varv[i]; privatePtr->fullNameObj = Tcl_ObjPrintf( PRIVATE_VARIABLE_PATTERN, creationEpoch, TclGetString(varv[i])); Tcl_IncrRefCount(privatePtr->fullNameObj); } else { Tcl_DecrRefCount(varv[i]); } } pvlPtr->num = n; /* * Shouldn't be necessary, but maintain num/list invariant. */ if (n != varc) { pvlPtr->list = (PrivateVariableMapping *) Tcl_Realloc(pvlPtr->list, sizeof(PrivateVariableMapping) * n); } Tcl_DeleteHashTable(&uniqueTable); } } /* * ---------------------------------------------------------------------- * * RenameDeleteMethod -- * * Core of the code to rename and delete methods. * * ---------------------------------------------------------------------- */ static int RenameDeleteMethod( Tcl_Interp *interp, Object *oPtr, int useClass, Tcl_Obj *const fromPtr, Tcl_Obj *const toPtr) { Tcl_HashEntry *hPtr, *newHPtr = NULL; Method *mPtr; int isNew; if (!useClass) { if (!oPtr->methodsPtr) { noSuchMethod: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "method %s does not exist", TclGetString(fromPtr))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(fromPtr), (char *)NULL); return TCL_ERROR; } hPtr = Tcl_FindHashEntry(oPtr->methodsPtr, fromPtr); if (hPtr == NULL) { goto noSuchMethod; } if (toPtr) { newHPtr = Tcl_CreateHashEntry(oPtr->methodsPtr, toPtr, &isNew); if (hPtr == newHPtr) { renameToSelf: Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot rename method to itself", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "RENAME_TO_SELF", (char *)NULL); return TCL_ERROR; } else if (!isNew) { renameToExisting: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "method called %s already exists", TclGetString(toPtr))); Tcl_SetErrorCode(interp, "TCL", "OO", "RENAME_OVER", (char *)NULL); return TCL_ERROR; } } } else { hPtr = Tcl_FindHashEntry(&oPtr->classPtr->classMethods, fromPtr); if (hPtr == NULL) { goto noSuchMethod; } if (toPtr) { newHPtr = Tcl_CreateHashEntry(&oPtr->classPtr->classMethods, (char *) toPtr, &isNew); if (hPtr == newHPtr) { goto renameToSelf; } else if (!isNew) { goto renameToExisting; } } } /* * Complete the splicing by changing the method's name. */ mPtr = (Method *) Tcl_GetHashValue(hPtr); if (toPtr) { Tcl_IncrRefCount(toPtr); Tcl_DecrRefCount(mPtr->namePtr); mPtr->namePtr = toPtr; Tcl_SetHashValue(newHPtr, mPtr); } else { if (!useClass) { RecomputeClassCacheFlag(oPtr); } TclOODelMethodRef(mPtr); } Tcl_DeleteHashEntry(hPtr); return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOOUnknownDefinition -- * * Handles what happens when an unknown command is encountered during the * processing of a definition script. Works by finding a command in the * operating definition namespace that the requested command is a unique * prefix of. * * ---------------------------------------------------------------------- */ int TclOOUnknownDefinition( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Namespace *nsPtr = (Namespace *) Tcl_GetCurrentNamespace(interp); FOREACH_HASH_DECLS; Tcl_Size soughtLen; const char *soughtStr, *nameStr, *matchedStr = NULL; if (objc < 2) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad call of unknown handler", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "BAD_UNKNOWN", (char *)NULL); return TCL_ERROR; } if (TclOOGetDefineCmdContext(interp) == NULL) { return TCL_ERROR; } soughtStr = TclGetStringFromObj(objv[1], &soughtLen); if (soughtLen == 0) { goto noMatch; } FOREACH_HASH_KEY(nameStr, &nsPtr->cmdTable) { if (strncmp(soughtStr, nameStr, soughtLen) == 0) { if (matchedStr != NULL) { goto noMatch; } matchedStr = nameStr; } } if (matchedStr != NULL) { /* * Got one match, and only one match! */ Tcl_Obj **newObjv = (Tcl_Obj **) TclStackAlloc(interp, sizeof(Tcl_Obj*) * (objc - 1)); int result; newObjv[0] = Tcl_NewStringObj(matchedStr, TCL_AUTO_LENGTH); Tcl_IncrRefCount(newObjv[0]); if (objc > 2) { memcpy(newObjv + 1, objv + 2, sizeof(Tcl_Obj *) * (objc - 2)); } result = Tcl_EvalObjv(interp, objc - 1, newObjv, 0); Tcl_DecrRefCount(newObjv[0]); TclStackFree(interp, newObjv); return result; } noMatch: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid command name \"%s\"", soughtStr)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COMMAND", soughtStr, (char *)NULL); return TCL_ERROR; } /* * ---------------------------------------------------------------------- * * FindCommand -- * * Specialized version of Tcl_FindCommand that handles command prefixes * and disallows namespace magic. * * ---------------------------------------------------------------------- */ static Tcl_Command FindCommand( Tcl_Interp *interp, Tcl_Obj *stringObj, Tcl_Namespace *const namespacePtr) { Tcl_Size length; const char *nameStr, *string = TclGetStringFromObj(stringObj, &length); Namespace *const nsPtr = (Namespace *) namespacePtr; FOREACH_HASH_DECLS; Tcl_Command cmd, cmd2; /* * If someone is playing games, we stop playing right now. */ if (string[0] == '\0' || strstr(string, "::") != NULL) { return NULL; } /* * Do the exact lookup first. */ cmd = Tcl_FindCommand(interp, string, namespacePtr, TCL_NAMESPACE_ONLY); if (cmd != NULL) { return cmd; } /* * Bother, need to perform an approximate match. Iterate across the hash * table of commands in the namespace. */ FOREACH_HASH(nameStr, cmd2, &nsPtr->cmdTable) { if (strncmp(string, nameStr, length) == 0) { if (cmd != NULL) { return NULL; } cmd = cmd2; } } /* * Either we found one thing or we found nothing. Either way, return it. */ return cmd; } /* * ---------------------------------------------------------------------- * * InitDefineContext -- * * Does the magic incantations necessary to push the special stack frame * used when processing object definitions. It is up to the caller to * dispose of the frame (with TclPopStackFrame) when finished. * * ---------------------------------------------------------------------- */ static inline int InitDefineContext( Tcl_Interp *interp, Tcl_Namespace *namespacePtr, Object *oPtr, int objc, Tcl_Obj *const objv[]) { CallFrame *framePtr, **framePtrPtr = &framePtr; if (namespacePtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "no definition namespace available", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return TCL_ERROR; } /* * framePtrPtr is needed to satisfy GCC 3.3's strict aliasing rules. */ (void) TclPushStackFrame(interp, (Tcl_CallFrame **) framePtrPtr, namespacePtr, FRAME_IS_OO_DEFINE); framePtr->clientData = oPtr; framePtr->objc = objc; framePtr->objv = objv; /* Reference counts do not need to be * incremented here. */ return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOOGetDefineCmdContext, TclOOGetClassDefineCmdContext -- * * Extracts the magic token from the current stack frame, or returns NULL * (and leaves an error message) otherwise. * * ---------------------------------------------------------------------- */ Tcl_Object TclOOGetDefineCmdContext( Tcl_Interp *interp) { Interp *iPtr = (Interp *) interp; Tcl_Object object; if ((iPtr->varFramePtr == NULL) || (iPtr->varFramePtr->isProcCallFrame != FRAME_IS_OO_DEFINE && iPtr->varFramePtr->isProcCallFrame != PRIVATE_FRAME)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "this command may only be called from within the context of" " an ::oo::define or ::oo::objdefine command", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return NULL; } object = (Tcl_Object) iPtr->varFramePtr->clientData; if (Tcl_ObjectDeleted(object)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "this command cannot be called when the object has been" " deleted", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return NULL; } return object; } Class * TclOOGetClassDefineCmdContext( Tcl_Interp *interp) { Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); if (oPtr == NULL) { return NULL; } if (!oPtr->classPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "attempt to misuse API", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", NULL); return NULL; } return oPtr->classPtr; } /* * ---------------------------------------------------------------------- * * GetClassInOuterContext, GetNamespaceInOuterContext -- * * Wrappers round Tcl_GetObjectFromObj and TclGetNamespaceFromObj to * perform the lookup in the context that called oo::define (or * equivalent). Note that this may have to go up multiple levels to get * the level that we started doing definitions at. * * ---------------------------------------------------------------------- */ static inline Class * GetClassInOuterContext( Tcl_Interp *interp, Tcl_Obj *className, const char *errMsg) { Interp *iPtr = (Interp *) interp; Object *oPtr; CallFrame *savedFramePtr = iPtr->varFramePtr; while (iPtr->varFramePtr->isProcCallFrame == FRAME_IS_OO_DEFINE || iPtr->varFramePtr->isProcCallFrame == PRIVATE_FRAME) { if (iPtr->varFramePtr->callerVarPtr == NULL) { Tcl_Panic("getting outer context when already in global context"); } iPtr->varFramePtr = iPtr->varFramePtr->callerVarPtr; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, className); iPtr->varFramePtr = savedFramePtr; if (oPtr == NULL) { return NULL; } if (oPtr->classPtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj(errMsg, TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CLASS", TclGetString(className), (char *)NULL); return NULL; } return oPtr->classPtr; } static inline Tcl_Namespace * GetNamespaceInOuterContext( Tcl_Interp *interp, Tcl_Obj *namespaceName) { Interp *iPtr = (Interp *) interp; Tcl_Namespace *nsPtr; int result; CallFrame *savedFramePtr = iPtr->varFramePtr; while (iPtr->varFramePtr->isProcCallFrame == FRAME_IS_OO_DEFINE || iPtr->varFramePtr->isProcCallFrame == PRIVATE_FRAME) { if (iPtr->varFramePtr->callerVarPtr == NULL) { Tcl_Panic("getting outer context when already in global context"); } iPtr->varFramePtr = iPtr->varFramePtr->callerVarPtr; } result = TclGetNamespaceFromObj(interp, namespaceName, &nsPtr); iPtr->varFramePtr = savedFramePtr; if (result != TCL_OK) { return NULL; } return nsPtr; } /* * ---------------------------------------------------------------------- * * GenerateErrorInfo -- * * Factored out code to generate part of the error trace messages. * * ---------------------------------------------------------------------- */ static inline void GenerateErrorInfo( Tcl_Interp *interp, /* Where to store the error info trace. */ Object *oPtr, /* What object (or class) was being configured * when the error occurred? */ Tcl_Obj *savedNameObj, /* Name of object saved from before script was * evaluated, which is needed if the object * goes away part way through execution. OTOH, * if the object isn't deleted then its * current name (post-execution) has to be * used. This matters, because the object * could have been renamed... */ const char *typeOfSubject) /* Part of the message, saying whether it was * an object, class or class-as-object that * was being configured. */ { Tcl_Size length; Tcl_Obj *realNameObj = Tcl_ObjectDeleted((Tcl_Object) oPtr) ? savedNameObj : TclOOObjectName(interp, oPtr); const char *objName = TclGetStringFromObj(realNameObj, &length); int limit = OBJNAME_LENGTH_IN_ERRORINFO_LIMIT; int overflow = (length > limit); Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (in definition script for %s \"%.*s%s\" line %d)", typeOfSubject, (overflow ? limit : (int) length), objName, (overflow ? "..." : ""), Tcl_GetErrorLine(interp))); } /* * ---------------------------------------------------------------------- * * MagicDefinitionInvoke -- * * Part of the implementation of the "oo::define" and "oo::objdefine" * commands that is used to implement the more-than-one-argument case, * applying ensemble-like tricks with dispatch so that error messages are * clearer. Doesn't handle the management of the stack frame. * * ---------------------------------------------------------------------- */ static inline int MagicDefinitionInvoke( Tcl_Interp *interp, Tcl_Namespace *nsPtr, int cmdIndex, int objc, Tcl_Obj *const *objv) { Tcl_Obj *objPtr, *obj2Ptr, **objs; Tcl_Command cmd; int isRoot, result, offset = cmdIndex + 1; Tcl_Size dummy; /* * More than one argument: fire them through the ensemble processing * engine so that everything appears to be good and proper in error * messages. Note that we cannot just concatenate and send through * Tcl_EvalObjEx, as that doesn't do ensemble processing, and we cannot go * through Tcl_EvalObjv without the extra work to pre-find the command, as * that finds command names in the wrong namespace at the moment. Ugly! */ isRoot = TclInitRewriteEnsemble(interp, offset, 1, objv); /* * Build the list of arguments using a Tcl_Obj as a workspace. See * comments above for why these contortions are necessary. */ TclNewObj(objPtr); TclNewObj(obj2Ptr); cmd = FindCommand(interp, objv[cmdIndex], nsPtr); if (cmd == NULL) { /* * Punt this case! */ Tcl_AppendObjToObj(obj2Ptr, objv[cmdIndex]); } else { Tcl_GetCommandFullName(interp, cmd, obj2Ptr); } Tcl_ListObjAppendElement(NULL, objPtr, obj2Ptr); // TODO: overflow? Tcl_ListObjReplace(NULL, objPtr, 1, 0, objc - offset, objv + offset); TclListObjGetElements(NULL, objPtr, &dummy, &objs); result = Tcl_EvalObjv(interp, objc - cmdIndex, objs, TCL_EVAL_INVOKE); if (isRoot) { TclResetRewriteEnsemble(interp, 1); } Tcl_DecrRefCount(objPtr); return result; } /* * ---------------------------------------------------------------------- * * TclOODefineObjCmd -- * * Implementation of the "oo::define" command. Works by effectively doing * the same as 'namespace eval', but with extra magic applied so that the * object to be modified is known to the commands in the target * namespace. Also does ensemble-like tricks with dispatch so that error * messages are clearer. * * ---------------------------------------------------------------------- */ int TclOODefineObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Tcl_Namespace *nsPtr; Object *oPtr; int result; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "className arg ?arg ...?"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } if (oPtr->classPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s does not refer to a class", TclGetString(objv[1]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CLASS", TclGetString(objv[1]), (char *)NULL); return TCL_ERROR; } /* * Make the oo::define namespace the current namespace and evaluate the * command(s). */ nsPtr = TclOOGetDefineContextNamespace(interp, oPtr, 1); if (InitDefineContext(interp, nsPtr, oPtr, objc, objv) != TCL_OK) { return TCL_ERROR; } AddRef(oPtr); if (objc == 3) { Tcl_Obj *objNameObj = TclOOObjectName(interp, oPtr); Tcl_IncrRefCount(objNameObj); result = TclEvalObjEx(interp, objv[2], 0, ((Interp *) interp)->cmdFramePtr, 2); if (result == TCL_ERROR) { GenerateErrorInfo(interp, oPtr, objNameObj, "class"); } TclDecrRefCount(objNameObj); } else { result = MagicDefinitionInvoke(interp, nsPtr, 2, objc, objv); } TclOODecrRefCount(oPtr); /* * Restore the previous "current" namespace. */ TclPopStackFrame(interp); return result; } /* * ---------------------------------------------------------------------- * * TclOOObjDefObjCmd -- * * Implementation of the "oo::objdefine" command. Works by effectively * doing the same as 'namespace eval', but with extra magic applied so * that the object to be modified is known to the commands in the target * namespace. Also does ensemble-like tricks with dispatch so that error * messages are clearer. * * ---------------------------------------------------------------------- */ int TclOOObjDefObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Tcl_Namespace *nsPtr; Object *oPtr; int result; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "objectName arg ?arg ...?"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } /* * Make the oo::objdefine namespace the current namespace and evaluate the * command(s). */ nsPtr = TclOOGetDefineContextNamespace(interp, oPtr, 0); if (InitDefineContext(interp, nsPtr, oPtr, objc, objv) != TCL_OK) { return TCL_ERROR; } AddRef(oPtr); if (objc == 3) { Tcl_Obj *objNameObj = TclOOObjectName(interp, oPtr); Tcl_IncrRefCount(objNameObj); result = TclEvalObjEx(interp, objv[2], 0, ((Interp *) interp)->cmdFramePtr, 2); if (result == TCL_ERROR) { GenerateErrorInfo(interp, oPtr, objNameObj, "object"); } TclDecrRefCount(objNameObj); } else { result = MagicDefinitionInvoke(interp, nsPtr, 2, objc, objv); } TclOODecrRefCount(oPtr); /* * Restore the previous "current" namespace. */ TclPopStackFrame(interp); return result; } /* * ---------------------------------------------------------------------- * * TclOODefineSelfObjCmd -- * * Implementation of the "self" subcommand of the "oo::define" command. * Works by effectively doing the same as 'namespace eval', but with * extra magic applied so that the object to be modified is known to the * commands in the target namespace. Also does ensemble-like tricks with * dispatch so that error messages are clearer. * * ---------------------------------------------------------------------- */ int TclOODefineSelfObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Tcl_Namespace *nsPtr; Object *oPtr; int result, isPrivate; oPtr = (Object *) TclOOGetDefineCmdContext(interp); if (oPtr == NULL) { return TCL_ERROR; } if (objc < 2) { Tcl_SetObjResult(interp, TclOOObjectName(interp, oPtr)); return TCL_OK; } isPrivate = IsPrivateDefine(interp); /* * Make the oo::objdefine namespace the current namespace and evaluate the * command(s). */ nsPtr = TclOOGetDefineContextNamespace(interp, oPtr, 0); if (InitDefineContext(interp, nsPtr, oPtr, objc, objv) != TCL_OK) { return TCL_ERROR; } if (isPrivate) { ((Interp *) interp)->varFramePtr->isProcCallFrame = PRIVATE_FRAME; } AddRef(oPtr); if (objc == 2) { Tcl_Obj *objNameObj = TclOOObjectName(interp, oPtr); Tcl_IncrRefCount(objNameObj); result = TclEvalObjEx(interp, objv[1], 0, ((Interp *) interp)->cmdFramePtr, 1); if (result == TCL_ERROR) { GenerateErrorInfo(interp, oPtr, objNameObj, "class object"); } TclDecrRefCount(objNameObj); } else { result = MagicDefinitionInvoke(interp, nsPtr, 1, objc, objv); } TclOODecrRefCount(oPtr); /* * Restore the previous "current" namespace. */ TclPopStackFrame(interp); return result; } /* * ---------------------------------------------------------------------- * * TclOODefineObjSelfObjCmd -- * * Implementation of the "self" subcommand of the "oo::objdefine" * command. * * ---------------------------------------------------------------------- */ int TclOODefineObjSelfObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Object *oPtr; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } oPtr = (Object *) TclOOGetDefineCmdContext(interp); if (oPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, TclOOObjectName(interp, oPtr)); return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOODefinePrivateObjCmd -- * * Implementation of the "private" subcommand of the "oo::define" * and "oo::objdefine" commands. * * ---------------------------------------------------------------------- */ int TclOODefinePrivateObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { int isInstancePrivate = (clientData != NULL); /* Just so that we can generate the correct * error message depending on the context of * usage of this function. */ Interp *iPtr = (Interp *) interp; Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); int saved; /* The saved flag. We restore it on exit so * that [private private ...] doesn't make * things go weird. */ int result; if (oPtr == NULL) { return TCL_ERROR; } if (objc == 1) { Tcl_SetObjResult(interp, Tcl_NewBooleanObj(IsPrivateDefine(interp))); return TCL_OK; } /* * Change the frame type flag while evaluating the body. */ saved = iPtr->varFramePtr->isProcCallFrame; iPtr->varFramePtr->isProcCallFrame = PRIVATE_FRAME; /* * Evaluate the body; standard pattern. */ AddRef(oPtr); if (objc == 2) { Tcl_Obj *objNameObj = TclOOObjectName(interp, oPtr); Tcl_IncrRefCount(objNameObj); result = TclEvalObjEx(interp, objv[1], 0, iPtr->cmdFramePtr, 1); if (result == TCL_ERROR) { GenerateErrorInfo(interp, oPtr, objNameObj, isInstancePrivate ? "object" : "class"); } TclDecrRefCount(objNameObj); } else { result = MagicDefinitionInvoke(interp, TclGetCurrentNamespace(interp), 1, objc, objv); } TclOODecrRefCount(oPtr); /* * Restore the frame type flag to what it was previously. */ iPtr->varFramePtr->isProcCallFrame = saved; return result; } /* * ---------------------------------------------------------------------- * * TclOODefineClassObjCmd -- * * Implementation of the "class" subcommand of the "oo::objdefine" * command. * * ---------------------------------------------------------------------- */ int TclOODefineClassObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Object *oPtr; Class *clsPtr; Foundation *fPtr = TclOOGetFoundation(interp); int wasClass, willBeClass; /* * Parse the context to get the object to operate on. */ oPtr = (Object *) TclOOGetDefineCmdContext(interp); if (oPtr == NULL) { return TCL_ERROR; } if (oPtr->flags & ROOT_OBJECT) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "may not modify the class of the root object class", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return TCL_ERROR; } if (oPtr->flags & ROOT_CLASS) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "may not modify the class of the class of classes", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return TCL_ERROR; } /* * Parse the argument to get the class to set the object's class to. */ if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "className"); return TCL_ERROR; } clsPtr = GetClassInOuterContext(interp, objv[1], "the class of an object must be a class"); if (clsPtr == NULL) { return TCL_ERROR; } if (oPtr == clsPtr->thisPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "may not change classes into an instance of themselves", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return TCL_ERROR; } /* * Set the object's class. */ wasClass = (oPtr->classPtr != NULL); willBeClass = (TclOOIsReachable(fPtr->classCls, clsPtr)); if (oPtr->selfCls != clsPtr) { TclOORemoveFromInstances(oPtr, oPtr->selfCls); TclOODecrRefCount(oPtr->selfCls->thisPtr); oPtr->selfCls = clsPtr; AddRef(oPtr->selfCls->thisPtr); TclOOAddToInstances(oPtr, oPtr->selfCls); /* * Create or delete the class guts if necessary. */ if (wasClass && !willBeClass) { /* * This is the most global of all epochs. Bump it! No cache can be * trusted! */ TclOORemoveFromMixins(oPtr->classPtr, oPtr); oPtr->fPtr->epoch++; oPtr->flags |= DONT_DELETE; TclOODeleteDescendants(interp, oPtr); oPtr->flags &= ~DONT_DELETE; TclOOReleaseClassContents(interp, oPtr); Tcl_Free(oPtr->classPtr); oPtr->classPtr = NULL; } else if (!wasClass && willBeClass) { TclOOAllocClass(interp, oPtr); } if (oPtr->classPtr != NULL) { BumpGlobalEpoch(interp, oPtr->classPtr); } else { BumpInstanceEpoch(oPtr); } } return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOODefineConstructorObjCmd -- * * Implementation of the "constructor" subcommand of the "oo::define" * command. * * ---------------------------------------------------------------------- */ int TclOODefineConstructorObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Class *clsPtr = TclOOGetClassDefineCmdContext(interp); Tcl_Method method; Tcl_Size bodyLength; if (clsPtr == NULL) { return TCL_ERROR; } else if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "arguments body"); return TCL_ERROR; } (void) TclGetStringFromObj(objv[2], &bodyLength); if (bodyLength > 0) { /* * Create the method structure. */ method = (Tcl_Method) TclOONewProcMethod(interp, clsPtr, PUBLIC_METHOD, NULL, objv[1], objv[2], NULL); if (method == NULL) { return TCL_ERROR; } } else { /* * Delete the constructor method record and set the field in the * class record to NULL. */ method = NULL; } /* * Place the method structure in the class record. Note that we might not * immediately delete the constructor as this might be being done during * execution of the constructor itself. */ Tcl_ClassSetConstructor(interp, (Tcl_Class) clsPtr, method); return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOODefineDefnNsObjCmd -- * * Implementation of the "definitionnamespace" subcommand of the * "oo::define" command. * * ---------------------------------------------------------------------- */ int TclOODefineDefnNsObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { static const char *kindList[] = { "-class", "-instance", NULL }; int kind = 0; Class *clsPtr = TclOOGetClassDefineCmdContext(interp); Tcl_Namespace *nsPtr; Tcl_Obj *nsNamePtr, **storagePtr; if (clsPtr == NULL) { return TCL_ERROR; } else if (clsPtr->thisPtr->flags & (ROOT_OBJECT | ROOT_CLASS)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "may not modify the definition namespace of the root classes", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return TCL_ERROR; } /* * Parse the arguments and work out what the user wants to do. */ if (objc != 2 && objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "?kind? namespace"); return TCL_ERROR; } if (objc == 3 && Tcl_GetIndexFromObj(interp, objv[1], kindList, "kind", 0, &kind) != TCL_OK) { return TCL_ERROR; } if (!TclGetString(objv[objc - 1])[0]) { nsNamePtr = NULL; } else { nsPtr = GetNamespaceInOuterContext(interp, objv[objc - 1]); if (nsPtr == NULL) { return TCL_ERROR; } nsNamePtr = TclNewNamespaceObj(nsPtr); Tcl_IncrRefCount(nsNamePtr); } /* * Update the correct field of the class definition. */ if (kind) { storagePtr = &clsPtr->objDefinitionNs; } else { storagePtr = &clsPtr->clsDefinitionNs; } if (*storagePtr != NULL) { Tcl_DecrRefCount(*storagePtr); } *storagePtr = nsNamePtr; return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOODefineDeleteMethodObjCmd -- * * Implementation of the "deletemethod" subcommand of the "oo::define" * and "oo::objdefine" commands. * * ---------------------------------------------------------------------- */ int TclOODefineDeleteMethodObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { int isInstanceDeleteMethod = (clientData != NULL); Object *oPtr; int i; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "name ?name ...?"); return TCL_ERROR; } oPtr = (Object *) TclOOGetDefineCmdContext(interp); if (oPtr == NULL) { return TCL_ERROR; } if (!isInstanceDeleteMethod && !oPtr->classPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "attempt to misuse API", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return TCL_ERROR; } for (i = 1; i < objc; i++) { /* * Delete the method structure from the appropriate hash table. */ if (RenameDeleteMethod(interp, oPtr, !isInstanceDeleteMethod, objv[i], NULL) != TCL_OK) { return TCL_ERROR; } } if (isInstanceDeleteMethod) { BumpInstanceEpoch(oPtr); } else { BumpGlobalEpoch(interp, oPtr->classPtr); } return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOODefineDestructorObjCmd -- * * Implementation of the "destructor" subcommand of the "oo::define" * command. * * ---------------------------------------------------------------------- */ int TclOODefineDestructorObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Tcl_Method method; Tcl_Size bodyLength; Class *clsPtr = TclOOGetClassDefineCmdContext(interp); if (clsPtr == NULL) { return TCL_ERROR; } else if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "body"); return TCL_ERROR; } (void) TclGetStringFromObj(objv[1], &bodyLength); if (bodyLength > 0) { /* * Create the method structure. */ method = (Tcl_Method) TclOONewProcMethod(interp, clsPtr, PUBLIC_METHOD, NULL, NULL, objv[1], NULL); if (method == NULL) { return TCL_ERROR; } } else { /* * Delete the destructor method record and set the field in the class * record to NULL. */ method = NULL; } /* * Place the method structure in the class record. Note that we might not * immediately delete the destructor as this might be being done during * execution of the destructor itself. Also note that setting a * destructor during a destructor is fairly dumb anyway. */ Tcl_ClassSetDestructor(interp, (Tcl_Class) clsPtr, method); return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOODefineExportObjCmd -- * * Implementation of the "export" subcommand of the "oo::define" and * "oo::objdefine" commands. * * ---------------------------------------------------------------------- */ int TclOODefineExportObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { int isInstanceExport = (clientData != NULL); Object *oPtr; Method *mPtr; Tcl_HashEntry *hPtr; Class *clsPtr; int i, isNew, changed = 0; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "name ?name ...?"); return TCL_ERROR; } oPtr = (Object *) TclOOGetDefineCmdContext(interp); if (oPtr == NULL) { return TCL_ERROR; } clsPtr = oPtr->classPtr; if (!isInstanceExport && !clsPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "attempt to misuse API", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return TCL_ERROR; } for (i = 1; i < objc; i++) { /* * Exporting is done by adding the PUBLIC_METHOD flag to the method * record. If there is no such method in this object or class (i.e. * the method comes from something inherited from or that we're an * instance of) then we put in a blank record with that flag; such * records are skipped over by the call chain engine *except* for * their flags member. */ if (isInstanceExport) { if (!oPtr->methodsPtr) { oPtr->methodsPtr = (Tcl_HashTable *) Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitObjHashTable(oPtr->methodsPtr); oPtr->flags &= ~USE_CLASS_CACHE; } hPtr = Tcl_CreateHashEntry(oPtr->methodsPtr, objv[i], &isNew); } else { hPtr = Tcl_CreateHashEntry(&clsPtr->classMethods, objv[i], &isNew); } if (isNew) { mPtr = (Method *) Tcl_Alloc(sizeof(Method)); memset(mPtr, 0, sizeof(Method)); mPtr->refCount = 1; mPtr->namePtr = objv[i]; Tcl_IncrRefCount(objv[i]); Tcl_SetHashValue(hPtr, mPtr); } else { mPtr = (Method *) Tcl_GetHashValue(hPtr); } if (isNew || !(mPtr->flags & (PUBLIC_METHOD | PRIVATE_METHOD))) { mPtr->flags |= PUBLIC_METHOD; mPtr->flags &= ~TRUE_PRIVATE_METHOD; changed = 1; } } /* * Bump the right epoch if we actually changed anything. */ if (changed) { if (isInstanceExport) { BumpInstanceEpoch(oPtr); } else { BumpGlobalEpoch(interp, clsPtr); } } return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOODefineForwardObjCmd -- * * Implementation of the "forward" subcommand of the "oo::define" and * "oo::objdefine" commands. * * ---------------------------------------------------------------------- */ int TclOODefineForwardObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { int isInstanceForward = (clientData != NULL); Object *oPtr; Method *mPtr; int isPublic; Tcl_Obj *prefixObj; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "name cmdName ?arg ...?"); return TCL_ERROR; } oPtr = (Object *) TclOOGetDefineCmdContext(interp); if (oPtr == NULL) { return TCL_ERROR; } if (!isInstanceForward && !oPtr->classPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "attempt to misuse API", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return TCL_ERROR; } isPublic = Tcl_StringMatch(TclGetString(objv[1]), PUBLIC_PATTERN) ? PUBLIC_METHOD : 0; if (IsPrivateDefine(interp)) { isPublic = TRUE_PRIVATE_METHOD; } /* * Create the method structure. */ prefixObj = Tcl_NewListObj(objc - 2, objv + 2); if (isInstanceForward) { mPtr = TclOONewForwardInstanceMethod(interp, oPtr, isPublic, objv[1], prefixObj); } else { mPtr = TclOONewForwardMethod(interp, oPtr->classPtr, isPublic, objv[1], prefixObj); } if (mPtr == NULL) { Tcl_DecrRefCount(prefixObj); return TCL_ERROR; } return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOODefineMethodObjCmd -- * * Implementation of the "method" subcommand of the "oo::define" and * "oo::objdefine" commands. * * ---------------------------------------------------------------------- */ int TclOODefineMethodObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { /* * Table of export modes for methods and their corresponding enum. */ static const char *const exportModes[] = { "-export", "-private", "-unexport", NULL }; enum ExportMode { MODE_EXPORT, MODE_PRIVATE, MODE_UNEXPORT } exportMode; int isInstanceMethod = (clientData != NULL); Object *oPtr; int isPublic = 0; if (objc < 4 || objc > 5) { Tcl_WrongNumArgs(interp, 1, objv, "name ?option? args body"); return TCL_ERROR; } oPtr = (Object *) TclOOGetDefineCmdContext(interp); if (oPtr == NULL) { return TCL_ERROR; } if (!isInstanceMethod && !oPtr->classPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "attempt to misuse API", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return TCL_ERROR; } if (objc == 5) { if (Tcl_GetIndexFromObj(interp, objv[2], exportModes, "export flag", 0, &exportMode) != TCL_OK) { return TCL_ERROR; } switch (exportMode) { case MODE_EXPORT: isPublic = PUBLIC_METHOD; break; case MODE_PRIVATE: isPublic = TRUE_PRIVATE_METHOD; break; case MODE_UNEXPORT: isPublic = 0; break; } } else { if (IsPrivateDefine(interp)) { isPublic = TRUE_PRIVATE_METHOD; } else { isPublic = Tcl_StringMatch(TclGetString(objv[1]), PUBLIC_PATTERN) ? PUBLIC_METHOD : 0; } } /* * Create the method by using the right back-end API. */ if (isInstanceMethod) { if (TclOONewProcInstanceMethod(interp, oPtr, isPublic, objv[1], objv[objc - 2], objv[objc - 1], NULL) == NULL) { return TCL_ERROR; } } else { if (TclOONewProcMethod(interp, oPtr->classPtr, isPublic, objv[1], objv[objc - 2], objv[objc - 1], NULL) == NULL) { return TCL_ERROR; } } return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOODefineRenameMethodObjCmd -- * * Implementation of the "renamemethod" subcommand of the "oo::define" * and "oo::objdefine" commands. * * ---------------------------------------------------------------------- */ int TclOODefineRenameMethodObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { int isInstanceRenameMethod = (clientData != NULL); Object *oPtr; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "oldName newName"); return TCL_ERROR; } oPtr = (Object *) TclOOGetDefineCmdContext(interp); if (oPtr == NULL) { return TCL_ERROR; } if (!isInstanceRenameMethod && !oPtr->classPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "attempt to misuse API", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return TCL_ERROR; } /* * Delete the method entry from the appropriate hash table, and transfer * the thing it points to to its new entry. To do this, we first need to * get the entries from the appropriate hash tables (this can generate a * range of errors...) */ if (RenameDeleteMethod(interp, oPtr, !isInstanceRenameMethod, objv[1], objv[2]) != TCL_OK) { return TCL_ERROR; } if (isInstanceRenameMethod) { BumpInstanceEpoch(oPtr); } else { BumpGlobalEpoch(interp, oPtr->classPtr); } return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOODefineUnexportObjCmd -- * * Implementation of the "unexport" subcommand of the "oo::define" and * "oo::objdefine" commands. * * ---------------------------------------------------------------------- */ int TclOODefineUnexportObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { int isInstanceUnexport = (clientData != NULL); Object *oPtr; Method *mPtr; Tcl_HashEntry *hPtr; Class *clsPtr; int i, isNew, changed = 0; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "name ?name ...?"); return TCL_ERROR; } oPtr = (Object *) TclOOGetDefineCmdContext(interp); if (oPtr == NULL) { return TCL_ERROR; } clsPtr = oPtr->classPtr; if (!isInstanceUnexport && !clsPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "attempt to misuse API", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return TCL_ERROR; } for (i = 1; i < objc; i++) { /* * Unexporting is done by removing the PUBLIC_METHOD flag from the * method record. If there is no such method in this object or class * (i.e. the method comes from something inherited from or that we're * an instance of) then we put in a blank record without that flag; * such records are skipped over by the call chain engine *except* for * their flags member. */ if (isInstanceUnexport) { if (!oPtr->methodsPtr) { oPtr->methodsPtr = (Tcl_HashTable *) Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitObjHashTable(oPtr->methodsPtr); oPtr->flags &= ~USE_CLASS_CACHE; } hPtr = Tcl_CreateHashEntry(oPtr->methodsPtr, objv[i], &isNew); } else { hPtr = Tcl_CreateHashEntry(&clsPtr->classMethods, objv[i], &isNew); } if (isNew) { mPtr = (Method *) Tcl_Alloc(sizeof(Method)); memset(mPtr, 0, sizeof(Method)); mPtr->refCount = 1; mPtr->namePtr = objv[i]; Tcl_IncrRefCount(objv[i]); Tcl_SetHashValue(hPtr, mPtr); } else { mPtr = (Method *) Tcl_GetHashValue(hPtr); } if (isNew || mPtr->flags & (PUBLIC_METHOD | TRUE_PRIVATE_METHOD)) { mPtr->flags &= ~(PUBLIC_METHOD | TRUE_PRIVATE_METHOD); changed = 1; } } /* * Bump the right epoch if we actually changed anything. */ if (changed) { if (isInstanceUnexport) { BumpInstanceEpoch(oPtr); } else { BumpGlobalEpoch(interp, clsPtr); } } return TCL_OK; } /* * ---------------------------------------------------------------------- * * Tcl_ClassSetConstructor, Tcl_ClassSetDestructor -- * * How to install a constructor or destructor into a class; API to call * from C. * * ---------------------------------------------------------------------- */ void Tcl_ClassSetConstructor( Tcl_Interp *interp, Tcl_Class clazz, Tcl_Method method) { Class *clsPtr = (Class *) clazz; if (method != (Tcl_Method) clsPtr->constructorPtr) { TclOODelMethodRef(clsPtr->constructorPtr); clsPtr->constructorPtr = (Method *) method; /* * Remember to invalidate the cached constructor chain for this class. * [Bug 2531577] */ if (clsPtr->constructorChainPtr) { TclOODeleteChain(clsPtr->constructorChainPtr); clsPtr->constructorChainPtr = NULL; } BumpGlobalEpoch(interp, clsPtr); } } void Tcl_ClassSetDestructor( Tcl_Interp *interp, Tcl_Class clazz, Tcl_Method method) { Class *clsPtr = (Class *) clazz; if (method != (Tcl_Method) clsPtr->destructorPtr) { TclOODelMethodRef(clsPtr->destructorPtr); clsPtr->destructorPtr = (Method *) method; if (clsPtr->destructorChainPtr) { TclOODeleteChain(clsPtr->destructorChainPtr); clsPtr->destructorChainPtr = NULL; } BumpGlobalEpoch(interp, clsPtr); } } /* * ---------------------------------------------------------------------- * * TclOODefineSlots -- * * Create the "::oo::Slot" class and its standard instances. Class * definition is empty at the stage (added by scripting). * * ---------------------------------------------------------------------- */ int TclOODefineSlots( Foundation *fPtr) { const DeclaredSlot *slotInfoPtr; Tcl_Interp *interp = fPtr->interp; Tcl_Obj *getName, *setName, *resolveName; Tcl_Object object = Tcl_NewObjectInstance(interp, (Tcl_Class) fPtr->classCls, "::oo::Slot", NULL, TCL_INDEX_NONE, NULL, 0); Class *slotCls; if (object == NULL) { return TCL_ERROR; } slotCls = ((Object *) object)->classPtr; if (slotCls == NULL) { return TCL_ERROR; } TclNewLiteralStringObj(getName, "Get"); TclNewLiteralStringObj(setName, "Set"); TclNewLiteralStringObj(resolveName, "Resolve"); for (slotInfoPtr = slots ; slotInfoPtr->name ; slotInfoPtr++) { Tcl_Object slotObject = Tcl_NewObjectInstance(interp, (Tcl_Class) slotCls, slotInfoPtr->name, NULL, TCL_INDEX_NONE, NULL, 0); if (slotObject == NULL) { continue; } TclNewInstanceMethod(interp, slotObject, getName, 0, &slotInfoPtr->getterType, NULL); TclNewInstanceMethod(interp, slotObject, setName, 0, &slotInfoPtr->setterType, NULL); if (slotInfoPtr->resolverType.callProc) { TclNewInstanceMethod(interp, slotObject, resolveName, 0, &slotInfoPtr->resolverType, NULL); } } Tcl_BounceRefCount(getName); Tcl_BounceRefCount(setName); Tcl_BounceRefCount(resolveName); return TCL_OK; } /* * ---------------------------------------------------------------------- * * ClassFilter_Get, ClassFilter_Set -- * * Implementation of the "filter" slot accessors of the "oo::define" * command. * * ---------------------------------------------------------------------- */ static int ClassFilter_Get( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Class *clsPtr = TclOOGetClassDefineCmdContext(interp); Tcl_Obj *resultObj, *filterObj; Tcl_Size i; if (clsPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, NULL); return TCL_ERROR; } TclNewObj(resultObj); FOREACH(filterObj, clsPtr->filters) { Tcl_ListObjAppendElement(NULL, resultObj, filterObj); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } static int ClassFilter_Set( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Class *clsPtr = TclOOGetClassDefineCmdContext(interp); Tcl_Size filterc; Tcl_Obj **filterv; if (clsPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "filterList"); return TCL_ERROR; } objv += Tcl_ObjectContextSkippedArgs(context); if (TclListObjGetElements(interp, objv[0], &filterc, &filterv) != TCL_OK) { return TCL_ERROR; } TclOOClassSetFilters(interp, clsPtr, filterc, filterv); return TCL_OK; } /* * ---------------------------------------------------------------------- * * ClassMixin_Get, ClassMixin_Set -- * * Implementation of the "mixin" slot accessors of the "oo::define" * command. * * ---------------------------------------------------------------------- */ static int ClassMixin_Get( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Class *clsPtr = TclOOGetClassDefineCmdContext(interp); Tcl_Obj *resultObj; Class *mixinPtr; Tcl_Size i; if (clsPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, NULL); return TCL_ERROR; } TclNewObj(resultObj); FOREACH(mixinPtr, clsPtr->mixins) { Tcl_ListObjAppendElement(NULL, resultObj, TclOOObjectName(interp, mixinPtr->thisPtr)); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } static int ClassMixin_Set( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Class *clsPtr = TclOOGetClassDefineCmdContext(interp); Tcl_Size mixinc, i; Tcl_Obj **mixinv; Class **mixins; /* The references to the classes to actually * install. */ Tcl_HashTable uniqueCheck; /* Note that this hash table is just used as a * set of class references; it has no payload * values and keys are always pointers. */ int isNew; if (clsPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "mixinList"); return TCL_ERROR; } objv += Tcl_ObjectContextSkippedArgs(context); if (TclListObjGetElements(interp, objv[0], &mixinc, &mixinv) != TCL_OK) { return TCL_ERROR; } mixins = (Class **) TclStackAlloc(interp, sizeof(Class *) * mixinc); Tcl_InitHashTable(&uniqueCheck, TCL_ONE_WORD_KEYS); for (i = 0; i < mixinc; i++) { mixins[i] = GetClassInOuterContext(interp, mixinv[i], "may only mix in classes"); if (mixins[i] == NULL) { i--; goto freeAndError; } (void) Tcl_CreateHashEntry(&uniqueCheck, (void *) mixins[i], &isNew); if (!isNew) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "class should only be a direct mixin once", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "REPETITIOUS", (char *)NULL); goto freeAndError; } if (TclOOIsReachable(clsPtr, mixins[i])) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "may not mix a class into itself", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "SELF_MIXIN", (char *)NULL); goto freeAndError; } } TclOOClassSetMixins(interp, clsPtr, mixinc, mixins); Tcl_DeleteHashTable(&uniqueCheck); TclStackFree(interp, mixins); return TCL_OK; freeAndError: Tcl_DeleteHashTable(&uniqueCheck); TclStackFree(interp, mixins); return TCL_ERROR; } /* * ---------------------------------------------------------------------- * * ClassSuper_Get, ClassSuper_Set -- * * Implementation of the "superclass" slot accessors of the "oo::define" * command. * * ---------------------------------------------------------------------- */ static int ClassSuper_Get( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Class *clsPtr = TclOOGetClassDefineCmdContext(interp); Tcl_Obj *resultObj; Class *superPtr; Tcl_Size i; if (clsPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, NULL); return TCL_ERROR; } TclNewObj(resultObj); FOREACH(superPtr, clsPtr->superclasses) { Tcl_ListObjAppendElement(NULL, resultObj, TclOOObjectName(interp, superPtr->thisPtr)); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } static int ClassSuper_Set( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Class *clsPtr = TclOOGetClassDefineCmdContext(interp); Tcl_Size superc, j; Tcl_Size i; Tcl_Obj **superv; Class **superclasses, *superPtr; if (clsPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "superclassList"); return TCL_ERROR; } objv += Tcl_ObjectContextSkippedArgs(context); Foundation *fPtr = clsPtr->thisPtr->fPtr; if (clsPtr == fPtr->objectCls) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "may not modify the superclass of the root object", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return TCL_ERROR; } else if (TclListObjGetElements(interp, objv[0], &superc, &superv) != TCL_OK) { return TCL_ERROR; } /* * Allocate some working space. */ superclasses = (Class **) Tcl_Alloc(sizeof(Class *) * superc); /* * Parse the arguments to get the class to use as superclasses. * * Note that zero classes is special, as it is equivalent to just the * class of objects. [Bug 9d61624b3d] */ if (superc == 0) { superclasses = (Class **) Tcl_Realloc(superclasses, sizeof(Class *)); if (TclOOIsReachable(fPtr->classCls, clsPtr)) { superclasses[0] = fPtr->classCls; } else { superclasses[0] = fPtr->objectCls; } superc = 1; AddRef(superclasses[0]->thisPtr); } else { for (i = 0; i < superc; i++) { superclasses[i] = GetClassInOuterContext(interp, superv[i], "only a class can be a superclass"); if (superclasses[i] == NULL) { goto failedAfterAlloc; } for (j = 0; j < i; j++) { if (superclasses[j] == superclasses[i]) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "class should only be a direct superclass once", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "REPETITIOUS",(char *)NULL); goto failedAfterAlloc; } } if (TclOOIsReachable(clsPtr, superclasses[i])) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "attempt to form circular dependency graph", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "CIRCULARITY", (char *)NULL); failedAfterAlloc: for (; i-- > 0 ;) { TclOODecrRefCount(superclasses[i]->thisPtr); } Tcl_Free(superclasses); return TCL_ERROR; } /* * Corresponding TclOODecrRefCount() is near the end of this * function. */ AddRef(superclasses[i]->thisPtr); } } /* * Install the list of superclasses into the class. Note that this also * involves splicing the class out of the superclasses' subclass list that * it used to be a member of and splicing it into the new superclasses' * subclass list. */ if (clsPtr->superclasses.num != 0) { FOREACH(superPtr, clsPtr->superclasses) { TclOORemoveFromSubclasses(clsPtr, superPtr); TclOODecrRefCount(superPtr->thisPtr); } Tcl_Free(clsPtr->superclasses.list); } clsPtr->superclasses.list = superclasses; clsPtr->superclasses.num = superc; FOREACH(superPtr, clsPtr->superclasses) { TclOOAddToSubclasses(clsPtr, superPtr); } BumpGlobalEpoch(interp, clsPtr); return TCL_OK; } /* * ---------------------------------------------------------------------- * * ClassVars_Get, ClassVars_Set -- * * Implementation of the "variable" slot accessors of the "oo::define" * command. * * ---------------------------------------------------------------------- */ static int ClassVars_Get( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Class *clsPtr = TclOOGetClassDefineCmdContext(interp); Tcl_Obj *resultObj; Tcl_Size i; if (clsPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, NULL); return TCL_ERROR; } TclNewObj(resultObj); if (IsPrivateDefine(interp)) { PrivateVariableMapping *privatePtr; FOREACH_STRUCT(privatePtr, clsPtr->privateVariables) { Tcl_ListObjAppendElement(NULL, resultObj, privatePtr->variableObj); } } else { Tcl_Obj *variableObj; FOREACH(variableObj, clsPtr->variables) { Tcl_ListObjAppendElement(NULL, resultObj, variableObj); } } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } static int ClassVars_Set( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Class *clsPtr = TclOOGetClassDefineCmdContext(interp); Tcl_Size i; Tcl_Size varc; Tcl_Obj **varv; if (clsPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "filterList"); return TCL_ERROR; } objv += Tcl_ObjectContextSkippedArgs(context); if (TclListObjGetElements(interp, objv[0], &varc, &varv) != TCL_OK) { return TCL_ERROR; } for (i = 0; i < varc; i++) { const char *varName = TclGetString(varv[i]); if (strstr(varName, "::") != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid declared variable name \"%s\": must not %s", varName, "contain namespace separators")); Tcl_SetErrorCode(interp, "TCL", "OO", "BAD_DECLVAR", (char *)NULL); return TCL_ERROR; } if (Tcl_StringMatch(varName, "*(*)")) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid declared variable name \"%s\": must not %s", varName, "refer to an array element")); Tcl_SetErrorCode(interp, "TCL", "OO", "BAD_DECLVAR", (char *)NULL); return TCL_ERROR; } } if (IsPrivateDefine(interp)) { InstallPrivateVariableMapping(&clsPtr->privateVariables, varc, varv, clsPtr->thisPtr->creationEpoch); } else { InstallStandardVariableMapping(&clsPtr->variables, varc, varv); } return TCL_OK; } /* * ---------------------------------------------------------------------- * * ObjFilter_Get, ObjFilter_Set -- * * Implementation of the "filter" slot accessors of the "oo::objdefine" * command. * * ---------------------------------------------------------------------- */ static int ObjFilter_Get( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); Tcl_Obj *resultObj, *filterObj; Tcl_Size i; if (Tcl_ObjectContextSkippedArgs(context) != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, NULL); return TCL_ERROR; } else if (oPtr == NULL) { return TCL_ERROR; } TclNewObj(resultObj); FOREACH(filterObj, oPtr->filters) { Tcl_ListObjAppendElement(NULL, resultObj, filterObj); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } static int ObjFilter_Set( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); Tcl_Size filterc; Tcl_Obj **filterv; if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "filterList"); return TCL_ERROR; } else if (oPtr == NULL) { return TCL_ERROR; } objv += Tcl_ObjectContextSkippedArgs(context); if (TclListObjGetElements(interp, objv[0], &filterc, &filterv) != TCL_OK) { return TCL_ERROR; } TclOOObjectSetFilters(oPtr, filterc, filterv); return TCL_OK; } /* * ---------------------------------------------------------------------- * * ObjMixin_Get, ObjMixin_Set -- * * Implementation of the "mixin" slot accessors of the "oo::objdefine" * command. * * ---------------------------------------------------------------------- */ static int ObjMixin_Get( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); Tcl_Obj *resultObj; Class *mixinPtr; Tcl_Size i; if (Tcl_ObjectContextSkippedArgs(context) != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, NULL); return TCL_ERROR; } else if (oPtr == NULL) { return TCL_ERROR; } TclNewObj(resultObj); FOREACH(mixinPtr, oPtr->mixins) { if (mixinPtr) { Tcl_ListObjAppendElement(NULL, resultObj, TclOOObjectName(interp, mixinPtr->thisPtr)); } } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } static int ObjMixin_Set( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); Tcl_Size mixinc, i; Tcl_Obj **mixinv; Class **mixins; /* The references to the classes to actually * install. */ Tcl_HashTable uniqueCheck; /* Note that this hash table is just used as a * set of class references; it has no payload * values and keys are always pointers. */ int isNew; if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "mixinList"); return TCL_ERROR; } else if (oPtr == NULL) { return TCL_ERROR; } objv += Tcl_ObjectContextSkippedArgs(context); if (TclListObjGetElements(interp, objv[0], &mixinc, &mixinv) != TCL_OK) { return TCL_ERROR; } mixins = (Class **) TclStackAlloc(interp, sizeof(Class *) * mixinc); Tcl_InitHashTable(&uniqueCheck, TCL_ONE_WORD_KEYS); for (i = 0; i < mixinc; i++) { mixins[i] = GetClassInOuterContext(interp, mixinv[i], "may only mix in classes"); if (mixins[i] == NULL) { goto freeAndError; } (void) Tcl_CreateHashEntry(&uniqueCheck, (void *) mixins[i], &isNew); if (!isNew) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "class should only be a direct mixin once", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "REPETITIOUS", (char *)NULL); goto freeAndError; } } TclOOObjectSetMixins(oPtr, mixinc, mixins); TclStackFree(interp, mixins); Tcl_DeleteHashTable(&uniqueCheck); return TCL_OK; freeAndError: TclStackFree(interp, mixins); Tcl_DeleteHashTable(&uniqueCheck); return TCL_ERROR; } /* * ---------------------------------------------------------------------- * * ObjVars_Get, ObjVars_Set -- * * Implementation of the "variable" slot accessors of the "oo::objdefine" * command. * * ---------------------------------------------------------------------- */ static int ObjVars_Get( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); Tcl_Obj *resultObj; Tcl_Size i; if (Tcl_ObjectContextSkippedArgs(context) != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, NULL); return TCL_ERROR; } else if (oPtr == NULL) { return TCL_ERROR; } TclNewObj(resultObj); if (IsPrivateDefine(interp)) { PrivateVariableMapping *privatePtr; FOREACH_STRUCT(privatePtr, oPtr->privateVariables) { Tcl_ListObjAppendElement(NULL, resultObj, privatePtr->variableObj); } } else { Tcl_Obj *variableObj; FOREACH(variableObj, oPtr->variables) { Tcl_ListObjAppendElement(NULL, resultObj, variableObj); } } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } static int ObjVars_Set( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); Tcl_Size varc, i; Tcl_Obj **varv; if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "variableList"); return TCL_ERROR; } else if (oPtr == NULL) { return TCL_ERROR; } objv += Tcl_ObjectContextSkippedArgs(context); if (TclListObjGetElements(interp, objv[0], &varc, &varv) != TCL_OK) { return TCL_ERROR; } for (i = 0; i < varc; i++) { const char *varName = TclGetString(varv[i]); if (strstr(varName, "::") != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid declared variable name \"%s\": must not %s", varName, "contain namespace separators")); Tcl_SetErrorCode(interp, "TCL", "OO", "BAD_DECLVAR", (char *)NULL); return TCL_ERROR; } if (Tcl_StringMatch(varName, "*(*)")) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid declared variable name \"%s\": must not %s", varName, "refer to an array element")); Tcl_SetErrorCode(interp, "TCL", "OO", "BAD_DECLVAR", (char *)NULL); return TCL_ERROR; } } if (IsPrivateDefine(interp)) { InstallPrivateVariableMapping(&oPtr->privateVariables, varc, varv, oPtr->creationEpoch); } else { InstallStandardVariableMapping(&oPtr->variables, varc, varv); } return TCL_OK; } /* * ---------------------------------------------------------------------- * * ResolveClass -- * * Implementation of the "Resolve" support method for some slots (those * that are slots around a list of classes). This resolves possible class * names to their fully-qualified names if possible. * * ---------------------------------------------------------------------- */ static int ResolveClass( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { int idx = Tcl_ObjectContextSkippedArgs(context); Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); Class *clsPtr; /* * Check if were called wrongly. The definition context isn't used... * except that GetClassInOuterContext() assumes that it is there. */ if (oPtr == NULL) { return TCL_ERROR; } else if (objc != idx + 1) { Tcl_WrongNumArgs(interp, idx, objv, "slotElement"); return TCL_ERROR; } /* * Resolve the class if possible. If not, remove any resolution error and * return what we've got anyway as the failure might not be fatal overall. */ clsPtr = GetClassInOuterContext(interp, objv[idx], "USER SHOULD NOT SEE THIS MESSAGE"); if (clsPtr == NULL) { Tcl_ResetResult(interp); Tcl_SetObjResult(interp, objv[idx]); } else { Tcl_SetObjResult(interp, TclOOObjectName(interp, clsPtr->thisPtr)); } return TCL_OK; } /* * ---------------------------------------------------------------------- * * Configurable_ClassReadableProps_Get, Configurable_ClassReadableProps_Set, * Configurable_ObjectReadableProps_Get, Configurable_ObjectReadableProps_Set -- * * Implementations of the "readableproperties" slot accessors for classes * and instances. * * ---------------------------------------------------------------------- */ static int Configurable_ClassReadableProps_Get( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Class *clsPtr = TclOOGetClassDefineCmdContext(interp); if (clsPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, TclOOGetPropertyList(&clsPtr->properties.readable)); return TCL_OK; } static int Configurable_ClassReadableProps_Set( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Class *clsPtr = TclOOGetClassDefineCmdContext(interp); Tcl_Size varc; Tcl_Obj **varv; if (clsPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "filterList"); return TCL_ERROR; } objv += Tcl_ObjectContextSkippedArgs(context); if (TclListObjGetElements(interp, objv[0], &varc, &varv) != TCL_OK) { return TCL_ERROR; } TclOOInstallReadableProperties(&clsPtr->properties, varc, varv); BumpGlobalEpoch(interp, clsPtr); return TCL_OK; } static int Configurable_ObjectReadableProps_Get( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); if (oPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, TclOOGetPropertyList(&oPtr->properties.readable)); return TCL_OK; } static int Configurable_ObjectReadableProps_Set( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); Tcl_Size varc; Tcl_Obj **varv; if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "filterList"); return TCL_ERROR; } objv += Tcl_ObjectContextSkippedArgs(context); if (oPtr == NULL) { return TCL_ERROR; } else if (TclListObjGetElements(interp, objv[0], &varc, &varv) != TCL_OK) { return TCL_ERROR; } TclOOInstallReadableProperties(&oPtr->properties, varc, varv); return TCL_OK; } /* * ---------------------------------------------------------------------- * * Configurable_ClassWritableProps_Get, Configurable_ClassWritableProps_Set, * Configurable_ObjectWritableProps_Get, Configurable_ObjectWritableProps_Set -- * * Implementations of the "writableproperties" slot accessors for classes * and instances. * * ---------------------------------------------------------------------- */ static int Configurable_ClassWritableProps_Get( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Class *clsPtr = TclOOGetClassDefineCmdContext(interp); if (clsPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, TclOOGetPropertyList(&clsPtr->properties.writable)); return TCL_OK; } static int Configurable_ClassWritableProps_Set( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Class *clsPtr = TclOOGetClassDefineCmdContext(interp); Tcl_Size varc; Tcl_Obj **varv; if (clsPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "propertyList"); return TCL_ERROR; } objv += Tcl_ObjectContextSkippedArgs(context); if (TclListObjGetElements(interp, objv[0], &varc, &varv) != TCL_OK) { return TCL_ERROR; } TclOOInstallWritableProperties(&clsPtr->properties, varc, varv); BumpGlobalEpoch(interp, clsPtr); return TCL_OK; } static int Configurable_ObjectWritableProps_Get( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); if (oPtr == NULL) { return TCL_ERROR; } else if (Tcl_ObjectContextSkippedArgs(context) != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, TclOOGetPropertyList(&oPtr->properties.writable)); return TCL_OK; } static int Configurable_ObjectWritableProps_Set( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv) { Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); Tcl_Size varc; Tcl_Obj **varv; if (Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "propertyList"); return TCL_ERROR; } objv += Tcl_ObjectContextSkippedArgs(context); if (oPtr == NULL) { return TCL_ERROR; } else if (TclListObjGetElements(interp, objv[0], &varc, &varv) != TCL_OK) { return TCL_ERROR; } TclOOInstallWritableProperties(&oPtr->properties, varc, varv); return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOORegisterProperty, TclOORegisterInstanceProperty -- * * Helpers to add or remove a name from the property slots of a class or * instance. * * BuildPropertyList -- * * Helper for the helpers. Scans a property list and does the filtering * or adding of the property to add or remove * * ---------------------------------------------------------------------- */ static int BuildPropertyList( PropertyList *propsList, /* Property list to scan. */ Tcl_Obj *propName, /* Property to add/remove. */ int addingProp, /* True if we're adding, false if removing. */ Tcl_Obj *listObj) /* The list of property names we're building */ { int present = 0, changed = 0, i; Tcl_Obj *other; Tcl_SetListObj(listObj, 0, NULL); FOREACH(other, *propsList) { if (!TclStringCmp(propName, other, 1, 0, TCL_INDEX_NONE)) { present = 1; if (!addingProp) { changed = 1; continue; } } Tcl_ListObjAppendElement(NULL, listObj, other); } if (!present && addingProp) { Tcl_ListObjAppendElement(NULL, listObj, propName); changed = 1; } return changed; } void TclOORegisterInstanceProperty( Object *oPtr, /* Object that owns the property slots. */ Tcl_Obj *propName, /* Property to add/remove. Must include the * hyphen if one is desired; this is the value * that is actually placed in the slot. */ int registerReader, /* True if we're adding the property name to * the readable property slot. False if we're * removing the property name from the slot. */ int registerWriter) /* True if we're adding the property name to * the writable property slot. False if we're * removing the property name from the slot. */ { Tcl_Obj *listObj = Tcl_NewObj(); /* Working buffer. */ Tcl_Obj **objv; Tcl_Size count; if (BuildPropertyList(&oPtr->properties.readable, propName, registerReader, listObj)) { TclListObjGetElements(NULL, listObj, &count, &objv); TclOOInstallReadableProperties(&oPtr->properties, count, objv); } if (BuildPropertyList(&oPtr->properties.writable, propName, registerWriter, listObj)) { TclListObjGetElements(NULL, listObj, &count, &objv); TclOOInstallWritableProperties(&oPtr->properties, count, objv); } Tcl_BounceRefCount(listObj); } void TclOORegisterProperty( Class *clsPtr, /* Class that owns the property slots. */ Tcl_Obj *propName, /* Property to add/remove. Must include the * hyphen if one is desired; this is the value * that is actually placed in the slot. */ int registerReader, /* True if we're adding the property name to * the readable property slot. False if we're * removing the property name from the slot. */ int registerWriter) /* True if we're adding the property name to * the writable property slot. False if we're * removing the property name from the slot. */ { Tcl_Obj *listObj = Tcl_NewObj(); /* Working buffer. */ Tcl_Obj **objv; Tcl_Size count; int changed = 0; if (BuildPropertyList(&clsPtr->properties.readable, propName, registerReader, listObj)) { TclListObjGetElements(NULL, listObj, &count, &objv); TclOOInstallReadableProperties(&clsPtr->properties, count, objv); changed = 1; } if (BuildPropertyList(&clsPtr->properties.writable, propName, registerWriter, listObj)) { TclListObjGetElements(NULL, listObj, &count, &objv); TclOOInstallWritableProperties(&clsPtr->properties, count, objv); changed = 1; } Tcl_BounceRefCount(listObj); if (changed) { BumpGlobalEpoch(clsPtr->thisPtr->fPtr->interp, clsPtr); } } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclOOInfo.c0000644000175000017500000012773714726623136015213 0ustar sergeisergei/* * tclOODefineCmds.c -- * * This file contains the implementation of the ::oo-related [info] * subcommands. * * Copyright © 2006-2019 Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "tclInt.h" #include "tclOOInt.h" static Tcl_ObjCmdProc InfoObjectCallCmd; static Tcl_ObjCmdProc InfoObjectClassCmd; static Tcl_ObjCmdProc InfoObjectDefnCmd; static Tcl_ObjCmdProc InfoObjectFiltersCmd; static Tcl_ObjCmdProc InfoObjectForwardCmd; static Tcl_ObjCmdProc InfoObjectIdCmd; static Tcl_ObjCmdProc InfoObjectIsACmd; static Tcl_ObjCmdProc InfoObjectMethodsCmd; static Tcl_ObjCmdProc InfoObjectMethodTypeCmd; static Tcl_ObjCmdProc InfoObjectMixinsCmd; static Tcl_ObjCmdProc InfoObjectNsCmd; static Tcl_ObjCmdProc InfoObjectVarsCmd; static Tcl_ObjCmdProc InfoObjectVariablesCmd; static Tcl_ObjCmdProc InfoClassCallCmd; static Tcl_ObjCmdProc InfoClassConstrCmd; static Tcl_ObjCmdProc InfoClassDefnCmd; static Tcl_ObjCmdProc InfoClassDefnNsCmd; static Tcl_ObjCmdProc InfoClassDestrCmd; static Tcl_ObjCmdProc InfoClassFiltersCmd; static Tcl_ObjCmdProc InfoClassForwardCmd; static Tcl_ObjCmdProc InfoClassInstancesCmd; static Tcl_ObjCmdProc InfoClassMethodsCmd; static Tcl_ObjCmdProc InfoClassMethodTypeCmd; static Tcl_ObjCmdProc InfoClassMixinsCmd; static Tcl_ObjCmdProc InfoClassSubsCmd; static Tcl_ObjCmdProc InfoClassSupersCmd; static Tcl_ObjCmdProc InfoClassVariablesCmd; /* * List of commands that are used to implement the [info object] subcommands. */ static const EnsembleImplMap infoObjectCmds[] = { {"call", InfoObjectCallCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"class", InfoObjectClassCmd, TclCompileInfoObjectClassCmd, NULL, NULL, 0}, {"creationid", InfoObjectIdCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"definition", InfoObjectDefnCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"filters", InfoObjectFiltersCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"forward", InfoObjectForwardCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"isa", InfoObjectIsACmd, TclCompileInfoObjectIsACmd, NULL, NULL, 0}, {"methods", InfoObjectMethodsCmd, TclCompileBasicMin1ArgCmd, NULL, NULL, 0}, {"methodtype", InfoObjectMethodTypeCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"mixins", InfoObjectMixinsCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"namespace", InfoObjectNsCmd, TclCompileInfoObjectNamespaceCmd, NULL, NULL, 0}, {"properties", TclOOInfoObjectPropCmd, TclCompileBasicMin1ArgCmd, NULL, NULL, 0}, {"variables", InfoObjectVariablesCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, {"vars", InfoObjectVarsCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, {NULL, NULL, NULL, NULL, NULL, 0} }; /* * List of commands that are used to implement the [info class] subcommands. */ static const EnsembleImplMap infoClassCmds[] = { {"call", InfoClassCallCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"constructor", InfoClassConstrCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"definition", InfoClassDefnCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"definitionnamespace", InfoClassDefnNsCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, {"destructor", InfoClassDestrCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"filters", InfoClassFiltersCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"forward", InfoClassForwardCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"instances", InfoClassInstancesCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, {"methods", InfoClassMethodsCmd, TclCompileBasicMin1ArgCmd, NULL, NULL, 0}, {"methodtype", InfoClassMethodTypeCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"mixins", InfoClassMixinsCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"properties", TclOOInfoClassPropCmd, TclCompileBasicMin1ArgCmd, NULL, NULL, 0}, {"subclasses", InfoClassSubsCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, {"superclasses", InfoClassSupersCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"variables", InfoClassVariablesCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, {NULL, NULL, NULL, NULL, NULL, 0} }; /* * ---------------------------------------------------------------------- * * LocalVarName -- * * Get the name of a local variable (especially a method argument) as a * Tcl value. * * ---------------------------------------------------------------------- */ static inline Tcl_Obj * LocalVarName( CompiledLocal *localPtr) { return Tcl_NewStringObj(localPtr->name, TCL_AUTO_LENGTH); } /* * ---------------------------------------------------------------------- * * TclOOInitInfo -- * * Adjusts the Tcl core [info] command to contain subcommands ("object" * and "class") for introspection of objects and classes. * * ---------------------------------------------------------------------- */ void TclOOInitInfo( Tcl_Interp *interp) { Tcl_Command infoCmd; Tcl_Obj *mapDict; /* * Build the ensembles used to implement [info object] and [info class]. */ TclMakeEnsemble(interp, "::oo::InfoObject", infoObjectCmds); TclMakeEnsemble(interp, "::oo::InfoClass", infoClassCmds); /* * Install into the [info] ensemble. */ infoCmd = Tcl_FindCommand(interp, "info", NULL, TCL_GLOBAL_ONLY); if (infoCmd) { Tcl_GetEnsembleMappingDict(NULL, infoCmd, &mapDict); TclDictPutString(NULL, mapDict, "object", "::oo::InfoObject"); TclDictPutString(NULL, mapDict, "class", "::oo::InfoClass"); Tcl_SetEnsembleMappingDict(interp, infoCmd, mapDict); } } /* * ---------------------------------------------------------------------- * * TclOOGetClassFromObj -- * * How to correctly get a class from a Tcl_Obj. Just a wrapper round * Tcl_GetObjectFromObj, but this is an idiom that was used heavily. * * ---------------------------------------------------------------------- */ Class * TclOOGetClassFromObj( Tcl_Interp *interp, Tcl_Obj *objPtr) { Object *oPtr = (Object *) Tcl_GetObjectFromObj(interp, objPtr); if (oPtr == NULL) { return NULL; } if (oPtr->classPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"%s\" is not a class", TclGetString(objPtr))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CLASS", TclGetString(objPtr), (char *)NULL); return NULL; } return oPtr->classPtr; } /* * ---------------------------------------------------------------------- * * InfoObjectClassCmd -- * * Implements [info object class $objName ?$className?] * * ---------------------------------------------------------------------- */ static int InfoObjectClassCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Object *oPtr; if (objc != 2 && objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "objName ?className?"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } if (objc == 2) { Tcl_SetObjResult(interp, TclOOObjectName(interp, oPtr->selfCls->thisPtr)); return TCL_OK; } else { Class *mixinPtr, *o2clsPtr; Tcl_Size i; o2clsPtr = TclOOGetClassFromObj(interp, objv[2]); if (o2clsPtr == NULL) { return TCL_ERROR; } FOREACH(mixinPtr, oPtr->mixins) { if (!mixinPtr) { continue; } if (TclOOIsReachable(o2clsPtr, mixinPtr)) { Tcl_SetObjResult(interp, Tcl_NewBooleanObj(1)); return TCL_OK; } } Tcl_SetObjResult(interp, Tcl_NewBooleanObj( TclOOIsReachable(o2clsPtr, oPtr->selfCls))); return TCL_OK; } } /* * ---------------------------------------------------------------------- * * InfoObjectDefnCmd -- * * Implements [info object definition $objName $methodName] * * ---------------------------------------------------------------------- */ static int InfoObjectDefnCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Object *oPtr; Tcl_HashEntry *hPtr; Proc *procPtr; CompiledLocal *localPtr; Tcl_Obj *resultObjs[2]; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "objName methodName"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } if (!oPtr->methodsPtr) { goto unknownMethod; } hPtr = Tcl_FindHashEntry(oPtr->methodsPtr, objv[2]); if (hPtr == NULL) { goto unknownMethod; } procPtr = TclOOGetProcFromMethod((Method *) Tcl_GetHashValue(hPtr)); if (procPtr == NULL) { goto wrongType; } /* * We now have the method to describe the definition of. */ TclNewObj(resultObjs[0]); for (localPtr=procPtr->firstLocalPtr; localPtr!=NULL; localPtr=localPtr->nextPtr) { if (TclIsVarArgument(localPtr)) { Tcl_Obj *argObj; TclNewObj(argObj); Tcl_ListObjAppendElement(NULL, argObj, LocalVarName(localPtr)); if (localPtr->defValuePtr != NULL) { Tcl_ListObjAppendElement(NULL, argObj, localPtr->defValuePtr); } Tcl_ListObjAppendElement(NULL, resultObjs[0], argObj); } } resultObjs[1] = TclOOGetMethodBody((Method *) Tcl_GetHashValue(hPtr)); Tcl_SetObjResult(interp, Tcl_NewListObj(2, resultObjs)); return TCL_OK; /* * Errors... */ unknownMethod: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown method \"%s\"", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; wrongType: Tcl_SetObjResult(interp, Tcl_NewStringObj( "definition not available for this kind of method", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; } /* * ---------------------------------------------------------------------- * * InfoObjectFiltersCmd -- * * Implements [info object filters $objName] * * ---------------------------------------------------------------------- */ static int InfoObjectFiltersCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Size i; Tcl_Obj *filterObj, *resultObj; Object *oPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "objName"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } TclNewObj(resultObj); FOREACH(filterObj, oPtr->filters) { Tcl_ListObjAppendElement(NULL, resultObj, filterObj); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoObjectForwardCmd -- * * Implements [info object forward $objName $methodName] * * ---------------------------------------------------------------------- */ static int InfoObjectForwardCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Object *oPtr; Tcl_HashEntry *hPtr; Tcl_Obj *prefixObj; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "objName methodName"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } if (!oPtr->methodsPtr) { goto unknownMethod; } hPtr = Tcl_FindHashEntry(oPtr->methodsPtr, objv[2]); if (hPtr == NULL) { goto unknownMethod; } prefixObj = TclOOGetFwdFromMethod((Method *) Tcl_GetHashValue(hPtr)); if (prefixObj == NULL) { goto wrongType; } /* * Describe the valid forward method. */ Tcl_SetObjResult(interp, prefixObj); return TCL_OK; /* * Errors... */ unknownMethod: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown method \"%s\"", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; wrongType: Tcl_SetObjResult(interp, Tcl_NewStringObj( "prefix argument list not available for this kind of method", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; } /* * ---------------------------------------------------------------------- * * InfoObjectIsACmd -- * * Implements [info object isa $category $objName ...] * * ---------------------------------------------------------------------- */ static int InfoObjectIsACmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { static const char *const categories[] = { "class", "metaclass", "mixin", "object", "typeof", NULL }; enum IsACats { IsClass, IsMetaclass, IsMixin, IsObject, IsType } idx; Object *oPtr, *o2Ptr; int result = 0; Tcl_Size i; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "category objName ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], categories, "category", 0, &idx) != TCL_OK) { return TCL_ERROR; } /* * Now we know what test we are doing, we can check we've got the right * number of arguments. */ switch (idx) { case IsObject: case IsClass: case IsMetaclass: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "objName"); return TCL_ERROR; } break; case IsMixin: case IsType: if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "objName className"); return TCL_ERROR; } break; } /* * Perform the check. Note that we can guarantee that we will not fail * from here on; "failures" result in a false-TCL_OK result. */ oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[2]); if (oPtr == NULL) { goto failPrecondition; } switch (idx) { case IsObject: result = 1; break; case IsClass: result = (oPtr->classPtr != NULL); break; case IsMetaclass: if (oPtr->classPtr != NULL) { result = TclOOIsReachable(TclOOGetFoundation(interp)->classCls, oPtr->classPtr); } break; case IsMixin: o2Ptr = (Object *) Tcl_GetObjectFromObj(interp, objv[3]); if (o2Ptr == NULL) { goto failPrecondition; } if (o2Ptr->classPtr != NULL) { Class *mixinPtr; FOREACH(mixinPtr, oPtr->mixins) { if (!mixinPtr) { continue; } if (TclOOIsReachable(o2Ptr->classPtr, mixinPtr)) { result = 1; break; } } } break; case IsType: o2Ptr = (Object *) Tcl_GetObjectFromObj(interp, objv[3]); if (o2Ptr == NULL) { goto failPrecondition; } if (o2Ptr->classPtr != NULL) { result = TclOOIsReachable(o2Ptr->classPtr, oPtr->selfCls); } break; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result)); return TCL_OK; failPrecondition: Tcl_ResetResult(interp); Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0)); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoObjectMethodsCmd -- * * Implements [info object methods $objName ?$option ...?] * * ---------------------------------------------------------------------- */ static int InfoObjectMethodsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { static const char *const options[] = { "-all", "-localprivate", "-private", "-scope", NULL }; enum Options { OPT_ALL, OPT_LOCALPRIVATE, OPT_PRIVATE, OPT_SCOPE } idx; static const char *const scopes[] = { "private", "public", "unexported" }; enum Scopes { SCOPE_PRIVATE, SCOPE_PUBLIC, SCOPE_UNEXPORTED, SCOPE_LOCALPRIVATE, SCOPE_DEFAULT = -1 }; Object *oPtr; int flag = PUBLIC_METHOD, recurse = 0, scope = SCOPE_DEFAULT; FOREACH_HASH_DECLS; Tcl_Obj *namePtr, *resultObj; Method *mPtr; /* * Parse arguments. */ if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "objName ?-option value ...?"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } if (objc != 2) { int i; for (i=2 ; i= objc) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "missing option for -scope")); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", (char *)NULL); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[i], scopes, "scope", 0, &scope) != TCL_OK) { return TCL_ERROR; } break; } } } if (scope != SCOPE_DEFAULT) { recurse = 0; switch (scope) { case SCOPE_PRIVATE: flag = TRUE_PRIVATE_METHOD; break; case SCOPE_PUBLIC: flag = PUBLIC_METHOD; break; case SCOPE_LOCALPRIVATE: flag = PRIVATE_METHOD; break; case SCOPE_UNEXPORTED: flag = 0; break; } } /* * List matching methods. */ TclNewObj(resultObj); if (recurse) { const char **names; int i, numNames = TclOOGetSortedMethodList(oPtr, NULL, NULL, flag, &names); for (i=0 ; i 0) { Tcl_Free((void *)names); } } else if (oPtr->methodsPtr) { if (scope == SCOPE_DEFAULT) { /* * Handle legacy-mode matching. [Bug 36e5517a6850] */ int scopeFilter = flag | TRUE_PRIVATE_METHOD; FOREACH_HASH(namePtr, mPtr, oPtr->methodsPtr) { if (mPtr->typePtr && (mPtr->flags & scopeFilter) == flag) { Tcl_ListObjAppendElement(NULL, resultObj, namePtr); } } } else { FOREACH_HASH(namePtr, mPtr, oPtr->methodsPtr) { if (mPtr->typePtr && (mPtr->flags & SCOPE_FLAGS) == flag) { Tcl_ListObjAppendElement(NULL, resultObj, namePtr); } } } } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoObjectMethodTypeCmd -- * * Implements [info object methodtype $objName $methodName] * * ---------------------------------------------------------------------- */ static int InfoObjectMethodTypeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Object *oPtr; Tcl_HashEntry *hPtr; Method *mPtr; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "objName methodName"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } if (!oPtr->methodsPtr) { goto unknownMethod; } hPtr = Tcl_FindHashEntry(oPtr->methodsPtr, objv[2]); if (hPtr == NULL) { goto unknownMethod; } mPtr = (Method *) Tcl_GetHashValue(hPtr); if (mPtr->typePtr == NULL) { /* * Special entry for visibility control: pretend the method doesnt * exist. */ goto unknownMethod; } Tcl_SetObjResult(interp, Tcl_NewStringObj(mPtr->typePtr->name, TCL_AUTO_LENGTH)); return TCL_OK; unknownMethod: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown method \"%s\"", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; } /* * ---------------------------------------------------------------------- * * InfoObjectMixinsCmd -- * * Implements [info object mixins $objName] * * ---------------------------------------------------------------------- */ static int InfoObjectMixinsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Class *mixinPtr; Object *oPtr; Tcl_Obj *resultObj; Tcl_Size i; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "objName"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } TclNewObj(resultObj); FOREACH(mixinPtr, oPtr->mixins) { if (!mixinPtr) { continue; } Tcl_ListObjAppendElement(NULL, resultObj, TclOOObjectName(interp, mixinPtr->thisPtr)); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoObjectIdCmd -- * * Implements [info object creationid $objName] * * ---------------------------------------------------------------------- */ static int InfoObjectIdCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Object *oPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "objName"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(oPtr->creationEpoch)); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoObjectNsCmd -- * * Implements [info object namespace $objName] * * ---------------------------------------------------------------------- */ static int InfoObjectNsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Object *oPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "objName"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, TclNewNamespaceObj(oPtr->namespacePtr)); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoObjectVariablesCmd -- * * Implements [info object variables $objName ?-private?] * * ---------------------------------------------------------------------- */ static int InfoObjectVariablesCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Object *oPtr; Tcl_Obj *resultObj; Tcl_Size i; int isPrivate = 0; if (objc != 2 && objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "objName ?-private?"); return TCL_ERROR; } if (objc == 3) { if (strcmp("-private", TclGetString(objv[2])) != 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "option \"%s\" is not exactly \"-private\"", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "OO", "BAD_ARG"); return TCL_ERROR; } isPrivate = 1; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } TclNewObj(resultObj); if (isPrivate) { PrivateVariableMapping *privatePtr; FOREACH_STRUCT(privatePtr, oPtr->privateVariables) { Tcl_ListObjAppendElement(NULL, resultObj, privatePtr->variableObj); } } else { Tcl_Obj *variableObj; FOREACH(variableObj, oPtr->variables) { Tcl_ListObjAppendElement(NULL, resultObj, variableObj); } } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoObjectVarsCmd -- * * Implements [info object vars $objName ?$pattern?] * * ---------------------------------------------------------------------- */ static int InfoObjectVarsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Object *oPtr; const char *pattern = NULL; FOREACH_HASH_DECLS; VarInHash *vihPtr; Tcl_Obj *nameObj, *resultObj; if (objc != 2 && objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "objName ?pattern?"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } if (objc == 3) { pattern = TclGetString(objv[2]); } TclNewObj(resultObj); /* * Extract the information we need from the object's namespace's table of * variables. Note that this involves horrific knowledge of the guts of * tclVar.c, so we can't leverage our hash-iteration macros properly. */ FOREACH_HASH_VALUE(vihPtr, &((Namespace *) oPtr->namespacePtr)->varTable.table) { nameObj = vihPtr->entry.key.objPtr; if (TclIsVarUndefined(&vihPtr->var) || !TclIsVarNamespaceVar(&vihPtr->var)) { continue; } if (pattern != NULL && !Tcl_StringMatch(TclGetString(nameObj), pattern)) { continue; } Tcl_ListObjAppendElement(NULL, resultObj, nameObj); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoClassConstrCmd -- * * Implements [info class constructor $clsName] * * ---------------------------------------------------------------------- */ static int InfoClassConstrCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Proc *procPtr; CompiledLocal *localPtr; Tcl_Obj *resultObjs[2]; Class *clsPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "className"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } if (clsPtr->constructorPtr == NULL) { return TCL_OK; } procPtr = TclOOGetProcFromMethod(clsPtr->constructorPtr); if (procPtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "definition not available for this kind of method", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "METHOD_TYPE", (char *)NULL); return TCL_ERROR; } TclNewObj(resultObjs[0]); for (localPtr=procPtr->firstLocalPtr; localPtr!=NULL; localPtr=localPtr->nextPtr) { if (TclIsVarArgument(localPtr)) { Tcl_Obj *argObj; TclNewObj(argObj); Tcl_ListObjAppendElement(NULL, argObj, LocalVarName(localPtr)); if (localPtr->defValuePtr != NULL) { Tcl_ListObjAppendElement(NULL, argObj, localPtr->defValuePtr); } Tcl_ListObjAppendElement(NULL, resultObjs[0], argObj); } } resultObjs[1] = TclOOGetMethodBody(clsPtr->constructorPtr); Tcl_SetObjResult(interp, Tcl_NewListObj(2, resultObjs)); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoClassDefnCmd -- * * Implements [info class definition $clsName $methodName] * * ---------------------------------------------------------------------- */ static int InfoClassDefnCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_HashEntry *hPtr; Proc *procPtr; CompiledLocal *localPtr; Tcl_Obj *resultObjs[2]; Class *clsPtr; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "className methodName"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } hPtr = Tcl_FindHashEntry(&clsPtr->classMethods, objv[2]); if (hPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown method \"%s\"", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; } procPtr = TclOOGetProcFromMethod((Method *) Tcl_GetHashValue(hPtr)); if (procPtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "definition not available for this kind of method", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; } TclNewObj(resultObjs[0]); for (localPtr=procPtr->firstLocalPtr; localPtr!=NULL; localPtr=localPtr->nextPtr) { if (TclIsVarArgument(localPtr)) { Tcl_Obj *argObj; TclNewObj(argObj); Tcl_ListObjAppendElement(NULL, argObj, LocalVarName(localPtr)); if (localPtr->defValuePtr != NULL) { Tcl_ListObjAppendElement(NULL, argObj, localPtr->defValuePtr); } Tcl_ListObjAppendElement(NULL, resultObjs[0], argObj); } } resultObjs[1] = TclOOGetMethodBody((Method *) Tcl_GetHashValue(hPtr)); Tcl_SetObjResult(interp, Tcl_NewListObj(2, resultObjs)); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoClassDefnNsCmd -- * * Implements [info class definitionnamespace $clsName ?$kind?] * * ---------------------------------------------------------------------- */ static int InfoClassDefnNsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { static const char *kindList[] = { "-class", "-instance", NULL }; int kind = 0; Tcl_Obj *nsNamePtr; Class *clsPtr; if (objc != 2 && objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "className ?kind?"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } if (objc == 3 && Tcl_GetIndexFromObj(interp, objv[2], kindList, "kind", 0, &kind) != TCL_OK) { return TCL_ERROR; } if (kind) { nsNamePtr = clsPtr->objDefinitionNs; } else { nsNamePtr = clsPtr->clsDefinitionNs; } if (nsNamePtr) { Tcl_SetObjResult(interp, nsNamePtr); } return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoClassDestrCmd -- * * Implements [info class destructor $clsName] * * ---------------------------------------------------------------------- */ static int InfoClassDestrCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Proc *procPtr; Class *clsPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "className"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } if (clsPtr->destructorPtr == NULL) { return TCL_OK; } procPtr = TclOOGetProcFromMethod(clsPtr->destructorPtr); if (procPtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "definition not available for this kind of method", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "METHOD_TYPE", (char *)NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, TclOOGetMethodBody(clsPtr->destructorPtr)); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoClassFiltersCmd -- * * Implements [info class filters $clsName] * * ---------------------------------------------------------------------- */ static int InfoClassFiltersCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Size i; Tcl_Obj *filterObj, *resultObj; Class *clsPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "className"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } TclNewObj(resultObj); FOREACH(filterObj, clsPtr->filters) { Tcl_ListObjAppendElement(NULL, resultObj, filterObj); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoClassForwardCmd -- * * Implements [info class forward $clsName $methodName] * * ---------------------------------------------------------------------- */ static int InfoClassForwardCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_HashEntry *hPtr; Tcl_Obj *prefixObj; Class *clsPtr; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "className methodName"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } hPtr = Tcl_FindHashEntry(&clsPtr->classMethods, objv[2]); if (hPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown method \"%s\"", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; } prefixObj = TclOOGetFwdFromMethod((Method *) Tcl_GetHashValue(hPtr)); if (prefixObj == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "prefix argument list not available for this kind of method", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, prefixObj); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoClassInstancesCmd -- * * Implements [info class instances $clsName ?$pattern?] * * ---------------------------------------------------------------------- */ static int InfoClassInstancesCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Object *oPtr; Class *clsPtr; Tcl_Size i; const char *pattern = NULL; Tcl_Obj *resultObj; if (objc != 2 && objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "className ?pattern?"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } if (objc == 3) { pattern = TclGetString(objv[2]); } TclNewObj(resultObj); FOREACH(oPtr, clsPtr->instances) { Tcl_Obj *tmpObj = TclOOObjectName(interp, oPtr); if (pattern && !Tcl_StringMatch(TclGetString(tmpObj), pattern)) { continue; } Tcl_ListObjAppendElement(NULL, resultObj, tmpObj); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoClassMethodsCmd -- * * Implements [info class methods $clsName ?options...?] * * ---------------------------------------------------------------------- */ static int InfoClassMethodsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { static const char *const options[] = { "-all", "-localprivate", "-private", "-scope", NULL }; enum Options { OPT_ALL, OPT_LOCALPRIVATE, OPT_PRIVATE, OPT_SCOPE } idx; static const char *const scopes[] = { "private", "public", "unexported" }; enum Scopes { SCOPE_PRIVATE, SCOPE_PUBLIC, SCOPE_UNEXPORTED, SCOPE_DEFAULT = -1 }; int flag = PUBLIC_METHOD, recurse = 0, scope = SCOPE_DEFAULT; Tcl_Obj *namePtr, *resultObj; Method *mPtr; Class *clsPtr; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "className ?-option value ...?"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } if (objc != 2) { int i; for (i=2 ; i= objc) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "missing option for -scope")); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", (char *)NULL); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[i], scopes, "scope", 0, &scope) != TCL_OK) { return TCL_ERROR; } break; } } } if (scope != SCOPE_DEFAULT) { recurse = 0; switch (scope) { case SCOPE_PRIVATE: flag = TRUE_PRIVATE_METHOD; break; case SCOPE_PUBLIC: flag = PUBLIC_METHOD; break; case SCOPE_UNEXPORTED: flag = 0; break; } } TclNewObj(resultObj); if (recurse) { const char **names; Tcl_Size i, numNames = TclOOGetSortedClassMethodList(clsPtr, flag, &names); for (i=0 ; i 0) { Tcl_Free((void *)names); } } else { FOREACH_HASH_DECLS; if (scope == SCOPE_DEFAULT) { /* * Handle legacy-mode matching. [Bug 36e5517a6850] */ int scopeFilter = flag | TRUE_PRIVATE_METHOD; FOREACH_HASH(namePtr, mPtr, &clsPtr->classMethods) { if (mPtr->typePtr && (mPtr->flags & scopeFilter) == flag) { Tcl_ListObjAppendElement(NULL, resultObj, namePtr); } } } else { FOREACH_HASH(namePtr, mPtr, &clsPtr->classMethods) { if (mPtr->typePtr && (mPtr->flags & SCOPE_FLAGS) == flag) { Tcl_ListObjAppendElement(NULL, resultObj, namePtr); } } } } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoClassMethodTypeCmd -- * * Implements [info class methodtype $clsName $methodName] * * ---------------------------------------------------------------------- */ static int InfoClassMethodTypeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_HashEntry *hPtr; Method *mPtr; Class *clsPtr; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "className methodName"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } hPtr = Tcl_FindHashEntry(&clsPtr->classMethods, objv[2]); if (hPtr == NULL) { goto unknownMethod; } mPtr = (Method *) Tcl_GetHashValue(hPtr); if (mPtr->typePtr == NULL) { /* * Special entry for visibility control: pretend the method doesnt * exist. */ goto unknownMethod; } Tcl_SetObjResult(interp, Tcl_NewStringObj(mPtr->typePtr->name, TCL_AUTO_LENGTH)); return TCL_OK; unknownMethod: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown method \"%s\"", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD", TclGetString(objv[2]), (char *)NULL); return TCL_ERROR; } /* * ---------------------------------------------------------------------- * * InfoClassMixinsCmd -- * * Implements [info class mixins $clsName] * * ---------------------------------------------------------------------- */ static int InfoClassMixinsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Class *clsPtr, *mixinPtr; Tcl_Obj *resultObj; Tcl_Size i; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "className"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } TclNewObj(resultObj); FOREACH(mixinPtr, clsPtr->mixins) { if (!mixinPtr) { continue; } Tcl_ListObjAppendElement(NULL, resultObj, TclOOObjectName(interp, mixinPtr->thisPtr)); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoClassSubsCmd -- * * Implements [info class subclasses $clsName ?$pattern?] * * ---------------------------------------------------------------------- */ static int InfoClassSubsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Class *clsPtr, *subclassPtr; Tcl_Obj *resultObj; Tcl_Size i; const char *pattern = NULL; if (objc != 2 && objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "className ?pattern?"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } if (objc == 3) { pattern = TclGetString(objv[2]); } TclNewObj(resultObj); FOREACH(subclassPtr, clsPtr->subclasses) { Tcl_Obj *tmpObj = TclOOObjectName(interp, subclassPtr->thisPtr); if (pattern && !Tcl_StringMatch(TclGetString(tmpObj), pattern)) { continue; } Tcl_ListObjAppendElement(NULL, resultObj, tmpObj); } FOREACH(subclassPtr, clsPtr->mixinSubs) { Tcl_Obj *tmpObj = TclOOObjectName(interp, subclassPtr->thisPtr); if (pattern && !Tcl_StringMatch(TclGetString(tmpObj), pattern)) { continue; } Tcl_ListObjAppendElement(NULL, resultObj, tmpObj); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoClassSupersCmd -- * * Implements [info class superclasses $clsName] * * ---------------------------------------------------------------------- */ static int InfoClassSupersCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Class *clsPtr, *superPtr; Tcl_Obj *resultObj; Tcl_Size i; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "className"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } TclNewObj(resultObj); FOREACH(superPtr, clsPtr->superclasses) { Tcl_ListObjAppendElement(NULL, resultObj, TclOOObjectName(interp, superPtr->thisPtr)); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoClassVariablesCmd -- * * Implements [info class variables $clsName ?-private?] * * ---------------------------------------------------------------------- */ static int InfoClassVariablesCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Class *clsPtr; Tcl_Obj *resultObj; Tcl_Size i; int isPrivate = 0; if (objc != 2 && objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "className ?-private?"); return TCL_ERROR; } if (objc == 3) { if (strcmp("-private", TclGetString(objv[2])) != 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "option \"%s\" is not exactly \"-private\"", TclGetString(objv[2]))); Tcl_SetErrorCode(interp, "TCL", "OO", "BAD_ARG"); return TCL_ERROR; } isPrivate = 1; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } TclNewObj(resultObj); if (isPrivate) { PrivateVariableMapping *privatePtr; FOREACH_STRUCT(privatePtr, clsPtr->privateVariables) { Tcl_ListObjAppendElement(NULL, resultObj, privatePtr->variableObj); } } else { Tcl_Obj *variableObj; FOREACH(variableObj, clsPtr->variables) { Tcl_ListObjAppendElement(NULL, resultObj, variableObj); } } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoObjectCallCmd -- * * Implements [info object call $objName $methodName] * * ---------------------------------------------------------------------- */ static int InfoObjectCallCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Object *oPtr; CallContext *contextPtr; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "objName methodName"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } /* * Get the call context and render its call chain. */ contextPtr = TclOOGetCallContext(oPtr, objv[2], PUBLIC_METHOD, NULL, NULL, NULL); if (contextPtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot construct any call chain", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "BAD_CALL_CHAIN"); return TCL_ERROR; } Tcl_SetObjResult(interp, TclOORenderCallChain(interp, contextPtr->callPtr)); TclOODeleteContext(contextPtr); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InfoClassCallCmd -- * * Implements [info class call $clsName $methodName] * * ---------------------------------------------------------------------- */ static int InfoClassCallCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Class *clsPtr; CallChain *callPtr; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "className methodName"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } /* * Get an render the stereotypical call chain. */ callPtr = TclOOGetStereotypeCallChain(clsPtr, objv[2], PUBLIC_METHOD); if (callPtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot construct any call chain", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "BAD_CALL_CHAIN"); return TCL_ERROR; } Tcl_SetObjResult(interp, TclOORenderCallChain(interp, callPtr)); TclOODeleteChain(callPtr); return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclOOInt.h0000644000175000017500000007060414726623136015045 0ustar sergeisergei/* * tclOOInt.h -- * * This file contains the structure definitions and some of the function * declarations for the object-system (NB: not Tcl_Obj, but ::oo). * * Copyright (c) 2006-2012 by Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef TCL_OO_INTERNAL_H #define TCL_OO_INTERNAL_H 1 #include "tclInt.h" #include "tclOO.h" /* * Hack to make things work with Objective C. Note that ObjC isn't really * supported, but we don't want to to be actively hostile to it. [Bug 2163447] */ #ifdef __OBJC__ #define Class TclOOClass #define Object TclOOObject #endif /* __OBJC__ */ /* * Forward declarations. */ typedef struct CallChain CallChain; typedef struct CallContext CallContext; typedef struct Class Class; typedef struct DeclaredClassMethod DeclaredClassMethod; typedef struct ForwardMethod ForwardMethod; typedef struct Foundation Foundation; typedef struct Method Method; typedef struct MInvoke MInvoke; typedef struct Object Object; typedef struct PrivateVariableMapping PrivateVariableMapping; typedef struct ProcedureMethod ProcedureMethod; typedef struct PropertyStorage PropertyStorage; /* * The data that needs to be stored per method. This record is used to collect * information about all sorts of methods, including forwards, constructors * and destructors. */ struct Method { union { const Tcl_MethodType *typePtr; const Tcl_MethodType2 *type2Ptr; }; /* The type of method. If NULL, this is a * special flag record which is just used for * the setting of the flags field. Note that * this is a union of two pointer types that * have the same layout at least as far as the * internal version field. */ Tcl_Size refCount; void *clientData; /* Type-specific data. */ Tcl_Obj *namePtr; /* Name of the method. */ Object *declaringObjectPtr; /* The object that declares this method, or * NULL if it was declared by a class. */ Class *declaringClassPtr; /* The class that declares this method, or * NULL if it was declared directly on an * object. */ int flags; /* Assorted flags. Includes whether this * method is public/exported or not. */ }; /* * Pre- and post-call callbacks, to allow procedure-like methods to be fine * tuned in their behaviour. */ typedef int (TclOO_PreCallProc)(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, Tcl_CallFrame *framePtr, int *isFinished); typedef int (TclOO_PostCallProc)(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, Tcl_Namespace *namespacePtr, int result); typedef void (TclOO_PmCDDeleteProc)(void *clientData); typedef void *(TclOO_PmCDCloneProc)(void *clientData); /* * Procedure-like methods have the following extra information. */ struct ProcedureMethod { int version; /* Version of this structure. Currently must * be TCLOO_PROCEDURE_METHOD_VERSION_1. */ Proc *procPtr; /* Core of the implementation of the method; * includes the argument definition and the * body bytecodes. */ int flags; /* Flags to control features. */ Tcl_Size refCount; void *clientData; TclOO_PmCDDeleteProc *deleteClientdataProc; TclOO_PmCDCloneProc *cloneClientdataProc; ProcErrorProc *errProc; /* Replacement error handler. */ TclOO_PreCallProc *preCallProc; /* Callback to allow for additional setup * before the method executes. */ TclOO_PostCallProc *postCallProc; /* Callback to allow for additional cleanup * after the method executes. */ GetFrameInfoValueProc *gfivProc; /* Callback to allow for fine tuning of how * the method reports itself. */ Command cmd; /* Space used to connect to [info frame] */ ExtraFrameInfo efi; /* Space used to store data for [info frame] */ Tcl_Interp *interp; /* Interpreter in which to compute the name of * the method. */ Tcl_Method method; /* Method to compute the name of. */ int callSiteFlags; /* Flags from the call chain. Only interested * in whether this is a constructor or * destructor, which we can't know until then * for messy reasons. Other flags are variable * but not used. */ }; enum ProcedureMethodVersion { TCLOO_PROCEDURE_METHOD_VERSION_1 = 0 }; #define TCLOO_PROCEDURE_METHOD_VERSION TCLOO_PROCEDURE_METHOD_VERSION_1 /* * Flags for use in a ProcedureMethod. * */ enum ProceudreMethodFlags { USE_DECLARER_NS = 0x80 /* When set, the method will use the namespace * of the object or class that declared it (or * the clone of it, if it was from such that * the implementation of the method came to the * particular use) instead of the namespace of * the object on which the method was invoked. * This flag must be distinct from all others * that are associated with methods. */ }; /* * Forwarded methods have the following extra information. */ struct ForwardMethod { Tcl_Obj *prefixObj; /* The list of values to use to replace the * object and method name with. Will be a * non-empty list. */ }; /* * Structure used in private variable mappings. Describes the mapping of a * single variable from the user's local name to the system's storage name. * [TIP #500] */ struct PrivateVariableMapping { Tcl_Obj *variableObj; /* Name used within methods. This is the part * that is properly under user control. */ Tcl_Obj *fullNameObj; /* Name used at the instance namespace level. */ }; /* * Helper definitions that declare a "list" array. The two varieties are * either optimized for simplicity (in the case that the whole array is * typically assigned at once) or efficiency (in the case that the array is * expected to be expanded over time). These lists are designed to be iterated * over with the help of the FOREACH macro (see later in this file). * * The "num" field always counts the number of listType_t elements used in the * "list" field. When a "size" field exists, it describes how many elements * are present in the list; when absent, exactly "num" elements are present. */ #define LIST_STATIC(listType_t) \ struct { Tcl_Size num; listType_t *list; } #define LIST_DYNAMIC(listType_t) \ struct { Tcl_Size num, size; listType_t *list; } /* * These types are needed in function arguments. */ typedef LIST_STATIC(Class *) ClassList; typedef LIST_DYNAMIC(Class *) VarClassList; typedef LIST_STATIC(Tcl_Obj *) FilterList; typedef LIST_DYNAMIC(Object *) ObjectList; typedef LIST_STATIC(Tcl_Obj *) VariableNameList; typedef LIST_STATIC(PrivateVariableMapping) PrivateVariableList; typedef LIST_STATIC(Tcl_Obj *) PropertyList; /* * This type is used in various places. It holds the parts of an object or * class relating to property information. */ struct PropertyStorage { PropertyList readable; /* The readable properties slot. */ PropertyList writable; /* The writable properties slot. */ Tcl_Obj *allReadableCache; /* The cache of all readable properties * exposed by this object or class (in its * stereotypical instancs). Contains a sorted * unique list if not NULL. */ Tcl_Obj *allWritableCache; /* The cache of all writable properties * exposed by this object or class (in its * stereotypical instances). Contains a sorted * unique list if not NULL. */ int epoch; /* The epoch that the caches are valid for. */ }; /* * Now, the definition of what an object actually is. */ struct Object { Foundation *fPtr; /* The basis for the object system, which is * conceptually part of the interpreter. */ Tcl_Namespace *namespacePtr;/* This object's namespace. */ Tcl_Command command; /* Reference to this object's public * command. */ Tcl_Command myCommand; /* Reference to this object's internal * command. */ Class *selfCls; /* This object's class. */ Tcl_HashTable *methodsPtr; /* Object-local Tcl_Obj (method name) to * Method* mapping. */ ClassList mixins; /* Classes mixed into this object. */ FilterList filters; /* List of filter names. */ Class *classPtr; /* This is non-NULL for all classes, and NULL * for everything else. It points to the class * structure. */ Tcl_Size refCount; /* Number of strong references to this object. * Note that there may be many more weak * references; this mechanism exists to * avoid Tcl_Preserve. */ int flags; /* See ObjectFlags. */ Tcl_Size creationEpoch; /* Unique value to make comparisons of objects * easier. */ Tcl_Size epoch; /* Per-object epoch, incremented when the way * an object should resolve call chains is * changed. */ Tcl_HashTable *metadataPtr; /* Mapping from pointers to metadata type to * the void *values that are the values * of each piece of attached metadata. This * field starts out as NULL and is only * allocated if metadata is attached. */ Tcl_Obj *cachedNameObj; /* Cache of the name of the object. */ Tcl_HashTable *chainCache; /* Place to keep unused contexts. This table * is indexed by method name as Tcl_Obj. */ Tcl_ObjectMapMethodNameProc *mapMethodNameProc; /* Function to allow remapping of method * names. For itcl-ng. */ VariableNameList variables; PrivateVariableList privateVariables; /* Configurations for the variable resolver * used inside methods. */ Tcl_Command myclassCommand; /* Reference to this object's class dispatcher * command. */ PropertyStorage properties; /* Information relating to the lists of * properties that this object *claims* to * support. */ }; enum ObjectFlags { OBJECT_DESTRUCTING = 1, /* Indicates that an object is being or has * been destroyed */ DESTRUCTOR_CALLED = 2, /* Indicates that evaluation of destructor * script for the object has began */ ROOT_OBJECT = 0x1000, /* Flag to say that this object is the root of * the class hierarchy and should be treated * specially during teardown. */ FILTER_HANDLING = 0x2000, /* Flag set when the object is processing a * filter; when set, filters are *not* * processed on the object, preventing nasty * recursive filtering problems. */ USE_CLASS_CACHE = 0x4000, /* Flag set to say that the object is a pure * instance of the class, and has had nothing * added that changes the dispatch chain (i.e. * no methods, mixins, or filters. */ ROOT_CLASS = 0x8000, /* Flag to say that this object is the root * class of classes, and should be treated * specially during teardown (and in a few * other spots). */ FORCE_UNKNOWN = 0x10000, /* States that we are *really* looking up the * unknown method handler at that point. */ DONT_DELETE = 0x20000, /* Inhibit deletion of this object. Used * during fundamental object type mutation to * make sure that the object actually survives * to the end of the operation. */ HAS_PRIVATE_METHODS = 0x40000 /* Object/class has (or had) private methods, * and so shouldn't be cached so * aggressively. */ }; /* * And the definition of a class. Note that every class also has an associated * object, through which it is manipulated. */ struct Class { Object *thisPtr; /* Reference to the object associated with * this class. */ int flags; /* Assorted flags. */ ClassList superclasses; /* List of superclasses, used for generation * of method call chains. */ VarClassList subclasses; /* List of subclasses, used to ensure deletion * of dependent entities happens properly when * the class itself is deleted. */ ObjectList instances; /* List of instances, used to ensure deletion * of dependent entities happens properly when * the class itself is deleted. */ FilterList filters; /* List of filter names, used for generation * of method call chains. */ ClassList mixins; /* List of mixin classes, used for generation * of method call chains. */ VarClassList mixinSubs; /* List of classes that this class is mixed * into, used to ensure deletion of dependent * entities happens properly when the class * itself is deleted. */ Tcl_HashTable classMethods; /* Hash table of all methods. Hash maps from * the (Tcl_Obj*) method name to the (Method*) * method record. */ Method *constructorPtr; /* Method record of the class constructor (if * any). */ Method *destructorPtr; /* Method record of the class destructor (if * any). */ Tcl_HashTable *metadataPtr; /* Mapping from pointers to metadata type to * the void *values that are the values * of each piece of attached metadata. This * field starts out as NULL and is only * allocated if metadata is attached. */ CallChain *constructorChainPtr; CallChain *destructorChainPtr; Tcl_HashTable *classChainCache; /* Places where call chains are stored. For * constructors, the class chain is always * used. For destructors and ordinary methods, * the class chain is only used when the * object doesn't override with its own mixins * (and filters and method implementations for * when getting method chains). */ VariableNameList variables; PrivateVariableList privateVariables; /* Configurations for the variable resolver * used inside methods. */ Tcl_Obj *clsDefinitionNs; /* Name of the namespace to use for * definitions commands of instances of this * class in when those instances are defined * as classes. If NULL, use the value from the * class hierarchy. It's an error at * [oo::define] call time if this namespace is * defined but doesn't exist; we also check at * setting time but don't check between * times. */ Tcl_Obj *objDefinitionNs; /* Name of the namespace to use for * definitions commands of instances of this * class in when those instances are defined * as instances. If NULL, use the value from * the class hierarchy. It's an error at * [oo::objdefine]/[self] call time if this * namespace is defined but doesn't exist; we * also check at setting time but don't check * between times. */ PropertyStorage properties; /* Information relating to the lists of * properties that this class *claims* to * support. */ }; /* * Master epoch counter for making unique IDs for objects that can be compared * cheaply. */ typedef struct ThreadLocalData { Tcl_Size nsCount; /* Epoch counter is used for keeping * the values used in Tcl_Obj internal * representations sane. Must be thread-local * because Tcl_Objs can cross interpreter * boundaries within a thread (objects don't * generally cross threads). */ } ThreadLocalData; /* * The foundation of the object system within an interpreter contains * references to the key classes and namespaces, together with a few other * useful bits and pieces. Probably ought to eventually go in the Interp * structure itself. */ struct Foundation { Tcl_Interp *interp; /* The interpreter this is attached to. */ Class *objectCls; /* The root of the object system. */ Class *classCls; /* The class of all classes. */ Tcl_Namespace *ooNs; /* ::oo namespace. */ Tcl_Namespace *helpersNs; /* Namespace containing the commands that are * only valid when executing inside a * procedural method. */ Tcl_Size epoch; /* Used to invalidate method chains when the * class structure changes. */ ThreadLocalData *tsdPtr; /* Counter so we can allocate a unique * namespace to each object. */ Tcl_Obj *unknownMethodNameObj; /* Shared object containing the name of the * unknown method handler method. */ Tcl_Obj *constructorName; /* Shared object containing the "name" of a * constructor. */ Tcl_Obj *destructorName; /* Shared object containing the "name" of a * destructor. */ Tcl_Obj *clonedName; /* Shared object containing the name of a * "" pseudo-constructor. */ Tcl_Obj *defineName; /* Fully qualified name of oo::define. */ Tcl_Obj *myName; /* The "my" shared object. */ Tcl_Obj *mcdName; /* The shared object for calling the helper to * mix in class delegates. */ }; /* * The number of MInvoke records in the CallChain before we allocate * separately. */ #define CALL_CHAIN_STATIC_SIZE 4 /* * Information relating to the invocation of a particular method implementation * in a call chain. */ struct MInvoke { Method *mPtr; /* Reference to the method implementation * record. */ int isFilter; /* Whether this is a filter invocation. */ Class *filterDeclarer; /* What class decided to add the filter; if * NULL, it was added by the object. */ }; /* * The cacheable part of a call context. */ struct CallChain { Tcl_Size objectCreationEpoch;/* The object's creation epoch. Note that the * object reference is not stored in the call * chain; it is in the call context. */ Tcl_Size objectEpoch; /* Local (object structure) epoch counter * snapshot. */ Tcl_Size epoch; /* Global (class structure) epoch counter * snapshot. */ int flags; /* Assorted flags, see below. */ Tcl_Size refCount; /* Reference count. */ Tcl_Size numChain; /* Size of the call chain. */ MInvoke *chain; /* Array of call chain entries. May point to * staticChain if the number of entries is * small. */ MInvoke staticChain[CALL_CHAIN_STATIC_SIZE]; }; /* * A call context structure is built when a method is called. It contains the * chain of method implementations that are to be invoked by a particular * call, and the process of calling walks the chain, with the [next] command * proceeding to the next entry in the chain. */ struct CallContext { Object *oPtr; /* The object associated with this call. */ Tcl_Size index; /* Index into the call chain of the currently * executing method implementation. */ Tcl_Size skip; /* Current number of arguments to skip; can * vary depending on whether it is a direct * method call or a continuation via the * [next] command. */ CallChain *callPtr; /* The actual call chain. */ }; /* * Bits for the 'flags' field of the call chain. */ enum TclOOCallChainFlags { PUBLIC_METHOD = 0x01, /* This is a public (exported) method. */ PRIVATE_METHOD = 0x02, /* This is a private (class's direct instances * only) method. Supports itcl. */ OO_UNKNOWN_METHOD = 0x04, /* This is an unknown method. */ CONSTRUCTOR = 0x08, /* This is a constructor. */ DESTRUCTOR = 0x10, /* This is a destructor. */ TRUE_PRIVATE_METHOD = 0x20 /* This is a private method only accessible * from other methods defined on this class * or instance. [TIP #500] */ }; #define SCOPE_FLAGS (PUBLIC_METHOD | PRIVATE_METHOD | TRUE_PRIVATE_METHOD) /* * Structure containing definition information about basic class methods. */ struct DeclaredClassMethod { const char *name; /* Name of the method in question. */ int isPublic; /* Whether the method is public by default. */ Tcl_MethodType definition; /* How to call the method. */ }; /* *---------------------------------------------------------------- * Commands relating to OO support. *---------------------------------------------------------------- */ MODULE_SCOPE int TclOOInit(Tcl_Interp *interp); MODULE_SCOPE Tcl_ObjCmdProc TclOODefineObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOOObjDefObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefineConstructorObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefineDefnNsObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefineDeleteMethodObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefineDestructorObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefineExportObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefineForwardObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefineMethodObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefineRenameMethodObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefineUnexportObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefineClassObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefineSelfObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefineObjSelfObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefinePrivateObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOODefinePropertyCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOOUnknownDefinition; MODULE_SCOPE Tcl_ObjCmdProc TclOOCopyObjectCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOONextObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOONextToObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOOSelfObjCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOOInfoObjectPropCmd; MODULE_SCOPE Tcl_ObjCmdProc TclOOInfoClassPropCmd; /* * Method implementations (in tclOOBasic.c). */ MODULE_SCOPE Tcl_MethodCallProc TclOO_Class_Constructor; MODULE_SCOPE Tcl_MethodCallProc TclOO_Class_Create; MODULE_SCOPE Tcl_MethodCallProc TclOO_Class_CreateNs; MODULE_SCOPE Tcl_MethodCallProc TclOO_Class_New; MODULE_SCOPE Tcl_MethodCallProc TclOO_Object_Destroy; MODULE_SCOPE Tcl_MethodCallProc TclOO_Object_Eval; MODULE_SCOPE Tcl_MethodCallProc TclOO_Object_LinkVar; MODULE_SCOPE Tcl_MethodCallProc TclOO_Object_Unknown; MODULE_SCOPE Tcl_MethodCallProc TclOO_Object_VarName; MODULE_SCOPE Tcl_MethodCallProc TclOO_Configurable_Configure; /* * Private definitions, some of which perhaps ought to be exposed properly or * maybe just put in the internal stubs table. */ MODULE_SCOPE void TclOOAddToInstances(Object *oPtr, Class *clsPtr); MODULE_SCOPE void TclOOAddToMixinSubs(Class *subPtr, Class *mixinPtr); MODULE_SCOPE void TclOOAddToSubclasses(Class *subPtr, Class *superPtr); MODULE_SCOPE Class * TclOOAllocClass(Tcl_Interp *interp, Object *useThisObj); MODULE_SCOPE int TclMethodIsType(Tcl_Method method, const Tcl_MethodType *typePtr, void **clientDataPtr); MODULE_SCOPE Tcl_Method TclNewInstanceMethod(Tcl_Interp *interp, Tcl_Object object, Tcl_Obj *nameObj, int flags, const Tcl_MethodType *typePtr, void *clientData); MODULE_SCOPE Tcl_Method TclNewMethod(Tcl_Class cls, Tcl_Obj *nameObj, int flags, const Tcl_MethodType *typePtr, void *clientData); MODULE_SCOPE int TclNRNewObjectInstance(Tcl_Interp *interp, Tcl_Class cls, const char *nameStr, const char *nsNameStr, Tcl_Size objc, Tcl_Obj *const *objv, Tcl_Size skip, Tcl_Object *objectPtr); MODULE_SCOPE Object * TclNewObjectInstanceCommon(Tcl_Interp *interp, Class *classPtr, const char *nameStr, const char *nsNameStr); MODULE_SCOPE int TclOODecrRefCount(Object *oPtr); MODULE_SCOPE int TclOOObjectDestroyed(Object *oPtr); MODULE_SCOPE int TclOODefineSlots(Foundation *fPtr); MODULE_SCOPE void TclOODeleteChain(CallChain *callPtr); MODULE_SCOPE void TclOODeleteChainCache(Tcl_HashTable *tablePtr); MODULE_SCOPE void TclOODeleteContext(CallContext *contextPtr); MODULE_SCOPE void TclOODeleteDescendants(Tcl_Interp *interp, Object *oPtr); MODULE_SCOPE void TclOODelMethodRef(Method *method); MODULE_SCOPE CallContext *TclOOGetCallContext(Object *oPtr, Tcl_Obj *methodNameObj, int flags, Object *contextObjPtr, Class *contextClsPtr, Tcl_Obj *cacheInThisObj); MODULE_SCOPE Class * TclOOGetClassDefineCmdContext(Tcl_Interp *interp); MODULE_SCOPE Class * TclOOGetClassFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr); MODULE_SCOPE Tcl_Namespace *TclOOGetDefineContextNamespace( Tcl_Interp *interp, Object *oPtr, int forClass); MODULE_SCOPE CallChain *TclOOGetStereotypeCallChain(Class *clsPtr, Tcl_Obj *methodNameObj, int flags); MODULE_SCOPE Foundation *TclOOGetFoundation(Tcl_Interp *interp); MODULE_SCOPE Tcl_Obj * TclOOGetFwdFromMethod(Method *mPtr); MODULE_SCOPE Proc * TclOOGetProcFromMethod(Method *mPtr); MODULE_SCOPE Tcl_Obj * TclOOGetMethodBody(Method *mPtr); MODULE_SCOPE size_t TclOOGetSortedClassMethodList(Class *clsPtr, int flags, const char ***stringsPtr); MODULE_SCOPE int TclOOGetSortedMethodList(Object *oPtr, Object *contextObj, Class *contextCls, int flags, const char ***stringsPtr); MODULE_SCOPE int TclOOInit(Tcl_Interp *interp); MODULE_SCOPE void TclOOInitInfo(Tcl_Interp *interp); MODULE_SCOPE int TclOOInvokeContext(void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); MODULE_SCOPE Tcl_Var TclOOLookupObjectVar(Tcl_Interp *interp, Tcl_Object object, Tcl_Obj *varName, Tcl_Var *aryPtr); MODULE_SCOPE int TclNRObjectContextInvokeNext(Tcl_Interp *interp, Tcl_ObjectContext context, Tcl_Size objc, Tcl_Obj *const *objv, Tcl_Size skip); MODULE_SCOPE void TclOODefineBasicMethods(Class *clsPtr, const DeclaredClassMethod *dcm); MODULE_SCOPE Tcl_Obj * TclOOObjectName(Tcl_Interp *interp, Object *oPtr); MODULE_SCOPE void TclOOReleaseClassContents(Tcl_Interp *interp, Object *oPtr); MODULE_SCOPE int TclOORemoveFromInstances(Object *oPtr, Class *clsPtr); MODULE_SCOPE int TclOORemoveFromMixins(Class *mixinPtr, Object *oPtr); MODULE_SCOPE int TclOORemoveFromMixinSubs(Class *subPtr, Class *mixinPtr); MODULE_SCOPE int TclOORemoveFromSubclasses(Class *subPtr, Class *superPtr); MODULE_SCOPE Tcl_Obj * TclOORenderCallChain(Tcl_Interp *interp, CallChain *callPtr); MODULE_SCOPE void TclOOStashContext(Tcl_Obj *objPtr, CallContext *contextPtr); MODULE_SCOPE Tcl_Obj * TclOOGetAllObjectProperties(Object *oPtr, int writable); MODULE_SCOPE void TclOOSetupVariableResolver(Tcl_Namespace *nsPtr); MODULE_SCOPE Tcl_Obj * TclOOGetPropertyList(PropertyList *propList); MODULE_SCOPE void TclOOReleasePropertyStorage(PropertyStorage *propsPtr); MODULE_SCOPE void TclOOInstallReadableProperties(PropertyStorage *props, Tcl_Size objc, Tcl_Obj *const objv[]); MODULE_SCOPE void TclOOInstallWritableProperties(PropertyStorage *props, Tcl_Size objc, Tcl_Obj *const objv[]); MODULE_SCOPE int TclOOInstallStdPropertyImpls(void *useInstance, Tcl_Interp *interp, Tcl_Obj *propName, int readable, int writable); MODULE_SCOPE void TclOORegisterProperty(Class *clsPtr, Tcl_Obj *propName, int mayRead, int mayWrite); MODULE_SCOPE void TclOORegisterInstanceProperty(Object *oPtr, Tcl_Obj *propName, int mayRead, int mayWrite); /* * Include all the private API, generated from tclOO.decls. */ #include "tclOOIntDecls.h" /* * Alternatives to Tcl_Preserve/Tcl_EventuallyFree/Tcl_Release. */ #define AddRef(ptr) ((ptr)->refCount++) /* * A convenience macro for iterating through the lists used in the internal * memory management of objects. * REQUIRES DECLARATION: Tcl_Size i; */ #define FOREACH(var,ary) \ for(i=0 ; i<(ary).num; i++) if ((ary).list[i] == NULL) { \ continue; \ } else if ((var) = (ary).list[i], 1) /* * A variation where the array is an array of structs. There's no issue with * possible NULLs; every element of the array will be iterated over and the * variable set to a pointer to each of those elements in turn. * REQUIRES DECLARATION: Tcl_Size i; See [96551aca55] for more FOREACH_STRUCT details. */ #define FOREACH_STRUCT(var,ary) \ if (i=0, (ary).num>0) for(; var=&((ary).list[i]), i<(ary).num; i++) /* * Convenience macros for iterating through hash tables. FOREACH_HASH_DECLS * sets up the declarations needed for the main macro, FOREACH_HASH, which * does the actual iteration. FOREACH_HASH_KEY and FOREACH_HASH_VALUE are * restricted versions that only iterate over keys or values respectively. * REQUIRES DECLARATION: FOREACH_HASH_DECLS; */ #define FOREACH_HASH_DECLS \ Tcl_HashEntry *hPtr;Tcl_HashSearch search #define FOREACH_HASH(key, val, tablePtr) \ for(hPtr = Tcl_FirstHashEntry((tablePtr), &search); hPtr != NULL ? \ (*(void **)&(key) = Tcl_GetHashKey((tablePtr), hPtr), \ *(void **)&(val) = Tcl_GetHashValue(hPtr), 1) : 0; \ hPtr = Tcl_NextHashEntry(&search)) #define FOREACH_HASH_KEY(key, tablePtr) \ for(hPtr = Tcl_FirstHashEntry((tablePtr), &search); hPtr != NULL ? \ (*(void **)&(key) = Tcl_GetHashKey((tablePtr), hPtr), 1) : 0; \ hPtr = Tcl_NextHashEntry(&search)) #define FOREACH_HASH_VALUE(val, tablePtr) \ for(hPtr = Tcl_FirstHashEntry((tablePtr), &search); hPtr != NULL ? \ (*(void **)&(val) = Tcl_GetHashValue(hPtr), 1) : 0; \ hPtr = Tcl_NextHashEntry(&search)) /* * Convenience macro for duplicating a list. Needs no external declaration, * but all arguments are used multiple times and so must have no side effects. */ #undef DUPLICATE /* prevent possible conflict with definition in WINAPI nb30.h */ #define DUPLICATE(target,source,type) \ do { \ size_t len = sizeof(type) * ((target).num=(source).num);\ if (len != 0) { \ memcpy(((target).list=(type*)Tcl_Alloc(len)), (source).list, len); \ } else { \ (target).list = NULL; \ } \ } while(0) #endif /* TCL_OO_INTERNAL_H */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclOOIntDecls.h0000644000175000017500000001610414731057471016012 0ustar sergeisergei/* * This file is (mostly) automatically generated from tclOO.decls. */ #ifndef _TCLOOINTDECLS #define _TCLOOINTDECLS /* !BEGIN!: Do not edit below this line. */ #ifdef __cplusplus extern "C" { #endif /* * Exported function declarations: */ /* 0 */ TCLAPI Tcl_Object TclOOGetDefineCmdContext(Tcl_Interp *interp); /* 1 */ TCLAPI Tcl_Method TclOOMakeProcInstanceMethod(Tcl_Interp *interp, Object *oPtr, int flags, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, const Tcl_MethodType *typePtr, void *clientData, Proc **procPtrPtr); /* 2 */ TCLAPI Tcl_Method TclOOMakeProcMethod(Tcl_Interp *interp, Class *clsPtr, int flags, Tcl_Obj *nameObj, const char *namePtr, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, const Tcl_MethodType *typePtr, void *clientData, Proc **procPtrPtr); /* 3 */ TCLAPI Method * TclOONewProcInstanceMethod(Tcl_Interp *interp, Object *oPtr, int flags, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, ProcedureMethod **pmPtrPtr); /* 4 */ TCLAPI Method * TclOONewProcMethod(Tcl_Interp *interp, Class *clsPtr, int flags, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, ProcedureMethod **pmPtrPtr); /* 5 */ TCLAPI int TclOOObjectCmdCore(Object *oPtr, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv, int publicOnly, Class *startCls); /* 6 */ TCLAPI int TclOOIsReachable(Class *targetPtr, Class *startPtr); /* 7 */ TCLAPI Method * TclOONewForwardMethod(Tcl_Interp *interp, Class *clsPtr, int isPublic, Tcl_Obj *nameObj, Tcl_Obj *prefixObj); /* 8 */ TCLAPI Method * TclOONewForwardInstanceMethod(Tcl_Interp *interp, Object *oPtr, int isPublic, Tcl_Obj *nameObj, Tcl_Obj *prefixObj); /* 9 */ TCLAPI Tcl_Method TclOONewProcInstanceMethodEx(Tcl_Interp *interp, Tcl_Object oPtr, TclOO_PreCallProc *preCallPtr, TclOO_PostCallProc *postCallPtr, ProcErrorProc *errProc, void *clientData, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, int flags, void **internalTokenPtr); /* 10 */ TCLAPI Tcl_Method TclOONewProcMethodEx(Tcl_Interp *interp, Tcl_Class clsPtr, TclOO_PreCallProc *preCallPtr, TclOO_PostCallProc *postCallPtr, ProcErrorProc *errProc, void *clientData, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, int flags, void **internalTokenPtr); /* 11 */ TCLAPI int TclOOInvokeObject(Tcl_Interp *interp, Tcl_Object object, Tcl_Class startCls, int publicPrivate, Tcl_Size objc, Tcl_Obj *const *objv); /* 12 */ TCLAPI void TclOOObjectSetFilters(Object *oPtr, Tcl_Size numFilters, Tcl_Obj *const *filters); /* 13 */ TCLAPI void TclOOClassSetFilters(Tcl_Interp *interp, Class *classPtr, Tcl_Size numFilters, Tcl_Obj *const *filters); /* 14 */ TCLAPI void TclOOObjectSetMixins(Object *oPtr, Tcl_Size numMixins, Class *const *mixins); /* 15 */ TCLAPI void TclOOClassSetMixins(Tcl_Interp *interp, Class *classPtr, Tcl_Size numMixins, Class *const *mixins); typedef struct TclOOIntStubs { int magic; void *hooks; Tcl_Object (*tclOOGetDefineCmdContext) (Tcl_Interp *interp); /* 0 */ Tcl_Method (*tclOOMakeProcInstanceMethod) (Tcl_Interp *interp, Object *oPtr, int flags, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, const Tcl_MethodType *typePtr, void *clientData, Proc **procPtrPtr); /* 1 */ Tcl_Method (*tclOOMakeProcMethod) (Tcl_Interp *interp, Class *clsPtr, int flags, Tcl_Obj *nameObj, const char *namePtr, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, const Tcl_MethodType *typePtr, void *clientData, Proc **procPtrPtr); /* 2 */ Method * (*tclOONewProcInstanceMethod) (Tcl_Interp *interp, Object *oPtr, int flags, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, ProcedureMethod **pmPtrPtr); /* 3 */ Method * (*tclOONewProcMethod) (Tcl_Interp *interp, Class *clsPtr, int flags, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, ProcedureMethod **pmPtrPtr); /* 4 */ int (*tclOOObjectCmdCore) (Object *oPtr, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv, int publicOnly, Class *startCls); /* 5 */ int (*tclOOIsReachable) (Class *targetPtr, Class *startPtr); /* 6 */ Method * (*tclOONewForwardMethod) (Tcl_Interp *interp, Class *clsPtr, int isPublic, Tcl_Obj *nameObj, Tcl_Obj *prefixObj); /* 7 */ Method * (*tclOONewForwardInstanceMethod) (Tcl_Interp *interp, Object *oPtr, int isPublic, Tcl_Obj *nameObj, Tcl_Obj *prefixObj); /* 8 */ Tcl_Method (*tclOONewProcInstanceMethodEx) (Tcl_Interp *interp, Tcl_Object oPtr, TclOO_PreCallProc *preCallPtr, TclOO_PostCallProc *postCallPtr, ProcErrorProc *errProc, void *clientData, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, int flags, void **internalTokenPtr); /* 9 */ Tcl_Method (*tclOONewProcMethodEx) (Tcl_Interp *interp, Tcl_Class clsPtr, TclOO_PreCallProc *preCallPtr, TclOO_PostCallProc *postCallPtr, ProcErrorProc *errProc, void *clientData, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, int flags, void **internalTokenPtr); /* 10 */ int (*tclOOInvokeObject) (Tcl_Interp *interp, Tcl_Object object, Tcl_Class startCls, int publicPrivate, Tcl_Size objc, Tcl_Obj *const *objv); /* 11 */ void (*tclOOObjectSetFilters) (Object *oPtr, Tcl_Size numFilters, Tcl_Obj *const *filters); /* 12 */ void (*tclOOClassSetFilters) (Tcl_Interp *interp, Class *classPtr, Tcl_Size numFilters, Tcl_Obj *const *filters); /* 13 */ void (*tclOOObjectSetMixins) (Object *oPtr, Tcl_Size numMixins, Class *const *mixins); /* 14 */ void (*tclOOClassSetMixins) (Tcl_Interp *interp, Class *classPtr, Tcl_Size numMixins, Class *const *mixins); /* 15 */ } TclOOIntStubs; extern const TclOOIntStubs *tclOOIntStubsPtr; #ifdef __cplusplus } #endif #if defined(USE_TCLOO_STUBS) /* * Inline function declarations: */ #define TclOOGetDefineCmdContext \ (tclOOIntStubsPtr->tclOOGetDefineCmdContext) /* 0 */ #define TclOOMakeProcInstanceMethod \ (tclOOIntStubsPtr->tclOOMakeProcInstanceMethod) /* 1 */ #define TclOOMakeProcMethod \ (tclOOIntStubsPtr->tclOOMakeProcMethod) /* 2 */ #define TclOONewProcInstanceMethod \ (tclOOIntStubsPtr->tclOONewProcInstanceMethod) /* 3 */ #define TclOONewProcMethod \ (tclOOIntStubsPtr->tclOONewProcMethod) /* 4 */ #define TclOOObjectCmdCore \ (tclOOIntStubsPtr->tclOOObjectCmdCore) /* 5 */ #define TclOOIsReachable \ (tclOOIntStubsPtr->tclOOIsReachable) /* 6 */ #define TclOONewForwardMethod \ (tclOOIntStubsPtr->tclOONewForwardMethod) /* 7 */ #define TclOONewForwardInstanceMethod \ (tclOOIntStubsPtr->tclOONewForwardInstanceMethod) /* 8 */ #define TclOONewProcInstanceMethodEx \ (tclOOIntStubsPtr->tclOONewProcInstanceMethodEx) /* 9 */ #define TclOONewProcMethodEx \ (tclOOIntStubsPtr->tclOONewProcMethodEx) /* 10 */ #define TclOOInvokeObject \ (tclOOIntStubsPtr->tclOOInvokeObject) /* 11 */ #define TclOOObjectSetFilters \ (tclOOIntStubsPtr->tclOOObjectSetFilters) /* 12 */ #define TclOOClassSetFilters \ (tclOOIntStubsPtr->tclOOClassSetFilters) /* 13 */ #define TclOOObjectSetMixins \ (tclOOIntStubsPtr->tclOOObjectSetMixins) /* 14 */ #define TclOOClassSetMixins \ (tclOOIntStubsPtr->tclOOClassSetMixins) /* 15 */ #endif /* defined(USE_TCLOO_STUBS) */ /* !END!: Do not edit above this line. */ #endif /* _TCLOOINTDECLS */ tcl9.0.1/generic/tclOOMethod.c0000644000175000017500000015405714726623136015533 0ustar sergeisergei/* * tclOOMethod.c -- * * This file contains code to create and manage methods. * * Copyright © 2005-2011 Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "tclInt.h" #include "tclOOInt.h" #include "tclCompile.h" /* * Structure used to contain all the information needed about a call frame * used in a procedure-like method. */ typedef struct PMFrameData { CallFrame *framePtr; /* Reference to the call frame itself (it's * actually allocated on the Tcl stack). */ ProcErrorProc *errProc; /* The error handler for the body. */ Tcl_Obj *nameObj; /* The "name" of the command. Only used for a * few moments, so not reference. */ } PMFrameData; /* * Structure used to pass information about variable resolution to the * on-the-ground resolvers used when working with resolved compiled variables. */ typedef struct OOResVarInfo { Tcl_ResolvedVarInfo info; /* "Type" information so that the compiled * variable can be linked to the namespace * variable at the right time. */ Tcl_Obj *variableObj; /* The name of the variable. */ Tcl_Var cachedObjectVar; /* TODO: When to flush this cache? Can class * variables be cached? */ } OOResVarInfo; /* * Function declarations for things defined in this file. */ static Tcl_Obj ** InitEnsembleRewrite(Tcl_Interp *interp, int objc, Tcl_Obj *const *objv, int toRewrite, int rewriteLength, Tcl_Obj *const *rewriteObjs, int *lengthPtr); static int InvokeProcedureMethod(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static Tcl_NRPostProc FinalizeForwardCall; static Tcl_NRPostProc FinalizePMCall; static int PushMethodCallFrame(Tcl_Interp *interp, CallContext *contextPtr, ProcedureMethod *pmPtr, int objc, Tcl_Obj *const *objv, PMFrameData *fdPtr); static void DeleteProcedureMethodRecord(ProcedureMethod *pmPtr); static void DeleteProcedureMethod(void *clientData); static int CloneProcedureMethod(Tcl_Interp *interp, void *clientData, void **newClientData); static ProcErrorProc MethodErrorHandler; static ProcErrorProc ConstructorErrorHandler; static ProcErrorProc DestructorErrorHandler; static Tcl_Obj * RenderMethodName(void *clientData); static Tcl_Obj * RenderDeclarerName(void *clientData); static int InvokeForwardMethod(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static void DeleteForwardMethod(void *clientData); static int CloneForwardMethod(Tcl_Interp *interp, void *clientData, void **newClientData); static Tcl_ResolveVarProc ProcedureMethodVarResolver; static Tcl_ResolveCompiledVarProc ProcedureMethodCompiledVarResolver; /* * The types of methods defined by the core OO system. */ static const Tcl_MethodType procMethodType = { TCL_OO_METHOD_VERSION_CURRENT, "method", InvokeProcedureMethod, DeleteProcedureMethod, CloneProcedureMethod }; static const Tcl_MethodType fwdMethodType = { TCL_OO_METHOD_VERSION_CURRENT, "forward", InvokeForwardMethod, DeleteForwardMethod, CloneForwardMethod }; /* * Helper macros (derived from things private to tclVar.c) */ #define TclVarTable(contextNs) \ ((Tcl_HashTable *) (&((Namespace *) (contextNs))->varTable)) #define TclVarHashGetValue(hPtr) \ ((Tcl_Var) ((char *)hPtr - offsetof(VarInHash, entry))) static inline ProcedureMethod * AllocProcedureMethodRecord( int flags) { ProcedureMethod *pmPtr = (ProcedureMethod *) Tcl_Alloc(sizeof(ProcedureMethod)); memset(pmPtr, 0, sizeof(ProcedureMethod)); pmPtr->version = TCLOO_PROCEDURE_METHOD_VERSION; pmPtr->flags = flags & USE_DECLARER_NS; pmPtr->refCount = 1; pmPtr->cmd.clientData = &pmPtr->efi; return pmPtr; } /* * ---------------------------------------------------------------------- * * Tcl_NewInstanceMethod -- * * Attach a method to an object instance. * * ---------------------------------------------------------------------- */ Tcl_Method TclNewInstanceMethod( TCL_UNUSED(Tcl_Interp *), Tcl_Object object, /* The object that has the method attached to * it. */ Tcl_Obj *nameObj, /* The name of the method. May be NULL; if so, * up to caller to manage storage (e.g., when * it is a constructor or destructor). */ int flags, /* Whether this is a public method. */ const Tcl_MethodType *typePtr, /* The type of method this is, which defines * how to invoke, delete and clone the * method. */ void *clientData) /* Some data associated with the particular * method to be created. */ { Object *oPtr = (Object *) object; Method *mPtr; Tcl_HashEntry *hPtr; int isNew; if (nameObj == NULL) { mPtr = (Method *) Tcl_Alloc(sizeof(Method)); mPtr->namePtr = NULL; mPtr->refCount = 1; goto populate; } if (!oPtr->methodsPtr) { oPtr->methodsPtr = (Tcl_HashTable *) Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitObjHashTable(oPtr->methodsPtr); oPtr->flags &= ~USE_CLASS_CACHE; } hPtr = Tcl_CreateHashEntry(oPtr->methodsPtr, nameObj, &isNew); if (isNew) { mPtr = (Method *) Tcl_Alloc(sizeof(Method)); mPtr->namePtr = nameObj; mPtr->refCount = 1; Tcl_IncrRefCount(nameObj); Tcl_SetHashValue(hPtr, mPtr); } else { mPtr = (Method *) Tcl_GetHashValue(hPtr); if (mPtr->typePtr != NULL && mPtr->typePtr->deleteProc != NULL) { mPtr->typePtr->deleteProc(mPtr->clientData); } } populate: mPtr->typePtr = typePtr; mPtr->clientData = clientData; mPtr->flags = 0; mPtr->declaringObjectPtr = oPtr; mPtr->declaringClassPtr = NULL; if (flags) { mPtr->flags |= flags & (PUBLIC_METHOD | PRIVATE_METHOD | TRUE_PRIVATE_METHOD); if (flags & TRUE_PRIVATE_METHOD) { oPtr->flags |= HAS_PRIVATE_METHODS; } } oPtr->epoch++; return (Tcl_Method) mPtr; } Tcl_Method Tcl_NewInstanceMethod( TCL_UNUSED(Tcl_Interp *), Tcl_Object object, /* The object that has the method attached to * it. */ Tcl_Obj *nameObj, /* The name of the method. May be NULL; if so, * up to caller to manage storage (e.g., when * it is a constructor or destructor). */ int flags, /* Whether this is a public method. */ const Tcl_MethodType *typePtr, /* The type of method this is, which defines * how to invoke, delete and clone the * method. */ void *clientData) /* Some data associated with the particular * method to be created. */ { if (typePtr->version > TCL_OO_METHOD_VERSION_1) { Tcl_Panic("%s: Wrong version in typePtr->version, should be %s", "Tcl_NewInstanceMethod", "TCL_OO_METHOD_VERSION_1"); } return TclNewInstanceMethod(NULL, object, nameObj, flags, typePtr, clientData); } Tcl_Method Tcl_NewInstanceMethod2( TCL_UNUSED(Tcl_Interp *), Tcl_Object object, /* The object that has the method attached to * it. */ Tcl_Obj *nameObj, /* The name of the method. May be NULL; if so, * up to caller to manage storage (e.g., when * it is a constructor or destructor). */ int flags, /* Whether this is a public method. */ const Tcl_MethodType2 *typePtr, /* The type of method this is, which defines * how to invoke, delete and clone the * method. */ void *clientData) /* Some data associated with the particular * method to be created. */ { if (typePtr->version < TCL_OO_METHOD_VERSION_2) { Tcl_Panic("%s: Wrong version in typePtr->version, should be %s", "Tcl_NewInstanceMethod2", "TCL_OO_METHOD_VERSION_2"); } return TclNewInstanceMethod(NULL, object, nameObj, flags, (const Tcl_MethodType *) typePtr, clientData); } /* * ---------------------------------------------------------------------- * * Tcl_NewMethod -- * * Attach a method to a class. * * ---------------------------------------------------------------------- */ Tcl_Method TclNewMethod( Tcl_Class cls, /* The class to attach the method to. */ Tcl_Obj *nameObj, /* The name of the object. May be NULL (e.g., * for constructors or destructors); if so, up * to caller to manage storage. */ int flags, /* Whether this is a public method. */ const Tcl_MethodType *typePtr, /* The type of method this is, which defines * how to invoke, delete and clone the * method. */ void *clientData) /* Some data associated with the particular * method to be created. */ { Class *clsPtr = (Class *) cls; Method *mPtr; Tcl_HashEntry *hPtr; int isNew; if (nameObj == NULL) { mPtr = (Method *) Tcl_Alloc(sizeof(Method)); mPtr->namePtr = NULL; mPtr->refCount = 1; goto populate; } hPtr = Tcl_CreateHashEntry(&clsPtr->classMethods, nameObj, &isNew); if (isNew) { mPtr = (Method *) Tcl_Alloc(sizeof(Method)); mPtr->refCount = 1; mPtr->namePtr = nameObj; Tcl_IncrRefCount(nameObj); Tcl_SetHashValue(hPtr, mPtr); } else { mPtr = (Method *) Tcl_GetHashValue(hPtr); if (mPtr->typePtr != NULL && mPtr->typePtr->deleteProc != NULL) { mPtr->typePtr->deleteProc(mPtr->clientData); } } populate: clsPtr->thisPtr->fPtr->epoch++; mPtr->typePtr = typePtr; mPtr->clientData = clientData; mPtr->flags = 0; mPtr->declaringObjectPtr = NULL; mPtr->declaringClassPtr = clsPtr; if (flags) { mPtr->flags |= flags & (PUBLIC_METHOD | PRIVATE_METHOD | TRUE_PRIVATE_METHOD); if (flags & TRUE_PRIVATE_METHOD) { clsPtr->flags |= HAS_PRIVATE_METHODS; } } return (Tcl_Method) mPtr; } Tcl_Method Tcl_NewMethod( TCL_UNUSED(Tcl_Interp *), Tcl_Class cls, /* The class to attach the method to. */ Tcl_Obj *nameObj, /* The name of the object. May be NULL (e.g., * for constructors or destructors); if so, up * to caller to manage storage. */ int flags, /* Whether this is a public method. */ const Tcl_MethodType *typePtr, /* The type of method this is, which defines * how to invoke, delete and clone the * method. */ void *clientData) /* Some data associated with the particular * method to be created. */ { if (typePtr->version > TCL_OO_METHOD_VERSION_1) { Tcl_Panic("%s: Wrong version in typePtr->version, should be %s", "Tcl_NewMethod", "TCL_OO_METHOD_VERSION_1"); } return TclNewMethod(cls, nameObj, flags, typePtr, clientData); } Tcl_Method Tcl_NewMethod2( TCL_UNUSED(Tcl_Interp *), Tcl_Class cls, /* The class to attach the method to. */ Tcl_Obj *nameObj, /* The name of the object. May be NULL (e.g., * for constructors or destructors); if so, up * to caller to manage storage. */ int flags, /* Whether this is a public method. */ const Tcl_MethodType2 *typePtr, /* The type of method this is, which defines * how to invoke, delete and clone the * method. */ void *clientData) /* Some data associated with the particular * method to be created. */ { if (typePtr->version < TCL_OO_METHOD_VERSION_2) { Tcl_Panic("%s: Wrong version in typePtr->version, should be %s", "Tcl_NewMethod2", "TCL_OO_METHOD_VERSION_2"); } return TclNewMethod(cls, nameObj, flags, (const Tcl_MethodType *) typePtr, clientData); } /* * ---------------------------------------------------------------------- * * TclOODelMethodRef -- * * How to delete a method. * * ---------------------------------------------------------------------- */ void TclOODelMethodRef( Method *mPtr) { if ((mPtr != NULL) && (mPtr->refCount-- <= 1)) { if (mPtr->typePtr != NULL && mPtr->typePtr->deleteProc != NULL) { mPtr->typePtr->deleteProc(mPtr->clientData); } if (mPtr->namePtr != NULL) { Tcl_DecrRefCount(mPtr->namePtr); } Tcl_Free(mPtr); } } /* * ---------------------------------------------------------------------- * * TclOODefineBasicMethods -- * * Helper that makes it cleaner to create very simple methods during * basic system initialization. Not suitable for general use. * * ---------------------------------------------------------------------- */ void TclOODefineBasicMethods( Class *clsPtr, /* Class to attach the methods to. */ const DeclaredClassMethod *dcmAry) /* Static table of method definitions. */ { int i; for (i = 0 ; dcmAry[i].name ; i++) { Tcl_Obj *namePtr = Tcl_NewStringObj(dcmAry[i].name, TCL_AUTO_LENGTH); TclNewMethod((Tcl_Class) clsPtr, namePtr, (dcmAry[i].isPublic ? PUBLIC_METHOD : 0), &dcmAry[i].definition, NULL); Tcl_BounceRefCount(namePtr); } } /* * ---------------------------------------------------------------------- * * TclOONewProcInstanceMethod -- * * Create a new procedure-like method for an object. * * ---------------------------------------------------------------------- */ Method * TclOONewProcInstanceMethod( Tcl_Interp *interp, /* The interpreter containing the object. */ Object *oPtr, /* The object to modify. */ int flags, /* Whether this is a public method. */ Tcl_Obj *nameObj, /* The name of the method, which must not be * NULL. */ Tcl_Obj *argsObj, /* The formal argument list for the method, * which must not be NULL. */ Tcl_Obj *bodyObj, /* The body of the method, which must not be * NULL. */ ProcedureMethod **pmPtrPtr) /* Place to write pointer to procedure method * structure to allow for deeper tuning of the * structure's contents. NULL if caller is not * interested. */ { Tcl_Size argsLen; ProcedureMethod *pmPtr; Tcl_Method method; if (TclListObjLength(interp, argsObj, &argsLen) != TCL_OK) { return NULL; } pmPtr = AllocProcedureMethodRecord(flags); method = TclOOMakeProcInstanceMethod(interp, oPtr, flags, nameObj, argsObj, bodyObj, &procMethodType, pmPtr, &pmPtr->procPtr); if (method == NULL) { Tcl_Free(pmPtr); } else if (pmPtrPtr != NULL) { *pmPtrPtr = pmPtr; } return (Method *) method; } /* * ---------------------------------------------------------------------- * * TclOONewProcMethod -- * * Create a new procedure-like method for a class. * * ---------------------------------------------------------------------- */ Method * TclOONewProcMethod( Tcl_Interp *interp, /* The interpreter containing the class. */ Class *clsPtr, /* The class to modify. */ int flags, /* Whether this is a public method. */ Tcl_Obj *nameObj, /* The name of the method, which may be NULL; * if so, up to caller to manage storage * (e.g., because it is a constructor or * destructor). */ Tcl_Obj *argsObj, /* The formal argument list for the method, * which may be NULL; if so, it is equivalent * to an empty list. */ Tcl_Obj *bodyObj, /* The body of the method, which must not be * NULL. */ ProcedureMethod **pmPtrPtr) /* Place to write pointer to procedure method * structure to allow for deeper tuning of the * structure's contents. NULL if caller is not * interested. */ { Tcl_Size argsLen; /* TCL_INDEX_NONE => delete argsObj before exit */ ProcedureMethod *pmPtr; const char *procName; Tcl_Method method; if (argsObj == NULL) { argsLen = TCL_INDEX_NONE; TclNewObj(argsObj); Tcl_IncrRefCount(argsObj); procName = ""; } else if (TclListObjLength(interp, argsObj, &argsLen) != TCL_OK) { return NULL; } else { procName = (nameObj==NULL ? "" : TclGetString(nameObj)); } pmPtr = AllocProcedureMethodRecord(flags); method = TclOOMakeProcMethod(interp, clsPtr, flags, nameObj, procName, argsObj, bodyObj, &procMethodType, pmPtr, &pmPtr->procPtr); if (argsLen == TCL_INDEX_NONE) { Tcl_DecrRefCount(argsObj); } if (method == NULL) { Tcl_Free(pmPtr); } else if (pmPtrPtr != NULL) { *pmPtrPtr = pmPtr; } return (Method *) method; } /* * ---------------------------------------------------------------------- * * InitCmdFrame -- * * Set up a CmdFrame to record the source location for a procedure * method. Assumes that the body is the last argument to the command * creating the method, a good assumption because putting the body * elsewhere is ugly. * * ---------------------------------------------------------------------- */ static inline void InitCmdFrame( Interp *iPtr, /* Where source locations are recorded. */ Proc *procPtr) /* Guts of the method being made. */ { if (iPtr->cmdFramePtr) { CmdFrame context = *iPtr->cmdFramePtr; if (context.type == TCL_LOCATION_BC) { /* * Retrieve source information from the bytecode, if possible. If * the information is retrieved successfully, context.type will be * TCL_LOCATION_SOURCE and the reference held by * context.data.eval.path will be counted. */ TclGetSrcInfoForPc(&context); } else if (context.type == TCL_LOCATION_SOURCE) { /* * The copy into 'context' up above has created another reference * to 'context.data.eval.path'; account for it. */ Tcl_IncrRefCount(context.data.eval.path); } if (context.type == TCL_LOCATION_SOURCE) { /* * We can account for source location within a proc only if the * proc body was not created by substitution. This is where we * assume that the body is the last argument; the index of the body * is NOT a fixed count of arguments in because of the alternate * form of [oo::define]/[oo::objdefine]. * (FIXME: check that this is sane and correct!) */ if (context.line && context.nline > 1 && (context.line[context.nline - 1] >= 0)) { int isNew; CmdFrame *cfPtr = (CmdFrame *) Tcl_Alloc(sizeof(CmdFrame)); Tcl_HashEntry *hPtr; cfPtr->level = -1; cfPtr->type = context.type; cfPtr->line = (Tcl_Size *) Tcl_Alloc(sizeof(Tcl_Size)); cfPtr->line[0] = context.line[context.nline - 1]; cfPtr->nline = 1; cfPtr->framePtr = NULL; cfPtr->nextPtr = NULL; cfPtr->data.eval.path = context.data.eval.path; Tcl_IncrRefCount(cfPtr->data.eval.path); cfPtr->cmd = NULL; cfPtr->len = 0; hPtr = Tcl_CreateHashEntry(iPtr->linePBodyPtr, procPtr, &isNew); Tcl_SetHashValue(hPtr, cfPtr); } /* * 'context' is going out of scope; account for the reference that * it's holding to the path name. */ Tcl_DecrRefCount(context.data.eval.path); context.data.eval.path = NULL; } }} /* * ---------------------------------------------------------------------- * * TclOOMakeProcInstanceMethod -- * * The guts of the code to make a procedure-like method for an object. * Split apart so that it is easier for other extensions to reuse (in * particular, it frees them from having to pry so deeply into Tcl's * guts). * * ---------------------------------------------------------------------- */ Tcl_Method TclOOMakeProcInstanceMethod( Tcl_Interp *interp, /* The interpreter containing the object. */ Object *oPtr, /* The object to modify. */ int flags, /* Whether this is a public method. */ Tcl_Obj *nameObj, /* The name of the method, which _must not_ be * NULL. */ Tcl_Obj *argsObj, /* The formal argument list for the method, * which _must not_ be NULL. */ Tcl_Obj *bodyObj, /* The body of the method, which _must not_ be * NULL. */ const Tcl_MethodType *typePtr, /* The type of the method to create. */ void *clientData, /* The per-method type-specific data. */ Proc **procPtrPtr) /* A pointer to the variable in which to write * the procedure record reference. Presumably * inside the structure indicated by the * pointer in clientData. */ { Interp *iPtr = (Interp *) interp; Proc *procPtr; if (TclCreateProc(interp, NULL, TclGetString(nameObj), argsObj, bodyObj, procPtrPtr) != TCL_OK) { return NULL; } procPtr = *procPtrPtr; procPtr->cmdPtr = NULL; InitCmdFrame(iPtr, procPtr); return TclNewInstanceMethod(interp, (Tcl_Object) oPtr, nameObj, flags, typePtr, clientData); } /* * ---------------------------------------------------------------------- * * TclOOMakeProcMethod -- * * The guts of the code to make a procedure-like method for a class. * Split apart so that it is easier for other extensions to reuse (in * particular, it frees them from having to pry so deeply into Tcl's * guts). * * ---------------------------------------------------------------------- */ Tcl_Method TclOOMakeProcMethod( Tcl_Interp *interp, /* The interpreter containing the class. */ Class *clsPtr, /* The class to modify. */ int flags, /* Whether this is a public method. */ Tcl_Obj *nameObj, /* The name of the method, which may be NULL; * if so, up to caller to manage storage * (e.g., because it is a constructor or * destructor). */ const char *namePtr, /* The name of the method as a string, which * _must not_ be NULL. */ Tcl_Obj *argsObj, /* The formal argument list for the method, * which _must not_ be NULL. */ Tcl_Obj *bodyObj, /* The body of the method, which _must not_ be * NULL. */ const Tcl_MethodType *typePtr, /* The type of the method to create. */ void *clientData, /* The per-method type-specific data. */ Proc **procPtrPtr) /* A pointer to the variable in which to write * the procedure record reference. Presumably * inside the structure indicated by the * pointer in clientData. */ { Interp *iPtr = (Interp *) interp; Proc *procPtr; if (TclCreateProc(interp, NULL, namePtr, argsObj, bodyObj, procPtrPtr) != TCL_OK) { return NULL; } procPtr = *procPtrPtr; procPtr->cmdPtr = NULL; InitCmdFrame(iPtr, procPtr); return TclNewMethod( (Tcl_Class) clsPtr, nameObj, flags, typePtr, clientData); } /* * ---------------------------------------------------------------------- * * InvokeProcedureMethod, PushMethodCallFrame -- * * How to invoke a procedure-like method. * * ---------------------------------------------------------------------- */ static int InvokeProcedureMethod( void *clientData, /* Pointer to some per-method context. */ Tcl_Interp *interp, Tcl_ObjectContext context, /* The method calling context. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments as actually seen. */ { ProcedureMethod *pmPtr = (ProcedureMethod *) clientData; int result; PMFrameData *fdPtr; /* Important data that has to have a lifetime * matched by this function (or rather, by the * call frame's lifetime). */ /* * If the object namespace (or interpreter) were deleted, we just skip to * the next thing in the chain. */ if (TclOOObjectDestroyed(((CallContext *) context)->oPtr) || Tcl_InterpDeleted(interp)) { return TclNRObjectContextInvokeNext(interp, context, objc, objv, Tcl_ObjectContextSkippedArgs(context)); } /* * Finishes filling out the extra frame info so that [info frame] works if * that is not already set up. */ if (pmPtr->efi.length == 0) { Tcl_Method method = Tcl_ObjectContextMethod(context); pmPtr->efi.length = 2; pmPtr->efi.fields[0].name = "method"; pmPtr->efi.fields[0].proc = RenderMethodName; pmPtr->efi.fields[0].clientData = pmPtr; pmPtr->callSiteFlags = ((CallContext *) context)->callPtr->flags & (CONSTRUCTOR | DESTRUCTOR); pmPtr->interp = interp; pmPtr->method = method; if (pmPtr->gfivProc != NULL) { pmPtr->efi.fields[1].name = ""; pmPtr->efi.fields[1].proc = pmPtr->gfivProc; pmPtr->efi.fields[1].clientData = pmPtr; } else { if (Tcl_MethodDeclarerObject(method) != NULL) { pmPtr->efi.fields[1].name = "object"; } else { pmPtr->efi.fields[1].name = "class"; } pmPtr->efi.fields[1].proc = RenderDeclarerName; pmPtr->efi.fields[1].clientData = pmPtr; } } /* * Allocate the special frame data. */ fdPtr = (PMFrameData *) TclStackAlloc(interp, sizeof(PMFrameData)); /* * Create a call frame for this method. */ result = PushMethodCallFrame(interp, (CallContext *) context, pmPtr, objc, objv, fdPtr); if (result != TCL_OK) { TclStackFree(interp, fdPtr); return result; } pmPtr->refCount++; /* * Give the pre-call callback a chance to do some setup and, possibly, * veto the call. */ if (pmPtr->preCallProc != NULL) { int isFinished; result = pmPtr->preCallProc(pmPtr->clientData, interp, context, (Tcl_CallFrame *) fdPtr->framePtr, &isFinished); if (isFinished || result != TCL_OK) { Tcl_PopCallFrame(interp); TclStackFree(interp, fdPtr->framePtr); if (pmPtr->refCount-- <= 1) { DeleteProcedureMethodRecord(pmPtr); } TclStackFree(interp, fdPtr); return result; } } /* * Now invoke the body of the method. */ TclNRAddCallback(interp, FinalizePMCall, pmPtr, context, fdPtr, NULL); return TclNRInterpProcCore(interp, fdPtr->nameObj, Tcl_ObjectContextSkippedArgs(context), fdPtr->errProc); } static int FinalizePMCall( void *data[], Tcl_Interp *interp, int result) { ProcedureMethod *pmPtr = (ProcedureMethod *) data[0]; Tcl_ObjectContext context = (Tcl_ObjectContext) data[1]; PMFrameData *fdPtr = (PMFrameData *) data[2]; /* * Give the post-call callback a chance to do some cleanup. Note that at * this point the call frame itself is invalid; it's already been popped. */ if (pmPtr->postCallProc) { result = pmPtr->postCallProc(pmPtr->clientData, interp, context, Tcl_GetObjectNamespace(Tcl_ObjectContextObject(context)), result); } /* * Scrap the special frame data now that we're done with it. Note that we * are inlining DeleteProcedureMethod() here; this location is highly * sensitive when it comes to performance! */ if (pmPtr->refCount-- <= 1) { DeleteProcedureMethodRecord(pmPtr); } TclStackFree(interp, fdPtr); return result; } static int PushMethodCallFrame( Tcl_Interp *interp, /* Current interpreter. */ CallContext *contextPtr, /* Current method call context. */ ProcedureMethod *pmPtr, /* Information about this procedure-like * method. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv, /* Array of arguments. */ PMFrameData *fdPtr) /* Place to store information about the call * frame. */ { Namespace *nsPtr = (Namespace *) contextPtr->oPtr->namespacePtr; int result; CallFrame **framePtrPtr = &fdPtr->framePtr; ByteCode *codePtr; /* * Compute basic information on the basis of the type of method it is. */ if (contextPtr->callPtr->flags & CONSTRUCTOR) { fdPtr->nameObj = contextPtr->oPtr->fPtr->constructorName; fdPtr->errProc = ConstructorErrorHandler; } else if (contextPtr->callPtr->flags & DESTRUCTOR) { fdPtr->nameObj = contextPtr->oPtr->fPtr->destructorName; fdPtr->errProc = DestructorErrorHandler; } else { fdPtr->nameObj = Tcl_MethodName( Tcl_ObjectContextMethod((Tcl_ObjectContext) contextPtr)); fdPtr->errProc = MethodErrorHandler; } if (pmPtr->errProc != NULL) { fdPtr->errProc = pmPtr->errProc; } /* * Magic to enable things like [incr Tcl], which wants methods to run in * their class's namespace. */ if (pmPtr->flags & USE_DECLARER_NS) { Method *mPtr = contextPtr->callPtr->chain[contextPtr->index].mPtr; if (mPtr->declaringClassPtr != NULL) { nsPtr = (Namespace *) mPtr->declaringClassPtr->thisPtr->namespacePtr; } else { nsPtr = (Namespace *) mPtr->declaringObjectPtr->namespacePtr; } } /* * Compile the body. * * [Bug 2037727] Always call TclProcCompileProc so that we check not only * that we have bytecode, but also that it remains valid. Note that we set * the namespace of the code here directly; this is a hack, but the * alternative is *so* slow... */ pmPtr->procPtr->cmdPtr = &pmPtr->cmd; ByteCodeGetInternalRep(pmPtr->procPtr->bodyPtr, &tclByteCodeType, codePtr); if (codePtr) { codePtr->nsPtr = nsPtr; } result = TclProcCompileProc(interp, pmPtr->procPtr, pmPtr->procPtr->bodyPtr, nsPtr, "body of method", TclGetString(fdPtr->nameObj)); if (result != TCL_OK) { return result; } /* * Make the stack frame and fill it out with information about this call. * This operation doesn't ever actually fail. */ (void) TclPushStackFrame(interp, (Tcl_CallFrame **) framePtrPtr, (Tcl_Namespace *) nsPtr, FRAME_IS_PROC|FRAME_IS_METHOD); fdPtr->framePtr->clientData = contextPtr; fdPtr->framePtr->objc = objc; fdPtr->framePtr->objv = objv; fdPtr->framePtr->procPtr = pmPtr->procPtr; return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOOSetupVariableResolver, etc. -- * * Variable resolution engine used to connect declared variables to local * variables used in methods. The compiled variable resolver is more * important, but both are needed as it is possible to have a variable * that is only referred to in ways that aren't compilable and we can't * force LVT presence. [TIP #320, #500] * * ---------------------------------------------------------------------- */ void TclOOSetupVariableResolver( Tcl_Namespace *nsPtr) { Tcl_ResolverInfo info; Tcl_GetNamespaceResolvers(nsPtr, &info); if (info.compiledVarResProc == NULL) { Tcl_SetNamespaceResolvers(nsPtr, NULL, ProcedureMethodVarResolver, ProcedureMethodCompiledVarResolver); } } static int ProcedureMethodVarResolver( Tcl_Interp *interp, const char *varName, Tcl_Namespace *contextNs, TCL_UNUSED(int) /*flags*/, // Ignoring variable access flags (???) Tcl_Var *varPtr) { int result; Tcl_ResolvedVarInfo *rPtr = NULL; result = ProcedureMethodCompiledVarResolver(interp, varName, strlen(varName), contextNs, &rPtr); if (result != TCL_OK) { return result; } *varPtr = rPtr->fetchProc(interp, rPtr); /* * Must not retain reference to resolved information. [Bug 3105999] */ rPtr->deleteProc(rPtr); return (*varPtr ? TCL_OK : TCL_CONTINUE); } static Tcl_Var ProcedureMethodCompiledVarConnect( Tcl_Interp *interp, Tcl_ResolvedVarInfo *rPtr) { OOResVarInfo *infoPtr = (OOResVarInfo *) rPtr; Interp *iPtr = (Interp *) interp; CallFrame *framePtr = iPtr->varFramePtr; CallContext *contextPtr; Tcl_Obj *variableObj; PrivateVariableMapping *privateVar; Tcl_HashEntry *hPtr; int isNew, cacheIt; Tcl_Size i, varLen, len; const char *match, *varName; /* * Check that the variable is being requested in a context that is also a * method call; if not (i.e. we're evaluating in the object's namespace or * in a procedure of that namespace) then we do nothing. */ if (framePtr == NULL || !(framePtr->isProcCallFrame & FRAME_IS_METHOD)) { return NULL; } contextPtr = (CallContext *) framePtr->clientData; /* * If we've done the work before (in a comparable context) then reuse that * rather than performing resolution ourselves. */ if (infoPtr->cachedObjectVar) { return infoPtr->cachedObjectVar; } /* * Check if the variable is one we want to resolve at all (i.e. whether it * is in the list provided by the user). If not, we mustn't do anything * either. */ varName = Tcl_GetStringFromObj(infoPtr->variableObj, &varLen); if (contextPtr->callPtr->chain[contextPtr->index] .mPtr->declaringClassPtr != NULL) { FOREACH_STRUCT(privateVar, contextPtr->callPtr->chain[contextPtr->index] .mPtr->declaringClassPtr->privateVariables) { match = Tcl_GetStringFromObj(privateVar->variableObj, &len); if ((len == varLen) && !memcmp(match, varName, len)) { variableObj = privateVar->fullNameObj; cacheIt = 0; goto gotMatch; } } FOREACH(variableObj, contextPtr->callPtr->chain[contextPtr->index] .mPtr->declaringClassPtr->variables) { match = Tcl_GetStringFromObj(variableObj, &len); if ((len == varLen) && !memcmp(match, varName, len)) { cacheIt = 0; goto gotMatch; } } } else { FOREACH_STRUCT(privateVar, contextPtr->oPtr->privateVariables) { match = Tcl_GetStringFromObj(privateVar->variableObj, &len); if ((len == varLen) && !memcmp(match, varName, len)) { variableObj = privateVar->fullNameObj; cacheIt = 1; goto gotMatch; } } FOREACH(variableObj, contextPtr->oPtr->variables) { match = Tcl_GetStringFromObj(variableObj, &len); if ((len == varLen) && !memcmp(match, varName, len)) { cacheIt = 1; goto gotMatch; } } } return NULL; /* * It is a variable we want to resolve, so resolve it. */ gotMatch: hPtr = Tcl_CreateHashEntry(TclVarTable(contextPtr->oPtr->namespacePtr), variableObj, &isNew); if (isNew) { TclSetVarNamespaceVar((Var *) TclVarHashGetValue(hPtr)); } if (cacheIt) { infoPtr->cachedObjectVar = TclVarHashGetValue(hPtr); /* * We must keep a reference to the variable so everything will * continue to work correctly even if it is unset; being unset does * not end the life of the variable at this level. [Bug 3185009] */ VarHashRefCount(infoPtr->cachedObjectVar)++; } return TclVarHashGetValue(hPtr); } static void ProcedureMethodCompiledVarDelete( Tcl_ResolvedVarInfo *rPtr) { OOResVarInfo *infoPtr = (OOResVarInfo *) rPtr; /* * Release the reference to the variable if we were holding it. */ if (infoPtr->cachedObjectVar) { VarHashRefCount(infoPtr->cachedObjectVar)--; TclCleanupVar((Var *) infoPtr->cachedObjectVar, NULL); } Tcl_DecrRefCount(infoPtr->variableObj); Tcl_Free(infoPtr); } static int ProcedureMethodCompiledVarResolver( TCL_UNUSED(Tcl_Interp *), const char *varName, Tcl_Size length, TCL_UNUSED(Tcl_Namespace *), Tcl_ResolvedVarInfo **rPtrPtr) { OOResVarInfo *infoPtr; Tcl_Obj *variableObj = Tcl_NewStringObj(varName, length); /* * Do not create resolvers for cases that contain namespace separators or * which look like array accesses. Both will lead us astray. */ if (strstr(TclGetString(variableObj), "::") != NULL || Tcl_StringMatch(TclGetString(variableObj), "*(*)")) { Tcl_DecrRefCount(variableObj); return TCL_CONTINUE; } infoPtr = (OOResVarInfo *) Tcl_Alloc(sizeof(OOResVarInfo)); infoPtr->info.fetchProc = ProcedureMethodCompiledVarConnect; infoPtr->info.deleteProc = ProcedureMethodCompiledVarDelete; infoPtr->cachedObjectVar = NULL; infoPtr->variableObj = variableObj; Tcl_IncrRefCount(variableObj); *rPtrPtr = &infoPtr->info; return TCL_OK; } /* * ---------------------------------------------------------------------- * * RenderMethodName -- * * Returns the name of the declared method. Used for producing information * for [info frame]. * * ---------------------------------------------------------------------- */ static Tcl_Obj * RenderMethodName( void *clientData) { ProcedureMethod *pmPtr = (ProcedureMethod *) clientData; if (pmPtr->callSiteFlags & CONSTRUCTOR) { return TclOOGetFoundation(pmPtr->interp)->constructorName; } else if (pmPtr->callSiteFlags & DESTRUCTOR) { return TclOOGetFoundation(pmPtr->interp)->destructorName; } else { return Tcl_MethodName(pmPtr->method); } } /* * ---------------------------------------------------------------------- * * RenderDeclarerName -- * * Returns the name of the entity (object or class) which declared a * method. Used for producing information for [info frame] in such a way * that the expensive part of this (generating the object or class name * itself) isn't done until it is needed. * * ---------------------------------------------------------------------- */ static Tcl_Obj * RenderDeclarerName( void *clientData) { ProcedureMethod *pmPtr = (ProcedureMethod *) clientData; Tcl_Object object = Tcl_MethodDeclarerObject(pmPtr->method); if (object == NULL) { object = Tcl_GetClassAsObject(Tcl_MethodDeclarerClass(pmPtr->method)); } return TclOOObjectName(pmPtr->interp, (Object *) object); } /* * ---------------------------------------------------------------------- * * MethodErrorHandler, ConstructorErrorHandler, DestructorErrorHandler -- * * How to fill in the stack trace correctly upon error in various forms * of procedure-like methods. LIMIT is how long the inserted strings in * the error traces should get before being converted to have ellipses, * and ELLIPSIFY is a macro to do the conversion (with the help of a * %.*s%s format field). Note that ELLIPSIFY is only safe for use in * suitable formatting contexts. * * ---------------------------------------------------------------------- */ // TODO: Check whether Tcl_AppendLimitedToObj() can work here. #define LIMIT 60 #define ELLIPSIFY(str,len) \ ((len) > LIMIT ? LIMIT : (int)(len)), (str), ((len) > LIMIT ? "..." : "") static void CommonMethErrorHandler( Tcl_Interp *interp, const char *special) { Tcl_Size objectNameLen; CallContext *contextPtr = (CallContext *)((Interp *) interp)->varFramePtr->clientData; Method *mPtr = contextPtr->callPtr->chain[contextPtr->index].mPtr; const char *objectName, *kindName = "instance"; Object *declarerPtr = NULL; if (mPtr->declaringObjectPtr != NULL) { declarerPtr = mPtr->declaringObjectPtr; kindName = "object"; } else if (mPtr->declaringClassPtr != NULL) { declarerPtr = mPtr->declaringClassPtr->thisPtr; kindName = "class"; } if (declarerPtr) { objectName = TclGetStringFromObj(TclOOObjectName(interp, declarerPtr), &objectNameLen); } else { objectName = "unknown or deleted"; objectNameLen = 18; } if (!special) { Tcl_Size nameLen; const char *methodName = TclGetStringFromObj(mPtr->namePtr, &nameLen); Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (%s \"%.*s%s\" method \"%.*s%s\" line %d)", kindName, ELLIPSIFY(objectName, objectNameLen), ELLIPSIFY(methodName, nameLen), Tcl_GetErrorLine(interp))); } else { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (%s \"%.*s%s\" %s line %d)", kindName, ELLIPSIFY(objectName, objectNameLen), special, Tcl_GetErrorLine(interp))); } } static void MethodErrorHandler( Tcl_Interp *interp, TCL_UNUSED(Tcl_Obj *) /*methodNameObj*/) /* We pull the method name out of context instead of from argument */ { CommonMethErrorHandler(interp, NULL); } static void ConstructorErrorHandler( Tcl_Interp *interp, TCL_UNUSED(Tcl_Obj *) /*methodNameObj*/) /* Ignore. We know it is the constructor. */ { CommonMethErrorHandler(interp, "constructor"); } static void DestructorErrorHandler( Tcl_Interp *interp, TCL_UNUSED(Tcl_Obj *) /*methodNameObj*/) /* Ignore. We know it is the destructor. */ { CommonMethErrorHandler(interp, "destructor"); } /* * ---------------------------------------------------------------------- * * DeleteProcedureMethod, CloneProcedureMethod -- * * How to delete and clone procedure-like methods. * * ---------------------------------------------------------------------- */ static void DeleteProcedureMethodRecord( ProcedureMethod *pmPtr) { TclProcDeleteProc(pmPtr->procPtr); if (pmPtr->deleteClientdataProc) { pmPtr->deleteClientdataProc(pmPtr->clientData); } Tcl_Free(pmPtr); } static void DeleteProcedureMethod( void *clientData) { ProcedureMethod *pmPtr = (ProcedureMethod *) clientData; if (pmPtr->refCount-- <= 1) { DeleteProcedureMethodRecord(pmPtr); } } static int CloneProcedureMethod( Tcl_Interp *interp, void *clientData, void **newClientData) { ProcedureMethod *pmPtr = (ProcedureMethod *) clientData; ProcedureMethod *pm2Ptr; Tcl_Obj *bodyObj, *argsObj; CompiledLocal *localPtr; /* * Copy the argument list. */ TclNewObj(argsObj); for (localPtr=pmPtr->procPtr->firstLocalPtr; localPtr!=NULL; localPtr=localPtr->nextPtr) { if (TclIsVarArgument(localPtr)) { Tcl_Obj *argObj; TclNewObj(argObj); Tcl_ListObjAppendElement(NULL, argObj, Tcl_NewStringObj(localPtr->name, TCL_AUTO_LENGTH)); if (localPtr->defValuePtr != NULL) { Tcl_ListObjAppendElement(NULL, argObj, localPtr->defValuePtr); } Tcl_ListObjAppendElement(NULL, argsObj, argObj); } } /* * Must strip the internal representation in order to ensure that any * bound references to instance variables are removed. [Bug 3609693] */ bodyObj = Tcl_DuplicateObj(pmPtr->procPtr->bodyPtr); TclGetString(bodyObj); Tcl_StoreInternalRep(pmPtr->procPtr->bodyPtr, &tclByteCodeType, NULL); /* * Create the actual copy of the method record, manufacturing a new proc * record. */ pm2Ptr = (ProcedureMethod *) Tcl_Alloc(sizeof(ProcedureMethod)); memcpy(pm2Ptr, pmPtr, sizeof(ProcedureMethod)); pm2Ptr->refCount = 1; pm2Ptr->cmd.clientData = &pm2Ptr->efi; pm2Ptr->efi.length = 0; /* Trigger a reinit of this. */ Tcl_IncrRefCount(argsObj); Tcl_IncrRefCount(bodyObj); if (TclCreateProc(interp, NULL, "", argsObj, bodyObj, &pm2Ptr->procPtr) != TCL_OK) { Tcl_DecrRefCount(argsObj); Tcl_DecrRefCount(bodyObj); Tcl_Free(pm2Ptr); return TCL_ERROR; } Tcl_DecrRefCount(argsObj); Tcl_DecrRefCount(bodyObj); if (pmPtr->cloneClientdataProc) { pm2Ptr->clientData = pmPtr->cloneClientdataProc(pmPtr->clientData); } *newClientData = pm2Ptr; return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOONewForwardInstanceMethod -- * * Create a forwarded method for an object. * * ---------------------------------------------------------------------- */ Method * TclOONewForwardInstanceMethod( Tcl_Interp *interp, /* Interpreter for error reporting. */ Object *oPtr, /* The object to attach the method to. */ int flags, /* Whether the method is public or not. */ Tcl_Obj *nameObj, /* The name of the method. */ Tcl_Obj *prefixObj) /* List of arguments that form the command * prefix to forward to. */ { Tcl_Size prefixLen; ForwardMethod *fmPtr; if (TclListObjLength(interp, prefixObj, &prefixLen) != TCL_OK) { return NULL; } if (prefixLen < 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "method forward prefix must be non-empty", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "BAD_FORWARD", (char *)NULL); return NULL; } fmPtr = (ForwardMethod *) Tcl_Alloc(sizeof(ForwardMethod)); fmPtr->prefixObj = prefixObj; Tcl_IncrRefCount(prefixObj); return (Method *) TclNewInstanceMethod(interp, (Tcl_Object) oPtr, nameObj, flags, &fwdMethodType, fmPtr); } /* * ---------------------------------------------------------------------- * * TclOONewForwardMethod -- * * Create a new forwarded method for a class. * * ---------------------------------------------------------------------- */ Method * TclOONewForwardMethod( Tcl_Interp *interp, /* Interpreter for error reporting. */ Class *clsPtr, /* The class to attach the method to. */ int flags, /* Whether the method is public or not. */ Tcl_Obj *nameObj, /* The name of the method. */ Tcl_Obj *prefixObj) /* List of arguments that form the command * prefix to forward to. */ { Tcl_Size prefixLen; ForwardMethod *fmPtr; if (TclListObjLength(interp, prefixObj, &prefixLen) != TCL_OK) { return NULL; } if (prefixLen < 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "method forward prefix must be non-empty", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "BAD_FORWARD", (char *)NULL); return NULL; } fmPtr = (ForwardMethod *) Tcl_Alloc(sizeof(ForwardMethod)); fmPtr->prefixObj = prefixObj; Tcl_IncrRefCount(prefixObj); return (Method *) TclNewMethod((Tcl_Class) clsPtr, nameObj, flags, &fwdMethodType, fmPtr); } /* * ---------------------------------------------------------------------- * * InvokeForwardMethod -- * * How to invoke a forwarded method. Works by doing some ensemble-like * command rearranging and then invokes some other Tcl command. * * ---------------------------------------------------------------------- */ static int InvokeForwardMethod( void *clientData, /* Pointer to some per-method context. */ Tcl_Interp *interp, Tcl_ObjectContext context, /* The method calling context. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments as actually seen. */ { CallContext *contextPtr = (CallContext *) context; ForwardMethod *fmPtr = (ForwardMethod *) clientData; Tcl_Obj **argObjs, **prefixObjs; Tcl_Size numPrefixes, skip = contextPtr->skip; int len; /* * Build the real list of arguments to use. Note that we know that the * prefixObj field of the ForwardMethod structure holds a reference to a * non-empty list, so there's a whole class of failures ("not a list") we * can ignore here. */ TclListObjGetElements(NULL, fmPtr->prefixObj, &numPrefixes, &prefixObjs); argObjs = InitEnsembleRewrite(interp, objc, objv, skip, numPrefixes, prefixObjs, &len); Tcl_NRAddCallback(interp, FinalizeForwardCall, argObjs, NULL, NULL, NULL); /* * NOTE: The combination of direct set of iPtr->lookupNsPtr and the use * of the TCL_EVAL_NOERR flag results in an evaluation configuration * very much like TCL_EVAL_INVOKE. */ ((Interp *) interp)->lookupNsPtr = (Namespace *) contextPtr->oPtr->namespacePtr; return TclNREvalObjv(interp, len, argObjs, TCL_EVAL_NOERR, NULL); } static int FinalizeForwardCall( void *data[], Tcl_Interp *interp, int result) { Tcl_Obj **argObjs = (Tcl_Obj **) data[0]; TclStackFree(interp, argObjs); return result; } /* * ---------------------------------------------------------------------- * * DeleteForwardMethod, CloneForwardMethod -- * * How to delete and clone forwarded methods. * * ---------------------------------------------------------------------- */ static void DeleteForwardMethod( void *clientData) { ForwardMethod *fmPtr = (ForwardMethod *) clientData; Tcl_DecrRefCount(fmPtr->prefixObj); Tcl_Free(fmPtr); } static int CloneForwardMethod( TCL_UNUSED(Tcl_Interp *), void *clientData, void **newClientData) { ForwardMethod *fmPtr = (ForwardMethod *) clientData; ForwardMethod *fm2Ptr = (ForwardMethod *) Tcl_Alloc(sizeof(ForwardMethod)); fm2Ptr->prefixObj = fmPtr->prefixObj; Tcl_IncrRefCount(fm2Ptr->prefixObj); *newClientData = fm2Ptr; return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOOGetProcFromMethod, TclOOGetFwdFromMethod -- * * Utility functions used for procedure-like and forwarding method * introspection. * * ---------------------------------------------------------------------- */ Proc * TclOOGetProcFromMethod( Method *mPtr) { if (mPtr->typePtr == &procMethodType) { ProcedureMethod *pmPtr = (ProcedureMethod *) mPtr->clientData; return pmPtr->procPtr; } return NULL; } Tcl_Obj * TclOOGetMethodBody( Method *mPtr) { if (mPtr->typePtr == &procMethodType) { ProcedureMethod *pmPtr = (ProcedureMethod *) mPtr->clientData; (void) TclGetString(pmPtr->procPtr->bodyPtr); return pmPtr->procPtr->bodyPtr; } return NULL; } Tcl_Obj * TclOOGetFwdFromMethod( Method *mPtr) { if (mPtr->typePtr == &fwdMethodType) { ForwardMethod *fwPtr = (ForwardMethod *) mPtr->clientData; return fwPtr->prefixObj; } return NULL; } /* * ---------------------------------------------------------------------- * * InitEnsembleRewrite -- * * Utility function that wraps up a lot of the complexity involved in * doing ensemble-like command forwarding. Here is a picture of memory * management plan: * * <-----------------objc----------------------> * objv: |=============|===============================| * <-toRewrite-> | * \ * <-rewriteLength-> \ * rewriteObjs: |=================| \ * | | * V V * argObjs: |=================|===============================| * <------------------*lengthPtr-------------------> * * ---------------------------------------------------------------------- */ static Tcl_Obj ** InitEnsembleRewrite( Tcl_Interp *interp, /* Place to log the rewrite info. */ int objc, /* Number of real arguments. */ Tcl_Obj *const *objv, /* The real arguments. */ int toRewrite, /* Number of real arguments to replace. */ int rewriteLength, /* Number of arguments to insert instead. */ Tcl_Obj *const *rewriteObjs,/* Arguments to insert instead. */ int *lengthPtr) /* Where to write the resulting length of the * array of rewritten arguments. */ { size_t len = rewriteLength + objc - toRewrite; Tcl_Obj **argObjs = (Tcl_Obj **) TclStackAlloc(interp, sizeof(Tcl_Obj *) * len); memcpy(argObjs, rewriteObjs, rewriteLength * sizeof(Tcl_Obj *)); memcpy(argObjs + rewriteLength, objv + toRewrite, sizeof(Tcl_Obj *) * (objc - toRewrite)); /* * Now plumb this into the core ensemble rewrite logging system so that * Tcl_WrongNumArgs() can rewrite its result appropriately. The rules for * how to store the rewrite rules get complex solely because of the case * where an ensemble rewrites itself out of the picture; when that * happens, the quality of the error message rewrite falls drastically * (and unavoidably). */ if (TclInitRewriteEnsemble(interp, toRewrite, rewriteLength, objv)) { TclNRAddCallback(interp, TclClearRootEnsemble, NULL, NULL, NULL, NULL); } *lengthPtr = len; return argObjs; } /* * ---------------------------------------------------------------------- * * assorted trivial 'getter' functions * * ---------------------------------------------------------------------- */ Tcl_Object Tcl_MethodDeclarerObject( Tcl_Method method) { return (Tcl_Object) ((Method *) method)->declaringObjectPtr; } Tcl_Class Tcl_MethodDeclarerClass( Tcl_Method method) { return (Tcl_Class) ((Method *) method)->declaringClassPtr; } Tcl_Obj * Tcl_MethodName( Tcl_Method method) { return ((Method *) method)->namePtr; } int TclMethodIsType( Tcl_Method method, const Tcl_MethodType *typePtr, void **clientDataPtr) { Method *mPtr = (Method *) method; if (mPtr->typePtr == typePtr) { if (clientDataPtr != NULL) { *clientDataPtr = mPtr->clientData; } return 1; } return 0; } int Tcl_MethodIsType( Tcl_Method method, const Tcl_MethodType *typePtr, void **clientDataPtr) { Method *mPtr = (Method *) method; if (typePtr->version > TCL_OO_METHOD_VERSION_1) { Tcl_Panic("%s: Wrong version in typePtr->version, should be %s", "Tcl_MethodIsType", "TCL_OO_METHOD_VERSION_1"); } if (mPtr->typePtr == typePtr) { if (clientDataPtr != NULL) { *clientDataPtr = mPtr->clientData; } return 1; } return 0; } int Tcl_MethodIsType2( Tcl_Method method, const Tcl_MethodType2 *typePtr, void **clientDataPtr) { Method *mPtr = (Method *) method; if (typePtr->version < TCL_OO_METHOD_VERSION_2) { Tcl_Panic("%s: Wrong version in typePtr->version, should be %s", "Tcl_MethodIsType2", "TCL_OO_METHOD_VERSION_2"); } if (mPtr->typePtr == (const Tcl_MethodType *) typePtr) { if (clientDataPtr != NULL) { *clientDataPtr = mPtr->clientData; } return 1; } return 0; } int Tcl_MethodIsPublic( Tcl_Method method) { return (((Method *) method)->flags & PUBLIC_METHOD) ? 1 : 0; } int Tcl_MethodIsPrivate( Tcl_Method method) { return (((Method *) method)->flags & TRUE_PRIVATE_METHOD) ? 1 : 0; } /* * Extended method construction for itcl-ng. */ Tcl_Method TclOONewProcInstanceMethodEx( Tcl_Interp *interp, /* The interpreter containing the object. */ Tcl_Object oPtr, /* The object to modify. */ TclOO_PreCallProc *preCallPtr, TclOO_PostCallProc *postCallPtr, ProcErrorProc *errProc, void *clientData, Tcl_Obj *nameObj, /* The name of the method, which must not be * NULL. */ Tcl_Obj *argsObj, /* The formal argument list for the method, * which must not be NULL. */ Tcl_Obj *bodyObj, /* The body of the method, which must not be * NULL. */ int flags, /* Whether this is a public method. */ void **internalTokenPtr) /* If non-NULL, points to a variable that gets * the reference to the ProcedureMethod * structure. */ { ProcedureMethod *pmPtr; Tcl_Method method = (Tcl_Method) TclOONewProcInstanceMethod(interp, (Object *) oPtr, flags, nameObj, argsObj, bodyObj, &pmPtr); if (method == NULL) { return NULL; } pmPtr->flags = flags & USE_DECLARER_NS; pmPtr->preCallProc = preCallPtr; pmPtr->postCallProc = postCallPtr; pmPtr->errProc = errProc; pmPtr->clientData = clientData; if (internalTokenPtr != NULL) { *internalTokenPtr = pmPtr; } return method; } Tcl_Method TclOONewProcMethodEx( Tcl_Interp *interp, /* The interpreter containing the class. */ Tcl_Class clsPtr, /* The class to modify. */ TclOO_PreCallProc *preCallPtr, TclOO_PostCallProc *postCallPtr, ProcErrorProc *errProc, void *clientData, Tcl_Obj *nameObj, /* The name of the method, which may be NULL; * if so, up to caller to manage storage * (e.g., because it is a constructor or * destructor). */ Tcl_Obj *argsObj, /* The formal argument list for the method, * which may be NULL; if so, it is equivalent * to an empty list. */ Tcl_Obj *bodyObj, /* The body of the method, which must not be * NULL. */ int flags, /* Whether this is a public method. */ void **internalTokenPtr) /* If non-NULL, points to a variable that gets * the reference to the ProcedureMethod * structure. */ { ProcedureMethod *pmPtr; Tcl_Method method = (Tcl_Method) TclOONewProcMethod(interp, (Class *) clsPtr, flags, nameObj, argsObj, bodyObj, &pmPtr); if (method == NULL) { return NULL; } pmPtr->flags = flags & USE_DECLARER_NS; pmPtr->preCallProc = preCallPtr; pmPtr->postCallProc = postCallPtr; pmPtr->errProc = errProc; pmPtr->clientData = clientData; if (internalTokenPtr != NULL) { *internalTokenPtr = pmPtr; } return method; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclOOProp.c0000644000175000017500000010567014726623136015230 0ustar sergeisergei/* * tclOOProp.c -- * * This file contains implementations of the configurable property * mecnanisms. * * Copyright © 2023-2024 Donal K. Fellows * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclOOInt.h" /* Short-term cache for GetPropertyName(). */ typedef struct GPNCache { Tcl_Obj *listPtr; /* Holds references to names. */ char *names[TCLFLEXARRAY]; /* NULL-terminated table of names. */ } GPNCache; enum GPNFlags { GPN_WRITABLE = 1, /* Are we looking for a writable property? */ GPN_FALLING_BACK = 2 /* Are we doing a recursive call to determine * if the property is of the other type? */ }; /* * Shared bits for [property] declarations. */ enum PropOpt { PROP_ALL, PROP_READABLE, PROP_WRITABLE }; static const char *const propOptNames[] = { "-all", "-readable", "-writable", NULL }; /* * Forward declarations. */ static int Configurable_Getter(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static int Configurable_Setter(void *clientData, Tcl_Interp *interp, Tcl_ObjectContext context, int objc, Tcl_Obj *const *objv); static void DetailsDeleter(void *clientData); static int DetailsCloner(Tcl_Interp *, void *oldClientData, void **newClientData); static void ImplementObjectProperty(Tcl_Object targetObject, Tcl_Obj *propNamePtr, int installGetter, int installSetter); static void ImplementClassProperty(Tcl_Class targetObject, Tcl_Obj *propNamePtr, int installGetter, int installSetter); /* * Method descriptors */ static const Tcl_MethodType GetterType = { TCL_OO_METHOD_VERSION_1, "PropertyGetter", Configurable_Getter, DetailsDeleter, DetailsCloner }; static const Tcl_MethodType SetterType = { TCL_OO_METHOD_VERSION_1, "PropertySetter", Configurable_Setter, DetailsDeleter, DetailsCloner }; /* * ---------------------------------------------------------------------- * * TclOO_Configurable_Configure -- * * Implementation of the oo::configurable->configure method. * * ---------------------------------------------------------------------- */ /* * Ugly thunks to read and write a property by calling the right method in * the right way. Note that we MUST be correct in holding references to Tcl_Obj * values, as this is potentially a call into user code. */ static inline int ReadProperty( Tcl_Interp *interp, Object *oPtr, const char *propName) { Tcl_Obj *args[] = { oPtr->fPtr->myName, Tcl_ObjPrintf("", propName) }; int code; Tcl_IncrRefCount(args[0]); Tcl_IncrRefCount(args[1]); code = TclOOPrivateObjectCmd(oPtr, interp, 2, args); Tcl_DecrRefCount(args[0]); Tcl_DecrRefCount(args[1]); switch (code) { case TCL_BREAK: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "property getter for %s did a break", propName)); return TCL_ERROR; case TCL_CONTINUE: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "property getter for %s did a continue", propName)); return TCL_ERROR; default: return code; } } static inline int WriteProperty( Tcl_Interp *interp, Object *oPtr, const char *propName, Tcl_Obj *valueObj) { Tcl_Obj *args[] = { oPtr->fPtr->myName, Tcl_ObjPrintf("", propName), valueObj }; int code; Tcl_IncrRefCount(args[0]); Tcl_IncrRefCount(args[1]); Tcl_IncrRefCount(args[2]); code = TclOOPrivateObjectCmd(oPtr, interp, 3, args); Tcl_DecrRefCount(args[0]); Tcl_DecrRefCount(args[1]); Tcl_DecrRefCount(args[2]); switch (code) { case TCL_BREAK: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "property setter for %s did a break", propName)); return TCL_ERROR; case TCL_CONTINUE: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "property setter for %s did a continue", propName)); return TCL_ERROR; default: return code; } } /* Look up a property full name. */ static Tcl_Obj * GetPropertyName( Tcl_Interp *interp, /* Context and error reporting. */ Object *oPtr, /* Object to get property name from. */ int flags, /* Are we looking for a writable property? * Can we do a fallback message? * See GPNFlags for possible values */ Tcl_Obj *namePtr, /* The name supplied by the user. */ GPNCache **cachePtr) /* Where to cache the table, if the caller * wants that. The contents are to be freed * with Tcl_Free if the cache is used. */ { Tcl_Size objc, index, i; Tcl_Obj *listPtr = TclOOGetAllObjectProperties( oPtr, flags & GPN_WRITABLE); Tcl_Obj **objv; GPNCache *tablePtr; (void) Tcl_ListObjGetElements(NULL, listPtr, &objc, &objv); if (cachePtr && *cachePtr) { tablePtr = *cachePtr; } else { tablePtr = (GPNCache *) TclStackAlloc(interp, offsetof(GPNCache, names) + sizeof(char *) * (objc + 1)); for (i = 0; i < objc; i++) { tablePtr->names[i] = TclGetString(objv[i]); } tablePtr->names[objc] = NULL; if (cachePtr) { /* * Have a cache, but nothing in it so far. * * We cache the list here so it doesn't vanish from under our * feet if a property implementation does something crazy like * changing the set of properties. The type of copy this does * means that the copy holds the references to the names in the * table. */ tablePtr->listPtr = TclListObjCopy(NULL, listPtr); Tcl_IncrRefCount(tablePtr->listPtr); *cachePtr = tablePtr; } else { tablePtr->listPtr = NULL; } } int result = Tcl_GetIndexFromObjStruct(interp, namePtr, tablePtr->names, sizeof(char *), "property", TCL_INDEX_TEMP_TABLE, &index); if (result == TCL_ERROR && !(flags & GPN_FALLING_BACK)) { /* * If property can be accessed the other way, use a special message. * We use a recursive call to look this up. */ Tcl_InterpState foo = Tcl_SaveInterpState(interp, result); Tcl_Obj *otherName = GetPropertyName(interp, oPtr, flags ^ (GPN_WRITABLE | GPN_FALLING_BACK), namePtr, NULL); result = Tcl_RestoreInterpState(interp, foo); if (otherName != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "property \"%s\" is %s only", TclGetString(otherName), (flags & GPN_WRITABLE) ? "read" : "write")); } } if (!cachePtr) { TclStackFree(interp, tablePtr); } if (result != TCL_OK) { return NULL; } return objv[index]; } /* Release the cache made by GetPropertyName(). */ static inline void ReleasePropertyNameCache( Tcl_Interp *interp, GPNCache **cachePtr) { if (*cachePtr) { GPNCache *tablePtr = *cachePtr; if (tablePtr->listPtr) { Tcl_DecrRefCount(tablePtr->listPtr); } TclStackFree(interp, tablePtr); *cachePtr = NULL; } } int TclOO_Configurable_Configure( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter used for the result, error * reporting, etc. */ Tcl_ObjectContext context, /* The object/call context. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* The actual arguments. */ { Object *oPtr = (Object *) Tcl_ObjectContextObject(context); Tcl_Size skip = Tcl_ObjectContextSkippedArgs(context); Tcl_Obj *namePtr; Tcl_Size i, namec; int code = TCL_OK; objc -= skip; if ((objc & 1) && (objc != 1)) { /* * Bad (odd > 1) number of arguments. */ Tcl_WrongNumArgs(interp, skip, objv, "?-option value ...?"); return TCL_ERROR; } objv += skip; if (objc == 0) { /* * Read all properties. */ Tcl_Obj *listPtr = TclOOGetAllObjectProperties(oPtr, 0); Tcl_Obj *resultPtr = Tcl_NewObj(), **namev; Tcl_IncrRefCount(listPtr); ListObjGetElements(listPtr, namec, namev); for (i = 0; i < namec; ) { code = ReadProperty(interp, oPtr, TclGetString(namev[i])); if (code != TCL_OK) { Tcl_DecrRefCount(resultPtr); break; } Tcl_DictObjPut(NULL, resultPtr, namev[i], Tcl_GetObjResult(interp)); if (++i >= namec) { Tcl_SetObjResult(interp, resultPtr); break; } Tcl_SetObjResult(interp, Tcl_NewObj()); } Tcl_DecrRefCount(listPtr); return code; } else if (objc == 1) { /* * Read a single named property. */ namePtr = GetPropertyName(interp, oPtr, 0, objv[0], NULL); if (namePtr == NULL) { return TCL_ERROR; } return ReadProperty(interp, oPtr, TclGetString(namePtr)); } else if (objc == 2) { /* * Special case for writing to one property. Saves fiddling with the * cache in this common case. */ namePtr = GetPropertyName(interp, oPtr, GPN_WRITABLE, objv[0], NULL); if (namePtr == NULL) { return TCL_ERROR; } code = WriteProperty(interp, oPtr, TclGetString(namePtr), objv[1]); if (code == TCL_OK) { Tcl_ResetResult(interp); } return code; } else { /* * Write properties. Slightly tricky because we want to cache the * table of property names. */ GPNCache *cache = NULL; code = TCL_OK; for (i = 0; i < objc; i += 2) { namePtr = GetPropertyName(interp, oPtr, GPN_WRITABLE, objv[i], &cache); if (namePtr == NULL) { code = TCL_ERROR; break; } code = WriteProperty(interp, oPtr, TclGetString(namePtr), objv[i + 1]); if (code != TCL_OK) { break; } } if (code == TCL_OK) { Tcl_ResetResult(interp); } ReleasePropertyNameCache(interp, &cache); return code; } } /* * ---------------------------------------------------------------------- * * Configurable_Getter, Configurable_Setter -- * * Standard property implementation. The clientData is a simple Tcl_Obj* * that contains the name of the property. * * ---------------------------------------------------------------------- */ static int Configurable_Getter( void *clientData, /* Which property to read. Actually a Tcl_Obj* * reference that is the name of the variable * in the cpntext object. */ Tcl_Interp *interp, /* Interpreter used for the result, error * reporting, etc. */ Tcl_ObjectContext context, /* The object/call context. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* The actual arguments. */ { Tcl_Obj *propNamePtr = (Tcl_Obj *) clientData; Tcl_Var varPtr, aryVar; Tcl_Obj *valuePtr; if ((int) Tcl_ObjectContextSkippedArgs(context) != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, NULL); return TCL_ERROR; } varPtr = TclOOLookupObjectVar(interp, Tcl_ObjectContextObject(context), propNamePtr, &aryVar); if (varPtr == NULL) { return TCL_ERROR; } valuePtr = TclPtrGetVar(interp, varPtr, aryVar, propNamePtr, NULL, TCL_NAMESPACE_ONLY | TCL_LEAVE_ERR_MSG); if (valuePtr == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, valuePtr); return TCL_OK; } static int Configurable_Setter( void *clientData, /* Which property to write. Actually a Tcl_Obj* * reference that is the name of the variable * in the cpntext object. */ Tcl_Interp *interp, /* Interpreter used for the result, error * reporting, etc. */ Tcl_ObjectContext context, /* The object/call context. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* The actual arguments. */ { Tcl_Obj *propNamePtr = (Tcl_Obj *) clientData; Tcl_Var varPtr, aryVar; if ((int) Tcl_ObjectContextSkippedArgs(context) + 1 != objc) { Tcl_WrongNumArgs(interp, Tcl_ObjectContextSkippedArgs(context), objv, "value"); return TCL_ERROR; } varPtr = TclOOLookupObjectVar(interp, Tcl_ObjectContextObject(context), propNamePtr, &aryVar); if (varPtr == NULL) { return TCL_ERROR; } if (TclPtrSetVar(interp, varPtr, aryVar, propNamePtr, NULL, objv[objc - 1], TCL_NAMESPACE_ONLY | TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } return TCL_OK; } // Simple support functions static void DetailsDeleter( void *clientData) { // Just drop the reference count Tcl_Obj *propNamePtr = (Tcl_Obj *) clientData; Tcl_DecrRefCount(propNamePtr); } static int DetailsCloner( TCL_UNUSED(Tcl_Interp *), void *oldClientData, void **newClientData) { // Just add another reference to this name; easy! Tcl_Obj *propNamePtr = (Tcl_Obj *) oldClientData; Tcl_IncrRefCount(propNamePtr); *newClientData = propNamePtr; return TCL_OK; } /* * ---------------------------------------------------------------------- * * ImplementObjectProperty, ImplementClassProperty -- * * Installs a basic property implementation for a property, either on * an instance or on a class. It's up to the code that calls these * to ensure that the property name is syntactically valid. * * ---------------------------------------------------------------------- */ void ImplementObjectProperty( Tcl_Object targetObject, /* What to install into. */ Tcl_Obj *propNamePtr, /* Property name. */ int installGetter, /* Whether to install a standard getter. */ int installSetter) /* Whether to install a standard setter. */ { const char *propName = TclGetString(propNamePtr); while (propName[0] == '-') { propName++; } if (installGetter) { Tcl_Obj *methodName = Tcl_ObjPrintf("", propName); Tcl_IncrRefCount(propNamePtr); // Paired with DetailsDeleter TclNewInstanceMethod( NULL, targetObject, methodName, 0, &GetterType, propNamePtr); Tcl_BounceRefCount(methodName); } if (installSetter) { Tcl_Obj *methodName = Tcl_ObjPrintf("", propName); Tcl_IncrRefCount(propNamePtr); // Paired with DetailsDeleter TclNewInstanceMethod( NULL, targetObject, methodName, 0, &SetterType, propNamePtr); Tcl_BounceRefCount(methodName); } } void ImplementClassProperty( Tcl_Class targetClass, /* What to install into. */ Tcl_Obj *propNamePtr, /* Property name. */ int installGetter, /* Whether to install a standard getter. */ int installSetter) /* Whether to install a standard setter. */ { const char *propName = TclGetString(propNamePtr); while (propName[0] == '-') { propName++; } if (installGetter) { Tcl_Obj *methodName = Tcl_ObjPrintf("", propName); Tcl_IncrRefCount(propNamePtr); // Paired with DetailsDeleter TclNewMethod(targetClass, methodName, 0, &GetterType, propNamePtr); Tcl_BounceRefCount(methodName); } if (installSetter) { Tcl_Obj *methodName = Tcl_ObjPrintf("", propName); Tcl_IncrRefCount(propNamePtr); // Paired with DetailsDeleter TclNewMethod(targetClass, methodName, 0, &SetterType, propNamePtr); Tcl_BounceRefCount(methodName); } } /* * ---------------------------------------------------------------------- * * FindClassProps -- * * Discover the properties known to a class and its superclasses. * The property names become the keys in the accumulator hash table * (which is used as a set). * * ---------------------------------------------------------------------- */ static void FindClassProps( Class *clsPtr, /* The object to inspect. Must exist. */ int writable, /* Whether we're after the readable or writable * property set. */ Tcl_HashTable *accumulator) /* Where to gather the names. */ { int i, dummy; Tcl_Obj *propName; Class *mixin, *sup; tailRecurse: if (writable) { FOREACH(propName, clsPtr->properties.writable) { Tcl_CreateHashEntry(accumulator, (void *) propName, &dummy); } } else { FOREACH(propName, clsPtr->properties.readable) { Tcl_CreateHashEntry(accumulator, (void *) propName, &dummy); } } if (clsPtr->thisPtr->flags & ROOT_OBJECT) { /* * We do *not* traverse upwards from the root! */ return; } FOREACH(mixin, clsPtr->mixins) { FindClassProps(mixin, writable, accumulator); } if (clsPtr->superclasses.num == 1) { clsPtr = clsPtr->superclasses.list[0]; goto tailRecurse; } FOREACH(sup, clsPtr->superclasses) { FindClassProps(sup, writable, accumulator); } } /* * ---------------------------------------------------------------------- * * FindObjectProps -- * * Discover the properties known to an object and all its classes. * The property names become the keys in the accumulator hash table * (which is used as a set). * * ---------------------------------------------------------------------- */ static void FindObjectProps( Object *oPtr, /* The object to inspect. Must exist. */ int writable, /* Whether we're after the readable or writable * property set. */ Tcl_HashTable *accumulator) /* Where to gather the names. */ { int i, dummy; Tcl_Obj *propName; Class *mixin; if (writable) { FOREACH(propName, oPtr->properties.writable) { Tcl_CreateHashEntry(accumulator, (void *) propName, &dummy); } } else { FOREACH(propName, oPtr->properties.readable) { Tcl_CreateHashEntry(accumulator, (void *) propName, &dummy); } } FOREACH(mixin, oPtr->mixins) { FindClassProps(mixin, writable, accumulator); } FindClassProps(oPtr->selfCls, writable, accumulator); } /* * ---------------------------------------------------------------------- * * GetAllClassProperties -- * * Get the list of all properties known to a class, including to its * superclasses. Manages a cache so this operation is usually cheap. * The order of properties in the resulting list is undefined. * * ---------------------------------------------------------------------- */ static Tcl_Obj * GetAllClassProperties( Class *clsPtr, /* The class to inspect. Must exist. */ int writable, /* Whether to get writable properties. If * false, readable properties will be returned * instead. */ int *allocated) /* Address of variable to set to true if a * Tcl_Obj was allocated and may be safely * modified by the caller. */ { Tcl_HashTable hashTable; FOREACH_HASH_DECLS; Tcl_Obj *propName, *result; /* * Look in the cache. */ if (clsPtr->properties.epoch == clsPtr->thisPtr->fPtr->epoch) { if (writable) { if (clsPtr->properties.allWritableCache) { *allocated = 0; return clsPtr->properties.allWritableCache; } } else { if (clsPtr->properties.allReadableCache) { *allocated = 0; return clsPtr->properties.allReadableCache; } } } /* * Gather the information. Unsorted! (Caller will sort.) */ *allocated = 1; Tcl_InitObjHashTable(&hashTable); FindClassProps(clsPtr, writable, &hashTable); TclNewObj(result); FOREACH_HASH_KEY(propName, &hashTable) { Tcl_ListObjAppendElement(NULL, result, propName); } Tcl_DeleteHashTable(&hashTable); /* * Cache the information. Also purges the cache. */ if (clsPtr->properties.epoch != clsPtr->thisPtr->fPtr->epoch) { if (clsPtr->properties.allWritableCache) { Tcl_DecrRefCount(clsPtr->properties.allWritableCache); clsPtr->properties.allWritableCache = NULL; } if (clsPtr->properties.allReadableCache) { Tcl_DecrRefCount(clsPtr->properties.allReadableCache); clsPtr->properties.allReadableCache = NULL; } } clsPtr->properties.epoch = clsPtr->thisPtr->fPtr->epoch; if (writable) { clsPtr->properties.allWritableCache = result; } else { clsPtr->properties.allReadableCache = result; } Tcl_IncrRefCount(result); return result; } /* * ---------------------------------------------------------------------- * * SortPropList -- * Sort a list of names of properties. Simple support function. Assumes * that the list Tcl_Obj is unshared and doesn't have a string * representation. * * ---------------------------------------------------------------------- */ static int PropNameCompare( const void *a, const void *b) { Tcl_Obj *first = *(Tcl_Obj **) a; Tcl_Obj *second = *(Tcl_Obj **) b; return TclStringCmp(first, second, 0, 0, TCL_INDEX_NONE); } static inline void SortPropList( Tcl_Obj *list) { Tcl_Size ec; Tcl_Obj **ev; if (Tcl_IsShared(list)) { Tcl_Panic("shared property list cannot be sorted"); } Tcl_ListObjGetElements(NULL, list, &ec, &ev); TclInvalidateStringRep(list); qsort(ev, ec, sizeof(Tcl_Obj *), PropNameCompare); } /* * ---------------------------------------------------------------------- * * TclOOGetAllObjectProperties -- * * Get the sorted list of all properties known to an object, including to * its classes. Manages a cache so this operation is usually cheap. * * ---------------------------------------------------------------------- */ Tcl_Obj * TclOOGetAllObjectProperties( Object *oPtr, /* The object to inspect. Must exist. */ int writable) /* Whether to get writable properties. If * false, readable properties will be returned * instead. */ { Tcl_HashTable hashTable; FOREACH_HASH_DECLS; Tcl_Obj *propName, *result; /* * Look in the cache. */ if (oPtr->properties.epoch == oPtr->fPtr->epoch) { if (writable) { if (oPtr->properties.allWritableCache) { return oPtr->properties.allWritableCache; } } else { if (oPtr->properties.allReadableCache) { return oPtr->properties.allReadableCache; } } } /* * Gather the information. Unsorted! (Caller will sort.) */ Tcl_InitObjHashTable(&hashTable); FindObjectProps(oPtr, writable, &hashTable); TclNewObj(result); FOREACH_HASH_KEY(propName, &hashTable) { Tcl_ListObjAppendElement(NULL, result, propName); } Tcl_DeleteHashTable(&hashTable); SortPropList(result); /* * Cache the information. */ if (oPtr->properties.epoch != oPtr->fPtr->epoch) { if (oPtr->properties.allWritableCache) { Tcl_DecrRefCount(oPtr->properties.allWritableCache); oPtr->properties.allWritableCache = NULL; } if (oPtr->properties.allReadableCache) { Tcl_DecrRefCount(oPtr->properties.allReadableCache); oPtr->properties.allReadableCache = NULL; } } oPtr->properties.epoch = oPtr->fPtr->epoch; if (writable) { oPtr->properties.allWritableCache = result; } else { oPtr->properties.allReadableCache = result; } Tcl_IncrRefCount(result); return result; } /* * ---------------------------------------------------------------------- * * SetPropertyList -- * * Helper for writing a property list (which is actually a set). * * ---------------------------------------------------------------------- */ static inline void SetPropertyList( PropertyList *propList, /* The property list to write. Replaces the * property list's contents. */ Tcl_Size objc, /* Number of property names. */ Tcl_Obj *const objv[]) /* Property names. */ { Tcl_Size i, n; Tcl_Obj *propObj; int created; Tcl_HashTable uniqueTable; for (i=0 ; ilist); } else if (i) { propList->list = (Tcl_Obj **) Tcl_Realloc(propList->list, sizeof(Tcl_Obj *) * objc); } else { propList->list = (Tcl_Obj **) Tcl_Alloc(sizeof(Tcl_Obj *) * objc); } } propList->num = 0; if (objc > 0) { Tcl_InitObjHashTable(&uniqueTable); for (i=n=0 ; ilist[n++] = objv[i]; } else { Tcl_DecrRefCount(objv[i]); } } propList->num = n; /* * Shouldn't be necessary, but maintain num/list invariant. */ if (n != objc) { propList->list = (Tcl_Obj **) Tcl_Realloc(propList->list, sizeof(Tcl_Obj *) * n); } Tcl_DeleteHashTable(&uniqueTable); } } /* * ---------------------------------------------------------------------- * * TclOOInstallReadableProperties -- * * Helper for writing the readable property list (which is actually a set) * that includes flushing the name cache. * * ---------------------------------------------------------------------- */ void TclOOInstallReadableProperties( PropertyStorage *props, /* Which property list to install into. */ Tcl_Size objc, /* Number of property names. */ Tcl_Obj *const objv[]) /* Property names. */ { if (props->allReadableCache) { Tcl_DecrRefCount(props->allReadableCache); props->allReadableCache = NULL; } SetPropertyList(&props->readable, objc, objv); } /* * ---------------------------------------------------------------------- * * TclOOInstallWritableProperties -- * * Helper for writing the writable property list (which is actually a set) * that includes flushing the name cache. * * ---------------------------------------------------------------------- */ void TclOOInstallWritableProperties( PropertyStorage *props, /* Which property list to install into. */ Tcl_Size objc, /* Number of property names. */ Tcl_Obj *const objv[]) /* Property names. */ { if (props->allWritableCache) { Tcl_DecrRefCount(props->allWritableCache); props->allWritableCache = NULL; } SetPropertyList(&props->writable, objc, objv); } /* * ---------------------------------------------------------------------- * * TclOOGetPropertyList -- * * Helper for reading a property list. * * ---------------------------------------------------------------------- */ Tcl_Obj * TclOOGetPropertyList( PropertyList *propList) /* The property list to read. */ { Tcl_Obj *resultObj, *propNameObj; Tcl_Size i; TclNewObj(resultObj); FOREACH(propNameObj, *propList) { Tcl_ListObjAppendElement(NULL, resultObj, propNameObj); } return resultObj; } /* * ---------------------------------------------------------------------- * * TclOOInstallStdPropertyImpls -- * * Validates a (dashless) property name, and installs implementation * methods if asked to do so (readable and writable flags). * * ---------------------------------------------------------------------- */ int TclOOInstallStdPropertyImpls( void *useInstance, Tcl_Interp *interp, Tcl_Obj *propName, int readable, int writable) { const char *name, *reason; Tcl_Size len; char flag = TCL_DONT_QUOTE_HASH; /* * Validate the property name. Note that just calling TclScanElement() is * cheaper than actually formatting a list and comparing the string * version of that with the original, as TclScanElement() is one of the * core parts of doing that; this skips a whole load of irrelevant memory * allocations! */ name = Tcl_GetStringFromObj(propName, &len); if (Tcl_StringMatch(name, "-*")) { reason = "must not begin with -"; goto badProp; } if (TclScanElement(name, len, &flag) != len) { reason = "must be a simple word"; goto badProp; } if (Tcl_StringMatch(name, "*::*")) { reason = "must not contain namespace separators"; goto badProp; } if (Tcl_StringMatch(name, "*[()]*")) { reason = "must not contain parentheses"; goto badProp; } /* * Install the implementations... if asked to do so. */ if (useInstance) { Tcl_Object object = TclOOGetDefineCmdContext(interp); if (!object) { return TCL_ERROR; } ImplementObjectProperty(object, propName, readable, writable); } else { Tcl_Class cls = (Tcl_Class) TclOOGetClassDefineCmdContext(interp); if (!cls) { return TCL_ERROR; } ImplementClassProperty(cls, propName, readable, writable); } return TCL_OK; badProp: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad property name \"%s\": %s", name, reason)); Tcl_SetErrorCode(interp, "TCL", "OO", "PROPERTY_FORMAT", NULL); return TCL_ERROR; } /* * ---------------------------------------------------------------------- * * TclOODefinePropertyCmd -- * * Implementation of the "property" definition for classes and instances * governed by the [oo::configurable] metaclass. * * ---------------------------------------------------------------------- */ int TclOODefinePropertyCmd( void *useInstance, /* NULL for class, non-NULL for object. */ Tcl_Interp *interp, /* For error reporting and lookup. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { int i; const char *const options[] = { "-get", "-kind", "-set", NULL }; enum Options { OPT_GET, OPT_KIND, OPT_SET }; const char *const kinds[] = { "readable", "readwrite", "writable", NULL }; enum Kinds { KIND_RO, KIND_RW, KIND_WO }; Object *oPtr = (Object *) TclOOGetDefineCmdContext(interp); if (oPtr == NULL) { return TCL_ERROR; } if (!useInstance && !oPtr->classPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "attempt to misuse API", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "OO", "MONKEY_BUSINESS", (char *)NULL); return TCL_ERROR; } for (i = 1; i < objc; i++) { Tcl_Obj *propObj = objv[i], *nextObj, *argObj, *hyphenated; Tcl_Obj *getterScript = NULL, *setterScript = NULL; /* * Parse the extra options for the property. */ int kind = KIND_RW; while (i + 1 < objc) { int option; nextObj = objv[i + 1]; if (TclGetString(nextObj)[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, nextObj, options, "option", 0, &option) != TCL_OK) { return TCL_ERROR; } if (i + 2 >= objc) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "missing %s to go with %s option", (option == OPT_KIND ? "kind value" : "body"), options[option])); Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", NULL); return TCL_ERROR; } argObj = objv[i + 2]; i += 2; switch (option) { case OPT_GET: getterScript = argObj; break; case OPT_SET: setterScript = argObj; break; case OPT_KIND: if (Tcl_GetIndexFromObj(interp, argObj, kinds, "kind", 0, &kind) != TCL_OK) { return TCL_ERROR; } break; } } /* * Install the property. Note that TclOOInstallStdPropertyImpls * validates the property name as well. */ if (TclOOInstallStdPropertyImpls(useInstance, interp, propObj, kind != KIND_WO && getterScript == NULL, kind != KIND_RO && setterScript == NULL) != TCL_OK) { return TCL_ERROR; } hyphenated = Tcl_ObjPrintf("-%s", TclGetString(propObj)); if (useInstance) { TclOORegisterInstanceProperty(oPtr, hyphenated, kind != KIND_WO, kind != KIND_RO); } else { TclOORegisterProperty(oPtr->classPtr, hyphenated, kind != KIND_WO, kind != KIND_RO); } Tcl_BounceRefCount(hyphenated); /* * Create property implementation methods by using the right * back-end API, but only if the user has given us the bodies of the * methods we'll make. */ if (getterScript != NULL) { Tcl_Obj *getterName = Tcl_ObjPrintf("", TclGetString(propObj)); Tcl_Obj *argsPtr = Tcl_NewObj(); Method *mPtr; Tcl_IncrRefCount(getterScript); if (useInstance) { mPtr = TclOONewProcInstanceMethod(interp, oPtr, 0, getterName, argsPtr, getterScript, NULL); } else { mPtr = TclOONewProcMethod(interp, oPtr->classPtr, 0, getterName, argsPtr, getterScript, NULL); } Tcl_BounceRefCount(getterName); Tcl_BounceRefCount(argsPtr); Tcl_DecrRefCount(getterScript); if (mPtr == NULL) { return TCL_ERROR; } } if (setterScript != NULL) { Tcl_Obj *setterName = Tcl_ObjPrintf("", TclGetString(propObj)); Tcl_Obj *argsPtr; Method *mPtr; TclNewLiteralStringObj(argsPtr, "value"); Tcl_IncrRefCount(setterScript); if (useInstance) { mPtr = TclOONewProcInstanceMethod(interp, oPtr, 0, setterName, argsPtr, setterScript, NULL); } else { mPtr = TclOONewProcMethod(interp, oPtr->classPtr, 0, setterName, argsPtr, setterScript, NULL); } Tcl_BounceRefCount(setterName); Tcl_BounceRefCount(argsPtr); Tcl_DecrRefCount(setterScript); if (mPtr == NULL) { return TCL_ERROR; } } } return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOOInfoClassPropCmd, TclOOInfoObjectPropCmd -- * * Implements [info class properties $clsName ?$option...?] and * [info object properties $objName ?$option...?] * * ---------------------------------------------------------------------- */ int TclOOInfoClassPropCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Class *clsPtr; int i, idx, all = 0, writable = 0, allocated = 0; Tcl_Obj *result; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "className ?options...?"); return TCL_ERROR; } clsPtr = TclOOGetClassFromObj(interp, objv[1]); if (clsPtr == NULL) { return TCL_ERROR; } for (i = 2; i < objc; i++) { if (Tcl_GetIndexFromObj(interp, objv[i], propOptNames, "option", 0, &idx) != TCL_OK) { return TCL_ERROR; } switch (idx) { case PROP_ALL: all = 1; break; case PROP_READABLE: writable = 0; break; case PROP_WRITABLE: writable = 1; break; } } /* * Get the properties. */ if (all) { result = GetAllClassProperties(clsPtr, writable, &allocated); if (allocated) { SortPropList(result); } } else { if (writable) { result = TclOOGetPropertyList(&clsPtr->properties.writable); } else { result = TclOOGetPropertyList(&clsPtr->properties.readable); } SortPropList(result); } Tcl_SetObjResult(interp, result); return TCL_OK; } int TclOOInfoObjectPropCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Object *oPtr; int i, idx, all = 0, writable = 0; Tcl_Obj *result; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "objName ?options...?"); return TCL_ERROR; } oPtr = (Object *) Tcl_GetObjectFromObj(interp, objv[1]); if (oPtr == NULL) { return TCL_ERROR; } for (i = 2; i < objc; i++) { if (Tcl_GetIndexFromObj(interp, objv[i], propOptNames, "option", 0, &idx) != TCL_OK) { return TCL_ERROR; } switch (idx) { case PROP_ALL: all = 1; break; case PROP_READABLE: writable = 0; break; case PROP_WRITABLE: writable = 1; break; } } /* * Get the properties. */ if (all) { result = TclOOGetAllObjectProperties(oPtr, writable); } else { if (writable) { result = TclOOGetPropertyList(&oPtr->properties.writable); } else { result = TclOOGetPropertyList(&oPtr->properties.readable); } SortPropList(result); } Tcl_SetObjResult(interp, result); return TCL_OK; } /* * ---------------------------------------------------------------------- * * TclOOReleasePropertyStorage -- * * Delete the memory associated with a class or object's properties. * * ---------------------------------------------------------------------- */ static inline void ReleasePropertyList( PropertyList *propList) { Tcl_Obj *propertyObj; Tcl_Size i; FOREACH(propertyObj, *propList) { Tcl_DecrRefCount(propertyObj); } Tcl_Free(propList->list); propList->list = NULL; propList->num = 0; } void TclOOReleasePropertyStorage( PropertyStorage *propsPtr) { if (propsPtr->allReadableCache) { Tcl_DecrRefCount(propsPtr->allReadableCache); } if (propsPtr->allWritableCache) { Tcl_DecrRefCount(propsPtr->allWritableCache); } if (propsPtr->readable.num) { ReleasePropertyList(&propsPtr->readable); } if (propsPtr->writable.num) { ReleasePropertyList(&propsPtr->writable); } } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclOOScript.h0000644000175000017500000002440014731042632015540 0ustar sergeisergei/* * tclOOScript.h -- * * This file contains support scripts for TclOO. They are defined here so * that the code can be definitely run even in safe interpreters; TclOO's * core setup is safe. * * Copyright (c) 2012-2018 Donal K. Fellows * Copyright (c) 2013 Andreas Kupries * Copyright (c) 2017 Gerald Lester * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef TCL_OO_SCRIPT_H #define TCL_OO_SCRIPT_H /* * The scripted part of the definitions of TclOO. * * Compiled from tools/tclOOScript.tcl by tools/makeHeader.tcl, which * contains the commented version of everything; *this* file is automatically * generated. */ static const char *tclOOSetupScript = /* !BEGIN!: Do not edit below this line. */ "::namespace eval ::oo {\n" "\t::namespace path {}\n" "\tnamespace eval Helpers {\n" "\t\tnamespace path {}\n" "\t\tproc callback {method args} {\n" "\t\t\tlist [uplevel 1 {::namespace which my}] $method {*}$args\n" "\t\t}\n" "\t\tnamespace export callback\n" "\t\tnamespace eval tmp {namespace import ::oo::Helpers::callback}\n" "\t\tnamespace export -clear\n" "\t\trename tmp::callback mymethod\n" "\t\tnamespace delete tmp\n" "\t\tproc classvariable {name args} {\n" "\t\t\tset ns [info object namespace [uplevel 1 {self class}]]\n" "\t\t\tforeach v [list $name {*}$args] {\n" "\t\t\t\tif {[string match *(*) $v]} {\n" "\t\t\t\t\tset reason \"can\'t create a scalar variable that looks like an array element\"\n" "\t\t\t\t\treturn -code error -errorcode {TCL UPVAR LOCAL_ELEMENT} \\\n" "\t\t\t\t\t\t[format {bad variable name \"%s\": %s} $v $reason]\n" "\t\t\t\t}\n" "\t\t\t\tif {[string match *::* $v]} {\n" "\t\t\t\t\tset reason \"can\'t create a local variable with a namespace separator in it\"\n" "\t\t\t\t\treturn -code error -errorcode {TCL UPVAR INVERTED} \\\n" "\t\t\t\t\t\t[format {bad variable name \"%s\": %s} $v $reason]\n" "\t\t\t\t}\n" "\t\t\t\tlappend vs $v $v\n" "\t\t\t}\n" "\t\t\ttailcall namespace upvar $ns {*}$vs\n" "\t\t}\n" "\t\tproc link {args} {\n" "\t\t\tset ns [uplevel 1 {::namespace current}]\n" "\t\t\tforeach link $args {\n" "\t\t\t\tif {[llength $link] == 2} {\n" "\t\t\t\t\tlassign $link src dst\n" "\t\t\t\t} elseif {[llength $link] == 1} {\n" "\t\t\t\t\tlassign $link src\n" "\t\t\t\t\tset dst $src\n" "\t\t\t\t} else {\n" "\t\t\t\t\treturn -code error -errorcode {TCL OO CMDLINK_FORMAT} \\\n" "\t\t\t\t\t\t\"bad link description; must only have one or two elements\"\n" "\t\t\t\t}\n" "\t\t\t\tif {![string match ::* $src]} {\n" "\t\t\t\t\tset src [string cat $ns :: $src]\n" "\t\t\t\t}\n" "\t\t\t\tinterp alias {} $src {} ${ns}::my $dst\n" "\t\t\t\ttrace add command ${ns}::my delete [list \\\n" "\t\t\t\t\t::oo::UnlinkLinkedCommand $src]\n" "\t\t\t}\n" "\t\t\treturn\n" "\t\t}\n" "\t}\n" "\tproc UnlinkLinkedCommand {cmd args} {\n" "\t\tif {[namespace which $cmd] ne {}} {\n" "\t\t\trename $cmd {}\n" "\t\t}\n" "\t}\n" "\tproc DelegateName {class} {\n" "\t\tstring cat [info object namespace $class] {:: oo ::delegate}\n" "\t}\n" "\tproc MixinClassDelegates {class} {\n" "\t\tif {![info object isa class $class]} {\n" "\t\t\treturn\n" "\t\t}\n" "\t\tset delegate [DelegateName $class]\n" "\t\tif {![info object isa class $delegate]} {\n" "\t\t\treturn\n" "\t\t}\n" "\t\tforeach c [info class superclass $class] {\n" "\t\t\tset d [DelegateName $c]\n" "\t\t\tif {![info object isa class $d]} {\n" "\t\t\t\tcontinue\n" "\t\t\t}\n" "\t\t\tdefine $delegate ::oo::define::superclass -appendifnew $d\n" "\t\t}\n" "\t\tobjdefine $class ::oo::objdefine::mixin -appendifnew $delegate\n" "\t}\n" "\tproc UpdateClassDelegatesAfterClone {originObject targetObject} {\n" "\t\tset originDelegate [DelegateName $originObject]\n" "\t\tset targetDelegate [DelegateName $targetObject]\n" "\t\tif {\n" "\t\t\t[info object isa class $originDelegate]\n" "\t\t\t&& ![info object isa class $targetDelegate]\n" "\t\t} then {\n" "\t\t\tcopy $originDelegate $targetDelegate\n" "\t\t\tobjdefine $targetObject ::oo::objdefine::mixin -set \\\n" "\t\t\t\t{*}[lmap c [info object mixin $targetObject] {\n" "\t\t\t\t\tif {$c eq $originDelegate} {set targetDelegate} {set c}\n" "\t\t\t\t}]\n" "\t\t}\n" "\t}\n" "\tproc define::classmethod {name args} {\n" "\t\t::set argc [::llength [::info level 0]]\n" "\t\t::if {$argc == 3} {\n" "\t\t\t::return -code error -errorcode {TCL WRONGARGS} [::format \\\n" "\t\t\t\t{wrong # args: should be \"%s name \?args body\?\"} \\\n" "\t\t\t\t[::lindex [::info level 0] 0]]\n" "\t\t}\n" "\t\t::set cls [::uplevel 1 self]\n" "\t\t::if {$argc == 4} {\n" "\t\t\t::oo::define [::oo::DelegateName $cls] method $name {*}$args\n" "\t\t}\n" "\t\t::tailcall forward $name myclass $name\n" "\t}\n" "\tproc define::initialise {body} {\n" "\t\t::set clsns [::info object namespace [::uplevel 1 self]]\n" "\t\t::tailcall apply [::list {} $body $clsns]\n" "\t}\n" "\tnamespace eval define {\n" "\t\t::namespace export initialise\n" "\t\t::namespace eval tmp {::namespace import ::oo::define::initialise}\n" "\t\t::namespace export -clear\n" "\t\t::rename tmp::initialise initialize\n" "\t\t::namespace delete tmp\n" "\t}\n" "\tdefine Slot {\n" "\t\tmethod Get -unexport {} {\n" "\t\t\treturn -code error -errorcode {TCL OO ABSTRACT_SLOT} \"unimplemented\"\n" "\t\t}\n" "\t\tmethod Set -unexport list {\n" "\t\t\treturn -code error -errorcode {TCL OO ABSTRACT_SLOT} \"unimplemented\"\n" "\t\t}\n" "\t\tmethod Resolve -unexport list {\n" "\t\t\treturn $list\n" "\t\t}\n" "\t\tmethod -set -export args {\n" "\t\t\tset my [namespace which my]\n" "\t\t\tset args [lmap a $args {uplevel 1 [list $my Resolve $a]}]\n" "\t\t\ttailcall my Set $args\n" "\t\t}\n" "\t\tmethod -append -export args {\n" "\t\t\tset my [namespace which my]\n" "\t\t\tset args [lmap a $args {uplevel 1 [list $my Resolve $a]}]\n" "\t\t\tset current [uplevel 1 [list $my Get]]\n" "\t\t\ttailcall my Set [list {*}$current {*}$args]\n" "\t\t}\n" "\t\tmethod -appendifnew -export args {\n" "\t\t\tset my [namespace which my]\n" "\t\t\tset current [uplevel 1 [list $my Get]]\n" "\t\t\tforeach a $args {\n" "\t\t\t\tset a [uplevel 1 [list $my Resolve $a]]\n" "\t\t\t\tif {$a ni $current} {\n" "\t\t\t\t\tlappend current $a\n" "\t\t\t\t}\n" "\t\t\t}\n" "\t\t\ttailcall my Set $current\n" "\t\t}\n" "\t\tmethod -clear -export {} {tailcall my Set {}}\n" "\t\tmethod -prepend -export args {\n" "\t\t\tset my [namespace which my]\n" "\t\t\tset args [lmap a $args {uplevel 1 [list $my Resolve $a]}]\n" "\t\t\tset current [uplevel 1 [list $my Get]]\n" "\t\t\ttailcall my Set [list {*}$args {*}$current]\n" "\t\t}\n" "\t\tmethod -remove -export args {\n" "\t\t\tset my [namespace which my]\n" "\t\t\tset args [lmap a $args {uplevel 1 [list $my Resolve $a]}]\n" "\t\t\tset current [uplevel 1 [list $my Get]]\n" "\t\t\ttailcall my Set [lmap val $current {\n" "\t\t\t\tif {$val in $args} continue else {set val}\n" "\t\t\t}]\n" "\t\t}\n" "\t\tforward --default-operation my -append\n" "\t\tmethod unknown -unexport {args} {\n" "\t\t\tset def --default-operation\n" "\t\t\tif {[llength $args] == 0} {\n" "\t\t\t\ttailcall my $def\n" "\t\t\t} elseif {![string match -* [lindex $args 0]]} {\n" "\t\t\t\ttailcall my $def {*}$args\n" "\t\t\t}\n" "\t\t\tnext {*}$args\n" "\t\t}\n" "\t\tunexport destroy\n" "\t}\n" "\tobjdefine define::superclass forward --default-operation my -set\n" "\tobjdefine define::mixin forward --default-operation my -set\n" "\tobjdefine objdefine::mixin forward --default-operation my -set\n" "\tdefine object method -unexport {originObject} {\n" "\t\tforeach p [info procs [info object namespace $originObject]::*] {\n" "\t\t\tset args [info args $p]\n" "\t\t\tset idx -1\n" "\t\t\tforeach a $args {\n" "\t\t\t\tif {[info default $p $a d]} {\n" "\t\t\t\t\tlset args [incr idx] [list $a $d]\n" "\t\t\t\t} else {\n" "\t\t\t\t\tlset args [incr idx] [list $a]\n" "\t\t\t\t}\n" "\t\t\t}\n" "\t\t\tset b [info body $p]\n" "\t\t\tset p [namespace tail $p]\n" "\t\t\tproc $p $args $b\n" "\t\t}\n" "\t\tforeach v [info vars [info object namespace $originObject]::*] {\n" "\t\t\tupvar 0 $v vOrigin\n" "\t\t\tnamespace upvar [namespace current] [namespace tail $v] vNew\n" "\t\t\tif {[info exists vOrigin]} {\n" "\t\t\t\tif {[array exists vOrigin]} {\n" "\t\t\t\t\tarray set vNew [array get vOrigin]\n" "\t\t\t\t} else {\n" "\t\t\t\t\tset vNew $vOrigin\n" "\t\t\t\t}\n" "\t\t\t}\n" "\t\t}\n" "\t}\n" "\tdefine class method -unexport {originObject} {\n" "\t\tnext $originObject\n" "\t\t::oo::UpdateClassDelegatesAfterClone $originObject [self]\n" "\t}\n" "\tclass create singleton {\n" "\t\tsuperclass class\n" "\t\tvariable object\n" "\t\tunexport create createWithNamespace\n" "\t\tmethod new args {\n" "\t\t\tif {![info exists object] || ![info object isa object $object]} {\n" "\t\t\t\tset object [next {*}$args]\n" "\t\t\t\t::oo::objdefine $object {\n" "\t\t\t\t\tmethod destroy {} {\n" "\t\t\t\t\t\t::return -code error -errorcode {TCL OO SINGLETON} \\\n" "\t\t\t\t\t\t\t\"may not destroy a singleton object\"\n" "\t\t\t\t\t}\n" "\t\t\t\t\tmethod -unexport {originObject} {\n" "\t\t\t\t\t\t::return -code error -errorcode {TCL OO SINGLETON} \\\n" "\t\t\t\t\t\t\t\"may not clone a singleton object\"\n" "\t\t\t\t\t}\n" "\t\t\t\t}\n" "\t\t\t}\n" "\t\t\treturn $object\n" "\t\t}\n" "\t}\n" "\tclass create abstract {\n" "\t\tsuperclass class\n" "\t\tunexport create createWithNamespace new\n" "\t}\n" "\tnamespace eval configuresupport {\n" "\t\t::namespace eval configurableclass {\n" "\t\t\t::proc properties args {::tailcall property {*}$args}\n" "\t\t\t::namespace path ::oo::define\n" "\t\t\t::namespace export property\n" "\t\t}\n" "\t\t::namespace eval configurableobject {\n" "\t\t\t::proc properties args {::tailcall property {*}$args}\n" "\t\t\t::namespace path ::oo::objdefine\n" "\t\t\t::namespace export property\n" "\t\t}\n" "\t\t::oo::define configurable {\n" "\t\t\tdefinitionnamespace -instance configurableobject\n" "\t\t\tdefinitionnamespace -class configurableclass\n" "\t\t}\n" "\t}\n" "\tclass create configurable {\n" "\t\tsuperclass class\n" "\t\tconstructor {{definitionScript \"\"}} {\n" "\t\t\tnext {mixin ::oo::configuresupport::configurable}\n" "\t\t\tnext $definitionScript\n" "\t\t}\n" "\t\tdefinitionnamespace -class configuresupport::configurableclass\n" "\t}\n" "}\n" /* !END!: Do not edit above this line. */ ; #endif /* TCL_OO_SCRIPT_H */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclOOStubInit.c0000644000175000017500000000461714731057471016047 0ustar sergeisergei/* * This file is (mostly) automatically generated from tclOO.decls. * It is compiled and linked in with the tclOO package proper. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "tclOOInt.h" MODULE_SCOPE const TclOOStubs tclOOStubs; #ifdef __GNUC__ #pragma GCC dependency "tclOO.decls" #endif /* !BEGIN!: Do not edit below this line. */ static const TclOOIntStubs tclOOIntStubs = { TCL_STUB_MAGIC, 0, TclOOGetDefineCmdContext, /* 0 */ TclOOMakeProcInstanceMethod, /* 1 */ TclOOMakeProcMethod, /* 2 */ TclOONewProcInstanceMethod, /* 3 */ TclOONewProcMethod, /* 4 */ TclOOObjectCmdCore, /* 5 */ TclOOIsReachable, /* 6 */ TclOONewForwardMethod, /* 7 */ TclOONewForwardInstanceMethod, /* 8 */ TclOONewProcInstanceMethodEx, /* 9 */ TclOONewProcMethodEx, /* 10 */ TclOOInvokeObject, /* 11 */ TclOOObjectSetFilters, /* 12 */ TclOOClassSetFilters, /* 13 */ TclOOObjectSetMixins, /* 14 */ TclOOClassSetMixins, /* 15 */ }; static const TclOOStubHooks tclOOStubHooks = { &tclOOIntStubs }; const TclOOStubs tclOOStubs = { TCL_STUB_MAGIC, &tclOOStubHooks, Tcl_CopyObjectInstance, /* 0 */ Tcl_GetClassAsObject, /* 1 */ Tcl_GetObjectAsClass, /* 2 */ Tcl_GetObjectCommand, /* 3 */ Tcl_GetObjectFromObj, /* 4 */ Tcl_GetObjectNamespace, /* 5 */ Tcl_MethodDeclarerClass, /* 6 */ Tcl_MethodDeclarerObject, /* 7 */ Tcl_MethodIsPublic, /* 8 */ Tcl_MethodIsType, /* 9 */ Tcl_MethodName, /* 10 */ Tcl_NewInstanceMethod, /* 11 */ Tcl_NewMethod, /* 12 */ Tcl_NewObjectInstance, /* 13 */ Tcl_ObjectDeleted, /* 14 */ Tcl_ObjectContextIsFiltering, /* 15 */ Tcl_ObjectContextMethod, /* 16 */ Tcl_ObjectContextObject, /* 17 */ Tcl_ObjectContextSkippedArgs, /* 18 */ Tcl_ClassGetMetadata, /* 19 */ Tcl_ClassSetMetadata, /* 20 */ Tcl_ObjectGetMetadata, /* 21 */ Tcl_ObjectSetMetadata, /* 22 */ Tcl_ObjectContextInvokeNext, /* 23 */ Tcl_ObjectGetMethodNameMapper, /* 24 */ Tcl_ObjectSetMethodNameMapper, /* 25 */ Tcl_ClassSetConstructor, /* 26 */ Tcl_ClassSetDestructor, /* 27 */ Tcl_GetObjectName, /* 28 */ Tcl_MethodIsPrivate, /* 29 */ Tcl_GetClassOfObject, /* 30 */ Tcl_GetObjectClassName, /* 31 */ Tcl_MethodIsType2, /* 32 */ Tcl_NewInstanceMethod2, /* 33 */ Tcl_NewMethod2, /* 34 */ }; /* !END!: Do not edit above this line. */ tcl9.0.1/generic/tclOOStubLib.c0000644000175000017500000000363514726623136015652 0ustar sergeisergei/* * ORIGINAL SOURCE: tk/generic/tkStubLib.c, version 1.9 2004/03/17 */ #include "tclOOInt.h" MODULE_SCOPE const TclOOStubs *tclOOStubsPtr; MODULE_SCOPE const TclOOIntStubs *tclOOIntStubsPtr; const TclOOStubs *tclOOStubsPtr = NULL; const TclOOIntStubs *tclOOIntStubsPtr = NULL; /* *---------------------------------------------------------------------- * * TclOOInitializeStubs -- * Load the tclOO package, initialize stub table pointer. Do not call * this function directly, use Tcl_OOInitStubs() macro instead. * * Results: * The actual version of the package that satisfies the request, or NULL * to indicate that an error occurred. * * Side effects: * Sets the stub table pointers. * *---------------------------------------------------------------------- */ #undef TclOOInitializeStubs MODULE_SCOPE const char * TclOOInitializeStubs( Tcl_Interp *interp, const char *version) { int exact = 0; const char *packageName = "tcl::oo"; const char *errMsg = NULL; TclOOStubs *stubsPtr = NULL; const char *actualVersion = tclStubsPtr->tcl_PkgRequireEx(interp, packageName, version, exact, &stubsPtr); if (actualVersion == NULL) { packageName = "TclOO"; actualVersion = tclStubsPtr->tcl_PkgRequireEx(interp, packageName, version, exact, &stubsPtr); if (actualVersion == NULL) { return NULL; } } if (stubsPtr == NULL) { errMsg = "missing stub table pointer"; } else { tclOOStubsPtr = stubsPtr; if (stubsPtr->hooks) { tclOOIntStubsPtr = stubsPtr->hooks->tclOOIntStubs; } else { tclOOIntStubsPtr = NULL; } return actualVersion; } tclStubsPtr->tcl_ResetResult(interp); tclStubsPtr->tcl_AppendResult(interp, "Error loading ", packageName, " (requested version ", version, ", actual version ", actualVersion, "): ", errMsg, NULL); return NULL; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclObj.c0000644000175000017500000041255714726623136014571 0ustar sergeisergei/* * tclObj.c -- * * This file contains Tcl object-related functions that are used by many * Tcl commands. * * Copyright © 1995-1997 Sun Microsystems, Inc. * Copyright © 1999 Scriptics Corporation. * Copyright © 2001 ActiveState Corporation. * Copyright © 2005 Kevin B. Kenny. All rights reserved. * Copyright © 2007 Daniel A. Steffen * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclTomMath.h" #include #include /* * Table of all object types. */ static Tcl_HashTable typeTable; static int typeTableInitialized = 0; /* 0 means not yet initialized. */ TCL_DECLARE_MUTEX(tableMutex) /* * Head of the list of free Tcl_Obj structs we maintain. */ Tcl_Obj *tclFreeObjList = NULL; /* * The object allocator is single threaded. This mutex is referenced by the * TclNewObj macro, however, so must be visible. */ #if TCL_THREADS MODULE_SCOPE Tcl_Mutex tclObjMutex; Tcl_Mutex tclObjMutex; #endif /* * Pointer to a heap-allocated string of length zero that the Tcl core uses as * the value of an empty string representation for an object. This value is * shared by all new objects allocated by Tcl_NewObj. */ char tclEmptyString = '\0'; #if TCL_THREADS && defined(TCL_MEM_DEBUG) /* * Structure for tracking the source file and line number where a given * Tcl_Obj was allocated. We also track the pointer to the Tcl_Obj itself, * for sanity checking purposes. */ typedef struct { Tcl_Obj *objPtr; /* The pointer to the allocated Tcl_Obj. */ const char *file; /* The name of the source file calling this * function; used for debugging. */ int line; /* Line number in the source file; used for * debugging. */ } ObjData; #endif /* TCL_MEM_DEBUG && TCL_THREADS */ /* * All static variables used in this file are collected into a single instance * of the following structure. For multi-threaded implementations, there is * one instance of this structure for each thread. * * Notice that different structures with the same name appear in other files. * The structure defined below is used in this file only. */ typedef struct { Tcl_HashTable *lineCLPtr; /* This table remembers for each Tcl_Obj * generated by a call to the function * TclSubstTokens() from a literal text * where bs+nl sequences occurred in it, if * any. I.e. this table keeps track of * invisible and stripped continuation lines. * Its keys are Tcl_Obj pointers, the values * are ContLineLoc pointers. See the file * tclCompile.h for the definition of this * structure, and for references to all * related places in the core. */ #if TCL_THREADS && defined(TCL_MEM_DEBUG) Tcl_HashTable *objThreadMap;/* Thread local table that is used to check * that a Tcl_Obj was not allocated by some * other thread. */ #endif /* TCL_MEM_DEBUG && TCL_THREADS */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; static void TclThreadFinalizeContLines(void *clientData); static ThreadSpecificData *TclGetContLineTable(void); /* * Nested Tcl_Obj deletion management support * * All context references used in the object freeing code are pointers to this * structure; every thread will have its own structure instance. The purpose * of this structure is to allow deeply nested collections of Tcl_Objs to be * freed without taking a vast depth of C stack (which could cause all sorts * of breakage.) */ typedef struct PendingObjData { int deletionCount; /* Count of the number of invocations of * TclFreeObj() are on the stack (at least * conceptually; many are actually expanded * macros). */ Tcl_Obj *deletionStack; /* Stack of objects that have had TclFreeObj() * invoked upon them but which can't be * deleted yet because they are in a nested * invocation of TclFreeObj(). By postponing * this way, we limit the maximum overall C * stack depth when deleting a complex object. * The down-side is that we alter the overall * behaviour by altering the order in which * objects are deleted, and we change the * order in which the string rep and the * internal rep of an object are deleted. Note * that code which assumes the previous * behaviour in either of these respects is * unsafe anyway; it was never documented as * to exactly what would happen in these * cases, and the overall contract of a * user-level Tcl_DecrRefCount() is still * preserved (assuming that a particular T_DRC * would delete an object is not very * safe). */ } PendingObjData; /* * These are separated out so that some semantic content is attached * to them. */ #define ObjDeletionLock(contextPtr) ((contextPtr)->deletionCount++) #define ObjDeletionUnlock(contextPtr) ((contextPtr)->deletionCount--) #define ObjDeletePending(contextPtr) ((contextPtr)->deletionCount > 0) #define ObjOnStack(contextPtr) ((contextPtr)->deletionStack != NULL) #define PushObjToDelete(contextPtr,objPtr) \ /* The string rep is already invalidated so we can use the bytes value \ * for our pointer chain: push onto the head of the stack. */ \ (objPtr)->bytes = (char *) ((contextPtr)->deletionStack); \ (contextPtr)->deletionStack = (objPtr) #define PopObjToDelete(contextPtr,objPtrVar) \ (objPtrVar) = (contextPtr)->deletionStack; \ (contextPtr)->deletionStack = (Tcl_Obj *) (objPtrVar)->bytes /* * Macro to set up the local reference to the deletion context. */ #if !TCL_THREADS static PendingObjData pendingObjData; #define ObjInitDeletionContext(contextPtr) \ PendingObjData *const contextPtr = &pendingObjData #elif defined(HAVE_FAST_TSD) static __thread PendingObjData pendingObjData; #define ObjInitDeletionContext(contextPtr) \ PendingObjData *const contextPtr = &pendingObjData #else static Tcl_ThreadDataKey pendingObjDataKey; #define ObjInitDeletionContext(contextPtr) \ PendingObjData *const contextPtr = \ (PendingObjData *)Tcl_GetThreadData(&pendingObjDataKey, sizeof(PendingObjData)) #endif /* * Macros to pack/unpack a bignum's fields in a Tcl_Obj internal rep */ #define PACK_BIGNUM(bignum, objPtr) \ if ((bignum).used > 0x7FFF) { \ mp_int *temp = (mp_int *)Tcl_Alloc(sizeof(mp_int)); \ *temp = bignum; \ (objPtr)->internalRep.twoPtrValue.ptr1 = temp; \ (objPtr)->internalRep.twoPtrValue.ptr2 = INT2PTR(-1); \ } else if (((bignum).alloc <= 0x7FFF) || (mp_shrink(&(bignum))) == MP_OKAY) { \ (objPtr)->internalRep.twoPtrValue.ptr1 = (bignum).dp; \ (objPtr)->internalRep.twoPtrValue.ptr2 = INT2PTR(((bignum).sign << 30) \ | ((bignum).alloc << 15) | ((bignum).used)); \ } /* * Prototypes for functions defined later in this file: */ static int ParseBoolean(Tcl_Obj *objPtr); static int SetDoubleFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); static int SetIntFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); static void UpdateStringOfDouble(Tcl_Obj *objPtr); static void UpdateStringOfInt(Tcl_Obj *objPtr); static void FreeBignum(Tcl_Obj *objPtr); static void DupBignum(Tcl_Obj *objPtr, Tcl_Obj *copyPtr); static void UpdateStringOfBignum(Tcl_Obj *objPtr); static int GetBignumFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int copy, mp_int *bignumValue); /* * Prototypes for the array hash key methods. */ static Tcl_HashEntry * AllocObjEntry(Tcl_HashTable *tablePtr, void *keyPtr); /* * Prototypes for the CommandName object type. */ static void DupCmdNameInternalRep(Tcl_Obj *objPtr, Tcl_Obj *copyPtr); static void FreeCmdNameInternalRep(Tcl_Obj *objPtr); static int SetCmdNameFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); /* * The structures below defines the Tcl object types defined in this file by * means of functions that can be invoked by generic object code. See also * tclStringObj.c, tclListObj.c, tclByteCode.c for other type manager * implementations. */ const Tcl_ObjType tclBooleanType= { "boolean", /* name */ NULL, /* freeIntRepProc */ NULL, /* dupIntRepProc */ NULL, /* updateStringProc */ TclSetBooleanFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V1(TclLengthOne) }; const Tcl_ObjType tclDoubleType= { "double", /* name */ NULL, /* freeIntRepProc */ NULL, /* dupIntRepProc */ UpdateStringOfDouble, /* updateStringProc */ SetDoubleFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V1(TclLengthOne) }; const Tcl_ObjType tclIntType = { "int", /* name */ NULL, /* freeIntRepProc */ NULL, /* dupIntRepProc */ UpdateStringOfInt, /* updateStringProc */ SetIntFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V1(TclLengthOne) }; const Tcl_ObjType tclBignumType = { "bignum", /* name */ FreeBignum, /* freeIntRepProc */ DupBignum, /* dupIntRepProc */ UpdateStringOfBignum, /* updateStringProc */ NULL, /* setFromAnyProc */ TCL_OBJTYPE_V1(TclLengthOne) }; /* * The structure below defines the Tcl obj hash key type. */ const Tcl_HashKeyType tclObjHashKeyType = { TCL_HASH_KEY_TYPE_VERSION, /* version */ TCL_HASH_KEY_DIRECT_COMPARE,/* allows compare keys by pointers */ TclHashObjKey, /* hashKeyProc */ TclCompareObjKeys, /* compareKeysProc */ AllocObjEntry, /* allocEntryProc */ TclFreeObjEntry /* freeEntryProc */ }; /* * The structure below defines the command name Tcl object type by means of * functions that can be invoked by generic object code. Objects of this type * cache the Command pointer that results from looking up command names in the * command hashtable. Such objects appear as the zeroth ("command name") * argument in a Tcl command. * * NOTE: the ResolvedCmdName that gets cached is stored in the * twoPtrValue.ptr1 field, and the twoPtrValue.ptr2 field is unused. You might * think you could use the simpler otherValuePtr field to store the single * ResolvedCmdName pointer, but DO NOT DO THIS. It seems that some extensions * use the second internal pointer field of the twoPtrValue field for their * own purposes. * * TRICKY POINT! Some extensions update this structure! (Notably, these * include TclBlend and TCom). This is highly ill-advised on their part, but * does allow them to delete a command when references to it are gone, which * is fragile but useful given their somewhat-OO style. Because of this, this * structure MUST NOT be const so that the C compiler puts the data in * writable memory. [Bug 2558422] [Bug 07d13d99b0a9] * TODO: Provide a better API for those extensions so that they can coexist... */ Tcl_ObjType tclCmdNameType = { "cmdName", /* name */ FreeCmdNameInternalRep, /* freeIntRepProc */ DupCmdNameInternalRep, /* dupIntRepProc */ NULL, /* updateStringProc */ SetCmdNameFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; /* * Structure containing a cached pointer to a command that is the result of * resolving the command's name in some namespace. It is the internal * representation for a cmdName object. It contains the pointer along with * some information that is used to check the pointer's validity. */ typedef struct ResolvedCmdName { Command *cmdPtr; /* A cached Command pointer. */ Namespace *refNsPtr; /* Points to the namespace containing the * reference (not the namespace that contains * the referenced command). NULL if the name * is fully qualified.*/ size_t refNsId; /* refNsPtr's unique namespace id. Used to * verify that refNsPtr is still valid (e.g., * it's possible that the cmd's containing * namespace was deleted and a new one created * at the same address). */ Tcl_Size refNsCmdEpoch; /* Value of the referencing namespace's * cmdRefEpoch when the pointer was cached. * Before using the cached pointer, we check * if the namespace's epoch was incremented; * if so, this cached pointer is invalid. */ Tcl_Size cmdEpoch; /* Value of the command's cmdEpoch when this * pointer was cached. Before using the cached * pointer, we check if the cmd's epoch was * incremented; if so, the cmd was renamed, * deleted, hidden, or exposed, and so the * pointer is invalid. */ size_t refCount; /* Reference count: 1 for each cmdName object * that has a pointer to this ResolvedCmdName * structure as its internal rep. This * structure can be freed when refCount * becomes zero. */ } ResolvedCmdName; #ifdef TCL_MEM_DEBUG /* * Filler matches the value used for filling freed memory in tclCkalloc. * On 32-bit systems, the ref counts do not cross 0x7fffffff. On 64-bit * implementations, ref counts will never reach this value (unless explicitly * incremented without actual references!) */ #define FREEDREFCOUNTFILLER \ (Tcl_Size)(sizeof(objPtr->refCount) == 4 ? 0xe8e8e8e8 : 0xe8e8e8e8e8e8e8e8) #endif /* *------------------------------------------------------------------------- * * TclInitObjectSubsystem -- * * This function is invoked to perform once-only initialization of the * type table. It also registers the object types defined in this file. * * Results: * None. * * Side effects: * Initializes the table of defined object types "typeTable" with builtin * object types defined in this file. * *------------------------------------------------------------------------- */ void TclInitObjSubsystem(void) { Tcl_MutexLock(&tableMutex); typeTableInitialized = 1; Tcl_InitHashTable(&typeTable, TCL_STRING_KEYS); Tcl_MutexUnlock(&tableMutex); Tcl_RegisterObjType(&tclByteCodeType); Tcl_RegisterObjType(&tclCmdNameType); Tcl_RegisterObjType(&tclDictType); Tcl_RegisterObjType(&tclDoubleType); Tcl_RegisterObjType(&tclListType); Tcl_RegisterObjType(&tclProcBodyType); Tcl_RegisterObjType(&tclRegexpType); Tcl_RegisterObjType(&tclStringType); #ifdef TCL_COMPILE_STATS Tcl_MutexLock(&tclObjMutex); tclObjsAlloced = 0; tclObjsFreed = 0; { int i; for (i=0 ; iobjThreadMap; if (tablePtr != NULL) { for (hPtr = Tcl_FirstHashEntry(tablePtr, &hSearch); hPtr != NULL; hPtr = Tcl_NextHashEntry(&hSearch)) { ObjData *objData = (ObjData *)Tcl_GetHashValue(hPtr); if (objData != NULL) { Tcl_Free(objData); } } Tcl_DeleteHashTable(tablePtr); Tcl_Free(tablePtr); tsdPtr->objThreadMap = NULL; } #endif } /* *---------------------------------------------------------------------- * * TclFinalizeObjects -- * * This function is called by Tcl_Finalize to clean up all registered * Tcl_ObjType's and to reset the tclFreeObjList. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclFinalizeObjects(void) { Tcl_MutexLock(&tableMutex); if (typeTableInitialized) { Tcl_DeleteHashTable(&typeTable); typeTableInitialized = 0; } Tcl_MutexUnlock(&tableMutex); /* * All we do here is reset the head pointer of the linked list of free * Tcl_Obj's to NULL; the memory finalization will take care of releasing * memory for us. */ Tcl_MutexLock(&tclObjMutex); tclFreeObjList = NULL; Tcl_MutexUnlock(&tclObjMutex); } /* *---------------------------------------------------------------------- * * TclGetContLineTable -- * * This procedure is a helper which returns the thread-specific * hash-table used to track continuation line information associated with * Tcl_Obj*, and the objThreadMap, etc. * * Results: * A reference to the thread-data. * * Side effects: * May allocate memory for the thread-data. * * TIP #280 *---------------------------------------------------------------------- */ static ThreadSpecificData * TclGetContLineTable(void) { /* * Initialize the hashtable tracking invisible continuation lines. For * the release we use a thread exit handler to ensure that this is done * before TSD blocks are made invalid. The TclFinalizeObjects() which * would be the natural place for this is invoked afterwards, meaning that * we try to operate on a data structure already gone. */ ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!tsdPtr->lineCLPtr) { tsdPtr->lineCLPtr = (Tcl_HashTable *)Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(tsdPtr->lineCLPtr, TCL_ONE_WORD_KEYS); Tcl_CreateThreadExitHandler(TclThreadFinalizeContLines,NULL); } return tsdPtr; } /* *---------------------------------------------------------------------- * * TclContinuationsEnter -- * * This procedure is a helper which saves the continuation line * information associated with a Tcl_Obj*. * * Results: * A reference to the newly created continuation line location table. * * Side effects: * Allocates memory for the table of continuation line locations. * * TIP #280 *---------------------------------------------------------------------- */ ContLineLoc * TclContinuationsEnter( Tcl_Obj *objPtr, Tcl_Size num, Tcl_Size *loc) { int newEntry; ThreadSpecificData *tsdPtr = TclGetContLineTable(); Tcl_HashEntry *hPtr = Tcl_CreateHashEntry(tsdPtr->lineCLPtr, objPtr, &newEntry); ContLineLoc *clLocPtr = (ContLineLoc *)Tcl_Alloc(offsetof(ContLineLoc, loc) + (num + 1U) *sizeof(Tcl_Size)); if (!newEntry) { /* * We're entering ContLineLoc data for the same value more than one * time. Taking care not to leak the old entry. * * This can happen when literals in a proc body are shared. See for * example test info-30.19 where the action (code) for all branches of * the switch command is identical, mapping them all to the same * literal. An interesting result of this is that the number and * locations (offset) of invisible continuation lines in the literal * are the same for all occurrences. * * Note that while reusing the existing entry is possible it requires * the same actions as for a new entry because we have to copy the * incoming num/loc data even so. Because we are called from * TclContinuationsEnterDerived for this case, which modified the * stored locations (Rebased to the proper relative offset). Just * returning the stored entry would rebase them a second time, or * more, hosing the data. It is easier to simply replace, as we are * doing. */ Tcl_Free(Tcl_GetHashValue(hPtr)); } clLocPtr->num = num; memcpy(&clLocPtr->loc, loc, num*sizeof(Tcl_Size)); clLocPtr->loc[num] = CLL_END; /* Sentinel */ Tcl_SetHashValue(hPtr, clLocPtr); return clLocPtr; } /* *---------------------------------------------------------------------- * * TclContinuationsEnterDerived -- * * This procedure is a helper which computes the continuation line * information associated with a Tcl_Obj* cut from the middle of a * script. * * Results: * None. * * Side effects: * Allocates memory for the table of continuation line locations. * * TIP #280 *---------------------------------------------------------------------- */ void TclContinuationsEnterDerived( Tcl_Obj *objPtr, Tcl_Size start, Tcl_Size *clNext) { Tcl_Size length; Tcl_Size end, num; Tcl_Size *wordCLLast = clNext; /* * We have to handle invisible continuations lines here as well, despite * the code we have in TclSubstTokens (TST) for that. Why ? Nesting. If * our script is the sole argument to an 'eval' command, for example, the * scriptCLLocPtr we are using was generated by a previous call to TST, * and while the words we have here may contain continuation lines they * are invisible already, and the inner call to TST had no bs+nl sequences * to trigger its code. * * Luckily for us, the table we have to create here for the current word * has to be a slice of the table currently in use, with the locations * suitably modified to be relative to the start of the word instead of * relative to the script. * * That is what we are doing now. Determine the slice we need, and if not * empty, wrap it into a new table, and save the result into our * thread-global hashtable, as usual. */ /* * First compute the range of the word within the script. (Is there a * better way which doesn't shimmer?) */ (void)TclGetStringFromObj(objPtr, &length); end = start + length; /* First char after the word */ /* * Then compute the table slice covering the range of the word. */ while (*wordCLLast >= 0 && *wordCLLast < end) { wordCLLast++; } /* * And generate the table from the slice, if it was not empty. */ num = wordCLLast - clNext; if (num) { Tcl_Size i; ContLineLoc *clLocPtr = TclContinuationsEnter(objPtr, num, clNext); /* * Re-base the locations. */ for (i=0 ; iloc[i] -= start; /* * Continuation lines coming before the string and affecting us * should not happen, due to the proper maintenance of clNext * during compilation. */ if (clLocPtr->loc[i] < 0) { Tcl_Panic("Derived ICL data for object using offsets from before the script"); } } } } /* *---------------------------------------------------------------------- * * TclContinuationsCopy -- * * This procedure is a helper which copies the continuation line * information associated with a Tcl_Obj* to another Tcl_Obj*. It is * assumed that both contain the same string/script. Use this when a * script is duplicated because it was shared. * * Results: * None. * * Side effects: * Allocates memory for the table of continuation line locations. * * TIP #280 *---------------------------------------------------------------------- */ void TclContinuationsCopy( Tcl_Obj *objPtr, Tcl_Obj *originObjPtr) { ThreadSpecificData *tsdPtr = TclGetContLineTable(); Tcl_HashEntry *hPtr = Tcl_FindHashEntry(tsdPtr->lineCLPtr, originObjPtr); if (hPtr) { ContLineLoc *clLocPtr = (ContLineLoc *)Tcl_GetHashValue(hPtr); TclContinuationsEnter(objPtr, clLocPtr->num, clLocPtr->loc); } } /* *---------------------------------------------------------------------- * * TclContinuationsGet -- * * This procedure is a helper which retrieves the continuation line * information associated with a Tcl_Obj*, if it has any. * * Results: * A reference to the continuation line location table, or NULL if the * Tcl_Obj* has no such information associated with it. * * Side effects: * None. * * TIP #280 *---------------------------------------------------------------------- */ ContLineLoc * TclContinuationsGet( Tcl_Obj *objPtr) { ThreadSpecificData *tsdPtr = TclGetContLineTable(); Tcl_HashEntry *hPtr = Tcl_FindHashEntry(tsdPtr->lineCLPtr, objPtr); if (!hPtr) { return NULL; } return (ContLineLoc *)Tcl_GetHashValue(hPtr); } /* *---------------------------------------------------------------------- * * TclThreadFinalizeContLines -- * * This procedure is a helper which releases all continuation line * information currently known. It is run as a thread exit handler. * * Results: * None. * * Side effects: * Releases memory. * * TIP #280 *---------------------------------------------------------------------- */ static void TclThreadFinalizeContLines( TCL_UNUSED(void *)) { /* * Release the hashtable tracking invisible continuation lines. */ ThreadSpecificData *tsdPtr = TclGetContLineTable(); Tcl_HashEntry *hPtr; Tcl_HashSearch hSearch; for (hPtr = Tcl_FirstHashEntry(tsdPtr->lineCLPtr, &hSearch); hPtr != NULL; hPtr = Tcl_NextHashEntry(&hSearch)) { Tcl_Free(Tcl_GetHashValue(hPtr)); Tcl_DeleteHashEntry(hPtr); } Tcl_DeleteHashTable(tsdPtr->lineCLPtr); Tcl_Free(tsdPtr->lineCLPtr); tsdPtr->lineCLPtr = NULL; } /* *-------------------------------------------------------------- * * Tcl_RegisterObjType -- * * This function is called to register a new Tcl object type in the table * of all object types supported by Tcl. * * Results: * None. * * Side effects: * The type is registered in the Tcl type table. If there was already a * type with the same name as in typePtr, it is replaced with the new * type. * *-------------------------------------------------------------- */ void Tcl_RegisterObjType( const Tcl_ObjType *typePtr) /* Information about object type; storage must * be statically allocated (must live * forever). */ { int isNew; Tcl_MutexLock(&tableMutex); Tcl_SetHashValue( Tcl_CreateHashEntry(&typeTable, typePtr->name, &isNew), typePtr); Tcl_MutexUnlock(&tableMutex); } /* *---------------------------------------------------------------------- * * Tcl_AppendAllObjTypes -- * * This function appends onto the argument object the name of each object * type as a list element. This includes the builtin object types (e.g. * int, list) as well as those added using Tcl_NewObj. These names can be * used, for example, with Tcl_GetObjType to get pointers to the * corresponding Tcl_ObjType structures. * * Results: * The return value is normally TCL_OK; in this case the object * referenced by objPtr has each type name appended to it. If an error * occurs, TCL_ERROR is returned and the interpreter's result holds an * error message. * * Side effects: * If necessary, the object referenced by objPtr is converted into a list * object. * *---------------------------------------------------------------------- */ int Tcl_AppendAllObjTypes( Tcl_Interp *interp, /* Interpreter used for error reporting. */ Tcl_Obj *objPtr) /* Points to the Tcl object onto which the * name of each registered type is appended as * a list element. */ { Tcl_HashEntry *hPtr; Tcl_HashSearch search; Tcl_Size numElems; /* * Get the test for a valid list out of the way first. */ if (TclListObjLength(interp, objPtr, &numElems) != TCL_OK) { return TCL_ERROR; } /* * Type names are NUL-terminated, not counted strings. This code relies on * that. */ Tcl_MutexLock(&tableMutex); for (hPtr = Tcl_FirstHashEntry(&typeTable, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj((char *)Tcl_GetHashKey(&typeTable, hPtr), -1)); } Tcl_MutexUnlock(&tableMutex); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_GetObjType -- * * This function looks up an object type by name. * * Results: * If an object type with name matching "typeName" is found, a pointer to * its Tcl_ObjType structure is returned; otherwise, NULL is returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ const Tcl_ObjType * Tcl_GetObjType( const char *typeName) /* Name of Tcl object type to look up. */ { Tcl_HashEntry *hPtr; const Tcl_ObjType *typePtr = NULL; Tcl_MutexLock(&tableMutex); hPtr = Tcl_FindHashEntry(&typeTable, typeName); if (hPtr != NULL) { typePtr = (const Tcl_ObjType *)Tcl_GetHashValue(hPtr); } Tcl_MutexUnlock(&tableMutex); return typePtr; } /* *---------------------------------------------------------------------- * * Tcl_ConvertToType -- * * Convert the Tcl object "objPtr" to have type "typePtr" if possible. * * Results: * The return value is TCL_OK on success and TCL_ERROR on failure. If * TCL_ERROR is returned, then the interpreter's result contains an error * message unless "interp" is NULL. Passing a NULL "interp" allows this * function to be used as a test whether the conversion could be done * (and in fact was done). * * Side effects: * Any internal representation for the old type is freed. * *---------------------------------------------------------------------- */ int Tcl_ConvertToType( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr, /* The object to convert. */ const Tcl_ObjType *typePtr) /* The target type. */ { if (objPtr->typePtr == typePtr) { return TCL_OK; } /* * Use the target type's Tcl_SetFromAnyProc to set "objPtr"s internal form * as appropriate for the target type. This frees the old internal * representation. */ if (typePtr->setFromAnyProc == NULL) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't convert value to type %s", typePtr->name)); Tcl_SetErrorCode(interp, "TCL", "API_ABUSE", (char *)NULL); } return TCL_ERROR; } return typePtr->setFromAnyProc(interp, objPtr); } /* *-------------------------------------------------------------- * * TclDbDumpActiveObjects -- * * This function is called to dump all of the active Tcl_Obj structs this * allocator knows about. * * Results: * None. * * Side effects: * None. * *-------------------------------------------------------------- */ #if TCL_THREADS && defined(TCL_MEM_DEBUG) void TclDbDumpActiveObjects( FILE *outFile) { Tcl_HashSearch hSearch; Tcl_HashEntry *hPtr; Tcl_HashTable *tablePtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); tablePtr = tsdPtr->objThreadMap; if (tablePtr != NULL) { fprintf(outFile, "total objects: %" TCL_SIZE_MODIFIER "d\n", tablePtr->numEntries); for (hPtr = Tcl_FirstHashEntry(tablePtr, &hSearch); hPtr != NULL; hPtr = Tcl_NextHashEntry(&hSearch)) { ObjData *objData = (ObjData *)Tcl_GetHashValue(hPtr); if (objData != NULL) { fprintf(outFile, "key = 0x%p, objPtr = 0x%p, file = %s, line = %d\n", Tcl_GetHashKey(tablePtr, hPtr), objData->objPtr, objData->file, objData->line); } else { fprintf(outFile, "key = 0x%p\n", Tcl_GetHashKey(tablePtr, hPtr)); } } } } #else void TclDbDumpActiveObjects( TCL_UNUSED(FILE *)) { } #endif /* *---------------------------------------------------------------------- * * TclDbInitNewObj -- * * Called via the TclNewObj or TclDbNewObj macros when TCL_MEM_DEBUG is * enabled. This function will initialize the members of a Tcl_Obj * struct. Initialization would be done inline via the TclNewObj macro * when compiling without TCL_MEM_DEBUG. * * Results: * The Tcl_Obj struct members are initialized. * * Side effects: * None. *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG void TclDbInitNewObj( Tcl_Obj *objPtr, const char *file, /* The name of the source file calling this * function; used for debugging. */ int line) /* Line number in the source file; used for * debugging. */ { objPtr->refCount = 0; objPtr->typePtr = NULL; TclInitEmptyStringRep(objPtr); #if TCL_THREADS /* * Add entry to a thread local map used to check if a Tcl_Obj was * allocated by the currently executing thread. */ if (!TclInExit()) { Tcl_HashEntry *hPtr; Tcl_HashTable *tablePtr; int isNew; ObjData *objData; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (tsdPtr->objThreadMap == NULL) { tsdPtr->objThreadMap = (Tcl_HashTable *)Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(tsdPtr->objThreadMap, TCL_ONE_WORD_KEYS); } tablePtr = tsdPtr->objThreadMap; hPtr = Tcl_CreateHashEntry(tablePtr, objPtr, &isNew); if (!isNew) { Tcl_Panic("expected to create new entry for object map"); } /* * Record the debugging information. */ objData = (ObjData *)Tcl_Alloc(sizeof(ObjData)); objData->objPtr = objPtr; objData->file = file; objData->line = line; Tcl_SetHashValue(hPtr, objData); } #endif /* TCL_THREADS */ } #endif /* TCL_MEM_DEBUG */ /* *---------------------------------------------------------------------- * * Tcl_NewObj -- * * This function is normally called when not debugging: i.e., when * TCL_MEM_DEBUG is not defined. It creates new Tcl objects that denote * the empty string. These objects have a NULL object type and NULL * string representation byte pointer. Type managers call this routine to * allocate new objects that they further initialize. * * When TCL_MEM_DEBUG is defined, this function just returns the result * of calling the debugging version Tcl_DbNewObj. * * Results: * The result is a newly allocated object that represents the empty * string. The new object's typePtr is set NULL and its ref count is set * to 0. * * Side effects: * If compiling with TCL_COMPILE_STATS, this function increments the * global count of allocated objects (tclObjsAlloced). * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG #undef Tcl_NewObj Tcl_Obj * Tcl_NewObj(void) { return Tcl_DbNewObj("unknown", 0); } #else /* if not TCL_MEM_DEBUG */ Tcl_Obj * Tcl_NewObj(void) { Tcl_Obj *objPtr; /* * Use the macro defined in tclInt.h - it will use the correct allocator. */ TclNewObj(objPtr); return objPtr; } #endif /* TCL_MEM_DEBUG */ /* *---------------------------------------------------------------------- * * Tcl_DbNewObj -- * * This function is normally called when debugging: i.e., when * TCL_MEM_DEBUG is defined. It creates new Tcl objects that denote the * empty string. It is the same as the Tcl_NewObj function above except * that it calls Tcl_DbCkalloc directly with the file name and line * number from its caller. This simplifies debugging since then the * [memory active] command will report the correct file name and line * number when reporting objects that haven't been freed. * * When TCL_MEM_DEBUG is not defined, this function just returns the * result of calling Tcl_NewObj. * * Results: * The result is a newly allocated that represents the empty string. The * new object's typePtr is set NULL and its ref count is set to 0. * * Side effects: * If compiling with TCL_COMPILE_STATS, this function increments the * global count of allocated objects (tclObjsAlloced). * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG Tcl_Obj * Tcl_DbNewObj( const char *file, /* The name of the source file calling this * function; used for debugging. */ int line) /* Line number in the source file; used for * debugging. */ { Tcl_Obj *objPtr; /* * Use the macro defined in tclInt.h - it will use the correct allocator. */ TclDbNewObj(objPtr, file, line); return objPtr; } #else /* if not TCL_MEM_DEBUG */ Tcl_Obj * Tcl_DbNewObj( TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { Tcl_Obj *objPtr; TclNewObj(objPtr); return objPtr; } #endif /* TCL_MEM_DEBUG */ /* *---------------------------------------------------------------------- * * TclAllocateFreeObjects -- * * Function to allocate a number of free Tcl_Objs. This is done using a * single Tcl_Alloc to reduce the overhead for Tcl_Obj allocation. * * Assumes mutex is held. * * Results: * None. * * Side effects: * tclFreeObjList, the head of the list of free Tcl_Objs, is set to the * first of a number of free Tcl_Obj's linked together by their * internalRep.twoPtrValue.ptr1's. * *---------------------------------------------------------------------- */ #define OBJS_TO_ALLOC_EACH_TIME 100 void TclAllocateFreeObjects(void) { size_t bytesToAlloc = (OBJS_TO_ALLOC_EACH_TIME * sizeof(Tcl_Obj)); char *basePtr; Tcl_Obj *prevPtr, *objPtr; int i; /* * This has been noted by Purify to be a potential leak. The problem is * that Tcl, when not TCL_MEM_DEBUG compiled, keeps around all allocated * Tcl_Obj's, pointed to by tclFreeObjList, when freed instead of actually * freeing the memory. TclFinalizeObjects() does not Tcl_Free() this memory, * but leaves it to Tcl's memory subsystem finalization to release it. * Purify apparently can't figure that out, and fires a false alarm. */ basePtr = (char *)Tcl_Alloc(bytesToAlloc); prevPtr = NULL; objPtr = (Tcl_Obj *) basePtr; for (i = 0; i < OBJS_TO_ALLOC_EACH_TIME; i++) { objPtr->internalRep.twoPtrValue.ptr1 = prevPtr; prevPtr = objPtr; objPtr++; } tclFreeObjList = prevPtr; } #undef OBJS_TO_ALLOC_EACH_TIME /* *---------------------------------------------------------------------- * * TclFreeObj -- * * This function frees the memory associated with the argument object. * It is called by the tcl.h macro Tcl_DecrRefCount when an object's ref * count is zero. It is only "public" since it must be callable by that * macro wherever the macro is used. It should not be directly called by * clients. * * Results: * None. * * Side effects: * Deallocates the storage for the object's Tcl_Obj structure after * deallocating the string representation and calling the type-specific * Tcl_FreeInternalRepProc to deallocate the object's internal * representation. If compiling with TCL_COMPILE_STATS, this function * increments the global count of freed objects (tclObjsFreed). * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG void TclFreeObj( Tcl_Obj *objPtr) /* The object to be freed. */ { const Tcl_ObjType *typePtr = objPtr->typePtr; /* * This macro declares a variable, so must come here... */ ObjInitDeletionContext(context); #if TCL_THREADS /* * Check to make sure that the Tcl_Obj was allocated by the current * thread. Don't do this check when shutting down since thread local * storage can be finalized before the last Tcl_Obj is freed. */ if (!TclInExit()) { Tcl_HashTable *tablePtr; Tcl_HashEntry *hPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); tablePtr = tsdPtr->objThreadMap; if (!tablePtr) { Tcl_Panic("TclFreeObj: object table not initialized"); } hPtr = Tcl_FindHashEntry(tablePtr, objPtr); if (hPtr) { /* * As the Tcl_Obj is going to be deleted we remove the entry. */ ObjData *objData = (ObjData *)Tcl_GetHashValue(hPtr); if (objData != NULL) { Tcl_Free(objData); } Tcl_DeleteHashEntry(hPtr); } } # endif /* * Check for a double free of the same value. This is slightly tricky * because it is customary to free a Tcl_Obj when its refcount falls * either from 1 to 0, or from 0 to -1. Falling from -1 to -2, though, * and so on, is always a sign of a botch in the caller. */ if (objPtr->refCount < -1) { Tcl_Panic("Reference count for %p was negative", objPtr); } /* * Now, in case we just approved drop from 1 to 0 as acceptable, make * sure we do not accept a second free when falling from 0 to -1. * Skip that possibility so any double free will trigger the panic. */ objPtr->refCount = TCL_INDEX_NONE; /* * Invalidate the string rep first so we can use the bytes value for our * pointer chain, and signal an obj deletion (as opposed to shimmering) * with 'length == TCL_INDEX_NONE'. */ TclInvalidateStringRep(objPtr); objPtr->length = TCL_INDEX_NONE; if (ObjDeletePending(context)) { PushObjToDelete(context, objPtr); } else { TCL_DTRACE_OBJ_FREE(objPtr); if ((typePtr != NULL) && (typePtr->freeIntRepProc != NULL)) { ObjDeletionLock(context); typePtr->freeIntRepProc(objPtr); ObjDeletionUnlock(context); } Tcl_MutexLock(&tclObjMutex); Tcl_Free(objPtr); Tcl_MutexUnlock(&tclObjMutex); TclIncrObjsFreed(); ObjDeletionLock(context); while (ObjOnStack(context)) { Tcl_Obj *objToFree; PopObjToDelete(context, objToFree); TCL_DTRACE_OBJ_FREE(objToFree); TclFreeInternalRep(objToFree); Tcl_MutexLock(&tclObjMutex); Tcl_Free(objToFree); Tcl_MutexUnlock(&tclObjMutex); TclIncrObjsFreed(); } ObjDeletionUnlock(context); } /* * We cannot use TclGetContinuationTable() here, because that may * re-initialize the thread-data for calls coming after the finalization. * We have to access it using the low-level call and then check for * validity. This function can be called after TclFinalizeThreadData() has * already killed the thread-global data structures. Performing * TCL_TSD_INIT will leave us with an uninitialized memory block upon * which we crash (if we where to access the uninitialized hashtable). */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Tcl_HashEntry *hPtr; if (tsdPtr->lineCLPtr) { hPtr = Tcl_FindHashEntry(tsdPtr->lineCLPtr, objPtr); if (hPtr) { Tcl_Free(Tcl_GetHashValue(hPtr)); Tcl_DeleteHashEntry(hPtr); } } } } #else /* TCL_MEM_DEBUG */ void TclFreeObj( Tcl_Obj *objPtr) /* The object to be freed. */ { /* * Invalidate the string rep first so we can use the bytes value for our * pointer chain, and signal an obj deletion (as opposed to shimmering) * with 'length == -1'. */ TclInvalidateStringRep(objPtr); objPtr->length = TCL_INDEX_NONE; if (!objPtr->typePtr || !objPtr->typePtr->freeIntRepProc) { /* * objPtr can be freed safely, as it will not attempt to free any * other objects: it will not cause recursive calls to this function. */ TCL_DTRACE_OBJ_FREE(objPtr); TclFreeObjStorage(objPtr); TclIncrObjsFreed(); } else { /* * This macro declares a variable, so must come here... */ ObjInitDeletionContext(context); if (ObjDeletePending(context)) { PushObjToDelete(context, objPtr); } else { /* * Note that the contents of the while loop assume that the string * rep has already been freed and we don't want to do anything * fancy with adding to the queue inside ourselves. Must take care * to unstack the object first since freeing the internal rep can * add further objects to the stack. The code assumes that it is * the first thing in a block; all current usages in the core * satisfy this. */ TCL_DTRACE_OBJ_FREE(objPtr); ObjDeletionLock(context); objPtr->typePtr->freeIntRepProc(objPtr); ObjDeletionUnlock(context); TclFreeObjStorage(objPtr); TclIncrObjsFreed(); ObjDeletionLock(context); while (ObjOnStack(context)) { Tcl_Obj *objToFree; PopObjToDelete(context, objToFree); TCL_DTRACE_OBJ_FREE(objToFree); if ((objToFree->typePtr != NULL) && (objToFree->typePtr->freeIntRepProc != NULL)) { objToFree->typePtr->freeIntRepProc(objToFree); } TclFreeObjStorage(objToFree); TclIncrObjsFreed(); } ObjDeletionUnlock(context); } } /* * We cannot use TclGetContinuationTable() here, because that may * re-initialize the thread-data for calls coming after the finalization. * We have to access it using the low-level call and then check for * validity. This function can be called after TclFinalizeThreadData() has * already killed the thread-global data structures. Performing * TCL_TSD_INIT will leave us with an uninitialized memory block upon * which we crash (if we where to access the uninitialized hashtable). */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Tcl_HashEntry *hPtr; if (tsdPtr->lineCLPtr) { hPtr = Tcl_FindHashEntry(tsdPtr->lineCLPtr, objPtr); if (hPtr) { Tcl_Free(Tcl_GetHashValue(hPtr)); Tcl_DeleteHashEntry(hPtr); } } } } #endif /* TCL_MEM_DEBUG */ /* *---------------------------------------------------------------------- * * TclObjBeingDeleted -- * * This function returns 1 when the Tcl_Obj is being deleted. It is * provided for the rare cases where the reason for the loss of an * internal rep might be relevant. [FR 1512138] * * Results: * 1 if being deleted, 0 otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclObjBeingDeleted( Tcl_Obj *objPtr) { return (objPtr->length == TCL_INDEX_NONE); } /* *---------------------------------------------------------------------- * * Tcl_DuplicateObj -- * * Create and return a new object that is a duplicate of the argument * object. * * Results: * The return value is a pointer to a newly created Tcl_Obj. This object * has reference count 0 and the same type, if any, as the source object * objPtr. Also: * 1) If the source object has a valid string rep, we copy it; * otherwise, the duplicate's string rep is set NULL to mark it * invalid. * 2) If the source object has an internal representation (i.e. its * typePtr is non-NULL), the new object's internal rep is set to a * copy; otherwise the new internal rep is marked invalid. * * Side effects: * What constitutes "copying" the internal representation depends on the * type. For example, if the argument object is a list, the element * objects it points to will not actually be copied but will be shared * with the duplicate list. That is, the ref counts of the element * objects will be incremented. * *---------------------------------------------------------------------- */ #define SetDuplicateObj(dupPtr, objPtr) \ { \ const Tcl_ObjType *typePtr = (objPtr)->typePtr; \ const char *bytes = (objPtr)->bytes; \ if (bytes) { \ TclInitStringRep((dupPtr), bytes, (objPtr)->length); \ } else { \ (dupPtr)->bytes = NULL; \ } \ if (typePtr) { \ if (typePtr->dupIntRepProc) { \ typePtr->dupIntRepProc((objPtr), (dupPtr)); \ } else { \ (dupPtr)->internalRep = (objPtr)->internalRep; \ (dupPtr)->typePtr = typePtr; \ } \ } \ } Tcl_Obj * Tcl_DuplicateObj( Tcl_Obj *objPtr) /* The object to duplicate. */ { Tcl_Obj *dupPtr; TclNewObj(dupPtr); SetDuplicateObj(dupPtr, objPtr); return dupPtr; } void TclSetDuplicateObj( Tcl_Obj *dupPtr, Tcl_Obj *objPtr) { if (Tcl_IsShared(dupPtr)) { Tcl_Panic("%s called with shared object", "TclSetDuplicateObj"); } TclInvalidateStringRep(dupPtr); TclFreeInternalRep(dupPtr); SetDuplicateObj(dupPtr, objPtr); } /* *---------------------------------------------------------------------- * * Tcl_GetString -- * * Returns the string representation byte array pointer for an object. * * Results: * Returns a pointer to the string representation of objPtr. The byte * array referenced by the returned pointer must not be modified by the * caller. Furthermore, the caller must copy the bytes if they need to * retain them since the object's string rep can change as a result of * other operations. * * Side effects: * May call the object's updateStringProc to update the string * representation from the internal representation. * *---------------------------------------------------------------------- */ #undef Tcl_GetString char * Tcl_GetString( Tcl_Obj *objPtr) /* Object whose string rep byte pointer should * be returned. */ { if (objPtr->bytes == NULL) { /* * Note we do not check for objPtr->typePtr == NULL. An invariant * of a properly maintained Tcl_Obj is that at least one of * objPtr->bytes and objPtr->typePtr must not be NULL. If broken * extensions fail to maintain that invariant, we can crash here. */ if (objPtr->typePtr->updateStringProc == NULL) { /* * Those Tcl_ObjTypes which choose not to define an * updateStringProc must be written in such a way that * (objPtr->bytes) never becomes NULL. */ Tcl_Panic("UpdateStringProc should not be invoked for type %s", objPtr->typePtr->name); } objPtr->typePtr->updateStringProc(objPtr); if (objPtr->bytes == NULL || objPtr->length == TCL_INDEX_NONE || objPtr->bytes[objPtr->length] != '\0') { Tcl_Panic("UpdateStringProc for type '%s' " "failed to create a valid string rep", objPtr->typePtr->name); } } return objPtr->bytes; } /* *---------------------------------------------------------------------- * * Tcl_GetStringFromObj/TclGetStringFromObj -- * * Returns the string representation's byte array pointer and length for * an object. * * Results: * Returns a pointer to the string representation of objPtr. If lengthPtr * isn't NULL, the length of the string representation is stored at * *lengthPtr. The byte array referenced by the returned pointer must not * be modified by the caller. Furthermore, the caller must copy the bytes * if they need to retain them since the object's string rep can change * as a result of other operations. * * Side effects: * May call the object's updateStringProc to update the string * representation from the internal representation. * *---------------------------------------------------------------------- */ #if !defined(TCL_NO_DEPRECATED) #undef TclGetStringFromObj char * TclGetStringFromObj( Tcl_Obj *objPtr, /* Object whose string rep byte pointer should * be returned. */ void *lengthPtr) /* If non-NULL, the location where the string * rep's byte array length should * be stored. * If NULL, no length is stored. */ { if (objPtr->bytes == NULL) { /* * Note we do not check for objPtr->typePtr == NULL. An invariant * of a properly maintained Tcl_Obj is that at least one of * objPtr->bytes and objPtr->typePtr must not be NULL. If broken * extensions fail to maintain that invariant, we can crash here. */ if (objPtr->typePtr->updateStringProc == NULL) { /* * Those Tcl_ObjTypes which choose not to define an * updateStringProc must be written in such a way that * (objPtr->bytes) never becomes NULL. */ Tcl_Panic("UpdateStringProc should not be invoked for type %s", objPtr->typePtr->name); } objPtr->typePtr->updateStringProc(objPtr); if (objPtr->bytes == NULL || objPtr->length == TCL_INDEX_NONE || objPtr->bytes[objPtr->length] != '\0') { Tcl_Panic("UpdateStringProc for type '%s' " "failed to create a valid string rep", objPtr->typePtr->name); } } if (lengthPtr != NULL) { if (objPtr->length > INT_MAX) { Tcl_Panic("Tcl_GetStringFromObj with 'int' lengthPtr" " cannot handle such long strings. Please use 'Tcl_Size'"); } *(int *)lengthPtr = (int)objPtr->length; } return objPtr->bytes; } #endif /* !defined(TCL_NO_DEPRECATED) */ #undef Tcl_GetStringFromObj char * Tcl_GetStringFromObj( Tcl_Obj *objPtr, /* Object whose string rep byte pointer should * be returned. */ Tcl_Size *lengthPtr) /* If non-NULL, the location where the string * rep's byte array length should * be stored. * If NULL, no length is stored. */ { if (objPtr->bytes == NULL) { /* * Note we do not check for objPtr->typePtr == NULL. An invariant * of a properly maintained Tcl_Obj is that at least one of * objPtr->bytes and objPtr->typePtr must not be NULL. If broken * extensions fail to maintain that invariant, we can crash here. */ if (objPtr->typePtr->updateStringProc == NULL) { /* * Those Tcl_ObjTypes which choose not to define an * updateStringProc must be written in such a way that * (objPtr->bytes) never becomes NULL. */ Tcl_Panic("UpdateStringProc should not be invoked for type %s", objPtr->typePtr->name); } objPtr->typePtr->updateStringProc(objPtr); if (objPtr->bytes == NULL || objPtr->bytes[objPtr->length] != '\0') { Tcl_Panic("UpdateStringProc for type '%s' " "failed to create a valid string rep", objPtr->typePtr->name); } } if (lengthPtr != NULL) { *lengthPtr = objPtr->length; } return objPtr->bytes; } /* *---------------------------------------------------------------------- * * Tcl_InitStringRep -- * * This function is called in several configurations to provide all * the tools needed to set an object's string representation. The * function is determined by the arguments. * * (objPtr->bytes != NULL && bytes != NULL) || (numBytes == -1) * Invalid call -- panic! * * objPtr->bytes == NULL && bytes == NULL && numBytes != -1 * Allocation only - allocate space for (numBytes+1) chars. * store in objPtr->bytes and return. Also sets * objPtr->length to 0 and objPtr->bytes[0] to NUL. * * objPtr->bytes == NULL && bytes != NULL && numBytes != -1 * Allocate and copy. bytes is assumed to point to chars to * copy into the string rep. objPtr->length = numBytes. Allocate * array of (numBytes + 1) chars. store in objPtr->bytes. Copy * numBytes chars from bytes to objPtr->bytes; Set * objPtr->bytes[numBytes] to NUL and return objPtr->bytes. * Caller must guarantee there are numBytes chars at bytes to * be copied. * * objPtr->bytes != NULL && bytes == NULL && numBytes != -1 * Truncate. Set objPtr->length to numBytes and * objPr->bytes[numBytes] to NUL. Caller has to guarantee * that a prior allocating call allocated enough bytes for * this to be valid. Return objPtr->bytes. * * Caller is expected to ascertain that the bytes copied into * the string rep make up complete valid UTF-8 characters. * * Results: * A pointer to the string rep of objPtr. * * Side effects: * As described above. * *---------------------------------------------------------------------- */ char * Tcl_InitStringRep( Tcl_Obj *objPtr, /* Object whose string rep is to be set */ const char *bytes, size_t numBytes) { assert(objPtr->bytes == NULL || bytes == NULL); if (objPtr->bytes == NULL) { /* Start with no string rep */ if (numBytes == 0) { TclInitEmptyStringRep(objPtr); return objPtr->bytes; } else { objPtr->bytes = (char *)Tcl_AttemptAlloc(numBytes + 1); if (objPtr->bytes) { objPtr->length = numBytes; if (bytes) { memcpy(objPtr->bytes, bytes, numBytes); } objPtr->bytes[objPtr->length] = '\0'; } } } else if (objPtr->bytes == &tclEmptyString) { /* Start with empty string rep (not allocated) */ if (numBytes == 0) { return objPtr->bytes; } else { objPtr->bytes = (char *)Tcl_AttemptAlloc(numBytes + 1); if (objPtr->bytes) { objPtr->length = numBytes; objPtr->bytes[objPtr->length] = '\0'; } } } else { /* Start with non-empty string rep (allocated) */ if (numBytes == 0) { Tcl_Free(objPtr->bytes); TclInitEmptyStringRep(objPtr); return objPtr->bytes; } else { objPtr->bytes = (char *)Tcl_AttemptRealloc(objPtr->bytes, numBytes + 1); if (objPtr->bytes) { objPtr->length = numBytes; objPtr->bytes[objPtr->length] = '\0'; } } } return objPtr->bytes; } /* *---------------------------------------------------------------------- * * Tcl_InvalidateStringRep -- * * This function is called to invalidate an object's string * representation. * * Results: * None. * * Side effects: * Deallocates the storage for any old string representation, then sets * the string representation NULL to mark it invalid. * *---------------------------------------------------------------------- */ void Tcl_InvalidateStringRep( Tcl_Obj *objPtr) /* Object whose string rep byte pointer should * be freed. */ { TclInvalidateStringRep(objPtr); } /* *---------------------------------------------------------------------- * * Tcl_HasStringRep -- * * This function reports whether object has a string representation. * * Results: * Boolean. *---------------------------------------------------------------------- */ int Tcl_HasStringRep( Tcl_Obj *objPtr) /* Object to test */ { return TclHasStringRep(objPtr); } /* *---------------------------------------------------------------------- * * Tcl_StoreInternalRep -- * * Called to set the object's internal representation to match a * particular type. * * It is the caller's responsibility to guarantee that * the value of the submitted internalrep is in agreement with * the value of any existing string rep. * * Results: * None. * * Side effects: * Calls the freeIntRepProc of the current Tcl_ObjType, if any. * Sets the internalRep and typePtr fields to the submitted values. * *---------------------------------------------------------------------- */ void Tcl_StoreInternalRep( Tcl_Obj *objPtr, /* Object whose internal rep should be set. */ const Tcl_ObjType *typePtr, /* New type for the object */ const Tcl_ObjInternalRep *irPtr) /* New internalrep for the object */ { /* Clear out any existing internalrep ( "shimmer" ) */ TclFreeInternalRep(objPtr); /* When irPtr == NULL, just leave objPtr with no internalrep for typePtr */ if (irPtr) { /* Copy the new internalrep into place */ objPtr->internalRep = *irPtr; /* Set the type to match */ objPtr->typePtr = typePtr; } } /* *---------------------------------------------------------------------- * * Tcl_FetchInternalRep -- * * This function is called to retrieve the object's internal * representation matching a requested type, if any. * * Results: * A read-only pointer to the associated Tcl_ObjInternalRep, or * NULL if no such internal representation exists. * * Side effects: * Calls the freeIntRepProc of the current Tcl_ObjType, if any. * Sets the internalRep and typePtr fields to the submitted values. * *---------------------------------------------------------------------- */ Tcl_ObjInternalRep * Tcl_FetchInternalRep( Tcl_Obj *objPtr, /* Object to fetch from. */ const Tcl_ObjType *typePtr) /* Requested type */ { return TclFetchInternalRep(objPtr, typePtr); } /* *---------------------------------------------------------------------- * * Tcl_FreeInternalRep -- * * This function is called to free an object's internal representation. * * Results: * None. * * Side effects: * Calls the freeIntRepProc of the current Tcl_ObjType, if any. * Sets typePtr field to NULL. * *---------------------------------------------------------------------- */ void Tcl_FreeInternalRep( Tcl_Obj *objPtr) /* Object whose internal rep should be freed. */ { TclFreeInternalRep(objPtr); } /* *---------------------------------------------------------------------- * * Tcl_GetBoolFromObj, Tcl_GetBooleanFromObj -- * * Attempt to return a boolean from the Tcl object "objPtr". This * includes conversion from any of Tcl's numeric types. * * Results: * The return value is a standard Tcl object result. If an error occurs * during conversion, an error message is left in the interpreter's * result unless "interp" is NULL. * * Side effects: * The internalrep of *objPtr may be changed. * *---------------------------------------------------------------------- */ #undef Tcl_GetBoolFromObj int Tcl_GetBoolFromObj( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr, /* The object from which to get boolean. */ int flags, char *charPtr) /* Place to store resulting boolean. */ { int result; Tcl_Size length; if ((flags & TCL_NULL_OK) && (objPtr == NULL || Tcl_GetString(objPtr)[0] == '\0')) { result = -1; goto boolEnd; } else if (objPtr == NULL) { if (interp) { TclNewObj(objPtr); TclParseNumber(interp, objPtr, (flags & TCL_NULL_OK) ? "boolean value or \"\"" : "boolean value", NULL, TCL_INDEX_NONE, NULL, 0); Tcl_DecrRefCount(objPtr); } return TCL_ERROR; } do { if (TclHasInternalRep(objPtr, &tclIntType) || TclHasInternalRep(objPtr, &tclBooleanType)) { result = (objPtr->internalRep.wideValue != 0); goto boolEnd; } if (TclHasInternalRep(objPtr, &tclDoubleType)) { /* * Caution: Don't be tempted to check directly for the "double" * Tcl_ObjType and then compare the internalrep to 0.0. This isn't * reliable because a "double" Tcl_ObjType can hold the NaN value. * Use the API Tcl_GetDoubleFromObj, which does the checking and * sets the proper error message for us. */ double d; if (Tcl_GetDoubleFromObj(interp, objPtr, &d) != TCL_OK) { return TCL_ERROR; } result = (d != 0.0); goto boolEnd; } if (TclHasInternalRep(objPtr, &tclBignumType)) { result = 1; boolEnd: if (charPtr != NULL) { flags &= (TCL_NULL_OK-2); if (flags) { if (flags == (int)sizeof(int)) { *(int *)charPtr = result; return TCL_OK; } else if (flags == (int)sizeof(short)) { *(short *)charPtr = result; return TCL_OK; } else { Tcl_Panic("Wrong bool var for %s", "Tcl_GetBoolFromObj"); } } *charPtr = result; } return TCL_OK; } /* Handle dict separately, because it doesn't have a lengthProc */ if (TclHasInternalRep(objPtr, &tclDictType)) { Tcl_DictObjSize(NULL, objPtr, &length); if (length > 0) { listRep: if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("expected boolean value%s but got a list", (flags & TCL_NULL_OK) ? " or \"\"" : "")); } return TCL_ERROR; } } Tcl_ObjTypeLengthProc *lengthProc = TclObjTypeHasProc(objPtr, lengthProc); if (lengthProc && lengthProc(objPtr) != 1) { goto listRep; } } while ((ParseBoolean(objPtr) == TCL_OK) || (TCL_OK == TclParseNumber(interp, objPtr, (flags & TCL_NULL_OK) ? "boolean value or \"\"" : "boolean value", NULL,-1,NULL,0))); return TCL_ERROR; } #undef Tcl_GetBooleanFromObj int Tcl_GetBooleanFromObj( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr, /* The object from which to get boolean. */ int *intPtr) /* Place to store resulting boolean. */ { return Tcl_GetBoolFromObj(interp, objPtr, (TCL_NULL_OK-2)&(int)sizeof(int), (char *)(void *)intPtr); } /* *---------------------------------------------------------------------- * * TclSetBooleanFromAny -- * * Attempt to generate a boolean internal form for the Tcl object * "objPtr". * * Results: * The return value is a standard Tcl result. If an error occurs during * conversion, an error message is left in the interpreter's result * unless "interp" is NULL. * * Side effects: * If no error occurs, an integer 1 or 0 is stored as "objPtr"s internal * representation and the type of "objPtr" is set to boolean or int. * *---------------------------------------------------------------------- */ int TclSetBooleanFromAny( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr) /* The object to convert. */ { /* * For some "pure" numeric Tcl_ObjTypes (no string rep), we can determine * whether a boolean conversion is possible without generating the string * rep. */ if (objPtr->bytes == NULL) { if (TclHasInternalRep(objPtr, &tclIntType)) { if ((Tcl_WideUInt)objPtr->internalRep.wideValue < 2) { return TCL_OK; } goto badBoolean; } if (TclHasInternalRep(objPtr, &tclBignumType)) { goto badBoolean; } if (TclHasInternalRep(objPtr, &tclDoubleType)) { goto badBoolean; } } if (ParseBoolean(objPtr) == TCL_OK) { return TCL_OK; } badBoolean: if (interp != NULL) { Tcl_Size length; const char *str = Tcl_GetStringFromObj(objPtr, &length); Tcl_Obj *msg; TclNewLiteralStringObj(msg, "expected boolean value but got \""); Tcl_AppendLimitedToObj(msg, str, length, 50, ""); Tcl_AppendToObj(msg, "\"", -1); Tcl_SetObjResult(interp, msg); Tcl_SetErrorCode(interp, "TCL", "VALUE", "BOOLEAN", (char *)NULL); } return TCL_ERROR; } static int ParseBoolean( Tcl_Obj *objPtr) /* The object to parse/convert. */ { int newBool; char lowerCase[6]; Tcl_Size i, length; const char *str = Tcl_GetStringFromObj(objPtr, &length); if ((length < 1) || (length > 5)) { /* * Longest valid boolean string rep. is "false". */ return TCL_ERROR; } switch (str[0]) { case '0': if (length == 1) { newBool = 0; goto numericBoolean; } return TCL_ERROR; case '1': if (length == 1) { newBool = 1; goto numericBoolean; } return TCL_ERROR; } /* * Force to lower case for case-insensitive detection. Filter out known * invalid characters at the same time. */ for (i=0; i < length; i++) { char c = str[i]; switch (c) { case 'A': case 'E': case 'F': case 'L': case 'N': case 'O': case 'R': case 'S': case 'T': case 'U': case 'Y': lowerCase[i] = c + (char) ('a' - 'A'); break; case 'a': case 'e': case 'f': case 'l': case 'n': case 'o': case 'r': case 's': case 't': case 'u': case 'y': lowerCase[i] = c; break; default: return TCL_ERROR; } } lowerCase[length] = 0; switch (lowerCase[0]) { case 'y': /* * Checking the 'y' is redundant, but makes the code clearer. */ if (strncmp(lowerCase, "yes", length) == 0) { newBool = 1; goto goodBoolean; } return TCL_ERROR; case 'n': if (strncmp(lowerCase, "no", length) == 0) { newBool = 0; goto goodBoolean; } return TCL_ERROR; case 't': if (strncmp(lowerCase, "true", length) == 0) { newBool = 1; goto goodBoolean; } return TCL_ERROR; case 'f': if (strncmp(lowerCase, "false", length) == 0) { newBool = 0; goto goodBoolean; } return TCL_ERROR; case 'o': if (length < 2) { return TCL_ERROR; } if (strncmp(lowerCase, "on", length) == 0) { newBool = 1; goto goodBoolean; } else if (strncmp(lowerCase, "off", length) == 0) { newBool = 0; goto goodBoolean; } return TCL_ERROR; default: return TCL_ERROR; } /* * Free the old internalRep before setting the new one. We do this as late * as possible to allow the conversion code, in particular * Tcl_GetStringFromObj, to use that old internalRep. */ goodBoolean: TclFreeInternalRep(objPtr); objPtr->internalRep.wideValue = newBool; objPtr->typePtr = &tclBooleanType; return TCL_OK; numericBoolean: TclFreeInternalRep(objPtr); objPtr->internalRep.wideValue = newBool; objPtr->typePtr = &tclIntType; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_NewDoubleObj -- * * This function is normally called when not debugging: i.e., when * TCL_MEM_DEBUG is not defined. It creates a new double object and * initializes it from the argument double value. * * When TCL_MEM_DEBUG is defined, this function just returns the result * of calling the debugging version Tcl_DbNewDoubleObj. * * Results: * The newly created object is returned. This object will have an * invalid string representation. The returned object has ref count 0. * * Side effects: * None. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG #undef Tcl_NewDoubleObj Tcl_Obj * Tcl_NewDoubleObj( double dblValue) /* Double used to initialize the object. */ { return Tcl_DbNewDoubleObj(dblValue, "unknown", 0); } #else /* if not TCL_MEM_DEBUG */ Tcl_Obj * Tcl_NewDoubleObj( double dblValue) /* Double used to initialize the object. */ { Tcl_Obj *objPtr; TclNewDoubleObj(objPtr, dblValue); return objPtr; } #endif /* if TCL_MEM_DEBUG */ /* *---------------------------------------------------------------------- * * Tcl_DbNewDoubleObj -- * * This function is normally called when debugging: i.e., when * TCL_MEM_DEBUG is defined. It creates new double objects. It is the * same as the Tcl_NewDoubleObj function above except that it calls * Tcl_DbCkalloc directly with the file name and line number from its * caller. This simplifies debugging since then the [memory active] * command will report the correct file name and line number when * reporting objects that haven't been freed. * * When TCL_MEM_DEBUG is not defined, this function just returns the * result of calling Tcl_NewDoubleObj. * * Results: * The newly created object is returned. This object will have an invalid * string representation. The returned object has ref count 0. * * Side effects: * None. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG Tcl_Obj * Tcl_DbNewDoubleObj( double dblValue, /* Double used to initialize the object. */ const char *file, /* The name of the source file calling this * function; used for debugging. */ int line) /* Line number in the source file; used for * debugging. */ { Tcl_Obj *objPtr; TclDbNewObj(objPtr, file, line); /* Optimized TclInvalidateStringRep() */ objPtr->bytes = NULL; objPtr->internalRep.doubleValue = dblValue; objPtr->typePtr = &tclDoubleType; return objPtr; } #else /* if not TCL_MEM_DEBUG */ Tcl_Obj * Tcl_DbNewDoubleObj( double dblValue, /* Double used to initialize the object. */ TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { return Tcl_NewDoubleObj(dblValue); } #endif /* TCL_MEM_DEBUG */ /* *---------------------------------------------------------------------- * * Tcl_SetDoubleObj -- * * Modify an object to be a double object and to have the specified * double value. * * Results: * None. * * Side effects: * The object's old string rep, if any, is freed. Also, any old internal * rep is freed. * *---------------------------------------------------------------------- */ void Tcl_SetDoubleObj( Tcl_Obj *objPtr, /* Object whose internal rep to init. */ double dblValue) /* Double used to set the object's value. */ { if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_SetDoubleObj"); } TclSetDoubleObj(objPtr, dblValue); } /* *---------------------------------------------------------------------- * * Tcl_GetDoubleFromObj -- * * Attempt to return a double from the Tcl object "objPtr". If the object * is not already a double, an attempt will be made to convert it to one. * * Results: * The return value is a standard Tcl object result. If an error occurs * during conversion, an error message is left in the interpreter's * result unless "interp" is NULL. * * Side effects: * If the object is not already a double, the conversion will free any * old internal representation. * *---------------------------------------------------------------------- */ int Tcl_GetDoubleFromObj( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr, /* The object from which to get a double. */ double *dblPtr) /* Place to store resulting double. */ { Tcl_Size length; do { if (TclHasInternalRep(objPtr, &tclDoubleType)) { if (isnan(objPtr->internalRep.doubleValue)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "floating point value is Not a Number", -1)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "DOUBLE", "NAN", (char *)NULL); } return TCL_ERROR; } *dblPtr = (double) objPtr->internalRep.doubleValue; return TCL_OK; } if (TclHasInternalRep(objPtr, &tclIntType)) { *dblPtr = (double) objPtr->internalRep.wideValue; return TCL_OK; } if (TclHasInternalRep(objPtr, &tclBignumType)) { mp_int big; TclUnpackBignum(objPtr, big); *dblPtr = TclBignumToDouble(&big); return TCL_OK; } /* Handle dict separately, because it doesn't have a lengthProc */ if (TclHasInternalRep(objPtr, &tclDictType)) { Tcl_DictObjSize(NULL, objPtr, &length); if (length > 0) { listRep: if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj("expected floating-point number but got a list", TCL_INDEX_NONE)); } return TCL_ERROR; } } Tcl_ObjTypeLengthProc *lengthProc = TclObjTypeHasProc(objPtr, lengthProc); if (lengthProc && lengthProc(objPtr) != 1) { goto listRep; } } while (SetDoubleFromAny(interp, objPtr) == TCL_OK); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * SetDoubleFromAny -- * * Attempt to generate an double-precision floating point internal form * for the Tcl object "objPtr". * * Results: * The return value is a standard Tcl object result. If an error occurs * during conversion, an error message is left in the interpreter's * result unless "interp" is NULL. * * Side effects: * If no error occurs, a double is stored as "objPtr"s internal * representation. * *---------------------------------------------------------------------- */ static int SetDoubleFromAny( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr) /* The object to convert. */ { return TclParseNumber(interp, objPtr, "floating-point number", NULL, -1, NULL, 0); } /* *---------------------------------------------------------------------- * * UpdateStringOfDouble -- * * Update the string representation for a double-precision floating point * object. Note: This function does not free an * existing old string rep so storage will be lost if this has not * already been done. * * Results: * None. * * Side effects: * The object's string is set to a valid string that results from the * double-to-string conversion. * *---------------------------------------------------------------------- */ static void UpdateStringOfDouble( Tcl_Obj *objPtr) /* Double obj with string rep to update. */ { char *dst = Tcl_InitStringRep(objPtr, NULL, TCL_DOUBLE_SPACE); TclOOM(dst, TCL_DOUBLE_SPACE + 1); Tcl_PrintDouble(NULL, objPtr->internalRep.doubleValue, dst); (void) Tcl_InitStringRep(objPtr, NULL, strlen(dst)); } /* *---------------------------------------------------------------------- * * Tcl_GetIntFromObj -- * * Retrieve the integer value of 'objPtr'. * * Value * * TCL_OK * * Success. * * TCL_ERROR * * An error occurred during conversion or the integral value can not * be represented as an integer (it might be too large). An error * message is left in the interpreter's result if 'interp' is not * NULL. * * Effect * * 'objPtr' is converted to an integer if necessary if it is not one * already. The conversion frees any previously-existing internal * representation. * *---------------------------------------------------------------------- */ int Tcl_GetIntFromObj( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr, /* The object from which to get a int. */ int *intPtr) /* Place to store resulting int. */ { #if (LONG_MAX == INT_MAX) return TclGetLongFromObj(interp, objPtr, (long *) intPtr); #else long l; if (TclGetLongFromObj(interp, objPtr, &l) != TCL_OK) { return TCL_ERROR; } if ((ULONG_MAX > UINT_MAX) && ((l > UINT_MAX) || (l < INT_MIN))) { if (interp != NULL) { const char *s = "integer value too large to represent"; Tcl_SetObjResult(interp, Tcl_NewStringObj(s, -1)); Tcl_SetErrorCode(interp, "ARITH", "IOVERFLOW", s, (char *)NULL); } return TCL_ERROR; } *intPtr = (int) l; return TCL_OK; #endif } /* *---------------------------------------------------------------------- * * SetIntFromAny -- * * Attempts to force the internal representation for a Tcl object to * tclIntType, specifically. * * Results: * The return value is a standard object Tcl result. If an error occurs * during conversion, an error message is left in the interpreter's * result unless "interp" is NULL. * *---------------------------------------------------------------------- */ static int SetIntFromAny( Tcl_Interp *interp, /* Tcl interpreter */ Tcl_Obj *objPtr) /* Pointer to the object to convert */ { Tcl_WideInt w; return TclGetWideIntFromObj(interp, objPtr, &w); } /* *---------------------------------------------------------------------- * * UpdateStringOfInt -- * * Update the string representation for an integer object. Note: This * function does not free an existing old string rep so storage will be * lost if this has not already been done. * * Results: * None. * * Side effects: * The object's string is set to a valid string that results from the * int-to-string conversion. * *---------------------------------------------------------------------- */ static void UpdateStringOfInt( Tcl_Obj *objPtr) /* Int object whose string rep to update. */ { char *dst = Tcl_InitStringRep( objPtr, NULL, TCL_INTEGER_SPACE); TclOOM(dst, TCL_INTEGER_SPACE + 1); (void) Tcl_InitStringRep(objPtr, NULL, TclFormatInt(dst, objPtr->internalRep.wideValue)); } /* *---------------------------------------------------------------------- * * Tcl_GetLongFromObj -- * * Attempt to return an long integer from the Tcl object "objPtr". If the * object is not already an int object, an attempt will be made to * convert it to one. * * Results: * The return value is a standard Tcl object result. If an error occurs * during conversion, an error message is left in the interpreter's * result unless "interp" is NULL. * * Side effects: * If the object is not already an int object, the conversion will free * any old internal representation. * *---------------------------------------------------------------------- */ int Tcl_GetLongFromObj( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr, /* The object from which to get a long. */ long *longPtr) /* Place to store resulting long. */ { Tcl_Size length; do { #ifdef TCL_WIDE_INT_IS_LONG if (TclHasInternalRep(objPtr, &tclIntType)) { *longPtr = objPtr->internalRep.wideValue; return TCL_OK; } #else if (TclHasInternalRep(objPtr, &tclIntType)) { /* * We return any integer in the range LONG_MIN to ULONG_MAX * converted to a long, ignoring overflow. The rule preserves * existing semantics for conversion of integers on input, but * avoids inadvertent demotion of wide integers to 32-bit ones in * the internal rep. */ Tcl_WideInt w = objPtr->internalRep.wideValue; if (w >= (Tcl_WideInt)(LONG_MIN) && w <= (Tcl_WideInt)(ULONG_MAX)) { *longPtr = (long)w; return TCL_OK; } goto tooLarge; } #endif if (TclHasInternalRep(objPtr, &tclDoubleType)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected integer but got \"%s\"", TclGetString(objPtr))); Tcl_SetErrorCode(interp, "TCL", "VALUE", "INTEGER", (char *)NULL); } return TCL_ERROR; } if (TclHasInternalRep(objPtr, &tclBignumType)) { /* * Must check for those bignum values that can fit in a long, even * when auto-narrowing is enabled. Only those values in the signed * long range get auto-narrowed to tclIntType, while all the * values in the unsigned long range will fit in a long. */ { mp_int big; unsigned long scratch, value = 0; unsigned char *bytes = (unsigned char *) &scratch; size_t numBytes; TclUnpackBignum(objPtr, big); if (mp_to_ubin(&big, bytes, sizeof(long), &numBytes) == MP_OKAY) { while (numBytes-- > 0) { value = (value << CHAR_BIT) | *bytes++; } if (big.sign) { if (value <= 1 + (unsigned long)LONG_MAX) { *longPtr = (long)(-value); return TCL_OK; } } else { if (value <= (unsigned long)ULONG_MAX) { *longPtr = (long)value; return TCL_OK; } } } } #ifndef TCL_WIDE_INT_IS_LONG tooLarge: #endif if (interp != NULL) { const char *s = "integer value too large to represent"; Tcl_Obj *msg = Tcl_NewStringObj(s, -1); Tcl_SetObjResult(interp, msg); Tcl_SetErrorCode(interp, "ARITH", "IOVERFLOW", s, (char *)NULL); } return TCL_ERROR; } /* Handle dict separately, because it doesn't have a lengthProc */ if (TclHasInternalRep(objPtr, &tclDictType)) { Tcl_DictObjSize(NULL, objPtr, &length); if (length > 0) { listRep: if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj("expected integer but got a list", -1)); } return TCL_ERROR; } } Tcl_ObjTypeLengthProc *lengthProc = TclObjTypeHasProc(objPtr, lengthProc); if (lengthProc && lengthProc(objPtr) != 1) { goto listRep; } } while (TclParseNumber(interp, objPtr, "integer", NULL, -1, NULL, TCL_PARSE_INTEGER_ONLY)==TCL_OK); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_NewWideIntObj -- * * If a client is compiled with TCL_MEM_DEBUG defined, calls to * Tcl_NewWideIntObj to create a new 64-bit integer object end up calling * the debugging function Tcl_DbNewWideIntObj instead. * * Otherwise, if the client is compiled without TCL_MEM_DEBUG defined, * calls to Tcl_NewWideIntObj result in a call to one of the two * Tcl_NewWideIntObj implementations below. We provide two * implementations so that the Tcl core can be compiled to do memory * debugging of the core even if a client does not request it for itself. * * Results: * The newly created object is returned. This object will have an invalid * string representation. The returned object has ref count 0. * * Side effects: * None. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG #undef Tcl_NewWideIntObj Tcl_Obj * Tcl_NewWideIntObj( Tcl_WideInt wideValue) /* Wide integer used to initialize the new * object. */ { return Tcl_DbNewWideIntObj(wideValue, "unknown", 0); } #else /* if not TCL_MEM_DEBUG */ Tcl_Obj * Tcl_NewWideIntObj( Tcl_WideInt wideValue) /* Wide integer used to initialize the new * object. */ { Tcl_Obj *objPtr; TclNewObj(objPtr); TclSetIntObj(objPtr, wideValue); return objPtr; } #endif /* if TCL_MEM_DEBUG */ /* *---------------------------------------------------------------------- * * Tcl_NewWideUIntObj -- * * Results: * The newly created object is returned. This object will have an invalid * string representation. The returned object has ref count 0. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_NewWideUIntObj( Tcl_WideUInt uwideValue) /* Wide integer used to initialize the new * object. */ { Tcl_Obj *objPtr; TclNewUIntObj(objPtr, uwideValue); return objPtr; } /* *---------------------------------------------------------------------- * * Tcl_DbNewWideIntObj -- * * If a client is compiled with TCL_MEM_DEBUG defined, calls to * Tcl_NewWideIntObj to create new wide integer end up calling the * debugging function Tcl_DbNewWideIntObj instead. We provide two * implementations of Tcl_DbNewWideIntObj so that whether the Tcl core is * compiled to do memory debugging of the core is independent of whether * a client requests debugging for itself. * * When the core is compiled with TCL_MEM_DEBUG defined, * Tcl_DbNewWideIntObj calls Tcl_DbCkalloc directly with the file name * and line number from its caller. This simplifies debugging since then * the checkmem command will report the caller's file name and line * number when reporting objects that haven't been freed. * * Otherwise, when the core is compiled without TCL_MEM_DEBUG defined, * this function just returns the result of calling Tcl_NewWideIntObj. * * Results: * The newly created wide integer object is returned. This object will * have an invalid string representation. The returned object has ref * count 0. * * Side effects: * Allocates memory. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG Tcl_Obj * Tcl_DbNewWideIntObj( Tcl_WideInt wideValue, /* Wide integer used to initialize the new * object. */ const char *file, /* The name of the source file calling this * function; used for debugging. */ int line) /* Line number in the source file; used for * debugging. */ { Tcl_Obj *objPtr; TclDbNewObj(objPtr, file, line); TclSetIntObj(objPtr, wideValue); return objPtr; } #else /* if not TCL_MEM_DEBUG */ Tcl_Obj * Tcl_DbNewWideIntObj( Tcl_WideInt wideValue, /* Long integer used to initialize the new * object. */ TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { return Tcl_NewWideIntObj(wideValue); } #endif /* TCL_MEM_DEBUG */ /* *---------------------------------------------------------------------- * * Tcl_SetWideIntObj -- * * Modify an object to be a wide integer object and to have the specified * wide integer value. * * Results: * None. * * Side effects: * The object's old string rep, if any, is freed. Also, any old internal * rep is freed. * *---------------------------------------------------------------------- */ void Tcl_SetWideIntObj( Tcl_Obj *objPtr, /* Object w. internal rep to init. */ Tcl_WideInt wideValue) /* Wide integer used to initialize the * object's value. */ { if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_SetWideIntObj"); } TclSetIntObj(objPtr, wideValue); } /* *---------------------------------------------------------------------- * * Tcl_SetWideUIntObj -- * * Modify an object to be a wide integer object or a bignum object * and to have the specified unsigned wide integer value. * * Results: * None. * * Side effects: * The object's old string rep, if any, is freed. Also, any old internal * rep is freed. * *---------------------------------------------------------------------- */ void Tcl_SetWideUIntObj( Tcl_Obj *objPtr, /* Object w. internal rep to init. */ Tcl_WideUInt uwideValue) /* Wide integer used to initialize the * object's value. */ { if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_SetWideUIntObj"); } if (uwideValue > WIDE_MAX) { mp_int bignumValue; if (mp_init_u64(&bignumValue, uwideValue) != MP_OKAY) { Tcl_Panic("%s: memory overflow", "Tcl_SetWideUIntObj"); } TclSetBignumInternalRep(objPtr, &bignumValue); } { TclSetIntObj(objPtr, (Tcl_WideInt)uwideValue); } } /* *---------------------------------------------------------------------- * * Tcl_GetWideIntFromObj -- * * Attempt to return a wide integer from the Tcl object "objPtr". If the * object is not already a wide int object, an attempt will be made to * convert it to one. * * Results: * The return value is a standard Tcl object result. If an error occurs * during conversion, an error message is left in the interpreter's * result unless "interp" is NULL. * * Side effects: * If the object is not already an int object, the conversion will free * any old internal representation. * *---------------------------------------------------------------------- */ int Tcl_GetWideIntFromObj( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr, /* Object from which to get a wide int. */ Tcl_WideInt *wideIntPtr) /* Place to store resulting long. */ { Tcl_Size length; do { if (TclHasInternalRep(objPtr, &tclIntType)) { *wideIntPtr = objPtr->internalRep.wideValue; return TCL_OK; } if (TclHasInternalRep(objPtr, &tclDoubleType)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected integer but got \"%s\"", TclGetString(objPtr))); Tcl_SetErrorCode(interp, "TCL", "VALUE", "INTEGER", (char *)NULL); } return TCL_ERROR; } if (TclHasInternalRep(objPtr, &tclBignumType)) { /* * Must check for those bignum values that can fit in a * Tcl_WideInt, even when auto-narrowing is enabled. */ mp_int big; Tcl_WideUInt value = 0; size_t numBytes; Tcl_WideInt scratch; unsigned char *bytes = (unsigned char *) &scratch; TclUnpackBignum(objPtr, big); if (mp_to_ubin(&big, bytes, sizeof(Tcl_WideInt), &numBytes) == MP_OKAY) { while (numBytes-- > 0) { value = (value << CHAR_BIT) | *bytes++; } if (big.sign) { if (value <= 1 + ~(Tcl_WideUInt)WIDE_MIN) { *wideIntPtr = (Tcl_WideInt)(-value); return TCL_OK; } } else { if (value <= (Tcl_WideUInt)WIDE_MAX) { *wideIntPtr = (Tcl_WideInt)value; return TCL_OK; } } } if (interp != NULL) { const char *s = "integer value too large to represent"; Tcl_Obj *msg = Tcl_NewStringObj(s, -1); Tcl_SetObjResult(interp, msg); Tcl_SetErrorCode(interp, "ARITH", "IOVERFLOW", s, (char *)NULL); } return TCL_ERROR; } /* Handle dict separately, because it doesn't have a lengthProc */ if (TclHasInternalRep(objPtr, &tclDictType)) { Tcl_DictObjSize(NULL, objPtr, &length); if (length > 0) { listRep: if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj("expected integer but got a list", -1)); } return TCL_ERROR; } } Tcl_ObjTypeLengthProc *lengthProc = TclObjTypeHasProc(objPtr, lengthProc); if (lengthProc && lengthProc(objPtr) != 1) { goto listRep; } } while (TclParseNumber(interp, objPtr, "integer", NULL, -1, NULL, TCL_PARSE_INTEGER_ONLY)==TCL_OK); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_GetWideUIntFromObj -- * * Attempt to return a unsigned wide integer from the Tcl object "objPtr". If the * object is not already a wide int object or a bignum object, an attempt will * be made to convert it to one. * * Results: * The return value is a standard Tcl object result. If an error occurs * during conversion, an error message is left in the interpreter's * result unless "interp" is NULL. * * Side effects: * If the object is not already an int object, the conversion will free * any old internal representation. * *---------------------------------------------------------------------- */ int Tcl_GetWideUIntFromObj( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr, /* Object from which to get a wide int. */ Tcl_WideUInt *wideUIntPtr) /* Place to store resulting long. */ { do { if (TclHasInternalRep(objPtr, &tclIntType)) { if (objPtr->internalRep.wideValue < 0) { wideUIntOutOfRange: if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected unsigned integer but got \"%s\"", TclGetString(objPtr))); Tcl_SetErrorCode(interp, "TCL", "VALUE", "INTEGER", (char *)NULL); } return TCL_ERROR; } *wideUIntPtr = (Tcl_WideUInt)objPtr->internalRep.wideValue; return TCL_OK; } if (TclHasInternalRep(objPtr, &tclDoubleType)) { goto wideUIntOutOfRange; } if (TclHasInternalRep(objPtr, &tclBignumType)) { /* * Must check for those bignum values that can fit in a * Tcl_WideUInt, even when auto-narrowing is enabled. */ mp_int big; Tcl_WideUInt value = 0; size_t numBytes; Tcl_WideUInt scratch; unsigned char *bytes = (unsigned char *) &scratch; TclUnpackBignum(objPtr, big); if (big.sign == MP_NEG) { goto wideUIntOutOfRange; } if (mp_to_ubin(&big, bytes, sizeof(Tcl_WideUInt), &numBytes) == MP_OKAY) { while (numBytes-- > 0) { value = (value << CHAR_BIT) | *bytes++; } *wideUIntPtr = (Tcl_WideUInt)value; return TCL_OK; } if (interp != NULL) { const char *s = "integer value too large to represent"; Tcl_Obj *msg = Tcl_NewStringObj(s, -1); Tcl_SetObjResult(interp, msg); Tcl_SetErrorCode(interp, "ARITH", "IOVERFLOW", s, (char *)NULL); } return TCL_ERROR; } } while (TclParseNumber(interp, objPtr, "integer", NULL, -1, NULL, TCL_PARSE_INTEGER_ONLY)==TCL_OK); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TclGetWideBitsFromObj -- * * Attempt to return a wide integer from the Tcl object "objPtr". If the * object is not already a int, double or bignum, an attempt will be made * to convert it to one of these. Out-of-range values don't result in an * error, but only the least significant 64 bits will be returned. * * Results: * The return value is a standard Tcl object result. If an error occurs * during conversion, an error message is left in the interpreter's * result unless "interp" is NULL. * * Side effects: * If the object is not already an int, double or bignum object, the * conversion will free any old internal representation. * *---------------------------------------------------------------------- */ int TclGetWideBitsFromObj( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr, /* Object from which to get a wide int. */ Tcl_WideInt *wideIntPtr) /* Place to store resulting wide integer. */ { do { if (TclHasInternalRep(objPtr, &tclIntType)) { *wideIntPtr = objPtr->internalRep.wideValue; return TCL_OK; } if (TclHasInternalRep(objPtr, &tclDoubleType)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected integer but got \"%s\"", TclGetString(objPtr))); Tcl_SetErrorCode(interp, "TCL", "VALUE", "INTEGER", (char *)NULL); } return TCL_ERROR; } if (TclHasInternalRep(objPtr, &tclBignumType)) { mp_int big; mp_err err; Tcl_WideUInt value = 0, scratch; size_t numBytes; unsigned char *bytes = (unsigned char *) &scratch; Tcl_GetBignumFromObj(NULL, objPtr, &big); err = mp_mod_2d(&big, (int) (CHAR_BIT * sizeof(Tcl_WideInt)), &big); if (err == MP_OKAY) { err = mp_to_ubin(&big, bytes, sizeof(Tcl_WideInt), &numBytes); } if (err != MP_OKAY) { return TCL_ERROR; } while (numBytes-- > 0) { value = (value << CHAR_BIT) | *bytes++; } *wideIntPtr = !big.sign ? (Tcl_WideInt)value : -(Tcl_WideInt)value; mp_clear(&big); return TCL_OK; } } while (TclParseNumber(interp, objPtr, "integer", NULL, -1, NULL, TCL_PARSE_INTEGER_ONLY)==TCL_OK); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_GetSizeIntFromObj -- * * Attempt to return a Tcl_Size from the Tcl object "objPtr". * * Results: * TCL_OK - the converted Tcl_Size value is stored in *sizePtr * TCL_ERROR - the error message is stored in interp * * Side effects: * The function may free up any existing internal representation. * *---------------------------------------------------------------------- */ int Tcl_GetSizeIntFromObj( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr, /* The object from which to get a int. */ Tcl_Size *sizePtr) /* Place to store resulting int. */ { if (sizeof(Tcl_Size) == sizeof(int)) { return TclGetIntFromObj(interp, objPtr, (int *)sizePtr); } else { Tcl_WideInt wide; if (TclGetWideIntFromObj(interp, objPtr, &wide) != TCL_OK) { return TCL_ERROR; } *sizePtr = (Tcl_Size)wide; return TCL_OK; } } /* *---------------------------------------------------------------------- * * FreeBignum -- * * This function frees the internal rep of a bignum. * * Results: * None. * *---------------------------------------------------------------------- */ static void FreeBignum( Tcl_Obj *objPtr) { mp_int toFree; /* Bignum to free */ TclUnpackBignum(objPtr, toFree); mp_clear(&toFree); if (PTR2INT(objPtr->internalRep.twoPtrValue.ptr2) < 0) { Tcl_Free(objPtr->internalRep.twoPtrValue.ptr1); } objPtr->typePtr = NULL; } /* *---------------------------------------------------------------------- * * DupBignum -- * * This function duplicates the internal rep of a bignum. * * Results: * None. * * Side effects: * The destination object receives a copy of the source object * *---------------------------------------------------------------------- */ static void DupBignum( Tcl_Obj *srcPtr, Tcl_Obj *copyPtr) { mp_int bignumVal; mp_int bignumCopy; copyPtr->typePtr = &tclBignumType; TclUnpackBignum(srcPtr, bignumVal); if (mp_init_copy(&bignumCopy, &bignumVal) != MP_OKAY) { Tcl_Panic("initialization failure in DupBignum"); } PACK_BIGNUM(bignumCopy, copyPtr); } /* *---------------------------------------------------------------------- * * UpdateStringOfBignum -- * * This function updates the string representation of a bignum object. * * Results: * None. * * Side effects: * The object's string is set to whatever results from the bignum- * to-string conversion. * * The object's existing string representation is NOT freed; memory will leak * if the string rep is still valid at the time this function is called. * *---------------------------------------------------------------------- */ static void UpdateStringOfBignum( Tcl_Obj *objPtr) { mp_int bignumVal; int size; char *stringVal; TclUnpackBignum(objPtr, bignumVal); if (MP_OKAY != mp_radix_size(&bignumVal, 10, &size)) { Tcl_Panic("radix size failure in UpdateStringOfBignum"); } if (size < 2) { /* * mp_radix_size() returns < 2 when more than INT_MAX bytes would be * needed to hold the string rep (because mp_radix_size ignores * integer overflow issues). * * Note that so long as we enforce our bignums to the size that fits * in a packed bignum, this branch will never be taken. */ Tcl_Panic("UpdateStringOfBignum: string length limit exceeded"); } stringVal = Tcl_InitStringRep(objPtr, NULL, size - 1); TclOOM(stringVal, size); if (MP_OKAY != mp_to_radix(&bignumVal, stringVal, size, NULL, 10)) { Tcl_Panic("conversion failure in UpdateStringOfBignum"); } } /* *---------------------------------------------------------------------- * * Tcl_NewBignumObj -- * * Creates and initializes a bignum object. * * Results: * Returns the newly created object. * * Side effects: * The bignum value is cleared, since ownership has transferred to Tcl. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG #undef Tcl_NewBignumObj Tcl_Obj * Tcl_NewBignumObj( void *bignumValue) { return Tcl_DbNewBignumObj(bignumValue, "unknown", 0); } #else Tcl_Obj * Tcl_NewBignumObj( void *bignumValue) { Tcl_Obj *objPtr; TclNewObj(objPtr); Tcl_SetBignumObj(objPtr, bignumValue); return objPtr; } #endif /* *---------------------------------------------------------------------- * * Tcl_DbNewBignumObj -- * * This function is normally called when debugging: that is, when * TCL_MEM_DEBUG is defined. It constructs a bignum object, recording the * creation point so that [memory active] can report it. * * Results: * Returns the newly created object. * * Side effects: * The bignum value is cleared, since ownership has transferred to Tcl. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG Tcl_Obj * Tcl_DbNewBignumObj( void *bignumValue, const char *file, int line) { Tcl_Obj *objPtr; TclDbNewObj(objPtr, file, line); Tcl_SetBignumObj(objPtr, bignumValue); return objPtr; } #else Tcl_Obj * Tcl_DbNewBignumObj( void *bignumValue, TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { return Tcl_NewBignumObj(bignumValue); } #endif /* *---------------------------------------------------------------------- * * GetBignumFromObj -- * * This function retrieves a 'bignum' value from a Tcl object, converting * the object if necessary. Either copies or transfers the mp_int value * depending on the copy flag value passed in. * * Results: * Returns TCL_OK if the conversion is successful, TCL_ERROR otherwise. * * Side effects: * A copy of bignum is stored in *bignumValue, which is expected to be * uninitialized or cleared. If conversion fails, and the 'interp' * argument is not NULL, an error message is stored in the interpreter * result. * *---------------------------------------------------------------------- */ static int GetBignumFromObj( Tcl_Interp *interp, /* Tcl interpreter for error reporting */ Tcl_Obj *objPtr, /* Object to read */ int copy, /* Whether to copy the returned bignum value */ mp_int *bignumValue) /* Returned bignum value. */ { do { if (TclHasInternalRep(objPtr, &tclBignumType)) { if (copy || Tcl_IsShared(objPtr)) { mp_int temp; TclUnpackBignum(objPtr, temp); if (mp_init_copy(bignumValue, &temp) != MP_OKAY) { return TCL_ERROR; } } else { TclUnpackBignum(objPtr, *bignumValue); /* Optimized TclFreeInternalRep */ objPtr->internalRep.twoPtrValue.ptr1 = NULL; objPtr->internalRep.twoPtrValue.ptr2 = NULL; objPtr->typePtr = NULL; /* * TODO: If objPtr has a string rep, this leaves * it undisturbed. Not clear that's proper. Pure * bignum values are converted to empty string. */ if (objPtr->bytes == NULL) { TclInitEmptyStringRep(objPtr); } } return TCL_OK; } if (TclHasInternalRep(objPtr, &tclIntType)) { if (mp_init_i64(bignumValue, objPtr->internalRep.wideValue) != MP_OKAY) { return TCL_ERROR; } return TCL_OK; } if (TclHasInternalRep(objPtr, &tclDoubleType)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected integer but got \"%s\"", TclGetString(objPtr))); Tcl_SetErrorCode(interp, "TCL", "VALUE", "INTEGER", (char *)NULL); } return TCL_ERROR; } } while (TclParseNumber(interp, objPtr, "integer", NULL, -1, NULL, TCL_PARSE_INTEGER_ONLY)==TCL_OK); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_GetBignumFromObj -- * * This function retrieves a 'bignum' value from a Tcl object, converting * the object if necessary. * * Results: * Returns TCL_OK if the conversion is successful, TCL_ERROR otherwise. * * Side effects: * A copy of bignum is stored in *bignumValue, which is expected to be * uninitialized or cleared. If conversion fails, an the 'interp' * argument is not NULL, an error message is stored in the interpreter * result. * * It is expected that the caller will NOT have invoked mp_init on the * bignum value before passing it in. Tcl will initialize the mp_int as * it sets the value. The value is a copy of the value in objPtr, so it * becomes the responsibility of the caller to call mp_clear on it. * *---------------------------------------------------------------------- */ int Tcl_GetBignumFromObj( Tcl_Interp *interp, /* Tcl interpreter for error reporting */ Tcl_Obj *objPtr, /* Object to read */ void *bignumValue) /* Returned bignum value. */ { return GetBignumFromObj(interp, objPtr, 1, (mp_int *)bignumValue); } /* *---------------------------------------------------------------------- * * Tcl_TakeBignumFromObj -- * * This function retrieves a 'bignum' value from a Tcl object, converting * the object if necessary. * * Results: * Returns TCL_OK if the conversion is successful, TCL_ERROR otherwise. * * Side effects: * A copy of bignum is stored in *bignumValue, which is expected to be * uninitialized or cleared. If conversion fails and the 'interp' * argument is not NULL, an error message is stored in the interpreter * result. * * It is expected that the caller will NOT have invoked mp_init on the * bignum value before passing it in. Tcl will initialize the mp_int as * it sets the value. The value is transferred from the internals of * objPtr to the caller, passing responsibility of the caller to call * mp_clear on it. The objPtr is cleared to hold an empty value. * *---------------------------------------------------------------------- */ int Tcl_TakeBignumFromObj( Tcl_Interp *interp, /* Tcl interpreter for error reporting */ Tcl_Obj *objPtr, /* Object to read */ void *bignumValue) /* Returned bignum value. */ { return GetBignumFromObj(interp, objPtr, 0, (mp_int *)bignumValue); } /* *---------------------------------------------------------------------- * * Tcl_SetBignumObj -- * * This function sets the value of a Tcl_Obj to a large integer. * * Results: * None. * * Side effects: * Object value is stored. The bignum value is cleared, since ownership * has transferred to Tcl. * *---------------------------------------------------------------------- */ void Tcl_SetBignumObj( Tcl_Obj *objPtr, /* Object to set */ void *big) /* Value to store */ { Tcl_WideUInt value = 0; size_t numBytes; Tcl_WideUInt scratch; unsigned char *bytes = (unsigned char *) &scratch; mp_int *bignumValue = (mp_int *) big; if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_SetBignumObj"); } if (mp_to_ubin(bignumValue, bytes, sizeof(Tcl_WideUInt), &numBytes) != MP_OKAY) { goto tooLargeForWide; } while (numBytes-- > 0) { value = (value << CHAR_BIT) | *bytes++; } if (value > ((Tcl_WideUInt)WIDE_MAX + bignumValue->sign)) { goto tooLargeForWide; } if (bignumValue->sign) { TclSetIntObj(objPtr, (Tcl_WideInt)(-value)); } else { TclSetIntObj(objPtr, (Tcl_WideInt)value); } mp_clear(bignumValue); return; tooLargeForWide: TclInvalidateStringRep(objPtr); TclFreeInternalRep(objPtr); TclSetBignumInternalRep(objPtr, bignumValue); } /* *---------------------------------------------------------------------- * * TclSetBignumInternalRep -- * * Install a bignum into the internal representation of an object. * * Results: * None. * * Side effects: * Object internal representation is updated and object type is set. The * bignum value is cleared, since ownership has transferred to the * object. * *---------------------------------------------------------------------- */ void TclSetBignumInternalRep( Tcl_Obj *objPtr, void *big) { mp_int *bignumValue = (mp_int *)big; objPtr->typePtr = &tclBignumType; PACK_BIGNUM(*bignumValue, objPtr); /* * Clear the mp_int value. * * Don't call mp_clear() because it would free the digit array we just * packed into the Tcl_Obj. */ bignumValue->dp = NULL; bignumValue->alloc = bignumValue->used = 0; bignumValue->sign = MP_NEG; } /* *---------------------------------------------------------------------- * * Tcl_GetNumberFromObj -- * * Extracts a number (of any possible numeric type) from an object. * * Results: * Whether the extraction worked. The type is stored in the variable * referred to by the typePtr argument, and a pointer to the * representation is stored in the variable referred to by the * clientDataPtr. * * Side effects: * Can allocate thread-specific data for handling the copy-out space for * bignums; this space is shared within a thread. * *---------------------------------------------------------------------- */ int Tcl_GetNumberFromObj( Tcl_Interp *interp, Tcl_Obj *objPtr, void **clientDataPtr, int *typePtr) { Tcl_Size length; do { if (TclHasInternalRep(objPtr, &tclDoubleType)) { if (isnan(objPtr->internalRep.doubleValue)) { *typePtr = TCL_NUMBER_NAN; } else { *typePtr = TCL_NUMBER_DOUBLE; } *clientDataPtr = &objPtr->internalRep.doubleValue; return TCL_OK; } if (TclHasInternalRep(objPtr, &tclIntType)) { *typePtr = TCL_NUMBER_INT; *clientDataPtr = &objPtr->internalRep.wideValue; return TCL_OK; } if (TclHasInternalRep(objPtr, &tclBignumType)) { static Tcl_ThreadDataKey bignumKey; mp_int *bigPtr = (mp_int *)Tcl_GetThreadData(&bignumKey, sizeof(mp_int)); TclUnpackBignum(objPtr, *bigPtr); *typePtr = TCL_NUMBER_BIG; *clientDataPtr = bigPtr; return TCL_OK; } /* Handle dict separately, because it doesn't have a lengthProc */ if (TclHasInternalRep(objPtr, &tclDictType)) { Tcl_DictObjSize(NULL, objPtr, &length); if (length > 0) { listRep: if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj("expected number but got a list", -1)); } return TCL_ERROR; } } Tcl_ObjTypeLengthProc *lengthProc = TclObjTypeHasProc(objPtr, lengthProc); if (lengthProc && lengthProc(objPtr) != 1) { goto listRep; } } while (TCL_OK == TclParseNumber(interp, objPtr, "number", NULL, -1, NULL, 0)); return TCL_ERROR; } int Tcl_GetNumber( Tcl_Interp *interp, const char *bytes, Tcl_Size numBytes, void **clientDataPtr, int *typePtr) { static Tcl_ThreadDataKey numberCacheKey; Tcl_Obj *objPtr = (Tcl_Obj *)Tcl_GetThreadData(&numberCacheKey, sizeof(Tcl_Obj)); Tcl_FreeInternalRep(objPtr); if (bytes == NULL) { bytes = &tclEmptyString; numBytes = 0; } if (numBytes < 0) { numBytes = strlen(bytes); } if (numBytes > INT_MAX) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "max size for a Tcl value (%d bytes) exceeded", INT_MAX)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); } return TCL_ERROR; } objPtr->bytes = (char *) bytes; objPtr->length = numBytes; return Tcl_GetNumberFromObj(interp, objPtr, clientDataPtr, typePtr); } /* *---------------------------------------------------------------------- * * Tcl_IncrRefCount -- * * Increments the reference count of the object. * * Results: * None. * *---------------------------------------------------------------------- */ #undef Tcl_IncrRefCount void Tcl_IncrRefCount( Tcl_Obj *objPtr) /* The object we are registering a reference to. */ { ++(objPtr)->refCount; } /* *---------------------------------------------------------------------- * * Tcl_DecrRefCount -- * * Decrements the reference count of the object. * * Results: * The storage for objPtr may be freed. * *---------------------------------------------------------------------- */ #undef Tcl_DecrRefCount void Tcl_DecrRefCount( Tcl_Obj *objPtr) /* The object we are releasing a reference to. */ { if (objPtr->refCount-- <= 1) { TclFreeObj(objPtr); } } /* *---------------------------------------------------------------------- * * TclUndoRefCount -- * * Decrement the refCount of objPtr without causing it to be freed if it * drops from 1 to 0. This allows a function increment a refCount but * then decrement it and still be able to pass return it to a caller, * possibly with a refCount of 0. The caller must have previously * incremented the refCount. * *---------------------------------------------------------------------- */ void TclUndoRefCount( Tcl_Obj *objPtr) /* The object we are releasing a reference to. */ { if (objPtr->refCount > 0) { --objPtr->refCount; } } /* *---------------------------------------------------------------------- * * Tcl_IsShared -- * * Tests if the object has a ref count greater than one. * * Results: * Boolean value that is the result of the test. * *---------------------------------------------------------------------- */ #undef Tcl_IsShared int Tcl_IsShared( Tcl_Obj *objPtr) /* The object to test for being shared. */ { return ((objPtr)->refCount > 1); } /* *---------------------------------------------------------------------- * * Tcl_DbIncrRefCount -- * * This function is normally called when debugging: i.e., when * TCL_MEM_DEBUG is defined. This checks to see whether or not the memory * has been freed before incrementing the ref count. * * When TCL_MEM_DEBUG is not defined, this function just increments the * reference count of the object. * * Results: * None. * * Side effects: * The object's ref count is incremented. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG void Tcl_DbIncrRefCount( Tcl_Obj *objPtr, /* The object we are registering a reference * to. */ const char *file, /* The name of the source file calling this * function; used for debugging. */ int line) /* Line number in the source file; used for * debugging. */ { if (objPtr->refCount == FREEDREFCOUNTFILLER) { fprintf(stderr, "file = %s, line = %d\n", file, line); fflush(stderr); Tcl_Panic("incrementing refCount of previously disposed object"); } #if TCL_THREADS /* * Check to make sure that the Tcl_Obj was allocated by the current * thread. Don't do this check when shutting down since thread local * storage can be finalized before the last Tcl_Obj is freed. */ if (!TclInExit()) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Tcl_HashTable *tablePtr = tsdPtr->objThreadMap; Tcl_HashEntry *hPtr; if (!tablePtr) { Tcl_Panic("object table not initialized"); } hPtr = Tcl_FindHashEntry(tablePtr, objPtr); if (!hPtr) { Tcl_Panic("Trying to %s of Tcl_Obj allocated in another thread", "incr ref count"); } } # endif /* TCL_THREADS */ ++(objPtr)->refCount; } #else /* !TCL_MEM_DEBUG */ void Tcl_DbIncrRefCount( Tcl_Obj *objPtr, /* The object we are registering a reference * to. */ TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { ++(objPtr)->refCount; } #endif /* TCL_MEM_DEBUG */ /* *---------------------------------------------------------------------- * * Tcl_DbDecrRefCount -- * * This function is normally called when debugging: i.e., when * TCL_MEM_DEBUG is defined. This checks to see whether or not the memory * has been freed before decrementing the ref count. * * When TCL_MEM_DEBUG is not defined, this function just decrements the * reference count of the object. * * Results: * None. * * Side effects: * The object's ref count is incremented. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG void Tcl_DbDecrRefCount( Tcl_Obj *objPtr, /* The object we are releasing a reference * to. */ const char *file, /* The name of the source file calling this * function; used for debugging. */ int line) /* Line number in the source file; used for * debugging. */ { if (objPtr->refCount == FREEDREFCOUNTFILLER) { fprintf(stderr, "file = %s, line = %d\n", file, line); fflush(stderr); Tcl_Panic("decrementing refCount of previously disposed object"); } #if TCL_THREADS /* * Check to make sure that the Tcl_Obj was allocated by the current * thread. Don't do this check when shutting down since thread local * storage can be finalized before the last Tcl_Obj is freed. */ if (!TclInExit()) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Tcl_HashTable *tablePtr = tsdPtr->objThreadMap; Tcl_HashEntry *hPtr; if (!tablePtr) { Tcl_Panic("object table not initialized"); } hPtr = Tcl_FindHashEntry(tablePtr, objPtr); if (!hPtr) { Tcl_Panic("Trying to %s of Tcl_Obj allocated in another thread", "decr ref count"); } } # endif /* TCL_THREADS */ if (objPtr->refCount-- <= 1) { TclFreeObj(objPtr); } } #else /* !TCL_MEM_DEBUG */ void Tcl_DbDecrRefCount( Tcl_Obj *objPtr, /* The object we are releasing a reference * to. */ TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { if (objPtr->refCount-- <= 1) { TclFreeObj(objPtr); } } #endif /* TCL_MEM_DEBUG */ /* *---------------------------------------------------------------------- * * Tcl_DbIsShared -- * * This function is normally called when debugging: i.e., when * TCL_MEM_DEBUG is defined. It tests whether the object has a ref count * greater than one. * * When TCL_MEM_DEBUG is not defined, this function just tests if the * object has a ref count greater than one. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_DbIsShared( Tcl_Obj *objPtr, /* The object to test for being shared. */ #ifdef TCL_MEM_DEBUG const char *file, /* The name of the source file calling this * function; used for debugging. */ int line) /* Line number in the source file; used for * debugging. */ #else TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) #endif { #ifdef TCL_MEM_DEBUG if (objPtr->refCount == FREEDREFCOUNTFILLER) { fprintf(stderr, "file = %s, line = %d\n", file, line); fflush(stderr); Tcl_Panic("checking whether previously disposed object is shared"); } #if TCL_THREADS /* * Check to make sure that the Tcl_Obj was allocated by the current * thread. Don't do this check when shutting down since thread local * storage can be finalized before the last Tcl_Obj is freed. */ if (!TclInExit()) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Tcl_HashTable *tablePtr = tsdPtr->objThreadMap; Tcl_HashEntry *hPtr; if (!tablePtr) { Tcl_Panic("object table not initialized"); } hPtr = Tcl_FindHashEntry(tablePtr, objPtr); if (!hPtr) { Tcl_Panic("Trying to %s of Tcl_Obj allocated in another thread", "check shared status"); } } # endif /* TCL_THREADS */ #endif /* TCL_MEM_DEBUG */ #ifdef TCL_COMPILE_STATS Tcl_MutexLock(&tclObjMutex); if ((objPtr)->refCount <= 1) { tclObjsShared[1]++; } else if ((objPtr)->refCount < TCL_MAX_SHARED_OBJ_STATS) { tclObjsShared[(objPtr)->refCount]++; } else { tclObjsShared[0]++; } Tcl_MutexUnlock(&tclObjMutex); #endif /* TCL_COMPILE_STATS */ return ((objPtr)->refCount > 1); } /* *---------------------------------------------------------------------- * * Tcl_InitObjHashTable -- * * Given storage for a hash table, set up the fields to prepare the hash * table for use, the keys are Tcl_Obj *. * * Results: * None. * * Side effects: * TablePtr is now ready to be passed to Tcl_FindHashEntry and * Tcl_CreateHashEntry. * *---------------------------------------------------------------------- */ void Tcl_InitObjHashTable( Tcl_HashTable *tablePtr) /* Pointer to table record, which is supplied * by the caller. */ { Tcl_InitCustomHashTable(tablePtr, TCL_CUSTOM_PTR_KEYS, &tclObjHashKeyType); } /* *---------------------------------------------------------------------- * * AllocObjEntry -- * * Allocate space for a Tcl_HashEntry containing the Tcl_Obj * key. * * Results: * The return value is a pointer to the created entry. * * Side effects: * Increments the reference count on the object. * *---------------------------------------------------------------------- */ static Tcl_HashEntry * AllocObjEntry( TCL_UNUSED(Tcl_HashTable *), void *keyPtr) /* Key to store in the hash table entry. */ { Tcl_Obj *objPtr = (Tcl_Obj *)keyPtr; Tcl_HashEntry *hPtr = (Tcl_HashEntry *)Tcl_Alloc(sizeof(Tcl_HashEntry)); hPtr->key.objPtr = objPtr; Tcl_IncrRefCount(objPtr); hPtr->clientData = NULL; return hPtr; } /* *---------------------------------------------------------------------- * * TclCompareObjKeys -- * * Compares two Tcl_Obj * keys. * * Results: * The return value is 0 if they are different and 1 if they are the * same. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclCompareObjKeys( void *keyPtr, /* New key to compare. */ Tcl_HashEntry *hPtr) /* Existing key to compare. */ { Tcl_Obj *objPtr1 = (Tcl_Obj *)keyPtr; Tcl_Obj *objPtr2 = (Tcl_Obj *)hPtr->key.oneWordValue; const char *p1, *p2; size_t l1, l2; /* * If the object pointers are the same then they match. * OPT: this comparison was moved to the caller if (objPtr1 == objPtr2) { return 1; } */ /* * Don't use Tcl_GetStringFromObj as it would prevent l1 and l2 being * in a register. */ p1 = TclGetString(objPtr1); l1 = objPtr1->length; p2 = TclGetString(objPtr2); l2 = objPtr2->length; /* * Only compare if the string representations are of the same length. */ if (l1 == l2) { for (;; p1++, p2++, l1--) { if (*p1 != *p2) { break; } if (l1 == 0) { return 1; } } } return 0; } /* *---------------------------------------------------------------------- * * TclFreeObjEntry -- * * Frees space for a Tcl_HashEntry containing the Tcl_Obj * key. * * Results: * The return value is a pointer to the created entry. * * Side effects: * Decrements the reference count of the object. * *---------------------------------------------------------------------- */ void TclFreeObjEntry( Tcl_HashEntry *hPtr) /* Hash entry to free. */ { Tcl_Obj *objPtr = (Tcl_Obj *) hPtr->key.oneWordValue; Tcl_DecrRefCount(objPtr); Tcl_Free(hPtr); } /* *---------------------------------------------------------------------- * * TclHashObjKey -- * * Compute a one-word summary of the string representation of the * Tcl_Obj, which can be used to generate a hash index. * * Results: * The return value is a one-word summary of the information in the * string representation of the Tcl_Obj. * * Side effects: * None. * *---------------------------------------------------------------------- */ size_t TclHashObjKey( TCL_UNUSED(Tcl_HashTable *), void *keyPtr) /* Key from which to compute hash value. */ { Tcl_Obj *objPtr = (Tcl_Obj *)keyPtr; Tcl_Size length; const char *string = Tcl_GetStringFromObj(objPtr, &length); size_t result = 0; /* * I tried a zillion different hash functions and asked many other people * for advice. Many people had their own favorite functions, all * different, but no-one had much idea why they were good ones. I chose * the one below (multiply by 9 and add new character) because of the * following reasons: * * 1. Multiplying by 10 is perfect for keys that are decimal strings, and * multiplying by 9 is just about as good. * 2. Times-9 is (shift-left-3) plus (old). This means that each * character's bits hang around in the low-order bits of the hash value * for ever, plus they spread fairly rapidly up to the high-order bits * to fill out the hash value. This seems works well both for decimal * and non-decimal strings. * * Note that this function is very weak against malicious strings; it's * very easy to generate multiple keys that have the same hashcode. On the * other hand, that hardly ever actually occurs and this function *is* * very cheap, even by comparison with industry-standard hashes like FNV. * If real strength of hash is required though, use a custom hash based on * Bob Jenkins's lookup3(), but be aware that it's significantly slower. * Tcl does not use that level of strength because it typically does not * need it (and some of the aspects of that strength are genuinely * unnecessary given the rest of Tcl's hash machinery, and the fact that * we do not either transfer hashes to another machine, use them as a true * substitute for equality, or attempt to minimize work in rebuilding the * hash table). * * See also HashStringKey in tclHash.c. * See also HashString in tclLiteral.c. * * See [tcl-Feature Request #2958832] */ if (length > 0) { result = UCHAR(*string); while (--length) { result += (result << 3) + UCHAR(*++string); } } return result; } /* *---------------------------------------------------------------------- * * Tcl_GetCommandFromObj -- * * Returns the command specified by the name in a Tcl_Obj. * * Results: * Returns a token for the command if it is found. Otherwise, if it can't * be found or there is an error, returns NULL. * * Side effects: * May update the internal representation for the object, caching the * command reference so that the next time this function is called with * the same object, the command can be found quickly. * *---------------------------------------------------------------------- */ Tcl_Command Tcl_GetCommandFromObj( Tcl_Interp *interp, /* The interpreter in which to resolve the * command and to report errors. */ Tcl_Obj *objPtr) /* The object containing the command's name. * If the name starts with "::", will be * looked up in global namespace. Else, looked * up first in the current namespace, then in * global namespace. */ { ResolvedCmdName *resPtr; /* * Get the internal representation, converting to a command type if * needed. The internal representation is a ResolvedCmdName that points to * the actual command. * * Check the context namespace and the namespace epoch of the resolved * symbol to make sure that it is fresh. Note that we verify that the * namespace id of the context namespace is the same as the one we cached; * this insures that the namespace wasn't deleted and a new one created at * the same address with the same command epoch. Note that fully qualified * names have a NULL refNsPtr, these checks needn't be made. * * Check also that the command's epoch is up to date, and that the command * is not deleted. * * If any check fails, then force another conversion to the command type, * to discard the old rep and create a new one. */ resPtr = (ResolvedCmdName *)objPtr->internalRep.twoPtrValue.ptr1; if (TclHasInternalRep(objPtr, &tclCmdNameType)) { Command *cmdPtr = resPtr->cmdPtr; if ((cmdPtr->cmdEpoch == resPtr->cmdEpoch) && (interp == cmdPtr->nsPtr->interp) && !(cmdPtr->nsPtr->flags & NS_DYING)) { Namespace *refNsPtr = (Namespace *) TclGetCurrentNamespace(interp); if ((resPtr->refNsPtr == NULL) || ((refNsPtr == resPtr->refNsPtr) && (resPtr->refNsId == refNsPtr->nsId) && (resPtr->refNsCmdEpoch == refNsPtr->cmdRefEpoch))) { return (Tcl_Command) cmdPtr; } } } /* * OK, must create a new internal representation (or fail) as any cache we * had is invalid one way or another. */ /* See [07d13d99b0a9] why we cannot call SetCmdNameFromAny() directly here. */ if (tclCmdNameType.setFromAnyProc(interp, objPtr) != TCL_OK) { return NULL; } resPtr = (ResolvedCmdName *)objPtr->internalRep.twoPtrValue.ptr1; return (Tcl_Command) (resPtr ? resPtr->cmdPtr : NULL); } /* *---------------------------------------------------------------------- * * TclSetCmdNameObj -- * * Modify an object to be an CmdName object that refers to the argument * Command structure. * * Results: * None. * * Side effects: * The object's old internal rep is freed. Its string rep is not * changed. The refcount in the Command structure is incremented to keep * it from being freed if the command is later deleted until * TclNRExecuteByteCode has a chance to recognize that it was deleted. * *---------------------------------------------------------------------- */ static void SetCmdNameObj( Tcl_Interp *interp, Tcl_Obj *objPtr, Command *cmdPtr, ResolvedCmdName *resPtr) { Interp *iPtr = (Interp *) interp; ResolvedCmdName *fillPtr; const char *name = TclGetString(objPtr); if (resPtr) { fillPtr = resPtr; } else { fillPtr = (ResolvedCmdName *)Tcl_Alloc(sizeof(ResolvedCmdName)); fillPtr->refCount = 1; } fillPtr->cmdPtr = cmdPtr; cmdPtr->refCount++; fillPtr->cmdEpoch = cmdPtr->cmdEpoch; /* NOTE: relying on NULL termination here. */ if ((name[0] == ':') && (name[1] == ':')) { /* * Fully qualified names always resolve to same thing. No need * to record resolution context information. */ fillPtr->refNsPtr = NULL; fillPtr->refNsId = 0; /* Will not be read */ fillPtr->refNsCmdEpoch = 0; /* Will not be read */ } else { /* * Record current state of current namespace as the resolution * context of this command name lookup. */ Namespace *currNsPtr = iPtr->varFramePtr->nsPtr; fillPtr->refNsPtr = currNsPtr; fillPtr->refNsId = currNsPtr->nsId; fillPtr->refNsCmdEpoch = currNsPtr->cmdRefEpoch; } if (resPtr == NULL) { TclFreeInternalRep(objPtr); objPtr->internalRep.twoPtrValue.ptr1 = fillPtr; objPtr->internalRep.twoPtrValue.ptr2 = NULL; objPtr->typePtr = &tclCmdNameType; } } void TclSetCmdNameObj( Tcl_Interp *interp, /* Points to interpreter containing command * that should be cached in objPtr. */ Tcl_Obj *objPtr, /* Points to Tcl object to be changed to a * CmdName object. */ Command *cmdPtr) /* Points to Command structure that the * CmdName object should refer to. */ { ResolvedCmdName *resPtr; if (TclHasInternalRep(objPtr, &tclCmdNameType)) { resPtr = (ResolvedCmdName *)objPtr->internalRep.twoPtrValue.ptr1; if (resPtr != NULL && resPtr->cmdPtr == cmdPtr) { return; } } SetCmdNameObj(interp, objPtr, cmdPtr, NULL); } /* *---------------------------------------------------------------------- * * FreeCmdNameInternalRep -- * * Frees the resources associated with a cmdName object's internal * representation. * * Results: * None. * * Side effects: * Decrements the ref count of any cached ResolvedCmdName structure * pointed to by the cmdName's internal representation. If this is the * last use of the ResolvedCmdName, it is freed. This in turn decrements * the ref count of the Command structure pointed to by the * ResolvedSymbol, which may free the Command structure. * *---------------------------------------------------------------------- */ static void FreeCmdNameInternalRep( Tcl_Obj *objPtr) /* CmdName object with internal * representation to free. */ { ResolvedCmdName *resPtr = (ResolvedCmdName *)objPtr->internalRep.twoPtrValue.ptr1; /* * Decrement the reference count of the ResolvedCmdName structure. If * there are no more uses, free the ResolvedCmdName structure. */ if (resPtr->refCount-- <= 1) { /* * Now free the cached command, unless it is still in its hash * table or if there are other references to it from other cmdName * objects. */ Command *cmdPtr = resPtr->cmdPtr; TclCleanupCommandMacro(cmdPtr); Tcl_Free(resPtr); } objPtr->typePtr = NULL; } /* *---------------------------------------------------------------------- * * DupCmdNameInternalRep -- * * Initialize the internal representation of an cmdName Tcl_Obj to a copy * of the internal representation of an existing cmdName object. * * Results: * None. * * Side effects: * "copyPtr"s internal rep is set to point to the ResolvedCmdName * structure corresponding to "srcPtr"s internal rep. Increments the ref * count of the ResolvedCmdName structure pointed to by the cmdName's * internal representation. * *---------------------------------------------------------------------- */ static void DupCmdNameInternalRep( Tcl_Obj *srcPtr, /* Object with internal rep to copy. */ Tcl_Obj *copyPtr) /* Object with internal rep to set. */ { ResolvedCmdName *resPtr = (ResolvedCmdName *)srcPtr->internalRep.twoPtrValue.ptr1; copyPtr->internalRep.twoPtrValue.ptr1 = resPtr; copyPtr->internalRep.twoPtrValue.ptr2 = NULL; resPtr->refCount++; copyPtr->typePtr = &tclCmdNameType; } /* *---------------------------------------------------------------------- * * SetCmdNameFromAny -- * * Generate an cmdName internal form for the Tcl object "objPtr". * * Results: * The return value is a standard Tcl result. The conversion always * succeeds and TCL_OK is returned. * * Side effects: * A pointer to a ResolvedCmdName structure that holds a cached pointer * to the command with a name that matches objPtr's string rep is stored * as objPtr's internal representation. This ResolvedCmdName pointer will * be NULL if no matching command was found. The ref count of the cached * Command's structure (if any) is also incremented. * *---------------------------------------------------------------------- */ static int SetCmdNameFromAny( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr) /* The object to convert. */ { const char *name; Command *cmdPtr; ResolvedCmdName *resPtr; if (interp == NULL) { return TCL_ERROR; } /* * Find the Command structure, if any, that describes the command called * "name". Build a ResolvedCmdName that holds a cached pointer to this * Command, and bump the reference count in the referenced Command * structure. A Command structure will not be deleted as long as it is * referenced from a CmdName object. */ name = TclGetString(objPtr); cmdPtr = (Command *) Tcl_FindCommand(interp, name, /*ns*/ NULL, /*flags*/ 0); /* * Stop shimmering and caching nothing when we found nothing. Just * report the failure to find the command as an error. */ if (cmdPtr == NULL || !TclRoutineHasName(cmdPtr)) { return TCL_ERROR; } resPtr = (ResolvedCmdName *)objPtr->internalRep.twoPtrValue.ptr1; if (TclHasInternalRep(objPtr, &tclCmdNameType) && (resPtr->refCount == 1)) { /* * Re-use existing ResolvedCmdName struct when possible. * Cleanup the old fields that need it. */ Command *oldCmdPtr = resPtr->cmdPtr; if (oldCmdPtr->refCount-- <= 1) { TclCleanupCommandMacro(oldCmdPtr); } } else { resPtr = NULL; } SetCmdNameObj(interp, objPtr, cmdPtr, resPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_RepresentationCmd -- * * Implementation of the "tcl::unsupported::representation" command. * * Results: * Reports the current representation (Tcl_Obj type) of its argument. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_RepresentationCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *descObj; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "value"); return TCL_ERROR; } /* * Value is a bignum with a refcount of 14, object pointer at 0x12345678, * internal representation 0x45671234:0x98765432, string representation * "1872361827361287" */ descObj = Tcl_ObjPrintf("value is a %s with a refcount of %" TCL_SIZE_MODIFIER "d," " object pointer at %p", objv[1]->typePtr ? objv[1]->typePtr->name : "pure string", objv[1]->refCount, objv[1]); if (objv[1]->typePtr) { if (TclHasInternalRep(objv[1], &tclDoubleType)) { Tcl_AppendPrintfToObj(descObj, ", internal representation %g", objv[1]->internalRep.doubleValue); } else { Tcl_AppendPrintfToObj(descObj, ", internal representation %p:%p", (void *) objv[1]->internalRep.twoPtrValue.ptr1, (void *) objv[1]->internalRep.twoPtrValue.ptr2); } } if (objv[1]->bytes) { Tcl_AppendToObj(descObj, ", string representation \"", -1); Tcl_AppendLimitedToObj(descObj, objv[1]->bytes, objv[1]->length, 16, "..."); Tcl_AppendToObj(descObj, "\"", -1); } else { Tcl_AppendToObj(descObj, ", no string representation", -1); } Tcl_SetObjResult(interp, descObj); return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * indent-tabs-mode: nil * End: */ tcl9.0.1/generic/tclOptimize.c0000644000175000017500000002636414726623136015654 0ustar sergeisergei/* * tclOptimize.c -- * * This file contains the bytecode optimizer. * * Copyright © 2013 Donal Fellows. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" #include /* * Forward declarations. */ static void AdvanceJumps(CompileEnv *envPtr); static void ConvertZeroEffectToNOP(CompileEnv *envPtr); static void LocateTargetAddresses(CompileEnv *envPtr, Tcl_HashTable *tablePtr); static void TrimUnreachable(CompileEnv *envPtr); /* * Helper macros. */ #define DefineTargetAddress(tablePtr, address) \ ((void) Tcl_CreateHashEntry((tablePtr), (address), &isNew)) #define IsTargetAddress(tablePtr, address) \ (Tcl_FindHashEntry((tablePtr), (void *) (address)) != NULL) #define AddrLength(address) \ (tclInstructionTable[*(unsigned char *)(address)].numBytes) #define InstLength(instruction) \ (tclInstructionTable[UCHAR(instruction)].numBytes) /* * ---------------------------------------------------------------------- * * LocateTargetAddresses -- * * Populate a hash table with places that we need to be careful around * because they're the targets of various kinds of jumps and other * non-local behavior. * * ---------------------------------------------------------------------- */ static void LocateTargetAddresses( CompileEnv *envPtr, Tcl_HashTable *tablePtr) { unsigned char *currentInstPtr, *targetInstPtr; int isNew; Tcl_Size i; Tcl_HashEntry *hPtr; Tcl_HashSearch hSearch; Tcl_InitHashTable(tablePtr, TCL_ONE_WORD_KEYS); /* * The starts of commands represent target addresses. */ for (i=0 ; inumCommands ; i++) { DefineTargetAddress(tablePtr, envPtr->codeStart + envPtr->cmdMapPtr[i].codeOffset); } /* * Find places where we should be careful about replacing instructions * because they are the targets of various types of jumps. */ for (currentInstPtr = envPtr->codeStart ; currentInstPtr < envPtr->codeNext ; currentInstPtr += AddrLength(currentInstPtr)) { switch (*currentInstPtr) { case INST_JUMP1: case INST_JUMP_TRUE1: case INST_JUMP_FALSE1: targetInstPtr = currentInstPtr+TclGetInt1AtPtr(currentInstPtr+1); goto storeTarget; case INST_JUMP4: case INST_JUMP_TRUE4: case INST_JUMP_FALSE4: case INST_START_CMD: targetInstPtr = currentInstPtr+TclGetInt4AtPtr(currentInstPtr+1); goto storeTarget; case INST_BEGIN_CATCH4: targetInstPtr = envPtr->codeStart + envPtr->exceptArrayPtr[ TclGetUInt4AtPtr(currentInstPtr+1)].codeOffset; storeTarget: DefineTargetAddress(tablePtr, targetInstPtr); break; case INST_JUMP_TABLE: hPtr = Tcl_FirstHashEntry( &JUMPTABLEINFO(envPtr, currentInstPtr+1)->hashTable, &hSearch); for (; hPtr ; hPtr = Tcl_NextHashEntry(&hSearch)) { targetInstPtr = currentInstPtr + PTR2INT(Tcl_GetHashValue(hPtr)); DefineTargetAddress(tablePtr, targetInstPtr); } break; case INST_RETURN_CODE_BRANCH: for (i=TCL_ERROR ; iexceptArrayNext ; i++) { ExceptionRange *rangePtr = &envPtr->exceptArrayPtr[i]; if (rangePtr->type == CATCH_EXCEPTION_RANGE) { targetInstPtr = envPtr->codeStart + rangePtr->catchOffset; DefineTargetAddress(tablePtr, targetInstPtr); } else { targetInstPtr = envPtr->codeStart + rangePtr->breakOffset; DefineTargetAddress(tablePtr, targetInstPtr); if (rangePtr->continueOffset != TCL_INDEX_NONE) { targetInstPtr = envPtr->codeStart + rangePtr->continueOffset; DefineTargetAddress(tablePtr, targetInstPtr); } } } } /* * ---------------------------------------------------------------------- * * TrimUnreachable -- * * Converts code that provably can't be executed into NOPs and reduces * the overall reported length of the bytecode where that is possible. * * ---------------------------------------------------------------------- */ static void TrimUnreachable( CompileEnv *envPtr) { unsigned char *currentInstPtr; Tcl_HashTable targets; LocateTargetAddresses(envPtr, &targets); for (currentInstPtr = envPtr->codeStart ; currentInstPtr < envPtr->codeNext-1 ; currentInstPtr += AddrLength(currentInstPtr)) { int clear = 0; if (*currentInstPtr != INST_DONE) { continue; } while (!IsTargetAddress(&targets, currentInstPtr + 1 + clear)) { clear += AddrLength(currentInstPtr + 1 + clear); } if (currentInstPtr + 1 + clear == envPtr->codeNext) { envPtr->codeNext -= clear; } else { while (clear --> 0) { *(currentInstPtr + 1 + clear) = INST_NOP; } } } Tcl_DeleteHashTable(&targets); } /* * ---------------------------------------------------------------------- * * ConvertZeroEffectToNOP -- * * Replace PUSH/POP sequences (when non-hazardous) with NOPs. Also * replace PUSH empty/STR_CONCAT and TRY_CVT_NUMERIC (when followed by an * operation that guarantees the check for arithmeticity) and eliminate * LNOT when we can invert the following JUMP condition. * * ---------------------------------------------------------------------- */ static void ConvertZeroEffectToNOP( CompileEnv *envPtr) { unsigned char *currentInstPtr; int size; Tcl_HashTable targets; LocateTargetAddresses(envPtr, &targets); for (currentInstPtr = envPtr->codeStart ; currentInstPtr < envPtr->codeNext ; currentInstPtr += size) { int blank = 0, i, nextInst; size = AddrLength(currentInstPtr); while ((currentInstPtr + size < envPtr->codeNext) && currentInstPtr[size] == INST_NOP) { if (IsTargetAddress(&targets, currentInstPtr + size)) { break; } size += InstLength(INST_NOP); } if (IsTargetAddress(&targets, currentInstPtr + size)) { continue; } nextInst = currentInstPtr[size]; switch (*currentInstPtr) { case INST_PUSH1: if (nextInst == INST_POP) { blank = size + InstLength(nextInst); } else if (nextInst == INST_STR_CONCAT1 && TclGetUInt1AtPtr(currentInstPtr + size + 1) == 2) { Tcl_Obj *litPtr = TclFetchLiteral(envPtr, TclGetUInt1AtPtr(currentInstPtr + 1)); Tcl_Size numBytes; (void) TclGetStringFromObj(litPtr, &numBytes); if (numBytes == 0) { blank = size + InstLength(nextInst); } } break; case INST_PUSH4: if (nextInst == INST_POP) { blank = size + 1; } else if (nextInst == INST_STR_CONCAT1 && TclGetUInt1AtPtr(currentInstPtr + size + 1) == 2) { Tcl_Obj *litPtr = TclFetchLiteral(envPtr, TclGetUInt4AtPtr(currentInstPtr + 1)); Tcl_Size numBytes; (void) TclGetStringFromObj(litPtr, &numBytes); if (numBytes == 0) { blank = size + InstLength(nextInst); } } break; case INST_LNOT: switch (nextInst) { case INST_JUMP_TRUE1: blank = size; currentInstPtr[size] = INST_JUMP_FALSE1; break; case INST_JUMP_FALSE1: blank = size; currentInstPtr[size] = INST_JUMP_TRUE1; break; case INST_JUMP_TRUE4: blank = size; currentInstPtr[size] = INST_JUMP_FALSE4; break; case INST_JUMP_FALSE4: blank = size; currentInstPtr[size] = INST_JUMP_TRUE4; break; } break; case INST_TRY_CVT_TO_NUMERIC: switch (nextInst) { case INST_JUMP_TRUE1: case INST_JUMP_TRUE4: case INST_JUMP_FALSE1: case INST_JUMP_FALSE4: case INST_INCR_SCALAR1: case INST_INCR_ARRAY1: case INST_INCR_ARRAY_STK: case INST_INCR_SCALAR_STK: case INST_INCR_STK: case INST_EQ: case INST_NEQ: case INST_LT: case INST_LE: case INST_GT: case INST_GE: case INST_MOD: case INST_LSHIFT: case INST_RSHIFT: case INST_BITOR: case INST_BITXOR: case INST_BITAND: case INST_EXPON: case INST_ADD: case INST_SUB: case INST_DIV: case INST_MULT: case INST_LNOT: case INST_BITNOT: case INST_UMINUS: case INST_UPLUS: case INST_TRY_CVT_TO_NUMERIC: blank = size; break; } break; } if (blank > 0) { for (i=0 ; icodeStart ; currentInstPtr < envPtr->codeNext-1 ; currentInstPtr += AddrLength(currentInstPtr)) { int offset, delta, isNew; switch (*currentInstPtr) { case INST_JUMP1: case INST_JUMP_TRUE1: case INST_JUMP_FALSE1: offset = TclGetInt1AtPtr(currentInstPtr + 1); Tcl_InitHashTable(&jumps, TCL_ONE_WORD_KEYS); for (delta=0 ; offset+delta != 0 ;) { if (offset + delta < -128 || offset + delta > 127) { break; } Tcl_CreateHashEntry(&jumps, INT2PTR(offset), &isNew); if (!isNew) { offset = TclGetInt1AtPtr(currentInstPtr + 1); break; } offset += delta; switch (currentInstPtr[offset]) { case INST_NOP: delta = InstLength(INST_NOP); continue; case INST_JUMP1: delta = TclGetInt1AtPtr(currentInstPtr + offset + 1); continue; case INST_JUMP4: delta = TclGetInt4AtPtr(currentInstPtr + offset + 1); continue; } break; } Tcl_DeleteHashTable(&jumps); TclStoreInt1AtPtr(offset, currentInstPtr + 1); continue; case INST_JUMP4: case INST_JUMP_TRUE4: case INST_JUMP_FALSE4: Tcl_InitHashTable(&jumps, TCL_ONE_WORD_KEYS); Tcl_CreateHashEntry(&jumps, INT2PTR(0), &isNew); for (offset = TclGetInt4AtPtr(currentInstPtr + 1); offset!=0 ;) { Tcl_CreateHashEntry(&jumps, INT2PTR(offset), &isNew); if (!isNew) { offset = TclGetInt4AtPtr(currentInstPtr + 1); break; } switch (currentInstPtr[offset]) { case INST_NOP: offset += InstLength(INST_NOP); continue; case INST_JUMP1: offset += TclGetInt1AtPtr(currentInstPtr + offset + 1); continue; case INST_JUMP4: offset += TclGetInt4AtPtr(currentInstPtr + offset + 1); continue; } break; } Tcl_DeleteHashTable(&jumps); TclStoreInt4AtPtr(offset, currentInstPtr + 1); continue; } } } /* * ---------------------------------------------------------------------- * * TclOptimizeBytecode -- * * A very simple peephole optimizer for bytecode. * * ---------------------------------------------------------------------- */ void TclOptimizeBytecode( void *envPtr) { ConvertZeroEffectToNOP((CompileEnv *)envPtr); AdvanceJumps((CompileEnv *)envPtr); TrimUnreachable((CompileEnv *)envPtr); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * End: */ tcl9.0.1/generic/tclPanic.c0000644000175000017500000000573714726623136015107 0ustar sergeisergei/* * tclPanic.c -- * * Source code for the "Tcl_Panic" library procedure for Tcl; individual * applications will probably call Tcl_SetPanicProc() to set an * application-specific panic procedure. * * Copyright © 1988-1993 The Regents of the University of California. * Copyright © 1994 Sun Microsystems, Inc. * Copyright © 1998-1999 Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #if defined(_WIN32) || defined(__CYGWIN__) MODULE_SCOPE void tclWinDebugPanic(const char *format, ...); #endif /* * The panicProc variable contains a pointer to an application specific panic * procedure. */ static Tcl_PanicProc *panicProc = NULL; /* *---------------------------------------------------------------------- * * Tcl_SetPanicProc -- * * Replace the default panic behavior with the specified function. * * Results: * None. * * Side effects: * Sets the panicProc variable. * *---------------------------------------------------------------------- */ const char * Tcl_SetPanicProc( Tcl_PanicProc *proc) { panicProc = proc; return Tcl_InitSubsystems(); } /* *---------------------------------------------------------------------- * * Tcl_Panic -- * * Print an error message and kill the process. * * Results: * None. * * Side effects: * The process dies, entering the debugger if possible. * *---------------------------------------------------------------------- */ /* * The following comment is here so that Coverity's static analyzer knows that * a Tcl_Panic() call can never return and avoids lots of false positives. */ /* coverity[+kill] */ TCL_NORETURN void Tcl_Panic( const char *format, ...) { va_list argList; char *arg1, *arg2, *arg3; /* Additional arguments (variable in number) * to pass to fprintf. */ char *arg4, *arg5, *arg6, *arg7, *arg8; va_start(argList, format); arg1 = va_arg(argList, char *); arg2 = va_arg(argList, char *); arg3 = va_arg(argList, char *); arg4 = va_arg(argList, char *); arg5 = va_arg(argList, char *); arg6 = va_arg(argList, char *); arg7 = va_arg(argList, char *); arg8 = va_arg(argList, char *); va_end (argList); if (panicProc != NULL) { panicProc(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } else { #if defined(_WIN32) || defined(__CYGWIN__) tclWinDebugPanic(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); #else fprintf(stderr, format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); fprintf(stderr, "\n"); fflush(stderr); #endif } #if defined(__GNUC__) __builtin_trap(); #elif defined(_WIN64) __debugbreak(); #elif defined(_MSC_VER) && defined (_M_IX86) _asm {int 3} #elif defined(_WIN32) DebugBreak(); #endif #if defined(_WIN32) ExitProcess(1); #else abort(); #endif } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclParse.c0000644000175000017500000021600014726623136015112 0ustar sergeisergei/* * tclParse.c -- * * This file contains functions that parse Tcl scripts. They do so in a * general-purpose fashion that can be used for many different purposes, * including compilation, direct execution, code analysis, etc. * * Copyright © 1997 Sun Microsystems, Inc. * Copyright © 1998-2000 Ajuba Solutions. * Contributions from Don Porter, NIST, 2002. (not subject to US copyright) * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclParse.h" #include /* * The following table provides parsing information about each possible 8-bit * character. The table is designed to be referenced with unsigned characters. * * The macro CHAR_TYPE is used to index into the table and return information * about its character argument. The following return values are defined. * * TYPE_NORMAL - All characters that don't have special significance to * the Tcl parser. * TYPE_SPACE - The character is a whitespace character other than * newline. * TYPE_COMMAND_END - Character is newline or semicolon. * TYPE_SUBS - Character begins a substitution or has other special * meaning in ParseTokens: backslash, dollar sign, or * open bracket. * TYPE_QUOTE - Character is a double quote. * TYPE_OPEN_PAREN - Character is a left parenthesis. * TYPE_CLOSE_PAREN - Character is a right parenthesis. * TYPE_CLOSE_BRACK - Character is a right square bracket. * TYPE_BRACE - Character is a curly brace (either left or right). */ const unsigned char tclCharTypeTable[] = { /* * Positive character values, from 0-127: */ TYPE_SUBS, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_SPACE, TYPE_COMMAND_END, TYPE_SPACE, TYPE_SPACE, TYPE_SPACE, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_SPACE, TYPE_NORMAL, TYPE_QUOTE, TYPE_NORMAL, TYPE_SUBS, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_OPEN_PAREN, TYPE_CLOSE_PAREN, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_COMMAND_END, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_SUBS, TYPE_SUBS, TYPE_CLOSE_BRACK, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_BRACE, TYPE_NORMAL, TYPE_BRACE, TYPE_NORMAL, TYPE_NORMAL, /* * Large unsigned character values, from 128-255: */ TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, TYPE_NORMAL, }; /* * Prototypes for local functions defined in this file: */ static int CommandComplete(const char *script, Tcl_Size numBytes); static Tcl_Size ParseComment(const char *src, Tcl_Size numBytes, Tcl_Parse *parsePtr); static int ParseTokens(const char *src, Tcl_Size numBytes, int mask, int flags, Tcl_Parse *parsePtr); static Tcl_Size ParseWhiteSpace(const char *src, Tcl_Size numBytes, int *incompletePtr, char *typePtr); static Tcl_Size ParseAllWhiteSpace(const char *src, Tcl_Size numBytes, int *incompletePtr); static int ParseHex(const char *src, Tcl_Size numBytes, int *resultPtr); /* *---------------------------------------------------------------------- * * TclParseInit -- * * Initialize the fields of a Tcl_Parse struct. * * Results: * None. * * Side effects: * The Tcl_Parse struct pointed to by parsePtr gets initialized. * *---------------------------------------------------------------------- */ void TclParseInit( Tcl_Interp *interp, /* Interpreter to use for error reporting */ const char *start, /* Start of string to be parsed. */ Tcl_Size numBytes, /* Total number of bytes in string. If -1, * the script consists of all bytes up to the * first null character. */ Tcl_Parse *parsePtr) /* Points to struct to initialize */ { parsePtr->numWords = 0; parsePtr->tokenPtr = parsePtr->staticTokens; parsePtr->numTokens = 0; parsePtr->tokensAvailable = NUM_STATIC_TOKENS; parsePtr->string = start; parsePtr->end = start + numBytes; parsePtr->term = parsePtr->end; parsePtr->interp = interp; parsePtr->incomplete = 0; parsePtr->errorType = TCL_PARSE_SUCCESS; } /* *---------------------------------------------------------------------- * * Tcl_ParseCommand -- * * Given a string, this function parses the first Tcl command in the * string and returns information about the structure of the command. * * Results: * The return value is TCL_OK if the command was parsed successfully and * TCL_ERROR otherwise. If an error occurs and interp isn't NULL then an * error message is left in its result. On a successful return, parsePtr * is filled in with information about the command that was parsed. * * Side effects: * If there is insufficient space in parsePtr to hold all the information * about the command, then additional space is malloc-ed. If the function * returns TCL_OK then the caller must eventually invoke Tcl_FreeParse to * release any additional space that was allocated. * *---------------------------------------------------------------------- */ int Tcl_ParseCommand( Tcl_Interp *interp, /* Interpreter to use for error reporting; if * NULL, then no error message is provided. */ const char *start, /* First character of string containing one or * more Tcl commands. */ Tcl_Size numBytes, /* Total number of bytes in string. If -1, * the script consists of all bytes up to the * first null character. */ int nested, /* Non-zero means this is a nested command: * close bracket should be considered a * command terminator. If zero, then close * bracket has no special meaning. */ Tcl_Parse *parsePtr) /* Structure to fill in with information about * the parsed command; any previous * information in the structure is ignored. */ { const char *src; /* Points to current character in the * command. */ char type; /* Result returned by CHAR_TYPE(*src). */ Tcl_Token *tokenPtr; /* Pointer to token being filled in. */ Tcl_Size wordIndex; /* Index of word token for current word. */ int terminators; /* CHAR_TYPE bits that indicate the end of a * command. */ const char *termPtr; /* Set by Tcl_ParseBraces/QuotedString to * point to char after terminating one. */ Tcl_Size scanned; if (numBytes < 0 && start) { numBytes = strlen(start); } TclParseInit(interp, start, numBytes, parsePtr); if ((start == NULL) && (numBytes != 0)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "can't parse a NULL pointer", -1)); } return TCL_ERROR; } parsePtr->commentStart = NULL; parsePtr->commentSize = 0; parsePtr->commandStart = NULL; parsePtr->commandSize = 0; if (nested != 0) { terminators = TYPE_COMMAND_END | TYPE_CLOSE_BRACK; } else { terminators = TYPE_COMMAND_END; } /* * Parse any leading space and comments before the first word of the * command. */ scanned = ParseComment(start, numBytes, parsePtr); src = (start + scanned); numBytes -= scanned; if (numBytes == 0) { if (nested) { parsePtr->incomplete = nested; } } /* * The following loop parses the words of the command, one word in each * iteration through the loop. */ parsePtr->commandStart = src; type = CHAR_TYPE(*src); scanned = 1; /* Can't have missing whitepsace before first word. */ while (1) { int expandWord = 0; /* Are we at command termination? */ if ((numBytes == 0) || (type & terminators) != 0) { parsePtr->term = src; parsePtr->commandSize = src + (numBytes != 0) - parsePtr->commandStart; return TCL_OK; } /* Are we missing white space after previous word? */ if (scanned == 0) { if (src[-1] == '"') { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "extra characters after close-quote", -1)); } parsePtr->errorType = TCL_PARSE_QUOTE_EXTRA; } else { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "extra characters after close-brace", -1)); } parsePtr->errorType = TCL_PARSE_BRACE_EXTRA; } parsePtr->term = src; error: Tcl_FreeParse(parsePtr); parsePtr->commandSize = parsePtr->end - parsePtr->commandStart; return TCL_ERROR; } /* * Create the token for the word. */ TclGrowParseTokenArray(parsePtr, 1); wordIndex = parsePtr->numTokens; tokenPtr = &parsePtr->tokenPtr[wordIndex]; tokenPtr->type = TCL_TOKEN_WORD; tokenPtr->start = src; parsePtr->numTokens++; parsePtr->numWords++; /* * At this point the word can have one of four forms: something * enclosed in quotes, something enclosed in braces, and expanding * word, or an unquoted word (anything else). */ parseWord: if (*src == '"') { if (Tcl_ParseQuotedString(interp, src, numBytes, parsePtr, 1, &termPtr) != TCL_OK) { goto error; } src = termPtr; numBytes = parsePtr->end - src; } else if (*src == '{') { Tcl_Size expIdx = wordIndex + 1; Tcl_Token *expPtr; if (Tcl_ParseBraces(interp, src, numBytes, parsePtr, 1, &termPtr) != TCL_OK) { goto error; } src = termPtr; numBytes = parsePtr->end - src; /* * Check whether the braces contained the word expansion prefix * {*} */ expPtr = &parsePtr->tokenPtr[expIdx]; if ((0 == expandWord) /* Haven't seen prefix already */ && (expIdx + 1 == parsePtr->numTokens) /* Only one token */ && (((1 == expPtr->size) /* Same length as prefix */ && (expPtr->start[0] == '*'))) /* Is the prefix */ && (numBytes > 0) && (0 == ParseWhiteSpace(termPtr, numBytes, &parsePtr->incomplete, &type)) && (type != TYPE_COMMAND_END) /* Non-whitespace follows */) { expandWord = 1; parsePtr->numTokens--; goto parseWord; } } else { /* * This is an unquoted word. Call ParseTokens and let it do all of * the work. */ if (ParseTokens(src, numBytes, TYPE_SPACE|terminators, TCL_SUBST_ALL, parsePtr) != TCL_OK) { goto error; } src = parsePtr->term; numBytes = parsePtr->end - src; } /* * Finish filling in the token for the word and check for the special * case of a word consisting of a single range of literal text. */ tokenPtr = &parsePtr->tokenPtr[wordIndex]; tokenPtr->size = src - tokenPtr->start; tokenPtr->numComponents = parsePtr->numTokens - (wordIndex + 1); if (expandWord) { Tcl_Size i; int isLiteral = 1; /* * When a command includes a word that is an expanded literal; for * example, {*}{1 2 3}, the parser performs that expansion * immediately, generating several TCL_TOKEN_SIMPLE_WORDs instead * of a single TCL_TOKEN_EXPAND_WORD that the Tcl_ParseCommand() * caller might have to expand. This notably makes it simpler for * those callers that wish to track line endings, such as those * that implement key parts of TIP 280. * * First check whether the thing to be expanded is a literal, * in the sense of being composed entirely of TCL_TOKEN_TEXT * tokens. */ for (i = 1; i <= tokenPtr->numComponents; i++) { if (tokenPtr[i].type != TCL_TOKEN_TEXT) { isLiteral = 0; break; } } if (isLiteral) { Tcl_Size elemCount = 0; int code = TCL_OK, literal = 1; const char *nextElem, *listEnd, *elemStart; /* * The word to be expanded is a literal, so determine the * boundaries of the literal string to be treated as a list * and expanded. That literal string starts at * tokenPtr[1].start, and includes all bytes up to, but not * including (tokenPtr[tokenPtr->numComponents].start + * tokenPtr[tokenPtr->numComponents].size) */ listEnd = (tokenPtr[tokenPtr->numComponents].start + tokenPtr[tokenPtr->numComponents].size); nextElem = tokenPtr[1].start; /* * Step through the literal string, parsing and counting list * elements. */ while (nextElem < listEnd) { Tcl_Size size; code = TclFindElement(NULL, nextElem, listEnd - nextElem, &elemStart, &nextElem, &size, &literal); if ((code != TCL_OK) || !literal) { break; } if (elemStart < listEnd) { elemCount++; } } if ((code != TCL_OK) || !literal) { /* * Some list element could not be parsed, or is not * present as a literal substring of the script. The * compiler cannot handle list elements that get generated * by a call to TclCopyAndCollapse(). Defer the * handling of this to compile/eval time, where code is * already in place to report the "attempt to expand a * non-list" error or expand lists that require * substitution. */ tokenPtr->type = TCL_TOKEN_EXPAND_WORD; } else if (elemCount == 0) { /* * We are expanding a literal empty list. This means that * the expanding word completely disappears, leaving no * word generated this pass through the loop. Adjust * accounting appropriately. */ parsePtr->numWords--; parsePtr->numTokens = wordIndex; } else { /* * Recalculate the number of Tcl_Tokens needed to store * tokens representing the expanded list. */ const char *listStart; Tcl_Size growthNeeded = wordIndex + 2*elemCount - parsePtr->numTokens; parsePtr->numWords += elemCount - 1; if (growthNeeded > 0) { TclGrowParseTokenArray(parsePtr, growthNeeded); tokenPtr = &parsePtr->tokenPtr[wordIndex]; } parsePtr->numTokens = wordIndex + 2*elemCount; /* * Generate a TCL_TOKEN_SIMPLE_WORD token sequence for * each element of the literal list we are expanding in * place. Take care with the start and size fields of each * token so they point to the right literal characters in * the original script to represent the right expanded * word value. */ listStart = nextElem = tokenPtr[1].start; while (nextElem < listEnd) { int quoted; tokenPtr->type = TCL_TOKEN_SIMPLE_WORD; tokenPtr->numComponents = 1; tokenPtr++; tokenPtr->type = TCL_TOKEN_TEXT; tokenPtr->numComponents = 0; TclFindElement(NULL, nextElem, listEnd - nextElem, &(tokenPtr->start), &nextElem, &(tokenPtr->size), NULL); quoted = (tokenPtr->start[-1] == '{' || tokenPtr->start[-1] == '"') && tokenPtr->start > listStart; tokenPtr[-1].start = tokenPtr->start - quoted; tokenPtr[-1].size = tokenPtr->start + tokenPtr->size - tokenPtr[-1].start + quoted; tokenPtr++; } } } else { /* * The word to be expanded is not a literal, so defer * expansion to compile/eval time by marking with a * TCL_TOKEN_EXPAND_WORD token. */ tokenPtr->type = TCL_TOKEN_EXPAND_WORD; } } else if ((tokenPtr->numComponents == 1) && (tokenPtr[1].type == TCL_TOKEN_TEXT)) { tokenPtr->type = TCL_TOKEN_SIMPLE_WORD; } /* Parse the whitespace between words. */ scanned = ParseWhiteSpace(src,numBytes, &parsePtr->incomplete, &type); src += scanned; numBytes -= scanned; } } /* *---------------------------------------------------------------------- * * TclIsSpaceProc -- * * Report whether byte is in the set of whitespace characters used by * Tcl to separate words in scripts or elements in lists. * * Results: * Returns 1, if byte is in the set, 0 otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclIsSpaceProc( int byte) { return CHAR_TYPE(byte) & (TYPE_SPACE) || byte == '\n'; } /* *---------------------------------------------------------------------- * * TclIsBareword-- * * Report whether byte is one that can be part of a "bareword". * This concept is named in expression parsing, where it determines * what can be a legal function name, but is the same definition used * in determining what variable names can be parsed as variable * substitutions without the benefit of enclosing braces. The set of * ASCII chars that are accepted are the numeric chars ('0'-'9'), * the alphabetic chars ('a'-'z', 'A'-'Z') and underscore ('_'). * * Results: * Returns 1, if byte is in the accepted set of chars, 0 otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclIsBareword( int byte) { if (byte < '0' || byte > 'z') { return 0; } if (byte <= '9' || byte >= 'a') { return 1; } if (byte == '_') { return 1; } if (byte < 'A' || byte > 'Z') { return 0; } return 1; } /* *---------------------------------------------------------------------- * * ParseWhiteSpace -- * * Scans up to numBytes bytes starting at src, consuming white space * between words as defined by Tcl's parsing rules. * * Results: * Returns the number of bytes recognized as white space. Records at * parsePtr, information about the parse. Records at typePtr the * character type of the non-whitespace character that terminated the * scan. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_Size ParseWhiteSpace( const char *src, /* First character to parse. */ Tcl_Size numBytes, /* Max number of bytes to scan. */ int *incompletePtr, /* Set this boolean memory to true if parsing * indicates an incomplete command. */ char *typePtr) /* Points to location to store character type * of character that ends run of whitespace */ { char type = TYPE_NORMAL; const char *p = src; while (1) { while (numBytes && ((type = CHAR_TYPE(*p)) & TYPE_SPACE)) { numBytes--; p++; } if (numBytes && (type & TYPE_SUBS)) { if (*p != '\\') { break; } if (--numBytes == 0) { break; } if (p[1] != '\n') { break; } p += 2; if (--numBytes == 0) { *incompletePtr = 1; break; } continue; } break; } *typePtr = type; return (p - src); } /* *---------------------------------------------------------------------- * * TclParseAllWhiteSpace -- * * Scans up to numBytes bytes starting at src, consuming all white space * including the command-terminating newline characters. * * Results: * Returns the number of bytes recognized as white space. * *---------------------------------------------------------------------- */ static Tcl_Size ParseAllWhiteSpace( const char *src, /* First character to parse. */ Tcl_Size numBytes, /* Max number of byes to scan */ int *incompletePtr) /* Set true if parse is incomplete. */ { char type; const char *p = src; do { Tcl_Size scanned = ParseWhiteSpace(p, numBytes, incompletePtr, &type); p += scanned; numBytes -= scanned; } while (numBytes && (*p == '\n') && (p++, --numBytes)); return (p-src); } Tcl_Size TclParseAllWhiteSpace( const char *src, /* First character to parse. */ Tcl_Size numBytes) /* Max number of byes to scan */ { int dummy; return ParseAllWhiteSpace(src, numBytes, &dummy); } /* *---------------------------------------------------------------------- * * ParseHex -- * * Scans a hexadecimal number as a Tcl_UniChar value (e.g., for parsing * \x and \u escape sequences). At most numBytes bytes are scanned. * * Results: * The numeric value is stored in *resultPtr. Returns the number of bytes * consumed. * * Notes: * Relies on the following properties of the ASCII character set, with * which UTF-8 is compatible: * * The digits '0' .. '9' and the letters 'A' .. 'Z' and 'a' .. 'z' occupy * consecutive code points, and '0' < 'A' < 'a'. * *---------------------------------------------------------------------- */ int ParseHex( const char *src, /* First character to parse. */ Tcl_Size numBytes, /* Max number of byes to scan */ int *resultPtr) /* Points to storage provided by caller where * the character resulting from the * conversion is to be written. */ { int result = 0; const char *p = src; while (numBytes--) { unsigned char digit = UCHAR(*p); if (!isxdigit(digit) || (result > 0x10FFF)) { break; } p++; result <<= 4; if (digit >= 'a') { result |= (10 + digit - 'a'); } else if (digit >= 'A') { result |= (10 + digit - 'A'); } else { result |= (digit - '0'); } } *resultPtr = result; return (p - src); } /* *---------------------------------------------------------------------- * * TclParseBackslash -- * * Scans up to numBytes bytes starting at src, consuming a backslash * sequence as defined by Tcl's parsing rules. * * Results: * Records at readPtr the number of bytes making up the backslash * sequence. Records at dst the UTF-8 encoded equivalent of that * backslash sequence. Returns the number of bytes written to dst, at * most TCL_UTF_MAX. Either readPtr or dst may be NULL, if the results * are not needed, but the return value is the same either way. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclParseBackslash( const char *src, /* Points to the backslash character of a * backslash sequence. */ Tcl_Size numBytes, /* Max number of bytes to scan. */ Tcl_Size *readPtr, /* NULL, or points to storage where the number * of bytes scanned should be written. */ char *dst) /* NULL, or points to buffer where the UTF-8 * encoding of the backslash sequence is to be * written. At most 4 bytes will be written there. */ { const char *p = src+1; int unichar; int result; Tcl_Size count; char buf[4] = ""; if (numBytes == 0) { if (readPtr != NULL) { *readPtr = 0; } return 0; } if (dst == NULL) { dst = buf; } if (numBytes == 1) { /* * Can only scan the backslash, so return it. */ result = '\\'; count = 1; goto done; } count = 2; switch (*p) { /* * Note: in the conversions below, use absolute values (e.g., 0xA) * rather than symbolic values (e.g. \n) that get converted by the * compiler. It's possible that compilers on some platforms will do * the symbolic conversions differently, which could result in * non-portable Tcl scripts. */ case 'a': result = 0x7; break; case 'b': result = 0x8; break; case 'f': result = 0xC; break; case 'n': result = 0xA; break; case 'r': result = 0xD; break; case 't': result = 0x9; break; case 'v': result = 0xB; break; case 'x': count += ParseHex(p+1, (numBytes > 3) ? 2 : numBytes-2, &result); if (count == 2) { /* * No hexdigits -> This is just "x". */ result = 'x'; } else { /* * Keep only the last byte (2 hex digits). */ result = UCHAR(result); } break; case 'u': count += ParseHex(p+1, (numBytes > 5) ? 4 : numBytes-2, &result); if (count == 2) { /* * No hexdigits -> This is just "u". */ result = 'u'; } break; case 'U': count += ParseHex(p+1, (numBytes > 9) ? 8 : numBytes-2, &result); if (count == 2) { /* * No hexdigits -> This is just "U". */ result = 'U'; } break; case '\n': count--; do { p++; count++; } while ((count < numBytes) && ((*p == ' ') || (*p == '\t'))); result = ' '; break; case 0: result = '\\'; count = 1; break; default: /* * Check for an octal number \oo?o? */ if (isdigit(UCHAR(*p)) && (UCHAR(*p) < '8')) { /* INTL: digit */ result = *p - '0'; p++; if ((numBytes == 2) || !isdigit(UCHAR(*p)) /* INTL: digit */ || (UCHAR(*p) >= '8')) { break; } count = 3; result = (result << 3) + (*p - '0'); p++; if ((numBytes == 3) || !isdigit(UCHAR(*p)) /* INTL: digit */ || (UCHAR(*p) >= '8') || (result >= 0x20)) { break; } count = 4; result = UCHAR((result << 3) + (*p - '0')); break; } /* * We have to convert here in case the user has put a backslash in * front of a multi-byte utf-8 character. While this means nothing * special, we shouldn't break up a correct utf-8 character. [Bug * #217987] test subst-3.2 */ if (Tcl_UtfCharComplete(p, numBytes - 1)) { count = TclUtfToUniChar(p, &unichar) + 1; /* +1 for '\' */ } else { char utfBytes[8]; memcpy(utfBytes, p, numBytes - 1); utfBytes[numBytes - 1] = '\0'; count = TclUtfToUniChar(utfBytes, &unichar) + 1; } result = unichar; break; } done: if (readPtr != NULL) { *readPtr = count; } count = Tcl_UniCharToUtf(result, dst); return count; } /* *---------------------------------------------------------------------- * * ParseComment -- * * Scans up to numBytes bytes starting at src, consuming a Tcl comment as * defined by Tcl's parsing rules. * * Results: * Records in parsePtr information about the parse. Returns the number of * bytes consumed. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_Size ParseComment( const char *src, /* First character to parse. */ Tcl_Size numBytes, /* Max number of bytes to scan. */ Tcl_Parse *parsePtr) /* Information about parse in progress. * Updated if parsing indicates an incomplete * command. */ { const char *p = src; int incomplete = parsePtr->incomplete; while (numBytes) { Tcl_Size scanned = ParseAllWhiteSpace(p, numBytes, &incomplete); p += scanned; numBytes -= scanned; if ((numBytes == 0) || (*p != '#')) { break; } if (parsePtr->commentStart == NULL) { parsePtr->commentStart = p; } p++; numBytes--; while (numBytes) { if (*p == '\n') { p++; numBytes--; break; } if (*p == '\\') { p++; numBytes--; if (numBytes == 0) { break; } } incomplete = (*p == '\n'); p++; numBytes--; } parsePtr->commentSize = p - parsePtr->commentStart; } parsePtr->incomplete = incomplete; return (p - src); } /* *---------------------------------------------------------------------- * * ParseTokens -- * * This function forms the heart of the Tcl parser. It parses one or more * tokens from a string, up to a termination point specified by the * caller. This function is used to parse unquoted command words (those * not in quotes or braces), words in quotes, and array indices for * variables. No more than numBytes bytes will be scanned. * * Results: * Tokens are added to parsePtr and parsePtr->term is filled in with the * address of the character that terminated the parse (the first one * whose CHAR_TYPE matched mask or the character at parsePtr->end). The * return value is TCL_OK if the parse completed successfully and * TCL_ERROR otherwise. If a parse error occurs and parsePtr->interp is * not NULL, then an error message is left in the interpreter's result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int ParseTokens( const char *src, /* First character to parse. */ Tcl_Size numBytes, /* Max number of bytes to scan. */ int mask, /* Specifies when to stop parsing. The parse * stops at the first unquoted character whose * CHAR_TYPE contains any of the bits in * mask. */ int flags, /* OR-ed bits indicating what substitutions to * perform: TCL_SUBST_COMMANDS, * TCL_SUBST_VARIABLES, and * TCL_SUBST_BACKSLASHES */ Tcl_Parse *parsePtr) /* Information about parse in progress. * Updated with additional tokens and * termination information. */ { char type; Tcl_Size originalTokens; int noSubstCmds = !(flags & TCL_SUBST_COMMANDS); int noSubstVars = !(flags & TCL_SUBST_VARIABLES); int noSubstBS = !(flags & TCL_SUBST_BACKSLASHES); Tcl_Token *tokenPtr; /* * Each iteration through the following loop adds one token of type * TCL_TOKEN_TEXT, TCL_TOKEN_BS, TCL_TOKEN_COMMAND, or TCL_TOKEN_VARIABLE * to parsePtr. For TCL_TOKEN_VARIABLE tokens, additional tokens are added * for the parsed variable name. */ originalTokens = parsePtr->numTokens; while (numBytes && !((type = CHAR_TYPE(*src)) & mask)) { TclGrowParseTokenArray(parsePtr, 1); tokenPtr = &parsePtr->tokenPtr[parsePtr->numTokens]; tokenPtr->start = src; tokenPtr->numComponents = 0; if ((type & TYPE_SUBS) == 0) { /* * This is a simple range of characters. Scan to find the end of * the range. */ while ((++src, --numBytes) && !(CHAR_TYPE(*src) & (mask | TYPE_SUBS))) { /* empty loop */ } tokenPtr->type = TCL_TOKEN_TEXT; tokenPtr->size = src - tokenPtr->start; parsePtr->numTokens++; } else if (*src == '$') { Tcl_Size varToken; if (noSubstVars) { tokenPtr->type = TCL_TOKEN_TEXT; tokenPtr->size = 1; parsePtr->numTokens++; src++; numBytes--; continue; } /* * This is a variable reference. Call Tcl_ParseVarName to do all * the dirty work of parsing the name. */ varToken = parsePtr->numTokens; if (Tcl_ParseVarName(parsePtr->interp, src, numBytes, parsePtr, 1) != TCL_OK) { return TCL_ERROR; } src += parsePtr->tokenPtr[varToken].size; numBytes -= parsePtr->tokenPtr[varToken].size; } else if (*src == '[') { Tcl_Parse *nestedPtr; if (noSubstCmds) { tokenPtr->type = TCL_TOKEN_TEXT; tokenPtr->size = 1; parsePtr->numTokens++; src++; numBytes--; continue; } /* * Command substitution. Call Tcl_ParseCommand recursively (and * repeatedly) to parse the nested command(s), then throw away the * parse information. */ src++; numBytes--; nestedPtr = (Tcl_Parse *)TclStackAlloc(parsePtr->interp, sizeof(Tcl_Parse)); while (1) { const char *curEnd; if (Tcl_ParseCommand(parsePtr->interp, src, numBytes, 1, nestedPtr) != TCL_OK) { parsePtr->errorType = nestedPtr->errorType; parsePtr->term = nestedPtr->term; parsePtr->incomplete = nestedPtr->incomplete; TclStackFree(parsePtr->interp, nestedPtr); return TCL_ERROR; } curEnd = src + numBytes; src = nestedPtr->commandStart + nestedPtr->commandSize; numBytes = curEnd - src; Tcl_FreeParse(nestedPtr); /* * Check for the closing ']' that ends the command * substitution. It must have been the last character of the * parsed command. */ if ((nestedPtr->term < parsePtr->end) && (*(nestedPtr->term) == ']') && !(nestedPtr->incomplete)) { break; } if (numBytes == 0) { if (parsePtr->interp != NULL) { Tcl_SetObjResult(parsePtr->interp, Tcl_NewStringObj( "missing close-bracket", -1)); } parsePtr->errorType = TCL_PARSE_MISSING_BRACKET; parsePtr->term = tokenPtr->start; parsePtr->incomplete = 1; TclStackFree(parsePtr->interp, nestedPtr); return TCL_ERROR; } } TclStackFree(parsePtr->interp, nestedPtr); tokenPtr->type = TCL_TOKEN_COMMAND; tokenPtr->size = src - tokenPtr->start; parsePtr->numTokens++; } else if (*src == '\\') { if (noSubstBS) { tokenPtr->type = TCL_TOKEN_TEXT; tokenPtr->size = 1; parsePtr->numTokens++; src++; numBytes--; continue; } /* * Backslash substitution. */ TclParseBackslash(src, numBytes, &tokenPtr->size, NULL); if (tokenPtr->size == 1) { /* * Just a backslash, due to end of string. */ tokenPtr->type = TCL_TOKEN_TEXT; parsePtr->numTokens++; src++; numBytes--; continue; } if (src[1] == '\n') { if (numBytes == 2) { parsePtr->incomplete = 1; } /* * Note: backslash-newline is special in that it is treated * the same as a space character would be. This means that it * could terminate the token. */ if (mask & TYPE_SPACE) { if (parsePtr->numTokens == originalTokens) { goto finishToken; } break; } } tokenPtr->type = TCL_TOKEN_BS; parsePtr->numTokens++; src += tokenPtr->size; numBytes -= tokenPtr->size; } else if (*src == 0) { tokenPtr->type = TCL_TOKEN_TEXT; tokenPtr->size = 1; parsePtr->numTokens++; src++; numBytes--; } else { Tcl_Panic("ParseTokens encountered unknown character"); } } if (parsePtr->numTokens == originalTokens) { /* * There was nothing in this range of text. Add an empty token for the * empty range, so that there is always at least one token added. */ TclGrowParseTokenArray(parsePtr, 1); tokenPtr = &parsePtr->tokenPtr[parsePtr->numTokens]; tokenPtr->start = src; tokenPtr->numComponents = 0; finishToken: tokenPtr->type = TCL_TOKEN_TEXT; tokenPtr->size = 0; parsePtr->numTokens++; } parsePtr->term = src; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_FreeParse -- * * This function is invoked to free any dynamic storage that may have * been allocated by a previous call to Tcl_ParseCommand. * * Results: * None. * * Side effects: * If there is any dynamically allocated memory in *parsePtr, it is * freed. * *---------------------------------------------------------------------- */ void Tcl_FreeParse( Tcl_Parse *parsePtr) /* Structure that was filled in by a previous * call to Tcl_ParseCommand. */ { if (parsePtr->tokenPtr != parsePtr->staticTokens) { Tcl_Free(parsePtr->tokenPtr); parsePtr->tokenPtr = parsePtr->staticTokens; } } /* *---------------------------------------------------------------------- * * Tcl_ParseVarName -- * * Given a string starting with a $ sign, parse off a variable name and * return information about the parse. No more than numBytes bytes will * be scanned. * * Results: * The return value is TCL_OK if the command was parsed successfully and * TCL_ERROR otherwise. If an error occurs and interp isn't NULL then an * error message is left in its result. On a successful return, tokenPtr * and numTokens fields of parsePtr are filled in with information about * the variable name that was parsed. The "size" field of the first new * token gives the total number of bytes in the variable name. Other * fields in parsePtr are undefined. * * Side effects: * If there is insufficient space in parsePtr to hold all the information * about the command, then additional space is malloc-ed. If the function * returns TCL_OK then the caller must eventually invoke Tcl_FreeParse to * release any additional space that was allocated. * *---------------------------------------------------------------------- */ int Tcl_ParseVarName( Tcl_Interp *interp, /* Interpreter to use for error reporting; if * NULL, then no error message is provided. */ const char *start, /* Start of variable substitution string. * First character must be "$". */ Tcl_Size numBytes, /* Total number of bytes in string. If -1, * the string consists of all bytes up to the * first null character. */ Tcl_Parse *parsePtr, /* Structure to fill in with information about * the variable name. */ int append) /* Non-zero means append tokens to existing * information in parsePtr; zero means ignore * existing tokens in parsePtr and * reinitialize it. */ { Tcl_Token *tokenPtr; const char *src; int varIndex; unsigned array; if (numBytes < 0 && start) { numBytes = strlen(start); } if (!append) { TclParseInit(interp, start, numBytes, parsePtr); } if ((numBytes == 0) || (start == NULL)) { return TCL_ERROR; } /* * Generate one token for the variable, an additional token for the name, * plus any number of additional tokens for the index, if there is one. */ src = start; TclGrowParseTokenArray(parsePtr, 2); tokenPtr = &parsePtr->tokenPtr[parsePtr->numTokens]; tokenPtr->type = TCL_TOKEN_VARIABLE; tokenPtr->start = src; varIndex = parsePtr->numTokens; parsePtr->numTokens++; tokenPtr++; src++; numBytes--; if (numBytes == 0) { goto justADollarSign; } tokenPtr->type = TCL_TOKEN_TEXT; tokenPtr->start = src; tokenPtr->numComponents = 0; /* * The name of the variable can have three forms: * 1. The $ sign is followed by an open curly brace. Then the variable * name is everything up to the next close curly brace, and the * variable is a scalar variable. * 2. The $ sign is not followed by an open curly brace. Then the variable * name is everything up to the next character that isn't a letter, * digit, or underscore. :: sequences are also considered part of the * variable name, in order to support namespaces. If the following * character is an open parenthesis, then the information between * parentheses is the array element name. * 3. The $ sign is followed by something that isn't a letter, digit, or * underscore: in this case, there is no variable name and the token is * just "$". */ if (*src == '{') { char ch; int braceCount = 0; src++; numBytes--; tokenPtr->type = TCL_TOKEN_TEXT; tokenPtr->start = src; tokenPtr->numComponents = 0; ch = *src; while (numBytes && (braceCount>0 || ch != '}')) { switch (ch) { case '{': braceCount++; break; case '}': braceCount--; break; case '\\': /* if 2 or more left, consume 2, else consume * just the \ and let it run into the end */ if (numBytes > 1) { src++; numBytes--; } } numBytes--; src++; ch= *src; } if (numBytes == 0) { if (parsePtr->interp != NULL) { Tcl_SetObjResult(parsePtr->interp, Tcl_NewStringObj( "missing close-brace for variable name", -1)); } parsePtr->errorType = TCL_PARSE_MISSING_VAR_BRACE; parsePtr->term = tokenPtr->start-1; parsePtr->incomplete = 1; goto error; } tokenPtr->size = src - tokenPtr->start; tokenPtr[-1].size = src - tokenPtr[-1].start; parsePtr->numTokens++; src++; } else { tokenPtr->type = TCL_TOKEN_TEXT; tokenPtr->start = src; tokenPtr->numComponents = 0; while (numBytes) { if (TclIsBareword(*src)) { src += 1; numBytes -= 1; continue; } if ((src[0] == ':') && (numBytes != 1) && (src[1] == ':')) { src += 2; numBytes -= 2; while (numBytes && (*src == ':')) { src++; numBytes--; } continue; } break; } /* * Support for empty array names here. */ array = (numBytes && (*src == '(')); tokenPtr->size = src - tokenPtr->start; if ((tokenPtr->size == 0) && !array) { goto justADollarSign; } parsePtr->numTokens++; if (array) { /* * This is a reference to an array element. Call ParseTokens * recursively to parse the element name, since it could contain * any number of substitutions. */ if (TCL_OK != ParseTokens(src+1, numBytes-1, TYPE_BAD_ARRAY_INDEX, TCL_SUBST_ALL, parsePtr)) { goto error; } if (parsePtr->term == src+numBytes){ if (parsePtr->interp != NULL) { Tcl_SetObjResult(parsePtr->interp, Tcl_NewStringObj( "missing )", -1)); } parsePtr->errorType = TCL_PARSE_MISSING_PAREN; parsePtr->term = src; parsePtr->incomplete = 1; goto error; } else if ((*parsePtr->term != ')')){ if (parsePtr->interp != NULL) { Tcl_SetObjResult(parsePtr->interp, Tcl_NewStringObj( "invalid character in array index", -1)); } parsePtr->errorType = TCL_PARSE_SYNTAX; parsePtr->term = src; goto error; } src = parsePtr->term + 1; } } tokenPtr = &parsePtr->tokenPtr[varIndex]; tokenPtr->size = src - tokenPtr->start; tokenPtr->numComponents = parsePtr->numTokens - (varIndex + 1); return TCL_OK; /* * The dollar sign isn't followed by a variable name. Replace the * TCL_TOKEN_VARIABLE token with a TCL_TOKEN_TEXT token for the dollar * sign. */ justADollarSign: tokenPtr = &parsePtr->tokenPtr[varIndex]; tokenPtr->type = TCL_TOKEN_TEXT; tokenPtr->size = 1; tokenPtr->numComponents = 0; return TCL_OK; error: Tcl_FreeParse(parsePtr); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_ParseVar -- * * Given a string starting with a $ sign, parse off a variable name and * return its value. * * Results: * The return value is the contents of the variable given by the leading * characters of string. If termPtr isn't NULL, *termPtr gets filled in * with the address of the character just after the last one in the * variable specifier. If the variable doesn't exist, then the return * value is NULL and an error message will be left in interp's result. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * Tcl_ParseVar( Tcl_Interp *interp, /* Context for looking up variable. */ const char *start, /* Start of variable substitution. First * character must be "$". */ const char **termPtr) /* If non-NULL, points to word to fill in with * character just after last one in the * variable specifier. */ { Tcl_Obj *objPtr; int code; Tcl_Parse *parsePtr = (Tcl_Parse *)TclStackAlloc(interp, sizeof(Tcl_Parse)); if (Tcl_ParseVarName(interp, start, TCL_INDEX_NONE, parsePtr, 0) != TCL_OK) { TclStackFree(interp, parsePtr); return NULL; } if (termPtr != NULL) { *termPtr = start + parsePtr->tokenPtr->size; } if (parsePtr->numTokens == 1) { /* * There isn't a variable name after all: the $ is just a $. */ TclStackFree(interp, parsePtr); return "$"; } code = TclSubstTokens(interp, parsePtr->tokenPtr, parsePtr->numTokens, NULL, 1, NULL, NULL); Tcl_FreeParse(parsePtr); TclStackFree(interp, parsePtr); if (code != TCL_OK) { return NULL; } objPtr = Tcl_GetObjResult(interp); /* * At this point we should have an object containing the value of a * variable. Just return the string from that object. * * Since TclSubstTokens above returned TCL_OK, we know that objPtr * is shared. It is in both the interp result and the value of the * variable. Returning the string relies on that to be true. */ assert( Tcl_IsShared(objPtr) ); Tcl_ResetResult(interp); return TclGetString(objPtr); } /* *---------------------------------------------------------------------- * * Tcl_ParseBraces -- * * Given a string in braces such as a Tcl command argument or a string * value in a Tcl expression, this function parses the string and returns * information about the parse. No more than numBytes bytes will be * scanned. * * Results: * The return value is TCL_OK if the string was parsed successfully and * TCL_ERROR otherwise. If an error occurs and interp isn't NULL then an * error message is left in its result. On a successful return, tokenPtr * and numTokens fields of parsePtr are filled in with information about * the string that was parsed. Other fields in parsePtr are undefined. * termPtr is set to point to the character just after the last one in * the braced string. * * Side effects: * If there is insufficient space in parsePtr to hold all the information * about the command, then additional space is malloc-ed. If the function * returns TCL_OK then the caller must eventually invoke Tcl_FreeParse to * release any additional space that was allocated. * *---------------------------------------------------------------------- */ int Tcl_ParseBraces( Tcl_Interp *interp, /* Interpreter to use for error reporting; if * NULL, then no error message is provided. */ const char *start, /* Start of string enclosed in braces. The * first character must be {'. */ Tcl_Size numBytes, /* Total number of bytes in string. If -1, * the string consists of all bytes up to the * first null character. */ Tcl_Parse *parsePtr, /* Structure to fill in with information about * the string. */ int append, /* Non-zero means append tokens to existing * information in parsePtr; zero means ignore * existing tokens in parsePtr and * reinitialize it. */ const char **termPtr) /* If non-NULL, points to word in which to * store a pointer to the character just after * the terminating '}' if the parse was * successful. */ { Tcl_Token *tokenPtr; const char *src; Tcl_Size length, startIndex, level; if (numBytes < 0 && start) { numBytes = strlen(start); } if (!append) { TclParseInit(interp, start, numBytes, parsePtr); } if ((numBytes == 0) || (start == NULL)) { return TCL_ERROR; } src = start; startIndex = parsePtr->numTokens; TclGrowParseTokenArray(parsePtr, 1); tokenPtr = &parsePtr->tokenPtr[startIndex]; tokenPtr->type = TCL_TOKEN_TEXT; tokenPtr->start = src+1; tokenPtr->numComponents = 0; level = 1; while (1) { while (++src, --numBytes) { if (CHAR_TYPE(*src) != TYPE_NORMAL) { break; } } if (numBytes == 0) { goto missingBraceError; } switch (*src) { case '{': level++; break; case '}': if (--level == 0) { /* * Decide if we need to finish emitting a partially-finished * token. There are 3 cases: * {abc \newline xyz} or {xyz} * - finish emitting "xyz" token * {abc \newline} * - don't emit token after \newline * {} - finish emitting zero-sized token * * The last case ensures that there is a token (even if empty) * that describes the braced string. */ if ((src != tokenPtr->start) || (parsePtr->numTokens == startIndex)) { tokenPtr->size = (src - tokenPtr->start); parsePtr->numTokens++; } if (termPtr != NULL) { *termPtr = src+1; } return TCL_OK; } break; case '\\': TclParseBackslash(src, numBytes, &length, NULL); if ((length > 1) && (src[1] == '\n')) { /* * A backslash-newline sequence must be collapsed, even inside * braces, so we have to split the word into multiple tokens * so that the backslash-newline can be represented * explicitly. */ if (numBytes == 2) { parsePtr->incomplete = 1; } tokenPtr->size = (src - tokenPtr->start); if (tokenPtr->size != 0) { parsePtr->numTokens++; } TclGrowParseTokenArray(parsePtr, 2); tokenPtr = &parsePtr->tokenPtr[parsePtr->numTokens]; tokenPtr->type = TCL_TOKEN_BS; tokenPtr->start = src; tokenPtr->size = length; tokenPtr->numComponents = 0; parsePtr->numTokens++; src += length - 1; numBytes -= length - 1; tokenPtr++; tokenPtr->type = TCL_TOKEN_TEXT; tokenPtr->start = src + 1; tokenPtr->numComponents = 0; } else { src += length - 1; numBytes -= length - 1; } break; } } missingBraceError: parsePtr->errorType = TCL_PARSE_MISSING_BRACE; parsePtr->term = start; parsePtr->incomplete = 1; if (parsePtr->interp == NULL) { /* * Skip straight to the exit code since we have no interpreter to put * error message in. */ goto error; } Tcl_SetObjResult(parsePtr->interp, Tcl_NewStringObj( "missing close-brace", -1)); /* * Guess if the problem is due to comments by searching the source string * for a possible open brace within the context of a comment. Since we * aren't performing a full Tcl parse, just look for an open brace * preceded by a '#' on the same line. */ { int openBrace = 0; while (--src > start) { switch (*src) { case '{': openBrace = 1; break; case '\n': openBrace = 0; break; case '#' : if (openBrace && TclIsSpaceProcM(src[-1])) { Tcl_AppendToObj(Tcl_GetObjResult(parsePtr->interp), ": possible unbalanced brace in comment", -1); goto error; } break; } } } error: Tcl_FreeParse(parsePtr); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_ParseQuotedString -- * * Given a double-quoted string such as a quoted Tcl command argument or * a quoted value in a Tcl expression, this function parses the string * and returns information about the parse. No more than numBytes bytes * will be scanned. * * Results: * The return value is TCL_OK if the string was parsed successfully and * TCL_ERROR otherwise. If an error occurs and interp isn't NULL then an * error message is left in its result. On a successful return, tokenPtr * and numTokens fields of parsePtr are filled in with information about * the string that was parsed. Other fields in parsePtr are undefined. * termPtr is set to point to the character just after the quoted * string's terminating close-quote. * * Side effects: * If there is insufficient space in parsePtr to hold all the information * about the command, then additional space is malloc-ed. If the function * returns TCL_OK then the caller must eventually invoke Tcl_FreeParse to * release any additional space that was allocated. * *---------------------------------------------------------------------- */ int Tcl_ParseQuotedString( Tcl_Interp *interp, /* Interpreter to use for error reporting; if * NULL, then no error message is provided. */ const char *start, /* Start of the quoted string. The first * character must be '"'. */ Tcl_Size numBytes, /* Total number of bytes in string. If -1, * the string consists of all bytes up to the * first null character. */ Tcl_Parse *parsePtr, /* Structure to fill in with information about * the string. */ int append, /* Non-zero means append tokens to existing * information in parsePtr; zero means ignore * existing tokens in parsePtr and * reinitialize it. */ const char **termPtr) /* If non-NULL, points to word in which to * store a pointer to the character just after * the quoted string's terminating close-quote * if the parse succeeds. */ { if (numBytes < 0 && start) { numBytes = strlen(start); } if (!append) { TclParseInit(interp, start, numBytes, parsePtr); } if ((numBytes == 0) || (start == NULL)) { return TCL_ERROR; } if (TCL_OK != ParseTokens(start+1, numBytes-1, TYPE_QUOTE, TCL_SUBST_ALL, parsePtr)) { goto error; } if (*parsePtr->term != '"') { if (parsePtr->interp != NULL) { Tcl_SetObjResult(parsePtr->interp, Tcl_NewStringObj( "missing \"", -1)); } parsePtr->errorType = TCL_PARSE_MISSING_QUOTE; parsePtr->term = start; parsePtr->incomplete = 1; goto error; } if (termPtr != NULL) { *termPtr = (parsePtr->term + 1); } return TCL_OK; error: Tcl_FreeParse(parsePtr); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TclSubstParse -- * * Token parser used by the [subst] command. Parses the string made up of * 'numBytes' bytes starting at 'bytes'. Parsing is controlled by the * flags argument to provide support for the -nobackslashes, -nocommands, * and -novariables options, as represented by the flag values * TCL_SUBST_BACKSLASHES, TCL_SUBST_COMMANDS, TCL_SUBST_VARIABLES. * * Results: * None. * * Side effects: * The Tcl_Parse struct '*parsePtr' is filled with parse results. * The caller is expected to eventually call Tcl_FreeParse() to properly * cleanup the value written there. * * If a parse error occurs, the Tcl_InterpState value '*statePtr' is * filled with the state created by that error. When *statePtr is written * to, the caller is expected to make the required calls to either * Tcl_RestoreInterpState() or Tcl_DiscardInterpState() to dispose of the * value written there. * *---------------------------------------------------------------------- */ void TclSubstParse( Tcl_Interp *interp, const char *bytes, Tcl_Size numBytes, int flags, Tcl_Parse *parsePtr, Tcl_InterpState *statePtr) { Tcl_Size length = numBytes; const char *p = bytes; TclParseInit(interp, p, length, parsePtr); /* * First parse the string rep of objPtr, as if it were enclosed as a * "-quoted word in a normal Tcl command. Honor flags that selectively * inhibit types of substitution. */ if (TCL_OK != ParseTokens(p, length, /* mask */ 0, flags, parsePtr)) { /* * There was a parse error. Save the interpreter state for possible * error reporting later. */ *statePtr = Tcl_SaveInterpState(interp, TCL_ERROR); /* * We need to re-parse to get the portion of the string we can [subst] * before the parse error. Sadly, all the Tcl_Token's created by the * first parse attempt are gone, freed according to the public spec * for the Tcl_Parse* routines. The only clue we have is parse.term, * which points to either the unmatched opener, or to characters that * follow a close brace or close quote. * * Call ParseTokens again, working on the string up to parse.term. * Keep repeating until we get a good parse on a prefix. */ do { parsePtr->numTokens = 0; parsePtr->tokensAvailable = NUM_STATIC_TOKENS; parsePtr->end = parsePtr->term; parsePtr->incomplete = 0; parsePtr->errorType = TCL_PARSE_SUCCESS; } while (TCL_OK != ParseTokens(p, parsePtr->end - p, 0, flags, parsePtr)); /* * The good parse will have to be followed by {, (, or [. */ switch (*(parsePtr->term)) { case '{': /* * Parse error was a missing } in a ${varname} variable * substitution at the toplevel. We will subst everything up to * that broken variable substitution before reporting the parse * error. Substituting the leftover '$' will have no side-effects, * so the current token stream is fine. */ break; case '(': /* * Parse error was during the parsing of the index part of an * array variable substitution at the toplevel. */ if (*(parsePtr->term - 1) == '$') { /* * Special case where removing the array index left us with * just a dollar sign (array variable with name the empty * string as its name), instead of with a scalar variable * reference. * * As in the previous case, existing token stream is OK. */ } else { /* * The current parse includes a successful parse of a scalar * variable substitution where there should have been an array * variable substitution. We remove that mistaken part of the * parse before moving on. A scalar variable substitution is * two tokens. */ Tcl_Token *varTokenPtr = parsePtr->tokenPtr + parsePtr->numTokens - 2; if (varTokenPtr->type != TCL_TOKEN_VARIABLE) { Tcl_Panic("TclSubstParse: programming error"); } if (varTokenPtr[1].type != TCL_TOKEN_TEXT) { Tcl_Panic("TclSubstParse: programming error"); } parsePtr->numTokens -= 2; } break; case '[': /* * Parse error occurred during parsing of a toplevel command * substitution. */ parsePtr->end = p + length; p = parsePtr->term + 1; length = parsePtr->end - p; if (length == 0) { /* * No commands, just an unmatched [. As in previous cases, * existing token stream is OK. */ } else { /* * We want to add the parsing of as many commands as we can * within that substitution until we reach the actual parse * error. We'll do additional parsing to determine what length * to claim for the final TCL_TOKEN_COMMAND token. */ Tcl_Token *tokenPtr; const char *lastTerm = parsePtr->term; Tcl_Parse *nestedPtr = (Tcl_Parse *) TclStackAlloc(interp, sizeof(Tcl_Parse)); while (TCL_OK == Tcl_ParseCommand(NULL, p, length, 0, nestedPtr)) { Tcl_FreeParse(nestedPtr); p = nestedPtr->term + (nestedPtr->term < nestedPtr->end); length = nestedPtr->end - p; if ((length == 0) && (nestedPtr->term == nestedPtr->end)) { /* * If we run out of string, blame the missing close * bracket on the last command, and do not evaluate it * during substitution. */ break; } lastTerm = nestedPtr->term; } TclStackFree(interp, nestedPtr); if (lastTerm == parsePtr->term) { /* * Parse error in first command. No commands to subst, add * no more tokens. */ break; } /* * Create a command substitution token for whatever commands * got parsed. */ TclGrowParseTokenArray(parsePtr, 1); tokenPtr = &(parsePtr->tokenPtr[parsePtr->numTokens]); tokenPtr->start = parsePtr->term; tokenPtr->numComponents = 0; tokenPtr->type = TCL_TOKEN_COMMAND; tokenPtr->size = lastTerm - tokenPtr->start + 1; parsePtr->numTokens++; } break; default: Tcl_Panic("bad parse in TclSubstParse: %c", p[length]); } } } /* *---------------------------------------------------------------------- * * TclSubstTokens -- * * Accepts an array of count Tcl_Token's, and creates a result value in * the interp from concatenating the results of performing Tcl * substitution on each Tcl_Token. Substitution is interrupted if any * non-TCL_OK completion code arises. * * Results: * The return value is a standard Tcl completion code. The result in * interp is the substituted value, or an error message if TCL_ERROR is * returned. If tokensLeftPtr is not NULL, then it points to an int where * the number of tokens remaining to be processed is written. * * Side effects: * Can be anything, depending on the types of substitution done. * *---------------------------------------------------------------------- */ int TclSubstTokens( Tcl_Interp *interp, /* Interpreter in which to lookup variables, * execute nested commands, and report * errors. */ Tcl_Token *tokenPtr, /* Pointer to first in an array of tokens to * evaluate and concatenate. */ Tcl_Size count, /* Number of tokens to consider at tokenPtr. * Must be at least 1. */ int *tokensLeftPtr, /* If not NULL, points to memory where an * integer representing the number of tokens * left to be substituted will be written */ Tcl_Size line, /* The line the script starts on. */ Tcl_Size *clNextOuter, /* Information about an outer context for */ const char *outerScript) /* continuation line data. This is set by * EvalEx() to properly handle [...]-nested * commands. The 'outerScript' refers to the * most-outer script containing the embedded * command, which is refered to by 'script'. * The 'clNextOuter' refers to the current * entry in the table of continuation lines in * this "main script", and the character * offsets are relative to the 'outerScript' * as well. * * If outerScript == script, then this call is * for words in the outer-most script or * command. See Tcl_EvalEx and TclEvalObjEx * for the places generating arguments for * which this is true. */ { Tcl_Obj *result; int code = TCL_OK; #define NUM_STATIC_POS 20 int isLiteral; Tcl_Size i, maxNumCL, numCL, adjust; Tcl_Size *clPosition = NULL; Interp *iPtr = (Interp *) interp; int inFile = iPtr->evalFlags & TCL_EVAL_FILE; /* * Each pass through this loop will substitute one token, and its * components, if any. The only thing tricky here is that we go to some * effort to pass Tcl_Obj's through untouched, to avoid string copying and * Tcl_Obj creation if possible, to aid performance and limit shimmering. * * Further optimization opportunities might be to check for the equivalent * of Tcl_SetObjResult(interp, Tcl_GetObjResult(interp)) and omit them. */ /* * For the handling of continuation lines in literals, first check if * this is actually a literal. If not then forego the additional * processing. Otherwise preallocate a small table to store the * locations of all continuation lines we find in this literal, if any. * The table is extended if needed. */ numCL = 0; maxNumCL = 0; isLiteral = 1; for (i=0 ; i < count; i++) { if ((tokenPtr[i].type != TCL_TOKEN_TEXT) && (tokenPtr[i].type != TCL_TOKEN_BS)) { isLiteral = 0; break; } } if (isLiteral) { maxNumCL = NUM_STATIC_POS; clPosition = (Tcl_Size *)Tcl_Alloc(maxNumCL * sizeof(Tcl_Size)); } adjust = 0; result = NULL; for (; count>0 && code==TCL_OK ; count--, tokenPtr++) { Tcl_Obj *appendObj = NULL; const char *append = NULL; int appendByteLength = 0; char utfCharBytes[4] = ""; switch (tokenPtr->type) { case TCL_TOKEN_TEXT: append = tokenPtr->start; appendByteLength = tokenPtr->size; break; case TCL_TOKEN_BS: appendByteLength = TclParseBackslash(tokenPtr->start, tokenPtr->size, NULL, utfCharBytes); append = utfCharBytes; /* * If the backslash sequence we found is in a literal, and * represented a continuation line, we compute and store its * location (as char offset to the beginning of the _result_ * script). We may have to extend the table of locations. * * Note that the continuation line information is relevant even if * the word we are processing is not a literal, as it can affect * nested commands. See the branch for TCL_TOKEN_COMMAND below, * where the adjustment we are tracking here is taken into * account. The good thing is that we do not need a table of * everything, just the number of lines we have to add as * correction. */ if ((appendByteLength == 1) && (utfCharBytes[0] == ' ') && (tokenPtr->start[1] == '\n')) { if (isLiteral) { Tcl_Size clPos; if (result == 0) { clPos = 0; } else { (void)TclGetStringFromObj(result, &clPos); } if (numCL >= maxNumCL) { maxNumCL *= 2; clPosition = (Tcl_Size *)Tcl_Realloc(clPosition, maxNumCL * sizeof(Tcl_Size)); } clPosition[numCL] = clPos; numCL++; } adjust++; } break; case TCL_TOKEN_COMMAND: { /* TIP #280: Transfer line information to nested command */ iPtr->numLevels++; code = TclInterpReady(interp); if (code == TCL_OK) { /* * Test cases: info-30.{6,8,9} */ Tcl_Size theline; TclAdvanceContinuations(&line, &clNextOuter, tokenPtr->start - outerScript); theline = line + adjust; code = TclEvalEx(interp, tokenPtr->start+1, tokenPtr->size-2, 0, theline, clNextOuter, outerScript); TclAdvanceLines(&line, tokenPtr->start+1, tokenPtr->start + tokenPtr->size - 1); /* * Restore flag reset by nested eval for future bracketed * commands and their cmdframe setup */ if (inFile) { iPtr->evalFlags |= TCL_EVAL_FILE; } } iPtr->numLevels--; TclResetCancellation(interp, 0); appendObj = Tcl_GetObjResult(interp); break; } case TCL_TOKEN_VARIABLE: { Tcl_Obj *arrayIndex = NULL; Tcl_Obj *varName = NULL; if (tokenPtr->numComponents > 1) { /* * Subst the index part of an array variable reference. */ code = TclSubstTokens(interp, tokenPtr+2, tokenPtr->numComponents - 1, NULL, line, NULL, NULL); arrayIndex = Tcl_GetObjResult(interp); Tcl_IncrRefCount(arrayIndex); } if (code == TCL_OK) { varName = Tcl_NewStringObj(tokenPtr[1].start, tokenPtr[1].size); appendObj = Tcl_ObjGetVar2(interp, varName, arrayIndex, TCL_LEAVE_ERR_MSG); Tcl_DecrRefCount(varName); if (appendObj == NULL) { code = TCL_ERROR; } } switch (code) { case TCL_OK: /* Got value */ case TCL_ERROR: /* Already have error message */ case TCL_BREAK: /* Will not substitute anyway */ case TCL_CONTINUE: /* Will not substitute anyway */ break; default: /* * All other return codes, we will subst the result from the * code-throwing evaluation. */ appendObj = Tcl_GetObjResult(interp); } if (arrayIndex != NULL) { Tcl_DecrRefCount(arrayIndex); } count -= tokenPtr->numComponents; tokenPtr += tokenPtr->numComponents; break; } default: Tcl_Panic("unexpected token type in TclSubstTokens: %d", tokenPtr->type); } if ((code == TCL_BREAK) || (code == TCL_CONTINUE)) { /* * Inhibit substitution. */ continue; } if (result == NULL) { /* * First pass through. If we have a Tcl_Obj, just use it. If not, * create one from our string. */ if (appendObj != NULL) { result = appendObj; } else { result = Tcl_NewStringObj(append, appendByteLength); } Tcl_IncrRefCount(result); } else { /* * Subsequent passes. Append to result. */ if (Tcl_IsShared(result)) { Tcl_DecrRefCount(result); result = Tcl_DuplicateObj(result); Tcl_IncrRefCount(result); } if (appendObj != NULL) { Tcl_AppendObjToObj(result, appendObj); } else { Tcl_AppendToObj(result, append, appendByteLength); } } } if (code != TCL_ERROR) { /* Keep error message in result! */ if (result != NULL) { Tcl_SetObjResult(interp, result); /* * If the code found continuation lines (which implies that this * word is a literal), then we store the accumulated table of * locations in the thread-global data structure for the bytecode * compiler to find later, assuming that the literal is a script * which will be compiled. */ if (numCL) { TclContinuationsEnter(result, numCL, clPosition); } /* * Release the temp table we used to collect the locations of * continuation lines, if any. */ if (maxNumCL) { Tcl_Free(clPosition); } } else { Tcl_ResetResult(interp); } } if (tokensLeftPtr != NULL) { *tokensLeftPtr = count; } if (result != NULL) { Tcl_DecrRefCount(result); } return code; } /* *---------------------------------------------------------------------- * * CommandComplete -- * * This function is shared by TclCommandComplete and * Tcl_ObjCommandComplete; it does all the real work of seeing whether a * script is complete * * Results: * 1 is returned if the script is complete, 0 if there are open * delimiters such as " or (. 1 is also returned if there is a parse * error in the script other than unmatched delimiters. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int CommandComplete( const char *script, /* Script to check. */ Tcl_Size numBytes) /* Number of bytes in script. */ { Tcl_Parse parse; const char *p, *end; int result; p = script; end = p + numBytes; while (Tcl_ParseCommand(NULL, p, end - p, 0, &parse) == TCL_OK) { p = parse.commandStart + parse.commandSize; if (p >= end) { break; } Tcl_FreeParse(&parse); } if (parse.incomplete) { result = 0; } else { result = 1; } Tcl_FreeParse(&parse); return result; } /* *---------------------------------------------------------------------- * * Tcl_CommandComplete -- * * Given a partial or complete Tcl script, this function determines * whether the script is complete in the sense of having matched braces * and quotes and brackets. * * Results: * 1 is returned if the script is complete, 0 otherwise. 1 is also * returned if there is a parse error in the script other than unmatched * delimiters. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_CommandComplete( const char *script) /* Script to check. */ { return CommandComplete(script, strlen(script)); } /* *---------------------------------------------------------------------- * * TclObjCommandComplete -- * * Given a partial or complete Tcl command in a Tcl object, this function * determines whether the command is complete in the sense of having * matched braces and quotes and brackets. * * Results: * 1 is returned if the command is complete, 0 otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclObjCommandComplete( Tcl_Obj *objPtr) /* Points to object holding script to * check. */ { Tcl_Size length; const char *script = TclGetStringFromObj(objPtr, &length); return CommandComplete(script, length); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclParse.h0000644000175000017500000000113114726623136015114 0ustar sergeisergei/* * Minimal set of shared flag definitions and declarations so that multiple * source files can make use of the parsing table in tclParse.c */ enum ParseTypeFlags { TYPE_NORMAL = 0, TYPE_SPACE = 0x1, TYPE_COMMAND_END = 0x2, TYPE_SUBS = 0x4, TYPE_QUOTE = 0x8, TYPE_CLOSE_PAREN = 0x10, TYPE_CLOSE_BRACK = 0x20, TYPE_BRACE = 0x40, TYPE_OPEN_PAREN = 0x80, TYPE_BAD_ARRAY_INDEX = ( TYPE_OPEN_PAREN | TYPE_CLOSE_PAREN | TYPE_QUOTE | TYPE_BRACE) }; #define CHAR_TYPE(c) tclCharTypeTable[(unsigned char)(c)] MODULE_SCOPE const unsigned char tclCharTypeTable[]; tcl9.0.1/generic/tclPathObj.c0000644000175000017500000022362014726623136015375 0ustar sergeisergei/* * tclPathObj.c -- * * This file contains the implementation of Tcl's "path" object type used * to represent and manipulate a general (virtual) filesystem entity in * an efficient manner. * * Copyright © 2003 Vince Darley. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclFileSystem.h" #include /* * Prototypes for functions defined later in this file. */ static Tcl_Obj * AppendPath(Tcl_Obj *head, Tcl_Obj *tail); static void DupFsPathInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr); static void FreeFsPathInternalRep(Tcl_Obj *pathPtr); static void UpdateStringOfFsPath(Tcl_Obj *pathPtr); static int SetFsPathFromAny(Tcl_Interp *interp, Tcl_Obj *pathPtr); static Tcl_Size FindSplitPos(const char *path, int separator); static int IsSeparatorOrNull(int ch); static Tcl_Obj * GetExtension(Tcl_Obj *pathPtr); static int MakePathFromNormalized(Tcl_Interp *interp, Tcl_Obj *pathPtr); static int MakeTildeRelativePath(Tcl_Interp *interp, const char *user, const char *subPath, Tcl_DString *dsPtr); /* * Define the 'path' object type, which Tcl uses to represent file paths * internally. */ static const Tcl_ObjType fsPathType = { "path", /* name */ FreeFsPathInternalRep, /* freeIntRepProc */ DupFsPathInternalRep, /* dupIntRepProc */ UpdateStringOfFsPath, /* updateStringProc */ SetFsPathFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; /* * struct FsPath -- * * Internal representation of a Tcl_Obj of fsPathType */ typedef struct { Tcl_Obj *translatedPathPtr; /* If the path has been normalized (flags == * 0), this is NULL. Otherwise it is a path * in which any ~user sequences have been * translated away. */ Tcl_Obj *normPathPtr; /* If the path has been normalized (flags == * 0), this is an absolute path without ., .. * or ~user components. Otherwise it is a * path, possibly absolute, to normalize * relative to cwdPtr. */ Tcl_Obj *cwdPtr; /* If NULL, either translatedPtr exists or * normPathPtr exists and is absolute. */ int flags; /* Flags to describe interpretation - see * below. */ void *nativePathPtr; /* Native representation of this path, which * is filesystem dependent. */ size_t filesystemEpoch; /* Used to ensure the path representation was * generated during the correct filesystem * epoch. The epoch changes when * filesystem-mounts are changed. */ const Tcl_Filesystem *fsPtr;/* The Tcl_Filesystem that claims this path */ } FsPath; /* * Flag values for FsPath->flags. */ #define TCLPATH_APPENDED 1 #define TCLPATH_NEEDNORM 4 /* * Define some macros to give us convenient access to path-object specific * fields. */ #define PATHOBJ(pathPtr) ((FsPath *) (TclFetchInternalRep((pathPtr), &fsPathType)->twoPtrValue.ptr1)) #define SETPATHOBJ(pathPtr,fsPathPtr) \ do { \ Tcl_ObjInternalRep ir; \ ir.twoPtrValue.ptr1 = (void *) (fsPathPtr); \ ir.twoPtrValue.ptr2 = NULL; \ Tcl_StoreInternalRep((pathPtr), &fsPathType, &ir); \ } while (0) #define PATHFLAGS(pathPtr) (PATHOBJ(pathPtr)->flags) /* *--------------------------------------------------------------------------- * * TclFSNormalizeAbsolutePath -- * * Takes an absolute path specification and computes a 'normalized' path * from it. * * A normalized path is one which has all '../', './' removed. Also it is * one which is in the 'standard' format for the native platform. On * Unix, this means the path must be free of symbolic links/aliases, and * on Windows it means we want the long form, with that long form's * case-dependence (which gives us a unique, case-dependent path). * * The behaviour of this function if passed a non-absolute path is NOT * defined. * * pathPtr may have a refCount of zero, or may be a shared object. * * Results: * The result is returned in a Tcl_Obj with a refCount already * incremented, which gives the caller ownership of it. The caller must * arrange for Tcl_DecRefCount to be called when the object is no-longer * needed. * * Side effects: * None (beyond the memory allocation for the result). * * Special note: * Originally based on code from Matt Newman and Jean-Claude Wippler. * Totally rewritten later by Vince Darley to handle symbolic links. * *--------------------------------------------------------------------------- */ Tcl_Obj * TclFSNormalizeAbsolutePath( Tcl_Interp *interp, /* Interpreter to use */ Tcl_Obj *pathPtr) /* Absolute path to normalize */ { const char *dirSep, *oldDirSep; int first = 1; /* Set to zero once we've passed the first * directory separator - we can't use '..' to * remove the volume in a path. */ Tcl_Obj *retVal = NULL; int zipVolumeLen; dirSep = TclGetString(pathPtr); zipVolumeLen = TclIsZipfsPath(dirSep); if (zipVolumeLen) { /* * NOTE: file normalization for zipfs is very specific to * format of zipfs volume being of the form //xxx:/ */ dirSep += zipVolumeLen-1; /* Start parse after : */ } else if (tclPlatform == TCL_PLATFORM_WINDOWS) { if ( (dirSep[0] == '/' || dirSep[0] == '\\') && (dirSep[1] == '/' || dirSep[1] == '\\') && (dirSep[2] == '?') && (dirSep[3] == '/' || dirSep[3] == '\\')) { /* NT extended path */ dirSep += 4; if ( (dirSep[0] == 'U' || dirSep[0] == 'u') && (dirSep[1] == 'N' || dirSep[1] == 'n') && (dirSep[2] == 'C' || dirSep[2] == 'c') && (dirSep[3] == '/' || dirSep[3] == '\\')) { /* NT extended UNC path */ dirSep += 4; } } if (dirSep[0] != 0 && dirSep[1] == ':' && (dirSep[2] == '/' || dirSep[2] == '\\')) { /* Do nothing */ } else if ((dirSep[0] == '/' || dirSep[0] == '\\') && (dirSep[1] == '/' || dirSep[1] == '\\')) { /* * UNC style path, where we must skip over the first separator, * since the first two segments are actually inseparable. */ dirSep += 2; dirSep += FindSplitPos(dirSep, '/'); if (*dirSep != 0) { dirSep++; } } } /* * Scan forward from one directory separator to the next, checking for * '..' and '.' sequences which must be handled specially. In particular * handling of '..' can be complicated if the directory before is a link, * since we will have to expand the link to be able to back up one level. */ while (*dirSep != 0) { oldDirSep = dirSep; if (!first) { dirSep++; } dirSep += FindSplitPos(dirSep, '/'); if (dirSep[0] == 0 || dirSep[1] == 0) { if (retVal != NULL) { Tcl_AppendToObj(retVal, oldDirSep, dirSep - oldDirSep); } break; } if (dirSep[1] == '.') { if (retVal != NULL) { Tcl_AppendToObj(retVal, oldDirSep, dirSep - oldDirSep); oldDirSep = dirSep; } again: if (IsSeparatorOrNull(dirSep[2])) { /* * Need to skip '.' in the path. */ Tcl_Size curLen; if (retVal == NULL) { const char *path = TclGetString(pathPtr); retVal = Tcl_NewStringObj(path, dirSep - path); Tcl_IncrRefCount(retVal); } (void)TclGetStringFromObj(retVal, &curLen); if (curLen == 0) { Tcl_AppendToObj(retVal, dirSep, 1); } dirSep += 2; oldDirSep = dirSep; if (dirSep[0] != 0 && dirSep[1] == '.') { goto again; } continue; } if (dirSep[2] == '.' && IsSeparatorOrNull(dirSep[3])) { Tcl_Obj *linkObj; Tcl_Size curLen; char *linkStr; /* * Have '..' so need to skip previous directory. */ if (retVal == NULL) { const char *path = TclGetString(pathPtr); retVal = Tcl_NewStringObj(path, dirSep - path); Tcl_IncrRefCount(retVal); } (void)TclGetStringFromObj(retVal, &curLen); if (curLen == 0) { Tcl_AppendToObj(retVal, dirSep, 1); } if (!first || (tclPlatform == TCL_PLATFORM_UNIX)) { if (zipVolumeLen) { linkObj = NULL; } else { linkObj = Tcl_FSLink(retVal, NULL, 0); /* Safety check in case driver caused sharing */ if (Tcl_IsShared(retVal)) { TclDecrRefCount(retVal); retVal = Tcl_DuplicateObj(retVal); Tcl_IncrRefCount(retVal); } } if (linkObj != NULL) { /* * Got a link. Need to check if the link is relative * or absolute, for those platforms where relative * links exist. */ if (tclPlatform != TCL_PLATFORM_WINDOWS && Tcl_FSGetPathType(linkObj) == TCL_PATH_RELATIVE) { /* * We need to follow this link which is relative * to retVal's directory. This means concatenating * the link onto the directory of the path so far. */ const char *path = TclGetStringFromObj(retVal, &curLen); while (curLen-- > 0) { if (IsSeparatorOrNull(path[curLen])) { break; } } /* * We want the trailing slash. */ Tcl_SetObjLength(retVal, curLen+1); Tcl_AppendObjToObj(retVal, linkObj); TclDecrRefCount(linkObj); linkStr = TclGetStringFromObj(retVal, &curLen); } else { /* * Absolute link. */ TclDecrRefCount(retVal); if (Tcl_IsShared(linkObj)) { retVal = Tcl_DuplicateObj(linkObj); TclDecrRefCount(linkObj); } else { retVal = linkObj; } linkStr = TclGetStringFromObj(retVal, &curLen); /* * Convert to forward-slashes on windows. */ if (tclPlatform == TCL_PLATFORM_WINDOWS) { Tcl_Size i; for (i = 0; i < curLen; i++) { if (linkStr[i] == '\\') { linkStr[i] = '/'; } } } } } else { linkStr = TclGetStringFromObj(retVal, &curLen); } /* * Either way, we now remove the last path element (but * not the first character of the path). In the case of * zipfs, make sure not to go beyond the zipfs volume. */ int minLen = zipVolumeLen ? zipVolumeLen - 1 : 0; while (--curLen >= minLen) { if (IsSeparatorOrNull(linkStr[curLen])) { if (curLen) { Tcl_SetObjLength(retVal, curLen); } else { Tcl_SetObjLength(retVal, 1); } break; } } } dirSep += 3; oldDirSep = dirSep; if ((curLen == 0) && (dirSep[0] != 0)) { Tcl_SetObjLength(retVal, 0); } if (dirSep[0] != 0 && dirSep[1] == '.') { goto again; } continue; } } first = 0; if (retVal != NULL) { Tcl_AppendToObj(retVal, oldDirSep, dirSep - oldDirSep); } } /* * If we didn't make any changes, just use the input path. */ if (retVal == NULL) { retVal = pathPtr; Tcl_IncrRefCount(retVal); if (Tcl_IsShared(retVal)) { /* * Unfortunately, the platform-specific normalization code which * will be called below has no way of dealing with the case where * an object is shared. It is expecting to modify an object in * place. So, we must duplicate this here to ensure an object with * a single ref-count. * * If that changes in the future (e.g. the normalize proc is given * one object and is able to return a different one), then we * could remove this code. */ TclDecrRefCount(retVal); retVal = Tcl_DuplicateObj(pathPtr); Tcl_IncrRefCount(retVal); } } /* * Ensure a windows drive like C:/ has a trailing separator. * Likewise for zipfs volumes. */ if (zipVolumeLen || (tclPlatform == TCL_PLATFORM_WINDOWS)) { int needTrailingSlash = 0; Tcl_Size len; const char *path = TclGetStringFromObj(retVal, &len); if (zipVolumeLen) { if (len == (zipVolumeLen - 1)) { needTrailingSlash = 1; } } else { if (len == 2 && path[0] != 0 && path[1] == ':') { needTrailingSlash = 1; } } if (needTrailingSlash) { if (Tcl_IsShared(retVal)) { TclDecrRefCount(retVal); retVal = Tcl_DuplicateObj(retVal); Tcl_IncrRefCount(retVal); } Tcl_AppendToObj(retVal, "/", 1); } } /* * Now we have an absolute path, with no '..', '.' sequences, but it still * may not be in 'unique' form, depending on the platform. For instance, * Unix is case-sensitive, so the path is ok. Windows is case-insensitive, * and also has the weird 'longname/shortname' thing (e.g. C:/Program * Files/ and C:/Progra~1/ are equivalent). * * Virtual file systems which may be registered may have other criteria * for normalizing a path. */ TclFSNormalizeToUniquePath(interp, retVal, 0); /* * Since we know it is a normalized path, we can actually convert this * object into an FsPath for greater efficiency */ MakePathFromNormalized(interp, retVal); /* * This has a refCount of 1 for the caller, unlike many Tcl_Obj APIs. */ return retVal; } /* *---------------------------------------------------------------------- * * Tcl_FSGetPathType -- * * Determines whether a given path is relative to the current directory, * relative to the current volume, or absolute. * * Results: * Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or * TCL_PATH_VOLUME_RELATIVE. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_PathType Tcl_FSGetPathType( Tcl_Obj *pathPtr) { return TclFSGetPathType(pathPtr, NULL, NULL); } /* *---------------------------------------------------------------------- * * TclFSGetPathType -- * * Determines whether a given path is relative to the current directory, * relative to the current volume, or absolute. If the caller wishes to * know which filesystem claimed the path (in the case for which the path * is absolute), then a reference to a filesystem pointer can be passed * in (but passing NULL is acceptable). * * Results: * Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or * TCL_PATH_VOLUME_RELATIVE. The filesystem reference will be set if and * only if it is non-NULL and the function's return value is * TCL_PATH_ABSOLUTE. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_PathType TclFSGetPathType( Tcl_Obj *pathPtr, const Tcl_Filesystem **filesystemPtrPtr, Tcl_Size *driveNameLengthPtr) { FsPath *fsPathPtr; if (Tcl_FSConvertToPathType(NULL, pathPtr) != TCL_OK) { return TclGetPathType(pathPtr, filesystemPtrPtr, driveNameLengthPtr, NULL); } fsPathPtr = PATHOBJ(pathPtr); if (fsPathPtr->cwdPtr == NULL) { return TclGetPathType(pathPtr, filesystemPtrPtr, driveNameLengthPtr, NULL); } if (PATHFLAGS(pathPtr) == 0) { /* The path is not absolute... */ #ifdef _WIN32 /* ... on Windows we must make another call to determine whether * it's relative or volumerelative [Bug 2571597]. */ return TclGetPathType(pathPtr, filesystemPtrPtr, driveNameLengthPtr, NULL); #else /* On other systems, quickly deduce !absolute -> relative */ return TCL_PATH_RELATIVE; #endif } return TclFSGetPathType(fsPathPtr->cwdPtr, filesystemPtrPtr, driveNameLengthPtr); } /* *--------------------------------------------------------------------------- * * TclPathPart * * This function calculates the requested part of the given path, which * can be: * * - the directory above ('file dirname') * - the tail ('file tail') * - the extension ('file extension') * - the root ('file root') * * The 'portion' parameter dictates which of these to calculate. There * are a number of special cases both to be more efficient, and because * the behaviour when given a path with only a single element is defined * to require the expansion of that single element, where possible. * * Should look into integrating 'FileBasename' in tclFCmd.c into this * function. * * Results: * NULL if an error occurred, otherwise a Tcl_Obj owned by the caller * (i.e. most likely with refCount 1). * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Obj * TclPathPart( TCL_UNUSED(Tcl_Interp *), /* Used for error reporting */ Tcl_Obj *pathPtr, /* Path to take dirname of */ Tcl_PathPart portion) /* Requested portion of name */ { if (TclHasInternalRep(pathPtr, &fsPathType)) { FsPath *fsPathPtr = PATHOBJ(pathPtr); if (PATHFLAGS(pathPtr) != 0) { switch (portion) { case TCL_PATH_DIRNAME: { /* * Check if the joined-on bit has any directory delimiters in * it. If so, the 'dirname' would be a joining of the main * part with the dirname of the joined-on bit. We could handle * that special case here, but we don't, and instead just use * the standardPath code. */ Tcl_Size numBytes; const char *rest = TclGetStringFromObj(fsPathPtr->normPathPtr, &numBytes); if (strchr(rest, '/') != NULL) { goto standardPath; } /* * If the joined-on bit is empty, then [file dirname] is * documented to return all but the last non-empty element * of the path, so we need to split apart the main part to * get the right answer. We could do that here, but it's * simpler to fall back to the standardPath code. * [Bug 2710920] */ if (numBytes == 0) { goto standardPath; } if (tclPlatform == TCL_PLATFORM_WINDOWS && strchr(rest, '\\') != NULL) { goto standardPath; } /* * The joined-on path is simple, so we can just return here. */ Tcl_IncrRefCount(fsPathPtr->cwdPtr); return fsPathPtr->cwdPtr; } case TCL_PATH_TAIL: { /* * Check if the joined-on bit has any directory delimiters in * it. If so, the 'tail' would be only the part following the * last delimiter. We could handle that special case here, but * we don't, and instead just use the standardPath code. */ Tcl_Size numBytes; const char *rest = TclGetStringFromObj(fsPathPtr->normPathPtr, &numBytes); if (strchr(rest, '/') != NULL) { goto standardPath; } /* * If the joined-on bit is empty, then [file tail] is * documented to return the last non-empty element * of the path, so we need to split off the last element * of the main part to get the right answer. We could do * that here, but it's simpler to fall back to the * standardPath code. [Bug 2710920] */ if (numBytes == 0) { goto standardPath; } if (tclPlatform == TCL_PLATFORM_WINDOWS && strchr(rest, '\\') != NULL) { goto standardPath; } Tcl_IncrRefCount(fsPathPtr->normPathPtr); return fsPathPtr->normPathPtr; } case TCL_PATH_EXTENSION: return GetExtension(fsPathPtr->normPathPtr); case TCL_PATH_ROOT: { const char *fileName, *extension; Tcl_Size length; fileName = TclGetStringFromObj(fsPathPtr->normPathPtr, &length); extension = TclGetExtension(fileName); if (extension == NULL) { /* * There is no extension so the root is the same as the * path we were given. */ Tcl_IncrRefCount(pathPtr); return pathPtr; } else { /* * Need to return the whole path with the extension * suffix removed. Do that by joining our "head" to * our "tail" with the extension suffix removed from * the tail. */ Tcl_Obj *resultPtr = TclNewFSPathObj(fsPathPtr->cwdPtr, fileName, length - strlen(extension)); Tcl_IncrRefCount(resultPtr); return resultPtr; } } default: /* We should never get here */ Tcl_Panic("Bad portion to TclPathPart"); /* For less clever compilers */ return NULL; } } else if (fsPathPtr->cwdPtr != NULL) { /* Relative path */ goto standardPath; } else { /* Absolute path */ goto standardPath; } } else { Tcl_Size splitElements; Tcl_Obj *splitPtr, *resultPtr; standardPath: resultPtr = NULL; if (portion == TCL_PATH_EXTENSION) { return GetExtension(pathPtr); } else if (portion == TCL_PATH_ROOT) { Tcl_Size length; const char *fileName, *extension; fileName = TclGetStringFromObj(pathPtr, &length); extension = TclGetExtension(fileName); if (extension == NULL) { Tcl_IncrRefCount(pathPtr); return pathPtr; } else { Tcl_Obj *root = Tcl_NewStringObj(fileName, length - strlen(extension)); Tcl_IncrRefCount(root); return root; } } /* * Tcl_FSSplitPath in the handling of home directories; * Tcl_FSSplitPath preserves the "~", but this code computes the * actual full path name, if we had just a single component. */ splitPtr = Tcl_FSSplitPath(pathPtr, &splitElements); Tcl_IncrRefCount(splitPtr); if (portion == TCL_PATH_TAIL) { /* * Return the last component, unless it is the only component, and * it is the root of an absolute path. */ if ((splitElements > 0) && ((splitElements > 1) || (Tcl_FSGetPathType(pathPtr) == TCL_PATH_RELATIVE))) { Tcl_ListObjIndex(NULL, splitPtr, splitElements-1, &resultPtr); } else { TclNewObj(resultPtr); } } else { /* * Return all but the last component. If there is only one * component, return it if the path was non-relative, otherwise * return the current directory. */ if (splitElements > 1) { resultPtr = Tcl_FSJoinPath(splitPtr, splitElements - 1); } else if (splitElements == 0 || (Tcl_FSGetPathType(pathPtr) == TCL_PATH_RELATIVE)) { TclNewLiteralStringObj(resultPtr, "."); } else { Tcl_ListObjIndex(NULL, splitPtr, 0, &resultPtr); } } Tcl_IncrRefCount(resultPtr); TclDecrRefCount(splitPtr); return resultPtr; } } /* * Simple helper function */ static Tcl_Obj * GetExtension( Tcl_Obj *pathPtr) { const char *tail, *extension; Tcl_Obj *ret; tail = TclGetString(pathPtr); extension = TclGetExtension(tail); if (extension == NULL) { TclNewObj(ret); } else { ret = Tcl_NewStringObj(extension, -1); } Tcl_IncrRefCount(ret); return ret; } /* *--------------------------------------------------------------------------- * * Tcl_FSJoinPath -- * * This function takes the given Tcl_Obj, which should be a valid list, * and returns the path object given by considering the first 'elements' * elements as valid path segments (each path segment may be a complete * path, a partial path or just a single possible directory or file * name). If any path segment is actually an absolute path, then all * prior path segments are discarded. * * If elements < 0, we use the entire list that was given. * * It is possible that the returned object is actually an element of the * given list, so the caller should be careful to store a refCount to it * before freeing the list. * * Results: * Returns object with refCount of zero, (or if non-zero, it has * references elsewhere in Tcl). Either way, the caller must increment * its refCount before use. Note that in the case where the caller has * asked to join zero elements of the list, the return value will be an * empty-string Tcl_Obj. * * If the given listObj was invalid, then the calling routine has a bug, * and this function will just return NULL. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSJoinPath( Tcl_Obj *listObj, /* Path elements to join, may have a zero * reference count. */ Tcl_Size elements) /* Number of elements to use (-1 = all) */ { Tcl_Obj *res; Tcl_Size objc; Tcl_Obj **objv; if (TclListObjLength(NULL, listObj, &objc) != TCL_OK) { return NULL; } elements = ((elements >= 0) && (elements <= objc)) ? elements : objc; TclListObjGetElements(NULL, listObj, &objc, &objv); res = TclJoinPath(elements, objv, 0); return res; } Tcl_Obj * TclJoinPath( Tcl_Size elements, /* Number of elements to use */ Tcl_Obj * const objv[], /* Path elements to join */ int forceRelative) /* If non-zero, assume all more paths are * relative (e.g. simple normalization) */ { Tcl_Obj *res = NULL; Tcl_Size i; const Tcl_Filesystem *fsPtr = NULL; if (elements == 0) { TclNewObj(res); return res; } assert ( elements > 0 ); if (elements == 2) { Tcl_Obj *elt = objv[0]; Tcl_ObjInternalRep *eltIr = TclFetchInternalRep(elt, &fsPathType); /* * This is a special case where we can be much more efficient, where * we are joining a single relative path onto an object that is * already of path type. The 'TclNewFSPathObj' call below creates an * object which can be normalized more efficiently. Currently we only * use the special case when we have exactly two elements, but we * could expand that in the future. * * Bugfix [a47641a0]. TclNewFSPathObj requires first argument * to be an absolute path. Added a check to ensure that elt is absolute. */ if ((eltIr) && !((elt->bytes != NULL) && (elt->bytes[0] == '\0')) && TclGetPathType(elt, NULL, NULL, NULL) == TCL_PATH_ABSOLUTE) { Tcl_Obj *tailObj = objv[1]; Tcl_PathType type; /* if forceRelative - second path is relative */ type = forceRelative ? TCL_PATH_RELATIVE : TclGetPathType(tailObj, NULL, NULL, NULL); if (type == TCL_PATH_RELATIVE) { const char *str; Tcl_Size len; str = TclGetStringFromObj(tailObj, &len); if (len == 0) { /* * This happens if we try to handle the root volume '/'. * There's no need to return a special path object, when * the base itself is just fine! */ return elt; } /* * If it doesn't begin with '.' and is a Unix path or it a * windows path without backslashes, then we can be very * efficient here. (In fact even a windows path with * backslashes can be joined efficiently, but the path object * would not have forward slashes only, and this would * therefore contradict our 'file join' documentation). */ if (str[0] != '.' && ((tclPlatform != TCL_PLATFORM_WINDOWS) || (strchr(str, '\\') == NULL))) { /* * Finally, on Windows, 'file join' is defined to convert * all backslashes to forward slashes, so the base part * cannot have backslashes either. */ if ((tclPlatform != TCL_PLATFORM_WINDOWS) || (strchr(TclGetString(elt), '\\') == NULL)) { if (PATHFLAGS(elt)) { return TclNewFSPathObj(elt, str, len); } if (TCL_PATH_ABSOLUTE != Tcl_FSGetPathType(elt)) { return TclNewFSPathObj(elt, str, len); } (void) Tcl_FSGetNormalizedPath(NULL, elt); if (elt == PATHOBJ(elt)->normPathPtr) { return TclNewFSPathObj(elt, str, len); } } } /* * Otherwise we don't have an easy join, and we must let the * more general code below handle things. */ } else if (tclPlatform == TCL_PLATFORM_UNIX) { return tailObj; } else { const char *str = TclGetString(tailObj); if (tclPlatform == TCL_PLATFORM_WINDOWS) { if (strchr(str, '\\') == NULL) { return tailObj; } } } } } assert ( res == NULL ); for (i = 0; i < elements; i++) { Tcl_Size driveNameLength; Tcl_Size strEltLen, length; Tcl_PathType type; char *strElt, *ptr; Tcl_Obj *driveName = NULL; Tcl_Obj *elt = objv[i]; strElt = TclGetStringFromObj(elt, &strEltLen); driveNameLength = 0; /* if forceRelative - all paths excepting first one are relative */ type = (forceRelative && (i > 0)) ? TCL_PATH_RELATIVE : TclGetPathType(elt, &fsPtr, &driveNameLength, &driveName); if (type != TCL_PATH_RELATIVE) { /* * Zero out the current result. */ if (res != NULL) { TclDecrRefCount(res); } if (driveName != NULL) { /* * We've been given a separate drive-name object, because the * prefix in 'elt' is not in a suitable format for us (e.g. it * may contain irrelevant multiple separators, like * C://///foo). */ res = Tcl_DuplicateObj(driveName); TclDecrRefCount(driveName); /* * Do not set driveName to NULL, because we will check its * value below (but we won't access the contents, since those * have been cleaned-up). */ } else { res = Tcl_NewStringObj(strElt, driveNameLength); } strElt += driveNameLength; } else if (driveName != NULL) { Tcl_DecrRefCount(driveName); } /* * Optimisation block: if this is the last element to be examined, and * it is absolute or the only element, and the drive-prefix was ok (if * there is one), it might be that the path is already in a suitable * form to be returned. Then we can short-cut the rest of this * function. */ if ((driveName == NULL) && (i == (elements - 1)) && (type != TCL_PATH_RELATIVE || res == NULL)) { /* * It's the last path segment. Perform a quick check if the path * is already in a suitable form. */ if (tclPlatform == TCL_PLATFORM_WINDOWS) { if (strchr(strElt, '\\') != NULL) { goto noQuickReturn; } } ptr = strElt; /* [Bug f34cf83dd0] */ if (driveNameLength > 0) { if (ptr[0] == '/' && ptr[-1] == '/') { goto noQuickReturn; } } while (*ptr != '\0') { if (*ptr == '/' && (ptr[1] == '/' || ptr[1] == '\0')) { /* * We have a repeated file separator, which means the path * is not in normalized form */ goto noQuickReturn; } ptr++; } if (res != NULL) { TclDecrRefCount(res); } /* * This element is just what we want to return already; no further * manipulation is requred. */ return elt; } /* * The path element was not of a suitable form to be returned as is. * We need to perform a more complex operation here. */ noQuickReturn: if (res == NULL) { TclNewObj(res); } ptr = TclGetStringFromObj(res, &length); /* * A NULL value for fsPtr at this stage basically means we're trying * to join a relative path onto something which is also relative (or * empty). There's nothing particularly wrong with that. */ if (*strElt == '\0') { continue; } if (fsPtr == &tclNativeFilesystem || fsPtr == NULL) { TclpNativeJoinPath(res, strElt); } else { char separator = '/'; int needsSep = 0; if (fsPtr->filesystemSeparatorProc != NULL) { Tcl_Obj *sep = fsPtr->filesystemSeparatorProc(res); if (sep != NULL) { separator = TclGetString(sep)[0]; TclDecrRefCount(sep); } /* Safety check in case the VFS driver caused sharing */ if (Tcl_IsShared(res)) { TclDecrRefCount(res); res = Tcl_DuplicateObj(res); Tcl_IncrRefCount(res); } } if (length > 0 && ptr[length -1] != '/') { Tcl_AppendToObj(res, &separator, 1); (void)TclGetStringFromObj(res, &length); } Tcl_SetObjLength(res, length + strlen(strElt)); ptr = TclGetString(res) + length; for (; *strElt != '\0'; strElt++) { if (*strElt == separator) { while (strElt[1] == separator) { strElt++; } if (strElt[1] != '\0') { if (needsSep) { *ptr++ = separator; } } } else { *ptr++ = *strElt; needsSep = 1; } } length = ptr - TclGetString(res); Tcl_SetObjLength(res, length); } } assert ( res != NULL ); return res; } /* *--------------------------------------------------------------------------- * * Tcl_FSConvertToPathType -- * * This function tries to convert the given Tcl_Obj to a valid Tcl path * type, taking account of the fact that the cwd may have changed even if * this object is already supposedly of the correct type. * * The filename may begin with "~" (to indicate current user's home * directory) or "~" (to indicate any user's home directory). * * Results: * Standard Tcl error code. * * Side effects: * The old representation may be freed, and new memory allocated. * *--------------------------------------------------------------------------- */ int Tcl_FSConvertToPathType( Tcl_Interp *interp, /* Interpreter in which to store error message * (if necessary). */ Tcl_Obj *pathPtr) /* Object to convert to a valid, current path * type. */ { /* * While it is bad practice to examine an object's type directly, this is * actually the best thing to do here. The reason is that if we are * converting this object to FsPath type for the first time, we don't need * to worry whether the 'cwd' has changed. On the other hand, if this * object is already of FsPath type, and is a relative path, we do have to * worry about the cwd. If the cwd has changed, we must recompute the * path. */ if (TclHasInternalRep(pathPtr, &fsPathType)) { if (TclFSEpochOk(PATHOBJ(pathPtr)->filesystemEpoch)) { return TCL_OK; } TclGetString(pathPtr); Tcl_StoreInternalRep(pathPtr, &fsPathType, NULL); } return SetFsPathFromAny(interp, pathPtr); } /* * Helper function for normalization. */ static int IsSeparatorOrNull( int ch) { if (ch == 0) { return 1; } switch (tclPlatform) { case TCL_PLATFORM_UNIX: return (ch == '/' ? 1 : 0); case TCL_PLATFORM_WINDOWS: return ((ch == '/' || ch == '\\') ? 1 : 0); } return 0; } /* * Helper function for SetFsPathFromAny. Returns position of first directory * delimiter in the path. If no separator is found, then returns the position * of the end of the string. */ static Tcl_Size FindSplitPos( const char *path, int separator) { int count = 0; switch (tclPlatform) { case TCL_PLATFORM_UNIX: while (path[count] != 0) { if (path[count] == separator) { return count; } count++; } break; case TCL_PLATFORM_WINDOWS: while (path[count] != 0) { if (path[count] == separator || path[count] == '\\') { return count; } count++; } break; } return count; } /* *--------------------------------------------------------------------------- * * TclNewFSPathObj -- * * Creates a path object whose string representation is '[file join * dirPtr addStrRep]', but does so in a way that allows for more * efficient creation and caching of normalized paths, and more efficient * 'file dirname', 'file tail', etc. * * Assumptions: * 'dirPtr' must be an absolute path. 'len' may not be zero. * * Results: * The new Tcl object, with refCount zero. * * Side effects: * Memory is allocated. 'dirPtr' gets an additional refCount. * *--------------------------------------------------------------------------- */ Tcl_Obj * TclNewFSPathObj( Tcl_Obj *dirPtr, const char *addStrRep, Tcl_Size len) { FsPath *fsPathPtr; Tcl_Obj *pathPtr; const char *p; int state = 0, count = 0; /* * This comment is kept from the days of tilde expansion because * it is illustrative of a more general problem. * [Bug 2806250] - this is only a partial solution of the problem. * The PATHFLAGS != 0 representation assumes in many places that * the "tail" part stored in the normPathPtr field is itself a * relative path. Strings that begin with "~" are not relative paths, * so we must prevent their storage in the normPathPtr field. * * More generally we ought to be testing "addStrRep" for any value * that is not a relative path, but in an unconstrained VFS world * that could be just about anything, and testing could be expensive. * Since this routine plays a big role in [glob], anything that slows * it down would be unwelcome. For now, continue the risk of further * bugs when some Tcl_Filesystem uses otherwise relative path strings * as absolute path strings. Sensible Tcl_Filesystems will avoid * that by mounting on path prefixes like foo:// which cannot be the * name of a file or directory read from a native [glob] operation. */ TclNewObj(pathPtr); fsPathPtr = (FsPath *)Tcl_Alloc(sizeof(FsPath)); /* * Set up the path. */ fsPathPtr->translatedPathPtr = NULL; fsPathPtr->normPathPtr = Tcl_NewStringObj(addStrRep, len); Tcl_IncrRefCount(fsPathPtr->normPathPtr); fsPathPtr->cwdPtr = dirPtr; Tcl_IncrRefCount(dirPtr); fsPathPtr->nativePathPtr = NULL; fsPathPtr->fsPtr = NULL; fsPathPtr->filesystemEpoch = 0; SETPATHOBJ(pathPtr, fsPathPtr); PATHFLAGS(pathPtr) = TCLPATH_APPENDED; TclInvalidateStringRep(pathPtr); /* * Look for path components made up of only "." * This is overly conservative analysis to keep simple. It may mark some * things as needing more aggressive normalization that don't actually * need it. No harm done. */ for (p = addStrRep; len > 0; p++, len--) { switch (state) { case 0: /* So far only "." since last dirsep or start */ switch (*p) { case '.': count = 1; break; case '/': case '\\': case ':': if (count) { PATHFLAGS(pathPtr) |= TCLPATH_NEEDNORM; len = 0; } break; default: count = 0; state = 1; } break; case 1: /* Scanning for next dirsep */ switch (*p) { case '/': case '\\': case ':': state = 0; break; } } } if (len == 0 && count) { PATHFLAGS(pathPtr) |= TCLPATH_NEEDNORM; } return pathPtr; } static Tcl_Obj * AppendPath( Tcl_Obj *head, Tcl_Obj *tail) { const char *bytes; Tcl_Obj *copy = Tcl_DuplicateObj(head); Tcl_Size length; /* * This is likely buggy when dealing with virtual filesystem drivers * that use some character other than "/" as a path separator. I know * of no evidence that such a foolish thing exists. This solution was * chosen so that "JoinPath" operations that pass through either path * internalrep produce the same results; that is, bugward compatibility. If * we need to fix that bug here, it needs fixing in TclJoinPath() too. */ bytes = TclGetStringFromObj(tail, &length); if (length == 0) { Tcl_AppendToObj(copy, "/", 1); } else { TclpNativeJoinPath(copy, bytes); } return copy; } /* *--------------------------------------------------------------------------- * * TclFSMakePathRelative -- * * Only for internal use. * * Takes a path and a directory, where we _assume_ both path and * directory are absolute, normalized and that the path lies inside the * directory. Returns a Tcl_Obj representing filename of the path * relative to the directory. * * Results: * NULL on error, otherwise a valid object, typically with refCount of * zero, which it is assumed the caller will increment. * * Side effects: * The old representation may be freed, and new memory allocated. * *--------------------------------------------------------------------------- */ Tcl_Obj * TclFSMakePathRelative( TCL_UNUSED(Tcl_Interp *), Tcl_Obj *pathPtr, /* The path we have. */ Tcl_Obj *cwdPtr) /* Make it relative to this. */ { Tcl_Size cwdLen, len; const char *tempStr; Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(pathPtr, &fsPathType); if (irPtr) { FsPath *fsPathPtr = PATHOBJ(pathPtr); if (PATHFLAGS(pathPtr) != 0 && fsPathPtr->cwdPtr == cwdPtr) { return fsPathPtr->normPathPtr; } } /* * We know the cwd is a normalised object which does not end in a * directory delimiter, unless the cwd is the name of a volume, in which * case it will end in a delimiter! We handle this situation here. A * better test than the '!= sep' might be to simply check if 'cwd' is a * root volume. * * Note that if we get this wrong, we will strip off either too much or * too little below, leading to wrong answers returned by glob. */ tempStr = TclGetStringFromObj(cwdPtr, &cwdLen); /* * Should we perhaps use 'Tcl_FSPathSeparator'? But then what about the * Windows special case? Perhaps we should just check if cwd is a root * volume. */ switch (tclPlatform) { case TCL_PLATFORM_UNIX: if (tempStr[cwdLen-1] != '/') { cwdLen++; } break; case TCL_PLATFORM_WINDOWS: if (tempStr[cwdLen-1] != '/' && tempStr[cwdLen-1] != '\\') { cwdLen++; } break; } tempStr = TclGetStringFromObj(pathPtr, &len); return Tcl_NewStringObj(tempStr + cwdLen, len - cwdLen); } /* *--------------------------------------------------------------------------- * * MakePathFromNormalized -- * * Like SetFsPathFromAny, but assumes the given object is an absolute * normalized path. Only for internal use. * * Results: * Standard Tcl error code. * * Side effects: * The old representation may be freed, and new memory allocated. * *--------------------------------------------------------------------------- */ static int MakePathFromNormalized( TCL_UNUSED(Tcl_Interp *), Tcl_Obj *pathPtr) /* The object to convert. */ { FsPath *fsPathPtr; if (TclHasInternalRep(pathPtr, &fsPathType)) { return TCL_OK; } fsPathPtr = (FsPath *)Tcl_Alloc(sizeof(FsPath)); /* * It's a pure normalized absolute path. */ fsPathPtr->translatedPathPtr = NULL; Tcl_IncrRefCount(fsPathPtr->normPathPtr = Tcl_DuplicateObj(pathPtr)); fsPathPtr->cwdPtr = NULL; fsPathPtr->nativePathPtr = NULL; fsPathPtr->fsPtr = NULL; /* Remember the epoch under which we decided pathPtr was normalized */ fsPathPtr->filesystemEpoch = TclFSEpoch(); SETPATHOBJ(pathPtr, fsPathPtr); PATHFLAGS(pathPtr) = 0; return TCL_OK; } /* *--------------------------------------------------------------------------- * * Tcl_FSNewNativePath -- * * Performs the something like the reverse of the usual * obj->path->nativerep conversions. If some code retrieves a path in * native form (from, e.g. readlink or a native dialog), and that path is * to be used at the Tcl level, then calling this function is an * efficient way of creating the appropriate path object type. * * Any memory which is allocated for 'clientData' should be retained * until clientData is passed to the filesystem's freeInternalRepProc * when it can be freed. The built in platform-specific filesystems use * 'Tcl_Alloc' to allocate clientData, and Tcl_Free to free it. * * Results: * NULL or a valid path object pointer, with refCount zero. * * Side effects: * New memory may be allocated. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSNewNativePath( const Tcl_Filesystem *fromFilesystem, void *clientData) { Tcl_Obj *pathPtr = NULL; FsPath *fsPathPtr; if (fromFilesystem->internalToNormalizedProc != NULL) { pathPtr = (*fromFilesystem->internalToNormalizedProc)(clientData); } if (pathPtr == NULL) { return NULL; } /* * Free old representation; shouldn't normally be any, but best to be * safe. */ Tcl_StoreInternalRep(pathPtr, &fsPathType, NULL); fsPathPtr = (FsPath *)Tcl_Alloc(sizeof(FsPath)); fsPathPtr->translatedPathPtr = NULL; Tcl_IncrRefCount(fsPathPtr->normPathPtr = Tcl_DuplicateObj(pathPtr)); fsPathPtr->cwdPtr = NULL; fsPathPtr->nativePathPtr = clientData; fsPathPtr->fsPtr = fromFilesystem; fsPathPtr->filesystemEpoch = TclFSEpoch(); SETPATHOBJ(pathPtr, fsPathPtr); PATHFLAGS(pathPtr) = 0; return pathPtr; } /* *--------------------------------------------------------------------------- * * Tcl_FSGetTranslatedPath -- * * Attempts to extract the translated path from the given * Tcl_Obj. If the translation succeeds (i.e. the object is a valid * path), then it is returned. Otherwise NULL is returned and an * error message may be left in the interpreter if it is not NULL. * * Results: * A Tcl_Obj pointer or NULL. * * Side effects: * pathPtr is converted to fsPathType if necessary. * * FsPath members are modified as needed. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSGetTranslatedPath( Tcl_Interp *interp, Tcl_Obj *pathPtr) { Tcl_Obj *retObj = NULL; FsPath *srcFsPathPtr; if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) { return NULL; } srcFsPathPtr = PATHOBJ(pathPtr); if (srcFsPathPtr->translatedPathPtr == NULL) { if (PATHFLAGS(pathPtr) == 0) { /* * Path is already normalized */ retObj = srcFsPathPtr->normPathPtr; } else { /* * We lack a translated path result, but we have a directory * (cwdPtr) and a tail (normPathPtr), and if we join the * translated version of cwdPtr to normPathPtr, we'll get the * translated result we need, and can store it for future use. */ Tcl_Obj *translatedCwdPtr = Tcl_FSGetTranslatedPath(interp, srcFsPathPtr->cwdPtr); Tcl_ObjInternalRep *translatedCwdIrPtr; if (translatedCwdPtr == NULL) { return NULL; } retObj = Tcl_FSJoinToPath(translatedCwdPtr, 1, &srcFsPathPtr->normPathPtr); Tcl_IncrRefCount(srcFsPathPtr->translatedPathPtr = retObj); translatedCwdIrPtr = TclFetchInternalRep(translatedCwdPtr, &fsPathType); if (translatedCwdIrPtr) { srcFsPathPtr->filesystemEpoch = PATHOBJ(translatedCwdPtr)->filesystemEpoch; } else { srcFsPathPtr->filesystemEpoch = 0; } Tcl_DecrRefCount(translatedCwdPtr); } } else { /* * It is an ordinary path object. */ retObj = srcFsPathPtr->translatedPathPtr; } if (retObj != NULL) { Tcl_IncrRefCount(retObj); } return retObj; } /* *--------------------------------------------------------------------------- * * Tcl_FSGetTranslatedStringPath -- * * This function attempts to extract the translated path from the given * Tcl_Obj. If the translation succeeds (i.e. the object is a valid * path), then the path is returned. Otherwise NULL will be returned, and * an error message may be left in the interpreter (if it is non-NULL) * * Results: * NULL or a valid string. * * Side effects: * Only those of 'Tcl_FSConvertToPathType' * *--------------------------------------------------------------------------- */ const char * Tcl_FSGetTranslatedStringPath( Tcl_Interp *interp, Tcl_Obj *pathPtr) { Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr); if (transPtr != NULL) { Tcl_Size len; const char *orig = TclGetStringFromObj(transPtr, &len); char *result = (char *)Tcl_Alloc(len+1); memcpy(result, orig, len+1); TclDecrRefCount(transPtr); return result; } return NULL; } /* *--------------------------------------------------------------------------- * * Tcl_FSGetNormalizedPath -- * * This important function attempts to extract from the given Tcl_Obj a * unique normalised path representation, whose string value can be used * as a unique identifier for the file. * * Results: * NULL or a valid path object pointer. * * Side effects: * New memory may be allocated. The Tcl 'errno' may be modified in the * process of trying to examine various path possibilities. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSGetNormalizedPath( Tcl_Interp *interp, Tcl_Obj *pathPtr) { FsPath *fsPathPtr; if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) { return NULL; } fsPathPtr = PATHOBJ(pathPtr); if (PATHFLAGS(pathPtr) != 0) { /* * This is a special path object which is the result of something like * 'file join' */ Tcl_Obj *dir, *copy; Tcl_Size tailLen, cwdLen; int pathType; pathType = Tcl_FSGetPathType(fsPathPtr->cwdPtr); dir = Tcl_FSGetNormalizedPath(interp, fsPathPtr->cwdPtr); if (dir == NULL) { return NULL; } /* TODO: Figure out why this is needed. */ TclGetString(pathPtr); (void)TclGetStringFromObj(fsPathPtr->normPathPtr, &tailLen); if (tailLen) { copy = AppendPath(dir, fsPathPtr->normPathPtr); } else { copy = Tcl_DuplicateObj(dir); } Tcl_IncrRefCount(dir); Tcl_IncrRefCount(copy); /* * We now own a reference on both 'dir' and 'copy' */ (void) TclGetStringFromObj(dir, &cwdLen); /* Normalize the combined string. */ if (PATHFLAGS(pathPtr) & TCLPATH_NEEDNORM) { /* * If the "tail" part has components (like /../) that cause the * combined path to need more complete normalizing, call on the * more powerful routine to accomplish that so we avoid [Bug * 2385549] ... */ Tcl_Obj *newCopy = TclFSNormalizeAbsolutePath(interp, copy); Tcl_DecrRefCount(copy); copy = newCopy; } else { /* * ... but in most cases where we join a trouble free tail to a * normalized head, we can more efficiently normalize the combined * path by passing over only the unnormalized tail portion. When * this is sufficient, prior developers claim this should be much * faster. We use 'cwdLen' so that we are already pointing at * the dir-separator that we know about. The normalization code * will actually start off directly after that separator. */ TclFSNormalizeToUniquePath(interp, copy, cwdLen); } /* Now we need to construct the new path object. */ if (pathType == TCL_PATH_RELATIVE) { Tcl_Obj *origDir = fsPathPtr->cwdPtr; /* * NOTE: here we are (dangerously?) assuming that origDir points * to a Tcl_Obj with Tcl_ObjType == &fsPathType. The * pathType = Tcl_FSGetPathType(fsPathPtr->cwdPtr); * above that set the pathType value should have established that, * but it's far less clear on what basis we know there's been no * shimmering since then. */ FsPath *origDirFsPathPtr = PATHOBJ(origDir); fsPathPtr->cwdPtr = origDirFsPathPtr->cwdPtr; Tcl_IncrRefCount(fsPathPtr->cwdPtr); TclDecrRefCount(fsPathPtr->normPathPtr); fsPathPtr->normPathPtr = copy; TclDecrRefCount(dir); TclDecrRefCount(origDir); } else { TclDecrRefCount(fsPathPtr->cwdPtr); fsPathPtr->cwdPtr = NULL; TclDecrRefCount(fsPathPtr->normPathPtr); fsPathPtr->normPathPtr = copy; TclDecrRefCount(dir); } PATHFLAGS(pathPtr) = 0; } /* * Ensure cwd hasn't changed. */ if (fsPathPtr->cwdPtr != NULL) { if (!TclFSCwdPointerEquals(&fsPathPtr->cwdPtr)) { TclGetString(pathPtr); Tcl_StoreInternalRep(pathPtr, &fsPathType, NULL); if (SetFsPathFromAny(interp, pathPtr) != TCL_OK) { return NULL; } fsPathPtr = PATHOBJ(pathPtr); } else if (fsPathPtr->normPathPtr == NULL) { Tcl_Size cwdLen; Tcl_Obj *copy; copy = AppendPath(fsPathPtr->cwdPtr, pathPtr); (void) TclGetStringFromObj(fsPathPtr->cwdPtr, &cwdLen); cwdLen += (TclGetString(copy)[cwdLen] == '/'); /* * Normalize the combined string, but only starting after the end * of the previously normalized 'dir'. This should be much faster! */ TclFSNormalizeToUniquePath(interp, copy, cwdLen-1); fsPathPtr->normPathPtr = copy; Tcl_IncrRefCount(fsPathPtr->normPathPtr); } } if (fsPathPtr->normPathPtr == NULL) { Tcl_Obj *useThisCwd = NULL; /* * Since normPathPtr is NULL but this is a valid path object, we know * that the translatedPathPtr cannot be NULL. */ Tcl_Obj *absolutePath = fsPathPtr->translatedPathPtr; const char *path = TclGetString(absolutePath); Tcl_IncrRefCount(absolutePath); /* * We have to be a little bit careful here to avoid infinite loops * we're asking Tcl_FSGetPathType to return the path's type, but that * call can actually result in a lot of other filesystem action, which * might loop back through here. */ if (path[0] == '\0') { /* * Special handling for the empty string value. This one is very * weird with [file normalize {}] => {}. (The reasoning supporting * this is unknown to DGP, but he fears changing it.) Attempt here * to keep the expectations of other parts of Tcl_Filesystem code * about state of the FsPath fields satisfied. * * In particular, capture the cwd value and save so it can be * stored in the cwdPtr field below. */ useThisCwd = Tcl_FSGetCwd(interp); } else { /* * We don't ask for the type of 'pathPtr' here, because that is * not correct for our purposes when we have a path like '~'. Tcl * has a bit of a contradiction in that '~' paths are defined as * 'absolute', but in reality can be just about anything, * depending on how env(HOME) is set. */ Tcl_PathType type = Tcl_FSGetPathType(absolutePath); if (type == TCL_PATH_RELATIVE) { useThisCwd = Tcl_FSGetCwd(interp); if (useThisCwd == NULL) { return NULL; } Tcl_DecrRefCount(absolutePath); absolutePath = Tcl_FSJoinToPath(useThisCwd, 1, &absolutePath); Tcl_IncrRefCount(absolutePath); /* * We have a refCount on the cwd. */ #ifdef _WIN32 } else if (type == TCL_PATH_VOLUME_RELATIVE) { /* * Only Windows has volume-relative paths. */ Tcl_DecrRefCount(absolutePath); absolutePath = TclWinVolumeRelativeNormalize(interp, path, &useThisCwd); if (absolutePath == NULL) { return NULL; } #endif /* _WIN32 */ } } /* * Already has refCount incremented. */ if (fsPathPtr->normPathPtr) { Tcl_DecrRefCount(fsPathPtr->normPathPtr); } fsPathPtr->normPathPtr = TclFSNormalizeAbsolutePath(interp, absolutePath); if (useThisCwd != NULL) { /* * We just need to free an object we allocated above for relative * paths (this was returned by Tcl_FSJoinToPath above), and then * of course store the cwd. */ fsPathPtr->cwdPtr = useThisCwd; } TclDecrRefCount(absolutePath); } return fsPathPtr->normPathPtr; } /* *--------------------------------------------------------------------------- * * Tcl_FSGetInternalRep -- * * Produces a native representation of a given path object in the given * filesystem. * * In the future it might be desirable to have separate versions * of this function with different signatures, for example * Tcl_FSGetNativeWinPath, Tcl_FSGetNativeUnixPath etc. Right now, since * native paths are all string based, we use just one function. * * Results: * * The native handle for the path, or NULL if the path is not handled by * the given filesystem * * Side effects: * * Tcl_FSCreateInternalRepProc if needed to produce the native * handle, which is then stored in the internal representation of pathPtr. * *--------------------------------------------------------------------------- */ void * Tcl_FSGetInternalRep( Tcl_Obj *pathPtr, const Tcl_Filesystem *fsPtr) { FsPath *srcFsPathPtr; if (Tcl_FSConvertToPathType(NULL, pathPtr) != TCL_OK) { return NULL; } srcFsPathPtr = PATHOBJ(pathPtr); /* * Currently there must be a unique bi-directional mapping between a path * and a filesystem, and therefore there is no way to "remap" a file, i.e., * to map a file in one filesystem into another. Another way of putting * this is that 'stacked' filesystems are not allowed. It could be useful * in the future to redesign the system to allow that. * * Even something simple like a 'pass through' filesystem which logs all * activity and passes the calls onto the native system would be nice, but * not currently easily achievable. */ if (srcFsPathPtr->fsPtr == NULL) { Tcl_FSGetFileSystemForPath(pathPtr); srcFsPathPtr = PATHOBJ(pathPtr); if (srcFsPathPtr->fsPtr == NULL) { /* * The path is probably not a valid path in the filesystsem, and is * most likely to be a use of the empty path "" via a direct call * to one of the objectified interfaces (e.g. from the Tcl * testsuite). */ return NULL; } } /* * If the file belongs to a different filesystem, perhaps it is actually * linked through to a file in the given filesystem. Check this by * inspecting the filesystem associated with the given path. */ if (fsPtr != srcFsPathPtr->fsPtr) { const Tcl_Filesystem *actualFs = Tcl_FSGetFileSystemForPath(pathPtr); if (actualFs == fsPtr) { return Tcl_FSGetInternalRep(pathPtr, fsPtr); } return NULL; } if (srcFsPathPtr->nativePathPtr == NULL) { Tcl_FSCreateInternalRepProc *proc; char *nativePathPtr; proc = srcFsPathPtr->fsPtr->createInternalRepProc; if (proc == NULL) { return NULL; } nativePathPtr = (char *)proc(pathPtr); srcFsPathPtr = PATHOBJ(pathPtr); srcFsPathPtr->nativePathPtr = nativePathPtr; srcFsPathPtr->filesystemEpoch = TclFSEpoch(); } return srcFsPathPtr->nativePathPtr; } /* *--------------------------------------------------------------------------- * * TclFSEnsureEpochOk -- * * Ensure that the path is a valid path, and that it has a * fsPathType internal representation that is not stale. * * Results: * A standard Tcl return code. * * Side effects: * The internal representation of fsPtrPtr is converted to fsPathType if * possible. * *--------------------------------------------------------------------------- */ int TclFSEnsureEpochOk( Tcl_Obj *pathPtr, const Tcl_Filesystem **fsPtrPtr) { FsPath *srcFsPathPtr; if (!TclHasInternalRep(pathPtr, &fsPathType)) { return TCL_OK; } srcFsPathPtr = PATHOBJ(pathPtr); if (!TclFSEpochOk(srcFsPathPtr->filesystemEpoch)) { /* * The filesystem has changed in some way since the internal * representation for this object was calculated. Discard the stale * representation and recalculate it. */ TclGetString(pathPtr); Tcl_StoreInternalRep(pathPtr, &fsPathType, NULL); if (SetFsPathFromAny(NULL, pathPtr) != TCL_OK) { return TCL_ERROR; } srcFsPathPtr = PATHOBJ(pathPtr); } if (srcFsPathPtr->fsPtr != NULL) { /* * There is already a filesystem assigned to this path. */ *fsPtrPtr = srcFsPathPtr->fsPtr; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * TclFSSetPathDetails -- * * ??? * * Results: * None * * Side effects: * ??? * *--------------------------------------------------------------------------- */ void TclFSSetPathDetails( Tcl_Obj *pathPtr, const Tcl_Filesystem *fsPtr, void *clientData) { FsPath *srcFsPathPtr; /* * Make sure pathPtr is of the correct type. */ if (!TclHasInternalRep(pathPtr, &fsPathType)) { if (SetFsPathFromAny(NULL, pathPtr) != TCL_OK) { return; } } srcFsPathPtr = PATHOBJ(pathPtr); srcFsPathPtr->fsPtr = fsPtr; srcFsPathPtr->nativePathPtr = clientData; srcFsPathPtr->filesystemEpoch = TclFSEpoch(); } /* *--------------------------------------------------------------------------- * * Tcl_FSEqualPaths -- * * This function tests whether the two paths given are equal path * objects. If either or both is NULL, 0 is always returned. * * Results: * 1 or 0. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int Tcl_FSEqualPaths( Tcl_Obj *firstPtr, Tcl_Obj *secondPtr) { const char *firstStr, *secondStr; Tcl_Size firstLen, secondLen; int tempErrno; if (firstPtr == secondPtr) { return 1; } if (firstPtr == NULL || secondPtr == NULL) { return 0; } firstStr = TclGetStringFromObj(firstPtr, &firstLen); secondStr = TclGetStringFromObj(secondPtr, &secondLen); if ((firstLen == secondLen) && !memcmp(firstStr, secondStr, firstLen)) { return 1; } /* * Try the most thorough, correct method of comparing fully normalized * paths. */ tempErrno = Tcl_GetErrno(); firstPtr = Tcl_FSGetNormalizedPath(NULL, firstPtr); secondPtr = Tcl_FSGetNormalizedPath(NULL, secondPtr); Tcl_SetErrno(tempErrno); if (firstPtr == NULL || secondPtr == NULL) { return 0; } firstStr = TclGetStringFromObj(firstPtr, &firstLen); secondStr = TclGetStringFromObj(secondPtr, &secondLen); return ((firstLen == secondLen) && !memcmp(firstStr, secondStr, firstLen)); } /* *--------------------------------------------------------------------------- * * SetFsPathFromAny -- * * Attempt to convert the internal representation of pathPtr to * fsPathType. * * Results: * Standard Tcl error code. * * Side effects: * The old representation may be freed, and new memory allocated. * *--------------------------------------------------------------------------- */ static int SetFsPathFromAny( TCL_UNUSED(Tcl_Interp *), /* Used for error reporting if not NULL. */ Tcl_Obj *pathPtr) /* The object to convert. */ { Tcl_Size len; FsPath *fsPathPtr; Tcl_Obj *transPtr; if (TclHasInternalRep(pathPtr, &fsPathType)) { return TCL_OK; } /* * First step is to translate the filename. This is similar to * Tcl_TranslateFilename, but shouldn't convert everything to windows * backslashes on that platform. The current implementation of this piece * is a slightly optimised version of the various Tilde/Split/Join stuff * to avoid multiple split/join operations. * * We remove any trailing directory separator. * * However, the split/join routines are quite complex, and one has to make * sure not to break anything on Unix or Win (fCmd.test, fileName.test and * cmdAH.test exercise most of the code). */ TclGetStringFromObj(pathPtr, &len); /* TODO: Is this needed? */ transPtr = TclJoinPath(1, &pathPtr, 1); /* * Now we have a translated filename in 'transPtr'. This will have forward * slashes on Windows, and will not contain any ~user sequences. */ fsPathPtr = (FsPath *)Tcl_Alloc(sizeof(FsPath)); if (transPtr == pathPtr) { (void) Tcl_GetString(pathPtr); TclFreeInternalRep(pathPtr); transPtr = Tcl_DuplicateObj(pathPtr); fsPathPtr->filesystemEpoch = 0; } else { fsPathPtr->filesystemEpoch = TclFSEpoch(); } Tcl_IncrRefCount(transPtr); fsPathPtr->translatedPathPtr = transPtr; fsPathPtr->normPathPtr = NULL; fsPathPtr->cwdPtr = NULL; fsPathPtr->nativePathPtr = NULL; fsPathPtr->fsPtr = NULL; SETPATHOBJ(pathPtr, fsPathPtr); PATHFLAGS(pathPtr) = 0; return TCL_OK; } static void FreeFsPathInternalRep( Tcl_Obj *pathPtr) /* Path object with internal rep to free. */ { FsPath *fsPathPtr = PATHOBJ(pathPtr); if (fsPathPtr->translatedPathPtr != NULL) { if (fsPathPtr->translatedPathPtr != pathPtr) { TclDecrRefCount(fsPathPtr->translatedPathPtr); } } if (fsPathPtr->normPathPtr != NULL) { if (fsPathPtr->normPathPtr != pathPtr) { TclDecrRefCount(fsPathPtr->normPathPtr); } fsPathPtr->normPathPtr = NULL; } if (fsPathPtr->cwdPtr != NULL) { TclDecrRefCount(fsPathPtr->cwdPtr); fsPathPtr->cwdPtr = NULL; } if (fsPathPtr->nativePathPtr != NULL && fsPathPtr->fsPtr != NULL) { Tcl_FSFreeInternalRepProc *freeProc = fsPathPtr->fsPtr->freeInternalRepProc; if (freeProc != NULL) { freeProc(fsPathPtr->nativePathPtr); fsPathPtr->nativePathPtr = NULL; } } Tcl_Free(fsPathPtr); } static void DupFsPathInternalRep( Tcl_Obj *srcPtr, /* Path obj with internal rep to copy. */ Tcl_Obj *copyPtr) /* Path obj with internal rep to set. */ { FsPath *srcFsPathPtr = PATHOBJ(srcPtr); FsPath *copyFsPathPtr = (FsPath *)Tcl_Alloc(sizeof(FsPath)); SETPATHOBJ(copyPtr, copyFsPathPtr); copyFsPathPtr->translatedPathPtr = srcFsPathPtr->translatedPathPtr; if (copyFsPathPtr->translatedPathPtr != NULL) { Tcl_IncrRefCount(copyFsPathPtr->translatedPathPtr); } copyFsPathPtr->normPathPtr = srcFsPathPtr->normPathPtr; if (copyFsPathPtr->normPathPtr != NULL) { Tcl_IncrRefCount(copyFsPathPtr->normPathPtr); } copyFsPathPtr->cwdPtr = srcFsPathPtr->cwdPtr; if (copyFsPathPtr->cwdPtr != NULL) { Tcl_IncrRefCount(copyFsPathPtr->cwdPtr); } copyFsPathPtr->flags = srcFsPathPtr->flags; if (srcFsPathPtr->fsPtr != NULL && srcFsPathPtr->nativePathPtr != NULL) { Tcl_FSDupInternalRepProc *dupProc = srcFsPathPtr->fsPtr->dupInternalRepProc; if (dupProc != NULL) { copyFsPathPtr->nativePathPtr = dupProc(srcFsPathPtr->nativePathPtr); } else { copyFsPathPtr->nativePathPtr = NULL; } } else { copyFsPathPtr->nativePathPtr = NULL; } copyFsPathPtr->fsPtr = srcFsPathPtr->fsPtr; copyFsPathPtr->filesystemEpoch = srcFsPathPtr->filesystemEpoch; } /* *--------------------------------------------------------------------------- * * UpdateStringOfFsPath -- * * Gives an object a valid string rep. * * Results: * None. * * Side effects: * Memory may be allocated. * *--------------------------------------------------------------------------- */ static void UpdateStringOfFsPath( Tcl_Obj *pathPtr) /* path obj with string rep to update. */ { FsPath *fsPathPtr = PATHOBJ(pathPtr); Tcl_Size cwdLen; Tcl_Obj *copy; if (PATHFLAGS(pathPtr) == 0 || fsPathPtr->cwdPtr == NULL) { if (fsPathPtr->translatedPathPtr == NULL) { Tcl_Panic("Called UpdateStringOfFsPath with invalid object"); } else { copy = Tcl_DuplicateObj(fsPathPtr->translatedPathPtr); } } else { copy = AppendPath(fsPathPtr->cwdPtr, fsPathPtr->normPathPtr); } if (Tcl_IsShared(copy)) { copy = Tcl_DuplicateObj(copy); } Tcl_IncrRefCount(copy); /* Steal copy's string rep */ pathPtr->bytes = TclGetStringFromObj(copy, &cwdLen); pathPtr->length = cwdLen; TclInitEmptyStringRep(copy); TclDecrRefCount(copy); } /* *--------------------------------------------------------------------------- * * TclNativePathInFilesystem -- * * Any path object is acceptable to the native filesystem, by default (we * will throw errors when illegal paths are actually tried to be used). * * However, this behavior means the native filesystem must be the last * filesystem in the lookup list (otherwise it will claim all files * belong to it, and other filesystems will never get a look in). * * Results: * TCL_OK, to indicate 'yes', -1 to indicate no. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int TclNativePathInFilesystem( Tcl_Obj *pathPtr, TCL_UNUSED(void **)) { /* * A special case is required to handle the empty path "". This is a valid * path (i.e. the user should be able to do 'file exists ""' without * throwing an error), but equally the path doesn't exist. Those are the * semantics of Tcl (at present anyway), so we have to abide by them here. */ if (TclHasInternalRep(pathPtr, &fsPathType)) { if (pathPtr->bytes != NULL && pathPtr->bytes[0] == '\0') { /* * We reject the empty path "". */ return -1; } /* * Otherwise there is no way this path can be empty. */ } else { /* * It is somewhat unusual to reach this code path without the object * being of fsPathType. However, we do our best to deal with the * situation. */ Tcl_Size len; (void) TclGetStringFromObj(pathPtr, &len); if (len == 0) { /* * We reject the empty path "". */ return -1; } } /* * Path is of correct type, or is of non-zero length, so we accept it. */ return TCL_OK; } /* *---------------------------------------------------------------------- * * MakeTildeRelativePath -- * * Returns a path relative to the home directory of a user. * Note there is a difference between not specifying a user and * explicitly specifying the current user. This mimics Tcl8's tilde * expansion. * * The subPath argument is joined to the expanded home directory * as in Tcl_JoinPath. This means if it is not relative, it will * returned as the result with the home directory only checked * for user name validity. * * Results: * Returns TCL_OK on success with home directory path in *dsPtr * and TCL_ERROR on failure with error message in interp if non-NULL. * *---------------------------------------------------------------------- */ int MakeTildeRelativePath( Tcl_Interp *interp, /* May be NULL. Only used for error messages */ const char *user, /* User name. NULL -> current user */ const char *subPath, /* Rest of path. May be NULL */ Tcl_DString *dsPtr) /* Output. Is initialized by the function. Must * be freed on success */ { const char *dir; Tcl_DString dirString; Tcl_DStringInit(dsPtr); Tcl_DStringInit(&dirString); if (user == NULL || user[0] == 0) { /* No user name specified -> current user */ dir = TclGetEnv("HOME", &dirString); if (dir == NULL) { if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "couldn't find HOME environment variable to expand path", -1)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "PATH", "HOMELESS", (char *)NULL); } return TCL_ERROR; } } else { /* User name specified - ~user */ dir = TclpGetUserHome(user, &dirString); if (dir == NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "user \"%s\" doesn't exist", user)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "PATH", "NOUSER", (char *)NULL); } return TCL_ERROR; } } if (subPath) { const char *parts[2]; parts[0] = dir; parts[1] = subPath; Tcl_JoinPath(2, parts, dsPtr); } else { Tcl_JoinPath(1, &dir, dsPtr); } Tcl_DStringFree(&dirString); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclGetHomeDirObj -- * * Wrapper around MakeTildeRelativePath. See that function. * * Results: * Returns a Tcl_Obj containing the home directory of a user * or NULL on failure with error message in interp if non-NULL. * *---------------------------------------------------------------------- */ Tcl_Obj * TclGetHomeDirObj( Tcl_Interp *interp, /* May be NULL. Only used for error messages */ const char *user) /* User name. NULL -> current user */ { Tcl_DString dirString; if (MakeTildeRelativePath(interp, user, NULL, &dirString) != TCL_OK) { return NULL; } return Tcl_DStringToObj(&dirString); } /* *---------------------------------------------------------------------- * * Tcl_FSTildeExpand -- * * Copies the path passed in to the output Tcl_DString dsPtr, * resolving leading ~ and ~user components in the path if present. * An error is returned if such a component IS present AND cannot * be resolved. * * The output dsPtr must be cleared by caller on success. * * Results: * TCL_OK - path did not contain leading ~ or it was successful resolved * TCL_ERROR - ~ component could not be resolved. * *---------------------------------------------------------------------- */ int Tcl_FSTildeExpand( Tcl_Interp *interp, /* May be NULL. Only used for error messages */ const char *path, /* Path to resolve tilde */ Tcl_DString *dsPtr) /* Output DString for resolved path. */ { Tcl_Size split; int result; assert(path); assert(dsPtr); Tcl_DStringInit(dsPtr); if (path[0] != '~') { Tcl_DStringAppend(dsPtr, path, -1); return TCL_OK; } /* * We have multiple cases '~', '~user', '~/foo/bar...', '~user/foo...' * FindSplitPos returns 1 for '~/...' as well as for '~'. Note on * Windows FindSplitPos implicitly checks for '\' as separator * in addition to what is passed. */ split = FindSplitPos(path, '/'); if (split == 1) { /* No user name specified '~' or '~/...' -> current user */ result = MakeTildeRelativePath(interp, NULL, path[1] ? 2 + path : NULL, dsPtr); } else { /* User name specified - ~user, ~user/... */ const char *user; Tcl_DString dsUser; Tcl_DStringInit(&dsUser); Tcl_DStringAppend(&dsUser, path+1, split-1); user = Tcl_DStringValue(&dsUser); /* path[split] is / for ~user/... or \0 for ~user */ result = MakeTildeRelativePath(interp, user, path[split] ? &path[split + 1] : NULL, dsPtr); Tcl_DStringFree(&dsUser); } if (result != TCL_OK) { /* Do not rely on caller to free in case of errors */ Tcl_DStringFree(dsPtr); } return result; } /* *---------------------------------------------------------------------- * * TclResolveTildePath -- * * If the passed path is begins with a tilde, does tilde resolution * and returns a Tcl_Obj containing the resolved path. If the tilde * component cannot be resolved, returns NULL. If the path does not * begin with a tilde, returns as is. * * Results: * Returns a Tcl_Obj with resolved path. This may be a new Tcl_Obj * with ref count 0 or that pathObj that was passed in without its * ref count modified. * Returns NULL if the path begins with a ~ that cannot be resolved * and stores an error message in interp if non-NULL. * *---------------------------------------------------------------------- */ Tcl_Obj * TclResolveTildePath( Tcl_Interp *interp, /* May be NULL. Only used for error messages */ Tcl_Obj *pathObj) { const char *path; Tcl_Size len; Tcl_DString resolvedPath; path = TclGetStringFromObj(pathObj, &len); /* Optimize to skip unnecessary calls below */ if (path[0] != '~') { return pathObj; } if (Tcl_FSTildeExpand(interp, path, &resolvedPath) != TCL_OK) { return NULL; } return Tcl_DStringToObj(&resolvedPath); } /* *---------------------------------------------------------------------- * * TclResolveTildePathList -- * * Given a Tcl_Obj that is a list of paths, returns a Tcl_Obj containing * the paths with any ~-prefixed paths resolved. * * Empty strings and ~-prefixed paths that cannot be resolved are * removed from the returned list. * * The trailing components of the path are returned verbatim. No * processing is done on them. Moreover, no assumptions should be * made about the separators in the returned path. They may be / * or native. Appropriate path manipulations functions should be * used by caller if desired. * * Results: * Returns a Tcl_Obj with resolved paths. This may be a new Tcl_Obj with * reference count 0 or the original passed-in Tcl_Obj if no paths needed * resolution. A NULL is returned if the passed in value is not a list * or was NULL. * *---------------------------------------------------------------------- */ Tcl_Obj * TclResolveTildePathList( Tcl_Obj *pathsObj) { Tcl_Obj **objv; Tcl_Size objc; Tcl_Size i; Tcl_Obj *resolvedPaths; const char *path; if (pathsObj == NULL) { return NULL; } if (Tcl_ListObjGetElements(NULL, pathsObj, &objc, &objv) != TCL_OK) { return NULL; /* Not a list */ } /* * Figure out if any paths need resolving to avoid unnecessary allocations. */ for (i = 0; i < objc; ++i) { path = Tcl_GetString(objv[i]); if (path[0] == '~') { break; /* At least one path needs resolution */ } } if (i == objc) { return pathsObj; /* No paths needed to be resolved */ } resolvedPaths = Tcl_NewListObj(objc, NULL); for (i = 0; i < objc; ++i) { Tcl_Obj *resolvedPath; path = Tcl_GetString(objv[i]); if (path[0] == 0) { continue; /* Skip empty strings */ } resolvedPath = TclResolveTildePath(NULL, objv[i]); if (resolvedPath) { /* Paths that cannot be resolved are skipped */ Tcl_ListObjAppendElement(NULL, resolvedPaths, resolvedPath); } } return resolvedPaths; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclPipe.c0000644000175000017500000007374214726623136014753 0ustar sergeisergei/* * tclPipe.c -- * * This file contains the generic portion of the command channel driver * as well as various utility routines used in managing subprocesses. * * Copyright © 1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * A linked list of the following structures is used to keep track of child * processes that have been detached but haven't exited yet, so we can make * sure that they're properly "reaped" (officially waited for) and don't lie * around as zombies cluttering the system. */ typedef struct Detached { Tcl_Pid pid; /* Id of process that's been detached but * isn't known to have exited. */ struct Detached *nextPtr; /* Next in list of all detached processes. */ } Detached; static Detached *detList = NULL;/* List of all detached proceses. */ TCL_DECLARE_MUTEX(pipeMutex) /* Guard access to detList. */ /* * Declarations for local functions defined in this file: */ static TclFile FileForRedirect(Tcl_Interp *interp, const char *spec, int atOk, const char *arg, const char *nextArg, int flags, int *skipPtr, int *closePtr, int *releasePtr); /* *---------------------------------------------------------------------- * * FileForRedirect -- * * This function does much of the work of parsing redirection operators. * It handles "@" if specified and allowed, and a file name, and opens * the file if necessary. * * Results: * The return value is the descriptor number for the file. If an error * occurs then NULL is returned and an error message is left in the * interp's result. Several arguments are side-effected; see the argument * list below for details. * * Side effects: * None. * *---------------------------------------------------------------------- */ static TclFile FileForRedirect( Tcl_Interp *interp, /* Interpreter to use for error reporting. */ const char *spec, /* Points to character just after redirection * character. */ int atOK, /* Non-zero means that '@' notation can be * used to specify a channel, zero means that * it isn't. */ const char *arg, /* Pointer to entire argument containing spec: * used for error reporting. */ const char *nextArg, /* Next argument in argc/argv array, if needed * for file name or channel name. May be * NULL. */ int flags, /* Flags to use for opening file or to specify * mode for channel. */ int *skipPtr, /* Filled with 1 if redirection target was in * spec, 2 if it was in nextArg. */ int *closePtr, /* Filled with one if the caller should close * the file when done with it, zero * otherwise. */ int *releasePtr) { int writing = (flags & O_WRONLY); Tcl_Channel chan; TclFile file; *skipPtr = 1; if ((atOK != 0) && (*spec == '@')) { spec++; if (*spec == '\0') { spec = nextArg; if (spec == NULL) { goto badLastArg; } *skipPtr = 2; } chan = Tcl_GetChannel(interp, spec, NULL); if (chan == (Tcl_Channel) NULL) { return NULL; } file = TclpMakeFile(chan, writing ? TCL_WRITABLE : TCL_READABLE); if (file == NULL) { Tcl_Obj *msg; Tcl_GetChannelError(chan, &msg); if (msg) { Tcl_SetObjResult(interp, msg); } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "channel \"%s\" wasn't opened for %s", Tcl_GetChannelName(chan), ((writing) ? "writing" : "reading"))); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", "BADCHAN", (char *)NULL); } return NULL; } *releasePtr = 1; if (writing) { /* * Be sure to flush output to the file, so that anything written * by the child appears after stuff we've already written. */ Tcl_Flush(chan); } } else { const char *name; Tcl_DString nameString; if (*spec == '\0') { spec = nextArg; if (spec == NULL) { goto badLastArg; } *skipPtr = 2; } name = Tcl_TranslateFileName(interp, spec, &nameString); if (name == NULL) { return NULL; } file = TclpOpenFile(name, flags); Tcl_DStringFree(&nameString); if (file == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't %s file \"%s\": %s", (writing ? "write" : "read"), spec, Tcl_PosixError(interp))); return NULL; } *closePtr = 1; } return file; badLastArg: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't specify \"%s\" as last word in command", arg)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", "SYNTAX", (char *)NULL); return NULL; } /* *---------------------------------------------------------------------- * * Tcl_DetachPids -- * * This function is called to indicate that one or more child processes * have been placed in background and will never be waited for; they * should eventually be reaped by Tcl_ReapDetachedProcs. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_DetachPids( Tcl_Size numPids, /* Number of pids to detach: gives size of * array pointed to by pidPtr. */ Tcl_Pid *pidPtr) /* Array of pids to detach. */ { Detached *detPtr; Tcl_Size i; Tcl_MutexLock(&pipeMutex); for (i = 0; i < numPids; i++) { detPtr = (Detached *)Tcl_Alloc(sizeof(Detached)); detPtr->pid = pidPtr[i]; detPtr->nextPtr = detList; detList = detPtr; } Tcl_MutexUnlock(&pipeMutex); } /* *---------------------------------------------------------------------- * * Tcl_ReapDetachedProcs -- * * This function checks to see if any detached processes have exited and, * if so, it "reaps" them by officially waiting on them. It should be * called "occasionally" to make sure that all detached processes are * eventually reaped. * * Results: * None. * * Side effects: * Processes are waited on, so that they can be reaped by the system. * *---------------------------------------------------------------------- */ void Tcl_ReapDetachedProcs(void) { Detached *detPtr; Detached *nextPtr, *prevPtr; int status, code; Tcl_MutexLock(&pipeMutex); for (detPtr = detList, prevPtr = NULL; detPtr != NULL; ) { status = TclProcessWait(detPtr->pid, WNOHANG, &code, NULL, NULL); if (status == TCL_PROCESS_UNCHANGED || (status == TCL_PROCESS_ERROR && code != ECHILD)) { prevPtr = detPtr; detPtr = detPtr->nextPtr; continue; } nextPtr = detPtr->nextPtr; if (prevPtr == NULL) { detList = detPtr->nextPtr; } else { prevPtr->nextPtr = detPtr->nextPtr; } Tcl_Free(detPtr); detPtr = nextPtr; } Tcl_MutexUnlock(&pipeMutex); } /* *---------------------------------------------------------------------- * * TclCleanupChildren -- * * This is a utility function used to wait for child processes to exit, * record information about abnormal exits, and then collect any stderr * output generated by them. * * Results: * The return value is a standard Tcl result. If anything at weird * happened with the child processes, TCL_ERROR is returned and a message * is left in the interp's result. * * Side effects: * If the last character of the interp's result is a newline, then it is * removed unless keepNewline is non-zero. File errorId gets closed, and * pidPtr is freed back to the storage allocator. * *---------------------------------------------------------------------- */ int TclCleanupChildren( Tcl_Interp *interp, /* Used for error messages. */ Tcl_Size numPids, /* Number of entries in pidPtr array. */ Tcl_Pid *pidPtr, /* Array of process ids of children. */ Tcl_Channel errorChan) /* Channel for file containing stderr output * from pipeline. NULL means there isn't any * stderr output. */ { int result = TCL_OK; int code, abnormalExit, anyErrorInfo; TclProcessWaitStatus waitStatus; Tcl_Size i; Tcl_Obj *msg, *error; abnormalExit = 0; for (i = 0; i < numPids; i++) { waitStatus = TclProcessWait(pidPtr[i], 0, &code, &msg, &error); if (waitStatus == TCL_PROCESS_ERROR) { result = TCL_ERROR; if (interp != NULL) { Tcl_SetObjErrorCode(interp, error); Tcl_SetObjResult(interp, msg); } Tcl_DecrRefCount(error); Tcl_DecrRefCount(msg); continue; } /* * Create error messages for unusual process exits. An extra newline * gets appended to each error message, but it gets removed below (in * the same fashion that an extra newline in the command's output is * removed). */ if (waitStatus != TCL_PROCESS_EXITED || code != 0) { result = TCL_ERROR; if (waitStatus == TCL_PROCESS_EXITED) { if (interp != NULL) { Tcl_SetObjErrorCode(interp, error); } abnormalExit = 1; } else if (interp != NULL) { Tcl_SetObjErrorCode(interp, error); Tcl_SetObjResult(interp, msg); } Tcl_DecrRefCount(error); Tcl_DecrRefCount(msg); } } /* * Read the standard error file. If there's anything there, then return an * error and add the file's contents to the result string. */ anyErrorInfo = 0; if (errorChan != NULL) { /* * Make sure we start at the beginning of the file. */ if (interp != NULL) { int count; Tcl_Obj *objPtr; Tcl_Seek(errorChan, 0, SEEK_SET); TclNewObj(objPtr); count = Tcl_ReadChars(errorChan, objPtr, TCL_INDEX_NONE, 0); if (count == -1) { result = TCL_ERROR; Tcl_DecrRefCount(objPtr); Tcl_ResetResult(interp); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error reading stderr output file: %s", Tcl_PosixError(interp))); } else if (count > 0) { anyErrorInfo = 1; Tcl_SetObjResult(interp, objPtr); result = TCL_ERROR; } else { Tcl_DecrRefCount(objPtr); } } Tcl_CloseEx(NULL, errorChan, 0); } /* * If a child exited abnormally but didn't output any error information at * all, generate an error message here. */ if ((abnormalExit != 0) && (anyErrorInfo == 0) && (interp != NULL)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "child process exited abnormally", -1)); } return result; } /* *---------------------------------------------------------------------- * * TclCreatePipeline -- * * Given an argc/argv array, instantiate a pipeline of processes as * described by the argv. * * This function is unofficially exported for use by BLT. * * Results: * The return value is a count of the number of new processes created, or * TCL_INDEX_NONE if an error occurred while creating the pipeline. *pidArrayPtr is * filled in with the address of a dynamically allocated array giving the * ids of all of the processes. It is up to the caller to free this array * when it isn't needed anymore. If inPipePtr is non-NULL, *inPipePtr is * filled in with the file id for the input pipe for the pipeline (if * any): the caller must eventually close this file. If outPipePtr isn't * NULL, then *outPipePtr is filled in with the file id for the output * pipe from the pipeline: the caller must close this file. If errFilePtr * isn't NULL, then *errFilePtr is filled with a file id that may be used * to read error output after the pipeline completes. * * Side effects: * Processes and pipes are created. * *---------------------------------------------------------------------- */ Tcl_Size TclCreatePipeline( Tcl_Interp *interp, /* Interpreter to use for error reporting. */ Tcl_Size argc, /* Number of entries in argv. */ const char **argv, /* Array of strings describing commands in * pipeline plus I/O redirection with <, <<, * >, etc. Argv[argc] must be NULL. */ Tcl_Pid **pidArrayPtr, /* Word at *pidArrayPtr gets filled in with * address of array of pids for processes in * pipeline (first pid is first process in * pipeline). */ TclFile *inPipePtr, /* If non-NULL, input to the pipeline comes * from a pipe (unless overridden by * redirection in the command). The file id * with which to write to this pipe is stored * at *inPipePtr. NULL means command specified * its own input source. */ TclFile *outPipePtr, /* If non-NULL, output to the pipeline goes to * a pipe, unless overridden by redirection in * the command. The file id with which to read * from this pipe is stored at *outPipePtr. * NULL means command specified its own output * sink. */ TclFile *errFilePtr) /* If non-NULL, all stderr output from the * pipeline will go to a temporary file * created here, and a descriptor to read the * file will be left at *errFilePtr. The file * will be removed already, so closing this * descriptor will be the end of the file. If * this is NULL, then all stderr output goes * to our stderr. If the pipeline specifies * redirection then the file will still be * created but it will never get any data. */ { Tcl_Pid *pidPtr = NULL; /* Points to malloc-ed array holding all the * pids of child processes. */ Tcl_Size numPids; /* Actual number of processes that exist at * *pidPtr right now. */ Tcl_Size cmdCount; /* Count of number of distinct commands found * in argc/argv. */ const char *inputLiteral = NULL; /* If non-null, then this points to a string * containing input data (specified via <<) to * be piped to the first process in the * pipeline. */ TclFile inputFile = NULL; /* If != NULL, gives file to use as input for * first process in pipeline (specified via < * or <@). */ int inputClose = 0; /* If non-zero, then inputFile should be * closed when cleaning up. */ int inputRelease = 0; TclFile outputFile = NULL; /* Writable file for output from last command * in pipeline (could be file or pipe). NULL * means use stdout. */ int outputClose = 0; /* If non-zero, then outputFile should be * closed when cleaning up. */ int outputRelease = 0; TclFile errorFile = NULL; /* Writable file for error output from all * commands in pipeline. NULL means use * stderr. */ int errorClose = 0; /* If non-zero, then errorFile should be * closed when cleaning up. */ int errorRelease = 0; const char *p; const char *nextArg; int skip, atOK, flags, needCmd, errorToOutput = 0; Tcl_Size i, j, lastArg, lastBar; Tcl_DString execBuffer; TclFile pipeIn; TclFile curInFile, curOutFile, curErrFile; Tcl_Channel channel; if (inPipePtr != NULL) { *inPipePtr = NULL; } if (outPipePtr != NULL) { *outPipePtr = NULL; } if (errFilePtr != NULL) { *errFilePtr = NULL; } Tcl_DStringInit(&execBuffer); pipeIn = NULL; curInFile = NULL; curOutFile = NULL; numPids = 0; /* * First, scan through all the arguments to figure out the structure of * the pipeline. Process all of the input and output redirection arguments * and remove them from the argument list in the pipeline. Count the * number of distinct processes (it's the number of "|" arguments plus * one) but don't remove the "|" arguments because they'll be used in the * second pass to separate the individual child processes. Cannot start * the child processes in this pass because the redirection symbols may * appear anywhere in the command line - e.g., the '<' that specifies the * input to the entire pipe may appear at the very end of the argument * list. */ lastBar = -1; cmdCount = 1; needCmd = 1; for (i = 0; i < argc; i++) { errorToOutput = 0; skip = 0; p = argv[i]; switch (*p++) { case '|': if (*p == '&') { p++; } if (*p == '\0') { if ((i == (lastBar + 1)) || (i == (argc - 1))) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "illegal use of | or |& in command", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", "PIPESYNTAX", (char *)NULL); goto error; } } lastBar = i; cmdCount++; needCmd = 1; break; case '<': if (inputClose != 0) { inputClose = 0; TclpCloseFile(inputFile); } if (inputRelease != 0) { inputRelease = 0; TclpReleaseFile(inputFile); } if (*p == '<') { inputFile = NULL; inputLiteral = p + 1; skip = 1; if (*inputLiteral == '\0') { inputLiteral = ((i + 1) == argc) ? NULL : argv[i + 1]; if (inputLiteral == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't specify \"%s\" as last word in command", argv[i])); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", "PIPESYNTAX", (char *)NULL); goto error; } skip = 2; } } else { nextArg = ((i + 1) == argc) ? NULL : argv[i + 1]; inputLiteral = NULL; inputFile = FileForRedirect(interp, p, 1, argv[i], nextArg, O_RDONLY, &skip, &inputClose, &inputRelease); if (inputFile == NULL) { goto error; } } break; case '>': atOK = 1; flags = O_WRONLY | O_CREAT | O_TRUNC; if (*p == '>') { p++; atOK = 0; /* * Note that the O_APPEND flag only has an effect on POSIX * platforms. On Windows, we just have to carry on regardless. */ flags = O_WRONLY | O_CREAT | O_APPEND; } if (*p == '&') { if (errorClose != 0) { errorClose = 0; TclpCloseFile(errorFile); } errorToOutput = 1; p++; } /* * Close the old output file, but only if the error file is not * also using it. */ if (outputClose != 0) { outputClose = 0; if (errorFile == outputFile) { errorClose = 1; } else { TclpCloseFile(outputFile); } } if (outputRelease != 0) { outputRelease = 0; if (errorFile == outputFile) { errorRelease = 1; } else { TclpReleaseFile(outputFile); } } nextArg = ((i + 1) == argc) ? NULL : argv[i + 1]; outputFile = FileForRedirect(interp, p, atOK, argv[i], nextArg, flags, &skip, &outputClose, &outputRelease); if (outputFile == NULL) { goto error; } if (errorToOutput) { if (errorClose != 0) { errorClose = 0; TclpCloseFile(errorFile); } if (errorRelease != 0) { errorRelease = 0; TclpReleaseFile(errorFile); } errorFile = outputFile; } break; case '2': if (*p != '>') { break; } p++; atOK = 1; flags = O_WRONLY | O_CREAT | O_TRUNC; if (*p == '>') { p++; atOK = 0; /* * Note that the O_APPEND flag only has an effect on POSIX * platforms. On Windows, we just have to carry on regardless. */ flags = O_WRONLY | O_CREAT | O_APPEND; } if (errorClose != 0) { errorClose = 0; TclpCloseFile(errorFile); } if (errorRelease != 0) { errorRelease = 0; TclpReleaseFile(errorFile); } if (atOK && p[0] == '@' && p[1] == '1' && p[2] == '\0') { /* * Special case handling of 2>@1 to redirect stderr to the * exec/open output pipe as well. This is meant for the end of * the command string, otherwise use |& between commands. */ if (i != argc-1) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "must specify \"%s\" as last word in command", argv[i])); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", "PIPESYNTAX", (char *)NULL); goto error; } errorFile = outputFile; errorToOutput = 2; skip = 1; } else { nextArg = ((i + 1) == argc) ? NULL : argv[i + 1]; errorFile = FileForRedirect(interp, p, atOK, argv[i], nextArg, flags, &skip, &errorClose, &errorRelease); if (errorFile == NULL) { goto error; } } break; default: /* * Got a command word, not a redirection. */ needCmd = 0; break; } if (skip != 0) { for (j = i + skip; j < argc; j++) { argv[j - skip] = argv[j]; } argc -= skip; i -= 1; } } if (needCmd) { /* * We had a bar followed only by redirections. */ Tcl_SetObjResult(interp, Tcl_NewStringObj( "illegal use of | or |& in command", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", "PIPESYNTAX", (char *)NULL); goto error; } if (inputFile == NULL) { if (inputLiteral != NULL) { /* * The input for the first process is immediate data coming from * Tcl. Create a temporary file for it and put the data into the * file. */ inputFile = TclpCreateTempFile(inputLiteral); if (inputFile == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't create input file for command: %s", Tcl_PosixError(interp))); goto error; } inputClose = 1; } else if (inPipePtr != NULL) { /* * The input for the first process in the pipeline is to come from * a pipe that can be written from by the caller. */ if (TclpCreatePipe(&inputFile, inPipePtr) == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't create input pipe for command: %s", Tcl_PosixError(interp))); goto error; } inputClose = 1; } else { /* * The input for the first process comes from stdin. */ channel = Tcl_GetStdChannel(TCL_STDIN); if (channel != NULL) { inputFile = TclpMakeFile(channel, TCL_READABLE); if (inputFile != NULL) { inputRelease = 1; } } } } if (outputFile == NULL) { if (outPipePtr != NULL) { /* * Output from the last process in the pipeline is to go to a pipe * that can be read by the caller. */ if (TclpCreatePipe(outPipePtr, &outputFile) == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't create output pipe for command: %s", Tcl_PosixError(interp))); goto error; } outputClose = 1; } else { /* * The output for the last process goes to stdout. */ channel = Tcl_GetStdChannel(TCL_STDOUT); if (channel) { outputFile = TclpMakeFile(channel, TCL_WRITABLE); if (outputFile != NULL) { outputRelease = 1; } } } } if (errorFile == NULL) { if (errorToOutput == 2) { /* * Handle 2>@1 special case at end of cmd line. */ errorFile = outputFile; } else if (errFilePtr != NULL) { /* * Set up the standard error output sink for the pipeline, if * requested. Use a temporary file which is opened, then deleted. * Could potentially just use pipe, but if it filled up it could * cause the pipeline to deadlock: we'd be waiting for processes * to complete before reading stderr, and processes couldn't * complete because stderr was backed up. */ errorFile = TclpCreateTempFile(NULL); if (errorFile == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't create error file for command: %s", Tcl_PosixError(interp))); goto error; } *errFilePtr = errorFile; } else { /* * Errors from the pipeline go to stderr. */ channel = Tcl_GetStdChannel(TCL_STDERR); if (channel) { errorFile = TclpMakeFile(channel, TCL_WRITABLE); if (errorFile != NULL) { errorRelease = 1; } } } } /* * Scan through the argc array, creating a process for each group of * arguments between the "|" characters. */ Tcl_ReapDetachedProcs(); pidPtr = (Tcl_Pid *)Tcl_Alloc(cmdCount * sizeof(Tcl_Pid)); curInFile = inputFile; for (i = 0; i < argc; i = lastArg + 1) { int result, joinThisError; Tcl_Pid pid; const char *oldName; /* * Convert the program name into native form. */ if (Tcl_TranslateFileName(interp, argv[i], &execBuffer) == NULL) { goto error; } /* * Find the end of the current segment of the pipeline. */ joinThisError = 0; for (lastArg = i; lastArg < argc; lastArg++) { if (argv[lastArg][0] != '|') { continue; } if (argv[lastArg][1] == '\0') { break; } if ((argv[lastArg][1] == '&') && (argv[lastArg][2] == '\0')) { joinThisError = 1; break; } } /* * If this is the last segment, use the specified outputFile. * Otherwise create an intermediate pipe. pipeIn will become the * curInFile for the next segment of the pipe. */ if (lastArg == argc) { curOutFile = outputFile; } else { argv[lastArg] = NULL; if (TclpCreatePipe(&pipeIn, &curOutFile) == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't create pipe: %s", Tcl_PosixError(interp))); goto error; } } if (joinThisError != 0) { curErrFile = curOutFile; } else { curErrFile = errorFile; } /* * Restore argv[i], since a caller wouldn't expect the contents of * argv to be modified. */ oldName = argv[i]; argv[i] = Tcl_DStringValue(&execBuffer); result = TclpCreateProcess(interp, lastArg - i, argv + i, curInFile, curOutFile, curErrFile, &pid); argv[i] = oldName; if (result != TCL_OK) { goto error; } Tcl_DStringFree(&execBuffer); pidPtr[numPids] = pid; numPids++; TclProcessCreated(pid); /* * Close off our copies of file descriptors that were set up for this * child, then set up the input for the next child. */ if ((curInFile != NULL) && (curInFile != inputFile)) { TclpCloseFile(curInFile); } curInFile = pipeIn; pipeIn = NULL; if ((curOutFile != NULL) && (curOutFile != outputFile)) { TclpCloseFile(curOutFile); } curOutFile = NULL; } *pidArrayPtr = pidPtr; /* * All done. Cleanup open files lying around and then return. */ cleanup: Tcl_DStringFree(&execBuffer); if (inputClose) { TclpCloseFile(inputFile); } else if (inputRelease) { TclpReleaseFile(inputFile); } if (outputClose) { TclpCloseFile(outputFile); } else if (outputRelease) { TclpReleaseFile(outputFile); } if (errorClose) { TclpCloseFile(errorFile); } else if (errorRelease) { TclpReleaseFile(errorFile); } return numPids; /* * An error occurred. There could have been extra files open, such as * pipes between children. Clean them all up. Detach any child processes * that have been created. */ error: if (pipeIn != NULL) { TclpCloseFile(pipeIn); } if ((curOutFile != NULL) && (curOutFile != outputFile)) { TclpCloseFile(curOutFile); } if ((curInFile != NULL) && (curInFile != inputFile)) { TclpCloseFile(curInFile); } if ((inPipePtr != NULL) && (*inPipePtr != NULL)) { TclpCloseFile(*inPipePtr); *inPipePtr = NULL; } if ((outPipePtr != NULL) && (*outPipePtr != NULL)) { TclpCloseFile(*outPipePtr); *outPipePtr = NULL; } if ((errFilePtr != NULL) && (*errFilePtr != NULL)) { TclpCloseFile(*errFilePtr); *errFilePtr = NULL; } if (pidPtr != NULL) { for (i = 0; i < numPids; i++) { if (pidPtr[i] != (Tcl_Pid)-1) { Tcl_DetachPids(1, &pidPtr[i]); } } Tcl_Free(pidPtr); } numPids = -1; goto cleanup; } /* *---------------------------------------------------------------------- * * Tcl_OpenCommandChannel -- * * Opens an I/O channel to one or more subprocesses specified by argc and * argv. The flags argument determines the disposition of the stdio * handles. If the TCL_STDIN flag is set then the standard input for the * first subprocess will be tied to the channel: writing to the channel * will provide input to the subprocess. If TCL_STDIN is not set, then * standard input for the first subprocess will be the same as this * application's standard input. If TCL_STDOUT is set then standard * output from the last subprocess can be read from the channel; * otherwise it goes to this application's standard output. If TCL_STDERR * is set, standard error output for all subprocesses is returned to the * channel and results in an error when the channel is closed; otherwise * it goes to this application's standard error. If TCL_ENFORCE_MODE is * not set, then argc and argv can redirect the stdio handles to override * TCL_STDIN, TCL_STDOUT, and TCL_STDERR; if it is set, then it is an * error for argc and argv to override stdio channels for which * TCL_STDIN, TCL_STDOUT, and TCL_STDERR have been set. * * Results: * A new command channel, or NULL on failure with an error message left * in interp. * * Side effects: * Creates processes, opens pipes. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_OpenCommandChannel( Tcl_Interp *interp, /* Interpreter for error reporting. Can NOT be * NULL. */ Tcl_Size argc, /* How many arguments. */ const char **argv, /* Array of arguments for command pipe. */ int flags) /* Or'ed combination of TCL_STDIN, TCL_STDOUT, * TCL_STDERR, and TCL_ENFORCE_MODE. */ { TclFile *inPipePtr, *outPipePtr, *errFilePtr; TclFile inPipe, outPipe, errFile; Tcl_Size numPids; Tcl_Pid *pidPtr = NULL; Tcl_Channel channel; inPipe = outPipe = errFile = NULL; inPipePtr = (flags & TCL_STDIN) ? &inPipe : NULL; outPipePtr = (flags & TCL_STDOUT) ? &outPipe : NULL; errFilePtr = (flags & TCL_STDERR) ? &errFile : NULL; numPids = TclCreatePipeline(interp, argc, argv, &pidPtr, inPipePtr, outPipePtr, errFilePtr); if (numPids < 0) { goto error; } /* * Verify that the pipes that were created satisfy the readable/writable * constraints. */ if (flags & TCL_ENFORCE_MODE) { if ((flags & TCL_STDOUT) && (outPipe == NULL)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "can't read output from command:" " standard output was redirected", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", "BADREDIRECT", (char *)NULL); goto error; } if ((flags & TCL_STDIN) && (inPipe == NULL)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "can't write input to command:" " standard input was redirected", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", "BADREDIRECT", (char *)NULL); goto error; } } channel = TclpCreateCommandChannel(outPipe, inPipe, errFile, numPids, pidPtr); if (channel == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "pipe for command could not be created", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", "NOPIPE", (char *)NULL); goto error; } return channel; error: if (pidPtr) { Tcl_DetachPids(numPids, pidPtr); Tcl_Free(pidPtr); } if (inPipe != NULL) { TclpCloseFile(inPipe); } if (outPipe != NULL) { TclpCloseFile(outPipe); } if (errFile != NULL) { TclpCloseFile(errFile); } return NULL; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclPkg.c0000644000175000017500000017510514726623136014573 0ustar sergeisergei/* * tclPkg.c -- * * This file implements package and version control for Tcl via the * "package" command and a few C APIs. * * Copyright © 1996 Sun Microsystems, Inc. * Copyright © 2006 Andreas Kupries * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * * TIP #268. * Heavily rewritten to handle the extend version numbers, and extended * package requirements. */ #include "tclInt.h" MODULE_SCOPE char *tclEmptyStringRep; char *tclEmptyStringRep = &tclEmptyString; /* * Each invocation of the "package ifneeded" command creates a structure of * the following type, which is used to load the package into the interpreter * if it is requested with a "package require" command. */ typedef struct PkgAvail { char *version; /* Version string; malloc'ed. */ char *script; /* Script to invoke to provide this version of * the package. Malloc'ed and protected by * Tcl_Preserve and Tcl_Release. */ char *pkgIndex; /* Full file name of pkgIndex file */ struct PkgAvail *nextPtr; /* Next in list of available versions of the * same package. */ } PkgAvail; typedef struct PkgName { struct PkgName *nextPtr; /* Next in list of package names being * initialized. */ char name[TCLFLEXARRAY]; } PkgName; typedef struct PkgFiles { PkgName *names; /* Package names being initialized. Must be * first field. */ Tcl_HashTable table; /* Table which contains files for each * package. */ } PkgFiles; /* * For each package that is known in any way to an interpreter, there is one * record of the following type. These records are stored in the * "packageTable" hash table in the interpreter, keyed by package name such as * "Tk" (no version number). */ typedef struct { Tcl_Obj *version; PkgAvail *availPtr; /* First in list of all available versions of * this package. */ const void *clientData; /* Client data. */ } Package; typedef struct Require { void *clientDataPtr; const char *name; Package *pkgPtr; char *versionToProvide; } Require; typedef struct RequireProcArgs { const char *name; void *clientDataPtr; } RequireProcArgs; /* * Prototypes for functions defined in this file: */ static int CheckVersionAndConvert(Tcl_Interp *interp, const char *string, char **internal, int *stable); static int CompareVersions(char *v1i, char *v2i, int *isMajorPtr); static int CheckRequirement(Tcl_Interp *interp, const char *string); static int CheckAllRequirements(Tcl_Interp *interp, int reqc, Tcl_Obj *const reqv[]); static int RequirementSatisfied(char *havei, const char *req); static int SomeRequirementSatisfied(char *havei, int reqc, Tcl_Obj *const reqv[]); static void AddRequirementsToResult(Tcl_Interp *interp, int reqc, Tcl_Obj *const reqv[]); static void AddRequirementsToDString(Tcl_DString *dstring, int reqc, Tcl_Obj *const reqv[]); static Package * FindPackage(Tcl_Interp *interp, const char *name); static int PkgRequireCore(void *data[], Tcl_Interp *interp, int result); static int PkgRequireCoreFinal(void *data[], Tcl_Interp *interp, int result); static int PkgRequireCoreCleanup(void *data[], Tcl_Interp *interp, int result); static int PkgRequireCoreStep1(void *data[], Tcl_Interp *interp, int result); static int PkgRequireCoreStep2(void *data[], Tcl_Interp *interp, int result); static int TclNRPkgRequireProc(void *clientData, Tcl_Interp *interp, int reqc, Tcl_Obj *const reqv[]); static int SelectPackage(void *data[], Tcl_Interp *interp, int result); static int SelectPackageFinal(void *data[], Tcl_Interp *interp, int result); static int TclNRPackageObjCmdCleanup(void *data[], Tcl_Interp *interp, int result); /* * Helper macros. */ #define DupBlock(v,s,len) \ ((v) = (char *)Tcl_Alloc(len), memcpy((v),(s),(len))) #define DupString(v,s) \ do { \ size_t local__len = strlen(s) + 1; \ DupBlock((v),(s),local__len); \ } while (0) /* *---------------------------------------------------------------------- * * Tcl_PkgProvide / Tcl_PkgProvideEx -- * * This function is invoked to declare that a particular version of a * particular package is now present in an interpreter. There must not be * any other version of this package already provided in the interpreter. * * Results: * Normally returns TCL_OK; if there is already another version of the * package loaded then TCL_ERROR is returned and an error message is left * in the interp's result. * * Side effects: * The interpreter remembers that this package is available, so that no * other version of the package may be provided for the interpreter. * *---------------------------------------------------------------------- */ #undef Tcl_PkgProvide int Tcl_PkgProvide( Tcl_Interp *interp, /* Interpreter in which package is now * available. */ const char *name, /* Name of package. */ const char *version) /* Version string for package. */ { return Tcl_PkgProvideEx(interp, name, version, NULL); } int Tcl_PkgProvideEx( Tcl_Interp *interp, /* Interpreter in which package is now * available. */ const char *name, /* Name of package. */ const char *version, /* Version string for package. */ const void *clientData) /* clientdata for this package (normally used * for C callback function table) */ { Package *pkgPtr; char *pvi, *vi; int res; pkgPtr = FindPackage(interp, name); if (pkgPtr->version == NULL) { pkgPtr->version = Tcl_NewStringObj(version, -1); Tcl_IncrRefCount(pkgPtr->version); pkgPtr->clientData = clientData; return TCL_OK; } if (CheckVersionAndConvert(interp, Tcl_GetString(pkgPtr->version), &pvi, NULL) != TCL_OK) { return TCL_ERROR; } else if (CheckVersionAndConvert(interp, version, &vi, NULL) != TCL_OK) { Tcl_Free(pvi); return TCL_ERROR; } res = CompareVersions(pvi, vi, NULL); Tcl_Free(pvi); Tcl_Free(vi); if (res == 0) { if (clientData != NULL) { pkgPtr->clientData = clientData; } return TCL_OK; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "conflicting versions provided for package \"%s\": %s, then %s", name, Tcl_GetString(pkgPtr->version), version)); Tcl_SetErrorCode(interp, "TCL", "PACKAGE", "VERSIONCONFLICT", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_PkgRequire / Tcl_PkgRequireEx / Tcl_PkgRequireProc -- * * This function is called by code that depends on a particular version * of a particular package. If the package is not already provided in the * interpreter, this function invokes a Tcl script to provide it. If the * package is already provided, this function makes sure that the * caller's needs don't conflict with the version that is present. * * Results: * If successful, returns the version string for the currently provided * version of the package, which may be different from the "version" * argument. If the caller's requirements cannot be met (e.g. the version * requested conflicts with a currently provided version, or the required * version cannot be found, or the script to provide the required version * generates an error), NULL is returned and an error message is left in * the interp's result. * * Side effects: * The script from some previous "package ifneeded" command may be * invoked to provide the package. * *---------------------------------------------------------------------- */ static void PkgFilesCleanupProc( void *clientData, TCL_UNUSED(Tcl_Interp *)) { PkgFiles *pkgFiles = (PkgFiles *) clientData; Tcl_HashSearch search; Tcl_HashEntry *entry; while (pkgFiles->names) { PkgName *name = pkgFiles->names; pkgFiles->names = name->nextPtr; Tcl_Free(name); } entry = Tcl_FirstHashEntry(&pkgFiles->table, &search); while (entry) { Tcl_Obj *obj = (Tcl_Obj *)Tcl_GetHashValue(entry); Tcl_DecrRefCount(obj); entry = Tcl_NextHashEntry(&search); } Tcl_DeleteHashTable(&pkgFiles->table); Tcl_Free(pkgFiles); return; } void * TclInitPkgFiles( Tcl_Interp *interp) { /* * If assocdata "tclPkgFiles" doesn't exist yet, create it. */ PkgFiles *pkgFiles = (PkgFiles *)Tcl_GetAssocData(interp, "tclPkgFiles", NULL); if (!pkgFiles) { pkgFiles = (PkgFiles *)Tcl_Alloc(sizeof(PkgFiles)); pkgFiles->names = NULL; Tcl_InitHashTable(&pkgFiles->table, TCL_STRING_KEYS); Tcl_SetAssocData(interp, "tclPkgFiles", PkgFilesCleanupProc, pkgFiles); } return pkgFiles; } void TclPkgFileSeen( Tcl_Interp *interp, const char *fileName) { PkgFiles *pkgFiles = (PkgFiles *) Tcl_GetAssocData(interp, "tclPkgFiles", NULL); if (pkgFiles && pkgFiles->names) { const char *name = pkgFiles->names->name; Tcl_HashTable *table = &pkgFiles->table; int isNew; Tcl_HashEntry *entry = (Tcl_HashEntry *)Tcl_CreateHashEntry(table, name, &isNew); Tcl_Obj *list; if (isNew) { TclNewObj(list); Tcl_SetHashValue(entry, list); Tcl_IncrRefCount(list); } else { list = (Tcl_Obj *)Tcl_GetHashValue(entry); } Tcl_ListObjAppendElement(interp, list, Tcl_NewStringObj(fileName, -1)); } } #undef Tcl_PkgRequire const char * Tcl_PkgRequire( Tcl_Interp *interp, /* Interpreter in which package is now * available. */ const char *name, /* Name of desired package. */ const char *version, /* Version string for desired version; NULL * means use the latest version available. */ int exact) /* Non-zero means that only the particular * version given is acceptable. Zero means use * the latest compatible version. */ { return Tcl_PkgRequireEx(interp, name, version, exact, NULL); } const char * Tcl_PkgRequireEx( Tcl_Interp *interp, /* Interpreter in which package is now * available. */ const char *name, /* Name of desired package. */ const char *version, /* Version string for desired version; NULL * means use the latest version available. */ int exact, /* Non-zero means that only the particular * version given is acceptable. Zero means use * the latest compatible version. */ void *clientDataPtr) /* Used to return the client data for this * package. If it is NULL then the client data * is not returned. This is unchanged if this * call fails for any reason. */ { Tcl_Obj *ov; const char *result = NULL; /* * If an attempt is being made to load this into a standalone executable * on a platform where backlinking is not supported then this must be a * shared version of Tcl (Otherwise the load would have failed). Detect * this situation by checking that this library has been correctly * initialised. If it has not been then return immediately as nothing will * work. */ if (tclEmptyStringRep == NULL) { /* * OK, so what's going on here? * * First, what are we doing? We are performing a check on behalf of * one particular caller, Tcl_InitStubs(). When a package is stub- * enabled, it is statically linked to libtclstub.a, which contains a * copy of Tcl_InitStubs(). When a stub-enabled package is loaded, its * *_Init() function is supposed to call Tcl_InitStubs() before * calling any other functions in the Tcl library. The first Tcl * function called by Tcl_InitStubs() through the stub table is * Tcl_PkgRequireEx(), so this code right here is the first code that * is part of the original Tcl library in the executable that gets * executed on behalf of a newly loaded stub-enabled package. * * One easy error for the developer/builder of a stub-enabled package * to make is to forget to define USE_TCL_STUBS when compiling the * package. When that happens, the package will contain symbols that * are references to the Tcl library, rather than function pointers * referencing the stub table. On platforms that lack backlinking, * those unresolved references may cause the loading of the package to * also load a second copy of the Tcl library, leading to all kinds of * trouble. We would like to catch that error and report a useful * message back to the user. That's what we're doing. * * Second, how does this work? If we reach this point, then the global * variable tclEmptyStringRep has the value NULL. Compare that with * the definition of tclEmptyStringRep near the top of this file. It * clearly should not have the value NULL; it should point to the char * tclEmptyString. If we see it having the value NULL, then somehow we * are seeing a Tcl library that isn't completely initialized, and * that's an indicator for the error condition described above. * (Further explanation is welcome.) * * Third, so what do we do about it? This situation indicates the * package we just loaded wasn't properly compiled to be stub-enabled, * yet it thinks it is stub-enabled (it called Tcl_InitStubs()). We * want to report that the package just loaded is broken, so we want * to place an error message in the interpreter result and return NULL * to indicate failure to Tcl_InitStubs() so that it will also fail. * (Further explanation why we don't want to Tcl_Panic() is welcome. * After all, two Tcl libraries can't be a good thing!) * * Trouble is that's going to be tricky. We're now using a Tcl library * that's not fully initialized. Functions in it may not work * reliably, so be very careful about adding any other calls here * without checking how they behave when initialization is incomplete. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Cannot load package \"%s\" in standalone executable:" " This package is not compiled with stub support", name)); Tcl_SetErrorCode(interp, "TCL", "PACKAGE", "UNSTUBBED", (char *)NULL); return NULL; } /* * Translate between old and new API, and defer to the new function. */ if (version == NULL) { if (Tcl_PkgRequireProc(interp, name, 0, NULL, clientDataPtr) == TCL_OK) { result = Tcl_GetStringResult(interp); Tcl_ResetResult(interp); } } else { if (exact && TCL_OK != CheckVersionAndConvert(interp, version, NULL, NULL)) { return NULL; } ov = Tcl_NewStringObj(version, -1); if (exact) { Tcl_AppendStringsToObj(ov, "-", version, (char *)NULL); } Tcl_IncrRefCount(ov); if (Tcl_PkgRequireProc(interp, name, 1, &ov, clientDataPtr) == TCL_OK) { result = Tcl_GetStringResult(interp); Tcl_ResetResult(interp); } TclDecrRefCount(ov); } return result; } int Tcl_PkgRequireProc( Tcl_Interp *interp, /* Interpreter in which package is now * available. */ const char *name, /* Name of desired package. */ Tcl_Size reqc, /* Requirements constraining the desired * version. */ Tcl_Obj *const reqv[], /* 0 means to use the latest version * available. */ void *clientDataPtr) { RequireProcArgs args; args.name = name; args.clientDataPtr = clientDataPtr; return Tcl_NRCallObjProc(interp, TclNRPkgRequireProc, (void *) &args, reqc, reqv); } static int TclNRPkgRequireProc( void *clientData, Tcl_Interp *interp, int reqc, Tcl_Obj *const reqv[]) { RequireProcArgs *args = (RequireProcArgs *)clientData; Tcl_NRAddCallback(interp, PkgRequireCore, (void *) args->name, INT2PTR(reqc), (void *) reqv, args->clientDataPtr); return TCL_OK; } static int PkgRequireCore( void *data[], Tcl_Interp *interp, TCL_UNUSED(int)) { const char *name = (const char *)data[0]; int reqc = (int)PTR2INT(data[1]); Tcl_Obj **reqv = (Tcl_Obj **)data[2]; int code = CheckAllRequirements(interp, reqc, reqv); Require *reqPtr; if (code != TCL_OK) { return code; } reqPtr = (Require *)Tcl_Alloc(sizeof(Require)); Tcl_NRAddCallback(interp, PkgRequireCoreCleanup, reqPtr, NULL, NULL, NULL); reqPtr->clientDataPtr = data[3]; reqPtr->name = name; reqPtr->pkgPtr = FindPackage(interp, name); if (reqPtr->pkgPtr->version == NULL) { Tcl_NRAddCallback(interp, SelectPackage, reqPtr, INT2PTR(reqc), reqv, (void *)PkgRequireCoreStep1); } else { Tcl_NRAddCallback(interp, PkgRequireCoreFinal, reqPtr, INT2PTR(reqc), reqv, NULL); } return TCL_OK; } static int PkgRequireCoreStep1( void *data[], Tcl_Interp *interp, TCL_UNUSED(int)) { Tcl_DString command; char *script; Require *reqPtr = (Require *)data[0]; int reqc = (int)PTR2INT(data[1]); Tcl_Obj **const reqv = (Tcl_Obj **)data[2]; const char *name = reqPtr->name /* Name of desired package. */; /* * If we've got the package in the DB already, go on to actually loading * it. */ if (reqPtr->pkgPtr->version != NULL) { Tcl_NRAddCallback(interp, PkgRequireCoreFinal, reqPtr, INT2PTR(reqc), (void *)reqv, NULL); return TCL_OK; } /* * The package is not in the database. If there is a "package unknown" * command, invoke it. */ script = ((Interp *) interp)->packageUnknown; if (script == NULL) { /* * No package unknown script. Move on to finalizing. */ Tcl_NRAddCallback(interp, PkgRequireCoreFinal, reqPtr, INT2PTR(reqc), (void *)reqv, NULL); return TCL_OK; } /* * Invoke the "package unknown" script synchronously. */ Tcl_DStringInit(&command); Tcl_DStringAppend(&command, script, -1); Tcl_DStringAppendElement(&command, name); AddRequirementsToDString(&command, reqc, reqv); Tcl_NRAddCallback(interp, PkgRequireCoreStep2, reqPtr, INT2PTR(reqc), (void *) reqv, NULL); Tcl_NREvalObj(interp, Tcl_DStringToObj(&command), TCL_EVAL_GLOBAL); return TCL_OK; } static int PkgRequireCoreStep2( void *data[], Tcl_Interp *interp, int result) { Require *reqPtr = (Require *)data[0]; int reqc = (int)PTR2INT(data[1]); Tcl_Obj **const reqv = (Tcl_Obj **)data[2]; const char *name = reqPtr->name; /* Name of desired package. */ if ((result != TCL_OK) && (result != TCL_ERROR)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad return code: %d", result)); Tcl_SetErrorCode(interp, "TCL", "PACKAGE", "BADRESULT", (char *)NULL); result = TCL_ERROR; } if (result == TCL_ERROR) { Tcl_AddErrorInfo(interp, "\n (\"package unknown\" script)"); return result; } Tcl_ResetResult(interp); /* * pkgPtr may now be invalid, so refresh it. */ reqPtr->pkgPtr = FindPackage(interp, name); Tcl_NRAddCallback(interp, SelectPackage, reqPtr, INT2PTR(reqc), reqv, (void *)PkgRequireCoreFinal); return TCL_OK; } static int PkgRequireCoreFinal( void *data[], Tcl_Interp *interp, TCL_UNUSED(int)) { Require *reqPtr = (Require *)data[0]; int reqc = (int)PTR2INT(data[1]), satisfies; Tcl_Obj **const reqv = (Tcl_Obj **)data[2]; char *pkgVersionI; void *clientDataPtr = reqPtr->clientDataPtr; const char *name = reqPtr->name; /* Name of desired package. */ if (reqPtr->pkgPtr->version == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't find package %s", name)); Tcl_SetErrorCode(interp, "TCL", "PACKAGE", "UNFOUND", (char *)NULL); AddRequirementsToResult(interp, reqc, reqv); return TCL_ERROR; } /* * Ensure that the provided version meets the current requirements. */ if (reqc != 0) { CheckVersionAndConvert(interp, Tcl_GetString(reqPtr->pkgPtr->version), &pkgVersionI, NULL); satisfies = SomeRequirementSatisfied(pkgVersionI, reqc, reqv); Tcl_Free(pkgVersionI); if (!satisfies) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "version conflict for package \"%s\": have %s, need", name, Tcl_GetString(reqPtr->pkgPtr->version))); Tcl_SetErrorCode(interp, "TCL", "PACKAGE", "VERSIONCONFLICT", (char *)NULL); AddRequirementsToResult(interp, reqc, reqv); return TCL_ERROR; } } if (clientDataPtr) { const void **ptr = (const void **) clientDataPtr; *ptr = reqPtr->pkgPtr->clientData; } Tcl_SetObjResult(interp, reqPtr->pkgPtr->version); return TCL_OK; } static int PkgRequireCoreCleanup( void *data[], TCL_UNUSED(Tcl_Interp *), int result) { Tcl_Free(data[0]); return result; } static int SelectPackage( void *data[], Tcl_Interp *interp, TCL_UNUSED(int)) { PkgAvail *availPtr, *bestPtr, *bestStablePtr; char *availVersion, *bestVersion, *bestStableVersion; /* Internal rep. of versions */ int availStable, satisfies; Require *reqPtr = (Require *)data[0]; int reqc = (int)PTR2INT(data[1]); Tcl_Obj **const reqv = (Tcl_Obj **)data[2]; const char *name = reqPtr->name; Package *pkgPtr = reqPtr->pkgPtr; Interp *iPtr = (Interp *) interp; /* * Check whether we're already attempting to load some version of this * package (circular dependency detection). */ if (pkgPtr->clientData != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "circular package dependency:" " attempt to provide %s %s requires %s", name, (char *) pkgPtr->clientData, name)); AddRequirementsToResult(interp, reqc, reqv); Tcl_SetErrorCode(interp, "TCL", "PACKAGE", "CIRCULARITY", (char *)NULL); return TCL_ERROR; } /* * The package isn't yet present. Search the list of available versions * and invoke the script for the best available version. We are actually * locating the best, and the best stable version. One of them is then * chosen based on the selection mode. */ bestPtr = NULL; bestStablePtr = NULL; bestVersion = NULL; bestStableVersion = NULL; for (availPtr = pkgPtr->availPtr; availPtr != NULL; availPtr = availPtr->nextPtr) { if (CheckVersionAndConvert(interp, availPtr->version, &availVersion, &availStable) != TCL_OK) { /* * The provided version number has invalid syntax. This should not * happen. This should have been caught by the 'package ifneeded' * registering the package. */ continue; } /* * Check satisfaction of requirements before considering the current * version further. */ if (reqc > 0) { satisfies = SomeRequirementSatisfied(availVersion, reqc, reqv); if (!satisfies) { Tcl_Free(availVersion); availVersion = NULL; continue; } } if (bestPtr != NULL) { int res = CompareVersions(availVersion, bestVersion, NULL); /* * Note: Used internal reps in the comparison! */ if (res > 0) { /* * The version of the package sought is better than the * currently selected version. */ Tcl_Free(bestVersion); bestVersion = NULL; goto newbest; } } else { newbest: /* * We have found a version which is better than our max. */ bestPtr = availPtr; CheckVersionAndConvert(interp, bestPtr->version, &bestVersion, NULL); } if (!availStable) { Tcl_Free(availVersion); availVersion = NULL; continue; } if (bestStablePtr != NULL) { int res = CompareVersions(availVersion, bestStableVersion, NULL); /* * Note: Used internal reps in the comparison! */ if (res > 0) { /* * This stable version of the package sought is better than * the currently selected stable version. */ Tcl_Free(bestStableVersion); bestStableVersion = NULL; goto newstable; } } else { newstable: /* * We have found a stable version which is better than our max * stable. */ bestStablePtr = availPtr; CheckVersionAndConvert(interp, bestStablePtr->version, &bestStableVersion, NULL); } Tcl_Free(availVersion); availVersion = NULL; } /* end for */ /* * Clean up memorized internal reps, if any. */ if (bestVersion != NULL) { Tcl_Free(bestVersion); bestVersion = NULL; } if (bestStableVersion != NULL) { Tcl_Free(bestStableVersion); bestStableVersion = NULL; } /* * Now choose a version among the two best. For 'latest' we simply take * (actually keep) the best. For 'stable' we take the best stable, if * there is any, or the best if there is nothing stable. */ if ((iPtr->packagePrefer == PKG_PREFER_STABLE) && (bestStablePtr != NULL)) { bestPtr = bestStablePtr; } if (bestPtr == NULL) { Tcl_NRAddCallback(interp, (Tcl_NRPostProc *)data[3], reqPtr, INT2PTR(reqc), (void *)reqv, NULL); } else { /* * We found an ifneeded script for the package. Be careful while * executing it: this could cause reentrancy, so (a) protect the * script itself from deletion and (b) don't assume that bestPtr will * still exist when the script completes. */ char *versionToProvide = bestPtr->version; PkgFiles *pkgFiles; PkgName *pkgName; Tcl_Preserve(versionToProvide); pkgPtr->clientData = versionToProvide; pkgFiles = (PkgFiles *)TclInitPkgFiles(interp); /* * Push "ifneeded" package name in "tclPkgFiles" assocdata. */ pkgName = (PkgName *)Tcl_Alloc(offsetof(PkgName, name) + 1 + strlen(name)); pkgName->nextPtr = pkgFiles->names; strcpy(pkgName->name, name); pkgFiles->names = pkgName; if (bestPtr->pkgIndex) { TclPkgFileSeen(interp, bestPtr->pkgIndex); } reqPtr->versionToProvide = versionToProvide; Tcl_NRAddCallback(interp, SelectPackageFinal, reqPtr, INT2PTR(reqc), (void *)reqv, data[3]); Tcl_NREvalObj(interp, Tcl_NewStringObj(bestPtr->script, -1), TCL_EVAL_GLOBAL); } return TCL_OK; } static int SelectPackageFinal( void *data[], Tcl_Interp *interp, int result) { Require *reqPtr = (Require *)data[0]; int reqc = (int)PTR2INT(data[1]); Tcl_Obj **const reqv = (Tcl_Obj **)data[2]; const char *name = reqPtr->name; char *versionToProvide = reqPtr->versionToProvide; /* * Pop the "ifneeded" package name from "tclPkgFiles" assocdata */ PkgFiles *pkgFiles = (PkgFiles *)Tcl_GetAssocData(interp, "tclPkgFiles", NULL); PkgName *pkgName = pkgFiles->names; pkgFiles->names = pkgName->nextPtr; Tcl_Free(pkgName); reqPtr->pkgPtr = FindPackage(interp, name); if (result == TCL_OK) { Tcl_ResetResult(interp); if (reqPtr->pkgPtr->version == NULL) { result = TCL_ERROR; Tcl_SetObjResult(interp, Tcl_ObjPrintf( "attempt to provide package %s %s failed:" " no version of package %s provided", name, versionToProvide, name)); Tcl_SetErrorCode(interp, "TCL", "PACKAGE", "UNPROVIDED", (char *)NULL); } else { char *pvi, *vi; if (TCL_OK != CheckVersionAndConvert(interp, Tcl_GetString(reqPtr->pkgPtr->version), &pvi, NULL)) { result = TCL_ERROR; } else if (CheckVersionAndConvert(interp, versionToProvide, &vi, NULL) != TCL_OK) { Tcl_Free(pvi); result = TCL_ERROR; } else { int res = CompareVersions(pvi, vi, NULL); Tcl_Free(pvi); Tcl_Free(vi); if (res != 0) { result = TCL_ERROR; Tcl_SetObjResult(interp, Tcl_ObjPrintf( "attempt to provide package %s %s failed:" " package %s %s provided instead", name, versionToProvide, name, Tcl_GetString(reqPtr->pkgPtr->version))); Tcl_SetErrorCode(interp, "TCL", "PACKAGE", "WRONGPROVIDE", (char *)NULL); } } } } else if (result != TCL_ERROR) { Tcl_Obj *codePtr; TclNewIntObj(codePtr, result); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "attempt to provide package %s %s failed:" " bad return code: %s", name, versionToProvide, TclGetString(codePtr))); Tcl_SetErrorCode(interp, "TCL", "PACKAGE", "BADRESULT", (char *)NULL); TclDecrRefCount(codePtr); result = TCL_ERROR; } if (result == TCL_ERROR) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (\"package ifneeded %s %s\" script)", name, versionToProvide)); } Tcl_Release(versionToProvide); if (result != TCL_OK) { /* * Take a non-TCL_OK code from the script as an indication the package * wasn't loaded properly, so the package system should not remember * an improper load. * * This is consistent with our returning NULL. If we're not willing to * tell our caller we got a particular version, we shouldn't store * that version for telling future callers either. */ if (reqPtr->pkgPtr->version != NULL) { Tcl_DecrRefCount(reqPtr->pkgPtr->version); reqPtr->pkgPtr->version = NULL; } reqPtr->pkgPtr->clientData = NULL; return result; } Tcl_NRAddCallback(interp, (Tcl_NRPostProc *)data[3], reqPtr, INT2PTR(reqc), (void *) reqv, NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_PkgPresent / Tcl_PkgPresentEx -- * * Checks to see whether the specified package is present. If it is not * then no additional action is taken. * * Results: * If successful, returns the version string for the currently provided * version of the package, which may be different from the "version" * argument. If the caller's requirements cannot be met (e.g. the version * requested conflicts with a currently provided version), NULL is * returned and an error message is left in interp->result. * * Side effects: * None. * *---------------------------------------------------------------------- */ #undef Tcl_PkgPresent const char * Tcl_PkgPresent( Tcl_Interp *interp, /* Interpreter in which package is now * available. */ const char *name, /* Name of desired package. */ const char *version, /* Version string for desired version; NULL * means use the latest version available. */ int exact) /* Non-zero means that only the particular * version given is acceptable. Zero means use * the latest compatible version. */ { return Tcl_PkgPresentEx(interp, name, version, exact, NULL); } const char * Tcl_PkgPresentEx( Tcl_Interp *interp, /* Interpreter in which package is now * available. */ const char *name, /* Name of desired package. */ const char *version, /* Version string for desired version; NULL * means use the latest version available. */ int exact, /* Non-zero means that only the particular * version given is acceptable. Zero means use * the latest compatible version. */ void *clientDataPtr) /* Used to return the client data for this * package. If it is NULL then the client data * is not returned. This is unchanged if this * call fails for any reason. */ { Interp *iPtr = (Interp *) interp; Tcl_HashEntry *hPtr; Package *pkgPtr; hPtr = Tcl_FindHashEntry(&iPtr->packageTable, name); if (hPtr) { pkgPtr = (Package *)Tcl_GetHashValue(hPtr); if (pkgPtr->version != NULL) { /* * At this point we know that the package is present. Make sure * that the provided version meets the current requirement by * calling Tcl_PkgRequireEx() to check for us. */ const char *foundVersion = Tcl_PkgRequireEx(interp, name, version, exact, clientDataPtr); if (foundVersion == NULL) { Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "PACKAGE", name, (char *)NULL); } return foundVersion; } } if (version != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "package %s %s is not present", name, version)); } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "package %s is not present", name)); } Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "PACKAGE", name, (char *)NULL); return NULL; } /* *---------------------------------------------------------------------- * * Tcl_PackageObjCmd -- * * This function is invoked to process the "package" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_PackageObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRPackageObjCmd, clientData, objc, objv); } int TclNRPackageObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const pkgOptions[] = { "files", "forget", "ifneeded", "names", "prefer", "present", "provide", "require", "unknown", "vcompare", "versions", "vsatisfies", NULL }; enum pkgOptionsEnum { PKG_FILES, PKG_FORGET, PKG_IFNEEDED, PKG_NAMES, PKG_PREFER, PKG_PRESENT, PKG_PROVIDE, PKG_REQUIRE, PKG_UNKNOWN, PKG_VCOMPARE, PKG_VERSIONS, PKG_VSATISFIES } optionIndex; Interp *iPtr = (Interp *) interp; int exact, satisfies; Tcl_Size i, newobjc; PkgAvail *availPtr, *prevPtr; Package *pkgPtr; Tcl_HashEntry *hPtr; Tcl_HashSearch search; Tcl_HashTable *tablePtr; const char *version; const char *argv2, *argv3, *argv4; char *iva = NULL, *ivb = NULL; Tcl_Obj *objvListPtr, **newObjvPtr; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], pkgOptions, "option", 0, &optionIndex) != TCL_OK) { return TCL_ERROR; } switch (optionIndex) { case PKG_FILES: { PkgFiles *pkgFiles; if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "package"); return TCL_ERROR; } pkgFiles = (PkgFiles *) Tcl_GetAssocData(interp, "tclPkgFiles", NULL); if (pkgFiles) { Tcl_HashEntry *entry = Tcl_FindHashEntry(&pkgFiles->table, TclGetString(objv[2])); if (entry) { Tcl_SetObjResult(interp, (Tcl_Obj *)Tcl_GetHashValue(entry)); } } break; } case PKG_FORGET: { const char *keyString; PkgFiles *pkgFiles = (PkgFiles *) Tcl_GetAssocData(interp, "tclPkgFiles", NULL); for (i = 2; i < objc; i++) { keyString = TclGetString(objv[i]); if (pkgFiles) { hPtr = Tcl_FindHashEntry(&pkgFiles->table, keyString); if (hPtr) { Tcl_Obj *obj = (Tcl_Obj *)Tcl_GetHashValue(hPtr); Tcl_DeleteHashEntry(hPtr); Tcl_DecrRefCount(obj); } } hPtr = Tcl_FindHashEntry(&iPtr->packageTable, keyString); if (hPtr == NULL) { continue; } pkgPtr = (Package *)Tcl_GetHashValue(hPtr); Tcl_DeleteHashEntry(hPtr); if (pkgPtr->version != NULL) { Tcl_DecrRefCount(pkgPtr->version); } while (pkgPtr->availPtr != NULL) { availPtr = pkgPtr->availPtr; pkgPtr->availPtr = availPtr->nextPtr; Tcl_EventuallyFree(availPtr->version, TCL_DYNAMIC); Tcl_EventuallyFree(availPtr->script, TCL_DYNAMIC); if (availPtr->pkgIndex) { Tcl_EventuallyFree(availPtr->pkgIndex, TCL_DYNAMIC); availPtr->pkgIndex = NULL; } Tcl_Free(availPtr); } Tcl_Free(pkgPtr); } break; } case PKG_IFNEEDED: { Tcl_Size length; int res; char *argv3i, *avi; if ((objc != 4) && (objc != 5)) { Tcl_WrongNumArgs(interp, 2, objv, "package version ?script?"); return TCL_ERROR; } argv3 = TclGetString(objv[3]); if (CheckVersionAndConvert(interp, argv3, &argv3i, NULL) != TCL_OK) { return TCL_ERROR; } argv2 = TclGetString(objv[2]); if (objc == 4) { hPtr = Tcl_FindHashEntry(&iPtr->packageTable, argv2); if (hPtr == NULL) { Tcl_Free(argv3i); return TCL_OK; } pkgPtr = (Package *)Tcl_GetHashValue(hPtr); } else { pkgPtr = FindPackage(interp, argv2); } argv3 = TclGetStringFromObj(objv[3], &length); for (availPtr = pkgPtr->availPtr, prevPtr = NULL; availPtr != NULL; prevPtr = availPtr, availPtr = availPtr->nextPtr) { if (CheckVersionAndConvert(interp, availPtr->version, &avi, NULL) != TCL_OK) { Tcl_Free(argv3i); return TCL_ERROR; } res = CompareVersions(avi, argv3i, NULL); Tcl_Free(avi); if (res == 0) { if (objc == 4) { Tcl_Free(argv3i); Tcl_SetObjResult(interp, Tcl_NewStringObj(availPtr->script, -1)); return TCL_OK; } Tcl_EventuallyFree(availPtr->script, TCL_DYNAMIC); if (availPtr->pkgIndex) { Tcl_EventuallyFree(availPtr->pkgIndex, TCL_DYNAMIC); availPtr->pkgIndex = NULL; } break; } } Tcl_Free(argv3i); if (objc == 4) { return TCL_OK; } if (availPtr == NULL) { availPtr = (PkgAvail *)Tcl_Alloc(sizeof(PkgAvail)); availPtr->pkgIndex = NULL; DupBlock(availPtr->version, argv3, length + 1); if (prevPtr == NULL) { availPtr->nextPtr = pkgPtr->availPtr; pkgPtr->availPtr = availPtr; } else { availPtr->nextPtr = prevPtr->nextPtr; prevPtr->nextPtr = availPtr; } } if (iPtr->scriptFile) { argv4 = TclGetStringFromObj(iPtr->scriptFile, &length); DupBlock(availPtr->pkgIndex, argv4, length + 1); } argv4 = TclGetStringFromObj(objv[4], &length); DupBlock(availPtr->script, argv4, length + 1); break; } case PKG_NAMES: if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } else { Tcl_Obj *resultObj; TclNewObj(resultObj); tablePtr = &iPtr->packageTable; for (hPtr = Tcl_FirstHashEntry(tablePtr, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { pkgPtr = (Package *)Tcl_GetHashValue(hPtr); if ((pkgPtr->version != NULL) || (pkgPtr->availPtr != NULL)) { Tcl_ListObjAppendElement(NULL,resultObj, Tcl_NewStringObj( (char *)Tcl_GetHashKey(tablePtr, hPtr), -1)); } } Tcl_SetObjResult(interp, resultObj); } break; case PKG_PRESENT: { const char *name; if (objc < 3) { goto require; } argv2 = TclGetString(objv[2]); if ((argv2[0] == '-') && (strcmp(argv2, "-exact") == 0)) { if (objc != 5) { goto requireSyntax; } exact = 1; name = TclGetString(objv[3]); } else { exact = 0; name = argv2; } hPtr = Tcl_FindHashEntry(&iPtr->packageTable, name); if (hPtr != NULL) { pkgPtr = (Package *)Tcl_GetHashValue(hPtr); if (pkgPtr->version != NULL) { goto require; } } version = NULL; if (exact) { version = TclGetString(objv[4]); if (CheckVersionAndConvert(interp, version, NULL, NULL) != TCL_OK) { return TCL_ERROR; } } else { if (CheckAllRequirements(interp, objc-3, objv+3) != TCL_OK) { return TCL_ERROR; } if ((objc > 3) && (CheckVersionAndConvert(interp, TclGetString(objv[3]), NULL, NULL) == TCL_OK)) { version = TclGetString(objv[3]); } } Tcl_PkgPresentEx(interp, name, version, exact, NULL); return TCL_ERROR; break; } case PKG_PROVIDE: if ((objc != 3) && (objc != 4)) { Tcl_WrongNumArgs(interp, 2, objv, "package ?version?"); return TCL_ERROR; } argv2 = TclGetString(objv[2]); if (objc == 3) { hPtr = Tcl_FindHashEntry(&iPtr->packageTable, argv2); if (hPtr != NULL) { pkgPtr = (Package *)Tcl_GetHashValue(hPtr); if (pkgPtr->version != NULL) { Tcl_SetObjResult(interp, pkgPtr->version); } } return TCL_OK; } argv3 = TclGetString(objv[3]); if (CheckVersionAndConvert(interp, argv3, NULL, NULL) != TCL_OK) { return TCL_ERROR; } return Tcl_PkgProvideEx(interp, argv2, argv3, NULL); case PKG_REQUIRE: require: if (objc < 3) { requireSyntax: Tcl_WrongNumArgs(interp, 2, objv, "?-exact? package ?requirement ...?"); return TCL_ERROR; } version = NULL; argv2 = TclGetString(objv[2]); if ((argv2[0] == '-') && (strcmp(argv2, "-exact") == 0)) { Tcl_Obj *ov; if (objc != 5) { goto requireSyntax; } version = TclGetString(objv[4]); if (CheckVersionAndConvert(interp, version, NULL, NULL) != TCL_OK) { return TCL_ERROR; } /* * Create a new-style requirement for the exact version. */ ov = Tcl_NewStringObj(version, -1); Tcl_AppendStringsToObj(ov, "-", version, (char *)NULL); version = NULL; argv3 = TclGetString(objv[3]); Tcl_IncrRefCount(objv[3]); objvListPtr = Tcl_NewListObj(0, NULL); Tcl_IncrRefCount(objvListPtr); Tcl_ListObjAppendElement(interp, objvListPtr, ov); TclListObjGetElements(interp, objvListPtr, &newobjc, &newObjvPtr); Tcl_NRAddCallback(interp, TclNRPackageObjCmdCleanup, objv[3], objvListPtr, NULL,NULL); Tcl_NRAddCallback(interp, PkgRequireCore, (void *) argv3, INT2PTR(newobjc), newObjvPtr, NULL); return TCL_OK; } else { Tcl_Obj *const *newobjv = objv + 3; newobjc = objc - 3; if (CheckAllRequirements(interp, objc-3, objv+3) != TCL_OK) { return TCL_ERROR; } objvListPtr = Tcl_NewListObj(0, NULL); Tcl_IncrRefCount(objvListPtr); Tcl_IncrRefCount(objv[2]); for (i = 0; i < newobjc; i++) { /* * Tcl_Obj structures may have come from another interpreter, * so duplicate them. */ Tcl_ListObjAppendElement(interp, objvListPtr, Tcl_DuplicateObj(newobjv[i])); } TclListObjGetElements(interp, objvListPtr, &newobjc, &newObjvPtr); Tcl_NRAddCallback(interp, TclNRPackageObjCmdCleanup, objv[2], objvListPtr, NULL,NULL); Tcl_NRAddCallback(interp, PkgRequireCore, (void *) argv2, INT2PTR(newobjc), newObjvPtr, NULL); return TCL_OK; } break; case PKG_UNKNOWN: { Tcl_Size length; if (objc == 2) { if (iPtr->packageUnknown != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj(iPtr->packageUnknown, -1)); } } else if (objc == 3) { if (iPtr->packageUnknown != NULL) { Tcl_Free(iPtr->packageUnknown); } argv2 = TclGetStringFromObj(objv[2], &length); if (argv2[0] == 0) { iPtr->packageUnknown = NULL; } else { DupBlock(iPtr->packageUnknown, argv2, length+1); } } else { Tcl_WrongNumArgs(interp, 2, objv, "?command?"); return TCL_ERROR; } break; } case PKG_PREFER: { static const char *const pkgPreferOptions[] = { "latest", "stable", NULL }; /* * See tclInt.h for the enum, just before Interp. */ if (objc > 3) { Tcl_WrongNumArgs(interp, 2, objv, "?latest|stable?"); return TCL_ERROR; } else if (objc == 3) { /* * Seting the value. */ int newPref; if (Tcl_GetIndexFromObj(interp, objv[2], pkgPreferOptions, "preference", 0, &newPref) != TCL_OK) { return TCL_ERROR; } if (newPref < iPtr->packagePrefer) { iPtr->packagePrefer = newPref; } } /* * Always return current value. */ Tcl_SetObjResult(interp, Tcl_NewStringObj(pkgPreferOptions[iPtr->packagePrefer], -1)); break; } case PKG_VCOMPARE: if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "version1 version2"); return TCL_ERROR; } argv3 = TclGetString(objv[3]); argv2 = TclGetString(objv[2]); if (CheckVersionAndConvert(interp, argv2, &iva, NULL) != TCL_OK || CheckVersionAndConvert(interp, argv3, &ivb, NULL) != TCL_OK) { if (iva != NULL) { Tcl_Free(iva); } /* * ivb cannot be set in this branch. */ return TCL_ERROR; } /* * Comparison is done on the internal representation. */ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(CompareVersions(iva, ivb, NULL))); Tcl_Free(iva); Tcl_Free(ivb); break; case PKG_VERSIONS: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "package"); return TCL_ERROR; } else { Tcl_Obj *resultObj; TclNewObj(resultObj); argv2 = TclGetString(objv[2]); hPtr = Tcl_FindHashEntry(&iPtr->packageTable, argv2); if (hPtr != NULL) { pkgPtr = (Package *)Tcl_GetHashValue(hPtr); for (availPtr = pkgPtr->availPtr; availPtr != NULL; availPtr = availPtr->nextPtr) { Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(availPtr->version, -1)); } } Tcl_SetObjResult(interp, resultObj); } break; case PKG_VSATISFIES: { char *argv2i = NULL; if (objc < 4) { Tcl_WrongNumArgs(interp, 2, objv, "version ?requirement ...?"); return TCL_ERROR; } argv2 = TclGetString(objv[2]); if (CheckVersionAndConvert(interp, argv2, &argv2i, NULL) != TCL_OK) { return TCL_ERROR; } else if (CheckAllRequirements(interp, objc-3, objv+3) != TCL_OK) { Tcl_Free(argv2i); return TCL_ERROR; } satisfies = SomeRequirementSatisfied(argv2i, objc-3, objv+3); Tcl_Free(argv2i); Tcl_SetObjResult(interp, Tcl_NewBooleanObj(satisfies)); break; } default: Tcl_Panic("Tcl_PackageObjCmd: bad option index to pkgOptions"); } return TCL_OK; } static int TclNRPackageObjCmdCleanup( void *data[], TCL_UNUSED(Tcl_Interp *), int result) { TclDecrRefCount((Tcl_Obj *) data[0]); TclDecrRefCount((Tcl_Obj *) data[1]); return result; } /* *---------------------------------------------------------------------- * * FindPackage -- * * This function finds the Package record for a particular package in a * particular interpreter, creating a record if one doesn't already * exist. * * Results: * The return value is a pointer to the Package record for the package. * * Side effects: * A new Package record may be created. * *---------------------------------------------------------------------- */ static Package * FindPackage( Tcl_Interp *interp, /* Interpreter to use for package lookup. */ const char *name) /* Name of package to fine. */ { Interp *iPtr = (Interp *) interp; Tcl_HashEntry *hPtr; int isNew; Package *pkgPtr; hPtr = Tcl_CreateHashEntry(&iPtr->packageTable, name, &isNew); if (isNew) { pkgPtr = (Package *)Tcl_Alloc(sizeof(Package)); pkgPtr->version = NULL; pkgPtr->availPtr = NULL; pkgPtr->clientData = NULL; Tcl_SetHashValue(hPtr, pkgPtr); } else { pkgPtr = (Package *)Tcl_GetHashValue(hPtr); } return pkgPtr; } /* *---------------------------------------------------------------------- * * TclFreePackageInfo -- * * This function is called during interpreter deletion to free all of the * package-related information for the interpreter. * * Results: * None. * * Side effects: * Memory is freed. * *---------------------------------------------------------------------- */ void TclFreePackageInfo( Interp *iPtr) /* Interpreter that is being deleted. */ { Package *pkgPtr; Tcl_HashSearch search; Tcl_HashEntry *hPtr; PkgAvail *availPtr; for (hPtr = Tcl_FirstHashEntry(&iPtr->packageTable, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { pkgPtr = (Package *)Tcl_GetHashValue(hPtr); if (pkgPtr->version != NULL) { Tcl_DecrRefCount(pkgPtr->version); } while (pkgPtr->availPtr != NULL) { availPtr = pkgPtr->availPtr; pkgPtr->availPtr = availPtr->nextPtr; Tcl_EventuallyFree(availPtr->version, TCL_DYNAMIC); Tcl_EventuallyFree(availPtr->script, TCL_DYNAMIC); if (availPtr->pkgIndex) { Tcl_EventuallyFree(availPtr->pkgIndex, TCL_DYNAMIC); availPtr->pkgIndex = NULL; } Tcl_Free(availPtr); } Tcl_Free(pkgPtr); } Tcl_DeleteHashTable(&iPtr->packageTable); if (iPtr->packageUnknown != NULL) { Tcl_Free(iPtr->packageUnknown); } } /* *---------------------------------------------------------------------- * * CheckVersionAndConvert -- * * This function checks to see whether a version number has valid syntax. * It also generates a semi-internal representation (string rep of a list * of numbers). * * Results: * If string is a properly formed version number the TCL_OK is returned. * Otherwise TCL_ERROR is returned and an error message is left in the * interp's result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int CheckVersionAndConvert( Tcl_Interp *interp, /* Used for error reporting. */ const char *string, /* Supposedly a version number, which is * groups of decimal digits separated by * dots. */ char **internal, /* Internal normalized representation */ int *stable) /* Flag: Version is (un)stable. */ { const char *p = string; char prevChar; int hasunstable = 0; /* * 4* assuming that each char is a separator (a,b become ' -x '). * 4+ to have space for an additional -2 at the end */ char *ibuf = (char *)Tcl_Alloc(4 + 4*strlen(string)); char *ip = ibuf; /* * Basic rules * (1) First character has to be a digit. * (2) All other characters have to be a digit or '.' * (3) Two '.'s may not follow each other. * * TIP 268, Modified rules * (1) s.a. * (2) All other characters have to be a digit, 'a', 'b', or '.' * (3) s.a. * (4) Only one of 'a' or 'b' may occur. * (5) Neither 'a', nor 'b' may occur before or after a '.' */ if (!isdigit(UCHAR(*p))) { /* INTL: digit */ goto error; } *ip++ = *p; for (prevChar = *p, p++; (*p != 0) && (*p != '+'); p++) { if (!isdigit(UCHAR(*p)) && /* INTL: digit */ ((*p!='.' && *p!='a' && *p!='b') || ((hasunstable && (*p=='a' || *p=='b')) || ((prevChar=='a' || prevChar=='b' || prevChar=='.') && (*p=='.')) || ((*p=='a' || *p=='b' || *p=='.') && prevChar=='.')))) { goto error; } if (*p == 'a' || *p == 'b') { hasunstable = 1; } /* * Translation to the internal rep. Regular version chars are copied * as is. The separators are translated to numerics. The new separator * for all parts is space. */ if (*p == '.') { *ip++ = ' '; *ip++ = '0'; *ip++ = ' '; } else if (*p == 'a') { *ip++ = ' '; *ip++ = '-'; *ip++ = '2'; *ip++ = ' '; } else if (*p == 'b') { *ip++ = ' '; *ip++ = '-'; *ip++ = '1'; *ip++ = ' '; } else { *ip++ = *p; } prevChar = *p; } if (prevChar!='.' && prevChar!='a' && prevChar!='b') { *ip = '\0'; if (internal != NULL) { *internal = ibuf; } else { Tcl_Free(ibuf); } if (stable != NULL) { *stable = !hasunstable; } return TCL_OK; } error: Tcl_Free(ibuf); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected version number but got \"%s\"", string)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "VERSION", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * CompareVersions -- * * This function compares two version numbers (in internal rep). * * Results: * The return value is -1 if v1 is less than v2, 0 if the two version * numbers are the same, and 1 if v1 is greater than v2. If *satPtr is * non-NULL, the word it points to is filled in with 1 if v2 >= v1 and * both numbers have the same major number or 0 otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int CompareVersions( char *v1, char *v2, /* Versions strings, of form 2.1.3 (any number * of version numbers). */ int *isMajorPtr) /* If non-null, the word pointed to is filled * in with a 0/1 value. 1 means that the * difference occurred in the first element. */ { int thisIsMajor, res, flip; char *s1, *e1, *s2, *e2, o1, o2; /* * Each iteration of the following loop processes one number from each * string, terminated by a " " (space). If those numbers don't match then * the comparison is over; otherwise, we loop back for the next number. * * TIP 268. * This is identical the function 'ComparePkgVersion', but using the new * space separator as used by the internal rep of version numbers. The * special separators 'a' and 'b' have already been dealt with in * 'CheckVersionAndConvert', they were translated into numbers as well. * This keeps the comparison sane. Otherwise we would have to compare * numerics, the separators, and also deal with the special case of * end-of-string compared to separators. The semi-list rep we get here is * much easier to handle, as it is still regular. * * Rewritten to not compute a numeric value for the extracted version * number, but do string comparison. Skip any leading zeros for that to * work. This change breaks through the 32bit-limit on version numbers. */ thisIsMajor = 1; s1 = v1; s2 = v2; while (1) { /* * Parse one decimal number from the front of each string. Skip * leading zeros. Terminate found number for upcoming string-wise * comparison, if needed. */ while ((*s1 != 0) && (*s1 == '0')) { s1++; } while ((*s2 != 0) && (*s2 == '0')) { s2++; } /* * s1, s2 now point to the beginnings of the numbers to compare. Test * for their signs first, as shortcut to the result (different signs), * or determines if result has to be flipped (both negative). If there * is no shortcut we have to insert terminators later to limit the * strcmp. */ if ((*s1 == '-') && (*s2 != '-')) { /* s1 < 0, s2 >= 0 => s1 < s2 */ res = -1; break; } if ((*s1 != '-') && (*s2 == '-')) { /* s1 >= 0, s2 < 0 => s1 > s2 */ res = 1; break; } if ((*s1 == '-') && (*s2 == '-')) { /* a < b => -a > -b, etc. */ s1++; s2++; flip = 1; } else { flip = 0; } /* * The string comparison is needed, so now we determine where the * numbers end. */ e1 = s1; while ((*e1 != 0) && (*e1 != ' ')) { e1++; } e2 = s2; while ((*e2 != 0) && (*e2 != ' ')) { e2++; } /* * s1 .. e1 and s2 .. e2 now bracket the numbers to compare. Insert * terminators, compare, and restore actual contents. First however * another shortcut. Compare lengths. Shorter string is smaller * number! Thus we strcmp only strings of identical length. */ if ((e1-s1) < (e2-s2)) { res = -1; } else if ((e2-s2) < (e1-s1)) { res = 1; } else { o1 = *e1; *e1 = '\0'; o2 = *e2; *e2 = '\0'; res = strcmp(s1, s2); res = (res < 0) ? -1 : (res ? 1 : 0); *e1 = o1; *e2 = o2; } /* * Stop comparing segments when a difference has been found. Here we * may have to flip the result to account for signs. */ if (res != 0) { if (flip) { res = -res; } break; } /* * Go on to the next version number if the current numbers match. * However stop processing if the end of both numbers has been * reached. */ s1 = e1; s2 = e2; if (*s1 != 0) { s1++; } else if (*s2 == 0) { /* * s1, s2 both at the end => identical */ res = 0; break; } if (*s2 != 0) { s2++; } thisIsMajor = 0; } if (isMajorPtr != NULL) { *isMajorPtr = thisIsMajor; } return res; } /* *---------------------------------------------------------------------- * * CheckAllRequirements -- * * This function checks to see whether all requirements in a set have * valid syntax. * * Results: * TCL_OK is returned if all requirements are valid. Otherwise TCL_ERROR * is returned and an error message is left in the interp's result. * * Side effects: * May modify the interpreter result. * *---------------------------------------------------------------------- */ static int CheckAllRequirements( Tcl_Interp *interp, int reqc, /* Requirements to check. */ Tcl_Obj *const reqv[]) { int i; for (i = 0; i < reqc; i++) { if ((CheckRequirement(interp, TclGetString(reqv[i])) != TCL_OK)) { return TCL_ERROR; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * CheckRequirement -- * * This function checks to see whether a requirement has valid syntax. * * Results: * If string is a properly formed requirement then TCL_OK is returned. * Otherwise TCL_ERROR is returned and an error message is left in the * interp's result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int CheckRequirement( Tcl_Interp *interp, /* Used for error reporting. */ const char *string) /* Supposedly a requirement. */ { /* * Syntax of requirement = version * = version-version * = version- */ char *dash = NULL, *buf; dash = strchr(string, '+') ? NULL : (char *)strchr(string, '-'); if (dash == NULL) { /* * '+' found or no dash found: has to be a simple version. */ return CheckVersionAndConvert(interp, string, NULL, NULL); } if (strchr(dash+1, '-') != NULL) { /* * More dashes found after the first. This is wrong. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected versionMin-versionMax but got \"%s\"", string)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "VERSIONRANGE", (char *)NULL); return TCL_ERROR; } /* * Exactly one dash is present. Copy the string, split at the location of * dash and check that both parts are versions. Note that the max part can * be empty. Also note that the string allocated with strdup() must be * freed with free() and not Tcl_Free(). */ DupString(buf, string); dash = buf + (dash - string); *dash = '\0'; /* buf now <=> min part */ dash++; /* dash now <=> max part */ if ((CheckVersionAndConvert(interp, buf, NULL, NULL) != TCL_OK) || ((*dash != '\0') && (CheckVersionAndConvert(interp, dash, NULL, NULL) != TCL_OK))) { Tcl_Free(buf); return TCL_ERROR; } Tcl_Free(buf); return TCL_OK; } /* *---------------------------------------------------------------------- * * AddRequirementsToResult -- * * This function accumulates requirements in the interpreter result. * * Results: * None. * * Side effects: * The interpreter result is extended. * *---------------------------------------------------------------------- */ static void AddRequirementsToResult( Tcl_Interp *interp, int reqc, /* Requirements constraining the desired * version. */ Tcl_Obj *const reqv[]) /* 0 means to use the latest version * available. */ { Tcl_Obj *result = Tcl_GetObjResult(interp); int i; Tcl_Size length; for (i = 0; i < reqc; i++) { const char *v = TclGetStringFromObj(reqv[i], &length); if ((length & 0x1) && (v[length/2] == '-') && (strncmp(v, v+((length+1)/2), length/2) == 0)) { Tcl_AppendPrintfToObj(result, " exactly %s", v+((length+1)/2)); } else { Tcl_AppendPrintfToObj(result, " %s", v); } } } /* *---------------------------------------------------------------------- * * AddRequirementsToDString -- * * This function accumulates requirements in a DString. * * Results: * None. * * Side effects: * The DString argument is extended. * *---------------------------------------------------------------------- */ static void AddRequirementsToDString( Tcl_DString *dsPtr, int reqc, /* Requirements constraining the desired * version. */ Tcl_Obj *const reqv[]) /* 0 means to use the latest version * available. */ { int i; if (reqc > 0) { for (i = 0; i < reqc; i++) { TclDStringAppendLiteral(dsPtr, " "); TclDStringAppendObj(dsPtr, reqv[i]); } } else { TclDStringAppendLiteral(dsPtr, " 0-"); } } /* *---------------------------------------------------------------------- * * SomeRequirementSatisfied -- * * This function checks to see whether a version satisfies at least one * of a set of requirements. * * Results: * If the requirements are satisfied 1 is returned. Otherwise 0 is * returned. The function assumes that all pieces have valid syntax. And * is allowed to make that assumption. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int SomeRequirementSatisfied( char *availVersionI, /* Candidate version to check against the * requirements. */ int reqc, /* Requirements constraining the desired * version. */ Tcl_Obj *const reqv[]) /* 0 means to use the latest version * available. */ { int i; for (i = 0; i < reqc; i++) { if (RequirementSatisfied(availVersionI, TclGetString(reqv[i]))) { return 1; } } return 0; } /* *---------------------------------------------------------------------- * * RequirementSatisfied -- * * This function checks to see whether a version satisfies a requirement. * * Results: * If the requirement is satisfied 1 is returned. Otherwise 0 is * returned. The function assumes that all pieces have valid syntax, and * is allowed to make that assumption. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int RequirementSatisfied( char *havei, /* Version string, of candidate package we * have. */ const char *req) /* Requirement string the candidate has to * satisfy. */ { /* * The have candidate is already in internal rep. */ int satisfied, res; char *dash = NULL, *buf, *min, *max; dash = (char *)strchr(req, '-'); if (dash == NULL) { /* * No dash found, is a simple version, fallback to regular check. The * 'CheckVersionAndConvert' cannot fail. We pad the requirement with * 'a0', i.e '-2' before doing the comparison to properly accept * unstables as well. */ char *reqi = NULL; int thisIsMajor; CheckVersionAndConvert(NULL, req, &reqi, NULL); strcat(reqi, " -2"); res = CompareVersions(havei, reqi, &thisIsMajor); satisfied = (res == 0) || ((res == 1) && !thisIsMajor); Tcl_Free(reqi); return satisfied; } /* * Exactly one dash is present (Assumption of valid syntax). Copy the req, * split at the location of dash and check that both parts are versions. * Note that the max part can be empty. */ DupString(buf, req); dash = buf + (dash - req); *dash = '\0'; /* buf now <=> min part */ dash++; /* dash now <=> max part */ if (*dash == '\0') { /* * We have a min, but no max. For the comparison we generate the * internal rep, padded with 'a0' i.e. '-2'. */ CheckVersionAndConvert(NULL, buf, &min, NULL); strcat(min, " -2"); satisfied = (CompareVersions(havei, min, NULL) >= 0); Tcl_Free(min); Tcl_Free(buf); return satisfied; } /* * We have both min and max, and generate their internal reps. When * identical we compare as is, otherwise we pad with 'a0' to over the range * a bit. */ CheckVersionAndConvert(NULL, buf, &min, NULL); CheckVersionAndConvert(NULL, dash, &max, NULL); if (CompareVersions(min, max, NULL) == 0) { satisfied = (CompareVersions(min, havei, NULL) == 0); } else { strcat(min, " -2"); strcat(max, " -2"); satisfied = ((CompareVersions(min, havei, NULL) <= 0) && (CompareVersions(havei, max, NULL) < 0)); } Tcl_Free(min); Tcl_Free(max); Tcl_Free(buf); return satisfied; } /* *---------------------------------------------------------------------- * * Tcl_PkgInitStubsCheck -- * * This is a replacement routine for Tcl_InitStubs() that is called * from code where -DUSE_TCL_STUBS has not been enabled. * * Results: * Returns the version of a conforming stubs table, or NULL, if * the table version doesn't satisfy the requested requirements, * according to historical practice. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * Tcl_PkgInitStubsCheck( Tcl_Interp *interp, const char * version, int exact) { const char *actualVersion = Tcl_PkgPresentEx(interp, "Tcl", version, 0, NULL); if ((exact&1) && actualVersion) { const char *p = version; int count = 0; while (*p) { count += !isdigit(UCHAR(*p++)); } if (count == 1) { if (0 != strncmp(version, actualVersion, strlen(version))) { /* Construct error message */ Tcl_PkgPresentEx(interp, "Tcl", version, 1, NULL); return NULL; } } else { return Tcl_PkgPresentEx(interp, "Tcl", version, 1, NULL); } } return actualVersion; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclPkgConfig.c0000644000175000017500000000672314726623136015720 0ustar sergeisergei/* * tclPkgConfig.c -- * * This file contains the configuration information to embed into the tcl * library. * * Copyright © 2002 Andreas Kupries * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ /* Note, the definitions in this module are influenced by the following C * preprocessor macros: * * OSCMa = shortcut for "old style configuration macro activates" * NSCMdt = shortcut for "new style configuration macro declares that" * * - TCL_THREADS OSCMa compilation as threaded core. * - TCL_MEM_DEBUG OSCMa memory debugging. * - TCL_COMPILE_DEBUG OSCMa debugging of bytecode compiler. * - TCL_COMPILE_STATS OSCMa bytecode compiler statistics. * * - TCL_CFG_DO64BIT NSCMdt tcl is compiled for a 64bit system. * - NDEBUG NSCMdt tcl is compiled with symbol info off. * - TCL_CFG_OPTIMIZED NSCMdt tcl is compiled with cc optimizations on * - TCL_CFG_PROFILED NSCMdt tcl is compiled with profiling info. * * - CFG_RUNTIME_* Paths to various stuff at runtime. * - CFG_INSTALL_* Paths to various stuff at installation time. * * - TCL_CFGVAL_ENCODING string containing the encoding used for the * configuration values. */ #include "tclInt.h" #ifndef TCL_CFGVAL_ENCODING # define TCL_CFGVAL_ENCODING "utf-8" #endif /* * Use C preprocessor statements to define the various values for the embedded * configuration information. */ #if TCL_THREADS # define CFG_THREADED "1" #else # define CFG_THREADED "0" #endif #ifdef TCL_MEM_DEBUG # define CFG_MEMDEBUG "1" #else # define CFG_MEMDEBUG "0" #endif #ifdef TCL_COMPILE_DEBUG # define CFG_COMPILE_DEBUG "1" #else # define CFG_COMPILE_DEBUG "0" #endif #ifdef TCL_COMPILE_STATS # define CFG_COMPILE_STATS "1" #else # define CFG_COMPILE_STATS "0" #endif #ifdef TCL_CFG_DO64BIT # define CFG_64 "1" #else # define CFG_64 "0" #endif #ifndef NDEBUG # define CFG_DEBUG "1" #else # define CFG_DEBUG "0" #endif #ifdef TCL_CFG_OPTIMIZED # define CFG_OPTIMIZED "1" #else # define CFG_OPTIMIZED "0" #endif #ifdef TCL_CFG_PROFILED # define CFG_PROFILED "1" #else # define CFG_PROFILED "0" #endif static Tcl_Config const cfg[] = { {"debug", CFG_DEBUG}, {"threaded", CFG_THREADED}, {"profiled", CFG_PROFILED}, {"64bit", CFG_64}, {"optimized", CFG_OPTIMIZED}, {"mem_debug", CFG_MEMDEBUG}, {"compile_debug", CFG_COMPILE_DEBUG}, {"compile_stats", CFG_COMPILE_STATS}, /* Runtime paths to various stuff */ {"libdir,runtime", CFG_RUNTIME_LIBDIR}, {"bindir,runtime", CFG_RUNTIME_BINDIR}, {"scriptdir,runtime", CFG_RUNTIME_SCRDIR}, {"includedir,runtime", CFG_RUNTIME_INCDIR}, {"docdir,runtime", CFG_RUNTIME_DOCDIR}, #if !defined(STATIC_BUILD) {"dllfile,runtime", CFG_RUNTIME_DLLFILE}, #endif /* Installation paths to various stuff */ {"libdir,install", CFG_INSTALL_LIBDIR}, {"bindir,install", CFG_INSTALL_BINDIR}, {"scriptdir,install", CFG_INSTALL_SCRDIR}, {"includedir,install", CFG_INSTALL_INCDIR}, {"docdir,install", CFG_INSTALL_DOCDIR}, /* Last entry, closes the array */ {NULL, NULL} }; void TclInitEmbeddedConfigurationInformation( Tcl_Interp *interp) /* Interpreter the configuration command is * registered in. */ { Tcl_RegisterConfig(interp, "tcl", cfg, TCL_CFGVAL_ENCODING); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclPlatDecls.h0000644000175000017500000001441014731057471015720 0ustar sergeisergei/* * tclPlatDecls.h -- * * Declarations of platform specific Tcl APIs. * * Copyright (c) 1998-1999 by Scriptics Corporation. * All rights reserved. */ #ifndef _TCLPLATDECLS #define _TCLPLATDECLS #undef TCL_STORAGE_CLASS #ifdef BUILD_tcl # define TCL_STORAGE_CLASS DLLEXPORT #else # ifdef USE_TCL_STUBS # define TCL_STORAGE_CLASS # else # define TCL_STORAGE_CLASS DLLIMPORT # endif #endif /* * WARNING: This file is automatically generated by the tools/genStubs.tcl * script. Any modifications to the function declarations below should be made * in the generic/tcl.decls script. */ /* * TCHAR is needed here for win32, so if it is not defined yet do it here. * This way, we don't need to include just for one define. */ #if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(_TCHAR_DEFINED) # if defined(_UNICODE) typedef wchar_t TCHAR; # else typedef char TCHAR; # endif # define _TCHAR_DEFINED #endif #ifndef MODULE_SCOPE # ifdef __cplusplus # define MODULE_SCOPE extern "C" # else # define MODULE_SCOPE extern # endif #endif #if TCL_MAJOR_VERSION < 9 #ifdef __cplusplus extern "C" { #endif /* * Exported function declarations: */ #if defined(_WIN32) || defined(__CYGWIN__) /* WIN */ /* 0 */ EXTERN TCHAR * Tcl_WinUtfToTChar(const char *str, int len, Tcl_DString *dsPtr); /* 1 */ EXTERN char * Tcl_WinTCharToUtf(const TCHAR *str, int len, Tcl_DString *dsPtr); /* Slot 2 is reserved */ /* 3 */ EXTERN void Tcl_WinConvertError(unsigned errCode); #endif /* WIN */ #ifdef MAC_OSX_TCL /* MACOSX */ /* 0 */ EXTERN int Tcl_MacOSXOpenBundleResources(Tcl_Interp *interp, const char *bundleName, int hasResourceFile, Tcl_Size maxPathLen, char *libraryPath); /* 1 */ EXTERN int Tcl_MacOSXOpenVersionedBundleResources( Tcl_Interp *interp, const char *bundleName, const char *bundleVersion, int hasResourceFile, Tcl_Size maxPathLen, char *libraryPath); /* 2 */ EXTERN void Tcl_MacOSXNotifierAddRunLoopMode( const void *runLoopMode); #endif /* MACOSX */ typedef struct TclPlatStubs { int magic; void *hooks; #if defined(_WIN32) || defined(__CYGWIN__) /* WIN */ TCHAR * (*tcl_WinUtfToTChar) (const char *str, int len, Tcl_DString *dsPtr); /* 0 */ char * (*tcl_WinTCharToUtf) (const TCHAR *str, int len, Tcl_DString *dsPtr); /* 1 */ void (*reserved2)(void); void (*tcl_WinConvertError) (unsigned errCode); /* 3 */ #endif /* WIN */ #ifdef MAC_OSX_TCL /* MACOSX */ int (*tcl_MacOSXOpenBundleResources) (Tcl_Interp *interp, const char *bundleName, int hasResourceFile, Tcl_Size maxPathLen, char *libraryPath); /* 0 */ int (*tcl_MacOSXOpenVersionedBundleResources) (Tcl_Interp *interp, const char *bundleName, const char *bundleVersion, int hasResourceFile, Tcl_Size maxPathLen, char *libraryPath); /* 1 */ void (*tcl_MacOSXNotifierAddRunLoopMode) (const void *runLoopMode); /* 2 */ #endif /* MACOSX */ } TclPlatStubs; extern const TclPlatStubs *tclPlatStubsPtr; #ifdef __cplusplus } #endif #if defined(USE_TCL_STUBS) /* * Inline function declarations: */ #if defined(_WIN32) || defined(__CYGWIN__) /* WIN */ #define Tcl_WinUtfToTChar \ (tclPlatStubsPtr->tcl_WinUtfToTChar) /* 0 */ #define Tcl_WinTCharToUtf \ (tclPlatStubsPtr->tcl_WinTCharToUtf) /* 1 */ /* Slot 2 is reserved */ #define Tcl_WinConvertError \ (tclPlatStubsPtr->tcl_WinConvertError) /* 3 */ #endif /* WIN */ #ifdef MAC_OSX_TCL /* MACOSX */ #define Tcl_MacOSXOpenBundleResources \ (tclPlatStubsPtr->tcl_MacOSXOpenBundleResources) /* 0 */ #define Tcl_MacOSXOpenVersionedBundleResources \ (tclPlatStubsPtr->tcl_MacOSXOpenVersionedBundleResources) /* 1 */ #define Tcl_MacOSXNotifierAddRunLoopMode \ (tclPlatStubsPtr->tcl_MacOSXNotifierAddRunLoopMode) /* 2 */ #endif /* MACOSX */ #endif /* defined(USE_TCL_STUBS) */ #else /* TCL_MAJOR_VERSION > 8 */ /* !BEGIN!: Do not edit below this line. */ #ifdef __cplusplus extern "C" { #endif /* * Exported function declarations: */ /* Slot 0 is reserved */ /* 1 */ EXTERN int Tcl_MacOSXOpenVersionedBundleResources( Tcl_Interp *interp, const char *bundleName, const char *bundleVersion, int hasResourceFile, Tcl_Size maxPathLen, char *libraryPath); /* 2 */ EXTERN void Tcl_MacOSXNotifierAddRunLoopMode( const void *runLoopMode); /* 3 */ EXTERN void Tcl_WinConvertError(unsigned errCode); typedef struct TclPlatStubs { int magic; void *hooks; void (*reserved0)(void); int (*tcl_MacOSXOpenVersionedBundleResources) (Tcl_Interp *interp, const char *bundleName, const char *bundleVersion, int hasResourceFile, Tcl_Size maxPathLen, char *libraryPath); /* 1 */ void (*tcl_MacOSXNotifierAddRunLoopMode) (const void *runLoopMode); /* 2 */ void (*tcl_WinConvertError) (unsigned errCode); /* 3 */ } TclPlatStubs; extern const TclPlatStubs *tclPlatStubsPtr; #ifdef __cplusplus } #endif #if defined(USE_TCL_STUBS) /* * Inline function declarations: */ /* Slot 0 is reserved */ #define Tcl_MacOSXOpenVersionedBundleResources \ (tclPlatStubsPtr->tcl_MacOSXOpenVersionedBundleResources) /* 1 */ #define Tcl_MacOSXNotifierAddRunLoopMode \ (tclPlatStubsPtr->tcl_MacOSXNotifierAddRunLoopMode) /* 2 */ #define Tcl_WinConvertError \ (tclPlatStubsPtr->tcl_WinConvertError) /* 3 */ #endif /* defined(USE_TCL_STUBS) */ /* !END!: Do not edit above this line. */ #endif /* TCL_MAJOR_VERSION */ #ifdef MAC_OSX_TCL /* MACOSX */ #undef Tcl_MacOSXOpenBundleResources #define Tcl_MacOSXOpenBundleResources(a,b,c,d,e) Tcl_MacOSXOpenVersionedBundleResources(a,b,NULL,c,d,e) #endif #undef TCL_STORAGE_CLASS #define TCL_STORAGE_CLASS DLLIMPORT #ifdef _WIN32 # undef Tcl_CreateFileHandler # undef Tcl_DeleteFileHandler # undef Tcl_GetOpenFile #endif #ifndef MAC_OSX_TCL # undef Tcl_MacOSXOpenVersionedBundleResources # undef Tcl_MacOSXNotifierAddRunLoopMode #endif #if defined(USE_TCL_STUBS) && (defined(_WIN32) || defined(__CYGWIN__))\ && (defined(TCL_NO_DEPRECATED) || TCL_MAJOR_VERSION > 8) #undef Tcl_WinUtfToTChar #undef Tcl_WinTCharToUtf #ifdef _WIN32 #define Tcl_WinUtfToTChar(string, len, dsPtr) (Tcl_DStringInit(dsPtr), \ (TCHAR *)Tcl_UtfToChar16DString((string), (len), (dsPtr))) #define Tcl_WinTCharToUtf(string, len, dsPtr) (Tcl_DStringInit(dsPtr), \ (char *)Tcl_Char16ToUtfDString((const unsigned short *)(string), ((((len) + 2) >> 1) - 1), (dsPtr))) #endif #endif #endif /* _TCLPLATDECLS */ tcl9.0.1/generic/tclPort.h0000644000175000017500000000133214726623136014771 0ustar sergeisergei/* * tclPort.h -- * * This header file handles porting issues that occur because * of differences between systems. It reads in platform specific * portability files. * * Copyright (c) 1994-1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef _TCLPORT #define _TCLPORT #ifdef HAVE_TCL_CONFIG_H #include "tclConfig.h" #endif #if defined(_WIN32) # include "tclWinPort.h" #else # include "tclUnixPort.h" #endif #include "tcl.h" #define UWIDE_MAX ((Tcl_WideUInt)-1) #define WIDE_MAX ((Tcl_WideInt)(UWIDE_MAX >> 1)) #define WIDE_MIN ((Tcl_WideInt)((Tcl_WideUInt)WIDE_MAX+1)) #endif /* _TCLPORT */ tcl9.0.1/generic/tclPosixStr.c0000644000175000017500000007633514726623136015652 0ustar sergeisergei/* * tclPosixStr.c -- * * This file contains procedures that generate strings corresponding to * various POSIX-related codes, such as errno and signals. * * Copyright © 1991-1994 The Regents of the University of California. * Copyright © 1994-1996 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* *---------------------------------------------------------------------- * * Tcl_ErrnoId -- * * Return a textual identifier for the current errno value. * * Results: * This procedure returns a machine-readable textual identifier that * corresponds to the current errno value (e.g. "EPERM"). The identifier * is the same as the #define name in errno.h. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * Tcl_ErrnoId(void) { switch (errno) { #if defined(E2BIG) && (!defined(EOVERFLOW) || (E2BIG != EOVERFLOW)) case E2BIG: return "E2BIG"; #endif #ifdef EACCES case EACCES: return "EACCES"; #endif #ifdef EADDRINUSE case EADDRINUSE: return "EADDRINUSE"; #endif #ifdef EADDRNOTAVAIL case EADDRNOTAVAIL: return "EADDRNOTAVAIL"; #endif #ifdef EADV case EADV: return "EADV"; #endif #ifdef EAFNOSUPPORT case EAFNOSUPPORT: return "EAFNOSUPPORT"; #endif #ifdef EAGAIN case EAGAIN: return "EAGAIN"; #endif #ifdef EALIGN case EALIGN: return "EALIGN"; #endif #if defined(EALREADY) && (!defined(EBUSY) || (EALREADY != EBUSY)) case EALREADY: return "EALREADY"; #endif #ifdef EBADCAT case EBADCAT: return "EBADCAT"; #endif #ifdef EBADE case EBADE: return "EBADE"; #endif #ifdef EBADF case EBADF: return "EBADF"; #endif #ifdef EBADFD case EBADFD: return "EBADFD"; #endif #ifdef EBADMSG case EBADMSG: return "EBADMSG"; #endif #ifdef EBADR case EBADR: return "EBADR"; #endif #ifdef EBADRPC case EBADRPC: return "EBADRPC"; #endif #ifdef EBADRQC case EBADRQC: return "EBADRQC"; #endif #ifdef EBADSLT case EBADSLT: return "EBADSLT"; #endif #ifdef EBFONT case EBFONT: return "EBFONT"; #endif #ifdef EBUSY case EBUSY: return "EBUSY"; #endif #ifdef ECANCELED case ECANCELED: return "ECANCELED"; #endif #ifdef ECASECLASH case ECASECLASH: return "ECASECLASH"; #endif #ifdef ECHILD case ECHILD: return "ECHILD"; #endif #ifdef ECHRNG case ECHRNG: return "ECHRNG"; #endif #ifdef ECOMM case ECOMM: return "ECOMM"; #endif #ifdef ECONNABORTED case ECONNABORTED: return "ECONNABORTED"; #endif #ifdef ECONNREFUSED case ECONNREFUSED: return "ECONNREFUSED"; #endif #ifdef ECONNRESET case ECONNRESET: return "ECONNRESET"; #endif #if defined(EDEADLK) && (!defined(EWOULDBLOCK) || (EDEADLK != EWOULDBLOCK)) case EDEADLK: return "EDEADLK"; #endif #if defined(EDEADLOCK) && (!defined(EDEADLK) || (EDEADLOCK != EDEADLK)) case EDEADLOCK: return "EDEADLOCK"; #endif #ifdef EDESTADDRREQ case EDESTADDRREQ: return "EDESTADDRREQ"; #endif #ifdef EDIRTY case EDIRTY: return "EDIRTY"; #endif #ifdef EDOM case EDOM: return "EDOM"; #endif #ifdef EDOTDOT case EDOTDOT: return "EDOTDOT"; #endif #ifdef EDQUOT case EDQUOT: return "EDQUOT"; #endif #ifdef EDUPPKG case EDUPPKG: return "EDUPPKG"; #endif #ifdef EEXIST case EEXIST: return "EEXIST"; #endif #ifdef EFAIL case EFAIL: return "EFAIL"; #endif #ifdef EFAULT case EFAULT: return "EFAULT"; #endif #ifdef EFBIG case EFBIG: return "EFBIG"; #endif #ifdef EFTYPE case EFTYPE: return "EFTYPE"; #endif #ifdef EHOSTDOWN case EHOSTDOWN: return "EHOSTDOWN"; #endif #ifdef EHOSTUNREACH case EHOSTUNREACH: return "EHOSTUNREACH"; #endif #if defined(EIDRM) && (!defined(EINPROGRESS) || (EIDRM != EINPROGRESS)) case EIDRM: return "EIDRM"; #endif #ifdef EINIT case EINIT: return "EINIT"; #endif #ifdef EILSEQ case EILSEQ: return "EILSEQ"; #endif #ifdef EINPROG case EINPROG: return "EINPROG"; #endif #ifdef EINPROGRESS case EINPROGRESS: return "EINPROGRESS"; #endif #ifdef EINTR case EINTR: return "EINTR"; #endif #ifdef EINVAL case EINVAL: return "EINVAL"; #endif #ifdef EIO case EIO: return "EIO"; #endif #ifdef EISCONN case EISCONN: return "EISCONN"; #endif #ifdef EISDIR case EISDIR: return "EISDIR"; #endif #ifdef EISNAM case EISNAM: return "EISNAM"; #endif #ifdef EL2HLT case EL2HLT: return "EL2HLT"; #endif #ifdef EL2NSYNC case EL2NSYNC: return "EL2NSYNC"; #endif #ifdef EL3HLT case EL3HLT: return "EL3HLT"; #endif #ifdef EL3RST case EL3RST: return "EL3RST"; #endif #ifdef ELBIN case ELBIN: return "ELBIN"; #endif #ifdef ELIBACC case ELIBACC: return "ELIBACC"; #endif #ifdef ELIBBAD case ELIBBAD: return "ELIBBAD"; #endif #ifdef ELIBEXEC case ELIBEXEC: return "ELIBEXEC"; #endif #if defined(ELIBMAX) && (!defined(ECANCELED) || (ELIBMAX != ECANCELED)) case ELIBMAX: return "ELIBMAX"; #endif #ifdef ELIBSCN case ELIBSCN: return "ELIBSCN"; #endif #ifdef ELNRNG case ELNRNG: return "ELNRNG"; #endif #if defined(ELOOP) && (!defined(ENOENT) || (ELOOP != ENOENT)) case ELOOP: return "ELOOP"; #endif #ifdef EMEDIUMTYPE case EMEDIUMTYPE: return "EMEDIUMTYPE"; #endif #ifdef EMFILE case EMFILE: return "EMFILE"; #endif #ifdef EMLINK case EMLINK: return "EMLINK"; #endif #ifdef EMSGSIZE case EMSGSIZE: return "EMSGSIZE"; #endif #ifdef EMULTIHOP case EMULTIHOP: return "EMULTIHOP"; #endif #ifdef ENAMETOOLONG case ENAMETOOLONG: return "ENAMETOOLONG"; #endif #ifdef ENAVAIL case ENAVAIL: return "ENAVAIL"; #endif #ifdef ENETDOWN case ENETDOWN: return "ENETDOWN"; #endif #ifdef ENETRESET case ENETRESET: return "ENETRESET"; #endif #ifdef ENETUNREACH case ENETUNREACH: return "ENETUNREACH"; #endif #ifdef ENFILE case ENFILE: return "ENFILE"; #endif #ifdef ENMFILE case ENMFILE: return "ENMFILE"; #endif #ifdef ENOANO case ENOANO: return "ENOANO"; #endif #if defined(ENOBUFS) && (!defined(ENOSR) || (ENOBUFS != ENOSR)) case ENOBUFS: return "ENOBUFS"; #endif #ifdef ENOCSI case ENOCSI: return "ENOCSI"; #endif #if defined(ENODATA) && (!defined(ECONNREFUSED) || (ENODATA != ECONNREFUSED)) case ENODATA: return "ENODATA"; #endif #ifdef ENODEV case ENODEV: return "ENODEV"; #endif #ifdef ENOENT case ENOENT: return "ENOENT"; #endif #ifdef ENOEXEC case ENOEXEC: return "ENOEXEC"; #endif #ifdef ENOLCK case ENOLCK: return "ENOLCK"; #endif #ifdef ENOLINK case ENOLINK: return "ENOLINK"; #endif #ifdef ENOMEM case ENOMEM: return "ENOMEM"; #endif #ifdef ENOMEDIUM case ENOMEDIUM: return "ENOMEDIUM"; #endif #ifdef ENOMSG case ENOMSG: return "ENOMSG"; #endif #ifdef ENONET case ENONET: return "ENONET"; #endif #ifdef ENOPKG case ENOPKG: return "ENOPKG"; #endif #ifdef ENOPROTOOPT case ENOPROTOOPT: return "ENOPROTOOPT"; #endif #ifdef ENOSHARE case ENOSHARE: return "ENOSHARE"; #endif #ifdef ENOSPC case ENOSPC: return "ENOSPC"; #endif #if defined(ENOSR) && (!defined(ENAMETOOLONG) || (ENAMETOOLONG != ENOSR)) case ENOSR: return "ENOSR"; #endif #if defined(ENOSTR) && (!defined(ENOTTY) || (ENOTTY != ENOSTR)) case ENOSTR: return "ENOSTR"; #endif #ifdef ENOSYM case ENOSYM: return "ENOSYM"; #endif #ifdef ENOSYS case ENOSYS: return "ENOSYS"; #endif #ifdef ENOTBLK case ENOTBLK: return "ENOTBLK"; #endif #ifdef ENOTCONN case ENOTCONN: return "ENOTCONN"; #endif #ifdef ENOTRECOVERABLE case ENOTRECOVERABLE: return "ENOTRECOVERABLE"; #endif #ifdef ENOTDIR case ENOTDIR: return "ENOTDIR"; #endif #if defined(ENOTEMPTY) && (!defined(EEXIST) || (ENOTEMPTY != EEXIST)) case ENOTEMPTY: return "ENOTEMPTY"; #endif #ifdef ENOTNAM case ENOTNAM: return "ENOTNAM"; #endif #ifdef ENOTSOCK case ENOTSOCK: return "ENOTSOCK"; #endif #ifdef ENOTSUP case ENOTSUP: return "ENOTSUP"; #endif #ifdef ENOTTY case ENOTTY: return "ENOTTY"; #endif #ifdef ENOTUNIQ case ENOTUNIQ: return "ENOTUNIQ"; #endif #ifdef ENWAIT case ENWAIT: return "ENWAIT"; #endif #ifdef ENXIO case ENXIO: return "ENXIO"; #endif #if defined(EOPNOTSUPP) && (!defined(ENOTSUP) || (ENOTSUP != EOPNOTSUPP)) case EOPNOTSUPP: return "EOPNOTSUPP"; #endif #ifdef EOTHER case EOTHER: return "EOTHER"; #endif #if defined(EOVERFLOW) && (!defined(EFBIG) || (EOVERFLOW != EFBIG)) && (!defined(EINVAL) || (EOVERFLOW != EINVAL)) case EOVERFLOW: return "EOVERFLOW"; #endif #ifdef EOWNERDEAD case EOWNERDEAD: return "EOWNERDEAD"; #endif #ifdef EPERM case EPERM: return "EPERM"; #endif #if defined(EPFNOSUPPORT) && (!defined(ENOLCK) || (ENOLCK != EPFNOSUPPORT)) case EPFNOSUPPORT: return "EPFNOSUPPORT"; #endif #ifdef EPIPE case EPIPE: return "EPIPE"; #endif #ifdef EPROCLIM case EPROCLIM: return "EPROCLIM"; #endif #ifdef EPROCUNAVAIL case EPROCUNAVAIL: return "EPROCUNAVAIL"; #endif #ifdef EPROGMISMATCH case EPROGMISMATCH: return "EPROGMISMATCH"; #endif #ifdef EPROGUNAVAIL case EPROGUNAVAIL: return "EPROGUNAVAIL"; #endif #ifdef EPROTO case EPROTO: return "EPROTO"; #endif #ifdef EPROTONOSUPPORT case EPROTONOSUPPORT: return "EPROTONOSUPPORT"; #endif #ifdef EPROTOTYPE case EPROTOTYPE: return "EPROTOTYPE"; #endif #ifdef ERANGE case ERANGE: return "ERANGE"; #endif #if defined(EREFUSED) && (!defined(ECONNREFUSED) || (EREFUSED != ECONNREFUSED)) case EREFUSED: return "EREFUSED"; #endif #ifdef EREMCHG case EREMCHG: return "EREMCHG"; #endif #ifdef EREMDEV case EREMDEV: return "EREMDEV"; #endif #ifdef EREMOTE case EREMOTE: return "EREMOTE"; #endif #ifdef EREMOTEIO case EREMOTEIO: return "EREMOTEIO"; #endif #ifdef EREMOTERELEASE case EREMOTERELEASE: return "EREMOTERELEASE"; #endif #ifdef ERESTART case ERESTART: return "ERESTART"; #endif #ifdef EROFS case EROFS: return "EROFS"; #endif #ifdef ERPCMISMATCH case ERPCMISMATCH: return "ERPCMISMATCH"; #endif #ifdef ERREMOTE case ERREMOTE: return "ERREMOTE"; #endif #ifdef ESHUTDOWN case ESHUTDOWN: return "ESHUTDOWN"; #endif #ifdef ESOCKTNOSUPPORT case ESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT"; #endif #ifdef ESPIPE case ESPIPE: return "ESPIPE"; #endif #ifdef ESRCH case ESRCH: return "ESRCH"; #endif #ifdef ESRMNT case ESRMNT: return "ESRMNT"; #endif #ifdef ESTALE case ESTALE: return "ESTALE"; #endif #ifdef ESUCCESS case ESUCCESS: return "ESUCCESS"; #endif #if defined(ETIME) && (!defined(ELOOP) || (ETIME != ELOOP)) case ETIME: return "ETIME"; #endif #if defined(ETIMEDOUT) && (!defined(ENOSTR) || (ETIMEDOUT != ENOSTR)) case ETIMEDOUT: return "ETIMEDOUT"; #endif #ifdef ETOOMANYREFS case ETOOMANYREFS: return "ETOOMANYREFS"; #endif #ifdef ETXTBSY case ETXTBSY: return "ETXTBSY"; #endif #ifdef EUCLEAN case EUCLEAN: return "EUCLEAN"; #endif #ifdef EUNATCH case EUNATCH: return "EUNATCH"; #endif #ifdef EUSERS case EUSERS: return "EUSERS"; #endif #ifdef EVERSION case EVERSION: return "EVERSION"; #endif #if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN)) case EWOULDBLOCK: return "EWOULDBLOCK"; #endif #ifdef EXDEV case EXDEV: return "EXDEV"; #endif #ifdef EXFULL case EXFULL: return "EXFULL"; #endif } return "unknown error"; } /* *---------------------------------------------------------------------- * * Tcl_ErrnoMsg -- * * Return a human-readable message corresponding to a given errno value. * * Results: * The return value is the standard POSIX error message for errno. This * procedure is used instead of strerror because strerror returns * slightly different values on different machines (e.g. different * capitalizations), which cause problems for things such as regression * tests. This procedure provides messages for most standard errors, then * it calls strerror for things it doesn't understand. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * Tcl_ErrnoMsg( int err) /* Error number (such as in errno variable). */ { switch (err) { #if defined(E2BIG) && (!defined(EOVERFLOW) || (E2BIG != EOVERFLOW)) case E2BIG: return "argument list too long"; #endif #ifdef EACCES case EACCES: return "permission denied"; #endif #ifdef EADDRINUSE case EADDRINUSE: return "address already in use"; #endif #ifdef EADDRNOTAVAIL case EADDRNOTAVAIL: return "cannot assign requested address"; #endif #ifdef EADV case EADV: return "advertise error"; #endif #ifdef EAFNOSUPPORT case EAFNOSUPPORT: return "address family not supported by protocol"; #endif #ifdef EAGAIN case EAGAIN: return "resource temporarily unavailable"; #endif #ifdef EALIGN case EALIGN: return "alignment error"; #endif #if defined(EALREADY) && (!defined(EBUSY) || (EALREADY != EBUSY)) case EALREADY: return "operation already in progress"; #endif #ifdef EBADCAT case EBADCAT: return "bad message catalogue format"; #endif #ifdef EBADE case EBADE: return "invalid exchange"; #endif #ifdef EBADF case EBADF: return "bad file descriptor"; #endif #ifdef EBADFD case EBADFD: return "file descriptor in bad state"; #endif #ifdef EBADMSG case EBADMSG: return "bad message"; #endif #ifdef EBADR case EBADR: return "invalid request descriptor"; #endif #ifdef EBADRPC case EBADRPC: return "RPC structure is bad"; #endif #ifdef EBADRQC case EBADRQC: return "invalid request code"; #endif #ifdef EBADSLT case EBADSLT: return "invalid slot"; #endif #ifdef EBFONT case EBFONT: return "bad font file format"; #endif #ifdef EBUSY case EBUSY: return "device or resource busy"; #endif #ifdef ECANCELED case ECANCELED: return "operation canceled"; #endif #ifdef ECASECLASH case ECASECLASH: return "filename exists with different case"; #endif #ifdef ECHILD case ECHILD: return "no child processes"; #endif #ifdef ECHRNG case ECHRNG: return "channel number out of range"; #endif #ifdef ECOMM case ECOMM: return "communication error on send"; #endif #ifdef ECONNABORTED case ECONNABORTED: return "software caused connection abort"; #endif #ifdef ECONNREFUSED case ECONNREFUSED: return "connection refused"; #endif #ifdef ECONNRESET case ECONNRESET: return "connection reset by peer"; #endif #if defined(EDEADLK) && (!defined(EWOULDBLOCK) || (EDEADLK != EWOULDBLOCK)) case EDEADLK: return "resource deadlock avoided"; #endif #if defined(EDEADLOCK) && (!defined(EDEADLK) || (EDEADLOCK != EDEADLK)) case EDEADLOCK: return "resource deadlock avoided"; #endif #ifdef EDESTADDRREQ case EDESTADDRREQ: return "destination address required"; #endif #ifdef EDIRTY case EDIRTY: return "mounting a dirty fs w/o force"; #endif #ifdef EDOM case EDOM: return "numerical argument out of domain"; #endif #ifdef EDOTDOT case EDOTDOT: return "cross mount point"; #endif #ifdef EDQUOT case EDQUOT: return "disk quota exceeded"; #endif #ifdef EDUPPKG case EDUPPKG: return "duplicate package name"; #endif #ifdef EEXIST case EEXIST: return "file exists"; #endif #ifdef EFAIL case EFAIL: return "cannot start operation"; #endif #ifdef EFAULT case EFAULT: return "bad address"; #endif #ifdef EFBIG case EFBIG: return "file too large"; #endif #ifdef EFTYPE case EFTYPE: return "inappropriate file type or format"; #endif #ifdef EHOSTDOWN case EHOSTDOWN: return "host is down"; #endif #ifdef EHOSTUNREACH case EHOSTUNREACH: return "no route to host"; #endif #if defined(EIDRM) && (!defined(EINPROGRESS) || (EIDRM != EINPROGRESS)) case EIDRM: return "identifier removed"; #endif #ifdef EINIT case EINIT: return "initialization error"; #endif #ifdef EILSEQ case EILSEQ: return "invalid or incomplete multibyte or wide character"; #endif #ifdef EINPROG case EINPROG: return "asynchronous operation in progress"; #endif #ifdef EINPROGRESS case EINPROGRESS: return "operation now in progress"; #endif #ifdef EINTR case EINTR: return "interrupted system call"; #endif #ifdef EINVAL case EINVAL: return "invalid argument"; #endif #ifdef EIO case EIO: return "input/output error"; #endif #ifdef EISCONN case EISCONN: return "transport endpoint is already connected"; #endif #ifdef EISDIR case EISDIR: return "is a directory"; #endif #ifdef EISNAM case EISNAM: return "is a named type file"; #endif #ifdef EL2HLT case EL2HLT: return "level 2 halted"; #endif #ifdef EL2NSYNC case EL2NSYNC: return "level 2 not synchronized"; #endif #ifdef EL3HLT case EL3HLT: return "level 3 halted"; #endif #ifdef EL3RST case EL3RST: return "level 3 reset"; #endif #ifdef ELBIN case ELBIN: return "inode is remote"; #endif #ifdef ELIBACC case ELIBACC: return "can not access a needed shared library"; #endif #ifdef ELIBBAD case ELIBBAD: return "accessing a corrupted shared library"; #endif #ifdef ELIBEXEC case ELIBEXEC: return "cannot exec a shared library directly"; #endif #if defined(ELIBMAX) && (!defined(ECANCELED) || (ELIBMAX != ECANCELED)) case ELIBMAX: return "attempting to link in too many shared libraries"; #endif #ifdef ELIBSCN case ELIBSCN: return ".lib section in a.out corrupted"; #endif #ifdef ELNRNG case ELNRNG: return "link number out of range"; #endif #if defined(ELOOP) && (!defined(ENOENT) || (ELOOP != ENOENT)) case ELOOP: return "too many levels of symbolic links"; #endif #ifdef EMEDIUMTYPE case EMEDIUMTYPE: return "wrong medium type"; #endif #ifdef EMFILE case EMFILE: return "too many open files"; #endif #ifdef EMLINK case EMLINK: return "too many links"; #endif #ifdef EMSGSIZE case EMSGSIZE: return "message too long"; #endif #ifdef EMULTIHOP case EMULTIHOP: return "multihop attempted"; #endif #ifdef ENAMETOOLONG case ENAMETOOLONG: return "file name too long"; #endif #ifdef ENAVAIL case ENAVAIL: return "not available"; #endif #ifdef ENETDOWN case ENETDOWN: return "network is down"; #endif #ifdef ENETRESET case ENETRESET: return "network dropped connection on reset"; #endif #ifdef ENETUNREACH case ENETUNREACH: return "network is unreachable"; #endif #ifdef ENFILE case ENFILE: return "too many open files in system"; #endif #ifdef ENMFILE case ENMFILE: return "no more files"; #endif #ifdef ENOANO case ENOANO: return "no anode"; #endif #if defined(ENOBUFS) && (!defined(ENOSR) || (ENOBUFS != ENOSR)) case ENOBUFS: return "no buffer space available"; #endif #ifdef ENOCSI case ENOCSI: return "no CSI structure available"; #endif #if defined(ENODATA) && (!defined(ECONNREFUSED) || (ENODATA != ECONNREFUSED)) case ENODATA: return "no data available"; #endif #ifdef ENODEV case ENODEV: return "no such device"; #endif #ifdef ENOENT case ENOENT: return "no such file or directory"; #endif #ifdef ENOEXEC case ENOEXEC: return "exec format error"; #endif #ifdef ENOLCK case ENOLCK: return "no locks available"; #endif #ifdef ENOLINK case ENOLINK: return "link has been severed"; #endif #ifdef ENOMEM case ENOMEM: return "cannot allocate memory"; #endif #ifdef ENOMEDIUM case ENOMEDIUM: return "no medium found"; #endif #ifdef ENOMSG case ENOMSG: return "no message of desired type"; #endif #ifdef ENONET case ENONET: return "machine is not on the network"; #endif #ifdef ENOPKG case ENOPKG: return "package not installed"; #endif #ifdef ENOPROTOOPT case ENOPROTOOPT: return "protocol not available"; #endif #ifdef ENOSHARE case ENOSHARE: return "no such host or network path"; #endif #ifdef ENOSPC case ENOSPC: return "no space left on device"; #endif #if defined(ENOSR) && (!defined(ENAMETOOLONG) || (ENAMETOOLONG != ENOSR)) case ENOSR: return "out of streams resources"; #endif #if defined(ENOSTR) && (!defined(ENOTTY) || (ENOTTY != ENOSTR)) case ENOSTR: return "device not a stream"; #endif #ifdef ENOSYM case ENOSYM: return "unresolved symbol name"; #endif #ifdef ENOSYS case ENOSYS: return "function not implemented"; #endif #ifdef ENOTBLK case ENOTBLK: return "block device required"; #endif #ifdef ENOTCONN case ENOTCONN: return "transport endpoint is not connected"; #endif #ifdef ENOTDIR case ENOTDIR: return "not a directory"; #endif #if defined(ENOTEMPTY) && (!defined(EEXIST) || (ENOTEMPTY != EEXIST)) case ENOTEMPTY: return "directory not empty"; #endif #ifdef ENOTNAM case ENOTNAM: return "not a name file"; #endif #ifdef ENOTRECOVERABLE case ENOTRECOVERABLE: return "state not recoverable"; #endif #ifdef ENOTSOCK case ENOTSOCK: return "socket operation on non-socket"; #endif #ifdef ENOTSUP case ENOTSUP: return "operation not supported"; #endif #ifdef ENOTTY case ENOTTY: return "inappropriate ioctl for device"; #endif #ifdef ENOTUNIQ case ENOTUNIQ: return "name not unique on network"; #endif #ifdef ENWAIT case ENWAIT: return "No waiting processes"; #endif #ifdef ENXIO case ENXIO: return "no such device or address"; #endif #if defined(EOPNOTSUPP) && (!defined(ENOTSUP) || (ENOTSUP != EOPNOTSUPP)) case EOPNOTSUPP: return "operation not supported on socket"; #endif #ifdef EOTHER case EOTHER: return "other error"; #endif #if defined(EOVERFLOW) && (!defined(EFBIG) || (EOVERFLOW != EFBIG)) && (!defined(EINVAL) || (EOVERFLOW != EINVAL)) case EOVERFLOW: return "value too large for defined data type"; #endif #ifdef EOWNERDEAD case EOWNERDEAD: return "owner died"; #endif #ifdef EPERM case EPERM: return "operation not permitted"; #endif #if defined(EPFNOSUPPORT) && (!defined(ENOLCK) || (ENOLCK != EPFNOSUPPORT)) case EPFNOSUPPORT: return "protocol family not supported"; #endif #ifdef EPIPE case EPIPE: return "broken pipe"; #endif #ifdef EPROCLIM case EPROCLIM: return "too many processes"; #endif #ifdef EPROCUNAVAIL case EPROCUNAVAIL: return "bad procedure for program"; #endif #ifdef EPROGMISMATCH case EPROGMISMATCH: return "program version wrong"; #endif #ifdef EPROGUNAVAIL case EPROGUNAVAIL: return "RPC program not available"; #endif #ifdef EPROTO case EPROTO: return "protocol error"; #endif #ifdef EPROTONOSUPPORT case EPROTONOSUPPORT: return "protocol not supported"; #endif #ifdef EPROTOTYPE case EPROTOTYPE: return "protocol wrong type for socket"; #endif #ifdef ERANGE case ERANGE: return "numerical result out of range"; #endif #if defined(EREFUSED) && (!defined(ECONNREFUSED) || (EREFUSED != ECONNREFUSED)) case EREFUSED: return "connection refused"; #endif #ifdef EREMCHG case EREMCHG: return "remote address changed"; #endif #ifdef EREMDEV case EREMDEV: return "remote device"; #endif #ifdef EREMOTE case EREMOTE: return "object is remote"; #endif #ifdef EREMOTEIO case EREMOTEIO: return "remote I/O error"; #endif #ifdef EREMOTERELEASE case EREMOTERELEASE: return "remote peer released connection"; #endif #ifdef ERESTART case ERESTART: return "interrupted system call should be restarted"; #endif #ifdef EROFS case EROFS: return "read-only file system"; #endif #ifdef ERPCMISMATCH case ERPCMISMATCH: return "RPC version is wrong"; #endif #ifdef ERREMOTE case ERREMOTE: return "object is remote"; #endif #ifdef ESHUTDOWN case ESHUTDOWN: return "cannot send after transport endpoint shutdown"; #endif #ifdef ESOCKTNOSUPPORT case ESOCKTNOSUPPORT: return "socket type not supported"; #endif #ifdef ESPIPE case ESPIPE: return "illegal seek"; #endif #ifdef ESRCH case ESRCH: return "no such process"; #endif #ifdef ESRMNT case ESRMNT: return "srmount error"; #endif #ifdef ESTALE case ESTALE: return "stale file handle"; #endif #ifdef ESTRPIPE case ESTRPIPE: return "streams pipe error"; #endif #ifdef ESUCCESS case ESUCCESS: return "success"; #endif #if defined(ETIME) && (!defined(ELOOP) || (ETIME != ELOOP)) case ETIME: return "timer expired"; #endif #if defined(ETIMEDOUT) && (!defined(ENOSTR) || (ETIMEDOUT != ENOSTR)) case ETIMEDOUT: return "connection timed out"; #endif #ifdef ETOOMANYREFS case ETOOMANYREFS: return "too many references: cannot splice"; #endif #ifdef ETXTBSY case ETXTBSY: return "text file busy"; #endif #ifdef EUCLEAN case EUCLEAN: return "structure needs cleaning"; #endif #ifdef EUNATCH case EUNATCH: return "protocol driver not attached"; #endif #ifdef EUSERS case EUSERS: return "too many users"; #endif #ifdef EVERSION case EVERSION: return "version mismatch"; #endif #if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN)) case EWOULDBLOCK: return "operation would block"; #endif #ifdef EXDEV case EXDEV: return "invalid cross-device link"; #endif #ifdef EXFULL case EXFULL: return "exchange full"; #endif default: #ifdef NO_STRERROR return "unknown POSIX error"; #else return strerror(err); #endif } } /* *---------------------------------------------------------------------- * * Tcl_SignalId -- * * Return a textual identifier for a signal number. * * Results: * This procedure returns a machine-readable textual identifier that * corresponds to sig. The identifier is the same as the #define name in * signal.h. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * Tcl_SignalId( int sig) /* Number of signal. */ { switch (sig) { #ifdef SIGABRT case SIGABRT: return "SIGABRT"; #endif #ifdef SIGALRM case SIGALRM: return "SIGALRM"; #endif #ifdef SIGBUS case SIGBUS: return "SIGBUS"; #endif #ifdef SIGCHLD case SIGCHLD: return "SIGCHLD"; #endif #if defined(SIGCLD) && (!defined(SIGCHLD) || (SIGCLD != SIGCHLD)) case SIGCLD: return "SIGCLD"; #endif #ifdef SIGCONT case SIGCONT: return "SIGCONT"; #endif #if defined(SIGEMT) && (!defined(SIGXCPU) || (SIGEMT != SIGXCPU)) case SIGEMT: return "SIGEMT"; #endif #ifdef SIGFPE case SIGFPE: return "SIGFPE"; #endif #ifdef SIGHUP case SIGHUP: return "SIGHUP"; #endif #ifdef SIGILL case SIGILL: return "SIGILL"; #endif #ifdef SIGINT case SIGINT: return "SIGINT"; #endif #ifdef SIGIO case SIGIO: return "SIGIO"; #endif #if defined(SIGIOT) && (!defined(SIGABRT) || (SIGIOT != SIGABRT)) case SIGIOT: return "SIGIOT"; #endif #ifdef SIGKILL case SIGKILL: return "SIGKILL"; #endif #if defined(SIGLOST) && (!defined(SIGIOT) || (SIGLOST != SIGIOT)) && (!defined(SIGURG) || (SIGLOST != SIGURG)) && (!defined(SIGPROF) || (SIGLOST != SIGPROF)) && (!defined(SIGIO) || (SIGLOST != SIGIO)) case SIGLOST: return "SIGLOST"; #endif #ifdef SIGPIPE case SIGPIPE: return "SIGPIPE"; #endif #if defined(SIGPOLL) && (!defined(SIGIO) || (SIGPOLL != SIGIO)) case SIGPOLL: return "SIGPOLL"; #endif #ifdef SIGPROF case SIGPROF: return "SIGPROF"; #endif #if defined(SIGPWR) && (!defined(SIGXFSZ) || (SIGPWR != SIGXFSZ)) && (!defined(SIGLOST) || (SIGPWR != SIGLOST)) case SIGPWR: return "SIGPWR"; #endif #ifdef SIGQUIT case SIGQUIT: return "SIGQUIT"; #endif #if defined(SIGSEGV) && (!defined(SIGBUS) || (SIGSEGV != SIGBUS)) case SIGSEGV: return "SIGSEGV"; #endif #ifdef SIGSTOP case SIGSTOP: return "SIGSTOP"; #endif #ifdef SIGSYS case SIGSYS: return "SIGSYS"; #endif #ifdef SIGTERM case SIGTERM: return "SIGTERM"; #endif #ifdef SIGTRAP case SIGTRAP: return "SIGTRAP"; #endif #ifdef SIGTSTP case SIGTSTP: return "SIGTSTP"; #endif #ifdef SIGTTIN case SIGTTIN: return "SIGTTIN"; #endif #ifdef SIGTTOU case SIGTTOU: return "SIGTTOU"; #endif #if defined(SIGURG) && (!defined(SIGIO) || (SIGURG != SIGIO)) case SIGURG: return "SIGURG"; #endif #if defined(SIGUSR1) && (!defined(SIGIO) || (SIGUSR1 != SIGIO)) case SIGUSR1: return "SIGUSR1"; #endif #if defined(SIGUSR2) && (!defined(SIGURG) || (SIGUSR2 != SIGURG)) case SIGUSR2: return "SIGUSR2"; #endif #ifdef SIGVTALRM case SIGVTALRM: return "SIGVTALRM"; #endif #ifdef SIGWINCH case SIGWINCH: return "SIGWINCH"; #endif #ifdef SIGXCPU case SIGXCPU: return "SIGXCPU"; #endif #ifdef SIGXFSZ case SIGXFSZ: return "SIGXFSZ"; #endif #if defined(SIGINFO) && (!defined(SIGPWR) || (SIGINFO != SIGPWR)) case SIGINFO: return "SIGINFO"; #endif } return "unknown signal"; } /* *---------------------------------------------------------------------- * * Tcl_SignalMsg -- * * Return a human-readable message describing a signal. * * Results: * This procedure returns a string describing sig that should make sense * to a human. It may not be easy for a machine to parse. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * Tcl_SignalMsg( int sig) /* Number of signal. */ { switch (sig) { #ifdef SIGABRT case SIGABRT: return "SIGABRT"; #endif #ifdef SIGALRM case SIGALRM: return "alarm clock"; #endif #ifdef SIGBUS case SIGBUS: return "bus error"; #endif #ifdef SIGCHLD case SIGCHLD: return "child status changed"; #endif #if defined(SIGCLD) && (!defined(SIGCHLD) || (SIGCLD != SIGCHLD)) case SIGCLD: return "child status changed"; #endif #ifdef SIGCONT case SIGCONT: return "continue after stop"; #endif #if defined(SIGEMT) && (!defined(SIGXCPU) || (SIGEMT != SIGXCPU)) case SIGEMT: return "EMT instruction"; #endif #ifdef SIGFPE case SIGFPE: return "floating-point exception"; #endif #ifdef SIGHUP case SIGHUP: return "hangup"; #endif #ifdef SIGILL case SIGILL: return "illegal instruction"; #endif #ifdef SIGINT case SIGINT: return "interrupt"; #endif #ifdef SIGIO case SIGIO: return "input/output possible on file"; #endif #if defined(SIGIOT) && (!defined(SIGABRT) || (SIGABRT != SIGIOT)) case SIGIOT: return "IOT instruction"; #endif #ifdef SIGKILL case SIGKILL: return "kill signal"; #endif #if defined(SIGLOST) && (!defined(SIGIOT) || (SIGLOST != SIGIOT)) && (!defined(SIGURG) || (SIGLOST != SIGURG)) && (!defined(SIGPROF) || (SIGLOST != SIGPROF)) && (!defined(SIGIO) || (SIGLOST != SIGIO)) case SIGLOST: return "resource lost"; #endif #ifdef SIGPIPE case SIGPIPE: return "write on pipe with no readers"; #endif #if defined(SIGPOLL) && (!defined(SIGIO) || (SIGPOLL != SIGIO)) case SIGPOLL: return "input/output possible on file"; #endif #ifdef SIGPROF case SIGPROF: return "profiling alarm"; #endif #if defined(SIGPWR) && (!defined(SIGXFSZ) || (SIGPWR != SIGXFSZ)) && (!defined(SIGLOST) || (SIGPWR != SIGLOST)) case SIGPWR: return "power-fail restart"; #endif #ifdef SIGQUIT case SIGQUIT: return "quit signal"; #endif #if defined(SIGSEGV) && (!defined(SIGBUS) || (SIGSEGV != SIGBUS)) case SIGSEGV: return "segmentation violation"; #endif #ifdef SIGSTOP case SIGSTOP: return "stop"; #endif #ifdef SIGSYS case SIGSYS: return "bad argument to system call"; #endif #ifdef SIGTERM case SIGTERM: return "software termination signal"; #endif #ifdef SIGTRAP case SIGTRAP: return "trace trap"; #endif #ifdef SIGTSTP case SIGTSTP: return "stop signal from tty"; #endif #ifdef SIGTTIN case SIGTTIN: return "background tty read"; #endif #ifdef SIGTTOU case SIGTTOU: return "background tty write"; #endif #if defined(SIGURG) && (!defined(SIGIO) || (SIGURG != SIGIO)) case SIGURG: return "urgent I/O condition"; #endif #if defined(SIGUSR1) && (!defined(SIGIO) || (SIGUSR1 != SIGIO)) case SIGUSR1: return "user-defined signal 1"; #endif #if defined(SIGUSR2) && (!defined(SIGURG) || (SIGUSR2 != SIGURG)) case SIGUSR2: return "user-defined signal 2"; #endif #ifdef SIGVTALRM case SIGVTALRM: return "virtual time alarm"; #endif #ifdef SIGWINCH case SIGWINCH: return "window changed"; #endif #ifdef SIGXCPU case SIGXCPU: return "exceeded CPU time limit"; #endif #ifdef SIGXFSZ case SIGXFSZ: return "exceeded file size limit"; #endif #if defined(SIGINFO) && (!defined(SIGPWR) || (SIGINFO != SIGPWR)) case SIGINFO: return "information request"; #endif } return "unknown signal"; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclPreserve.c0000644000175000017500000003150314726623136015636 0ustar sergeisergei/* * tclPreserve.c -- * * This file contains a collection of functions that are used to make * sure that widget records and other data structures aren't reallocated * when there are nested functions that depend on their existence. * * Copyright © 1991-1994 The Regents of the University of California. * Copyright © 1994-1998 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * The following data structure is used to keep track of all the Tcl_Preserve * calls that are still in effect. It grows as needed to accommodate any * number of calls in effect. */ typedef struct { void *clientData; /* Address of preserved block. */ size_t refCount; /* Number of Tcl_Preserve calls in effect for * block. */ int mustFree; /* Non-zero means Tcl_EventuallyFree was * called while a Tcl_Preserve call was in * effect, so the structure must be freed when * refCount becomes zero. */ Tcl_FreeProc *freeProc; /* Function to call to free. */ } Reference; /* * Global data structures used to hold the list of preserved data references. * These variables are protected by "preserveMutex". */ static Reference *refArray = NULL; /* First in array of references. */ static size_t spaceAvl = 0; /* Total number of structures available at * *firstRefPtr. */ static size_t inUse = 0; /* Count of structures currently in use in * refArray. */ TCL_DECLARE_MUTEX(preserveMutex)/* To protect the above statics */ #define INITIAL_SIZE 2 /* Initial number of reference slots to make */ /* * The following data structure is used to keep track of whether an arbitrary * block of memory has been deleted. This is used by the TclHandle code to * avoid the more time-expensive algorithm of Tcl_Preserve(). This mechanism * is mainly used when we have lots of references to a few big, expensive * objects that we don't want to live any longer than necessary. */ typedef struct { void *ptr; /* Pointer to the memory block being tracked. * This field will become NULL when the memory * block is deleted. This field must be the * first in the structure. */ #ifdef TCL_MEM_DEBUG void *ptr2; /* Backup copy of the above pointer used to * ensure that the contents of the handle are * not changed by anyone else. */ #endif size_t refCount; /* Number of TclHandlePreserve() calls in * effect on this handle. */ } HandleStruct; /* *---------------------------------------------------------------------- * * TclFinalizePreserve -- * * Called during exit processing to clean up the reference array. * * Results: * None. * * Side effects: * Frees the storage of the reference array. * *---------------------------------------------------------------------- */ void TclFinalizePreserve(void) { Tcl_MutexLock(&preserveMutex); if (spaceAvl != 0) { Tcl_Free(refArray); refArray = NULL; inUse = 0; spaceAvl = 0; } Tcl_MutexUnlock(&preserveMutex); } /* *---------------------------------------------------------------------- * * Tcl_Preserve -- * * This function is used by a function to declare its interest in a * particular block of memory, so that the block will not be reallocated * until a matching call to Tcl_Release has been made. * * Results: * None. * * Side effects: * Information is retained so that the block of memory will not be freed * until at least the matching call to Tcl_Release. * *---------------------------------------------------------------------- */ void Tcl_Preserve( void *clientData) /* Pointer to malloc'ed block of memory. */ { Reference *refPtr; size_t i; /* * See if there is already a reference for this pointer. If so, just * increment its reference count. */ Tcl_MutexLock(&preserveMutex); for (i=0, refPtr=refArray ; iclientData == clientData) { refPtr->refCount++; Tcl_MutexUnlock(&preserveMutex); return; } } /* * Make a reference array if it doesn't already exist, or make it bigger * if it is full. */ if (inUse == spaceAvl) { spaceAvl = spaceAvl ? 2*spaceAvl : INITIAL_SIZE; refArray = (Reference *)Tcl_Realloc(refArray, spaceAvl * sizeof(Reference)); } /* * Make a new entry for the new reference. */ refPtr = &refArray[inUse]; refPtr->clientData = clientData; refPtr->refCount = 1; refPtr->mustFree = 0; refPtr->freeProc = 0; inUse += 1; Tcl_MutexUnlock(&preserveMutex); } /* *---------------------------------------------------------------------- * * Tcl_Release -- * * This function is called to cancel a previous call to Tcl_Preserve, * thereby allowing a block of memory to be freed (if no one else cares * about it). * * Results: * None. * * Side effects: * If Tcl_EventuallyFree has been called for clientData, and if no other * call to Tcl_Preserve is still in effect, the block of memory is freed. * *---------------------------------------------------------------------- */ void Tcl_Release( void *clientData) /* Pointer to malloc'ed block of memory. */ { Reference *refPtr; size_t i; Tcl_MutexLock(&preserveMutex); for (i=0, refPtr=refArray ; iclientData != clientData) { continue; } if (refPtr->refCount-- > 1) { Tcl_MutexUnlock(&preserveMutex); return; } /* * Must remove information from the slot before calling freeProc to * avoid reentrancy problems if the freeProc calls Tcl_Preserve on the * same clientData. Copy down the last reference in the array to * overwrite the current slot. */ freeProc = refPtr->freeProc; mustFree = refPtr->mustFree; inUse--; if (i < inUse) { refArray[i] = refArray[inUse]; } /* * Now committed to disposing the data. But first, we've patched up * all the global data structures so we should release the mutex now. * Only then should we dabble around with potentially-slow memory * managers... */ Tcl_MutexUnlock(&preserveMutex); if (mustFree) { if (freeProc == TCL_DYNAMIC) { Tcl_Free(clientData); } else { freeProc((char *)clientData); } } return; } Tcl_MutexUnlock(&preserveMutex); /* * Reference not found. This is a bug in the caller. */ Tcl_Panic("Tcl_Release couldn't find reference for %p", clientData); } /* *---------------------------------------------------------------------- * * Tcl_EventuallyFree -- * * Free up a block of memory, unless a call to Tcl_Preserve is in effect * for that block. In this case, defer the free until all calls to * Tcl_Preserve have been undone by matching calls to Tcl_Release. * * Results: * None. * * Side effects: * Ptr may be released by calling free(). * *---------------------------------------------------------------------- */ void Tcl_EventuallyFree( void *clientData, /* Pointer to malloc'ed block of memory. */ Tcl_FreeProc *freeProc) /* Function to actually do free. */ { Reference *refPtr; size_t i; /* * See if there is a reference for this pointer. If so, set its "mustFree" * flag (the flag had better not be set already!). */ Tcl_MutexLock(&preserveMutex); for (i = 0, refPtr = refArray; i < inUse; i++, refPtr++) { if (refPtr->clientData != clientData) { continue; } if (refPtr->mustFree) { Tcl_Panic("Tcl_EventuallyFree called twice for %p", clientData); } refPtr->mustFree = 1; refPtr->freeProc = freeProc; Tcl_MutexUnlock(&preserveMutex); return; } Tcl_MutexUnlock(&preserveMutex); /* * No reference for this block. Free it now. */ if (freeProc == TCL_DYNAMIC) { Tcl_Free(clientData); } else { freeProc(clientData); } } /* *--------------------------------------------------------------------------- * * TclHandleCreate -- * * Allocate a handle that contains enough information to determine if an * arbitrary malloc'd block has been deleted. This is used to avoid the * more time-expensive algorithm of Tcl_Preserve(). * * Results: * The return value is a TclHandle that refers to the given malloc'd * block. Doubly dereferencing the returned handle will give back the * pointer to the block, or will give NULL if the block has been deleted. * * Side effects: * The caller must keep track of this handle (generally by storing it in * a field in the malloc'd block) and call TclHandleFree() on this handle * when the block is deleted. Everything else that wishes to keep track * of whether the malloc'd block has been deleted should use calls to * TclHandlePreserve() and TclHandleRelease() on the associated handle. * *--------------------------------------------------------------------------- */ TclHandle TclHandleCreate( void *ptr) /* Pointer to an arbitrary block of memory to * be tracked for deletion. Must not be * NULL. */ { HandleStruct *handlePtr = (HandleStruct *)Tcl_Alloc(sizeof(HandleStruct)); handlePtr->ptr = ptr; #ifdef TCL_MEM_DEBUG handlePtr->ptr2 = ptr; #endif handlePtr->refCount = 0; return (TclHandle) handlePtr; } /* *--------------------------------------------------------------------------- * * TclHandleFree -- * * Called when the arbitrary malloc'd block associated with the handle is * being deleted. Modifies the handle so that doubly dereferencing it * will give NULL. This informs any user of the handle that the block of * memory formerly referenced by the handle has been freed. * * Results: * None. * * Side effects: * If nothing is referring to the handle, the handle will be reclaimed. * *--------------------------------------------------------------------------- */ void TclHandleFree( TclHandle handle) /* Previously created handle associated with a * malloc'd block that is being deleted. The * handle is modified so that doubly * dereferencing it will give NULL. */ { HandleStruct *handlePtr; handlePtr = (HandleStruct *) handle; #ifdef TCL_MEM_DEBUG if (handlePtr->refCount == 0x61616161) { Tcl_Panic("using previously disposed TclHandle %p", handlePtr); } if (handlePtr->ptr2 != handlePtr->ptr) { Tcl_Panic("someone has changed the block referenced by the handle %p\nfrom %p to %p", handlePtr, handlePtr->ptr2, handlePtr->ptr); } #endif handlePtr->ptr = NULL; if (handlePtr->refCount == 0) { Tcl_Free(handlePtr); } } /* *--------------------------------------------------------------------------- * * TclHandlePreserve -- * * Declare an interest in the arbitrary malloc'd block associated with * the handle. * * Results: * The return value is the handle argument, with its ref count * incremented. * * Side effects: * For each call to TclHandlePreserve(), there should be a matching call * to TclHandleRelease() when the caller is no longer interested in the * malloc'd block associated with the handle. * *--------------------------------------------------------------------------- */ TclHandle TclHandlePreserve( TclHandle handle) /* Declare an interest in the block of memory * referenced by this handle. */ { HandleStruct *handlePtr; handlePtr = (HandleStruct *) handle; #ifdef TCL_MEM_DEBUG if (handlePtr->refCount == 0x61616161) { Tcl_Panic("using previously disposed TclHandle %p", handlePtr); } if ((handlePtr->ptr != NULL) && (handlePtr->ptr != handlePtr->ptr2)) { Tcl_Panic("someone has changed the block referenced by the handle %p\nfrom %p to %p", handlePtr, handlePtr->ptr2, handlePtr->ptr); } #endif handlePtr->refCount++; return handle; } /* *--------------------------------------------------------------------------- * * TclHandleRelease -- * * This function is called to release an interest in the malloc'd block * associated with the handle. * * Results: * None. * * Side effects: * The ref count of the handle is decremented. If the malloc'd block has * been freed and if no one is using the handle any more, the handle will * be reclaimed. * *--------------------------------------------------------------------------- */ void TclHandleRelease( TclHandle handle) /* Unregister interest in the block of memory * referenced by this handle. */ { HandleStruct *handlePtr; handlePtr = (HandleStruct *) handle; #ifdef TCL_MEM_DEBUG if (handlePtr->refCount == 0x61616161) { Tcl_Panic("using previously disposed TclHandle %p", handlePtr); } if ((handlePtr->ptr != NULL) && (handlePtr->ptr != handlePtr->ptr2)) { Tcl_Panic("someone has changed the block referenced by the handle %p\nfrom %p to %p", handlePtr, handlePtr->ptr2, handlePtr->ptr); } #endif if ((handlePtr->refCount-- <= 1) && (handlePtr->ptr == NULL)) { Tcl_Free(handlePtr); } } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclProc.c0000644000175000017500000023636314726623136014761 0ustar sergeisergei/* * tclProc.c -- * * This file contains routines that implement Tcl procedures, including * the "proc" and "uplevel" commands. * * Copyright © 1987-1993 The Regents of the University of California. * Copyright © 1994-1998 Sun Microsystems, Inc. * Copyright © 2004-2006 Miguel Sofer * Copyright © 2007 Daniel A. Steffen * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" #include /* * Variables that are part of the [apply] command implementation and which * have to be passed to the other side of the NRE call. */ typedef struct { Command cmd; ExtraFrameInfo efi; } ApplyExtraData; /* * Prototypes for static functions in this file */ static void DupLambdaInternalRep(Tcl_Obj *objPtr, Tcl_Obj *copyPtr); static void FreeLambdaInternalRep(Tcl_Obj *objPtr); static int InitArgsAndLocals(Tcl_Interp *interp, int skip); static void InitResolvedLocals(Tcl_Interp *interp, ByteCode *codePtr, Var *defPtr, Namespace *nsPtr); static void InitLocalCache(Proc *procPtr); static void ProcBodyDup(Tcl_Obj *srcPtr, Tcl_Obj *dupPtr); static void ProcBodyFree(Tcl_Obj *objPtr); static int ProcWrongNumArgs(Tcl_Interp *interp, int skip); static void MakeProcError(Tcl_Interp *interp, Tcl_Obj *procNameObj); static void MakeLambdaError(Tcl_Interp *interp, Tcl_Obj *procNameObj); static int SetLambdaFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); static Tcl_NRPostProc ApplyNR2; static Tcl_NRPostProc InterpProcNR2; static Tcl_NRPostProc Uplevel_Callback; static Tcl_ObjCmdProc NRInterpProc; /* * The ProcBodyObjType type */ const Tcl_ObjType tclProcBodyType = { "procbody", /* name for this type */ ProcBodyFree, /* FreeInternalRep function */ ProcBodyDup, /* DupInternalRep function */ NULL, /* UpdateString function; Tcl_GetString and * Tcl_GetStringFromObj should panic * instead. */ NULL, /* SetFromAny function; Tcl_ConvertToType * should panic instead. */ TCL_OBJTYPE_V0 }; #define ProcSetInternalRep(objPtr, procPtr) \ do { \ Tcl_ObjInternalRep ir; \ (procPtr)->refCount++; \ ir.twoPtrValue.ptr1 = (procPtr); \ ir.twoPtrValue.ptr2 = NULL; \ Tcl_StoreInternalRep((objPtr), &tclProcBodyType, &ir); \ } while (0) #define ProcGetInternalRep(objPtr, procPtr) \ do { \ const Tcl_ObjInternalRep *irPtr; \ irPtr = TclFetchInternalRep((objPtr), &tclProcBodyType); \ (procPtr) = irPtr ? (Proc *)irPtr->twoPtrValue.ptr1 : NULL; \ } while (0) /* * The [upvar]/[uplevel] level reference type. Uses the wideValue field * to remember the integer value of a parsed # format. * * Uses the default behaviour throughout, and never disposes of the string * rep; it's just a cache type. */ static const Tcl_ObjType levelReferenceType = { "levelReference", NULL, NULL, NULL, NULL, TCL_OBJTYPE_V1(TclLengthOne) }; /* * The type of lambdas. Note that every lambda will *always* have a string * representation. * * Internally, ptr1 is a pointer to a Proc instance that is not bound to a * command name, and ptr2 is a pointer to the namespace that the Proc instance * will execute within. IF YOU CHANGE THIS, CHECK IN tclDisassemble.c TOO. */ static const Tcl_ObjType lambdaType = { "lambdaExpr", /* name */ FreeLambdaInternalRep, /* freeIntRepProc */ DupLambdaInternalRep, /* dupIntRepProc */ NULL, /* updateStringProc */ SetLambdaFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; #define LambdaSetInternalRep(objPtr, procPtr, nsObjPtr) \ do { \ Tcl_ObjInternalRep ir; \ ir.twoPtrValue.ptr1 = (procPtr); \ ir.twoPtrValue.ptr2 = (nsObjPtr); \ Tcl_IncrRefCount((nsObjPtr)); \ Tcl_StoreInternalRep((objPtr), &lambdaType, &ir); \ } while (0) #define LambdaGetInternalRep(objPtr, procPtr, nsObjPtr) \ do { \ const Tcl_ObjInternalRep *irPtr; \ irPtr = TclFetchInternalRep((objPtr), &lambdaType); \ (procPtr) = irPtr ? (Proc *)irPtr->twoPtrValue.ptr1 : NULL; \ (nsObjPtr) = irPtr ? (Tcl_Obj *)irPtr->twoPtrValue.ptr2 : NULL; \ } while (0) /* *---------------------------------------------------------------------- * * Tcl_ProcObjCmd -- * * This object-based function is invoked to process the "proc" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result value. * * Side effects: * A new procedure gets created. * *---------------------------------------------------------------------- */ #undef TclObjInterpProc int Tcl_ProcObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; Proc *procPtr; const char *procName; const char *simpleName, *procArgs, *procBody; Namespace *nsPtr, *altNsPtr, *cxtNsPtr; Tcl_Command cmd; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "name args body"); return TCL_ERROR; } /* * Determine the namespace where the procedure should reside. Unless the * command name includes namespace qualifiers, this will be the current * namespace. */ procName = TclGetString(objv[1]); TclGetNamespaceForQualName(interp, procName, NULL, 0, &nsPtr, &altNsPtr, &cxtNsPtr, &simpleName); if (nsPtr == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't create procedure \"%s\": unknown namespace", procName)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMMAND", (char *)NULL); return TCL_ERROR; } if (simpleName == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't create procedure \"%s\": bad procedure name", procName)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMMAND", (char *)NULL); return TCL_ERROR; } /* * Create the data structure to represent the procedure. */ if (TclCreateProc(interp, /*ignored nsPtr*/ NULL, simpleName, objv[2], objv[3], &procPtr) != TCL_OK) { Tcl_AddErrorInfo(interp, "\n (creating proc \""); Tcl_AddErrorInfo(interp, simpleName); Tcl_AddErrorInfo(interp, "\")"); return TCL_ERROR; } cmd = TclNRCreateCommandInNs(interp, simpleName, (Tcl_Namespace *) nsPtr, TclObjInterpProc, NRInterpProc, procPtr, TclProcDeleteProc); /* * Now initialize the new procedure's cmdPtr field. This will be used * later when the procedure is called to determine what namespace the * procedure will run in. This will be different than the current * namespace if the proc was renamed into a different namespace. */ procPtr->cmdPtr = (Command *) cmd; /* * TIP #280: Remember the line the procedure body is starting on. In a * bytecode context we ask the engine to provide us with the necessary * information. This is for the initialization of the byte code compiler * when the body is used for the first time. * * This code is nearly identical to the #280 code in SetLambdaFromAny, see * this file. The differences are the different index of the body in the * line array of the context, and the lambda code requires some special * processing. Find a way to factor the common elements into a single * function. */ if (iPtr->cmdFramePtr) { CmdFrame *contextPtr = (CmdFrame *)TclStackAlloc(interp, sizeof(CmdFrame)); *contextPtr = *iPtr->cmdFramePtr; if (contextPtr->type == TCL_LOCATION_BC) { /* * Retrieve source information from the bytecode, if possible. If * the information is retrieved successfully, context.type will be * TCL_LOCATION_SOURCE and the reference held by * context.data.eval.path will be counted. */ TclGetSrcInfoForPc(contextPtr); } else if (contextPtr->type == TCL_LOCATION_SOURCE) { /* * The copy into 'context' up above has created another reference * to 'context.data.eval.path'; account for it. */ Tcl_IncrRefCount(contextPtr->data.eval.path); } if (contextPtr->type == TCL_LOCATION_SOURCE) { /* * We can account for source location within a proc only if the * proc body was not created by substitution. */ if (contextPtr->line && (contextPtr->nline >= 4) && (contextPtr->line[3] >= 0)) { int isNew; Tcl_HashEntry *hePtr; CmdFrame *cfPtr = (CmdFrame *)Tcl_Alloc(sizeof(CmdFrame)); cfPtr->level = -1; cfPtr->type = contextPtr->type; cfPtr->line = (Tcl_Size *)Tcl_Alloc(sizeof(Tcl_Size)); cfPtr->line[0] = contextPtr->line[3]; cfPtr->nline = 1; cfPtr->framePtr = NULL; cfPtr->nextPtr = NULL; cfPtr->data.eval.path = contextPtr->data.eval.path; Tcl_IncrRefCount(cfPtr->data.eval.path); cfPtr->cmd = NULL; cfPtr->len = 0; hePtr = Tcl_CreateHashEntry(iPtr->linePBodyPtr, procPtr, &isNew); if (!isNew) { /* * Get the old command frame and release it. See also * TclProcCleanupProc in this file. Currently it seems as * if only the procbodytest::proc command of the testsuite * is able to trigger this situation. */ CmdFrame *cfOldPtr = (CmdFrame *)Tcl_GetHashValue(hePtr); if (cfOldPtr->type == TCL_LOCATION_SOURCE) { Tcl_DecrRefCount(cfOldPtr->data.eval.path); cfOldPtr->data.eval.path = NULL; } Tcl_Free(cfOldPtr->line); cfOldPtr->line = NULL; Tcl_Free(cfOldPtr); } Tcl_SetHashValue(hePtr, cfPtr); } /* * 'contextPtr' is going out of scope; account for the reference * that it's holding to the path name. */ Tcl_DecrRefCount(contextPtr->data.eval.path); contextPtr->data.eval.path = NULL; } TclStackFree(interp, contextPtr); } /* * Optimize for no-op procs: if the body is not precompiled (like a TclPro * procbody), and the argument list is just "args" and the body is empty, * define a compileProc to compile a no-op. * * Notes: * - cannot be done for any argument list without having different * compiled/not-compiled behaviour in the "wrong argument #" case, or * making this code much more complicated. In any case, it doesn't * seem to make a lot of sense to verify the number of arguments we * are about to ignore ... * - could be enhanced to handle also non-empty bodies that contain only * comments; however, parsing the body will slow down the compilation * of all procs whose argument list is just _args_ */ if (TclHasInternalRep(objv[3], &tclProcBodyType)) { goto done; } procArgs = TclGetString(objv[2]); while (*procArgs == ' ') { procArgs++; } if ((procArgs[0] == 'a') && (strncmp(procArgs, "args", 4) == 0)) { Tcl_Size numBytes; procArgs +=4; while (*procArgs != '\0') { if (*procArgs != ' ') { goto done; } procArgs++; } /* * The argument list is just "args"; check the body */ procBody = TclGetStringFromObj(objv[3], &numBytes); if (TclParseAllWhiteSpace(procBody, numBytes) < numBytes) { goto done; } /* * The body is just spaces: link the compileProc */ ((Command *) cmd)->compileProc = TclCompileNoOp; } done: return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCreateProc -- * * Creates the data associated with a Tcl procedure definition. This * function knows how to handle two types of body objects: strings and * procbody. Strings are the traditional (and common) value for bodies, * procbody are values created by extensions that have loaded a * previously compiled script. * * Results: * Returns TCL_OK on success, along with a pointer to a Tcl procedure * definition in procPtrPtr where the cmdPtr field is not initialised. * This definition should be freed by calling TclProcCleanupProc() when * it is no longer needed. Returns TCL_ERROR if anything goes wrong. * * Side effects: * If anything goes wrong, this function returns an error message in the * interpreter. * *---------------------------------------------------------------------- */ int TclCreateProc( Tcl_Interp *interp, /* Interpreter containing proc. */ TCL_UNUSED(Namespace *) /*nsPtr*/, const char *procName, /* Unqualified name of this proc. */ Tcl_Obj *argsPtr, /* Description of arguments. */ Tcl_Obj *bodyPtr, /* Command body. */ Proc **procPtrPtr) /* Returns: pointer to proc data. */ { Interp *iPtr = (Interp *) interp; Proc *procPtr = NULL; Tcl_Size i, numArgs; CompiledLocal *localPtr = NULL; Tcl_Obj **argArray; int precompiled = 0, result; ProcGetInternalRep(bodyPtr, procPtr); if (procPtr != NULL) { /* * Because the body is a TclProProcBody, the actual body is already * compiled, and it is not shared with anyone else, so it's OK not to * unshare it (as a matter of fact, it is bad to unshare it, because * there may be no source code). * * We don't create and initialize a Proc structure for the procedure; * rather, we use what is in the body object. We increment the ref * count of the Proc struct since the command (soon to be created) * will be holding a reference to it. */ procPtr->iPtr = iPtr; procPtr->refCount++; precompiled = 1; } else { /* * If the procedure's body object is shared because its string value * is identical to, e.g., the body of another procedure, we must * create a private copy for this procedure to use. Such sharing of * procedure bodies is rare but can cause problems. A procedure body * is compiled in a context that includes the number of "slots" * allocated by the compiler for local variables. There is a local * variable slot for each formal parameter (the * "procPtr->numCompiledLocals = numArgs" assignment below). This * means that the same code can not be shared by two procedures that * have a different number of arguments, even if their bodies are * identical. Note that we don't use Tcl_DuplicateObj since we would * not want any bytecode internal representation. */ if (Tcl_IsShared(bodyPtr)) { const char *bytes; Tcl_Size length; Tcl_Obj *sharedBodyPtr = bodyPtr; bytes = TclGetStringFromObj(bodyPtr, &length); bodyPtr = Tcl_NewStringObj(bytes, length); /* * TIP #280. * Ensure that the continuation line data for the original body is * not lost and applies to the new body as well. */ TclContinuationsCopy(bodyPtr, sharedBodyPtr); } /* * Create and initialize a Proc structure for the procedure. We * increment the ref count of the procedure's body object since there * will be a reference to it in the Proc structure. */ Tcl_IncrRefCount(bodyPtr); procPtr = (Proc *)Tcl_Alloc(sizeof(Proc)); procPtr->iPtr = iPtr; procPtr->refCount = 1; procPtr->bodyPtr = bodyPtr; procPtr->numArgs = 0; /* Actual argument count is set below. */ procPtr->numCompiledLocals = 0; procPtr->firstLocalPtr = NULL; procPtr->lastLocalPtr = NULL; } /* * Break up the argument list into argument specifiers, then process each * argument specifier. If the body is precompiled, processing is limited * to checking that the parsed argument is consistent with the one stored * in the Proc. */ result = TclListObjGetElements(interp, argsPtr, &numArgs, &argArray); if (result != TCL_OK) { goto procError; } if (precompiled) { if (numArgs > procPtr->numArgs) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "procedure \"%s\": arg list contains %" TCL_SIZE_MODIFIER "d entries, " "precompiled header expects %" TCL_SIZE_MODIFIER "d", procName, numArgs, procPtr->numArgs)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "PROC", "BYTECODELIES", (char *)NULL); goto procError; } localPtr = procPtr->firstLocalPtr; } else { procPtr->numArgs = numArgs; procPtr->numCompiledLocals = numArgs; } for (i = 0; i < numArgs; i++) { const char *argname, *argnamei, *argnamelast; Tcl_Size fieldCount, nameLength; Tcl_Obj **fieldValues; /* * Now divide the specifier up into name and default. */ result = TclListObjGetElements(interp, argArray[i], &fieldCount, &fieldValues); if (result != TCL_OK) { goto procError; } if (fieldCount > 2) { Tcl_Obj *errorObj = Tcl_NewStringObj( "too many fields in argument specifier \"", -1); Tcl_AppendObjToObj(errorObj, argArray[i]); Tcl_AppendToObj(errorObj, "\"", -1); Tcl_SetObjResult(interp, errorObj); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "PROC", "FORMALARGUMENTFORMAT", (char *)NULL); goto procError; } if ((fieldCount == 0) || (Tcl_GetCharLength(fieldValues[0]) == 0)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "argument with no name", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "PROC", "FORMALARGUMENTFORMAT", (char *)NULL); goto procError; } argname = TclGetStringFromObj(fieldValues[0], &nameLength); /* * Check that the formal parameter name is a scalar. */ argnamei = argname; argnamelast = (nameLength > 0) ? (argname + nameLength - 1) : argname; while (argnamei < argnamelast) { if (*argnamei == '(') { if (*argnamelast == ')') { /* We have an array element. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "formal parameter \"%s\" is an array element", TclGetString(fieldValues[0]))); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "PROC", "FORMALARGUMENTFORMAT", (char *)NULL); goto procError; } } else if (argnamei[0] == ':' && argnamei[1] == ':') { Tcl_Obj *errorObj = Tcl_NewStringObj( "formal parameter \"", -1); Tcl_AppendObjToObj(errorObj, fieldValues[0]); Tcl_AppendToObj(errorObj, "\" is not a simple name", -1); Tcl_SetObjResult(interp, errorObj); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "PROC", "FORMALARGUMENTFORMAT", (char *)NULL); goto procError; } argnamei++; } if (precompiled) { /* * Compare the parsed argument with the stored one. Note that the * only flag value that makes sense at this point is VAR_ARGUMENT * (its value was kept the same as pre VarReform to simplify * tbcload's processing of older byetcodes). * * The only other flag value that is important to retrieve from * precompiled procs is VAR_TEMPORARY (also unchanged). It is * needed later when retrieving the variable names. */ if ((localPtr->nameLength != nameLength) || (memcmp(localPtr->name, argname, nameLength) != 0) || (localPtr->frameIndex != i) || !(localPtr->flags & VAR_ARGUMENT) || (localPtr->defValuePtr == NULL && fieldCount == 2) || (localPtr->defValuePtr != NULL && fieldCount != 2)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "procedure \"%s\": formal parameter %" TCL_SIZE_MODIFIER "d is " "inconsistent with precompiled body", procName, i)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "PROC", "BYTECODELIES", (char *)NULL); goto procError; } /* * Compare the default value if any. */ if (localPtr->defValuePtr != NULL) { Tcl_Size tmpLength, valueLength; const char *tmpPtr = TclGetStringFromObj(localPtr->defValuePtr, &tmpLength); const char *value = TclGetStringFromObj(fieldValues[1], &valueLength); if ((valueLength != tmpLength) || memcmp(value, tmpPtr, tmpLength) != 0) { Tcl_Obj *errorObj = Tcl_ObjPrintf( "procedure \"%s\": formal parameter \"", procName); Tcl_AppendObjToObj(errorObj, fieldValues[0]); Tcl_AppendToObj(errorObj, "\" has " "default value inconsistent with precompiled body", -1); Tcl_SetObjResult(interp, errorObj); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "PROC", "BYTECODELIES", (char *)NULL); goto procError; } } if ((i == numArgs - 1) && (localPtr->nameLength == 4) && (localPtr->name[0] == 'a') && (strcmp(localPtr->name, "args") == 0)) { localPtr->flags |= VAR_IS_ARGS; } localPtr = localPtr->nextPtr; } else { /* * Allocate an entry in the runtime procedure frame's array of * local variables for the argument. */ localPtr = (CompiledLocal *)Tcl_Alloc( offsetof(CompiledLocal, name) + 1U + fieldValues[0]->length); if (procPtr->firstLocalPtr == NULL) { procPtr->firstLocalPtr = procPtr->lastLocalPtr = localPtr; } else { procPtr->lastLocalPtr->nextPtr = localPtr; procPtr->lastLocalPtr = localPtr; } localPtr->nextPtr = NULL; localPtr->nameLength = nameLength; localPtr->frameIndex = i; localPtr->flags = VAR_ARGUMENT; localPtr->resolveInfo = NULL; if (fieldCount == 2) { localPtr->defValuePtr = fieldValues[1]; Tcl_IncrRefCount(localPtr->defValuePtr); } else { localPtr->defValuePtr = NULL; } memcpy(localPtr->name, argname, fieldValues[0]->length + 1); if ((i == numArgs - 1) && (localPtr->nameLength == 4) && (localPtr->name[0] == 'a') && (memcmp(localPtr->name, "args", 4) == 0)) { localPtr->flags |= VAR_IS_ARGS; } } } *procPtrPtr = procPtr; return TCL_OK; procError: if (precompiled) { procPtr->refCount--; } else { Tcl_DecrRefCount(bodyPtr); while (procPtr->firstLocalPtr != NULL) { localPtr = procPtr->firstLocalPtr; procPtr->firstLocalPtr = localPtr->nextPtr; if (localPtr->defValuePtr != NULL) { Tcl_DecrRefCount(localPtr->defValuePtr); } Tcl_Free(localPtr); } Tcl_Free(procPtr); } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TclGetFrame -- * * Given a description of a procedure frame, such as the first argument * to an "uplevel" or "upvar" command, locate the call frame for the * appropriate level of procedure. * * Results: * The return value is -1 if an error occurred in finding the frame (in * this case an error message is left in the interp's result). 1 is * returned if string was either a number or a number preceded by "#" and * it specified a valid frame. 0 is returned if string isn't one of the * two things above (in this case, the lookup acts as if string were * "1"). The variable pointed to by framePtrPtr is filled in with the * address of the desired frame (unless an error occurs, in which case it * isn't modified). * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclGetFrame( Tcl_Interp *interp, /* Interpreter in which to find frame. */ const char *name, /* String describing frame. */ CallFrame **framePtrPtr) /* Store pointer to frame here (or NULL if * global frame indicated). */ { int result; Tcl_Obj obj; obj.bytes = (char *) name; obj.length = strlen(name); obj.typePtr = NULL; result = TclObjGetFrame(interp, &obj, framePtrPtr); TclFreeInternalRep(&obj); return result; } /* *---------------------------------------------------------------------- * * TclObjGetFrame -- * * Given a description of a procedure frame, such as the first argument * to an "uplevel" or "upvar" command, locate the call frame for the * appropriate level of procedure. * * Results: * The return value is -1 if an error occurred in finding the frame (in * this case an error message is left in the interp's result). 1 is * returned if objPtr was either an int or an int preceded by "#" and * it specified a valid frame. 0 is returned if objPtr isn't one of the * two things above (in this case, the lookup acts as if objPtr were * "1"). The variable pointed to by framePtrPtr is filled in with the * address of the desired frame (unless an error occurs, in which case it * isn't modified). * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclObjGetFrame( Tcl_Interp *interp, /* Interpreter in which to find frame. */ Tcl_Obj *objPtr, /* Object describing frame. */ CallFrame **framePtrPtr) /* Store pointer to frame here (or NULL if * global frame indicated). */ { Interp *iPtr = (Interp *) interp; int curLevel, level, result; const Tcl_ObjInternalRep *irPtr; const char *name = NULL; Tcl_WideInt w; /* * Parse object to figure out which level number to go to. */ result = 0; curLevel = iPtr->varFramePtr->level; /* * Check for integer first, since that has potential to spare us * a generation of a stringrep. */ if (objPtr == NULL) { /* Do nothing */ } else if (TCL_OK == Tcl_GetIntFromObj(NULL, objPtr, &level)) { TclGetWideIntFromObj(NULL, objPtr, &w); if (w < 0 || w > INT_MAX || curLevel > w + INT_MAX) { result = -1; } else { level = curLevel - level; result = 1; } } else if ((irPtr = TclFetchInternalRep(objPtr, &levelReferenceType))) { level = irPtr->wideValue; result = 1; } else { name = TclGetString(objPtr); if (name[0] == '#') { if (TCL_OK == Tcl_GetInt(NULL, name+1, &level)) { if (level < 0 || (level > 0 && name[1] == '-')) { result = -1; } else { Tcl_ObjInternalRep ir; ir.wideValue = level; Tcl_StoreInternalRep(objPtr, &levelReferenceType, &ir); result = 1; } } else { result = -1; } } else if (TclGetWideBitsFromObj(NULL, objPtr, &w) == TCL_OK) { /* * If this were an integer, we'd have succeeded already. * Docs say we have to treat this as a 'bad level' error. */ result = -1; } } if (result != -1) { /* if relative current level */ if (result == 0) { if (!curLevel) { /* we are in top-level, so simply generate bad level */ name = "1"; goto badLevel; } level = curLevel - 1; } if (level >= 0) { CallFrame *framePtr; for (framePtr = iPtr->varFramePtr; framePtr != NULL; framePtr = framePtr->callerVarPtr) { if ((int)framePtr->level == level) { *framePtrPtr = framePtr; return result; } } } } badLevel: if (name == NULL) { name = objPtr ? TclGetString(objPtr) : "1" ; } Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad level \"%s\"", name)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "LEVEL", name, (char *)NULL); return -1; } /* *---------------------------------------------------------------------- * * Tcl_UplevelObjCmd -- * * This object function is invoked to process the "uplevel" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl object result value. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int Uplevel_Callback( void *data[], Tcl_Interp *interp, int result) { CallFrame *savedVarFramePtr = (CallFrame *)data[0]; if (result == TCL_ERROR) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (\"uplevel\" body line %d)", Tcl_GetErrorLine(interp))); } /* * Restore the variable frame, and return. */ ((Interp *)interp)->varFramePtr = savedVarFramePtr; return result; } int Tcl_UplevelObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRUplevelObjCmd, clientData, objc, objv); } int TclNRUplevelObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; CmdFrame *invoker = NULL; int word = 0; int result; CallFrame *savedVarFramePtr, *framePtr; Tcl_Obj *objPtr; if (objc < 2) { /* to do * simplify things by interpreting the argument as a command when there * is only one argument. This requires a TIP since currently a single * argument is interpreted as a level indicator if possible. */ uplevelSyntax: Tcl_WrongNumArgs(interp, 1, objv, "?level? command ?arg ...?"); return TCL_ERROR; } else if (!TclHasStringRep(objv[1]) && objc == 2) { int status; Tcl_Size llength; status = TclListObjLength(interp, objv[1], &llength); if (status == TCL_OK && llength > 1) { /* the first argument can't interpreted as a level. Avoid * generating a string representation of the script. */ result = TclGetFrame(interp, "1", &framePtr); if (result == -1) { return TCL_ERROR; } objc -= 1; objv += 1; goto havelevel; } } /* * Find the level to use for executing the command. */ result = TclObjGetFrame(interp, objv[1], &framePtr); if (result == -1) { return TCL_ERROR; } objc -= result + 1; if (objc == 0) { goto uplevelSyntax; } objv += result + 1; havelevel: /* * Modify the interpreter state to execute in the given frame. */ savedVarFramePtr = iPtr->varFramePtr; iPtr->varFramePtr = framePtr; /* * Execute the residual arguments as a command. */ if (objc == 1) { /* * TIP #280. Make actual argument location available to eval'd script */ TclArgumentGet(interp, objv[0], &invoker, &word); objPtr = objv[0]; } else { /* * More than one argument: concatenate them together with spaces * between, then evaluate the result. Tcl_EvalObjEx will delete the * object when it decrements its refcount after eval'ing it. */ objPtr = Tcl_ConcatObj(objc, objv); } TclNRAddCallback(interp, Uplevel_Callback, savedVarFramePtr, NULL, NULL, NULL); return TclNREvalObjEx(interp, objPtr, 0, invoker, word); } /* *---------------------------------------------------------------------- * * TclFindProc -- * * Given the name of a procedure, return a pointer to the record * describing the procedure. The procedure will be looked up using the * usual rules: first in the current namespace and then in the global * namespace. * * Results: * NULL is returned if the name doesn't correspond to any procedure. * Otherwise, the return value is a pointer to the procedure's record. If * the name is found but refers to an imported command that points to a * "real" procedure defined in another namespace, a pointer to that * "real" procedure's structure is returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ Proc * TclFindProc( Interp *iPtr, /* Interpreter in which to look. */ const char *procName) /* Name of desired procedure. */ { Tcl_Command cmd; Command *cmdPtr; cmd = Tcl_FindCommand((Tcl_Interp *) iPtr, procName, NULL, /*flags*/ 0); if (cmd == (Tcl_Command) NULL) { return NULL; } cmdPtr = (Command *) cmd; return TclIsProc(cmdPtr); } /* *---------------------------------------------------------------------- * * TclIsProc -- * * Tells whether a command is a Tcl procedure or not. * * Results: * If the given command is actually a Tcl procedure, the return value is * the address of the record describing the procedure. Otherwise the * return value is 0. * * Side effects: * None. * *---------------------------------------------------------------------- */ Proc * TclIsProc( Command *cmdPtr) /* Command to test. */ { Tcl_Command origCmd = TclGetOriginalCommand((Tcl_Command) cmdPtr); if (origCmd != NULL) { cmdPtr = (Command *) origCmd; } if (cmdPtr->deleteProc == TclProcDeleteProc) { return (Proc *)cmdPtr->objClientData; } return NULL; } static int ProcWrongNumArgs( Tcl_Interp *interp, int skip) { CallFrame *framePtr = ((Interp *)interp)->varFramePtr; Proc *procPtr = framePtr->procPtr; Tcl_Size localCt = procPtr->numCompiledLocals, numArgs, i; Tcl_Obj **desiredObjs; const char *final = NULL; /* * Build up desired argument list for Tcl_WrongNumArgs */ numArgs = framePtr->procPtr->numArgs; desiredObjs = (Tcl_Obj **)TclStackAlloc(interp, sizeof(Tcl_Obj *) * (numArgs+1)); if (framePtr->isProcCallFrame & FRAME_IS_LAMBDA) { desiredObjs[0] = Tcl_NewStringObj("lambdaExpr", -1); } else { desiredObjs[0] = framePtr->objv[skip-1]; } Tcl_IncrRefCount(desiredObjs[0]); if (localCt > 0) { Var *defPtr = (Var *)(&framePtr->localCachePtr->varName0 + localCt); for (i=1 ; i<=numArgs ; i++, defPtr++) { Tcl_Obj *argObj; Tcl_Obj *namePtr = localName(framePtr, i-1); if (defPtr->value.objPtr != NULL) { TclNewObj(argObj); Tcl_AppendStringsToObj(argObj, "?", TclGetString(namePtr), "?", (char *)NULL); } else if (defPtr->flags & VAR_IS_ARGS) { numArgs--; final = "?arg ...?"; break; } else { argObj = namePtr; Tcl_IncrRefCount(namePtr); } desiredObjs[i] = argObj; } } Tcl_ResetResult(interp); Tcl_WrongNumArgs(interp, numArgs+1, desiredObjs, final); for (i=0 ; i<=numArgs ; i++) { Tcl_DecrRefCount(desiredObjs[i]); } TclStackFree(interp, desiredObjs); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * InitResolvedLocals -- * * This routine is invoked in order to initialize the compiled locals * table for a new call frame. * * Results: * None. * * Side effects: * May invoke various name resolvers in order to determine which * variables are being referenced at runtime. * *---------------------------------------------------------------------- */ static void InitResolvedLocals( Tcl_Interp *interp, /* Current interpreter. */ ByteCode *codePtr, Var *varPtr, Namespace *nsPtr) /* Pointer to current namespace. */ { Interp *iPtr = (Interp *) interp; int haveResolvers = (nsPtr->compiledVarResProc || iPtr->resolverPtr); CompiledLocal *firstLocalPtr, *localPtr; int varNum; Tcl_ResolvedVarInfo *resVarInfo; /* * Find the localPtr corresponding to varPtr */ varNum = varPtr - iPtr->framePtr->compiledLocals; localPtr = iPtr->framePtr->procPtr->firstLocalPtr; while (varNum--) { localPtr = localPtr->nextPtr; } if (!(haveResolvers && (codePtr->flags & TCL_BYTECODE_RESOLVE_VARS))) { goto doInitResolvedLocals; } /* * This is the first run after a recompile, or else the resolver epoch * has changed: update the resolver cache. */ firstLocalPtr = localPtr; for (; localPtr != NULL; localPtr = localPtr->nextPtr) { if (localPtr->resolveInfo) { if (localPtr->resolveInfo->deleteProc) { localPtr->resolveInfo->deleteProc(localPtr->resolveInfo); } else { Tcl_Free(localPtr->resolveInfo); } localPtr->resolveInfo = NULL; } localPtr->flags &= ~VAR_RESOLVED; if (haveResolvers && !(localPtr->flags & (VAR_ARGUMENT|VAR_TEMPORARY))) { ResolverScheme *resPtr = iPtr->resolverPtr; Tcl_ResolvedVarInfo *vinfo; int result; if (nsPtr->compiledVarResProc) { result = nsPtr->compiledVarResProc(nsPtr->interp, localPtr->name, localPtr->nameLength, (Tcl_Namespace *) nsPtr, &vinfo); } else { result = TCL_CONTINUE; } while ((result == TCL_CONTINUE) && resPtr) { if (resPtr->compiledVarResProc) { result = resPtr->compiledVarResProc(nsPtr->interp, localPtr->name, localPtr->nameLength, (Tcl_Namespace *) nsPtr, &vinfo); } resPtr = resPtr->nextPtr; } if (result == TCL_OK) { localPtr->resolveInfo = vinfo; localPtr->flags |= VAR_RESOLVED; } } } localPtr = firstLocalPtr; codePtr->flags &= ~TCL_BYTECODE_RESOLVE_VARS; /* * Initialize the array of local variables stored in the call frame. Some * variables may have special resolution rules. In that case, we call * their "resolver" procs to get our hands on the variable, and we make * the compiled local a link to the real variable. */ doInitResolvedLocals: for (; localPtr != NULL; varPtr++, localPtr = localPtr->nextPtr) { varPtr->flags = 0; varPtr->value.objPtr = NULL; /* * Now invoke the resolvers to determine the exact variables that * should be used. */ resVarInfo = localPtr->resolveInfo; if (resVarInfo && resVarInfo->fetchProc) { Var *resolvedVarPtr = (Var *) resVarInfo->fetchProc(interp, resVarInfo); if (resolvedVarPtr) { if (TclIsVarInHash(resolvedVarPtr)) { VarHashRefCount(resolvedVarPtr)++; } varPtr->flags = VAR_LINK; varPtr->value.linkPtr = resolvedVarPtr; } } } } void TclFreeLocalCache( Tcl_Interp *interp, LocalCache *localCachePtr) { Tcl_Size i; Tcl_Obj **namePtrPtr = &localCachePtr->varName0; for (i = 0; i < localCachePtr->numVars; i++, namePtrPtr++) { Tcl_Obj *objPtr = *namePtrPtr; if (objPtr) { /* TclReleaseLiteral calls Tcl_DecrRefCount for us */ TclReleaseLiteral(interp, objPtr); } } Tcl_Free(localCachePtr); } static void InitLocalCache( Proc *procPtr) { Interp *iPtr = procPtr->iPtr; ByteCode *codePtr; Tcl_Size localCt = procPtr->numCompiledLocals; Tcl_Size numArgs = procPtr->numArgs, i = 0; Tcl_Obj **namePtr; Var *varPtr; LocalCache *localCachePtr; CompiledLocal *localPtr; int isNew; ByteCodeGetInternalRep(procPtr->bodyPtr, &tclByteCodeType, codePtr); /* * Cache the names and initial values of local variables; store the * cache in both the framePtr for this execution and in the codePtr * for future calls. */ localCachePtr = (LocalCache *)Tcl_Alloc(offsetof(LocalCache, varName0) + localCt * sizeof(Tcl_Obj *) + numArgs * sizeof(Var)); namePtr = &localCachePtr->varName0; varPtr = (Var *) (namePtr + localCt); localPtr = procPtr->firstLocalPtr; while (localPtr) { if (TclIsVarTemporary(localPtr)) { *namePtr = NULL; } else { *namePtr = TclCreateLiteral(iPtr, localPtr->name, localPtr->nameLength, /* hash */ TCL_INDEX_NONE, &isNew, /* nsPtr */ NULL, 0, NULL); Tcl_IncrRefCount(*namePtr); } if (i < numArgs) { varPtr->flags = (localPtr->flags & VAR_IS_ARGS); varPtr->value.objPtr = localPtr->defValuePtr; varPtr++; i++; } namePtr++; localPtr = localPtr->nextPtr; } codePtr->localCachePtr = localCachePtr; localCachePtr->refCount = 1; localCachePtr->numVars = localCt; } /* *---------------------------------------------------------------------- * * InitArgsAndLocals -- * * This routine is invoked in order to initialize the arguments and other * compiled locals table for a new call frame. * * Results: * A standard Tcl result. * * Side effects: * Allocates memory on the stack for the compiled local variables, the * caller is responsible for freeing them. Initialises all variables. May * invoke various name resolvers in order to determine which variables * are being referenced at runtime. * *---------------------------------------------------------------------- */ static int InitArgsAndLocals( Tcl_Interp *interp, /* Interpreter in which procedure was * invoked. */ int skip) /* Number of initial arguments to be skipped, * i.e., words in the "command name". */ { CallFrame *framePtr = ((Interp *)interp)->varFramePtr; Proc *procPtr = framePtr->procPtr; ByteCode *codePtr; Var *varPtr, *defPtr; Tcl_Size localCt = procPtr->numCompiledLocals, numArgs, argCt, i, imax; Tcl_Obj *const *argObjs; ByteCodeGetInternalRep(procPtr->bodyPtr, &tclByteCodeType, codePtr); /* * Make sure that the local cache of variable names and initial values has * been initialised properly . */ if (localCt) { if (!codePtr->localCachePtr) { InitLocalCache(procPtr) ; } framePtr->localCachePtr = codePtr->localCachePtr; framePtr->localCachePtr->refCount++; defPtr = (Var *) (&framePtr->localCachePtr->varName0 + localCt); } else { defPtr = NULL; } /* * Create the "compiledLocals" array. Make sure it is large enough to hold * all the procedure's compiled local variables, including its formal * parameters. */ varPtr = (Var *)TclStackAlloc(interp, localCt * sizeof(Var)); framePtr->compiledLocals = varPtr; framePtr->numCompiledLocals = localCt; /* * Match and assign the call's actual parameters to the procedure's formal * arguments. The formal arguments are described by the first numArgs * entries in both the Proc structure's local variable list and the call * frame's local variable array. */ numArgs = procPtr->numArgs; argCt = framePtr->objc - skip; /* Set it to the number of args to the * procedure. */ if (numArgs == 0) { if (argCt) { goto incorrectArgs; } else { goto correctArgs; } } argObjs = framePtr->objv + skip; imax = ((argCt < numArgs-1) ? argCt : numArgs-1); for (i = 0; i < imax; i++, varPtr++, defPtr ? defPtr++ : defPtr) { /* * "Normal" arguments; last formal is special, depends on it being * 'args'. */ Tcl_Obj *objPtr = argObjs[i]; varPtr->flags = 0; varPtr->value.objPtr = objPtr; Tcl_IncrRefCount(objPtr); /* Local var is a reference. */ } for (; i < numArgs-1; i++, varPtr++, defPtr ? defPtr++ : defPtr) { /* * This loop is entered if argCt < (numArgs-1). Set default values; * last formal is special. */ Tcl_Obj *objPtr = defPtr ? defPtr->value.objPtr : NULL; if (!objPtr) { goto incorrectArgs; } varPtr->flags = 0; varPtr->value.objPtr = objPtr; Tcl_IncrRefCount(objPtr); /* Local var reference. */ } /* * When we get here, the last formal argument remains to be defined: * defPtr and varPtr point to the last argument to be initialized. */ varPtr->flags = 0; if (defPtr && defPtr->flags & VAR_IS_ARGS) { Tcl_Obj *listPtr = Tcl_NewListObj((argCt>i)? argCt-i : 0, argObjs+i); varPtr->value.objPtr = listPtr; Tcl_IncrRefCount(listPtr); /* Local var is a reference. */ } else if (argCt == numArgs) { Tcl_Obj *objPtr = argObjs[i]; varPtr->value.objPtr = objPtr; Tcl_IncrRefCount(objPtr); /* Local var is a reference. */ } else if ((argCt < numArgs) && defPtr && defPtr->value.objPtr) { Tcl_Obj *objPtr = defPtr->value.objPtr; varPtr->value.objPtr = objPtr; Tcl_IncrRefCount(objPtr); /* Local var is a reference. */ } else { goto incorrectArgs; } varPtr++; /* * Initialise and resolve the remaining compiledLocals. In the absence of * resolvers, they are undefined local vars: (flags=0, value=NULL). */ correctArgs: if (numArgs < localCt) { if (!framePtr->nsPtr->compiledVarResProc && !((Interp *)interp)->resolverPtr) { memset(varPtr, 0, (localCt - numArgs)*sizeof(Var)); } else { InitResolvedLocals(interp, codePtr, varPtr, framePtr->nsPtr); } } return TCL_OK; /* * Initialise all compiled locals to avoid problems at DeleteLocalVars. */ incorrectArgs: if ((skip != 1) && TclInitRewriteEnsemble(interp, skip-1, 0, framePtr->objv)) { TclNRAddCallback(interp, TclClearRootEnsemble, NULL, NULL, NULL, NULL); } memset(varPtr, 0, ((framePtr->compiledLocals + localCt)-varPtr) * sizeof(Var)); return ProcWrongNumArgs(interp, skip); } /* *---------------------------------------------------------------------- * * TclPushProcCallFrame -- * * Compiles a proc body if necessary, then pushes a CallFrame suitable * for executing it. * * Results: * A standard Tcl object result value. * * Side effects: * The proc's body may be recompiled. A CallFrame is pushed, it will have * to be popped by the caller. * *---------------------------------------------------------------------- */ int TclPushProcCallFrame( void *clientData, /* Record describing procedure to be * interpreted. */ Tcl_Interp *interp, /* Interpreter in which procedure was * invoked. */ Tcl_Size objc, /* Count of number of arguments to this * procedure. */ Tcl_Obj *const objv[], /* Argument value objects. */ int isLambda) /* 1 if this is a call by ApplyObjCmd: it * needs special rules for error msg */ { Proc *procPtr = (Proc *)clientData; Namespace *nsPtr = procPtr->cmdPtr->nsPtr; CallFrame *framePtr, **framePtrPtr; int result; ByteCode *codePtr; /* * If necessary (i.e. if we haven't got a suitable compilation already * cached) compile the procedure's body. The compiler will allocate frame * slots for the procedure's non-argument local variables. Note that * compiling the body might increase procPtr->numCompiledLocals if new * local variables are found while compiling. */ ByteCodeGetInternalRep(procPtr->bodyPtr, &tclByteCodeType, codePtr); if (codePtr != NULL) { Interp *iPtr = (Interp *) interp; /* * When we've got bytecode, this is the check for validity. That is, * the bytecode must be for the right interpreter (no cross-leaks!), * the code must be from the current epoch (so subcommand compilation * is up-to-date), the namespace must match (so variable handling * is right) and the resolverEpoch must match (so that new shadowed * commands and/or resolver changes are considered). * Ensure the ByteCode's procPtr is the same (or it's precompiled). */ if (((Interp *) *codePtr->interpHandle != iPtr) || (codePtr->compileEpoch != iPtr->compileEpoch) || (codePtr->nsPtr != nsPtr) || (codePtr->nsEpoch != nsPtr->resolverEpoch) || ((codePtr->procPtr != procPtr) && procPtr->bodyPtr->bytes)) { goto doCompilation; } } else { doCompilation: result = TclProcCompileProc(interp, procPtr, procPtr->bodyPtr, nsPtr, (isLambda ? "body of lambda term" : "body of proc"), TclGetString(objv[isLambda])); if (result != TCL_OK) { return result; } } /* * Set up and push a new call frame for the new procedure invocation. * This call frame will execute in the proc's namespace, which might be * different than the current namespace. The proc's namespace is that of * its command, which can change if the command is renamed from one * namespace to another. */ framePtrPtr = &framePtr; (void) TclPushStackFrame(interp, (Tcl_CallFrame **) framePtrPtr, (Tcl_Namespace *) nsPtr, (isLambda? (FRAME_IS_PROC|FRAME_IS_LAMBDA) : FRAME_IS_PROC)); framePtr->objc = objc; framePtr->objv = objv; framePtr->procPtr = procPtr; return TCL_OK; } /* *---------------------------------------------------------------------- * * TclObjInterpProc -- * * When a Tcl procedure gets invoked during bytecode evaluation, this * object-based routine gets invoked to interpret the procedure. * * Results: * A standard Tcl object result value. * * Side effects: * Depends on the commands in the procedure. * *---------------------------------------------------------------------- */ int TclObjInterpProc( void *clientData, /* Record describing procedure to be * interpreted. */ Tcl_Interp *interp, /* Interpreter in which procedure was * invoked. */ int objc, /* Count of number of arguments to this * procedure. */ Tcl_Obj *const objv[]) /* Argument value objects. */ { /* * Not used much in the core; external interface for iTcl */ return Tcl_NRCallObjProc(interp, NRInterpProc, clientData, objc, objv); } int TclNRInterpProc( void *clientData, /* Record describing procedure to be * interpreted. */ Tcl_Interp *interp, /* Interpreter in which procedure was * invoked. */ Tcl_Size objc, /* Count of number of arguments to this * procedure. */ Tcl_Obj *const objv[]) /* Argument value objects. */ { int result = TclPushProcCallFrame(clientData, interp, objc, objv, /*isLambda*/ 0); if (result != TCL_OK) { return TCL_ERROR; } return TclNRInterpProcCore(interp, objv[0], 1, &MakeProcError); } static int NRInterpProc( void *clientData, /* Record describing procedure to be * interpreted. */ Tcl_Interp *interp, /* Interpreter in which procedure was * invoked. */ int objc, /* Count of number of arguments to this * procedure. */ Tcl_Obj *const objv[]) /* Argument value objects. */ { int result = TclPushProcCallFrame(clientData, interp, objc, objv, /*isLambda*/ 0); if (result != TCL_OK) { return TCL_ERROR; } return TclNRInterpProcCore(interp, objv[0], 1, &MakeProcError); } static int ObjInterpProc2( void *clientData, /* Record describing procedure to be * interpreted. */ Tcl_Interp *interp, /* Interpreter in which procedure was * invoked. */ Tcl_Size objc, /* Count of number of arguments to this * procedure. */ Tcl_Obj *const objv[]) /* Argument value objects. */ { /* * Not used much in the core; external interface for iTcl */ return Tcl_NRCallObjProc2(interp, TclNRInterpProc, clientData, objc, objv); } /* *---------------------------------------------------------------------- * * TclNRInterpProcCore -- * * When a Tcl procedure, lambda term or anything else that works like a * procedure gets invoked during bytecode evaluation, this object-based * routine gets invoked to interpret the body. * * Results: * A standard Tcl object result value. * * Side effects: * Nearly anything; depends on the commands in the procedure body. * *---------------------------------------------------------------------- */ int TclNRInterpProcCore( Tcl_Interp *interp, /* Interpreter in which procedure was * invoked. */ Tcl_Obj *procNameObj, /* Procedure name for error reporting. */ Tcl_Size skip, /* Number of initial arguments to be skipped, * i.e., words in the "command name". */ ProcErrorProc *errorProc) /* How to convert results from the script into * results of the overall procedure. */ { Interp *iPtr = (Interp *) interp; Proc *procPtr = iPtr->varFramePtr->procPtr; int result; CallFrame *freePtr; ByteCode *codePtr; result = InitArgsAndLocals(interp, skip); if (result != TCL_OK) { freePtr = iPtr->framePtr; Tcl_PopCallFrame(interp); /* Pop but do not free. */ TclStackFree(interp, freePtr->compiledLocals); /* Free compiledLocals. */ TclStackFree(interp, freePtr); /* Free CallFrame. */ return TCL_ERROR; } #if defined(TCL_COMPILE_DEBUG) if (tclTraceExec >= 1) { CallFrame *framePtr = iPtr->varFramePtr; Tcl_Size i; if (framePtr->isProcCallFrame & FRAME_IS_LAMBDA) { fprintf(stdout, "Calling lambda "); } else { fprintf(stdout, "Calling proc "); } for (i = 0; i < framePtr->objc; i++) { TclPrintObject(stdout, framePtr->objv[i], 15); fprintf(stdout, " "); } fprintf(stdout, "\n"); fflush(stdout); } #endif /*TCL_COMPILE_DEBUG*/ #ifdef USE_DTRACE if (TCL_DTRACE_PROC_ARGS_ENABLED()) { Tcl_Size l = iPtr->varFramePtr->isProcCallFrame & FRAME_IS_LAMBDA ? 1 : 0; const char *a[10]; Tcl_Size i; for (i = 0 ; i < 10 ; i++) { a[i] = (l < iPtr->varFramePtr->objc ? TclGetString(iPtr->varFramePtr->objv[l]) : NULL); l++; } TCL_DTRACE_PROC_ARGS(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]); } if (TCL_DTRACE_PROC_INFO_ENABLED() && iPtr->cmdFramePtr) { Tcl_Obj *info = TclInfoFrame(interp, iPtr->cmdFramePtr); const char *a[6]; Tcl_Size i[2]; TclDTraceInfo(info, a, i); TCL_DTRACE_PROC_INFO(a[0], a[1], a[2], a[3], i[0], i[1], a[4], a[5]); TclDecrRefCount(info); } if (TCL_DTRACE_PROC_ENTRY_ENABLED()) { Tcl_Size l = iPtr->varFramePtr->isProcCallFrame & FRAME_IS_LAMBDA ? 1 : 0; TCL_DTRACE_PROC_ENTRY(l < iPtr->varFramePtr->objc ? TclGetString(iPtr->varFramePtr->objv[l]) : NULL, iPtr->varFramePtr->objc - l - 1, (Tcl_Obj **)(iPtr->varFramePtr->objv + l + 1)); } if (TCL_DTRACE_PROC_ENTRY_ENABLED()) { Tcl_Size l = iPtr->varFramePtr->isProcCallFrame & FRAME_IS_LAMBDA ? 1 : 0; TCL_DTRACE_PROC_ENTRY(l < iPtr->varFramePtr->objc ? TclGetString(iPtr->varFramePtr->objv[l]) : NULL, iPtr->varFramePtr->objc - l - 1, (Tcl_Obj **)(iPtr->varFramePtr->objv + l + 1)); } #endif /* USE_DTRACE */ /* * Invoke the commands in the procedure's body. */ procPtr->refCount++; ByteCodeGetInternalRep(procPtr->bodyPtr, &tclByteCodeType, codePtr); TclNRAddCallback(interp, InterpProcNR2, procNameObj, errorProc, NULL, NULL); return TclNRExecuteByteCode(interp, codePtr); } static int InterpProcNR2( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; Proc *procPtr = iPtr->varFramePtr->procPtr; CallFrame *freePtr; Tcl_Obj *procNameObj = (Tcl_Obj *)data[0]; ProcErrorProc *errorProc = (ProcErrorProc *)data[1]; if (TCL_DTRACE_PROC_RETURN_ENABLED()) { Tcl_Size l = iPtr->varFramePtr->isProcCallFrame & FRAME_IS_LAMBDA ? 1 : 0; TCL_DTRACE_PROC_RETURN(l < iPtr->varFramePtr->objc ? TclGetString(iPtr->varFramePtr->objv[l]) : NULL, result); } if (procPtr->refCount-- <= 1) { TclProcCleanupProc(procPtr); } /* * Free the stack-allocated compiled locals and CallFrame. It is important * to pop the call frame without freeing it first: the compiledLocals * cannot be freed before the frame is popped, as the local variables must * be deleted. But the compiledLocals must be freed first, as they were * allocated later on the stack. */ if (result != TCL_OK) { goto process; } done: if (TCL_DTRACE_PROC_RESULT_ENABLED()) { Tcl_Size l = iPtr->varFramePtr->isProcCallFrame & FRAME_IS_LAMBDA ? 1 : 0; Tcl_Obj *r = Tcl_GetObjResult(interp); TCL_DTRACE_PROC_RESULT(l < iPtr->varFramePtr->objc ? TclGetString(iPtr->varFramePtr->objv[l]) : NULL, result, TclGetString(r), r); } freePtr = iPtr->framePtr; Tcl_PopCallFrame(interp); /* Pop but do not free. */ TclStackFree(interp, freePtr->compiledLocals); /* Free compiledLocals. */ TclStackFree(interp, freePtr); /* Free CallFrame. */ return result; /* * Process any non-TCL_OK result code. */ process: switch (result) { case TCL_RETURN: /* * If it is a 'return', do the TIP#90 processing now. */ result = TclUpdateReturnInfo((Interp *) interp); break; case TCL_CONTINUE: case TCL_BREAK: /* * It's an error to get to this point from a 'break' or 'continue', so * transform to an error now. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invoked \"%s\" outside of a loop", ((result == TCL_BREAK) ? "break" : "continue"))); Tcl_SetErrorCode(interp, "TCL", "RESULT", "UNEXPECTED", (char *)NULL); result = TCL_ERROR; /* FALLTHRU */ case TCL_ERROR: /* * Now it _must_ be an error, so we need to log it as such. This means * filling out the error trace. Luckily, we just hand this off to the * function handed to us as an argument. */ errorProc(interp, procNameObj); } goto done; } /* *---------------------------------------------------------------------- * * TclProcCompileProc -- * * Called just before a procedure is executed to compile the body to byte * codes. If the type of the body is not "byte code" or if the compile * conditions have changed (namespace context, epoch counters, etc.) then * the body is recompiled. Otherwise, this function does nothing. * * Results: * None. * * Side effects: * May change the internal representation of the body object to compiled * code. * *---------------------------------------------------------------------- */ int TclProcCompileProc( Tcl_Interp *interp, /* Interpreter containing procedure. */ Proc *procPtr, /* Data associated with procedure. */ Tcl_Obj *bodyPtr, /* Body of proc. (Usually procPtr->bodyPtr, * but could be any code fragment compiled in * the context of this procedure.) */ Namespace *nsPtr, /* Namespace containing procedure. */ const char *description, /* string describing this body of code. */ const char *procName) /* Name of this procedure. */ { Interp *iPtr = (Interp *) interp; Tcl_CallFrame *framePtr; ByteCode *codePtr; ByteCodeGetInternalRep(bodyPtr, &tclByteCodeType, codePtr); /* * If necessary, compile the procedure's body. The compiler will allocate * frame slots for the procedure's non-argument local variables. If the * ByteCode already exists, make sure it hasn't been invalidated by * someone redefining a core command (this might make the compiled code * wrong). Also, if the code was compiled in/for a different interpreter, * we recompile it. Note that compiling the body might increase * procPtr->numCompiledLocals if new local variables are found while * compiling. * * Ensure the ByteCode's procPtr is the same (or it is pure precompiled). * Precompiled procedure bodies, however, are immutable and therefore they * are not recompiled, even if things have changed. */ if (codePtr != NULL) { if (((Interp *) *codePtr->interpHandle == iPtr) && (codePtr->compileEpoch == iPtr->compileEpoch) && (codePtr->nsPtr == nsPtr) && (codePtr->nsEpoch == nsPtr->resolverEpoch) && ((codePtr->procPtr == procPtr) || !bodyPtr->bytes)) { return TCL_OK; } if (codePtr->flags & TCL_BYTECODE_PRECOMPILED) { if ((Interp *) *codePtr->interpHandle != iPtr) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "a precompiled script jumped interps", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "PROC", "CROSSINTERPBYTECODE", (char *)NULL); return TCL_ERROR; } codePtr->compileEpoch = iPtr->compileEpoch; codePtr->nsPtr = nsPtr; } else { Tcl_StoreInternalRep(bodyPtr, &tclByteCodeType, NULL); codePtr = NULL; } } if (codePtr == NULL) { Tcl_HashEntry *hePtr; #ifdef TCL_COMPILE_DEBUG if (tclTraceCompile >= 1) { /* * Display a line summarizing the top level command we are about * to compile. */ Tcl_Obj *message; TclNewLiteralStringObj(message, "Compiling "); Tcl_IncrRefCount(message); Tcl_AppendStringsToObj(message, description, " \"", (char *)NULL); Tcl_AppendLimitedToObj(message, procName, TCL_INDEX_NONE, 50, NULL); fprintf(stdout, "%s\"\n", TclGetString(message)); Tcl_DecrRefCount(message); } #else (void)description; (void)procName; #endif /* * Plug the current procPtr into the interpreter and coerce the code * body to byte codes. The interpreter needs to know which proc it's * compiling so that it can access its list of compiled locals. * * TRICKY NOTE: Be careful to push a call frame with the proper * namespace context, so that the byte codes are compiled in the * appropriate class context. */ iPtr->compiledProcPtr = procPtr; if (procPtr->numCompiledLocals > procPtr->numArgs) { CompiledLocal *clPtr = procPtr->firstLocalPtr; CompiledLocal *lastPtr = NULL; int i, numArgs = procPtr->numArgs; for (i = 0; i < numArgs; i++) { lastPtr = clPtr; clPtr = clPtr->nextPtr; } if (lastPtr) { lastPtr->nextPtr = NULL; } else { procPtr->firstLocalPtr = NULL; } procPtr->lastLocalPtr = lastPtr; while (clPtr) { CompiledLocal *toFree = clPtr; clPtr = clPtr->nextPtr; if (toFree->resolveInfo) { if (toFree->resolveInfo->deleteProc) { toFree->resolveInfo->deleteProc(toFree->resolveInfo); } else { Tcl_Free(toFree->resolveInfo); } } Tcl_Free(toFree); } procPtr->numCompiledLocals = procPtr->numArgs; } (void) TclPushStackFrame(interp, &framePtr, (Tcl_Namespace *) nsPtr, /* isProcCallFrame */ 0); /* * TIP #280: We get the invoking context from the cmdFrame which * was saved by 'Tcl_ProcObjCmd' (using linePBodyPtr). */ hePtr = Tcl_FindHashEntry(iPtr->linePBodyPtr, procPtr); /* * Constructed saved frame has body as word 0. See Tcl_ProcObjCmd. */ iPtr->invokeWord = 0; iPtr->invokeCmdFramePtr = hePtr ? (CmdFrame *)Tcl_GetHashValue(hePtr) : NULL; TclSetByteCodeFromAny(interp, bodyPtr, NULL, NULL); iPtr->invokeCmdFramePtr = NULL; TclPopStackFrame(interp); } else if (codePtr->nsEpoch != nsPtr->resolverEpoch) { /* * The resolver epoch has changed, but we only need to invalidate the * resolver cache. */ codePtr->nsEpoch = nsPtr->resolverEpoch; codePtr->flags |= TCL_BYTECODE_RESOLVE_VARS; } return TCL_OK; } /* *---------------------------------------------------------------------- * * MakeProcError -- * * Function called by TclObjInterpProc to create the stack information * upon an error from a procedure. * * Results: * The interpreter's error info trace is set to a value that supplements * the error code. * * Side effects: * none. * *---------------------------------------------------------------------- */ static void MakeProcError( Tcl_Interp *interp, /* The interpreter in which the procedure was * called. */ Tcl_Obj *procNameObj) /* Name of the procedure. Used for error * messages and trace information. */ { int overflow, limit = 60; Tcl_Size nameLen; const char *procName = TclGetStringFromObj(procNameObj, &nameLen); overflow = (nameLen > (Tcl_Size)limit); Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (procedure \"%.*s%s\" line %d)", (overflow ? limit : (int)nameLen), procName, (overflow ? "..." : ""), Tcl_GetErrorLine(interp))); } /* *---------------------------------------------------------------------- * * TclProcDeleteProc -- * * This function is invoked just before a command procedure is removed * from an interpreter. Its job is to release all the resources allocated * to the procedure. * * Results: * None. * * Side effects: * Memory gets freed, unless the procedure is actively being executed. * In this case the cleanup is delayed until the last call to the current * procedure completes. * *---------------------------------------------------------------------- */ void TclProcDeleteProc( void *clientData) /* Procedure to be deleted. */ { Proc *procPtr = (Proc *)clientData; if (procPtr->refCount-- <= 1) { TclProcCleanupProc(procPtr); } } /* *---------------------------------------------------------------------- * * TclProcCleanupProc -- * * This function does all the real work of freeing up a Proc structure. * It's called only when the structure's reference count becomes zero. * * Results: * None. * * Side effects: * Memory gets freed. * *---------------------------------------------------------------------- */ void TclProcCleanupProc( Proc *procPtr) /* Procedure to be deleted. */ { CompiledLocal *localPtr; Tcl_Obj *bodyPtr = procPtr->bodyPtr; Tcl_Obj *defPtr; Tcl_ResolvedVarInfo *resVarInfo; Tcl_HashEntry *hePtr = NULL; CmdFrame *cfPtr = NULL; Interp *iPtr = procPtr->iPtr; if (bodyPtr != NULL) { /* procPtr is stored in body's ByteCode, so ensure to reset it. */ ByteCode *codePtr; ByteCodeGetInternalRep(bodyPtr, &tclByteCodeType, codePtr); if (codePtr != NULL && codePtr->procPtr == procPtr) { codePtr->procPtr = NULL; } Tcl_DecrRefCount(bodyPtr); } for (localPtr = procPtr->firstLocalPtr; localPtr != NULL; ) { CompiledLocal *nextPtr = localPtr->nextPtr; resVarInfo = localPtr->resolveInfo; if (resVarInfo) { if (resVarInfo->deleteProc) { resVarInfo->deleteProc(resVarInfo); } else { Tcl_Free(resVarInfo); } } if (localPtr->defValuePtr != NULL) { defPtr = localPtr->defValuePtr; Tcl_DecrRefCount(defPtr); } Tcl_Free(localPtr); localPtr = nextPtr; } Tcl_Free(procPtr); /* * TIP #280: Release the location data associated with this Proc * structure, if any. The interpreter may not exist (For example for * procbody structures created by tbcload. */ if (iPtr == NULL) { return; } hePtr = Tcl_FindHashEntry(iPtr->linePBodyPtr, procPtr); if (!hePtr) { return; } cfPtr = (CmdFrame *)Tcl_GetHashValue(hePtr); if (cfPtr) { if (cfPtr->type == TCL_LOCATION_SOURCE) { Tcl_DecrRefCount(cfPtr->data.eval.path); cfPtr->data.eval.path = NULL; } Tcl_Free(cfPtr->line); cfPtr->line = NULL; Tcl_Free(cfPtr); } Tcl_DeleteHashEntry(hePtr); } /* *---------------------------------------------------------------------- * * TclUpdateReturnInfo -- * * This function is called when procedures return, and at other points * where the TCL_RETURN code is used. It examines the returnLevel and * returnCode to determine the real return status. * * Results: * The return value is the true completion code to use for the procedure * or script, instead of TCL_RETURN. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclUpdateReturnInfo( Interp *iPtr) /* Interpreter for which TCL_RETURN exception * is being processed. */ { int code = TCL_RETURN; iPtr->returnLevel--; if (iPtr->returnLevel < 0) { Tcl_Panic("TclUpdateReturnInfo: negative return level"); } if (iPtr->returnLevel == 0) { /* * Now we've reached the level to return the requested -code. * Since iPtr->returnLevel and iPtr->returnCode have completed * their task, we now reset them to default values so that any * bare "return TCL_RETURN" that may follow will work [Bug 2152286]. */ code = iPtr->returnCode; iPtr->returnLevel = 1; iPtr->returnCode = TCL_OK; if (code == TCL_ERROR) { iPtr->flags |= ERR_LEGACY_COPY; } } return code; } /* *---------------------------------------------------------------------- * * TclGetObjInterpProc/TclGetObjInterpProc2 -- * * Returns a pointer to the TclObjInterpProc/ObjInterpProc2 functions; * this is different from the value obtained from the TclObjInterpProc * reference on systems like Windows where import and export versions * of a function exported by a DLL exist. * * Results: * Returns the internal address of the TclObjInterpProc/ObjInterpProc2 * functions. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_ObjCmdProc * TclGetObjInterpProc(void) { return TclObjInterpProc; } Tcl_ObjCmdProc2 * TclGetObjInterpProc2(void) { return ObjInterpProc2; } /* *---------------------------------------------------------------------- * * TclNewProcBodyObj -- * * Creates a new object, of type "procbody", whose internal * representation is the given Proc struct. The newly created object's * reference count is 0. * * Results: * Returns a pointer to a newly allocated Tcl_Obj, NULL on error. * * Side effects: * The reference count in the ByteCode attached to the Proc is bumped up * by one, since the internal rep stores a pointer to it. * *---------------------------------------------------------------------- */ Tcl_Obj * TclNewProcBodyObj( Proc *procPtr) /* the Proc struct to store as the internal * representation. */ { Tcl_Obj *objPtr; if (!procPtr) { return NULL; } TclNewObj(objPtr); if (objPtr) { ProcSetInternalRep(objPtr, procPtr); } return objPtr; } /* *---------------------------------------------------------------------- * * ProcBodyDup -- * * Tcl_ObjType's Dup function for the proc body object. Bumps the * reference count on the Proc stored in the internal representation. * * Results: * None. * * Side effects: * Sets up the object in dupPtr to be a duplicate of the one in srcPtr. * *---------------------------------------------------------------------- */ static void ProcBodyDup( Tcl_Obj *srcPtr, /* Object to copy. */ Tcl_Obj *dupPtr) /* Target object for the duplication. */ { Proc *procPtr; ProcGetInternalRep(srcPtr, procPtr); ProcSetInternalRep(dupPtr, procPtr); } /* *---------------------------------------------------------------------- * * ProcBodyFree -- * * Tcl_ObjType's Free function for the proc body object. The reference * count on its Proc struct is decreased by 1; if the count reaches 0, * the proc is freed. * * Results: * None. * * Side effects: * If the reference count on the Proc struct reaches 0, the struct is * freed. * *---------------------------------------------------------------------- */ static void ProcBodyFree( Tcl_Obj *objPtr) /* The object to clean up. */ { Proc *procPtr; ProcGetInternalRep(objPtr, procPtr); if (procPtr->refCount-- <= 1) { TclProcCleanupProc(procPtr); } } /* *---------------------------------------------------------------------- * * DupLambdaInternalRep, FreeLambdaInternalRep, SetLambdaFromAny -- * * How to manage the internal representations of lambda term objects. * Syntactically they look like a two- or three-element list, where the * first element is the formal arguments, the second is the body, and * the (optional) third is the namespace to execute the lambda term * within (the global namespace is assumed if it is absent). * *---------------------------------------------------------------------- */ static void DupLambdaInternalRep( Tcl_Obj *srcPtr, /* Object with internal rep to copy. */ Tcl_Obj *copyPtr) /* Object with internal rep to set. */ { Proc *procPtr; Tcl_Obj *nsObjPtr; LambdaGetInternalRep(srcPtr, procPtr, nsObjPtr); assert(procPtr != NULL); procPtr->refCount++; LambdaSetInternalRep(copyPtr, procPtr, nsObjPtr); } static void FreeLambdaInternalRep( Tcl_Obj *objPtr) /* CmdName object with internal representation * to free. */ { Proc *procPtr; Tcl_Obj *nsObjPtr; LambdaGetInternalRep(objPtr, procPtr, nsObjPtr); assert(procPtr != NULL); if (procPtr->refCount-- <= 1) { TclProcCleanupProc(procPtr); } TclDecrRefCount(nsObjPtr); } static int SetLambdaFromAny( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr) /* The object to convert. */ { Interp *iPtr = (Interp *) interp; const char *name; Tcl_Obj *argsPtr, *bodyPtr, *nsObjPtr, **objv; int isNew, result; Tcl_Size objc; CmdFrame *cfPtr = NULL; Proc *procPtr; if (interp == NULL) { return TCL_ERROR; } /* * Convert objPtr to list type first; if it cannot be converted, or if its * length is not 2, then it cannot be converted to lambdaType. */ result = TclListObjLength(NULL, objPtr, &objc); if ((result != TCL_OK) || ((objc != 2) && (objc != 3))) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't interpret \"%s\" as a lambda expression", Tcl_GetString(objPtr))); Tcl_SetErrorCode(interp, "TCL", "VALUE", "LAMBDA", (char *)NULL); return TCL_ERROR; } result = TclListObjGetElements(NULL, objPtr, &objc, &objv); if ((result != TCL_OK) || ((objc != 2) && (objc != 3))) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't interpret \"%s\" as a lambda expression", TclGetString(objPtr))); Tcl_SetErrorCode(interp, "TCL", "VALUE", "LAMBDA", (char *)NULL); return TCL_ERROR; } argsPtr = objv[0]; bodyPtr = objv[1]; /* * Create and initialize the Proc struct. The cmdPtr field is set to NULL * to signal that this is an anonymous function. */ name = TclGetString(objPtr); if (TclCreateProc(interp, /*ignored nsPtr*/ NULL, name, argsPtr, bodyPtr, &procPtr) != TCL_OK) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (parsing lambda expression \"%s\")", name)); return TCL_ERROR; } /* * CAREFUL: TclCreateProc returns refCount==1! [Bug 1578454] * procPtr->refCount = 1; */ procPtr->cmdPtr = NULL; /* * TIP #280: Remember the line the apply body is starting on. In a Byte * code context we ask the engine to provide us with the necessary * information. This is for the initialization of the byte code compiler * when the body is used for the first time. * * NOTE: The body is the second word in the 'objPtr'. Its location, * accessible through 'context.line[1]' (see below) is therefore only the * first approximation of the actual line the body is on. We have to use * the string rep of the 'objPtr' to determine the exact line. This is * available already through 'name'. Use 'TclListLines', see 'switch' * (tclCmdMZ.c). * * This code is nearly identical to the #280 code in Tcl_ProcObjCmd, see * this file. The differences are the different index of the body in the * line array of the context, and the special processing mentioned in the * previous paragraph to track into the list. Find a way to factor the * common elements into a single function. */ if (iPtr->cmdFramePtr) { CmdFrame *contextPtr = (CmdFrame *)TclStackAlloc(interp, sizeof(CmdFrame)); *contextPtr = *iPtr->cmdFramePtr; if (contextPtr->type == TCL_LOCATION_BC) { /* * Retrieve the source context from the bytecode. This call * accounts for the reference to the source file, if any, held in * 'context.data.eval.path'. */ TclGetSrcInfoForPc(contextPtr); } else if (contextPtr->type == TCL_LOCATION_SOURCE) { /* * We created a new reference to the source file path name when we * created 'context' above. Account for the reference. */ Tcl_IncrRefCount(contextPtr->data.eval.path); } if (contextPtr->type == TCL_LOCATION_SOURCE) { /* * We can record source location within a lambda only if the body * was not created by substitution. */ if (contextPtr->line && (contextPtr->nline >= 2) && (contextPtr->line[1] >= 0)) { Tcl_Size buf[2]; /* * Move from approximation (line of list cmd word) to actual * location (line of 2nd list element). */ cfPtr = (CmdFrame *)Tcl_Alloc(sizeof(CmdFrame)); TclListLines(objPtr, contextPtr->line[1], 2, buf, NULL); cfPtr->level = -1; cfPtr->type = contextPtr->type; cfPtr->line = (Tcl_Size *)Tcl_Alloc(sizeof(Tcl_Size)); cfPtr->line[0] = buf[1]; cfPtr->nline = 1; cfPtr->framePtr = NULL; cfPtr->nextPtr = NULL; cfPtr->data.eval.path = contextPtr->data.eval.path; Tcl_IncrRefCount(cfPtr->data.eval.path); cfPtr->cmd = NULL; cfPtr->len = 0; } /* * 'contextPtr' is going out of scope. Release the reference that * it's holding to the source file path */ Tcl_DecrRefCount(contextPtr->data.eval.path); } TclStackFree(interp, contextPtr); } Tcl_SetHashValue(Tcl_CreateHashEntry(iPtr->linePBodyPtr, procPtr, &isNew), cfPtr); /* * Set the namespace for this lambda: given by objv[2] understood as a * global reference, or else global per default. */ if (objc == 2) { TclNewLiteralStringObj(nsObjPtr, "::"); } else { const char *nsName = TclGetString(objv[2]); if ((nsName[0] != ':') || (nsName[1] != ':')) { TclNewLiteralStringObj(nsObjPtr, "::"); Tcl_AppendObjToObj(nsObjPtr, objv[2]); } else { nsObjPtr = objv[2]; } } /* * Free the list internalrep of objPtr - this will free argsPtr, but * bodyPtr retains a reference from the Proc structure. Then finish the * conversion to lambdaType. */ LambdaSetInternalRep(objPtr, procPtr, nsObjPtr); return TCL_OK; } Proc * TclGetLambdaFromObj( Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj **nsObjPtrPtr) { Proc *procPtr; Tcl_Obj *nsObjPtr; LambdaGetInternalRep(objPtr, procPtr, nsObjPtr); if (procPtr == NULL) { if (SetLambdaFromAny(interp, objPtr) != TCL_OK) { return NULL; } LambdaGetInternalRep(objPtr, procPtr, nsObjPtr); } assert(procPtr != NULL); if (procPtr->iPtr != (Interp *)interp) { return NULL; } *nsObjPtrPtr = nsObjPtr; return procPtr; } /* *---------------------------------------------------------------------- * * Tcl_ApplyObjCmd -- * * This object-based function is invoked to process the "apply" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result value. * * Side effects: * Depends on the content of the lambda term (i.e., objv[1]). * *---------------------------------------------------------------------- */ int Tcl_ApplyObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, TclNRApplyObjCmd, clientData, objc, objv); } int TclNRApplyObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Proc *procPtr = NULL; Tcl_Obj *lambdaPtr, *nsObjPtr; int result; Tcl_Namespace *nsPtr; ApplyExtraData *extraPtr; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "lambdaExpr ?arg ...?"); return TCL_ERROR; } /* * Set lambdaPtr, convert it to tclLambdaType in the current interp if * necessary. */ lambdaPtr = objv[1]; procPtr = TclGetLambdaFromObj(interp, lambdaPtr, &nsObjPtr); if (procPtr == NULL) { return TCL_ERROR; } /* * Push a call frame for the lambda namespace. * Note that TclObjInterpProc() will pop it. */ result = TclGetNamespaceFromObj(interp, nsObjPtr, &nsPtr); if (result != TCL_OK) { return TCL_ERROR; } extraPtr = (ApplyExtraData *)TclStackAlloc(interp, sizeof(ApplyExtraData)); memset(&extraPtr->cmd, 0, sizeof(Command)); procPtr->cmdPtr = &extraPtr->cmd; extraPtr->cmd.nsPtr = (Namespace *) nsPtr; /* * TIP#280 (semi-)HACK! * * Using cmd.clientData to tell [info frame] how to render the lambdaPtr. * The InfoFrameCmd will detect this case by testing cmd.hPtr for NULL. * This condition holds here because of the memset() above, and nowhere * else (in the core). Regular commands always have a valid hPtr, and * lambda's never. */ extraPtr->efi.length = 1; extraPtr->efi.fields[0].name = "lambda"; extraPtr->efi.fields[0].proc = NULL; extraPtr->efi.fields[0].clientData = lambdaPtr; extraPtr->cmd.clientData = &extraPtr->efi; result = TclPushProcCallFrame(procPtr, interp, objc, objv, 1); if (result == TCL_OK) { TclNRAddCallback(interp, ApplyNR2, extraPtr, NULL, NULL, NULL); result = TclNRInterpProcCore(interp, objv[1], 2, &MakeLambdaError); } return result; } static int ApplyNR2( void *data[], Tcl_Interp *interp, int result) { ApplyExtraData *extraPtr = (ApplyExtraData *)data[0]; TclStackFree(interp, extraPtr); return result; } /* *---------------------------------------------------------------------- * * MakeLambdaError -- * * Function called by TclObjInterpProc to create the stack information * upon an error from a lambda term. * * Results: * The interpreter's error info trace is set to a value that supplements * the error code. * * Side effects: * none. * *---------------------------------------------------------------------- */ static void MakeLambdaError( Tcl_Interp *interp, /* The interpreter in which the procedure was * called. */ Tcl_Obj *procNameObj) /* Name of the procedure. Used for error * messages and trace information. */ { int overflow, limit = 60; Tcl_Size nameLen; const char *procName = TclGetStringFromObj(procNameObj, &nameLen); overflow = (nameLen > (Tcl_Size)limit); Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (lambda term \"%.*s%s\" line %d)", (overflow ? limit : (int)nameLen), procName, (overflow ? "..." : ""), Tcl_GetErrorLine(interp))); } /* *---------------------------------------------------------------------- * * TclGetCmdFrameForProcedure -- * * How to get the CmdFrame information for a procedure. * * Results: * A pointer to the CmdFrame (only guaranteed to be valid until the next * Tcl command is processed or the interpreter's state is otherwise * modified) or a NULL if the information is not available. * * Side effects: * none. * *---------------------------------------------------------------------- */ CmdFrame * TclGetCmdFrameForProcedure( Proc *procPtr) /* The procedure whose cmd-frame is to be * looked up. */ { Tcl_HashEntry *hePtr; if (procPtr == NULL || procPtr->iPtr == NULL) { return NULL; } hePtr = Tcl_FindHashEntry(procPtr->iPtr->linePBodyPtr, procPtr); if (hePtr == NULL) { return NULL; } return (CmdFrame *) Tcl_GetHashValue(hePtr); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclProcess.c0000644000175000017500000005653514726623136015475 0ustar sergeisergei/* * tclProcess.c -- * * This file implements the "tcl::process" ensemble for subprocess * management as defined by TIP #462. * * Copyright © 2017 Frederic Bonnet. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * Autopurge flag. Process-global because of the way Tcl manages child * processes (see tclPipe.c). */ static int autopurge = 1; /* Autopurge flag. */ /* * Hash tables that keeps track of all child process statuses. Keys are the * child process ids and resolved pids, values are (ProcessInfo *). */ typedef struct ProcessInfo { Tcl_Pid pid; /* Process id. */ int resolvedPid; /* Resolved process id. */ int purge; /* Purge eventualy. */ TclProcessWaitStatus status;/* Process status. */ int code; /* Error code, exit status or signal * number. */ Tcl_Obj *msg; /* Error message. */ Tcl_Obj *error; /* Error code. */ } ProcessInfo; static Tcl_HashTable infoTablePerPid; static Tcl_HashTable infoTablePerResolvedPid; static int infoTablesInitialized = 0; /* 0 means not yet initialized. */ TCL_DECLARE_MUTEX(infoTablesMutex) /* * Prototypes for functions defined later in this file: */ static void InitProcessInfo(ProcessInfo *info, Tcl_Pid pid, Tcl_Size resolvedPid); static void FreeProcessInfo(ProcessInfo *info); static int RefreshProcessInfo(ProcessInfo *info, int options); static TclProcessWaitStatus WaitProcessStatus(Tcl_Pid pid, Tcl_Size resolvedPid, int options, int *codePtr, Tcl_Obj **msgPtr, Tcl_Obj **errorObjPtr); static Tcl_Obj * BuildProcessStatusObj(ProcessInfo *info); static Tcl_ObjCmdProc ProcessListObjCmd; static Tcl_ObjCmdProc ProcessStatusObjCmd; static Tcl_ObjCmdProc ProcessPurgeObjCmd; static Tcl_ObjCmdProc ProcessAutopurgeObjCmd; /* *---------------------------------------------------------------------- * * InitProcessInfo -- * * Initializes the ProcessInfo structure. * * Results: * None. * * Side effects: * Memory written. * *---------------------------------------------------------------------- */ void InitProcessInfo( ProcessInfo *info, /* Structure to initialize. */ Tcl_Pid pid, /* Process id. */ Tcl_Size resolvedPid) /* Resolved process id. */ { info->pid = pid; info->resolvedPid = resolvedPid; info->purge = 0; info->status = TCL_PROCESS_UNCHANGED; info->code = 0; info->msg = NULL; info->error = NULL; } /* *---------------------------------------------------------------------- * * FreeProcessInfo -- * * Free the ProcessInfo structure. * * Results: * None. * * Side effects: * Memory deallocated, Tcl_Obj refcount decreased. * *---------------------------------------------------------------------- */ void FreeProcessInfo( ProcessInfo *info) /* Structure to free. */ { /* * Free stored Tcl_Objs. */ if (info->msg) { Tcl_DecrRefCount(info->msg); } if (info->error) { Tcl_DecrRefCount(info->error); } /* * Free allocated structure. */ Tcl_Free(info); } /* *---------------------------------------------------------------------- * * RefreshProcessInfo -- * * Refresh process info. * * Results: * Nonzero if state changed, else zero. * * Side effects: * May call WaitProcessStatus, which can block if WNOHANG option is set. * *---------------------------------------------------------------------- */ int RefreshProcessInfo( ProcessInfo *info, /* Structure to refresh. */ int options) /* Options passed to WaitProcessStatus. */ { if (info->status == TCL_PROCESS_UNCHANGED) { /* * Refresh & store status. */ info->status = WaitProcessStatus(info->pid, info->resolvedPid, options, &info->code, &info->msg, &info->error); if (info->msg) { Tcl_IncrRefCount(info->msg); } if (info->error) { Tcl_IncrRefCount(info->error); } return (info->status != TCL_PROCESS_UNCHANGED); } else { /* * No change. */ return 0; } } /* *---------------------------------------------------------------------- * * WaitProcessStatus -- * * Wait for process status to change. * * Results: * TclProcessWaitStatus enum value. * * Side effects: * May call WaitProcessStatus, which can block if WNOHANG option is set. * *---------------------------------------------------------------------- */ TclProcessWaitStatus WaitProcessStatus( Tcl_Pid pid, /* Process id. */ Tcl_Size resolvedPid, /* Resolved process id. */ int options, /* Options passed to Tcl_WaitPid. */ int *codePtr, /* If non-NULL, will receive either: * - 0 for normal exit. * - errno in case of error. * - non-zero exit code for abormal exit. * - signal number if killed or suspended. * - Tcl_WaitPid status in all other cases. */ Tcl_Obj **msgObjPtr, /* If non-NULL, will receive error message. */ Tcl_Obj **errorObjPtr) /* If non-NULL, will receive error code. */ { int waitStatus; Tcl_Obj *errorStrings[5]; const char *msg; pid = Tcl_WaitPid(pid, &waitStatus, options); if (pid == 0) { /* * No change. */ return TCL_PROCESS_UNCHANGED; } /* * Get process status. */ if (pid == (Tcl_Pid)-1) { /* * POSIX errName msg */ msg = Tcl_ErrnoMsg(errno); if (errno == ECHILD) { /* * This changeup in message suggested by Mark Diekhans to * remind people that ECHILD errors can occur on some * systems if SIGCHLD isn't in its default state. */ msg = "child process lost (is SIGCHLD ignored or trapped?)"; } if (codePtr) { *codePtr = errno; } if (msgObjPtr) { *msgObjPtr = Tcl_ObjPrintf( "error waiting for process to exit: %s", msg); } if (errorObjPtr) { errorStrings[0] = Tcl_NewStringObj("POSIX", -1); errorStrings[1] = Tcl_NewStringObj(Tcl_ErrnoId(), -1); errorStrings[2] = Tcl_NewStringObj(msg, -1); *errorObjPtr = Tcl_NewListObj(3, errorStrings); } return TCL_PROCESS_ERROR; } else if (WIFEXITED(waitStatus)) { if (codePtr) { *codePtr = WEXITSTATUS(waitStatus); } if (!WEXITSTATUS(waitStatus)) { /* * Normal exit. */ if (msgObjPtr) { *msgObjPtr = NULL; } if (errorObjPtr) { *errorObjPtr = NULL; } } else { /* * CHILDSTATUS pid code * * Child exited with a non-zero exit status. */ if (msgObjPtr) { *msgObjPtr = Tcl_NewStringObj( "child process exited abnormally", -1); } if (errorObjPtr) { errorStrings[0] = Tcl_NewStringObj("CHILDSTATUS", -1); TclNewIntObj(errorStrings[1], resolvedPid); TclNewIntObj(errorStrings[2], WEXITSTATUS(waitStatus)); *errorObjPtr = Tcl_NewListObj(3, errorStrings); } } return TCL_PROCESS_EXITED; } else if (WIFSIGNALED(waitStatus)) { /* * CHILDKILLED pid sigName msg * * Child killed because of a signal. */ msg = Tcl_SignalMsg(WTERMSIG(waitStatus)); if (codePtr) { *codePtr = WTERMSIG(waitStatus); } if (msgObjPtr) { *msgObjPtr = Tcl_ObjPrintf("child killed: %s", msg); } if (errorObjPtr) { errorStrings[0] = Tcl_NewStringObj("CHILDKILLED", -1); TclNewIntObj(errorStrings[1], resolvedPid); errorStrings[2] = Tcl_NewStringObj(Tcl_SignalId(WTERMSIG(waitStatus)), -1); errorStrings[3] = Tcl_NewStringObj(msg, -1); *errorObjPtr = Tcl_NewListObj(4, errorStrings); } return TCL_PROCESS_SIGNALED; } else if (WIFSTOPPED(waitStatus)) { /* * CHILDSUSP pid sigName msg * * Child suspended because of a signal. */ msg = Tcl_SignalMsg(WSTOPSIG(waitStatus)); if (codePtr) { *codePtr = WSTOPSIG(waitStatus); } if (msgObjPtr) { *msgObjPtr = Tcl_ObjPrintf("child suspended: %s", msg); } if (errorObjPtr) { errorStrings[0] = Tcl_NewStringObj("CHILDSUSP", -1); TclNewIntObj(errorStrings[1], resolvedPid); errorStrings[2] = Tcl_NewStringObj(Tcl_SignalId(WSTOPSIG(waitStatus)), -1); errorStrings[3] = Tcl_NewStringObj(msg, -1); *errorObjPtr = Tcl_NewListObj(4, errorStrings); } return TCL_PROCESS_STOPPED; } else { /* * TCL OPERATION EXEC ODDWAITRESULT * * Child wait status didn't make sense. */ if (codePtr) { *codePtr = waitStatus; } if (msgObjPtr) { *msgObjPtr = Tcl_NewStringObj( "child wait status didn't make sense\n", -1); } if (errorObjPtr) { errorStrings[0] = Tcl_NewStringObj("TCL", -1); errorStrings[1] = Tcl_NewStringObj("OPERATION", -1); errorStrings[2] = Tcl_NewStringObj("EXEC", -1); errorStrings[3] = Tcl_NewStringObj("ODDWAITRESULT", -1); TclNewIntObj(errorStrings[4], resolvedPid); *errorObjPtr = Tcl_NewListObj(5, errorStrings); } return TCL_PROCESS_UNKNOWN_STATUS; } } /* *---------------------------------------------------------------------- * * BuildProcessStatusObj -- * * Build a list object with process status. The first element is always * a standard Tcl return value, which can be either TCL_OK or TCL_ERROR. * In the latter case, the second element is the error message and the * third element is a Tcl error code (see tclvars). * * Results: * A list object. * * Side effects: * Tcl_Objs are created. * *---------------------------------------------------------------------- */ Tcl_Obj * BuildProcessStatusObj( ProcessInfo *info) { Tcl_Obj *resultObjs[3]; if (info->status == TCL_PROCESS_UNCHANGED) { /* * Process still running, return empty obj. */ return Tcl_NewObj(); } if (info->status == TCL_PROCESS_EXITED && info->code == 0) { /* * Normal exit, return TCL_OK. */ return Tcl_NewWideIntObj(TCL_OK); } /* * Abnormal exit, return {TCL_ERROR msg error} */ TclNewIntObj(resultObjs[0], TCL_ERROR); resultObjs[1] = info->msg; resultObjs[2] = info->error; return Tcl_NewListObj(3, resultObjs); } /*---------------------------------------------------------------------- * * ProcessListObjCmd -- * * This function implements the 'tcl::process list' Tcl command. * Refer to the user documentation for details on what it does. * * Results: * Returns a standard Tcl result. * * Side effects: * Access to the internal structures is protected by infoTablesMutex. * *---------------------------------------------------------------------- */ static int ProcessListObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *list; Tcl_HashEntry *entry; Tcl_HashSearch search; ProcessInfo *info; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } /* * Return the list of all chid process ids. */ list = Tcl_NewListObj(0, NULL); Tcl_MutexLock(&infoTablesMutex); for (entry = Tcl_FirstHashEntry(&infoTablePerResolvedPid, &search); entry != NULL; entry = Tcl_NextHashEntry(&search)) { info = (ProcessInfo *) Tcl_GetHashValue(entry); Tcl_ListObjAppendElement(interp, list, Tcl_NewWideIntObj(info->resolvedPid)); } Tcl_MutexUnlock(&infoTablesMutex); Tcl_SetObjResult(interp, list); return TCL_OK; } /*---------------------------------------------------------------------- * * ProcessStatusObjCmd -- * * This function implements the 'tcl::process status' Tcl command. * Refer to the user documentation for details on what it does. * * Results: * Returns a standard Tcl result. * * Side effects: * Access to the internal structures is protected by infoTablesMutex. * Calls RefreshProcessInfo, which can block if -wait switch is given. * *---------------------------------------------------------------------- */ static int ProcessStatusObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *dict; int options = WNOHANG; Tcl_HashEntry *entry; Tcl_HashSearch search; ProcessInfo *info; Tcl_Size i, numPids; Tcl_Obj **pidObjs; int result; int pid; Tcl_Obj *const *savedobjv = objv; static const char *const switches[] = { "-wait", "--", NULL }; enum switchesEnum { STATUS_WAIT, STATUS_LAST } index; while (objc > 1) { if (TclGetString(objv[1])[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, objv[1], switches, "switches", 0, &index) != TCL_OK) { return TCL_ERROR; } ++objv; --objc; if (STATUS_WAIT == index) { options = 0; } else { break; } } if (objc != 1 && objc != 2) { Tcl_WrongNumArgs(interp, 1, savedobjv, "?switches? ?pids?"); return TCL_ERROR; } if (objc == 1) { /* * Return a dict with all child process statuses. */ dict = Tcl_NewDictObj(); Tcl_MutexLock(&infoTablesMutex); for (entry = Tcl_FirstHashEntry(&infoTablePerResolvedPid, &search); entry != NULL; entry = Tcl_NextHashEntry(&search)) { info = (ProcessInfo *) Tcl_GetHashValue(entry); RefreshProcessInfo(info, options); if (info->purge && autopurge) { /* * Purge entry. */ Tcl_DeleteHashEntry(entry); entry = Tcl_FindHashEntry(&infoTablePerPid, info->pid); Tcl_DeleteHashEntry(entry); FreeProcessInfo(info); } else { /* * Add to result. */ Tcl_DictObjPut(NULL, dict, Tcl_NewIntObj(info->resolvedPid), BuildProcessStatusObj(info)); } } Tcl_MutexUnlock(&infoTablesMutex); } else { /* * Only return statuses of provided processes. */ result = TclListObjGetElements(interp, objv[1], &numPids, &pidObjs); if (result != TCL_OK) { return result; } dict = Tcl_NewDictObj(); Tcl_MutexLock(&infoTablesMutex); for (i = 0; i < numPids; i++) { result = Tcl_GetIntFromObj(interp, pidObjs[i], &pid); if (result != TCL_OK) { Tcl_MutexUnlock(&infoTablesMutex); Tcl_DecrRefCount(dict); return result; } entry = Tcl_FindHashEntry(&infoTablePerResolvedPid, INT2PTR(pid)); if (!entry) { /* * Skip unknown process. */ continue; } info = (ProcessInfo *) Tcl_GetHashValue(entry); RefreshProcessInfo(info, options); if (info->purge && autopurge) { /* * Purge entry. */ Tcl_DeleteHashEntry(entry); entry = Tcl_FindHashEntry(&infoTablePerPid, info->pid); Tcl_DeleteHashEntry(entry); FreeProcessInfo(info); } else { /* * Add to result. */ Tcl_DictObjPut(NULL, dict, Tcl_NewIntObj(info->resolvedPid), BuildProcessStatusObj(info)); } } Tcl_MutexUnlock(&infoTablesMutex); } Tcl_SetObjResult(interp, dict); return TCL_OK; } /*---------------------------------------------------------------------- * * ProcessPurgeObjCmd -- * * This function implements the 'tcl::process purge' Tcl command. * Refer to the user documentation for details on what it does. * * Results: * Returns a standard Tcl result. * * Side effects: * Frees all ProcessInfo structures with their purge flag set. * *---------------------------------------------------------------------- */ static int ProcessPurgeObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_HashEntry *entry; Tcl_HashSearch search; ProcessInfo *info; Tcl_Size i, numPids; Tcl_Obj **pidObjs; int result, pid; if (objc != 1 && objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "?pids?"); return TCL_ERROR; } /* * First reap detached procs so that their purge flag is up-to-date. */ Tcl_ReapDetachedProcs(); if (objc == 1) { /* * Purge all terminated processes. */ Tcl_MutexLock(&infoTablesMutex); for (entry = Tcl_FirstHashEntry(&infoTablePerResolvedPid, &search); entry != NULL; entry = Tcl_NextHashEntry(&search)) { info = (ProcessInfo *) Tcl_GetHashValue(entry); if (info->purge) { Tcl_DeleteHashEntry(entry); entry = Tcl_FindHashEntry(&infoTablePerPid, info->pid); Tcl_DeleteHashEntry(entry); FreeProcessInfo(info); } } Tcl_MutexUnlock(&infoTablesMutex); } else { /* * Purge only provided processes. */ result = TclListObjGetElements(interp, objv[1], &numPids, &pidObjs); if (result != TCL_OK) { return result; } Tcl_MutexLock(&infoTablesMutex); for (i = 0; i < numPids; i++) { result = Tcl_GetIntFromObj(interp, pidObjs[i], &pid); if (result != TCL_OK) { Tcl_MutexUnlock(&infoTablesMutex); return result; } entry = Tcl_FindHashEntry(&infoTablePerResolvedPid, INT2PTR(pid)); if (!entry) { /* * Skip unknown process. */ continue; } info = (ProcessInfo *) Tcl_GetHashValue(entry); if (info->purge) { Tcl_DeleteHashEntry(entry); entry = Tcl_FindHashEntry(&infoTablePerPid, info->pid); Tcl_DeleteHashEntry(entry); FreeProcessInfo(info); } } Tcl_MutexUnlock(&infoTablesMutex); } return TCL_OK; } /*---------------------------------------------------------------------- * * ProcessAutopurgeObjCmd -- * * This function implements the 'tcl::process autopurge' Tcl command. * Refer to the user documentation for details on what it does. * * Results: * Returns a standard Tcl result. * * Side effects: * Alters detached process handling by Tcl_ReapDetachedProcs(). * *---------------------------------------------------------------------- */ static int ProcessAutopurgeObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc != 1 && objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "?flag?"); return TCL_ERROR; } if (objc == 2) { /* * Set given value. */ int flag; int result = Tcl_GetBooleanFromObj(interp, objv[1], &flag); if (result != TCL_OK) { return result; } autopurge = !!flag; } /* * Return current value. */ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(autopurge)); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclInitProcessCmd -- * * This procedure creates the "tcl::process" Tcl command. See the user * documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ Tcl_Command TclInitProcessCmd( Tcl_Interp *interp) /* Current interpreter. */ { static const EnsembleImplMap processImplMap[] = { {"list", ProcessListObjCmd, TclCompileBasic0ArgCmd, NULL, NULL, 1}, {"status", ProcessStatusObjCmd, TclCompileBasicMin0ArgCmd, NULL, NULL, 1}, {"purge", ProcessPurgeObjCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 1}, {"autopurge", ProcessAutopurgeObjCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 1}, {NULL, NULL, NULL, NULL, NULL, 0} }; Tcl_Command processCmd; if (infoTablesInitialized == 0) { Tcl_MutexLock(&infoTablesMutex); if (infoTablesInitialized == 0) { Tcl_InitHashTable(&infoTablePerPid, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(&infoTablePerResolvedPid, TCL_ONE_WORD_KEYS); infoTablesInitialized = 1; } Tcl_MutexUnlock(&infoTablesMutex); } processCmd = TclMakeEnsemble(interp, "::tcl::process", processImplMap); Tcl_Export(interp, Tcl_FindNamespace(interp, "::tcl", NULL, 0), "process", 0); return processCmd; } /* *---------------------------------------------------------------------- * * TclProcessCreated -- * * Called when a child process has been created by Tcl. * * Results: * None. * * Side effects: * Internal structures are updated with a new ProcessInfo. * *---------------------------------------------------------------------- */ void TclProcessCreated( Tcl_Pid pid) /* Process id. */ { Tcl_Size resolvedPid; Tcl_HashEntry *entry, *entry2; int isNew; ProcessInfo *info; /* * Get resolved pid first. */ resolvedPid = TclpGetPid(pid); Tcl_MutexLock(&infoTablesMutex); /* * Create entry in pid table. */ entry = Tcl_CreateHashEntry(&infoTablePerPid, pid, &isNew); if (!isNew) { /* * Pid was reused, free old info and reuse structure. */ info = (ProcessInfo *) Tcl_GetHashValue(entry); entry2 = Tcl_FindHashEntry(&infoTablePerResolvedPid, INT2PTR(resolvedPid)); if (entry2) { Tcl_DeleteHashEntry(entry2); } FreeProcessInfo(info); } /* * Allocate and initialize info structure. */ info = (ProcessInfo *)Tcl_Alloc(sizeof(ProcessInfo)); InitProcessInfo(info, pid, resolvedPid); /* * Add entry to tables. */ Tcl_SetHashValue(entry, info); entry = Tcl_CreateHashEntry(&infoTablePerResolvedPid, INT2PTR(resolvedPid), &isNew); Tcl_SetHashValue(entry, info); Tcl_MutexUnlock(&infoTablesMutex); } /* *---------------------------------------------------------------------- * * TclProcessWait -- * * Wait for process status to change. * * Results: * TclProcessWaitStatus enum value. * * Side effects: * Completed process info structures are purged immediately (autopurge on) * or eventually (autopurge off). * *---------------------------------------------------------------------- */ TclProcessWaitStatus TclProcessWait( Tcl_Pid pid, /* Process id. */ int options, /* Options passed to WaitProcessStatus. */ int *codePtr, /* If non-NULL, will receive either: * - 0 for normal exit. * - errno in case of error. * - non-zero exit code for abormal exit. * - signal number if killed or suspended. * - Tcl_WaitPid status in all other cases. */ Tcl_Obj **msgObjPtr, /* If non-NULL, will receive error message. */ Tcl_Obj **errorObjPtr) /* If non-NULL, will receive error code. */ { Tcl_HashEntry *entry; ProcessInfo *info; TclProcessWaitStatus result; /* * First search for pid in table. */ Tcl_MutexLock(&infoTablesMutex); entry = Tcl_FindHashEntry(&infoTablePerPid, pid); if (!entry) { /* * Unknown process, just call WaitProcessStatus and return. */ result = WaitProcessStatus(pid, TclpGetPid(pid), options, codePtr, msgObjPtr, errorObjPtr); if (msgObjPtr && *msgObjPtr) { Tcl_IncrRefCount(*msgObjPtr); } if (errorObjPtr && *errorObjPtr) { Tcl_IncrRefCount(*errorObjPtr); } Tcl_MutexUnlock(&infoTablesMutex); return result; } info = (ProcessInfo *) Tcl_GetHashValue(entry); if (info->purge) { /* * Process has completed but TclProcessWait has already been called, * so report no change. */ Tcl_MutexUnlock(&infoTablesMutex); return TCL_PROCESS_UNCHANGED; } RefreshProcessInfo(info, options); if (info->status == TCL_PROCESS_UNCHANGED) { /* * No change, stop there. */ Tcl_MutexUnlock(&infoTablesMutex); return TCL_PROCESS_UNCHANGED; } /* * Set return values. */ result = info->status; if (codePtr) { *codePtr = info->code; } if (msgObjPtr) { *msgObjPtr = info->msg; } if (errorObjPtr) { *errorObjPtr = info->error; } if (msgObjPtr && *msgObjPtr) { Tcl_IncrRefCount(*msgObjPtr); } if (errorObjPtr && *errorObjPtr) { Tcl_IncrRefCount(*errorObjPtr); } if (autopurge) { /* * Purge now. */ Tcl_DeleteHashEntry(entry); entry = Tcl_FindHashEntry(&infoTablePerResolvedPid, INT2PTR(info->resolvedPid)); Tcl_DeleteHashEntry(entry); FreeProcessInfo(info); } else { /* * Eventually purge. Subsequent calls will return * TCL_PROCESS_UNCHANGED. */ info->purge = 1; } Tcl_MutexUnlock(&infoTablesMutex); return result; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclRegexp.c0000644000175000017500000007570414726623136015310 0ustar sergeisergei/* * tclRegexp.c -- * * This file contains the public interfaces to the Tcl regular expression * mechanism. * * Copyright © 1998 Sun Microsystems, Inc. * Copyright © 1998-1999 Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclRegexp.h" #include "tclTomMath.h" #include /* *---------------------------------------------------------------------- * The routines in this file use Henry Spencer's regular expression package * contained in the following additional source files: * * regc_color.c regc_cvec.c regc_lex.c * regc_nfa.c regcomp.c regcustom.h * rege_dfa.c regerror.c regerrs.h * regex.h regexec.c regfree.c * regfronts.c regguts.h * * Copyright © 1998 Henry Spencer. All rights reserved. * * Development of this software was funded, in part, by Cray Research Inc., * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics * Corporation, none of whom are responsible for the results. The author * thanks all of them. * * Redistribution and use in source and binary forms -- with or without * modification -- are permitted for any purpose, provided that * redistributions in source form retain this entire copyright notice and * indicate the origin and nature of any modifications. * * I'd appreciate being given credit for this package in the documentation of * software which uses it, but that is not a requirement. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * *** NOTE: this code has been altered slightly for use in Tcl: *** * *** 1. Names have been changed, e.g. from re_comp to *** * *** TclRegComp, to avoid clashes with other *** * *** regexp implementations used by applications. *** */ /* * Thread local storage used to maintain a per-thread cache of compiled * regular expressions. */ #define NUM_REGEXPS 30 typedef struct { int initialized; /* Set to 1 when the module is initialized. */ char *patterns[NUM_REGEXPS];/* Strings corresponding to compiled regular * expression patterns. NULL means that this * slot isn't used. Malloc-ed. */ size_t patLengths[NUM_REGEXPS];/* Number of non-null characters in * corresponding entry in patterns. -1 means * entry isn't used. */ struct TclRegexp *regexps[NUM_REGEXPS]; /* Compiled forms of above strings. Also * malloc-ed, or NULL if not in use yet. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * Declarations for functions used only in this file. */ static TclRegexp * CompileRegexp(Tcl_Interp *interp, const char *pattern, size_t length, int flags); static void DupRegexpInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr); static void FinalizeRegexp(void *clientData); static void FreeRegexp(TclRegexp *regexpPtr); static void FreeRegexpInternalRep(Tcl_Obj *objPtr); static int RegExpExecUniChar(Tcl_Interp *interp, Tcl_RegExp re, const Tcl_UniChar *uniString, size_t numChars, size_t nmatches, int flags); static int SetRegexpFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); /* * The regular expression Tcl object type. This serves as a cache of the * compiled form of the regular expression. */ const Tcl_ObjType tclRegexpType = { "regexp", /* name */ FreeRegexpInternalRep, /* freeIntRepProc */ DupRegexpInternalRep, /* dupIntRepProc */ NULL, /* updateStringProc */ SetRegexpFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; #define RegexpSetInternalRep(objPtr, rePtr) \ do { \ Tcl_ObjInternalRep ir; \ (rePtr)->refCount++; \ ir.twoPtrValue.ptr1 = (rePtr); \ ir.twoPtrValue.ptr2 = NULL; \ Tcl_StoreInternalRep((objPtr), &tclRegexpType, &ir); \ } while (0) #define RegexpGetInternalRep(objPtr, rePtr) \ do { \ const Tcl_ObjInternalRep *irPtr; \ irPtr = TclFetchInternalRep((objPtr), &tclRegexpType); \ (rePtr) = irPtr ? (TclRegexp *)irPtr->twoPtrValue.ptr1 : NULL; \ } while (0) /* *---------------------------------------------------------------------- * * Tcl_RegExpCompile -- * * Compile a regular expression into a form suitable for fast matching. * This function is DEPRECATED in favor of the object version of the * command. * * Results: * The return value is a pointer to the compiled form of string, suitable * for passing to Tcl_RegExpExec. This compiled form is only valid up * until the next call to this function, so don't keep these around for a * long time! If an error occurred while compiling the pattern, then NULL * is returned and an error message is left in the interp's result. * * Side effects: * Updates the cache of compiled regexps. * *---------------------------------------------------------------------- */ Tcl_RegExp Tcl_RegExpCompile( Tcl_Interp *interp, /* For use in error reporting and to access * the interp regexp cache. */ const char *pattern) /* String for which to produce compiled * regular expression. */ { return (Tcl_RegExp) CompileRegexp(interp, pattern, strlen(pattern), REG_ADVANCED); } /* *---------------------------------------------------------------------- * * Tcl_RegExpExec -- * * Execute the regular expression matcher using a compiled form of a * regular expression and save information about any match that is found. * * Results: * If an error occurs during the matching operation then -1 is returned * and the interp's result contains an error message. Otherwise the * return value is 1 if a matching range is found and 0 if there is no * matching range. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_RegExpExec( Tcl_Interp *interp, /* Interpreter to use for error reporting. */ Tcl_RegExp re, /* Compiled regular expression; must have been * returned by previous call to * Tcl_GetRegExpFromObj. */ const char *text, /* Text against which to match re. */ const char *start) /* If text is part of a larger string, this * identifies beginning of larger string, so * that "^" won't match. */ { int flags, result; size_t numChars; TclRegexp *regexp = (TclRegexp *) re; Tcl_DString ds; const Tcl_UniChar *ustr; /* * If the starting point is offset from the beginning of the buffer, then * we need to tell the regexp engine not to match "^". */ if (text > start) { flags = REG_NOTBOL; } else { flags = 0; } /* * Remember the string for use by Tcl_RegExpRange(). */ regexp->string = text; regexp->objPtr = NULL; /* * Convert the string to Unicode and perform the match. */ Tcl_DStringInit(&ds); ustr = Tcl_UtfToUniCharDString(text, TCL_INDEX_NONE, &ds); numChars = Tcl_DStringLength(&ds) / sizeof(Tcl_UniChar); result = RegExpExecUniChar(interp, re, ustr, numChars, TCL_INDEX_NONE /* nmatches */, flags); Tcl_DStringFree(&ds); return result; } /* *--------------------------------------------------------------------------- * * Tcl_RegExpRange -- * * Returns pointers describing the range of a regular expression match, * or one of the subranges within the match. * * Results: * The variables at *startPtr and *endPtr are modified to hold the * addresses of the endpoints of the range given by index. If the * specified range doesn't exist then NULLs are returned. * * Side effects: * None. * *--------------------------------------------------------------------------- */ void Tcl_RegExpRange( Tcl_RegExp re, /* Compiled regular expression that has been * passed to Tcl_RegExpExec. */ Tcl_Size index, /* 0 means give the range of the entire match, * > 0 means give the range of a matching * subrange. */ const char **startPtr, /* Store address of first character in * (sub-)range here. */ const char **endPtr) /* Store address of character just after last * in (sub-)range here. */ { TclRegexp *regexpPtr = (TclRegexp *) re; const char *string; if (index < 0 || (size_t) index > regexpPtr->re.re_nsub) { *startPtr = *endPtr = NULL; } else if (regexpPtr->matches[index].rm_so == (size_t) -1) { *startPtr = *endPtr = NULL; } else { if (regexpPtr->objPtr) { string = TclGetString(regexpPtr->objPtr); } else { string = regexpPtr->string; } *startPtr = Tcl_UtfAtIndex(string, regexpPtr->matches[index].rm_so); *endPtr = Tcl_UtfAtIndex(string, regexpPtr->matches[index].rm_eo); } } /* *--------------------------------------------------------------------------- * * RegExpExecUniChar -- * * Execute the regular expression matcher using a compiled form of a * regular expression and save information about any match that is found. * * Results: * If an error occurs during the matching operation then -1 is returned * and an error message is left in interp's result. Otherwise the return * value is 1 if a matching range was found or 0 if there was no matching * range. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int RegExpExecUniChar( Tcl_Interp *interp, /* Interpreter to use for error reporting. */ Tcl_RegExp re, /* Compiled regular expression; returned by a * previous call to Tcl_GetRegExpFromObj */ const Tcl_UniChar *wString, /* String against which to match re. */ size_t numChars, /* Length of Tcl_UniChar string. */ size_t nm, /* How many subexpression matches (counting * the whole match as subexpression 0) are of * interest. -1 means "don't know". */ int flags) /* Regular expression flags. */ { int status; TclRegexp *regexpPtr = (TclRegexp *) re; size_t last = regexpPtr->re.re_nsub + 1; if (nm >= last) { nm = last; } status = TclReExec(®expPtr->re, wString, numChars, ®expPtr->details, nm, regexpPtr->matches, flags); /* * Check for errors. */ if (status != REG_OKAY) { if (status == REG_NOMATCH) { return 0; } if (interp != NULL) { TclRegError(interp, "error while matching regular expression: ", status); } return -1; } return 1; } /* *--------------------------------------------------------------------------- * * TclRegExpRangeUniChar -- * * Returns pointers describing the range of a regular expression match, * or one of the subranges within the match, or the hypothetical range * represented by the rm_extend field of the rm_detail_t. * * Results: * The variables at *startPtr and *endPtr are modified to hold the * offsets of the endpoints of the range given by index. If the specified * range doesn't exist then -1s are supplied. * * Side effects: * None. * *--------------------------------------------------------------------------- */ void TclRegExpRangeUniChar( Tcl_RegExp re, /* Compiled regular expression that has been * passed to Tcl_RegExpExec. */ Tcl_Size index, /* 0 means give the range of the entire match, * > 0 means give the range of a matching * subrange, -1 means the range of the * rm_extend field. */ Tcl_Size *startPtr, /* Store address of first character in * (sub-)range here. */ Tcl_Size *endPtr) /* Store address of character just after last * in (sub-)range here. */ { TclRegexp *regexpPtr = (TclRegexp *) re; if ((regexpPtr->flags®_EXPECT) && (index == -1)) { *startPtr = regexpPtr->details.rm_extend.rm_so; *endPtr = regexpPtr->details.rm_extend.rm_eo; } else if (index < 0 || (size_t) index > regexpPtr->re.re_nsub + 1) { *startPtr = -1; *endPtr = -1; } else { *startPtr = regexpPtr->matches[index].rm_so; *endPtr = regexpPtr->matches[index].rm_eo; } } /* *---------------------------------------------------------------------- * * Tcl_RegExpMatch -- * * See if a string matches a regular expression. * * Results: * If an error occurs during the matching operation then -1 is returned * and the interp's result contains an error message. Otherwise the * return value is 1 if "text" matches "pattern" and 0 otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_RegExpMatch( Tcl_Interp *interp, /* Used for error reporting. May be NULL. */ const char *text, /* Text to search for pattern matches. */ const char *pattern) /* Regular expression to match against text. */ { Tcl_RegExp re = Tcl_RegExpCompile(interp, pattern); if (re == NULL) { return -1; } return Tcl_RegExpExec(interp, re, text, text); } /* *---------------------------------------------------------------------- * * Tcl_RegExpExecObj -- * * Execute a precompiled regexp against the given object. * * Results: * If an error occurs during the matching operation then -1 is returned * and the interp's result contains an error message. Otherwise the * return value is 1 if "string" matches "pattern" and 0 otherwise. * * Side effects: * Converts the object to a Unicode object. * *---------------------------------------------------------------------- */ int Tcl_RegExpExecObj( Tcl_Interp *interp, /* Interpreter to use for error reporting. */ Tcl_RegExp re, /* Compiled regular expression; must have been * returned by previous call to * Tcl_GetRegExpFromObj. */ Tcl_Obj *textObj, /* Text against which to match re. */ Tcl_Size offset, /* Character index that marks where matching * should begin. */ Tcl_Size nmatches, /* How many subexpression matches (counting * the whole match as subexpression 0) are of * interest. -1 means all of them. */ int flags) /* Regular expression execution flags. */ { TclRegexp *regexpPtr = (TclRegexp *) re; Tcl_UniChar *udata; Tcl_Size length; int reflags = regexpPtr->flags; #define TCL_REG_GLOBOK_FLAGS \ (TCL_REG_ADVANCED | TCL_REG_NOSUB | TCL_REG_NOCASE) /* * Take advantage of the equivalent glob pattern, if one exists. * This is possible based only on the right mix of incoming flags (0) * and regexp compile flags. */ if ((offset == 0) && (nmatches == 0) && (flags == 0) && !(reflags & ~TCL_REG_GLOBOK_FLAGS) && (regexpPtr->globObjPtr != NULL)) { int nocase = (reflags & TCL_REG_NOCASE) ? TCL_MATCH_NOCASE : 0; /* * Pass to TclStringMatchObj for obj-specific handling. * XXX: Currently doesn't take advantage of exact-ness that * XXX: TclReToGlob tells us about */ return TclStringMatchObj(textObj, regexpPtr->globObjPtr, nocase); } /* * Save the target object so we can extract strings from it later. */ regexpPtr->string = NULL; regexpPtr->objPtr = textObj; udata = Tcl_GetUnicodeFromObj(textObj, &length); if (offset > length) { offset = length; } udata += offset; length -= offset; return RegExpExecUniChar(interp, re, udata, length, nmatches, flags); } /* *---------------------------------------------------------------------- * * Tcl_RegExpMatchObj -- * * See if an object matches a regular expression. * * Results: * If an error occurs during the matching operation then -1 is returned * and the interp's result contains an error message. Otherwise the * return value is 1 if "text" matches "pattern" and 0 otherwise. * * Side effects: * Changes the internal rep of the pattern and string objects. * *---------------------------------------------------------------------- */ int Tcl_RegExpMatchObj( Tcl_Interp *interp, /* Used for error reporting. May be NULL. */ Tcl_Obj *textObj, /* Object containing the String to search. */ Tcl_Obj *patternObj) /* Regular expression to match against * string. */ { Tcl_RegExp re; /* * For performance reasons, first try compiling the RE without support for * subexpressions. On failure, try again without TCL_REG_NOSUB in case the * RE has backreferences in it. Closely related to [Bug 1366683]. If this * still fails, an error message will be left in the interpreter. */ if (!(re = Tcl_GetRegExpFromObj(interp, patternObj, TCL_REG_ADVANCED | TCL_REG_NOSUB)) && !(re = Tcl_GetRegExpFromObj(interp, patternObj, TCL_REG_ADVANCED))) { return -1; } return Tcl_RegExpExecObj(interp, re, textObj, 0 /* offset */, 0 /* nmatches */, 0 /* flags */); } /* *---------------------------------------------------------------------- * * Tcl_RegExpGetInfo -- * * Retrieve information about the current match. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_RegExpGetInfo( Tcl_RegExp regexp, /* Pattern from which to get subexpressions. */ Tcl_RegExpInfo *infoPtr) /* Match information is stored here. */ { TclRegexp *regexpPtr = (TclRegexp *) regexp; infoPtr->nsubs = regexpPtr->re.re_nsub; infoPtr->matches = (Tcl_RegExpIndices *) regexpPtr->matches; infoPtr->extendStart = regexpPtr->details.rm_extend.rm_so; } /* *---------------------------------------------------------------------- * * Tcl_GetRegExpFromObj -- * * Compile a regular expression into a form suitable for fast matching. * This function caches the result in a Tcl_Obj. * * Results: * The return value is a pointer to the compiled form of string, suitable * for passing to Tcl_RegExpExec. If an error occurred while compiling * the pattern, then NULL is returned and an error message is left in the * interp's result. * * Side effects: * Updates the native rep of the Tcl_Obj. * *---------------------------------------------------------------------- */ Tcl_RegExp Tcl_GetRegExpFromObj( Tcl_Interp *interp, /* For use in error reporting, and to access * the interp regexp cache. */ Tcl_Obj *objPtr, /* Object whose string rep contains regular * expression pattern. Internal rep will be * changed to compiled form of this regular * expression. */ int flags) /* Regular expression compilation flags. */ { Tcl_Size length; TclRegexp *regexpPtr; const char *pattern; RegexpGetInternalRep(objPtr, regexpPtr); if ((regexpPtr == NULL) || (regexpPtr->flags != flags)) { pattern = TclGetStringFromObj(objPtr, &length); regexpPtr = CompileRegexp(interp, pattern, length, flags); if (regexpPtr == NULL) { return NULL; } RegexpSetInternalRep(objPtr, regexpPtr); } return (Tcl_RegExp) regexpPtr; } /* *---------------------------------------------------------------------- * * TclRegAbout -- * * Return information about a compiled regular expression. * * Results: * The return value is -1 for failure, 0 for success, although at the * moment there's nothing that could fail. On success, a list is left in * the interp's result: first element is the subexpression count, second * is a list of re_info bit names. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclRegAbout( Tcl_Interp *interp, /* For use in variable assignment. */ Tcl_RegExp re) /* The compiled regular expression. */ { TclRegexp *regexpPtr = (TclRegexp *) re; struct infoname { int bit; const char *text; }; static const struct infoname infonames[] = { {REG_UBACKREF, "REG_UBACKREF"}, {REG_ULOOKAHEAD, "REG_ULOOKAHEAD"}, {REG_UBOUNDS, "REG_UBOUNDS"}, {REG_UBRACES, "REG_UBRACES"}, {REG_UBSALNUM, "REG_UBSALNUM"}, {REG_UPBOTCH, "REG_UPBOTCH"}, {REG_UBBS, "REG_UBBS"}, {REG_UNONPOSIX, "REG_UNONPOSIX"}, {REG_UUNSPEC, "REG_UUNSPEC"}, {REG_UUNPORT, "REG_UUNPORT"}, {REG_ULOCALE, "REG_ULOCALE"}, {REG_UEMPTYMATCH, "REG_UEMPTYMATCH"}, {REG_UIMPOSSIBLE, "REG_UIMPOSSIBLE"}, {REG_USHORTEST, "REG_USHORTEST"}, {0, NULL} }; const struct infoname *inf; Tcl_Obj *infoObj, *resultObj; /* * The reset here guarantees that the interpreter result is empty and * unshared. This means that we can use Tcl_ListObjAppendElement on the * result object quite safely. */ Tcl_ResetResult(interp); /* * Assume that there will never be more than INT_MAX subexpressions. This * is a pretty reasonable assumption; the RE engine doesn't scale _that_ * well and Tcl has other limits that constrain things as well... */ TclNewObj(resultObj); TclNewIndexObj(infoObj, regexpPtr->re.re_nsub); Tcl_ListObjAppendElement(NULL, resultObj, infoObj); /* * Now append a list of all the bit-flags set for the RE. */ TclNewObj(infoObj); for (inf=infonames ; inf->bit != 0 ; inf++) { if (regexpPtr->re.re_info & inf->bit) { Tcl_ListObjAppendElement(NULL, infoObj, Tcl_NewStringObj(inf->text, -1)); } } Tcl_ListObjAppendElement(NULL, resultObj, infoObj); Tcl_SetObjResult(interp, resultObj); return 0; } /* *---------------------------------------------------------------------- * * TclRegError -- * * Generate an error message based on the regexp status code. * * Results: * Places an error in the interpreter. * * Side effects: * Sets errorCode as well. * *---------------------------------------------------------------------- */ void TclRegError( Tcl_Interp *interp, /* Interpreter for error reporting. */ const char *msg, /* Message to prepend to error. */ int status) /* Status code to report. */ { char buf[100]; /* ample in practice */ char cbuf[TCL_INTEGER_SPACE]; size_t n; const char *p; Tcl_ResetResult(interp); n = TclReError(status, buf, sizeof(buf)); p = (n > sizeof(buf)) ? "..." : ""; Tcl_SetObjResult(interp, Tcl_ObjPrintf("%s%s%s", msg, buf, p)); snprintf(cbuf, sizeof(cbuf), "%d", status); (void) TclReError(REG_ITOA, cbuf, sizeof(cbuf)); Tcl_SetErrorCode(interp, "REGEXP", cbuf, buf, (char *)NULL); } /* *---------------------------------------------------------------------- * * FreeRegexpInternalRep -- * * Deallocate the storage associated with a regexp object's internal * representation. * * Results: * None. * * Side effects: * Frees the compiled regular expression. * *---------------------------------------------------------------------- */ static void FreeRegexpInternalRep( Tcl_Obj *objPtr) /* Regexp object with internal rep to free. */ { TclRegexp *regexpRepPtr; RegexpGetInternalRep(objPtr, regexpRepPtr); assert(regexpRepPtr != NULL); /* * If this is the last reference to the regexp, free it. */ if (regexpRepPtr->refCount-- <= 1) { FreeRegexp(regexpRepPtr); } } /* *---------------------------------------------------------------------- * * DupRegexpInternalRep -- * * We copy the reference to the compiled regexp and bump its reference * count. * * Results: * None. * * Side effects: * Increments the reference count of the regexp. * *---------------------------------------------------------------------- */ static void DupRegexpInternalRep( Tcl_Obj *srcPtr, /* Object with internal rep to copy. */ Tcl_Obj *copyPtr) /* Object with internal rep to set. */ { TclRegexp *regexpPtr; RegexpGetInternalRep(srcPtr, regexpPtr); assert(regexpPtr != NULL); RegexpSetInternalRep(copyPtr, regexpPtr); } /* *---------------------------------------------------------------------- * * SetRegexpFromAny -- * * Attempt to generate a compiled regular expression for the Tcl object * "objPtr". * * Results: * The return value is TCL_OK or TCL_ERROR. If an error occurs during * conversion, an error message is left in the interpreter's result * unless "interp" is NULL. * * Side effects: * If no error occurs, a regular expression is stored as "objPtr"s * internal representation. * *---------------------------------------------------------------------- */ static int SetRegexpFromAny( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr) /* The object to convert. */ { if (Tcl_GetRegExpFromObj(interp, objPtr, REG_ADVANCED) == NULL) { return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * CompileRegexp -- * * Attempt to compile the given regexp pattern. If the compiled regular * expression can be found in the per-thread cache, it will be used * instead of compiling a new copy. * * Results: * The return value is a pointer to a newly allocated TclRegexp that * represents the compiled pattern, or NULL if the pattern could not be * compiled. If NULL is returned, an error message is left in the * interp's result. * * Side effects: * The thread-local regexp cache is updated and a new TclRegexp may be * allocated. * *---------------------------------------------------------------------- */ static TclRegexp * CompileRegexp( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ const char *string, /* The regexp to compile (UTF-8). */ size_t length, /* The length of the string in bytes. */ int flags) /* Compilation flags. */ { TclRegexp *regexpPtr; const Tcl_UniChar *uniString; int numChars, status, i, exact; Tcl_DString stringBuf; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!tsdPtr->initialized) { tsdPtr->initialized = 1; Tcl_CreateThreadExitHandler(FinalizeRegexp, NULL); } /* * This routine maintains a second-level regular expression cache in * addition to the per-object regexp cache. The per-thread cache is needed * to handle the case where for various reasons the object is lost between * invocations of the regexp command, but the literal pattern is the same. */ /* * Check the per-thread compiled regexp cache. We can only reuse a regexp * if it has the same pattern and the same flags. */ for (i = 0; (i < NUM_REGEXPS) && (tsdPtr->patterns[i] != NULL); i++) { if ((length == tsdPtr->patLengths[i]) && (tsdPtr->regexps[i]->flags == flags) && (strcmp(string, tsdPtr->patterns[i]) == 0)) { /* * Move the matched pattern to the first slot in the cache and * shift the other patterns down one position. */ if (i != 0) { int j; char *cachedString; cachedString = tsdPtr->patterns[i]; regexpPtr = tsdPtr->regexps[i]; for (j = i-1; j >= 0; j--) { tsdPtr->patterns[j+1] = tsdPtr->patterns[j]; tsdPtr->patLengths[j+1] = tsdPtr->patLengths[j]; tsdPtr->regexps[j+1] = tsdPtr->regexps[j]; } tsdPtr->patterns[0] = cachedString; tsdPtr->patLengths[0] = length; tsdPtr->regexps[0] = regexpPtr; } return tsdPtr->regexps[0]; } } /* * This is a new expression, so compile it and add it to the cache. */ regexpPtr = (TclRegexp*)Tcl_Alloc(sizeof(TclRegexp)); regexpPtr->objPtr = NULL; regexpPtr->string = NULL; regexpPtr->details.rm_extend.rm_so = TCL_INDEX_NONE; regexpPtr->details.rm_extend.rm_eo = TCL_INDEX_NONE; /* * Get the up-to-date string representation and map to unicode. */ Tcl_DStringInit(&stringBuf); uniString = Tcl_UtfToUniCharDString(string, length, &stringBuf); numChars = Tcl_DStringLength(&stringBuf) / sizeof(Tcl_UniChar); /* * Compile the string and check for errors. */ regexpPtr->flags = flags; status = TclReComp(®expPtr->re, uniString, (size_t) numChars, flags); Tcl_DStringFree(&stringBuf); if (status != REG_OKAY) { /* * Clean up and report errors in the interpreter, if possible. */ Tcl_Free(regexpPtr); if (interp) { TclRegError(interp, "cannot compile regular expression pattern: ", status); } return NULL; } /* * Convert RE to a glob pattern equivalent, if any, and cache it. If this * is not possible, then globObjPtr will be NULL. This is used by * Tcl_RegExpExecObj to optionally do a fast match (avoids RE engine). */ if (TclReToGlob(NULL, string, length, &stringBuf, &exact, NULL) == TCL_OK) { regexpPtr->globObjPtr = Tcl_DStringToObj(&stringBuf); Tcl_IncrRefCount(regexpPtr->globObjPtr); } else { regexpPtr->globObjPtr = NULL; } /* * Allocate enough space for all of the subexpressions, plus one extra for * the entire pattern. */ regexpPtr->matches = (regmatch_t*)Tcl_Alloc(sizeof(regmatch_t) * (regexpPtr->re.re_nsub + 1)); /* * Initialize the refcount to one initially, since it is in the cache. */ regexpPtr->refCount = 1; /* * Free the last regexp, if necessary, and make room at the head of the * list for the new regexp. */ if (tsdPtr->patterns[NUM_REGEXPS-1] != NULL) { TclRegexp *oldRegexpPtr = tsdPtr->regexps[NUM_REGEXPS-1]; if (oldRegexpPtr->refCount-- <= 1) { FreeRegexp(oldRegexpPtr); } Tcl_Free(tsdPtr->patterns[NUM_REGEXPS-1]); } for (i = NUM_REGEXPS - 2; i >= 0; i--) { tsdPtr->patterns[i+1] = tsdPtr->patterns[i]; tsdPtr->patLengths[i+1] = tsdPtr->patLengths[i]; tsdPtr->regexps[i+1] = tsdPtr->regexps[i]; } tsdPtr->patterns[0] = (char *)Tcl_Alloc(length + 1); memcpy(tsdPtr->patterns[0], string, length + 1); tsdPtr->patLengths[0] = length; tsdPtr->regexps[0] = regexpPtr; return regexpPtr; } /* *---------------------------------------------------------------------- * * FreeRegexp -- * * Release the storage associated with a TclRegexp. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void FreeRegexp( TclRegexp *regexpPtr) /* Compiled regular expression to free. */ { TclReFree(®expPtr->re); if (regexpPtr->globObjPtr) { TclDecrRefCount(regexpPtr->globObjPtr); } if (regexpPtr->matches) { Tcl_Free(regexpPtr->matches); } Tcl_Free(regexpPtr); } /* *---------------------------------------------------------------------- * * FinalizeRegexp -- * * Release the storage associated with the per-thread regexp cache. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void FinalizeRegexp( TCL_UNUSED(void *)) { int i; TclRegexp *regexpPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); for (i = 0; (i < NUM_REGEXPS) && (tsdPtr->patterns[i] != NULL); i++) { regexpPtr = tsdPtr->regexps[i]; if (regexpPtr->refCount-- <= 1) { FreeRegexp(regexpPtr); } Tcl_Free(tsdPtr->patterns[i]); tsdPtr->patterns[i] = NULL; } /* * We may find ourselves reinitialized if another finalization routine * invokes regexps. */ tsdPtr->initialized = 0; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclRegexp.h0000644000175000017500000000321114726623136015275 0ustar sergeisergei/* * tclRegexp.h -- * * This file contains definitions used internally by Henry Spencer's * regular expression code. * * Copyright (c) 1998 by Sun Microsystems, Inc. * Copyright (c) 1998-1999 by Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef _TCLREGEXP #define _TCLREGEXP #include "regex.h" /* * The TclRegexp structure encapsulates a compiled regex_t, the flags that * were used to compile it, and an array of pointers that are used to indicate * subexpressions after a call to Tcl_RegExpExec. Note that the string and * objPtr are mutually exclusive. These values are needed by Tcl_RegExpRange * in order to return pointers into the original string. */ typedef struct TclRegexp { int flags; /* Regexp compile flags. */ regex_t re; /* Compiled re, includes number of * subexpressions. */ const char *string; /* Last string passed to Tcl_RegExpExec. */ Tcl_Obj *objPtr; /* Last object passed to Tcl_RegExpExecObj. */ Tcl_Obj *globObjPtr; /* Glob pattern rep of RE or NULL if none. */ regmatch_t *matches; /* Array of indices into the Tcl_UniChar * representation of the last string matched * with this regexp to indicate the location * of subexpressions. */ rm_detail_t details; /* Detailed information on match (currently * used only for REG_EXPECT). */ Tcl_Size refCount; /* Count of number of references to this * compiled regexp. */ } TclRegexp; #endif /* _TCLREGEXP */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclResolve.c0000644000175000017500000003254614726623136015472 0ustar sergeisergei/* * tclResolve.c -- * * Contains hooks for customized command/variable name resolution * schemes. These hooks allow extensions like [incr Tcl] to add their own * name resolution rules to the Tcl language. Rules can be applied to a * particular namespace, to the interpreter as a whole, or both. * * Copyright © 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 "tclInt.h" /* * Declarations for functions local to this file: */ static void BumpCmdRefEpochs(Namespace *nsPtr); /* *---------------------------------------------------------------------- * * Tcl_AddInterpResolvers -- * * Adds a set of command/variable resolution functions to an interpreter. * These functions are consulted when commands are resolved in * Tcl_FindCommand, and when variables are resolved in TclLookupVar and * LookupCompiledLocal. Each namespace may also have its own set of * resolution functions which take precedence over those for the * interpreter. * * When a name is resolved, it is handled as follows. First, the name is * passed to the resolution functions for the namespace. If not resolved, * the name is passed to each of the resolution functions added to the * interpreter. Finally, if still not resolved, the name is handled using * the default Tcl rules for name resolution. * * Results: * Returns pointers to the current name resolution functions in the * cmdProcPtr, varProcPtr and compiledVarProcPtr arguments. * * Side effects: * If a compiledVarProc is specified, this function bumps the * compileEpoch for the interpreter, forcing all code to be recompiled. * If a cmdProc is specified, this function bumps the cmdRefEpoch in all * namespaces, forcing commands to be resolved again using the new rules. * *---------------------------------------------------------------------- */ void Tcl_AddInterpResolvers( Tcl_Interp *interp, /* Interpreter whose name resolution rules are * being modified. */ const char *name, /* Name of this resolution scheme. */ Tcl_ResolveCmdProc *cmdProc,/* New function for command resolution. */ Tcl_ResolveVarProc *varProc,/* Function for variable resolution at * runtime. */ Tcl_ResolveCompiledVarProc *compiledVarProc) /* Function for variable resolution at compile * time. */ { Interp *iPtr = (Interp *) interp; ResolverScheme *resPtr; unsigned len; /* * Since we're adding a new name resolution scheme, we must force all code * to be recompiled to use the new scheme. If there are new compiled * variable resolution rules, bump the compiler epoch to invalidate * compiled code. If there are new command resolution rules, bump the * cmdRefEpoch in all namespaces. */ if (compiledVarProc) { iPtr->compileEpoch++; } if (cmdProc) { BumpCmdRefEpochs(iPtr->globalNsPtr); } /* * Look for an existing scheme with the given name. If found, then replace * its rules. */ for (resPtr=iPtr->resolverPtr ; resPtr!=NULL ; resPtr=resPtr->nextPtr) { if (*name == *resPtr->name && strcmp(name, resPtr->name) == 0) { resPtr->cmdResProc = cmdProc; resPtr->varResProc = varProc; resPtr->compiledVarResProc = compiledVarProc; return; } } /* * Otherwise, this is a new scheme. Add it to the FRONT of the linked * list, so that it overrides existing schemes. */ resPtr = (ResolverScheme *)Tcl_Alloc(sizeof(ResolverScheme)); len = strlen(name) + 1; resPtr->name = (char *)Tcl_Alloc(len); memcpy(resPtr->name, name, len); resPtr->cmdResProc = cmdProc; resPtr->varResProc = varProc; resPtr->compiledVarResProc = compiledVarProc; resPtr->nextPtr = iPtr->resolverPtr; iPtr->resolverPtr = resPtr; } /* *---------------------------------------------------------------------- * * Tcl_GetInterpResolvers -- * * Looks for a set of command/variable resolution functions with the * given name in an interpreter. These functions are registered by * calling Tcl_AddInterpResolvers. * * Results: * If the name is recognized, this function returns non-zero, along with * pointers to the name resolution functions in the Tcl_ResolverInfo * structure. If the name is not recognized, this function returns zero. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_GetInterpResolvers( Tcl_Interp *interp, /* Interpreter whose name resolution rules are * being queried. */ const char *name, /* Look for a scheme with this name. */ Tcl_ResolverInfo *resInfoPtr) /* Returns pointers to the functions, if * found */ { Interp *iPtr = (Interp *) interp; ResolverScheme *resPtr; /* * Look for an existing scheme with the given name. If found, then return * pointers to its functions. */ for (resPtr=iPtr->resolverPtr ; resPtr!=NULL ; resPtr=resPtr->nextPtr) { if (*name == *resPtr->name && strcmp(name, resPtr->name) == 0) { resInfoPtr->cmdResProc = resPtr->cmdResProc; resInfoPtr->varResProc = resPtr->varResProc; resInfoPtr->compiledVarResProc = resPtr->compiledVarResProc; return 1; } } return 0; } /* *---------------------------------------------------------------------- * * Tcl_RemoveInterpResolvers -- * * Removes a set of command/variable resolution functions previously * added by Tcl_AddInterpResolvers. The next time a command/variable name * is resolved, these functions won't be consulted. * * Results: * Returns non-zero if the name was recognized and the resolution scheme * was deleted. Returns zero otherwise. * * Side effects: * If a scheme with a compiledVarProc was deleted, this function bumps * the compileEpoch for the interpreter, forcing all code to be * recompiled. If a scheme with a cmdProc was deleted, this function * bumps the cmdRefEpoch in all namespaces, forcing commands to be * resolved again using the new rules. * *---------------------------------------------------------------------- */ int Tcl_RemoveInterpResolvers( Tcl_Interp *interp, /* Interpreter whose name resolution rules are * being modified. */ const char *name) /* Name of the scheme to be removed. */ { Interp *iPtr = (Interp *) interp; ResolverScheme **prevPtrPtr, *resPtr; /* * Look for an existing scheme with the given name. */ prevPtrPtr = &iPtr->resolverPtr; for (resPtr=iPtr->resolverPtr ; resPtr!=NULL ; resPtr=resPtr->nextPtr) { if (*name == *resPtr->name && strcmp(name, resPtr->name) == 0) { break; } prevPtrPtr = &resPtr->nextPtr; } /* * If we found the scheme, delete it. */ if (resPtr) { /* * If we're deleting a scheme with compiled variable resolution rules, * bump the compiler epoch to invalidate compiled code. If we're * deleting a scheme with command resolution rules, bump the * cmdRefEpoch in all namespaces. */ if (resPtr->compiledVarResProc) { iPtr->compileEpoch++; } if (resPtr->cmdResProc) { BumpCmdRefEpochs(iPtr->globalNsPtr); } *prevPtrPtr = resPtr->nextPtr; Tcl_Free(resPtr->name); Tcl_Free(resPtr); return 1; } return 0; } /* *---------------------------------------------------------------------- * * BumpCmdRefEpochs -- * * This function is used to bump the cmdRefEpoch counters in the * specified namespace and all of its child namespaces. It is used * whenever name resolution schemes are added/removed from an * interpreter, to invalidate all command references. * * Results: * None. * * Side effects: * Bumps the cmdRefEpoch in the specified namespace and its children, * recursively. * *---------------------------------------------------------------------- */ static void BumpCmdRefEpochs( Namespace *nsPtr) /* Namespace being modified. */ { Tcl_HashEntry *entry; Tcl_HashSearch search; nsPtr->cmdRefEpoch++; #ifndef BREAK_NAMESPACE_COMPAT for (entry = Tcl_FirstHashEntry(&nsPtr->childTable, &search); entry != NULL; entry = Tcl_NextHashEntry(&search)) { Namespace *childNsPtr = (Namespace *)Tcl_GetHashValue(entry); BumpCmdRefEpochs(childNsPtr); } #else if (nsPtr->childTablePtr != NULL) { for (entry = Tcl_FirstHashEntry(nsPtr->childTablePtr, &search); entry != NULL; entry = Tcl_NextHashEntry(&search)) { Namespace *childNsPtr = Tcl_GetHashValue(entry); BumpCmdRefEpochs(childNsPtr); } } #endif TclInvalidateNsPath(nsPtr); } /* *---------------------------------------------------------------------- * * Tcl_SetNamespaceResolvers -- * * Sets the command/variable resolution functions for a namespace, * thereby changing the way that command/variable names are interpreted. * This allows extension writers to support different name resolution * schemes, such as those for object-oriented packages. * * Command resolution is handled by a function of the following type: * * typedef int (Tcl_ResolveCmdProc)(Tcl_Interp *interp, * const char *name, Tcl_Namespace *context, * int flags, Tcl_Command *rPtr); * * Whenever a command is executed or Tcl_FindCommand is invoked within * the namespace, this function is called to resolve the command name. If * this function is able to resolve the name, it should return the status * code TCL_OK, along with the corresponding Tcl_Command in the rPtr * argument. Otherwise, the function can return TCL_CONTINUE, and the * command will be treated under the usual name resolution rules. Or, it * can return TCL_ERROR, and the command will be considered invalid. * * Variable resolution is handled by two functions. The first is called * whenever a variable needs to be resolved at compile time: * * typedef int (Tcl_ResolveCompiledVarProc)(Tcl_Interp *interp, * const char *name, Tcl_Namespace *context, * Tcl_ResolvedVarInfo *rPtr); * * If this function is able to resolve the name, it should return the * status code TCL_OK, along with variable resolution info in the rPtr * argument; this info will be used to set up compiled locals in the call * frame at runtime. The function may also return TCL_CONTINUE, and the * variable will be treated under the usual name resolution rules. Or, it * can return TCL_ERROR, and the variable will be considered invalid. * * Another function is used whenever a variable needs to be resolved at * runtime but it is not recognized as a compiled local. (For example, * the variable may be requested via Tcl_FindNamespaceVar.) This function * has the following type: * * typedef int (Tcl_ResolveVarProc)(Tcl_Interp *interp, * const char *name, Tcl_Namespace *context, * int flags, Tcl_Var *rPtr); * * This function is quite similar to the compile-time version. It returns * the same status codes, but if variable resolution succeeds, this * function returns a Tcl_Var directly via the rPtr argument. * * Results: * Nothing. * * Side effects: * Bumps the command epoch counter for the namespace, invalidating all * command references in that namespace. Also bumps the resolver epoch * counter for the namespace, forcing all code in the namespace to be * recompiled. * *---------------------------------------------------------------------- */ void Tcl_SetNamespaceResolvers( Tcl_Namespace *namespacePtr,/* Namespace whose resolution rules are being * modified. */ Tcl_ResolveCmdProc *cmdProc,/* Function for command resolution */ Tcl_ResolveVarProc *varProc,/* Function for variable resolution at * run-time */ Tcl_ResolveCompiledVarProc *compiledVarProc) /* Function for variable resolution at compile * time. */ { Namespace *nsPtr = (Namespace *) namespacePtr; /* * Plug in the new command resolver, and bump the epoch counters so that * all code will have to be recompiled and all commands will have to be * resolved again using the new policy. */ nsPtr->cmdResProc = cmdProc; nsPtr->varResProc = varProc; nsPtr->compiledVarResProc = compiledVarProc; nsPtr->cmdRefEpoch++; nsPtr->resolverEpoch++; TclInvalidateNsPath(nsPtr); } /* *---------------------------------------------------------------------- * * Tcl_GetNamespaceResolvers -- * * Returns the current command/variable resolution functions for a * namespace. By default, these functions are NULL. New functions can be * installed by calling Tcl_SetNamespaceResolvers, to provide new name * resolution rules. * * Results: * Returns non-zero if any name resolution functions have been assigned * to this namespace; also returns pointers to the functions in the * Tcl_ResolverInfo structure. Returns zero otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_GetNamespaceResolvers( Tcl_Namespace *namespacePtr,/* Namespace whose resolution rules are being * modified. */ Tcl_ResolverInfo *resInfoPtr) /* Returns: pointers for all name resolution * functions assigned to this namespace. */ { Namespace *nsPtr = (Namespace *) namespacePtr; resInfoPtr->cmdResProc = nsPtr->cmdResProc; resInfoPtr->varResProc = nsPtr->varResProc; resInfoPtr->compiledVarResProc = nsPtr->compiledVarResProc; if (nsPtr->cmdResProc != NULL || nsPtr->varResProc != NULL || nsPtr->compiledVarResProc != NULL) { return 1; } return 0; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclResult.c0000644000175000017500000007440614726623136015332 0ustar sergeisergei/* * tclResult.c -- * * This file contains code to manage the interpreter result. * * Copyright © 1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include /* * Indices of the standard return options dictionary keys. */ enum returnKeys { KEY_CODE, KEY_ERRORCODE, KEY_ERRORINFO, KEY_ERRORLINE, KEY_LEVEL, KEY_OPTIONS, KEY_ERRORSTACK, KEY_LAST }; /* * Function prototypes for local functions in this file: */ static Tcl_Obj ** GetKeys(void); static void ReleaseKeys(void *clientData); static void ResetObjResult(Interp *iPtr); /* * This structure is used to take a snapshot of the interpreter state in * Tcl_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 { int status; /* return code status */ int flags; /* Each remaining field saves the */ int returnLevel; /* corresponding field of the Interp */ int returnCode; /* struct. These fields taken together are */ Tcl_Obj *errorInfo; /* the "state" of the interp. */ Tcl_Obj *errorCode; Tcl_Obj *returnOpts; Tcl_Obj *objResult; Tcl_Obj *errorStack; int resetErrorStack; } InterpState; /* *---------------------------------------------------------------------- * * Tcl_SaveInterpState -- * * Fills a token with a snapshot of the current state of the interpreter. * The snapshot can be restored at any point by Tcl_RestoreInterpState. * * The token returned must be eventually passed to one of the routines * Tcl_RestoreInterpState or Tcl_DiscardInterpState, or there will be a * memory leak. * * Results: * Returns a token representing the interp state. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_InterpState Tcl_SaveInterpState( Tcl_Interp *interp, /* Interpreter's state to be saved */ int status) /* status code for current operation */ { Interp *iPtr = (Interp *) interp; InterpState *statePtr = (InterpState *)Tcl_Alloc(sizeof(InterpState)); statePtr->status = status; statePtr->flags = iPtr->flags & ERR_ALREADY_LOGGED; statePtr->returnLevel = iPtr->returnLevel; statePtr->returnCode = iPtr->returnCode; statePtr->errorInfo = iPtr->errorInfo; statePtr->errorStack = iPtr->errorStack; statePtr->resetErrorStack = iPtr->resetErrorStack; if (statePtr->errorInfo) { Tcl_IncrRefCount(statePtr->errorInfo); } statePtr->errorCode = iPtr->errorCode; if (statePtr->errorCode) { Tcl_IncrRefCount(statePtr->errorCode); } statePtr->returnOpts = iPtr->returnOpts; if (statePtr->returnOpts) { Tcl_IncrRefCount(statePtr->returnOpts); } if (statePtr->errorStack) { Tcl_IncrRefCount(statePtr->errorStack); } statePtr->objResult = Tcl_GetObjResult(interp); Tcl_IncrRefCount(statePtr->objResult); return (Tcl_InterpState) statePtr; } /* *---------------------------------------------------------------------- * * Tcl_RestoreInterpState -- * * Accepts an interp and a token previously returned by * Tcl_SaveInterpState. Restore the state of the interp to what it was at * the time of the Tcl_SaveInterpState call. * * Results: * Returns the status value originally passed in to Tcl_SaveInterpState. * * Side effects: * Restores the interp state and frees memory held by token. * *---------------------------------------------------------------------- */ int Tcl_RestoreInterpState( Tcl_Interp *interp, /* Interpreter's state to be restored. */ Tcl_InterpState state) /* Saved interpreter state. */ { Interp *iPtr = (Interp *) interp; InterpState *statePtr = (InterpState *) state; int status = statePtr->status; iPtr->flags &= ~ERR_ALREADY_LOGGED; iPtr->flags |= (statePtr->flags & ERR_ALREADY_LOGGED); iPtr->returnLevel = statePtr->returnLevel; iPtr->returnCode = statePtr->returnCode; iPtr->resetErrorStack = statePtr->resetErrorStack; if (iPtr->errorInfo) { Tcl_DecrRefCount(iPtr->errorInfo); } iPtr->errorInfo = statePtr->errorInfo; if (iPtr->errorInfo) { Tcl_IncrRefCount(iPtr->errorInfo); } if (iPtr->errorCode) { Tcl_DecrRefCount(iPtr->errorCode); } iPtr->errorCode = statePtr->errorCode; if (iPtr->errorCode) { Tcl_IncrRefCount(iPtr->errorCode); } if (iPtr->errorStack) { Tcl_DecrRefCount(iPtr->errorStack); } iPtr->errorStack = statePtr->errorStack; if (iPtr->errorStack) { Tcl_IncrRefCount(iPtr->errorStack); } if (iPtr->returnOpts) { Tcl_DecrRefCount(iPtr->returnOpts); } iPtr->returnOpts = statePtr->returnOpts; if (iPtr->returnOpts) { Tcl_IncrRefCount(iPtr->returnOpts); } Tcl_SetObjResult(interp, statePtr->objResult); Tcl_DiscardInterpState(state); return status; } /* *---------------------------------------------------------------------- * * Tcl_DiscardInterpState -- * * Accepts a token previously returned by Tcl_SaveInterpState. Frees the * memory it uses. * * Results: * None. * * Side effects: * Frees memory. * *---------------------------------------------------------------------- */ void Tcl_DiscardInterpState( Tcl_InterpState state) /* saved interpreter state */ { InterpState *statePtr = (InterpState *) state; if (statePtr->errorInfo) { Tcl_DecrRefCount(statePtr->errorInfo); } if (statePtr->errorCode) { Tcl_DecrRefCount(statePtr->errorCode); } if (statePtr->returnOpts) { Tcl_DecrRefCount(statePtr->returnOpts); } if (statePtr->errorStack) { Tcl_DecrRefCount(statePtr->errorStack); } Tcl_DecrRefCount(statePtr->objResult); Tcl_Free(statePtr); } /* *---------------------------------------------------------------------- * * Tcl_SetObjResult -- * Makes objPtr the interpreter's result value. * * Results: * None. * * Side effects: * Stores objPtr interp->objResultPtr, increments its reference count, and * decrements the reference count of any existing interp->objResultPtr. * * The string result is reset. * *---------------------------------------------------------------------- */ void Tcl_SetObjResult( Tcl_Interp *interp, /* Interpreter to set the result for. */ Tcl_Obj *objPtr) /* The value to set as the result. */ { Interp *iPtr = (Interp *) interp; Tcl_Obj *oldObjResult = iPtr->objResultPtr; if (objPtr == oldObjResult) { /* This should be impossible */ assert(objPtr->refCount != 0); return; } else { iPtr->objResultPtr = objPtr; Tcl_IncrRefCount(objPtr); TclDecrRefCount(oldObjResult); } } /* *---------------------------------------------------------------------- * * Tcl_GetObjResult -- * * Returns an interpreter's result value as a Tcl object. The object's * reference count is not modified; the caller must do that if it needs * to hold on to a long-term reference to it. * * Results: * The interpreter's result as an object. * * Side effects: * If the interpreter has a non-empty string result, the result object is * either empty or stale because some function set interp->result * directly. If so, the string result is moved to the result object then * the string result is reset. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_GetObjResult( Tcl_Interp *interp) /* Interpreter whose result to return. */ { Interp *iPtr = (Interp *) interp; return iPtr->objResultPtr; } /* *---------------------------------------------------------------------- * * Tcl_AppendResult -- * * Append a variable number of strings onto the interpreter's result. * * Results: * None. * * Side effects: * The result of the interpreter given by the first argument is extended * by the strings given by the second and following arguments (up to a * terminating NULL argument). * * If the string result is non-empty, the object result forced to be a * duplicate of it first. There will be a string result afterwards. * *---------------------------------------------------------------------- */ void Tcl_AppendResult( Tcl_Interp *interp, ...) { va_list argList; Tcl_Obj *objPtr; va_start(argList, interp); objPtr = Tcl_GetObjResult(interp); if (Tcl_IsShared(objPtr)) { objPtr = Tcl_DuplicateObj(objPtr); } while (1) { const char *bytes = va_arg(argList, char *); if (bytes == NULL) { break; } Tcl_AppendToObj(objPtr, bytes, -1); } Tcl_SetObjResult(interp, objPtr); va_end(argList); } /* *---------------------------------------------------------------------- * * Tcl_AppendElement -- * * Convert a string to a valid Tcl list element and append it to the * result (which is ostensibly a list). * * Results: * None. * * Side effects: * The result in the interpreter given by the first argument is extended * with a list element converted from string. A separator space is added * before the converted list element unless the current result is empty, * contains the single character "{", or ends in " {". * * If the string result is empty, the object result is moved to the * string result, then the object result is reset. * *---------------------------------------------------------------------- */ void Tcl_AppendElement( Tcl_Interp *interp, /* Interpreter whose result is to be * extended. */ const char *element) /* String to convert to list element and add * to result. */ { Interp *iPtr = (Interp *) interp; Tcl_Obj *elementPtr = Tcl_NewStringObj(element, -1); Tcl_Obj *listPtr = Tcl_NewListObj(1, &elementPtr); const char *bytes; Tcl_Size length; if (Tcl_IsShared(iPtr->objResultPtr)) { Tcl_SetObjResult(interp, Tcl_DuplicateObj(iPtr->objResultPtr)); } bytes = TclGetStringFromObj(iPtr->objResultPtr, &length); if (TclNeedSpace(bytes, bytes + length)) { Tcl_AppendToObj(iPtr->objResultPtr, " ", 1); } Tcl_AppendObjToObj(iPtr->objResultPtr, listPtr); Tcl_DecrRefCount(listPtr); } /* *---------------------------------------------------------------------- * * Tcl_ResetResult -- * * This function resets both the interpreter's string and object results. * * Results: * None. * * Side effects: * It resets the result object to an unshared empty object. It then * restores the interpreter's string result area to its default * initialized state, freeing up any memory that may have been allocated. * It also clears any error information for the interpreter. * *---------------------------------------------------------------------- */ void Tcl_ResetResult( Tcl_Interp *interp) /* Interpreter for which to clear result. */ { Interp *iPtr = (Interp *) interp; ResetObjResult(iPtr); if (iPtr->errorCode) { /* Legacy support */ if (iPtr->flags & ERR_LEGACY_COPY) { Tcl_ObjSetVar2(interp, iPtr->ecVar, NULL, iPtr->errorCode, TCL_GLOBAL_ONLY); } Tcl_DecrRefCount(iPtr->errorCode); iPtr->errorCode = NULL; } if (iPtr->errorInfo) { /* Legacy support */ if (iPtr->flags & ERR_LEGACY_COPY) { Tcl_ObjSetVar2(interp, iPtr->eiVar, NULL, iPtr->errorInfo, TCL_GLOBAL_ONLY); } Tcl_DecrRefCount(iPtr->errorInfo); iPtr->errorInfo = NULL; } iPtr->resetErrorStack = 1; iPtr->returnLevel = 1; iPtr->returnCode = TCL_OK; if (iPtr->returnOpts) { Tcl_DecrRefCount(iPtr->returnOpts); iPtr->returnOpts = NULL; } iPtr->flags &= ~(ERR_ALREADY_LOGGED | ERR_LEGACY_COPY); } /* *---------------------------------------------------------------------- * * ResetObjResult -- * * Function used to reset an interpreter's Tcl result object. * * Results: * None. * * Side effects: * Resets the interpreter's result object to an unshared empty string * object with ref count one. It does not clear any error information in * the interpreter. * *---------------------------------------------------------------------- */ static void ResetObjResult( Interp *iPtr) /* Points to the interpreter whose result * object should be reset. */ { Tcl_Obj *objResultPtr = iPtr->objResultPtr; if (Tcl_IsShared(objResultPtr)) { TclDecrRefCount(objResultPtr); TclNewObj(objResultPtr); Tcl_IncrRefCount(objResultPtr); iPtr->objResultPtr = objResultPtr; } else { if (objResultPtr->bytes != &tclEmptyString) { if (objResultPtr->bytes) { Tcl_Free(objResultPtr->bytes); } objResultPtr->bytes = &tclEmptyString; objResultPtr->length = 0; } TclFreeInternalRep(objResultPtr); } } /* *---------------------------------------------------------------------- * * Tcl_SetErrorCode -- * * This function is called to record machine-readable information about * an error that is about to be returned. * * Results: * None. * * Side effects: * The errorCode field of the interp is modified to hold all of the * arguments to this function, in a list form with each argument becoming * one element of the list. * *---------------------------------------------------------------------- */ void Tcl_SetErrorCode( Tcl_Interp *interp, ...) { va_list argList; Tcl_Obj *errorObj; /* * Scan through the arguments one at a time, appending them to the * errorCode field as list elements. */ va_start(argList, interp); TclNewObj(errorObj); /* * Scan through the arguments one at a time, appending them to the * errorCode field as list elements. */ while (1) { char *elem = va_arg(argList, char *); if (elem == NULL) { break; } Tcl_ListObjAppendElement(NULL, errorObj, Tcl_NewStringObj(elem, -1)); } Tcl_SetObjErrorCode(interp, errorObj); va_end(argList); } /* *---------------------------------------------------------------------- * * Tcl_SetObjErrorCode -- * * This function is called to record machine-readable information about * an error that is about to be returned. The caller should build a list * object up and pass it to this routine. * * Results: * None. * * Side effects: * The errorCode field of the interp is set to the new value. * *---------------------------------------------------------------------- */ void Tcl_SetObjErrorCode( Tcl_Interp *interp, Tcl_Obj *errorObjPtr) { Interp *iPtr = (Interp *) interp; if (iPtr->errorCode) { Tcl_DecrRefCount(iPtr->errorCode); } iPtr->errorCode = errorObjPtr; Tcl_IncrRefCount(iPtr->errorCode); } /* *---------------------------------------------------------------------- * * Tcl_GetErrorLine -- * * Returns the line number associated with the current error. * *---------------------------------------------------------------------- */ int Tcl_GetErrorLine( Tcl_Interp *interp) { return ((Interp *) interp)->errorLine; } /* *---------------------------------------------------------------------- * * Tcl_SetErrorLine -- * * Sets the line number associated with the current error. * *---------------------------------------------------------------------- */ void Tcl_SetErrorLine( Tcl_Interp *interp, int value) { ((Interp *) interp)->errorLine = value; } /* *---------------------------------------------------------------------- * * GetKeys -- * * Returns a Tcl_Obj * array of the standard keys used in the return * options dictionary. * * Broadly sharing one copy of these key values helps with both memory * efficiency and dictionary lookup times. * * Results: * A Tcl_Obj * array. * * Side effects: * First time called in a thread, creates the keys (allocating memory) * and arranges for their cleanup at thread exit. * *---------------------------------------------------------------------- */ static Tcl_Obj ** GetKeys(void) { static Tcl_ThreadDataKey returnKeysKey; Tcl_Obj **keys = (Tcl_Obj **)Tcl_GetThreadData(&returnKeysKey, KEY_LAST * sizeof(Tcl_Obj *)); if (keys[0] == NULL) { /* * First call in this thread, create the keys... */ int i; TclNewLiteralStringObj(keys[KEY_CODE], "-code"); TclNewLiteralStringObj(keys[KEY_ERRORCODE], "-errorcode"); TclNewLiteralStringObj(keys[KEY_ERRORINFO], "-errorinfo"); TclNewLiteralStringObj(keys[KEY_ERRORLINE], "-errorline"); TclNewLiteralStringObj(keys[KEY_ERRORSTACK],"-errorstack"); TclNewLiteralStringObj(keys[KEY_LEVEL], "-level"); TclNewLiteralStringObj(keys[KEY_OPTIONS], "-options"); for (i = KEY_CODE; i < KEY_LAST; i++) { Tcl_IncrRefCount(keys[i]); } /* * ... and arrange for their clenaup. */ Tcl_CreateThreadExitHandler(ReleaseKeys, keys); } return keys; } /* *---------------------------------------------------------------------- * * ReleaseKeys -- * * Called as a thread exit handler to cleanup return options dictionary * keys. * * Results: * None. * * Side effects: * Frees memory. * *---------------------------------------------------------------------- */ static void ReleaseKeys( void *clientData) { Tcl_Obj **keys = (Tcl_Obj **)clientData; int i; for (i = KEY_CODE; i < KEY_LAST; i++) { Tcl_DecrRefCount(keys[i]); keys[i] = NULL; } } /* *---------------------------------------------------------------------- * * TclProcessReturn -- * * Does the work of the [return] command based on the code, level, and * returnOpts arguments. Note that the code argument must agree with the * -code entry in returnOpts and the level argument must agree with the * -level entry in returnOpts, as is the case for values returned from * TclMergeReturnOptions. * * Results: * Returns the return code the [return] command should return. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclProcessReturn( Tcl_Interp *interp, int code, int level, Tcl_Obj *returnOpts) { Interp *iPtr = (Interp *) interp; Tcl_Obj *valuePtr; Tcl_Obj **keys = GetKeys(); /* * Store the merged return options. */ if (iPtr->returnOpts != returnOpts) { if (iPtr->returnOpts) { Tcl_DecrRefCount(iPtr->returnOpts); } iPtr->returnOpts = returnOpts; Tcl_IncrRefCount(iPtr->returnOpts); } if (code == TCL_ERROR) { if (iPtr->errorInfo) { Tcl_DecrRefCount(iPtr->errorInfo); iPtr->errorInfo = NULL; } Tcl_DictObjGet(NULL, iPtr->returnOpts, keys[KEY_ERRORINFO], &valuePtr); if (valuePtr != NULL) { Tcl_Size length; (void)TclGetStringFromObj(valuePtr, &length); if (length) { iPtr->errorInfo = valuePtr; Tcl_IncrRefCount(iPtr->errorInfo); iPtr->flags |= ERR_ALREADY_LOGGED; } } Tcl_DictObjGet(NULL, iPtr->returnOpts, keys[KEY_ERRORSTACK], &valuePtr); if (valuePtr != NULL) { Tcl_Size len, valueObjc; Tcl_Obj **valueObjv; if (Tcl_IsShared(iPtr->errorStack)) { Tcl_Obj *newObj; newObj = Tcl_DuplicateObj(iPtr->errorStack); Tcl_DecrRefCount(iPtr->errorStack); Tcl_IncrRefCount(newObj); iPtr->errorStack = newObj; } /* * List extraction done after duplication to avoid moving the rug * if someone does [return -errorstack [info errorstack]] */ if (TclListObjGetElements(interp, valuePtr, &valueObjc, &valueObjv) == TCL_ERROR) { return TCL_ERROR; } iPtr->resetErrorStack = 0; TclListObjLength(interp, iPtr->errorStack, &len); /* * Reset while keeping the list internalrep as much as possible. */ Tcl_ListObjReplace(interp, iPtr->errorStack, 0, len, valueObjc, valueObjv); } Tcl_DictObjGet(NULL, iPtr->returnOpts, keys[KEY_ERRORCODE], &valuePtr); if (valuePtr != NULL) { Tcl_SetObjErrorCode(interp, valuePtr); } else { Tcl_SetErrorCode(interp, "NONE", (char *)NULL); } Tcl_DictObjGet(NULL, iPtr->returnOpts, keys[KEY_ERRORLINE], &valuePtr); if (valuePtr != NULL) { TclGetIntFromObj(NULL, valuePtr, &iPtr->errorLine); } } if (level != 0) { iPtr->returnLevel = level; iPtr->returnCode = code; return TCL_RETURN; } if (code == TCL_ERROR) { iPtr->flags |= ERR_LEGACY_COPY; } return code; } /* *---------------------------------------------------------------------- * * TclMergeReturnOptions -- * * Parses, checks, and stores the options to the [return] command. * * Results: * Returns TCL_ERROR if any of the option values are invalid. Otherwise, * returns TCL_OK, and writes the returnOpts, code, and level values to * the pointers provided. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclMergeReturnOptions( Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[], /* Argument objects. */ Tcl_Obj **optionsPtrPtr, /* If not NULL, points to space for a (Tcl_Obj * *) where the pointer to the merged return * options dictionary should be written. */ int *codePtr, /* If not NULL, points to space where the * -code value should be written. */ int *levelPtr) /* If not NULL, points to space where the * -level value should be written. */ { int code = TCL_OK; int level = 1; Tcl_Obj *valuePtr; Tcl_Obj *returnOpts; Tcl_Obj **keys = GetKeys(); TclNewObj(returnOpts); for (; objc > 1; objv += 2, objc -= 2) { const char *opt = TclGetString(objv[0]); const char *compare = TclGetString(keys[KEY_OPTIONS]); if ((objv[0]->length == keys[KEY_OPTIONS]->length) && (memcmp(opt, compare, objv[0]->length) == 0)) { Tcl_DictSearch search; int done = 0; Tcl_Obj *keyPtr; Tcl_Obj *dict = objv[1]; nestedOptions: if (TCL_ERROR == Tcl_DictObjFirst(NULL, dict, &search, &keyPtr, &valuePtr, &done)) { /* * Value is not a legal dictionary. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad %s value: expected dictionary but got \"%s\"", compare, TclGetString(objv[1]))); Tcl_SetErrorCode(interp, "TCL", "RESULT", "ILLEGAL_OPTIONS", (char *)NULL); goto error; } while (!done) { Tcl_DictObjPut(NULL, returnOpts, keyPtr, valuePtr); Tcl_DictObjNext(&search, &keyPtr, &valuePtr, &done); } Tcl_DictObjGet(NULL, returnOpts, keys[KEY_OPTIONS], &valuePtr); if (valuePtr != NULL) { dict = valuePtr; Tcl_DictObjRemove(NULL, returnOpts, keys[KEY_OPTIONS]); goto nestedOptions; } } else { Tcl_DictObjPut(NULL, returnOpts, objv[0], objv[1]); } } /* * Check for bogus -code value. */ Tcl_DictObjGet(NULL, returnOpts, keys[KEY_CODE], &valuePtr); if (valuePtr != NULL) { if (TclGetCompletionCodeFromObj(interp, valuePtr, &code) == TCL_ERROR) { goto error; } Tcl_DictObjRemove(NULL, returnOpts, keys[KEY_CODE]); } /* * Check for bogus -level value. */ Tcl_DictObjGet(NULL, returnOpts, keys[KEY_LEVEL], &valuePtr); if (valuePtr != NULL) { if ((TCL_ERROR == TclGetIntFromObj(NULL, valuePtr, &level)) || (level < 0)) { /* * Value is not a legal level. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad -level value: expected non-negative integer but got" " \"%s\"", TclGetString(valuePtr))); Tcl_SetErrorCode(interp, "TCL", "RESULT", "ILLEGAL_LEVEL", (char *)NULL); goto error; } Tcl_DictObjRemove(NULL, returnOpts, keys[KEY_LEVEL]); } /* * Check for bogus -errorcode value. */ Tcl_DictObjGet(NULL, returnOpts, keys[KEY_ERRORCODE], &valuePtr); if (valuePtr != NULL) { Tcl_Size length; if (TCL_ERROR == TclListObjLength(NULL, valuePtr, &length )) { /* * Value is not a list, which is illegal for -errorcode. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad -errorcode value: expected a list but got \"%s\"", TclGetString(valuePtr))); Tcl_SetErrorCode(interp, "TCL", "RESULT", "ILLEGAL_ERRORCODE", (char *)NULL); goto error; } } /* * Check for bogus -errorstack value. */ Tcl_DictObjGet(NULL, returnOpts, keys[KEY_ERRORSTACK], &valuePtr); if (valuePtr != NULL) { Tcl_Size length; if (TCL_ERROR == TclListObjLength(NULL, valuePtr, &length)) { /* * Value is not a list, which is illegal for -errorstack. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad -errorstack value: expected a list but got \"%s\"", TclGetString(valuePtr))); Tcl_SetErrorCode(interp, "TCL", "RESULT", "NONLIST_ERRORSTACK", (char *)NULL); goto error; } if (length % 2) { /* * Errorstack must always be an even-sized list */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "forbidden odd-sized list for -errorstack: \"%s\"", TclGetString(valuePtr))); Tcl_SetErrorCode(interp, "TCL", "RESULT", "ODDSIZEDLIST_ERRORSTACK", (char *)NULL); goto error; } } /* * Convert [return -code return -level X] to [return -code ok -level X+1] */ if (code == TCL_RETURN) { level++; code = TCL_OK; } if (codePtr != NULL) { *codePtr = code; } if (levelPtr != NULL) { *levelPtr = level; } if (optionsPtrPtr == NULL) { /* * Not passing back the options (?!), so clean them up. */ Tcl_DecrRefCount(returnOpts); } else { *optionsPtrPtr = returnOpts; } return TCL_OK; error: Tcl_DecrRefCount(returnOpts); return TCL_ERROR; } /* *------------------------------------------------------------------------- * * Tcl_GetReturnOptions -- * * Packs up the interp state into a dictionary of return options. * * Results: * A dictionary of return options. * * Side effects: * None. * *------------------------------------------------------------------------- */ Tcl_Obj * Tcl_GetReturnOptions( Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; Tcl_Obj *options; Tcl_Obj **keys = GetKeys(); if (iPtr->returnOpts) { options = Tcl_DuplicateObj(iPtr->returnOpts); } else { TclNewObj(options); } if (result == TCL_RETURN) { Tcl_DictObjPut(NULL, options, keys[KEY_CODE], Tcl_NewWideIntObj(iPtr->returnCode)); Tcl_DictObjPut(NULL, options, keys[KEY_LEVEL], Tcl_NewWideIntObj(iPtr->returnLevel)); } else { Tcl_DictObjPut(NULL, options, keys[KEY_CODE], Tcl_NewWideIntObj(result)); Tcl_DictObjPut(NULL, options, keys[KEY_LEVEL], Tcl_NewWideIntObj(0)); } if (result == TCL_ERROR) { Tcl_AddErrorInfo(interp, ""); Tcl_DictObjPut(NULL, options, keys[KEY_ERRORSTACK], iPtr->errorStack); } if (iPtr->errorCode) { Tcl_DictObjPut(NULL, options, keys[KEY_ERRORCODE], iPtr->errorCode); } if (iPtr->errorInfo) { Tcl_DictObjPut(NULL, options, keys[KEY_ERRORINFO], iPtr->errorInfo); Tcl_DictObjPut(NULL, options, keys[KEY_ERRORLINE], Tcl_NewWideIntObj(iPtr->errorLine)); } return options; } /* *------------------------------------------------------------------------- * * TclNoErrorStack -- * * Removes the -errorstack entry from an options dict to avoid reference * cycles. * * Results: * The (unshared) argument options dict, modified in -place. * *------------------------------------------------------------------------- */ Tcl_Obj * TclNoErrorStack( Tcl_Interp *interp, Tcl_Obj *options) { Tcl_Obj **keys = GetKeys(); Tcl_DictObjRemove(interp, options, keys[KEY_ERRORSTACK]); return options; } /* *------------------------------------------------------------------------- * * Tcl_SetReturnOptions -- * * Accepts an interp and a dictionary of return options, and sets the * return options of the interp to match the dictionary. * * Results: * A standard status code. Usually TCL_OK, but TCL_ERROR if an invalid * option value was found in the dictionary. If a -level value of 0 is in * the dictionary, then the -code value in the dictionary will be * returned (TCL_OK default). * * Side effects: * Sets the state of the interp. * *------------------------------------------------------------------------- */ int Tcl_SetReturnOptions( Tcl_Interp *interp, Tcl_Obj *options) { Tcl_Size objc; int level, code; Tcl_Obj **objv, *mergedOpts; Tcl_IncrRefCount(options); if (TCL_ERROR == TclListObjGetElements(interp, options, &objc, &objv) || (objc % 2)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "expected dict but got \"%s\"", TclGetString(options))); Tcl_SetErrorCode(interp, "TCL", "RESULT", "ILLEGAL_OPTIONS", (char *)NULL); code = TCL_ERROR; } else if (TCL_ERROR == TclMergeReturnOptions(interp, objc, objv, &mergedOpts, &code, &level)) { code = TCL_ERROR; } else { code = TclProcessReturn(interp, code, level, mergedOpts); } Tcl_DecrRefCount(options); return code; } /* *------------------------------------------------------------------------- * * Tcl_TransferResult -- * * Transfer the result (and error information) from one interp to another. * Used when one interp has caused another interp to evaluate a script * and then wants to transfer the results back to itself. * * Results: * The result of targetInterp is set to the result read from sourceInterp. * The return options dictionary of sourceInterp is transferred to * targetInterp as appropriate for the return code value code. * * Side effects: * None. * *------------------------------------------------------------------------- */ void Tcl_TransferResult( Tcl_Interp *sourceInterp, /* Interp whose result and return options * should be moved to the target interp. * After moving result, this interp's result * is reset. */ int code, /* The return code value active in * sourceInterp. Controls how the return options * dictionary is retrieved from sourceInterp, * same as in Tcl_GetReturnOptions, to then be * transferred to targetInterp. */ Tcl_Interp *targetInterp) /* Interp where result and return options * should be stored. If source and target are * the same, nothing is done. */ { Interp *tiPtr = (Interp *) targetInterp; Interp *siPtr = (Interp *) sourceInterp; if (sourceInterp == targetInterp) { return; } if (code == TCL_OK && siPtr->returnOpts == NULL) { /* * Special optimization for the common case of normal command return * code and no explicit return options. */ if (tiPtr->returnOpts) { Tcl_DecrRefCount(tiPtr->returnOpts); tiPtr->returnOpts = NULL; } } else { Tcl_SetReturnOptions(targetInterp, Tcl_GetReturnOptions(sourceInterp, code)); tiPtr->flags &= ~(ERR_ALREADY_LOGGED); } Tcl_SetObjResult(targetInterp, Tcl_GetObjResult(sourceInterp)); Tcl_ResetResult(sourceInterp); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * indent-tabs-mode: nil * End: */ tcl9.0.1/generic/tclScan.c0000644000175000017500000006437214731032403014724 0ustar sergeisergei/* * tclScan.c -- * * This file contains the implementation of the "scan" command. * * Copyright © 1998 Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclTomMath.h" #include /* * Flag values used by Tcl_ScanObjCmd. */ enum ScanFlags { SCAN_NOSKIP = 0x1, /* Don't skip blanks. */ SCAN_SUPPRESS = 0x2, /* Suppress assignment. */ SCAN_UNSIGNED = 0x4, /* Read an unsigned value. */ SCAN_WIDTH = 0x8, /* A width value was supplied. */ SCAN_LONGER = 0x400, /* Asked for a wide value. */ SCAN_BIG = 0x800 /* Asked for a bignum value. */ }; /* * The following structure contains the information associated with a * character set. */ typedef struct { Tcl_UniChar start; Tcl_UniChar end; } Range; typedef struct { int exclude; /* 1 if this is an exclusion set. */ int nchars; Tcl_UniChar *chars; int nranges; Range *ranges; } CharSet; /* * Declarations for functions used only in this file. */ static const char * BuildCharSet(CharSet *cset, const char *format); static int CharInSet(CharSet *cset, int ch); static void ReleaseCharSet(CharSet *cset); static int ValidateFormat(Tcl_Interp *interp, const char *format, int numVars, int *totalVars); /* *---------------------------------------------------------------------- * * BuildCharSet -- * * This function examines a character set format specification and builds * a CharSet containing the individual characters and character ranges * specified. * * Results: * Returns the next format position. * * Side effects: * Initializes the charset. * *---------------------------------------------------------------------- */ static const char * BuildCharSet( CharSet *cset, const char *format) /* Points to first char of set. */ { Tcl_UniChar ch = 0, start; int offset, nranges; const char *end; memset(cset, 0, sizeof(CharSet)); offset = TclUtfToUniChar(format, &ch); if (ch == '^') { cset->exclude = 1; format += offset; offset = TclUtfToUniChar(format, &ch); } end = format + offset; /* * Find the close bracket so we can overallocate the set. */ if (ch == ']') { end += TclUtfToUniChar(end, &ch); } nranges = 0; while (ch != ']') { if (ch == '-') { nranges++; } end += TclUtfToUniChar(end, &ch); } cset->chars = (Tcl_UniChar *)Tcl_Alloc(sizeof(Tcl_UniChar) * (end - format - 1)); if (nranges > 0) { cset->ranges = (Range *)Tcl_Alloc(sizeof(Range) * nranges); } else { cset->ranges = NULL; } /* * Now build the character set. */ cset->nchars = cset->nranges = 0; format += TclUtfToUniChar(format, &ch); start = ch; if (ch == ']' || ch == '-') { cset->chars[cset->nchars++] = ch; format += TclUtfToUniChar(format, &ch); } while (ch != ']') { if (*format == '-') { /* * This may be the first character of a range, so don't add it * yet. */ start = ch; } else if (ch == '-') { /* * Check to see if this is the last character in the set, in which * case it is not a range and we should add the previous character * as well as the dash. */ if (*format == ']' || !cset->ranges) { cset->chars[cset->nchars++] = start; cset->chars[cset->nchars++] = ch; } else { format += TclUtfToUniChar(format, &ch); /* * Check to see if the range is in reverse order. */ if (start < ch) { cset->ranges[cset->nranges].start = start; cset->ranges[cset->nranges].end = ch; } else { cset->ranges[cset->nranges].start = ch; cset->ranges[cset->nranges].end = start; } cset->nranges++; } } else { cset->chars[cset->nchars++] = ch; } format += TclUtfToUniChar(format, &ch); } return format; } /* *---------------------------------------------------------------------- * * CharInSet -- * * Check to see if a character matches the given set. * * Results: * Returns non-zero if the character matches the given set. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int CharInSet( CharSet *cset, int c) /* Character to test, passed as int because of * non-ANSI prototypes. */ { Tcl_UniChar ch = (Tcl_UniChar) c; int i, match = 0; for (i = 0; i < cset->nchars; i++) { if (cset->chars[i] == ch) { match = 1; break; } } if (!match) { for (i = 0; i < cset->nranges; i++) { if ((cset->ranges[i].start <= ch) && (ch <= cset->ranges[i].end)) { match = 1; break; } } } return (cset->exclude ? !match : match); } /* *---------------------------------------------------------------------- * * ReleaseCharSet -- * * Free the storage associated with a character set. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void ReleaseCharSet( CharSet *cset) { Tcl_Free(cset->chars); if (cset->ranges) { Tcl_Free(cset->ranges); } } /* *---------------------------------------------------------------------- * * ValidateFormat -- * * Parse the format string and verify that it is properly formed and that * there are exactly enough variables on the command line. * * Results: * A standard Tcl result. * * Side effects: * May place an error in the interpreter result. * *---------------------------------------------------------------------- */ static int ValidateFormat( Tcl_Interp *interp, /* Current interpreter. */ const char *format, /* The format string. */ int numVars, /* The number of variables passed to the scan * command. */ int *totalSubs) /* The number of variables that will be * required. */ { int gotXpg, gotSequential, i, flags; char *end; Tcl_UniChar ch = 0; int objIndex, xpgSize, nspace = numVars; int *nassign = (int *)TclStackAlloc(interp, nspace * sizeof(int)); Tcl_Obj *errorMsg; /* Place to build an error messages. Note that * these are messy operations because we do * not want to use the formatting engine; * we're inside there! */ char buf[5] = ""; /* * Initialize an array that records the number of times a variable is * assigned to by the format string. We use this to detect if a variable * is multiply assigned or left unassigned. */ for (i = 0; i < nspace; i++) { nassign[i] = 0; } xpgSize = objIndex = gotXpg = gotSequential = 0; while (*format != '\0') { format += TclUtfToUniChar(format, &ch); flags = 0; if (ch != '%') { continue; } format += TclUtfToUniChar(format, &ch); if (ch == '%') { continue; } if (ch == '*') { flags |= SCAN_SUPPRESS; format += TclUtfToUniChar(format, &ch); goto xpgCheckDone; } if ((ch < 0x80) && isdigit(UCHAR(ch))) { /* INTL: "C" locale. */ /* * Check for an XPG3-style %n$ specification. Note: there must * not be a mixture of XPG3 specs and non-XPG3 specs in the same * format string. */ /* assert(value is >= 0) because of the isdigit() check above */ unsigned long long ull = strtoull(format-1, &end, 10); /* INTL: "C" locale. */ if (*end != '$') { goto notXpg; } format = end+1; format += TclUtfToUniChar(format, &ch); gotXpg = 1; if (gotSequential) { goto mixedXPG; } /* >=INT_MAX because 9.0 does not support more than INT_MAX-1 args */ if (ull == 0 || ull >= INT_MAX) { goto badIndex; } objIndex = (int) ull - 1; if (numVars && (objIndex >= numVars)) { goto badIndex; } else if (numVars == 0) { /* * In the case where no vars are specified, the user can * specify %9999$ legally, so we have to consider special * rules for growing the assign array. 'ull' is guaranteed * to be > 0 and < INT_MAX as per checks above. */ xpgSize = (xpgSize > (int)ull) ? xpgSize : (int)ull; } goto xpgCheckDone; } notXpg: gotSequential = 1; if (gotXpg) { mixedXPG: Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot mix \"%\" and \"%n$\" conversion specifiers", -1)); Tcl_SetErrorCode(interp, "TCL", "FORMAT", "MIXEDSPECTYPES", (char *)NULL); goto error; } xpgCheckDone: /* * Parse any width specifier. */ if ((ch < 0x80) && isdigit(UCHAR(ch))) { /* INTL: "C" locale. */ /* Note ull >= 0 because of isdigit check above */ unsigned long long ull; ull = strtoull( format - 1, (char **)&format, 10); /* INTL: "C" locale. */ /* Note >=, not >, to leave room for a nul */ if (ull >= TCL_SIZE_MAX) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "specified field width %" TCL_LL_MODIFIER "u exceeds limit %" TCL_SIZE_MODIFIER "d.", ull, (Tcl_Size)TCL_SIZE_MAX-1)); Tcl_SetErrorCode( interp, "TCL", "FORMAT", "WIDTHLIMIT", (char *)NULL); goto error; } flags |= SCAN_WIDTH; format += TclUtfToUniChar(format, &ch); } /* * Handle any size specifier. */ switch (ch) { case 'z': case 't': if (sizeof(void *) > sizeof(int)) { flags |= SCAN_LONGER; } format += TclUtfToUniChar(format, &ch); break; case 'L': flags |= SCAN_BIG; format += TclUtfToUniChar(format, &ch); break; case 'l': if (*format == 'l') { flags |= SCAN_BIG; format += 1; format += TclUtfToUniChar(format, &ch); break; } /* FALLTHRU */ case 'j': case 'q': flags |= SCAN_LONGER; /* FALLTHRU */ case 'h': format += TclUtfToUniChar(format, &ch); } if (!(flags & SCAN_SUPPRESS) && numVars && (objIndex >= numVars)) { goto badIndex; } /* * Handle the various field types. */ switch (ch) { case 'c': if (flags & SCAN_WIDTH) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "field width may not be specified in %c conversion", -1)); Tcl_SetErrorCode(interp, "TCL", "FORMAT", "BADWIDTH", (char *)NULL); goto error; } /* FALLTHRU */ case 'n': case 's': if (flags & (SCAN_LONGER|SCAN_BIG)) { invalidFieldSize: buf[Tcl_UniCharToUtf(ch, buf)] = '\0'; errorMsg = Tcl_NewStringObj( "field size modifier may not be specified in %", -1); Tcl_AppendToObj(errorMsg, buf, -1); Tcl_AppendToObj(errorMsg, " conversion", -1); Tcl_SetObjResult(interp, errorMsg); Tcl_SetErrorCode(interp, "TCL", "FORMAT", "BADSIZE", (char *)NULL); goto error; } /* * Fall through! */ case 'd': case 'e': case 'E': case 'f': case 'g': case 'G': case 'i': case 'o': case 'x': case 'X': case 'b': case 'u': break; /* * Bracket terms need special checking */ case '[': if (flags & (SCAN_LONGER|SCAN_BIG)) { goto invalidFieldSize; } if (*format == '\0') { goto badSet; } format += TclUtfToUniChar(format, &ch); if (ch == '^') { if (*format == '\0') { goto badSet; } format += TclUtfToUniChar(format, &ch); } if (ch == ']') { if (*format == '\0') { goto badSet; } format += TclUtfToUniChar(format, &ch); } while (ch != ']') { if (*format == '\0') { goto badSet; } format += TclUtfToUniChar(format, &ch); } break; badSet: Tcl_SetObjResult(interp, Tcl_NewStringObj( "unmatched [ in format string", -1)); Tcl_SetErrorCode(interp, "TCL", "FORMAT", "BRACKET", (char *)NULL); goto error; default: buf[Tcl_UniCharToUtf(ch, buf)] = '\0'; errorMsg = Tcl_NewStringObj( "bad scan conversion character \"", -1); Tcl_AppendToObj(errorMsg, buf, -1); Tcl_AppendToObj(errorMsg, "\"", -1); Tcl_SetObjResult(interp, errorMsg); Tcl_SetErrorCode(interp, "TCL", "FORMAT", "BADTYPE", (char *)NULL); goto error; } if (!(flags & SCAN_SUPPRESS)) { if (objIndex >= nspace) { /* * Expand the nassign buffer. If we are using XPG specifiers, * make sure that we grow to a large enough size. xpgSize is * guaranteed to be at least one larger than objIndex. */ int nspaceOrig = nspace; if (xpgSize) { nspace = xpgSize; } else { nspace += 16; /* formerly STATIC_LIST_SIZE */ } nassign = (int *)TclStackRealloc(interp, nassign, nspace * sizeof(int)); for (i = nspaceOrig; i < nspace; i++) { nassign[i] = 0; } } nassign[objIndex]++; objIndex++; } } /* * Verify that all of the variable were assigned exactly once. */ if (numVars == 0) { if (xpgSize) { numVars = xpgSize; } else { numVars = objIndex; } } if (totalSubs) { *totalSubs = numVars; } for (i = 0; i < numVars; i++) { if (nassign[i] > 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "variable is assigned by multiple \"%n$\" conversion specifiers", -1)); Tcl_SetErrorCode(interp, "TCL", "FORMAT", "POLYASSIGNED", (char *)NULL); goto error; } else if (!xpgSize && (nassign[i] == 0)) { /* * If the space is empty, and xpgSize is 0 (means XPG wasn't used, * and/or numVars != 0), then too many vars were given */ Tcl_SetObjResult(interp, Tcl_NewStringObj( "variable is not assigned by any conversion specifiers", -1)); Tcl_SetErrorCode(interp, "TCL", "FORMAT", "UNASSIGNED", (char *)NULL); goto error; } } TclStackFree(interp, nassign); return TCL_OK; badIndex: if (gotXpg) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "\"%n$\" argument index out of range", -1)); Tcl_SetErrorCode(interp, "TCL", "FORMAT", "INDEXRANGE", (char *)NULL); } else { Tcl_SetObjResult(interp, Tcl_NewStringObj( "different numbers of variable names and field specifiers", -1)); Tcl_SetErrorCode(interp, "TCL", "FORMAT", "FIELDVARMISMATCH", (char *)NULL); } error: TclStackFree(interp, nassign); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_ScanObjCmd -- * * This function is invoked to process the "scan" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_ScanObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *format; int numVars, nconversions, totalVars = -1; int objIndex, offset, i, result, code; int value; const char *string, *end, *baseString; char op = 0; int underflow = 0; Tcl_Size width; Tcl_WideInt wideValue; Tcl_UniChar ch = 0, sch = 0; Tcl_Obj **objs = NULL, *objPtr = NULL; int flags; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "string format ?varName ...?"); return TCL_ERROR; } format = TclGetString(objv[2]); numVars = objc-3; /* * Check for errors in the format string. */ if (ValidateFormat(interp, format, numVars, &totalVars) == TCL_ERROR) { return TCL_ERROR; } /* * Allocate space for the result objects. */ if (totalVars > 0) { objs = (Tcl_Obj **)Tcl_Alloc(sizeof(Tcl_Obj *) * totalVars); for (i = 0; i < totalVars; i++) { objs[i] = NULL; } } string = TclGetString(objv[1]); baseString = string; /* * Iterate over the format string filling in the result objects until we * reach the end of input, the end of the format string, or there is a * mismatch. */ objIndex = 0; nconversions = 0; while (*format != '\0') { int parseFlag = TCL_PARSE_NO_WHITESPACE; format += TclUtfToUniChar(format, &ch); flags = 0; /* * If we see whitespace in the format, skip whitespace in the string. */ if (Tcl_UniCharIsSpace(ch)) { offset = TclUtfToUniChar(string, &sch); while (Tcl_UniCharIsSpace(sch)) { if (*string == '\0') { goto done; } string += offset; offset = TclUtfToUniChar(string, &sch); } continue; } if (ch != '%') { literal: if (*string == '\0') { underflow = 1; goto done; } string += TclUtfToUniChar(string, &sch); if (ch != sch) { goto done; } continue; } format += TclUtfToUniChar(format, &ch); if (ch == '%') { goto literal; } /* * Check for assignment suppression ('*') or an XPG3-style assignment * ('%n$'). */ if (ch == '*') { flags |= SCAN_SUPPRESS; format += TclUtfToUniChar(format, &ch); } else if ((ch < 0x80) && isdigit(UCHAR(ch))) { /* INTL: "C" locale. */ char *formatEnd; /* Note currently XPG3 range limited to INT_MAX to match type of objc */ value = strtoul(format-1, &formatEnd, 10);/* INTL: "C" locale. */ if (*formatEnd == '$') { format = formatEnd+1; format += TclUtfToUniChar(format, &ch); objIndex = (int)value - 1; } } /* * Parse any width specifier. */ if ((ch < 0x80) && isdigit(UCHAR(ch))) { /* INTL: "C" locale. */ unsigned long long ull; ull = strtoull(format-1, (char **)&format, 10); /* INTL: "C" locale. */ assert(ull <= TCL_SIZE_MAX); /* Else ValidateFormat should've error'ed */ width = (Tcl_Size)ull; format += TclUtfToUniChar(format, &ch); } else { width = 0; } /* * Handle any size specifier. */ switch (ch) { case 'z': case 't': if (sizeof(void *) > sizeof(int)) { flags |= SCAN_LONGER; } format += TclUtfToUniChar(format, &ch); break; case 'L': flags |= SCAN_BIG; format += TclUtfToUniChar(format, &ch); break; case 'l': if (*format == 'l') { flags |= SCAN_BIG; format += 1; format += TclUtfToUniChar(format, &ch); break; } /* FALLTHRU */ case 'j': case 'q': flags |= SCAN_LONGER; /* FALLTHRU */ case 'h': format += TclUtfToUniChar(format, &ch); } /* * Handle the various field types. */ switch (ch) { case 'n': if (!(flags & SCAN_SUPPRESS)) { TclNewIntObj(objPtr, string - baseString); Tcl_IncrRefCount(objPtr); CLANG_ASSERT(objs); objs[objIndex++] = objPtr; } nconversions++; continue; case 'd': op = 'i'; parseFlag |= TCL_PARSE_DECIMAL_ONLY; break; case 'i': op = 'i'; parseFlag |= TCL_PARSE_SCAN_PREFIXES; break; case 'o': op = 'i'; parseFlag |= TCL_PARSE_OCTAL_ONLY | TCL_PARSE_SCAN_PREFIXES; break; case 'x': case 'X': op = 'i'; parseFlag |= TCL_PARSE_HEXADECIMAL_ONLY; break; case 'b': op = 'i'; parseFlag |= TCL_PARSE_BINARY_ONLY; break; case 'u': op = 'i'; parseFlag |= TCL_PARSE_DECIMAL_ONLY; flags |= SCAN_UNSIGNED; break; case 'f': case 'e': case 'E': case 'g': case 'G': op = 'f'; break; case 's': op = 's'; break; case 'c': op = 'c'; flags |= SCAN_NOSKIP; break; case '[': op = '['; flags |= SCAN_NOSKIP; break; } /* * At this point, we will need additional characters from the string * to proceed. */ if (*string == '\0') { underflow = 1; goto done; } /* * Skip any leading whitespace at the beginning of a field unless the * format suppresses this behavior. */ if (!(flags & SCAN_NOSKIP)) { while (*string != '\0') { offset = TclUtfToUniChar(string, &sch); if (!Tcl_UniCharIsSpace(sch)) { break; } string += offset; } if (*string == '\0') { underflow = 1; goto done; } } /* * Perform the requested scanning operation. */ switch (op) { case 's': /* * Scan a string up to width characters or whitespace. */ if (width == 0) { width = ~0; } end = string; while (*end != '\0') { offset = TclUtfToUniChar(end, &sch); if (Tcl_UniCharIsSpace(sch)) { break; } end += offset; if (--width == 0) { break; } } if (!(flags & SCAN_SUPPRESS)) { objPtr = Tcl_NewStringObj(string, end-string); Tcl_IncrRefCount(objPtr); CLANG_ASSERT(objs); objs[objIndex++] = objPtr; } string = end; break; case '[': { CharSet cset; if (width == 0) { width = ~0; } end = string; format = BuildCharSet(&cset, format); while (*end != '\0') { offset = TclUtfToUniChar(end, &sch); if (!CharInSet(&cset, (int)sch)) { break; } end += offset; if (--width == 0) { break; } } ReleaseCharSet(&cset); if (string == end) { /* * Nothing matched the range, stop processing. */ goto done; } if (!(flags & SCAN_SUPPRESS)) { objPtr = Tcl_NewStringObj(string, end-string); Tcl_IncrRefCount(objPtr); objs[objIndex++] = objPtr; } string = end; break; } case 'c': /* * Scan a single Unicode character. */ offset = TclUtfToUniChar(string, &i); string += offset; if (!(flags & SCAN_SUPPRESS)) { TclNewIntObj(objPtr, i); Tcl_IncrRefCount(objPtr); CLANG_ASSERT(objs); objs[objIndex++] = objPtr; } break; case 'i': /* * Scan an unsigned or signed integer. */ TclNewIntObj(objPtr, 0); Tcl_IncrRefCount(objPtr); if (width == 0) { width = ~0; } if (TCL_OK != TclParseNumber(NULL, objPtr, NULL, string, width, &end, TCL_PARSE_INTEGER_ONLY | TCL_PARSE_NO_UNDERSCORE | parseFlag)) { Tcl_DecrRefCount(objPtr); if (width < 0) { if (*end == '\0') { underflow = 1; } } else { if (end == string + width) { underflow = 1; } } goto done; } string = end; if (flags & SCAN_SUPPRESS) { Tcl_DecrRefCount(objPtr); break; } if (flags & SCAN_LONGER) { if (TclGetWideIntFromObj(NULL, objPtr, &wideValue) != TCL_OK) { if (TclGetString(objPtr)[0] == '-') { wideValue = WIDE_MIN; } else { wideValue = WIDE_MAX; } } if ((flags & SCAN_UNSIGNED) && (wideValue < 0)) { mp_int big; if (mp_init_u64(&big, (Tcl_WideUInt)wideValue) != MP_OKAY) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "insufficient memory to create bignum", -1)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); return TCL_ERROR; } else { Tcl_SetBignumObj(objPtr, &big); } } else { TclSetIntObj(objPtr, wideValue); } } else if (flags & SCAN_BIG) { if (flags & SCAN_UNSIGNED) { mp_int big; int res = Tcl_GetBignumFromObj(interp, objPtr, &big); if (res == TCL_OK) { if (mp_isneg(&big)) { res = TCL_ERROR; } mp_clear(&big); } if (res == TCL_ERROR) { if (objs != NULL) { Tcl_Free(objs); } Tcl_DecrRefCount(objPtr); Tcl_SetObjResult(interp, Tcl_NewStringObj( "unsigned bignum scans are invalid", -1)); Tcl_SetErrorCode(interp, "TCL", "FORMAT", "BADUNSIGNED", (char *)NULL); return TCL_ERROR; } } } else { if (TclGetIntFromObj(NULL, objPtr, &value) != TCL_OK) { if (TclGetString(objPtr)[0] == '-') { value = INT_MIN; } else { value = INT_MAX; } } if ((flags & SCAN_UNSIGNED) && (value < 0)) { #ifdef TCL_WIDE_INT_IS_LONG mp_int big; if (mp_init_u64(&big, (unsigned long)value) != MP_OKAY) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "insufficient memory to create bignum", -1)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); return TCL_ERROR; } else { Tcl_SetBignumObj(objPtr, &big); } #else Tcl_SetWideIntObj(objPtr, (unsigned long)value); #endif } else { TclSetIntObj(objPtr, value); } } objs[objIndex++] = objPtr; break; case 'f': /* * Scan a floating point number */ TclNewDoubleObj(objPtr, 0.0); Tcl_IncrRefCount(objPtr); if (width == 0) { width = ~0; } if (TCL_OK != TclParseNumber(NULL, objPtr, NULL, string, width, &end, TCL_PARSE_DECIMAL_ONLY | TCL_PARSE_NO_WHITESPACE | TCL_PARSE_NO_UNDERSCORE)) { Tcl_DecrRefCount(objPtr); if (width < 0) { if (*end == '\0') { underflow = 1; } } else { if (end == string + width) { underflow = 1; } } goto done; } else if (flags & SCAN_SUPPRESS) { Tcl_DecrRefCount(objPtr); string = end; } else { double dvalue; if (Tcl_GetDoubleFromObj(NULL, objPtr, &dvalue) != TCL_OK) { #ifdef ACCEPT_NAN const Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(objPtr, &tclDoubleType); if (irPtr) { dvalue = irPtr->doubleValue; } else #endif { Tcl_DecrRefCount(objPtr); goto done; } } Tcl_SetDoubleObj(objPtr, dvalue); CLANG_ASSERT(objs); objs[objIndex++] = objPtr; string = end; } } nconversions++; } done: result = 0; code = TCL_OK; if (numVars) { /* * In this case, variables were specified (classic scan). */ for (i = 0; i < totalVars; i++) { if (objs[i] == NULL) { continue; } result++; /* * In case of multiple errors in setting variables, just report * the first one. */ if (Tcl_ObjSetVar2(interp, objv[i+3], NULL, objs[i], (code == TCL_OK) ? TCL_LEAVE_ERR_MSG : 0) == NULL) { code = TCL_ERROR; } Tcl_DecrRefCount(objs[i]); } } else { /* * Here no vars were specified, we want a list returned (inline scan) * We create an empty Tcl_Obj to fill missing values rather than * allocating a new Tcl_Obj every time. See test scan-bigdata-XX. */ Tcl_Obj *emptyObj = NULL; TclNewObj(objPtr); for (i = 0; code == TCL_OK && i < totalVars; i++) { if (objs[i] != NULL) { code = Tcl_ListObjAppendElement(interp, objPtr, objs[i]); Tcl_DecrRefCount(objs[i]); } else { /* * More %-specifiers than matching chars, so we just spit out * empty strings for these. */ if (!emptyObj) { TclNewObj(emptyObj); } code = Tcl_ListObjAppendElement(interp, objPtr, emptyObj); } } if (code != TCL_OK) { /* If error'ed out, free up remaining. i contains last index freed */ while (++i < totalVars) { if (objs[i] != NULL) { Tcl_DecrRefCount(objs[i]); } } Tcl_DecrRefCount(objPtr); objPtr = NULL; } } if (objs != NULL) { Tcl_Free(objs); } if (code == TCL_OK) { if (underflow && (nconversions == 0)) { if (numVars) { TclNewIntObj(objPtr, -1); } else { if (objPtr) { Tcl_SetListObj(objPtr, 0, NULL); } else { TclNewObj(objPtr); } } } else if (numVars) { TclNewIntObj(objPtr, result); } Tcl_SetObjResult(interp, objPtr); } return code; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclStrIdxTree.c0000644000175000017500000003341714726623136016106 0ustar sergeisergei/* * tclStrIdxTree.c -- * * Contains the routines for managing string index tries in Tcl. * * This code is back-ported from the tclSE engine, by Serg G. Brester. * * Copyright (c) 2016 by Sergey G. Brester aka sebres. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * * ----------------------------------------------------------------------- * * String index tries are prepaired structures used for fast greedy search of the string * (index) by unique string prefix as key. * * Index tree build for two lists together can be explained in the following datagram * * Lists: * * {Januar Februar Maerz April Mai Juni Juli August September Oktober November Dezember} * {Jnr Fbr Mrz Apr Mai Jni Jli Agt Spt Okt Nvb Dzb} * * Index-Tree: * * j 0 * ... * anuar 1 * * u 0 * a 0 * ni 6 * pril 4 * li 7 * ugust 8 * n 0 * gt 8 * r 1 * s 9 * i 6 * eptember 9 * li 7 * pt 9 * f 2 * oktober 10 * ebruar 2 * n 11 * br 2 * ovember 11 * m 0 * vb 11 * a 0 * d 12 * erz 3 * ezember 12 * i 5 * zb 12 * rz 3 * * ... * * Thereby value 0 shows pure group items (corresponding ambigous matches). * But the group may have a value if it contains only same values * (see for example group "f" above). * * StrIdxTree's are very fast, so: * build of above-mentioned tree takes about 10 microseconds. * search of string index in this tree takes fewer as 0.1 microseconds. * */ #include "tclInt.h" #include "tclStrIdxTree.h" static void StrIdxTreeObj_DupIntRepProc(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr); static void StrIdxTreeObj_FreeIntRepProc(Tcl_Obj *objPtr); static void StrIdxTreeObj_UpdateStringProc(Tcl_Obj *objPtr); static const Tcl_ObjType StrIdxTreeObjType = { "str-idx-tree", /* name */ StrIdxTreeObj_FreeIntRepProc, /* freeIntRepProc */ StrIdxTreeObj_DupIntRepProc, /* dupIntRepProc */ StrIdxTreeObj_UpdateStringProc, /* updateStringProc */ NULL, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; /* *---------------------------------------------------------------------- * * TclStrIdxTreeSearch -- * * Find largest part of string "start" in indexed tree (case sensitive). * * Also used for building of string index tree. * * Results: * Return position of UTF character in start after last equal character * and found item (with parent). * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * TclStrIdxTreeSearch( TclStrIdxTree **foundParent,/* Return value of found sub tree (used for tree build) */ TclStrIdx **foundItem, /* Return value of found item */ TclStrIdxTree *tree, /* Index tree will be browsed */ const char *start, /* UTF string to find in tree */ const char *end) /* End of string */ { TclStrIdxTree *parent = tree, *prevParent = tree; TclStrIdx *item = tree->firstPtr, *prevItem = NULL; const char *s = start, *f, *cin, *cinf, *prevf = NULL; Tcl_Size offs = 0; if (item == NULL) { goto done; } /* search in tree */ do { cinf = cin = TclGetString(item->key) + offs; f = TclUtfFindEqualNCInLwr(s, end, cin, cin + item->length - offs, &cinf); /* if something was found */ if (f > s) { /* if whole string was found */ if (f >= end) { start = f; goto done; } /* set new offset and shift start string */ offs += cinf - cin; s = f; /* if match item, go deeper as long as possible */ if (offs >= item->length && item->childTree.firstPtr) { /* save previuosly found item (if not ambigous) for * possible fallback (few greedy match) */ if (item->value != NULL) { prevf = f; prevItem = item; prevParent = parent; } parent = &item->childTree; item = item->childTree.firstPtr; continue; } /* no children - return this item and current chars found */ start = f; goto done; } item = item->nextPtr; } while (item != NULL); /* fallback (few greedy match) not ambigous (has a value) */ if (prevItem != NULL) { item = prevItem; parent = prevParent; start = prevf; } done: if (foundParent) { *foundParent = parent; } if (foundItem) { *foundItem = item; } return start; } void TclStrIdxTreeFree( TclStrIdx *tree) { while (tree != NULL) { TclStrIdx *t = tree; Tcl_DecrRefCount(tree->key); if (tree->childTree.firstPtr != NULL) { TclStrIdxTreeFree(tree->childTree.firstPtr); } tree = tree->nextPtr; Tcl_Free(t); } } /* * Several bidirectional list primitives */ static inline void TclStrIdxTreeInsertBranch( TclStrIdxTree *parent, TclStrIdx *item, TclStrIdx *child) { if (parent->firstPtr == child) { parent->firstPtr = item; } if (parent->lastPtr == child) { parent->lastPtr = item; } if ((item->nextPtr = child->nextPtr) != NULL) { item->nextPtr->prevPtr = item; child->nextPtr = NULL; } if ((item->prevPtr = child->prevPtr) != NULL) { item->prevPtr->nextPtr = item; child->prevPtr = NULL; } item->childTree.firstPtr = child; item->childTree.lastPtr = child; } static inline void TclStrIdxTreeAppend( TclStrIdxTree *parent, TclStrIdx *item) { if (parent->lastPtr != NULL) { parent->lastPtr->nextPtr = item; } item->prevPtr = parent->lastPtr; item->nextPtr = NULL; parent->lastPtr = item; if (parent->firstPtr == NULL) { parent->firstPtr = item; } } /* *---------------------------------------------------------------------- * * TclStrIdxTreeBuildFromList -- * * Build or extend string indexed tree from tcl list. If the values not * given the values of built list are indices starts with 1. Value of 0 * is thereby reserved to the ambigous values. * * Important: by multiple lists, optimal tree can be created only if list * with larger strings used firstly. * * Results: * Returns a standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclStrIdxTreeBuildFromList( TclStrIdxTree *idxTree, Tcl_Size lstc, Tcl_Obj **lstv, void **values) { Tcl_Obj **lwrv; Tcl_Size i; int ret = TCL_ERROR; void *val; const char *s, *e, *f; TclStrIdx *item; /* create lowercase reflection of the list keys */ lwrv = (Tcl_Obj **) Tcl_AttemptAlloc(sizeof(Tcl_Obj*) * lstc); if (lwrv == NULL) { return TCL_ERROR; } for (i = 0; i < lstc; i++) { lwrv[i] = Tcl_DuplicateObj(lstv[i]); Tcl_IncrRefCount(lwrv[i]); lwrv[i]->length = Tcl_UtfToLower(TclGetString(lwrv[i])); } /* build index tree of the list keys */ for (i = 0; i < lstc; i++) { TclStrIdxTree *foundParent = idxTree; e = s = TclGetString(lwrv[i]); e += lwrv[i]->length; val = values ? values[i] : INT2PTR(i+1); /* ignore empty keys (impossible to index it) */ if (lwrv[i]->length == 0) { continue; } item = NULL; if (idxTree->firstPtr != NULL) { TclStrIdx *foundItem; f = TclStrIdxTreeSearch(&foundParent, &foundItem, idxTree, s, e); /* if common prefix was found */ if (f > s) { /* ignore element if fulfilled or ambigous */ if (f == e) { continue; } /* if shortest key was found with the same value, * just replace its current key with longest key */ if (foundItem->value == val && foundItem->length <= lwrv[i]->length && foundItem->length <= (f - s) // only if found item is covered in full && foundItem->childTree.firstPtr == NULL) { TclSetObjRef(foundItem->key, lwrv[i]); foundItem->length = lwrv[i]->length; continue; } /* split tree (e. g. j->(jan,jun) + jul == j->(jan,ju->(jun,jul)) ) * but don't split by fulfilled child of found item ( ii->iii->iiii ) */ if (foundItem->length != (f - s)) { /* first split found item (insert one between parent and found + new one) */ item = (TclStrIdx *) Tcl_AttemptAlloc(sizeof(TclStrIdx)); if (item == NULL) { goto done; } TclInitObjRef(item->key, foundItem->key); item->length = f - s; /* set value or mark as ambigous if not the same value of both */ item->value = (foundItem->value == val) ? val : NULL; /* insert group item between foundParent and foundItem */ TclStrIdxTreeInsertBranch(foundParent, item, foundItem); foundParent = &item->childTree; } else { /* the new item should be added as child of found item */ foundParent = &foundItem->childTree; } } } /* append item at end of found parent */ item = (TclStrIdx *) Tcl_AttemptAlloc(sizeof(TclStrIdx)); if (item == NULL) { goto done; } item->childTree.lastPtr = item->childTree.firstPtr = NULL; TclInitObjRef(item->key, lwrv[i]); item->length = lwrv[i]->length; item->value = val; TclStrIdxTreeAppend(foundParent, item); } ret = TCL_OK; done: if (lwrv != NULL) { for (i = 0; i < lstc; i++) { Tcl_DecrRefCount(lwrv[i]); } Tcl_Free(lwrv); } if (ret != TCL_OK) { if (idxTree->firstPtr != NULL) { TclStrIdxTreeFree(idxTree->firstPtr); } } return ret; } /* Is a Tcl_Obj (of right type) holding a smart pointer link? */ static inline int IsLink( Tcl_Obj *objPtr) { Tcl_ObjInternalRep *irPtr = &objPtr->internalRep; return irPtr->twoPtrValue.ptr1 && !irPtr->twoPtrValue.ptr2; } /* Follow links (smart pointers) if present. */ static inline Tcl_Obj * FollowPossibleLink( Tcl_Obj *objPtr) { if (IsLink(objPtr)) { objPtr = (Tcl_Obj *) objPtr->internalRep.twoPtrValue.ptr1; } /* assert(!IsLink(objPtr)); */ return objPtr; } Tcl_Obj * TclStrIdxTreeNewObj(void) { Tcl_Obj *objPtr = Tcl_NewObj(); TclStrIdxTree *tree = (TclStrIdxTree *) &objPtr->internalRep.twoPtrValue; /* * This assert states that we can safely directly have a tree node as the * internal representation of a Tcl_Obj instead of needing to hang it * off the back with an extra alloc. */ TCL_CT_ASSERT(sizeof(TclStrIdxTree) <= sizeof(Tcl_ObjInternalRep)); tree->firstPtr = NULL; tree->lastPtr = NULL; objPtr->typePtr = &StrIdxTreeObjType; /* return tree root in internal representation */ return objPtr; } static void StrIdxTreeObj_DupIntRepProc( Tcl_Obj *srcPtr, Tcl_Obj *copyPtr) { /* follow links (smart pointers) */ srcPtr = FollowPossibleLink(srcPtr); /* create smart pointer to it (ptr1 != NULL, ptr2 = NULL) */ TclInitObjRef(*((Tcl_Obj **) ©Ptr->internalRep.twoPtrValue.ptr1), srcPtr); copyPtr->internalRep.twoPtrValue.ptr2 = NULL; copyPtr->typePtr = &StrIdxTreeObjType; } static void StrIdxTreeObj_FreeIntRepProc( Tcl_Obj *objPtr) { /* follow links (smart pointers) */ if (IsLink(objPtr)) { /* is a link */ TclUnsetObjRef(*((Tcl_Obj **) &objPtr->internalRep.twoPtrValue.ptr1)); } else { /* is a tree */ TclStrIdxTree *tree = (TclStrIdxTree *) &objPtr->internalRep.twoPtrValue; if (tree->firstPtr != NULL) { TclStrIdxTreeFree(tree->firstPtr); } tree->firstPtr = NULL; tree->lastPtr = NULL; } objPtr->typePtr = NULL; } static void StrIdxTreeObj_UpdateStringProc( Tcl_Obj *objPtr) { /* currently only dummy empty string possible */ objPtr->length = 0; objPtr->bytes = &tclEmptyString; } TclStrIdxTree * TclStrIdxTreeGetFromObj( Tcl_Obj *objPtr) { if (objPtr->typePtr != &StrIdxTreeObjType) { return NULL; } /* follow links (smart pointers) */ objPtr = FollowPossibleLink(objPtr); /* return tree root in internal representation */ return (TclStrIdxTree *) &objPtr->internalRep.twoPtrValue; } /* * Several debug primitives */ #ifdef TEST_STR_IDX_TREE /* currently unused, debug resp. test purposes only */ static void TclStrIdxTreePrint( Tcl_Interp *interp, TclStrIdx *tree, int offs) { Tcl_Obj *obj[2]; const char *s; TclInitObjRef(obj[0], Tcl_NewStringObj("::puts", TCL_AUTO_LENGTH)); while (tree != NULL) { s = TclGetString(tree->key) + offs; TclInitObjRef(obj[1], Tcl_ObjPrintf("%*s%.*s\t:%d", offs, "", tree->length - offs, s, tree->value)); Tcl_PutsObjCmd(NULL, interp, 2, obj); TclUnsetObjRef(obj[1]); if (tree->childTree.firstPtr != NULL) { TclStrIdxTreePrint(interp, tree->childTree.firstPtr, tree->length); } tree = tree->nextPtr; } TclUnsetObjRef(obj[0]); } int TclStrIdxTreeTestObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { const char *cs, *cin, *ret; static const char *const options[] = { "findequal", "index", "puts-index", NULL }; enum optionInd { O_FINDEQUAL, O_INDEX, O_PUTS_INDEX }; int optionIndex; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], options, "option", 0, &optionIndex) != TCL_OK) { Tcl_SetErrorCode(interp, "CLOCK", "badOption", TclGetString(objv[1]), (char *)NULL); return TCL_ERROR; } switch (optionIndex) { case O_FINDEQUAL: if (objc < 4) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } cs = TclGetString(objv[2]); cin = TclGetString(objv[3]); ret = TclUtfFindEqual( cs, cs + objv[1]->length, cin, cin + objv[2]->length); Tcl_SetObjResult(interp, Tcl_NewIntObj(ret - cs)); break; case O_INDEX: case O_PUTS_INDEX: { Tcl_Obj **lstv; Tcl_Size i, lstc; TclStrIdxTree idxTree = {NULL, NULL}; i = 1; while (++i < objc) { if (TclListObjGetElements(interp, objv[i], &lstc, &lstv) != TCL_OK) { return TCL_ERROR; } TclStrIdxTreeBuildFromList(&idxTree, lstc, lstv, NULL); } if (optionIndex == O_PUTS_INDEX) { TclStrIdxTreePrint(interp, idxTree.firstPtr, 0); } TclStrIdxTreeFree(idxTree.firstPtr); break; } } return TCL_OK; } #endif /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclStrIdxTree.h0000644000175000017500000001134714726623136016111 0ustar sergeisergei/* * tclStrIdxTree.h -- * * Declarations of string index tries and other primitives currently * back-ported from tclSE. * * Copyright (c) 2016 Serg G. Brester (aka sebres) * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef _TCLSTRIDXTREE_H #define _TCLSTRIDXTREE_H #include "tclInt.h" /* * Main structures declarations of index tree and entry */ typedef struct TclStrIdx TclStrIdx; /* * Top level structure of the tree, or first two fields of the interior * structure. * * Note that this is EXACTLY two pointers so it is the same size as the * twoPtrValue of a Tcl_ObjInternalRep. This is how the top level structure * of the tree is always allocated. (This type constraint is asserted in * TclStrIdxTreeNewObj() so it's guaranteed.) * * Also note that if firstPtr is not NULL, lastPtr must also be not NULL. * The case where firstPtr is not NULL and lastPtr is NULL is special (a * smart pointer to one of these) and is not actually a valid instance of * this structure. */ typedef struct TclStrIdxTree { TclStrIdx *firstPtr; TclStrIdx *lastPtr; } TclStrIdxTree; /* * An interior node of the tree. Always directly allocated. */ struct TclStrIdx { TclStrIdxTree childTree; TclStrIdx *nextPtr; TclStrIdx *prevPtr; Tcl_Obj *key; Tcl_Size length; void *value; }; /* *---------------------------------------------------------------------- * * TclUtfFindEqual, TclUtfFindEqualNC -- * * Find largest part of string cs in string cin (case sensitive and not). * * Results: * Return position of UTF character in cs after last equal character. * * Side effects: * None. * *---------------------------------------------------------------------- */ static inline const char * TclUtfFindEqual( const char *cs, /* UTF string to find in cin. */ const char *cse, /* End of cs */ const char *cin, /* UTF string will be browsed. */ const char *cine) /* End of cin */ { const char *ret = cs; Tcl_UniChar ch1, ch2; do { cs += TclUtfToUniChar(cs, &ch1); cin += TclUtfToUniChar(cin, &ch2); if (ch1 != ch2) { break; } } while ((ret = cs) < cse && cin < cine); return ret; } static inline const char * TclUtfFindEqualNC( const char *cs, /* UTF string to find in cin. */ const char *cse, /* End of cs */ const char *cin, /* UTF string will be browsed. */ const char *cine, /* End of cin */ const char **cinfnd) /* Return position in cin */ { const char *ret = cs; Tcl_UniChar ch1, ch2; do { cs += TclUtfToUniChar(cs, &ch1); cin += TclUtfToUniChar(cin, &ch2); if (ch1 != ch2) { ch1 = Tcl_UniCharToLower(ch1); ch2 = Tcl_UniCharToLower(ch2); if (ch1 != ch2) { break; } } *cinfnd = cin; } while ((ret = cs) < cse && cin < cine); return ret; } static inline const char * TclUtfFindEqualNCInLwr( const char *cs, /* UTF string (in anycase) to find in cin. */ const char *cse, /* End of cs */ const char *cin, /* UTF string (in lowercase) will be browsed. */ const char *cine, /* End of cin */ const char **cinfnd) /* Return position in cin */ { const char *ret = cs; Tcl_UniChar ch1, ch2; do { cs += TclUtfToUniChar(cs, &ch1); cin += TclUtfToUniChar(cin, &ch2); if (ch1 != ch2) { ch1 = Tcl_UniCharToLower(ch1); if (ch1 != ch2) { break; } } *cinfnd = cin; } while ((ret = cs) < cse && cin < cine); return ret; } /* * Primitives to safe set, reset and free references. */ #define TclUnsetObjRef(obj) \ do { \ if (obj != NULL) { \ Tcl_DecrRefCount(obj); \ obj = NULL; \ } \ } while (0) #define TclInitObjRef(obj, val) \ do { \ obj = (val); \ if (obj) { \ Tcl_IncrRefCount(obj); \ } \ } while (0) #define TclSetObjRef(obj, val) \ do { \ Tcl_Obj *nval = (val); \ if (obj != nval) { \ Tcl_Obj *prev = obj; \ TclInitObjRef(obj, nval); \ if (prev != NULL) { \ Tcl_DecrRefCount(prev); \ } \ } \ } while (0) /* * Prototypes of module functions. */ MODULE_SCOPE const char*TclStrIdxTreeSearch(TclStrIdxTree **foundParent, TclStrIdx **foundItem, TclStrIdxTree *tree, const char *start, const char *end); MODULE_SCOPE int TclStrIdxTreeBuildFromList(TclStrIdxTree *idxTree, Tcl_Size lstc, Tcl_Obj **lstv, void **values); MODULE_SCOPE Tcl_Obj * TclStrIdxTreeNewObj(void); MODULE_SCOPE TclStrIdxTree*TclStrIdxTreeGetFromObj(Tcl_Obj *objPtr); #ifdef TEST_STR_IDX_TREE /* currently unused, debug resp. test purposes only */ MODULE_SCOPE Tcl_ObjCmdProc TclStrIdxTreeTestObjCmd; #endif #endif /* _TCLSTRIDXTREE_H */ tcl9.0.1/generic/tclStrToD.c0000644000175000017500000042342014726623136015225 0ustar sergeisergei/* * tclStrToD.c -- * * This file contains a collection of procedures for managing conversions * to/from floating-point in Tcl. They include TclParseNumber, which * parses numbers from strings; TclDoubleDigits, which formats numbers * into strings of digits, and procedures for interconversion among * 'double' and 'mp_int' types. * * Copyright © 2005 Kevin B. Kenny. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclTomMath.h" #include #include #ifdef _WIN32 #define copysign _copysign #endif #ifndef PRIx64 # define PRIx64 TCL_LL_MODIFIER "x" #endif /* * This code supports (at least hypothetically), IBM, Cray, VAX and IEEE-754 * floating point; of these, only IEEE-754 can represent NaN. IEEE-754 can be * uniquely determined by radix and by the widths of significand and exponent. */ #if (FLT_RADIX == 2) && (DBL_MANT_DIG == 53) && (DBL_MAX_EXP == 1024) # define IEEE_FLOATING_POINT #endif /* * Rounding controls. (Thanks a lot, Intel!) */ #ifdef __i386 /* * gcc on x86 needs access to rounding controls, because of a questionable * feature where it retains intermediate results as IEEE 'long double' values * somewhat unpredictably. It is tempting to include fpu_control.h, but that * file exists only on Linux; it is missing on Cygwin and MinGW. Most gcc-isms * and ix86-isms are factored out here. */ # if defined(__GNUC__) typedef unsigned int fpu_control_t __attribute__ ((__mode__ (__HI__))); # define _FPU_GETCW(cw) __asm__ __volatile__ ("fnstcw %0" : "=m" (*&cw)) # define _FPU_SETCW(cw) __asm__ __volatile__ ("fldcw %0" : : "m" (*&cw)) # define FPU_IEEE_ROUNDING 0x027F # define ADJUST_FPU_CONTROL_WORD # define TCL_IEEE_DOUBLE_ROUNDING_DECL \ fpu_control_t roundTo53Bits = FPU_IEEE_ROUNDING; \ fpu_control_t oldRoundingMode; # define TCL_IEEE_DOUBLE_ROUNDING \ _FPU_GETCW(oldRoundingMode); \ _FPU_SETCW(roundTo53Bits) # define TCL_DEFAULT_DOUBLE_ROUNDING \ _FPU_SETCW(oldRoundingMode) /* * Sun ProC needs sunmath for rounding control on x86 like gcc above. */ # elif defined(__sun) # include # define TCL_IEEE_DOUBLE_ROUNDING_DECL # define TCL_IEEE_DOUBLE_ROUNDING \ ieee_flags("set","precision","double",NULL) # define TCL_DEFAULT_DOUBLE_ROUNDING \ ieee_flags("clear","precision",NULL,NULL) # endif #endif /* * Other platforms are assumed to always operate in full IEEE mode, so we make * the macros to go in and out of that mode do nothing. */ #ifndef TCL_IEEE_DOUBLE_ROUNDING /* !__i386 || (!__GNUC__ && !__sun) */ # define TCL_IEEE_DOUBLE_ROUNDING_DECL # define TCL_IEEE_DOUBLE_ROUNDING ((void) 0) # define TCL_DEFAULT_DOUBLE_ROUNDING ((void) 0) #endif /* * MIPS floating-point units need special settings in control registers to use * gradual underflow as we expect. This fix is for the MIPSpro compiler. */ #if defined(__sgi) && defined(_COMPILER_VERSION) #include #endif /* * HP's PA_RISC architecture uses 7ff4000000000000 to represent a quiet NaN. * Everyone else uses 7ff8000000000000. (Why, HP, why?) */ #ifdef __hppa # define NAN_START 0x7FF4 # define NAN_MASK (((Tcl_WideUInt) 1) << 50) #else # define NAN_START 0x7FF8 # define NAN_MASK (((Tcl_WideUInt) 1) << 51) #endif /* * Constants used by this file (most of which are only ever calculated at * runtime). */ /* Magic constants */ #define LOG10_2 0.3010299956639812 #define TWO_OVER_3LOG10 0.28952965460216784 #define LOG10_3HALVES_PLUS_FUDGE 0.1760912590558 /* * Definitions of the parts of an IEEE754-format floating point number. */ #define SIGN_BIT 0x80000000 /* Mask for the sign bit in the first word of * a double. */ #define EXP_MASK 0x7FF00000 /* Mask for the exponent field in the first * word of a double. */ #define EXP_SHIFT 20 /* Shift count to make the exponent an * integer. */ #define HIDDEN_BIT (((Tcl_WideUInt) 0x00100000) << 32) /* Hidden 1 bit for the significand. */ #define HI_ORDER_SIG_MASK 0x000FFFFF /* Mask for the high-order part of the * significand in the first word of a * double. */ #define SIG_MASK (((Tcl_WideUInt) HI_ORDER_SIG_MASK << 32) \ | 0xFFFFFFFF) /* Mask for the 52-bit significand. */ #define FP_PRECISION 53 /* Number of bits of significand plus the * hidden bit. */ #define EXPONENT_BIAS 0x3FF /* Bias of the exponent 0. */ /* * Derived quantities. */ #define TEN_PMAX 22 /* floor(FP_PRECISION*log(2)/log(5)) */ #define QUICK_MAX 14 /* floor((FP_PRECISION-1)*log(2)/log(10))-1 */ #define BLETCH 0x10 /* Highest power of two that is greater than * DBL_MAX_10_EXP, divided by 16. */ #define DIGIT_GROUP 8 /* floor(MP_DIGIT_BIT*log(2)/log(10)) */ /* * Union used to dismantle floating point numbers. */ typedef union Double { struct { #ifdef WORDS_BIGENDIAN int word0; int word1; #else int word1; int word0; #endif } w; double d; Tcl_WideUInt q; } Double; static int maxpow10_wide; /* The powers of ten that can be represented * exactly as wide integers. */ static Tcl_WideUInt *pow10_wide; #define MAXPOW 22 static double pow10vals[MAXPOW+1]; /* The powers of ten that can be represented * exactly as IEEE754 doubles. */ static int mmaxpow; /* Largest power of ten that can be * represented exactly in a 'double'. */ static int log10_DIGIT_MAX; /* The number of decimal digits that fit in an * mp_digit. */ static int log2FLT_RADIX; /* Logarithm of the floating point radix. */ static int mantBits; /* Number of bits in a double's significand */ static mp_int pow5[9]; /* Table of powers of 5**(2**n), up to * 5**256 */ static double tiny = 0.0; /* The smallest representable double. */ static int maxDigits; /* The maximum number of digits to the left of * the decimal point of a double. */ static int minDigits; /* The maximum number of digits to the right * of the decimal point in a double. */ static const double pow_10_2_n[] = { /* Inexact higher powers of ten. */ 1.0, 100.0, 10000.0, 1.0e+8, 1.0e+16, 1.0e+32, 1.0e+64, 1.0e+128, 1.0e+256 }; static int n770_fp; /* Flag is 1 on Nokia N770 floating point. * Nokia's floating point has the words * reversed: if big-endian is 7654 3210, * and little-endian is 0123 4567, * then Nokia's FP is 4567 0123; * little-endian within the 32-bit words but * big-endian between them. */ /* * Table of powers of 5 that are small enough to fit in an mp_digit. */ static const mp_digit dpow5[13] = { 1, 5, 25, 125, 625, 3125, 15625, 78125, 390625, 1953125, 9765625, 48828125, 244140625 }; /* * Table of powers: pow5_13[n] = 5**(13*2**(n+1)) */ static mp_int pow5_13[5]; /* Table of powers: 5**13, 5**26, 5**52, * 5**104, 5**208 */ static const double tens[] = { 1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 }; static const int itens [] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; static const double bigtens[] = { 1e016, 1e032, 1e064, 1e128, 1e256 }; #define N_BIGTENS 5 static const int log2pow5[27] = { 01, 3, 5, 7, 10, 12, 14, 17, 19, 21, 24, 26, 28, 31, 33, 35, 38, 40, 42, 45, 47, 49, 52, 54, 56, 59, 61 }; #define N_LOG2POW5 27 static const Tcl_WideUInt wuipow5[] = { (Tcl_WideUInt) 1U, /* 5**0 */ (Tcl_WideUInt) 5U, (Tcl_WideUInt) 25U, (Tcl_WideUInt) 125U, (Tcl_WideUInt) 625U, (Tcl_WideUInt) 3125U, /* 5**5 */ (Tcl_WideUInt) 3125U*5U, (Tcl_WideUInt) 3125U*25U, (Tcl_WideUInt) 3125U*125U, (Tcl_WideUInt) 3125U*625U, (Tcl_WideUInt) 3125U*3125U, /* 5**10 */ (Tcl_WideUInt) 3125U*3125U*5U, (Tcl_WideUInt) 3125U*3125U*25U, (Tcl_WideUInt) 3125U*3125U*125U, (Tcl_WideUInt) 3125U*3125U*625U, (Tcl_WideUInt) 3125U*3125U*3125U, /* 5**15 */ (Tcl_WideUInt) 3125U*3125U*3125U*5U, (Tcl_WideUInt) 3125U*3125U*3125U*25U, (Tcl_WideUInt) 3125U*3125U*3125U*125U, (Tcl_WideUInt) 3125U*3125U*3125U*625U, (Tcl_WideUInt) 3125U*3125U*3125U*3125U, /* 5**20 */ (Tcl_WideUInt) 3125U*3125U*3125U*3125U*5U, (Tcl_WideUInt) 3125U*3125U*3125U*3125U*25U, (Tcl_WideUInt) 3125U*3125U*3125U*3125U*125U, (Tcl_WideUInt) 3125U*3125U*3125U*3125U*625U, (Tcl_WideUInt) 3125U*3125U*3125U*3125U*3125U, /* 5**25 */ (Tcl_WideUInt) 3125U*3125U*3125U*3125U*3125U*5U, (Tcl_WideUInt) 3125U*3125U*3125U*3125U*3125U*25U /* 5**27 */ }; /* * Static functions defined in this file. */ static int AccumulateDecimalDigit(unsigned, int, Tcl_WideUInt *, mp_int *, int); static double MakeHighPrecisionDouble(int signum, mp_int *significand, int nSigDigs, long exponent); static double MakeLowPrecisionDouble(int signum, Tcl_WideUInt significand, int nSigDigs, long exponent); #ifdef IEEE_FLOATING_POINT static double MakeNaN(int signum, Tcl_WideUInt tag); #endif static double RefineApproximation(double approx, mp_int *exactSignificand, int exponent); static mp_err MulPow5(mp_int *, unsigned, mp_int *) MP_WUR; static int NormalizeRightward(Tcl_WideUInt *); static int RequiredPrecision(Tcl_WideUInt); static void DoubleToExpAndSig(double, Tcl_WideUInt *, int *, int *); static void TakeAbsoluteValue(Double *, int *); static char * FormatInfAndNaN(Double *, int *, char **); static char * FormatZero(int *, char **); static int ApproximateLog10(Tcl_WideUInt, int, int); static int BetterLog10(double, int, int *); static void ComputeScale(int, int, int *, int *, int *, int *); static void SetPrecisionLimits(int, int, int *, int *, int *, int *); static char * BumpUp(char *, char *, int *); static int AdjustRange(double *, int); static char * ShorteningQuickFormat(double, int, int, double, char *, int *); static char * StrictQuickFormat(double, int, int, double, char *, int *); static char * QuickConversion(double, int, int, int, int, int, int, int *, char **); static void CastOutPowersOf2(int *, int *, int *); static char * ShorteningInt64Conversion(Double *, Tcl_WideUInt, int, int, int, int, int, int, int, int, int, int, int, int *, char **); static char * StrictInt64Conversion(Tcl_WideUInt, int, int, int, int, int, int, int, int, int *, char **); static int ShouldBankerRoundUpPowD(mp_int *, int, int); static int ShouldBankerRoundUpToNextPowD(mp_int *, mp_int *, int, int, mp_int *); static char * ShorteningBignumConversionPowD(Double *dPtr, Tcl_WideUInt bw, int b2, int b5, int m2plus, int m2minus, int m5, int sd, int k, int len, int ilim, int ilim1, int *decpt, char **endPtr); static char * StrictBignumConversionPowD( Tcl_WideUInt bw, int b2, int b5, int sd, int k, int len, int ilim, int ilim1, int *decpt, char **endPtr); static int ShouldBankerRoundUp(mp_int *, mp_int *, int); static int ShouldBankerRoundUpToNext(mp_int *, mp_int *, mp_int *, int); static char * ShorteningBignumConversion(Double *dPtr, Tcl_WideUInt bw, int b2, int m2plus, int m2minus, int s2, int s5, int k, int len, int ilim, int ilim1, int *decpt, char **endPtr); static char * StrictBignumConversion( Tcl_WideUInt bw, int b2, int s2, int s5, int k, int len, int ilim, int ilim1, int *decpt, char **endPtr); static double BignumToBiasedFrExp(const mp_int *big, int *machexp); static double Pow10TimesFrExp(int exponent, double fraction, int *machexp); static double SafeLdExp(double fraction, int exponent); #ifdef IEEE_FLOATING_POINT static Tcl_WideUInt Nokia770Twiddle(Tcl_WideUInt w); #endif /* *---------------------------------------------------------------------- * * TclParseNumber -- * * Scans bytes, interpreted as characters in Tcl's internal encoding, and * parses the longest prefix that is the string representation of a * number in a format recognized by Tcl. * * The arguments bytes, numBytes, and objPtr are the inputs which * determine the string to be parsed. If bytes is non-NULL, it points to * the first byte to be scanned. If bytes is NULL, then objPtr must be * non-NULL, and the string representation of objPtr will be scanned * (generated first, if necessary). The numBytes argument determines the * number of bytes to be scanned. If numBytes is TCL_INDEX_NONE, the first NUL * byte encountered will terminate the scan. Otherwise, * no more than numBytes bytes will be scanned. * * The argument flags is an input that controls the numeric formats * recognized by the parser. The flag bits are: * * - TCL_PARSE_INTEGER_ONLY: accept only integer values; reject * strings that denote floating point values (or accept only the * leading portion of them that are integer values). * - TCL_PARSE_SCAN_PREFIXES: ignore the prefixes 0b and 0o that are * not part of the [scan] command's vocabulary. Use only in * combination with TCL_PARSE_INTEGER_ONLY. * - TCL_PARSE_BINARY_ONLY: parse only in the binary format, whether * or not a prefix is present that would lead to binary parsing. * Use only in combination with TCL_PARSE_INTEGER_ONLY. * - TCL_PARSE_OCTAL_ONLY: parse only in the octal format, whether * or not a prefix is present that would lead to octal parsing. * Use only in combination with TCL_PARSE_INTEGER_ONLY. * - TCL_PARSE_HEXADECIMAL_ONLY: parse only in the hexadecimal format, * whether or not a prefix is present that would lead to * hexadecimal parsing. Use only in combination with * TCL_PARSE_INTEGER_ONLY. * - TCL_PARSE_DECIMAL_ONLY: parse only in the decimal format, no * matter whether a 0 prefix would normally force a different * base. * - TCL_PARSE_NO_WHITESPACE: reject any leading/trailing whitespace * * The arguments interp and expected are inputs that control error * message generation. If interp is NULL, no error message will be * generated. If interp is non-NULL, then expected must also be non-NULL. * When TCL_ERROR is returned, an error message will be left in the * result of interp, and the expected argument will appear in the error * message as the thing TclParseNumber expected, but failed to find in * the string. * * The arguments objPtr and endPtrPtr as well as the return code are the * outputs. * * When the parser cannot find any prefix of the string that matches a * format it is looking for, TCL_ERROR is returned and an error message * may be generated and returned as described above. The contents of * objPtr will not be changed. If endPtrPtr is non-NULL, a pointer to the * character in the string that terminated the scan will be written to * *endPtrPtr. * * When the parser determines that the entire string matches a format it * is looking for, TCL_OK is returned, and if objPtr is non-NULL, then * the internal rep and Tcl_ObjType of objPtr are set to the "canonical" * numeric value that matches the scanned string. If endPtrPtr is not * NULL, a pointer to the end of the string will be written to *endPtrPtr * (that is, either bytes+numBytes or a pointer to a terminating NUL * byte). * * When the parser determines that a partial string matches a format it * is looking for, the value of endPtrPtr determines what happens: * * - If endPtrPtr is NULL, then TCL_ERROR is returned, with error message * generation as above. * * - If endPtrPtr is non-NULL, then TCL_OK is returned and objPtr * internals are set as above. Also, a pointer to the first * character following the parsed numeric string is written to * *endPtrPtr. * * In some cases where the string being scanned is the string rep of * objPtr, this routine can leave objPtr in an inconsistent state where * its string rep and its internal rep do not agree. In these cases the * internal rep will be in agreement with only some substring of the * string rep. This might happen if the caller passes in a non-NULL bytes * value that points somewhere into the string rep. It might happen if * the caller passes in a numBytes value that limits the scan to only a * prefix of the string rep. Or it might happen if a non-NULL value of * endPtrPtr permits a TCL_OK return from only a partial string match. It * is the responsibility of the caller to detect and correct such * inconsistencies when they can and do arise. * * Results: * Returns a standard Tcl result. * * Side effects: * The string representaton of objPtr may be generated. * * The internal representation and Tcl_ObjType of objPtr may be changed. * This may involve allocation and/or freeing of memory. * *---------------------------------------------------------------------- */ int TclParseNumber( Tcl_Interp *interp, /* Used for error reporting. May be NULL. */ Tcl_Obj *objPtr, /* Object to receive the internal rep. */ const char *expected, /* Description of the type of number the * caller expects to be able to parse * ("integer", "boolean value", etc.). */ const char *bytes, /* Pointer to the start of the string to * scan. */ Tcl_Size numBytes, /* Maximum number of bytes to scan, see * above. */ const char **endPtrPtr, /* Place to store pointer to the character * that terminated the scan. */ int flags) /* Flags governing the parse. */ { enum State { INITIAL, SIGNUM, ZERO, ZERO_X, ZERO_O, ZERO_B, ZERO_D, BINARY, HEXADECIMAL, OCTAL, DECIMAL, LEADING_RADIX_POINT, FRACTION, EXPONENT_START, EXPONENT_SIGNUM, EXPONENT, sI, sIN, sINF, sINFI, sINFIN, sINFINI, sINFINIT, sINFINITY #ifdef IEEE_FLOATING_POINT , sN, sNA, sNAN, sNANPAREN, sNANHEX, sNANFINISH #endif } state = INITIAL; enum State acceptState = INITIAL; int signum = 0; /* Sign of the number being parsed. */ Tcl_WideUInt significandWide = 0; /* Significand of the number being parsed (if * no overflow). */ mp_int significandBig; /* Significand of the number being parsed (if * it overflows significandWide). */ int significandOverflow = 0;/* Flag==1 iff significandBig is used. */ Tcl_WideUInt octalSignificandWide = 0; /* Significand of an octal number; needed * because we don't know whether a number with * a leading zero is octal or decimal until * we've scanned forward to a '.' or 'e'. */ mp_int octalSignificandBig; /* Significand of octal number once * octalSignificandWide overflows. */ int octalSignificandOverflow = 0; /* Flag==1 if octalSignificandBig is used. */ int numSigDigs = 0; /* Number of significant digits in the decimal * significand. */ int numTrailZeros = 0; /* Number of trailing zeroes at the current * point in the parse. */ int numDigitsAfterDp = 0; /* Number of digits scanned after the decimal * point. */ int exponentSignum = 0; /* Signum of the exponent of a floating point * number. */ long exponent = 0; /* Exponent of a floating point number. */ const char *p; /* Pointer to next character to scan. */ Tcl_Size len; /* Number of characters remaining after p. */ const char *acceptPoint; /* Pointer to position after last character in * an acceptable number. */ Tcl_Size acceptLen; /* Number of characters following that * point. */ int status = TCL_OK; /* Status to return to caller. */ char d = 0; /* Last hexadecimal digit scanned; initialized * to avoid a compiler warning. */ int shift = 0; /* Amount to shift when accumulating binary */ mp_err err = MP_OKAY; #define MOST_BITS (UWIDE_MAX >> 1) /* * Initialize bytes to start of the object's string rep if the caller * didn't pass anything else. */ if (bytes == NULL) { if (interp == NULL && endPtrPtr == NULL) { if (TclHasInternalRep(objPtr, &tclDictType)) { /* A dict can never be a (single) number */ return TCL_ERROR; } if (TclHasInternalRep(objPtr, &tclListType)) { Tcl_Size length; /* A list can only be a (single) number if its length == 1 */ TclListObjLength(NULL, objPtr, &length); if (length != 1) { return TCL_ERROR; } } } bytes = TclGetString(objPtr); } p = bytes; len = numBytes; acceptPoint = p; acceptLen = len; while (1) { char c = len ? *p : '\0'; /* * Filter out Numeric Whitespace. Expects: * * ::digit:: '_' ::digit:: * * Verify current '_' is ok, then move on to next character, * otherwise follow through on to error. */ if (c == '_' && !(flags & TCL_PARSE_NO_UNDERSCORE)) { const char *before, *after; if (p==bytes) { /* Not allowed at beginning */ goto endgame; } /* * span multiple numeric whitespace * V * example: 5___6 */ for (before = (p - 1); (before && *before == '_'); before = (before > p ? (before - 1) : NULL)); for (after = (p + 1); (after && *after && *after == '_'); after = (*after && *after == '_') ? (after + 1) : NULL); switch (state) { case ZERO_B: case BINARY: if ((before && (*before != '0' && *before != '1')) || (after && (*after != '0' && *after != '1'))) { /* Not a valid digit */ goto endgame; } break; case ZERO_O: case OCTAL: if (((before && (*before < '0' || '7' < *before))) || ((after && (*after < '0' || '7' < *after)))) { goto endgame; } break; case FRACTION: case ZERO: case ZERO_D: case DECIMAL: case LEADING_RADIX_POINT: case EXPONENT_START: case EXPONENT_SIGNUM: case EXPONENT: if ((!before || isdigit(UCHAR(*before))) && (!after || isdigit(UCHAR(*after)))) { break; } if (after && *after=='(') { /* could be function */ goto continue_num; } goto endgame; case ZERO_X: case HEXADECIMAL: if ( (!before || isxdigit(UCHAR(*before))) && (!after || isxdigit(UCHAR(*after)))) { break; } goto endgame; default: /* * Not whitespace, but could be legal for other reasons. * Continue number processing for current character. */ goto continue_num; } /* Valid whitespace found, move on to the next character */ goto next; } continue_num: switch (state) { case INITIAL: /* * Initial state. Acceptable characters are +, -, digits, period, * I, N, and whitespace. */ if (TclIsSpaceProcM(c)) { if (flags & TCL_PARSE_NO_WHITESPACE) { goto endgame; } break; } else if (c == '+') { state = SIGNUM; break; } else if (c == '-') { signum = 1; state = SIGNUM; break; } /* FALLTHROUGH */ case SIGNUM: /* * Scanned a leading + or -. Acceptable characters are digits, * period, I, and N. */ if (c == '0') { if (flags & TCL_PARSE_DECIMAL_ONLY) { state = DECIMAL; } else { state = ZERO; } break; } else if (flags & TCL_PARSE_HEXADECIMAL_ONLY) { goto zerox; } else if (flags & TCL_PARSE_BINARY_ONLY) { goto zerob; } else if (flags & TCL_PARSE_OCTAL_ONLY) { goto zeroo; } else if (isdigit(UCHAR(c))) { significandWide = c - '0'; numSigDigs = 1; state = DECIMAL; break; } else if (flags & TCL_PARSE_INTEGER_ONLY) { goto endgame; } else if (c == '.') { state = LEADING_RADIX_POINT; break; } else if (c == 'I' || c == 'i') { state = sI; break; #ifdef IEEE_FLOATING_POINT } else if (c == 'N' || c == 'n') { state = sN; break; #endif } goto endgame; case ZERO: /* * Scanned a leading zero (perhaps with a + or -). Acceptable * inputs are digits, period, X, b, and E. If 8 or 9 is * encountered, the number can't be octal. This state and the * OCTAL state differ only in whether they recognize 'X' and 'b'. */ acceptState = state; acceptPoint = p; acceptLen = len; if (c == 'x' || c == 'X') { if (flags & (TCL_PARSE_OCTAL_ONLY|TCL_PARSE_BINARY_ONLY)) { goto endgame; } state = ZERO_X; break; } if (flags & TCL_PARSE_HEXADECIMAL_ONLY) { goto zerox; } if (flags & TCL_PARSE_SCAN_PREFIXES) { goto zeroo; } if (c == 'b' || c == 'B') { if ((flags & TCL_PARSE_OCTAL_ONLY)) { goto endgame; } state = ZERO_B; break; } if (flags & TCL_PARSE_BINARY_ONLY) { goto zerob; } if (c == 'o' || c == 'O') { state = ZERO_O; break; } if (c == 'd' || c == 'D') { state = ZERO_D; break; } goto decimal; case OCTAL: /* * Scanned an optional + or -, followed by a string of octal * digits. Acceptable inputs are more digits, period, or E. If 8 * or 9 is encountered, commit to floating point. */ acceptState = state; acceptPoint = p; acceptLen = len; /* FALLTHROUGH */ case ZERO_O: zeroo: if (c == '0') { numTrailZeros++; state = OCTAL; break; } else if (c >= '1' && c <= '7') { if (objPtr != NULL) { shift = 3 * (numTrailZeros + 1); significandOverflow = AccumulateDecimalDigit( (unsigned)(c-'0'), numTrailZeros, &significandWide, &significandBig, significandOverflow); if (!octalSignificandOverflow) { /* * Shifting by as many or more bits than are in the * value being shifted is undefined behavior. Check * for too large shifts first. */ if ((octalSignificandWide != 0) && (((size_t)shift >= CHAR_BIT*sizeof(Tcl_WideUInt)) || (octalSignificandWide > (UWIDE_MAX >> shift)))) { octalSignificandOverflow = 1; err = mp_init_u64(&octalSignificandBig, octalSignificandWide); } } if (!octalSignificandOverflow) { /* * When the significand is 0, it is possible for the * amount to be shifted to equal or exceed the width * of the significand. Do not shift when the * significand is 0 to avoid undefined behavior. */ if (octalSignificandWide != 0) { octalSignificandWide <<= shift; } octalSignificandWide += c - '0'; } else { if (err == MP_OKAY) { err = mp_mul_2d(&octalSignificandBig, shift, &octalSignificandBig); } if (err == MP_OKAY) { err = mp_add_d(&octalSignificandBig, (mp_digit)(c - '0'), &octalSignificandBig); } } if (err != MP_OKAY) { return TCL_ERROR; } } if (numSigDigs != 0) { numSigDigs += numTrailZeros+1; } else { numSigDigs = 1; } numTrailZeros = 0; state = OCTAL; break; } goto endgame; /* * Scanned 0x. If state is HEXADECIMAL, scanned at least one * character following the 0x. The only acceptable inputs are * hexadecimal digits. */ case HEXADECIMAL: acceptState = state; acceptPoint = p; acceptLen = len; /* FALLTHROUGH */ case ZERO_X: zerox: if (c == '0') { numTrailZeros++; state = HEXADECIMAL; break; } else if (isdigit(UCHAR(c))) { d = (c-'0'); } else if (c >= 'A' && c <= 'F') { d = (c-'A'+10); } else if (c >= 'a' && c <= 'f') { d = (c-'a'+10); } else { goto endgame; } if (objPtr != NULL) { shift = 4 * (numTrailZeros + 1); if (!significandOverflow) { /* * Shifting by as many or more bits than are in the * value being shifted is undefined behavior. Check * for too large shifts first. */ if (significandWide != 0 && ((size_t)shift >= CHAR_BIT*sizeof(Tcl_WideUInt) || significandWide > (UWIDE_MAX >> shift))) { significandOverflow = 1; err = mp_init_u64(&significandBig, significandWide); } } if (!significandOverflow) { /* * When the significand is 0, it is possible for the * amount to be shifted to equal or exceed the width * of the significand. Do not shift when the * significand is 0 to avoid undefined behavior. */ if (significandWide != 0) { significandWide <<= shift; } significandWide += d; } else if (err == MP_OKAY) { err = mp_mul_2d(&significandBig, shift, &significandBig); if (err == MP_OKAY) { err = mp_add_d(&significandBig, (mp_digit) d, &significandBig); } } } if (err != MP_OKAY) { return TCL_ERROR; } numTrailZeros = 0; state = HEXADECIMAL; break; case BINARY: acceptState = state; acceptPoint = p; acceptLen = len; /* FALLTHRU */ case ZERO_B: zerob: if (c == '0') { numTrailZeros++; state = BINARY; break; } else if (c != '1') { goto endgame; } if (objPtr != NULL) { shift = numTrailZeros + 1; if (!significandOverflow) { /* * Shifting by as many or more bits than are in the * value being shifted is undefined behavior. Check * for too large shifts first. */ if (significandWide != 0 && ((size_t)shift >= CHAR_BIT*sizeof(Tcl_WideUInt) || significandWide > (UWIDE_MAX >> shift))) { significandOverflow = 1; err = mp_init_u64(&significandBig, significandWide); } } if (!significandOverflow) { /* * When the significand is 0, it is possible for the * amount to be shifted to equal or exceed the width * of the significand. Do not shift when the * significand is 0 to avoid undefined behavior. */ if (significandWide != 0) { significandWide <<= shift; } significandWide += 1; } else if (err == MP_OKAY) { err = mp_mul_2d(&significandBig, shift, &significandBig); if (err == MP_OKAY) { err = mp_add_d(&significandBig, (mp_digit) 1, &significandBig); } } } if (err != MP_OKAY) { return TCL_ERROR; } numTrailZeros = 0; state = BINARY; break; case ZERO_D: if (c == '0') { numTrailZeros++; } else if ( ! isdigit(UCHAR(c))) { goto endgame; } state = DECIMAL; flags |= TCL_PARSE_INTEGER_ONLY; /* FALLTHROUGH */ case DECIMAL: /* * Scanned an optional + or - followed by a string of decimal * digits. */ decimal: acceptState = state; acceptPoint = p; acceptLen = len; if (c == '0') { numTrailZeros++; state = DECIMAL; break; } else if (isdigit(UCHAR(c))) { if (objPtr != NULL) { significandOverflow = AccumulateDecimalDigit( (unsigned)(c - '0'), numTrailZeros, &significandWide, &significandBig, significandOverflow); } numSigDigs += numTrailZeros+1; numTrailZeros = 0; state = DECIMAL; break; } else if (flags & TCL_PARSE_INTEGER_ONLY) { goto endgame; } else if (c == '.') { state = FRACTION; break; } else if (c == 'E' || c == 'e') { state = EXPONENT_START; break; } goto endgame; /* * Found a decimal point. If no digits have yet been scanned, E is * not allowed; otherwise, it introduces the exponent. If at least * one digit has been found, we have a possible complete number. */ case FRACTION: acceptState = state; acceptPoint = p; acceptLen = len; if (c == 'E' || c=='e') { state = EXPONENT_START; break; } /* FALLTHROUGH */ case LEADING_RADIX_POINT: if (c == '0') { numDigitsAfterDp++; numTrailZeros++; state = FRACTION; break; } else if (isdigit(UCHAR(c))) { numDigitsAfterDp++; if (objPtr != NULL) { significandOverflow = AccumulateDecimalDigit( (unsigned)(c-'0'), numTrailZeros, &significandWide, &significandBig, significandOverflow); } if (numSigDigs != 0) { numSigDigs += numTrailZeros+1; } else { numSigDigs = 1; } numTrailZeros = 0; state = FRACTION; break; } goto endgame; case EXPONENT_START: /* * Scanned the E at the start of an exponent. Make sure a legal * character follows before using the C library strtol routine, * which allows whitespace. */ if (c == '+') { state = EXPONENT_SIGNUM; break; } else if (c == '-') { exponentSignum = 1; state = EXPONENT_SIGNUM; break; } /* FALLTHROUGH */ case EXPONENT_SIGNUM: /* * Found the E at the start of the exponent, followed by a sign * character. */ if (isdigit(UCHAR(c))) { exponent = c - '0'; state = EXPONENT; break; } goto endgame; case EXPONENT: /* * Found an exponent with at least one digit. Accumulate it, * making sure to hard-pin it to LONG_MAX on overflow. */ acceptState = state; acceptPoint = p; acceptLen = len; if (isdigit(UCHAR(c))) { if (exponent < (LONG_MAX - 9) / 10) { exponent = 10 * exponent + (c - '0'); } else { exponent = LONG_MAX; } state = EXPONENT; break; } goto endgame; /* * Parse out INFINITY by simply spelling it out. INF is accepted * as an abbreviation; other prefices are not. */ case sI: if (c == 'n' || c == 'N') { state = sIN; break; } goto endgame; case sIN: if (c == 'f' || c == 'F') { state = sINF; break; } goto endgame; case sINF: acceptState = state; acceptPoint = p; acceptLen = len; if (c == 'i' || c == 'I') { state = sINFI; break; } goto endgame; case sINFI: if (c == 'n' || c == 'N') { state = sINFIN; break; } goto endgame; case sINFIN: if (c == 'i' || c == 'I') { state = sINFINI; break; } goto endgame; case sINFINI: if (c == 't' || c == 'T') { state = sINFINIT; break; } goto endgame; case sINFINIT: if (c == 'y' || c == 'Y') { state = sINFINITY; break; } goto endgame; /* * Parse NaN's. */ #ifdef IEEE_FLOATING_POINT case sN: if (c == 'a' || c == 'A') { state = sNA; break; } goto endgame; case sNA: if (c == 'n' || c == 'N') { state = sNAN; break; } goto endgame; case sNAN: acceptState = state; acceptPoint = p; acceptLen = len; if (c == '(') { state = sNANPAREN; break; } goto endgame; /* * Parse NaN(hexdigits) */ case sNANHEX: if (c == ')') { state = sNANFINISH; break; } /* FALLTHROUGH */ case sNANPAREN: if (TclIsSpaceProcM(c)) { break; } if (numSigDigs < 13) { if (c >= '0' && c <= '9') { d = c - '0'; } else if (c >= 'a' && c <= 'f') { d = 10 + c - 'a'; } else if (c >= 'A' && c <= 'F') { d = 10 + c - 'A'; } else { goto endgame; } numSigDigs++; significandWide = (significandWide << 4) + d; state = sNANHEX; break; } goto endgame; case sNANFINISH: #endif case sINFINITY: acceptState = state; acceptPoint = p; acceptLen = len; goto endgame; } next: p++; len--; } endgame: if (acceptState == INITIAL) { /* * No numeric string at all found. */ status = TCL_ERROR; if (endPtrPtr != NULL) { *endPtrPtr = p; } } else { /* * Back up to the last accepting state in the lexer. * If the last char seen is the numeric whitespace character '_', * backup to that. */ p = acceptPoint; len = acceptLen; if (!(flags & TCL_PARSE_NO_WHITESPACE)) { /* * Accept trailing whitespace. */ while (len != 0 && TclIsSpaceProcM(*p)) { p++; len--; } } if (endPtrPtr == NULL) { if ((len != 0) && ((numBytes + 1 > 1) || (*p != '\0'))) { status = TCL_ERROR; } } else { *endPtrPtr = p; } } /* * Generate and store the appropriate internal rep. */ if (status == TCL_OK && objPtr != NULL) { TclFreeInternalRep(objPtr); switch (acceptState) { case SIGNUM: case ZERO_X: case ZERO_O: case ZERO_B: case ZERO_D: case LEADING_RADIX_POINT: case EXPONENT_START: case EXPONENT_SIGNUM: case sI: case sIN: case sINFI: case sINFIN: case sINFINI: case sINFINIT: #ifdef IEEE_FLOATING_POINT case sN: case sNA: case sNANPAREN: case sNANHEX: #endif Tcl_Panic("TclParseNumber: bad acceptState %d parsing '%s'", acceptState, bytes); case BINARY: shift = numTrailZeros; if (!significandOverflow && significandWide != 0 && ((size_t)shift >= CHAR_BIT*sizeof(Tcl_WideUInt) || significandWide > (MOST_BITS + signum) >> shift)) { significandOverflow = 1; err = mp_init_u64(&significandBig, significandWide); } if (shift) { if (!significandOverflow) { /* * When the significand is 0, it is possible for the * amount to be shifted to equal or exceed the width * of the significand. Do not shift when the * significand is 0 to avoid undefined behavior. */ if (significandWide != 0) { significandWide <<= shift; } } else if (err == MP_OKAY) { err = mp_mul_2d(&significandBig, shift, &significandBig); } } if (err != MP_OKAY) { return TCL_ERROR; } goto returnInteger; case HEXADECIMAL: /* * Returning a hex integer. Final scaling step. */ shift = 4 * numTrailZeros; if (!significandOverflow && significandWide !=0 && ((size_t)shift >= CHAR_BIT*sizeof(Tcl_WideUInt) || significandWide > (MOST_BITS + signum) >> shift)) { significandOverflow = 1; err = mp_init_u64(&significandBig, significandWide); } if (shift) { if (!significandOverflow) { /* * When the significand is 0, it is possible for the * amount to be shifted to equal or exceed the width * of the significand. Do not shift when the * significand is 0 to avoid undefined behavior. */ if (significandWide != 0) { significandWide <<= shift; } } else if (err == MP_OKAY) { err = mp_mul_2d(&significandBig, shift, &significandBig); } } if (err != MP_OKAY) { return TCL_ERROR; } goto returnInteger; case OCTAL: /* * Returning an octal integer. Final scaling step. */ shift = 3 * numTrailZeros; if (!octalSignificandOverflow && octalSignificandWide != 0 && ((size_t)shift >= CHAR_BIT*sizeof(Tcl_WideUInt) || octalSignificandWide > (MOST_BITS + signum) >> shift)) { octalSignificandOverflow = 1; err = mp_init_u64(&octalSignificandBig, octalSignificandWide); } if (shift) { if (!octalSignificandOverflow) { /* * When the significand is 0, it is possible for the * amount to be shifted to equal or exceed the width * of the significand. Do not shift when the * significand is 0 to avoid undefined behavior. */ if (octalSignificandWide != 0) { octalSignificandWide <<= shift; } } else if (err == MP_OKAY) { err = mp_mul_2d(&octalSignificandBig, shift, &octalSignificandBig); } } if (!octalSignificandOverflow) { if ((err == MP_OKAY) && (octalSignificandWide > (MOST_BITS + signum))) { err = mp_init_u64(&octalSignificandBig, octalSignificandWide); octalSignificandOverflow = 1; } else { objPtr->typePtr = &tclIntType; if (signum) { objPtr->internalRep.wideValue = (Tcl_WideInt)(-octalSignificandWide); } else { objPtr->internalRep.wideValue = (Tcl_WideInt)octalSignificandWide; } } } if ((err == MP_OKAY) && octalSignificandOverflow) { if (signum) { err = mp_neg(&octalSignificandBig, &octalSignificandBig); } TclSetBignumInternalRep(objPtr, &octalSignificandBig); } if (err != MP_OKAY) { return TCL_ERROR; } break; case ZERO: case DECIMAL: significandOverflow = AccumulateDecimalDigit(0, numTrailZeros-1, &significandWide, &significandBig, significandOverflow); if ((err == MP_OKAY) && !significandOverflow && (significandWide > MOST_BITS+signum)) { significandOverflow = 1; err = mp_init_u64(&significandBig, significandWide); } returnInteger: if (!significandOverflow) { if ((err == MP_OKAY) && (significandWide > MOST_BITS+signum)) { err = mp_init_u64(&significandBig, significandWide); significandOverflow = 1; } else { objPtr->typePtr = &tclIntType; if (signum) { objPtr->internalRep.wideValue = (Tcl_WideInt)(-significandWide); } else { objPtr->internalRep.wideValue = (Tcl_WideInt)significandWide; } } } if ((err == MP_OKAY) && significandOverflow) { if (signum) { err = mp_neg(&significandBig, &significandBig); } TclSetBignumInternalRep(objPtr, &significandBig); } if (err != MP_OKAY) { return TCL_ERROR; } break; case FRACTION: case EXPONENT: /* * Here, we're parsing a floating-point number. 'significandWide' * or 'significandBig' contains the exact significand, according * to whether 'significandOverflow' is set. The desired floating * point value is significand * 10**k, where * k = numTrailZeros+exponent-numDigitsAfterDp. */ objPtr->typePtr = &tclDoubleType; if (exponentSignum) { /* * At this point exponent>=0, so the following calculation * cannot underflow. */ exponent = -exponent; } /* * Adjust the exponent for the number of trailing zeros that * have not been accumulated, and the number of digits after * the decimal point. Pin any overflow to LONG_MAX/LONG_MIN * respectively. */ if (exponent >= 0) { if (exponent - numDigitsAfterDp > LONG_MAX - numTrailZeros) { exponent = LONG_MAX; } else { exponent = exponent - numDigitsAfterDp + numTrailZeros; } } else { if (exponent + numTrailZeros < LONG_MIN + numDigitsAfterDp) { exponent = LONG_MIN; } else { exponent = exponent + numTrailZeros - numDigitsAfterDp; } } /* * The desired number is now significandWide * 10**exponent * or significandBig * 10**exponent, depending on whether * the significand has overflowed a wide int. */ if (!significandOverflow) { objPtr->internalRep.doubleValue = MakeLowPrecisionDouble( signum, significandWide, numSigDigs, exponent); } else { objPtr->internalRep.doubleValue = MakeHighPrecisionDouble( signum, &significandBig, numSigDigs, exponent); } break; case sINF: case sINFINITY: if (signum) { objPtr->internalRep.doubleValue = -HUGE_VAL; } else { objPtr->internalRep.doubleValue = HUGE_VAL; } objPtr->typePtr = &tclDoubleType; break; #ifdef IEEE_FLOATING_POINT case sNAN: case sNANFINISH: objPtr->internalRep.doubleValue = MakeNaN(signum, significandWide); objPtr->typePtr = &tclDoubleType; break; #endif case INITIAL: /* This case only to silence compiler warning. */ Tcl_Panic("TclParseNumber: state INITIAL can't happen here"); } } /* * Format an error message when an invalid number is encountered. */ if (status != TCL_OK) { if (interp != NULL) { Tcl_Obj *msg = Tcl_ObjPrintf("expected %s but got ", expected); Tcl_Size argc; const char **argv; if ((TclMaxListLength(bytes, TCL_INDEX_NONE, NULL) > 1) && Tcl_SplitList(NULL, bytes, &argc, &argv) == TCL_OK) { Tcl_Free(argv); Tcl_AppendToObj(msg, "a list", -1); } else { Tcl_AppendToObj(msg, "\"", -1); Tcl_AppendLimitedToObj(msg, bytes, numBytes, 50, ""); Tcl_AppendToObj(msg, "\"", -1); } Tcl_SetObjResult(interp, msg); Tcl_SetErrorCode(interp, "TCL", "VALUE", "NUMBER", (char *)NULL); } } /* * Free memory. */ if (octalSignificandOverflow) { mp_clear(&octalSignificandBig); } if (significandOverflow) { mp_clear(&significandBig); } return status; } /* *---------------------------------------------------------------------- * * AccumulateDecimalDigit -- * * Consume a decimal digit in a number being scanned. * * Results: * Returns 1 if the number has overflowed to a bignum, 0 if it still fits * in a wide integer. * * Side effects: * Updates either the wide or bignum representation. * *---------------------------------------------------------------------- */ static int AccumulateDecimalDigit( unsigned digit, /* Digit being scanned. */ int numZeros, /* Count of zero digits preceding the digit * being scanned. */ Tcl_WideUInt *wideRepPtr, /* Representation of the partial number as a * wide integer. */ mp_int *bignumRepPtr, /* Representation of the partial number as a * bignum. */ int bignumFlag) /* Flag == 1 if the number overflowed previous * to this digit. */ { int i, n; Tcl_WideUInt w; /* * Try wide multiplication first. */ if (!bignumFlag) { w = *wideRepPtr; if (w == 0) { /* * There's no need to multiply if the multiplicand is zero. */ *wideRepPtr = digit; return 0; } else if (numZeros >= maxpow10_wide || w > (UWIDE_MAX-digit)/pow10_wide[numZeros+1]) { /* * Wide multiplication will overflow. Expand the number to a * bignum and fall through into the bignum case. */ if (mp_init_u64(bignumRepPtr, w) != MP_OKAY) { return 0; } } else { /* * Wide multiplication. */ *wideRepPtr = w * pow10_wide[numZeros+1] + digit; return 0; } } /* * Bignum multiplication. */ if (numZeros < log10_DIGIT_MAX) { /* * Up to about 8 zeros - single digit multiplication. */ if ((mp_mul_d(bignumRepPtr, (mp_digit) pow10_wide[numZeros+1], bignumRepPtr) != MP_OKAY) || (mp_add_d(bignumRepPtr, (mp_digit) digit, bignumRepPtr) != MP_OKAY)) return 0; } else { mp_err err; /* * More than single digit multiplication. Multiply by the appropriate * small powers of 5, and then shift. Large strings of zeroes are * eaten 256 at a time; this is less efficient than it could be, but * seems implausible. We presume that MP_DIGIT_BIT is at least 27. The * first multiplication, by up to 10**7, is done with a one-DIGIT * multiply (this presumes that MP_DIGIT_BIT >= 24). */ n = numZeros + 1; err = mp_mul_d(bignumRepPtr, (mp_digit) pow10_wide[n&0x7], bignumRepPtr); for (i = 3; (err == MP_OKAY) && (i <= 7); ++i) { if (n & (1 << i)) { err = mp_mul(bignumRepPtr, pow5+i, bignumRepPtr); } } while ((err == MP_OKAY) && (n >= 256)) { err = mp_mul(bignumRepPtr, pow5+8, bignumRepPtr); n -= 256; } if ((err != MP_OKAY) || (mp_mul_2d(bignumRepPtr, (int)(numZeros+1)&~0x7, bignumRepPtr) != MP_OKAY) || (mp_add_d(bignumRepPtr, (mp_digit) digit, bignumRepPtr) != MP_OKAY)) { return 0; } } return 1; } /* *---------------------------------------------------------------------- * * MakeLowPrecisionDouble -- * * Makes the double precision number, signum*significand*10**exponent. * * Results: * Returns the constructed number. * * Common cases, where there are few enough digits that the number can be * represented with at most roundoff, are handled specially here. If the * number requires more than one rounded operation to compute, the code * promotes the significand to a bignum and calls MakeHighPrecisionDouble * to do it instead. * *---------------------------------------------------------------------- */ static double MakeLowPrecisionDouble( int signum, /* 1 if the number is negative, 0 otherwise */ Tcl_WideUInt significand, /* Significand of the number */ int numSigDigs, /* Number of digits in the significand */ long exponent) /* Power of ten */ { TCL_IEEE_DOUBLE_ROUNDING_DECL mp_int significandBig; /* Significand expressed as a bignum. */ /* * With gcc on x86, the floating point rounding mode is double-extended. * This causes the result of double-precision calculations to be rounded * twice: once to the precision of double-extended and then again to the * precision of double. Double-rounding introduces gratuitous errors of 1 * ulp, so we need to change rounding mode to 53-bits. We also make * 'retval' volatile, so that it doesn't get promoted to a register. */ volatile double retval; /* Value of the number. */ /* * Test for zero significand, which requires explicit construction * of -0.0. (Unary minus returns a positive zero.) */ if (significand == 0) { return copysign(0.0, -signum); } /* * Set the FP control word for 53 bits, WARNING: It must be reset * before returning. */ TCL_IEEE_DOUBLE_ROUNDING; if (numSigDigs <= QUICK_MAX) { if (exponent >= 0) { if (exponent <= mmaxpow) { /* * The significand is an exact integer, and so is * 10**exponent. The product will be correct to within 1/2 ulp * without special handling. */ retval = (double) ((Tcl_WideInt)significand * pow10vals[exponent]); goto returnValue; } else { int diff = QUICK_MAX - numSigDigs; if (exponent-diff <= mmaxpow) { /* * 10**exponent is not an exact integer, but * 10**(exponent-diff) is exact, and so is * significand*10**diff, so we can still compute the value * with only one roundoff. */ volatile double factor = (double) ((Tcl_WideInt)significand * pow10vals[diff]); retval = factor * pow10vals[exponent-diff]; goto returnValue; } } } else { if (exponent >= -mmaxpow) { /* * 10**-exponent is an exact integer, and so is the * significand. Compute the result by one division, again with * only one rounding. */ retval = (double) ((Tcl_WideInt)significand / pow10vals[-exponent]); goto returnValue; } } } /* * All the easy cases have failed. Promote the significand to bignum and * call MakeHighPrecisionDouble to do it the hard way. */ if (mp_init_u64(&significandBig, significand) != MP_OKAY) { return 0.0; } retval = MakeHighPrecisionDouble(0, &significandBig, numSigDigs, exponent); mp_clear(&significandBig); /* * Come here to return the computed value. */ returnValue: if (signum) { retval = -retval; } /* * On gcc on x86, restore the floating point mode word. */ TCL_DEFAULT_DOUBLE_ROUNDING; return retval; } /* *---------------------------------------------------------------------- * * MakeHighPrecisionDouble -- * * Makes the double precision number, signum*significand*10**exponent. * * Results: * Returns the constructed number. * * MakeHighPrecisionDouble is used when arbitrary-precision arithmetic is * needed to ensure correct rounding. It begins by calculating a * low-precision approximation to the desired number, and then refines * the answer in high precision. * *---------------------------------------------------------------------- */ static double MakeHighPrecisionDouble( int signum, /* 1=negative, 0=nonnegative */ mp_int *significand, /* Exact significand of the number */ int numSigDigs, /* Number of significant digits */ long exponent) /* Power of 10 by which to multiply */ { TCL_IEEE_DOUBLE_ROUNDING_DECL int machexp = 0; /* Machine exponent of a power of 10. */ /* * With gcc on x86, the floating point rounding mode is double-extended. * This causes the result of double-precision calculations to be rounded * twice: once to the precision of double-extended and then again to the * precision of double. Double-rounding introduces gratuitous errors of 1 * ulp, so we need to change rounding mode to 53-bits. We also make * 'retval' volatile to make sure that it doesn't get promoted to a * register. */ volatile double retval; /* * A zero significand requires explicit construction of -0.0. * (Unary minus returns positive zero.) */ if (mp_iszero(significand)) { return copysign(0.0, -signum); } /* * Set the 53-bit rounding mode. WARNING: It must be reset before * returning. */ TCL_IEEE_DOUBLE_ROUNDING; /* * Make quick checks for over/underflow. Be careful to avoid * integer overflow when calculating with 'exponent'. */ if (exponent >= 0 && exponent-1 > maxDigits-numSigDigs) { retval = HUGE_VAL; goto returnValue; } else if (exponent < 0 && numSigDigs+exponent < minDigits+1) { retval = 0.0; goto returnValue; } /* * Develop a first approximation to the significand. It is tempting simply * to force bignum to double, but that will overflow on input numbers like * 1.[string repeat 0 1000]1; while this is a not terribly likely * scenario, we still have to deal with it. Use fraction and exponent * instead. Once we have the significand, multiply by 10**exponent. Test * for overflow. Convert back to a double, and test for underflow. */ retval = BignumToBiasedFrExp(significand, &machexp); retval = Pow10TimesFrExp(exponent, retval, &machexp); if (machexp > DBL_MAX_EXP*log2FLT_RADIX) { retval = HUGE_VAL; goto returnValue; } retval = SafeLdExp(retval, machexp); if (tiny == 0.0) { tiny = SafeLdExp(1.0, DBL_MIN_EXP * log2FLT_RADIX - mantBits); } if (retval < tiny) { retval = tiny; } /* * Refine the result twice. (The second refinement should be necessary * only if the best approximation is a power of 2 minus 1/2 ulp). */ retval = RefineApproximation(retval, significand, exponent); retval = RefineApproximation(retval, significand, exponent); /* * Come here to return the computed value. */ returnValue: if (signum) { retval = -retval; } /* * On gcc on x86, restore the floating point mode word. */ TCL_DEFAULT_DOUBLE_ROUNDING; return retval; } /* *---------------------------------------------------------------------- * * MakeNaN -- * * Makes a "Not a Number" given a set of bits to put in the tag bits * * Note that a signalling NaN is never returned. * *---------------------------------------------------------------------- */ #ifdef IEEE_FLOATING_POINT static double MakeNaN( int signum, /* Sign bit (1=negative, 0=nonnegative. */ Tcl_WideUInt tags) /* Tag bits to put in the NaN. */ { union { Tcl_WideUInt iv; double dv; } theNaN; theNaN.iv = tags; theNaN.iv &= (((Tcl_WideUInt) 1) << 51) - 1; if (signum) { theNaN.iv |= ((Tcl_WideUInt) (0x8000 | NAN_START)) << 48; } else { theNaN.iv |= ((Tcl_WideUInt) NAN_START) << 48; } if (n770_fp) { theNaN.iv = Nokia770Twiddle(theNaN.iv); } return theNaN.dv; } #endif /* *---------------------------------------------------------------------- * * RefineApproximation -- * * Given a poor approximation to a floating point number, returns a * better one. (The better approximation is correct to within 1 ulp, and * is entirely correct if the poor approximation is correct to 1 ulp.) * * Results: * Returns the improved result. * *---------------------------------------------------------------------- */ static double RefineApproximation( double approxResult, /* Approximate result of conversion. */ mp_int *exactSignificand, /* Integer significand. */ int exponent) /* Power of 10 to multiply by significand. */ { int M2, M5; /* Powers of 2 and of 5 needed to put the * decimal and binary numbers over a common * denominator. */ double significand; /* Sigificand of the binary number. */ int binExponent; /* Exponent of the binary number. */ int msb; /* Most significant bit position of an * intermediate result. */ int nDigits; /* Number of mp_digit's in an intermediate * result. */ mp_int twoMv; /* Approx binary value expressed as an exact * integer scaled by the multiplier 2M. */ mp_int twoMd; /* Exact decimal value expressed as an exact * integer scaled by the multiplier 2M. */ int scale; /* Scale factor for M. */ int multiplier; /* Power of two to scale M. */ double num, den; /* Numerator and denominator of the correction * term. */ double quot; /* Correction term. */ double minincr; /* Lower bound on the absolute value of the * correction term. */ int roundToEven = 0; /* Flag == TRUE if we need to invoke * "round to even" functionality */ double rteSignificand; /* Significand of the round-to-even result */ int rteExponent; /* Exponent of the round-to-even result */ int shift; /* Shift count for converting numerator * and denominator of corrector to floating * point */ Tcl_WideInt rteSigWide; /* Wide integer version of the significand * for testing evenness */ int i; mp_err err = MP_OKAY; /* * The first approximation is always low. If we find that it's HUGE_VAL, * we're done. */ if (approxResult == HUGE_VAL) { return approxResult; } significand = frexp(approxResult, &binExponent); /* * We are trying to compute a corrector term that, when added to the * approximate result, will yield close to the exact result. * The exact result is exactSignificand * 10**exponent. * The approximate result is significand * 2**binExponent * If exponent<0, we need to multiply the exact value by 10**-exponent * to make it an integer, plus another factor of 2 to decide on rounding. * Similarly if binExponent 0) { M5 = 0; } else { M5 = -exponent; if (M5 - 1 > M2) { M2 = M5 - 1; } } /* * Compute twoMv as 2*M*v, where v is the approximate value. * This is done by bit-whacking to calculate 2**(M2+1)*significand, * and then multiplying by 5**M5. */ msb = binExponent + M2; /* 1008 */ nDigits = msb / MP_DIGIT_BIT + 1; if (mp_init_size(&twoMv, nDigits) != MP_OKAY) { return approxResult; } i = (msb % MP_DIGIT_BIT + 1); twoMv.used = nDigits; significand *= SafeLdExp(1.0, i); while (--nDigits >= 0) { twoMv.dp[nDigits] = (mp_digit) significand; significand -= (mp_digit) significand; significand = SafeLdExp(significand, MP_DIGIT_BIT); } for (i = 0; i <= 8; ++i) { if (M5 & (1 << i) && (mp_mul(&twoMv, pow5+i, &twoMv) != MP_OKAY)) { mp_clear(&twoMv); return approxResult; } } /* * Compute twoMd as 2*M*d, where d is the exact value. * This is done by multiplying by 5**(M5+exponent) and then multiplying * by 2**(M5+exponent+1), which is, of course, a left shift. */ if (mp_init_copy(&twoMd, exactSignificand) != MP_OKAY) { mp_clear(&twoMv); return approxResult; } for (i = 0; (i <= 8); ++i) { if ((M5 + exponent) & (1 << i)) { err = mp_mul(&twoMd, pow5+i, &twoMd); } } if (err == MP_OKAY) { err = mp_mul_2d(&twoMd, M2+exponent+1, &twoMd); } /* * Now let twoMd = twoMd - twoMv, the difference between the exact and * approximate values. */ if (err == MP_OKAY) { err = mp_sub(&twoMd, &twoMv, &twoMd); } /* * The result, 2Mv-2Md, needs to be divided by 2M to yield a correction * term. Because 2M may well overflow a double, we need to scale the * denominator by a factor of 2**binExponent-mantBits. Place that factor * times 1/2 ULP into twoMd. */ scale = binExponent - mantBits - 1; mp_set_u64(&twoMv, 1); for (i = 0; (i <= 8) && (err == MP_OKAY); ++i) { if (M5 & (1 << i)) { err = mp_mul(&twoMv, pow5+i, &twoMv); } } multiplier = M2 + scale + 1; if (err != MP_OKAY) { mp_clear(&twoMd); mp_clear(&twoMv); return approxResult; } else if (multiplier > 0) { err = mp_mul_2d(&twoMv, multiplier, &twoMv); } else if (multiplier < 0) { err = mp_div_2d(&twoMv, -multiplier, &twoMv, NULL); } if (err != MP_OKAY) { mp_clear(&twoMd); mp_clear(&twoMv); return approxResult; } /* * Will the eventual correction term be less than, equal to, or * greater than 1/2 ULP? */ switch (mp_cmp_mag(&twoMd, &twoMv)) { case MP_LT: /* * If the error is less than 1/2 ULP, there's no correction to make. */ mp_clear(&twoMd); mp_clear(&twoMv); return approxResult; case MP_EQ: /* * If the error is exactly 1/2 ULP, we need to round to even. */ roundToEven = 1; break; case MP_GT: /* * We need to correct the result if the error exceeds 1/2 ULP. */ break; } /* * If we're in the 'round to even' case, and the significand is already * even, we're done. Return the approximate result. */ if (roundToEven) { rteSignificand = frexp(approxResult, &rteExponent); rteSigWide = (Tcl_WideInt)ldexp(rteSignificand, FP_PRECISION); if ((rteSigWide & 1) == 0) { mp_clear(&twoMd); mp_clear(&twoMv); return approxResult; } } /* * Reduce the numerator and denominator of the corrector term so that * they will fit in the floating point precision. */ shift = mp_count_bits(&twoMv) - FP_PRECISION - 1; if (shift > 0) { err = mp_div_2d(&twoMv, shift, &twoMv, NULL); if (err == MP_OKAY) { err = mp_div_2d(&twoMd, shift, &twoMd, NULL); } } if (err != MP_OKAY) { mp_clear(&twoMd); mp_clear(&twoMv); return approxResult; } /* * Convert the numerator and denominator of the corrector term accurately * to floating point numbers. */ num = TclBignumToDouble(&twoMd); den = TclBignumToDouble(&twoMv); quot = SafeLdExp(num/den, scale); minincr = SafeLdExp(1.0, binExponent-mantBits); if (quot<0. && quot>-minincr) { quot = -minincr; } else if (quot>0. && quot>= 1; ++r; } if ((err == MP_OKAY) && (p != result)) { err = mp_copy(p, result); } return err; } /* *---------------------------------------------------------------------- * * NormalizeRightward -- * * Shifts a number rightward until it is odd (that is, until the least * significant bit is nonzero. * * Results: * Returns the number of bit positions by which the number was shifted. * * Side effects: * Shifts the number in place; *wPtr is replaced by the shifted number. * *---------------------------------------------------------------------- */ static inline int NormalizeRightward( Tcl_WideUInt *wPtr) /* INOUT: Number to shift. */ { int rv = 0; Tcl_WideUInt w = *wPtr; if (!(w & (Tcl_WideUInt) 0xFFFFFFFF)) { w >>= 32; rv += 32; } if (!(w & (Tcl_WideUInt) 0xFFFF)) { w >>= 16; rv += 16; } if (!(w & (Tcl_WideUInt) 0xFF)) { w >>= 8; rv += 8; } if (!(w & (Tcl_WideUInt) 0xF)) { w >>= 4; rv += 4; } if (!(w & 0x3)) { w >>= 2; rv += 2; } if (!(w & 0x1)) { w >>= 1; ++rv; } *wPtr = w; return rv; } /* *---------------------------------------------------------------------- * * RequiredPrecision -- * * Determines the number of bits needed to hold an integer. * * Results: * Returns the position of the most significant bit (0 - 63). Returns 0 * if the number is zero. * *---------------------------------------------------------------------- */ static int RequiredPrecision( Tcl_WideUInt w) /* Number to interrogate. */ { int rv; unsigned long wi; if (w & ((Tcl_WideUInt) 0xFFFFFFFF << 32)) { wi = (unsigned long) (w >> 32); rv = 32; } else { wi = (unsigned long) w; rv = 0; } if (wi & 0xFFFF0000) { wi >>= 16; rv += 16; } if (wi & 0xFF00) { wi >>= 8; rv += 8; } if (wi & 0xF0) { wi >>= 4; rv += 4; } if (wi & 0xC) { wi >>= 2; rv += 2; } if (wi & 0x2) { wi >>= 1; ++rv; } if (wi & 0x1) { ++rv; } return rv; } /* *---------------------------------------------------------------------- * * DoubleToExpAndSig -- * * Separates a 'double' into exponent and significand. * * Side effects: * Stores the significand in '*significand' and the exponent in '*expon' * so that dv == significand * 2.0**expon, and significand is odd. Also * stores the position of the leftmost 1-bit in 'significand' in 'bits'. * *---------------------------------------------------------------------- */ static inline void DoubleToExpAndSig( double dv, /* Number to convert. */ Tcl_WideUInt *significand, /* OUTPUT: Significand of the number. */ int *expon, /* OUTPUT: Exponent to multiply the number * by. */ int *bits) /* OUTPUT: Number of significant bits. */ { Double d; /* Number being converted. */ Tcl_WideUInt z; /* Significand under construction. */ int de; /* Exponent of the number. */ int k; /* Bit count. */ d.d = dv; /* * Extract exponent and significand. */ de = (d.w.word0 & EXP_MASK) >> EXP_SHIFT; z = d.q & SIG_MASK; if (de != 0) { z |= HIDDEN_BIT; k = NormalizeRightward(&z); *bits = FP_PRECISION - k; *expon = k + (de - EXPONENT_BIAS) - (FP_PRECISION-1); } else { k = NormalizeRightward(&z); *expon = k + (de - EXPONENT_BIAS) - (FP_PRECISION-1) + 1; *bits = RequiredPrecision(z); } *significand = z; } /* *---------------------------------------------------------------------- * * TakeAbsoluteValue -- * * Takes the absolute value of a 'double' including 0, Inf and NaN * * Side effects: * The 'double' in *d is replaced with its absolute value. The signum is * stored in 'sign': 1 for negative, 0 for nonnegative. * *---------------------------------------------------------------------- */ static inline void TakeAbsoluteValue( Double *d, /* Number to replace with absolute value. */ int *sign) /* Place to put the signum. */ { if (d->w.word0 & SIGN_BIT) { *sign = 1; d->w.word0 &= ~SIGN_BIT; } else { *sign = 0; } } /* *---------------------------------------------------------------------- * * FormatInfAndNaN -- * * Bailout for formatting infinities and Not-A-Number. * * Results: * Returns one of the strings 'Infinity' and 'NaN'. The string returned * must be freed by the caller using 'Tcl_Free'. * * Side effects: * Stores 9999 in *decpt, and sets '*endPtr' to designate the terminating * NUL byte of the string if 'endPtr' is not NULL. * *---------------------------------------------------------------------- */ static inline char * FormatInfAndNaN( Double *d, /* Exceptional number to format. */ int *decpt, /* Decimal point to set to a bogus value. */ char **endPtr) /* Pointer to the end of the formatted data */ { char *retval; *decpt = 9999; if (!(d->w.word1) && !(d->w.word0 & HI_ORDER_SIG_MASK)) { retval = (char *)Tcl_Alloc(9); strcpy(retval, "Infinity"); if (endPtr) { *endPtr = retval + 8; } } else { retval = (char *)Tcl_Alloc(4); strcpy(retval, "NaN"); if (endPtr) { *endPtr = retval + 3; } } return retval; } /* *---------------------------------------------------------------------- * * FormatZero -- * * Bailout to format a zero floating-point number. * * Results: * Returns the constant string "0" * * Side effects: * Stores 1 in '*decpt' and puts a pointer to the NUL byte terminating * the string in '*endPtr' if 'endPtr' is not NULL. * *---------------------------------------------------------------------- */ static inline char * FormatZero( int *decpt, /* Location of the decimal point. */ char **endPtr) /* Pointer to the end of the formatted data */ { char *retval = (char *)Tcl_Alloc(2); strcpy(retval, "0"); if (endPtr) { *endPtr = retval+1; } *decpt = 0; return retval; } /* *---------------------------------------------------------------------- * * ApproximateLog10 -- * * Computes a two-term Taylor series approximation to the common log of a * number, and computes the number's binary log. * * Results: * Return an approximation to floor(log10(bw*2**be)) that is either exact * or 1 too high. * *---------------------------------------------------------------------- */ static inline int ApproximateLog10( Tcl_WideUInt bw, /* Integer significand of the number. */ int be, /* Power of two to scale bw. */ int bbits) /* Number of bits of precision in bw. */ { int i; /* Log base 2 of the number. */ int k; /* Floor(Log base 10 of the number) */ double ds; /* Mantissa of the number. */ Double d2; /* * Compute i and d2 such that d = d2*2**i, and 1 < d2 < 2. * Compute an approximation to log10(d), * log10(d) ~ log10(2) * i + log10(1.5) * + (significand-1.5)/(1.5 * log(10)) */ d2.q = bw << (FP_PRECISION - bbits) & SIG_MASK; d2.w.word0 |= (EXPONENT_BIAS) << EXP_SHIFT; i = be + bbits - 1; ds = (d2.d - 1.5) * TWO_OVER_3LOG10 + LOG10_3HALVES_PLUS_FUDGE + LOG10_2 * i; k = (int) ds; if (k > ds) { --k; } return k; } /* *---------------------------------------------------------------------- * * BetterLog10 -- * * Improves the result of ApproximateLog10 for numbers in the range * 1 .. 10**(TEN_PMAX)-1 * * Side effects: * Sets k_check to 0 if the new result is known to be exact, and to 1 if * it may still be one too high. * * Results: * Returns the improved approximation to log10(d). * *---------------------------------------------------------------------- */ static inline int BetterLog10( double d, /* Original number to format. */ int k, /* Characteristic(Log base 10) of the * number. */ int *k_check) /* Flag == 1 if k is inexact. */ { /* * Performance hack. If k is in the range 0..TEN_PMAX, then we can use a * powers-of-ten table to check it. */ if (k >= 0 && k <= TEN_PMAX) { if (d < tens[k]) { k--; } *k_check = 0; } else { *k_check = 1; } return k; } /* *---------------------------------------------------------------------- * * ComputeScale -- * * Prepares to format a floating-point number as decimal. * * Parameters: * floor(log10*x) is k (or possibly k-1). floor(log2(x) is i. The * significand of x requires bbits bits to represent. * * Results: * Determines integers b2, b5, s2, s5 so that sig*2**b2*5**b5/2**s2*2**s5 * exactly represents the value of the x/10**k. This value will lie in * the range [1 .. 10), and allows for computing successive digits by * multiplying sig%10 by 10. * *---------------------------------------------------------------------- */ static inline void ComputeScale( int be, /* Exponent part of number: d = bw * 2**be. */ int k, /* Characteristic of log10(number). */ int *b2, /* OUTPUT: Power of 2 in the numerator. */ int *b5, /* OUTPUT: Power of 5 in the numerator. */ int *s2, /* OUTPUT: Power of 2 in the denominator. */ int *s5) /* OUTPUT: Power of 5 in the denominator. */ { /* * Scale numerator and denominator powers of 2 so that the input binary * number is the ratio of integers. */ if (be <= 0) { *b2 = 0; *s2 = -be; } else { *b2 = be; *s2 = 0; } /* * Scale numerator and denominator so that the output decimal number is * the ratio of integers. */ if (k >= 0) { *b5 = 0; *s5 = k; *s2 += k; } else { *b2 -= k; *b5 = -k; *s5 = 0; } } /* *---------------------------------------------------------------------- * * SetPrecisionLimits -- * * Determines how many digits of significance should be computed (and, * hence, how much memory need be allocated) for formatting a floating * point number. * * Given that 'k' is floor(log10(x)): * if 'shortest' format is used, there will be at most 18 digits in the * result. * if 'F' format is used, there will be at most 'ndigits' + k + 1 digits * if 'E' format is used, there will be exactly 'ndigits' digits. * * Side effects: * Adjusts '*ndigitsPtr' to have a valid value. Stores the maximum memory * allocation needed in *iPtr. Sets '*iLimPtr' to the limiting number of * digits to convert if k has been guessed correctly, and '*iLim1Ptr' to * the limiting number of digits to convert if k has been guessed to be * one too high. * *---------------------------------------------------------------------- */ static inline void SetPrecisionLimits( int flags, /* Type of conversion: TCL_DD_SHORTEST, * TCL_DD_E_FMT, TCL_DD_F_FMT. */ int k, /* Floor(log10(number to convert)) */ int *ndigitsPtr, /* IN/OUT: Number of digits requested (will be * adjusted if needed). */ int *iPtr, /* OUT: Maximum number of digits to return. */ int *iLimPtr, /* OUT: Number of digits of significance if * the bignum method is used.*/ int *iLim1Ptr) /* OUT: Number of digits of significance if * the quick method is used. */ { switch (flags & TCL_DD_CONVERSION_TYPE_MASK) { case TCL_DD_E_FORMAT: if (*ndigitsPtr <= 0) { *ndigitsPtr = 1; } *iLimPtr = *iLim1Ptr = *iPtr = *ndigitsPtr; break; case TCL_DD_F_FORMAT: *iPtr = *ndigitsPtr + k + 1; *iLimPtr = *iPtr; *iLim1Ptr = *iPtr - 1; if (*iPtr <= 0) { *iPtr = 1; } break; default: *iLimPtr = *iLim1Ptr = -1; *iPtr = 18; *ndigitsPtr = 0; break; } } /* *---------------------------------------------------------------------- * * BumpUp -- * * Increases a string of digits ending in a series of nines to designate * the next higher number. xxxxb9999... -> xxxx(b+1)0000... * * Results: * Returns a pointer to the end of the adjusted string. * * Side effects: * In the case that the string consists solely of '999999', sets it to * "1" and moves the decimal point (*kPtr) one place to the right. * *---------------------------------------------------------------------- */ static inline char * BumpUp( char *s, /* Cursor pointing one past the end of the * string. */ char *retval, /* Start of the string of digits. */ int *kPtr) /* Position of the decimal point. */ { while (*--s == '9') { if (s == retval) { ++(*kPtr); *s = '1'; return s+1; } } ++*s; ++s; return s; } /* *---------------------------------------------------------------------- * * AdjustRange -- * * Rescales a 'double' in preparation for formatting it using the 'quick' * double-to-string method. * * Results: * Returns the precision that has been lost in the prescaling as a count * of units in the least significant place. * *---------------------------------------------------------------------- */ static inline int AdjustRange( double *dPtr, /* INOUT: Number to adjust. */ int k) /* IN: floor(log10(d)) */ { int ieps; /* Number of roundoff errors that have * accumulated. */ double d = *dPtr; /* Number to adjust. */ double ds; int i, j, j1; ieps = 2; if (k > 0) { /* * The number must be reduced to bring it into range. */ ds = tens[k & 0xF]; j = k >> 4; if (j & BLETCH) { j &= (BLETCH-1); d /= bigtens[N_BIGTENS - 1]; ieps++; } i = 0; for (; j != 0; j>>=1) { if (j & 1) { ds *= bigtens[i]; ++ieps; } ++i; } d /= ds; } else if ((j1 = -k) != 0) { /* * The number must be increased to bring it into range. */ d *= tens[j1 & 0xF]; i = 0; for (j = j1>>4; j; j>>=1) { if (j & 1) { ieps++; d *= bigtens[i]; } ++i; } } *dPtr = d; return ieps; } /* *---------------------------------------------------------------------- * * ShorteningQuickFormat -- * * Returns a 'quick' format of a double precision number to a string of * digits, preferring a shorter string of digits if the shorter string is * still within 1/2 ulp of the number. * * Results: * Returns the string of digits. Returns NULL if the 'quick' method fails * and the bignum method must be used. * * Side effects: * Stores the position of the decimal point at '*kPtr'. * *---------------------------------------------------------------------- */ static inline char * ShorteningQuickFormat( double d, /* Number to convert. */ int k, /* floor(log10(d)) */ int ilim, /* Number of significant digits to return. */ double eps, /* Estimated roundoff error. */ char *retval, /* Buffer to receive the digit string. */ int *kPtr) /* Pointer to stash the position of the * decimal point. */ { char *s = retval; /* Cursor in the return value. */ int digit; /* Current digit. */ int i; eps = 0.5 / tens[ilim-1] - eps; i = 0; for (;;) { /* * Convert a digit. */ digit = (int) d; d -= digit; *s++ = '0' + digit; /* * Truncate the conversion if the string of digits is within 1/2 ulp * of the actual value. */ if (d < eps) { *kPtr = k; return s; } if ((1. - d) < eps) { *kPtr = k; return BumpUp(s, retval, kPtr); } /* * Bail out if the conversion fails to converge to a sufficiently * precise value. */ if (++i >= ilim) { return NULL; } /* * Bring the next digit to the integer part. */ eps *= 10; d *= 10.0; } } /* *---------------------------------------------------------------------- * * StrictQuickFormat -- * * Convert a double precision number of a string of a precise number of * digits, using the 'quick' double precision method. * * Results: * Returns the digit string, or NULL if the bignum method must be used to * do the formatting. * * Side effects: * Stores the position of the decimal point in '*kPtr'. * *---------------------------------------------------------------------- */ static inline char * StrictQuickFormat( double d, /* Number to convert. */ int k, /* floor(log10(d)) */ int ilim, /* Number of significant digits to return. */ double eps, /* Estimated roundoff error. */ char *retval, /* Start of the digit string. */ int *kPtr) /* Pointer to stash the position of the * decimal point. */ { char *s = retval; /* Cursor in the return value. */ int digit; /* Current digit of the answer. */ int i; eps *= tens[ilim-1]; i = 1; for (;;) { /* * Extract a digit. */ digit = (int) d; d -= digit; if (d == 0.0) { ilim = i; } *s++ = '0' + digit; /* * When the given digit count is reached, handle trailing strings of 0 * and 9. */ if (i == ilim) { if (d > 0.5 + eps) { *kPtr = k; return BumpUp(s, retval, kPtr); } else if (d < 0.5 - eps) { while (*--s == '0') { /* do nothing */ } s++; *kPtr = k; return s; } else { return NULL; } } /* * Advance to the next digit. */ ++i; d *= 10.0; } } /* *---------------------------------------------------------------------- * * QuickConversion -- * * Converts a floating point number the 'quick' way, when only a limited * number of digits is required and floating point arithmetic can * therefore be used for the intermediate results. * * Results: * Returns the converted string, or NULL if the bignum method must be * used. * *---------------------------------------------------------------------- */ static inline char * QuickConversion( double e, /* Number to format. */ int k, /* floor(log10(d)), approximately. */ int k_check, /* 0 if k is exact, 1 if it may be too high */ int flags, /* Flags passed to dtoa: * TCL_DD_SHORTEST */ int len, /* Length of the return value. */ int ilim, /* Number of digits to store. */ int ilim1, /* Number of digits to store if we misguessed * k. */ int *decpt, /* OUTPUT: Location of the decimal point. */ char **endPtr) /* OUTPUT: Pointer to the terminal null * byte. */ { int ieps; /* Number of 1-ulp roundoff errors that have * accumulated in the calculation. */ Double eps; /* Estimated roundoff error. */ char *retval; /* Returned string. */ char *end; /* Pointer to the terminal null byte in the * returned string. */ volatile double d; /* Workaround for a bug in mingw gcc 3.4.5 */ /* * Bring d into the range [1 .. 10). */ ieps = AdjustRange(&e, k); d = e; /* * If the guessed value of k didn't get d into range, adjust it by one. If * that leaves us outside the range in which quick format is accurate, * bail out. */ if (k_check && d < 1. && ilim > 0) { if (ilim1 < 0) { return NULL; } ilim = ilim1; --k; d = d * 10.0; ++ieps; } /* * Compute estimated roundoff error. */ eps.d = ieps * d + 7.; eps.w.word0 -= (FP_PRECISION-1) << EXP_SHIFT; /* * Handle the peculiar case where the result has no significant digits. */ retval = (char *)Tcl_Alloc(len + 1); if (ilim == 0) { d = d - 5.; if (d > eps.d) { *retval = '1'; *decpt = k; return retval; } else if (d < -eps.d) { *decpt = k; return retval; } else { Tcl_Free(retval); return NULL; } } /* * Format the digit string. */ if (flags & TCL_DD_SHORTEST) { end = ShorteningQuickFormat(d, k, ilim, eps.d, retval, decpt); } else { end = StrictQuickFormat(d, k, ilim, eps.d, retval, decpt); } if (end == NULL) { Tcl_Free(retval); return NULL; } *end = '\0'; if (endPtr != NULL) { *endPtr = end; } return retval; } /* *---------------------------------------------------------------------- * * CastOutPowersOf2 -- * * Adjust the factors 'b2', 'm2', and 's2' to cast out common powers of 2 * from numerator and denominator in preparation for the 'bignum' method * of floating point conversion. * *---------------------------------------------------------------------- */ static inline void CastOutPowersOf2( int *b2, /* Power of 2 to multiply the significand. */ int *m2, /* Power of 2 to multiply 1/2 ulp. */ int *s2) /* Power of 2 to multiply the common * denominator. */ { int i; if (*m2 > 0 && *s2 > 0) { /* Find the smallest power of 2 in the * numerator. */ if (*m2 < *s2) { /* Find the lowest common denominator. */ i = *m2; } else { i = *s2; } *b2 -= i; /* Reduce to lowest terms. */ *m2 -= i; *s2 -= i; } } /* *---------------------------------------------------------------------- * * ShorteningInt64Conversion -- * * Converts a double-precision number to the shortest string of digits * that reconverts exactly to the given number, or to 'ilim' digits if * that will yield a shorter result. The numerator and denominator in * David Gay's conversion algorithm are known to fit in Tcl_WideUInt, * giving considerably faster arithmetic than mp_int's. * * Results: * Returns the string of significant decimal digits, in newly allocated * memory * * Side effects: * Stores the location of the decimal point in '*decpt' and the location * of the terminal null byte in '*endPtr'. * *---------------------------------------------------------------------- */ static inline char * ShorteningInt64Conversion( Double *dPtr, /* Original number to convert. */ Tcl_WideUInt bw, /* Integer significand. */ int b2, int b5, /* Scale factor for the significand in the * numerator. */ int m2plus, int m2minus, int m5, /* Scale factors for 1/2 ulp in the numerator * (will be different if bw == 1. */ int s2, int s5, /* Scale factors for the denominator. */ int k, /* Number of output digits before the decimal * point. */ int len, /* Number of digits to allocate. */ int ilim, /* Number of digits to convert if b >= s */ int ilim1, /* Number of digits to convert if b < s */ int *decpt, /* OUTPUT: Position of the decimal point. */ char **endPtr) /* OUTPUT: Position of the terminal '\0' at * the end of the returned string. */ { char *retval = (char *)Tcl_Alloc(len + 1); /* Output buffer. */ Tcl_WideUInt b = (bw * wuipow5[b5]) << b2; /* Numerator of the fraction being * converted. */ Tcl_WideUInt S = wuipow5[s5] << s2; /* Denominator of the fraction being * converted. */ Tcl_WideUInt mplus, mminus; /* Ranges for testing whether the result is * within roundoff of being exact. */ int digit; /* Current output digit. */ char *s = retval; /* Cursor in the output buffer. */ int i; /* Current position in the output buffer. */ /* * Adjust if the logarithm was guessed wrong. */ if (b < S) { b = 10 * b; ++m2plus; ++m2minus; ++m5; ilim = ilim1; --k; } /* * Compute roundoff ranges. */ mplus = wuipow5[m5] << m2plus; mminus = wuipow5[m5] << m2minus; /* * Loop through the digits. */ i = 1; for (;;) { digit = (int)(b / S); if (digit > 10) { Tcl_Panic("wrong digit!"); } b = b % S; /* * Does the current digit put us on the low side of the exact value * but within roundoff of being exact? */ if (b < mplus || (b == mplus && (dPtr->w.word1 & 1) == 0)) { /* * Make sure we shouldn't be rounding *up* instead, in case the * next number above is closer. */ if (2 * b > S || (2 * b == S && (digit & 1) != 0)) { ++digit; if (digit == 10) { *s++ = '9'; s = BumpUp(s, retval, &k); break; } } /* * Stash the current digit. */ *s++ = '0' + digit; break; } /* * Does one plus the current digit put us within roundoff of the * number? */ if (b > S - mminus || (b == S - mminus && (dPtr->w.word1 & 1) == 0)) { if (digit == 9) { *s++ = '9'; s = BumpUp(s, retval, &k); break; } ++digit; *s++ = '0' + digit; break; } /* * Have we converted all the requested digits? */ *s++ = '0' + digit; if (i == ilim) { if (2*b > S || (2*b == S && (digit & 1) != 0)) { s = BumpUp(s, retval, &k); } break; } /* * Advance to the next digit. */ b = 10 * b; mplus = 10 * mplus; mminus = 10 * mminus; ++i; } /* * Endgame - store the location of the decimal point and the end of the * string. */ *s = '\0'; *decpt = k; if (endPtr) { *endPtr = s; } return retval; } /* *---------------------------------------------------------------------- * * StrictInt64Conversion -- * * Converts a double-precision number to a fixed-length string of 'ilim' * digits that reconverts exactly to the given number. ('ilim' should be * replaced with 'ilim1' in the case where log10(d) has been * overestimated). The numerator and denominator in David Gay's * conversion algorithm are known to fit in Tcl_WideUInt, giving * considerably faster arithmetic than mp_int's. * * Results: * Returns the string of significant decimal digits, in newly allocated * memory * * Side effects: * Stores the location of the decimal point in '*decpt' and the location * of the terminal null byte in '*endPtr'. * *---------------------------------------------------------------------- */ static inline char * StrictInt64Conversion( Tcl_WideUInt bw, /* Integer significand. */ int b2, int b5, /* Scale factor for the significand in the * numerator. */ int s2, int s5, /* Scale factors for the denominator. */ int k, /* Number of output digits before the decimal * point. */ int len, /* Number of digits to allocate. */ int ilim, /* Number of digits to convert if b >= s */ int ilim1, /* Number of digits to convert if b < s */ int *decpt, /* OUTPUT: Position of the decimal point. */ char **endPtr) /* OUTPUT: Position of the terminal '\0' at * the end of the returned string. */ { char *retval = (char *)Tcl_Alloc(len + 1); /* Output buffer. */ Tcl_WideUInt b = (bw * wuipow5[b5]) << b2; /* Numerator of the fraction being * converted. */ Tcl_WideUInt S = wuipow5[s5] << s2; /* Denominator of the fraction being * converted. */ int digit; /* Current output digit. */ char *s = retval; /* Cursor in the output buffer. */ int i; /* Current position in the output buffer. */ /* * Adjust if the logarithm was guessed wrong. */ if (b < S) { b = 10 * b; ilim = ilim1; --k; } /* * Loop through the digits. */ i = 1; for (;;) { digit = (int)(b / S); if (digit > 10) { Tcl_Panic("wrong digit!"); } b = b % S; /* * Have we converted all the requested digits? */ *s++ = '0' + digit; if (i == ilim) { if (2*b > S || (2*b == S && (digit & 1) != 0)) { s = BumpUp(s, retval, &k); } else { while (*--s == '0') { /* do nothing */ } ++s; } break; } /* * Advance to the next digit. */ b = 10 * b; ++i; } /* * Endgame - store the location of the decimal point and the end of the * string. */ *s = '\0'; *decpt = k; if (endPtr) { *endPtr = s; } return retval; } /* *---------------------------------------------------------------------- * * ShouldBankerRoundUpPowD -- * * Test whether bankers' rounding should round a digit up. Assumption is * made that the denominator of the fraction being tested is a power of * 2**MP_DIGIT_BIT. * * Results: * Returns 1 iff the fraction is more than 1/2, or if the fraction is * exactly 1/2 and the digit is odd. * *---------------------------------------------------------------------- */ static inline int ShouldBankerRoundUpPowD( mp_int *b, /* Numerator of the fraction. */ int sd, /* Denominator is 2**(sd*MP_DIGIT_BIT). */ int isodd) /* 1 if the digit is odd, 0 if even. */ { int i; static const mp_digit topbit = ((mp_digit)1) << (MP_DIGIT_BIT - 1); if (b->used < sd || (b->dp[sd-1] & topbit) == 0) { return 0; } if (b->dp[sd-1] != topbit) { return 1; } for (i = sd-2; i >= 0; --i) { if (b->dp[i] != 0) { return 1; } } return isodd; } /* *---------------------------------------------------------------------- * * ShouldBankerRoundUpToNextPowD -- * * Tests whether bankers' rounding will round down in the "denominator is * a power of 2**MP_DIGIT" case. * * Results: * Returns 1 if the rounding will be performed - which increases the * digit by one - and 0 otherwise. * *---------------------------------------------------------------------- */ static inline int ShouldBankerRoundUpToNextPowD( mp_int *b, /* Numerator of the fraction. */ mp_int *m, /* Numerator of the rounding tolerance. */ int sd, /* Common denominator is 2**(sd*MP_DIGIT_BIT). */ int isodd, /* 1 if the integer significand is odd. */ mp_int *temp) /* Work area for the calculation. */ { int i; /* * Compare B and S-m - which is the same as comparing B+m and S - which we * do by computing b+m and doing a bitwhack compare against * 2**(MP_DIGIT_BIT*sd) */ if ((mp_add(b, m, temp) != MP_OKAY) || (temp->used <= sd)) { /* Too few digits to be > s */ return 0; } if (temp->used > sd+1 || temp->dp[sd] > 1) { /* >= 2s */ return 1; } for (i = sd-1; i >= 0; --i) { /* Check for ==s */ if (temp->dp[i] != 0) { /* > s */ return 1; } } return isodd; } /* *---------------------------------------------------------------------- * * ShorteningBignumConversionPowD -- * * Converts a double-precision number to the shortest string of digits * that reconverts exactly to the given number, or to 'ilim' digits if * that will yield a shorter result. The denominator in David Gay's * conversion algorithm is known to be a power of 2**MP_DIGIT_BIT, and hence * the division in the main loop may be replaced by a digit shift and * mask. * * Results: * Returns the string of significant decimal digits, in newly allocated * memory * * Side effects: * Stores the location of the decimal point in '*decpt' and the location * of the terminal null byte in '*endPtr'. * *---------------------------------------------------------------------- */ static inline char * ShorteningBignumConversionPowD( Double *dPtr, /* Original number to convert. */ Tcl_WideUInt bw, /* Integer significand. */ int b2, int b5, /* Scale factor for the significand in the * numerator. */ int m2plus, int m2minus, int m5, /* Scale factors for 1/2 ulp in the numerator * (will be different if bw == 1). */ int sd, /* Scale factor for the denominator. */ int k, /* Number of output digits before the decimal * point. */ int len, /* Number of digits to allocate. */ int ilim, /* Number of digits to convert if b >= s */ int ilim1, /* Number of digits to convert if b < s */ int *decpt, /* OUTPUT: Position of the decimal point. */ char **endPtr) /* OUTPUT: Position of the terminal '\0' at * the end of the returned string. */ { char *retval = (char *)Tcl_Alloc(len + 1); /* Output buffer. */ mp_int b; /* Numerator of the fraction being * converted. */ mp_int mplus, mminus; /* Bounds for roundoff. */ mp_digit digit; /* Current output digit. */ char *s = retval; /* Cursor in the output buffer. */ int i; /* Index in the output buffer. */ mp_int temp; int r1; mp_err err = MP_OKAY; /* * b = bw * 2**b2 * 5**b5 * mminus = 5**m5 */ if ((retval == NULL) || (mp_init_u64(&b, bw) != MP_OKAY)) { return NULL; } if (mp_init_set(&mminus, 1) != MP_OKAY) { mp_clear(&b); return NULL; } err = MulPow5(&b, b5, &b); if (err == MP_OKAY) { err = mp_mul_2d(&b, b2, &b); } /* * Adjust if the logarithm was guessed wrong. */ if ((err == MP_OKAY) && (b.used <= sd)) { err = mp_mul_d(&b, 10, &b); ++m2plus; ++m2minus; ++m5; ilim = ilim1; --k; } /* * mminus = 5**m5 * 2**m2minus * mplus = 5**m5 * 2**m2plus */ if (err == MP_OKAY) { err = mp_mul_2d(&mminus, m2minus, &mminus); } if (err == MP_OKAY) { err = MulPow5(&mminus, m5, &mminus); } if ((err == MP_OKAY) && (m2plus > m2minus)) { err = mp_init_copy(&mplus, &mminus); if (err == MP_OKAY) { err = mp_mul_2d(&mplus, m2plus-m2minus, &mplus); } } if (err == MP_OKAY) { err = mp_init(&temp); } /* * Loop through the digits. Do division and mod by s == 2**(sd*MP_DIGIT_BIT) * by mp_digit extraction. */ i = 0; for (;;) { if (b.used <= sd) { digit = 0; } else { digit = b.dp[sd]; if (b.used > sd+1 || digit >= 10) { Tcl_Panic("wrong digit!"); } --b.used; mp_clamp(&b); } /* * Does the current digit put us on the low side of the exact value * but within roundoff of being exact? */ r1 = mp_cmp_mag(&b, (m2plus > m2minus)? &mplus : &mminus); if (r1 == MP_LT || (r1 == MP_EQ && (dPtr->w.word1 & 1) == 0)) { /* * Make sure we shouldn't be rounding *up* instead, in case the * next number above is closer. */ if (ShouldBankerRoundUpPowD(&b, sd, digit&1)) { ++digit; if (digit == 10) { *s++ = '9'; s = BumpUp(s, retval, &k); break; } } /* * Stash the last digit. */ *s++ = '0' + digit; break; } /* * Does one plus the current digit put us within roundoff of the * number? */ if (ShouldBankerRoundUpToNextPowD(&b, &mminus, sd, dPtr->w.word1 & 1, &temp)) { if (digit == 9) { *s++ = '9'; s = BumpUp(s, retval, &k); break; } ++digit; *s++ = '0' + digit; break; } /* * Have we converted all the requested digits? */ *s++ = '0' + digit; if (i == ilim) { if (ShouldBankerRoundUpPowD(&b, sd, digit&1)) { s = BumpUp(s, retval, &k); } break; } /* * Advance to the next digit. */ if (err == MP_OKAY) { err = mp_mul_d(&b, 10, &b); } if (err == MP_OKAY) { err = mp_mul_d(&mminus, 10, &mminus); } if ((err == MP_OKAY) && (m2plus > m2minus)) { err = mp_mul_2d(&mminus, m2plus-m2minus, &mplus); } ++i; } /* * Endgame - store the location of the decimal point and the end of the * string. */ if (m2plus > m2minus) { mp_clear(&mplus); } mp_clear_multi(&b, &mminus, &temp, (void *)NULL); *s = '\0'; *decpt = k; if (endPtr) { *endPtr = s; } return (err == MP_OKAY) ? retval : NULL; } /* *---------------------------------------------------------------------- * * StrictBignumConversionPowD -- * * Converts a double-precision number to a fixed-lengt string of 'ilim' * digits (or 'ilim1' if log10(d) has been overestimated). The * denominator in David Gay's conversion algorithm is known to be a power * of 2**MP_DIGIT_BIT, and hence the division in the main loop may be * replaced by a digit shift and mask. * * Results: * Returns the string of significant decimal digits, in newly allocated * memory. * * Side effects: * Stores the location of the decimal point in '*decpt' and the location * of the terminal null byte in '*endPtr'. * *---------------------------------------------------------------------- */ static inline char * StrictBignumConversionPowD( Tcl_WideUInt bw, /* Integer significand. */ int b2, int b5, /* Scale factor for the significand in the * numerator. */ int sd, /* Scale factor for the denominator. */ int k, /* Number of output digits before the decimal * point. */ int len, /* Number of digits to allocate. */ int ilim, /* Number of digits to convert if b >= s */ int ilim1, /* Number of digits to convert if b < s */ int *decpt, /* OUTPUT: Position of the decimal point. */ char **endPtr) /* OUTPUT: Position of the terminal '\0' at * the end of the returned string. */ { char *retval = (char *)Tcl_Alloc(len + 1); /* Output buffer. */ mp_int b; /* Numerator of the fraction being * converted. */ mp_digit digit; /* Current output digit. */ char *s = retval; /* Cursor in the output buffer. */ int i; /* Index in the output buffer. */ mp_err err; /* * b = bw * 2**b2 * 5**b5 */ if (mp_init_u64(&b, bw) != MP_OKAY) { return NULL; } err = MulPow5(&b, b5, &b); if (err == MP_OKAY) { err = mp_mul_2d(&b, b2, &b); } /* * Adjust if the logarithm was guessed wrong. */ if ((err == MP_OKAY) && (b.used <= sd)) { err = mp_mul_d(&b, 10, &b); ilim = ilim1; --k; } /* * Loop through the digits. Do division and mod by s == 2**(sd*MP_DIGIT_BIT) * by mp_digit extraction. */ i = 1; while (err == MP_OKAY) { if (b.used <= sd) { digit = 0; } else { digit = b.dp[sd]; if (b.used > sd+1 || digit >= 10) { Tcl_Panic("wrong digit!"); } --b.used; mp_clamp(&b); } /* * Have we converted all the requested digits? */ *s++ = '0' + digit; if (i == ilim) { if (ShouldBankerRoundUpPowD(&b, sd, digit&1)) { s = BumpUp(s, retval, &k); } while (*--s == '0') { /* do nothing */ } ++s; break; } /* * Advance to the next digit. */ err = mp_mul_d(&b, 10, &b); ++i; } /* * Endgame - store the location of the decimal point and the end of the * string. */ mp_clear(&b); *s = '\0'; *decpt = k; if (endPtr) { *endPtr = s; } return retval; } /* *---------------------------------------------------------------------- * * ShouldBankerRoundUp -- * * Tests whether a digit should be rounded up or down when finishing * bignum-based floating point conversion. * * Results: * Returns 1 if the number needs to be rounded up, 0 otherwise. * *---------------------------------------------------------------------- */ static inline int ShouldBankerRoundUp( mp_int *twor, /* 2x the remainder from thd division that * produced the last digit. */ mp_int *S, /* Denominator. */ int isodd) /* Flag == 1 if the last digit is odd. */ { int r = mp_cmp_mag(twor, S); switch (r) { case MP_EQ: return isodd; case MP_GT: return 1; default: return 0; } } /* *---------------------------------------------------------------------- * * ShouldBankerRoundUpToNext -- * * Tests whether the remainder is great enough to force rounding to the * next higher digit. * * Results: * Returns 1 if the number should be rounded up, 0 otherwise. * *---------------------------------------------------------------------- */ static inline int ShouldBankerRoundUpToNext( mp_int *b, /* Remainder from the division that produced * the last digit. */ mp_int *m, /* Numerator of the rounding tolerance. */ mp_int *S, /* Denominator. */ int isodd) /* 1 if the integer significand is odd. */ { int r; mp_int temp; /* * Compare b and S-m: this is the same as comparing B+m and S. */ if ((mp_init(&temp) != MP_OKAY) || (mp_add(b, m, &temp) != MP_OKAY)) { return 0; } r = mp_cmp_mag(&temp, S); mp_clear(&temp); switch (r) { case MP_EQ: return isodd; case MP_GT: return 1; default: return 0; } } /* *---------------------------------------------------------------------- * * ShorteningBignumConversion -- * * Convert a floating point number to a variable-length digit string * using the multiprecision method. * * Results: * Returns the string of digits. * * Side effects: * Stores the position of the decimal point in *decpt. Stores a pointer * to the end of the number in *endPtr. * *---------------------------------------------------------------------- */ static inline char * ShorteningBignumConversion( Double *dPtr, /* Original number being converted. */ Tcl_WideUInt bw, /* Integer significand and exponent. */ int b2, /* Scale factor for the significand. */ int m2plus, int m2minus, /* Scale factors for 1/2 ulp in numerator. */ int s2, int s5, /* Scale factors for denominator. */ int k, /* Guessed position of the decimal point. */ int len, /* Size of the digit buffer to allocate. */ int ilim, /* Number of digits to convert if b >= s */ int ilim1, /* Number of digits to convert if b < s */ int *decpt, /* OUTPUT: Position of the decimal point. */ char **endPtr) /* OUTPUT: Pointer to the end of the number */ { char *retval = (char *)Tcl_Alloc(len+1); /* Buffer of digits to return. */ char *s = retval; /* Cursor in the return value. */ mp_int b; /* Numerator of the result. */ mp_int mminus; /* 1/2 ulp below the result. */ mp_int mplus; /* 1/2 ulp above the result. */ mp_int S; /* Denominator of the result. */ mp_int dig; /* Current digit of the result. */ int digit; /* Current digit of the result. */ int minit = 1; /* Fudge factor for when we misguess k. */ int i; int r1; mp_err err; /* * b = bw * 2**b2 * 5**b5 * S = 2**s2 * 5*s5 */ if ((retval == NULL) || (mp_init_u64(&b, bw) != MP_OKAY)) { return NULL; } err = mp_mul_2d(&b, b2, &b); if (err == MP_OKAY) { err = mp_init_set(&S, 1); } if (err == MP_OKAY) { err = MulPow5(&S, s5, &S); } if (err == MP_OKAY) { err = mp_mul_2d(&S, s2, &S); } /* * Handle the case where we guess the position of the decimal point wrong. */ if ((err == MP_OKAY) && (mp_cmp_mag(&b, &S) == MP_LT)) { err = mp_mul_d(&b, 10, &b); minit = 10; ilim =ilim1; --k; } /* * mminus = 2**m2minus * 5**m5 */ if (err == MP_OKAY) { err = mp_init_set(&mminus, minit); } if (err == MP_OKAY) { err = mp_mul_2d(&mminus, m2minus, &mminus); } if ((err == MP_OKAY) && (m2plus > m2minus)) { err = mp_init_copy(&mplus, &mminus); if (err == MP_OKAY) { err = mp_mul_2d(&mplus, m2plus-m2minus, &mplus); } } /* * Loop through the digits. */ if (err == MP_OKAY) { err = mp_init(&dig); } i = 1; while (err == MP_OKAY) { err = mp_div(&b, &S, &dig, &b); if (dig.used > 1 || dig.dp[0] >= 10) { Tcl_Panic("wrong digit!"); } digit = dig.dp[0]; /* * Does the current digit leave us with a remainder small enough to * round to it? */ r1 = mp_cmp_mag(&b, (m2plus > m2minus)? &mplus : &mminus); if (r1 == MP_LT || (r1 == MP_EQ && (dPtr->w.word1 & 1) == 0)) { err = mp_mul_2d(&b, 1, &b); if (ShouldBankerRoundUp(&b, &S, digit&1)) { ++digit; if (digit == 10) { *s++ = '9'; s = BumpUp(s, retval, &k); break; } } *s++ = '0' + digit; break; } /* * Does the current digit leave us with a remainder large enough to * commit to rounding up to the next higher digit? */ if (ShouldBankerRoundUpToNext(&b, &mminus, &S, dPtr->w.word1 & 1)) { ++digit; if (digit == 10) { *s++ = '9'; s = BumpUp(s, retval, &k); break; } *s++ = '0' + digit; break; } /* * Have we converted all the requested digits? */ *s++ = '0' + digit; if ((err == MP_OKAY) && (i == ilim)) { err = mp_mul_2d(&b, 1, &b); if (ShouldBankerRoundUp(&b, &S, digit&1)) { s = BumpUp(s, retval, &k); } break; } /* * Advance to the next digit. */ if ((err == MP_OKAY) && (s5 > 0)) { /* * Can possibly shorten the denominator. */ err = mp_mul_2d(&b, 1, &b); if (err == MP_OKAY) { err = mp_mul_2d(&mminus, 1, &mminus); } if ((err == MP_OKAY) && (m2plus > m2minus)) { err = mp_mul_2d(&mplus, 1, &mplus); } if (err == MP_OKAY) { err = mp_div_d(&S, 5, &S, NULL); } --s5; /* * IDEA: It might possibly be a win to fall back to int64_t * arithmetic here if S < 2**64/10. But it's a win only for * a fairly narrow range of magnitudes so perhaps not worth * bothering. We already know that we shorten the * denominator by at least 1 mp_digit, perhaps 2, as we do * the conversion for 17 digits of significance. * Possible savings: * 10**26 1 trip through loop before fallback possible * 10**27 1 trip * 10**28 2 trips * 10**29 3 trips * 10**30 4 trips * 10**31 5 trips * 10**32 6 trips * 10**33 7 trips * 10**34 8 trips * 10**35 9 trips * 10**36 10 trips * 10**37 11 trips * 10**38 12 trips * 10**39 13 trips * 10**40 14 trips * 10**41 15 trips * 10**42 16 trips * thereafter no gain. */ } else if (err == MP_OKAY) { err = mp_mul_d(&b, 10, &b); if (err == MP_OKAY) { err = mp_mul_d(&mminus, 10, &mminus); } if ((err == MP_OKAY) && (m2plus > m2minus)) { err = mp_mul_2d(&mplus, 10, &mplus); } } ++i; } /* * Endgame - store the location of the decimal point and the end of the * string. */ if (m2plus > m2minus) { mp_clear(&mplus); } mp_clear_multi(&b, &mminus, &dig, &S, (void *)NULL); *s = '\0'; *decpt = k; if (endPtr) { *endPtr = s; } return retval; } /* *---------------------------------------------------------------------- * * StrictBignumConversion -- * * Convert a floating point number to a fixed-length digit string using * the multiprecision method. * * Results: * Returns the string of digits. * * Side effects: * Stores the position of the decimal point in *decpt. Stores a pointer * to the end of the number in *endPtr. * *---------------------------------------------------------------------- */ static inline char * StrictBignumConversion( Tcl_WideUInt bw, /* Integer significand and exponent. */ int b2, /* Scale factor for the significand. */ int s2, int s5, /* Scale factors for denominator. */ int k, /* Guessed position of the decimal point. */ int len, /* Size of the digit buffer to allocate. */ int ilim, /* Number of digits to convert if b >= s */ int ilim1, /* Number of digits to convert if b < s */ int *decpt, /* OUTPUT: Position of the decimal point. */ char **endPtr) /* OUTPUT: Pointer to the end of the number */ { char *retval = (char *)Tcl_Alloc(len+1); /* Buffer of digits to return. */ char *s = retval; /* Cursor in the return value. */ mp_int b; /* Numerator of the result. */ mp_int S; /* Denominator of the result. */ mp_int dig; /* Current digit of the result. */ int digit; /* Current digit of the result. */ int g; /* Size of the current digit ground. */ int i, j; mp_err err; /* * b = bw * 2**b2 * 5**b5 * S = 2**s2 * 5*s5 */ if (mp_init(&dig) != MP_OKAY) { return NULL; } if (mp_init_u64(&b, bw) != MP_OKAY) { mp_clear(&dig); return NULL; } err = mp_mul_2d(&b, b2, &b); if (err == MP_OKAY) { err = mp_init_set(&S, 1); } if (err == MP_OKAY) { err = MulPow5(&S, s5, &S); if (err == MP_OKAY) { err = mp_mul_2d(&S, s2, &S); } } /* * Handle the case where we guess the position of the decimal point wrong. */ if ((mp_cmp_mag(&b, &S) == MP_LT) && (mp_mul_d(&b, 10, &b) == MP_OKAY)) { ilim =ilim1; --k; } /* * Convert the leading digit. */ i = 0; err = mp_div(&b, &S, &dig, &b); if (dig.used > 1 || dig.dp[0] >= 10) { Tcl_Panic("wrong digit!"); } digit = dig.dp[0]; /* * Is a single digit all that was requested? */ *s++ = '0' + digit; if (++i >= ilim) { if ((mp_mul_2d(&b, 1, &b) == MP_OKAY) && ShouldBankerRoundUp(&b, &S, digit&1)) { s = BumpUp(s, retval, &k); } } else { while (err == MP_OKAY) { /* * Shift by a group of digits. */ g = ilim - i; if (g > DIGIT_GROUP) { g = DIGIT_GROUP; } if (s5 >= g) { err = mp_div_d(&S, dpow5[g], &S, NULL); s5 -= g; } else if (s5 > 0) { err = mp_div_d(&S, dpow5[s5], &S, NULL); if (err == MP_OKAY) { err = mp_mul_d(&b, dpow5[g - s5], &b); } s5 = 0; } else { err = mp_mul_d(&b, dpow5[g], &b); } if (err == MP_OKAY) { err = mp_mul_2d(&b, g, &b); } /* * As with the shortening bignum conversion, it's possible at this * point that we will have reduced the denominator to less than * 2**64/10, at which point it would be possible to fall back to * to int64_t arithmetic. But the potential payoff is tremendously * less - unless we're working in F format - because we know that * three groups of digits will always suffice for %#.17e, the * longest format that doesn't introduce empty precision. * * Extract the next group of digits. */ if ((err != MP_OKAY) || (mp_div(&b, &S, &dig, &b) != MP_OKAY) || (dig.used > 1)) { Tcl_Panic("wrong digit!"); } digit = dig.dp[0]; for (j = g-1; j >= 0; --j) { int t = itens[j]; *s++ = digit / t + '0'; digit %= t; } i += g; /* * Have we converted all the requested digits? */ if (i == ilim) { if ((mp_mul_2d(&b, 1, &b) == MP_OKAY) && ShouldBankerRoundUp(&b, &S, digit&1)) { s = BumpUp(s, retval, &k); } break; } } } while (*--s == '0') { /* do nothing */ } ++s; /* * Endgame - store the location of the decimal point and the end of the * string. */ mp_clear_multi(&b, &S, &dig, (void *)NULL); *s = '\0'; *decpt = k; if (endPtr) { *endPtr = s; } return retval; } /* *---------------------------------------------------------------------- * * TclDoubleDigits -- * * Core of Tcl's conversion of double-precision floating point numbers to * decimal. * * Results: * Returns a newly-allocated string of digits. * * Side effects: * Sets *decpt to the index of the character in the string before the * place that the decimal point should go. If 'endPtr' is not NULL, sets * endPtr to point to the terminating '\0' byte of the string. Sets *sign * to 1 if a minus sign should be printed with the number, or 0 if a plus * sign (or no sign) should appear. * * This function is a service routine that produces the string of digits for * floating-point-to-decimal conversion. It can do a number of things * according to the 'flags' argument. Valid values for 'flags' include: * TCL_DD_SHORTEST - This is the default for floating point conversion. * It constructs the shortest string of * digits that will reconvert to the given number when scanned. * For floating point numbers that are exactly between two * decimal numbers, it resolves using the 'round to even' rule. * With this value, the 'ndigits' parameter is ignored. * TCL_DD_E_FORMAT - This value is used to prepare numbers for %e format * conversion. It constructs a string of at most 'ndigits' digits, * choosing the one that is closest to the given number (and * resolving ties with 'round to even'). It is allowed to return * fewer than 'ndigits' if the number converts exactly; if the * TCL_DD_E_FORMAT|TCL_DD_SHORTEST is supplied instead, it * also returns fewer digits if the shorter string will still * reconvert without loss to the given input number. In any case, * strings of trailing zeroes are suppressed. * TCL_DD_F_FORMAT - This value is used to prepare numbers for %f format * conversion. It requests that conversion proceed until * 'ndigits' digits after the decimal point have been converted. * It is possible for this format to result in a zero-length * string if the number is sufficiently small. Again, it is * permissible for TCL_DD_F_FORMAT to return fewer digits for a * number that converts exactly, and changing the argument to * TCL_DD_F_FORMAT|TCL_DD_SHORTEST will allow the routine * also to return fewer digits if the shorter string will still * reconvert without loss to the given input number. Strings of * trailing zeroes are suppressed. * * To any of these flags may be OR'ed TCL_DD_NO_QUICK; this flag requires * all calculations to be done in exact arithmetic. Normally, E and F * format with fewer than about 14 digits will be done with a quick * floating point approximation and fall back on the exact arithmetic * only if the input number is close enough to the midpoint between two * decimal strings that more precision is needed to resolve which string * is correct. * * The value stored in the 'decpt' argument on return may be negative * (indicating that the decimal point falls to the left of the string) or * greater than the length of the string. In addition, the value -9999 is used * as a sentinel to indicate that the string is one of the special values * "Infinity" and "NaN", and that no decimal point should be inserted. * *---------------------------------------------------------------------- */ char * TclDoubleDigits( double dv, /* Number to convert. */ int ndigits, /* Number of digits requested. */ int flags, /* Conversion flags. */ int *decpt, /* OUTPUT: Position of the decimal point. */ int *sign, /* OUTPUT: 1 if the result is negative. */ char **endPtr) /* OUTPUT: If not NULL, receives a pointer to * one character beyond the end of the * returned string. */ { Double d; /* Union for deconstructing doubles. */ Tcl_WideUInt bw; /* Integer significand. */ int be; /* Power of 2 by which b must be multiplied */ int bbits; /* Number of bits needed to represent b. */ int denorm; /* Flag == 1 iff the input number was * denormalized. */ int k; /* Estimate of floor(log10(d)). */ int k_check; /* Flag == 1 if d is near enough to a power of * ten that k must be checked. */ int b2, b5, s2, s5; /* Powers of 2 and 5 in the numerator and * denominator of intermediate results. */ int ilim = -1, ilim1 = -1; /* Number of digits to convert, and number to * convert if log10(d) has been * overestimated. */ char *retval; /* Return value from this function. */ int i = -1; /* * Put the input number into a union for bit-whacking. */ d.d = dv; /* * Handle the cases of negative numbers (by taking the absolute value: * this includes -Inf and -NaN!), infinity, Not a Number, and zero. */ TakeAbsoluteValue(&d, sign); if ((d.w.word0 & EXP_MASK) == EXP_MASK) { return FormatInfAndNaN(&d, decpt, endPtr); } if (d.d == 0.0) { return FormatZero(decpt, endPtr); } /* * Unpack the floating point into a wide integer and an exponent. * Determine the number of bits that the big integer requires, and compute * a quick approximation (which may be one too high) of ceil(log10(d.d)). */ denorm = ((d.w.word0 & EXP_MASK) == 0); DoubleToExpAndSig(d.d, &bw, &be, &bbits); k = ApproximateLog10(bw, be, bbits); k = BetterLog10(d.d, k, &k_check); /* At this point, we have: * d is the number to convert. * bw are significand and exponent: d == bw*2**be, * bbits is the length of bw: 2**bbits-1 <= bw < 2**bbits * k is either ceil(log10(d)) or ceil(log10(d))+1. k_check is 0 if we * know that k is exactly ceil(log10(d)) and 1 if we need to check. * We want a rational number * r = b * 10**(1-k) = bw * 2**b2 * 5**b5 / (2**s2 / 5**s5), * with b2, b5, s2, s5 >= 0. Note that the most significant decimal * digit is floor(r) and that successive digits can be obtained by * setting r <- 10*floor(r) (or b <= 10 * (b % S)). Find appropriate * b2, b5, s2, s5. */ ComputeScale(be, k, &b2, &b5, &s2, &s5); /* * Correct an incorrect caller-supplied 'ndigits'. Also determine: * i = The maximum number of decimal digits that will be returned in the * formatted string. This is k + 1 + ndigits for F format, 18 for * shortest, and ndigits for E format. * ilim = The number of significant digits to convert if k has been * guessed correctly. This is -1 for shortest (which * stop when all significance has been lost), 'ndigits' for E * format, and 'k + 1 + ndigits' for F format. * ilim1 = The minimum number of significant digits to convert if k has * been guessed 1 too high. This, too, is -1 for shortest, * and 'ndigits' for E format, but it's 'ndigits-1' for F * format. */ SetPrecisionLimits(flags, k, &ndigits, &i, &ilim, &ilim1); /* * Try to do low-precision conversion in floating point rather than * resorting to expensive multiprecision arithmetic. */ if (ilim >= 0 && ilim <= QUICK_MAX && !(flags & TCL_DD_NO_QUICK)) { retval = QuickConversion(d.d, k, k_check, flags, i, ilim, ilim1, decpt, endPtr); if (retval != NULL) { return retval; } } /* * For shortening conversions, determine the upper and lower bounds for * the remainder at which we can stop. * m+ = (2**m2plus * 5**m5) / (2**s2 * 5**s5) is the limit on the high * side, and * m- = (2**m2minus * 5**m5) / (2**s2 * 5**s5) is the limit on the low * side. * We may need to increase s2 to put m2plus, m2minus, b2 over a common * denominator. */ if (flags & TCL_DD_SHORTEST) { int m2minus = b2; int m2plus; int m5 = b5; int len = i; /* * Find the quantity i so that (2**i*5**b5)/(2**s2*5**s5) is 1/2 unit * in the least significant place of the floating point number. */ if (denorm) { i = be + EXPONENT_BIAS + (FP_PRECISION-1); } else { i = 1 + FP_PRECISION - bbits; } b2 += i; s2 += i; /* * Reduce the fractions to lowest terms, since the above calculation * may have left excess powers of 2 in numerator and denominator. */ CastOutPowersOf2(&b2, &m2minus, &s2); /* * In the special case where bw==1, the nearest floating point number * to it on the low side is 1/4 ulp below it. Adjust accordingly. */ m2plus = m2minus; if (!denorm && bw == 1) { ++b2; ++s2; ++m2plus; } if (s5+1 < N_LOG2POW5 && s2+1 + log2pow5[s5+1] < 64) { /* * If 10*2**s2*5**s5 == 2**(s2+1)+5**(s5+1) fits in a 64-bit word, * then all our intermediate calculations can be done using exact * 64-bit arithmetic with no need for expensive multiprecision * operations. (This will be true for all numbers in the range * [1.0e-3 .. 1.0e+24]). */ return ShorteningInt64Conversion(&d, bw, b2, b5, m2plus, m2minus, m5, s2, s5, k, len, ilim, ilim1, decpt, endPtr); } else if (s5 == 0) { /* * The denominator is a power of 2, so we can replace division by * digit shifts. First we round up s2 to a multiple of MP_DIGIT_BIT, * and adjust m2 and b2 accordingly. Then we launch into a version * of the comparison that's specialized for the 'power of mp_digit * in the denominator' case. */ if (s2 % MP_DIGIT_BIT != 0) { int delta = MP_DIGIT_BIT - (s2 % MP_DIGIT_BIT); b2 += delta; m2plus += delta; m2minus += delta; s2 += delta; } return ShorteningBignumConversionPowD(&d, bw, b2, b5, m2plus, m2minus, m5, s2/MP_DIGIT_BIT, k, len, ilim, ilim1, decpt, endPtr); } else { /* * Alas, there's no helpful special case; use full-up bignum * arithmetic for the conversion. */ return ShorteningBignumConversion(&d, bw, b2, m2plus, m2minus, s2, s5, k, len, ilim, ilim1, decpt, endPtr); } } else { /* * Non-shortening conversion. */ int len = i; /* * Reduce numerator and denominator to lowest terms. */ if (b2 >= s2 && s2 > 0) { b2 -= s2; s2 = 0; } else if (s2 >= b2 && b2 > 0) { s2 -= b2; b2 = 0; } if (s5+1 < N_LOG2POW5 && s2+1 + log2pow5[s5+1] < 64) { /* * If 10*2**s2*5**s5 == 2**(s2+1)+5**(s5+1) fits in a 64-bit word, * then all our intermediate calculations can be done using exact * 64-bit arithmetic with no need for expensive multiprecision * operations. */ return StrictInt64Conversion(bw, b2, b5, s2, s5, k, len, ilim, ilim1, decpt, endPtr); } else if (s5 == 0) { /* * The denominator is a power of 2, so we can replace division by * digit shifts. First we round up s2 to a multiple of MP_DIGIT_BIT, * and adjust m2 and b2 accordingly. Then we launch into a version * of the comparison that's specialized for the 'power of mp_digit * in the denominator' case. */ if (s2 % MP_DIGIT_BIT != 0) { int delta = MP_DIGIT_BIT - (s2 % MP_DIGIT_BIT); b2 += delta; s2 += delta; } return StrictBignumConversionPowD(bw, b2, b5, s2/MP_DIGIT_BIT, k, len, ilim, ilim1, decpt, endPtr); } else { /* * There are no helpful special cases, but at least we know in * advance how many digits we will convert. We can run the * conversion in steps of DIGIT_GROUP digits, so as to have many * fewer mp_int divisions. */ return StrictBignumConversion(bw, b2, s2, s5, k, len, ilim, ilim1, decpt, endPtr); } } } /* *---------------------------------------------------------------------- * * TclInitDoubleConversion -- * * Initializes constants that are needed for conversions to and from * 'double' * * Results: * None. * * Side effects: * The log base 2 of the floating point radix, the number of bits in a * double mantissa, and a table of the powers of five and ten are * computed and stored. * *---------------------------------------------------------------------- */ void TclInitDoubleConversion(void) { int i; int x; Tcl_WideUInt u; double d; #ifdef IEEE_FLOATING_POINT union { double dv; Tcl_WideUInt iv; } bitwhack; #endif mp_err err = MP_OKAY; #if defined(__sgi) && defined(_COMPILER_VERSION) union fpc_csr mipsCR; mipsCR.fc_word = get_fpc_csr(); mipsCR.fc_struct.flush = 0; set_fpc_csr(mipsCR.fc_word); #endif /* * Initialize table of powers of 10 expressed as wide integers. */ maxpow10_wide = (int) floor(sizeof(Tcl_WideUInt) * CHAR_BIT * log(2.) / log(10.)); pow10_wide = (Tcl_WideUInt *) Tcl_Alloc((maxpow10_wide + 1) * sizeof(Tcl_WideUInt)); u = 1; for (i = 0; i < maxpow10_wide; ++i) { pow10_wide[i] = u; u *= 10; } pow10_wide[i] = u; /* * Determine how many bits of precision a double has, and how many decimal * digits that represents. */ if (frexp((double) FLT_RADIX, &log2FLT_RADIX) != 0.5) { Tcl_Panic("This code doesn't work on a decimal machine!"); } log2FLT_RADIX--; mantBits = DBL_MANT_DIG * log2FLT_RADIX; d = 1.0; /* * Initialize a table of powers of ten that can be exactly represented in * a double. */ x = (int) (DBL_MANT_DIG * log((double) FLT_RADIX) / log(5.0)); if (x < MAXPOW) { mmaxpow = x; } else { mmaxpow = MAXPOW; } for (i=0 ; i<=mmaxpow ; ++i) { pow10vals[i] = d; d *= 10.0; } /* * Initialize a table of large powers of five. */ for (i=0; i<9; ++i) { err = err || mp_init(pow5 + i); } mp_set_u64(pow5, 5); for (i=0; i<8; ++i) { err = err || mp_sqr(pow5+i, pow5+i+1); } err = err || mp_init_u64(pow5_13, 1220703125); for (i = 1; i < 5; ++i) { err = err || mp_init(pow5_13 + i); err = err || mp_sqr(pow5_13 + i - 1, pow5_13 + i); } if (err != MP_OKAY) { Tcl_Panic("out of memory"); } /* * Determine the number of decimal digits to the left and right of the * decimal point in the largest and smallest double, the smallest double * that differs from zero, and the number of mp_digits needed to represent * the significand of a double. */ maxDigits = (int) ((DBL_MAX_EXP * log((double) FLT_RADIX) + 0.5 * log(10.)) / log(10.)); minDigits = (int) floor((DBL_MIN_EXP - DBL_MANT_DIG) * log((double) FLT_RADIX) / log(10.)); log10_DIGIT_MAX = (int) floor(MP_DIGIT_BIT * log(2.) / log(10.)); /* * Nokia 770's software-emulated floating point is "middle endian": the * bytes within a 32-bit word are little-endian (like the native * integers), but the two words of a 'double' are presented most * significant word first. */ #ifdef IEEE_FLOATING_POINT bitwhack.dv = 1.000000238418579; /* 3ff0 0000 4000 0000 */ if ((bitwhack.iv >> 32) == 0x3FF00000) { n770_fp = 0; } else if ((bitwhack.iv & 0xFFFFFFFF) == 0x3FF00000) { n770_fp = 1; } else { Tcl_Panic("unknown floating point word order on this machine"); } #endif } /* *---------------------------------------------------------------------- * * TclFinalizeDoubleConversion -- * * Cleans up this file on exit. * * Results: * None * * Side effects: * Memory allocated by TclInitDoubleConversion is freed. * *---------------------------------------------------------------------- */ void TclFinalizeDoubleConversion(void) { int i; Tcl_Free(pow10_wide); for (i=0; i<9; ++i) { mp_clear(pow5 + i); } for (i=0; i < 5; ++i) { mp_clear(pow5_13 + i); } } /* *---------------------------------------------------------------------- * * Tcl_InitBignumFromDouble -- * * Extracts the integer part of a double and converts it to an arbitrary * precision integer. * * Results: * None. * * Side effects: * Initializes the bignum supplied, and stores the converted number in * it. * *---------------------------------------------------------------------- */ int Tcl_InitBignumFromDouble( Tcl_Interp *interp, /* For error message. */ double d, /* Number to convert. */ void *big) /* Place to store the result. */ { double fract; int expt; mp_err err; mp_int *b = (mp_int *)big; /* * Infinite values can't convert to bignum. */ if (isinf(d)) { if (interp != NULL) { const char *s = "integer value too large to represent"; Tcl_SetObjResult(interp, Tcl_NewStringObj(s, -1)); Tcl_SetErrorCode(interp, "ARITH", "IOVERFLOW", s, (char *)NULL); } return TCL_ERROR; } fract = frexp(d, &expt); if (expt <= 0) { err = mp_init(b); mp_zero(b); } else { Tcl_WideInt w = (Tcl_WideInt)ldexp(fract, mantBits); int shift = expt - mantBits; err = mp_init_i64(b, w); if (err != MP_OKAY) { /* just skip */ } else if (shift < 0) { err = mp_div_2d(b, -shift, b, NULL); } else if (shift > 0) { err = mp_mul_2d(b, shift, b); } } if (err != MP_OKAY) { return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclBignumToDouble -- * * Convert an arbitrary-precision integer to a native floating point * number. * * Results: * Returns the converted number. Sets errno to ERANGE if the number is * too large to convert. * *---------------------------------------------------------------------- */ double TclBignumToDouble( const void *big) /* Integer to convert. */ { mp_int b; int bits, shift, i, lsb; double r; mp_err err; const mp_int *a = (const mp_int *)big; /* * We need a 'mantBits'-bit significand. Determine what shift will * give us that. */ bits = mp_count_bits(a); if (bits > DBL_MAX_EXP*log2FLT_RADIX) { errno = ERANGE; if (mp_isneg(a)) { return -HUGE_VAL; } else { return HUGE_VAL; } } shift = mantBits - bits; /* * If shift > 0, shift the significand left by the requisite number of * bits. If shift == 0, the significand is already exactly 'mantBits' * in length. If shift < 0, we will need to shift the significand right * by the requisite number of bits, and round it. If the '1-shift' * least significant bits are 0, but the 'shift'th bit is nonzero, * then the significand lies exactly between two values and must be * 'rounded to even'. */ err = mp_init(&b); if (err != MP_OKAY) { /* just skip */ } else if (shift == 0) { err = mp_copy(a, &b); } else if (shift > 0) { err = mp_mul_2d(a, shift, &b); } else if (shift < 0) { lsb = mp_cnt_lsb(a); if (lsb == -1-shift) { /* * Round to even */ err = mp_div_2d(a, -shift, &b, NULL); if ((err == MP_OKAY) && mp_isodd(&b)) { if (mp_isneg(&b)) { err = mp_sub_d(&b, 1, &b); } else { err = mp_add_d(&b, 1, &b); } } } else { /* * Ordinary rounding */ err = mp_div_2d(a, -1-shift, &b, NULL); if (err != MP_OKAY) { /* just skip */ } else if (mp_isneg(&b)) { err = mp_sub_d(&b, 1, &b); } else { err = mp_add_d(&b, 1, &b); } err = mp_div_2d(&b, 1, &b, NULL); } } /* * Accumulate the result, one mp_digit at a time. */ if (err != MP_OKAY) { return 0.0; } r = 0.0; for (i = b.used-1; i>=0; --i) { r = ldexp(r, MP_DIGIT_BIT) + b.dp[i]; } mp_clear(&b); /* * Scale the result to the correct number of bits. */ r = ldexp(r, bits - mantBits); /* * Return the result with the appropriate sign. */ if (mp_isneg(a)) { return -r; } else { return r; } } /* *---------------------------------------------------------------------- * * TclCeil -- * * Computes the smallest floating point number that is at least the * mp_int argument. * * Results: * Returns the floating point number. * *---------------------------------------------------------------------- */ double TclCeil( const void *big) /* Integer to convert. */ { double r = 0.0; mp_int b; mp_err err; const mp_int *a = (const mp_int *)big; err = mp_init(&b); if ((err == MP_OKAY) && mp_isneg(a)) { err = mp_neg(a, &b); r = -TclFloor(&b); } else { int bits = mp_count_bits(a); if (bits > DBL_MAX_EXP*log2FLT_RADIX) { r = HUGE_VAL; } else { int i, exact = 1, shift = mantBits - bits; if (err != MP_OKAY) { /* just skip */ } else if (shift > 0) { err = mp_mul_2d(a, shift, &b); } else if (shift < 0) { mp_int d; err = mp_init(&d); if (err == MP_OKAY) { err = mp_div_2d(a, -shift, &b, &d); } exact = mp_iszero(&d); mp_clear(&d); } else { err = mp_copy(a, &b); } if ((err == MP_OKAY) && !exact) { err = mp_add_d(&b, 1, &b); } if (err != MP_OKAY) { return 0.0; } for (i=b.used-1 ; i>=0 ; --i) { r = ldexp(r, MP_DIGIT_BIT) + b.dp[i]; } r = ldexp(r, bits - mantBits); } } mp_clear(&b); return r; } /* *---------------------------------------------------------------------- * * TclFloor -- * * Computes the largest floating point number less than or equal to the * mp_int argument. * * Results: * Returns the floating point value. * *---------------------------------------------------------------------- */ double TclFloor( const void *big) /* Integer to convert. */ { double r = 0.0; mp_int b; mp_err err; const mp_int *a = (const mp_int *)big; err = mp_init(&b); if ((err == MP_OKAY) && mp_isneg(a)) { err = mp_neg(a, &b); r = -TclCeil(&b); } else { int bits = mp_count_bits(a); if (bits > DBL_MAX_EXP*log2FLT_RADIX) { r = DBL_MAX; } else { int i, shift = mantBits - bits; if (shift > 0) { err = mp_mul_2d(a, shift, &b); } else if (shift < 0) { err = mp_div_2d(a, -shift, &b, NULL); } else { err = mp_copy(a, &b); } if (err != MP_OKAY) { return 0.0; } for (i=b.used-1 ; i>=0 ; --i) { r = ldexp(r, MP_DIGIT_BIT) + b.dp[i]; } r = ldexp(r, bits - mantBits); } } mp_clear(&b); return r; } /* *---------------------------------------------------------------------- * * BignumToBiasedFrExp -- * * Convert an arbitrary-precision integer to a native floating point * number in the range [0.5,1) times a power of two. NOTE: Intentionally * converts to a number that's a few ulp too small, so that * RefineApproximation will not overflow near the high end of the * machine's arithmetic range. * * Results: * Returns the converted number. * * Side effects: * Stores the exponent of two in 'machexp'. * *---------------------------------------------------------------------- */ static double BignumToBiasedFrExp( const mp_int *a, /* Integer to convert. */ int *machexp) /* Power of two. */ { mp_int b; int bits; int shift; int i; double r; mp_err err = MP_OKAY; /* * Determine how many bits we need, and extract that many from the input. * Round to nearest unit in the last place. */ bits = mp_count_bits(a); shift = mantBits - 2 - bits; if (mp_init(&b)) { return 0.0; } if (shift > 0) { err = mp_mul_2d(a, shift, &b); } else if (shift < 0) { err = mp_div_2d(a, -shift, &b, NULL); } else { err = mp_copy(a, &b); } /* * Accumulate the result, one mp_digit at a time. */ r = 0.0; if (err == MP_OKAY) { for (i=b.used-1; i>=0; --i) { r = ldexp(r, MP_DIGIT_BIT) + b.dp[i]; } } mp_clear(&b); /* * Return the result with the appropriate sign. */ *machexp = bits - mantBits + 2; return (mp_isneg(a) ? -r : r); } /* *---------------------------------------------------------------------- * * Pow10TimesFrExp -- * * Multiply a power of ten by a number expressed as fraction and * exponent. * * Results: * Returns the significand of the result. * * Side effects: * Overwrites the 'machexp' parameter with the exponent of the result. * * Assumes that 'exponent' is such that 10**exponent would be a double, even * though 'fraction*10**(machexp+exponent)' might overflow. * *---------------------------------------------------------------------- */ static double Pow10TimesFrExp( int exponent, /* Power of 10 to multiply by. */ double fraction, /* Significand of multiplicand. */ int *machexp) /* On input, exponent of multiplicand. On * output, exponent of result. */ { int i, j; int expt = *machexp; double retval = fraction; if (exponent > 0) { /* * Multiply by 10**exponent. */ retval = frexp(retval * pow10vals[exponent & 0xF], &j); expt += j; for (i=4; i<9; ++i) { if (exponent & (1<> 32) & 0xFFFFFFFF) | (w << 32)); } #endif /* *---------------------------------------------------------------------- * * TclNokia770Doubles -- * * Transpose the two words of a number for Nokia 770 floating point * handling. * *---------------------------------------------------------------------- */ int TclNokia770Doubles(void) { return n770_fp; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclStringObj.c0000644000175000017500000034741614726623136015761 0ustar sergeisergei/* * tclStringObj.c -- * * This file contains functions that implement string operations on Tcl * objects. Some string operations work with UTF-8 encoding forms. * Functions that require knowledge of the width of each character, * such as indexing, operate on fixed width encoding forms such as UTF-32. * * Conceptually, a string is a sequence of Unicode code points. Internally * it may be stored in an encoding form such as a modified version of * UTF-8 or UTF-32. * * The String object is optimized for the case where each UTF char * in a string is only one byte. In this case, we store the value of * numChars, but we don't store the fixed form encoding (unless * Tcl_GetUnicode is explicitly called). * * The String object type stores one or both formats. The default * behavior is to store UTF-8. Once UTF-16/UTF32 is calculated, it is * stored in the internal rep for future access (without an additional * O(n) cost). * * To allow many appends to be done to an object without constantly * reallocating space, we allocate double the space and use the * internal representation to keep track of how much space is used vs. * allocated. * * Copyright © 1995-1997 Sun Microsystems, Inc. * Copyright © 1999 Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclTomMath.h" #include "tclStringRep.h" #include /* * Prototypes for functions defined later in this file: */ static void AppendPrintfToObjVA(Tcl_Obj *objPtr, const char *format, va_list argList); static void AppendUnicodeToUnicodeRep(Tcl_Obj *objPtr, const Tcl_UniChar *unicode, Tcl_Size appendNumChars); static void AppendUnicodeToUtfRep(Tcl_Obj *objPtr, const Tcl_UniChar *unicode, Tcl_Size numChars); static void AppendUtfToUnicodeRep(Tcl_Obj *objPtr, const char *bytes, Tcl_Size numBytes); static void AppendUtfToUtfRep(Tcl_Obj *objPtr, const char *bytes, Tcl_Size numBytes); static void DupStringInternalRep(Tcl_Obj *objPtr, Tcl_Obj *copyPtr); static Tcl_Size ExtendStringRepWithUnicode(Tcl_Obj *objPtr, const Tcl_UniChar *unicode, Tcl_Size numChars); static void ExtendUnicodeRepWithString(Tcl_Obj *objPtr, const char *bytes, Tcl_Size numBytes, Tcl_Size numAppendChars); static void FillUnicodeRep(Tcl_Obj *objPtr); static void FreeStringInternalRep(Tcl_Obj *objPtr); static void GrowStringBuffer(Tcl_Obj *objPtr, Tcl_Size needed, int flag); static void GrowUnicodeBuffer(Tcl_Obj *objPtr, Tcl_Size needed); static int SetStringFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); static void SetUnicodeObj(Tcl_Obj *objPtr, const Tcl_UniChar *unicode, Tcl_Size numChars); static Tcl_Size UnicodeLength(const Tcl_UniChar *unicode); static void UpdateStringOfString(Tcl_Obj *objPtr); #define ISCONTINUATION(bytes) (\ ((bytes)[0] & 0xC0) == 0x80) /* * The structure below defines the string Tcl object type by means of * functions that can be invoked by generic object code. */ const Tcl_ObjType tclStringType = { "string", /* name */ FreeStringInternalRep, /* freeIntRepPro */ DupStringInternalRep, /* dupIntRepProc */ UpdateStringOfString, /* updateStringProc */ SetStringFromAny, /* setFromAnyProc */ TCL_OBJTYPE_V0 }; /* * TCL STRING GROWTH ALGORITHM * * When growing strings (during an append, for example), the following growth * algorithm is used: * * Attempt to allocate 2 * (originalLength + appendLength) * On failure: * attempt to allocate originalLength + 2*appendLength + TCL_MIN_GROWTH * * This algorithm allows very good performance, as it rapidly increases the * memory allocated for a given string, which minimizes the number of * reallocations that must be performed. However, using only the doubling * algorithm can lead to a significant waste of memory. In particular, it may * fail even when there is sufficient memory available to complete the append * request (but there is not 2*totalLength memory available). So when the * doubling fails (because there is not enough memory available), the * algorithm requests a smaller amount of memory, which is still enough to * cover the request, but which hopefully will be less than the total * available memory. * * The addition of TCL_MIN_GROWTH allows for efficient handling of very * small appends. Without this extra slush factor, a sequence of several small * appends would cause several memory allocations. As long as * TCL_MIN_GROWTH is a reasonable size, we can avoid that behavior. * * The growth algorithm can be tuned by adjusting the following parameters: * * TCL_MIN_GROWTH Additional space, in bytes, to allocate when * the double allocation has failed. Default is * 1024 (1 kilobyte). See tclInt.h. */ #ifndef TCL_MIN_UNICHAR_GROWTH #define TCL_MIN_UNICHAR_GROWTH TCL_MIN_GROWTH/sizeof(Tcl_UniChar) #endif static void GrowStringBuffer( Tcl_Obj *objPtr, Tcl_Size needed, /* Not including terminating nul */ int flag) /* If 0, try to overallocate */ { /* * Preconditions: * TclHasInternalRep(objPtr, &tclStringType) * needed > stringPtr->allocated * flag || objPtr->bytes != NULL */ String *stringPtr = GET_STRING(objPtr); char *ptr; Tcl_Size capacity; assert(needed <= TCL_SIZE_MAX - 1); needed += 1; /* Include terminating nul */ if (objPtr->bytes == &tclEmptyString) { objPtr->bytes = NULL; } /* * In code below, note 'capacity' and 'needed' include terminating nul, * while stringPtr->allocated does not. */ if (flag == 0 || stringPtr->allocated > 0) { ptr = (char *)TclReallocEx(objPtr->bytes, needed, &capacity); } else { /* Allocate exact size */ ptr = (char *)Tcl_Realloc(objPtr->bytes, needed); capacity = needed; } objPtr->bytes = ptr; stringPtr->allocated = capacity - 1; /* Does not include slot for end nul */ } static void GrowUnicodeBuffer( Tcl_Obj *objPtr, Tcl_Size needed) { /* * Preconditions: * TclHasInternalRep(objPtr, &tclStringType) * needed > stringPtr->maxChars */ String *stringPtr = GET_STRING(objPtr); Tcl_Size maxChars; /* Note STRING_MAXCHARS already takes into account space for nul */ if (needed > STRING_MAXCHARS) { Tcl_Panic("max size for a Tcl unicode rep (%" TCL_Z_MODIFIER "d bytes) exceeded", STRING_MAXCHARS); } if (stringPtr->maxChars > 0) { /* Expansion - try allocating extra space */ stringPtr = (String *) TclReallocElemsEx(stringPtr, needed + 1, /* +1 for nul */ sizeof(Tcl_UniChar), offsetof(String, unicode), &maxChars); maxChars -= 1; /* End nul not included */ } else { /* * First allocation - just big enough. Note needed does * not include terminating nul but STRING_SIZE does */ stringPtr = (String *)Tcl_Realloc(stringPtr, STRING_SIZE(needed)); maxChars = needed; } stringPtr->maxChars = maxChars; SET_STRING(objPtr, stringPtr); } /* *---------------------------------------------------------------------- * * Tcl_NewStringObj -- * * This function is normally called when not debugging: i.e., when * TCL_MEM_DEBUG is not defined. It creates a new string object and * initializes it from the byte pointer and length arguments. * * When TCL_MEM_DEBUG is defined, this function just returns the result * of calling the debugging version Tcl_DbNewStringObj. * * Results: * A newly created string object is returned that has ref count zero. * * Side effects: * The new object's internal string representation will be set to a copy * of the length bytes starting at "bytes". If "length" is TCL_INDEX_NONE, use * bytes up to the first NUL byte; i.e., assume "bytes" points to a * C-style NUL-terminated string. The object's type is set to NULL. An * extra NUL is added to the end of the new object's byte array. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG #undef Tcl_NewStringObj Tcl_Obj * Tcl_NewStringObj( const char *bytes, /* Points to the first of the length bytes * used to initialize the new object. */ Tcl_Size length) /* The number of bytes to copy from "bytes" * when initializing the new object. If * TCL_INDEX_NONE, use bytes up to the first NUL * byte. */ { return Tcl_DbNewStringObj(bytes, length, "unknown", 0); } #else /* if not TCL_MEM_DEBUG */ Tcl_Obj * Tcl_NewStringObj( const char *bytes, /* Points to the first of the length bytes * used to initialize the new object. */ Tcl_Size length) /* The number of bytes to copy from "bytes" * when initializing the new object. If -1, * use bytes up to the first NUL byte. */ { Tcl_Obj *objPtr; if (length < 0) { length = (bytes? strlen(bytes) : 0); } TclNewStringObj(objPtr, bytes, length); return objPtr; } #endif /* TCL_MEM_DEBUG */ /* *---------------------------------------------------------------------- * * Tcl_DbNewStringObj -- * * This function is normally called when debugging: i.e., when * TCL_MEM_DEBUG is defined. It creates new string objects. It is the * same as the Tcl_NewStringObj function above except that it calls * Tcl_DbCkalloc directly with the file name and line number from its * caller. This simplifies debugging since then the [memory active] * command will report the correct file name and line number when * reporting objects that haven't been freed. * * When TCL_MEM_DEBUG is not defined, this function just returns the * result of calling Tcl_NewStringObj. * * Results: * A newly created string object is returned that has ref count zero. * * Side effects: * The new object's internal string representation will be set to a copy * of the length bytes starting at "bytes". If "length" is TCL_INDEX_NONE, use * bytes up to the first NUL byte; i.e., assume "bytes" points to a * C-style NUL-terminated string. The object's type is set to NULL. An * extra NUL is added to the end of the new object's byte array. * *---------------------------------------------------------------------- */ #ifdef TCL_MEM_DEBUG Tcl_Obj * Tcl_DbNewStringObj( const char *bytes, /* Points to the first of the length bytes * used to initialize the new object. */ Tcl_Size length, /* The number of bytes to copy from "bytes" * when initializing the new object. If -1, * use bytes up to the first NUL byte. */ const char *file, /* The name of the source file calling this * function; used for debugging. */ int line) /* Line number in the source file; used for * debugging. */ { Tcl_Obj *objPtr; if (length == TCL_INDEX_NONE) { length = (bytes? strlen(bytes) : 0); } TclDbNewObj(objPtr, file, line); TclInitStringRep(objPtr, bytes, length); return objPtr; } #else /* if not TCL_MEM_DEBUG */ Tcl_Obj * Tcl_DbNewStringObj( const char *bytes, /* Points to the first of the length bytes * used to initialize the new object. */ Tcl_Size length, /* The number of bytes to copy from "bytes" * when initializing the new object. If -1, * use bytes up to the first NUL byte. */ TCL_UNUSED(const char *) /*file*/, TCL_UNUSED(int) /*line*/) { return Tcl_NewStringObj(bytes, length); } #endif /* TCL_MEM_DEBUG */ /* *--------------------------------------------------------------------------- * * Tcl_NewUnicodeObj -- * * This function is creates a new String object and initializes it from * the given Unicode String. If the Utf String is the same size as the * Unicode string, don't duplicate the data. * * Results: * The newly created object is returned. This object will have no initial * string representation. The returned object has a ref count of 0. * * Side effects: * Memory allocated for new object and copy of Unicode argument. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_NewUnicodeObj( const Tcl_UniChar *unicode, /* The unicode string used to initialize the * new object. */ Tcl_Size numChars) /* Number of characters in the unicode * string. */ { Tcl_Obj *objPtr; TclNewObj(objPtr); SetUnicodeObj(objPtr, unicode, numChars); return objPtr; } /* *---------------------------------------------------------------------- * * Tcl_GetCharLength -- * * Get the length of the Unicode string from the Tcl object. * * Results: * Pointer to Unicode string representing the Unicode object. * * Side effects: * Frees old internal rep. Allocates memory for new "String" internal * rep. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_GetCharLength( Tcl_Obj *objPtr) /* The String object to get the num chars * of. */ { String *stringPtr; Tcl_Size numChars = 0; /* * Quick, no-shimmer return for short string reps. */ if ((objPtr->bytes) && (objPtr->length < 2)) { /* 0 bytes -> 0 chars; 1 byte -> 1 char */ return objPtr->length; } /* * Optimize the case where we're really dealing with a bytearray object; * we don't need to convert to a string to perform the get-length operation. * * Starting in Tcl 8.7, we check for a "pure" bytearray, because the * machinery behind that test is using a proper bytearray ObjType. We * could also compute length of an improper bytearray without shimmering * but there's no value in that. We *want* to shimmer an improper bytearray * because improper bytearrays have worthless internal reps. */ if (TclIsPureByteArray(objPtr)) { (void) Tcl_GetBytesFromObj(NULL, objPtr, &numChars); return numChars; } /* * OK, need to work with the object as a string. */ SetStringFromAny(NULL, objPtr); stringPtr = GET_STRING(objPtr); numChars = stringPtr->numChars; /* * If numChars is unknown, compute it. */ if (numChars < 0) { TclNumUtfCharsM(numChars, objPtr->bytes, objPtr->length); stringPtr->numChars = numChars; } return numChars; } Tcl_Size TclGetCharLength( Tcl_Obj *objPtr) /* The String object to get the num chars * of. */ { Tcl_Size numChars = 0; /* * Quick, no-shimmer return for short string reps. */ if ((objPtr->bytes) && (objPtr->length < 2)) { /* 0 bytes -> 0 chars; 1 byte -> 1 char */ return objPtr->length; } /* * Optimize the case where we're really dealing with a bytearray object; * we don't need to convert to a string to perform the get-length operation. * * Starting in Tcl 8.7, we check for a "pure" bytearray, because the * machinery behind that test is using a proper bytearray ObjType. We * could also compute length of an improper bytearray without shimmering * but there's no value in that. We *want* to shimmer an improper bytearray * because improper bytearrays have worthless internal reps. */ if (TclIsPureByteArray(objPtr)) { (void) Tcl_GetBytesFromObj(NULL, objPtr, &numChars); } else { TclGetString(objPtr); numChars = TclNumUtfChars(objPtr->bytes, objPtr->length); } return numChars; } /* *---------------------------------------------------------------------- * * TclCheckEmptyString -- * * Determine whether the string value of an object is or would be the * empty string, without generating a string representation. * * Results: * Returns 1 if empty, 0 if not, and -1 if unknown. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclCheckEmptyString( Tcl_Obj *objPtr) { Tcl_Size length = TCL_INDEX_NONE; if (objPtr->bytes == &tclEmptyString) { return TCL_EMPTYSTRING_YES; } if (TclIsPureByteArray(objPtr) && Tcl_GetCharLength(objPtr) == 0) { return TCL_EMPTYSTRING_YES; } if (TclListObjIsCanonical(objPtr)) { TclListObjLength(NULL, objPtr, &length); return length == 0; } if (TclIsPureDict(objPtr)) { Tcl_DictObjSize(NULL, objPtr, &length); return length == 0; } if (objPtr->bytes == NULL) { return TCL_EMPTYSTRING_UNKNOWN; } return objPtr->length == 0; } /* *---------------------------------------------------------------------- * * Tcl_GetUniChar -- * * Get the index'th Unicode character from the String object. If index * is out of range or it references a low surrogate preceded by a high * surrogate, the result = -1; * * Results: * Returns the index'th Unicode character in the Object. * * Side effects: * Fills unichar with the index'th Unicode character. * *---------------------------------------------------------------------- */ int Tcl_GetUniChar( Tcl_Obj *objPtr, /* The object to get the Unicode charater * from. */ Tcl_Size index) /* Get the index'th Unicode character. */ { String *stringPtr; int ch; if (index < 0) { return -1; } /* * Optimize the case where we're really dealing with a ByteArray object * we don't need to convert to a string to perform the indexing operation. */ if (TclIsPureByteArray(objPtr)) { Tcl_Size length = 0; unsigned char *bytes = Tcl_GetBytesFromObj(NULL, objPtr, &length); if (index >= length) { return -1; } return bytes[index]; } /* * OK, need to work with the object as a string. */ SetStringFromAny(NULL, objPtr); stringPtr = GET_STRING(objPtr); if (stringPtr->hasUnicode == 0) { /* * If numChars is unknown, compute it. */ if (stringPtr->numChars == TCL_INDEX_NONE) { TclNumUtfCharsM(stringPtr->numChars, objPtr->bytes, objPtr->length); } if (index >= stringPtr->numChars) { return -1; } if (stringPtr->numChars == objPtr->length) { return (unsigned char) objPtr->bytes[index]; } FillUnicodeRep(objPtr); stringPtr = GET_STRING(objPtr); } if (index >= stringPtr->numChars) { return -1; } ch = stringPtr->unicode[index]; return ch; } int TclGetUniChar( Tcl_Obj *objPtr, /* The object to get the Unicode character * from. */ Tcl_Size index) /* Get the index'th Unicode character. */ { int ch = 0; if (index < 0) { return -1; } /* * Optimize the ByteArray case: no need to convert to a string to * perform the indexing operation. */ if (TclIsPureByteArray(objPtr)) { Tcl_Size length = 0; unsigned char *bytes = Tcl_GetBytesFromObj(NULL, objPtr, &length); if (index >= length) { return -1; } return bytes[index]; } Tcl_Size numChars = TclNumUtfChars(objPtr->bytes, objPtr->length); if (index >= numChars) { return -1; } const char *begin = TclUtfAtIndex(objPtr->bytes, index); TclUtfToUniChar(begin, &ch); return ch; } /* *---------------------------------------------------------------------- * * Tcl_GetUnicodeFromObj/TclGetUnicodeFromObj -- * * Get the Unicode form of the String object with length. If the object * is not already a String object, it will be converted to one. If the * String object does not have a Unicode rep, then one is create from the * UTF string format. * * Results: * Returns a pointer to the object's internal Unicode string. * * Side effects: * Converts the object to have the String internal rep. * *---------------------------------------------------------------------- */ #undef Tcl_GetUnicodeFromObj #if !defined(TCL_NO_DEPRECATED) Tcl_UniChar * TclGetUnicodeFromObj( Tcl_Obj *objPtr, /* The object to find the Unicode string * for. */ void *lengthPtr) /* If non-NULL, the location where the string * rep's Tcl_UniChar length should be stored. If * NULL, no length is stored. */ { String *stringPtr; SetStringFromAny(NULL, objPtr); stringPtr = GET_STRING(objPtr); if (stringPtr->hasUnicode == 0) { FillUnicodeRep(objPtr); stringPtr = GET_STRING(objPtr); } if (lengthPtr != NULL) { if (stringPtr->numChars > INT_MAX) { Tcl_Panic("Tcl_GetUnicodeFromObj with 'int' lengthPtr" " cannot handle such long strings. Please use 'Tcl_Size'"); } *(int *)lengthPtr = (int)stringPtr->numChars; } return stringPtr->unicode; } #endif /* !defined(TCL_NO_DEPRECATED) */ Tcl_UniChar * Tcl_GetUnicodeFromObj( Tcl_Obj *objPtr, /* The object to find the unicode string * for. */ Tcl_Size *lengthPtr) /* If non-NULL, the location where the string * rep's unichar length should be stored. If * NULL, no length is stored. */ { String *stringPtr; SetStringFromAny(NULL, objPtr); stringPtr = GET_STRING(objPtr); if (stringPtr->hasUnicode == 0) { FillUnicodeRep(objPtr); stringPtr = GET_STRING(objPtr); } if (lengthPtr != NULL) { *lengthPtr = stringPtr->numChars; } return stringPtr->unicode; } /* *---------------------------------------------------------------------- * * Tcl_GetRange -- * * Create a Tcl Object that contains the chars between first and last of * the object indicated by "objPtr". If the object is not already a * String object, convert it to one. If first is TCL_INDEX_NONE, the * returned string start at the beginning of objPtr. If last is * TCL_INDEX_NONE, the returned string ends at the end of objPtr. * * Results: * Returns a new Tcl Object of the String type. * * Side effects: * Changes the internal rep of "objPtr" to the String type. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_GetRange( Tcl_Obj *objPtr, /* The Tcl object to find the range of. */ Tcl_Size first, /* First index of the range. */ Tcl_Size last) /* Last index of the range. */ { Tcl_Obj *newObjPtr; /* The Tcl object to find the range of. */ String *stringPtr; Tcl_Size length = 0; if (first < 0) { first = 0; } /* * Optimize the case where we're really dealing with a bytearray object * we don't need to convert to a string to perform the substring operation. */ if (TclIsPureByteArray(objPtr)) { unsigned char *bytes = Tcl_GetBytesFromObj(NULL, objPtr, &length); if (last < 0 || last >= length) { last = length - 1; } if (last < first) { TclNewObj(newObjPtr); return newObjPtr; } return Tcl_NewByteArrayObj(bytes + first, last - first + 1); } /* * OK, need to work with the object as a string. */ SetStringFromAny(NULL, objPtr); stringPtr = GET_STRING(objPtr); if (stringPtr->hasUnicode == 0) { /* * If numChars is unknown, compute it. */ if (stringPtr->numChars == TCL_INDEX_NONE) { TclNumUtfCharsM(stringPtr->numChars, objPtr->bytes, objPtr->length); } if (stringPtr->numChars == objPtr->length) { if (last < 0 || last >= stringPtr->numChars) { last = stringPtr->numChars - 1; } if (last < first) { TclNewObj(newObjPtr); return newObjPtr; } newObjPtr = Tcl_NewStringObj(objPtr->bytes + first, last - first + 1); /* * Since we know the char length of the result, store it. */ SetStringFromAny(NULL, newObjPtr); stringPtr = GET_STRING(newObjPtr); stringPtr->numChars = newObjPtr->length; return newObjPtr; } FillUnicodeRep(objPtr); stringPtr = GET_STRING(objPtr); } if (last < 0 || last >= stringPtr->numChars) { last = stringPtr->numChars - 1; } if (last < first) { TclNewObj(newObjPtr); return newObjPtr; } return Tcl_NewUnicodeObj(stringPtr->unicode + first, last - first + 1); } Tcl_Obj * TclGetRange( Tcl_Obj *objPtr, /* The Tcl object to find the range of. */ Tcl_Size first, /* First index of the range. */ Tcl_Size last) /* Last index of the range. */ { Tcl_Obj *newObjPtr; /* The Tcl object to find the range of. */ Tcl_Size length = 0; if (first < 0) { first = TCL_INDEX_START; } /* * Optimize the case where we're really dealing with a bytearray object * we don't need to convert to a string to perform the substring operation. */ if (TclIsPureByteArray(objPtr)) { unsigned char *bytes = Tcl_GetBytesFromObj(NULL, objPtr, &length); if (last < 0 || last >= length) { last = length - 1; } if (last < first) { TclNewObj(newObjPtr); return newObjPtr; } return Tcl_NewByteArrayObj(bytes + first, last - first + 1); } Tcl_Size numChars = TclNumUtfChars(objPtr->bytes, objPtr->length); if (last < 0 || last >= numChars) { last = numChars - 1; } if (last < first) { TclNewObj(newObjPtr); return newObjPtr; } const char *begin = TclUtfAtIndex(objPtr->bytes, first); const char *end = TclUtfAtIndex(objPtr->bytes, last + 1); return Tcl_NewStringObj(begin, end - begin); } /* *---------------------------------------------------------------------- * * Tcl_SetStringObj -- * * Modify an object to hold a string that is a copy of the bytes * indicated by the byte pointer and length arguments. * * Results: * None. * * Side effects: * The object's string representation will be set to a copy of the * "length" bytes starting at "bytes". If "length" is TCL_INDEX_NONE, use bytes * up to the first NUL byte; i.e., assume "bytes" points to a C-style * NUL-terminated string. The object's old string and internal * representations are freed and the object's type is set NULL. * *---------------------------------------------------------------------- */ void Tcl_SetStringObj( Tcl_Obj *objPtr, /* Object whose internal rep to init. */ const char *bytes, /* Points to the first of the length bytes * used to initialize the object. */ Tcl_Size length) /* The number of bytes to copy from "bytes" * when initializing the object. If -1, * use bytes up to the first NUL byte.*/ { if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_SetStringObj"); } /* * Set the type to NULL and free any internal rep for the old type. */ TclFreeInternalRep(objPtr); /* * Free any old string rep, then set the string rep to a copy of the * length bytes starting at "bytes". */ TclInvalidateStringRep(objPtr); if (length == TCL_INDEX_NONE) { length = (bytes? strlen(bytes) : 0); } TclInitStringRep(objPtr, bytes, length); } /* *---------------------------------------------------------------------- * * Tcl_SetObjLength -- * * Changes the length of the string representation of objPtr. * * Results: * None. * * Side effects: * If the size of objPtr's string representation is greater than length, a * new terminating null byte is stored in objPtr->bytes at length, and * bytes at positions past length have no meaning. If the length of the * string representation is greater than length, the storage space is * reallocated to length+1. * * The object's internal representation is changed to &tclStringType. * *---------------------------------------------------------------------- */ void Tcl_SetObjLength( Tcl_Obj *objPtr, /* Pointer to object. This object must not * currently be shared. */ Tcl_Size length) /* Number of bytes desired for string * representation of object, not including * terminating null byte. */ { String *stringPtr; if (length < 0) { Tcl_Panic("Tcl_SetObjLength: length requested is negative: " "%" TCL_SIZE_MODIFIER "d (integer overflow?)", length); } if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_SetObjLength"); } if (objPtr->bytes && objPtr->length == length) { return; } SetStringFromAny(NULL, objPtr); stringPtr = GET_STRING(objPtr); if (objPtr->bytes != NULL) { /* * Change length of an existing string rep. */ if (length > stringPtr->allocated) { /* * Need to enlarge the buffer. */ if (objPtr->bytes == &tclEmptyString) { objPtr->bytes = (char *)Tcl_Alloc(length + 1); } else { objPtr->bytes = (char *)Tcl_Realloc(objPtr->bytes, length + 1); } stringPtr->allocated = length; } objPtr->length = length; objPtr->bytes[length] = 0; /* * Invalidate the Unicode data. */ stringPtr->numChars = TCL_INDEX_NONE; stringPtr->hasUnicode = 0; } else { if (length > stringPtr->maxChars) { stringPtr = stringRealloc(stringPtr, length); SET_STRING(objPtr, stringPtr); stringPtr->maxChars = length; } /* * Mark the new end of the Unicode string */ stringPtr->numChars = length; stringPtr->unicode[length] = 0; stringPtr->hasUnicode = 1; /* * Can only get here when objPtr->bytes == NULL. No need to invalidate * the string rep. */ } } /* *---------------------------------------------------------------------- * * Tcl_AttemptSetObjLength -- * * This function changes the length of the string representation of an * object. It uses the attempt* (non-panic'ing) memory allocators. * * Results: * 1 if the requested memory was allocated, 0 otherwise. * * Side effects: * If the size of objPtr's string representation is greater than length, * then it is reduced to length and a new terminating null byte is stored * in the strength. If the length of the string representation is greater * than length, the storage space is reallocated to the given length; a * null byte is stored at the end, but other bytes past the end of the * original string representation are undefined. The object's internal * representation is changed to "expendable string". * *---------------------------------------------------------------------- */ int Tcl_AttemptSetObjLength( Tcl_Obj *objPtr, /* Pointer to object. This object must not * currently be shared. */ Tcl_Size length) /* Number of bytes desired for string * representation of object, not including * terminating null byte. */ { String *stringPtr; if (length < 0) { /* Negative lengths => most likely integer overflow */ return 0; } if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_AttemptSetObjLength"); } if (objPtr->bytes && objPtr->length == length) { return 1; } SetStringFromAny(NULL, objPtr); stringPtr = GET_STRING(objPtr); if (objPtr->bytes != NULL) { /* * Change length of an existing string rep. */ if (length > stringPtr->allocated) { /* * Need to enlarge the buffer. */ char *newBytes; if (objPtr->bytes == &tclEmptyString) { newBytes = (char *)Tcl_AttemptAlloc(length + 1U); } else { newBytes = (char *)Tcl_AttemptRealloc(objPtr->bytes, length + 1U); } if (newBytes == NULL) { return 0; } objPtr->bytes = newBytes; stringPtr->allocated = length; } objPtr->length = length; objPtr->bytes[length] = 0; /* * Invalidate the Unicode data. */ stringPtr->numChars = TCL_INDEX_NONE; stringPtr->hasUnicode = 0; } else { /* * Changing length of pure Unicode string. */ if (length > stringPtr->maxChars) { stringPtr = stringAttemptRealloc(stringPtr, length); if (stringPtr == NULL) { return 0; } SET_STRING(objPtr, stringPtr); stringPtr->maxChars = length; } /* * Mark the new end of the Unicode string. */ stringPtr->unicode[length] = 0; stringPtr->numChars = length; stringPtr->hasUnicode = 1; /* * Can only get here when objPtr->bytes == NULL. No need to invalidate * the string rep. */ } return 1; } /* *--------------------------------------------------------------------------- * * Tcl_SetUnicodeObj -- * * Modify an object to hold the Unicode string indicated by "unicode". * * Results: * None. * * Side effects: * Memory allocated for new "String" internal rep. * *--------------------------------------------------------------------------- */ void Tcl_SetUnicodeObj( Tcl_Obj *objPtr, /* The object to set the string of. */ const Tcl_UniChar *unicode, /* The Unicode string used to initialize the * object. */ Tcl_Size numChars) /* Number of characters in the Unicode * string. */ { if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_SetUnicodeObj"); } TclFreeInternalRep(objPtr); SetUnicodeObj(objPtr, unicode, numChars); } static Tcl_Size UnicodeLength( const Tcl_UniChar *unicode) { Tcl_Size numChars = 0; if (unicode) { /* TODO - is this overflow check really necessary? */ while ((numChars >= 0) && (unicode[numChars] != 0)) { numChars++; } } return numChars; } static void SetUnicodeObj( Tcl_Obj *objPtr, /* The object to set the string of. */ const Tcl_UniChar *unicode, /* The Unicode string used to initialize the * object. */ Tcl_Size numChars) /* Number of characters in the Unicode * string. */ { String *stringPtr; if (numChars < 0) { numChars = UnicodeLength(unicode); } /* * Allocate enough space for the String structure + Unicode string. */ stringPtr = stringAlloc(numChars); SET_STRING(objPtr, stringPtr); objPtr->typePtr = &tclStringType; stringPtr->maxChars = numChars; memcpy(stringPtr->unicode, unicode, numChars * sizeof(Tcl_UniChar)); stringPtr->unicode[numChars] = 0; stringPtr->numChars = numChars; stringPtr->hasUnicode = 1; TclInvalidateStringRep(objPtr); stringPtr->allocated = 0; } /* *---------------------------------------------------------------------- * * Tcl_AppendLimitedToObj -- * * This function appends a limited number of bytes from a sequence of * bytes to an object, marking any limitation with an ellipsis. * * Results: * None. * * Side effects: * The bytes at *bytes are appended to the string representation of * objPtr. * *---------------------------------------------------------------------- */ void Tcl_AppendLimitedToObj( Tcl_Obj *objPtr, /* Points to the object to append to. */ const char *bytes, /* Points to the bytes to append to the * object. */ Tcl_Size length, /* The number of bytes available to be * appended from "bytes". If -1, then * all bytes up to a NUL byte are available. */ Tcl_Size limit, /* The maximum number of bytes to append to * the object. */ const char *ellipsis) /* Ellipsis marker string, appended to the * object to indicate not all available bytes * at "bytes" were appended. */ { String *stringPtr; Tcl_Size toCopy = 0; Tcl_Size eLen = 0; if (length < 0) { length = (bytes ? strlen(bytes) : 0); } if (length == 0) { return; } if (limit <= 0) { return; } if (length <= limit) { toCopy = length; } else { if (ellipsis == NULL) { ellipsis = "..."; } eLen = strlen(ellipsis); while (eLen > limit) { eLen = Tcl_UtfPrev(ellipsis+eLen, ellipsis) - ellipsis; } toCopy = Tcl_UtfPrev(bytes+limit+1-eLen, bytes) - bytes; } /* * If objPtr has a valid Unicode rep, then append the Unicode conversion * of "bytes" to the objPtr's Unicode rep, otherwise append "bytes" to * objPtr's string rep. */ if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_AppendLimitedToObj"); } SetStringFromAny(NULL, objPtr); stringPtr = GET_STRING(objPtr); /* If appended string starts with a continuation byte or a lower surrogate, * force objPtr to unicode representation. See [7f1162a867] */ if (bytes && ISCONTINUATION(bytes)) { Tcl_GetUnicode(objPtr); stringPtr = GET_STRING(objPtr); } if (stringPtr->hasUnicode && (stringPtr->numChars > 0)) { AppendUtfToUnicodeRep(objPtr, bytes, toCopy); } else { AppendUtfToUtfRep(objPtr, bytes, toCopy); } if (length <= limit) { return; } stringPtr = GET_STRING(objPtr); if (stringPtr->hasUnicode && (stringPtr->numChars > 0)) { AppendUtfToUnicodeRep(objPtr, ellipsis, eLen); } else { AppendUtfToUtfRep(objPtr, ellipsis, eLen); } } /* *---------------------------------------------------------------------- * * Tcl_AppendToObj -- * * This function appends a sequence of bytes to an object. * * Results: * None. * * Side effects: * The bytes at *bytes are appended to the string representation of * objPtr. * *---------------------------------------------------------------------- */ void Tcl_AppendToObj( Tcl_Obj *objPtr, /* Points to the object to append to. */ const char *bytes, /* Points to the bytes to append to the * object. */ Tcl_Size length) /* The number of bytes to append from "bytes". * If TCL_INDEX_NONE, then append all bytes up to NUL * byte. */ { Tcl_AppendLimitedToObj(objPtr, bytes, length, TCL_SIZE_MAX, NULL); } /* *---------------------------------------------------------------------- * * Tcl_AppendUnicodeToObj -- * * This function appends a Unicode string to an object in the most * efficient manner possible. * * Results: * None. * * Side effects: * Invalidates the string rep and creates a new Unicode string. * *---------------------------------------------------------------------- */ void Tcl_AppendUnicodeToObj( Tcl_Obj *objPtr, /* Points to the object to append to. */ const Tcl_UniChar *unicode, /* The Unicode string to append to the * object. */ Tcl_Size length) /* Number of chars in Unicode. Negative * lengths means nul terminated */ { String *stringPtr; if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_AppendUnicodeToObj"); } if (length == 0) { return; } SetStringFromAny(NULL, objPtr); stringPtr = GET_STRING(objPtr); /* * If objPtr has a valid Unicode rep, then append the "unicode" to the * objPtr's Unicode rep, otherwise the UTF conversion of "unicode" to * objPtr's string rep. */ if (stringPtr->hasUnicode) { AppendUnicodeToUnicodeRep(objPtr, unicode, length); } else { AppendUnicodeToUtfRep(objPtr, unicode, length); } } /* *---------------------------------------------------------------------- * * Tcl_AppendObjToObj -- * * This function appends the string rep of one object to another. * "objPtr" cannot be a shared object. * * Results: * None. * * Side effects: * The string rep of appendObjPtr is appended to the string * representation of objPtr. * IMPORTANT: This routine does not and MUST NOT shimmer appendObjPtr. * Callers are counting on that. * *---------------------------------------------------------------------- */ void Tcl_AppendObjToObj( Tcl_Obj *objPtr, /* Points to the object to append to. */ Tcl_Obj *appendObjPtr) /* Object to append. */ { String *stringPtr; Tcl_Size length = 0, numChars; Tcl_Size appendNumChars = TCL_INDEX_NONE; const char *bytes; if (TclCheckEmptyString(appendObjPtr) == TCL_EMPTYSTRING_YES) { return; } if (TclCheckEmptyString(objPtr) == TCL_EMPTYSTRING_YES) { TclSetDuplicateObj(objPtr, appendObjPtr); return; } if (TclIsPureByteArray(appendObjPtr) && (TclIsPureByteArray(objPtr) || objPtr->bytes == &tclEmptyString)) { /* * Both bytearray objects are pure, so the second internal bytearray value * can be appended to the first, with no need to modify the "bytes" field. */ /* * One might expect the code here to be * * bytes = Tcl_GetBytesFromObj(NULL, appendObjPtr, &length); * TclAppendBytesToByteArray(objPtr, bytes, length); * * and essentially all of the time that would be fine. However, it * would run into trouble in the case where objPtr and appendObjPtr * point to the same thing. That may never be a good idea. It seems to * violate Copy On Write, and we don't have any tests for the * situation, since making any Tcl commands that call * Tcl_AppendObjToObj() do that appears impossible (They honor Copy On * Write!). For the sake of extensions that go off into that realm, * though, here's a more complex approach that can handle all the * cases. * * First, get the lengths. */ Tcl_Size lengthSrc = 0; (void) Tcl_GetBytesFromObj(NULL, objPtr, &length); (void) Tcl_GetBytesFromObj(NULL, appendObjPtr, &lengthSrc); /* * Grow buffer enough for the append. */ TclAppendBytesToByteArray(objPtr, NULL, lengthSrc); /* * Reset objPtr back to the original value. */ Tcl_SetByteArrayLength(objPtr, length); /* * Now do the append knowing that buffer growth cannot cause any * trouble. */ TclAppendBytesToByteArray(objPtr, Tcl_GetBytesFromObj(NULL, appendObjPtr, (Tcl_Size *) NULL), lengthSrc); return; } /* * Must append as strings. */ SetStringFromAny(NULL, objPtr); stringPtr = GET_STRING(objPtr); /* If appended string starts with a continuation byte or a lower surrogate, * force objPtr to unicode representation. See [7f1162a867] * This fixes append-3.4, append-3.7 and utf-1.18 testcases. */ if (ISCONTINUATION(TclGetString(appendObjPtr))) { Tcl_GetUnicode(objPtr); stringPtr = GET_STRING(objPtr); } /* * If objPtr has a valid Unicode rep, then get a Unicode string from * appendObjPtr and append it. */ if (stringPtr->hasUnicode) { /* * If appendObjPtr is not of the "String" type, don't convert it. */ if (TclHasInternalRep(appendObjPtr, &tclStringType)) { Tcl_UniChar *unicode = Tcl_GetUnicodeFromObj(appendObjPtr, &numChars); AppendUnicodeToUnicodeRep(objPtr, unicode, numChars); } else { bytes = TclGetStringFromObj(appendObjPtr, &length); AppendUtfToUnicodeRep(objPtr, bytes, length); } return; } /* * Append to objPtr's UTF string rep. If we know the number of characters * in both objects before appending, then set the combined number of * characters in the final (appended-to) object. */ bytes = TclGetStringFromObj(appendObjPtr, &length); numChars = stringPtr->numChars; if ((numChars >= 0) && TclHasInternalRep(appendObjPtr, &tclStringType)) { String *appendStringPtr = GET_STRING(appendObjPtr); appendNumChars = appendStringPtr->numChars; } AppendUtfToUtfRep(objPtr, bytes, length); if ((numChars >= 0) && (appendNumChars >= 0)) { stringPtr->numChars = numChars + appendNumChars; } } /* *---------------------------------------------------------------------- * * AppendUnicodeToUnicodeRep -- * * Appends the contents of unicode to the Unicode rep of * objPtr, which must already have a valid Unicode rep. * * Results: * None. * * Side effects: * objPtr's internal rep is reallocated. * *---------------------------------------------------------------------- */ static void AppendUnicodeToUnicodeRep( Tcl_Obj *objPtr, /* Points to the object to append to. */ const Tcl_UniChar *unicode, /* String to append. */ Tcl_Size appendNumChars) /* Number of chars of "unicode" to append. */ { String *stringPtr; Tcl_Size numChars; if (appendNumChars < 0) { appendNumChars = UnicodeLength(unicode); } if (appendNumChars == 0) { return; } SetStringFromAny(NULL, objPtr); stringPtr = GET_STRING(objPtr); /* * If not enough space has been allocated for the Unicode rep, reallocate * the internal rep object with additional space. First try to double the * required allocation; if that fails, try a more modest increase. See the * "TCL STRING GROWTH ALGORITHM" comment at the top of this file for an * explanation of this growth algorithm. */ numChars = stringPtr->numChars + appendNumChars; if (numChars > stringPtr->maxChars) { Tcl_Size offset = -1; /* * Protect against case where Unicode points into the existing * stringPtr->unicode array. Force it to follow any relocations due to * the reallocs below. */ if (unicode && unicode >= stringPtr->unicode && unicode <= stringPtr->unicode + stringPtr->maxChars) { offset = unicode - stringPtr->unicode; } GrowUnicodeBuffer(objPtr, numChars); stringPtr = GET_STRING(objPtr); /* * Relocate Unicode if needed; see above. */ if (offset >= 0) { unicode = stringPtr->unicode + offset; } } /* * Copy the new string onto the end of the old string, then add the * trailing null. */ if (unicode) { memmove(stringPtr->unicode + stringPtr->numChars, unicode, appendNumChars * sizeof(Tcl_UniChar)); } stringPtr->unicode[numChars] = 0; stringPtr->numChars = numChars; stringPtr->allocated = 0; TclInvalidateStringRep(objPtr); } /* *---------------------------------------------------------------------- * * AppendUnicodeToUtfRep -- * * This function converts the contents of "unicode" to UTF and appends * the UTF to the string rep of "objPtr". * * Results: * None. * * Side effects: * objPtr's internal rep is reallocated. * *---------------------------------------------------------------------- */ static void AppendUnicodeToUtfRep( Tcl_Obj *objPtr, /* Points to the object to append to. */ const Tcl_UniChar *unicode, /* String to convert to UTF. */ Tcl_Size numChars) /* Number of chars of Unicode to convert. */ { String *stringPtr = GET_STRING(objPtr); numChars = ExtendStringRepWithUnicode(objPtr, unicode, numChars); if (stringPtr->numChars != TCL_INDEX_NONE) { stringPtr->numChars += numChars; } } /* *---------------------------------------------------------------------- * * AppendUtfToUnicodeRep -- * * This function converts the contents of "bytes" to Unicode and appends * the Unicode to the Unicode rep of "objPtr". objPtr must already have a * valid Unicode rep. numBytes must be non-negative. * * Results: * None. * * Side effects: * objPtr's internal rep is reallocated and string rep is cleaned. * *---------------------------------------------------------------------- */ static void AppendUtfToUnicodeRep( Tcl_Obj *objPtr, /* Points to the object to append to. */ const char *bytes, /* String to convert to Unicode. */ Tcl_Size numBytes) /* Number of bytes of "bytes" to convert. */ { String *stringPtr; if (numBytes == 0) { return; } ExtendUnicodeRepWithString(objPtr, bytes, numBytes, -1); TclInvalidateStringRep(objPtr); stringPtr = GET_STRING(objPtr); stringPtr->allocated = 0; } /* *---------------------------------------------------------------------- * * AppendUtfToUtfRep -- * * This function appends "numBytes" bytes of "bytes" to the UTF string * rep of "objPtr". objPtr must already have a valid String rep. * numBytes must be non-negative. * * Results: * None. * * Side effects: * objPtr's string rep is reallocated (by TCL STRING GROWTH ALGORITHM). * *---------------------------------------------------------------------- */ static void AppendUtfToUtfRep( Tcl_Obj *objPtr, /* Points to the object to append to. */ const char *bytes, /* String to append. */ Tcl_Size numBytes) /* Number of bytes of "bytes" to append. */ { String *stringPtr; Tcl_Size newLength, oldLength; if (numBytes == 0) { return; } /* * Copy the new string onto the end of the old string, then add the * trailing null. */ if (objPtr->bytes == NULL) { objPtr->length = 0; } oldLength = objPtr->length; if (numBytes > TCL_SIZE_MAX - oldLength) { Tcl_Panic("max size for a Tcl value (%" TCL_SIZE_MODIFIER "d bytes) exceeded", TCL_SIZE_MAX); } newLength = numBytes + oldLength; stringPtr = GET_STRING(objPtr); if (newLength > stringPtr->allocated) { Tcl_Size offset = -1; /* * Protect against case where unicode points into the existing * stringPtr->unicode array. Force it to follow any relocations due to * the reallocs below. */ if (bytes && objPtr->bytes && (bytes >= objPtr->bytes) && (bytes <= objPtr->bytes + objPtr->length)) { offset = bytes - objPtr->bytes; } /* * TODO: consider passing flag=1: no overalloc on first append. This * would make test stringObj-8.1 fail. */ GrowStringBuffer(objPtr, newLength, 0); /* * Relocate bytes if needed; see above. */ if (offset >= 0) { bytes = objPtr->bytes + offset; } } /* * Invalidate the unicode data. */ stringPtr->numChars = -1; stringPtr->hasUnicode = 0; if (bytes) { memmove(objPtr->bytes + oldLength, bytes, numBytes); } objPtr->bytes[newLength] = 0; objPtr->length = newLength; } /* *---------------------------------------------------------------------- * * TclAppendUtfToUtf -- * * This function appends "numBytes" bytes of "bytes" to the UTF string * rep of "objPtr" (objPtr's internal rep converted to string on demand). * numBytes must be non-negative. * * Results: * None. * * Side effects: * objPtr's string rep is reallocated (by TCL STRING GROWTH ALGORITHM). * *---------------------------------------------------------------------- */ void TclAppendUtfToUtf( Tcl_Obj *objPtr, /* Points to the object to append to. */ const char *bytes, /* String to append (or NULL to enlarge buffer). */ Tcl_Size numBytes) /* Number of bytes of "bytes" to append. */ { if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "TclAppendUtfToUtf"); } SetStringFromAny(NULL, objPtr); AppendUtfToUtfRep(objPtr, bytes, numBytes); } /* *---------------------------------------------------------------------- * * Tcl_AppendStringsToObj -- * * This function appends one or more null-terminated strings to an * object. * * Results: * None. * * Side effects: * The contents of all the string arguments are appended to the string * representation of objPtr. * *---------------------------------------------------------------------- */ void Tcl_AppendStringsToObj( Tcl_Obj *objPtr, ...) { va_list argList; va_start(argList, objPtr); if (Tcl_IsShared(objPtr)) { Tcl_Panic("%s called with shared object", "Tcl_AppendStringsToObj"); } while (1) { const char *bytes = va_arg(argList, char *); if (bytes == NULL) { break; } Tcl_AppendToObj(objPtr, bytes, -1); } va_end(argList); } /* *---------------------------------------------------------------------- * * Tcl_AppendFormatToObj -- * * This function appends a list of Tcl_Obj's to a Tcl_Obj according to * the formatting instructions embedded in the format string. The * formatting instructions are inspired by sprintf(). Returns TCL_OK when * successful. If there's an error in the arguments, TCL_ERROR is * returned, and an error message is written to the interp, if non-NULL. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_AppendFormatToObj( Tcl_Interp *interp, Tcl_Obj *appendObj, const char *format, Tcl_Size objc, Tcl_Obj *const objv[]) { const char *span = format, *msg, *errCode; int gotXpg = 0, gotSequential = 0; Tcl_Size objIndex = 0, originalLength, limit, numBytes = 0; Tcl_UniChar ch = 0; static const char *mixedXPG = "cannot mix \"%\" and \"%n$\" conversion specifiers"; static const char *const badIndex[2] = { "not enough arguments for all format specifiers", "\"%n$\" argument index out of range" }; static const char *overflow = "max size for a Tcl value exceeded"; if (Tcl_IsShared(appendObj)) { Tcl_Panic("%s called with shared object", "Tcl_AppendFormatToObj"); } (void)TclGetStringFromObj(appendObj, &originalLength); limit = TCL_SIZE_MAX - originalLength; /* * Format string is NUL-terminated. */ while (*format != '\0') { char *end; int gotMinus = 0, gotHash = 0, gotZero = 0, gotSpace = 0, gotPlus = 0; int gotPrecision, sawFlag, useShort = 0, useBig = 0; Tcl_WideInt width, precision; int useWide = 0; int newXpg, allocSegment = 0; Tcl_Size numChars, segmentLimit, segmentNumBytes; Tcl_Obj *segment; int step = TclUtfToUniChar(format, &ch); format += step; if (ch != '%') { numBytes += step; continue; } if (numBytes) { if (numBytes > limit) { msg = overflow; errCode = "OVERFLOW"; goto errorMsg; } Tcl_AppendToObj(appendObj, span, numBytes); limit -= numBytes; numBytes = 0; } /* * Saw a % : process the format specifier. * * Step 0. Handle special case of escaped format marker (i.e., %%). */ step = TclUtfToUniChar(format, &ch); if (ch == '%') { span = format; numBytes = step; format += step; continue; } /* * Step 1. XPG3 position specifier */ newXpg = 0; if (isdigit(UCHAR(ch))) { int position = strtoul(format, &end, 10); if (*end == '$') { newXpg = 1; objIndex = position - 1; format = end + 1; step = TclUtfToUniChar(format, &ch); } } if (newXpg) { if (gotSequential) { msg = mixedXPG; errCode = "MIXEDSPECTYPES"; goto errorMsg; } gotXpg = 1; } else { if (gotXpg) { msg = mixedXPG; errCode = "MIXEDSPECTYPES"; goto errorMsg; } gotSequential = 1; } if ((objIndex < 0) || (objIndex >= objc)) { msg = badIndex[gotXpg]; errCode = gotXpg ? "INDEXRANGE" : "FIELDVARMISMATCH"; goto errorMsg; } /* * Step 2. Set of flags. */ sawFlag = 1; do { switch (ch) { case '-': gotMinus = 1; break; case '#': gotHash = 1; break; case '0': gotZero = 1; break; case ' ': gotSpace = 1; break; case '+': gotPlus = 1; break; default: sawFlag = 0; } if (sawFlag) { format += step; step = TclUtfToUniChar(format, &ch); } } while (sawFlag); /* * Step 3. Minimum field width. */ width = 0; if (isdigit(UCHAR(ch))) { /* Note ull will be >= 0 because of isdigit check above */ unsigned long long ull; ull = strtoull(format, &end, 10); /* Comparison is >=, not >, to leave room for nul */ if (ull >= WIDE_MAX) { msg = overflow; errCode = "OVERFLOW"; goto errorMsg; } width = (Tcl_WideInt)ull; format = end; step = TclUtfToUniChar(format, &ch); } else if (ch == '*') { if (objIndex >= objc - 1) { msg = badIndex[gotXpg]; errCode = gotXpg ? "INDEXRANGE" : "FIELDVARMISMATCH"; goto errorMsg; } if (TclGetWideIntFromObj(interp, objv[objIndex], &width) != TCL_OK) { goto error; } if (width < 0) { width = -width; gotMinus = 1; } objIndex++; format += step; step = TclUtfToUniChar(format, &ch); } if (width > limit) { msg = overflow; errCode = "OVERFLOW"; goto errorMsg; } /* * Step 4. Precision. */ gotPrecision = precision = 0; if (ch == '.') { gotPrecision = 1; format += step; step = TclUtfToUniChar(format, &ch); } if (isdigit(UCHAR(ch))) { /* Note ull will be >= 0 because of isdigit check above */ unsigned long long ull; ull = strtoull(format, &end, 10); /* Comparison is >=, not >, to leave room for nul */ if (ull >= WIDE_MAX) { msg = overflow; errCode = "OVERFLOW"; goto errorMsg; } precision = (Tcl_WideInt)ull; format = end; step = TclUtfToUniChar(format, &ch); } else if (ch == '*') { if (objIndex >= objc - 1) { msg = badIndex[gotXpg]; errCode = gotXpg ? "INDEXRANGE" : "FIELDVARMISMATCH"; goto errorMsg; } if (TclGetWideIntFromObj(interp, objv[objIndex], &precision) != TCL_OK) { goto error; } /* * TODO: Check this truncation logic. */ if (precision < 0) { precision = 0; } objIndex++; format += step; step = TclUtfToUniChar(format, &ch); } /* * Step 5. Length modifier. */ if (ch == 'h') { useShort = 1; format += step; step = TclUtfToUniChar(format, &ch); } else if (ch == 'l') { format += step; step = TclUtfToUniChar(format, &ch); if (ch == 'l') { useBig = 1; format += step; step = TclUtfToUniChar(format, &ch); } else { useWide = 1; } } else if (ch == 'I') { if ((format[1] == '6') && (format[2] == '4')) { format += (step + 2); step = TclUtfToUniChar(format, &ch); useWide = 1; } else if ((format[1] == '3') && (format[2] == '2')) { format += (step + 2); step = TclUtfToUniChar(format, &ch); } else { format += step; step = TclUtfToUniChar(format, &ch); } } else if ((ch == 'q') || (ch == 'j')) { format += step; step = TclUtfToUniChar(format, &ch); useWide = 1; } else if ((ch == 't') || (ch == 'z')) { format += step; step = TclUtfToUniChar(format, &ch); if (sizeof(void *) > sizeof(int)) { useWide = 1; } } else if (ch == 'L') { format += step; step = TclUtfToUniChar(format, &ch); useBig = 1; } format += step; span = format; /* * Step 6. The actual conversion character. */ segment = objv[objIndex]; numChars = -1; if (ch == 'i') { ch = 'd'; } switch (ch) { case '\0': msg = "format string ended in middle of field specifier"; errCode = "INCOMPLETE"; goto errorMsg; case 's': if (gotPrecision) { numChars = Tcl_GetCharLength(segment); if (precision < numChars) { if (precision < 1) { TclNewObj(segment); } else { segment = Tcl_GetRange(segment, 0, precision - 1); } numChars = precision; Tcl_IncrRefCount(segment); allocSegment = 1; } } break; case 'c': { char buf[4] = ""; int code, length; if (TclGetIntFromObj(interp, segment, &code) != TCL_OK) { goto error; } if ((unsigned)code > 0x10FFFF) { code = 0xFFFD; } length = Tcl_UniCharToUtf(code, buf); segment = Tcl_NewStringObj(buf, length); Tcl_IncrRefCount(segment); allocSegment = 1; break; } case 'u': /* FALLTHRU */ case 'd': case 'o': case 'p': case 'x': case 'X': case 'b': { short s = 0; /* Silence compiler warning; only defined and * used when useShort is true. */ int l; Tcl_WideInt w; mp_int big; int isNegative = 0; Tcl_Size toAppend; if ((ch == 'p') && (sizeof(void *) > sizeof(int))) { useWide = 1; } if (useBig) { int cmpResult; if (Tcl_GetBignumFromObj(interp, segment, &big) != TCL_OK) { goto error; } cmpResult = mp_cmp_d(&big, 0); isNegative = (cmpResult == MP_LT); if (cmpResult == MP_EQ) { gotHash = 0; } if (ch == 'u') { if (isNegative) { mp_clear(&big); msg = "unsigned bignum format is invalid"; errCode = "BADUNSIGNED"; goto errorMsg; } else { ch = 'd'; } } } else if (useWide) { if (TclGetWideBitsFromObj(interp, segment, &w) != TCL_OK) { goto error; } isNegative = (w < (Tcl_WideInt) 0); if (w == (Tcl_WideInt) 0) { gotHash = 0; } } else if (TclGetIntFromObj(NULL, segment, &l) != TCL_OK) { if (TclGetWideBitsFromObj(interp, segment, &w) != TCL_OK) { goto error; } else { l = (int) w; } if (useShort) { s = (short) l; isNegative = (s < (short) 0); if (s == (short) 0) { gotHash = 0; } } else { isNegative = (l < (int) 0); if (l == (int) 0) { gotHash = 0; } } } else if (useShort) { s = (short) l; isNegative = (s < (short) 0); if (s == (short) 0) { gotHash = 0; } } else { isNegative = (l < (int) 0); if (l == (int) 0) { gotHash = 0; } } TclNewObj(segment); allocSegment = 1; segmentLimit = TCL_SIZE_MAX; Tcl_IncrRefCount(segment); if ((isNegative || gotPlus || gotSpace) && (useBig || ch=='d')) { Tcl_AppendToObj(segment, (isNegative ? "-" : gotPlus ? "+" : " "), 1); segmentLimit -= 1; } if (gotHash || (ch == 'p')) { switch (ch) { case 'o': Tcl_AppendToObj(segment, "0o", 2); segmentLimit -= 2; break; case 'p': case 'x': case 'X': Tcl_AppendToObj(segment, "0x", 2); segmentLimit -= 2; break; case 'b': Tcl_AppendToObj(segment, "0b", 2); segmentLimit -= 2; break; case 'd': Tcl_AppendToObj(segment, "0d", 2); segmentLimit -= 2; break; } } switch (ch) { case 'd': { Tcl_Size length; Tcl_Obj *pure; const char *bytes; if (useShort) { TclNewIntObj(pure, s); } else if (useWide) { TclNewIntObj(pure, w); } else if (useBig) { pure = Tcl_NewBignumObj(&big); } else { TclNewIntObj(pure, l); } Tcl_IncrRefCount(pure); bytes = TclGetStringFromObj(pure, &length); /* * Already did the sign above. */ if (*bytes == '-') { length--; bytes++; } toAppend = length; /* * Canonical decimal string reps for integers are composed * entirely of one-byte encoded characters, so "length" is the * number of chars. */ if (gotPrecision) { if (length < precision) { segmentLimit -= precision - length; } while (length < precision) { Tcl_AppendToObj(segment, "0", 1); length++; } gotZero = 0; } if (gotZero) { length += Tcl_GetCharLength(segment); if (length < width) { segmentLimit -= width - length; } while (length < width) { Tcl_AppendToObj(segment, "0", 1); length++; } } if (toAppend > segmentLimit) { msg = overflow; errCode = "OVERFLOW"; goto errorMsg; } Tcl_AppendToObj(segment, bytes, toAppend); Tcl_DecrRefCount(pure); break; } case 'u': case 'o': case 'p': case 'x': case 'X': case 'b': { Tcl_WideUInt bits = 0; Tcl_WideInt numDigits = 0; int numBits = 4, base = 16, index = 0, shift = 0; Tcl_Size length; Tcl_Obj *pure; char *bytes; if (ch == 'u') { base = 10; } else if (ch == 'o') { base = 8; numBits = 3; } else if (ch == 'b') { base = 2; numBits = 1; } if (useShort) { unsigned short us = (unsigned short) s; bits = (Tcl_WideUInt) us; while (us) { numDigits++; us /= base; } } else if (useWide) { Tcl_WideUInt uw = (Tcl_WideUInt) w; bits = uw; while (uw) { numDigits++; uw /= base; } } else if (useBig && !mp_iszero(&big)) { int leftover = (big.used * MP_DIGIT_BIT) % numBits; mp_digit mask = (~(mp_digit)0) << (MP_DIGIT_BIT-leftover); numDigits = 1 + (((Tcl_WideInt) big.used * MP_DIGIT_BIT) / numBits); while ((mask & big.dp[big.used-1]) == 0) { numDigits--; mask >>= numBits; } if (numDigits > INT_MAX) { msg = overflow; errCode = "OVERFLOW"; goto errorMsg; } } else if (!useBig) { unsigned ul = (unsigned) l; bits = (Tcl_WideUInt) ul; while (ul) { numDigits++; ul /= base; } } /* * Need to be sure zero becomes "0", not "". */ if (numDigits == 0) { numDigits = 1; } TclNewObj(pure); Tcl_SetObjLength(pure, (Tcl_Size)numDigits); bytes = TclGetString(pure); toAppend = length = numDigits; while (numDigits--) { int digitOffset; if (useBig && !mp_iszero(&big)) { if (index < big.used && (size_t) shift < CHAR_BIT*sizeof(Tcl_WideUInt) - MP_DIGIT_BIT) { bits |= ((Tcl_WideUInt) big.dp[index++]) << shift; shift += MP_DIGIT_BIT; } shift -= numBits; } digitOffset = bits % base; if (digitOffset > 9) { if (ch == 'X') { bytes[numDigits] = 'A' + digitOffset - 10; } else { bytes[numDigits] = 'a' + digitOffset - 10; } } else { bytes[numDigits] = '0' + digitOffset; } bits /= base; } if (useBig) { mp_clear(&big); } if (gotPrecision) { if (length < precision) { segmentLimit -= precision - length; } while (length < precision) { Tcl_AppendToObj(segment, "0", 1); length++; } gotZero = 0; } if (gotZero) { length += Tcl_GetCharLength(segment); if (length < width) { segmentLimit -= width - length; } while (length < width) { Tcl_AppendToObj(segment, "0", 1); length++; } } if (toAppend > segmentLimit) { msg = overflow; errCode = "OVERFLOW"; goto errorMsg; } Tcl_AppendObjToObj(segment, pure); Tcl_DecrRefCount(pure); break; } } break; } case 'a': case 'A': case 'e': case 'E': case 'f': case 'g': case 'G': { #define MAX_FLOAT_SIZE 320 char spec[2*TCL_INTEGER_SPACE + 9], *p = spec; double d; int length = MAX_FLOAT_SIZE; char *bytes; if (Tcl_GetDoubleFromObj(interp, segment, &d) != TCL_OK) { /* TODO: Figure out ACCEPT_NAN here */ goto error; } *p++ = '%'; if (gotMinus) { *p++ = '-'; } if (gotHash) { *p++ = '#'; } if (gotZero) { *p++ = '0'; } if (gotSpace) { *p++ = ' '; } if (gotPlus) { *p++ = '+'; } if (width) { p += snprintf(p, TCL_INTEGER_SPACE, "%" TCL_LL_MODIFIER "d", width); if (width > length) { length = width; } } if (gotPrecision) { *p++ = '.'; p += snprintf(p, TCL_INTEGER_SPACE, "%" TCL_LL_MODIFIER "d", precision); if (precision > TCL_SIZE_MAX - length) { msg = overflow; errCode = "OVERFLOW"; goto errorMsg; } length += precision; } /* * Don't pass length modifiers! */ *p++ = (char) ch; *p = '\0'; TclNewObj(segment); allocSegment = 1; if (!Tcl_AttemptSetObjLength(segment, length)) { if (allocSegment) { Tcl_DecrRefCount(segment); } msg = overflow; errCode = "OVERFLOW"; goto errorMsg; } bytes = TclGetString(segment); if (!Tcl_AttemptSetObjLength(segment, snprintf(bytes, segment->length, spec, d))) { if (allocSegment) { Tcl_DecrRefCount(segment); } msg = overflow; errCode = "OVERFLOW"; goto errorMsg; } if (ch == 'A') { char *q = TclGetString(segment) + 1; *q = 'x'; q = strchr(q, 'P'); if (q) { *q = 'p'; } } break; } default: if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad field specifier \"%c\"", ch)); Tcl_SetErrorCode(interp, "TCL", "FORMAT", "BADTYPE", (char *)NULL); } goto error; } if (width>0 && numChars<0) { numChars = Tcl_GetCharLength(segment); } if (!gotMinus && width>0) { if (numChars < width) { limit -= width - numChars; } while (numChars < width) { Tcl_AppendToObj(appendObj, (gotZero ? "0" : " "), 1); numChars++; } } (void)TclGetStringFromObj(segment, &segmentNumBytes); if (segmentNumBytes > limit) { if (allocSegment) { Tcl_DecrRefCount(segment); } msg = overflow; errCode = "OVERFLOW"; goto errorMsg; } Tcl_AppendObjToObj(appendObj, segment); limit -= segmentNumBytes; if (allocSegment) { Tcl_DecrRefCount(segment); } if (width > 0) { if (numChars < width) { limit -= width-numChars; } while (numChars < width) { Tcl_AppendToObj(appendObj, (gotZero ? "0" : " "), 1); numChars++; } } objIndex += gotSequential; } if (numBytes) { if (numBytes > limit) { msg = overflow; errCode = "OVERFLOW"; goto errorMsg; } Tcl_AppendToObj(appendObj, span, numBytes); limit -= numBytes; numBytes = 0; } return TCL_OK; errorMsg: if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj(msg, -1)); Tcl_SetErrorCode(interp, "TCL", "FORMAT", errCode, (char *)NULL); } error: Tcl_SetObjLength(appendObj, originalLength); return TCL_ERROR; } /* *--------------------------------------------------------------------------- * * Tcl_Format -- * * Results: * A refcount zero Tcl_Obj. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_Format( Tcl_Interp *interp, const char *format, Tcl_Size objc, Tcl_Obj *const objv[]) { int result; Tcl_Obj *objPtr; TclNewObj(objPtr); result = Tcl_AppendFormatToObj(interp, objPtr, format, objc, objv); if (result != TCL_OK) { Tcl_DecrRefCount(objPtr); return NULL; } return objPtr; } /* *--------------------------------------------------------------------------- * * AppendPrintfToObjVA -- * * Results: * * Side effects: * *--------------------------------------------------------------------------- */ static Tcl_Obj * NewLongObj( char c, long value) { if ((value < 0) && strchr("puoxX", c)) { Tcl_Obj *obj; TclNewUIntObj(obj, (unsigned long)value); return obj; } return Tcl_NewWideIntObj((long)value); } static Tcl_Obj * NewWideIntObj( char c, Tcl_WideInt value) { if ((value < 0) && strchr("puoxX", c)) { Tcl_Obj *obj; TclNewUIntObj(obj, (Tcl_WideUInt)value); return obj; } return Tcl_NewWideIntObj(value); } static void AppendPrintfToObjVA( Tcl_Obj *objPtr, const char *format, va_list argList) { int code; Tcl_Size objc; Tcl_Obj **objv, *list; const char *p; TclNewObj(list); p = format; Tcl_IncrRefCount(list); while (*p != '\0') { int size = 0, seekingConversion = 1, gotPrecision = 0; int lastNum = -1; if (*p++ != '%') { continue; } if (*p == '%') { p++; continue; } do { switch (*p) { case '\0': seekingConversion = 0; break; case 's': { const char *q, *end, *bytes = va_arg(argList, char *); seekingConversion = 0; /* * The buffer to copy characters from starts at bytes and ends * at either the first NUL byte, or after lastNum bytes, when * caller has indicated a limit. */ end = bytes; while ((!gotPrecision || lastNum--) && (*end != '\0')) { end++; } /* * Within that buffer, we trim both ends if needed so that we * copy only whole characters, and avoid copying any partial * multi-byte characters. */ q = Tcl_UtfPrev(end, bytes); if (!Tcl_UtfCharComplete(q, (end - q))) { end = q; } q = bytes + 4; while ((bytes < end) && (bytes < q) && ((*bytes & 0xC0) == 0x80)) { bytes++; } Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(bytes , (end - bytes))); break; } case 'p': if (sizeof(size_t) == sizeof(Tcl_WideInt)) { size = 2; } /* FALLTHRU */ case 'c': case 'i': case 'u': case 'd': case 'o': case 'x': case 'X': seekingConversion = 0; switch (size) { case -1: case 0: Tcl_ListObjAppendElement(NULL, list, Tcl_NewIntObj( va_arg(argList, int))); break; case 1: Tcl_ListObjAppendElement(NULL, list, NewLongObj(*p, va_arg(argList, long))); break; case 2: Tcl_ListObjAppendElement(NULL, list, NewWideIntObj(*p, va_arg(argList, Tcl_WideInt))); break; case 3: Tcl_ListObjAppendElement(NULL, list, Tcl_NewBignumObj( va_arg(argList, mp_int *))); break; } break; case 'a': case 'A': case 'e': case 'E': case 'f': case 'g': case 'G': if (size > 0) { Tcl_ListObjAppendElement(NULL, list, Tcl_NewDoubleObj( (double)va_arg(argList, long double))); } else { Tcl_ListObjAppendElement(NULL, list, Tcl_NewDoubleObj( va_arg(argList, double))); } seekingConversion = 0; break; case '*': lastNum = va_arg(argList, int); Tcl_ListObjAppendElement(NULL, list, Tcl_NewWideIntObj(lastNum)); p++; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { char *end; lastNum = strtoul(p, &end, 10); p = end; break; } case '.': gotPrecision = 1; p++; break; case 'l': ++size; p++; break; case 't': case 'z': if (sizeof(size_t) == sizeof(Tcl_WideInt)) { size = 2; } p++; break; case 'j': case 'q': size = 2; p++; break; case 'I': if (p[1]=='6' && p[2]=='4') { p += 2; size = 2; } else if (p[1]=='3' && p[2]=='2') { p += 2; } else if (sizeof(size_t) == sizeof(Tcl_WideInt)) { size = 2; } p++; break; case 'L': size = 3; p++; break; case 'h': size = -1; /* FALLTHRU */ default: p++; } } while (seekingConversion); } TclListObjGetElements(NULL, list, &objc, &objv); code = Tcl_AppendFormatToObj(NULL, objPtr, format, objc, objv); if (code != TCL_OK) { Tcl_AppendPrintfToObj(objPtr, "Unable to format \"%s\" with supplied arguments: %s", format, TclGetString(list)); } Tcl_DecrRefCount(list); } /* *--------------------------------------------------------------------------- * * Tcl_AppendPrintfToObj -- * * Results: * A standard Tcl result. * * Side effects: * None. * *--------------------------------------------------------------------------- */ void Tcl_AppendPrintfToObj( Tcl_Obj *objPtr, const char *format, ...) { va_list argList; va_start(argList, format); AppendPrintfToObjVA(objPtr, format, argList); va_end(argList); } /* *--------------------------------------------------------------------------- * * Tcl_ObjPrintf -- * * Results: * A refcount zero Tcl_Obj. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_ObjPrintf( const char *format, ...) { va_list argList; Tcl_Obj *objPtr; TclNewObj(objPtr); va_start(argList, format); AppendPrintfToObjVA(objPtr, format, argList); va_end(argList); return objPtr; } /* *--------------------------------------------------------------------------- * * TclGetStringStorage -- * * Returns the string storage space of a Tcl_Obj. * * Results: * The pointer value objPtr->bytes is returned and the number of bytes * allocated there is written to *sizePtr (if known). * * Side effects: * May set objPtr->bytes. * *--------------------------------------------------------------------------- */ char * TclGetStringStorage( Tcl_Obj *objPtr, Tcl_Size *sizePtr) { String *stringPtr; if (!TclHasInternalRep(objPtr, &tclStringType) || objPtr->bytes == NULL) { return TclGetStringFromObj(objPtr, sizePtr); } stringPtr = GET_STRING(objPtr); *sizePtr = stringPtr->allocated; return objPtr->bytes; } /* *--------------------------------------------------------------------------- * * TclStringRepeat -- * * Performs the [string repeat] function. * * Results: * A (Tcl_Obj *) pointing to the result value, or NULL in case of an * error. * * Side effects: * On error, when interp is not NULL, error information is left in it. * *--------------------------------------------------------------------------- */ Tcl_Obj * TclStringRepeat( Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size count, int flags) { Tcl_Obj *objResultPtr; int inPlace = flags & TCL_STRING_IN_PLACE; Tcl_Size length = 0; int unichar = 0; Tcl_Size done = 1; int binary = TclIsPureByteArray(objPtr); Tcl_Size maxCount; /* * Analyze to determine what representation result should be. * GOALS: Avoid shimmering & string rep generation. * Produce pure bytearray when possible. * Error on overflow. */ if (!binary) { if (TclHasInternalRep(objPtr, &tclStringType)) { String *stringPtr = GET_STRING(objPtr); if (stringPtr->hasUnicode) { unichar = 1; } } } if (binary) { /* Result will be pure byte array. Pre-size it */ (void)Tcl_GetBytesFromObj(NULL, objPtr, &length); maxCount = TCL_SIZE_MAX; } else if (unichar) { /* Result will be pure Tcl_UniChar array. Pre-size it. */ (void)Tcl_GetUnicodeFromObj(objPtr, &length); maxCount = TCL_SIZE_MAX/sizeof(Tcl_UniChar); } else { /* Result will be concat of string reps. Pre-size it. */ (void)TclGetStringFromObj(objPtr, &length); maxCount = TCL_SIZE_MAX; } if (length == 0) { /* Any repeats of empty is empty. */ return objPtr; } /* maxCount includes space for null */ if (count > (maxCount-1)) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "max size for a Tcl value (%" TCL_SIZE_MODIFIER "d bytes) exceeded", TCL_SIZE_MAX)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); } return NULL; } if (binary) { /* Efficiently produce a pure byte array result */ objResultPtr = (!inPlace || Tcl_IsShared(objPtr)) ? Tcl_DuplicateObj(objPtr) : objPtr; /* Allocate count*length space */ Tcl_SetByteArrayLength(objResultPtr, count*length); /* PANIC? */ Tcl_SetByteArrayLength(objResultPtr, length); while (count - done > done) { Tcl_AppendObjToObj(objResultPtr, objResultPtr); done *= 2; } TclAppendBytesToByteArray(objResultPtr, Tcl_GetBytesFromObj(NULL, objResultPtr, (Tcl_Size *) NULL), (count - done) * length); } else if (unichar) { /* * Efficiently produce a pure Tcl_UniChar array result. */ if (!inPlace || Tcl_IsShared(objPtr)) { objResultPtr = Tcl_NewUnicodeObj(Tcl_GetUnicode(objPtr), length); } else { TclInvalidateStringRep(objPtr); objResultPtr = objPtr; } /* TODO - overflow check */ if (0 == Tcl_AttemptSetObjLength(objResultPtr, count*length)) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "string size overflow: unable to alloc %" TCL_SIZE_MODIFIER "d bytes", STRING_SIZE(count*length))); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); } return NULL; } Tcl_SetObjLength(objResultPtr, length); while (count - done > done) { Tcl_AppendObjToObj(objResultPtr, objResultPtr); done *= 2; } Tcl_AppendUnicodeToObj(objResultPtr, Tcl_GetUnicode(objResultPtr), (count - done) * length); } else { /* * Efficiently concatenate string reps. */ if (!inPlace || Tcl_IsShared(objPtr)) { objResultPtr = Tcl_NewStringObj(TclGetString(objPtr), length); } else { TclFreeInternalRep(objPtr); objResultPtr = objPtr; } /* TODO - overflow check */ if (0 == Tcl_AttemptSetObjLength(objResultPtr, count*length)) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "string size overflow: unable to alloc %" TCL_SIZE_MODIFIER "d bytes", count*length)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); } return NULL; } Tcl_SetObjLength(objResultPtr, length); while (count - done > done) { Tcl_AppendObjToObj(objResultPtr, objResultPtr); done *= 2; } Tcl_AppendToObj(objResultPtr, TclGetString(objResultPtr), (count - done) * length); } return objResultPtr; } /* *--------------------------------------------------------------------------- * * TclStringCat -- * * Performs the [string cat] function. * * Results: * A (Tcl_Obj *) pointing to the result value, or NULL in case of an * error. * * Side effects: * On error, when interp is not NULL, error information is left in it. * *--------------------------------------------------------------------------- */ Tcl_Obj * TclStringCat( Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj * const objv[], int flags) { Tcl_Obj *objResultPtr, * const *ov; int binary = 1; Tcl_Size oc, length = 0; int allowUniChar = 1, requestUniChar = 0, forceUniChar = 0; Tcl_Size first = objc - 1; /* Index of first value possibly not empty */ Tcl_Size last = 0; /* Index of last value possibly not empty */ int inPlace = (flags & TCL_STRING_IN_PLACE) && !Tcl_IsShared(*objv); if (objc <= 1) { if (objc != 1) { /* Negative (shouldn't be) no objects; return empty */ Tcl_Obj *obj; TclNewObj(obj); return obj; } /* One object; return first */ return objv[0]; } /* * Analyze to determine what representation result should be. * GOALS: Avoid shimmering & string rep generation. * Produce pure bytearray when possible. * Error on overflow. */ ov = objv, oc = objc; do { Tcl_Obj *objPtr = *ov++; if (TclIsPureByteArray(objPtr)) { allowUniChar = 0; } else if (objPtr->bytes) { /* Value has a string rep. */ if (objPtr->length) { /* * Non-empty string rep. Not a pure bytearray, so we won't * create a pure bytearray. */ binary = 0; if (ov > objv+1 && ISCONTINUATION(TclGetString(objPtr))) { forceUniChar = 1; } else if ((objPtr->typePtr) && !TclHasInternalRep(objPtr, &tclStringType)) { /* Prevent shimmer of non-string types. */ allowUniChar = 0; } } } else { binary = 0; if (TclHasInternalRep(objPtr, &tclStringType)) { /* Have a pure Unicode value; ask to preserve it */ requestUniChar = 1; } else { /* Have another type; prevent shimmer */ allowUniChar = 0; } } } while (--oc && (binary || allowUniChar)); if (binary) { /* * Result will be pure byte array. Pre-size it */ Tcl_Size numBytes = 0; ov = objv; oc = objc; do { Tcl_Obj *objPtr = *ov++; /* * Every argument is either a bytearray with a ("pure") * value we know we can safely use, or it is an empty string. * We don't need to count bytes for the empty strings. */ if (TclIsPureByteArray(objPtr)) { (void)Tcl_GetBytesFromObj(NULL, objPtr, &numBytes); /* PANIC? */ if (numBytes) { last = objc - oc; if (length == 0) { first = last; } if (length > (TCL_SIZE_MAX-numBytes)) { goto overflow; } length += numBytes; } } } while (--oc); } else if ((allowUniChar && requestUniChar) || forceUniChar) { /* * Result will be pure Tcl_UniChar array. Pre-size it. */ ov = objv; oc = objc; do { Tcl_Obj *objPtr = *ov++; if ((objPtr->bytes == NULL) || (objPtr->length)) { Tcl_Size numChars; (void)Tcl_GetUnicodeFromObj(objPtr, &numChars); /* PANIC? */ if (numChars) { last = objc - oc; if (length == 0) { first = last; } if (length > (Tcl_Size) ((TCL_SIZE_MAX/sizeof(Tcl_UniChar))-numChars)) { goto overflow; } length += numChars; } } } while (--oc); } else { /* Result will be concat of string reps. Pre-size it. */ ov = objv; oc = objc; do { Tcl_Obj *pendingPtr = NULL; /* * Loop until a possibly non-empty value is reached. * Keep string rep generation pending when possible. */ do { Tcl_Obj *objPtr = *ov++; if (objPtr->bytes == NULL && TclCheckEmptyString(objPtr) != TCL_EMPTYSTRING_YES) { /* No string rep; Take the chance we can avoid making it */ pendingPtr = objPtr; } else { (void) TclGetStringFromObj(objPtr, &length); /* PANIC? */ } } while (--oc && (length == 0) && (pendingPtr == NULL)); /* * Either we found a possibly non-empty value, and we remember * this index as the first and last such value so far seen, * or (oc == 0) and all values are known empty, * so first = last = objc - 1 signals the right quick return. */ first = last = objc - oc - 1; if (oc && (length == 0)) { Tcl_Size numBytes; /* * There's a pending value followed by more values. Loop over * remaining values generating strings until a non-empty value * is found, or the pending value gets its string generated. */ do { Tcl_Obj *objPtr = *ov++; (void)TclGetStringFromObj(objPtr, &numBytes); /* PANIC? */ } while (--oc && numBytes == 0 && pendingPtr->bytes == NULL); if (numBytes) { last = objc -oc -1; } if (oc || numBytes) { (void)TclGetStringFromObj(pendingPtr, &length); } if (length == 0) { if (numBytes) { first = last; } } else if (numBytes > (TCL_SIZE_MAX - length)) { goto overflow; } length += numBytes; } } while (oc && (length == 0)); while (oc) { Tcl_Size numBytes; Tcl_Obj *objPtr = *ov++; TclGetString(objPtr); /* PANIC? */ numBytes = objPtr->length; if (numBytes) { last = objc - oc; if (numBytes > (TCL_SIZE_MAX - length)) { goto overflow; } length += numBytes; } --oc; } } if (last <= first /*|| length == 0 */) { /* Only one non-empty value or zero length; return first */ /* NOTE: (length == 0) implies (last <= first) */ return objv[first]; } objv += first; objc = (last - first + 1); inPlace = (flags & TCL_STRING_IN_PLACE) && !Tcl_IsShared(*objv); if (binary) { /* Efficiently produce a pure byte array result */ unsigned char *dst; /* * Broken interface! Byte array value routines offer no way to handle * failure to allocate enough space. Following stanza may panic. */ if (inPlace) { Tcl_Size start = 0; objResultPtr = *objv++; objc--; (void)Tcl_GetBytesFromObj(NULL, objResultPtr, &start); dst = Tcl_SetByteArrayLength(objResultPtr, length) + start; } else { objResultPtr = Tcl_NewByteArrayObj(NULL, length); dst = Tcl_SetByteArrayLength(objResultPtr, length); } while (objc--) { Tcl_Obj *objPtr = *objv++; /* * Every argument is either a bytearray with a ("pure") * value we know we can safely use, or it is an empty string. * We don't need to copy bytes from the empty strings. */ if (TclIsPureByteArray(objPtr)) { Tcl_Size more = 0; unsigned char *src = Tcl_GetBytesFromObj(NULL, objPtr, &more); memcpy(dst, src, more); dst += more; } } } else if ((allowUniChar && requestUniChar) || forceUniChar) { /* Efficiently produce a pure Tcl_UniChar array result */ Tcl_UniChar *dst; if (inPlace) { Tcl_Size start; objResultPtr = *objv++; objc--; /* Ugly interface! Force resize of the unicode array. */ (void)Tcl_GetUnicodeFromObj(objResultPtr, &start); Tcl_InvalidateStringRep(objResultPtr); if (0 == Tcl_AttemptSetObjLength(objResultPtr, length)) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "concatenation failed: unable to alloc %" TCL_Z_MODIFIER "u bytes", STRING_SIZE(length))); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); } return NULL; } dst = Tcl_GetUnicode(objResultPtr) + start; } else { Tcl_UniChar ch = 0; /* Ugly interface! No scheme to init array size. */ objResultPtr = Tcl_NewUnicodeObj(&ch, 0); /* PANIC? */ if (0 == Tcl_AttemptSetObjLength(objResultPtr, length)) { Tcl_DecrRefCount(objResultPtr); if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "concatenation failed: unable to alloc %" TCL_Z_MODIFIER "u bytes", STRING_SIZE(length))); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); } return NULL; } dst = Tcl_GetUnicode(objResultPtr); } while (objc--) { Tcl_Obj *objPtr = *objv++; if ((objPtr->bytes == NULL) || (objPtr->length)) { Tcl_Size more; Tcl_UniChar *src = Tcl_GetUnicodeFromObj(objPtr, &more); memcpy(dst, src, more * sizeof(Tcl_UniChar)); dst += more; } } } else { /* Efficiently concatenate string reps */ char *dst; if (inPlace) { Tcl_Size start; objResultPtr = *objv++; objc--; (void)TclGetStringFromObj(objResultPtr, &start); if (0 == Tcl_AttemptSetObjLength(objResultPtr, length)) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "concatenation failed: unable to alloc %" TCL_SIZE_MODIFIER "d bytes", length)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); } return NULL; } dst = TclGetString(objResultPtr) + start; TclFreeInternalRep(objResultPtr); } else { TclNewObj(objResultPtr); /* PANIC? */ if (0 == Tcl_AttemptSetObjLength(objResultPtr, length)) { Tcl_DecrRefCount(objResultPtr); if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "concatenation failed: unable to alloc %" TCL_SIZE_MODIFIER "d bytes", length)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); } return NULL; } dst = TclGetString(objResultPtr); } while (objc--) { Tcl_Obj *objPtr = *objv++; if ((objPtr->bytes == NULL) || (objPtr->length)) { Tcl_Size more; char *src = TclGetStringFromObj(objPtr, &more); memcpy(dst, src, more); dst += more; } } /* Must NUL-terminate! */ *dst = '\0'; } return objResultPtr; overflow: if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "max size for a Tcl value (%" TCL_SIZE_MODIFIER "d bytes) exceeded", TCL_SIZE_MAX)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); } return NULL; } /* *--------------------------------------------------------------------------- * * TclStringCmp -- * Compare two Tcl_Obj values as strings. * * Results: * Like memcmp, return -1, 0, or 1. * * Side effects: * String representations may be generated. Internal representation may * be changed. * *--------------------------------------------------------------------------- */ static int UniCharNcasememcmp( const void *ucsPtr, /* Unicode string to compare to uct. */ const void *uctPtr, /* Unicode string ucs is compared to. */ size_t numChars) /* Number of Unichars to compare. */ { const Tcl_UniChar *ucs = (const Tcl_UniChar *)ucsPtr; const Tcl_UniChar *uct = (const Tcl_UniChar *)uctPtr; for ( ; numChars != 0; numChars--, ucs++, uct++) { if (*ucs != *uct) { Tcl_UniChar lcs = Tcl_UniCharToLower(*ucs); Tcl_UniChar lct = Tcl_UniCharToLower(*uct); if (lcs != lct) { return (lcs - lct); } } } return 0; } static int UtfNmemcmp( const void *csPtr, /* UTF string to compare to ct. */ const void *ctPtr, /* UTF string cs is compared to. */ size_t numChars) /* Number of UTF chars to compare. */ { Tcl_UniChar ch1 = 0, ch2 = 0; const char *cs = (const char *)csPtr; const char *ct = (const char *)ctPtr; /* * Cannot use 'memcmp(cs, ct, n);' as byte representation of \u0000 (the * pair of bytes 0xC0,0x80) is larger than byte representation of \u0001 * (the byte 0x01.) */ while (numChars-- > 0) { /* * n must be interpreted as chars, not bytes. This should be called * only when both strings are of at least n chars long (no need for \0 * check) */ cs += TclUtfToUniChar(cs, &ch1); ct += TclUtfToUniChar(ct, &ch2); if (ch1 != ch2) { return (ch1 - ch2); } } return 0; } static int UtfNcasememcmp( const void *csPtr, /* UTF string to compare to ct. */ const void *ctPtr, /* UTF string cs is compared to. */ size_t numChars) /* Number of UTF chars to compare. */ { Tcl_UniChar ch1 = 0, ch2 = 0; const char *cs = (const char *)csPtr; const char *ct = (const char *)ctPtr; while (numChars-- > 0) { /* * n must be interpreted as chars, not bytes. * This should be called only when both strings are of * at least n chars long (no need for \0 check) */ cs += TclUtfToUniChar(cs, &ch1); ct += TclUtfToUniChar(ct, &ch2); if (ch1 != ch2) { ch1 = Tcl_UniCharToLower(ch1); ch2 = Tcl_UniCharToLower(ch2); if (ch1 != ch2) { return (ch1 - ch2); } } } return 0; } static int UniCharNmemcmp( const void *ucsPtr, /* Unicode string to compare to uct. */ const void *uctPtr, /* Unicode string ucs is compared to. */ size_t numChars) /* Number of unichars to compare. */ { const Tcl_UniChar *ucs = (const Tcl_UniChar *)ucsPtr; const Tcl_UniChar *uct = (const Tcl_UniChar *)uctPtr; #if defined(WORDS_BIGENDIAN) /* * We are definitely on a big-endian machine; memcmp() is safe */ return memcmp(ucs, uct, numChars*sizeof(Tcl_UniChar)); #else /* !WORDS_BIGENDIAN */ /* * We can't simply call memcmp() because that is not lexically correct. */ for ( ; numChars != 0; ucs++, uct++, numChars--) { if (*ucs != *uct) { return (*ucs - *uct); } } return 0; #endif /* WORDS_BIGENDIAN */ } int TclStringCmp( Tcl_Obj *value1Ptr, Tcl_Obj *value2Ptr, int checkEq, /* comparison is only for equality */ int nocase, /* comparison is not case sensitive */ Tcl_Size reqlength) /* requested length in characters; * TCL_INDEX_NONE to compare whole strings */ { const char *s1, *s2; int empty, match; Tcl_Size length, s1len = 0, s2len = 0; memCmpFn_t memCmpFn; if ((reqlength == 0) || (value1Ptr == value2Ptr)) { /* * Always match at 0 chars of if it is the same obj. * Note: as documented reqlength negative means it is ignored */ match = 0; } else { if (!nocase && TclIsPureByteArray(value1Ptr) && TclIsPureByteArray(value2Ptr)) { /* * Use binary versions of comparisons since that won't cause undue * type conversions and it is much faster. Only do this if we're * case-sensitive (which is all that really makes sense with byte * arrays anyway, and we have no memcasecmp() for some reason... :^) */ s1 = (char *) Tcl_GetBytesFromObj(NULL, value1Ptr, &s1len); s2 = (char *) Tcl_GetBytesFromObj(NULL, value2Ptr, &s2len); memCmpFn = memcmp; } else if (TclHasInternalRep(value1Ptr, &tclStringType) && TclHasInternalRep(value2Ptr, &tclStringType)) { /* * Do a Unicode-specific comparison if both of the args are of String * type. If the char length == byte length, we can do a memcmp. In * benchmark testing this proved the most efficient check between the * Unicode and string comparison operations. */ if (nocase) { s1 = (char *) Tcl_GetUnicodeFromObj(value1Ptr, &s1len); s2 = (char *) Tcl_GetUnicodeFromObj(value2Ptr, &s2len); memCmpFn = UniCharNcasememcmp; } else { s1len = Tcl_GetCharLength(value1Ptr); s2len = Tcl_GetCharLength(value2Ptr); if ((s1len == value1Ptr->length) && (value1Ptr->bytes != NULL) && (s2len == value2Ptr->length) && (value2Ptr->bytes != NULL)) { /* each byte represents one character so s1l3n, s2l3n, * and reqlength are in both bytes and characters */ s1 = value1Ptr->bytes; s2 = value2Ptr->bytes; memCmpFn = memcmp; } else { s1 = (char *) Tcl_GetUnicode(value1Ptr); s2 = (char *) Tcl_GetUnicode(value2Ptr); if ( #if defined(WORDS_BIGENDIAN) 1 #else checkEq #endif ) { memCmpFn = memcmp; s1len *= sizeof(Tcl_UniChar); s2len *= sizeof(Tcl_UniChar); if (reqlength > 0) { reqlength *= sizeof(Tcl_UniChar); } } else { memCmpFn = UniCharNmemcmp; } } } } else { empty = TclCheckEmptyString(value1Ptr); if (empty > 0) { switch (TclCheckEmptyString(value2Ptr)) { case -1: s1 = ""; s1len = 0; s2 = TclGetStringFromObj(value2Ptr, &s2len); break; case 0: match = -1; goto matchdone; case 1: default: /* avoid warn: `s2` may be used uninitialized */ match = 0; goto matchdone; } } else if (TclCheckEmptyString(value2Ptr) > 0) { switch (empty) { case -1: s2 = ""; s2len = 0; s1 = TclGetStringFromObj(value1Ptr, &s1len); break; case 0: match = 1; goto matchdone; case 1: default: /* avoid warn: `s1` may be used uninitialized */ match = 0; goto matchdone; } } else { s1 = TclGetStringFromObj(value1Ptr, &s1len); s2 = TclGetStringFromObj(value2Ptr, &s2len); } if (!nocase && checkEq && reqlength < 0) { /* * When we have equal-length we can check only for * (in)equality. We can use memcmp in all (n)eq cases because * we don't need to worry about lexical LE/BE variance. */ memCmpFn = memcmp; } else { /* * As a catch-all we will work with UTF-8. We cannot use * memcmp() as that is unsafe with any string containing NUL * (\xC0\x80 in Tcl's utf rep). We can use the more efficient * TclpUtfNcmp2 if we are case-sensitive and no specific * length was requested. */ if ((reqlength < 0) && !nocase) { memCmpFn = TclpUtfNcmp2; } else { s1len = Tcl_NumUtfChars(s1, s1len); s2len = Tcl_NumUtfChars(s2, s2len); memCmpFn = nocase ? UtfNcasememcmp : UtfNmemcmp; } } } /* At this point s1len, s2len, and reqlength should by now have been * adjusted so that they are all in the units expected by the selected * comparison function. */ length = (s1len < s2len) ? s1len : s2len; if (reqlength < 0) { /* * The requested length is negative, so ignore it by setting it * to length + 1 to correct the match var. */ reqlength = length + 1; } else if (reqlength > 0 && reqlength < length) { length = reqlength; } if (checkEq && reqlength < 0 && (s1len != s2len)) { match = 1; /* This will be reversed below. */ } else { /* * The comparison function should compare up to the minimum byte * length only. */ match = memCmpFn(s1, s2, length); } if ((match == 0) && (reqlength > length)) { match = s1len - s2len; } match = (match > 0) ? 1 : (match < 0) ? -1 : 0; } matchdone: return match; } /* *--------------------------------------------------------------------------- * * TclStringFirst -- * * Implements the [string first] operation. * * Results: * If needle is found as a substring of haystack, the index of the * first instance of such a find is returned. If needle is not present * as a substring of haystack, -1 is returned. * * Side effects: * needle and haystack may have their Tcl_ObjType changed. * *--------------------------------------------------------------------------- */ Tcl_Obj * TclStringFirst( Tcl_Obj *needle, Tcl_Obj *haystack, Tcl_Size start) { Tcl_Size lh = 0, ln = Tcl_GetCharLength(needle); Tcl_Size value = -1; Tcl_UniChar *checkStr, *endStr, *uh, *un; Tcl_Obj *obj; if (start < 0) { start = 0; } if (ln == 0) { /* We don't find empty substrings. Bizarre! * Whenever this routine is turned into a proper substring * finder, change to `return start` after limits imposed. */ goto firstEnd; } if (TclIsPureByteArray(needle) && TclIsPureByteArray(haystack)) { unsigned char *end, *check, *bh; unsigned char *bn = Tcl_GetBytesFromObj(NULL, needle, &ln); /* Find bytes in bytes */ bh = Tcl_GetBytesFromObj(NULL, haystack, &lh); if ((lh < ln) || (start > lh - ln)) { /* Don't start the loop if there cannot be a valid answer */ goto firstEnd; } end = bh + lh; check = bh + start; while (check + ln <= end) { /* * Look for the leading byte of the needle in the haystack * starting at check and stopping when there's not enough room * for the needle left. */ check = (unsigned char *)memchr(check, bn[0], (end + 1 - ln) - check); if (check == NULL) { /* Leading byte not found -> needle cannot be found. */ goto firstEnd; } /* Leading byte found, check rest of needle. */ if (0 == memcmp(check+1, bn+1, ln-1)) { /* Checks! Return the successful index. */ value = (check - bh); goto firstEnd; } /* Rest of needle match failed; Iterate to continue search. */ check++; } goto firstEnd; } /* * TODO: It might be nice to support some cases where it is not * necessary to shimmer to &tclStringType to compute the result, * and instead operate just on the objPtr->bytes values directly. * However, we also do not want the answer to change based on the * code pathway, or if it does we want that to be for some values * we explicitly decline to support. Getting there will involve * locking down in practice more firmly just what encodings produce * what supported results for the objPtr->bytes values. For now, * do only the well-defined Tcl_UniChar array search. */ un = Tcl_GetUnicodeFromObj(needle, &ln); uh = Tcl_GetUnicodeFromObj(haystack, &lh); if ((lh < ln) || (start > lh - ln)) { /* Don't start the loop if there cannot be a valid answer */ goto firstEnd; } endStr = uh + lh; for (checkStr = uh + start; checkStr + ln <= endStr; checkStr++) { if ((*checkStr == *un) && (0 == memcmp(checkStr + 1, un + 1, (ln-1) * sizeof(Tcl_UniChar)))) { value = (checkStr - uh); goto firstEnd; } } firstEnd: TclNewIndexObj(obj, value); return obj; } /* *--------------------------------------------------------------------------- * * TclStringLast -- * * Implements the [string last] operation. * * Results: * If needle is found as a substring of haystack, the index of the * last instance of such a find is returned. If needle is not present * as a substring of haystack, -1 is returned. * * Side effects: * needle and haystack may have their Tcl_ObjType changed. * *--------------------------------------------------------------------------- */ Tcl_Obj * TclStringLast( Tcl_Obj *needle, Tcl_Obj *haystack, Tcl_Size last) { Tcl_Size lh = 0, ln = Tcl_GetCharLength(needle); Tcl_Size value = -1; Tcl_UniChar *checkStr, *uh, *un; Tcl_Obj *obj; if (ln == 0) { /* * We don't find empty substrings. Bizarre! * * TODO: When we one day make this a true substring * finder, change this to "return last", after limitation. */ goto lastEnd; } if (TclIsPureByteArray(needle) && TclIsPureByteArray(haystack)) { unsigned char *check, *bh = Tcl_GetBytesFromObj(NULL, haystack, &lh); unsigned char *bn = Tcl_GetBytesFromObj(NULL, needle, &ln); if (last >= lh) { last = lh - 1; } if (last + 1 < ln) { /* Don't start the loop if there cannot be a valid answer */ goto lastEnd; } check = bh + last + 1 - ln; while (check >= bh) { if ((*check == bn[0]) && (0 == memcmp(check+1, bn+1, ln-1))) { value = (check - bh); goto lastEnd; } check--; } goto lastEnd; } uh = Tcl_GetUnicodeFromObj(haystack, &lh); un = Tcl_GetUnicodeFromObj(needle, &ln); if (last >= lh) { last = lh - 1; } if (last + 1 < ln) { /* Don't start the loop if there cannot be a valid answer */ goto lastEnd; } checkStr = uh + last + 1 - ln; while (checkStr >= uh) { if ((*checkStr == un[0]) && (0 == memcmp(checkStr+1, un+1, (ln-1)*sizeof(Tcl_UniChar)))) { value = (checkStr - uh); goto lastEnd; } checkStr--; } lastEnd: TclNewIndexObj(obj, value); return obj; } /* *--------------------------------------------------------------------------- * * TclStringReverse -- * * Implements the [string reverse] operation. * * Results: * A Tcl value which is the [string reverse] of the argument supplied. * When sharing rules permit and the caller requests, the returned value * might be the argument with modifications done in place. * * Side effects: * May allocate a new Tcl_Obj. * *--------------------------------------------------------------------------- */ static void ReverseBytes( unsigned char *to, /* Copy bytes into here... */ unsigned char *from, /* ...from here... */ Tcl_Size count) /* Until this many are copied, */ /* reversing as you go. */ { unsigned char *src = from + count; if (to == from) { /* Reversing in place */ while (--src > to) { unsigned char c = *src; *src = *to; *to++ = c; } } else { while (--src >= from) { *to++ = *src; } } } Tcl_Obj * TclStringReverse( Tcl_Obj *objPtr, int flags) { String *stringPtr; Tcl_UniChar ch = 0; int inPlace = flags & TCL_STRING_IN_PLACE; if (TclIsPureByteArray(objPtr)) { Tcl_Size numBytes = 0; unsigned char *from = Tcl_GetBytesFromObj(NULL, objPtr, &numBytes); if (!inPlace || Tcl_IsShared(objPtr)) { objPtr = Tcl_NewByteArrayObj(NULL, numBytes); } ReverseBytes(Tcl_GetBytesFromObj(NULL, objPtr, (Tcl_Size *)NULL), from, numBytes); return objPtr; } SetStringFromAny(NULL, objPtr); stringPtr = GET_STRING(objPtr); if (stringPtr->hasUnicode) { Tcl_UniChar *from = Tcl_GetUnicode(objPtr); stringPtr = GET_STRING(objPtr); Tcl_UniChar *src = from + stringPtr->numChars; Tcl_UniChar *to; if (!inPlace || Tcl_IsShared(objPtr)) { /* * Create a non-empty, pure Unicode value, so we can coax * Tcl_SetObjLength into growing the Unicode rep buffer. */ objPtr = Tcl_NewUnicodeObj(&ch, 1); Tcl_SetObjLength(objPtr, stringPtr->numChars); to = Tcl_GetUnicode(objPtr); stringPtr = GET_STRING(objPtr); while (--src >= from) { *to++ = *src; } } else { /* * Reversing in place. */ while (--src > from) { ch = *src; *src = *from; *from++ = ch; } } } if (objPtr->bytes) { Tcl_Size numChars = stringPtr->numChars; Tcl_Size numBytes = objPtr->length; char *to, *from = objPtr->bytes; if (!inPlace || Tcl_IsShared(objPtr)) { TclNewObj(objPtr); Tcl_SetObjLength(objPtr, numBytes); } to = objPtr->bytes; if (numChars < numBytes) { /* * Either numChars == -1 and we don't know how many chars are * represented by objPtr->bytes and we need Pass 1 just in case, * or numChars >= 0 and we know we have fewer chars than bytes, so * we know there's a multibyte character needing Pass 1. * * Pass 1. Reverse the bytes of each multi-byte character. */ Tcl_Size bytesLeft = numBytes; int chw; while (bytesLeft) { /* * NOTE: We know that the from buffer is NUL-terminated. It's * part of the contract for objPtr->bytes values. Thus, we can * skip calling Tcl_UtfCharComplete() here. */ int bytesInChar = TclUtfToUniChar(from, &chw); ReverseBytes((unsigned char *)to, (unsigned char *)from, bytesInChar); to += bytesInChar; from += bytesInChar; bytesLeft -= bytesInChar; } from = to = objPtr->bytes; } /* Pass 2. Reverse all the bytes. */ ReverseBytes((unsigned char *)to, (unsigned char *)from, numBytes); } return objPtr; } /* *--------------------------------------------------------------------------- * * TclStringReplace -- * * Implements the inner engine of the [string replace] and * [string insert] commands. * * The result is a concatenation of a prefix from objPtr, characters * 0 through first-1, the insertPtr string value, and a suffix from * objPtr, characters from first + count to the end. The effect is as if * the inner substring of characters first through first+count-1 are * removed and replaced with insertPtr. If insertPtr is NULL, it is * treated as an empty string. When passed the flag TCL_STRING_IN_PLACE, * this routine will try to do the work within objPtr, so long as no * sharing forbids it. Without that request, or as needed, a new Tcl * value will be allocated to be the result. * * Results: * A Tcl value that is the result of the substring replacement. May * return NULL in case of an error. When NULL is returned and interp is * non-NULL, error information is left in interp * *--------------------------------------------------------------------------- */ Tcl_Obj * TclStringReplace( Tcl_Interp *interp, /* For error reporting, may be NULL */ Tcl_Obj *objPtr, /* String to act upon */ Tcl_Size first, /* First index to replace */ Tcl_Size count, /* How many chars to replace */ Tcl_Obj *insertPtr, /* Replacement string, may be NULL */ int flags) /* TCL_STRING_IN_PLACE => attempt in-place */ { int inPlace = flags & TCL_STRING_IN_PLACE; Tcl_Obj *result; /* Replace nothing with nothing */ if ((insertPtr == NULL) && (count <= 0)) { if (inPlace) { return objPtr; } else { return Tcl_DuplicateObj(objPtr); } } if (first < 0) { first = 0; } /* * The caller very likely had to call Tcl_GetCharLength() or similar * to be able to process index values. This means it is likely that * objPtr is either a proper "bytearray" or a "string" or else it has * a known and short string rep. */ if (TclIsPureByteArray(objPtr)) { Tcl_Size numBytes = 0; unsigned char *bytes = Tcl_GetBytesFromObj(NULL, objPtr, &numBytes); if (insertPtr == NULL) { /* Replace something with nothing. */ assert ( first <= numBytes ) ; assert ( count <= numBytes ) ; assert ( first + count <= numBytes ) ; result = Tcl_NewByteArrayObj(NULL, numBytes - count);/* PANIC? */ TclAppendBytesToByteArray(result, bytes, first); TclAppendBytesToByteArray(result, bytes + first + count, numBytes - count - first); return result; } /* Replace everything */ if ((first == 0) && (count == numBytes)) { return insertPtr; } if (TclIsPureByteArray(insertPtr)) { Tcl_Size newBytes = 0; unsigned char *iBytes = Tcl_GetBytesFromObj(NULL, insertPtr, &newBytes); if (count == newBytes && inPlace && !Tcl_IsShared(objPtr)) { /* * Removal count and replacement count are equal. * Other conditions permit. Do in-place splice. */ memcpy(bytes + first, iBytes, count); Tcl_InvalidateStringRep(objPtr); return objPtr; } if (newBytes > (TCL_SIZE_MAX - (numBytes - count))) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "max size for a Tcl value (%" TCL_SIZE_MODIFIER "d bytes) exceeded", TCL_SIZE_MAX)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); } return NULL; } result = Tcl_NewByteArrayObj(NULL, numBytes - count + newBytes); /* PANIC? */ Tcl_SetByteArrayLength(result, 0); TclAppendBytesToByteArray(result, bytes, first); TclAppendBytesToByteArray(result, iBytes, newBytes); TclAppendBytesToByteArray(result, bytes + first + count, numBytes - count - first); return result; } /* Flow through to try other approaches below */ } /* * TODO: Figure out how not to generate a Tcl_UniChar array rep * when it can be determined objPtr->bytes points to a string of * all single-byte characters so we can index it directly. */ /* The traditional implementation... */ { Tcl_Size numChars; Tcl_UniChar *ustring = Tcl_GetUnicodeFromObj(objPtr, &numChars); /* TODO: Is there an in-place option worth pursuing here? */ result = Tcl_NewUnicodeObj(ustring, first); if (insertPtr) { Tcl_AppendObjToObj(result, insertPtr); } if ((first + count) < numChars) { Tcl_AppendUnicodeToObj(result, ustring + first + count, numChars - first - count); } return result; } } /* *--------------------------------------------------------------------------- * * FillUnicodeRep -- * * Populate the Unicode internal rep with the Unicode form of its string * rep. The object must already have a "String" internal rep. * * Results: * None. * * Side effects: * Reallocates the String internal rep. * *--------------------------------------------------------------------------- */ static void FillUnicodeRep( Tcl_Obj *objPtr) /* The object in which to fill the unicode * rep. */ { String *stringPtr = GET_STRING(objPtr); ExtendUnicodeRepWithString(objPtr, objPtr->bytes, objPtr->length, stringPtr->numChars); } static void ExtendUnicodeRepWithString( Tcl_Obj *objPtr, const char *bytes, Tcl_Size numBytes, Tcl_Size numAppendChars) { String *stringPtr = GET_STRING(objPtr); Tcl_Size needed, numOrigChars = 0; Tcl_UniChar *dst, unichar = 0; if (stringPtr->hasUnicode) { numOrigChars = stringPtr->numChars; } if (numAppendChars == TCL_INDEX_NONE) { TclNumUtfCharsM(numAppendChars, bytes, numBytes); } needed = numOrigChars + numAppendChars; if (needed > stringPtr->maxChars) { GrowUnicodeBuffer(objPtr, needed); stringPtr = GET_STRING(objPtr); } stringPtr->hasUnicode = 1; if (bytes) { stringPtr->numChars = needed; } else { numAppendChars = 0; } dst = stringPtr->unicode + numOrigChars; if (numAppendChars-- > 0) { bytes += TclUtfToUniChar(bytes, &unichar); *dst++ = unichar; while (numAppendChars-- > 0) { bytes += TclUtfToUniChar(bytes, &unichar); *dst++ = unichar; } } *dst = 0; } /* *---------------------------------------------------------------------- * * DupStringInternalRep -- * * Initialize the internal representation of a new Tcl_Obj to a copy of * the internal representation of an existing string object. * * Results: * None. * * Side effects: * copyPtr's internal rep is set to a copy of srcPtr's internal * representation. * *---------------------------------------------------------------------- */ static void DupStringInternalRep( Tcl_Obj *srcPtr, /* Object with internal rep to copy. Must have * an internal rep of type "String". */ Tcl_Obj *copyPtr) /* Object with internal rep to set. Must not * currently have an internal rep.*/ { String *srcStringPtr = GET_STRING(srcPtr); String *copyStringPtr = NULL; if (srcStringPtr->numChars == TCL_INDEX_NONE) { /* * The String struct in the source value holds zero useful data. Don't * bother copying it. Don't even bother allocating space in which to * copy it. Just let the copy be untyped. */ return; } if (srcStringPtr->hasUnicode) { int copyMaxChars; if (srcStringPtr->maxChars / 2 >= srcStringPtr->numChars) { copyMaxChars = 2 * srcStringPtr->numChars; } else { copyMaxChars = srcStringPtr->maxChars; } copyStringPtr = stringAttemptAlloc(copyMaxChars); if (copyStringPtr == NULL) { copyMaxChars = srcStringPtr->numChars; copyStringPtr = stringAlloc(copyMaxChars); } copyStringPtr->maxChars = copyMaxChars; memcpy(copyStringPtr->unicode, srcStringPtr->unicode, srcStringPtr->numChars * sizeof(Tcl_UniChar)); copyStringPtr->unicode[srcStringPtr->numChars] = 0; } else { copyStringPtr = stringAlloc(0); copyStringPtr->maxChars = 0; copyStringPtr->unicode[0] = 0; } copyStringPtr->hasUnicode = srcStringPtr->hasUnicode; copyStringPtr->numChars = srcStringPtr->numChars; /* * Tricky point: the string value was copied by generic object management * code, so it doesn't contain any extra bytes that might exist in the * source object. */ copyStringPtr->allocated = copyPtr->bytes ? copyPtr->length : 0; SET_STRING(copyPtr, copyStringPtr); copyPtr->typePtr = &tclStringType; } /* *---------------------------------------------------------------------- * * SetStringFromAny -- * * Create an internal representation of type "String" for an object. * * Results: * This operation always succeeds and returns TCL_OK. * * Side effects: * Any old internal representation for objPtr is freed and the internal * representation is set to &tclStringType. * *---------------------------------------------------------------------- */ static int SetStringFromAny( TCL_UNUSED(Tcl_Interp *), Tcl_Obj *objPtr) /* The object to convert. */ { if (!TclHasInternalRep(objPtr, &tclStringType)) { String *stringPtr = stringAlloc(0); /* * Convert whatever we have into an untyped value. Just A String. */ (void) TclGetString(objPtr); TclFreeInternalRep(objPtr); /* * Create a basic String internalrep that just points to the UTF-8 string * already in place at objPtr->bytes. */ stringPtr->numChars = -1; stringPtr->allocated = objPtr->length; stringPtr->maxChars = 0; stringPtr->hasUnicode = 0; SET_STRING(objPtr, stringPtr); objPtr->typePtr = &tclStringType; } return TCL_OK; } /* *---------------------------------------------------------------------- * * UpdateStringOfString -- * * Update the string representation for an object whose internal * representation is "String". * * Results: * None. * * Side effects: * The object's string may be set by converting its Unicode representation * to UTF format. * *---------------------------------------------------------------------- */ static void UpdateStringOfString( Tcl_Obj *objPtr) /* Object with string rep to update. */ { String *stringPtr = GET_STRING(objPtr); /* * This routine is only called when we need to generate the * string rep objPtr->bytes because it does not exist -- it is NULL. * In that circumstance, any lingering claim about the size of * memory pointed to by that NULL pointer is clearly bogus, and * needs a reset. */ stringPtr->allocated = 0; if (stringPtr->numChars == 0) { TclInitEmptyStringRep(objPtr); } else { (void) ExtendStringRepWithUnicode(objPtr, stringPtr->unicode, stringPtr->numChars); } } static Tcl_Size ExtendStringRepWithUnicode( Tcl_Obj *objPtr, const Tcl_UniChar *unicode, Tcl_Size numChars) { /* * Precondition: this is the "string" Tcl_ObjType. */ Tcl_Size i, origLength, size = 0; char *dst; String *stringPtr = GET_STRING(objPtr); if (numChars < 0) { numChars = UnicodeLength(unicode); } if (numChars == 0) { return 0; } if (objPtr->bytes == NULL) { objPtr->length = 0; } size = origLength = objPtr->length; /* * Quick cheap check in case we have more than enough room. */ if (numChars <= (TCL_SIZE_MAX - size)/TCL_UTF_MAX && stringPtr->allocated >= size + numChars * TCL_UTF_MAX) { goto copyBytes; } for (i = 0; i < numChars && size >= 0; i++) { /* TODO - overflow check! I don't think check below at end suffices */ size += TclUtfCount(unicode[i]); } if (size < 0) { Tcl_Panic("max size for a Tcl value (%" TCL_SIZE_MODIFIER "d bytes) exceeded", TCL_SIZE_MAX); } /* * Grow space if needed. */ if (size > stringPtr->allocated) { GrowStringBuffer(objPtr, size, 1); } copyBytes: dst = objPtr->bytes + origLength; for (i = 0; i < numChars; i++) { dst += Tcl_UniCharToUtf(unicode[i], dst); } *dst = '\0'; objPtr->length = dst - objPtr->bytes; return numChars; } /* *---------------------------------------------------------------------- * * FreeStringInternalRep -- * * Deallocate the storage associated with a String data object's internal * representation. * * Results: * None. * * Side effects: * Frees memory. * *---------------------------------------------------------------------- */ static void FreeStringInternalRep( Tcl_Obj *objPtr) /* Object with internal rep to free. */ { Tcl_Free(GET_STRING(objPtr)); objPtr->typePtr = NULL; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclStringRep.h0000644000175000017500000000570014726623136015765 0ustar sergeisergei/* * tclStringRep.h -- * * This file contains the definition of internal representations of a string * and macros to access it. * * Conceptually, a string is a sequence of Unicode code points. Internally * it may be stored in an encoding form such as a modified version of UTF-8 * or UTF-32. * * Copyright (c) 1995-1997 Sun Microsystems, Inc. * Copyright (c) 1999 by Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef _TCLSTRINGREP #define _TCLSTRINGREP /* * The following structure is the internal rep for a String object. It keeps * track of how much memory has been used and how much has been allocated for * the various representations to enable growing and shrinking of * the String object with fewer mallocs. To optimize string * length and indexing operations, this structure also stores the number of * code points (independent of encoding form) once that value has been computed. */ typedef struct { Tcl_Size numChars; /* The number of chars in the string. * TCL_INDEX_NONE means this value has not been * calculated. Any other means that there is a valid * Unicode rep, or that the number of UTF bytes == * the number of chars. */ Tcl_Size allocated; /* The amount of space allocated for * the UTF-8 string. Does not include nul * terminator so actual allocation is * (allocated+1). */ Tcl_Size maxChars; /* Max number of chars that can fit in the * space allocated for the Unicode array. */ int hasUnicode; /* Boolean determining whether the string has * a Tcl_UniChar representation. */ Tcl_UniChar unicode[TCLFLEXARRAY]; /* The array of Tcl_UniChar units. * The actual size of this field depends on * the maxChars field above. */ } String; /* Limit on string lengths. The -1 because limit does not include the nul */ #define STRING_MAXCHARS \ ((Tcl_Size)((TCL_SIZE_MAX - offsetof(String, unicode))/sizeof(Tcl_UniChar) - 1)) /* Memory needed to hold a string of length numChars - including NUL */ #define STRING_SIZE(numChars) \ (offsetof(String, unicode) + sizeof(Tcl_UniChar) + ((numChars) * sizeof(Tcl_UniChar))) #define stringAttemptAlloc(numChars) \ (String *) Tcl_AttemptAlloc(STRING_SIZE(numChars)) #define stringAlloc(numChars) \ (String *) Tcl_Alloc(STRING_SIZE(numChars)) #define stringRealloc(ptr, numChars) \ (String *) Tcl_Realloc((ptr), STRING_SIZE(numChars)) #define stringAttemptRealloc(ptr, numChars) \ (String *) Tcl_AttemptRealloc((ptr), STRING_SIZE(numChars)) #define GET_STRING(objPtr) \ ((String *) (objPtr)->internalRep.twoPtrValue.ptr1) #define SET_STRING(objPtr, stringPtr) \ ((objPtr)->internalRep.twoPtrValue.ptr2 = NULL), \ ((objPtr)->internalRep.twoPtrValue.ptr1 = (void *) (stringPtr)) #endif /* _TCLSTRINGREP */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclStringTrim.h0000644000175000017500000000236214726623136016153 0ustar sergeisergei/* * tclStringTrim.h -- * * This file contains the definition of what characters are to be trimmed * from a string by [string trim] by default. It's only needed by Tcl's * implementation; it does not form a public or private API at all. * * Copyright (c) 1987-1993 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. * Copyright (c) 1998-2000 Scriptics Corporation. * Copyright (c) 2002 ActiveState Corporation. * Copyright (c) 2003-2013 Donal K. Fellows. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef TCL_STRING_TRIM_H #define TCL_STRING_TRIM_H /* * Default set of characters to trim in [string trim] and friends. This is a * UTF-8 literal string containing all Unicode space characters. [TIP #413] */ MODULE_SCOPE const char tclDefaultTrimSet[]; /* * The whitespace trimming set used when [concat]enating. This is a subset of * the above, and deliberately so. * * TODO: Find a reasonable way to guarantee in sync with TclIsSpaceProc() */ #define CONCAT_TRIM_SET " \f\v\r\t\n" #endif /* TCL_STRING_TRIM_H */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclStubCall.c0000644000175000017500000000710014726623136015550 0ustar sergeisergei/* * tclStubCall.c -- * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #ifndef _WIN32 # include #else # define dlopen(a,b) (void *)LoadLibraryW(JOIN(L,a)) # define dlsym(a,b) (void *)GetProcAddress((HMODULE)(a),b) # define dlerror() "" #endif MODULE_SCOPE void *tclStubsHandle; /* *---------------------------------------------------------------------- * * TclStubCall -- * * Load the Tcl core dynamically, version "9.0" (or higher, in future versions). * * Results: * Returns a function from the Tcl dynamic library or a function * returning NULL if that function cannot be found. See PROCNAME table. * * The functions Tcl_MainEx and Tcl_MainExW never return. * Tcl_GetMemoryInfo and Tcl_StaticLibrary return (void), * Tcl_SetExitProc returns its previous exitProc and * Tcl_SetPreInitScript returns the previous script. This means that * those 6 functions cannot be used to initialize the stub-table, * only the first 4 functions in the table can do that. * *---------------------------------------------------------------------- */ /* Table containing which function will be returned, depending on the "arg" */ static const char PROCNAME[][24] = { "_Tcl_SetPanicProc", /* Default, whenever "arg" <= 0 or "arg" > 9 */ "_Tcl_InitSubsystems", /* "arg" == (void *)1 */ "_Tcl_FindExecutable", /* "arg" == (void *)2 */ "_TclZipfs_AppHook", /* "arg" == (void *)3 */ "_Tcl_MainExW", /* "arg" == (void *)4 */ "_Tcl_MainEx", /* "arg" == (void *)5 */ "_Tcl_StaticLibrary", /* "arg" == (void *)6 */ "_Tcl_SetExitProc", /* "arg" == (void *)7 */ "_Tcl_GetMemoryInfo", /* "arg" == (void *)8 */ "_Tcl_SetPreInitScript" /* "arg" == (void *)9 */ }; MODULE_SCOPE const void *nullVersionProc(void) { return NULL; } static const char CANNOTCALL[] = "Cannot call %s from stubbed extension\n"; static const char CANNOTFIND[] = "Cannot find %s: %s\n"; MODULE_SCOPE void * TclStubCall(void *arg) { static void *stubFn[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}; size_t index = PTR2UINT(arg); if (index >= sizeof(PROCNAME)/sizeof(PROCNAME[0])) { /* Any other value means Tcl_SetPanicProc() with non-null panicProc */ index = 0; } if (tclStubsHandle == INT2PTR(-1)) { if ((index == 0) && (arg != NULL)) { ((Tcl_PanicProc *)arg)(CANNOTCALL, PROCNAME[index] + 1); } else { fprintf(stderr, CANNOTCALL, PROCNAME[index] + 1); abort(); } } if (!stubFn[index]) { if (!tclStubsHandle) { tclStubsHandle = dlopen(CFG_RUNTIME_DLLFILE, RTLD_NOW|RTLD_LOCAL); if (!tclStubsHandle) { #if defined(_WIN32) tclStubsHandle = dlopen(CFG_RUNTIME_BINDIR "\\" CFG_RUNTIME_DLLFILE, RTLD_NOW|RTLD_LOCAL); #elif defined(__CYGWIN__) tclStubsHandle = dlopen(CFG_RUNTIME_BINDIR "/" CFG_RUNTIME_DLLFILE, RTLD_NOW|RTLD_LOCAL); #else tclStubsHandle = dlopen(CFG_RUNTIME_LIBDIR "/" CFG_RUNTIME_DLLFILE, RTLD_NOW|RTLD_LOCAL); #endif } if (!tclStubsHandle) { if ((index == 0) && (arg != NULL)) { ((Tcl_PanicProc *)arg)(CANNOTFIND, CFG_RUNTIME_DLLFILE, dlerror()); } else { fprintf(stderr, CANNOTFIND, CFG_RUNTIME_DLLFILE, dlerror()); abort(); } } } stubFn[index] = dlsym(tclStubsHandle, PROCNAME[index] + 1); if (!stubFn[index]) { stubFn[index] = dlsym(tclStubsHandle, PROCNAME[index]); if (!stubFn[index]) { stubFn[index] = (void *)nullVersionProc; } } } return stubFn[index]; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclStubInit.c0000644000175000017500000012634614731057471015615 0ustar sergeisergei/* * tclStubInit.c -- * * This file contains the initializers for the Tcl stub vectors. * * Copyright © 1998-1999 Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tommath_private.h" #include "tclTomMath.h" #ifdef __CYGWIN__ # include #endif #ifdef __GNUC__ #pragma GCC dependency "tcl.decls" #pragma GCC dependency "tclInt.decls" #pragma GCC dependency "tclTomMath.decls" #endif /* * Remove macros that will interfere with the definitions below. */ #undef Tcl_Alloc #undef Tcl_Free #undef Tcl_Realloc #undef Tcl_NewBooleanObj #undef Tcl_NewByteArrayObj #undef Tcl_NewDoubleObj #undef Tcl_NewIntObj #undef Tcl_NewListObj #undef Tcl_NewLongObj #undef Tcl_DbNewLongObj #undef Tcl_NewObj #undef Tcl_NewStringObj #undef Tcl_GetUnicode #undef Tcl_GetUnicodeFromObj #undef Tcl_NewUnicodeObj #undef Tcl_SetUnicodeObj #undef Tcl_DumpActiveMemory #undef Tcl_ValidateAllMemory #undef Tcl_FindHashEntry #undef Tcl_CreateHashEntry #undef Tcl_Panic #undef Tcl_FindExecutable #undef Tcl_SetExitProc #undef Tcl_SetPanicProc #undef TclpGetPid #undef TclSockMinimumBuffers #undef Tcl_SetIntObj #undef Tcl_SetLongObj #undef Tcl_ListObjGetElements #undef Tcl_ListObjLength #undef Tcl_DictObjSize #undef Tcl_SplitList #undef Tcl_SplitPath #undef Tcl_FSSplitPath #undef Tcl_ParseArgsObjv #undef TclStaticLibrary #define TclStaticLibrary Tcl_StaticLibrary #undef TclObjInterpProc #if !defined(_WIN32) && !defined(__CYGWIN__) # undef Tcl_WinConvertError # define Tcl_WinConvertError 0 #endif #undef TclGetStringFromObj #if defined(TCL_NO_DEPRECATED) # define TclGetStringFromObj 0 # define TclGetBytesFromObj 0 # define TclGetUnicodeFromObj 0 #endif #undef Tcl_Close #define Tcl_Close 0 #undef Tcl_GetByteArrayFromObj #define Tcl_GetByteArrayFromObj 0 #define TclUnusedStubEntry 0 #define TclUtfCharComplete Tcl_UtfCharComplete #define TclUtfNext Tcl_UtfNext #define TclUtfPrev Tcl_UtfPrev #undef TclListObjGetElements #undef TclListObjLength #if defined(TCL_NO_DEPRECATED) # define TclListObjGetElements 0 # define TclListObjLength 0 # define TclDictObjSize 0 # define TclSplitList 0 # define TclSplitPath 0 # define TclFSSplitPath 0 # define TclParseArgsObjv 0 # define TclGetAliasObj 0 #else /* !defined(TCL_NO_DEPRECATED) */ int TclListObjGetElements(Tcl_Interp *interp, Tcl_Obj *listPtr, void *objcPtr, Tcl_Obj ***objvPtr) { Tcl_Size n = TCL_INDEX_NONE; int result = Tcl_ListObjGetElements(interp, listPtr, &n, objvPtr); if (objcPtr) { if ((sizeof(int) != sizeof(Tcl_Size)) && (result == TCL_OK) && (n > INT_MAX)) { if (interp) { Tcl_AppendResult(interp, "List too large to be processed", (char *)NULL); } return TCL_ERROR; } *(int *)objcPtr = (int)n; } return result; } int TclListObjLength(Tcl_Interp *interp, Tcl_Obj *listPtr, void *lengthPtr) { Tcl_Size n = TCL_INDEX_NONE; int result = Tcl_ListObjLength(interp, listPtr, &n); if (lengthPtr) { if ((sizeof(int) != sizeof(Tcl_Size)) && (result == TCL_OK) && (n > INT_MAX)) { if (interp) { Tcl_AppendResult(interp, "List too large to be processed", (char *)NULL); } return TCL_ERROR; } *(int *)lengthPtr = (int)n; } return result; } int TclDictObjSize(Tcl_Interp *interp, Tcl_Obj *dictPtr, void *sizePtr) { Tcl_Size n = TCL_INDEX_NONE; int result = Tcl_DictObjSize(interp, dictPtr, &n); if (sizePtr) { if ((sizeof(int) != sizeof(Tcl_Size)) && (result == TCL_OK) && (n > INT_MAX)) { if (interp) { Tcl_AppendResult(interp, "Dict too large to be processed", (char *)NULL); } return TCL_ERROR; } *(int *)sizePtr = (int)n; } return result; } int TclSplitList(Tcl_Interp *interp, const char *listStr, void *argcPtr, const char ***argvPtr) { Tcl_Size n = TCL_INDEX_NONE; int result = Tcl_SplitList(interp, listStr, &n, argvPtr); if (argcPtr) { if ((sizeof(int) != sizeof(Tcl_Size)) && (result == TCL_OK) && (n > INT_MAX)) { if (interp) { Tcl_AppendResult(interp, "List too large to be processed", (char *)NULL); } Tcl_Free((void *)*argvPtr); return TCL_ERROR; } *(int *)argcPtr = (int)n; } return result; } void TclSplitPath(const char *path, void *argcPtr, const char ***argvPtr) { Tcl_Size n = TCL_INDEX_NONE; Tcl_SplitPath(path, &n, argvPtr); if (argcPtr) { if ((sizeof(int) != sizeof(Tcl_Size)) && (n > INT_MAX)) { n = TCL_INDEX_NONE; /* No other way to return an error-situation */ Tcl_Free((void *)*argvPtr); *argvPtr = NULL; } *(int *)argcPtr = (int)n; } } Tcl_Obj *TclFSSplitPath(Tcl_Obj *pathPtr, void *lenPtr) { Tcl_Size n = TCL_INDEX_NONE; Tcl_Obj *result = Tcl_FSSplitPath(pathPtr, &n); if (lenPtr) { if ((sizeof(int) != sizeof(Tcl_Size)) && result && (n > INT_MAX)) { Tcl_DecrRefCount(result); return NULL; } *(int *)lenPtr = (int)n; } return result; } int TclParseArgsObjv(Tcl_Interp *interp, const Tcl_ArgvInfo *argTable, void *objcPtr, Tcl_Obj *const *objv, Tcl_Obj ***remObjv) { Tcl_Size n = (*(int *)objcPtr < 0) ? TCL_INDEX_NONE: (Tcl_Size)*(int *)objcPtr ; int result = Tcl_ParseArgsObjv(interp, argTable, &n, objv, remObjv); *(int *)objcPtr = (int)n; return result; } int TclGetAliasObj(Tcl_Interp *interp, const char *childCmd, Tcl_Interp **targetInterpPtr, const char **targetCmdPtr, int *objcPtr, Tcl_Obj ***objv) { Tcl_Size n = TCL_INDEX_NONE; int result = Tcl_GetAliasObj(interp, childCmd, targetInterpPtr, targetCmdPtr, &n, objv); if (objcPtr) { if ((sizeof(int) != sizeof(Tcl_Size)) && (result == TCL_OK) && (n > INT_MAX)) { if (interp) { Tcl_AppendResult(interp, "List too large to be processed", NULL); } return TCL_ERROR; } *objcPtr = (int)n; } return result; } #endif /* !defined(TCL_NO_DEPRECATED) */ #define TclBN_mp_add mp_add #define TclBN_mp_add_d mp_add_d #define TclBN_mp_and mp_and #define TclBN_mp_clamp mp_clamp #define TclBN_mp_clear mp_clear #define TclBN_mp_clear_multi mp_clear_multi #define TclBN_mp_cmp mp_cmp #define TclBN_mp_cmp_d mp_cmp_d #define TclBN_mp_cmp_mag mp_cmp_mag #define TclBN_mp_cnt_lsb mp_cnt_lsb #define TclBN_mp_copy mp_copy #define TclBN_mp_count_bits mp_count_bits #define TclBN_mp_div mp_div #define TclBN_mp_div_d mp_div_d #define TclBN_mp_div_2 mp_div_2 #define TclBN_mp_div_2d mp_div_2d #define TclBN_mp_exch mp_exch #define TclBN_mp_get_mag_u64 mp_get_mag_u64 #define TclBN_mp_grow mp_grow #define TclBN_mp_init mp_init #define TclBN_mp_init_copy mp_init_copy #define TclBN_mp_init_multi mp_init_multi #define TclBN_mp_init_set mp_init_set #define TclBN_mp_init_size mp_init_size #define TclBN_mp_init_i64 mp_init_i64 #define TclBN_mp_init_u64 mp_init_u64 #define TclBN_mp_lshd mp_lshd #define TclBN_mp_mod mp_mod #define TclBN_mp_mod_2d mp_mod_2d #define TclBN_mp_mul mp_mul #define TclBN_mp_mul_d mp_mul_d #define TclBN_mp_mul_2 mp_mul_2 #define TclBN_mp_mul_2d mp_mul_2d #define TclBN_mp_neg mp_neg #define TclBN_mp_or mp_or #define TclBN_mp_pack mp_pack #define TclBN_mp_pack_count mp_pack_count #define TclBN_mp_radix_size mp_radix_size #define TclBN_mp_read_radix mp_read_radix #define TclBN_mp_rshd mp_rshd #define TclBN_mp_set_i64 mp_set_i64 #define TclBN_mp_set_u64 mp_set_u64 #define TclBN_mp_shrink mp_shrink #define TclBN_mp_sqr mp_sqr #define TclBN_mp_sqrt mp_sqrt #define TclBN_mp_sub mp_sub #define TclBN_mp_sub_d mp_sub_d #define TclBN_mp_signed_rsh mp_signed_rsh #define TclBN_mp_to_radix mp_to_radix #define TclBN_mp_to_ubin mp_to_ubin #define TclBN_mp_ubin_size mp_ubin_size #define TclBN_mp_unpack mp_unpack #define TclBN_mp_xor mp_xor #define TclBN_mp_zero mp_zero #define TclBN_s_mp_add s_mp_add #define TclBN_mp_balance_mul s_mp_balance_mul #define TclBN_mp_div_3 s_mp_div_3 #define TclBN_mp_karatsuba_mul s_mp_karatsuba_mul #define TclBN_mp_karatsuba_sqr s_mp_karatsuba_sqr #define TclBN_mp_mul_digs s_mp_mul_digs #define TclBN_mp_mul_digs_fast s_mp_mul_digs_fast #define TclBN_mp_reverse s_mp_reverse #define TclBN_s_mp_sqr s_mp_sqr #define TclBN_mp_sqr_fast s_mp_sqr_fast #define TclBN_s_mp_sub s_mp_sub #define TclBN_mp_toom_mul s_mp_toom_mul #define TclBN_mp_toom_sqr s_mp_toom_sqr #ifndef MAC_OSX_TCL /* On UNIX, fill with other stub entries */ # define Tcl_MacOSXOpenVersionedBundleResources 0 # define Tcl_MacOSXNotifierAddRunLoopMode 0 #endif #ifdef _WIN32 # define Tcl_CreateFileHandler 0 # define Tcl_DeleteFileHandler 0 # define Tcl_GetOpenFile 0 #else # define TclpIsAtty isatty #endif #ifdef _WIN32 # define TclUnixWaitForFile 0 # define TclUnixCopyFile 0 # define TclUnixOpenTemporaryFile 0 # define TclpReaddir 0 # undef TclpIsAtty # define TclpIsAtty 0 #elif defined(__CYGWIN__) # define TclpIsAtty isatty static void doNothing(void) { /* dummy implementation, no need to do anything */ } # define TclWinAddProcess (void (*) (void *, Tcl_Size)) doNothing # define TclWinFlushDirtyChannels doNothing #define TclWinNoBackslash winNoBackslash static char * TclWinNoBackslash(char *path) { char *p; for (p = path; *p != '\0'; p++) { if (*p == '\\') { *p = '/'; } } return path; } void *TclWinGetTclInstance(void) { void *hInstance = NULL; GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (const wchar_t *)&TclWinNoBackslash, &hInstance); return hInstance; } Tcl_Size TclpGetPid(Tcl_Pid pid) { return (Tcl_Size)PTR2INT(pid); } #if defined(TCL_WIDE_INT_IS_LONG) /* On Cygwin64, long is 64-bit while on Win64 long is 32-bit. Therefore * we have to make sure that all stub entries on Cygwin64 follow the Win64 * signature. Tcl 9 must find a better solution, but that cannot be done * without introducing a binary incompatibility. */ #define Tcl_GetLongFromObj (int(*)(Tcl_Interp*,Tcl_Obj*,long*))(void *)Tcl_GetIntFromObj static int exprInt(Tcl_Interp *interp, const char *expr, int *ptr){ long longValue; int result = Tcl_ExprLong(interp, expr, &longValue); if (result == TCL_OK) { if ((longValue >= (long)(INT_MIN)) && (longValue <= (long)(UINT_MAX))) { *ptr = (int)longValue; } else { Tcl_SetObjResult(interp, Tcl_NewStringObj( "integer value too large to represent", -1)); result = TCL_ERROR; } } return result; } #define Tcl_ExprLong (int(*)(Tcl_Interp*,const char*,long*))(void *)exprInt static int exprIntObj(Tcl_Interp *interp, Tcl_Obj*expr, int *ptr){ long longValue; int result = Tcl_ExprLongObj(interp, expr, &longValue); if (result == TCL_OK) { if ((longValue >= (long)(INT_MIN)) && (longValue <= (long)(UINT_MAX))) { *ptr = (int)longValue; } else { Tcl_SetObjResult(interp, Tcl_NewStringObj( "integer value too large to represent", -1)); result = TCL_ERROR; } } return result; } #define Tcl_ExprLongObj (int(*)(Tcl_Interp*,Tcl_Obj*,long*))(void *)exprIntObj #endif /* TCL_WIDE_INT_IS_LONG */ #else /* __CYGWIN__ */ # define TclWinGetTclInstance 0 # define TclpGetPid 0 # define TclWinFlushDirtyChannels 0 # define TclWinNoBackslash 0 # define TclWinAddProcess 0 #endif /* * WARNING: The contents of this file is automatically generated by the * tools/genStubs.tcl script. Any modifications to the function declarations * below should be made in the generic/tcl.decls script. */ MODULE_SCOPE const TclStubs tclStubs; MODULE_SCOPE const TclTomMathStubs tclTomMathStubs; #ifdef __GNUC__ /* * The rest of this file shouldn't warn about deprecated functions; they're * there because we intend them to be so and know that this file is OK to * touch those fields. */ #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #ifdef TCL_WITH_EXTERNAL_TOMMATH /* If Tcl is linked with an external libtommath 1.2.x, then mp_expt_n doesn't * exist (since that was introduced in libtommath 1.3.0. Provide it here.) */ mp_err MP_WUR TclBN_mp_expt_n(const mp_int *a, int b, mp_int *c) { if ((unsigned)b > MP_MIN(MP_DIGIT_MAX, INT_MAX)) { return MP_VAL; } return mp_expt_u32(a, (uint32_t)b, c);; } #endif /* TCL_WITH_EXTERNAL_TOMMATH */ /* !BEGIN!: Do not edit below this line. */ static const TclIntStubs tclIntStubs = { TCL_STUB_MAGIC, 0, 0, /* 0 */ 0, /* 1 */ 0, /* 2 */ TclAllocateFreeObjects, /* 3 */ 0, /* 4 */ TclCleanupChildren, /* 5 */ TclCleanupCommand, /* 6 */ TclCopyAndCollapse, /* 7 */ 0, /* 8 */ TclCreatePipeline, /* 9 */ TclCreateProc, /* 10 */ TclDeleteCompiledLocalVars, /* 11 */ TclDeleteVars, /* 12 */ 0, /* 13 */ TclDumpMemoryInfo, /* 14 */ 0, /* 15 */ TclExprFloatError, /* 16 */ 0, /* 17 */ 0, /* 18 */ 0, /* 19 */ 0, /* 20 */ 0, /* 21 */ TclFindElement, /* 22 */ TclFindProc, /* 23 */ TclFormatInt, /* 24 */ TclFreePackageInfo, /* 25 */ 0, /* 26 */ 0, /* 27 */ TclpGetDefaultStdChannel, /* 28 */ 0, /* 29 */ 0, /* 30 */ TclGetExtension, /* 31 */ TclGetFrame, /* 32 */ 0, /* 33 */ 0, /* 34 */ 0, /* 35 */ 0, /* 36 */ 0, /* 37 */ TclGetNamespaceForQualName, /* 38 */ TclGetObjInterpProc, /* 39 */ TclGetOpenMode, /* 40 */ TclGetOriginalCommand, /* 41 */ TclpGetUserHome, /* 42 */ TclGetObjInterpProc2, /* 43 */ 0, /* 44 */ TclHideUnsafeCommands, /* 45 */ TclInExit, /* 46 */ 0, /* 47 */ 0, /* 48 */ 0, /* 49 */ 0, /* 50 */ TclInterpInit, /* 51 */ 0, /* 52 */ 0, /* 53 */ 0, /* 54 */ TclIsProc, /* 55 */ 0, /* 56 */ 0, /* 57 */ TclLookupVar, /* 58 */ 0, /* 59 */ TclNeedSpace, /* 60 */ TclNewProcBodyObj, /* 61 */ TclObjCommandComplete, /* 62 */ 0, /* 63 */ TclObjInvoke, /* 64 */ 0, /* 65 */ 0, /* 66 */ 0, /* 67 */ 0, /* 68 */ TclpAlloc, /* 69 */ 0, /* 70 */ 0, /* 71 */ 0, /* 72 */ 0, /* 73 */ TclpFree, /* 74 */ TclpGetClicks, /* 75 */ TclpGetSeconds, /* 76 */ 0, /* 77 */ 0, /* 78 */ 0, /* 79 */ 0, /* 80 */ TclpRealloc, /* 81 */ 0, /* 82 */ 0, /* 83 */ 0, /* 84 */ 0, /* 85 */ 0, /* 86 */ 0, /* 87 */ 0, /* 88 */ TclPreventAliasLoop, /* 89 */ 0, /* 90 */ TclProcCleanupProc, /* 91 */ TclProcCompileProc, /* 92 */ TclProcDeleteProc, /* 93 */ 0, /* 94 */ 0, /* 95 */ TclRenameCommand, /* 96 */ TclResetShadowedCmdRefs, /* 97 */ TclServiceIdle, /* 98 */ 0, /* 99 */ 0, /* 100 */ 0, /* 101 */ TclSetupEnv, /* 102 */ TclSockGetPort, /* 103 */ 0, /* 104 */ 0, /* 105 */ 0, /* 106 */ 0, /* 107 */ TclTeardownNamespace, /* 108 */ TclUpdateReturnInfo, /* 109 */ TclSockMinimumBuffers, /* 110 */ Tcl_AddInterpResolvers, /* 111 */ 0, /* 112 */ 0, /* 113 */ 0, /* 114 */ 0, /* 115 */ 0, /* 116 */ 0, /* 117 */ Tcl_GetInterpResolvers, /* 118 */ Tcl_GetNamespaceResolvers, /* 119 */ Tcl_FindNamespaceVar, /* 120 */ 0, /* 121 */ 0, /* 122 */ 0, /* 123 */ 0, /* 124 */ 0, /* 125 */ Tcl_GetVariableFullName, /* 126 */ 0, /* 127 */ Tcl_PopCallFrame, /* 128 */ Tcl_PushCallFrame, /* 129 */ Tcl_RemoveInterpResolvers, /* 130 */ Tcl_SetNamespaceResolvers, /* 131 */ 0, /* 132 */ 0, /* 133 */ 0, /* 134 */ 0, /* 135 */ 0, /* 136 */ 0, /* 137 */ TclGetEnv, /* 138 */ 0, /* 139 */ 0, /* 140 */ TclpGetCwd, /* 141 */ TclSetByteCodeFromAny, /* 142 */ TclAddLiteralObj, /* 143 */ TclHideLiteral, /* 144 */ TclGetAuxDataType, /* 145 */ TclHandleCreate, /* 146 */ TclHandleFree, /* 147 */ TclHandlePreserve, /* 148 */ TclHandleRelease, /* 149 */ TclRegAbout, /* 150 */ TclRegExpRangeUniChar, /* 151 */ 0, /* 152 */ 0, /* 153 */ 0, /* 154 */ 0, /* 155 */ TclRegError, /* 156 */ TclVarTraceExists, /* 157 */ 0, /* 158 */ 0, /* 159 */ 0, /* 160 */ TclChannelTransform, /* 161 */ TclChannelEventScriptInvoker, /* 162 */ TclGetInstructionTable, /* 163 */ TclExpandCodeArray, /* 164 */ TclpSetInitialEncodings, /* 165 */ TclListObjSetElement, /* 166 */ 0, /* 167 */ 0, /* 168 */ TclpUtfNcmp2, /* 169 */ TclCheckInterpTraces, /* 170 */ TclCheckExecutionTraces, /* 171 */ TclInThreadExit, /* 172 */ TclUniCharMatch, /* 173 */ 0, /* 174 */ TclCallVarTraces, /* 175 */ TclCleanupVar, /* 176 */ TclVarErrMsg, /* 177 */ 0, /* 178 */ 0, /* 179 */ 0, /* 180 */ 0, /* 181 */ 0, /* 182 */ 0, /* 183 */ 0, /* 184 */ 0, /* 185 */ 0, /* 186 */ 0, /* 187 */ 0, /* 188 */ 0, /* 189 */ 0, /* 190 */ 0, /* 191 */ 0, /* 192 */ 0, /* 193 */ 0, /* 194 */ 0, /* 195 */ 0, /* 196 */ 0, /* 197 */ TclObjGetFrame, /* 198 */ 0, /* 199 */ TclpObjRemoveDirectory, /* 200 */ TclpObjCopyDirectory, /* 201 */ TclpObjCreateDirectory, /* 202 */ TclpObjDeleteFile, /* 203 */ TclpObjCopyFile, /* 204 */ TclpObjRenameFile, /* 205 */ TclpObjStat, /* 206 */ TclpObjAccess, /* 207 */ TclpOpenFileChannel, /* 208 */ 0, /* 209 */ 0, /* 210 */ 0, /* 211 */ TclpFindExecutable, /* 212 */ TclGetObjNameOfExecutable, /* 213 */ TclSetObjNameOfExecutable, /* 214 */ TclStackAlloc, /* 215 */ TclStackFree, /* 216 */ TclPushStackFrame, /* 217 */ TclPopStackFrame, /* 218 */ TclpCreateTemporaryDirectory, /* 219 */ 0, /* 220 */ TclListTestObj, /* 221 */ TclListObjValidate, /* 222 */ TclGetCStackPtr, /* 223 */ TclGetPlatform, /* 224 */ TclTraceDictPath, /* 225 */ TclObjBeingDeleted, /* 226 */ TclSetNsPath, /* 227 */ 0, /* 228 */ TclPtrMakeUpvar, /* 229 */ TclObjLookupVar, /* 230 */ TclGetNamespaceFromObj, /* 231 */ TclEvalObjEx, /* 232 */ TclGetSrcInfoForPc, /* 233 */ TclVarHashCreateVar, /* 234 */ TclInitVarHashTable, /* 235 */ 0, /* 236 */ TclResetCancellation, /* 237 */ TclNRInterpProc, /* 238 */ TclNRInterpProcCore, /* 239 */ TclNRRunCallbacks, /* 240 */ TclNREvalObjEx, /* 241 */ TclNREvalObjv, /* 242 */ TclDbDumpActiveObjects, /* 243 */ TclGetNamespaceChildTable, /* 244 */ TclGetNamespaceCommandTable, /* 245 */ TclInitRewriteEnsemble, /* 246 */ TclResetRewriteEnsemble, /* 247 */ TclCopyChannel, /* 248 */ TclDoubleDigits, /* 249 */ TclSetChildCancelFlags, /* 250 */ TclRegisterLiteral, /* 251 */ TclPtrGetVar, /* 252 */ TclPtrSetVar, /* 253 */ TclPtrIncrObjVar, /* 254 */ TclPtrObjMakeUpvar, /* 255 */ TclPtrUnsetVar, /* 256 */ TclStaticLibrary, /* 257 */ 0, /* 258 */ 0, /* 259 */ 0, /* 260 */ TclUnusedStubEntry, /* 261 */ }; static const TclIntPlatStubs tclIntPlatStubs = { TCL_STUB_MAGIC, 0, 0, /* 0 */ TclpCloseFile, /* 1 */ TclpCreateCommandChannel, /* 2 */ TclpCreatePipe, /* 3 */ TclWinGetTclInstance, /* 4 */ TclUnixWaitForFile, /* 5 */ TclpMakeFile, /* 6 */ TclpOpenFile, /* 7 */ TclpGetPid, /* 8 */ TclpCreateTempFile, /* 9 */ 0, /* 10 */ TclGetAndDetachPids, /* 11 */ 0, /* 12 */ 0, /* 13 */ 0, /* 14 */ TclpCreateProcess, /* 15 */ TclpIsAtty, /* 16 */ TclUnixCopyFile, /* 17 */ 0, /* 18 */ 0, /* 19 */ TclWinAddProcess, /* 20 */ 0, /* 21 */ 0, /* 22 */ 0, /* 23 */ TclWinNoBackslash, /* 24 */ 0, /* 25 */ 0, /* 26 */ TclWinFlushDirtyChannels, /* 27 */ 0, /* 28 */ TclWinCPUID, /* 29 */ TclUnixOpenTemporaryFile, /* 30 */ }; static const TclPlatStubs tclPlatStubs = { TCL_STUB_MAGIC, 0, 0, /* 0 */ Tcl_MacOSXOpenVersionedBundleResources, /* 1 */ Tcl_MacOSXNotifierAddRunLoopMode, /* 2 */ Tcl_WinConvertError, /* 3 */ }; const TclTomMathStubs tclTomMathStubs = { TCL_STUB_MAGIC, 0, TclBN_epoch, /* 0 */ TclBN_revision, /* 1 */ TclBN_mp_add, /* 2 */ TclBN_mp_add_d, /* 3 */ TclBN_mp_and, /* 4 */ TclBN_mp_clamp, /* 5 */ TclBN_mp_clear, /* 6 */ TclBN_mp_clear_multi, /* 7 */ TclBN_mp_cmp, /* 8 */ TclBN_mp_cmp_d, /* 9 */ TclBN_mp_cmp_mag, /* 10 */ TclBN_mp_copy, /* 11 */ TclBN_mp_count_bits, /* 12 */ TclBN_mp_div, /* 13 */ TclBN_mp_div_d, /* 14 */ TclBN_mp_div_2, /* 15 */ TclBN_mp_div_2d, /* 16 */ 0, /* 17 */ TclBN_mp_exch, /* 18 */ TclBN_mp_expt_n, /* 19 */ TclBN_mp_grow, /* 20 */ TclBN_mp_init, /* 21 */ TclBN_mp_init_copy, /* 22 */ TclBN_mp_init_multi, /* 23 */ TclBN_mp_init_set, /* 24 */ TclBN_mp_init_size, /* 25 */ TclBN_mp_lshd, /* 26 */ TclBN_mp_mod, /* 27 */ TclBN_mp_mod_2d, /* 28 */ TclBN_mp_mul, /* 29 */ TclBN_mp_mul_d, /* 30 */ TclBN_mp_mul_2, /* 31 */ TclBN_mp_mul_2d, /* 32 */ TclBN_mp_neg, /* 33 */ TclBN_mp_or, /* 34 */ TclBN_mp_radix_size, /* 35 */ TclBN_mp_read_radix, /* 36 */ TclBN_mp_rshd, /* 37 */ TclBN_mp_shrink, /* 38 */ 0, /* 39 */ 0, /* 40 */ TclBN_mp_sqrt, /* 41 */ TclBN_mp_sub, /* 42 */ TclBN_mp_sub_d, /* 43 */ 0, /* 44 */ 0, /* 45 */ 0, /* 46 */ TclBN_mp_ubin_size, /* 47 */ TclBN_mp_xor, /* 48 */ TclBN_mp_zero, /* 49 */ 0, /* 50 */ 0, /* 51 */ 0, /* 52 */ 0, /* 53 */ 0, /* 54 */ 0, /* 55 */ 0, /* 56 */ 0, /* 57 */ 0, /* 58 */ 0, /* 59 */ 0, /* 60 */ 0, /* 61 */ 0, /* 62 */ TclBN_mp_cnt_lsb, /* 63 */ 0, /* 64 */ TclBN_mp_init_i64, /* 65 */ TclBN_mp_init_u64, /* 66 */ 0, /* 67 */ TclBN_mp_set_u64, /* 68 */ TclBN_mp_get_mag_u64, /* 69 */ TclBN_mp_set_i64, /* 70 */ TclBN_mp_unpack, /* 71 */ TclBN_mp_pack, /* 72 */ 0, /* 73 */ 0, /* 74 */ 0, /* 75 */ TclBN_mp_signed_rsh, /* 76 */ TclBN_mp_pack_count, /* 77 */ TclBN_mp_to_ubin, /* 78 */ 0, /* 79 */ TclBN_mp_to_radix, /* 80 */ }; static const TclStubHooks tclStubHooks = { &tclPlatStubs, &tclIntStubs, &tclIntPlatStubs }; const TclStubs tclStubs = { TCL_STUB_MAGIC, &tclStubHooks, Tcl_PkgProvideEx, /* 0 */ Tcl_PkgRequireEx, /* 1 */ Tcl_Panic, /* 2 */ Tcl_Alloc, /* 3 */ Tcl_Free, /* 4 */ Tcl_Realloc, /* 5 */ Tcl_DbCkalloc, /* 6 */ Tcl_DbCkfree, /* 7 */ Tcl_DbCkrealloc, /* 8 */ Tcl_CreateFileHandler, /* 9 */ Tcl_DeleteFileHandler, /* 10 */ Tcl_SetTimer, /* 11 */ Tcl_Sleep, /* 12 */ Tcl_WaitForEvent, /* 13 */ Tcl_AppendAllObjTypes, /* 14 */ Tcl_AppendStringsToObj, /* 15 */ Tcl_AppendToObj, /* 16 */ Tcl_ConcatObj, /* 17 */ Tcl_ConvertToType, /* 18 */ Tcl_DbDecrRefCount, /* 19 */ Tcl_DbIncrRefCount, /* 20 */ Tcl_DbIsShared, /* 21 */ 0, /* 22 */ Tcl_DbNewByteArrayObj, /* 23 */ Tcl_DbNewDoubleObj, /* 24 */ Tcl_DbNewListObj, /* 25 */ 0, /* 26 */ Tcl_DbNewObj, /* 27 */ Tcl_DbNewStringObj, /* 28 */ Tcl_DuplicateObj, /* 29 */ TclFreeObj, /* 30 */ Tcl_GetBoolean, /* 31 */ Tcl_GetBooleanFromObj, /* 32 */ Tcl_GetByteArrayFromObj, /* 33 */ Tcl_GetDouble, /* 34 */ Tcl_GetDoubleFromObj, /* 35 */ 0, /* 36 */ Tcl_GetInt, /* 37 */ Tcl_GetIntFromObj, /* 38 */ Tcl_GetLongFromObj, /* 39 */ Tcl_GetObjType, /* 40 */ TclGetStringFromObj, /* 41 */ Tcl_InvalidateStringRep, /* 42 */ Tcl_ListObjAppendList, /* 43 */ Tcl_ListObjAppendElement, /* 44 */ TclListObjGetElements, /* 45 */ Tcl_ListObjIndex, /* 46 */ TclListObjLength, /* 47 */ Tcl_ListObjReplace, /* 48 */ 0, /* 49 */ Tcl_NewByteArrayObj, /* 50 */ Tcl_NewDoubleObj, /* 51 */ 0, /* 52 */ Tcl_NewListObj, /* 53 */ 0, /* 54 */ Tcl_NewObj, /* 55 */ Tcl_NewStringObj, /* 56 */ 0, /* 57 */ Tcl_SetByteArrayLength, /* 58 */ Tcl_SetByteArrayObj, /* 59 */ Tcl_SetDoubleObj, /* 60 */ 0, /* 61 */ Tcl_SetListObj, /* 62 */ 0, /* 63 */ Tcl_SetObjLength, /* 64 */ Tcl_SetStringObj, /* 65 */ 0, /* 66 */ 0, /* 67 */ Tcl_AllowExceptions, /* 68 */ Tcl_AppendElement, /* 69 */ Tcl_AppendResult, /* 70 */ Tcl_AsyncCreate, /* 71 */ Tcl_AsyncDelete, /* 72 */ Tcl_AsyncInvoke, /* 73 */ Tcl_AsyncMark, /* 74 */ Tcl_AsyncReady, /* 75 */ 0, /* 76 */ 0, /* 77 */ Tcl_BadChannelOption, /* 78 */ Tcl_CallWhenDeleted, /* 79 */ Tcl_CancelIdleCall, /* 80 */ Tcl_Close, /* 81 */ Tcl_CommandComplete, /* 82 */ Tcl_Concat, /* 83 */ Tcl_ConvertElement, /* 84 */ Tcl_ConvertCountedElement, /* 85 */ Tcl_CreateAlias, /* 86 */ Tcl_CreateAliasObj, /* 87 */ Tcl_CreateChannel, /* 88 */ Tcl_CreateChannelHandler, /* 89 */ Tcl_CreateCloseHandler, /* 90 */ Tcl_CreateCommand, /* 91 */ Tcl_CreateEventSource, /* 92 */ Tcl_CreateExitHandler, /* 93 */ Tcl_CreateInterp, /* 94 */ 0, /* 95 */ Tcl_CreateObjCommand, /* 96 */ Tcl_CreateChild, /* 97 */ Tcl_CreateTimerHandler, /* 98 */ Tcl_CreateTrace, /* 99 */ Tcl_DeleteAssocData, /* 100 */ Tcl_DeleteChannelHandler, /* 101 */ Tcl_DeleteCloseHandler, /* 102 */ Tcl_DeleteCommand, /* 103 */ Tcl_DeleteCommandFromToken, /* 104 */ Tcl_DeleteEvents, /* 105 */ Tcl_DeleteEventSource, /* 106 */ Tcl_DeleteExitHandler, /* 107 */ Tcl_DeleteHashEntry, /* 108 */ Tcl_DeleteHashTable, /* 109 */ Tcl_DeleteInterp, /* 110 */ Tcl_DetachPids, /* 111 */ Tcl_DeleteTimerHandler, /* 112 */ Tcl_DeleteTrace, /* 113 */ Tcl_DontCallWhenDeleted, /* 114 */ Tcl_DoOneEvent, /* 115 */ Tcl_DoWhenIdle, /* 116 */ Tcl_DStringAppend, /* 117 */ Tcl_DStringAppendElement, /* 118 */ Tcl_DStringEndSublist, /* 119 */ Tcl_DStringFree, /* 120 */ Tcl_DStringGetResult, /* 121 */ Tcl_DStringInit, /* 122 */ Tcl_DStringResult, /* 123 */ Tcl_DStringSetLength, /* 124 */ Tcl_DStringStartSublist, /* 125 */ Tcl_Eof, /* 126 */ Tcl_ErrnoId, /* 127 */ Tcl_ErrnoMsg, /* 128 */ 0, /* 129 */ Tcl_EvalFile, /* 130 */ 0, /* 131 */ Tcl_EventuallyFree, /* 132 */ Tcl_Exit, /* 133 */ Tcl_ExposeCommand, /* 134 */ Tcl_ExprBoolean, /* 135 */ Tcl_ExprBooleanObj, /* 136 */ Tcl_ExprDouble, /* 137 */ Tcl_ExprDoubleObj, /* 138 */ Tcl_ExprLong, /* 139 */ Tcl_ExprLongObj, /* 140 */ Tcl_ExprObj, /* 141 */ Tcl_ExprString, /* 142 */ Tcl_Finalize, /* 143 */ 0, /* 144 */ Tcl_FirstHashEntry, /* 145 */ Tcl_Flush, /* 146 */ 0, /* 147 */ 0, /* 148 */ TclGetAliasObj, /* 149 */ Tcl_GetAssocData, /* 150 */ Tcl_GetChannel, /* 151 */ Tcl_GetChannelBufferSize, /* 152 */ Tcl_GetChannelHandle, /* 153 */ Tcl_GetChannelInstanceData, /* 154 */ Tcl_GetChannelMode, /* 155 */ Tcl_GetChannelName, /* 156 */ Tcl_GetChannelOption, /* 157 */ Tcl_GetChannelType, /* 158 */ Tcl_GetCommandInfo, /* 159 */ Tcl_GetCommandName, /* 160 */ Tcl_GetErrno, /* 161 */ Tcl_GetHostName, /* 162 */ Tcl_GetInterpPath, /* 163 */ Tcl_GetParent, /* 164 */ Tcl_GetNameOfExecutable, /* 165 */ Tcl_GetObjResult, /* 166 */ Tcl_GetOpenFile, /* 167 */ Tcl_GetPathType, /* 168 */ Tcl_Gets, /* 169 */ Tcl_GetsObj, /* 170 */ Tcl_GetServiceMode, /* 171 */ Tcl_GetChild, /* 172 */ Tcl_GetStdChannel, /* 173 */ 0, /* 174 */ 0, /* 175 */ Tcl_GetVar2, /* 176 */ 0, /* 177 */ 0, /* 178 */ Tcl_HideCommand, /* 179 */ Tcl_Init, /* 180 */ Tcl_InitHashTable, /* 181 */ Tcl_InputBlocked, /* 182 */ Tcl_InputBuffered, /* 183 */ Tcl_InterpDeleted, /* 184 */ Tcl_IsSafe, /* 185 */ Tcl_JoinPath, /* 186 */ Tcl_LinkVar, /* 187 */ 0, /* 188 */ Tcl_MakeFileChannel, /* 189 */ 0, /* 190 */ Tcl_MakeTcpClientChannel, /* 191 */ Tcl_Merge, /* 192 */ Tcl_NextHashEntry, /* 193 */ Tcl_NotifyChannel, /* 194 */ Tcl_ObjGetVar2, /* 195 */ Tcl_ObjSetVar2, /* 196 */ Tcl_OpenCommandChannel, /* 197 */ Tcl_OpenFileChannel, /* 198 */ Tcl_OpenTcpClient, /* 199 */ Tcl_OpenTcpServer, /* 200 */ Tcl_Preserve, /* 201 */ Tcl_PrintDouble, /* 202 */ Tcl_PutEnv, /* 203 */ Tcl_PosixError, /* 204 */ Tcl_QueueEvent, /* 205 */ Tcl_Read, /* 206 */ Tcl_ReapDetachedProcs, /* 207 */ Tcl_RecordAndEval, /* 208 */ Tcl_RecordAndEvalObj, /* 209 */ Tcl_RegisterChannel, /* 210 */ Tcl_RegisterObjType, /* 211 */ Tcl_RegExpCompile, /* 212 */ Tcl_RegExpExec, /* 213 */ Tcl_RegExpMatch, /* 214 */ Tcl_RegExpRange, /* 215 */ Tcl_Release, /* 216 */ Tcl_ResetResult, /* 217 */ Tcl_ScanElement, /* 218 */ Tcl_ScanCountedElement, /* 219 */ 0, /* 220 */ Tcl_ServiceAll, /* 221 */ Tcl_ServiceEvent, /* 222 */ Tcl_SetAssocData, /* 223 */ Tcl_SetChannelBufferSize, /* 224 */ Tcl_SetChannelOption, /* 225 */ Tcl_SetCommandInfo, /* 226 */ Tcl_SetErrno, /* 227 */ Tcl_SetErrorCode, /* 228 */ Tcl_SetMaxBlockTime, /* 229 */ 0, /* 230 */ Tcl_SetRecursionLimit, /* 231 */ 0, /* 232 */ Tcl_SetServiceMode, /* 233 */ Tcl_SetObjErrorCode, /* 234 */ Tcl_SetObjResult, /* 235 */ Tcl_SetStdChannel, /* 236 */ 0, /* 237 */ Tcl_SetVar2, /* 238 */ Tcl_SignalId, /* 239 */ Tcl_SignalMsg, /* 240 */ Tcl_SourceRCFile, /* 241 */ TclSplitList, /* 242 */ TclSplitPath, /* 243 */ 0, /* 244 */ 0, /* 245 */ 0, /* 246 */ 0, /* 247 */ Tcl_TraceVar2, /* 248 */ Tcl_TranslateFileName, /* 249 */ Tcl_Ungets, /* 250 */ Tcl_UnlinkVar, /* 251 */ Tcl_UnregisterChannel, /* 252 */ 0, /* 253 */ Tcl_UnsetVar2, /* 254 */ 0, /* 255 */ Tcl_UntraceVar2, /* 256 */ Tcl_UpdateLinkedVar, /* 257 */ 0, /* 258 */ Tcl_UpVar2, /* 259 */ Tcl_VarEval, /* 260 */ 0, /* 261 */ Tcl_VarTraceInfo2, /* 262 */ Tcl_Write, /* 263 */ Tcl_WrongNumArgs, /* 264 */ Tcl_DumpActiveMemory, /* 265 */ Tcl_ValidateAllMemory, /* 266 */ 0, /* 267 */ 0, /* 268 */ Tcl_HashStats, /* 269 */ Tcl_ParseVar, /* 270 */ 0, /* 271 */ Tcl_PkgPresentEx, /* 272 */ 0, /* 273 */ 0, /* 274 */ 0, /* 275 */ 0, /* 276 */ Tcl_WaitPid, /* 277 */ 0, /* 278 */ Tcl_GetVersion, /* 279 */ Tcl_InitMemory, /* 280 */ Tcl_StackChannel, /* 281 */ Tcl_UnstackChannel, /* 282 */ Tcl_GetStackedChannel, /* 283 */ Tcl_SetMainLoop, /* 284 */ Tcl_GetAliasObj, /* 285 */ Tcl_AppendObjToObj, /* 286 */ Tcl_CreateEncoding, /* 287 */ Tcl_CreateThreadExitHandler, /* 288 */ Tcl_DeleteThreadExitHandler, /* 289 */ 0, /* 290 */ Tcl_EvalEx, /* 291 */ Tcl_EvalObjv, /* 292 */ Tcl_EvalObjEx, /* 293 */ Tcl_ExitThread, /* 294 */ Tcl_ExternalToUtf, /* 295 */ Tcl_ExternalToUtfDString, /* 296 */ Tcl_FinalizeThread, /* 297 */ Tcl_FinalizeNotifier, /* 298 */ Tcl_FreeEncoding, /* 299 */ Tcl_GetCurrentThread, /* 300 */ Tcl_GetEncoding, /* 301 */ Tcl_GetEncodingName, /* 302 */ Tcl_GetEncodingNames, /* 303 */ Tcl_GetIndexFromObjStruct, /* 304 */ Tcl_GetThreadData, /* 305 */ Tcl_GetVar2Ex, /* 306 */ Tcl_InitNotifier, /* 307 */ Tcl_MutexLock, /* 308 */ Tcl_MutexUnlock, /* 309 */ Tcl_ConditionNotify, /* 310 */ Tcl_ConditionWait, /* 311 */ TclNumUtfChars, /* 312 */ Tcl_ReadChars, /* 313 */ 0, /* 314 */ 0, /* 315 */ Tcl_SetSystemEncoding, /* 316 */ Tcl_SetVar2Ex, /* 317 */ Tcl_ThreadAlert, /* 318 */ Tcl_ThreadQueueEvent, /* 319 */ Tcl_UniCharAtIndex, /* 320 */ Tcl_UniCharToLower, /* 321 */ Tcl_UniCharToTitle, /* 322 */ Tcl_UniCharToUpper, /* 323 */ Tcl_UniCharToUtf, /* 324 */ TclUtfAtIndex, /* 325 */ TclUtfCharComplete, /* 326 */ Tcl_UtfBackslash, /* 327 */ Tcl_UtfFindFirst, /* 328 */ Tcl_UtfFindLast, /* 329 */ TclUtfNext, /* 330 */ TclUtfPrev, /* 331 */ Tcl_UtfToExternal, /* 332 */ Tcl_UtfToExternalDString, /* 333 */ Tcl_UtfToLower, /* 334 */ Tcl_UtfToTitle, /* 335 */ Tcl_UtfToChar16, /* 336 */ Tcl_UtfToUpper, /* 337 */ Tcl_WriteChars, /* 338 */ Tcl_WriteObj, /* 339 */ Tcl_GetString, /* 340 */ 0, /* 341 */ 0, /* 342 */ Tcl_AlertNotifier, /* 343 */ Tcl_ServiceModeHook, /* 344 */ Tcl_UniCharIsAlnum, /* 345 */ Tcl_UniCharIsAlpha, /* 346 */ Tcl_UniCharIsDigit, /* 347 */ Tcl_UniCharIsLower, /* 348 */ Tcl_UniCharIsSpace, /* 349 */ Tcl_UniCharIsUpper, /* 350 */ Tcl_UniCharIsWordChar, /* 351 */ Tcl_Char16Len, /* 352 */ 0, /* 353 */ Tcl_Char16ToUtfDString, /* 354 */ Tcl_UtfToChar16DString, /* 355 */ Tcl_GetRegExpFromObj, /* 356 */ 0, /* 357 */ Tcl_FreeParse, /* 358 */ Tcl_LogCommandInfo, /* 359 */ Tcl_ParseBraces, /* 360 */ Tcl_ParseCommand, /* 361 */ Tcl_ParseExpr, /* 362 */ Tcl_ParseQuotedString, /* 363 */ Tcl_ParseVarName, /* 364 */ Tcl_GetCwd, /* 365 */ Tcl_Chdir, /* 366 */ Tcl_Access, /* 367 */ Tcl_Stat, /* 368 */ TclUtfNcmp, /* 369 */ TclUtfNcasecmp, /* 370 */ Tcl_StringCaseMatch, /* 371 */ Tcl_UniCharIsControl, /* 372 */ Tcl_UniCharIsGraph, /* 373 */ Tcl_UniCharIsPrint, /* 374 */ Tcl_UniCharIsPunct, /* 375 */ Tcl_RegExpExecObj, /* 376 */ Tcl_RegExpGetInfo, /* 377 */ Tcl_NewUnicodeObj, /* 378 */ Tcl_SetUnicodeObj, /* 379 */ TclGetCharLength, /* 380 */ TclGetUniChar, /* 381 */ 0, /* 382 */ TclGetRange, /* 383 */ Tcl_AppendUnicodeToObj, /* 384 */ Tcl_RegExpMatchObj, /* 385 */ Tcl_SetNotifier, /* 386 */ Tcl_GetAllocMutex, /* 387 */ Tcl_GetChannelNames, /* 388 */ Tcl_GetChannelNamesEx, /* 389 */ Tcl_ProcObjCmd, /* 390 */ Tcl_ConditionFinalize, /* 391 */ Tcl_MutexFinalize, /* 392 */ Tcl_CreateThread, /* 393 */ Tcl_ReadRaw, /* 394 */ Tcl_WriteRaw, /* 395 */ Tcl_GetTopChannel, /* 396 */ Tcl_ChannelBuffered, /* 397 */ Tcl_ChannelName, /* 398 */ Tcl_ChannelVersion, /* 399 */ Tcl_ChannelBlockModeProc, /* 400 */ 0, /* 401 */ Tcl_ChannelClose2Proc, /* 402 */ Tcl_ChannelInputProc, /* 403 */ Tcl_ChannelOutputProc, /* 404 */ 0, /* 405 */ Tcl_ChannelSetOptionProc, /* 406 */ Tcl_ChannelGetOptionProc, /* 407 */ Tcl_ChannelWatchProc, /* 408 */ Tcl_ChannelGetHandleProc, /* 409 */ Tcl_ChannelFlushProc, /* 410 */ Tcl_ChannelHandlerProc, /* 411 */ Tcl_JoinThread, /* 412 */ Tcl_IsChannelShared, /* 413 */ Tcl_IsChannelRegistered, /* 414 */ Tcl_CutChannel, /* 415 */ Tcl_SpliceChannel, /* 416 */ Tcl_ClearChannelHandlers, /* 417 */ Tcl_IsChannelExisting, /* 418 */ 0, /* 419 */ 0, /* 420 */ 0, /* 421 */ 0, /* 422 */ Tcl_InitCustomHashTable, /* 423 */ Tcl_InitObjHashTable, /* 424 */ Tcl_CommandTraceInfo, /* 425 */ Tcl_TraceCommand, /* 426 */ Tcl_UntraceCommand, /* 427 */ Tcl_AttemptAlloc, /* 428 */ Tcl_AttemptDbCkalloc, /* 429 */ Tcl_AttemptRealloc, /* 430 */ Tcl_AttemptDbCkrealloc, /* 431 */ Tcl_AttemptSetObjLength, /* 432 */ Tcl_GetChannelThread, /* 433 */ TclGetUnicodeFromObj, /* 434 */ 0, /* 435 */ 0, /* 436 */ Tcl_SubstObj, /* 437 */ Tcl_DetachChannel, /* 438 */ Tcl_IsStandardChannel, /* 439 */ Tcl_FSCopyFile, /* 440 */ Tcl_FSCopyDirectory, /* 441 */ Tcl_FSCreateDirectory, /* 442 */ Tcl_FSDeleteFile, /* 443 */ Tcl_FSLoadFile, /* 444 */ Tcl_FSMatchInDirectory, /* 445 */ Tcl_FSLink, /* 446 */ Tcl_FSRemoveDirectory, /* 447 */ Tcl_FSRenameFile, /* 448 */ Tcl_FSLstat, /* 449 */ Tcl_FSUtime, /* 450 */ Tcl_FSFileAttrsGet, /* 451 */ Tcl_FSFileAttrsSet, /* 452 */ Tcl_FSFileAttrStrings, /* 453 */ Tcl_FSStat, /* 454 */ Tcl_FSAccess, /* 455 */ Tcl_FSOpenFileChannel, /* 456 */ Tcl_FSGetCwd, /* 457 */ Tcl_FSChdir, /* 458 */ Tcl_FSConvertToPathType, /* 459 */ Tcl_FSJoinPath, /* 460 */ TclFSSplitPath, /* 461 */ Tcl_FSEqualPaths, /* 462 */ Tcl_FSGetNormalizedPath, /* 463 */ Tcl_FSJoinToPath, /* 464 */ Tcl_FSGetInternalRep, /* 465 */ Tcl_FSGetTranslatedPath, /* 466 */ Tcl_FSEvalFile, /* 467 */ Tcl_FSNewNativePath, /* 468 */ Tcl_FSGetNativePath, /* 469 */ Tcl_FSFileSystemInfo, /* 470 */ Tcl_FSPathSeparator, /* 471 */ Tcl_FSListVolumes, /* 472 */ Tcl_FSRegister, /* 473 */ Tcl_FSUnregister, /* 474 */ Tcl_FSData, /* 475 */ Tcl_FSGetTranslatedStringPath, /* 476 */ Tcl_FSGetFileSystemForPath, /* 477 */ Tcl_FSGetPathType, /* 478 */ Tcl_OutputBuffered, /* 479 */ Tcl_FSMountsChanged, /* 480 */ Tcl_EvalTokensStandard, /* 481 */ Tcl_GetTime, /* 482 */ Tcl_CreateObjTrace, /* 483 */ Tcl_GetCommandInfoFromToken, /* 484 */ Tcl_SetCommandInfoFromToken, /* 485 */ Tcl_DbNewWideIntObj, /* 486 */ Tcl_GetWideIntFromObj, /* 487 */ Tcl_NewWideIntObj, /* 488 */ Tcl_SetWideIntObj, /* 489 */ Tcl_AllocStatBuf, /* 490 */ Tcl_Seek, /* 491 */ Tcl_Tell, /* 492 */ Tcl_ChannelWideSeekProc, /* 493 */ Tcl_DictObjPut, /* 494 */ Tcl_DictObjGet, /* 495 */ Tcl_DictObjRemove, /* 496 */ TclDictObjSize, /* 497 */ Tcl_DictObjFirst, /* 498 */ Tcl_DictObjNext, /* 499 */ Tcl_DictObjDone, /* 500 */ Tcl_DictObjPutKeyList, /* 501 */ Tcl_DictObjRemoveKeyList, /* 502 */ Tcl_NewDictObj, /* 503 */ Tcl_DbNewDictObj, /* 504 */ Tcl_RegisterConfig, /* 505 */ Tcl_CreateNamespace, /* 506 */ Tcl_DeleteNamespace, /* 507 */ Tcl_AppendExportList, /* 508 */ Tcl_Export, /* 509 */ Tcl_Import, /* 510 */ Tcl_ForgetImport, /* 511 */ Tcl_GetCurrentNamespace, /* 512 */ Tcl_GetGlobalNamespace, /* 513 */ Tcl_FindNamespace, /* 514 */ Tcl_FindCommand, /* 515 */ Tcl_GetCommandFromObj, /* 516 */ Tcl_GetCommandFullName, /* 517 */ Tcl_FSEvalFileEx, /* 518 */ 0, /* 519 */ Tcl_LimitAddHandler, /* 520 */ Tcl_LimitRemoveHandler, /* 521 */ Tcl_LimitReady, /* 522 */ Tcl_LimitCheck, /* 523 */ Tcl_LimitExceeded, /* 524 */ Tcl_LimitSetCommands, /* 525 */ Tcl_LimitSetTime, /* 526 */ Tcl_LimitSetGranularity, /* 527 */ Tcl_LimitTypeEnabled, /* 528 */ Tcl_LimitTypeExceeded, /* 529 */ Tcl_LimitTypeSet, /* 530 */ Tcl_LimitTypeReset, /* 531 */ Tcl_LimitGetCommands, /* 532 */ Tcl_LimitGetTime, /* 533 */ Tcl_LimitGetGranularity, /* 534 */ Tcl_SaveInterpState, /* 535 */ Tcl_RestoreInterpState, /* 536 */ Tcl_DiscardInterpState, /* 537 */ Tcl_SetReturnOptions, /* 538 */ Tcl_GetReturnOptions, /* 539 */ Tcl_IsEnsemble, /* 540 */ Tcl_CreateEnsemble, /* 541 */ Tcl_FindEnsemble, /* 542 */ Tcl_SetEnsembleSubcommandList, /* 543 */ Tcl_SetEnsembleMappingDict, /* 544 */ Tcl_SetEnsembleUnknownHandler, /* 545 */ Tcl_SetEnsembleFlags, /* 546 */ Tcl_GetEnsembleSubcommandList, /* 547 */ Tcl_GetEnsembleMappingDict, /* 548 */ Tcl_GetEnsembleUnknownHandler, /* 549 */ Tcl_GetEnsembleFlags, /* 550 */ Tcl_GetEnsembleNamespace, /* 551 */ Tcl_SetTimeProc, /* 552 */ Tcl_QueryTimeProc, /* 553 */ Tcl_ChannelThreadActionProc, /* 554 */ Tcl_NewBignumObj, /* 555 */ Tcl_DbNewBignumObj, /* 556 */ Tcl_SetBignumObj, /* 557 */ Tcl_GetBignumFromObj, /* 558 */ Tcl_TakeBignumFromObj, /* 559 */ Tcl_TruncateChannel, /* 560 */ Tcl_ChannelTruncateProc, /* 561 */ Tcl_SetChannelErrorInterp, /* 562 */ Tcl_GetChannelErrorInterp, /* 563 */ Tcl_SetChannelError, /* 564 */ Tcl_GetChannelError, /* 565 */ Tcl_InitBignumFromDouble, /* 566 */ Tcl_GetNamespaceUnknownHandler, /* 567 */ Tcl_SetNamespaceUnknownHandler, /* 568 */ Tcl_GetEncodingFromObj, /* 569 */ Tcl_GetEncodingSearchPath, /* 570 */ Tcl_SetEncodingSearchPath, /* 571 */ Tcl_GetEncodingNameFromEnvironment, /* 572 */ Tcl_PkgRequireProc, /* 573 */ Tcl_AppendObjToErrorInfo, /* 574 */ Tcl_AppendLimitedToObj, /* 575 */ Tcl_Format, /* 576 */ Tcl_AppendFormatToObj, /* 577 */ Tcl_ObjPrintf, /* 578 */ Tcl_AppendPrintfToObj, /* 579 */ Tcl_CancelEval, /* 580 */ Tcl_Canceled, /* 581 */ Tcl_CreatePipe, /* 582 */ Tcl_NRCreateCommand, /* 583 */ Tcl_NREvalObj, /* 584 */ Tcl_NREvalObjv, /* 585 */ Tcl_NRCmdSwap, /* 586 */ Tcl_NRAddCallback, /* 587 */ Tcl_NRCallObjProc, /* 588 */ Tcl_GetFSDeviceFromStat, /* 589 */ Tcl_GetFSInodeFromStat, /* 590 */ Tcl_GetModeFromStat, /* 591 */ Tcl_GetLinkCountFromStat, /* 592 */ Tcl_GetUserIdFromStat, /* 593 */ Tcl_GetGroupIdFromStat, /* 594 */ Tcl_GetDeviceTypeFromStat, /* 595 */ Tcl_GetAccessTimeFromStat, /* 596 */ Tcl_GetModificationTimeFromStat, /* 597 */ Tcl_GetChangeTimeFromStat, /* 598 */ Tcl_GetSizeFromStat, /* 599 */ Tcl_GetBlocksFromStat, /* 600 */ Tcl_GetBlockSizeFromStat, /* 601 */ Tcl_SetEnsembleParameterList, /* 602 */ Tcl_GetEnsembleParameterList, /* 603 */ TclParseArgsObjv, /* 604 */ Tcl_GetErrorLine, /* 605 */ Tcl_SetErrorLine, /* 606 */ Tcl_TransferResult, /* 607 */ Tcl_InterpActive, /* 608 */ Tcl_BackgroundException, /* 609 */ Tcl_ZlibDeflate, /* 610 */ Tcl_ZlibInflate, /* 611 */ Tcl_ZlibCRC32, /* 612 */ Tcl_ZlibAdler32, /* 613 */ Tcl_ZlibStreamInit, /* 614 */ Tcl_ZlibStreamGetCommandName, /* 615 */ Tcl_ZlibStreamEof, /* 616 */ Tcl_ZlibStreamChecksum, /* 617 */ Tcl_ZlibStreamPut, /* 618 */ Tcl_ZlibStreamGet, /* 619 */ Tcl_ZlibStreamClose, /* 620 */ Tcl_ZlibStreamReset, /* 621 */ Tcl_SetStartupScript, /* 622 */ Tcl_GetStartupScript, /* 623 */ Tcl_CloseEx, /* 624 */ Tcl_NRExprObj, /* 625 */ Tcl_NRSubstObj, /* 626 */ Tcl_LoadFile, /* 627 */ Tcl_FindSymbol, /* 628 */ Tcl_FSUnloadFile, /* 629 */ Tcl_ZlibStreamSetCompressionDictionary, /* 630 */ Tcl_OpenTcpServerEx, /* 631 */ TclZipfs_Mount, /* 632 */ TclZipfs_Unmount, /* 633 */ TclZipfs_TclLibrary, /* 634 */ TclZipfs_MountBuffer, /* 635 */ Tcl_FreeInternalRep, /* 636 */ Tcl_InitStringRep, /* 637 */ Tcl_FetchInternalRep, /* 638 */ Tcl_StoreInternalRep, /* 639 */ Tcl_HasStringRep, /* 640 */ Tcl_IncrRefCount, /* 641 */ Tcl_DecrRefCount, /* 642 */ Tcl_IsShared, /* 643 */ Tcl_LinkArray, /* 644 */ Tcl_GetIntForIndex, /* 645 */ Tcl_UtfToUniChar, /* 646 */ Tcl_UniCharToUtfDString, /* 647 */ Tcl_UtfToUniCharDString, /* 648 */ TclGetBytesFromObj, /* 649 */ Tcl_GetBytesFromObj, /* 650 */ Tcl_GetStringFromObj, /* 651 */ Tcl_GetUnicodeFromObj, /* 652 */ Tcl_GetSizeIntFromObj, /* 653 */ Tcl_UtfCharComplete, /* 654 */ Tcl_UtfNext, /* 655 */ Tcl_UtfPrev, /* 656 */ Tcl_FSTildeExpand, /* 657 */ Tcl_ExternalToUtfDStringEx, /* 658 */ Tcl_UtfToExternalDStringEx, /* 659 */ Tcl_AsyncMarkFromSignal, /* 660 */ Tcl_ListObjGetElements, /* 661 */ Tcl_ListObjLength, /* 662 */ Tcl_DictObjSize, /* 663 */ Tcl_SplitList, /* 664 */ Tcl_SplitPath, /* 665 */ Tcl_FSSplitPath, /* 666 */ Tcl_ParseArgsObjv, /* 667 */ Tcl_UniCharLen, /* 668 */ Tcl_NumUtfChars, /* 669 */ Tcl_GetCharLength, /* 670 */ Tcl_UtfAtIndex, /* 671 */ Tcl_GetRange, /* 672 */ Tcl_GetUniChar, /* 673 */ Tcl_GetBool, /* 674 */ Tcl_GetBoolFromObj, /* 675 */ Tcl_CreateObjCommand2, /* 676 */ Tcl_CreateObjTrace2, /* 677 */ Tcl_NRCreateCommand2, /* 678 */ Tcl_NRCallObjProc2, /* 679 */ Tcl_GetNumberFromObj, /* 680 */ Tcl_GetNumber, /* 681 */ Tcl_RemoveChannelMode, /* 682 */ Tcl_GetEncodingNulLength, /* 683 */ Tcl_GetWideUIntFromObj, /* 684 */ Tcl_DStringToObj, /* 685 */ Tcl_UtfNcmp, /* 686 */ Tcl_UtfNcasecmp, /* 687 */ Tcl_NewWideUIntObj, /* 688 */ Tcl_SetWideUIntObj, /* 689 */ TclUnusedStubEntry, /* 690 */ }; /* !END!: Do not edit above this line. */ tcl9.0.1/generic/tclStubLib.c0000644000175000017500000000675314726623136015420 0ustar sergeisergei/* * tclStubLib.c -- * * Stub object that will be statically linked into extensions that want * to access Tcl. * * Copyright © 1998-1999 Scriptics Corporation. * Copyright © 1998 Paul Duffin. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" MODULE_SCOPE const TclStubs *tclStubsPtr; MODULE_SCOPE const TclPlatStubs *tclPlatStubsPtr; MODULE_SCOPE const TclIntStubs *tclIntStubsPtr; MODULE_SCOPE const TclIntPlatStubs *tclIntPlatStubsPtr; MODULE_SCOPE void *tclStubsHandle; const TclStubs *tclStubsPtr = NULL; const TclPlatStubs *tclPlatStubsPtr = NULL; const TclIntStubs *tclIntStubsPtr = NULL; const TclIntPlatStubs *tclIntPlatStubsPtr = NULL; void *tclStubsHandle = NULL; /* * Use our own ISDIGIT to avoid linking to libc on windows */ #define ISDIGIT(c) (((unsigned)((c)-'0')) <= 9) /* *---------------------------------------------------------------------- * * Tcl_InitStubs -- * * Tries to initialise the stub table pointers and ensures that the * correct version of Tcl is loaded. * * Results: * The actual version of Tcl that satisfies the request, or NULL to * indicate that an error occurred. * * Side effects: * Sets the stub table pointers. * *---------------------------------------------------------------------- */ #undef Tcl_InitStubs MODULE_SCOPE const char * Tcl_InitStubs( Tcl_Interp *interp, const char *version, int exact, int magic) { Interp *iPtr = (Interp *)interp; const char *actualVersion = NULL; void *pkgData = NULL; const TclStubs *stubsPtr = iPtr->stubTable; const char *tclName = (((exact&0xFF00) >= 0x900) ? "tcl" : "Tcl"); #undef TCL_STUB_MAGIC /* We need the TCL_STUB_MAGIC from Tcl 8.x here */ #define TCL_STUB_MAGIC ((int) 0xFCA3BACF) /* * We can't optimize this check by caching tclStubsPtr because that * prevents apps from being able to load/unload Tcl dynamically multiple * times. [Bug 615304] */ if (!stubsPtr || (stubsPtr->magic != (((exact&0xFF00) >= 0x900) ? magic : TCL_STUB_MAGIC))) { iPtr->legacyResult = "interpreter uses an incompatible stubs mechanism"; iPtr->legacyFreeProc = 0; /* TCL_STATIC */ return NULL; } actualVersion = stubsPtr->tcl_PkgRequireEx(interp, tclName, version, 0, &pkgData); if (actualVersion == NULL) { return NULL; } if (exact&1) { const char *p = version; int count = 0; while (*p) { count += !ISDIGIT(*p++); } if (count == 1) { const char *q = actualVersion; p = version; while (*p && (*p == *q)) { p++; q++; } if (*p || ISDIGIT(*q)) { /* Construct error message */ stubsPtr->tcl_PkgRequireEx(interp, tclName, version, 1, NULL); return NULL; } } else { actualVersion = stubsPtr->tcl_PkgRequireEx(interp, tclName, version, 1, NULL); if (actualVersion == NULL) { return NULL; } } } if (((exact&0xFF00) < 0x900)) { /* We are running Tcl 8.x */ stubsPtr = (TclStubs *)pkgData; } if (tclStubsHandle == NULL) { tclStubsHandle = INT2PTR(-1); } tclStubsPtr = stubsPtr; if (stubsPtr->hooks) { tclPlatStubsPtr = stubsPtr->hooks->tclPlatStubs; tclIntStubsPtr = stubsPtr->hooks->tclIntStubs; tclIntPlatStubsPtr = stubsPtr->hooks->tclIntPlatStubs; } else { tclPlatStubsPtr = NULL; tclIntStubsPtr = NULL; tclIntPlatStubsPtr = NULL; } return actualVersion; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclStubLibTbl.c0000644000175000017500000000317214726623136016052 0ustar sergeisergei/* * tclStubLibTbl.c -- * * Stub object that will be statically linked into extensions that want * to access Tcl. * * Copyright (c) 1998-1999 by Scriptics Corporation. * Copyright (c) 1998 Paul Duffin. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" MODULE_SCOPE void *tclStubsHandle; /* *---------------------------------------------------------------------- * * TclInitStubTable -- * * Initialize the stub table, using the structure pointed at * by the "version" argument. * * Results: * Outputs the value of the "version" argument. * * Side effects: * Sets the stub table pointers. * *---------------------------------------------------------------------- */ MODULE_SCOPE const char * TclInitStubTable( const char *version) /* points to the version field of a * structure variable. */ { if (version) { if (tclStubsHandle == NULL) { /* This can only happen with -DBUILD_STATIC, so simulate * that the loading of Tcl succeeded, although we didn't * actually load it dynamically */ tclStubsHandle = (void *)1; } tclStubsPtr = ((const TclStubs **) version)[-1]; if (tclStubsPtr->hooks) { tclPlatStubsPtr = tclStubsPtr->hooks->tclPlatStubs; tclIntStubsPtr = tclStubsPtr->hooks->tclIntStubs; tclIntPlatStubsPtr = tclStubsPtr->hooks->tclIntPlatStubs; } else { tclPlatStubsPtr = NULL; tclIntStubsPtr = NULL; tclIntPlatStubsPtr = NULL; } } return version; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclTest.c0000644000175000017500000073264214726623136014776 0ustar sergeisergei/* * tclTest.c -- * * This file contains C command functions for a bunch of additional Tcl * commands that are used for testing out Tcl's C interfaces. These * commands are not normally included in Tcl applications; they're only * used for testing. * * Copyright © 1993-1994 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * Copyright © 1998-2000 Ajuba Solutions. * Copyright © 2003 Kevin B. Kenny. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #define TCL_8_API #undef BUILD_tcl #undef STATIC_BUILD #ifndef USE_TCL_STUBS # define USE_TCL_STUBS #endif #define TCLBOOLWARNING(boolPtr) /* needed here because we compile with -Wc++-compat */ #include "tclInt.h" #include "tclOO.h" #include /* * Required for Testregexp*Cmd */ #include "tclRegexp.h" /* * Required for the TestChannelCmd and TestChannelEventCmd */ #include "tclIO.h" #include "tclUuid.h" /* * Declare external functions used in Windows tests. */ DLLEXPORT int Tcltest_Init(Tcl_Interp *interp); DLLEXPORT int Tcltest_SafeInit(Tcl_Interp *interp); /* * Dynamic string shared by TestdcallCmd and DelCallbackProc; used to collect * the results of the various deletion callbacks. */ static Tcl_DString delString; static Tcl_Interp *delInterp; /* * One of the following structures exists for each command created by the * "testcmdtoken" command. */ typedef struct TestCommandTokenRef { int id; /* Identifier for this reference. */ Tcl_Command token; /* Tcl's token for the command. */ const char *value; struct TestCommandTokenRef *nextPtr; /* Next in list of references. */ } TestCommandTokenRef; static TestCommandTokenRef *firstCommandTokenRef = NULL; static int nextCommandTokenRefId = 1; /* * One of the following structures exists for each asynchronous handler * created by the "testasync" command". */ typedef struct TestAsyncHandler { int id; /* Identifier for this handler. */ Tcl_AsyncHandler handler; /* Tcl's token for the handler. */ char *command; /* Command to invoke when the handler is * invoked. */ struct TestAsyncHandler *nextPtr; /* Next is list of handlers. */ } TestAsyncHandler; /* * Start of the socket driver state structure to acces field testFlags */ typedef struct TcpState TcpState; struct TcpState { Tcl_Channel channel; /* Channel associated with this socket. */ int flags; /* ORed combination of various bitfields. */ }; TCL_DECLARE_MUTEX(asyncTestMutex) static TestAsyncHandler *firstHandler = NULL; /* * The dynamic string below is used by the "testdstring" command to test the * dynamic string facilities. */ static Tcl_DString dstring; /* * The command trace below is used by the "testcmdtraceCmd" command to test * the command tracing facilities. */ static Tcl_Trace cmdTrace; /* * One of the following structures exists for each command created by * TestdelCmd: */ typedef struct { Tcl_Interp *interp; /* Interpreter in which command exists. */ char *deleteCmd; /* Script to execute when command is deleted. * Malloc'ed. */ } DelCmd; /* * The following is used to keep track of an encoding that invokes a Tcl * command. */ typedef struct { Tcl_Interp *interp; char *toUtfCmd; char *fromUtfCmd; } TclEncoding; /* * Boolean flag used by the "testsetmainloop" and "testexitmainloop" commands. */ static int exitMainLoop = 0; /* * Event structure used in testing the event queue management procedures. */ typedef struct { Tcl_Event header; /* Header common to all events */ Tcl_Interp *interp; /* Interpreter that will handle the event */ Tcl_Obj *command; /* Command to evaluate when the event occurs */ Tcl_Obj *tag; /* Tag for this event used to delete it */ } TestEvent; /* * Simple detach/attach facility for testchannel cut|splice. Allow testing of * channel transfer in core testsuite. */ typedef struct TestChannel { Tcl_Channel chan; /* Detached channel */ struct TestChannel *nextPtr;/* Next in detached channel pool */ } TestChannel; static TestChannel *firstDetached; /* * Forward declarations for procedures defined later in this file: */ static int AsyncHandlerProc(void *clientData, Tcl_Interp *interp, int code); static Tcl_ThreadCreateType AsyncThreadProc(void *); static void CleanupTestSetassocdataTests( void *clientData, Tcl_Interp *interp); static void CmdDelProc1(void *clientData); static void CmdDelProc2(void *clientData); static Tcl_CmdProc CmdProc1; static Tcl_CmdProc CmdProc2; static Tcl_CmdObjTraceProc CmdTraceDeleteProc; static Tcl_CmdObjTraceProc CmdTraceProc; static Tcl_ObjCmdProc CreatedCommandProc; static Tcl_ObjCmdProc CreatedCommandProc2; static void DelCallbackProc(void *clientData, Tcl_Interp *interp); static Tcl_ObjCmdProc DelCmdProc; static void DelDeleteProc(void *clientData); static void EncodingFreeProc(void *clientData); static int EncodingToUtfProc(void *clientData, const char *src, int srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, int dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr); static int EncodingFromUtfProc(void *clientData, const char *src, int srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, int dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr); static void ExitProcEven(void *clientData); static void ExitProcOdd(void *clientData); static Tcl_ObjCmdProc GetTimesCmd; static Tcl_ResolveCompiledVarProc InterpCompiledVarResolver; static void MainLoop(void); static Tcl_CmdProc NoopCmd; static Tcl_ObjCmdProc NoopObjCmd; static Tcl_CmdObjTraceProc TraceProc; static void ObjTraceDeleteProc(void *clientData); static void PrintParse(Tcl_Interp *interp, Tcl_Parse *parsePtr); static Tcl_FreeProc SpecialFree; static int StaticInitProc(Tcl_Interp *interp); static Tcl_ObjCmdProc TestasyncCmd; static Tcl_ObjCmdProc TestbumpinterpepochCmd; static Tcl_ObjCmdProc TestbytestringCmd; static Tcl_ObjCmdProc TestsetbytearraylengthCmd; static Tcl_ObjCmdProc TestpurebytesobjCmd; static Tcl_ObjCmdProc TeststringbytesCmd; static Tcl_ObjCmdProc2 Testcmdobj2Cmd; static Tcl_ObjCmdProc TestcmdinfoCmd; static Tcl_ObjCmdProc TestcmdtokenCmd; static Tcl_ObjCmdProc TestcmdtraceCmd; static Tcl_ObjCmdProc TestconcatobjCmd; static Tcl_ObjCmdProc TestcreatecommandCmd; static Tcl_ObjCmdProc TestdcallCmd; static Tcl_ObjCmdProc TestdelCmd; static Tcl_ObjCmdProc TestdelassocdataCmd; static Tcl_ObjCmdProc TestdoubledigitsCmd; static Tcl_ObjCmdProc TestdstringCmd; static Tcl_ObjCmdProc TestencodingCmd; static Tcl_ObjCmdProc TestevalexCmd; static Tcl_ObjCmdProc TestevalobjvCmd; static Tcl_ObjCmdProc TesteventCmd; static int TesteventProc(Tcl_Event *event, int flags); static int TesteventDeleteProc(Tcl_Event *event, void *clientData); static Tcl_ObjCmdProc TestexithandlerCmd; static Tcl_ObjCmdProc TestexprlongCmd; static Tcl_ObjCmdProc TestexprlongobjCmd; static Tcl_ObjCmdProc TestexprdoubleCmd; static Tcl_ObjCmdProc TestexprdoubleobjCmd; static Tcl_ObjCmdProc TestexprparserCmd; static Tcl_ObjCmdProc TestexprstringCmd; static Tcl_ObjCmdProc TestfileCmd; static Tcl_ObjCmdProc TestfilelinkCmd; static Tcl_ObjCmdProc TestfeventCmd; static Tcl_ObjCmdProc TestgetassocdataCmd; static Tcl_ObjCmdProc TestgetintCmd; static Tcl_ObjCmdProc TestlongsizeCmd; static Tcl_ObjCmdProc TestgetplatformCmd; static Tcl_ObjCmdProc TestgetvarfullnameCmd; static Tcl_ObjCmdProc TestinterpdeleteCmd; static Tcl_ObjCmdProc TestlinkCmd; static Tcl_ObjCmdProc TestlinkarrayCmd; static Tcl_ObjCmdProc TestlistrepCmd; static Tcl_ObjCmdProc TestlocaleCmd; static Tcl_ObjCmdProc TestmainthreadCmd; static Tcl_ObjCmdProc TestsetmainloopCmd; static Tcl_ObjCmdProc TestexitmainloopCmd; static Tcl_ObjCmdProc TestpanicCmd; static Tcl_ObjCmdProc TestparseargsCmd; static Tcl_ObjCmdProc TestparserCmd; static Tcl_ObjCmdProc TestparsevarCmd; static Tcl_ObjCmdProc TestparsevarnameCmd; static Tcl_ObjCmdProc TestpreferstableCmd; static Tcl_ObjCmdProc TestprintCmd; static Tcl_ObjCmdProc TestregexpCmd; static Tcl_ObjCmdProc TestreturnCmd; static void TestregexpXflags(const char *string, size_t length, int *cflagsPtr, int *eflagsPtr); static Tcl_ObjCmdProc TestsetassocdataCmd; static Tcl_ObjCmdProc TestsetCmd; static Tcl_ObjCmdProc Testset2Cmd; static Tcl_ObjCmdProc TestseterrorcodeCmd; static Tcl_ObjCmdProc TestsetobjerrorcodeCmd; static Tcl_ObjCmdProc TestsetplatformCmd; static Tcl_ObjCmdProc TestSizeCmd; static Tcl_ObjCmdProc TeststaticlibraryCmd; static Tcl_ObjCmdProc TesttranslatefilenameCmd; static Tcl_ObjCmdProc TestfstildeexpandCmd; static Tcl_ObjCmdProc TestupvarCmd; static Tcl_ObjCmdProc2 TestWrongNumArgsCmd; static Tcl_ObjCmdProc TestGetIndexFromObjStructCmd; static Tcl_ObjCmdProc TestChannelCmd; static Tcl_ObjCmdProc TestChannelEventCmd; static Tcl_ObjCmdProc TestSocketCmd; static Tcl_ObjCmdProc TestFilesystemCmd; static Tcl_ObjCmdProc TestSimpleFilesystemCmd; static void TestReport(const char *cmd, Tcl_Obj *arg1, Tcl_Obj *arg2); static Tcl_Obj * TestReportGetNativePath(Tcl_Obj *pathPtr); static Tcl_FSStatProc TestReportStat; static Tcl_FSAccessProc TestReportAccess; static Tcl_FSOpenFileChannelProc TestReportOpenFileChannel; static Tcl_FSMatchInDirectoryProc TestReportMatchInDirectory; static Tcl_FSChdirProc TestReportChdir; static Tcl_FSLstatProc TestReportLstat; static Tcl_FSCopyFileProc TestReportCopyFile; static Tcl_FSDeleteFileProc TestReportDeleteFile; static Tcl_FSRenameFileProc TestReportRenameFile; static Tcl_FSCreateDirectoryProc TestReportCreateDirectory; static Tcl_FSCopyDirectoryProc TestReportCopyDirectory; static Tcl_FSRemoveDirectoryProc TestReportRemoveDirectory; static int TestReportLoadFile(Tcl_Interp *interp, Tcl_Obj *pathPtr, Tcl_LoadHandle *handlePtr, Tcl_FSUnloadFileProc **unloadProcPtr); static Tcl_FSLinkProc TestReportLink; static Tcl_FSFileAttrStringsProc TestReportFileAttrStrings; static Tcl_FSFileAttrsGetProc TestReportFileAttrsGet; static Tcl_FSFileAttrsSetProc TestReportFileAttrsSet; static Tcl_FSUtimeProc TestReportUtime; static Tcl_FSNormalizePathProc TestReportNormalizePath; static Tcl_FSPathInFilesystemProc TestReportInFilesystem; static Tcl_FSFreeInternalRepProc TestReportFreeInternalRep; static Tcl_FSDupInternalRepProc TestReportDupInternalRep; static Tcl_ObjCmdProc TestServiceModeCmd; static Tcl_FSStatProc SimpleStat; static Tcl_FSAccessProc SimpleAccess; static Tcl_FSOpenFileChannelProc SimpleOpenFileChannel; static Tcl_FSListVolumesProc SimpleListVolumes; static Tcl_FSPathInFilesystemProc SimplePathInFilesystem; static Tcl_Obj * SimpleRedirect(Tcl_Obj *pathPtr); static Tcl_FSMatchInDirectoryProc SimpleMatchInDirectory; static Tcl_ObjCmdProc TestUtfNextCmd; static Tcl_ObjCmdProc TestUtfPrevCmd; static Tcl_ObjCmdProc TestNumUtfCharsCmd; static Tcl_ObjCmdProc TestGetUniCharCmd; static Tcl_ObjCmdProc TestFindFirstCmd; static Tcl_ObjCmdProc TestFindLastCmd; static Tcl_ObjCmdProc TestHashSystemHashCmd; static Tcl_ObjCmdProc TestGetIntForIndexCmd; static Tcl_ObjCmdProc TestLutilCmd; static Tcl_NRPostProc NREUnwind_callback; static Tcl_ObjCmdProc TestNREUnwind; static Tcl_ObjCmdProc TestNRELevels; static Tcl_ObjCmdProc TestInterpResolverCmd; #if defined(HAVE_CPUID) && !defined(MAC_OSX_TCL) static Tcl_ObjCmdProc TestcpuidCmd; #endif static Tcl_ObjCmdProc TestApplyLambdaCmd; static const Tcl_Filesystem testReportingFilesystem = { "reporting", sizeof(Tcl_Filesystem), TCL_FILESYSTEM_VERSION_1, TestReportInFilesystem, /* path in */ TestReportDupInternalRep, TestReportFreeInternalRep, NULL, /* native to norm */ NULL, /* convert to native */ TestReportNormalizePath, NULL, /* path type */ NULL, /* separator */ TestReportStat, TestReportAccess, TestReportOpenFileChannel, TestReportMatchInDirectory, TestReportUtime, TestReportLink, NULL /* list volumes */, TestReportFileAttrStrings, TestReportFileAttrsGet, TestReportFileAttrsSet, TestReportCreateDirectory, TestReportRemoveDirectory, TestReportDeleteFile, TestReportCopyFile, TestReportRenameFile, TestReportCopyDirectory, TestReportLstat, (Tcl_FSLoadFileProc *) TestReportLoadFile, NULL /* cwd */, TestReportChdir }; static const Tcl_Filesystem simpleFilesystem = { "simple", sizeof(Tcl_Filesystem), TCL_FILESYSTEM_VERSION_1, SimplePathInFilesystem, NULL, NULL, /* No internal to normalized, since we don't create any * pure 'internal' Tcl_Obj path representations */ NULL, /* No create native rep function, since we don't use it * or 'Tcl_FSNewNativePath' */ NULL, /* Normalize path isn't needed - we assume paths only have * one representation */ NULL, NULL, NULL, SimpleStat, SimpleAccess, SimpleOpenFileChannel, SimpleMatchInDirectory, NULL, /* We choose not to support symbolic links inside our vfs's */ NULL, SimpleListVolumes, NULL, NULL, NULL, NULL, NULL, NULL, /* No copy file - fallback will occur at Tcl level */ NULL, /* No rename file - fallback will occur at Tcl level */ NULL, /* No copy directory - fallback will occur at Tcl level */ NULL, /* Use stat for lstat */ NULL, /* No load - fallback on core implementation */ NULL, /* We don't need a getcwd or chdir - fallback on Tcl's versions */ NULL, NULL }; /* *---------------------------------------------------------------------- * * Tcltest_Init -- * * This procedure performs application-specific initialization. Most * applications, especially those that incorporate additional packages, * will have their own version of this procedure. * * Results: * Returns a standard Tcl completion code, and leaves an error message in * the interp's result if an error occurs. * * Side effects: * Depends on the startup script. * *---------------------------------------------------------------------- */ #ifndef STRINGIFY # define STRINGIFY(x) STRINGIFY1(x) # define STRINGIFY1(x) #x #endif static const char version[] = TCL_PATCH_LEVEL "+" STRINGIFY(TCL_VERSION_UUID) #if defined(__clang__) && defined(__clang_major__) ".clang-" STRINGIFY(__clang_major__) #if __clang_minor__ < 10 "0" #endif STRINGIFY(__clang_minor__) #endif #ifdef TCL_COMPILE_DEBUG ".compiledebug" #endif #ifdef TCL_COMPILE_STATS ".compilestats" #endif #if defined(__cplusplus) && !defined(__OBJC__) ".cplusplus" #endif #ifndef NDEBUG ".debug" #endif #if !defined(__clang__) && !defined(__INTEL_COMPILER) && defined(__GNUC__) ".gcc-" STRINGIFY(__GNUC__) #if __GNUC_MINOR__ < 10 "0" #endif STRINGIFY(__GNUC_MINOR__) #endif #ifdef __INTEL_COMPILER ".icc-" STRINGIFY(__INTEL_COMPILER) #endif #if (defined(_WIN32) && !defined(_WIN64)) || (ULONG_MAX == 0xffffffffUL) ".ilp32" #endif #ifdef TCL_MEM_DEBUG ".memdebug" #endif #if defined(_MSC_VER) ".msvc-" STRINGIFY(_MSC_VER) #endif #ifdef USE_NMAKE ".nmake" #endif #if !TCL_THREADS ".no-thread" #endif #ifndef TCL_CFG_OPTIMIZED ".no-optimize" #endif #ifdef __OBJC__ ".objective-c" #if defined(__cplusplus) "plusplus" #endif #endif #ifdef TCL_CFG_PROFILED ".profile" #endif #ifdef PURIFY ".purify" #endif #ifdef STATIC_BUILD ".static" #endif #if TCL_UTF_MAX < 4 ".utf-16" #endif ; int Tcltest_Init( Tcl_Interp *interp) /* Interpreter for application. */ { Tcl_CmdInfo info; Tcl_Obj **objv, *objPtr; Tcl_Size objc; int index; static const char *const specialOptions[] = { "-appinitprocerror", "-appinitprocdeleteinterp", "-appinitprocclosestderr", "-appinitprocsetrcfile", NULL }; if (Tcl_InitStubs(interp, "8.7-", 0) == NULL) { return TCL_ERROR; } if (Tcl_OOInitStubs(interp) == NULL) { return TCL_ERROR; } if (Tcl_GetCommandInfo(interp, "::tcl::build-info", &info)) { #if TCL_MAJOR_VERSION > 8 if (info.isNativeObjectProc == 2) { Tcl_CreateObjCommand2(interp, "::tcl::test::build-info", info.objProc2, (void *)version, NULL); } else #endif Tcl_CreateObjCommand(interp, "::tcl::test::build-info", info.objProc, (void *)version, NULL); } if (Tcl_PkgProvideEx(interp, "tcl::test", TCL_PATCH_LEVEL, NULL) == TCL_ERROR) { return TCL_ERROR; } /* * Create additional commands and math functions for testing Tcl. */ Tcl_CreateObjCommand(interp, "gettimes", GetTimesCmd, NULL, NULL); Tcl_CreateCommand(interp, "noop", NoopCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "noop", NoopObjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testpurebytesobj", TestpurebytesobjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testsetbytearraylength", TestsetbytearraylengthCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testbytestring", TestbytestringCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "teststringbytes", TeststringbytesCmd, NULL, NULL); Tcl_CreateObjCommand2(interp, "testwrongnumargs", TestWrongNumArgsCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testfilesystem", TestFilesystemCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testsimplefilesystem", TestSimpleFilesystemCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testgetindexfromobjstruct", TestGetIndexFromObjStructCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testasync", TestasyncCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testbumpinterpepoch", TestbumpinterpepochCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testchannel", TestChannelCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testchannelevent", TestChannelEventCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testcmdtoken", TestcmdtokenCmd, NULL, NULL); Tcl_CreateObjCommand2(interp, "testcmdobj2", Testcmdobj2Cmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testcmdinfo", TestcmdinfoCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testcmdtrace", TestcmdtraceCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testconcatobj", TestconcatobjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testcreatecommand", TestcreatecommandCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testdcall", TestdcallCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testdel", TestdelCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testdelassocdata", TestdelassocdataCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testdoubledigits", TestdoubledigitsCmd, NULL, NULL); Tcl_DStringInit(&dstring); Tcl_CreateObjCommand(interp, "testdstring", TestdstringCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testencoding", TestencodingCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testevalex", TestevalexCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testevalobjv", TestevalobjvCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testevent", TesteventCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testexithandler", TestexithandlerCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testexprlong", TestexprlongCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testexprlongobj", TestexprlongobjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testexprdouble", TestexprdoubleCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testexprdoubleobj", TestexprdoubleobjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testexprparser", TestexprparserCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testexprstring", TestexprstringCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testfevent", TestfeventCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testfilelink", TestfilelinkCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testfile", TestfileCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testhashsystemhash", TestHashSystemHashCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testgetassocdata", TestgetassocdataCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testgetint", TestgetintCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testlongsize", TestlongsizeCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testgetplatform", TestgetplatformCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testgetvarfullname", TestgetvarfullnameCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testinterpdelete", TestinterpdeleteCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testlink", TestlinkCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testlinkarray", TestlinkarrayCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testlistrep", TestlistrepCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testlocale", TestlocaleCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testpanic", TestpanicCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testparseargs", TestparseargsCmd,NULL,NULL); Tcl_CreateObjCommand(interp, "testparser", TestparserCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testparsevar", TestparsevarCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testparsevarname", TestparsevarnameCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testpreferstable", TestpreferstableCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testprint", TestprintCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testregexp", TestregexpCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testreturn", TestreturnCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testservicemode", TestServiceModeCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testsetassocdata", TestsetassocdataCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testsetnoerr", TestsetCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testseterr", TestsetCmd, INT2PTR(TCL_LEAVE_ERR_MSG), NULL); Tcl_CreateObjCommand(interp, "testset2", Testset2Cmd, INT2PTR(TCL_LEAVE_ERR_MSG), NULL); Tcl_CreateObjCommand(interp, "testseterrorcode", TestseterrorcodeCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testsetobjerrorcode", TestsetobjerrorcodeCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testutfnext", TestUtfNextCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testutfprev", TestUtfPrevCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testnumutfchars", TestNumUtfCharsCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testgetunichar", TestGetUniCharCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testfindfirst", TestFindFirstCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testfindlast", TestFindLastCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testgetintforindex", TestGetIntForIndexCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testsetplatform", TestsetplatformCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testsize", TestSizeCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testsocket", TestSocketCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "teststaticlibrary", TeststaticlibraryCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testtranslatefilename", TesttranslatefilenameCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testfstildeexpand", TestfstildeexpandCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testupvar", TestupvarCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testmainthread", TestmainthreadCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testsetmainloop", TestsetmainloopCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testexitmainloop", TestexitmainloopCmd, NULL, NULL); #if defined(HAVE_CPUID) && !defined(MAC_OSX_TCL) Tcl_CreateObjCommand(interp, "testcpuid", TestcpuidCmd, NULL, NULL); #endif Tcl_CreateObjCommand(interp, "testnreunwind", TestNREUnwind, NULL, NULL); Tcl_CreateObjCommand(interp, "testnrelevels", TestNRELevels, NULL, NULL); Tcl_CreateObjCommand(interp, "testinterpresolver", TestInterpResolverCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testapplylambda", TestApplyLambdaCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testlutil", TestLutilCmd, NULL, NULL); if (TclObjTest_Init(interp) != TCL_OK) { return TCL_ERROR; } if (Procbodytest_Init(interp) != TCL_OK) { return TCL_ERROR; } #if TCL_THREADS if (TclThread_Init(interp) != TCL_OK) { return TCL_ERROR; } #endif if (Tcl_ABSListTest_Init(interp) != TCL_OK) { return TCL_ERROR; } /* * Check for special options used in ../tests/main.test */ objPtr = Tcl_GetVar2Ex(interp, "argv", NULL, TCL_GLOBAL_ONLY); if (objPtr != NULL) { if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { return TCL_ERROR; } if (objc && (Tcl_GetIndexFromObj(NULL, objv[0], specialOptions, NULL, TCL_EXACT, &index) == TCL_OK)) { switch (index) { case 0: return TCL_ERROR; case 1: Tcl_DeleteInterp(interp); return TCL_ERROR; case 2: { int mode; Tcl_UnregisterChannel(interp, Tcl_GetChannel(interp, "stderr", &mode)); return TCL_ERROR; } case 3: if (objc > 1) { Tcl_SetVar2Ex(interp, "tcl_rcFileName", NULL, objv[1], TCL_GLOBAL_ONLY); } return TCL_ERROR; } } } /* * And finally add any platform specific test commands. */ return TclplatformtestInit(interp); } /* *---------------------------------------------------------------------- * * Tcltest_SafeInit -- * * This procedure performs application-specific initialization. Most * applications, especially those that incorporate additional packages, * will have their own version of this procedure. * * Results: * Returns a standard Tcl completion code, and leaves an error message in * the interp's result if an error occurs. * * Side effects: * Depends on the startup script. * *---------------------------------------------------------------------- */ int Tcltest_SafeInit( Tcl_Interp *interp) /* Interpreter for application. */ { Tcl_CmdInfo info; if (Tcl_InitStubs(interp, "8.7-", 0) == NULL) { return TCL_ERROR; } if (Tcl_GetCommandInfo(interp, "::tcl::build-info", &info)) { #if TCL_MAJOR_VERSION > 8 if (info.isNativeObjectProc == 2) { Tcl_CreateObjCommand2(interp, "::tcl::test::build-info", info.objProc2, (void *)version, NULL); } else #endif Tcl_CreateObjCommand(interp, "::tcl::test::build-info", info.objProc, (void *)version, NULL); } if (Tcl_PkgProvideEx(interp, "tcl::test", TCL_PATCH_LEVEL, NULL) == TCL_ERROR) { return TCL_ERROR; } return Procbodytest_SafeInit(interp); } /* *---------------------------------------------------------------------- * * TestasyncCmd -- * * This procedure implements the "testasync" command. It is used * to test the asynchronous handler facilities of Tcl. * * Results: * A standard Tcl result. * * Side effects: * Creates, deletes, and invokes handlers. * *---------------------------------------------------------------------- */ static int TestasyncCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { TestAsyncHandler *asyncPtr, *prevPtr; int id, code; static int nextId = 1; if (objc < 2) { wrongNumArgs: Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[1]), "create") == 0) { if (objc != 3) { goto wrongNumArgs; } asyncPtr = (TestAsyncHandler *)Tcl_Alloc(sizeof(TestAsyncHandler)); asyncPtr->command = (char *)Tcl_Alloc(strlen(Tcl_GetString(objv[2])) + 1); strcpy(asyncPtr->command, Tcl_GetString(objv[2])); Tcl_MutexLock(&asyncTestMutex); asyncPtr->id = nextId; nextId++; asyncPtr->handler = Tcl_AsyncCreate(AsyncHandlerProc, INT2PTR(asyncPtr->id)); asyncPtr->nextPtr = firstHandler; firstHandler = asyncPtr; Tcl_MutexUnlock(&asyncTestMutex); Tcl_SetObjResult(interp, Tcl_NewWideIntObj(asyncPtr->id)); } else if (strcmp(Tcl_GetString(objv[1]), "delete") == 0) { if (objc == 2) { Tcl_MutexLock(&asyncTestMutex); while (firstHandler != NULL) { asyncPtr = firstHandler; firstHandler = asyncPtr->nextPtr; Tcl_AsyncDelete(asyncPtr->handler); Tcl_Free(asyncPtr->command); Tcl_Free(asyncPtr); } Tcl_MutexUnlock(&asyncTestMutex); return TCL_OK; } if (objc != 3) { goto wrongNumArgs; } if (Tcl_GetIntFromObj(interp, objv[2], &id) != TCL_OK) { return TCL_ERROR; } Tcl_MutexLock(&asyncTestMutex); for (prevPtr = NULL, asyncPtr = firstHandler; asyncPtr != NULL; prevPtr = asyncPtr, asyncPtr = asyncPtr->nextPtr) { if (asyncPtr->id != id) { continue; } if (prevPtr == NULL) { firstHandler = asyncPtr->nextPtr; } else { prevPtr->nextPtr = asyncPtr->nextPtr; } Tcl_AsyncDelete(asyncPtr->handler); Tcl_Free(asyncPtr->command); Tcl_Free(asyncPtr); break; } Tcl_MutexUnlock(&asyncTestMutex); } else if (strcmp(Tcl_GetString(objv[1]), "mark") == 0) { if (objc != 5) { goto wrongNumArgs; } if ((Tcl_GetIntFromObj(interp, objv[2], &id) != TCL_OK) || (Tcl_GetIntFromObj(interp, objv[4], &code) != TCL_OK)) { return TCL_ERROR; } Tcl_MutexLock(&asyncTestMutex); for (asyncPtr = firstHandler; asyncPtr != NULL; asyncPtr = asyncPtr->nextPtr) { if (asyncPtr->id == id) { Tcl_AsyncMark(asyncPtr->handler); break; } } Tcl_SetObjResult(interp, objv[3]); Tcl_MutexUnlock(&asyncTestMutex); return code; } else if (strcmp(Tcl_GetString(objv[1]), "marklater") == 0) { if (objc != 3) { goto wrongNumArgs; } if (Tcl_GetIntFromObj(interp, objv[2], &id) != TCL_OK) { return TCL_ERROR; } Tcl_MutexLock(&asyncTestMutex); for (asyncPtr = firstHandler; asyncPtr != NULL; asyncPtr = asyncPtr->nextPtr) { if (asyncPtr->id == id) { Tcl_ThreadId threadID; if (Tcl_CreateThread(&threadID, AsyncThreadProc, INT2PTR(id), TCL_THREAD_STACK_DEFAULT, TCL_THREAD_NOFLAGS) != TCL_OK) { Tcl_AppendResult(interp, "cannot create thread", (char *)NULL); Tcl_MutexUnlock(&asyncTestMutex); return TCL_ERROR; } break; } } Tcl_MutexUnlock(&asyncTestMutex); } else { Tcl_AppendResult(interp, "bad option \"", Tcl_GetString(objv[1]), "\": must be create, delete, int, mark, or marklater", (char *)NULL); return TCL_ERROR; } return TCL_OK; } static int AsyncHandlerProc( void *clientData, /* If of TestAsyncHandler structure. * in global list. */ Tcl_Interp *interp, /* Interpreter in which command was * executed, or NULL. */ int code) /* Current return code from command. */ { TestAsyncHandler *asyncPtr; int id = PTR2INT(clientData); const char *listArgv[4]; char *cmd; char string[TCL_INTEGER_SPACE]; Tcl_MutexLock(&asyncTestMutex); for (asyncPtr = firstHandler; asyncPtr != NULL; asyncPtr = asyncPtr->nextPtr) { if (asyncPtr->id == id) { break; } } Tcl_MutexUnlock(&asyncTestMutex); if (!asyncPtr) { /* Woops - this one was deleted between the AsyncMark and now */ return TCL_OK; } TclFormatInt(string, code); listArgv[0] = asyncPtr->command; listArgv[1] = Tcl_GetStringResult(interp); listArgv[2] = string; listArgv[3] = NULL; cmd = Tcl_Merge(3, listArgv); if (interp != NULL) { code = Tcl_EvalEx(interp, cmd, TCL_INDEX_NONE, 0); } else { /* * this should not happen, but by definition of how async handlers are * invoked, it's possible. Better error checking is needed here. */ } Tcl_Free(cmd); return code; } /* *---------------------------------------------------------------------- * * AsyncThreadProc -- * * Delivers an asynchronous event to a handler in another thread. * * Results: * None. * * Side effects: * Invokes Tcl_AsyncMark on the handler * *---------------------------------------------------------------------- */ static Tcl_ThreadCreateType AsyncThreadProc( void *clientData) /* Parameter is the id of a * TestAsyncHandler, defined above. */ { TestAsyncHandler *asyncPtr; int id = PTR2INT(clientData); Tcl_Sleep(1); Tcl_MutexLock(&asyncTestMutex); for (asyncPtr = firstHandler; asyncPtr != NULL; asyncPtr = asyncPtr->nextPtr) { if (asyncPtr->id == id) { Tcl_AsyncMark(asyncPtr->handler); break; } } Tcl_MutexUnlock(&asyncTestMutex); Tcl_ExitThread(TCL_OK); TCL_THREAD_CREATE_RETURN; } static int TestbumpinterpepochCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *)interp; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } iPtr->compileEpoch++; return TCL_OK; } /* *---------------------------------------------------------------------- * * Testcmdobj2 -- * * Mock up to test the Tcl_CreateObjCommand2 functionality * * Results: * Standard Tcl result. * * Side effects: * Sets interpreter result to number of arguments, first arg, last arg. * *---------------------------------------------------------------------- */ static int Testcmdobj2Cmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *resultObj; resultObj = Tcl_NewListObj(0, NULL); Tcl_ListObjAppendElement(interp, resultObj, Tcl_NewWideIntObj(objc)); if (objc > 1) { Tcl_ListObjAppendElement(interp, resultObj, objv[1]); Tcl_ListObjAppendElement(interp, resultObj, objv[objc-1]); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestcmdinfoCmd -- * * This procedure implements the "testcmdinfo" command. It is used to * test Tcl_GetCommandInfo, Tcl_SetCommandInfo, and command creation and * deletion. * * Results: * A standard Tcl result. * * Side effects: * Creates and deletes various commands and modifies their data. * *---------------------------------------------------------------------- */ static int TestcmdinfoCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const subcmds[] = { "call", "call2", "create", "delete", "get", "modify", NULL }; enum options { CMDINFO_CALL, CMDINFO_CALL2, CMDINFO_CREATE, CMDINFO_DELETE, CMDINFO_GET, CMDINFO_MODIFY } idx; Tcl_CmdInfo info; Tcl_Obj **cmdObjv; Tcl_Size cmdObjc; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "command arg"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], subcmds, "option", 0, &idx) != TCL_OK) { return TCL_ERROR; } switch (idx) { case CMDINFO_CALL: case CMDINFO_CALL2: if (Tcl_ListObjGetElements(interp, objv[2], &cmdObjc, &cmdObjv) != TCL_OK) { return TCL_ERROR; } if (cmdObjc == 0) { Tcl_AppendResult(interp, "No command name given", NULL); return TCL_ERROR; } if (Tcl_GetCommandInfo(interp, Tcl_GetString(cmdObjv[0]), &info) == 0) { return TCL_ERROR; } if (idx == CMDINFO_CALL) { /* * Note when calling through the old 32-bit API, it is the caller's * responsibility to check that number of arguments is <= INT_MAX. * We do not do that here just so we can test what happens if the * caller mistakenly passes more arguments. */ return info.objProc(info.objClientData, interp, cmdObjc, cmdObjv); } else { return info.objProc2(info.objClientData2, interp, cmdObjc, cmdObjv); } case CMDINFO_CREATE: Tcl_CreateCommand(interp, Tcl_GetString(objv[2]), CmdProc1, (void *)"original", CmdDelProc1); break; case CMDINFO_DELETE: Tcl_DStringInit(&delString); Tcl_DeleteCommand(interp, Tcl_GetString(objv[2])); Tcl_DStringResult(interp, &delString); break; case CMDINFO_GET: if (Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ==0) { Tcl_AppendResult(interp, "??", (char *)NULL); return TCL_OK; } if (info.proc == CmdProc1) { Tcl_AppendResult(interp, "CmdProc1", " ", (char *)info.clientData, (char *)NULL); } else if (info.proc == CmdProc2) { Tcl_AppendResult(interp, "CmdProc2", " ", (char *)info.clientData, (char *)NULL); } else { Tcl_AppendResult(interp, "unknown", (char *)NULL); } if (info.deleteProc == CmdDelProc1) { Tcl_AppendResult(interp, " CmdDelProc1", " ", (char *)info.deleteData, (char *)NULL); } else if (info.deleteProc == CmdDelProc2) { Tcl_AppendResult(interp, " CmdDelProc2", " ", (char *)info.deleteData, (char *)NULL); } else { Tcl_AppendResult(interp, " unknown", (char *)NULL); } Tcl_AppendResult(interp, " ", info.namespacePtr->fullName, (char *)NULL); if (info.isNativeObjectProc == 0) { Tcl_AppendResult(interp, " stringProc", (char *)NULL); } else if (info.isNativeObjectProc == 1) { Tcl_AppendResult(interp, " nativeObjectProc", (char *)NULL); } else if (info.isNativeObjectProc == 2) { Tcl_AppendResult(interp, " nativeObjectProc2", (char *)NULL); } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf("Invalid isNativeObjectProc value %d", info.isNativeObjectProc)); return TCL_ERROR; } break; case CMDINFO_MODIFY: info.proc = CmdProc2; info.clientData = (void *) "new_command_data"; info.objProc = NULL; info.objClientData = NULL; info.deleteProc = CmdDelProc2; info.deleteData = (void *) "new_delete_data"; info.namespacePtr = NULL; info.objProc2 = NULL; info.objClientData2 = NULL; if (Tcl_SetCommandInfo(interp, Tcl_GetString(objv[2]), &info) == 0) { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(0)); } else { Tcl_SetObjResult(interp, Tcl_NewWideIntObj(1)); } break; } return TCL_OK; } static int CmdProc0( void *clientData, /* String to return. */ Tcl_Interp *interp, /* Current interpreter. */ TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *) /*objv*/) { TestCommandTokenRef *refPtr = (TestCommandTokenRef *) clientData; Tcl_AppendResult(interp, "CmdProc1 ", refPtr->value, (char *)NULL); return TCL_OK; } static int CmdProc1( void *clientData, /* String to return. */ Tcl_Interp *interp, /* Current interpreter. */ TCL_UNUSED(int) /*argc*/, TCL_UNUSED(const char **) /*argv*/) { Tcl_AppendResult(interp, "CmdProc1 ", (char *)clientData, (char *)NULL); return TCL_OK; } static int CmdProc2( void *clientData, /* String to return. */ Tcl_Interp *interp, /* Current interpreter. */ TCL_UNUSED(int) /*argc*/, TCL_UNUSED(const char **) /*argv*/) { Tcl_AppendResult(interp, "CmdProc2 ", (char *)clientData, (char *)NULL); return TCL_OK; } static void CmdDelProc0( void *clientData) /* String to save. */ { TestCommandTokenRef *thisRefPtr, *prevRefPtr = NULL; TestCommandTokenRef *refPtr = (TestCommandTokenRef *) clientData; int id = refPtr->id; for (thisRefPtr = firstCommandTokenRef; refPtr != NULL; thisRefPtr = thisRefPtr->nextPtr) { if (thisRefPtr->id == id) { if (prevRefPtr != NULL) { prevRefPtr->nextPtr = thisRefPtr->nextPtr; } else { firstCommandTokenRef = thisRefPtr->nextPtr; } break; } prevRefPtr = thisRefPtr; } Tcl_Free(refPtr); } static void CmdDelProc1( void *clientData) /* String to save. */ { Tcl_DStringInit(&delString); Tcl_DStringAppend(&delString, "CmdDelProc1 ", -1); Tcl_DStringAppend(&delString, (char *)clientData, -1); } static void CmdDelProc2( void *clientData) /* String to save. */ { Tcl_DStringInit(&delString); Tcl_DStringAppend(&delString, "CmdDelProc2 ", -1); Tcl_DStringAppend(&delString, (char *)clientData, -1); } /* *---------------------------------------------------------------------- * * TestcmdtokenCmd -- * * This procedure implements the "testcmdtoken" command. It is used to * test Tcl_Command tokens and procedures such as Tcl_GetCommandFullName. * * Results: * A standard Tcl result. * * Side effects: * Creates and deletes various commands and modifies their data. * *---------------------------------------------------------------------- */ static int TestcmdtokenCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { TestCommandTokenRef *refPtr; int id; char buf[30]; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "option arg"); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[1]), "create") == 0) { refPtr = (TestCommandTokenRef *)Tcl_Alloc(sizeof(TestCommandTokenRef)); refPtr->token = Tcl_CreateObjCommand(interp, Tcl_GetString(objv[2]), CmdProc0, refPtr, CmdDelProc0); refPtr->id = nextCommandTokenRefId; refPtr->value = "original"; nextCommandTokenRefId++; refPtr->nextPtr = firstCommandTokenRef; firstCommandTokenRef = refPtr; snprintf(buf, sizeof(buf), "%d", refPtr->id); Tcl_AppendResult(interp, buf, (char *)NULL); } else { if (sscanf(Tcl_GetString(objv[2]), "%d", &id) != 1) { Tcl_AppendResult(interp, "bad command token \"", Tcl_GetString(objv[2]), "\"", (char *)NULL); return TCL_ERROR; } for (refPtr = firstCommandTokenRef; refPtr != NULL; refPtr = refPtr->nextPtr) { if (refPtr->id == id) { break; } } if (refPtr == NULL) { Tcl_AppendResult(interp, "bad command token \"", Tcl_GetString(objv[2]), "\"", (char *)NULL); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[1]), "name") == 0) { Tcl_Obj *objPtr; objPtr = Tcl_NewObj(); Tcl_GetCommandFullName(interp, refPtr->token, objPtr); Tcl_AppendElement(interp, Tcl_GetCommandName(interp, refPtr->token)); Tcl_AppendElement(interp, Tcl_GetString(objPtr)); Tcl_DecrRefCount(objPtr); } else { Tcl_AppendResult(interp, "bad option \"", Tcl_GetString(objv[1]), "\": must be create, name, or free", (char *)NULL); return TCL_ERROR; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TestcmdtraceCmd -- * * This procedure implements the "testcmdtrace" command. It is used * to test Tcl_CreateTrace and Tcl_DeleteTrace. * * Results: * A standard Tcl result. * * Side effects: * Creates and deletes a command trace, and tests the invocation of * a procedure by the command trace. * *---------------------------------------------------------------------- */ static int TestcmdtraceCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Argument strings. */ { Tcl_DString buffer; int result; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "option script"); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[1]), "tracetest") == 0) { Tcl_DStringInit(&buffer); cmdTrace = Tcl_CreateObjTrace(interp, 50000, 0, CmdTraceProc, &buffer, NULL); result = Tcl_EvalEx(interp, Tcl_GetString(objv[2]), TCL_INDEX_NONE, 0); if (result == TCL_OK) { Tcl_ResetResult(interp); Tcl_AppendResult(interp, Tcl_DStringValue(&buffer), (char *)NULL); } Tcl_DeleteTrace(interp, cmdTrace); Tcl_DStringFree(&buffer); } else if (strcmp(Tcl_GetString(objv[1]), "deletetest") == 0) { /* * Create a command trace then eval a script to check whether it is * called. Note that this trace procedure removes itself as a further * check of the robustness of the trace proc calling code in * TclNRExecuteByteCode. */ cmdTrace = Tcl_CreateObjTrace(interp, 50000, 0, CmdTraceDeleteProc, NULL, NULL); Tcl_EvalEx(interp, Tcl_GetString(objv[2]), TCL_INDEX_NONE, 0); } else if (strcmp(Tcl_GetString(objv[1]), "leveltest") == 0) { Interp *iPtr = (Interp *) interp; Tcl_DStringInit(&buffer); cmdTrace = Tcl_CreateObjTrace(interp, iPtr->numLevels + 4, 0, CmdTraceProc, &buffer, NULL); result = Tcl_EvalEx(interp, Tcl_GetString(objv[2]), TCL_INDEX_NONE, 0); if (result == TCL_OK) { Tcl_ResetResult(interp); Tcl_AppendResult(interp, Tcl_DStringValue(&buffer), (char *)NULL); } Tcl_DeleteTrace(interp, cmdTrace); Tcl_DStringFree(&buffer); } else if (strcmp(Tcl_GetString(objv[1]), "resulttest") == 0) { /* Create an object-based trace, then eval a script. This is used * to test return codes other than TCL_OK from the trace engine. */ static int deleteCalled; deleteCalled = 0; cmdTrace = Tcl_CreateObjTrace(interp, 50000, TCL_ALLOW_INLINE_COMPILATION, TraceProc, &deleteCalled, ObjTraceDeleteProc); result = Tcl_EvalEx(interp, Tcl_GetString(objv[2]), TCL_INDEX_NONE, 0); Tcl_DeleteTrace(interp, cmdTrace); if (!deleteCalled) { Tcl_AppendResult(interp, "Delete wasn't called", (char *)NULL); return TCL_ERROR; } else { return result; } } else if (strcmp(Tcl_GetString(objv[1]), "doubletest") == 0) { Tcl_Trace t1, t2; Tcl_DStringInit(&buffer); t1 = Tcl_CreateObjTrace(interp, 1, 0, CmdTraceProc, &buffer, NULL); t2 = Tcl_CreateObjTrace(interp, 50000, 0, CmdTraceProc, &buffer, NULL); result = Tcl_EvalEx(interp, Tcl_GetString(objv[2]), TCL_INDEX_NONE, 0); if (result == TCL_OK) { Tcl_ResetResult(interp); Tcl_AppendResult(interp, Tcl_DStringValue(&buffer), (char *)NULL); } Tcl_DeleteTrace(interp, t2); Tcl_DeleteTrace(interp, t1); Tcl_DStringFree(&buffer); } else { Tcl_AppendResult(interp, "bad option \"", Tcl_GetString(objv[1]), "\": must be tracetest, deletetest, doubletest or resulttest", (char *)NULL); return TCL_ERROR; } return TCL_OK; } static int CmdTraceProc( void *clientData, /* Pointer to buffer in which the * command and arguments are appended. * Accumulates test result. */ TCL_UNUSED(Tcl_Interp *), TCL_UNUSED(int) /*level*/, const char *command, /* The command being traced (after * substitutions). */ TCL_UNUSED(Tcl_Command) /*cmdProc*/, int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { Tcl_DString *bufPtr = (Tcl_DString *) clientData; int i; Tcl_DStringAppendElement(bufPtr, command); Tcl_DStringStartSublist(bufPtr); for (i = 0; i < objc; i++) { Tcl_DStringAppendElement(bufPtr, Tcl_GetString(objv[i])); } Tcl_DStringEndSublist(bufPtr); return TCL_OK; } static int CmdTraceDeleteProc( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ TCL_UNUSED(int) /*level*/, TCL_UNUSED(const char *) /*command*/, TCL_UNUSED(Tcl_Command), TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *) /*objv*/) { /* * Remove ourselves to test whether calling Tcl_DeleteTrace within a trace * callback causes the for loop in TclNRExecuteByteCode that calls traces to * reference freed memory. */ Tcl_DeleteTrace(interp, cmdTrace); return TCL_OK; } static int TraceProc( TCL_UNUSED(void *), Tcl_Interp *interp, /* Tcl interpreter */ TCL_UNUSED(int) /*level*/, const char *command, TCL_UNUSED(Tcl_Command), TCL_UNUSED(int) /*objc*/, Tcl_Obj *const objv[]) /* Argument objects. */ { const char *word = Tcl_GetString(objv[0]); if (!strcmp(word, "Error")) { Tcl_SetObjResult(interp, Tcl_NewStringObj(command, -1)); return TCL_ERROR; } else if (!strcmp(word, "Break")) { return TCL_BREAK; } else if (!strcmp(word, "Continue")) { return TCL_CONTINUE; } else if (!strcmp(word, "Return")) { return TCL_RETURN; } else if (!strcmp(word, "OtherStatus")) { return 6; } else { return TCL_OK; } } static void ObjTraceDeleteProc( void *clientData) { int *intPtr = (int *) clientData; *intPtr = 1; /* Record that the trace was deleted */ } /* *---------------------------------------------------------------------- * * TestcreatecommandCmd -- * * This procedure implements the "testcreatecommand" command. It is used * to test that the Tcl_CreateCommand creates a new command in the * namespace specified as part of its name, if any. It also checks that * the namespace code ignore single ":"s in the middle or end of a * command name. * * Results: * A standard Tcl result. * * Side effects: * Creates and deletes two commands ("test_ns_basic::createdcommand" * and "value:at:"). * *---------------------------------------------------------------------- */ static int TestcreatecommandCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument strings. */ { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "option"); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[1]), "create") == 0) { Tcl_CreateObjCommand(interp, "test_ns_basic::createdcommand", CreatedCommandProc, NULL, NULL); } else if (strcmp(Tcl_GetString(objv[1]), "delete") == 0) { Tcl_DeleteCommand(interp, "test_ns_basic::createdcommand"); } else if (strcmp(Tcl_GetString(objv[1]), "create2") == 0) { Tcl_CreateObjCommand(interp, "value:at:", CreatedCommandProc2, NULL, NULL); } else if (strcmp(Tcl_GetString(objv[1]), "delete2") == 0) { Tcl_DeleteCommand(interp, "value:at:"); } else { Tcl_AppendResult(interp, "bad option \"", Tcl_GetString(objv[1]), "\": must be create, delete, create2, or delete2", (char *)NULL); return TCL_ERROR; } return TCL_OK; } static int CreatedCommandProc( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *) /*objv*/) { Tcl_CmdInfo info; int found; found = Tcl_GetCommandInfo(interp, "test_ns_basic::createdcommand", &info); if (!found) { Tcl_AppendResult(interp, "CreatedCommandProc could not get command info for test_ns_basic::createdcommand", (char *)NULL); return TCL_ERROR; } Tcl_AppendResult(interp, "CreatedCommandProc in ", info.namespacePtr->fullName, (char *)NULL); return TCL_OK; } static int CreatedCommandProc2( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *) /*objv*/) { Tcl_CmdInfo info; int found; found = Tcl_GetCommandInfo(interp, "value:at:", &info); if (!found) { Tcl_AppendResult(interp, "CreatedCommandProc2 could not get command info for test_ns_basic::createdcommand", (char *)NULL); return TCL_ERROR; } Tcl_AppendResult(interp, "CreatedCommandProc2 in ", info.namespacePtr->fullName, (char *)NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestdcallCmd -- * * This procedure implements the "testdcall" command. It is used * to test Tcl_CallWhenDeleted. * * Results: * A standard Tcl result. * * Side effects: * Creates and deletes interpreters. * *---------------------------------------------------------------------- */ static int TestdcallCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { int i; int id; delInterp = Tcl_CreateInterp(); Tcl_DStringInit(&delString); for (i = 1; i < objc; i++) { if (Tcl_GetIntFromObj(interp, objv[i], &id) != TCL_OK) { return TCL_ERROR; } if (id < 0) { Tcl_DontCallWhenDeleted(delInterp, DelCallbackProc, INT2PTR(-id)); } else { Tcl_CallWhenDeleted(delInterp, DelCallbackProc, INT2PTR(id)); } } Tcl_DeleteInterp(delInterp); Tcl_DStringResult(interp, &delString); return TCL_OK; } /* * The deletion callback used by TestdcallCmd: */ static void DelCallbackProc( void *clientData, /* Numerical value to append to delString. */ Tcl_Interp *interp) /* Interpreter being deleted. */ { int id = PTR2INT(clientData); char buffer[TCL_INTEGER_SPACE]; TclFormatInt(buffer, id); Tcl_DStringAppendElement(&delString, buffer); if (interp != delInterp) { Tcl_DStringAppendElement(&delString, "bogus interpreter argument!"); } } /* *---------------------------------------------------------------------- * * TestdelCmd -- * * This procedure implements the "testdel" command. It is used * to test calling of command deletion callbacks. * * Results: * A standard Tcl result. * * Side effects: * Creates a command. * *---------------------------------------------------------------------- */ static int TestdelCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { DelCmd *dPtr; Tcl_Interp *child; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "interp name delcmdname"); return TCL_ERROR; } child = Tcl_GetChild(interp, Tcl_GetString(objv[1])); if (child == NULL) { return TCL_ERROR; } dPtr = (DelCmd *)Tcl_Alloc(sizeof(DelCmd)); dPtr->interp = interp; dPtr->deleteCmd = (char *)Tcl_Alloc(strlen(Tcl_GetString(objv[3])) + 1); strcpy(dPtr->deleteCmd, Tcl_GetString(objv[3])); Tcl_CreateObjCommand(child, Tcl_GetString(objv[2]), DelCmdProc, dPtr, DelDeleteProc); return TCL_OK; } static int DelCmdProc( void *clientData, /* String result to return. */ Tcl_Interp *interp, /* Current interpreter. */ TCL_UNUSED(int) /*objv*/, TCL_UNUSED(Tcl_Obj *const *) /*objv*/) { DelCmd *dPtr = (DelCmd *) clientData; Tcl_AppendResult(interp, dPtr->deleteCmd, (char *)NULL); Tcl_Free(dPtr->deleteCmd); Tcl_Free(dPtr); return TCL_OK; } static void DelDeleteProc( void *clientData) /* String command to evaluate. */ { DelCmd *dPtr = (DelCmd *)clientData; Tcl_EvalEx(dPtr->interp, dPtr->deleteCmd, TCL_INDEX_NONE, 0); Tcl_ResetResult(dPtr->interp); Tcl_Free(dPtr->deleteCmd); Tcl_Free(dPtr); } /* *---------------------------------------------------------------------- * * TestdelassocdataCmd -- * * This procedure implements the "testdelassocdata" command. It is used * to test Tcl_DeleteAssocData. * * Results: * A standard Tcl result. * * Side effects: * Deletes an association between a key and associated data from an * interpreter. * *---------------------------------------------------------------------- */ static int TestdelassocdataCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "data_key"); return TCL_ERROR; } Tcl_DeleteAssocData(interp, Tcl_GetString(objv[1])); return TCL_OK; } /* *----------------------------------------------------------------------------- * * TestdoubledigitsCmd -- * * This procedure implements the 'testdoubledigits' command. It is * used to test the low-level floating-point formatting primitives * in Tcl. * * Usage: * testdoubledigits fpval ndigits type ?shorten" * * Parameters: * fpval - Floating-point value to format. * ndigits - Digit count to request from Tcl_DoubleDigits * type - One of 'shortest', 'e', 'f' * shorten - Indicates that the 'shorten' flag should be passed in. * *----------------------------------------------------------------------------- */ static int TestdoubledigitsCmd( TCL_UNUSED(void *), Tcl_Interp* interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj* const objv[]) /* Parameter vector */ { static const char *options[] = { "shortest", "e", "f", NULL }; static const int types[] = { TCL_DD_SHORTEST, TCL_DD_E_FORMAT, TCL_DD_F_FORMAT }; const Tcl_ObjType* doubleType; double d; int status; int ndigits; int type; int decpt; int signum; char *str; char *endPtr; Tcl_Obj* strObj; Tcl_Obj* retval; if (objc < 4 || objc > 5) { Tcl_WrongNumArgs(interp, 1, objv, "fpval ndigits type ?shorten?"); return TCL_ERROR; } status = Tcl_GetDoubleFromObj(interp, objv[1], &d); if (status != TCL_OK) { doubleType = Tcl_GetObjType("double"); if (Tcl_FetchInternalRep(objv[1], doubleType) && isnan(objv[1]->internalRep.doubleValue)) { status = TCL_OK; memcpy(&d, &(objv[1]->internalRep.doubleValue), sizeof(double)); } } if (status != TCL_OK || Tcl_GetIntFromObj(interp, objv[2], &ndigits) != TCL_OK || Tcl_GetIndexFromObj(interp, objv[3], options, "conversion type", TCL_EXACT, &type) != TCL_OK) { fprintf(stderr, "bad value? %g\n", d); return TCL_ERROR; } type = types[type]; if (objc > 4) { if (strcmp(Tcl_GetString(objv[4]), "shorten")) { Tcl_SetObjResult(interp, Tcl_NewStringObj("bad flag", -1)); return TCL_ERROR; } type |= TCL_DD_SHORTEST; } str = TclDoubleDigits(d, ndigits, type, &decpt, &signum, &endPtr); strObj = Tcl_NewStringObj(str, endPtr-str); Tcl_Free(str); retval = Tcl_NewListObj(1, &strObj); Tcl_ListObjAppendElement(NULL, retval, Tcl_NewWideIntObj(decpt)); strObj = Tcl_NewStringObj(signum ? "-" : "+", 1); Tcl_ListObjAppendElement(NULL, retval, strObj); Tcl_SetObjResult(interp, retval); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestdstringCmd -- * * This procedure implements the "testdstring" command. It is used * to test the dynamic string facilities of Tcl. * * Results: * A standard Tcl result. * * Side effects: * Creates, deletes, and invokes handlers. * *---------------------------------------------------------------------- */ static int TestdstringCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { int count; if (objc < 2) { wrongNumArgs: Tcl_WrongNumArgs(interp, 1, objv, "option ?args?"); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[1]), "append") == 0) { if (objc != 4) { goto wrongNumArgs; } if (Tcl_GetIntFromObj(interp, objv[3], &count) != TCL_OK) { return TCL_ERROR; } Tcl_DStringAppend(&dstring, Tcl_GetString(objv[2]), count); } else if (strcmp(Tcl_GetString(objv[1]), "element") == 0) { if (objc != 3) { goto wrongNumArgs; } Tcl_DStringAppendElement(&dstring, Tcl_GetString(objv[2])); } else if (strcmp(Tcl_GetString(objv[1]), "end") == 0) { if (objc != 2) { goto wrongNumArgs; } Tcl_DStringEndSublist(&dstring); } else if (strcmp(Tcl_GetString(objv[1]), "free") == 0) { if (objc != 2) { goto wrongNumArgs; } Tcl_DStringFree(&dstring); } else if (strcmp(Tcl_GetString(objv[1]), "get") == 0) { if (objc != 2) { goto wrongNumArgs; } Tcl_SetResult(interp, Tcl_DStringValue(&dstring), TCL_VOLATILE); } else if (strcmp(Tcl_GetString(objv[1]), "gresult") == 0) { if (objc != 3) { goto wrongNumArgs; } if (strcmp(Tcl_GetString(objv[2]), "staticsmall") == 0) { Tcl_AppendResult(interp, "short", (char *)NULL); } else if (strcmp(Tcl_GetString(objv[2]), "staticlarge") == 0) { Tcl_AppendResult(interp, "first0 first1 first2 first3 first4 first5 first6 first7 first8 first9\nsecond0 second1 second2 second3 second4 second5 second6 second7 second8 second9\nthird0 third1 third2 third3 third4 third5 third6 third7 third8 third9\nfourth0 fourth1 fourth2 fourth3 fourth4 fourth5 fourth6 fourth7 fourth8 fourth9\nfifth0 fifth1 fifth2 fifth3 fifth4 fifth5 fifth6 fifth7 fifth8 fifth9\nsixth0 sixth1 sixth2 sixth3 sixth4 sixth5 sixth6 sixth7 sixth8 sixth9\nseventh0 seventh1 seventh2 seventh3 seventh4 seventh5 seventh6 seventh7 seventh8 seventh9\n", (char *)NULL); } else if (strcmp(Tcl_GetString(objv[2]), "free") == 0) { char *s = (char *)Tcl_Alloc(100); strcpy(s, "This is a malloc-ed string"); Tcl_SetResult(interp, s, TCL_DYNAMIC); } else if (strcmp(Tcl_GetString(objv[2]), "special") == 0) { char *s = (char *)Tcl_Alloc(100) + 16; strcpy(s, "This is a specially-allocated string"); Tcl_SetResult(interp, s, SpecialFree); } else { Tcl_AppendResult(interp, "bad gresult option \"", Tcl_GetString(objv[2]), "\": must be staticsmall, staticlarge, free, or special", (char *)NULL); return TCL_ERROR; } Tcl_DStringGetResult(interp, &dstring); } else if (strcmp(Tcl_GetString(objv[1]), "length") == 0) { if (objc != 2) { goto wrongNumArgs; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(Tcl_DStringLength(&dstring))); } else if (strcmp(Tcl_GetString(objv[1]), "result") == 0) { if (objc != 2) { goto wrongNumArgs; } Tcl_DStringResult(interp, &dstring); } else if (strcmp(Tcl_GetString(objv[1]), "toobj") == 0) { if (objc != 2) { goto wrongNumArgs; } Tcl_SetObjResult(interp, Tcl_DStringToObj(&dstring)); } else if (strcmp(Tcl_GetString(objv[1]), "trunc") == 0) { if (objc != 3) { goto wrongNumArgs; } if (Tcl_GetIntFromObj(interp, objv[2], &count) != TCL_OK) { return TCL_ERROR; } Tcl_DStringSetLength(&dstring, count); } else if (strcmp(Tcl_GetString(objv[1]), "start") == 0) { if (objc != 2) { goto wrongNumArgs; } Tcl_DStringStartSublist(&dstring); } else { Tcl_AppendResult(interp, "bad option \"", Tcl_GetString(objv[1]), "\": must be append, element, end, free, get, gresult, length, " "result, start, toobj, or trunc", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* * The procedure below is used as a special freeProc to test how well * Tcl_DStringGetResult handles freeProc's other than free. */ static void SpecialFree( #if TCL_MAJOR_VERSION > 8 void *blockPtr /* Block to free. */ #else char *blockPtr /* Block to free. */ #endif ) { Tcl_Free(((char *)blockPtr) - 16); } /* *------------------------------------------------------------------------ * * UtfTransformFn -- * * Implements a direct call into Tcl_UtfToExternal and Tcl_ExternalToUtf * as otherwise there is no script level command that directly exercises * these functions (i/o command cannot test all combinations) * The arguments at the script level are roughly those of the above * functions: * encodingname srcbytes flags state dstlen ?srcreadvar? ?dstwrotevar? ?dstcharsvar? * * Results: * TCL_OK or TCL_ERROR. This indicates any errors running the test, NOT the * result of Tcl_UtfToExternal or Tcl_ExternalToUtf. * * Side effects: * * The result in the interpreter is a list of the return code from the * Tcl_UtfToExternal/Tcl_ExternalToUtf functions, the encoding state, and * an encoded binary string of length dstLen. Note the string is the * entire output buffer, not just the part containing the decoded * portion. This allows for additional checks at test script level. * * If any of the srcreadvar, dstwrotevar and dstcharsvar are specified and * not empty, they are treated as names of variables where the *srcRead, * *dstWrote and *dstChars output from the functions are stored. * * The function also checks internally whether nuls are correctly * appended as requested but the TCL_ENCODING_NO_TERMINATE flag * and that no buffer overflows occur. *------------------------------------------------------------------------ */ typedef int UtfTransformFn(Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, Tcl_Size dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr); static int UtfExtWrapper( Tcl_Interp *interp, UtfTransformFn *transformer, int objc, Tcl_Obj *const objv[]) { Tcl_Encoding encoding; Tcl_EncodingState encState, *encStatePtr; Tcl_Size srcLen, bufLen; const unsigned char *bytes; unsigned char *bufPtr; int srcRead, dstLen, dstWrote, dstChars; Tcl_Obj *srcReadVar, *dstWroteVar, *dstCharsVar; int result; int flags; Tcl_Obj **flagObjs; Tcl_Size nflags; static const struct { const char *flagKey; int flag; } flagMap[] = { {"start", TCL_ENCODING_START}, {"end", TCL_ENCODING_END}, {"noterminate", TCL_ENCODING_NO_TERMINATE}, {"charlimit", TCL_ENCODING_CHAR_LIMIT}, {"tcl8", TCL_ENCODING_PROFILE_TCL8}, {"strict", TCL_ENCODING_PROFILE_STRICT}, {"replace", TCL_ENCODING_PROFILE_REPLACE}, {NULL, 0} }; Tcl_Size i; Tcl_WideInt wide; if (objc < 7 || objc > 10) { Tcl_WrongNumArgs(interp, 2, objv, "encoding srcbytes flags state dstlen ?srcreadvar? ?dstwrotevar? ?dstcharsvar?"); return TCL_ERROR; } if (Tcl_GetEncodingFromObj(interp, objv[2], &encoding) != TCL_OK) { return TCL_ERROR; } /* Flags may be specified as list of integers and keywords */ flags = 0; if (Tcl_ListObjGetElements(interp, objv[4], &nflags, &flagObjs) != TCL_OK) { return TCL_ERROR; } for (i = 0; i < nflags; ++i) { int flag; if (Tcl_GetIntFromObj(NULL, flagObjs[i], &flag) == TCL_OK) { flags |= flag; } else { int idx; if (Tcl_GetIndexFromObjStruct(interp, flagObjs[i], flagMap, sizeof(flagMap[0]), "flag", 0, &idx) != TCL_OK) { return TCL_ERROR; } flags |= flagMap[idx].flag; } } /* Assumes state is integer if not "" */ if (Tcl_GetWideIntFromObj(interp, objv[5], &wide) == TCL_OK) { encState = (Tcl_EncodingState)(size_t)wide; encStatePtr = &encState; } else if (Tcl_GetCharLength(objv[5]) == 0) { encStatePtr = NULL; } else { return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[6], &dstLen) != TCL_OK) { return TCL_ERROR; } srcReadVar = NULL; dstWroteVar = NULL; dstCharsVar = NULL; if (objc > 7) { /* Has caller requested srcRead? */ if (Tcl_GetCharLength(objv[7])) { srcReadVar = objv[7]; } if (objc > 8) { /* Ditto for dstWrote */ if (Tcl_GetCharLength(objv[8])) { dstWroteVar = objv[8]; } if (objc > 9) { if (Tcl_GetCharLength(objv[9])) { dstCharsVar = objv[9]; } } } } if (flags & TCL_ENCODING_CHAR_LIMIT) { /* Caller should have specified the dest char limit */ Tcl_Obj *valueObj; if (dstCharsVar == NULL || (valueObj = Tcl_ObjGetVar2(interp, dstCharsVar, NULL, 0)) == NULL ) { Tcl_SetResult(interp, "dstCharsVar must be specified with integer value if " "TCL_ENCODING_CHAR_LIMIT set in flags.", TCL_STATIC); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, valueObj, &dstChars) != TCL_OK) { return TCL_ERROR; } } else { dstChars = 0; /* Only used for output */ } bufLen = dstLen + 4; /* 4 -> overflow detection */ bufPtr = (unsigned char *) Tcl_Alloc(bufLen); memset(bufPtr, 0xFF, dstLen); /* Need to check nul terminator */ memmove(bufPtr + dstLen, "\xAB\xCD\xEF\xAB", 4); /* overflow detection */ bytes = Tcl_GetByteArrayFromObj(objv[3], &srcLen); /* Last! to avoid shimmering */ result = (*transformer)(interp, encoding, (const char *)bytes, srcLen, flags, encStatePtr, (char *)bufPtr, dstLen, srcReadVar ? &srcRead : NULL, &dstWrote, dstCharsVar ? &dstChars : NULL); if (memcmp(bufPtr + bufLen - 4, "\xAB\xCD\xEF\xAB", 4)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("%s wrote past output buffer", transformer == Tcl_ExternalToUtf ? "Tcl_ExternalToUtf" : "Tcl_UtfToExternal")); result = TCL_ERROR; } else if (result != TCL_ERROR) { Tcl_Obj *resultObjs[3]; switch (result) { case TCL_OK: resultObjs[0] = Tcl_NewStringObj("ok", TCL_INDEX_NONE); break; case TCL_CONVERT_MULTIBYTE: resultObjs[0] = Tcl_NewStringObj("multibyte", TCL_INDEX_NONE); break; case TCL_CONVERT_SYNTAX: resultObjs[0] = Tcl_NewStringObj("syntax", TCL_INDEX_NONE); break; case TCL_CONVERT_UNKNOWN: resultObjs[0] = Tcl_NewStringObj("unknown", TCL_INDEX_NONE); break; case TCL_CONVERT_NOSPACE: resultObjs[0] = Tcl_NewStringObj("nospace", TCL_INDEX_NONE); break; default: resultObjs[0] = Tcl_NewIntObj(result); break; } result = TCL_OK; resultObjs[1] = encStatePtr ? Tcl_NewWideIntObj((Tcl_WideInt)(size_t)encState) : Tcl_NewObj(); resultObjs[2] = Tcl_NewByteArrayObj(bufPtr, dstLen); if (srcReadVar) { if (Tcl_ObjSetVar2(interp, srcReadVar, NULL, Tcl_NewIntObj(srcRead), TCL_LEAVE_ERR_MSG) == NULL) { result = TCL_ERROR; } } if (dstWroteVar) { if (Tcl_ObjSetVar2(interp, dstWroteVar, NULL, Tcl_NewIntObj(dstWrote), TCL_LEAVE_ERR_MSG) == NULL) { result = TCL_ERROR; } } if (dstCharsVar) { if (Tcl_ObjSetVar2(interp, dstCharsVar, NULL, Tcl_NewIntObj(dstChars), TCL_LEAVE_ERR_MSG) == NULL) { result = TCL_ERROR; } } Tcl_SetObjResult(interp, Tcl_NewListObj(3, resultObjs)); } Tcl_Free(bufPtr); Tcl_FreeEncoding(encoding); /* Free returned reference */ return result; } /* *---------------------------------------------------------------------- * * TestencodingCmd -- * * This procedure implements the "testencoding" command. It is used * to test the encoding package. * * Results: * A standard Tcl result. * * Side effects: * Load encodings. * *---------------------------------------------------------------------- */ static int TestencodingCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Encoding encoding; Tcl_Size length; const char *string; TclEncoding *encodingPtr; static const char *const optionStrings[] = { "create", "delete", "nullength", "Tcl_ExternalToUtf", "Tcl_UtfToExternal", NULL }; enum options { ENC_CREATE, ENC_DELETE, ENC_NULLENGTH, ENC_EXTTOUTF, ENC_UTFTOEXT } index; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "command ?args?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case ENC_CREATE: { Tcl_EncodingType type; if (objc != 5) { Tcl_WrongNumArgs(interp, 2, objv, "name toutfcmd fromutfcmd"); return TCL_ERROR; } encodingPtr = (TclEncoding *)Tcl_Alloc(sizeof(TclEncoding)); encodingPtr->interp = interp; string = Tcl_GetStringFromObj(objv[3], &length); encodingPtr->toUtfCmd = (char *)Tcl_Alloc(length + 1); memcpy(encodingPtr->toUtfCmd, string, length + 1); string = Tcl_GetStringFromObj(objv[4], &length); encodingPtr->fromUtfCmd = (char *)Tcl_Alloc(length + 1); memcpy(encodingPtr->fromUtfCmd, string, length + 1); string = Tcl_GetStringFromObj(objv[2], &length); type.encodingName = string; type.toUtfProc = EncodingToUtfProc; type.fromUtfProc = EncodingFromUtfProc; type.freeProc = EncodingFreeProc; type.clientData = encodingPtr; type.nullSize = 1; Tcl_CreateEncoding(&type); break; } case ENC_DELETE: if (objc != 3) { return TCL_ERROR; } if (TCL_OK != Tcl_GetEncodingFromObj(interp, objv[2], &encoding)) { return TCL_ERROR; } Tcl_FreeEncoding(encoding); /* Free returned reference */ Tcl_FreeEncoding(encoding); /* Free to match CREATE */ TclFreeInternalRep(objv[2]); /* Free the cached ref */ break; case ENC_NULLENGTH: if (objc > 3) { Tcl_WrongNumArgs(interp, 2, objv, "?encoding?"); return TCL_ERROR; } encoding = Tcl_GetEncoding(interp, objc == 2 ? NULL : Tcl_GetString(objv[2])); if (encoding == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewIntObj(Tcl_GetEncodingNulLength(encoding))); Tcl_FreeEncoding(encoding); break; case ENC_EXTTOUTF: return UtfExtWrapper(interp,Tcl_ExternalToUtf,objc,objv); case ENC_UTFTOEXT: return UtfExtWrapper(interp,Tcl_UtfToExternal,objc,objv); } return TCL_OK; } static int EncodingToUtfProc( void *clientData, /* TclEncoding structure. */ TCL_UNUSED(const char *) /*src*/, int srcLen, /* Source string length in bytes. */ TCL_UNUSED(int) /*flags*/, TCL_UNUSED(Tcl_EncodingState *), char *dst, /* Output buffer. */ int dstLen, /* The maximum length of output buffer. */ int *srcReadPtr, /* Filled with number of bytes read. */ int *dstWrotePtr, /* Filled with number of bytes stored. */ int *dstCharsPtr) /* Filled with number of chars stored. */ { int len; TclEncoding *encodingPtr; encodingPtr = (TclEncoding *) clientData; Tcl_EvalEx(encodingPtr->interp, encodingPtr->toUtfCmd, TCL_INDEX_NONE, TCL_EVAL_GLOBAL); len = strlen(Tcl_GetStringResult(encodingPtr->interp)); if (len > dstLen) { len = dstLen; } memcpy(dst, Tcl_GetStringResult(encodingPtr->interp), len); Tcl_ResetResult(encodingPtr->interp); *srcReadPtr = srcLen; *dstWrotePtr = len; *dstCharsPtr = len; return TCL_OK; } static int EncodingFromUtfProc( void *clientData, /* TclEncoding structure. */ TCL_UNUSED(const char *) /*src*/, int srcLen, /* Source string length in bytes. */ TCL_UNUSED(int) /*flags*/, TCL_UNUSED(Tcl_EncodingState *), char *dst, /* Output buffer. */ int dstLen, /* The maximum length of output buffer. */ int *srcReadPtr, /* Filled with number of bytes read. */ int *dstWrotePtr, /* Filled with number of bytes stored. */ int *dstCharsPtr) /* Filled with number of chars stored. */ { int len; TclEncoding *encodingPtr; encodingPtr = (TclEncoding *) clientData; Tcl_EvalEx(encodingPtr->interp, encodingPtr->fromUtfCmd, TCL_INDEX_NONE, TCL_EVAL_GLOBAL); len = strlen(Tcl_GetStringResult(encodingPtr->interp)); if (len > dstLen) { len = dstLen; } memcpy(dst, Tcl_GetStringResult(encodingPtr->interp), len); Tcl_ResetResult(encodingPtr->interp); *srcReadPtr = srcLen; *dstWrotePtr = len; *dstCharsPtr = len; return TCL_OK; } static void EncodingFreeProc( void *clientData) /* ClientData associated with type. */ { TclEncoding *encodingPtr = (TclEncoding *)clientData; Tcl_Free(encodingPtr->toUtfCmd); Tcl_Free(encodingPtr->fromUtfCmd); Tcl_Free(encodingPtr); } /* *---------------------------------------------------------------------- * * TestevalexCmd -- * * This procedure implements the "testevalex" command. It is * used to test Tcl_EvalEx. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestevalexCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int flags; Tcl_Size length; const char *script; flags = 0; if (objc == 3) { const char *global = Tcl_GetString(objv[2]); if (strcmp(global, "global") != 0) { Tcl_AppendResult(interp, "bad value \"", global, "\": must be global", (char *)NULL); return TCL_ERROR; } flags = TCL_EVAL_GLOBAL; } else if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "script ?global?"); return TCL_ERROR; } script = Tcl_GetStringFromObj(objv[1], &length); return Tcl_EvalEx(interp, script, length, flags); } /* *---------------------------------------------------------------------- * * TestevalobjvCmd -- * * This procedure implements the "testevalobjv" command. It is * used to test Tcl_EvalObjv. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestevalobjvCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int evalGlobal; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "global word ?word ...?"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[1], &evalGlobal) != TCL_OK) { return TCL_ERROR; } return Tcl_EvalObjv(interp, objc-2, objv+2, (evalGlobal) ? TCL_EVAL_GLOBAL : 0); } /* *---------------------------------------------------------------------- * * TesteventCmd -- * * This procedure implements a 'testevent' command. The command * is used to test event queue management. * * The command takes two forms: * - testevent queue name position script * Queues an event at the given position in the queue, and * associates a given name with it (the same name may be * associated with multiple events). When the event comes * to the head of the queue, executes the given script at * global level in the current interp. The position may be * one of 'head', 'tail' or 'mark'. * - testevent delete name * Deletes any events associated with the given name from * the queue. * * Return value: * Returns a standard Tcl result. * * Side effects: * Manipulates the event queue as directed. * *---------------------------------------------------------------------- */ static int TesteventCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const objv[]) /* Parameter vector */ { static const char *const subcommands[] = { /* Possible subcommands */ "queue", "delete", NULL }; int subCmdIndex; /* Index of the chosen subcommand */ static const char *const positions[] = { /* Possible queue positions */ "head", "tail", "mark", NULL }; int posIndex; /* Index of the chosen position */ static const int posNum[] = { /* Interpretation of the chosen position */ TCL_QUEUE_HEAD, TCL_QUEUE_TAIL, TCL_QUEUE_MARK }; TestEvent *ev; /* Event to be queued */ if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], subcommands, "subcommand", TCL_EXACT, &subCmdIndex) != TCL_OK) { return TCL_ERROR; } switch (subCmdIndex) { case 0: /* queue */ if (objc != 5) { Tcl_WrongNumArgs(interp, 2, objv, "name position script"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[3], positions, "position specifier", TCL_EXACT, &posIndex) != TCL_OK) { return TCL_ERROR; } ev = (TestEvent *)Tcl_Alloc(sizeof(TestEvent)); ev->header.proc = TesteventProc; ev->header.nextPtr = NULL; ev->interp = interp; ev->command = objv[4]; Tcl_IncrRefCount(ev->command); ev->tag = objv[2]; Tcl_IncrRefCount(ev->tag); Tcl_QueueEvent((Tcl_Event *) ev, posNum[posIndex]); break; case 1: /* delete */ if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "name"); return TCL_ERROR; } Tcl_DeleteEvents(TesteventDeleteProc, objv[2]); break; } return TCL_OK; } /* *---------------------------------------------------------------------- * * TesteventProc -- * * Delivers a test event to the Tcl interpreter as part of event * queue testing. * * Results: * Returns 1 if the event has been serviced, 0 otherwise. * * Side effects: * Evaluates the event's callback script, so has whatever side effects * the callback has. The return value of the callback script becomes the * return value of this function. If the callback script reports an * error, it is reported as a background error. * *---------------------------------------------------------------------- */ static int TesteventProc( Tcl_Event *event, /* Event to deliver */ TCL_UNUSED(int) /*flags*/) { TestEvent *ev = (TestEvent *) event; Tcl_Interp *interp = ev->interp; Tcl_Obj *command = ev->command; int result = Tcl_EvalObjEx(interp, command, TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT); int retval; if (result != TCL_OK) { Tcl_AddErrorInfo(interp, " (command bound to \"testevent\" callback)"); Tcl_BackgroundException(interp, TCL_ERROR); return 1; /* Avoid looping on errors */ } if (Tcl_GetBooleanFromObj(interp, Tcl_GetObjResult(interp), &retval) != TCL_OK) { Tcl_AddErrorInfo(interp, " (return value from \"testevent\" callback)"); Tcl_BackgroundException(interp, TCL_ERROR); return 1; } if (retval) { Tcl_DecrRefCount(ev->tag); Tcl_DecrRefCount(ev->command); } return retval; } /* *---------------------------------------------------------------------- * * TesteventDeleteProc -- * * Removes some set of events from the queue. * * This procedure is used as part of testing event queue management. * * Results: * Returns 1 if a given event should be deleted, 0 otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TesteventDeleteProc( Tcl_Event *event, /* Event to examine */ void *clientData) /* Tcl_Obj containing the name of the event(s) * to remove */ { TestEvent *ev; /* Event to examine */ const char *evNameStr; Tcl_Obj *targetName; /* Name of the event(s) to delete */ const char *targetNameStr; if (event->proc != TesteventProc) { return 0; } targetName = (Tcl_Obj *) clientData; targetNameStr = (char *)Tcl_GetString(targetName); ev = (TestEvent *) event; evNameStr = Tcl_GetString(ev->tag); if (strcmp(evNameStr, targetNameStr) == 0) { Tcl_DecrRefCount(ev->tag); Tcl_DecrRefCount(ev->command); return 1; } else { return 0; } } /* *---------------------------------------------------------------------- * * TestexithandlerCmd -- * * This procedure implements the "testexithandler" command. It is * used to test Tcl_CreateExitHandler and Tcl_DeleteExitHandler. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestexithandlerCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { int value; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "create|delete value"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[2], &value) != TCL_OK) { return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[1]), "create") == 0) { Tcl_CreateExitHandler((value & 1) ? ExitProcOdd : ExitProcEven, INT2PTR(value)); } else if (strcmp(Tcl_GetString(objv[1]), "delete") == 0) { Tcl_DeleteExitHandler((value & 1) ? ExitProcOdd : ExitProcEven, INT2PTR(value)); } else { Tcl_AppendResult(interp, "bad option \"", Tcl_GetString(objv[1]), "\": must be create or delete", (char *)NULL); return TCL_ERROR; } return TCL_OK; } static void ExitProcOdd( void *clientData) /* Integer value to print. */ { char buf[16 + TCL_INTEGER_SPACE]; int len; snprintf(buf, sizeof(buf), "odd %d\n", (int)PTR2INT(clientData)); len = strlen(buf); if (len != (int) write(1, buf, len)) { Tcl_Panic("ExitProcOdd: unable to write to stdout"); } } static void ExitProcEven( void *clientData) /* Integer value to print. */ { char buf[16 + TCL_INTEGER_SPACE]; int len; snprintf(buf, sizeof(buf), "even %d\n", (int)PTR2INT(clientData)); len = strlen(buf); if (len != (int) write(1, buf, len)) { Tcl_Panic("ExitProcEven: unable to write to stdout"); } } /* *---------------------------------------------------------------------- * * TestexprlongCmd -- * * This procedure verifies that Tcl_ExprLong does not modify the * interpreter result if there is no error. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestexprlongCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { long exprResult; char buf[4 + TCL_INTEGER_SPACE]; int result; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "expression"); return TCL_ERROR; } Tcl_AppendResult(interp, "This is a result", (char *)NULL); result = Tcl_ExprLong(interp, Tcl_GetString(objv[1]), &exprResult); if (result != TCL_OK) { return result; } snprintf(buf, sizeof(buf), ": %ld", exprResult); Tcl_AppendResult(interp, buf, (char *)NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestexprlongobjCmd -- * * This procedure verifies that Tcl_ExprLongObj does not modify the * interpreter result if there is no error. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestexprlongobjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Argument objects. */ { long exprResult; char buf[4 + TCL_INTEGER_SPACE]; int result; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "expression"); return TCL_ERROR; } Tcl_AppendResult(interp, "This is a result", (char *)NULL); result = Tcl_ExprLongObj(interp, objv[1], &exprResult); if (result != TCL_OK) { return result; } snprintf(buf, sizeof(buf), ": %ld", exprResult); Tcl_AppendResult(interp, buf, (char *)NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestexprdoubleCmd -- * * This procedure verifies that Tcl_ExprDouble does not modify the * interpreter result if there is no error. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestexprdoubleCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { double exprResult; char buf[4 + TCL_DOUBLE_SPACE]; int result; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "expression"); return TCL_ERROR; } Tcl_AppendResult(interp, "This is a result", (char *)NULL); result = Tcl_ExprDouble(interp, Tcl_GetString(objv[1]), &exprResult); if (result != TCL_OK) { return result; } strcpy(buf, ": "); Tcl_PrintDouble(interp, exprResult, buf+2); Tcl_AppendResult(interp, buf, (char *)NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestexprdoubleobjCmd -- * * This procedure verifies that Tcl_ExprLongObj does not modify the * interpreter result if there is no error. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestexprdoubleobjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Argument objects. */ { double exprResult; char buf[4 + TCL_DOUBLE_SPACE]; int result; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "expression"); return TCL_ERROR; } Tcl_AppendResult(interp, "This is a result", (char *)NULL); result = Tcl_ExprDoubleObj(interp, objv[1], &exprResult); if (result != TCL_OK) { return result; } strcpy(buf, ": "); Tcl_PrintDouble(interp, exprResult, buf+2); Tcl_AppendResult(interp, buf, (char *)NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestexprstringCmd -- * * This procedure tests the basic operation of Tcl_ExprString. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestexprstringCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Argument strings. */ { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "expression"); return TCL_ERROR; } return Tcl_ExprString(interp, Tcl_GetString(objv[1])); } /* *---------------------------------------------------------------------- * * TestfilelinkCmd -- * * This procedure implements the "testfilelink" command. It is used to * test the effects of creating and manipulating filesystem links in Tcl. * * Results: * A standard Tcl result. * * Side effects: * May create a link on disk. * *---------------------------------------------------------------------- */ static int TestfilelinkCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { Tcl_Obj *contents; if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "source ?target?"); return TCL_ERROR; } if (Tcl_FSConvertToPathType(interp, objv[1]) != TCL_OK) { return TCL_ERROR; } if (objc == 3) { /* Create link from source to target */ contents = Tcl_FSLink(objv[1], objv[2], TCL_CREATE_SYMBOLIC_LINK|TCL_CREATE_HARD_LINK); if (contents == NULL) { Tcl_AppendResult(interp, "could not create link from \"", Tcl_GetString(objv[1]), "\" to \"", Tcl_GetString(objv[2]), "\": ", Tcl_PosixError(interp), (char *)NULL); return TCL_ERROR; } } else { /* Read link */ contents = Tcl_FSLink(objv[1], NULL, 0); if (contents == NULL) { Tcl_AppendResult(interp, "could not read link \"", Tcl_GetString(objv[1]), "\": ", Tcl_PosixError(interp), (char *)NULL); return TCL_ERROR; } } Tcl_SetObjResult(interp, contents); if (objc == 2) { /* * If we are creating a link, this will actually just * be objv[3], and we don't own it */ Tcl_DecrRefCount(contents); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TestgetassocdataCmd -- * * This procedure implements the "testgetassocdata" command. It is * used to test Tcl_GetAssocData. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestgetassocdataCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { char *res; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "data_key"); return TCL_ERROR; } res = (char *)Tcl_GetAssocData(interp, Tcl_GetString(objv[1]), NULL); if (res != NULL) { Tcl_AppendResult(interp, res, (char *)NULL); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TestgetplatformCmd -- * * This procedure implements the "testgetplatform" command. It is * used to retrieve the value of the tclPlatform global variable. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestgetplatformCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { static const char *const platformStrings[] = { "unix", "mac", "windows" }; TclPlatformType *platform; platform = TclGetPlatform(); if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } Tcl_AppendResult(interp, platformStrings[*platform], (char *)NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestinterpdeleteCmd -- * * This procedure tests the code in tclInterp.c that deals with * interpreter deletion. It deletes a user-specified interpreter * from the hierarchy, and subsequent code checks integrity. * * Results: * A standard Tcl result. * * Side effects: * Deletes one or more interpreters. * *---------------------------------------------------------------------- */ static int TestinterpdeleteCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { Tcl_Interp *childToDelete; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "path"); return TCL_ERROR; } childToDelete = Tcl_GetChild(interp, Tcl_GetString(objv[1])); if (childToDelete == NULL) { return TCL_ERROR; } Tcl_DeleteInterp(childToDelete); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestlinkCmd -- * * This procedure implements the "testlink" command. It is used * to test Tcl_LinkVar and related library procedures. * * Results: * A standard Tcl result. * * Side effects: * Creates and deletes various variable links, plus returns * values of the linked variables. * *---------------------------------------------------------------------- */ static int TestlinkCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { static int intVar = 43; static int boolVar = 4; static double realVar = 1.23; static Tcl_WideInt wideVar = 79; static char *stringVar = NULL; static char charVar = '@'; static unsigned char ucharVar = 130; static short shortVar = 3000; static unsigned short ushortVar = 60000; static unsigned int uintVar = 0xBEEFFEED; static long longVar = 123456789L; static unsigned long ulongVar = 3456789012UL; static float floatVar = 4.5; static Tcl_WideUInt uwideVar = 123; static int created = 0; char buffer[2*TCL_DOUBLE_SPACE]; int writable, flag; Tcl_Obj *tmp; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg arg arg arg arg arg arg arg arg arg arg" " arg arg?"); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[1]), "create") == 0) { if (objc != 16) { Tcl_WrongNumArgs(interp, 2, objv, "intRO realRO boolRO stringRO wideRO charRO ucharRO shortRO" " ushortRO uintRO longRO ulongRO floatRO uwideRO"); return TCL_ERROR; } if (created) { Tcl_UnlinkVar(interp, "int"); Tcl_UnlinkVar(interp, "real"); Tcl_UnlinkVar(interp, "bool"); Tcl_UnlinkVar(interp, "string"); Tcl_UnlinkVar(interp, "wide"); Tcl_UnlinkVar(interp, "char"); Tcl_UnlinkVar(interp, "uchar"); Tcl_UnlinkVar(interp, "short"); Tcl_UnlinkVar(interp, "ushort"); Tcl_UnlinkVar(interp, "uint"); Tcl_UnlinkVar(interp, "long"); Tcl_UnlinkVar(interp, "ulong"); Tcl_UnlinkVar(interp, "float"); Tcl_UnlinkVar(interp, "uwide"); } created = 1; if (Tcl_GetBooleanFromObj(interp, objv[2], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "int", &intVar, TCL_LINK_INT | flag) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[3], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "real", &realVar, TCL_LINK_DOUBLE | flag) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[4], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "bool", &boolVar, TCL_LINK_BOOLEAN | flag) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[5], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "string", &stringVar, TCL_LINK_STRING | flag) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[6], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "wide", &wideVar, TCL_LINK_WIDE_INT | flag) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[7], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "char", &charVar, TCL_LINK_CHAR | flag) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[8], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "uchar", &ucharVar, TCL_LINK_UCHAR | flag) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[9], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "short", &shortVar, TCL_LINK_SHORT | flag) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[10], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "ushort", &ushortVar, TCL_LINK_USHORT | flag) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[11], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "uint", &uintVar, TCL_LINK_UINT | flag) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[12], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "long", &longVar, TCL_LINK_LONG | flag) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[13], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "ulong", &ulongVar, TCL_LINK_ULONG | flag) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[14], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "float", &floatVar, TCL_LINK_FLOAT | flag) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[15], &writable) != TCL_OK) { return TCL_ERROR; } flag = writable ? 0 : TCL_LINK_READ_ONLY; if (Tcl_LinkVar(interp, "uwide", &uwideVar, TCL_LINK_WIDE_UINT | flag) != TCL_OK) { return TCL_ERROR; } } else if (strcmp(Tcl_GetString(objv[1]), "delete") == 0) { Tcl_UnlinkVar(interp, "int"); Tcl_UnlinkVar(interp, "real"); Tcl_UnlinkVar(interp, "bool"); Tcl_UnlinkVar(interp, "string"); Tcl_UnlinkVar(interp, "wide"); Tcl_UnlinkVar(interp, "char"); Tcl_UnlinkVar(interp, "uchar"); Tcl_UnlinkVar(interp, "short"); Tcl_UnlinkVar(interp, "ushort"); Tcl_UnlinkVar(interp, "uint"); Tcl_UnlinkVar(interp, "long"); Tcl_UnlinkVar(interp, "ulong"); Tcl_UnlinkVar(interp, "float"); Tcl_UnlinkVar(interp, "uwide"); created = 0; } else if (strcmp(Tcl_GetString(objv[1]), "get") == 0) { TclFormatInt(buffer, intVar); Tcl_AppendElement(interp, buffer); Tcl_PrintDouble(NULL, realVar, buffer); Tcl_AppendElement(interp, buffer); TclFormatInt(buffer, boolVar); Tcl_AppendElement(interp, buffer); Tcl_AppendElement(interp, (stringVar == NULL) ? "-" : stringVar); /* * Wide ints only have an object-based interface. */ tmp = Tcl_NewWideIntObj(wideVar); Tcl_AppendElement(interp, Tcl_GetString(tmp)); Tcl_DecrRefCount(tmp); TclFormatInt(buffer, (int) charVar); Tcl_AppendElement(interp, buffer); TclFormatInt(buffer, (int) ucharVar); Tcl_AppendElement(interp, buffer); TclFormatInt(buffer, (int) shortVar); Tcl_AppendElement(interp, buffer); TclFormatInt(buffer, (int) ushortVar); Tcl_AppendElement(interp, buffer); TclFormatInt(buffer, (int) uintVar); Tcl_AppendElement(interp, buffer); tmp = Tcl_NewWideIntObj(longVar); Tcl_AppendElement(interp, Tcl_GetString(tmp)); Tcl_DecrRefCount(tmp); tmp = Tcl_NewWideUIntObj(ulongVar); Tcl_AppendElement(interp, Tcl_GetString(tmp)); Tcl_DecrRefCount(tmp); Tcl_PrintDouble(NULL, (double)floatVar, buffer); Tcl_AppendElement(interp, buffer); tmp = Tcl_NewWideUIntObj(uwideVar); Tcl_AppendElement(interp, Tcl_GetString(tmp)); Tcl_DecrRefCount(tmp); } else if (strcmp(Tcl_GetString(objv[1]), "set") == 0) { int v; if (objc != 16) { Tcl_WrongNumArgs(interp, 2, objv, "intValue realValue boolValue stringValue wideValue" " charValue ucharValue shortValue ushortValue uintValue" " longValue ulongValue floatValue uwideValue"); return TCL_ERROR; } if (Tcl_GetString(objv[2])[0] != 0) { if (Tcl_GetIntFromObj(interp, objv[2], &intVar) != TCL_OK) { return TCL_ERROR; } } if (Tcl_GetString(objv[3])[0] != 0) { if (Tcl_GetDoubleFromObj(interp, objv[3], &realVar) != TCL_OK) { return TCL_ERROR; } } if (Tcl_GetString(objv[4])[0] != 0) { if (Tcl_GetBooleanFromObj(interp, objv[4], &boolVar) != TCL_OK) { return TCL_ERROR; } } if (Tcl_GetString(objv[5])[0] != 0) { if (stringVar != NULL) { Tcl_Free(stringVar); } if (strcmp(Tcl_GetString(objv[5]), "-") == 0) { stringVar = NULL; } else { stringVar = (char *)Tcl_Alloc(strlen(Tcl_GetString(objv[5])) + 1); strcpy(stringVar, Tcl_GetString(objv[5])); } } if (Tcl_GetString(objv[6])[0] != 0) { tmp = Tcl_NewStringObj(Tcl_GetString(objv[6]), -1); if (Tcl_GetWideIntFromObj(interp, tmp, &wideVar) != TCL_OK) { Tcl_DecrRefCount(tmp); return TCL_ERROR; } Tcl_DecrRefCount(tmp); } if (Tcl_GetString(objv[7])[0]) { if (Tcl_GetIntFromObj(interp, objv[7], &v) != TCL_OK) { return TCL_ERROR; } charVar = (char) v; } if (Tcl_GetString(objv[8])[0]) { if (Tcl_GetIntFromObj(interp, objv[8], &v) != TCL_OK) { return TCL_ERROR; } ucharVar = (unsigned char) v; } if (Tcl_GetString(objv[9])[0]) { if (Tcl_GetIntFromObj(interp, objv[9], &v) != TCL_OK) { return TCL_ERROR; } shortVar = (short) v; } if (Tcl_GetString(objv[10])[0]) { if (Tcl_GetIntFromObj(interp, objv[10], &v) != TCL_OK) { return TCL_ERROR; } ushortVar = (unsigned short) v; } if (Tcl_GetString(objv[11])[0]) { if (Tcl_GetIntFromObj(interp, objv[11], &v) != TCL_OK) { return TCL_ERROR; } uintVar = (unsigned int) v; } if (Tcl_GetString(objv[12])[0]) { if (Tcl_GetIntFromObj(interp, objv[12], &v) != TCL_OK) { return TCL_ERROR; } longVar = (long) v; } if (Tcl_GetString(objv[13])[0]) { if (Tcl_GetIntFromObj(interp, objv[13], &v) != TCL_OK) { return TCL_ERROR; } ulongVar = (unsigned long) v; } if (Tcl_GetString(objv[14])[0]) { double d; if (Tcl_GetDoubleFromObj(interp, objv[14], &d) != TCL_OK) { return TCL_ERROR; } floatVar = (float) d; } if (Tcl_GetString(objv[15])[0]) { Tcl_WideInt w; tmp = Tcl_NewStringObj(Tcl_GetString(objv[15]), -1); if (Tcl_GetWideIntFromObj(interp, tmp, &w) != TCL_OK) { Tcl_DecrRefCount(tmp); return TCL_ERROR; } Tcl_DecrRefCount(tmp); uwideVar = (Tcl_WideUInt)w; } } else if (strcmp(Tcl_GetString(objv[1]), "update") == 0) { int v; if (objc != 16) { Tcl_WrongNumArgs(interp, 2, objv, "intValue realValue boolValue stringValue wideValue" " charValue ucharValue shortValue ushortValue uintValue" " longValue ulongValue floatValue uwideValue"); return TCL_ERROR; } if (Tcl_GetString(objv[2])[0] != 0) { if (Tcl_GetIntFromObj(interp, objv[2], &intVar) != TCL_OK) { return TCL_ERROR; } Tcl_UpdateLinkedVar(interp, "int"); } if (Tcl_GetString(objv[3])[0] != 0) { if (Tcl_GetDoubleFromObj(interp, objv[3], &realVar) != TCL_OK) { return TCL_ERROR; } Tcl_UpdateLinkedVar(interp, "real"); } if (Tcl_GetString(objv[4])[0] != 0) { if (Tcl_GetIntFromObj(interp, objv[4], &boolVar) != TCL_OK) { return TCL_ERROR; } Tcl_UpdateLinkedVar(interp, "bool"); } if (Tcl_GetString(objv[5])[0] != 0) { if (stringVar != NULL) { Tcl_Free(stringVar); } if (strcmp(Tcl_GetString(objv[5]), "-") == 0) { stringVar = NULL; } else { stringVar = (char *)Tcl_Alloc(strlen(Tcl_GetString(objv[5])) + 1); strcpy(stringVar, Tcl_GetString(objv[5])); } Tcl_UpdateLinkedVar(interp, "string"); } if (Tcl_GetString(objv[6])[0] != 0) { tmp = Tcl_NewStringObj(Tcl_GetString(objv[6]), -1); if (Tcl_GetWideIntFromObj(interp, tmp, &wideVar) != TCL_OK) { Tcl_DecrRefCount(tmp); return TCL_ERROR; } Tcl_DecrRefCount(tmp); Tcl_UpdateLinkedVar(interp, "wide"); } if (Tcl_GetString(objv[7])[0]) { if (Tcl_GetIntFromObj(interp, objv[7], &v) != TCL_OK) { return TCL_ERROR; } charVar = (char) v; Tcl_UpdateLinkedVar(interp, "char"); } if (Tcl_GetString(objv[8])[0]) { if (Tcl_GetIntFromObj(interp, objv[8], &v) != TCL_OK) { return TCL_ERROR; } ucharVar = (unsigned char) v; Tcl_UpdateLinkedVar(interp, "uchar"); } if (Tcl_GetString(objv[9])[0]) { if (Tcl_GetIntFromObj(interp, objv[9], &v) != TCL_OK) { return TCL_ERROR; } shortVar = (short) v; Tcl_UpdateLinkedVar(interp, "short"); } if (Tcl_GetString(objv[10])[0]) { if (Tcl_GetIntFromObj(interp, objv[10], &v) != TCL_OK) { return TCL_ERROR; } ushortVar = (unsigned short) v; Tcl_UpdateLinkedVar(interp, "ushort"); } if (Tcl_GetString(objv[11])[0]) { if (Tcl_GetIntFromObj(interp, objv[11], &v) != TCL_OK) { return TCL_ERROR; } uintVar = (unsigned int) v; Tcl_UpdateLinkedVar(interp, "uint"); } if (Tcl_GetString(objv[12])[0]) { if (Tcl_GetIntFromObj(interp, objv[12], &v) != TCL_OK) { return TCL_ERROR; } longVar = (long) v; Tcl_UpdateLinkedVar(interp, "long"); } if (Tcl_GetString(objv[13])[0]) { if (Tcl_GetIntFromObj(interp, objv[13], &v) != TCL_OK) { return TCL_ERROR; } ulongVar = (unsigned long) v; Tcl_UpdateLinkedVar(interp, "ulong"); } if (Tcl_GetString(objv[14])[0]) { double d; if (Tcl_GetDoubleFromObj(interp, objv[14], &d) != TCL_OK) { return TCL_ERROR; } floatVar = (float) d; Tcl_UpdateLinkedVar(interp, "float"); } if (Tcl_GetString(objv[15])[0]) { Tcl_WideInt w; tmp = Tcl_NewStringObj(Tcl_GetString(objv[15]), -1); if (Tcl_GetWideIntFromObj(interp, tmp, &w) != TCL_OK) { Tcl_DecrRefCount(tmp); return TCL_ERROR; } Tcl_DecrRefCount(tmp); uwideVar = (Tcl_WideUInt)w; Tcl_UpdateLinkedVar(interp, "uwide"); } } else { Tcl_AppendResult(interp, "bad option \"", Tcl_GetString(objv[1]), "\": should be create, delete, get, set, or update", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * TestlinkarrayCmd -- * * This function is invoked to process the "testlinkarray" Tcl command. * It is used to test the 'Tcl_LinkArray' function. * * Results: * A standard Tcl result. * * Side effects: * Creates, deletes, and invokes variable links. * *---------------------------------------------------------------------- */ static int TestlinkarrayCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *LinkOption[] = { "update", "remove", "create", NULL }; enum LinkOptionEnum { LINK_UPDATE, LINK_REMOVE, LINK_CREATE } optionIndex; static const char *LinkType[] = { "char", "uchar", "short", "ushort", "int", "uint", "long", "ulong", "wide", "uwide", "float", "double", "string", "char*", "binary", NULL }; /* all values after TCL_LINK_CHARS_ARRAY are used as arrays (see below) */ static int LinkTypes[] = { TCL_LINK_CHAR, TCL_LINK_UCHAR, TCL_LINK_SHORT, TCL_LINK_USHORT, TCL_LINK_INT, TCL_LINK_UINT, TCL_LINK_LONG, TCL_LINK_ULONG, TCL_LINK_WIDE_INT, TCL_LINK_WIDE_UINT, TCL_LINK_FLOAT, TCL_LINK_DOUBLE, TCL_LINK_STRING, TCL_LINK_CHARS, TCL_LINK_BINARY }; int typeIndex, readonly, size; Tcl_Size i, length; char *name, *arg; Tcl_WideInt addr; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option args"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], LinkOption, "option", 0, &optionIndex) != TCL_OK) { return TCL_ERROR; } switch (optionIndex) { case LINK_UPDATE: for (i=2; i 5) { Tcl_WrongNumArgs(interp, 2, objv, "length ?leadSpace endSpace?"); return TCL_ERROR; } else { Tcl_WideUInt length; Tcl_WideUInt leadSpace = 0; Tcl_WideUInt endSpace = 0; if (Tcl_GetWideUIntFromObj(interp, objv[2], &length) != TCL_OK) { return TCL_ERROR; } if (objc > 3) { if (Tcl_GetWideUIntFromObj(interp, objv[3], &leadSpace) != TCL_OK) { return TCL_ERROR; } if (objc > 4) { if (Tcl_GetWideUIntFromObj(interp, objv[4], &endSpace) != TCL_OK) { return TCL_ERROR; } } } resultObj = TclListTestObj(length, leadSpace, endSpace); if (resultObj == NULL) { Tcl_AppendResult(interp, "List capacity exceeded", (char *)NULL); return TCL_ERROR; } } break; case LISTREP_DESCRIBE: #define APPEND_FIELD(targetObj_, structPtr_, fld_) \ do { \ Tcl_ListObjAppendElement( \ interp, (targetObj_), Tcl_NewStringObj(#fld_, -1)); \ Tcl_ListObjAppendElement( \ interp, (targetObj_), Tcl_NewWideIntObj((structPtr_)->fld_)); \ } while (0) if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "object"); return TCL_ERROR; } else { Tcl_Obj **objs; Tcl_Size nobjs; ListRep listRep; Tcl_Obj *listRepObjs[4]; /* Force list representation */ if (Tcl_ListObjGetElements(interp, objv[2], &nobjs, &objs) != TCL_OK) { return TCL_ERROR; } ListObjGetRep(objv[2], &listRep); listRepObjs[0] = Tcl_NewStringObj("store", -1); listRepObjs[1] = Tcl_NewListObj(12, NULL); Tcl_ListObjAppendElement(interp, listRepObjs[1], Tcl_NewStringObj("memoryAddress", -1)); Tcl_ListObjAppendElement(interp, listRepObjs[1], Tcl_ObjPrintf("%p", listRep.storePtr)); APPEND_FIELD(listRepObjs[1], listRep.storePtr, firstUsed); APPEND_FIELD(listRepObjs[1], listRep.storePtr, numUsed); APPEND_FIELD(listRepObjs[1], listRep.storePtr, numAllocated); APPEND_FIELD(listRepObjs[1], listRep.storePtr, refCount); APPEND_FIELD(listRepObjs[1], listRep.storePtr, flags); if (listRep.spanPtr) { listRepObjs[2] = Tcl_NewStringObj("span", -1); listRepObjs[3] = Tcl_NewListObj(8, NULL); Tcl_ListObjAppendElement(interp, listRepObjs[3], Tcl_NewStringObj("memoryAddress", -1)); Tcl_ListObjAppendElement(interp, listRepObjs[3], Tcl_ObjPrintf("%p", listRep.spanPtr)); APPEND_FIELD(listRepObjs[3], listRep.spanPtr, spanStart); APPEND_FIELD(listRepObjs[3], listRep.spanPtr, spanLength); APPEND_FIELD(listRepObjs[3], listRep.spanPtr, refCount); } resultObj = Tcl_NewListObj(listRep.spanPtr ? 4 : 2, listRepObjs); } #undef APPEND_FIELD break; case LISTREP_CONFIG: if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, "object"); return TCL_ERROR; } resultObj = Tcl_NewListObj(2, NULL); Tcl_ListObjAppendElement( NULL, resultObj, Tcl_NewStringObj("LIST_SPAN_THRESHOLD", -1)); Tcl_ListObjAppendElement( NULL, resultObj, Tcl_NewWideIntObj(LIST_SPAN_THRESHOLD)); break; case LISTREP_VALIDATE: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "object"); return TCL_ERROR; } TclListObjValidate(interp, objv[2]); /* Panics if invalid */ resultObj = Tcl_NewObj(); break; } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestlocaleCmd -- * * This procedure implements the "testlocale" command. It is used * to test the effects of setting different locales in Tcl. * * Results: * A standard Tcl result. * * Side effects: * Modifies the current C locale. * *---------------------------------------------------------------------- */ static int TestlocaleCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { int index; const char *locale; static const char *const optionStrings[] = { "ctype", "numeric", "time", "collate", "monetary", "all", NULL }; static const int lcTypes[] = { LC_CTYPE, LC_NUMERIC, LC_TIME, LC_COLLATE, LC_MONETARY, LC_ALL }; /* * LC_CTYPE, etc. correspond to the indices for the strings. */ if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "category ?locale?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } if (objc == 3) { locale = Tcl_GetString(objv[2]); } else { locale = NULL; } locale = setlocale(lcTypes[index], locale); if (locale) { Tcl_SetStringObj(Tcl_GetObjResult(interp), locale, -1); } return TCL_OK; } /* *---------------------------------------------------------------------- * * CleanupTestSetassocdataTests -- * * This function is called when an interpreter is deleted to clean * up any data left over from running the testsetassocdata command. * * Results: * None. * * Side effects: * Releases storage. * *---------------------------------------------------------------------- */ static void CleanupTestSetassocdataTests( void *clientData, /* Data to be released. */ TCL_UNUSED(Tcl_Interp *)) { Tcl_Free(clientData); } /* *---------------------------------------------------------------------- * * TestparserCmd -- * * This procedure implements the "testparser" command. It is * used for testing the new Tcl script parser in Tcl 8.1. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestparserCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { const char *script; Tcl_Size dummy; int length; Tcl_Parse parse; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "script length"); return TCL_ERROR; } script = Tcl_GetStringFromObj(objv[1], &dummy); if (Tcl_GetIntFromObj(interp, objv[2], &length)) { return TCL_ERROR; } if (length == 0) { length = dummy; } if (Tcl_ParseCommand(interp, script, length, 0, &parse) != TCL_OK) { Tcl_AddErrorInfo(interp, "\n (remainder of script: \""); Tcl_AddErrorInfo(interp, parse.term); Tcl_AddErrorInfo(interp, "\")"); return TCL_ERROR; } /* * The parse completed successfully. Just print out the contents * of the parse structure into the interpreter's result. */ PrintParse(interp, &parse); Tcl_FreeParse(&parse); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestexprparserCmd -- * * This procedure implements the "testexprparser" command. It is * used for testing the new Tcl expression parser in Tcl 8.1. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestexprparserCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { const char *script; Tcl_Size dummy; int length; Tcl_Parse parse; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "expr length"); return TCL_ERROR; } script = Tcl_GetStringFromObj(objv[1], &dummy); if (Tcl_GetIntFromObj(interp, objv[2], &length)) { return TCL_ERROR; } if (length == 0) { length = dummy; } parse.commentStart = NULL; parse.commentSize = 0; parse.commandStart = NULL; parse.commandSize = 0; if (Tcl_ParseExpr(interp, script, length, &parse) != TCL_OK) { Tcl_AddErrorInfo(interp, "\n (remainder of expr: \""); Tcl_AddErrorInfo(interp, parse.term); Tcl_AddErrorInfo(interp, "\")"); return TCL_ERROR; } /* * The parse completed successfully. Just print out the contents * of the parse structure into the interpreter's result. */ PrintParse(interp, &parse); Tcl_FreeParse(&parse); return TCL_OK; } /* *---------------------------------------------------------------------- * * PrintParse -- * * This procedure prints out the contents of a Tcl_Parse structure * in the result of an interpreter. * * Results: * Interp's result is set to a prettily formatted version of the * contents of parsePtr. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void PrintParse( Tcl_Interp *interp, /* Interpreter whose result is to be set to * the contents of a parse structure. */ Tcl_Parse *parsePtr) /* Parse structure to print out. */ { Tcl_Obj *objPtr; const char *typeString; Tcl_Token *tokenPtr; Tcl_Size i; objPtr = Tcl_GetObjResult(interp); if (parsePtr->commentSize > 0) { Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(parsePtr->commentStart, parsePtr->commentSize)); } else { Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("-", 1)); } Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(parsePtr->commandStart, parsePtr->commandSize)); Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewWideIntObj(parsePtr->numWords)); for (i = 0; i < parsePtr->numTokens; i++) { tokenPtr = &parsePtr->tokenPtr[i]; switch (tokenPtr->type) { case TCL_TOKEN_EXPAND_WORD: typeString = "expand"; break; case TCL_TOKEN_WORD: typeString = "word"; break; case TCL_TOKEN_SIMPLE_WORD: typeString = "simple"; break; case TCL_TOKEN_TEXT: typeString = "text"; break; case TCL_TOKEN_BS: typeString = "backslash"; break; case TCL_TOKEN_COMMAND: typeString = "command"; break; case TCL_TOKEN_VARIABLE: typeString = "variable"; break; case TCL_TOKEN_SUB_EXPR: typeString = "subexpr"; break; case TCL_TOKEN_OPERATOR: typeString = "operator"; break; default: typeString = "??"; break; } Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(typeString, -1)); Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(tokenPtr->start, tokenPtr->size)); Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewWideIntObj(tokenPtr->numComponents)); } Tcl_ListObjAppendElement(NULL, objPtr, parsePtr->commandStart ? Tcl_NewStringObj(parsePtr->commandStart + parsePtr->commandSize, TCL_INDEX_NONE) : Tcl_NewObj()); } /* *---------------------------------------------------------------------- * * TestparsevarCmd -- * * This procedure implements the "testparsevar" command. It is * used for testing Tcl_ParseVar. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestparsevarCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { const char *value, *name, *termPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "varName"); return TCL_ERROR; } name = Tcl_GetString(objv[1]); value = Tcl_ParseVar(interp, name, &termPtr); if (value == NULL) { return TCL_ERROR; } Tcl_AppendElement(interp, value); Tcl_AppendElement(interp, termPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestparsevarnameCmd -- * * This procedure implements the "testparsevarname" command. It is * used for testing the new Tcl script parser in Tcl 8.1. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestparsevarnameCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { const char *script; int length, append; Tcl_Size dummy; Tcl_Parse parse; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "script length append"); return TCL_ERROR; } script = Tcl_GetStringFromObj(objv[1], &dummy); if (Tcl_GetIntFromObj(interp, objv[2], &length)) { return TCL_ERROR; } if (length == 0) { length = dummy; } if (Tcl_GetIntFromObj(interp, objv[3], &append)) { return TCL_ERROR; } if (Tcl_ParseVarName(interp, script, length, &parse, append) != TCL_OK) { Tcl_AddErrorInfo(interp, "\n (remainder of script: \""); Tcl_AddErrorInfo(interp, parse.term); Tcl_AddErrorInfo(interp, "\")"); return TCL_ERROR; } /* * The parse completed successfully. Just print out the contents * of the parse structure into the interpreter's result. */ parse.commentSize = 0; parse.commandStart = script + parse.tokenPtr->size; parse.commandSize = 0; PrintParse(interp, &parse); Tcl_FreeParse(&parse); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestpreferstableCmd -- * * This procedure implements the "testpreferstable" command. It is * used for being able to test the "package" command even when the * environment variable TCL_PKG_PREFER_LATEST is set in your environment. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestpreferstableCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *) /*objv*/) { Interp *iPtr = (Interp *) interp; iPtr->packagePrefer = PKG_PREFER_STABLE; return TCL_OK; } /* *---------------------------------------------------------------------- * * TestprintCmd -- * * This procedure implements the "testprint" command. It is * used for being able to test the Tcl_ObjPrintf() function. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestprintCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { Tcl_WideInt argv1 = 0; size_t argv2; long argv3; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "format wideint"); return TCL_OK; } Tcl_GetWideIntFromObj(interp, objv[2], &argv1); argv2 = (size_t)argv1; argv3 = (long)argv1; Tcl_SetObjResult(interp, Tcl_ObjPrintf(Tcl_GetString(objv[1]), argv1, argv2, argv3, argv3)); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestregexpCmd -- * * This procedure implements the "testregexp" command. It is used to give * a direct interface for regexp flags. It's identical to * Tcl_RegexpObjCmd except for the -xflags option, and the consequences * thereof (including the REG_EXPECT kludge). * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int TestregexpCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int indices, match, about; Tcl_Size stringLength, i, ii; int hasxflags, cflags, eflags; Tcl_RegExp regExpr; const char *string; Tcl_Obj *objPtr; Tcl_RegExpInfo info; static const char *const options[] = { "-indices", "-nocase", "-about", "-expanded", "-line", "-linestop", "-lineanchor", "-xflags", "--", NULL }; enum optionsEnum { REGEXP_INDICES, REGEXP_NOCASE, REGEXP_ABOUT, REGEXP_EXPANDED, REGEXP_MULTI, REGEXP_NOCROSS, REGEXP_NEWL, REGEXP_XFLAGS, REGEXP_LAST } index; indices = 0; about = 0; cflags = REG_ADVANCED; eflags = 0; hasxflags = 0; for (i = 1; i < objc; i++) { const char *name; name = Tcl_GetString(objv[i]); if (name[0] != '-') { break; } if (Tcl_GetIndexFromObj(interp, objv[i], options, "switch", TCL_EXACT, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case REGEXP_INDICES: indices = 1; break; case REGEXP_NOCASE: cflags |= REG_ICASE; break; case REGEXP_ABOUT: about = 1; break; case REGEXP_EXPANDED: cflags |= REG_EXPANDED; break; case REGEXP_MULTI: cflags |= REG_NEWLINE; break; case REGEXP_NOCROSS: cflags |= REG_NLSTOP; break; case REGEXP_NEWL: cflags |= REG_NLANCH; break; case REGEXP_XFLAGS: hasxflags = 1; break; case REGEXP_LAST: i++; goto endOfForLoop; } } endOfForLoop: if (objc + about < hasxflags + 2 + i) { Tcl_WrongNumArgs(interp, 1, objv, "?-switch ...? exp string ?matchVar? ?subMatchVar ...?"); return TCL_ERROR; } objc -= i; objv += i; if (hasxflags) { string = Tcl_GetStringFromObj(objv[0], &stringLength); TestregexpXflags(string, stringLength, &cflags, &eflags); objc--; objv++; } regExpr = Tcl_GetRegExpFromObj(interp, objv[0], cflags); if (regExpr == NULL) { return TCL_ERROR; } if (about) { if (TclRegAbout(interp, regExpr) < 0) { return TCL_ERROR; } return TCL_OK; } objPtr = objv[1]; match = Tcl_RegExpExecObj(interp, regExpr, objPtr, 0 /* offset */, objc-2 /* nmatches */, eflags); if (match < 0) { return TCL_ERROR; } if (match == 0) { /* * Set the interpreter's object result to an integer object w/ * value 0. */ Tcl_SetWideIntObj(Tcl_GetObjResult(interp), 0); if (objc > 2 && (cflags®_EXPECT) && indices) { const char *varName; const char *value; Tcl_Size start, end; char resinfo[TCL_INTEGER_SPACE * 2]; varName = Tcl_GetString(objv[2]); TclRegExpRangeUniChar(regExpr, TCL_INDEX_NONE, &start, &end); snprintf(resinfo, sizeof(resinfo), "%" TCL_Z_MODIFIER "d %" TCL_Z_MODIFIER "d", start, end-1); value = Tcl_SetVar2(interp, varName, NULL, resinfo, 0); if (value == NULL) { Tcl_AppendResult(interp, "couldn't set variable \"", varName, "\"", (char *)NULL); return TCL_ERROR; } } else if (cflags & TCL_REG_CANMATCH) { const char *varName; const char *value; char resinfo[TCL_INTEGER_SPACE * 2]; Tcl_RegExpGetInfo(regExpr, &info); varName = Tcl_GetString(objv[2]); snprintf(resinfo, sizeof(resinfo), "%" TCL_Z_MODIFIER "d", info.extendStart); value = Tcl_SetVar2(interp, varName, NULL, resinfo, 0); if (value == NULL) { Tcl_AppendResult(interp, "couldn't set variable \"", varName, "\"", (char *)NULL); return TCL_ERROR; } } return TCL_OK; } /* * If additional variable names have been specified, return * index information in those variables. */ objc -= 2; objv += 2; Tcl_RegExpGetInfo(regExpr, &info); for (i = 0; i < objc; i++) { Tcl_Size start, end; Tcl_Obj *newPtr, *varPtr, *valuePtr; varPtr = objv[i]; ii = ((cflags®_EXPECT) && i == objc-1) ? TCL_INDEX_NONE : i; if (indices) { Tcl_Obj *objs[2]; if (ii == TCL_INDEX_NONE) { TclRegExpRangeUniChar(regExpr, ii, &start, &end); } else if (ii > info.nsubs) { start = TCL_INDEX_NONE; end = TCL_INDEX_NONE; } else { start = info.matches[ii].start; end = info.matches[ii].end; } /* * Adjust index so it refers to the last character in the match * instead of the first character after the match. */ if (end != TCL_INDEX_NONE) { end--; } objs[0] = Tcl_NewWideIntObj(start); objs[1] = Tcl_NewWideIntObj(end); newPtr = Tcl_NewListObj(2, objs); } else { if (ii == TCL_INDEX_NONE) { TclRegExpRangeUniChar(regExpr, ii, &start, &end); newPtr = Tcl_GetRange(objPtr, start, end); } else if (ii > info.nsubs || info.matches[ii].end <= 0) { newPtr = Tcl_NewObj(); } else { newPtr = Tcl_GetRange(objPtr, info.matches[ii].start, info.matches[ii].end - 1); } } valuePtr = Tcl_ObjSetVar2(interp, varPtr, NULL, newPtr, TCL_LEAVE_ERR_MSG); if (valuePtr == NULL) { return TCL_ERROR; } } /* * Set the interpreter's object result to an integer object w/ value 1. */ Tcl_SetWideIntObj(Tcl_GetObjResult(interp), 1); return TCL_OK; } /* *--------------------------------------------------------------------------- * * TestregexpXflags -- * * Parse a string of extended regexp flag letters, for testing. * * Results: * No return value (you're on your own for errors here). * * Side effects: * Modifies *cflagsPtr, a regcomp flags word, and *eflagsPtr, a * regexec flags word, as appropriate. * *---------------------------------------------------------------------- */ static void TestregexpXflags( const char *string, /* The string of flags. */ size_t length, /* The length of the string in bytes. */ int *cflagsPtr, /* compile flags word */ int *eflagsPtr) /* exec flags word */ { size_t i; int cflags, eflags; cflags = *cflagsPtr; eflags = *eflagsPtr; for (i = 0; i < length; i++) { switch (string[i]) { case 'a': cflags |= REG_ADVF; break; case 'b': cflags &= ~REG_ADVANCED; break; case 'c': cflags |= TCL_REG_CANMATCH; break; case 'e': cflags &= ~REG_ADVANCED; cflags |= REG_EXTENDED; break; case 'q': cflags &= ~REG_ADVANCED; cflags |= REG_QUOTE; break; case 'o': /* o for opaque */ cflags |= REG_NOSUB; break; case 's': /* s for start */ cflags |= REG_BOSONLY; break; case '+': cflags |= REG_FAKE; break; case ',': cflags |= REG_PROGRESS; break; case '.': cflags |= REG_DUMP; break; case ':': eflags |= REG_MTRACE; break; case ';': eflags |= REG_FTRACE; break; case '^': eflags |= REG_NOTBOL; break; case '$': eflags |= REG_NOTEOL; break; case 't': cflags |= REG_EXPECT; break; case '%': eflags |= REG_SMALL; break; } } *cflagsPtr = cflags; *eflagsPtr = eflags; } /* *---------------------------------------------------------------------- * * TestreturnCmd -- * * This procedure implements the "testreturn" command. It is * used to verify that a * return TCL_RETURN; * has same behavior as * return Tcl_SetReturnOptions(interp, Tcl_NewObj()); * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int TestreturnCmd( TCL_UNUSED(void *), TCL_UNUSED(Tcl_Interp *), TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *) /*objv*/) { return TCL_RETURN; } /* *---------------------------------------------------------------------- * * TestsetassocdataCmd -- * * This procedure implements the "testsetassocdata" command. It is used * to test Tcl_SetAssocData. * * Results: * A standard Tcl result. * * Side effects: * Modifies or creates an association between a key and associated * data for this interpreter. * *---------------------------------------------------------------------- */ static int TestsetassocdataCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { char *buf, *oldData; Tcl_InterpDeleteProc *procPtr; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "data_key data_item"); return TCL_ERROR; } buf = (char *)Tcl_Alloc(strlen(Tcl_GetString(objv[2])) + 1); strcpy(buf, Tcl_GetString(objv[2])); /* * If we previously associated a malloced value with the variable, * free it before associating a new value. */ oldData = (char *)Tcl_GetAssocData(interp, Tcl_GetString(objv[1]), &procPtr); if ((oldData != NULL) && (procPtr == CleanupTestSetassocdataTests)) { Tcl_Free(oldData); } Tcl_SetAssocData(interp, Tcl_GetString(objv[1]), CleanupTestSetassocdataTests, buf); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestsetplatformCmd -- * * This procedure implements the "testsetplatform" command. It is * used to change the tclPlatform global variable so all file * name conversions can be tested on a single platform. * * Results: * A standard Tcl result. * * Side effects: * Sets the tclPlatform global variable. * *---------------------------------------------------------------------- */ static int TestsetplatformCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { Tcl_Size length; TclPlatformType *platform; platform = TclGetPlatform(); if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "platform"); return TCL_ERROR; } const char *argv1 = Tcl_GetStringFromObj(objv[1], &length); if (strncmp(argv1, "unix", length) == 0) { *platform = TCL_PLATFORM_UNIX; } else if (strncmp(argv1, "windows", length) == 0) { *platform = TCL_PLATFORM_WINDOWS; } else { Tcl_AppendResult(interp, "unsupported platform: should be one of " "unix, or windows", (char *)NULL); return TCL_ERROR; } return TCL_OK; } static int TestSizeCmd( TCL_UNUSED(void *), /* Unused */ Tcl_Interp* interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const * objv) /* Parameter vector */ { if (objc != 2) { goto syntax; } if (strcmp(Tcl_GetString(objv[1]), "st_mtime") == 0) { Tcl_StatBuf *statPtr; Tcl_SetObjResult(interp, Tcl_NewWideIntObj(sizeof(statPtr->st_mtime))); return TCL_OK; } syntax: Tcl_WrongNumArgs(interp, 1, objv, "st_mtime"); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TeststaticlibraryCmd -- * * This procedure implements the "teststaticlibrary" command. * It is used to test the procedure Tcl_StaticLibrary. * * Results: * A standard Tcl result. * * Side effects: * When the package given by objv[1] is loaded into an interpreter, * variable "x" in that interpreter is set to "loaded". * *---------------------------------------------------------------------- */ static int TeststaticlibraryCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Argument strings. */ { int safe, loaded; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "prefix safe loaded"); return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[2], &safe) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[3], &loaded) != TCL_OK) { return TCL_ERROR; } Tcl_StaticLibrary((loaded) ? interp : NULL, Tcl_GetString(objv[1]), StaticInitProc, (safe) ? StaticInitProc : NULL); return TCL_OK; } static int StaticInitProc( Tcl_Interp *interp) /* Interpreter in which package is supposedly * being loaded. */ { Tcl_SetVar2(interp, "x", NULL, "loaded", TCL_GLOBAL_ONLY); return TCL_OK; } /* *---------------------------------------------------------------------- * * TesttranslatefilenameCmd -- * * This procedure implements the "testtranslatefilename" command. * It is used to test the Tcl_TranslateFileName command. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TesttranslatefilenameCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { Tcl_DString buffer; const char *result; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "path"); return TCL_ERROR; } result = Tcl_TranslateFileName(interp, Tcl_GetString(objv[1]), &buffer); if (result == NULL) { return TCL_ERROR; } Tcl_AppendResult(interp, result, (char *)NULL); Tcl_DStringFree(&buffer); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestfstildeexpandCmd -- * * This procedure implements the "testfstildeexpand" command. * It is used to test the Tcl_FSTildeExpand command. It differs * from the script level "file tildeexpand" tests because of a * slightly different code path. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestfstildeexpandCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { Tcl_DString buffer; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "PATH"); return TCL_ERROR; } if (Tcl_FSTildeExpand(interp, Tcl_GetString(objv[1]), &buffer) != TCL_OK) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_DStringToObj(&buffer)); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestupvarCmd -- * * This procedure implements the "testupvar" command. It is used * to test Tcl_UpVar and Tcl_UpVar2. * * Results: * A standard Tcl result. * * Side effects: * Creates or modifies an "upvar" reference. * *---------------------------------------------------------------------- */ static int TestupvarCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { int flags = 0; if ((objc != 5) && (objc != 6)) { Tcl_WrongNumArgs(interp, 1, objv, "level name ?name2? dest global"); return TCL_ERROR; } if (objc == 5) { if (strcmp(Tcl_GetString(objv[4]), "global") == 0) { flags = TCL_GLOBAL_ONLY; } else if (strcmp(Tcl_GetString(objv[4]), "namespace") == 0) { flags = TCL_NAMESPACE_ONLY; } return Tcl_UpVar2(interp, Tcl_GetString(objv[1]), Tcl_GetString(objv[2]), NULL, Tcl_GetString(objv[3]), flags); } else { if (strcmp(Tcl_GetString(objv[5]), "global") == 0) { flags = TCL_GLOBAL_ONLY; } else if (strcmp(Tcl_GetString(objv[5]), "namespace") == 0) { flags = TCL_NAMESPACE_ONLY; } return Tcl_UpVar2(interp, Tcl_GetString(objv[1]), Tcl_GetString(objv[2]), (Tcl_GetString(objv[3])[0] == 0) ? NULL : Tcl_GetString(objv[3]), Tcl_GetString(objv[4]), flags); } } /* *---------------------------------------------------------------------- * * TestseterrorcodeCmd -- * * This procedure implements the "testseterrorcodeCmd". This tests up to * five elements passed to the Tcl_SetErrorCode command. * * Results: * A standard Tcl result. Always returns TCL_ERROR so that * the error code can be tested. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestseterrorcodeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { if (objc > 6) { Tcl_AppendResult(interp, "too many args", (char *)NULL); return TCL_ERROR; } switch (objc) { case 1: Tcl_SetErrorCode(interp, "NONE", (char *)NULL); break; case 2: Tcl_SetErrorCode(interp, Tcl_GetString(objv[1]), (char *)NULL); break; case 3: Tcl_SetErrorCode(interp, Tcl_GetString(objv[1]), Tcl_GetString(objv[2]), (char *)NULL); break; case 4: Tcl_SetErrorCode(interp, Tcl_GetString(objv[1]), Tcl_GetString(objv[2]), Tcl_GetString(objv[3]), (char *)NULL); break; case 5: Tcl_SetErrorCode(interp, Tcl_GetString(objv[1]), Tcl_GetString(objv[2]), Tcl_GetString(objv[3]), Tcl_GetString(objv[4]), (char *)NULL); break; case 6: Tcl_SetErrorCode(interp, Tcl_GetString(objv[1]), Tcl_GetString(objv[2]), Tcl_GetString(objv[3]), Tcl_GetString(objv[4]), Tcl_GetString(objv[5]), (char *)NULL); } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TestsetobjerrorcodeCmd -- * * This procedure implements the "testsetobjerrorcodeCmd". * This tests the Tcl_SetObjErrorCode function. * * Results: * A standard Tcl result. Always returns TCL_ERROR so that * the error code can be tested. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestsetobjerrorcodeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { Tcl_SetObjErrorCode(interp, Tcl_ConcatObj(objc - 1, objv + 1)); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TestfeventCmd -- * * This procedure implements the "testfevent" command. It is * used for testing the "fileevent" command. * * Results: * A standard Tcl result. * * Side effects: * Creates and deletes interpreters. * *---------------------------------------------------------------------- */ static int TestfeventCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { static Tcl_Interp *interp2 = NULL; int code; Tcl_Channel chan; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[1]), "cmd") == 0) { if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "script"); return TCL_ERROR; } if (interp2 != NULL) { code = Tcl_EvalEx(interp2, Tcl_GetString(objv[2]), TCL_INDEX_NONE, TCL_EVAL_GLOBAL); Tcl_SetObjResult(interp, Tcl_GetObjResult(interp2)); return code; } else { Tcl_AppendResult(interp, "called \"testfevent code\" before \"testfevent create\"", (char *)NULL); return TCL_ERROR; } } else if (strcmp(Tcl_GetString(objv[1]), "create") == 0) { if (interp2 != NULL) { Tcl_DeleteInterp(interp2); } interp2 = Tcl_CreateInterp(); return Tcl_Init(interp2); } else if (strcmp(Tcl_GetString(objv[1]), "delete") == 0) { if (interp2 != NULL) { Tcl_DeleteInterp(interp2); } interp2 = NULL; } else if (strcmp(Tcl_GetString(objv[1]), "share") == 0) { if (interp2 != NULL) { chan = Tcl_GetChannel(interp, Tcl_GetString(objv[2]), NULL); if (chan == (Tcl_Channel) NULL) { return TCL_ERROR; } Tcl_RegisterChannel(interp2, chan); } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TestpanicCmd -- * * Calls the panic routine. * * Results: * Always returns TCL_OK. * * Side effects: * May exit application. * *---------------------------------------------------------------------- */ static int TestpanicCmd( TCL_UNUSED(void *), TCL_UNUSED(Tcl_Interp *), int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { /* * Put the arguments into a var args structure * Append all of the arguments together separated by spaces */ Tcl_Obj *list = Tcl_NewListObj(objc-1, objv+1); Tcl_Panic("%s", Tcl_GetString(list)); Tcl_DecrRefCount(list); return TCL_OK; } static int TestfileCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* The argument objects. */ { int force, i, result; Tcl_Obj *error = NULL; const char *subcmd; int j; if (objc < 3) { return TCL_ERROR; } force = 0; i = 2; if (strcmp(Tcl_GetString(objv[2]), "-force") == 0) { force = 1; i = 3; } if (objc - i > 2) { return TCL_ERROR; } for (j = i; j < objc; j++) { if (Tcl_FSGetNormalizedPath(interp, objv[j]) == NULL) { return TCL_ERROR; } } subcmd = Tcl_GetString(objv[1]); if (strcmp(subcmd, "mv") == 0) { result = TclpObjRenameFile(objv[i], objv[i + 1]); } else if (strcmp(subcmd, "cp") == 0) { result = TclpObjCopyFile(objv[i], objv[i + 1]); } else if (strcmp(subcmd, "rm") == 0) { result = TclpObjDeleteFile(objv[i]); } else if (strcmp(subcmd, "mkdir") == 0) { result = TclpObjCreateDirectory(objv[i]); } else if (strcmp(subcmd, "cpdir") == 0) { result = TclpObjCopyDirectory(objv[i], objv[i + 1], &error); } else if (strcmp(subcmd, "rmdir") == 0) { result = TclpObjRemoveDirectory(objv[i], force, &error); } else { result = TCL_ERROR; goto end; } if (result != TCL_OK) { if (error != NULL) { if (Tcl_GetString(error)[0] != '\0') { Tcl_AppendResult(interp, Tcl_GetString(error), " ", (char *)NULL); } Tcl_DecrRefCount(error); } Tcl_AppendResult(interp, Tcl_ErrnoId(), (char *)NULL); } end: return result; } /* *---------------------------------------------------------------------- * * TestgetvarfullnameCmd -- * * Implements the "testgetvarfullname" cmd that is used when testing * the Tcl_GetVariableFullName procedure. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestgetvarfullnameCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { const char *name, *arg; int flags = 0; Tcl_Namespace *namespacePtr; Tcl_CallFrame *framePtr; Tcl_Var variable; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "name scope"); return TCL_ERROR; } name = Tcl_GetString(objv[1]); arg = Tcl_GetString(objv[2]); if (strcmp(arg, "global") == 0) { flags = TCL_GLOBAL_ONLY; } else if (strcmp(arg, "namespace") == 0) { flags = TCL_NAMESPACE_ONLY; } /* * This command, like any other created with Tcl_Create[Obj]Command, runs * in the global namespace. As a "namespace-aware" command that needs to * run in a particular namespace, it must activate that namespace itself. */ if (flags == TCL_NAMESPACE_ONLY) { namespacePtr = Tcl_FindNamespace(interp, "::test_ns_var", NULL, TCL_LEAVE_ERR_MSG); if (namespacePtr == NULL) { return TCL_ERROR; } (void) TclPushStackFrame(interp, &framePtr, namespacePtr, /*isProcCallFrame*/ 0); } variable = Tcl_FindNamespaceVar(interp, name, NULL, (flags | TCL_LEAVE_ERR_MSG)); if (flags == TCL_NAMESPACE_ONLY) { TclPopStackFrame(interp); } if (variable == (Tcl_Var) NULL) { return TCL_ERROR; } Tcl_GetVariableFullName(interp, variable, Tcl_GetObjResult(interp)); return TCL_OK; } /* *---------------------------------------------------------------------- * * GetTimesCmd -- * * This procedure implements the "gettimes" command. It is used for * computing the time needed for various basic operations such as reading * variables, allocating memory, snprintf, converting variables, etc. * * Results: * A standard Tcl result. * * Side effects: * Allocates and frees memory, sets a variable "a" in the interpreter. * *---------------------------------------------------------------------- */ static int GetTimesCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* The current interpreter. */ TCL_UNUSED(int) /*cobjc*/, TCL_UNUSED(Tcl_Obj *const *) /*cobjv*/) { Interp *iPtr = (Interp *) interp; int i, n; double timePer; Tcl_Time start, stop; Tcl_Obj *objPtr, **objv; const char *s; char newString[TCL_INTEGER_SPACE]; /* alloc & free 100000 times */ fprintf(stderr, "alloc & free 100000 6 word items\n"); Tcl_GetTime(&start); for (i = 0; i < 100000; i++) { objPtr = (Tcl_Obj *)Tcl_Alloc(sizeof(Tcl_Obj)); Tcl_Free(objPtr); } Tcl_GetTime(&stop); timePer = (stop.sec - start.sec)*1000000 + (stop.usec - start.usec); fprintf(stderr, " %.3f usec per alloc+free\n", timePer/100000); /* alloc 5000 times */ fprintf(stderr, "alloc 5000 6 word items\n"); objv = (Tcl_Obj **)Tcl_Alloc(5000 * sizeof(Tcl_Obj *)); Tcl_GetTime(&start); for (i = 0; i < 5000; i++) { objv[i] = (Tcl_Obj *)Tcl_Alloc(sizeof(Tcl_Obj)); } Tcl_GetTime(&stop); timePer = (stop.sec - start.sec)*1000000 + (stop.usec - start.usec); fprintf(stderr, " %.3f usec per alloc\n", timePer/5000); /* free 5000 times */ fprintf(stderr, "free 5000 6 word items\n"); Tcl_GetTime(&start); for (i = 0; i < 5000; i++) { Tcl_Free(objv[i]); } Tcl_GetTime(&stop); timePer = (stop.sec - start.sec)*1000000 + (stop.usec - start.usec); fprintf(stderr, " %.3f usec per free\n", timePer/5000); /* Tcl_NewObj 5000 times */ fprintf(stderr, "Tcl_NewObj 5000 times\n"); Tcl_GetTime(&start); for (i = 0; i < 5000; i++) { objv[i] = Tcl_NewObj(); } Tcl_GetTime(&stop); timePer = (stop.sec - start.sec)*1000000 + (stop.usec - start.usec); fprintf(stderr, " %.3f usec per Tcl_NewObj\n", timePer/5000); /* Tcl_DecrRefCount 5000 times */ fprintf(stderr, "Tcl_DecrRefCount 5000 times\n"); Tcl_GetTime(&start); for (i = 0; i < 5000; i++) { objPtr = objv[i]; Tcl_DecrRefCount(objPtr); } Tcl_GetTime(&stop); timePer = (stop.sec - start.sec)*1000000 + (stop.usec - start.usec); fprintf(stderr, " %.3f usec per Tcl_DecrRefCount\n", timePer/5000); Tcl_Free(objv); /* TclGetString 100000 times */ fprintf(stderr, "Tcl_GetStringFromObj of \"12345\" 100000 times\n"); objPtr = Tcl_NewStringObj("12345", -1); Tcl_GetTime(&start); for (i = 0; i < 100000; i++) { (void) TclGetString(objPtr); } Tcl_GetTime(&stop); timePer = (stop.sec - start.sec)*1000000 + (stop.usec - start.usec); fprintf(stderr, " %.3f usec per Tcl_GetStringFromObj of \"12345\"\n", timePer/100000); /* Tcl_GetIntFromObj 100000 times */ fprintf(stderr, "Tcl_GetIntFromObj of \"12345\" 100000 times\n"); Tcl_GetTime(&start); for (i = 0; i < 100000; i++) { if (Tcl_GetIntFromObj(interp, objPtr, &n) != TCL_OK) { return TCL_ERROR; } } Tcl_GetTime(&stop); timePer = (stop.sec - start.sec)*1000000 + (stop.usec - start.usec); fprintf(stderr, " %.3f usec per Tcl_GetIntFromObj of \"12345\"\n", timePer/100000); Tcl_DecrRefCount(objPtr); /* Tcl_GetInt 100000 times */ fprintf(stderr, "Tcl_GetInt of \"12345\" 100000 times\n"); Tcl_GetTime(&start); for (i = 0; i < 100000; i++) { if (Tcl_GetInt(interp, "12345", &n) != TCL_OK) { return TCL_ERROR; } } Tcl_GetTime(&stop); timePer = (stop.sec - start.sec)*1000000 + (stop.usec - start.usec); fprintf(stderr, " %.3f usec per Tcl_GetInt of \"12345\"\n", timePer/100000); /* snprintf 100000 times */ fprintf(stderr, "snprintf of 12345 100000 times\n"); Tcl_GetTime(&start); for (i = 0; i < 100000; i++) { snprintf(newString, sizeof(newString), "%d", 12345); } Tcl_GetTime(&stop); timePer = (stop.sec - start.sec)*1000000 + (stop.usec - start.usec); fprintf(stderr, " %.3f usec per snprintf of 12345\n", timePer/100000); /* hashtable lookup 100000 times */ fprintf(stderr, "hashtable lookup of \"gettimes\" 100000 times\n"); Tcl_GetTime(&start); for (i = 0; i < 100000; i++) { (void) Tcl_FindHashEntry(&iPtr->globalNsPtr->cmdTable, "gettimes"); } Tcl_GetTime(&stop); timePer = (stop.sec - start.sec)*1000000 + (stop.usec - start.usec); fprintf(stderr, " %.3f usec per hashtable lookup of \"gettimes\"\n", timePer/100000); /* Tcl_SetVar 100000 times */ fprintf(stderr, "Tcl_SetVar2 of \"12345\" 100000 times\n"); Tcl_GetTime(&start); for (i = 0; i < 100000; i++) { s = Tcl_SetVar2(interp, "a", NULL, "12345", TCL_LEAVE_ERR_MSG); if (s == NULL) { return TCL_ERROR; } } Tcl_GetTime(&stop); timePer = (stop.sec - start.sec)*1000000 + (stop.usec - start.usec); fprintf(stderr, " %.3f usec per Tcl_SetVar of a to \"12345\"\n", timePer/100000); /* Tcl_GetVar 100000 times */ fprintf(stderr, "Tcl_GetVar of a==\"12345\" 100000 times\n"); Tcl_GetTime(&start); for (i = 0; i < 100000; i++) { s = Tcl_GetVar2(interp, "a", NULL, TCL_LEAVE_ERR_MSG); if (s == NULL) { return TCL_ERROR; } } Tcl_GetTime(&stop); timePer = (stop.sec - start.sec)*1000000 + (stop.usec - start.usec); fprintf(stderr, " %.3f usec per Tcl_GetVar of a==\"12345\"\n", timePer/100000); Tcl_ResetResult(interp); return TCL_OK; } /* *---------------------------------------------------------------------- * * NoopCmd -- * * This procedure is just used to time the overhead involved in * parsing and invoking a command. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int NoopCmd( TCL_UNUSED(void *), TCL_UNUSED(Tcl_Interp *), TCL_UNUSED(int) /*argc*/, TCL_UNUSED(const char **) /*argv*/) { return TCL_OK; } /* *---------------------------------------------------------------------- * * NoopObjCmd -- * * This object-based procedure is just used to time the overhead * involved in parsing and invoking a command. * * Results: * Returns the TCL_OK result code. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int NoopObjCmd( TCL_UNUSED(void *), TCL_UNUSED(Tcl_Interp *), TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *) /*objv*/) { return TCL_OK; } /* *---------------------------------------------------------------------- * * TeststringbytesCmd -- * Returns bytearray value of the bytes in argument string rep * * Results: * Returns the TCL_OK result code. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TeststringbytesCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { Tcl_Size n; const unsigned char *p; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "value"); return TCL_ERROR; } p = (const unsigned char *)Tcl_GetStringFromObj(objv[1], &n); Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(p, n)); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestpurebytesobjCmd -- * * This object-based procedure constructs a pure bytes object * without type and with internal representation containing NULL's. * * If no argument supplied it returns empty object with tclEmptyStringRep, * otherwise it returns this as pure bytes object with bytes value equal * string. * * Results: * Returns the TCL_OK result code. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestpurebytesobjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { Tcl_Obj *objPtr; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?string?"); return TCL_ERROR; } objPtr = Tcl_NewObj(); /* objPtr->internalRep.twoPtrValue.ptr1 = NULL; objPtr->internalRep.twoPtrValue.ptr2 = NULL; */ memset(&objPtr->internalRep, 0, sizeof(objPtr->internalRep)); if (objc == 2) { const char *s = Tcl_GetString(objv[1]); objPtr->length = objv[1]->length; objPtr->bytes = (char *)Tcl_Alloc(objPtr->length + 1); memcpy(objPtr->bytes, s, objPtr->length); objPtr->bytes[objPtr->length] = 0; } Tcl_SetObjResult(interp, objPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestsetbytearraylengthCmd -- * * Testing command 'testsetbytearraylength` used to test the public * interface routine Tcl_SetByteArrayLength(). * * Results: * Returns the TCL_OK result code. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestsetbytearraylengthCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { int n; Tcl_Obj *obj = NULL; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "value length"); return TCL_ERROR; } if (TCL_OK != Tcl_GetIntFromObj(interp, objv[2], &n)) { return TCL_ERROR; } obj = objv[1]; if (Tcl_IsShared(obj)) { obj = Tcl_DuplicateObj(obj); } if (Tcl_SetByteArrayLength(obj, n) == NULL) { if (obj != objv[1]) { Tcl_DecrRefCount(obj); } Tcl_AppendResult(interp, "expected bytes", (char *)NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, obj); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestbytestringCmd -- * * This object-based procedure constructs a string which can * possibly contain invalid UTF-8 bytes. * * Results: * Returns the TCL_OK result code. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestbytestringCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* The argument objects. */ { struct { #if !defined(TCL_NO_DEPRECATED) int n; /* On purpose, not Tcl_Size, in order to demonstrate what happens */ #else Tcl_Size n; #endif int m; /* This variable should not be overwritten */ } x = {0, 1}; const char *p; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "bytearray"); return TCL_ERROR; } p = (const char *)Tcl_GetBytesFromObj(interp, objv[1], &x.n); if (p == NULL) { return TCL_ERROR; } if (x.m != 1) { Tcl_AppendResult(interp, "Tcl_GetBytesFromObj() overwrites variable", (char *)NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewStringObj(p, x.n)); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestsetCmd -- * * Implements the "testset{err,noerr}" cmds that are used when testing * Tcl_Set/GetVar C Api with/without TCL_LEAVE_ERR_MSG flag * * Results: * A standard Tcl result. * * Side effects: * Variables may be set. * *---------------------------------------------------------------------- */ static int TestsetCmd( void *data, /* Additional flags for Get/SetVar2. */ Tcl_Interp *interp,/* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { int flags = PTR2INT(data); const char *value; if (objc == 2) { Tcl_AppendResult(interp, "before get", (char *)NULL); value = Tcl_GetVar2(interp, Tcl_GetString(objv[1]), NULL, flags); if (value == NULL) { return TCL_ERROR; } Tcl_AppendElement(interp, value); return TCL_OK; } else if (objc == 3) { Tcl_AppendResult(interp, "before set", (char *)NULL); value = Tcl_SetVar2(interp, Tcl_GetString(objv[1]), NULL, Tcl_GetString(objv[2]), flags); if (value == NULL) { return TCL_ERROR; } Tcl_AppendElement(interp, value); return TCL_OK; } else { Tcl_WrongNumArgs(interp, 1, objv, "varName ?newValue?"); return TCL_ERROR; } } static int Testset2Cmd( void *data, /* Additional flags for Get/SetVar2. */ Tcl_Interp *interp,/* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Argument strings. */ { int flags = PTR2INT(data); const char *value; if (objc == 3) { Tcl_AppendResult(interp, "before get", (char *)NULL); value = Tcl_GetVar2(interp, Tcl_GetString(objv[1]), Tcl_GetString(objv[2]), flags); if (value == NULL) { return TCL_ERROR; } Tcl_AppendElement(interp, value); return TCL_OK; } else if (objc == 4) { Tcl_AppendResult(interp, "before set", (char *)NULL); value = Tcl_SetVar2(interp, Tcl_GetString(objv[1]), Tcl_GetString(objv[2]), Tcl_GetString(objv[3]), flags); if (value == NULL) { return TCL_ERROR; } Tcl_AppendElement(interp, value); return TCL_OK; } else { Tcl_WrongNumArgs(interp, 1, objv, "varName elemName ?newValue??"); return TCL_ERROR; } } /* *---------------------------------------------------------------------- * * TestmainthreadCmd -- * * Implements the "testmainthread" cmd that is used to test the * 'Tcl_GetCurrentThread' API. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestmainthreadCmd( TCL_UNUSED(void *), Tcl_Interp *interp,/* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) { if (objc == 1) { Tcl_Obj *idObj = Tcl_NewWideIntObj((Tcl_WideInt)(size_t)Tcl_GetCurrentThread()); Tcl_SetObjResult(interp, idObj); return TCL_OK; } else { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } } /* *---------------------------------------------------------------------- * * MainLoop -- * * A main loop set by TestsetmainloopCmd below. * * Results: * None. * * Side effects: * Event handlers could do anything. * *---------------------------------------------------------------------- */ static void MainLoop(void) { while (!exitMainLoop) { Tcl_DoOneEvent(0); } fprintf(stdout,"Exit MainLoop\n"); fflush(stdout); } /* *---------------------------------------------------------------------- * * TestsetmainloopCmd -- * * Implements the "testsetmainloop" cmd that is used to test the * 'Tcl_SetMainLoop' API. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestsetmainloopCmd( TCL_UNUSED(void *), TCL_UNUSED(Tcl_Interp *), TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *) /*objv*/) { exitMainLoop = 0; Tcl_SetMainLoop(MainLoop); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestexitmainloopCmd -- * * Implements the "testexitmainloop" cmd that is used to test the * 'Tcl_SetMainLoop' API. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestexitmainloopCmd( TCL_UNUSED(void *), TCL_UNUSED(Tcl_Interp *), TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *) /*objv*/) { exitMainLoop = 1; return TCL_OK; } /* *---------------------------------------------------------------------- * * TestChannelCmd -- * * Implements the Tcl "testchannel" debugging command and its * subcommands. This is part of the testing environment. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestChannelCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter for result. */ int objc, /* Count of additional args. */ Tcl_Obj *const *objv) /* Additional args. */ { const char *cmdName; /* Sub command. */ Tcl_HashTable *hTblPtr; /* Hash table of channels. */ Tcl_HashSearch hSearch; /* Search variable. */ Tcl_HashEntry *hPtr; /* Search variable. */ Channel *chanPtr; /* The actual channel. */ ChannelState *statePtr; /* state info for channel */ Tcl_Channel chan; /* The opaque type. */ Tcl_Size len; /* Length of subcommand string. */ int IOQueued; /* How much IO is queued inside channel? */ char buf[TCL_INTEGER_SPACE];/* For snprintf. */ int mode; /* rw mode of the channel */ if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?additional args..?"); return TCL_ERROR; } cmdName = Tcl_GetStringFromObj(objv[1], &len); chanPtr = NULL; if (objc > 2) { if ((cmdName[0] == 's') && (strncmp(cmdName, "splice", len) == 0)) { /* For splice access the pool of detached channels. * Locate channel, remove from the list. */ TestChannel **nextPtrPtr, *curPtr; chan = (Tcl_Channel) NULL; for (nextPtrPtr = &firstDetached, curPtr = firstDetached; curPtr != NULL; nextPtrPtr = &(curPtr->nextPtr), curPtr = curPtr->nextPtr) { if (strcmp(Tcl_GetString(objv[2]), Tcl_GetChannelName(curPtr->chan)) == 0) { *nextPtrPtr = curPtr->nextPtr; curPtr->nextPtr = NULL; chan = curPtr->chan; Tcl_Free(curPtr); break; } } } else { chan = Tcl_GetChannel(interp, Tcl_GetString(objv[2]), &mode); } if (chan == (Tcl_Channel) NULL) { return TCL_ERROR; } chanPtr = (Channel *)chan; statePtr = chanPtr->state; chanPtr = statePtr->topChanPtr; chan = (Tcl_Channel) chanPtr; } else { statePtr = NULL; chan = NULL; } if ((cmdName[0] == 's') && (strncmp(cmdName, "setchannelerror", len) == 0)) { Tcl_Obj *msg = objv[3]; Tcl_IncrRefCount(msg); Tcl_SetChannelError(chan, msg); Tcl_DecrRefCount(msg); Tcl_GetChannelError(chan, &msg); Tcl_SetObjResult(interp, msg); Tcl_DecrRefCount(msg); return TCL_OK; } if ((cmdName[0] == 's') && (strncmp(cmdName, "setchannelerrorinterp", len) == 0)) { Tcl_Obj *msg = objv[3]; Tcl_IncrRefCount(msg); Tcl_SetChannelErrorInterp(interp, msg); Tcl_DecrRefCount(msg); Tcl_GetChannelErrorInterp(interp, &msg); Tcl_SetObjResult(interp, msg); Tcl_DecrRefCount(msg); return TCL_OK; } /* * "cut" is actually more a simplified detach facility as provided by the * Thread package. Without the safeguards of a regular command (no * checking that the command is truly cut'able, no mutexes for * thread-safety). Its complementary command is "splice", see below. */ if ((cmdName[0] == 'c') && (strncmp(cmdName, "cut", len) == 0)) { TestChannel *det; if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "channel"); return TCL_ERROR; } Tcl_RegisterChannel(NULL, chan); /* prevent closing */ Tcl_UnregisterChannel(interp, chan); Tcl_CutChannel(chan); /* Remember the channel in the pool of detached channels */ det = (TestChannel *)Tcl_Alloc(sizeof(TestChannel)); det->chan = chan; det->nextPtr = firstDetached; firstDetached = det; return TCL_OK; } if ((cmdName[0] == 'c') && (strncmp(cmdName, "clearchannelhandlers", len) == 0)) { if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "channel"); return TCL_ERROR; } Tcl_ClearChannelHandlers(chan); return TCL_OK; } if ((cmdName[0] == 'i') && (strncmp(cmdName, "info", len) == 0)) { if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "channel"); return TCL_ERROR; } Tcl_AppendElement(interp, Tcl_GetString(objv[2])); Tcl_AppendElement(interp, Tcl_ChannelName(chanPtr->typePtr)); if (statePtr->flags & TCL_READABLE) { Tcl_AppendElement(interp, "read"); } else { Tcl_AppendElement(interp, ""); } if (statePtr->flags & TCL_WRITABLE) { Tcl_AppendElement(interp, "write"); } else { Tcl_AppendElement(interp, ""); } if (statePtr->flags & CHANNEL_NONBLOCKING) { Tcl_AppendElement(interp, "nonblocking"); } else { Tcl_AppendElement(interp, "blocking"); } if (statePtr->flags & CHANNEL_LINEBUFFERED) { Tcl_AppendElement(interp, "line"); } else if (statePtr->flags & CHANNEL_UNBUFFERED) { Tcl_AppendElement(interp, "none"); } else { Tcl_AppendElement(interp, "full"); } if (statePtr->flags & BG_FLUSH_SCHEDULED) { Tcl_AppendElement(interp, "async_flush"); } else { Tcl_AppendElement(interp, ""); } if (statePtr->flags & CHANNEL_EOF) { Tcl_AppendElement(interp, "eof"); } else { Tcl_AppendElement(interp, ""); } if (statePtr->flags & CHANNEL_BLOCKED) { Tcl_AppendElement(interp, "blocked"); } else { Tcl_AppendElement(interp, "unblocked"); } if (statePtr->inputTranslation == TCL_TRANSLATE_AUTO) { Tcl_AppendElement(interp, "auto"); if (statePtr->flags & INPUT_SAW_CR) { Tcl_AppendElement(interp, "saw_cr"); } else { Tcl_AppendElement(interp, ""); } } else if (statePtr->inputTranslation == TCL_TRANSLATE_LF) { Tcl_AppendElement(interp, "lf"); Tcl_AppendElement(interp, ""); } else if (statePtr->inputTranslation == TCL_TRANSLATE_CR) { Tcl_AppendElement(interp, "cr"); Tcl_AppendElement(interp, ""); } else if (statePtr->inputTranslation == TCL_TRANSLATE_CRLF) { Tcl_AppendElement(interp, "crlf"); if (statePtr->flags & INPUT_SAW_CR) { Tcl_AppendElement(interp, "queued_cr"); } else { Tcl_AppendElement(interp, ""); } } if (statePtr->outputTranslation == TCL_TRANSLATE_AUTO) { Tcl_AppendElement(interp, "auto"); } else if (statePtr->outputTranslation == TCL_TRANSLATE_LF) { Tcl_AppendElement(interp, "lf"); } else if (statePtr->outputTranslation == TCL_TRANSLATE_CR) { Tcl_AppendElement(interp, "cr"); } else if (statePtr->outputTranslation == TCL_TRANSLATE_CRLF) { Tcl_AppendElement(interp, "crlf"); } IOQueued = Tcl_InputBuffered(chan); TclFormatInt(buf, IOQueued); Tcl_AppendElement(interp, buf); IOQueued = Tcl_OutputBuffered(chan); TclFormatInt(buf, IOQueued); Tcl_AppendElement(interp, buf); TclFormatInt(buf, (int)Tcl_Tell(chan)); Tcl_AppendElement(interp, buf); TclFormatInt(buf, statePtr->refCount); Tcl_AppendElement(interp, buf); return TCL_OK; } if ((cmdName[0] == 'i') && (strncmp(cmdName, "inputbuffered", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } IOQueued = Tcl_InputBuffered(chan); TclFormatInt(buf, IOQueued); Tcl_AppendResult(interp, buf, (char *)NULL); return TCL_OK; } if ((cmdName[0] == 'i') && (strncmp(cmdName, "isshared", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } TclFormatInt(buf, Tcl_IsChannelShared(chan)); Tcl_AppendResult(interp, buf, (char *)NULL); return TCL_OK; } if ((cmdName[0] == 'i') && (strncmp(cmdName, "isstandard", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } TclFormatInt(buf, Tcl_IsStandardChannel(chan)); Tcl_AppendResult(interp, buf, (char *)NULL); return TCL_OK; } if ((cmdName[0] == 'm') && (strncmp(cmdName, "mode", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } if (statePtr->flags & TCL_READABLE) { Tcl_AppendElement(interp, "read"); } else { Tcl_AppendElement(interp, ""); } if (statePtr->flags & TCL_WRITABLE) { Tcl_AppendElement(interp, "write"); } else { Tcl_AppendElement(interp, ""); } return TCL_OK; } if ((cmdName[0] == 'm') && (strncmp(cmdName, "maxmode", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } if (statePtr->maxPerms & TCL_READABLE) { Tcl_AppendElement(interp, "read"); } else { Tcl_AppendElement(interp, ""); } if (statePtr->maxPerms & TCL_WRITABLE) { Tcl_AppendElement(interp, "write"); } else { Tcl_AppendElement(interp, ""); } return TCL_OK; } if ((cmdName[0] == 'm') && (strncmp(cmdName, "mremove-rd", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } return Tcl_RemoveChannelMode(interp, chan, TCL_READABLE); } if ((cmdName[0] == 'm') && (strncmp(cmdName, "mremove-wr", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } return Tcl_RemoveChannelMode(interp, chan, TCL_WRITABLE); } if ((cmdName[0] == 'm') && (strncmp(cmdName, "mthread", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj( (Tcl_WideInt)(size_t)Tcl_GetChannelThread(chan))); return TCL_OK; } if ((cmdName[0] == 'n') && (strncmp(cmdName, "name", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } Tcl_AppendResult(interp, statePtr->channelName, (char *)NULL); return TCL_OK; } if ((cmdName[0] == 'o') && (strncmp(cmdName, "open", len) == 0)) { hTblPtr = (Tcl_HashTable *) Tcl_GetAssocData(interp, "tclIO", NULL); if (hTblPtr == NULL) { return TCL_OK; } for (hPtr = Tcl_FirstHashEntry(hTblPtr, &hSearch); hPtr != NULL; hPtr = Tcl_NextHashEntry(&hSearch)) { Tcl_AppendElement(interp, (char *)Tcl_GetHashKey(hTblPtr, hPtr)); } return TCL_OK; } if ((cmdName[0] == 'o') && (strncmp(cmdName, "outputbuffered", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } IOQueued = Tcl_OutputBuffered(chan); TclFormatInt(buf, IOQueued); Tcl_AppendResult(interp, buf, (char *)NULL); return TCL_OK; } if ((cmdName[0] == 'q') && (strncmp(cmdName, "queuedcr", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } Tcl_AppendResult(interp, (statePtr->flags & INPUT_SAW_CR) ? "1" : "0", (char *)NULL); return TCL_OK; } if ((cmdName[0] == 'r') && (strncmp(cmdName, "readable", len) == 0)) { hTblPtr = (Tcl_HashTable *) Tcl_GetAssocData(interp, "tclIO", NULL); if (hTblPtr == NULL) { return TCL_OK; } for (hPtr = Tcl_FirstHashEntry(hTblPtr, &hSearch); hPtr != NULL; hPtr = Tcl_NextHashEntry(&hSearch)) { chanPtr = (Channel *)Tcl_GetHashValue(hPtr); statePtr = chanPtr->state; if (statePtr->flags & TCL_READABLE) { Tcl_AppendElement(interp, (char *)Tcl_GetHashKey(hTblPtr, hPtr)); } } return TCL_OK; } if ((cmdName[0] == 'r') && (strncmp(cmdName, "refcount", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } TclFormatInt(buf, statePtr->refCount); Tcl_AppendResult(interp, buf, (char *)NULL); return TCL_OK; } /* * "splice" is actually more a simplified attach facility as provided by * the Thread package. Without the safeguards of a regular command (no * checking that the command is truly cut'able, no mutexes for * thread-safety). Its complementary command is "cut", see above. */ if ((cmdName[0] == 's') && (strncmp(cmdName, "splice", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } Tcl_SpliceChannel(chan); Tcl_RegisterChannel(interp, chan); Tcl_UnregisterChannel(NULL, chan); return TCL_OK; } if ((cmdName[0] == 't') && (strncmp(cmdName, "type", len) == 0)) { if (objc != 3) { Tcl_AppendResult(interp, "channel name required", (char *)NULL); return TCL_ERROR; } Tcl_AppendResult(interp, Tcl_ChannelName(chanPtr->typePtr), (char *)NULL); return TCL_OK; } if ((cmdName[0] == 'w') && (strncmp(cmdName, "writable", len) == 0)) { hTblPtr = (Tcl_HashTable *) Tcl_GetAssocData(interp, "tclIO", NULL); if (hTblPtr == NULL) { return TCL_OK; } for (hPtr = Tcl_FirstHashEntry(hTblPtr, &hSearch); hPtr != NULL; hPtr = Tcl_NextHashEntry(&hSearch)) { chanPtr = (Channel *)Tcl_GetHashValue(hPtr); statePtr = chanPtr->state; if (statePtr->flags & TCL_WRITABLE) { Tcl_AppendElement(interp, (char *)Tcl_GetHashKey(hTblPtr, hPtr)); } } return TCL_OK; } if ((cmdName[0] == 't') && (strncmp(cmdName, "transform", len) == 0)) { /* * Syntax: transform channel -command command */ if (objc != 5) { Tcl_WrongNumArgs(interp, 2, objv, "channel -command cmd"); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[3]), "-command") != 0) { Tcl_AppendResult(interp, "bad argument \"", Tcl_GetString(objv[3]), "\": should be \"-command\"", (char *)NULL); return TCL_ERROR; } return TclChannelTransform(interp, chan, objv[4]); } if ((cmdName[0] == 'u') && (strncmp(cmdName, "unstack", len) == 0)) { /* * Syntax: unstack channel */ if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "channel"); return TCL_ERROR; } return Tcl_UnstackChannel(interp, chan); } Tcl_AppendResult(interp, "bad option \"", cmdName, "\": should be " "cut, clearchannelhandlers, info, isshared, mode, open, " "readable, splice, writable, transform, unstack", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TestChannelEventCmd -- * * This procedure implements the "testchannelevent" command. It is used * to test the Tcl channel event mechanism. * * Results: * A standard Tcl result. * * Side effects: * Creates, deletes and returns channel event handlers. * *---------------------------------------------------------------------- */ static int TestChannelEventCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { Tcl_Obj *resultListPtr; Channel *chanPtr; ChannelState *statePtr; /* state info for channel */ EventScriptRecord *esPtr, *prevEsPtr, *nextEsPtr; const char *cmd; int index, i, mask; Tcl_Size len; if ((objc < 3) || (objc > 5)) { Tcl_WrongNumArgs(interp, 1, objv, "channel cmd ?arg1? ?arg2?"); return TCL_ERROR; } chanPtr = (Channel *)Tcl_GetChannel(interp, Tcl_GetString(objv[1]), NULL); if (chanPtr == NULL) { return TCL_ERROR; } statePtr = chanPtr->state; cmd = Tcl_GetStringFromObj(objv[2], &len); if ((cmd[0] == 'a') && (strncmp(cmd, "add", len) == 0)) { if (objc != 5) { Tcl_WrongNumArgs(interp, 1, objv, "channel add eventSpec script"); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[3]), "readable") == 0) { mask = TCL_READABLE; } else if (strcmp(Tcl_GetString(objv[3]), "writable") == 0) { mask = TCL_WRITABLE; } else if (strcmp(Tcl_GetString(objv[3]), "none") == 0) { mask = 0; } else { Tcl_AppendResult(interp, "bad event name \"", Tcl_GetString(objv[3]), "\": must be readable, writable, or none", (char *)NULL); return TCL_ERROR; } esPtr = (EventScriptRecord *)Tcl_Alloc(sizeof(EventScriptRecord)); esPtr->nextPtr = statePtr->scriptRecordPtr; statePtr->scriptRecordPtr = esPtr; esPtr->chanPtr = chanPtr; esPtr->interp = interp; esPtr->mask = mask; esPtr->scriptPtr = objv[4]; Tcl_IncrRefCount(esPtr->scriptPtr); Tcl_CreateChannelHandler((Tcl_Channel) chanPtr, mask, TclChannelEventScriptInvoker, esPtr); return TCL_OK; } if ((cmd[0] == 'd') && (strncmp(cmd, "delete", len) == 0)) { if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "channel delete index"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[3], &index) == TCL_ERROR) { return TCL_ERROR; } if (index < 0) { Tcl_AppendResult(interp, "bad event index: ", Tcl_GetString(objv[3]), ": must be nonnegative", (char *)NULL); return TCL_ERROR; } for (i = 0, esPtr = statePtr->scriptRecordPtr; (i < index) && (esPtr != NULL); i++, esPtr = esPtr->nextPtr) { /* Empty loop body. */ } if (esPtr == NULL) { Tcl_AppendResult(interp, "bad event index ", Tcl_GetString(objv[3]), ": out of range", (char *)NULL); return TCL_ERROR; } if (esPtr == statePtr->scriptRecordPtr) { statePtr->scriptRecordPtr = esPtr->nextPtr; } else { for (prevEsPtr = statePtr->scriptRecordPtr; (prevEsPtr != NULL) && (prevEsPtr->nextPtr != esPtr); prevEsPtr = prevEsPtr->nextPtr) { /* Empty loop body. */ } if (prevEsPtr == NULL) { Tcl_Panic("TestChannelEventCmd: damaged event script list"); } prevEsPtr->nextPtr = esPtr->nextPtr; } Tcl_DeleteChannelHandler((Tcl_Channel) chanPtr, TclChannelEventScriptInvoker, esPtr); Tcl_DecrRefCount(esPtr->scriptPtr); Tcl_Free(esPtr); return TCL_OK; } if ((cmd[0] == 'l') && (strncmp(cmd, "list", len) == 0)) { if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "channel list"); return TCL_ERROR; } resultListPtr = Tcl_GetObjResult(interp); for (esPtr = statePtr->scriptRecordPtr; esPtr != NULL; esPtr = esPtr->nextPtr) { if (esPtr->mask) { Tcl_ListObjAppendElement(interp, resultListPtr, Tcl_NewStringObj( (esPtr->mask == TCL_READABLE) ? "readable" : "writable", -1)); } else { Tcl_ListObjAppendElement(interp, resultListPtr, Tcl_NewStringObj("none", -1)); } Tcl_ListObjAppendElement(interp, resultListPtr, esPtr->scriptPtr); } Tcl_SetObjResult(interp, resultListPtr); return TCL_OK; } if ((cmd[0] == 'r') && (strncmp(cmd, "removeall", len) == 0)) { if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "channel removeall"); return TCL_ERROR; } for (esPtr = statePtr->scriptRecordPtr; esPtr != NULL; esPtr = nextEsPtr) { nextEsPtr = esPtr->nextPtr; Tcl_DeleteChannelHandler((Tcl_Channel) chanPtr, TclChannelEventScriptInvoker, esPtr); Tcl_DecrRefCount(esPtr->scriptPtr); Tcl_Free(esPtr); } statePtr->scriptRecordPtr = NULL; return TCL_OK; } if ((cmd[0] == 's') && (strncmp(cmd, "set", len) == 0)) { if (objc != 5) { Tcl_WrongNumArgs(interp, 1, objv, "channel delete index event"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[3], &index) == TCL_ERROR) { return TCL_ERROR; } if (index < 0) { Tcl_AppendResult(interp, "bad event index: ", Tcl_GetString(objv[3]), ": must be nonnegative", (char *)NULL); return TCL_ERROR; } for (i = 0, esPtr = statePtr->scriptRecordPtr; (i < index) && (esPtr != NULL); i++, esPtr = esPtr->nextPtr) { /* Empty loop body. */ } if (esPtr == NULL) { Tcl_AppendResult(interp, "bad event index ", Tcl_GetString(objv[3]), ": out of range", (char *)NULL); return TCL_ERROR; } if (strcmp(Tcl_GetString(objv[4]), "readable") == 0) { mask = TCL_READABLE; } else if (strcmp(Tcl_GetString(objv[4]), "writable") == 0) { mask = TCL_WRITABLE; } else if (strcmp(Tcl_GetString(objv[4]), "none") == 0) { mask = 0; } else { Tcl_AppendResult(interp, "bad event name \"", Tcl_GetString(objv[4]), "\": must be readable, writable, or none", (char *)NULL); return TCL_ERROR; } esPtr->mask = mask; Tcl_CreateChannelHandler((Tcl_Channel) chanPtr, mask, TclChannelEventScriptInvoker, esPtr); return TCL_OK; } Tcl_AppendResult(interp, "bad command ", cmd, ", must be one of " "add, delete, list, set, or removeall", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TestSocketCmd -- * * Implements the Tcl "testsocket" debugging command and its * subcommands. This is part of the testing environment. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ #define TCP_ASYNC_TEST_MODE (1<<8) /* Async testing activated. Do not * automatically continue connection * process. */ static int TestSocketCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Interpreter for result. */ int objc, /* Count of additional args. */ Tcl_Obj *const *objv) /* Additional args. */ { const char *cmdName; /* Sub command. */ Tcl_Size len; /* Length of subcommand string. */ if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?additional args..?"); return TCL_ERROR; } cmdName = Tcl_GetStringFromObj(objv[1], &len); if ((cmdName[0] == 't') && (strncmp(cmdName, "testflags", len) == 0)) { Tcl_Channel hChannel; int modePtr; int testMode; TcpState *statePtr; /* Set test value in the socket driver */ /* Check for argument "channel name" */ if (objc < 4) { Tcl_WrongNumArgs(interp, 2, objv, "channel flags"); return TCL_ERROR; } hChannel = Tcl_GetChannel(interp, Tcl_GetString(objv[2]), &modePtr); if ( NULL == hChannel ) { Tcl_AppendResult(interp, "unknown channel:", Tcl_GetString(objv[2]), (char *)NULL); return TCL_ERROR; } statePtr = (TcpState *)Tcl_GetChannelInstanceData(hChannel); if ( NULL == statePtr) { Tcl_AppendResult(interp, "No channel instance data:", Tcl_GetString(objv[2]), (char *)NULL); return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[3], &testMode) != TCL_OK) { return TCL_ERROR; } if (testMode) { statePtr->flags |= TCP_ASYNC_TEST_MODE; } else { statePtr->flags &= ~TCP_ASYNC_TEST_MODE; } return TCL_OK; } Tcl_AppendResult(interp, "bad option \"", cmdName, "\": should be " "testflags", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TestServiceModeCmd -- * * This procedure implements the "testservicemode" command which gets or * sets the current Tcl ServiceMode. There are several tests which open * a file and assign various handlers to it. For these tests to be * deterministic it is important that file events not be processed until * all of the handlers are in place. * * Results: * A standard Tcl result. * * Side effects: * May change the ServiceMode setting. * *---------------------------------------------------------------------- */ static int TestServiceModeCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const *objv) /* Arguments. */ { int newmode, oldmode; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?newmode?"); return TCL_ERROR; } oldmode = (Tcl_GetServiceMode() != TCL_SERVICE_NONE); if (objc == 2) { if (Tcl_GetIntFromObj(interp, objv[1], &newmode) == TCL_ERROR) { return TCL_ERROR; } if (newmode == 0) { Tcl_SetServiceMode(TCL_SERVICE_NONE); } else { Tcl_SetServiceMode(TCL_SERVICE_ALL); } } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(oldmode)); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestWrongNumArgsCmd -- * * Test the Tcl_WrongNumArgs function. * * Results: * Standard Tcl result. * * Side effects: * Sets interpreter result. * *---------------------------------------------------------------------- */ static int TestWrongNumArgsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size i, length; const char *msg; if (objc < 3) { goto insufArgs; } if (Tcl_GetIntForIndex(interp, objv[1], TCL_INDEX_NONE, &i) != TCL_OK) { return TCL_ERROR; } msg = Tcl_GetStringFromObj(objv[2], &length); if (length == 0) { msg = NULL; } if (i > objc - 3) { /* * Asked for more arguments than were given. */ insufArgs: Tcl_AppendResult(interp, "insufficient arguments", (char *)NULL); return TCL_ERROR; } Tcl_WrongNumArgs(interp, i, &(objv[3]), msg); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestGetIndexFromObjStructCmd -- * * Test the Tcl_GetIndexFromObjStruct function. * * Results: * Standard Tcl result. * * Side effects: * Sets interpreter result. * *---------------------------------------------------------------------- */ static int TestGetIndexFromObjStructCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *const ary[] = { "a", "b", "c", "d", "ee", "ff", NULL, NULL }; int target, flags = 0; signed char idx[8]; if (objc != 3 && objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "argument targetvalue ?flags?"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[2], &target) != TCL_OK) { return TCL_ERROR; } if ((objc > 3) && (Tcl_GetIntFromObj(interp, objv[3], &flags) != TCL_OK)) { return TCL_ERROR; } memset(idx, 85, sizeof(idx)); if (Tcl_GetIndexFromObjStruct(interp, (Tcl_GetString(objv[1])[0] ? objv[1] : NULL), ary, 2*sizeof(char *), "dummy", flags, &idx[1]) != TCL_OK) { return TCL_ERROR; } if (idx[0] != 85 || idx[2] != 85) { Tcl_AppendResult(interp, "Tcl_GetIndexFromObjStruct overwrites bytes near index variable", (char *)NULL); return TCL_ERROR; } else if (idx[1] != target) { char buffer[64]; snprintf(buffer, sizeof(buffer), "%d", idx[1]); Tcl_AppendResult(interp, "index value comparison failed: got ", buffer, (char *)NULL); snprintf(buffer, sizeof(buffer), "%d", target); Tcl_AppendResult(interp, " when ", buffer, " expected", (char *)NULL); return TCL_ERROR; } Tcl_WrongNumArgs(interp, objc, objv, NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestFilesystemCmd -- * * This procedure implements the "testfilesystem" command. It is used to * test Tcl_FSRegister, Tcl_FSUnregister, and can be used to test that * the pluggable filesystem works. * * Results: * A standard Tcl result. * * Side effects: * Inserts or removes a filesystem from Tcl's stack. * *---------------------------------------------------------------------- */ static int TestFilesystemCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int res, boolVal; const char *msg; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "boolean"); return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[1], &boolVal) != TCL_OK) { return TCL_ERROR; } if (boolVal) { res = Tcl_FSRegister(interp, &testReportingFilesystem); msg = (res == TCL_OK) ? "registered" : "failed"; } else { res = Tcl_FSUnregister(&testReportingFilesystem); msg = (res == TCL_OK) ? "unregistered" : "failed"; } Tcl_SetObjResult(interp, Tcl_NewStringObj(msg , -1)); return res; } static int TestReportInFilesystem( Tcl_Obj *pathPtr, void **clientDataPtr) { static Tcl_Obj *lastPathPtr = NULL; Tcl_Obj *newPathPtr; if (pathPtr == lastPathPtr) { /* Reject all files second time around */ return -1; } /* Try to claim all files first time around */ newPathPtr = Tcl_DuplicateObj(pathPtr); lastPathPtr = newPathPtr; Tcl_IncrRefCount(newPathPtr); if (Tcl_FSGetFileSystemForPath(newPathPtr) == NULL) { /* Nothing claimed it. Therefore we don't either */ Tcl_DecrRefCount(newPathPtr); lastPathPtr = NULL; return -1; } lastPathPtr = NULL; *clientDataPtr = newPathPtr; return TCL_OK; } /* * Simple helper function to extract the native vfs representation of a path * object, or NULL if no such representation exists. */ static Tcl_Obj * TestReportGetNativePath( Tcl_Obj *pathPtr) { return (Tcl_Obj*) Tcl_FSGetInternalRep(pathPtr, &testReportingFilesystem); } static void TestReportFreeInternalRep( void *clientData) { Tcl_Obj *nativeRep = (Tcl_Obj *) clientData; if (nativeRep != NULL) { /* Free the path */ Tcl_DecrRefCount(nativeRep); } } static void * TestReportDupInternalRep( void *clientData) { Tcl_Obj *original = (Tcl_Obj *) clientData; Tcl_IncrRefCount(original); return clientData; } static void TestReport( const char *cmd, Tcl_Obj *path, Tcl_Obj *arg2) { Tcl_Interp *interp = (Tcl_Interp *) Tcl_FSData(&testReportingFilesystem); if (interp == NULL) { /* This is bad, but not much we can do about it */ } else { Tcl_Obj *savedResult; Tcl_DString ds; Tcl_DStringInit(&ds); Tcl_DStringAppend(&ds, "lappend filesystemReport ", -1); Tcl_DStringStartSublist(&ds); Tcl_DStringAppendElement(&ds, cmd); if (path != NULL) { Tcl_DStringAppendElement(&ds, Tcl_GetString(path)); } if (arg2 != NULL) { Tcl_DStringAppendElement(&ds, Tcl_GetString(arg2)); } Tcl_DStringEndSublist(&ds); savedResult = Tcl_GetObjResult(interp); Tcl_IncrRefCount(savedResult); Tcl_SetObjResult(interp, Tcl_NewObj()); Tcl_EvalEx(interp, Tcl_DStringValue(&ds), TCL_INDEX_NONE, 0); Tcl_DStringFree(&ds); Tcl_ResetResult(interp); Tcl_SetObjResult(interp, savedResult); Tcl_DecrRefCount(savedResult); } } static int TestReportStat( Tcl_Obj *path, /* Path of file to stat (in current CP). */ Tcl_StatBuf *buf) /* Filled with results of stat call. */ { TestReport("stat", path, NULL); return Tcl_FSStat(TestReportGetNativePath(path), buf); } static int TestReportLstat( Tcl_Obj *path, /* Path of file to stat (in current CP). */ Tcl_StatBuf *buf) /* Filled with results of stat call. */ { TestReport("lstat", path, NULL); return Tcl_FSLstat(TestReportGetNativePath(path), buf); } static int TestReportAccess( Tcl_Obj *path, /* Path of file to access (in current CP). */ int mode) /* Permission setting. */ { TestReport("access", path, NULL); return Tcl_FSAccess(TestReportGetNativePath(path), mode); } static Tcl_Channel TestReportOpenFileChannel( Tcl_Interp *interp, /* Interpreter for error reporting; can be * NULL. */ Tcl_Obj *fileName, /* Name of file to open. */ int mode, /* POSIX open mode. */ int permissions) /* If the open involves creating a file, with * what modes to create it? */ { TestReport("open", fileName, NULL); return TclpOpenFileChannel(interp, TestReportGetNativePath(fileName), mode, permissions); } static int TestReportMatchInDirectory( Tcl_Interp *interp, /* Interpreter for error messages. */ Tcl_Obj *resultPtr, /* Object to lappend results. */ Tcl_Obj *dirPtr, /* Contains path to directory to search. */ const char *pattern, /* Pattern to match against. */ Tcl_GlobTypeData *types) /* Object containing list of acceptable types. * May be NULL. */ { if (types != NULL && types->type & TCL_GLOB_TYPE_MOUNT) { TestReport("matchmounts", dirPtr, NULL); return TCL_OK; } else { TestReport("matchindirectory", dirPtr, NULL); return Tcl_FSMatchInDirectory(interp, resultPtr, TestReportGetNativePath(dirPtr), pattern, types); } } static int TestReportChdir( Tcl_Obj *dirName) { TestReport("chdir", dirName, NULL); return Tcl_FSChdir(TestReportGetNativePath(dirName)); } static int TestReportLoadFile( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Obj *fileName, /* Name of the file containing the desired * code. */ Tcl_LoadHandle *handlePtr, /* Filled with token for dynamically loaded * file which will be passed back to * (*unloadProcPtr)() to unload the file. */ Tcl_FSUnloadFileProc **unloadProcPtr) /* Filled with address of Tcl_FSUnloadFileProc * function which should be used for * this file. */ { TestReport("loadfile", fileName, NULL); return Tcl_FSLoadFile(interp, TestReportGetNativePath(fileName), NULL, NULL, NULL, NULL, handlePtr, unloadProcPtr); } static Tcl_Obj * TestReportLink( Tcl_Obj *path, /* Path of file to readlink or link */ Tcl_Obj *to, /* Path of file to link to, or NULL */ int linkType) { TestReport("link", path, to); return Tcl_FSLink(TestReportGetNativePath(path), to, linkType); } static int TestReportRenameFile( Tcl_Obj *src, /* Pathname of file or dir to be renamed * (UTF-8). */ Tcl_Obj *dst) /* New pathname of file or directory * (UTF-8). */ { TestReport("renamefile", src, dst); return Tcl_FSRenameFile(TestReportGetNativePath(src), TestReportGetNativePath(dst)); } static int TestReportCopyFile( Tcl_Obj *src, /* Pathname of file to be copied (UTF-8). */ Tcl_Obj *dst) /* Pathname of file to copy to (UTF-8). */ { TestReport("copyfile", src, dst); return Tcl_FSCopyFile(TestReportGetNativePath(src), TestReportGetNativePath(dst)); } static int TestReportDeleteFile( Tcl_Obj *path) /* Pathname of file to be removed (UTF-8). */ { TestReport("deletefile", path, NULL); return Tcl_FSDeleteFile(TestReportGetNativePath(path)); } static int TestReportCreateDirectory( Tcl_Obj *path) /* Pathname of directory to create (UTF-8). */ { TestReport("createdirectory", path, NULL); return Tcl_FSCreateDirectory(TestReportGetNativePath(path)); } static int TestReportCopyDirectory( Tcl_Obj *src, /* Pathname of directory to be copied * (UTF-8). */ Tcl_Obj *dst, /* Pathname of target directory (UTF-8). */ Tcl_Obj **errorPtr) /* If non-NULL, to be filled with UTF-8 name * of file causing error. */ { TestReport("copydirectory", src, dst); return Tcl_FSCopyDirectory(TestReportGetNativePath(src), TestReportGetNativePath(dst), errorPtr); } static int TestReportRemoveDirectory( Tcl_Obj *path, /* Pathname of directory to be removed * (UTF-8). */ int recursive, /* If non-zero, removes directories that * are nonempty. Otherwise, will only remove * empty directories. */ Tcl_Obj **errorPtr) /* If non-NULL, to be filled with UTF-8 name * of file causing error. */ { TestReport("removedirectory", path, NULL); return Tcl_FSRemoveDirectory(TestReportGetNativePath(path), recursive, errorPtr); } static const char *const * TestReportFileAttrStrings( Tcl_Obj *fileName, Tcl_Obj **objPtrRef) { TestReport("fileattributestrings", fileName, NULL); return Tcl_FSFileAttrStrings(TestReportGetNativePath(fileName), objPtrRef); } static int TestReportFileAttrsGet( Tcl_Interp *interp, /* The interpreter for error reporting. */ int index, /* index of the attribute command. */ Tcl_Obj *fileName, /* filename we are operating on. */ Tcl_Obj **objPtrRef) /* for output. */ { TestReport("fileattributesget", fileName, NULL); return Tcl_FSFileAttrsGet(interp, index, TestReportGetNativePath(fileName), objPtrRef); } static int TestReportFileAttrsSet( Tcl_Interp *interp, /* The interpreter for error reporting. */ int index, /* index of the attribute command. */ Tcl_Obj *fileName, /* filename we are operating on. */ Tcl_Obj *objPtr) /* for input. */ { TestReport("fileattributesset", fileName, objPtr); return Tcl_FSFileAttrsSet(interp, index, TestReportGetNativePath(fileName), objPtr); } static int TestReportUtime( Tcl_Obj *fileName, struct utimbuf *tval) { TestReport("utime", fileName, NULL); return Tcl_FSUtime(TestReportGetNativePath(fileName), tval); } static int TestReportNormalizePath( TCL_UNUSED(Tcl_Interp *), Tcl_Obj *pathPtr, int nextCheckpoint) { TestReport("normalizepath", pathPtr, NULL); return nextCheckpoint; } static int SimplePathInFilesystem( Tcl_Obj *pathPtr, TCL_UNUSED(void **)) { const char *str = Tcl_GetString(pathPtr); if (strncmp(str, "simplefs:/", 10)) { return -1; } return TCL_OK; } /* * This is a slightly 'hacky' filesystem which is used just to test a few * important features of the vfs code: (1) that you can load a shared library * from a vfs, (2) that when copying files from one fs to another, the 'mtime' * is preserved. (3) that recursive cross-filesystem directory copies have the * correct behaviour with/without -force. * * It treats any file in 'simplefs:/' as a file, which it routes to the * current directory. The real file it uses is whatever follows the trailing * '/' (e.g. 'foo' in 'simplefs:/foo'), and that file exists or not according * to what is in the native pwd. * * Please do not consider this filesystem a model of how things are to be * done. It is quite the opposite! But, it does allow us to test some * important features. */ static int TestSimpleFilesystemCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int res, boolVal; const char *msg; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "boolean"); return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[1], &boolVal) != TCL_OK) { return TCL_ERROR; } if (boolVal) { res = Tcl_FSRegister(interp, &simpleFilesystem); msg = (res == TCL_OK) ? "registered" : "failed"; } else { res = Tcl_FSUnregister(&simpleFilesystem); msg = (res == TCL_OK) ? "unregistered" : "failed"; } Tcl_SetObjResult(interp, Tcl_NewStringObj(msg , -1)); return res; } /* * Treats a file name 'simplefs:/foo' by using the file 'foo' in the current * (native) directory. */ static Tcl_Obj * SimpleRedirect( Tcl_Obj *pathPtr) /* Name of file to copy. */ { Tcl_Size len; const char *str; Tcl_Obj *origPtr; /* * We assume the same name in the current directory is ok. */ str = Tcl_GetStringFromObj(pathPtr, &len); if (len < 10 || strncmp(str, "simplefs:/", 10)) { /* Probably shouldn't ever reach here */ Tcl_IncrRefCount(pathPtr); return pathPtr; } origPtr = Tcl_NewStringObj(str+10, -1); Tcl_IncrRefCount(origPtr); return origPtr; } static int SimpleMatchInDirectory( Tcl_Interp *interp, /* Interpreter for error * messages. */ Tcl_Obj *resultPtr, /* Object to lappend results. */ Tcl_Obj *dirPtr, /* Contains path to directory to search. */ const char *pattern, /* Pattern to match against. */ Tcl_GlobTypeData *types) /* Object containing list of acceptable types. * May be NULL. */ { int res; Tcl_Obj *origPtr; Tcl_Obj *resPtr; /* We only provide a new volume, therefore no mounts at all */ if (types != NULL && types->type & TCL_GLOB_TYPE_MOUNT) { return TCL_OK; } /* * We assume the same name in the current directory is ok. */ resPtr = Tcl_NewObj(); Tcl_IncrRefCount(resPtr); origPtr = SimpleRedirect(dirPtr); res = Tcl_FSMatchInDirectory(interp, resPtr, origPtr, pattern, types); if (res == TCL_OK) { Tcl_Size gLength, j; Tcl_ListObjLength(NULL, resPtr, &gLength); for (j = 0; j < gLength; j++) { Tcl_Obj *gElt, *nElt; Tcl_ListObjIndex(NULL, resPtr, j, &gElt); nElt = Tcl_NewStringObj("simplefs:/",10); Tcl_AppendObjToObj(nElt, gElt); Tcl_ListObjAppendElement(NULL, resultPtr, nElt); } } Tcl_DecrRefCount(origPtr); Tcl_DecrRefCount(resPtr); return res; } static Tcl_Channel SimpleOpenFileChannel( Tcl_Interp *interp, /* Interpreter for error reporting; can be * NULL. */ Tcl_Obj *pathPtr, /* Name of file to open. */ int mode, /* POSIX open mode. */ int permissions) /* If the open involves creating a file, with * what modes to create it? */ { Tcl_Obj *tempPtr; Tcl_Channel chan; if ((mode & O_ACCMODE) != O_RDONLY) { Tcl_AppendResult(interp, "read-only", (char *)NULL); return NULL; } tempPtr = SimpleRedirect(pathPtr); chan = Tcl_FSOpenFileChannel(interp, tempPtr, "r", permissions); Tcl_DecrRefCount(tempPtr); return chan; } static int SimpleAccess( Tcl_Obj *pathPtr, /* Path of file to access (in current CP). */ int mode) /* Permission setting. */ { Tcl_Obj *tempPtr = SimpleRedirect(pathPtr); int res = Tcl_FSAccess(tempPtr, mode); Tcl_DecrRefCount(tempPtr); return res; } static int SimpleStat( Tcl_Obj *pathPtr, /* Path of file to stat (in current CP). */ Tcl_StatBuf *bufPtr) /* Filled with results of stat call. */ { Tcl_Obj *tempPtr = SimpleRedirect(pathPtr); int res = Tcl_FSStat(tempPtr, bufPtr); Tcl_DecrRefCount(tempPtr); return res; } static Tcl_Obj * SimpleListVolumes(void) { /* Add one new volume */ Tcl_Obj *retVal; retVal = Tcl_NewStringObj("simplefs:/", -1); Tcl_IncrRefCount(retVal); return retVal; } /* * Used to check operations of Tcl_UtfNext. * * Usage: testutfnext -bytestring $bytes */ static int TestUtfNextCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Size numBytes; char *bytes; const char *result, *first; char buffer[32]; static const char tobetested[] = "A\xA0\xC0\xC1\xC2\xD0\xE0\xE8\xF2\xF7\xF8\xFE\xFF"; const char *p = tobetested; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "?-bytestring? bytes"); return TCL_ERROR; } bytes = Tcl_GetStringFromObj(objv[1], &numBytes); if ((size_t)numBytes > sizeof(buffer) - 4) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "\"testutfnext\" can only handle %" TCL_Z_MODIFIER "u bytes", sizeof(buffer) - 4)); return TCL_ERROR; } memcpy(buffer + 1, bytes, numBytes); buffer[0] = buffer[numBytes + 1] = buffer[numBytes + 2] = buffer[numBytes + 3] = '\xA0'; first = result = Tcl_UtfNext(buffer + 1); while ((buffer[0] = *p++) != '\0') { /* Run Tcl_UtfNext with many more possible bytes at src[-1], all should give the same result */ result = Tcl_UtfNext(buffer + 1); if (first != result) { Tcl_AppendResult(interp, "Tcl_UtfNext is not supposed to read src[-1]", (char *)NULL); return TCL_ERROR; } } p = tobetested; while ((buffer[numBytes + 1] = *p++) != '\0') { /* Run Tcl_UtfNext with many more possible bytes at src[end], all should give the same result */ result = Tcl_UtfNext(buffer + 1); if (first != result) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Tcl_UtfNext is not supposed to read src[end]\n" "Different result when src[end] is %#x", UCHAR(p[-1]))); return TCL_ERROR; } } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(first - buffer - 1)); return TCL_OK; } /* * Used to check operations of Tcl_UtfPrev. * * Usage: testutfprev $bytes $offset */ static int TestUtfPrevCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Size numBytes, offset; char *bytes; const char *result; if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "bytes ?offset?"); return TCL_ERROR; } bytes = Tcl_GetStringFromObj(objv[1], &numBytes); if (objc == 3) { if (TCL_OK != Tcl_GetIntForIndex(interp, objv[2], numBytes, &offset)) { return TCL_ERROR; } if (offset == TCL_INDEX_NONE) { offset = 0; } if (offset > numBytes) { offset = numBytes; } } else { offset = numBytes; } result = Tcl_UtfPrev(bytes + offset, bytes); Tcl_SetObjResult(interp, Tcl_NewWideIntObj(result - bytes)); return TCL_OK; } /* * Used to check correct string-length determining in Tcl_NumUtfChars */ static int TestNumUtfCharsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc > 1) { Tcl_Size numBytes, len, limit = TCL_INDEX_NONE; const char *bytes = Tcl_GetStringFromObj(objv[1], &numBytes); if (objc > 2) { if (Tcl_GetIntForIndex(interp, objv[2], numBytes, &limit) != TCL_OK) { return TCL_ERROR; } if (limit > numBytes + 1) { limit = numBytes + 1; } } len = Tcl_NumUtfChars(bytes, limit); Tcl_SetObjResult(interp, Tcl_NewWideIntObj(len)); } return TCL_OK; } /* * Used to check correct operation of Tcl_GetUniChar * testgetunichar STRING INDEX * This differs from just using "string index" in being a direct * call to Tcl_GetUniChar without any prior range checking. */ static int TestGetUniCharCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter */ int objc, /* Number of arguments */ Tcl_Obj *const objv[] /* Argument strings */ ) { int index; int c ; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "STRING INDEX"); return TCL_ERROR; } Tcl_GetIntFromObj(interp, objv[2], &index); c = Tcl_GetUniChar(objv[1], index); Tcl_SetObjResult(interp, Tcl_NewIntObj(c)); return TCL_OK; } /* * Used to check correct operation of Tcl_UtfFindFirst */ static int TestFindFirstCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc > 1) { int len = -1; if (objc > 2) { (void) Tcl_GetIntFromObj(interp, objv[2], &len); } Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_UtfFindFirst(Tcl_GetString(objv[1]), len), -1)); } return TCL_OK; } /* * Used to check correct operation of Tcl_UtfFindLast */ static int TestFindLastCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc > 1) { int len = -1; if (objc > 2) { (void) Tcl_GetIntFromObj(interp, objv[2], &len); } Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_UtfFindLast(Tcl_GetString(objv[1]), len), -1)); } return TCL_OK; } static int TestGetIntForIndexCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Size result; Tcl_WideInt endvalue; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "index endvalue"); return TCL_ERROR; } if (Tcl_GetWideIntFromObj(interp, objv[2], &endvalue) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetIntForIndex(interp, objv[1], endvalue, &result) != TCL_OK) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(result)); return TCL_OK; } #if defined(HAVE_CPUID) && !defined(MAC_OSX_TCL) /* *---------------------------------------------------------------------- * * TestcpuidCmd -- * * Retrieves CPU ID information. * * Usage: * testwincpuid * * Parameters: * eax - The value to pass in the EAX register to a CPUID instruction. * * Results: * Returns a four-element list containing the values from the EAX, EBX, * ECX and EDX registers returned from the CPUID instruction. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestcpuidCmd( TCL_UNUSED(void *), Tcl_Interp* interp, /* Tcl interpreter */ int objc, /* Parameter count */ Tcl_Obj *const * objv) /* Parameter vector */ { int status, index, i; int regs[4]; Tcl_Obj *regsObjs[4]; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "eax"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[1], &index) != TCL_OK) { return TCL_ERROR; } status = TclWinCPUID(index, regs); if (status != TCL_OK) { Tcl_SetObjResult(interp, Tcl_NewStringObj("operation not available", -1)); return status; } for (i=0 ; i<4 ; ++i) { regsObjs[i] = Tcl_NewWideIntObj(regs[i]); } Tcl_SetObjResult(interp, Tcl_NewListObj(4, regsObjs)); return TCL_OK; } #endif /* * Used to do basic checks of the TCL_HASH_KEY_SYSTEM_HASH flag */ static int TestHashSystemHashCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { static const Tcl_HashKeyType hkType = { TCL_HASH_KEY_TYPE_VERSION, TCL_HASH_KEY_SYSTEM_HASH, NULL, NULL, NULL, NULL }; Tcl_HashTable hash; Tcl_HashEntry *hPtr; int i, isNew, limit = 100; if (objc>1 && Tcl_GetIntFromObj(interp, objv[1], &limit)!=TCL_OK) { return TCL_ERROR; } Tcl_InitCustomHashTable(&hash, TCL_CUSTOM_TYPE_KEYS, &hkType); if (hash.numEntries != 0) { Tcl_AppendResult(interp, "non-zero initial size", (char *)NULL); Tcl_DeleteHashTable(&hash); return TCL_ERROR; } for (i=0 ; i 1) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(sizeof(long))); return TCL_OK; } static int NREUnwind_callback( void *data[], Tcl_Interp *interp, TCL_UNUSED(int) /*result*/) { void *cStackPtr = TclGetCStackPtr(); if (data[0] == INT2PTR(-1)) { Tcl_NRAddCallback(interp, NREUnwind_callback, cStackPtr, INT2PTR(-1), INT2PTR(-1), NULL); } else if (data[1] == INT2PTR(-1)) { Tcl_NRAddCallback(interp, NREUnwind_callback, data[0], cStackPtr, INT2PTR(-1), NULL); } else if (data[2] == INT2PTR(-1)) { Tcl_NRAddCallback(interp, NREUnwind_callback, data[0], data[1], cStackPtr, NULL); } else { Tcl_Obj *idata[3]; idata[0] = Tcl_NewWideIntObj(((char *)data[1] - (char *)data[0])); idata[1] = Tcl_NewWideIntObj(((char *)data[2] - (char *)data[0])); idata[2] = Tcl_NewWideIntObj(((char *)cStackPtr - (char *)data[0])); Tcl_SetObjResult(interp, Tcl_NewListObj(3, idata)); } return TCL_OK; } static int TestNREUnwind( TCL_UNUSED(void *), Tcl_Interp *interp, TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *) /*objv*/) { /* * Insure that callbacks effectively run at the proper level during the * unwinding of the NRE stack. */ Tcl_NRAddCallback(interp, NREUnwind_callback, INT2PTR(-1), INT2PTR(-1), INT2PTR(-1), NULL); return TCL_OK; } static int TestNRELevels( TCL_UNUSED(void *), Tcl_Interp *interp, TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *) /*objv*/) { Interp *iPtr = (Interp *) interp; static Tcl_Size *refDepth = NULL; Tcl_Size depth; Tcl_Obj *levels[6]; Tcl_Size i = 0; NRE_callback *cbPtr = iPtr->execEnvPtr->callbackPtr; if (refDepth == NULL) { refDepth = (ptrdiff_t *)TclGetCStackPtr(); } depth = (refDepth - (ptrdiff_t *)TclGetCStackPtr()); levels[0] = Tcl_NewWideIntObj(depth); levels[1] = Tcl_NewWideIntObj(iPtr->numLevels); levels[2] = Tcl_NewWideIntObj(iPtr->cmdFramePtr->level); levels[3] = Tcl_NewWideIntObj(iPtr->varFramePtr->level); levels[4] = Tcl_NewWideIntObj(iPtr->execEnvPtr->execStackPtr->tosPtr - iPtr->execEnvPtr->execStackPtr->stackWords); while (cbPtr) { i++; cbPtr = cbPtr->nextPtr; } levels[5] = Tcl_NewWideIntObj(i); Tcl_SetObjResult(interp, Tcl_NewListObj(6, levels)); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestconcatobjCmd -- * * This procedure implements the "testconcatobj" command. It is used * to test that Tcl_ConcatObj does indeed return a fresh Tcl_Obj in all * cases and that it never corrupts its arguments. In other words, that * [Bug 1447328] was fixed properly. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestconcatobjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *) /*objv*/) { Tcl_Obj *list1Ptr, *list2Ptr, *emptyPtr, *concatPtr, *tmpPtr; int result = TCL_OK; Tcl_Size len; Tcl_Obj *objv[3]; /* * Set the start of the error message as obj result; it will be cleared at * the end if no errors were found. */ Tcl_SetObjResult(interp, Tcl_NewStringObj("Tcl_ConcatObj is unsafe:", -1)); emptyPtr = Tcl_NewObj(); list1Ptr = Tcl_NewStringObj("foo bar sum", -1); Tcl_ListObjLength(NULL, list1Ptr, &len); Tcl_InvalidateStringRep(list1Ptr); list2Ptr = Tcl_NewStringObj("eeny meeny", -1); Tcl_ListObjLength(NULL, list2Ptr, &len); Tcl_InvalidateStringRep(list2Ptr); /* * Verify that concat'ing a list obj with one or more empty strings does * return a fresh Tcl_Obj (see also [Bug 2055782]). */ tmpPtr = Tcl_DuplicateObj(list1Ptr); objv[0] = tmpPtr; objv[1] = emptyPtr; concatPtr = Tcl_ConcatObj(2, objv); if (concatPtr->refCount != 0) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (a) concatObj does not have refCount 0", (char *)NULL); } if (concatPtr == tmpPtr) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (a) concatObj is not a new obj ", (char *)NULL); switch (tmpPtr->refCount) { case 0: Tcl_AppendResult(interp, "(no new refCount)", (char *)NULL); break; case 1: Tcl_AppendResult(interp, "(refCount added)", (char *)NULL); break; default: Tcl_AppendResult(interp, "(more than one refCount added!)", (char *)NULL); Tcl_Panic("extremely unsafe behaviour by Tcl_ConcatObj()"); } tmpPtr = Tcl_DuplicateObj(list1Ptr); objv[0] = tmpPtr; } Tcl_DecrRefCount(concatPtr); Tcl_IncrRefCount(tmpPtr); concatPtr = Tcl_ConcatObj(2, objv); if (concatPtr->refCount != 0) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (b) concatObj does not have refCount 0", (char *)NULL); } if (concatPtr == tmpPtr) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (b) concatObj is not a new obj ", (char *)NULL); switch (tmpPtr->refCount) { case 0: Tcl_AppendResult(interp, "(refCount removed?)", (char *)NULL); Tcl_Panic("extremely unsafe behaviour by Tcl_ConcatObj()"); break; case 1: Tcl_AppendResult(interp, "(no new refCount)", (char *)NULL); break; case 2: Tcl_AppendResult(interp, "(refCount added)", (char *)NULL); Tcl_DecrRefCount(tmpPtr); break; default: Tcl_AppendResult(interp, "(more than one refCount added!)", (char *)NULL); Tcl_Panic("extremely unsafe behaviour by Tcl_ConcatObj()"); } tmpPtr = Tcl_DuplicateObj(list1Ptr); objv[0] = tmpPtr; } Tcl_DecrRefCount(concatPtr); objv[0] = emptyPtr; objv[1] = tmpPtr; objv[2] = emptyPtr; concatPtr = Tcl_ConcatObj(3, objv); if (concatPtr->refCount != 0) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (c) concatObj does not have refCount 0", (char *)NULL); } if (concatPtr == tmpPtr) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (c) concatObj is not a new obj ", (char *)NULL); switch (tmpPtr->refCount) { case 0: Tcl_AppendResult(interp, "(no new refCount)", (char *)NULL); break; case 1: Tcl_AppendResult(interp, "(refCount added)", (char *)NULL); break; default: Tcl_AppendResult(interp, "(more than one refCount added!)", (char *)NULL); Tcl_Panic("extremely unsafe behaviour by Tcl_ConcatObj()"); } tmpPtr = Tcl_DuplicateObj(list1Ptr); objv[1] = tmpPtr; } Tcl_DecrRefCount(concatPtr); Tcl_IncrRefCount(tmpPtr); concatPtr = Tcl_ConcatObj(3, objv); if (concatPtr->refCount != 0) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (d) concatObj does not have refCount 0", (char *)NULL); } if (concatPtr == tmpPtr) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (d) concatObj is not a new obj ", (char *)NULL); switch (tmpPtr->refCount) { case 0: Tcl_AppendResult(interp, "(refCount removed?)", (char *)NULL); Tcl_Panic("extremely unsafe behaviour by Tcl_ConcatObj()"); break; case 1: Tcl_AppendResult(interp, "(no new refCount)", (char *)NULL); break; case 2: Tcl_AppendResult(interp, "(refCount added)", (char *)NULL); Tcl_DecrRefCount(tmpPtr); break; default: Tcl_AppendResult(interp, "(more than one refCount added!)", (char *)NULL); Tcl_Panic("extremely unsafe behaviour by Tcl_ConcatObj()"); } tmpPtr = Tcl_DuplicateObj(list1Ptr); objv[1] = tmpPtr; } Tcl_DecrRefCount(concatPtr); /* * Verify that an unshared list is not corrupted when concat'ing things to * it. */ objv[0] = tmpPtr; objv[1] = list2Ptr; concatPtr = Tcl_ConcatObj(2, objv); if (concatPtr->refCount != 0) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (e) concatObj does not have refCount 0", (char *)NULL); } if (concatPtr == tmpPtr) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (e) concatObj is not a new obj ", (char *)NULL); (void) Tcl_ListObjLength(NULL, concatPtr, &len); switch (tmpPtr->refCount) { case 3: Tcl_AppendResult(interp, "(failed to concat)", (char *)NULL); break; default: Tcl_AppendResult(interp, "(corrupted input!)", (char *)NULL); } if (Tcl_IsShared(tmpPtr)) { Tcl_DecrRefCount(tmpPtr); } tmpPtr = Tcl_DuplicateObj(list1Ptr); objv[0] = tmpPtr; } Tcl_DecrRefCount(concatPtr); objv[0] = tmpPtr; objv[1] = list2Ptr; Tcl_IncrRefCount(tmpPtr); concatPtr = Tcl_ConcatObj(2, objv); if (concatPtr->refCount != 0) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (f) concatObj does not have refCount 0", (char *)NULL); } if (concatPtr == tmpPtr) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (f) concatObj is not a new obj ", (char *)NULL); (void) Tcl_ListObjLength(NULL, concatPtr, &len); switch (tmpPtr->refCount) { case 3: Tcl_AppendResult(interp, "(failed to concat)", (char *)NULL); break; default: Tcl_AppendResult(interp, "(corrupted input!)", (char *)NULL); } if (Tcl_IsShared(tmpPtr)) { Tcl_DecrRefCount(tmpPtr); } tmpPtr = Tcl_DuplicateObj(list1Ptr); objv[0] = tmpPtr; } Tcl_DecrRefCount(concatPtr); objv[0] = tmpPtr; objv[1] = list2Ptr; Tcl_IncrRefCount(tmpPtr); Tcl_IncrRefCount(tmpPtr); concatPtr = Tcl_ConcatObj(2, objv); if (concatPtr->refCount != 0) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (g) concatObj does not have refCount 0", (char *)NULL); } if (concatPtr == tmpPtr) { result = TCL_ERROR; Tcl_AppendResult(interp, "\n\t* (g) concatObj is not a new obj ", (char *)NULL); (void) Tcl_ListObjLength(NULL, concatPtr, &len); switch (tmpPtr->refCount) { case 3: Tcl_AppendResult(interp, "(failed to concat)", (char *)NULL); break; default: Tcl_AppendResult(interp, "(corrupted input!)", (char *)NULL); } Tcl_DecrRefCount(tmpPtr); if (Tcl_IsShared(tmpPtr)) { Tcl_DecrRefCount(tmpPtr); } tmpPtr = Tcl_DuplicateObj(list1Ptr); objv[0] = tmpPtr; } Tcl_DecrRefCount(concatPtr); /* * Clean everything up. Note that we don't actually know how many * references there are to tmpPtr here; in the no-error case, it should be * five... [Bug 2895367] */ Tcl_DecrRefCount(list1Ptr); Tcl_DecrRefCount(list2Ptr); Tcl_DecrRefCount(emptyPtr); while (tmpPtr->refCount > 1) { Tcl_DecrRefCount(tmpPtr); } Tcl_DecrRefCount(tmpPtr); if (result == TCL_OK) { Tcl_ResetResult(interp); } return result; } /* *---------------------------------------------------------------------- * * TestparseargsCmd -- * * This procedure implements the "testparseargs" command. It is used to * test that Tcl_ParseArgsObjv does indeed return the right number of * arguments. In other words, that [Bug 3413857] was fixed properly. * Also test for bug [7cb7409e05] * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_Size ParseMedia( TCL_UNUSED(void *), Tcl_Interp *interp, TCL_UNUSED(Tcl_Size), Tcl_Obj *const *objv, void *dstPtr) { static const char *const mediaOpts[] = {"A4", "Legal", "Letter", NULL}; static const char *const ExtendedMediaOpts[] = { "Paper size is ISO A4", "Paper size is US Legal", "Paper size is US Letter", NULL}; int index; const char **media = (const char **) dstPtr; if (Tcl_GetIndexFromObjStruct(interp, objv[0], mediaOpts, sizeof(char *), "media", 0, &index) != TCL_OK) { return -1; } *media = ExtendedMediaOpts[index]; return 1; } static int TestparseargsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Arguments. */ { static int foo = 0; const char *media = NULL, *color = NULL; Tcl_Size count = objc; Tcl_Obj **remObjv, *result[5]; const Tcl_ArgvInfo argTable[] = { {TCL_ARGV_CONSTANT, "-bool", INT2PTR(1), &foo, "booltest", NULL}, {TCL_ARGV_STRING, "-colormode" , NULL, &color, "color mode", NULL}, {TCL_ARGV_GENFUNC, "-media", (void *)ParseMedia, &media, "media page size", NULL}, TCL_ARGV_AUTO_REST, TCL_ARGV_AUTO_HELP, TCL_ARGV_TABLE_END }; foo = 0; if (Tcl_ParseArgsObjv(interp, argTable, &count, objv, &remObjv)!=TCL_OK) { return TCL_ERROR; } result[0] = Tcl_NewWideIntObj(foo); result[1] = Tcl_NewWideIntObj(count); result[2] = Tcl_NewListObj(count, remObjv); result[3] = Tcl_NewStringObj(color ? color : "NULL", -1); result[4] = Tcl_NewStringObj(media ? media : "NULL", -1); Tcl_SetObjResult(interp, Tcl_NewListObj(5, result)); Tcl_Free(remObjv); return TCL_OK; } /** * Test harness for command and variable resolvers. */ static int InterpCmdResolver( Tcl_Interp *interp, const char *name, TCL_UNUSED(Tcl_Namespace *), TCL_UNUSED(int) /* flags */, Tcl_Command *rPtr) { Interp *iPtr = (Interp *) interp; CallFrame *varFramePtr = iPtr->varFramePtr; Proc *procPtr = (varFramePtr->isProcCallFrame & FRAME_IS_PROC) ? varFramePtr->procPtr : NULL; Namespace *callerNsPtr = varFramePtr->nsPtr; Tcl_Command resolvedCmdPtr = NULL; /* * Just do something special on a cmd literal "z" in two cases: * A) when the caller is a proc "x", and the proc is either in "::" or in "::ns2". * B) the caller's namespace is "ctx1" or "ctx2" */ if ( (name[0] == 'z') && (name[1] == '\0') ) { Namespace *ns2NsPtr = (Namespace *) Tcl_FindNamespace(interp, "::ns2", NULL, 0); if (procPtr != NULL && ((procPtr->cmdPtr->nsPtr == iPtr->globalNsPtr) || (ns2NsPtr != NULL && procPtr->cmdPtr->nsPtr == ns2NsPtr) ) ) { /* * Case A) * * - The context, in which this resolver becomes active, is * determined by the name of the caller proc, which has to be * named "x". * * - To determine the name of the caller proc, the proc is taken * from the topmost stack frame. * * - Note that the context is NOT provided during byte-code * compilation (e.g. in TclProcCompileProc) * * When these conditions hold, this function resolves the * passed-in cmd literal into a cmd "y", which is taken from * the global namespace (for simplicity). */ const char *callingCmdName = Tcl_GetCommandName(interp, (Tcl_Command) procPtr->cmdPtr); if ( callingCmdName[0] == 'x' && callingCmdName[1] == '\0' ) { resolvedCmdPtr = Tcl_FindCommand(interp, "y", NULL, TCL_GLOBAL_ONLY); } } else if (callerNsPtr != NULL) { /* * Case B) * * - The context, in which this resolver becomes active, is * determined by the name of the parent namespace, which has * to be named "ctx1" or "ctx2". * * - To determine the name of the parent namesace, it is taken * from the 2nd highest stack frame. * * - Note that the context can be provided during byte-code * compilation (e.g. in TclProcCompileProc) * * When these conditions hold, this function resolves the * passed-in cmd literal into a cmd "y" or "Y" depending on the * context. The resolved procs are taken from the global * namespace (for simplicity). */ CallFrame *parentFramePtr = varFramePtr->callerPtr; const char *context = parentFramePtr != NULL ? parentFramePtr->nsPtr->name : "(NULL)"; if (strcmp(context, "ctx1") == 0 && (name[0] == 'z') && (name[1] == '\0')) { resolvedCmdPtr = Tcl_FindCommand(interp, "y", NULL, TCL_GLOBAL_ONLY); /* fprintf(stderr, "... y ==> %p\n", resolvedCmdPtr);*/ } else if (strcmp(context, "ctx2") == 0 && (name[0] == 'z') && (name[1] == '\0')) { resolvedCmdPtr = Tcl_FindCommand(interp, "Y", NULL, TCL_GLOBAL_ONLY); /*fprintf(stderr, "... Y ==> %p\n", resolvedCmdPtr);*/ } } if (resolvedCmdPtr != NULL) { *rPtr = resolvedCmdPtr; return TCL_OK; } } return TCL_CONTINUE; } static int InterpVarResolver( TCL_UNUSED(Tcl_Interp *), TCL_UNUSED(const char *), TCL_UNUSED(Tcl_Namespace *), TCL_UNUSED(int), /* flags */ TCL_UNUSED(Tcl_Var *)) { /* * Don't resolve the variable; use standard rules. */ return TCL_CONTINUE; } typedef struct MyResolvedVarInfo { Tcl_ResolvedVarInfo vInfo; /* This must be the first element. */ Tcl_Var var; Tcl_Obj *nameObj; } MyResolvedVarInfo; static inline void HashVarFree( Tcl_Var var) { if (VarHashRefCount(var) < 2) { Tcl_Free(var); } else { VarHashRefCount(var)--; } } static void MyCompiledVarFree( Tcl_ResolvedVarInfo *vInfoPtr) { MyResolvedVarInfo *resVarInfo = (MyResolvedVarInfo *) vInfoPtr; Tcl_DecrRefCount(resVarInfo->nameObj); if (resVarInfo->var) { HashVarFree(resVarInfo->var); } Tcl_Free(vInfoPtr); } #define TclVarHashGetValue(hPtr) \ ((Var *) ((char *)hPtr - offsetof(VarInHash, entry))) static Tcl_Var MyCompiledVarFetch( Tcl_Interp *interp, Tcl_ResolvedVarInfo *vinfoPtr) { MyResolvedVarInfo *resVarInfo = (MyResolvedVarInfo *) vinfoPtr; Tcl_Var var = resVarInfo->var; int isNewVar; Interp *iPtr = (Interp *) interp; Tcl_HashEntry *hPtr; if (var != NULL) { if (!(((Var *) var)->flags & VAR_DEAD_HASH)) { /* * The cached variable is valid, return it. */ return var; } /* * The variable is not valid anymore. Clean it up. */ HashVarFree(var); } hPtr = Tcl_CreateHashEntry((Tcl_HashTable *) &iPtr->globalNsPtr->varTable, resVarInfo->nameObj, &isNewVar); if (hPtr) { var = (Tcl_Var) TclVarHashGetValue(hPtr); } else { var = NULL; } resVarInfo->var = var; /* * Increment the reference counter to avoid Tcl_Free() of the variable in * Tcl's FreeVarEntry(); for cleanup, we provide our own HashVarFree(); */ VarHashRefCount(var)++; return var; } static int InterpCompiledVarResolver( TCL_UNUSED(Tcl_Interp *), const char *name, TCL_UNUSED(Tcl_Size) /* length */, TCL_UNUSED(Tcl_Namespace *), Tcl_ResolvedVarInfo **rPtr) { if (*name == 'T') { MyResolvedVarInfo *resVarInfo = (MyResolvedVarInfo *)Tcl_Alloc(sizeof(MyResolvedVarInfo)); resVarInfo->vInfo.fetchProc = MyCompiledVarFetch; resVarInfo->vInfo.deleteProc = MyCompiledVarFree; resVarInfo->var = NULL; resVarInfo->nameObj = Tcl_NewStringObj(name, -1); Tcl_IncrRefCount(resVarInfo->nameObj); *rPtr = &resVarInfo->vInfo; return TCL_OK; } return TCL_CONTINUE; } static int TestInterpResolverCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { static const char *const table[] = { "down", "up", NULL }; int idx; #define RESOLVER_KEY "testInterpResolver" if ((objc < 2) || (objc > 3)) { Tcl_WrongNumArgs(interp, 1, objv, "up|down ?interp?"); return TCL_ERROR; } if (objc == 3) { interp = Tcl_GetChild(interp, Tcl_GetString(objv[2])); if (interp == NULL) { Tcl_AppendResult(interp, "provided interpreter not found", (char *)NULL); return TCL_ERROR; } } if (Tcl_GetIndexFromObj(interp, objv[1], table, "operation", TCL_EXACT, &idx) != TCL_OK) { return TCL_ERROR; } switch (idx) { case 1: /* up */ Tcl_AddInterpResolvers(interp, RESOLVER_KEY, InterpCmdResolver, InterpVarResolver, InterpCompiledVarResolver); break; case 0: /*down*/ if (!Tcl_RemoveInterpResolvers(interp, RESOLVER_KEY)) { Tcl_AppendResult(interp, "could not remove the resolver scheme", (char *)NULL); return TCL_ERROR; } } return TCL_OK; } /* *------------------------------------------------------------------------ * * TestApplyLambdaCmd -- * * Implements the Tcl command testapplylambda. This tests the apply * implementation handling of a lambda where the lambda has a list * internal representation where the second element's internal * representation is already a byte code object. * * Results: * TCL_OK - Success. Caller should check result is 42 * TCL_ERROR - Error. * * Side effects: * In the presence of the apply bug, may panic. Otherwise * Interpreter result holds result or error message. * *------------------------------------------------------------------------ */ int TestApplyLambdaCmd ( TCL_UNUSED(void*), Tcl_Interp *interp, /* Current interpreter. */ TCL_UNUSED(int), /* objc. */ TCL_UNUSED(Tcl_Obj *const *)) /* objv. */ { Tcl_Obj *lambdaObjs[2]; Tcl_Obj *evalObjs[2]; Tcl_Obj *lambdaObj; int result; /* Create a lambda {{} {set a 42}} */ lambdaObjs[0] = Tcl_NewObj(); /* No parameters */ lambdaObjs[1] = Tcl_NewStringObj("set a 42", -1); /* Body */ lambdaObj = Tcl_NewListObj(2, lambdaObjs); Tcl_IncrRefCount(lambdaObj); /* Create the command "apply {{} {set a 42}" */ evalObjs[0] = Tcl_NewStringObj("apply", -1); Tcl_IncrRefCount(evalObjs[0]); /* * NOTE: IMPORTANT TO EXHIBIT THE BUG. We duplicate the lambda because * it will get shimmered to a Lambda internal representation but we * want to hold on to our list representation. */ evalObjs[1] = Tcl_DuplicateObj(lambdaObj); Tcl_IncrRefCount(evalObjs[1]); /* Evaluate it */ result = Tcl_EvalObjv(interp, 2, evalObjs, TCL_EVAL_GLOBAL); if (result != TCL_OK) { Tcl_DecrRefCount(evalObjs[0]); Tcl_DecrRefCount(evalObjs[1]); return result; } /* * So far so good. At this point, * - evalObjs[1] has an internal representation of Lambda * - lambdaObj[1] ({set a 42}) has been shimmered to * an internal representation of ByteCode. */ Tcl_DecrRefCount(evalObjs[1]); /* Don't need this anymore */ /* * The bug trigger. Repeating the command but: * - we are calling apply with a lambda that is a list (as BEFORE), * BUT * - The body of the lambda (lambdaObjs[1]) ALREADY has internal * representation of ByteCode and thus will not be compiled again */ evalObjs[1] = lambdaObj; /* lambdaObj already has a ref count so no need for IncrRef */ result = Tcl_EvalObjv(interp, 2, evalObjs, TCL_EVAL_GLOBAL); Tcl_DecrRefCount(evalObjs[0]); Tcl_DecrRefCount(lambdaObj); return result; } /* *---------------------------------------------------------------------- * * TestLutilCmd -- * * This procedure implements the "testlequal" command. It is used to * test compare two lists for equality using the string representation * of each element. Implemented in C because script level loops are * too slow for comparing large (GB count) lists. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TestLutilCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Arguments. */ { Tcl_Size nL1, nL2; Tcl_Obj *l1Obj = NULL; Tcl_Obj *l2Obj = NULL; Tcl_Obj **l1Elems; Tcl_Obj **l2Elems; static const char *const subcmds[] = { "equal", "diffindex", NULL }; enum options { LUTIL_EQUAL, LUTIL_DIFFINDEX } idx; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "list1 list2"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], subcmds, "option", 0, &idx) != TCL_OK) { return TCL_ERROR; } /* Protect against shimmering, just to be safe */ l1Obj = Tcl_DuplicateObj(objv[2]); l2Obj = Tcl_DuplicateObj(objv[3]); int ret = TCL_ERROR; if (Tcl_ListObjGetElements(interp, l1Obj, &nL1, &l1Elems) != TCL_OK) { goto vamoose; } if (Tcl_ListObjGetElements(interp, l2Obj, &nL2, &l2Elems) != TCL_OK) { goto vamoose; } Tcl_Size i, nCmp; ret = TCL_OK; switch (idx) { case LUTIL_EQUAL: /* Avoid the loop below if lengths differ */ if (nL1 != nL2) { Tcl_SetObjResult(interp, Tcl_NewIntObj(0)); break; } /* FALLTHRU */ case LUTIL_DIFFINDEX: nCmp = nL1 <= nL2 ? nL1 : nL2; for (i = 0; i < nCmp; ++i) { if (strcmp(Tcl_GetString(l1Elems[i]), Tcl_GetString(l2Elems[i]))) { break; } } if (i == nCmp && nCmp == nL1 && nCmp == nL2) { nCmp = idx == LUTIL_EQUAL ? 1 : -1; } else { nCmp = idx == LUTIL_EQUAL ? 0 : i; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nCmp)); break; } vamoose: if (l1Obj) { Tcl_DecrRefCount(l1Obj); } if (l2Obj) { Tcl_DecrRefCount(l2Obj); } return ret; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * indent-tabs-mode: nil * End: */ tcl9.0.1/generic/tclTestABSList.c0000644000175000017500000010061514726623136016145 0ustar sergeisergei// Tcl Abstract List test command: "lstring" #undef BUILD_tcl #undef STATIC_BUILD #ifndef USE_TCL_STUBS # define USE_TCL_STUBS #endif #include #include #include "tclInt.h" /* * Forward references */ Tcl_Obj *myNewLStringObj(Tcl_WideInt start, Tcl_WideInt length); static void freeRep(Tcl_Obj* alObj); static Tcl_Obj* my_LStringObjSetElem(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size numIndcies, Tcl_Obj *const indicies[], Tcl_Obj *valueObj); static void DupLStringRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr); static Tcl_Size my_LStringObjLength(Tcl_Obj *lstringObjPtr); static int my_LStringObjIndex(Tcl_Interp *interp, Tcl_Obj *lstringObj, Tcl_Size index, Tcl_Obj **charObjPtr); static int my_LStringObjRange(Tcl_Interp *interp, Tcl_Obj *lstringObj, Tcl_Size fromIdx, Tcl_Size toIdx, Tcl_Obj **newObjPtr); static int my_LStringObjReverse(Tcl_Interp *interp, Tcl_Obj *srcObj, Tcl_Obj **newObjPtr); static int my_LStringReplace(Tcl_Interp *interp, Tcl_Obj *listObj, Tcl_Size first, Tcl_Size numToDelete, Tcl_Size numToInsert, Tcl_Obj *const insertObjs[]); static int my_LStringGetElements(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size *objcptr, Tcl_Obj ***objvptr); static void lstringFreeElements(Tcl_Obj* lstringObj); static void UpdateStringOfLString(Tcl_Obj *objPtr); /* * Internal Representation of an lstring type value */ typedef struct LString { char *string; // NULL terminated utf-8 string Tcl_Size strlen; // num bytes in string Tcl_Size allocated; // num bytes allocated Tcl_Obj**elements; // elements array, allocated when GetElements is // called } LString; /* * AbstractList definition of an lstring type */ static const Tcl_ObjType lstringTypes[11] = { {/*0*/ "lstring", freeRep, DupLStringRep, UpdateStringOfLString, NULL, TCL_OBJTYPE_V2( my_LStringObjLength, /* Length */ my_LStringObjIndex, /* Index */ my_LStringObjRange, /* Slice */ my_LStringObjReverse, /* Reverse */ my_LStringGetElements, /* GetElements */ my_LStringObjSetElem, /* SetElement */ my_LStringReplace, /* Replace */ NULL) /* "in" operator */ }, {/*1*/ "lstring", freeRep, DupLStringRep, UpdateStringOfLString, NULL, TCL_OBJTYPE_V2( NULL, /* Length */ my_LStringObjIndex, /* Index */ my_LStringObjRange, /* Slice */ my_LStringObjReverse, /* Reverse */ my_LStringGetElements, /* GetElements */ my_LStringObjSetElem, /* SetElement */ my_LStringReplace, /* Replace */ NULL) /* "in" operator */ }, {/*2*/ "lstring", freeRep, DupLStringRep, UpdateStringOfLString, NULL, TCL_OBJTYPE_V2( my_LStringObjLength, /* Length */ NULL, /* Index */ my_LStringObjRange, /* Slice */ my_LStringObjReverse, /* Reverse */ my_LStringGetElements, /* GetElements */ my_LStringObjSetElem, /* SetElement */ my_LStringReplace, /* Replace */ NULL) /* "in" operator */ }, {/*3*/ "lstring", freeRep, DupLStringRep, UpdateStringOfLString, NULL, TCL_OBJTYPE_V2( my_LStringObjLength, /* Length */ my_LStringObjIndex, /* Index */ NULL, /* Slice */ my_LStringObjReverse, /* Reverse */ my_LStringGetElements, /* GetElements */ my_LStringObjSetElem, /* SetElement */ my_LStringReplace, /* Replace */ NULL) /* "in" operator */ }, {/*4*/ "lstring", freeRep, DupLStringRep, UpdateStringOfLString, NULL, TCL_OBJTYPE_V2( my_LStringObjLength, /* Length */ my_LStringObjIndex, /* Index */ my_LStringObjRange, /* Slice */ NULL, /* Reverse */ my_LStringGetElements, /* GetElements */ my_LStringObjSetElem, /* SetElement */ my_LStringReplace, /* Replace */ NULL) /* "in" operator */ }, {/*5*/ "lstring", freeRep, DupLStringRep, UpdateStringOfLString, NULL, TCL_OBJTYPE_V2( my_LStringObjLength, /* Length */ my_LStringObjIndex, /* Index */ my_LStringObjRange, /* Slice */ my_LStringObjReverse, /* Reverse */ NULL, /* GetElements */ my_LStringObjSetElem, /* SetElement */ my_LStringReplace, /* Replace */ NULL) /* "in" operator */ }, {/*6*/ "lstring", freeRep, DupLStringRep, UpdateStringOfLString, NULL, TCL_OBJTYPE_V2( my_LStringObjLength, /* Length */ my_LStringObjIndex, /* Index */ my_LStringObjRange, /* Slice */ my_LStringObjReverse, /* Reverse */ my_LStringGetElements, /* GetElements */ NULL, /* SetElement */ my_LStringReplace, /* Replace */ NULL) /* "in" operator */ }, {/*7*/ "lstring", freeRep, DupLStringRep, UpdateStringOfLString, NULL, TCL_OBJTYPE_V2( my_LStringObjLength, /* Length */ my_LStringObjIndex, /* Index */ my_LStringObjRange, /* Slice */ my_LStringObjReverse, /* Reverse */ my_LStringGetElements, /* GetElements */ my_LStringObjSetElem, /* SetElement */ NULL, /* Replace */ NULL) /* "in" operator */ }, {/*8*/ "lstring", freeRep, DupLStringRep, UpdateStringOfLString, NULL, TCL_OBJTYPE_V2( my_LStringObjLength, /* Length */ my_LStringObjIndex, /* Index */ my_LStringObjRange, /* Slice */ my_LStringObjReverse, /* Reverse */ my_LStringGetElements, /* GetElements */ my_LStringObjSetElem, /* SetElement */ my_LStringReplace, /* Replace */ NULL) /* "in" operator */ }, {/*9*/ "lstring", freeRep, DupLStringRep, UpdateStringOfLString, NULL, TCL_OBJTYPE_V2( my_LStringObjLength, /* Length */ my_LStringObjIndex, /* Index */ my_LStringObjRange, /* Slice */ my_LStringObjReverse, /* Reverse */ my_LStringGetElements, /* GetElements */ my_LStringObjSetElem, /* SetElement */ my_LStringReplace, /* Replace */ NULL) /* "in" operator */ }, {/*10*/ "lstring", freeRep, DupLStringRep, UpdateStringOfLString, NULL, TCL_OBJTYPE_V2( my_LStringObjLength, /* Length */ my_LStringObjIndex, /* Index */ my_LStringObjRange, /* Slice */ my_LStringObjReverse, /* Reverse */ my_LStringGetElements, /* GetElements */ my_LStringObjSetElem, /* SetElement */ my_LStringReplace, /* Replace */ NULL) /* "in" operator */ } }; /* *---------------------------------------------------------------------- * * my_LStringObjIndex -- * * Implements the AbstractList Index function for the lstring type. The * Index function returns the value at the index position given. Caller * is resposible for freeing the Obj. * * Results: * TCL_OK on success. Returns a new Obj, with a 0 refcount in the * supplied charObjPtr location. Call has ownership of the Obj. * * Side effects: * Obj allocated. * *---------------------------------------------------------------------- */ static int my_LStringObjIndex( Tcl_Interp *interp, Tcl_Obj *lstringObj, Tcl_Size index, Tcl_Obj **charObjPtr) { LString *lstringRepPtr = (LString*)lstringObj->internalRep.twoPtrValue.ptr1; (void)interp; if (index < lstringRepPtr->strlen) { char cchar[2]; cchar[0] = lstringRepPtr->string[index]; cchar[1] = 0; *charObjPtr = Tcl_NewStringObj(cchar,1); } else { *charObjPtr = NULL; } return TCL_OK; } /* *---------------------------------------------------------------------- * * my_LStringObjLength -- * * Implements the AbstractList Length function for the lstring type. * The Length function returns the number of elements in the list. * * Results: * WideInt number of elements in the list. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tcl_Size my_LStringObjLength(Tcl_Obj *lstringObjPtr) { LString *lstringRepPtr = (LString *)lstringObjPtr->internalRep.twoPtrValue.ptr1; return lstringRepPtr->strlen; } /* *---------------------------------------------------------------------- * * DupLStringRep -- * * Replicates the internal representation of the src value, and storing * it in the copy * * Results: * void * * Side effects: * Modifies the rep of the copyObj. * *---------------------------------------------------------------------- */ static void DupLStringRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr) { LString *srcLString = (LString*)srcPtr->internalRep.twoPtrValue.ptr1; LString *copyLString = (LString*)Tcl_Alloc(sizeof(LString)); memcpy(copyLString, srcLString, sizeof(LString)); copyLString->string = (char*)Tcl_Alloc(srcLString->allocated); strncpy(copyLString->string, srcLString->string, srcLString->strlen); copyLString->string[srcLString->strlen] = '\0'; copyLString->elements = NULL; Tcl_ObjInternalRep itr; itr.twoPtrValue.ptr1 = copyLString; itr.twoPtrValue.ptr2 = NULL; Tcl_StoreInternalRep(copyPtr, srcPtr->typePtr, &itr); return; } /* *---------------------------------------------------------------------- * * my_LStringObjSetElem -- * * Replace the element value at the given (nested) index with the * valueObj provided. If the lstring obj is shared, a new list is * created conntaining the modifed element. * * Results: * The modifed lstring is returned, either new or original. If the * index is invalid, NULL is returned, and an error is added to the * interp, if provided. * * Side effects: * A new obj may be created. * *---------------------------------------------------------------------- */ static Tcl_Obj* my_LStringObjSetElem( Tcl_Interp *interp, Tcl_Obj *lstringObj, Tcl_Size numIndicies, Tcl_Obj *const indicies[], Tcl_Obj *valueObj) { LString *lstringRepPtr = (LString*)lstringObj->internalRep.twoPtrValue.ptr1; Tcl_Size index; int status; Tcl_Obj *returnObj; if (numIndicies > 1) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("Multiple indicies not supported by lstring.")); return NULL; } status = Tcl_GetIntForIndex(interp, indicies[0], lstringRepPtr->strlen, &index); if (status != TCL_OK) { return NULL; } returnObj = Tcl_IsShared(lstringObj) ? Tcl_DuplicateObj(lstringObj) : lstringObj; lstringRepPtr = (LString*)returnObj->internalRep.twoPtrValue.ptr1; if (index >= lstringRepPtr->strlen) { index = lstringRepPtr->strlen; lstringRepPtr->strlen++; lstringRepPtr->string = (char*)Tcl_Realloc(lstringRepPtr->string, lstringRepPtr->strlen+1); } if (valueObj) { const char newvalue = Tcl_GetString(valueObj)[0]; lstringRepPtr->string[index] = newvalue; } else if (index < lstringRepPtr->strlen) { /* Remove the char by sliding the tail of the string down */ char *sptr = &lstringRepPtr->string[index]; /* This is an overlapping copy, by definition */ lstringRepPtr->strlen--; memmove(sptr, (sptr+1), (lstringRepPtr->strlen - index)); } // else do nothing Tcl_InvalidateStringRep(returnObj); return returnObj; } /* *---------------------------------------------------------------------- * * my_LStringObjRange -- * * Creates a new Obj with a slice of the src listPtr. * * Results: * A new Obj is assigned to newObjPtr. Returns TCL_OK * * Side effects: * A new Obj is created. * *---------------------------------------------------------------------- */ static int my_LStringObjRange( Tcl_Interp *interp, Tcl_Obj *lstringObj, Tcl_Size fromIdx, Tcl_Size toIdx, Tcl_Obj **newObjPtr) { Tcl_Obj *rangeObj; LString *lstringRepPtr = (LString*)lstringObj->internalRep.twoPtrValue.ptr1; LString *rangeRep; Tcl_WideInt len = toIdx - fromIdx + 1; if (lstringRepPtr->strlen < fromIdx || lstringRepPtr->strlen < toIdx) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("Range out of bounds ")); return TCL_ERROR; } if (len <= 0) { // Return empty value; *newObjPtr = Tcl_NewObj(); } else { rangeRep = (LString*)Tcl_Alloc(sizeof(LString)); rangeRep->allocated = len+1; rangeRep->strlen = len; rangeRep->string = (char*)Tcl_Alloc(rangeRep->allocated); strncpy(rangeRep->string,&lstringRepPtr->string[fromIdx],len); rangeRep->string[len] = 0; rangeRep->elements = NULL; rangeObj = Tcl_NewObj(); Tcl_ObjInternalRep itr; itr.twoPtrValue.ptr1 = rangeRep; itr.twoPtrValue.ptr2 = NULL; Tcl_StoreInternalRep(rangeObj, lstringObj->typePtr, &itr); if (rangeRep->strlen > 0) { Tcl_InvalidateStringRep(rangeObj); } else { Tcl_InitStringRep(rangeObj, NULL, 0); } *newObjPtr = rangeObj; } return TCL_OK; } /* *---------------------------------------------------------------------- * * my_LStringObjReverse -- * * Creates a new Obj with the order of the elements in the lstring * value reversed, where first is last and last is first, etc. * * Results: * A new Obj is assigned to newObjPtr. Returns TCL_OK * * Side effects: * A new Obj is created. * *---------------------------------------------------------------------- */ static int my_LStringObjReverse(Tcl_Interp *interp, Tcl_Obj *srcObj, Tcl_Obj **newObjPtr) { LString *srcRep = (LString*)srcObj->internalRep.twoPtrValue.ptr1; Tcl_Obj *revObj; LString *revRep = (LString*)Tcl_Alloc(sizeof(LString)); Tcl_ObjInternalRep itr; Tcl_Size len; char *srcp, *dstp, *endp; (void)interp; len = srcRep->strlen; revRep->strlen = len; revRep->allocated = len+1; revRep->string = (char*)Tcl_Alloc(revRep->allocated); revRep->elements = NULL; srcp = srcRep->string; endp = &srcRep->string[len]; dstp = &revRep->string[len]; *dstp-- = 0; while (srcp < endp) { *dstp-- = *srcp++; } revObj = Tcl_NewObj(); itr.twoPtrValue.ptr1 = revRep; itr.twoPtrValue.ptr2 = NULL; Tcl_StoreInternalRep(revObj, srcObj->typePtr, &itr); if (revRep->strlen > 0) { Tcl_InvalidateStringRep(revObj); } else { Tcl_InitStringRep(revObj, NULL, 0); } *newObjPtr = revObj; return TCL_OK; } /* *---------------------------------------------------------------------- * * my_LStringReplace -- * * Delete and/or Insert elements in the list, starting at index first. * See more details in the comments below. This should not be called with * a Shared Obj. * * Results: * The value of the listObj is modified. * * Side effects: * The string rep is invalidated. * *---------------------------------------------------------------------- */ static int my_LStringReplace( Tcl_Interp *interp, Tcl_Obj *listObj, Tcl_Size first, Tcl_Size numToDelete, Tcl_Size numToInsert, Tcl_Obj *const insertObjs[]) { LString *lstringRep = (LString*)listObj->internalRep.twoPtrValue.ptr1; Tcl_Size newLen; Tcl_Size x, ix, kx; char *newStr; char *oldStr = lstringRep->string; (void)interp; newLen = lstringRep->strlen - numToDelete + numToInsert; if (newLen >= lstringRep->allocated) { lstringRep->allocated = newLen+1; newStr = (char*)Tcl_Alloc(lstringRep->allocated); newStr[newLen] = 0; } else { newStr = oldStr; } /* Tcl_ListObjReplace replaces zero or more elements of the list * referenced by listPtr with the objc values in the array referenced by * objv. * * If listPtr does not point to a list value, Tcl_ListObjReplace * will attempt to convert it to one; if the conversion fails, it returns * TCL_ERROR and leaves an error message in the interpreter's result value * if interp is not NULL. Otherwise, it returns TCL_OK after replacing the * values. * * * If objv is NULL, no new elements are added. * * * If the argument first is zero or negative, it refers to the first * element. * * * If first is greater than or equal to the number of elements in the * list, then no elements are deleted; the new elements are appended * to the list. count gives the number of elements to replace. * * * If count is zero or negative then no elements are deleted; the new * elements are simply inserted before the one designated by first. * Tcl_ListObjReplace invalidates listPtr's old string representation. * * * The reference counts of any elements inserted from objv are * incremented since the resulting list now refers to them. Similarly, * the reference counts for any replaced values are decremented. */ // copy 0 to first-1 if (newStr != oldStr) { strncpy(newStr, oldStr, first); } // move front elements to keep for(x=0, kx=0; xstrlen && xstring = newStr; lstringRep->strlen = newLen; /* Changes made to value, string rep and elements array no longer valid */ Tcl_InvalidateStringRep(listObj); lstringFreeElements(listObj); return TCL_OK; } static const Tcl_ObjType * my_SetAbstractProc(int ptype) { const Tcl_ObjType *typePtr = &lstringTypes[0]; /* default value */ if (4 <= ptype && ptype <= 11) { /* Table has no entries for the slots upto setfromany */ typePtr = &lstringTypes[(ptype-3)]; } return typePtr; } /* *---------------------------------------------------------------------- * * my_NewLStringObj -- * * Creates a new lstring Obj using the string value of objv[0] * * Results: * results * * Side effects: * side effects * *---------------------------------------------------------------------- */ static Tcl_Obj * my_NewLStringObj( Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]) { LString *lstringRepPtr; Tcl_ObjInternalRep itr; size_t repSize; Tcl_Obj *lstringPtr; const char *string; static const char* procTypeNames[] = { "FREEREP", "DUPREP", "UPDATESTRING", "SETFROMANY", "LENGTH", "INDEX", "SLICE", "REVERSE", "GETELEMENTS", "SETELEMENT", "REPLACE", NULL }; int i = 0; int ptype; const Tcl_ObjType *lstringTypePtr = &lstringTypes[10]; repSize = sizeof(LString); lstringRepPtr = (LString*)Tcl_Alloc(repSize); while (istrlen = strlen(string); lstringRepPtr->allocated = lstringRepPtr->strlen + 1; lstringRepPtr->string = (char*)Tcl_Alloc(lstringRepPtr->allocated); strcpy(lstringRepPtr->string, string); lstringRepPtr->elements = NULL; lstringPtr = Tcl_NewObj(); itr.twoPtrValue.ptr1 = lstringRepPtr; itr.twoPtrValue.ptr2 = NULL; Tcl_StoreInternalRep(lstringPtr, lstringTypePtr, &itr); if (lstringRepPtr->strlen > 0) { Tcl_InvalidateStringRep(lstringPtr); } else { Tcl_InitStringRep(lstringPtr, NULL, 0); } return lstringPtr; } /* *---------------------------------------------------------------------- * * freeElements -- * * Free the element array * */ static void lstringFreeElements(Tcl_Obj* lstringObj) { LString *lstringRepPtr = (LString*)lstringObj->internalRep.twoPtrValue.ptr1; if (lstringRepPtr->elements) { Tcl_Obj **objptr = lstringRepPtr->elements; while (objptr < &lstringRepPtr->elements[lstringRepPtr->strlen]) { Tcl_DecrRefCount(*objptr++); } Tcl_Free((char*)lstringRepPtr->elements); lstringRepPtr->elements = NULL; } } /* *---------------------------------------------------------------------- * * freeRep -- * * Free the value storage of the lstring Obj. * * Results: * void * * Side effects: * Memory free'd. * *---------------------------------------------------------------------- */ static void freeRep(Tcl_Obj* lstringObj) { LString *lstringRepPtr = (LString*)lstringObj->internalRep.twoPtrValue.ptr1; if (lstringRepPtr->string) { Tcl_Free(lstringRepPtr->string); } lstringFreeElements(lstringObj); Tcl_Free((char*)lstringRepPtr); lstringObj->internalRep.twoPtrValue.ptr1 = NULL; } /* *---------------------------------------------------------------------- * * my_LStringGetElements -- * * Get the elements of the list in an array. * * Results: * objc, objv return values * * Side effects: * A Tcl_Obj is stored for every element of the abstract list * *---------------------------------------------------------------------- */ static int my_LStringGetElements(Tcl_Interp *interp, Tcl_Obj *lstringObj, Tcl_Size *objcptr, Tcl_Obj ***objvptr) { LString *lstringRepPtr = (LString*)lstringObj->internalRep.twoPtrValue.ptr1; Tcl_Obj **objPtr; char *cptr = lstringRepPtr->string; (void)interp; if (lstringRepPtr->strlen == 0) { *objcptr = 0; *objvptr = NULL; return TCL_OK; } if (lstringRepPtr->elements == NULL) { lstringRepPtr->elements = (Tcl_Obj**)Tcl_Alloc(sizeof(Tcl_Obj*) * lstringRepPtr->strlen); objPtr=lstringRepPtr->elements; while (objPtr < &lstringRepPtr->elements[lstringRepPtr->strlen]) { *objPtr = Tcl_NewStringObj(cptr++,1); Tcl_IncrRefCount(*objPtr++); } } *objvptr = lstringRepPtr->elements; *objcptr = lstringRepPtr->strlen; return TCL_OK; } /* ** UpdateStringRep */ static void UpdateStringOfLString(Tcl_Obj *objPtr) { # define LOCAL_SIZE 64 int localFlags[LOCAL_SIZE], *flagPtr = NULL; Tcl_ObjType const *typePtr = objPtr->typePtr; char *p; int bytesNeeded = 0; int llen, i; /* * Handle empty list case first, so rest of the routine is simpler. */ llen = typePtr->lengthProc(objPtr); if (llen <= 0) { Tcl_InitStringRep(objPtr, NULL, 0); return; } /* * Pass 1: estimate space. */ if (llen <= LOCAL_SIZE) { flagPtr = localFlags; } else { /* We know numElems <= LIST_MAX, so this is safe. */ flagPtr = (int *) Tcl_Alloc(llen*sizeof(int)); } for (bytesNeeded = 0, i = 0; i < llen; i++) { Tcl_Obj *elemObj; const char *elemStr; Tcl_Size elemLen; flagPtr[i] = (i ? TCL_DONT_QUOTE_HASH : 0); typePtr->indexProc(NULL, objPtr, i, &elemObj); Tcl_IncrRefCount(elemObj); elemStr = Tcl_GetStringFromObj(elemObj, &elemLen); /* Note TclScanElement updates flagPtr[i] */ bytesNeeded += Tcl_ScanCountedElement(elemStr, elemLen, &flagPtr[i]); if (bytesNeeded < 0) { Tcl_Panic("max size for a Tcl value (%d bytes) exceeded", INT_MAX); } Tcl_DecrRefCount(elemObj); } if (bytesNeeded > INT_MAX - llen + 1) { Tcl_Panic("max size for a Tcl value (%d bytes) exceeded", INT_MAX); } bytesNeeded += llen; /* Separating spaces and terminating nul */ /* * Pass 2: generate the string repr. */ objPtr->bytes = (char *) Tcl_Alloc(bytesNeeded); p = objPtr->bytes; for (i = 0; i < llen; i++) { Tcl_Obj *elemObj; const char *elemStr; Tcl_Size elemLen; flagPtr[i] |= (i ? TCL_DONT_QUOTE_HASH : 0); typePtr->indexProc(NULL, objPtr, i, &elemObj); Tcl_IncrRefCount(elemObj); elemStr = Tcl_GetStringFromObj(elemObj, &elemLen); p += Tcl_ConvertCountedElement(elemStr, elemLen, p, flagPtr[i]); *p++ = ' '; Tcl_DecrRefCount(elemObj); } p[-1] = '\0'; /* Overwrite last space added */ /* Length of generated string */ objPtr->length = p - 1 - objPtr->bytes; if (flagPtr != localFlags) { Tcl_Free(flagPtr); } } /* *---------------------------------------------------------------------- * * lLStringObjCmd -- * * Script level command that creats an lstring Obj value. * * Results: * Returns and lstring Obj value in the interp results. * * Side effects: * Interp results modified. * *---------------------------------------------------------------------- */ static int lLStringObjCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]) { Tcl_Obj *lstringObj; (void)clientData; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "string"); return TCL_ERROR; } lstringObj = my_NewLStringObj(interp, objc-1, &objv[1]); if (lstringObj) { Tcl_SetObjResult(interp, lstringObj); return TCL_OK; } return TCL_ERROR; } /* ** lgen - Derived from TIP 192 - Lazy Lists ** Generate a list using a command provided as argument(s). ** The command computes the value for a given index. */ /* * Internal rep for the Generate Series */ typedef struct LgenSeries { Tcl_Interp *interp; // used to evaluate gen script Tcl_Size len; // list length Tcl_Size nargs; // Number of arguments in genFn including "index" Tcl_Obj *genFnObj; // The preformed command as a list. Index is set in // the last element (last argument) } LgenSeries; /* * Evaluate the generation function. * The provided funtion computes the value for a give index */ static Tcl_Obj* lgen( Tcl_Obj* objPtr, Tcl_Size index) { LgenSeries *lgenSeriesPtr = (LgenSeries*)objPtr->internalRep.twoPtrValue.ptr1; Tcl_Obj *elemObj = NULL; Tcl_Interp *intrp = lgenSeriesPtr->interp; Tcl_Obj *genCmd = lgenSeriesPtr->genFnObj; Tcl_Size endidx = lgenSeriesPtr->nargs-1; if (0 <= index && index < lgenSeriesPtr->len) { Tcl_Obj *indexObj = Tcl_NewWideIntObj(index); Tcl_ListObjReplace(intrp, genCmd, endidx, 1, 1, &indexObj); // EVAL DIRECT to avoid interfering with bytecode compile which may be // active on the stack int flags = TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT; int status = Tcl_EvalObjEx(intrp, genCmd, flags); elemObj = Tcl_GetObjResult(intrp); if (status != TCL_OK) { Tcl_SetObjResult(intrp, Tcl_ObjPrintf( "Error: %s\nwhile executing %s\n", elemObj ? Tcl_GetString(elemObj) : "NULL", Tcl_GetString(genCmd))); return NULL; } } return elemObj; } /* * Abstract List Length function */ static Tcl_Size lgenSeriesObjLength(Tcl_Obj *objPtr) { LgenSeries *lgenSeriesRepPtr = (LgenSeries *)objPtr->internalRep.twoPtrValue.ptr1; return lgenSeriesRepPtr->len; } /* * Abstract List Index function */ static int lgenSeriesObjIndex( Tcl_Interp *interp, Tcl_Obj *lgenSeriesObjPtr, Tcl_Size index, Tcl_Obj **elemPtr) { LgenSeries *lgenSeriesRepPtr; Tcl_Obj *element; lgenSeriesRepPtr = (LgenSeries*)lgenSeriesObjPtr->internalRep.twoPtrValue.ptr1; if (index < 0 || index >= lgenSeriesRepPtr->len) { *elemPtr = NULL; return TCL_OK; } if (lgenSeriesRepPtr->interp == NULL && interp == NULL) { return TCL_ERROR; } lgenSeriesRepPtr->interp = interp; element = lgen(lgenSeriesObjPtr, index); if (element) { *elemPtr = element; } else { return TCL_ERROR; } return TCL_OK; } /* ** UpdateStringRep */ static void UpdateStringOfLgen(Tcl_Obj *objPtr) { LgenSeries *lgenSeriesRepPtr; Tcl_Obj *element; Tcl_Size i; size_t bytlen; Tcl_Obj *tmpstr = Tcl_NewObj(); lgenSeriesRepPtr = (LgenSeries*)objPtr->internalRep.twoPtrValue.ptr1; for (i=0, bytlen=0; ilen; i++) { element = lgen(objPtr, i); if (element) { if (i) { Tcl_AppendToObj(tmpstr," ",1); } Tcl_AppendObjToObj(tmpstr,element); } } bytlen = Tcl_GetCharLength(tmpstr); Tcl_InitStringRep(objPtr, Tcl_GetString(tmpstr), bytlen); Tcl_DecrRefCount(tmpstr); return; } /* * ObjType Free Internal Rep function */ static void FreeLgenInternalRep(Tcl_Obj *objPtr) { LgenSeries *lgenSeries = (LgenSeries*)objPtr->internalRep.twoPtrValue.ptr1; if (lgenSeries->genFnObj) { Tcl_DecrRefCount(lgenSeries->genFnObj); } lgenSeries->interp = NULL; Tcl_Free(lgenSeries); objPtr->internalRep.twoPtrValue.ptr1 = 0; } static void DupLgenSeriesRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr); /* * Abstract List ObjType definition */ static const Tcl_ObjType lgenType = { "lgenseries", FreeLgenInternalRep, DupLgenSeriesRep, UpdateStringOfLgen, NULL, /* SetFromAnyProc */ TCL_OBJTYPE_V2( lgenSeriesObjLength, lgenSeriesObjIndex, NULL, /* slice */ NULL, /* reverse */ NULL, /* get elements */ NULL, /* set element */ NULL, /* replace */ NULL) /* "in" operator */ }; /* * ObjType Duplicate Internal Rep Function */ static void DupLgenSeriesRep( Tcl_Obj *srcPtr, Tcl_Obj *copyPtr) { LgenSeries *srcLgenSeries = (LgenSeries*)srcPtr->internalRep.twoPtrValue.ptr1; Tcl_Size repSize = sizeof(LgenSeries); LgenSeries *copyLgenSeries = (LgenSeries*)Tcl_Alloc(repSize); copyLgenSeries->interp = srcLgenSeries->interp; copyLgenSeries->nargs = srcLgenSeries->nargs; copyLgenSeries->len = srcLgenSeries->len; copyLgenSeries->genFnObj = Tcl_DuplicateObj(srcLgenSeries->genFnObj); Tcl_IncrRefCount(copyLgenSeries->genFnObj); copyPtr->typePtr = &lgenType; copyPtr->internalRep.twoPtrValue.ptr1 = copyLgenSeries; copyPtr->internalRep.twoPtrValue.ptr2 = NULL; return; } /* * Create a new lgen Tcl_Obj */ Tcl_Obj * newLgenObj( Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]) { Tcl_WideInt length; LgenSeries *lGenSeriesRepPtr; Tcl_Size repSize; Tcl_Obj *lGenSeriesObj; if (objc < 2) { return NULL; } if (Tcl_GetWideIntFromObj(NULL, objv[0], &length) != TCL_OK || length < 0) { return NULL; } lGenSeriesObj = Tcl_NewObj(); repSize = sizeof(LgenSeries); lGenSeriesRepPtr = (LgenSeries*)Tcl_Alloc(repSize); lGenSeriesRepPtr->interp = interp; //Tcl_CreateInterp(); lGenSeriesRepPtr->len = length; // Allocate array of *obj for cmd + index + args // objv length cmd arg1 arg2 arg3 ... // argsv 0 1 2 3 ... index lGenSeriesRepPtr->nargs = objc; lGenSeriesRepPtr->genFnObj = Tcl_NewListObj(objc-1, objv+1); // Addd 0 placeholder for index Tcl_ListObjAppendElement(interp, lGenSeriesRepPtr->genFnObj, Tcl_NewIntObj(0)); Tcl_IncrRefCount(lGenSeriesRepPtr->genFnObj); lGenSeriesObj->internalRep.twoPtrValue.ptr1 = lGenSeriesRepPtr; lGenSeriesObj->internalRep.twoPtrValue.ptr2 = NULL; lGenSeriesObj->typePtr = &lgenType; if (length > 0) { Tcl_InvalidateStringRep(lGenSeriesObj); } else { Tcl_InitStringRep(lGenSeriesObj, NULL, 0); } return lGenSeriesObj; } /* * The [lgen] command */ static int lGenObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]) { Tcl_Obj *genObj = newLgenObj(interp, objc-1, &objv[1]); if (genObj) { Tcl_SetObjResult(interp, genObj); return TCL_OK; } Tcl_WrongNumArgs(interp, 1, objv, "length cmd ?args?"); return TCL_ERROR; } /* * lgen package init */ int Lgen_Init(Tcl_Interp *interp) { if (Tcl_InitStubs(interp, "8.7", 0) == NULL) { return TCL_ERROR; } Tcl_CreateObjCommand(interp, "lgen", lGenObjCmd, NULL, NULL); Tcl_PkgProvide(interp, "lgen", "1.0"); return TCL_OK; } /* *---------------------------------------------------------------------- * * ABSListTest_Init -- * * Provides Abstract List implemenations via new commands * * lstring command * Usage: * lstring /string/ * * Description: * Creates a list where each character in the string is treated as an * element. The string is kept as a string, not an actual list. Indexing * is done by char. * * lgen command * Usage: * lgen /length/ /cmd/ ?args...? * * The /cmd/ should take the last argument as the index value, and return * a value for that element. * * Results: * The commands listed above are added to the interp. * * Side effects: * New commands defined. * *---------------------------------------------------------------------- */ int Tcl_ABSListTest_Init(Tcl_Interp *interp) { if (Tcl_InitStubs(interp, "8.7-", 0) == NULL) { return TCL_ERROR; } Tcl_CreateObjCommand(interp, "lstring", lLStringObjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "lgen", lGenObjCmd, NULL, NULL); Tcl_PkgProvide(interp, "abstractlisttest", "1.0.0"); return TCL_OK; } tcl9.0.1/generic/tclTestObj.c0000644000175000017500000014474414726623136015431 0ustar sergeisergei/* * tclTestObj.c -- * * This file contains C command functions for the additional Tcl commands * that are used for testing implementations of the Tcl object types. * These commands are not normally included in Tcl applications; they're * only used for testing. * * Copyright © 1995-1998 Sun Microsystems, Inc. * Copyright © 1999 Scriptics Corporation. * Copyright © 2005 Kevin B. Kenny. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #undef BUILD_tcl #ifndef USE_TCL_STUBS # define USE_TCL_STUBS #endif #define TCLBOOLWARNING(boolPtr) /* needed here because we compile with -Wc++-compat */ #include "tclInt.h" #ifdef TCL_WITH_EXTERNAL_TOMMATH # include "tommath.h" #else # include "tclTomMath.h" #endif #include "tclStringRep.h" #include /* * Forward declarations for functions defined later in this file: */ static int CheckIfVarUnset(Tcl_Interp *interp, Tcl_Obj **varPtr, Tcl_Size varIndex); static int GetVariableIndex(Tcl_Interp *interp, Tcl_Obj *obj, Tcl_Size *indexPtr); static void SetVarToObj(Tcl_Obj **varPtr, Tcl_Size varIndex, Tcl_Obj *objPtr); static Tcl_ObjCmdProc TestbignumobjCmd; static Tcl_ObjCmdProc TestbooleanobjCmd; static Tcl_ObjCmdProc TestdoubleobjCmd; static Tcl_ObjCmdProc TestindexobjCmd; static Tcl_ObjCmdProc TestintobjCmd; static Tcl_ObjCmdProc TestlistobjCmd; static Tcl_ObjCmdProc TestobjCmd; static Tcl_ObjCmdProc TeststringobjCmd; static Tcl_ObjCmdProc TestbigdataCmd; #define VARPTR_KEY "TCLOBJTEST_VARPTR" #define NUMBER_OF_OBJECT_VARS 20 static void VarPtrDeleteProc( void *clientData, TCL_UNUSED(Tcl_Interp *)) { int i; Tcl_Obj **varPtr = (Tcl_Obj **) clientData; for (i = 0; i < NUMBER_OF_OBJECT_VARS; i++) { if (varPtr[i]) { Tcl_DecrRefCount(varPtr[i]); } } Tcl_Free(varPtr); } static Tcl_Obj ** GetVarPtr( Tcl_Interp *interp) { Tcl_InterpDeleteProc *proc; return (Tcl_Obj **) Tcl_GetAssocData(interp, VARPTR_KEY, &proc); } /* *---------------------------------------------------------------------- * * TclObjTest_Init -- * * This function creates additional commands that are used to test the * Tcl object support. * * Results: * Returns a standard Tcl completion code, and leaves an error * message in the interp's result if an error occurs. * * Side effects: * Creates and registers several new testing commands. * *---------------------------------------------------------------------- */ int TclObjTest_Init( Tcl_Interp *interp) { int i; /* * An array of Tcl_Obj pointers used in the commands that operate on or get * the values of Tcl object-valued variables. varPtr[i] is the i-th variable's * Tcl_Obj *. */ Tcl_Obj **varPtr; #ifndef TCL_WITH_EXTERNAL_TOMMATH if (Tcl_TomMath_InitStubs(interp, "8.7-") == NULL) { return TCL_ERROR; } #endif varPtr = (Tcl_Obj **)Tcl_Alloc(NUMBER_OF_OBJECT_VARS * sizeof(varPtr[0])); if (!varPtr) { return TCL_ERROR; } Tcl_SetAssocData(interp, VARPTR_KEY, VarPtrDeleteProc, varPtr); for (i = 0; i < NUMBER_OF_OBJECT_VARS; i++) { varPtr[i] = NULL; } Tcl_CreateObjCommand(interp, "testbignumobj", TestbignumobjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testbooleanobj", TestbooleanobjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testdoubleobj", TestdoubleobjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testintobj", TestintobjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testindexobj", TestindexobjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testlistobj", TestlistobjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "testobj", TestobjCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "teststringobj", TeststringobjCmd, NULL, NULL); if (sizeof(Tcl_Size) == sizeof(Tcl_WideInt)) { Tcl_CreateObjCommand(interp, "testbigdata", TestbigdataCmd, NULL, NULL); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TestbignumobjCmd -- * * This function implements the "testbignumobj" command. It is used * to exercise the bignum Tcl object type implementation. * * Results: * Returns a standard Tcl object result. * * Side effects: * Creates and frees bignum objects; converts objects to have bignum * type. * *---------------------------------------------------------------------- */ static int TestbignumobjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Argument count */ Tcl_Obj *const objv[]) /* Argument vector */ { static const char *const subcmds[] = { "set", "get", "mult10", "div10", "iseven", "radixsize", NULL }; enum options { BIGNUM_SET, BIGNUM_GET, BIGNUM_MULT10, BIGNUM_DIV10, BIGNUM_ISEVEN, BIGNUM_RADIXSIZE } idx; int index; Tcl_Size varIndex; const char *string; mp_int bignumValue; Tcl_Obj **varPtr; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], subcmds, "option", 0, &idx) != TCL_OK) { return TCL_ERROR; } if (GetVariableIndex(interp, objv[2], &varIndex) != TCL_OK) { return TCL_ERROR; } varPtr = GetVarPtr(interp); switch (idx) { case BIGNUM_SET: if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "var value"); return TCL_ERROR; } string = Tcl_GetString(objv[3]); if (mp_init(&bignumValue) != MP_OKAY) { Tcl_SetObjResult(interp, Tcl_NewStringObj("error in mp_init", -1)); return TCL_ERROR; } if (mp_read_radix(&bignumValue, string, 10) != MP_OKAY) { mp_clear(&bignumValue); Tcl_SetObjResult(interp, Tcl_NewStringObj("error in mp_read_radix", -1)); return TCL_ERROR; } /* * If the object currently bound to the variable with index varIndex * has ref count 1 (i.e. the object is unshared) we can modify that * object directly. Otherwise, if RC>1 (i.e. the object is shared), * we must create a new object to modify/set and decrement the old * formerly-shared object's ref count. This is "copy on write". */ if ((varPtr[varIndex] != NULL) && !Tcl_IsShared(varPtr[varIndex])) { Tcl_SetBignumObj(varPtr[varIndex], &bignumValue); } else { SetVarToObj(varPtr, varIndex, Tcl_NewBignumObj(&bignumValue)); } break; case BIGNUM_GET: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "varIndex"); return TCL_ERROR; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } break; case BIGNUM_MULT10: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "varIndex"); return TCL_ERROR; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } if (Tcl_GetBignumFromObj(interp, varPtr[varIndex], &bignumValue) != TCL_OK) { return TCL_ERROR; } if (mp_mul_d(&bignumValue, 10, &bignumValue) != MP_OKAY) { mp_clear(&bignumValue); Tcl_SetObjResult(interp, Tcl_NewStringObj("error in mp_mul_d", -1)); return TCL_ERROR; } if (!Tcl_IsShared(varPtr[varIndex])) { Tcl_SetBignumObj(varPtr[varIndex], &bignumValue); } else { SetVarToObj(varPtr, varIndex, Tcl_NewBignumObj(&bignumValue)); } break; case BIGNUM_DIV10: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "varIndex"); return TCL_ERROR; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } if (Tcl_GetBignumFromObj(interp, varPtr[varIndex], &bignumValue) != TCL_OK) { return TCL_ERROR; } if (mp_div_d(&bignumValue, 10, &bignumValue, NULL) != MP_OKAY) { mp_clear(&bignumValue); Tcl_SetObjResult(interp, Tcl_NewStringObj("error in mp_div_d", -1)); return TCL_ERROR; } if (!Tcl_IsShared(varPtr[varIndex])) { Tcl_SetBignumObj(varPtr[varIndex], &bignumValue); } else { SetVarToObj(varPtr, varIndex, Tcl_NewBignumObj(&bignumValue)); } break; case BIGNUM_ISEVEN: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "varIndex"); return TCL_ERROR; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } if (Tcl_GetBignumFromObj(interp, varPtr[varIndex], &bignumValue) != TCL_OK) { return TCL_ERROR; } if (mp_mod_2d(&bignumValue, 1, &bignumValue) != MP_OKAY) { mp_clear(&bignumValue); Tcl_SetObjResult(interp, Tcl_NewStringObj("error in mp_mod_2d", -1)); return TCL_ERROR; } if (!Tcl_IsShared(varPtr[varIndex])) { Tcl_SetBooleanObj(varPtr[varIndex], mp_iszero(&bignumValue)); } else { SetVarToObj(varPtr, varIndex, Tcl_NewBooleanObj(mp_iszero(&bignumValue))); } mp_clear(&bignumValue); break; case BIGNUM_RADIXSIZE: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "varIndex"); return TCL_ERROR; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } if (Tcl_GetBignumFromObj(interp, varPtr[varIndex], &bignumValue) != TCL_OK) { return TCL_ERROR; } if (mp_radix_size(&bignumValue, 10, &index) != MP_OKAY) { return TCL_ERROR; } if (!Tcl_IsShared(varPtr[varIndex])) { Tcl_SetWideIntObj(varPtr[varIndex], index); } else { SetVarToObj(varPtr, varIndex, Tcl_NewWideIntObj(index)); } mp_clear(&bignumValue); break; } Tcl_SetObjResult(interp, varPtr[varIndex]); return TCL_OK; } /* *---------------------------------------------------------------------- * * TestbooleanobjCmd -- * * This function implements the "testbooleanobj" command. It is used to * test the boolean Tcl object type implementation. * * Results: * A standard Tcl object result. * * Side effects: * Creates and frees boolean objects, and also converts objects to * have boolean type. * *---------------------------------------------------------------------- */ static int TestbooleanobjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size varIndex; int boolValue; const char *subCmd; Tcl_Obj **varPtr; if (objc < 3) { wrongNumArgs: Tcl_WrongNumArgs(interp, 1, objv, "option arg ?arg ...?"); return TCL_ERROR; } if (GetVariableIndex(interp, objv[2], &varIndex) != TCL_OK) { return TCL_ERROR; } varPtr = GetVarPtr(interp); subCmd = Tcl_GetString(objv[1]); if (strcmp(subCmd, "set") == 0) { if (objc != 4) { goto wrongNumArgs; } if (Tcl_GetBooleanFromObj(interp, objv[3], &boolValue) != TCL_OK) { return TCL_ERROR; } /* * If the object currently bound to the variable with index varIndex * has ref count 1 (i.e. the object is unshared) we can modify that * object directly. Otherwise, if RC>1 (i.e. the object is shared), * we must create a new object to modify/set and decrement the old * formerly-shared object's ref count. This is "copy on write". */ if ((varPtr[varIndex] != NULL) && !Tcl_IsShared(varPtr[varIndex])) { Tcl_SetBooleanObj(varPtr[varIndex], boolValue); } else { SetVarToObj(varPtr, varIndex, Tcl_NewBooleanObj(boolValue)); } Tcl_SetObjResult(interp, varPtr[varIndex]); } else if (strcmp(subCmd, "get") == 0) { if (objc != 3) { goto wrongNumArgs; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } Tcl_SetObjResult(interp, varPtr[varIndex]); } else if (strcmp(subCmd, "not") == 0) { if (objc != 3) { goto wrongNumArgs; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, varPtr[varIndex], &boolValue) != TCL_OK) { return TCL_ERROR; } if (!Tcl_IsShared(varPtr[varIndex])) { Tcl_SetBooleanObj(varPtr[varIndex], !boolValue); } else { SetVarToObj(varPtr, varIndex, Tcl_NewBooleanObj(!boolValue)); } Tcl_SetObjResult(interp, varPtr[varIndex]); } else { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "bad option \"", Tcl_GetString(objv[1]), "\": must be set, get, or not", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * TestdoubleobjCmd -- * * This function implements the "testdoubleobj" command. It is used to * test the double-precision floating point Tcl object type * implementation. * * Results: * A standard Tcl object result. * * Side effects: * Creates and frees double objects, and also converts objects to * have double type. * *---------------------------------------------------------------------- */ static int TestdoubleobjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size varIndex; double doubleValue; const char *subCmd; Tcl_Obj **varPtr; if (objc < 3) { wrongNumArgs: Tcl_WrongNumArgs(interp, 1, objv, "option arg ?arg ...?"); return TCL_ERROR; } varPtr = GetVarPtr(interp); if (GetVariableIndex(interp, objv[2], &varIndex) != TCL_OK) { return TCL_ERROR; } subCmd = Tcl_GetString(objv[1]); if (strcmp(subCmd, "set") == 0) { if (objc != 4) { goto wrongNumArgs; } if (Tcl_GetDouble(interp, Tcl_GetString(objv[3]), &doubleValue) != TCL_OK) { return TCL_ERROR; } /* * If the object currently bound to the variable with index varIndex * has ref count 1 (i.e. the object is unshared) we can modify that * object directly. Otherwise, if RC>1 (i.e. the object is shared), we * must create a new object to modify/set and decrement the old * formerly-shared object's ref count. This is "copy on write". */ if ((varPtr[varIndex] != NULL) && !Tcl_IsShared(varPtr[varIndex])) { Tcl_SetDoubleObj(varPtr[varIndex], doubleValue); } else { SetVarToObj(varPtr, varIndex, Tcl_NewDoubleObj(doubleValue)); } Tcl_SetObjResult(interp, varPtr[varIndex]); } else if (strcmp(subCmd, "get") == 0) { if (objc != 3) { goto wrongNumArgs; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } Tcl_SetObjResult(interp, varPtr[varIndex]); } else if (strcmp(subCmd, "mult10") == 0) { if (objc != 3) { goto wrongNumArgs; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } if (Tcl_GetDoubleFromObj(interp, varPtr[varIndex], &doubleValue) != TCL_OK) { return TCL_ERROR; } if (!Tcl_IsShared(varPtr[varIndex])) { Tcl_SetDoubleObj(varPtr[varIndex], doubleValue * 10.0); } else { SetVarToObj(varPtr, varIndex, Tcl_NewDoubleObj(doubleValue * 10.0)); } Tcl_SetObjResult(interp, varPtr[varIndex]); } else if (strcmp(subCmd, "div10") == 0) { if (objc != 3) { goto wrongNumArgs; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } if (Tcl_GetDoubleFromObj(interp, varPtr[varIndex], &doubleValue) != TCL_OK) { return TCL_ERROR; } if (!Tcl_IsShared(varPtr[varIndex])) { Tcl_SetDoubleObj(varPtr[varIndex], doubleValue / 10.0); } else { SetVarToObj(varPtr, varIndex, Tcl_NewDoubleObj(doubleValue / 10.0)); } Tcl_SetObjResult(interp, varPtr[varIndex]); } else { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "bad option \"", Tcl_GetString(objv[1]), "\": must be set, get, mult10, or div10", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * TestindexobjCmd -- * * This function implements the "testindexobj" command. It is used to * test the index Tcl object type implementation. * * Results: * A standard Tcl object result. * * Side effects: * Creates and frees int objects, and also converts objects to * have int type. * *---------------------------------------------------------------------- */ static int TestindexobjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int allowAbbrev, index, setError, i, result; Tcl_Size index2; const char **argv; static const char *const tablePtr[] = {"a", "b", "check", NULL}; /* * Keep this structure declaration in sync with tclIndexObj.c */ struct IndexRep { void *tablePtr; /* Pointer to the table of strings. */ Tcl_Size offset; /* Offset between table entries. */ Tcl_Size index; /* Selected index into table. */ } *indexRep; if ((objc == 3) && (strcmp(Tcl_GetString(objv[1]), "check") == 0)) { /* * This code checks to be sure that the results of Tcl_GetIndexFromObj * are properly cached in the object and returned on subsequent * lookups. */ if (Tcl_GetIntForIndex(interp, objv[2], TCL_INDEX_NONE, &index2) != TCL_OK) { return TCL_ERROR; } Tcl_GetIndexFromObj(NULL, objv[1], tablePtr, "token", 0, &index); indexRep = (struct IndexRep *)objv[1]->internalRep.twoPtrValue.ptr1; indexRep->index = index2; result = Tcl_GetIndexFromObj(NULL, objv[1], tablePtr, "token", 0, &index); if (result == TCL_OK) { Tcl_SetWideIntObj(Tcl_GetObjResult(interp), index); } return result; } if (objc < 5) { Tcl_AppendToObj(Tcl_GetObjResult(interp), "wrong # args", -1); return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[1], &setError) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[2], &allowAbbrev) != TCL_OK) { return TCL_ERROR; } argv = (const char **)Tcl_Alloc((objc-3) * sizeof(char *)); for (i = 4; i < objc; i++) { argv[i-4] = Tcl_GetString(objv[i]); } argv[objc-4] = NULL; result = Tcl_GetIndexFromObj((setError? interp : NULL), objv[3], argv, "token", TCL_INDEX_TEMP_TABLE|(allowAbbrev? 0 : TCL_EXACT), &index); Tcl_Free((void *)argv); if (result == TCL_OK) { Tcl_SetWideIntObj(Tcl_GetObjResult(interp), index); } return result; } /* *---------------------------------------------------------------------- * * TestintobjCmd -- * * This function implements the "testintobj" command. It is used to * test the int Tcl object type implementation. * * Results: * A standard Tcl object result. * * Side effects: * Creates and frees int objects, and also converts objects to * have int type. * *---------------------------------------------------------------------- */ static int TestintobjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size varIndex; #if (INT_MAX != LONG_MAX) /* int is not the same size as long */ int i; #endif Tcl_WideInt wideValue; const char *subCmd; Tcl_Obj **varPtr; if (objc < 3) { wrongNumArgs: Tcl_WrongNumArgs(interp, 1, objv, "option arg ?arg ...?"); return TCL_ERROR; } varPtr = GetVarPtr(interp); if (GetVariableIndex(interp, objv[2], &varIndex) != TCL_OK) { return TCL_ERROR; } subCmd = Tcl_GetString(objv[1]); if (strcmp(subCmd, "set") == 0) { if (objc != 4) { goto wrongNumArgs; } if (Tcl_GetWideIntFromObj(interp, objv[3], &wideValue) != TCL_OK) { return TCL_ERROR; } /* * If the object currently bound to the variable with index varIndex * has ref count 1 (i.e. the object is unshared) we can modify that * object directly. Otherwise, if RC>1 (i.e. the object is shared), we * must create a new object to modify/set and decrement the old * formerly-shared object's ref count. This is "copy on write". */ if ((varPtr[varIndex] != NULL) && !Tcl_IsShared(varPtr[varIndex])) { Tcl_SetWideIntObj(varPtr[varIndex], wideValue); } else { SetVarToObj(varPtr, varIndex, Tcl_NewWideIntObj(wideValue)); } Tcl_SetObjResult(interp, varPtr[varIndex]); } else if (strcmp(subCmd, "set2") == 0) { /* doesn't set result */ if (objc != 4) { goto wrongNumArgs; } if (Tcl_GetWideIntFromObj(interp, objv[3], &wideValue) != TCL_OK) { return TCL_ERROR; } if ((varPtr[varIndex] != NULL) && !Tcl_IsShared(varPtr[varIndex])) { Tcl_SetWideIntObj(varPtr[varIndex], wideValue); } else { SetVarToObj(varPtr, varIndex, Tcl_NewWideIntObj(wideValue)); } } else if (strcmp(subCmd, "setint") == 0) { if (objc != 4) { goto wrongNumArgs; } if (Tcl_GetWideIntFromObj(interp, objv[3], &wideValue) != TCL_OK) { return TCL_ERROR; } if ((varPtr[varIndex] != NULL) && !Tcl_IsShared(varPtr[varIndex])) { Tcl_SetWideIntObj(varPtr[varIndex], wideValue); } else { SetVarToObj(varPtr, varIndex, Tcl_NewWideIntObj(wideValue)); } Tcl_SetObjResult(interp, varPtr[varIndex]); } else if (strcmp(subCmd, "setmax") == 0) { Tcl_WideInt maxWide = WIDE_MAX; if (objc != 3) { goto wrongNumArgs; } if ((varPtr[varIndex] != NULL) && !Tcl_IsShared(varPtr[varIndex])) { Tcl_SetWideIntObj(varPtr[varIndex], maxWide); } else { SetVarToObj(varPtr, varIndex, Tcl_NewWideIntObj(maxWide)); } } else if (strcmp(subCmd, "ismax") == 0) { if (objc != 3) { goto wrongNumArgs; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } if (Tcl_GetWideIntFromObj(interp, varPtr[varIndex], &wideValue) != TCL_OK) { return TCL_ERROR; } Tcl_AppendToObj(Tcl_GetObjResult(interp), ((wideValue == WIDE_MAX)? "1" : "0"), -1); } else if (strcmp(subCmd, "get") == 0) { if (objc != 3) { goto wrongNumArgs; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } Tcl_SetObjResult(interp, varPtr[varIndex]); } else if (strcmp(subCmd, "get2") == 0) { if (objc != 3) { goto wrongNumArgs; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } Tcl_AppendToObj(Tcl_GetObjResult(interp), Tcl_GetString(varPtr[varIndex]), -1); } else if (strcmp(subCmd, "inttoobigtest") == 0) { /* * If long ints have more bits than ints on this platform, verify that * Tcl_GetIntFromObj returns an error if the long int held in an * integer object's internal representation is too large to fit in an * int. */ if (objc != 3) { goto wrongNumArgs; } #if (INT_MAX == LONG_MAX) /* int is same size as long int */ Tcl_AppendToObj(Tcl_GetObjResult(interp), "1", -1); #else if ((varPtr[varIndex] != NULL) && !Tcl_IsShared(varPtr[varIndex])) { Tcl_SetWideIntObj(varPtr[varIndex], LONG_MAX); } else { SetVarToObj(varPtr, varIndex, Tcl_NewWideIntObj(LONG_MAX)); } if (Tcl_GetIntFromObj(interp, varPtr[varIndex], &i) != TCL_OK) { Tcl_ResetResult(interp); Tcl_AppendToObj(Tcl_GetObjResult(interp), "1", -1); return TCL_OK; } Tcl_AppendToObj(Tcl_GetObjResult(interp), "0", -1); #endif } else if (strcmp(subCmd, "mult10") == 0) { if (objc != 3) { goto wrongNumArgs; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } if (Tcl_GetWideIntFromObj(interp, varPtr[varIndex], &wideValue) != TCL_OK) { return TCL_ERROR; } if (!Tcl_IsShared(varPtr[varIndex])) { Tcl_SetWideIntObj(varPtr[varIndex], wideValue * 10); } else { SetVarToObj(varPtr, varIndex, Tcl_NewWideIntObj(wideValue * 10)); } Tcl_SetObjResult(interp, varPtr[varIndex]); } else if (strcmp(subCmd, "div10") == 0) { if (objc != 3) { goto wrongNumArgs; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } if (Tcl_GetWideIntFromObj(interp, varPtr[varIndex], &wideValue) != TCL_OK) { return TCL_ERROR; } if (!Tcl_IsShared(varPtr[varIndex])) { Tcl_SetWideIntObj(varPtr[varIndex], wideValue / 10); } else { SetVarToObj(varPtr, varIndex, Tcl_NewWideIntObj(wideValue / 10)); } Tcl_SetObjResult(interp, varPtr[varIndex]); } else { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "bad option \"", Tcl_GetString(objv[1]), "\": must be set, get, get2, mult10, or div10", (char *)NULL); return TCL_ERROR; } return TCL_OK; } /* *----------------------------------------------------------------------------- * * TestlistobjCmd -- * * This function implements the 'testlistobj' command. It is used to * test a few possible corner cases in list object manipulation from * C code that cannot occur at the Tcl level. * * Following new commands are added for 8.7 as regression tests for * memory leaks and use-after-free. Unlike 8.6, 8.7 has multiple internal * representations for lists. It has to be ensured that corresponding * implementations obey the invariants of the C list API. The script * level tests do not suffice as Tcl list commands do not execute * the same exact code path as the exported C API. * * Note these new commands are only useful when Tcl is compiled with * TCL_MEM_DEBUG defined. * * indexmemcheck - loops calling Tcl_ListObjIndex on each element. This * is to test that abstract lists returning elements do not depend * on caller to free them. The test case should check allocated counts * with the following sequence: * set before * testobj set VARINDEX [list a b c] (or lseq etc.) * testlistobj indexnoop VARINDEX * testobj unset VARINDEX * set after * after calling this command AND freeing the passed list. The targeted * bug is if Tcl_LOI returns a ephemeral Tcl_Obj with no other reference * resulting in a memory leak. Conversely, the command also checks * that the Tcl_Obj returned by Tcl_LOI does not have a zero reference * count since it is supposed to have at least one reference held * by the list implementation. Returns a message in interp otherwise. * * getelementsmemcheck - as above but for Tcl_ListObjGetElements * * Results: * A standard Tcl object result. * * Side effects: * Creates, manipulates and frees list objects. * *----------------------------------------------------------------------------- */ static int TestlistobjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Tcl interpreter */ int objc, /* Number of arguments */ Tcl_Obj *const objv[]) /* Argument objects */ { /* Subcommands supported by this command */ static const char* const subcommands[] = { "set", "get", "replace", "indexmemcheck", "getelementsmemcheck", "index", NULL }; enum listobjCmdIndex { LISTOBJ_SET, LISTOBJ_GET, LISTOBJ_REPLACE, LISTOBJ_INDEXMEMCHECK, LISTOBJ_GETELEMENTSMEMCHECK, LISTOBJ_INDEX, } cmdIndex; Tcl_Size varIndex; /* Variable number converted to binary */ Tcl_Size first; /* First index in the list */ Tcl_Size count; /* Count of elements in a list */ Tcl_Obj **varPtr; Tcl_Size i, len; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "option arg ?arg...?"); return TCL_ERROR; } varPtr = GetVarPtr(interp); if (GetVariableIndex(interp, objv[2], &varIndex) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], subcommands, "command", 0, &cmdIndex) != TCL_OK) { return TCL_ERROR; } switch(cmdIndex) { case LISTOBJ_SET: if ((varPtr[varIndex] != NULL) && !Tcl_IsShared(varPtr[varIndex])) { Tcl_SetListObj(varPtr[varIndex], objc-3, objv+3); } else { SetVarToObj(varPtr, varIndex, Tcl_NewListObj(objc-3, objv+3)); } Tcl_SetObjResult(interp, varPtr[varIndex]); break; case LISTOBJ_GET: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "varIndex"); return TCL_ERROR; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } Tcl_SetObjResult(interp, varPtr[varIndex]); break; case LISTOBJ_REPLACE: if (objc < 5) { Tcl_WrongNumArgs(interp, 2, objv, "varIndex start count ?element...?"); return TCL_ERROR; } if (Tcl_GetIntForIndex(interp, objv[3], TCL_INDEX_NONE, &first) != TCL_OK || Tcl_GetIntForIndex(interp, objv[4], TCL_INDEX_NONE, &count) != TCL_OK) { return TCL_ERROR; } if (Tcl_IsShared(varPtr[varIndex])) { SetVarToObj(varPtr, varIndex, Tcl_DuplicateObj(varPtr[varIndex])); } Tcl_ResetResult(interp); return Tcl_ListObjReplace(interp, varPtr[varIndex], first, count, objc-5, objv+5); case LISTOBJ_INDEXMEMCHECK: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "varIndex"); return TCL_ERROR; } if (CheckIfVarUnset(interp, varPtr, varIndex)) { return TCL_ERROR; } if (Tcl_ListObjLength(interp, varPtr[varIndex], &len) != TCL_OK) { return TCL_ERROR; } for (i = 0; i < len; ++i) { Tcl_Obj *objP; if (Tcl_ListObjIndex(interp, varPtr[varIndex], i, &objP) != TCL_OK) { return TCL_ERROR; } if (objP->refCount < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "Tcl_ListObjIndex returned object with ref count < 0", TCL_INDEX_NONE)); /* Keep looping since we are also looping for leaks */ } Tcl_BounceRefCount(objP); } break; case LISTOBJ_GETELEMENTSMEMCHECK: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "varIndex"); return TCL_ERROR; } if (CheckIfVarUnset(interp, varPtr, varIndex)) { return TCL_ERROR; } else { Tcl_Obj **elems; if (Tcl_ListObjGetElements(interp, varPtr[varIndex], &len, &elems) != TCL_OK) { return TCL_ERROR; } for (i = 0; i < len; ++i) { if (elems[i]->refCount <= 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "Tcl_ListObjGetElements element has ref count <= 0", TCL_INDEX_NONE)); break; } } } break; case LISTOBJ_INDEX: /* * Tcl_ListObjIndex semantics differ from lindex for out of bounds. * Hence this explicit test. */ if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "varIndex listIndex"); return TCL_ERROR; } if (Tcl_GetIntForIndex(interp, objv[3], TCL_INDEX_NONE, &first) != TCL_OK) { return TCL_ERROR; } else { Tcl_Obj *objP; if (Tcl_ListObjIndex(interp, varPtr[varIndex], first, &objP) != TCL_OK) { return TCL_ERROR; } Tcl_SetObjResult(interp, objP ? objP : Tcl_NewStringObj("null", -1)); } break; } return TCL_OK; } /* *---------------------------------------------------------------------- * * TestobjCmd -- * * This function implements the "testobj" command. It is used to test * the type-independent portions of the Tcl object type implementation. * * Results: * A standard Tcl object result. * * Side effects: * Creates and frees objects. * *---------------------------------------------------------------------- */ static Tcl_Size V1TestListObjLength(TCL_UNUSED(Tcl_Obj *)) { return 100; } static int V1TestListObjIndex( TCL_UNUSED(Tcl_Interp *), TCL_UNUSED(Tcl_Obj *), TCL_UNUSED(Tcl_Size), Tcl_Obj **objPtr) { *objPtr = Tcl_NewStringObj("This indexProc should never be accessed (bug: e58d7e19e9)", -1); return TCL_OK; } static const Tcl_ObjType v1TestListType = { "testlist", /* name */ NULL, /* freeIntRepProc */ NULL, /* dupIntRepProc */ NULL, /* updateStringProc */ NULL, /* setFromAnyProc */ offsetof(Tcl_ObjType, indexProc), /* This is a V1 objType, which doesn't have an indexProc */ V1TestListObjLength, /* always return 100, doesn't really matter */ V1TestListObjIndex, /* should never be accessed, because this objType = V1*/ NULL, NULL, NULL, NULL, NULL, NULL }; static int TestobjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size varIndex, destIndex; int i; const Tcl_ObjType *targetType; Tcl_Obj **varPtr; static const char *const subcommands[] = { "freeallvars", "bug3598580", "buge58d7e19e9", "types", "objtype", "newobj", "set", "assign", "convert", "duplicate", "invalidateStringRep", "refcount", "type", NULL }; enum testobjCmdIndex { TESTOBJ_FREEALLVARS, TESTOBJ_BUG3598580, TESTOBJ_BUGE58D7E19E9, TESTOBJ_TYPES, TESTOBJ_OBJTYPE, TESTOBJ_NEWOBJ, TESTOBJ_SET, TESTOBJ_ASSIGN, TESTOBJ_CONVERT, TESTOBJ_DUPLICATE, TESTOBJ_INVALIDATESTRINGREP, TESTOBJ_REFCOUNT, TESTOBJ_TYPE, } cmdIndex; if (objc < 2) { wrongNumArgs: Tcl_WrongNumArgs(interp, 1, objv, "option arg ?arg ...?"); return TCL_ERROR; } varPtr = GetVarPtr(interp); if (Tcl_GetIndexFromObj( interp, objv[1], subcommands, "command", 0, &cmdIndex) != TCL_OK) { return TCL_ERROR; } switch (cmdIndex) { case TESTOBJ_FREEALLVARS: if (objc != 2) { goto wrongNumArgs; } for (i = 0; i < NUMBER_OF_OBJECT_VARS; i++) { if (varPtr[i] != NULL) { Tcl_DecrRefCount(varPtr[i]); varPtr[i] = NULL; } } return TCL_OK; case TESTOBJ_BUG3598580: if (objc != 2) { goto wrongNumArgs; } else { Tcl_Obj *listObjPtr, *elemObjPtr; elemObjPtr = Tcl_NewWideIntObj(123); listObjPtr = Tcl_NewListObj(1, &elemObjPtr); /* Replace the single list element through itself, nonsense but * legal. */ Tcl_ListObjReplace(interp, listObjPtr, 0, 1, 1, &elemObjPtr); Tcl_SetObjResult(interp, listObjPtr); } return TCL_OK; case TESTOBJ_BUGE58D7E19E9: if (objc != 3) { goto wrongNumArgs; } else { Tcl_Obj *listObjPtr = Tcl_NewStringObj(Tcl_GetString(objv[2]), -1); listObjPtr->typePtr = &v1TestListType; Tcl_SetObjResult(interp, listObjPtr); } return TCL_OK; case TESTOBJ_TYPES: if (objc != 2) { goto wrongNumArgs; } else { Tcl_Obj *typesObj = Tcl_NewListObj(0, NULL); Tcl_AppendAllObjTypes(interp, typesObj); Tcl_SetObjResult(interp, typesObj); } return TCL_OK; case TESTOBJ_OBJTYPE: /* * Return an object containing the name of the argument's type of * internal rep. If none exists, return "none". */ if (objc != 3) { goto wrongNumArgs; } else { const char *typeName; if (objv[2]->typePtr == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("none", -1)); } else { typeName = objv[2]->typePtr->name; Tcl_SetObjResult(interp, Tcl_NewStringObj(typeName, -1)); } } return TCL_OK; case TESTOBJ_NEWOBJ: if (objc != 3) { goto wrongNumArgs; } if (GetVariableIndex(interp, objv[2], &varIndex) != TCL_OK) { return TCL_ERROR; } SetVarToObj(varPtr, varIndex, Tcl_NewObj()); Tcl_SetObjResult(interp, varPtr[varIndex]); return TCL_OK; case TESTOBJ_SET: if (objc != 4) { goto wrongNumArgs; } if (GetVariableIndex(interp, objv[2], &varIndex) != TCL_OK) { return TCL_ERROR; } SetVarToObj(varPtr, varIndex, objv[3]); return TCL_OK; default: break; } /* All further commands expect an occupied varindex argument */ if (objc < 3) { goto wrongNumArgs; } if (GetVariableIndex(interp, objv[2], &varIndex) != TCL_OK) { return TCL_ERROR; } if (CheckIfVarUnset(interp, varPtr, varIndex)) { return TCL_ERROR; } switch (cmdIndex) { case TESTOBJ_ASSIGN: if (objc != 4) { goto wrongNumArgs; } if (GetVariableIndex(interp, objv[3], &destIndex) != TCL_OK) { return TCL_ERROR; } SetVarToObj(varPtr, destIndex, varPtr[varIndex]); Tcl_SetObjResult(interp, varPtr[destIndex]); break; case TESTOBJ_CONVERT: if (objc != 4) { goto wrongNumArgs; } if ((targetType = Tcl_GetObjType(Tcl_GetString(objv[3]))) == NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "no type ", Tcl_GetString(objv[3]), " found", (char *)NULL); return TCL_ERROR; } if (Tcl_ConvertToType(interp, varPtr[varIndex], targetType) != TCL_OK) { return TCL_ERROR; } Tcl_SetObjResult(interp, varPtr[varIndex]); break; case TESTOBJ_DUPLICATE: if (objc != 4) { goto wrongNumArgs; } if (GetVariableIndex(interp, objv[3], &destIndex) != TCL_OK) { return TCL_ERROR; } SetVarToObj(varPtr, destIndex, Tcl_DuplicateObj(varPtr[varIndex])); Tcl_SetObjResult(interp, varPtr[destIndex]); break; case TESTOBJ_INVALIDATESTRINGREP: if (objc != 3) { goto wrongNumArgs; } Tcl_InvalidateStringRep(varPtr[varIndex]); Tcl_SetObjResult(interp, varPtr[varIndex]); break; case TESTOBJ_REFCOUNT: if (objc != 3) { goto wrongNumArgs; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(varPtr[varIndex]->refCount)); break; case TESTOBJ_TYPE: if (objc != 3) { goto wrongNumArgs; } if (varPtr[varIndex]->typePtr == NULL) { /* a string! */ Tcl_AppendToObj(Tcl_GetObjResult(interp), "string", -1); } else { Tcl_AppendToObj(Tcl_GetObjResult(interp), varPtr[varIndex]->typePtr->name, -1); } break; default: break; } return TCL_OK; } /* *---------------------------------------------------------------------- * * TeststringobjCmd -- * * This function implements the "teststringobj" command. It is used to * test the string Tcl object type implementation. * * Results: * A standard Tcl object result. * * Side effects: * Creates and frees string objects, and also converts objects to * have string type. * *---------------------------------------------------------------------- */ static int TeststringobjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_UniChar *unicode; Tcl_Size size, varIndex; int option, i; Tcl_Size length; #define MAX_STRINGS 11 const char *string, *strings[MAX_STRINGS+1]; String *strPtr; Tcl_Obj **varPtr; static const char *const options[] = { "append", "appendstrings", "get", "get2", "length", "length2", "set", "set2", "setlength", "maxchars", "range", "appendself", "appendself2", "newunicode", NULL }; if (objc < 3) { wrongNumArgs: Tcl_WrongNumArgs(interp, 1, objv, "option arg ?arg ...?"); return TCL_ERROR; } varPtr = GetVarPtr(interp); if (GetVariableIndex(interp, objv[2], &varIndex) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], options, "option", 0, &option) != TCL_OK) { return TCL_ERROR; } switch (option) { case 0: /* append */ if (objc != 5) { goto wrongNumArgs; } if (Tcl_GetIntForIndex(interp, objv[4], TCL_INDEX_NONE, &length) != TCL_OK) { return TCL_ERROR; } if (varPtr[varIndex] == NULL) { SetVarToObj(varPtr, varIndex, Tcl_NewObj()); } /* * If the object bound to variable "varIndex" is shared, we must * "copy on write" and append to a copy of the object. */ if (Tcl_IsShared(varPtr[varIndex])) { SetVarToObj(varPtr, varIndex, Tcl_DuplicateObj(varPtr[varIndex])); } Tcl_AppendToObj(varPtr[varIndex], Tcl_GetString(objv[3]), length); Tcl_SetObjResult(interp, varPtr[varIndex]); break; case 1: /* appendstrings */ if (objc > (MAX_STRINGS+3)) { goto wrongNumArgs; } if (varPtr[varIndex] == NULL) { SetVarToObj(varPtr, varIndex, Tcl_NewObj()); } /* * If the object bound to variable "varIndex" is shared, we must * "copy on write" and append to a copy of the object. */ if (Tcl_IsShared(varPtr[varIndex])) { SetVarToObj(varPtr, varIndex, Tcl_DuplicateObj(varPtr[varIndex])); } for (i = 3; i < objc; i++) { strings[i-3] = Tcl_GetString(objv[i]); } for ( ; i < 12 + 3; i++) { strings[i - 3] = NULL; } Tcl_AppendStringsToObj(varPtr[varIndex], strings[0], strings[1], strings[2], strings[3], strings[4], strings[5], strings[6], strings[7], strings[8], strings[9], strings[10], strings[11], (char *)NULL); Tcl_SetObjResult(interp, varPtr[varIndex]); break; case 2: /* get */ if (objc != 3) { goto wrongNumArgs; } if (CheckIfVarUnset(interp, varPtr,varIndex)) { return TCL_ERROR; } Tcl_SetObjResult(interp, varPtr[varIndex]); break; case 3: /* get2 */ if (objc != 3) { goto wrongNumArgs; } if (CheckIfVarUnset(interp, varPtr, varIndex)) { return TCL_ERROR; } Tcl_AppendToObj(Tcl_GetObjResult(interp), Tcl_GetString(varPtr[varIndex]), -1); break; case 4: /* length */ if (objc != 3) { goto wrongNumArgs; } Tcl_SetWideIntObj(Tcl_GetObjResult(interp), (varPtr[varIndex] != NULL) ? (Tcl_WideInt)varPtr[varIndex]->length : (Tcl_WideInt)-1); break; case 5: /* length2 */ if (objc != 3) { goto wrongNumArgs; } if (varPtr[varIndex] != NULL) { Tcl_ConvertToType(NULL, varPtr[varIndex], Tcl_GetObjType("string")); strPtr = (String *)varPtr[varIndex]->internalRep.twoPtrValue.ptr1; length = strPtr->allocated; } else { length = TCL_INDEX_NONE; } Tcl_SetWideIntObj(Tcl_GetObjResult(interp), (Tcl_WideInt)((Tcl_WideUInt)(length + 1U)) - 1); break; case 6: /* set */ if (objc != 4) { goto wrongNumArgs; } /* * If the object currently bound to the variable with index * varIndex has ref count 1 (i.e. the object is unshared) we can * modify that object directly. Otherwise, if RC>1 (i.e. the * object is shared), we must create a new object to modify/set * and decrement the old formerly-shared object's ref count. This * is "copy on write". */ string = Tcl_GetStringFromObj(objv[3], &size); if ((varPtr[varIndex] != NULL) && !Tcl_IsShared(varPtr[varIndex])) { Tcl_SetStringObj(varPtr[varIndex], string, size); } else { SetVarToObj(varPtr, varIndex, Tcl_NewStringObj(string, size)); } Tcl_SetObjResult(interp, varPtr[varIndex]); break; case 7: /* set2 */ if (objc != 4) { goto wrongNumArgs; } SetVarToObj(varPtr, varIndex, objv[3]); break; case 8: /* setlength */ if (objc != 4) { goto wrongNumArgs; } if (Tcl_GetIntForIndex(interp, objv[3], TCL_INDEX_NONE, &length) != TCL_OK) { return TCL_ERROR; } if (varPtr[varIndex] != NULL) { Tcl_SetObjLength(varPtr[varIndex], length); } break; case 9: /* maxchars */ if (objc != 3) { goto wrongNumArgs; } if (varPtr[varIndex] != NULL) { Tcl_ConvertToType(NULL, varPtr[varIndex], Tcl_GetObjType("string")); strPtr = (String *)varPtr[varIndex]->internalRep.twoPtrValue.ptr1; length = strPtr->maxChars; } else { length = TCL_INDEX_NONE; } Tcl_SetWideIntObj(Tcl_GetObjResult(interp), length); break; case 10: { /* range */ Tcl_Size first, last; if (objc != 5) { goto wrongNumArgs; } if ((Tcl_GetIntForIndex(interp, objv[3], TCL_INDEX_NONE, &first) != TCL_OK) || (Tcl_GetIntForIndex(interp, objv[4], TCL_INDEX_NONE, &last) != TCL_OK)) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_GetRange(varPtr[varIndex], first, last)); break; } case 11: /* appendself */ if (objc != 4) { goto wrongNumArgs; } if (varPtr[varIndex] == NULL) { SetVarToObj(varPtr, varIndex, Tcl_NewObj()); } /* * If the object bound to variable "varIndex" is shared, we must * "copy on write" and append to a copy of the object. */ if (Tcl_IsShared(varPtr[varIndex])) { SetVarToObj(varPtr, varIndex, Tcl_DuplicateObj(varPtr[varIndex])); } string = Tcl_GetStringFromObj(varPtr[varIndex], &size); if (Tcl_GetIntForIndex(interp, objv[3], size-1, &length) != TCL_OK) { return TCL_ERROR; } if (length == TCL_INDEX_NONE) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "index value out of range", -1)); return TCL_ERROR; } Tcl_AppendToObj(varPtr[varIndex], string + length, size - length); Tcl_SetObjResult(interp, varPtr[varIndex]); break; case 12: /* appendself2 */ if (objc != 4) { goto wrongNumArgs; } if (varPtr[varIndex] == NULL) { SetVarToObj(varPtr, varIndex, Tcl_NewObj()); } /* * If the object bound to variable "varIndex" is shared, we must * "copy on write" and append to a copy of the object. */ if (Tcl_IsShared(varPtr[varIndex])) { SetVarToObj(varPtr, varIndex, Tcl_DuplicateObj(varPtr[varIndex])); } unicode = Tcl_GetUnicodeFromObj(varPtr[varIndex], &size); if (Tcl_GetIntForIndex(interp, objv[3], size-1, &length) != TCL_OK) { return TCL_ERROR; } if (length == TCL_INDEX_NONE) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "index value out of range", -1)); return TCL_ERROR; } Tcl_AppendUnicodeToObj(varPtr[varIndex], unicode + length, size - length); Tcl_SetObjResult(interp, varPtr[varIndex]); break; case 13: /* newunicode*/ unicode = (Tcl_UniChar *)Tcl_Alloc((objc - 3) * sizeof(Tcl_UniChar)); for (i = 0; i < (objc - 3); ++i) { int val; if (Tcl_GetIntFromObj(interp, objv[i + 3], &val) != TCL_OK) { break; } unicode[i] = (Tcl_UniChar)val; } if (i < (objc-3)) { Tcl_Free(unicode); return TCL_ERROR; } SetVarToObj(varPtr, varIndex, Tcl_NewUnicodeObj(unicode, objc - 3)); Tcl_SetObjResult(interp, varPtr[varIndex]); Tcl_Free(unicode); break; } return TCL_OK; } /* *------------------------------------------------------------------------ * * TestbigdataCmd -- * * Implements the Tcl command testbigdata * testbigdata string ?LEN? ?SPLIT? - returns 01234567890123... * testbigdata bytearray ?LEN? ?SPLIT? - returns {0 1 2 3 4 5 6 7 8 9 0 1 ...} * testbigdata dict ?SIZE? - returns dict mapping integers to themselves * If no arguments given, returns the pattern used to generate strings. * If SPLIT is specified, the character at that position is set to "X". * * Results: * TCL_OK - Success. * TCL_ERROR - Error. * * Side effects: * Interpreter result holds result or error message. * *------------------------------------------------------------------------ */ static int TestbigdataCmd ( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const subcmds[] = { "string", "bytearray", "list", "dict", NULL }; enum options { BIGDATA_STRING, BIGDATA_BYTEARRAY, BIGDATA_LIST, BIGDATA_DICT } idx; char *s; unsigned char *p; Tcl_Size i, len, split; Tcl_DString ds; Tcl_Obj *objPtr; #define PATTERN_LEN 10 Tcl_Obj *patternObjs[PATTERN_LEN]; if (objc < 2 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "command ?len? ?split?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], subcmds, "option", 0, &idx) != TCL_OK) { return TCL_ERROR; } split = -1; if (objc == 2) { len = PATTERN_LEN; } else { if (Tcl_GetSizeIntFromObj(interp, objv[2], &len) != TCL_OK) { return TCL_ERROR; } if (objc == 4) { if (Tcl_GetSizeIntFromObj(interp, objv[3], &split) != TCL_OK) { return TCL_ERROR; } if (split >= len) { split = len - 1; /* Last position */ } } } /* Need one byte for nul terminator */ Tcl_Size limit = TCL_SIZE_MAX-1; if (len < 0 || len > limit) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s is greater than max permitted length %" TCL_SIZE_MODIFIER "d", Tcl_GetString(objv[2]), limit)); return TCL_ERROR; } switch (idx) { case BIGDATA_STRING: Tcl_DStringInit(&ds); Tcl_DStringSetLength(&ds, len);/* Also stores \0 at index len+1 */ s = Tcl_DStringValue(&ds); for (i = 0; i < len; ++i) { s[i] = '0' + (i % PATTERN_LEN); } if (split >= 0) { assert(split < len); s[split] = 'X'; } Tcl_DStringResult(interp, &ds); break; case BIGDATA_BYTEARRAY: objPtr = Tcl_NewByteArrayObj(NULL, len); p = Tcl_GetByteArrayFromObj(objPtr, &len); for (i = 0; i < len; ++i) { p[i] = '0' + (i % PATTERN_LEN); } if (split >= 0) { assert(split < len); p[split] = 'X'; } Tcl_SetObjResult(interp, objPtr); break; case BIGDATA_LIST: for (i = 0; i < PATTERN_LEN; ++i) { patternObjs[i] = Tcl_NewIntObj(i); Tcl_IncrRefCount(patternObjs[i]); } objPtr = Tcl_NewListObj(len, NULL); for (i = 0; i < len; ++i) { Tcl_ListObjAppendElement( interp, objPtr, patternObjs[i % PATTERN_LEN]); } if (split >= 0) { assert(split < len); Tcl_Obj *splitMarker = Tcl_NewStringObj("X", 1); Tcl_ListObjReplace(interp, objPtr, split, 1, 1, &splitMarker); } for (i = 0; i < PATTERN_LEN; ++i) { patternObjs[i] = Tcl_NewIntObj(i); Tcl_DecrRefCount(patternObjs[i]); } Tcl_SetObjResult(interp, objPtr); break; case BIGDATA_DICT: objPtr = Tcl_NewDictObj(); for (i = 0; i < len; ++i) { Tcl_Obj *objPtr2 = Tcl_NewWideIntObj(i); Tcl_DictObjPut(interp, objPtr, objPtr2, objPtr2); } Tcl_SetObjResult(interp, objPtr); break; } return TCL_OK; } /* *---------------------------------------------------------------------- * * SetVarToObj -- * * Utility routine to assign a Tcl_Obj* to a test variable. The * Tcl_Obj* can be NULL. * * Results: * None. * * Side effects: * This routine handles ref counting details for assignment: i.e. the old * value's ref count must be decremented (if not NULL) and the new one * incremented (also if not NULL). * *---------------------------------------------------------------------- */ static void SetVarToObj( Tcl_Obj **varPtr, Tcl_Size varIndex, /* Designates the assignment variable. */ Tcl_Obj *objPtr) /* Points to object to assign to var. */ { if (varPtr[varIndex] != NULL) { Tcl_DecrRefCount(varPtr[varIndex]); } varPtr[varIndex] = objPtr; if (objPtr != NULL) { Tcl_IncrRefCount(objPtr); } } /* *---------------------------------------------------------------------- * * GetVariableIndex -- * * Utility routine to get a test variable index from the command line. * * Results: * A standard Tcl object result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int GetVariableIndex( Tcl_Interp *interp, /* Interpreter for error reporting. */ Tcl_Obj *obj, /* The variable index * specified as a nonnegative number less than * NUMBER_OF_OBJECT_VARS. */ Tcl_Size *indexPtr) /* Place to store converted result. */ { Tcl_Size index; if (Tcl_GetIntForIndex(interp, obj, NUMBER_OF_OBJECT_VARS - 1, &index) != TCL_OK) { return TCL_ERROR; } if (index == TCL_INDEX_NONE) { Tcl_ResetResult(interp); Tcl_AppendToObj(Tcl_GetObjResult(interp), "bad variable index", -1); return TCL_ERROR; } *indexPtr = index; return TCL_OK; } /* *---------------------------------------------------------------------- * * CheckIfVarUnset -- * * Utility function that checks whether a test variable is readable: * i.e., that varPtr[varIndex] is non-NULL. * * Results: * 1 if the test variable is unset (NULL); 0 otherwise. * * Side effects: * Sets the interpreter result to an error message if the variable is * unset (NULL). * *---------------------------------------------------------------------- */ static int CheckIfVarUnset( Tcl_Interp *interp, /* Interpreter for error reporting. */ Tcl_Obj ** varPtr, Tcl_Size varIndex) /* Index of the test variable to check. */ { if (varIndex < 0 || varPtr[varIndex] == NULL) { char buf[32 + TCL_INTEGER_SPACE]; snprintf(buf, sizeof(buf), "variable %" TCL_SIZE_MODIFIER "d is unset (NULL)", varIndex); Tcl_ResetResult(interp); Tcl_AppendToObj(Tcl_GetObjResult(interp), buf, -1); return 1; } return 0; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclTestProcBodyObj.c0000644000175000017500000002157114726623136017063 0ustar sergeisergei/* * tclTestProcBodyObj.c -- * * Implements the "procbodytest" package, which contains commands to test * creation of Tcl procedures whose body argument is a Tcl_Obj of type * "procbody" rather than a string. * * Copyright © 1998 Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #undef BUILD_tcl #undef STATIC_BUILD #ifndef USE_TCL_STUBS # define USE_TCL_STUBS #endif #include "tclInt.h" /* * name and version of this package */ static const char packageName[] = "tcl::procbodytest"; static const char packageVersion[] = "1.1"; /* * Name of the commands exported by this package */ static const char procCommand[] = "proc"; static const char checkCommand[] = "check"; /* * this struct describes an entry in the table of command names and command * procs */ typedef struct { const char *cmdName; /* command name */ Tcl_ObjCmdProc *proc; /* command proc */ int exportIt; /* if 1, export the command */ } CmdTable; /* * Declarations for functions defined in this file. */ static Tcl_ObjCmdProc ProcBodyTestProcCmd; static Tcl_ObjCmdProc ProcBodyTestCheckCmd; static int ProcBodyTestInitInternal(Tcl_Interp *interp, int isSafe); static int RegisterCommand(Tcl_Interp* interp, const char *namesp, const CmdTable *cmdTablePtr); /* * List of commands to create when the package is loaded; must go after the * declarations of the enable command procedure. */ static const CmdTable commands[] = { { procCommand, ProcBodyTestProcCmd, 1 }, { checkCommand, ProcBodyTestCheckCmd, 1 }, { 0, 0, 0 } }; static const CmdTable safeCommands[] = { { procCommand, ProcBodyTestProcCmd, 1 }, { checkCommand, ProcBodyTestCheckCmd, 1 }, { 0, 0, 0 } }; /* *---------------------------------------------------------------------- * * Procbodytest_Init -- * * This function initializes the "tcl::procbodytest" package. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Procbodytest_Init( Tcl_Interp *interp) /* the Tcl interpreter for which the package * is initialized */ { return ProcBodyTestInitInternal(interp, 0); } /* *---------------------------------------------------------------------- * * Procbodytest_SafeInit -- * * This function initializes the "tcl::procbodytest" package. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Procbodytest_SafeInit( Tcl_Interp *interp) /* the Tcl interpreter for which the package * is initialized */ { return ProcBodyTestInitInternal(interp, 1); } /* *---------------------------------------------------------------------- * * RegisterCommand -- * * This function registers a command in the context of the given * namespace. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int RegisterCommand( Tcl_Interp* interp, /* the Tcl interpreter for which the operation * is performed */ const char *namesp, /* the namespace in which the command is * registered */ const CmdTable *cmdTablePtr)/* the command to register */ { char buf[128]; if (cmdTablePtr->exportIt) { snprintf(buf, sizeof(buf), "namespace eval %s { namespace export %s }", namesp, cmdTablePtr->cmdName); if (Tcl_EvalEx(interp, buf, TCL_INDEX_NONE, 0) != TCL_OK) { return TCL_ERROR; } } snprintf(buf, sizeof(buf), "%s::%s", namesp, cmdTablePtr->cmdName); Tcl_CreateObjCommand(interp, buf, cmdTablePtr->proc, 0, 0); return TCL_OK; } /* *---------------------------------------------------------------------- * * ProcBodyTestInitInternal -- * * This function initializes the Loader package. * The isSafe flag is 1 if the interpreter is safe, 0 otherwise. * * Results: * A standard Tcl result. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int ProcBodyTestInitInternal( Tcl_Interp *interp, /* the Tcl interpreter for which the package * is initialized */ int isSafe) /* 1 if this is a safe interpreter */ { const CmdTable *cmdTablePtr; cmdTablePtr = (isSafe) ? &safeCommands[0] : &commands[0]; for ( ; cmdTablePtr->cmdName ; cmdTablePtr++) { if (RegisterCommand(interp, packageName, cmdTablePtr) != TCL_OK) { return TCL_ERROR; } } return Tcl_PkgProvideEx(interp, packageName, packageVersion, NULL); } /* *---------------------------------------------------------------------- * * ProcBodyTestProcCmd -- * * Implements the "procbodytest::proc" command. Here is the command * description: * procbodytest::proc newName argList bodyName * Looks up a procedure called $bodyName and, if the procedure exists, * constructs a Tcl_Obj of type "procbody" and calls Tcl_ProcObjCmd. * Arguments: * newName the name of the procedure to be created * argList the argument list for the procedure * bodyName the name of an existing procedure from which the * body is to be copied. * This command can be used to trigger the branches in Tcl_ProcObjCmd that * construct a proc from a "procbody", for example: * proc a {x} {return $x} * a 123 * procbodytest::proc b {x} a * Note the call to "a 123", which is necessary so that the Proc pointer * for "a" is filled in by the internal compiler; this is a hack. * * Results: * Returns a standard Tcl code. * * Side effects: * A new procedure is created. * Leaves an error message in the interp's result on error. * *---------------------------------------------------------------------- */ static int ProcBodyTestProcCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* the current interpreter */ int objc, /* argument count */ Tcl_Obj *const objv[]) /* arguments */ { const char *fullName; Tcl_Command procCmd; Command *cmdPtr; Proc *procPtr = NULL; Tcl_Obj *bodyObjPtr; Tcl_Obj *myobjv[5]; int result; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "newName argsList bodyName"); return TCL_ERROR; } /* * Find the Command pointer to this procedure */ fullName = Tcl_GetString(objv[3]); procCmd = Tcl_FindCommand(interp, fullName, NULL, TCL_LEAVE_ERR_MSG); if (procCmd == NULL) { return TCL_ERROR; } cmdPtr = (Command *) procCmd; /* * check that this is a procedure and not a builtin command: * If a procedure, cmdPtr->objClientData is TclIsProc(cmdPtr). */ if (cmdPtr->objClientData != TclIsProc(cmdPtr)) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "command \"", fullName, "\" is not a Tcl procedure", (char *)NULL); return TCL_ERROR; } /* * it is a Tcl procedure: the client data is the Proc structure */ procPtr = (Proc *) cmdPtr->objClientData; if (procPtr == NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "procedure \"", fullName, "\" does not have a Proc struct!", (char *)NULL); return TCL_ERROR; } /* * create a new object, initialize our argument vector, call into Tcl */ bodyObjPtr = TclNewProcBodyObj(procPtr); if (bodyObjPtr == NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "failed to create a procbody object for procedure \"", fullName, "\"", (char *)NULL); return TCL_ERROR; } Tcl_IncrRefCount(bodyObjPtr); myobjv[0] = objv[0]; myobjv[1] = objv[1]; myobjv[2] = objv[2]; myobjv[3] = bodyObjPtr; myobjv[4] = NULL; result = Tcl_ProcObjCmd(NULL, interp, objc, myobjv); Tcl_DecrRefCount(bodyObjPtr); return result; } /* *---------------------------------------------------------------------- * * ProcBodyTestCheckCmd -- * * Implements the "procbodytest::check" command. Here is the command * description: * procbodytest::check * * Performs an internal check that the Tcl_PkgPresent() command returns * the same version number as was registered when the tcl::procbodytest package * was provided. Places a boolean in the interp result indicating the * test outcome. * * Results: * Returns a standard Tcl code. * *---------------------------------------------------------------------- */ static int ProcBodyTestCheckCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* the current interpreter */ int objc, /* argument count */ Tcl_Obj *const objv[]) /* arguments */ { const char *version; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } version = Tcl_PkgPresentEx(interp, packageName, packageVersion, 1, NULL); Tcl_SetObjResult(interp, Tcl_NewBooleanObj( strcmp(version, packageVersion) == 0)); return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclThread.c0000644000175000017500000002555614726623136015265 0ustar sergeisergei/* * tclThread.c -- * * This file implements Platform independent thread operations. Most of * the real work is done in the platform dependent files. * * Copyright © 1998 Sun Microsystems, Inc. * Copyright © 2008 George Peter Staplin * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * There are three classes of synchronization objects: mutexes, thread data * keys, and condition variables. The following are used to record the memory * used for these objects so they can be finalized. * * These statics are guarded by the mutex in the caller of * TclRememberThreadData, e.g., TclpThreadDataKeyInit */ typedef struct { int num; /* Number of objects remembered */ int max; /* Max size of the array */ void **list; /* List of pointers */ } SyncObjRecord; static SyncObjRecord keyRecord = {0, 0, NULL}; static SyncObjRecord mutexRecord = {0, 0, NULL}; static SyncObjRecord condRecord = {0, 0, NULL}; /* * Prototypes of functions used only in this file. */ static void ForgetSyncObject(void *objPtr, SyncObjRecord *recPtr); static void RememberSyncObject(void *objPtr, SyncObjRecord *recPtr); /* *---------------------------------------------------------------------- * * Tcl_GetThreadData -- * * This function allocates and initializes a chunk of thread local * storage. * * Results: * A thread-specific pointer to the data structure. * * Side effects: * Will allocate memory the first time this thread calls for this chunk * of storage. * *---------------------------------------------------------------------- */ void * Tcl_GetThreadData( Tcl_ThreadDataKey *keyPtr, /* Identifier for the data chunk */ Tcl_Size size) /* Size of storage block */ { void *result; #if TCL_THREADS /* * Initialize the key for this thread. */ result = TclThreadStorageKeyGet(keyPtr); if (result == NULL) { result = Tcl_Alloc(size); memset(result, 0, size); TclThreadStorageKeySet(keyPtr, result); } #else /* TCL_THREADS */ if (*keyPtr == NULL) { result = Tcl_Alloc(size); memset(result, 0, size); *keyPtr = (Tcl_ThreadDataKey)result; RememberSyncObject(keyPtr, &keyRecord); } else { result = *keyPtr; } #endif /* TCL_THREADS */ return result; } /* *---------------------------------------------------------------------- * * TclThreadDataKeyGet -- * * This function returns a pointer to a block of thread local storage. * * Results: * A thread-specific pointer to the data structure, or NULL if the memory * has not been assigned to this key for this thread. * * Side effects: * None. * *---------------------------------------------------------------------- */ void * TclThreadDataKeyGet( Tcl_ThreadDataKey *keyPtr) /* Identifier for the data chunk. */ { #if TCL_THREADS return TclThreadStorageKeyGet(keyPtr); #else /* TCL_THREADS */ return *keyPtr; #endif /* TCL_THREADS */ } /* *---------------------------------------------------------------------- * * RememberSyncObject * * Keep a list of (mutexes/condition variable/data key) used during * finalization. * * Assume global lock is held. * * Results: * None. * * Side effects: * Add to the appropriate list. * *---------------------------------------------------------------------- */ static void RememberSyncObject( void *objPtr, /* Pointer to sync object */ SyncObjRecord *recPtr) /* Record of sync objects */ { void **newList; int i, j; /* * Reuse any free slot in the list. */ for (i=0 ; i < recPtr->num ; ++i) { if (recPtr->list[i] == NULL) { recPtr->list[i] = objPtr; return; } } /* * Grow the list of pointers if necessary, copying only non-NULL * pointers to the new list. */ if (recPtr->num >= recPtr->max) { recPtr->max += 8; newList = (void **)Tcl_Alloc(recPtr->max * sizeof(void *)); for (i=0,j=0 ; inum ; i++) { if (recPtr->list[i] != NULL) { newList[j++] = recPtr->list[i]; } } if (recPtr->list != NULL) { Tcl_Free(recPtr->list); } recPtr->list = newList; recPtr->num = j; } recPtr->list[recPtr->num] = objPtr; recPtr->num++; } /* *---------------------------------------------------------------------- * * ForgetSyncObject * * Remove a single object from the list. * Assume global lock is held. * * Results: * None. * * Side effects: * Remove from the appropriate list. * *---------------------------------------------------------------------- */ static void ForgetSyncObject( void *objPtr, /* Pointer to sync object */ SyncObjRecord *recPtr) /* Record of sync objects */ { int i; for (i=0 ; inum ; i++) { if (objPtr == recPtr->list[i]) { recPtr->list[i] = NULL; return; } } } /* *---------------------------------------------------------------------- * * TclRememberMutex * * Keep a list of mutexes used during finalization. * Assume global lock is held. * * Results: * None. * * Side effects: * Add to the mutex list. * *---------------------------------------------------------------------- */ void TclRememberMutex( Tcl_Mutex *mutexPtr) { RememberSyncObject(mutexPtr, &mutexRecord); } /* *---------------------------------------------------------------------- * * Tcl_MutexFinalize -- * * Finalize a single mutex and remove it from the list of remembered * objects. * * Results: * None. * * Side effects: * Remove the mutex from the list. * *---------------------------------------------------------------------- */ #undef Tcl_MutexFinalize void Tcl_MutexFinalize( Tcl_Mutex *mutexPtr) { #if TCL_THREADS TclpFinalizeMutex(mutexPtr); #endif TclpGlobalLock(); ForgetSyncObject(mutexPtr, &mutexRecord); TclpGlobalUnlock(); } /* *---------------------------------------------------------------------- * * TclRememberCondition * * Keep a list of condition variables used during finalization. * Assume global lock is held. * * Results: * None. * * Side effects: * Add to the condition variable list. * *---------------------------------------------------------------------- */ void TclRememberCondition( Tcl_Condition *condPtr) { RememberSyncObject(condPtr, &condRecord); } /* *---------------------------------------------------------------------- * * Tcl_ConditionFinalize -- * * Finalize a single condition variable and remove it from the list of * remembered objects. * * Results: * None. * * Side effects: * Remove the condition variable from the list. * *---------------------------------------------------------------------- */ #undef Tcl_ConditionFinalize void Tcl_ConditionFinalize( Tcl_Condition *condPtr) { #if TCL_THREADS TclpFinalizeCondition(condPtr); #endif TclpGlobalLock(); ForgetSyncObject(condPtr, &condRecord); TclpGlobalUnlock(); } /* *---------------------------------------------------------------------- * * TclFinalizeThreadData -- * * This function cleans up the thread-local storage. Secondary, it cleans * thread alloc cache. * This is called once for each thread before thread exits. * * Results: * None. * * Side effects: * Frees up all thread local storage. * *---------------------------------------------------------------------- */ void TclFinalizeThreadData(int quick) { TclFinalizeThreadDataThread(); #if TCL_THREADS && defined(USE_THREAD_ALLOC) if (!quick) { /* * Quick exit principle makes it useless to terminate allocators */ TclFinalizeThreadAllocThread(); } #else (void)quick; #endif } /* *---------------------------------------------------------------------- * * TclFinalizeSynchronization -- * * This function cleans up all synchronization objects: mutexes, * condition variables, and thread-local storage. * * Results: * None. * * Side effects: * Frees up the memory. * *---------------------------------------------------------------------- */ void TclFinalizeSynchronization(void) { int i; void *blockPtr; Tcl_ThreadDataKey *keyPtr; #if TCL_THREADS Tcl_Mutex *mutexPtr; Tcl_Condition *condPtr; TclpGlobalLock(); #endif /* * If we're running unthreaded, the TSD blocks are simply stored inside * their thread data keys. Free them here. */ if (keyRecord.list != NULL) { for (i=0 ; i> 5)) #define MAXALLOC (MINALLOC << (NBUCKETS - 1)) /* * The following structure defines a bucket of blocks with various accounting * and statistics information. */ typedef struct { Block *firstPtr; /* First block available */ Block *lastPtr; /* End of block list */ size_t numFree; /* Number of blocks available */ /* All fields below for accounting only */ size_t numRemoves; /* Number of removes from bucket */ size_t numInserts; /* Number of inserts into bucket */ size_t numLocks; /* Number of locks acquired */ size_t totalAssigned; /* Total space assigned to bucket */ } Bucket; /* * The following structure defines a cache of buckets and objs, of which there * will be (at most) one per thread. Any changes need to be reflected in the * struct AllocCache defined in tclInt.h, possibly also in the initialisation * code in Tcl_CreateInterp(). */ typedef struct Cache { struct Cache *nextPtr; /* Linked list of cache entries */ Tcl_ThreadId owner; /* Which thread's cache is this? */ Tcl_Obj *firstObjPtr; /* List of free objects for thread */ size_t numObjects; /* Number of objects for thread */ Tcl_Obj *lastPtr; /* Last object in this cache */ size_t totalAssigned; /* Total space assigned to thread */ Bucket buckets[NBUCKETS]; /* The buckets for this thread */ } Cache; /* * The following array specifies various per-bucket limits and locks. The * values are statically initialized to avoid calculating them repeatedly. */ static struct { size_t blockSize; /* Bucket blocksize. */ size_t maxBlocks; /* Max blocks before move to share. */ size_t numMove; /* Num blocks to move to share. */ Tcl_Mutex *lockPtr; /* Share bucket lock. */ } bucketInfo[NBUCKETS]; /* * Static functions defined in this file. */ static Cache * GetCache(void); static void LockBucket(Cache *cachePtr, int bucket); static void UnlockBucket(Cache *cachePtr, int bucket); static void PutBlocks(Cache *cachePtr, int bucket, size_t numMove); static int GetBlocks(Cache *cachePtr, int bucket); static Block * Ptr2Block(void *ptr); static void * Block2Ptr(Block *blockPtr, int bucket, size_t reqSize); static void MoveObjs(Cache *fromPtr, Cache *toPtr, size_t numMove); static void PutObjs(Cache *fromPtr, size_t numMove); /* * Local variables defined in this file and initialized at startup. */ static Tcl_Mutex *listLockPtr; static Tcl_Mutex *objLockPtr; static Cache sharedCache; static Cache *sharedPtr = &sharedCache; static Cache *firstCachePtr = &sharedCache; #if defined(HAVE_FAST_TSD) static __thread Cache *tcachePtr; # define GETCACHE(cachePtr) \ do { \ if (!tcachePtr) { \ tcachePtr = GetCache(); \ } \ (cachePtr) = tcachePtr; \ } while (0) #else # define GETCACHE(cachePtr) \ do { \ (cachePtr) = (Cache*)TclpGetAllocCache(); \ if ((cachePtr) == NULL) { \ (cachePtr) = GetCache(); \ } \ } while (0) #endif /* *---------------------------------------------------------------------- * * GetCache --- * * Gets per-thread memory cache, allocating it if necessary. * * Results: * Pointer to cache. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Cache * GetCache(void) { Cache *cachePtr; /* * Check for first-time initialization. */ if (listLockPtr == NULL) { Tcl_Mutex *initLockPtr; initLockPtr = Tcl_GetAllocMutex(); Tcl_MutexLock(initLockPtr); if (listLockPtr == NULL) { TclInitThreadAlloc(); } Tcl_MutexUnlock(initLockPtr); } /* * Get this thread's cache, allocating if necessary. */ cachePtr = (Cache*)TclpGetAllocCache(); if (cachePtr == NULL) { cachePtr = (Cache*)TclpSysAlloc(sizeof(Cache)); if (cachePtr == NULL) { Tcl_Panic("alloc: could not allocate new cache"); } memset(cachePtr, 0, sizeof(Cache)); Tcl_MutexLock(listLockPtr); cachePtr->nextPtr = firstCachePtr; firstCachePtr = cachePtr; Tcl_MutexUnlock(listLockPtr); cachePtr->owner = Tcl_GetCurrentThread(); TclpSetAllocCache(cachePtr); } return cachePtr; } /* *---------------------------------------------------------------------- * * TclFreeAllocCache -- * * Flush and delete a cache, removing from list of caches. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclFreeAllocCache( void *arg) { Cache *cachePtr = (Cache*)arg; Cache **nextPtrPtr; unsigned int bucket; /* * Flush blocks. */ for (bucket = 0; bucket < NBUCKETS; ++bucket) { if (cachePtr->buckets[bucket].numFree > 0) { PutBlocks(cachePtr, bucket, cachePtr->buckets[bucket].numFree); } } /* * Flush objs. */ if (cachePtr->numObjects > 0) { PutObjs(cachePtr, cachePtr->numObjects); } /* * Remove from pool list. */ Tcl_MutexLock(listLockPtr); nextPtrPtr = &firstCachePtr; while (*nextPtrPtr != cachePtr) { nextPtrPtr = &(*nextPtrPtr)->nextPtr; } *nextPtrPtr = cachePtr->nextPtr; cachePtr->nextPtr = NULL; Tcl_MutexUnlock(listLockPtr); TclpSysFree(cachePtr); } /* *---------------------------------------------------------------------- * * TclpAlloc -- * * Allocate memory. * * Results: * Pointer to memory just beyond Block pointer. * * Side effects: * May allocate more blocks for a bucket. * *---------------------------------------------------------------------- */ void * TclpAlloc( size_t reqSize) { Cache *cachePtr; Block *blockPtr; int bucket; size_t size; GETCACHE(cachePtr); /* * Increment the requested size to include room for the Block structure. * Call TclpSysAlloc() directly if the required amount is greater than the * largest block, otherwise pop the smallest block large enough, * allocating more blocks if necessary. */ blockPtr = NULL; size = reqSize + sizeof(Block); #if RCHECK size++; #endif if (size > MAXALLOC) { bucket = NBUCKETS; blockPtr = (Block *)TclpSysAlloc(size); if (blockPtr != NULL) { cachePtr->totalAssigned += reqSize; } } else { bucket = 0; while (bucketInfo[bucket].blockSize < size) { bucket++; } if (cachePtr->buckets[bucket].numFree || GetBlocks(cachePtr, bucket)) { blockPtr = cachePtr->buckets[bucket].firstPtr; cachePtr->buckets[bucket].firstPtr = blockPtr->nextBlock; cachePtr->buckets[bucket].numFree--; cachePtr->buckets[bucket].numRemoves++; cachePtr->buckets[bucket].totalAssigned += reqSize; } } if (blockPtr == NULL) { return NULL; } return Block2Ptr(blockPtr, bucket, reqSize); } /* *---------------------------------------------------------------------- * * TclpFree -- * * Return blocks to the thread block cache. * * Results: * None. * * Side effects: * May move blocks to shared cache. * *---------------------------------------------------------------------- */ void TclpFree( void *ptr) { Cache *cachePtr; Block *blockPtr; int bucket; if (ptr == NULL) { return; } GETCACHE(cachePtr); /* * Get the block back from the user pointer and call system free directly * for large blocks. Otherwise, push the block back on the bucket and move * blocks to the shared cache if there are now too many free. */ blockPtr = Ptr2Block(ptr); bucket = blockPtr->sourceBucket; if (bucket == NBUCKETS) { cachePtr->totalAssigned -= blockPtr->blockReqSize; TclpSysFree(blockPtr); return; } cachePtr->buckets[bucket].totalAssigned -= blockPtr->blockReqSize; blockPtr->nextBlock = cachePtr->buckets[bucket].firstPtr; cachePtr->buckets[bucket].firstPtr = blockPtr; if (cachePtr->buckets[bucket].numFree == 0) { cachePtr->buckets[bucket].lastPtr = blockPtr; } cachePtr->buckets[bucket].numFree++; cachePtr->buckets[bucket].numInserts++; if (cachePtr != sharedPtr && cachePtr->buckets[bucket].numFree > bucketInfo[bucket].maxBlocks) { PutBlocks(cachePtr, bucket, bucketInfo[bucket].numMove); } } /* *---------------------------------------------------------------------- * * TclpRealloc -- * * Re-allocate memory to a larger or smaller size. * * Results: * Pointer to memory just beyond Block pointer. * * Side effects: * Previous memory, if any, may be freed. * *---------------------------------------------------------------------- */ void * TclpRealloc( void *ptr, size_t reqSize) { Cache *cachePtr; Block *blockPtr; void *newPtr; size_t size, min; int bucket; if (ptr == NULL) { return TclpAlloc(reqSize); } GETCACHE(cachePtr); /* * If the block is not a system block and fits in place, simply return the * existing pointer. Otherwise, if the block is a system block and the new * size would also require a system block, call TclpSysRealloc() directly. */ blockPtr = Ptr2Block(ptr); size = reqSize + sizeof(Block); #if RCHECK size++; #endif bucket = blockPtr->sourceBucket; if (bucket != NBUCKETS) { if (bucket > 0) { min = bucketInfo[bucket-1].blockSize; } else { min = 0; } if (size > min && size <= bucketInfo[bucket].blockSize) { cachePtr->buckets[bucket].totalAssigned -= blockPtr->blockReqSize; cachePtr->buckets[bucket].totalAssigned += reqSize; return Block2Ptr(blockPtr, bucket, reqSize); } } else if (size > MAXALLOC) { cachePtr->totalAssigned -= blockPtr->blockReqSize; cachePtr->totalAssigned += reqSize; blockPtr = (Block*)TclpSysRealloc(blockPtr, size); if (blockPtr == NULL) { return NULL; } return Block2Ptr(blockPtr, NBUCKETS, reqSize); } /* * Finally, perform an expensive malloc/copy/free. */ newPtr = TclpAlloc(reqSize); if (newPtr != NULL) { if (reqSize > blockPtr->blockReqSize) { reqSize = blockPtr->blockReqSize; } memcpy(newPtr, ptr, reqSize); TclpFree(ptr); } return newPtr; } /* *---------------------------------------------------------------------- * * TclThreadAllocObj -- * * Allocate a Tcl_Obj from the per-thread cache. * * Results: * Pointer to uninitialized Tcl_Obj. * * Side effects: * May move Tcl_Obj's from shared cached or allocate new Tcl_Obj's if * list is empty. * * Note: * If this code is updated, the changes need to be reflected in the macro * TclAllocObjStorageEx() defined in tclInt.h * *---------------------------------------------------------------------- */ Tcl_Obj * TclThreadAllocObj(void) { Cache *cachePtr; Tcl_Obj *objPtr; GETCACHE(cachePtr); /* * Get this thread's obj list structure and move or allocate new objs if * necessary. */ if (cachePtr->numObjects == 0) { size_t numMove; Tcl_MutexLock(objLockPtr); numMove = sharedPtr->numObjects; if (numMove > 0) { if (numMove > NOBJALLOC) { numMove = NOBJALLOC; } MoveObjs(sharedPtr, cachePtr, numMove); } Tcl_MutexUnlock(objLockPtr); if (cachePtr->numObjects == 0) { Tcl_Obj *newObjsPtr; cachePtr->numObjects = numMove = NOBJALLOC; newObjsPtr = (Tcl_Obj *)TclpSysAlloc(sizeof(Tcl_Obj) * numMove); if (newObjsPtr == NULL) { Tcl_Panic("alloc: could not allocate %" TCL_Z_MODIFIER "u new objects", numMove); } cachePtr->lastPtr = newObjsPtr + numMove - 1; objPtr = cachePtr->firstObjPtr; /* NULL */ while (numMove-- > 0) { newObjsPtr[numMove].internalRep.twoPtrValue.ptr1 = objPtr; objPtr = newObjsPtr + numMove; } cachePtr->firstObjPtr = newObjsPtr; } } /* * Pop the first object. */ objPtr = cachePtr->firstObjPtr; cachePtr->firstObjPtr = (Tcl_Obj *)objPtr->internalRep.twoPtrValue.ptr1; cachePtr->numObjects--; return objPtr; } /* *---------------------------------------------------------------------- * * TclThreadFreeObj -- * * Return a free Tcl_Obj to the per-thread cache. * * Results: * None. * * Side effects: * May move free Tcl_Obj's to shared list upon hitting high water mark. * * Note: * If this code is updated, the changes need to be reflected in the macro * TclAllocObjStorageEx() defined in tclInt.h * *---------------------------------------------------------------------- */ void TclThreadFreeObj( Tcl_Obj *objPtr) { Cache *cachePtr; GETCACHE(cachePtr); /* * Get this thread's list and push on the free Tcl_Obj. */ objPtr->internalRep.twoPtrValue.ptr1 = cachePtr->firstObjPtr; cachePtr->firstObjPtr = objPtr; if (cachePtr->numObjects == 0) { cachePtr->lastPtr = objPtr; } cachePtr->numObjects++; /* * If the number of free objects has exceeded the high water mark, move * some blocks to the shared list. */ if (cachePtr->numObjects > NOBJHIGH) { PutObjs(cachePtr, NOBJALLOC); } } /* *---------------------------------------------------------------------- * * Tcl_GetMemoryInfo -- * * Return a list-of-lists of memory stats. * * Results: * None. * * Side effects: * List appended to given dstring. * *---------------------------------------------------------------------- */ void Tcl_GetMemoryInfo( Tcl_DString *dsPtr) { Cache *cachePtr; char buf[200]; unsigned int n; Tcl_MutexLock(listLockPtr); cachePtr = firstCachePtr; while (cachePtr != NULL) { Tcl_DStringStartSublist(dsPtr); if (cachePtr == sharedPtr) { Tcl_DStringAppendElement(dsPtr, "shared"); } else { snprintf(buf, sizeof(buf), "thread%p", cachePtr->owner); Tcl_DStringAppendElement(dsPtr, buf); } for (n = 0; n < NBUCKETS; ++n) { snprintf(buf, sizeof(buf), "%" TCL_Z_MODIFIER "u %" TCL_Z_MODIFIER "u %" TCL_Z_MODIFIER "u %" TCL_Z_MODIFIER "u %" TCL_Z_MODIFIER "u %" TCL_Z_MODIFIER "u", bucketInfo[n].blockSize, cachePtr->buckets[n].numFree, cachePtr->buckets[n].numRemoves, cachePtr->buckets[n].numInserts, cachePtr->buckets[n].totalAssigned, cachePtr->buckets[n].numLocks); Tcl_DStringAppendElement(dsPtr, buf); } Tcl_DStringEndSublist(dsPtr); cachePtr = cachePtr->nextPtr; } Tcl_MutexUnlock(listLockPtr); } /* *---------------------------------------------------------------------- * * MoveObjs -- * * Move Tcl_Obj's between caches. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void MoveObjs( Cache *fromPtr, Cache *toPtr, size_t numMove) { Tcl_Obj *objPtr = fromPtr->firstObjPtr; Tcl_Obj *fromFirstObjPtr = objPtr; toPtr->numObjects += numMove; fromPtr->numObjects -= numMove; /* * Find the last object to be moved; set the next one (the first one not * to be moved) as the first object in the 'from' cache. */ while (numMove-- > 1) { objPtr = (Tcl_Obj *)objPtr->internalRep.twoPtrValue.ptr1; } fromPtr->firstObjPtr = (Tcl_Obj *)objPtr->internalRep.twoPtrValue.ptr1; /* * Move all objects as a block - they are already linked to each other, we * just have to update the first and last. */ toPtr->lastPtr = objPtr; objPtr->internalRep.twoPtrValue.ptr1 = toPtr->firstObjPtr; /* NULL */ toPtr->firstObjPtr = fromFirstObjPtr; } /* *---------------------------------------------------------------------- * * PutObjs -- * * Move Tcl_Obj's from thread cache to shared cache. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void PutObjs( Cache *fromPtr, size_t numMove) { size_t keep = fromPtr->numObjects - numMove; Tcl_Obj *firstPtr, *lastPtr = NULL; fromPtr->numObjects = keep; firstPtr = fromPtr->firstObjPtr; if (keep == 0) { fromPtr->firstObjPtr = NULL; } else { do { lastPtr = firstPtr; firstPtr = (Tcl_Obj *)firstPtr->internalRep.twoPtrValue.ptr1; } while (keep-- > 1); lastPtr->internalRep.twoPtrValue.ptr1 = NULL; } /* * Move all objects as a block - they are already linked to each other, we * just have to update the first and last. */ Tcl_MutexLock(objLockPtr); fromPtr->lastPtr->internalRep.twoPtrValue.ptr1 = sharedPtr->firstObjPtr; sharedPtr->firstObjPtr = firstPtr; if (sharedPtr->numObjects == 0) { sharedPtr->lastPtr = fromPtr->lastPtr; } sharedPtr->numObjects += numMove; Tcl_MutexUnlock(objLockPtr); fromPtr->lastPtr = lastPtr; } /* *---------------------------------------------------------------------- * * Block2Ptr, Ptr2Block -- * * Convert between internal blocks and user pointers. * * Results: * User pointer or internal block. * * Side effects: * Invalid blocks will abort the server. * *---------------------------------------------------------------------- */ static void * Block2Ptr( Block *blockPtr, int bucket, size_t reqSize) { void *ptr; blockPtr->magicNum1 = blockPtr->magicNum2 = MAGIC; blockPtr->sourceBucket = bucket; blockPtr->blockReqSize = reqSize; ptr = ((void *) (blockPtr + 1)); #if RCHECK ((unsigned char *)(ptr))[reqSize] = MAGIC; #endif return ptr; } static Block * Ptr2Block( void *ptr) { Block *blockPtr; blockPtr = (((Block *) ptr) - 1); if (blockPtr->magicNum1 != MAGIC || blockPtr->magicNum2 != MAGIC) { Tcl_Panic("alloc: invalid block: %p: %x %x", blockPtr, blockPtr->magicNum1, blockPtr->magicNum2); } #if RCHECK if (((unsigned char *) ptr)[blockPtr->blockReqSize] != MAGIC) { Tcl_Panic("alloc: invalid block: %p: %x %x %x", blockPtr, blockPtr->magicNum1, blockPtr->magicNum2, ((unsigned char *) ptr)[blockPtr->blockReqSize]); } #endif return blockPtr; } /* *---------------------------------------------------------------------- * * LockBucket, UnlockBucket -- * * Set/unset the lock to access a bucket in the shared cache. * * Results: * None. * * Side effects: * Lock activity and contention are monitored globally and on a per-cache * basis. * *---------------------------------------------------------------------- */ static void LockBucket( Cache *cachePtr, int bucket) { Tcl_MutexLock(bucketInfo[bucket].lockPtr); cachePtr->buckets[bucket].numLocks++; sharedPtr->buckets[bucket].numLocks++; } static void UnlockBucket( TCL_UNUSED(Cache *), int bucket) { Tcl_MutexUnlock(bucketInfo[bucket].lockPtr); } /* *---------------------------------------------------------------------- * * PutBlocks -- * * Return unused blocks to the shared cache. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void PutBlocks( Cache *cachePtr, int bucket, size_t numMove) { /* * We have numFree. Want to shed numMove. So compute how many * Blocks to keep. */ size_t keep = cachePtr->buckets[bucket].numFree - numMove; Block *lastPtr = NULL, *firstPtr; cachePtr->buckets[bucket].numFree = keep; firstPtr = cachePtr->buckets[bucket].firstPtr; if (keep == 0) { cachePtr->buckets[bucket].firstPtr = NULL; } else { do { lastPtr = firstPtr; firstPtr = firstPtr->nextBlock; } while (keep-- > 1); lastPtr->nextBlock = NULL; } /* * Aquire the lock and place the list of blocks at the front of the shared * cache bucket. */ LockBucket(cachePtr, bucket); cachePtr->buckets[bucket].lastPtr->nextBlock = sharedPtr->buckets[bucket].firstPtr; sharedPtr->buckets[bucket].firstPtr = firstPtr; if (sharedPtr->buckets[bucket].numFree == 0) { sharedPtr->buckets[bucket].lastPtr = cachePtr->buckets[bucket].lastPtr; } sharedPtr->buckets[bucket].numFree += numMove; UnlockBucket(cachePtr, bucket); cachePtr->buckets[bucket].lastPtr = lastPtr; } /* *---------------------------------------------------------------------- * * GetBlocks -- * * Get more blocks for a bucket. * * Results: * 1 if blocks where allocated, 0 otherwise. * * Side effects: * Cache may be filled with available blocks. * *---------------------------------------------------------------------- */ static int GetBlocks( Cache *cachePtr, int bucket) { Block *blockPtr; size_t n; /* * First, attempt to move blocks from the shared cache. Note the * potentially dirty read of numFree before acquiring the lock which is a * slight performance enhancement. The value is verified after the lock is * actually acquired. */ if (cachePtr != sharedPtr && sharedPtr->buckets[bucket].numFree > 0) { LockBucket(cachePtr, bucket); if (sharedPtr->buckets[bucket].numFree > 0) { /* * Either move the entire list or walk the list to find the last * block to move. */ n = bucketInfo[bucket].numMove; if (n >= sharedPtr->buckets[bucket].numFree) { cachePtr->buckets[bucket].firstPtr = sharedPtr->buckets[bucket].firstPtr; cachePtr->buckets[bucket].lastPtr = sharedPtr->buckets[bucket].lastPtr; cachePtr->buckets[bucket].numFree = sharedPtr->buckets[bucket].numFree; sharedPtr->buckets[bucket].firstPtr = NULL; sharedPtr->buckets[bucket].numFree = 0; } else { blockPtr = sharedPtr->buckets[bucket].firstPtr; cachePtr->buckets[bucket].firstPtr = blockPtr; sharedPtr->buckets[bucket].numFree -= n; cachePtr->buckets[bucket].numFree = n; while (n-- > 1) { blockPtr = blockPtr->nextBlock; } sharedPtr->buckets[bucket].firstPtr = blockPtr->nextBlock; cachePtr->buckets[bucket].lastPtr = blockPtr; blockPtr->nextBlock = NULL; } } UnlockBucket(cachePtr, bucket); } if (cachePtr->buckets[bucket].numFree == 0) { size_t size; /* * If no blocks could be moved from shared, first look for a larger * block in this cache to split up. */ blockPtr = NULL; n = NBUCKETS; size = 0; while (n-- > (size_t)bucket + 1) { if (cachePtr->buckets[n].numFree > 0) { size = bucketInfo[n].blockSize; blockPtr = cachePtr->buckets[n].firstPtr; cachePtr->buckets[n].firstPtr = blockPtr->nextBlock; cachePtr->buckets[n].numFree--; break; } } /* * Otherwise, allocate a big new block directly. */ if (blockPtr == NULL) { size = MAXALLOC; blockPtr = (Block*)TclpSysAlloc(size); if (blockPtr == NULL) { return 0; } } /* * Split the larger block into smaller blocks for this bucket. */ n = size / bucketInfo[bucket].blockSize; cachePtr->buckets[bucket].numFree = n; cachePtr->buckets[bucket].firstPtr = blockPtr; while (n-- > 1) { blockPtr->nextBlock = (Block *) ((char *) blockPtr + bucketInfo[bucket].blockSize); blockPtr = blockPtr->nextBlock; } cachePtr->buckets[bucket].lastPtr = blockPtr; blockPtr->nextBlock = NULL; } return 1; } /* *---------------------------------------------------------------------- * * TclInitThreadAlloc -- * * Initializes the allocator cache-maintenance structures. * It is done early and protected during the Tcl_InitSubsystems(). * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclInitThreadAlloc(void) { unsigned int i; listLockPtr = TclpNewAllocMutex(); objLockPtr = TclpNewAllocMutex(); for (i = 0; i < NBUCKETS; ++i) { bucketInfo[i].blockSize = MINALLOC << i; bucketInfo[i].maxBlocks = ((size_t)1) << (NBUCKETS - 1 - i); bucketInfo[i].numMove = i < NBUCKETS - 1 ? (size_t)1 << (NBUCKETS - 2 - i) : 1; bucketInfo[i].lockPtr = TclpNewAllocMutex(); } TclpInitAllocCache(); } /* *---------------------------------------------------------------------- * * TclFinalizeThreadAlloc -- * * This procedure is used to destroy all private resources used in this * file. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclFinalizeThreadAlloc(void) { unsigned int i; for (i = 0; i < NBUCKETS; ++i) { TclpFreeAllocMutex(bucketInfo[i].lockPtr); bucketInfo[i].lockPtr = NULL; } TclpFreeAllocMutex(objLockPtr); objLockPtr = NULL; TclpFreeAllocMutex(listLockPtr); listLockPtr = NULL; TclpFreeAllocCache(NULL); } /* *---------------------------------------------------------------------- * * TclFinalizeThreadAllocThread -- * * This procedure is used to destroy single thread private resources * defined in this file. Called either during Tcl_FinalizeThread() or * Tcl_Finalize(). * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclFinalizeThreadAllocThread(void) { Cache *cachePtr = (Cache *)TclpGetAllocCache(); if (cachePtr != NULL) { TclpFreeAllocCache(cachePtr); } } #else /* !(TCL_THREADS && USE_THREAD_ALLOC) */ /* *---------------------------------------------------------------------- * * Tcl_GetMemoryInfo -- * * Return a list-of-lists of memory stats. * * Results: * None. * * Side effects: * List appended to given dstring. * *---------------------------------------------------------------------- */ void Tcl_GetMemoryInfo( TCL_UNUSED(Tcl_DString *)) { Tcl_Panic("Tcl_GetMemoryInfo called when threaded memory allocator not in use"); } /* *---------------------------------------------------------------------- * * TclFinalizeThreadAlloc -- * * This procedure is used to destroy all private resources used in this * file. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclFinalizeThreadAlloc(void) { Tcl_Panic("TclFinalizeThreadAlloc called when threaded memory allocator not in use"); } #endif /* TCL_THREADS && USE_THREAD_ALLOC */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclThreadJoin.c0000644000175000017500000002241614726623136016075 0ustar sergeisergei/* * tclThreadJoin.c -- * * This file implements a platform independent emulation layer for the * handling of joinable threads. The Windows platform uses this code to * provide the functionality of joining threads. This code is currently * not necessary on Unix. * * Copyright © 2000 Scriptics Corporation * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #ifdef _WIN32 /* * The information about each joinable thread is remembered in a structure as * defined below. */ typedef struct JoinableThread { Tcl_ThreadId id; /* The id of the joinable thread. */ int result; /* A place for the result after the demise of * the thread. */ int done; /* Boolean flag. Initialized to 0 and set to 1 * after the exit of the thread. This allows a * thread requesting a join to detect when * waiting is not necessary. */ int waitedUpon; /* Boolean flag. Initialized to 0 and set to 1 * by the thread waiting for this one via * Tcl_JoinThread. Used to lock any other * thread trying to wait on this one. */ Tcl_Mutex threadMutex; /* The mutex used to serialize access to this * structure. */ Tcl_Condition cond; /* This is the condition a thread has to wait * upon to get notified of the end of the * described thread. It is signaled indirectly * by Tcl_ExitThread. */ struct JoinableThread *nextThreadPtr; /* Reference to the next thread in the list of * joinable threads. */ } JoinableThread; /* * The following variable is used to maintain the global list of all joinable * threads. Usage by a thread is allowed only if the thread acquired the * 'joinMutex'. */ TCL_DECLARE_MUTEX(joinMutex) static JoinableThread *firstThreadPtr; /* *---------------------------------------------------------------------- * * TclJoinThread -- * * This procedure waits for the exit of the thread with the specified id * and returns its result. * * Results: * A standard tcl result signaling the overall success/failure of the * operation and an integer result delivered by the thread which was * waited upon. * * Side effects: * Deallocates the memory allocated by TclRememberJoinableThread. * Removes the data associated to the thread waited upon from the list of * joinable threads. * *---------------------------------------------------------------------- */ int TclJoinThread( Tcl_ThreadId id, /* The id of the thread to wait upon. */ int *result) /* Reference to a location for the result of * the thread we are waiting upon. */ { JoinableThread *threadPtr; /* * Steps done here: * i. Acquire the joinMutex and search for the thread. * ii. Error out if it could not be found. * iii. If found, switch from exclusive access to the list to exclusive * access to the thread structure. * iv. Error out if some other is already waiting. * v. Skip the waiting part of the thread is already done. * vi. Wait for the thread to exit, mark it as waited upon too. * vii. Get the result form the structure, * viii. switch to exclusive access of the list, * ix. remove the structure from the list, * x. then switch back to exclusive access to the structure * xi. and delete it. */ Tcl_MutexLock(&joinMutex); threadPtr = firstThreadPtr; while (threadPtr!=NULL && threadPtr->id!=id) { threadPtr = threadPtr->nextThreadPtr; } if (threadPtr == NULL) { /* * Thread not found. Either not joinable, or already waited upon and * exited. Whatever, an error is in order. */ Tcl_MutexUnlock(&joinMutex); return TCL_ERROR; } /* * [1] If we don't lock the structure before giving up exclusive access to * the list some other thread just completing its wait on the same thread * can delete the structure from under us, leaving us with a dangling * pointer. */ Tcl_MutexLock(&threadPtr->threadMutex); Tcl_MutexUnlock(&joinMutex); /* * [2] Now that we have the structure mutex any other thread that just * tries to delete structure will wait at location [3] until we are done * with the structure. And in that case we are done with it rather quickly * as 'waitedUpon' will be set and we will have to error out. */ if (threadPtr->waitedUpon) { Tcl_MutexUnlock(&threadPtr->threadMutex); return TCL_ERROR; } /* * We are waiting now, let other threads recognize this. */ threadPtr->waitedUpon = 1; while (!threadPtr->done) { Tcl_ConditionWait(&threadPtr->cond, &threadPtr->threadMutex, NULL); } /* * We have to release the structure before trying to access the list again * or we can run into deadlock with a thread at [1] (see above) because of * us holding the structure and the other holding the list. There is no * problem with dangling pointers here as 'waitedUpon == 1' is still valid * and any other thread will error out and not come to this place. IOW, * the fact that we are here also means that no other thread came here * before us and is able to delete the structure. */ Tcl_MutexUnlock(&threadPtr->threadMutex); Tcl_MutexLock(&joinMutex); /* * We have to search the list again as its structure may (may, almost * certainly) have changed while we were waiting. Especially now is the * time to compute the predecessor in the list. Any earlier result can be * dangling by now. */ if (firstThreadPtr == threadPtr) { firstThreadPtr = threadPtr->nextThreadPtr; } else { JoinableThread *prevThreadPtr = firstThreadPtr; while (prevThreadPtr->nextThreadPtr != threadPtr) { prevThreadPtr = prevThreadPtr->nextThreadPtr; } prevThreadPtr->nextThreadPtr = threadPtr->nextThreadPtr; } Tcl_MutexUnlock(&joinMutex); /* * [3] Now that the structure is not part of the list anymore no other * thread can acquire its mutex from now on. But it is possible that * another thread is still holding the mutex though, see location [2]. So * we have to acquire the mutex one more time to wait for that thread to * finish. We can (and have to) release the mutex immediately. */ Tcl_MutexLock(&threadPtr->threadMutex); Tcl_MutexUnlock(&threadPtr->threadMutex); /* * Copy the result to us, finalize the synchronisation objects, then free * the structure and return. */ *result = threadPtr->result; Tcl_ConditionFinalize(&threadPtr->cond); Tcl_MutexFinalize(&threadPtr->threadMutex); Tcl_Free(threadPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclRememberJoinableThread -- * * This procedure remembers a thread as joinable. Only a call to * TclJoinThread will remove the structure created (and initialized) here. * IOW, not waiting upon a joinable thread will cause memory leaks. * * Results: * None. * * Side effects: * Allocates memory, adds it to the global list of all joinable threads. * *---------------------------------------------------------------------- */ void TclRememberJoinableThread( Tcl_ThreadId id) /* The thread to remember as joinable */ { JoinableThread *threadPtr; threadPtr = (JoinableThread *)Tcl_Alloc(sizeof(JoinableThread)); threadPtr->id = id; threadPtr->done = 0; threadPtr->waitedUpon = 0; threadPtr->threadMutex = (Tcl_Mutex) NULL; threadPtr->cond = (Tcl_Condition) NULL; Tcl_MutexLock(&joinMutex); threadPtr->nextThreadPtr = firstThreadPtr; firstThreadPtr = threadPtr; Tcl_MutexUnlock(&joinMutex); } /* *---------------------------------------------------------------------- * * TclSignalExitThread -- * * This procedure signals that the specified thread is done with its * work. If the thread is joinable this signal is propagated to the * thread waiting upon it. * * Results: * None. * * Side effects: * Modifies the associated structure to hold the result. * *---------------------------------------------------------------------- */ void TclSignalExitThread( Tcl_ThreadId id, /* Id of the thread signaling its exit. */ int result) /* The result from the thread. */ { JoinableThread *threadPtr; Tcl_MutexLock(&joinMutex); threadPtr = firstThreadPtr; while ((threadPtr != NULL) && (threadPtr->id != id)) { threadPtr = threadPtr->nextThreadPtr; } if (threadPtr == NULL) { /* * Thread not found. Not joinable. No problem, nothing to do. */ Tcl_MutexUnlock(&joinMutex); return; } /* * Switch over the exclusive access from the list to the structure, then * store the result, set the flag and notify the waiting thread, provided * that it exists. The order of lock/unlock ensures that a thread entering * 'TclJoinThread' will not interfere with us. */ Tcl_MutexLock(&threadPtr->threadMutex); Tcl_MutexUnlock(&joinMutex); threadPtr->done = 1; threadPtr->result = result; if (threadPtr->waitedUpon) { Tcl_ConditionNotify(&threadPtr->cond); } Tcl_MutexUnlock(&threadPtr->threadMutex); } #else TCL_MAC_EMPTY_FILE(generic_tclThreadJoin_c) #endif /* _WIN32 */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclThreadStorage.c0000644000175000017500000002127114726623136016600 0ustar sergeisergei/* * tclThreadStorage.c -- * * This file implements platform independent thread storage operations to * work around system limits on the number of thread-specific variables. * * Copyright © 2003-2004 Joe Mistachkin * Copyright © 2008 George Peter Staplin * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #if TCL_THREADS #include /* * IMPLEMENTATION NOTES: * * The primary idea is that we create one platform-specific TSD slot, and use * it for storing a table pointer. Each Tcl_ThreadDataKey has an offset into * the table of TSD values. We don't use more than 1 platform-specific TSD * slot, because there is a hard limit on the number of TSD slots. Valid key * offsets are greater than 0; 0 is for the initialized Tcl_ThreadDataKey. */ /* * The global collection of information about TSDs. This is shared across the * whole process, and includes the mutex used to protect it. */ static struct { void *key; /* Key into the system TSD structure. The * collection of Tcl TSD values for a * particular thread will hang off the * back-end of this. */ sig_atomic_t counter; /* The number of different Tcl TSDs used * across *all* threads. This is a strictly * increasing value. */ Tcl_Mutex mutex; /* Protection for the rest of this structure, * which holds per-process data. */ } tsdGlobal = { NULL, 0, NULL }; /* * The type of the data held per thread in a system TSD. */ typedef struct { void **tablePtr; /* The table of Tcl TSDs. */ sig_atomic_t allocated; /* The size of the table in the current * thread. */ } TSDTable; /* * The actual type of Tcl_ThreadDataKey. */ typedef union { volatile sig_atomic_t offset; /* The type is really an offset into the * thread-local table of TSDs, which is this * field. */ void *ptr; /* For alignment purposes only. Not actually * accessed through this. */ } TSDUnion; /* * Forward declarations of functions in this file. */ static TSDTable * TSDTableCreate(void); static void TSDTableDelete(TSDTable *tsdTablePtr); static void TSDTableGrow(TSDTable *tsdTablePtr, sig_atomic_t atLeast); /* * Allocator and deallocator for a TSDTable structure. */ static TSDTable * TSDTableCreate(void) { TSDTable *tsdTablePtr; sig_atomic_t i; tsdTablePtr = (TSDTable *)TclpSysAlloc(sizeof(TSDTable)); if (tsdTablePtr == NULL) { Tcl_Panic("unable to allocate TSDTable"); } tsdTablePtr->allocated = 8; tsdTablePtr->tablePtr = (void **)TclpSysAlloc(sizeof(void *) * tsdTablePtr->allocated); if (tsdTablePtr->tablePtr == NULL) { Tcl_Panic("unable to allocate TSDTable"); } for (i = 0; i < tsdTablePtr->allocated; ++i) { tsdTablePtr->tablePtr[i] = NULL; } return tsdTablePtr; } static void TSDTableDelete( TSDTable *tsdTablePtr) { sig_atomic_t i; for (i=0 ; iallocated ; i++) { if (tsdTablePtr->tablePtr[i] != NULL) { /* * These values were allocated in Tcl_GetThreadData in tclThread.c * and must now be deallocated or they will leak. */ Tcl_Free(tsdTablePtr->tablePtr[i]); } } TclpSysFree(tsdTablePtr->tablePtr); TclpSysFree(tsdTablePtr); } /* *---------------------------------------------------------------------- * * TSDTableGrow -- * * This procedure makes the passed TSDTable grow to fit the atLeast * value. * * Results: * None. * * Side effects: * The table is enlarged. * *---------------------------------------------------------------------- */ static void TSDTableGrow( TSDTable *tsdTablePtr, sig_atomic_t atLeast) { sig_atomic_t newAllocated = tsdTablePtr->allocated * 2; void **newTablePtr; sig_atomic_t i; if (newAllocated <= atLeast) { newAllocated = atLeast + 10; } newTablePtr = (void **)TclpSysRealloc(tsdTablePtr->tablePtr, sizeof(void *) * newAllocated); if (newTablePtr == NULL) { Tcl_Panic("unable to reallocate TSDTable"); } for (i = tsdTablePtr->allocated; i < newAllocated; ++i) { newTablePtr[i] = NULL; } tsdTablePtr->allocated = newAllocated; tsdTablePtr->tablePtr = newTablePtr; } /* *---------------------------------------------------------------------- * * TclThreadStorageKeyGet -- * * This procedure gets the value associated with the passed key. * * Results: * A pointer value associated with the Tcl_ThreadDataKey or NULL. * * Side effects: * None. * *---------------------------------------------------------------------- */ void * TclThreadStorageKeyGet( Tcl_ThreadDataKey *dataKeyPtr) { TSDTable *tsdTablePtr = (TSDTable *)TclpThreadGetGlobalTSD(tsdGlobal.key); void *resultPtr = NULL; TSDUnion *keyPtr = (TSDUnion *) dataKeyPtr; sig_atomic_t offset = keyPtr->offset; if ((tsdTablePtr != NULL) && (offset > 0) && (offset < tsdTablePtr->allocated)) { resultPtr = tsdTablePtr->tablePtr[offset]; } return resultPtr; } /* *---------------------------------------------------------------------- * * TclThreadStorageKeySet -- * * This procedure set an association of value with the key passed. The * associated value may be retrieved with TclThreadDataKeyGet(). * * Results: * None. * * Side effects: * The thread-specific table may be created or reallocated. * *---------------------------------------------------------------------- */ void TclThreadStorageKeySet( Tcl_ThreadDataKey *dataKeyPtr, void *value) { TSDTable *tsdTablePtr = (TSDTable *)TclpThreadGetGlobalTSD(tsdGlobal.key); TSDUnion *keyPtr = (TSDUnion *) dataKeyPtr; if (tsdTablePtr == NULL) { tsdTablePtr = TSDTableCreate(); TclpThreadSetGlobalTSD(tsdGlobal.key, tsdTablePtr); } /* * Get the lock while we check if this TSD is new or not. Note that this * is the only place where Tcl_ThreadDataKey values are set. We use a * double-checked lock to try to avoid having to grab this lock a lot, * since it is on quite a few critical paths and will only get set once in * each location. */ if (keyPtr->offset == 0) { Tcl_MutexLock(&tsdGlobal.mutex); if (keyPtr->offset == 0) { /* * The Tcl_ThreadDataKey hasn't been used yet. Make a new one. */ keyPtr->offset = ++tsdGlobal.counter; } Tcl_MutexUnlock(&tsdGlobal.mutex); } /* * Check if this is the first time this Tcl_ThreadDataKey has been used * with the current thread. Note that we don't need to hold a lock when * doing this, as we are *definitely* the only point accessing this * tsdTablePtr right now; it's thread-local. */ if (keyPtr->offset >= tsdTablePtr->allocated) { TSDTableGrow(tsdTablePtr, keyPtr->offset); } /* * Set the value in the Tcl thread-local variable. */ tsdTablePtr->tablePtr[keyPtr->offset] = value; } /* *---------------------------------------------------------------------- * * TclFinalizeThreadDataThread -- * * This procedure finalizes the data for a single thread. * * Results: * None. * * Side effects: * The TSDTable is deleted/freed. * *---------------------------------------------------------------------- */ void TclFinalizeThreadDataThread(void) { TSDTable *tsdTablePtr = (TSDTable *)TclpThreadGetGlobalTSD(tsdGlobal.key); if (tsdTablePtr != NULL) { TSDTableDelete(tsdTablePtr); TclpThreadSetGlobalTSD(tsdGlobal.key, NULL); } } /* *---------------------------------------------------------------------- * * TclInitializeThreadStorage -- * * This procedure initializes the TSD subsystem with per-platform code. * This should be called before any Tcl threads are created. * * Results: * None. * * Side effects: * Allocates a system TSD. * *---------------------------------------------------------------------- */ void TclInitThreadStorage(void) { tsdGlobal.key = TclpThreadCreateKey(); } /* *---------------------------------------------------------------------- * * TclFinalizeThreadStorage -- * * This procedure cleans up the thread storage data key for all threads. * IMPORTANT: All Tcl threads must be finalized before calling this! * * Results: * None. * * Side effects: * Releases the thread data key. * *---------------------------------------------------------------------- */ void TclFinalizeThreadStorage(void) { TclpThreadDeleteKey(tsdGlobal.key); tsdGlobal.key = NULL; } #else /* !TCL_THREADS */ /* * Stub functions for non-threaded builds */ void TclInitThreadStorage(void) { } void TclFinalizeThreadDataThread(void) { } void TclFinalizeThreadStorage(void) { } #endif /* TCL_THREADS */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclThreadTest.c0000644000175000017500000007534114726623136016122 0ustar sergeisergei/* * tclThreadTest.c -- * * This file implements the testthread command. Eventually this should be * tclThreadCmd.c * Some of this code is based on work done by Richard Hipp on behalf of * Conservation Through Innovation, Limited, with their permission. * * Copyright © 1998 Sun Microsystems, Inc. * Copyright © 2006-2008 Joe Mistachkin. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #undef BUILD_tcl #undef STATIC_BUILD #ifndef USE_TCL_STUBS # define USE_TCL_STUBS #endif #include "tclInt.h" #if TCL_THREADS /* * Each thread has an single instance of the following structure. There is one * instance of this structure per thread even if that thread contains multiple * interpreters. The interpreter identified by this structure is the main * interpreter for the thread. * * The main interpreter is the one that will process any messages received by * a thread. Any thread can send messages but only the main interpreter can * receive them. */ typedef struct ThreadSpecificData { Tcl_ThreadId threadId; /* Tcl ID for this thread */ Tcl_Interp *interp; /* Main interpreter for this thread */ int flags; /* See the TP_ defines below... */ struct ThreadSpecificData *nextPtr; /* List for "thread names" */ struct ThreadSpecificData *prevPtr; /* List for "thread names" */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * This list is used to list all threads that have interpreters. This is * protected by threadMutex. */ static ThreadSpecificData *threadList = NULL; /* * The following bit-values are legal for the "flags" field of the * ThreadSpecificData structure. */ #define TP_Dying 0x001 /* This thread is being canceled */ /* * An instance of the following structure contains all information that is * passed into a new thread when the thread is created using either the * "thread create" Tcl command or the ThreadCreate() C function. */ typedef struct ThreadCtrl { const char *script; /* The Tcl command this thread should * execute */ int flags; /* Initial value of the "flags" field in the * ThreadSpecificData structure for the new * thread. Might contain TP_Detached or * TP_TclThread. */ Tcl_Condition condWait; /* This condition variable is used to * synchronize the parent and child threads. * The child won't run until it acquires * threadMutex, and the parent function won't * complete until signaled on this condition * variable. */ } ThreadCtrl; /* * This is the event used to send scripts to other threads. */ typedef struct ThreadEvent { Tcl_Event event; /* Must be first */ char *script; /* The script to execute. */ struct ThreadEventResult *resultPtr; /* To communicate the result. This is NULL if * we don't care about it. */ } ThreadEvent; typedef struct ThreadEventResult { Tcl_Condition done; /* Signaled when the script completes */ int code; /* Return value of Tcl_Eval */ char *result; /* Result from the script */ char *errorInfo; /* Copy of errorInfo variable */ char *errorCode; /* Copy of errorCode variable */ Tcl_ThreadId srcThreadId; /* Id of sending thread, in case it dies */ Tcl_ThreadId dstThreadId; /* Id of target thread, in case it dies */ struct ThreadEvent *eventPtr; /* Back pointer */ struct ThreadEventResult *nextPtr; /* List for cleanup */ struct ThreadEventResult *prevPtr; } ThreadEventResult; static ThreadEventResult *resultList; /* * This is for simple error handling when a thread script exits badly. */ static Tcl_ThreadId mainThreadId; static Tcl_ThreadId errorThreadId; static char *errorProcString; /* * Access to the list of threads and to the thread send results is guarded by * this mutex. */ TCL_DECLARE_MUTEX(threadMutex) static Tcl_ObjCmdProc ThreadCmd; static int ThreadCreate(Tcl_Interp *interp, const char *script, int joinable); static int ThreadList(Tcl_Interp *interp); static int ThreadSend(Tcl_Interp *interp, Tcl_ThreadId id, const char *script, int wait); static int ThreadCancel(Tcl_Interp *interp, Tcl_ThreadId id, const char *result, int flags); static Tcl_ThreadCreateType NewTestThread(void *clientData); static void ListRemove(ThreadSpecificData *tsdPtr); static void ListUpdateInner(ThreadSpecificData *tsdPtr); static int ThreadEventProc(Tcl_Event *evPtr, int mask); static void ThreadErrorProc(Tcl_Interp *interp); static void ThreadFreeProc(void *clientData); static int ThreadDeleteEvent(Tcl_Event *eventPtr, void *clientData); static void ThreadExitProc(void *clientData); extern int Tcltest_Init(Tcl_Interp *interp); /* *---------------------------------------------------------------------- * * TclThread_Init -- * * Initialize the test thread command. * * Results: * TCL_OK if the package was properly initialized. * * Side effects: * Add the "testthread" command to the interp. * *---------------------------------------------------------------------- */ int TclThread_Init( Tcl_Interp *interp) /* The current Tcl interpreter */ { /* * If the main thread Id has not been set, do it now. */ Tcl_MutexLock(&threadMutex); if (mainThreadId == 0) { mainThreadId = Tcl_GetCurrentThread(); } Tcl_MutexUnlock(&threadMutex); Tcl_CreateObjCommand(interp, "testthread", ThreadCmd, NULL, NULL); return TCL_OK; } /* *---------------------------------------------------------------------- * * ThreadCmd -- * * This procedure is invoked to process the "testthread" Tcl command. See * the user documentation for details on what it does. * * thread cancel ?-unwind? id ?result? * thread create ?-joinable? ?script? * thread send ?-async? id script * thread event * thread exit * thread id ?-main? * thread names * thread wait * thread errorproc proc * thread join id * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int ThreadCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); static const char *const threadOptions[] = { "cancel", "create", "event", "exit", "id", "join", "names", "send", "wait", "errorproc", NULL }; enum options { THREAD_CANCEL, THREAD_CREATE, THREAD_EVENT, THREAD_EXIT, THREAD_ID, THREAD_JOIN, THREAD_NAMES, THREAD_SEND, THREAD_WAIT, THREAD_ERRORPROC } option; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], threadOptions, "option", 0, &option) != TCL_OK) { return TCL_ERROR; } /* * Make sure the initial thread is on the list before doing anything. */ if (tsdPtr->interp == NULL) { Tcl_MutexLock(&threadMutex); tsdPtr->interp = interp; ListUpdateInner(tsdPtr); Tcl_CreateThreadExitHandler(ThreadExitProc, NULL); Tcl_MutexUnlock(&threadMutex); } switch (option) { case THREAD_CANCEL: { Tcl_WideInt id; const char *result; int flags, arg; if ((objc < 3) || (objc > 5)) { Tcl_WrongNumArgs(interp, 2, objv, "?-unwind? id ?result?"); return TCL_ERROR; } flags = 0; arg = 2; if ((objc == 4) || (objc == 5)) { if (strcmp("-unwind", Tcl_GetString(objv[arg])) == 0) { flags = TCL_CANCEL_UNWIND; arg++; } } if (Tcl_GetWideIntFromObj(interp, objv[arg], &id) != TCL_OK) { return TCL_ERROR; } arg++; if (arg < objc) { result = Tcl_GetString(objv[arg]); } else { result = NULL; } return ThreadCancel(interp, (Tcl_ThreadId) INT2PTR(id), result, flags); } case THREAD_CREATE: { const char *script; int joinable; Tcl_Size len; if (objc == 2) { /* * Neither joinable nor special script */ joinable = 0; script = "testthread wait"; /* Just enter event loop */ } else if (objc == 3) { /* * Possibly -joinable, then no special script, no joinable, then * its a script. */ script = Tcl_GetStringFromObj(objv[2], &len); if ((len > 1) && (script[0] == '-') && (script[1] == 'j') && (0 == strncmp(script, "-joinable", len))) { joinable = 1; script = "testthread wait"; /* Just enter event loop */ } else { /* * Remember the script */ joinable = 0; } } else if (objc == 4) { /* * Definitely a script available, but is the flag -joinable? */ script = Tcl_GetStringFromObj(objv[2], &len); joinable = ((len > 1) && (script[0] == '-') && (script[1] == 'j') && (0 == strncmp(script, "-joinable", len))); script = Tcl_GetString(objv[3]); } else { Tcl_WrongNumArgs(interp, 2, objv, "?-joinable? ?script?"); return TCL_ERROR; } return ThreadCreate(interp, script, joinable); } case THREAD_EXIT: if (objc > 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } ListRemove(NULL); Tcl_ExitThread(0); return TCL_OK; case THREAD_ID: if (objc == 2 || objc == 3) { Tcl_Obj *idObj; /* * Check if they want the main thread id or the current thread id. */ if (objc == 2) { idObj = Tcl_NewWideIntObj((Tcl_WideInt)PTR2INT(Tcl_GetCurrentThread())); } else if (objc == 3 && strcmp("-main", Tcl_GetString(objv[2])) == 0) { Tcl_MutexLock(&threadMutex); idObj = Tcl_NewWideIntObj((Tcl_WideInt)PTR2INT(mainThreadId)); Tcl_MutexUnlock(&threadMutex); } else { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, idObj); return TCL_OK; } else { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } case THREAD_JOIN: { Tcl_WideInt id; int result, status; if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "id"); return TCL_ERROR; } if (Tcl_GetWideIntFromObj(interp, objv[2], &id) != TCL_OK) { return TCL_ERROR; } result = Tcl_JoinThread((Tcl_ThreadId)INT2PTR(id), &status); if (result == TCL_OK) { Tcl_SetIntObj(Tcl_GetObjResult(interp), status); } else { char buf[TCL_INTEGER_SPACE]; snprintf(buf, sizeof(buf), "%" TCL_LL_MODIFIER "d", (long long)id); Tcl_AppendResult(interp, "cannot join thread ", buf, (char *)NULL); } return result; } case THREAD_NAMES: if (objc > 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } return ThreadList(interp); case THREAD_SEND: { Tcl_WideInt id; const char *script; int wait, arg; if ((objc != 4) && (objc != 5)) { Tcl_WrongNumArgs(interp, 2, objv, "?-async? id script"); return TCL_ERROR; } if (objc == 5) { if (strcmp("-async", Tcl_GetString(objv[2])) != 0) { Tcl_WrongNumArgs(interp, 2, objv, "?-async? id script"); return TCL_ERROR; } wait = 0; arg = 3; } else { wait = 1; arg = 2; } if (Tcl_GetWideIntFromObj(interp, objv[arg], &id) != TCL_OK) { return TCL_ERROR; } arg++; script = Tcl_GetString(objv[arg]); return ThreadSend(interp, (Tcl_ThreadId)INT2PTR(id), script, wait); } case THREAD_EVENT: { if (objc > 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj( Tcl_DoOneEvent(TCL_ALL_EVENTS | TCL_DONT_WAIT))); return TCL_OK; } case THREAD_ERRORPROC: { /* * Arrange for this proc to handle thread death errors. */ const char *proc; if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "proc"); return TCL_ERROR; } Tcl_MutexLock(&threadMutex); errorThreadId = Tcl_GetCurrentThread(); if (errorProcString) { Tcl_Free(errorProcString); } proc = Tcl_GetString(objv[2]); errorProcString = (char *)Tcl_Alloc(strlen(proc) + 1); strcpy(errorProcString, proc); Tcl_MutexUnlock(&threadMutex); return TCL_OK; } case THREAD_WAIT: if (objc > 2) { Tcl_WrongNumArgs(interp, 2, objv, ""); return TCL_ERROR; } while (1) { /* * If the script has been unwound, bail out immediately. This does * not follow the recommended guidelines for how extensions should * handle the script cancellation functionality because this is * not a "normal" extension. Most extensions do not have a command * that simply enters an infinite Tcl event loop. Normal * extensions should not specify the TCL_CANCEL_UNWIND when * calling Tcl_Canceled to check if the command has been canceled. */ if (Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG | TCL_CANCEL_UNWIND) == TCL_ERROR) { break; } (void) Tcl_DoOneEvent(TCL_ALL_EVENTS); } /* * If we get to this point, we have been canceled by another thread, * which is considered to be an "error". */ ThreadErrorProc(interp); return TCL_OK; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ThreadCreate -- * * This procedure is invoked to create a thread containing an interp to * run a script. This returns after the thread has started executing. * * Results: * A standard Tcl result, which is the thread ID. * * Side effects: * Create a thread. * *---------------------------------------------------------------------- */ static int ThreadCreate( Tcl_Interp *interp, /* Current interpreter. */ const char *script, /* Script to execute */ int joinable) /* Flag, joinable thread or not */ { ThreadCtrl ctrl; Tcl_ThreadId id; ctrl.script = script; ctrl.condWait = NULL; ctrl.flags = 0; joinable = joinable ? TCL_THREAD_JOINABLE : TCL_THREAD_NOFLAGS; Tcl_MutexLock(&threadMutex); if (Tcl_CreateThread(&id, NewTestThread, &ctrl, TCL_THREAD_STACK_DEFAULT, joinable) != TCL_OK) { Tcl_MutexUnlock(&threadMutex); Tcl_AppendResult(interp, "cannot create a new thread", (char *)NULL); return TCL_ERROR; } /* * Wait for the thread to start because it is using something on our stack! */ Tcl_ConditionWait(&ctrl.condWait, &threadMutex, NULL); Tcl_MutexUnlock(&threadMutex); Tcl_ConditionFinalize(&ctrl.condWait); Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt)(size_t)id)); return TCL_OK; } /* *------------------------------------------------------------------------ * * NewTestThread -- * * This routine is the "main()" for a new thread whose task is to execute * a single Tcl script. The argument to this function is a pointer to a * structure that contains the text of the TCL script to be executed. * * Space to hold the script field of the ThreadControl structure passed * in as the only argument was obtained from malloc() and must be freed * by this function before it exits. Space to hold the ThreadControl * structure itself is released by the calling function, and the two * condition variables in the ThreadControl structure are destroyed by * the calling function. The calling function will destroy the * ThreadControl structure and the condition variable as soon as * ctrlPtr->condWait is signaled, so this routine must make copies of any * data it might need after that point. * * Results: * None * * Side effects: * A Tcl script is executed in a new thread. * *------------------------------------------------------------------------ */ Tcl_ThreadCreateType NewTestThread( void *clientData) { ThreadCtrl *ctrlPtr = (ThreadCtrl *)clientData; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); int result; char *threadEvalScript; /* * Initialize the interpreter. This should be more general. */ tsdPtr->interp = Tcl_CreateInterp(); result = Tcl_Init(tsdPtr->interp); if (result != TCL_OK) { ThreadErrorProc(tsdPtr->interp); } /* * This is part of the test facility. Initialize _ALL_ test commands for * use by the new thread. */ result = Tcltest_Init(tsdPtr->interp); if (result != TCL_OK) { ThreadErrorProc(tsdPtr->interp); } /* * Update the list of threads. */ Tcl_MutexLock(&threadMutex); ListUpdateInner(tsdPtr); /* * We need to keep a pointer to the alloc'ed mem of the script we are * eval'ing, for the case that we exit during evaluation */ threadEvalScript = (char *)Tcl_Alloc(strlen(ctrlPtr->script) + 1); strcpy(threadEvalScript, ctrlPtr->script); Tcl_CreateThreadExitHandler(ThreadExitProc, threadEvalScript); /* * Notify the parent we are alive. */ Tcl_ConditionNotify(&ctrlPtr->condWait); Tcl_MutexUnlock(&threadMutex); /* * Run the script. */ Tcl_Preserve(tsdPtr->interp); result = Tcl_EvalEx(tsdPtr->interp, threadEvalScript, TCL_INDEX_NONE, 0); if (result != TCL_OK) { ThreadErrorProc(tsdPtr->interp); } /* * Clean up. */ Tcl_DeleteInterp(tsdPtr->interp); Tcl_Release(tsdPtr->interp); ListRemove(tsdPtr); Tcl_ExitThread(result); TCL_THREAD_CREATE_RETURN; } /* *------------------------------------------------------------------------ * * ThreadErrorProc -- * * Send a message to the thread willing to hear about errors. * * Results: * None * * Side effects: * Send an event. * *------------------------------------------------------------------------ */ static void ThreadErrorProc( Tcl_Interp *interp) /* Interp that failed */ { Tcl_Channel errChannel; const char *errorInfo, *argv[3]; char *script; char buf[TCL_DOUBLE_SPACE+1]; snprintf(buf, sizeof(buf), "%p", Tcl_GetCurrentThread()); errorInfo = Tcl_GetVar2(interp, "errorInfo", NULL, TCL_GLOBAL_ONLY); if (errorProcString == NULL) { errChannel = Tcl_GetStdChannel(TCL_STDERR); Tcl_WriteChars(errChannel, "Error from thread ", -1); Tcl_WriteChars(errChannel, buf, -1); Tcl_WriteChars(errChannel, "\n", 1); Tcl_WriteChars(errChannel, errorInfo, -1); Tcl_WriteChars(errChannel, "\n", 1); } else { argv[0] = errorProcString; argv[1] = buf; argv[2] = errorInfo; script = Tcl_Merge(3, argv); ThreadSend(interp, errorThreadId, script, 0); Tcl_Free(script); } } /* *------------------------------------------------------------------------ * * ListUpdateInner -- * * Add the thread local storage to the list. This assumes the caller has * obtained the mutex. * * Results: * None * * Side effects: * Add the thread local storage to its list. * *------------------------------------------------------------------------ */ static void ListUpdateInner( ThreadSpecificData *tsdPtr) { if (tsdPtr == NULL) { tsdPtr = TCL_TSD_INIT(&dataKey); } tsdPtr->threadId = Tcl_GetCurrentThread(); tsdPtr->nextPtr = threadList; if (threadList) { threadList->prevPtr = tsdPtr; } tsdPtr->prevPtr = NULL; threadList = tsdPtr; } /* *------------------------------------------------------------------------ * * ListRemove -- * * Remove the thread local storage from its list. This grabs the mutex to * protect the list. * * Results: * None * * Side effects: * Remove the thread local storage from its list. * *------------------------------------------------------------------------ */ static void ListRemove( ThreadSpecificData *tsdPtr) { if (tsdPtr == NULL) { tsdPtr = TCL_TSD_INIT(&dataKey); } Tcl_MutexLock(&threadMutex); if (tsdPtr->prevPtr) { tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; } else { threadList = tsdPtr->nextPtr; } if (tsdPtr->nextPtr) { tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; } tsdPtr->nextPtr = tsdPtr->prevPtr = 0; tsdPtr->interp = NULL; Tcl_MutexUnlock(&threadMutex); } /* *------------------------------------------------------------------------ * * ThreadList -- * * Return a list of threads running Tcl interpreters. * * Results: * A standard Tcl result. * * Side effects: * None. * *------------------------------------------------------------------------ */ static int ThreadList( Tcl_Interp *interp) { ThreadSpecificData *tsdPtr; Tcl_Obj *listPtr; listPtr = Tcl_NewListObj(0, NULL); Tcl_MutexLock(&threadMutex); for (tsdPtr = threadList ; tsdPtr ; tsdPtr = tsdPtr->nextPtr) { Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewWideIntObj((Tcl_WideInt)(size_t)tsdPtr->threadId)); } Tcl_MutexUnlock(&threadMutex); Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* *------------------------------------------------------------------------ * * ThreadSend -- * * Send a script to another thread. * * Results: * A standard Tcl result. * * Side effects: * None. * *------------------------------------------------------------------------ */ static int ThreadSend( Tcl_Interp *interp, /* The current interpreter. */ Tcl_ThreadId id, /* Thread Id of other interpreter. */ const char *script, /* The script to evaluate. */ int wait) /* If 1, we block for the result. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); ThreadEvent *threadEventPtr; ThreadEventResult *resultPtr; int found, code; Tcl_ThreadId threadId = (Tcl_ThreadId) id; /* * Verify the thread exists. */ Tcl_MutexLock(&threadMutex); found = 0; for (tsdPtr = threadList ; tsdPtr ; tsdPtr = tsdPtr->nextPtr) { if (tsdPtr->threadId == threadId) { found = 1; break; } } if (!found) { Tcl_MutexUnlock(&threadMutex); Tcl_AppendResult(interp, "invalid thread id", (char *)NULL); return TCL_ERROR; } /* * Short circuit sends to ourself. Ought to do something with -async, like * run in an idle handler. */ if (threadId == Tcl_GetCurrentThread()) { Tcl_MutexUnlock(&threadMutex); return Tcl_EvalEx(interp, script,-1,TCL_EVAL_GLOBAL); } /* * Create the event for its event queue. */ threadEventPtr = (ThreadEvent*)Tcl_Alloc(sizeof(ThreadEvent)); threadEventPtr->script = (char *)Tcl_Alloc(strlen(script) + 1); strcpy(threadEventPtr->script, script); if (!wait) { resultPtr = threadEventPtr->resultPtr = NULL; } else { resultPtr = (ThreadEventResult *)Tcl_Alloc(sizeof(ThreadEventResult)); threadEventPtr->resultPtr = resultPtr; /* * Initialize the result fields. */ resultPtr->done = NULL; resultPtr->code = 0; resultPtr->result = NULL; resultPtr->errorInfo = NULL; resultPtr->errorCode = NULL; /* * Maintain the cleanup list. */ resultPtr->srcThreadId = Tcl_GetCurrentThread(); resultPtr->dstThreadId = threadId; resultPtr->eventPtr = threadEventPtr; resultPtr->nextPtr = resultList; if (resultList) { resultList->prevPtr = resultPtr; } resultPtr->prevPtr = NULL; resultList = resultPtr; } /* * Queue the event and poke the other thread's notifier. */ threadEventPtr->event.proc = ThreadEventProc; Tcl_ThreadQueueEvent(threadId, (Tcl_Event *) threadEventPtr, TCL_QUEUE_TAIL|TCL_QUEUE_ALERT_IF_EMPTY); if (!wait) { Tcl_MutexUnlock(&threadMutex); return TCL_OK; } /* * Block on the results and then get them. */ Tcl_ResetResult(interp); while (resultPtr->result == NULL) { Tcl_ConditionWait(&resultPtr->done, &threadMutex, NULL); } /* * Unlink result from the result list. */ if (resultPtr->prevPtr) { resultPtr->prevPtr->nextPtr = resultPtr->nextPtr; } else { resultList = resultPtr->nextPtr; } if (resultPtr->nextPtr) { resultPtr->nextPtr->prevPtr = resultPtr->prevPtr; } resultPtr->eventPtr = NULL; resultPtr->nextPtr = NULL; resultPtr->prevPtr = NULL; Tcl_MutexUnlock(&threadMutex); if (resultPtr->code != TCL_OK) { if (resultPtr->errorCode) { Tcl_SetErrorCode(interp, resultPtr->errorCode, (char *)NULL); Tcl_Free(resultPtr->errorCode); } if (resultPtr->errorInfo) { Tcl_AddErrorInfo(interp, resultPtr->errorInfo); Tcl_Free(resultPtr->errorInfo); } } Tcl_AppendResult(interp, resultPtr->result, (char *)NULL); Tcl_ConditionFinalize(&resultPtr->done); code = resultPtr->code; Tcl_Free(resultPtr->result); Tcl_Free(resultPtr); return code; } /* *------------------------------------------------------------------------ * * ThreadCancel -- * * Cancels a script in another thread. * * Results: * A standard Tcl result. * * Side effects: * None. * *------------------------------------------------------------------------ */ static int ThreadCancel( Tcl_Interp *interp, /* The current interpreter. */ Tcl_ThreadId id, /* Thread Id of other interpreter. */ const char *result, /* The result or NULL for default. */ int flags) /* Flags for Tcl_CancelEval. */ { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); int found; Tcl_ThreadId threadId = (Tcl_ThreadId) id; /* * Verify the thread exists. */ Tcl_MutexLock(&threadMutex); found = 0; for (tsdPtr = threadList ; tsdPtr ; tsdPtr = tsdPtr->nextPtr) { if (tsdPtr->threadId == threadId) { found = 1; break; } } if (!found) { Tcl_MutexUnlock(&threadMutex); Tcl_AppendResult(interp, "invalid thread id", (char *)NULL); return TCL_ERROR; } /* * Since Tcl_CancelEval can be safely called from any thread, * we do it now. */ Tcl_MutexUnlock(&threadMutex); Tcl_ResetResult(interp); return Tcl_CancelEval(tsdPtr->interp, (result != NULL) ? Tcl_NewStringObj(result, -1) : NULL, 0, flags); } /* *------------------------------------------------------------------------ * * ThreadEventProc -- * * Handle the event in the target thread. * * Results: * Returns 1 to indicate that the event was processed. * * Side effects: * Fills out the ThreadEventResult struct. * *------------------------------------------------------------------------ */ static int ThreadEventProc( Tcl_Event *evPtr, /* Really ThreadEvent */ TCL_UNUSED(int) /*mask*/) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); ThreadEvent *threadEventPtr = (ThreadEvent *) evPtr; ThreadEventResult *resultPtr = threadEventPtr->resultPtr; Tcl_Interp *interp = tsdPtr->interp; int code; const char *result, *errorCode, *errorInfo; if (interp == NULL) { code = TCL_ERROR; result = "no target interp!"; errorCode = "THREAD"; errorInfo = ""; } else { Tcl_Preserve(interp); Tcl_ResetResult(interp); Tcl_CreateThreadExitHandler(ThreadFreeProc, threadEventPtr->script); code = Tcl_EvalEx(interp, threadEventPtr->script,-1,TCL_EVAL_GLOBAL); Tcl_DeleteThreadExitHandler(ThreadFreeProc, threadEventPtr->script); if (code != TCL_OK) { errorCode = Tcl_GetVar2(interp, "errorCode", NULL, TCL_GLOBAL_ONLY); errorInfo = Tcl_GetVar2(interp, "errorInfo", NULL, TCL_GLOBAL_ONLY); } else { errorCode = errorInfo = NULL; } result = Tcl_GetStringResult(interp); } Tcl_Free(threadEventPtr->script); if (resultPtr) { Tcl_MutexLock(&threadMutex); resultPtr->code = code; resultPtr->result = (char *)Tcl_Alloc(strlen(result) + 1); strcpy(resultPtr->result, result); if (errorCode != NULL) { resultPtr->errorCode = (char *)Tcl_Alloc(strlen(errorCode) + 1); strcpy(resultPtr->errorCode, errorCode); } if (errorInfo != NULL) { resultPtr->errorInfo = (char *)Tcl_Alloc(strlen(errorInfo) + 1); strcpy(resultPtr->errorInfo, errorInfo); } Tcl_ConditionNotify(&resultPtr->done); Tcl_MutexUnlock(&threadMutex); } if (interp != NULL) { Tcl_Release(interp); } return 1; } /* *------------------------------------------------------------------------ * * ThreadFreeProc -- * * This is called from when we are exiting and memory needs * to be freed. * * Results: * None. * * Side effects: * Clears up mem specified in clientData * *------------------------------------------------------------------------ */ static void ThreadFreeProc( void *clientData) { if (clientData) { Tcl_Free(clientData); } } /* *------------------------------------------------------------------------ * * ThreadDeleteEvent -- * * This is called from the ThreadExitProc to delete memory related * to events that we put on the queue. * * Results: * 1 it was our event and we want it removed, 0 otherwise. * * Side effects: * It cleans up our events in the event queue for this thread. * *------------------------------------------------------------------------ */ static int ThreadDeleteEvent( Tcl_Event *eventPtr, /* Really ThreadEvent */ TCL_UNUSED(void *)) { if (eventPtr->proc == ThreadEventProc) { Tcl_Free(((ThreadEvent *) eventPtr)->script); return 1; } /* * If it was NULL, we were in the middle of servicing the event and it * should be removed */ return (eventPtr->proc == NULL); } /* *------------------------------------------------------------------------ * * ThreadExitProc -- * * This is called when the thread exits. * * Results: * None. * * Side effects: * It unblocks anyone that is waiting on a send to this thread. It cleans * up any events in the event queue for this thread. * *------------------------------------------------------------------------ */ static void ThreadExitProc( void *clientData) { char *threadEvalScript = (char *)clientData; ThreadEventResult *resultPtr, *nextPtr; Tcl_ThreadId self = Tcl_GetCurrentThread(); ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (tsdPtr->interp != NULL) { ListRemove(tsdPtr); } Tcl_MutexLock(&threadMutex); if (self == errorThreadId) { if (errorProcString) { /* Extra safety */ Tcl_Free(errorProcString); errorProcString = NULL; } errorThreadId = 0; } if (threadEvalScript) { Tcl_Free(threadEvalScript); threadEvalScript = NULL; } Tcl_DeleteEvents((Tcl_EventDeleteProc *) ThreadDeleteEvent, NULL); for (resultPtr = resultList ; resultPtr ; resultPtr = nextPtr) { nextPtr = resultPtr->nextPtr; if (resultPtr->srcThreadId == self) { /* * We are going away. By freeing up the result we signal to the * other thread we don't care about the result. */ if (resultPtr->prevPtr) { resultPtr->prevPtr->nextPtr = resultPtr->nextPtr; } else { resultList = resultPtr->nextPtr; } if (resultPtr->nextPtr) { resultPtr->nextPtr->prevPtr = resultPtr->prevPtr; } resultPtr->nextPtr = resultPtr->prevPtr = 0; resultPtr->eventPtr->resultPtr = NULL; Tcl_Free(resultPtr); } else if (resultPtr->dstThreadId == self) { /* * Dang. The target is going away. Unblock the caller. The result * string must be dynamically allocated because the main thread is * going to call free on it. */ const char *msg = "target thread died"; resultPtr->result = (char *)Tcl_Alloc(strlen(msg) + 1); strcpy(resultPtr->result, msg); resultPtr->code = TCL_ERROR; Tcl_ConditionNotify(&resultPtr->done); } } Tcl_MutexUnlock(&threadMutex); } #endif /* TCL_THREADS */ /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclTimer.c0000644000175000017500000010654214726623136015131 0ustar sergeisergei/* * tclTimer.c -- * * This file provides timer event management facilities for Tcl, * including the "after" command. * * Copyright © 1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * For each timer callback that's pending there is one record of the following * type. The normal handlers (created by Tcl_CreateTimerHandler) are chained * together in a list sorted by time (earliest event first). */ typedef struct TimerHandler { Tcl_Time time; /* When timer is to fire. */ Tcl_TimerProc *proc; /* Function to call. */ void *clientData; /* Argument to pass to proc. */ Tcl_TimerToken token; /* Identifies handler so it can be deleted. */ struct TimerHandler *nextPtr; /* Next event in queue, or NULL for end of * queue. */ } TimerHandler; /* * The data structure below is used by the "after" command to remember the * command to be executed later. All of the pending "after" commands for an * interpreter are linked together in a list. */ typedef struct AfterInfo { struct AfterAssocData *assocPtr; /* Pointer to the "tclAfter" assocData for the * interp in which command will be * executed. */ Tcl_Obj *commandPtr; /* Command to execute. */ int id; /* Integer identifier for command; used to * cancel it. */ Tcl_TimerToken token; /* Used to cancel the "after" command. NULL * means that the command is run as an idle * handler rather than as a timer handler. * NULL means this is an "after idle" handler * rather than a timer handler. */ struct AfterInfo *nextPtr; /* Next in list of all "after" commands for * this interpreter. */ } AfterInfo; /* * One of the following structures is associated with each interpreter for * which an "after" command has ever been invoked. A pointer to this structure * is stored in the AssocData for the "tclAfter" key. */ typedef struct AfterAssocData { Tcl_Interp *interp; /* The interpreter for which this data is * registered. */ AfterInfo *firstAfterPtr; /* First in list of all "after" commands still * pending for this interpreter, or NULL if * none. */ } AfterAssocData; /* * There is one of the following structures for each of the handlers declared * in a call to Tcl_DoWhenIdle. All of the currently-active handlers are * linked together into a list. */ typedef struct IdleHandler { Tcl_IdleProc *proc; /* Function to call. */ void *clientData; /* Value to pass to proc. */ int generation; /* Used to distinguish older handlers from * recently-created ones. */ struct IdleHandler *nextPtr;/* Next in list of active handlers. */ } IdleHandler; /* * The timer and idle queues are per-thread because they are associated with * the notifier, which is also per-thread. * * All static variables used in this file are collected into a single instance * of the following structure. For multi-threaded implementations, there is * one instance of this structure for each thread. * * Notice that different structures with the same name appear in other files. * The structure defined below is used in this file only. */ typedef struct { TimerHandler *firstTimerHandlerPtr; /* First event in queue. */ int lastTimerId; /* Timer identifier of most recently created * timer. */ int timerPending; /* 1 if a timer event is in the queue. */ IdleHandler *idleList; /* First in list of all idle handlers. */ IdleHandler *lastIdlePtr; /* Last in list (or NULL for empty list). */ int idleGeneration; /* Used to fill in the "generation" fields of * IdleHandler structures. Increments each * time Tcl_DoOneEvent starts calling idle * handlers, so that all old handlers can be * called without calling any of the new ones * created by old ones. */ int afterId; /* For unique identifiers of after events. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * Helper macros for working with times. TCL_TIME_BEFORE encodes how to write * the ordering relation on (normalized) times, and TCL_TIME_DIFF_MS computes * the number of milliseconds difference between two times. Both macros use * both of their arguments multiple times, so make sure they are cheap and * side-effect free. The "prototypes" for these macros are: * * static int TCL_TIME_BEFORE(Tcl_Time t1, Tcl_Time t2); * static Tcl_WideInt TCL_TIME_DIFF_MS(Tcl_Time t1, Tcl_Time t2); */ #define TCL_TIME_BEFORE(t1, t2) \ (((t1).sec<(t2).sec) || ((t1).sec==(t2).sec && (t1).usec<(t2).usec)) #define TCL_TIME_DIFF_MS(t1, t2) \ (1000*((Tcl_WideInt)(t1).sec - (Tcl_WideInt)(t2).sec) + \ ((t1).usec - (t2).usec)/1000) #define TCL_TIME_DIFF_MS_CEILING(t1, t2) \ (1000*((Tcl_WideInt)(t1).sec - (Tcl_WideInt)(t2).sec) + \ ((t1).usec - (t2).usec + 999)/1000) /* * Sleeps under that number of milliseconds don't get double-checked * and are done in exactly one Tcl_Sleep(). This to limit gettimeofday()s. */ #define SLEEP_OFFLOAD_GETTIMEOFDAY 20 /* * The maximum number of milliseconds for each Tcl_Sleep call in AfterDelay. * This is used to limit the maximum lag between interp limit and script * cancellation checks. */ #define TCL_TIME_MAXIMUM_SLICE 500 /* * Prototypes for functions referenced only in this file: */ static void AfterCleanupProc(void *clientData, Tcl_Interp *interp); static int AfterDelay(Tcl_Interp *interp, Tcl_WideInt ms); static void AfterProc(void *clientData); static void FreeAfterPtr(AfterInfo *afterPtr); static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, Tcl_Obj *commandPtr); static ThreadSpecificData *InitTimer(void); static void TimerExitProc(void *clientData); static int TimerHandlerEventProc(Tcl_Event *evPtr, int flags); static void TimerCheckProc(void *clientData, int flags); static void TimerSetupProc(void *clientData, int flags); /* *---------------------------------------------------------------------- * * InitTimer -- * * This function initializes the timer module. * * Results: * A pointer to the thread specific data. * * Side effects: * Registers the idle and timer event sources. * *---------------------------------------------------------------------- */ static ThreadSpecificData * InitTimer(void) { ThreadSpecificData *tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); if (tsdPtr == NULL) { tsdPtr = TCL_TSD_INIT(&dataKey); Tcl_CreateEventSource(TimerSetupProc, TimerCheckProc, NULL); Tcl_CreateThreadExitHandler(TimerExitProc, NULL); } return tsdPtr; } /* *---------------------------------------------------------------------- * * TimerExitProc -- * * This function is call at exit or unload time to remove the timer and * idle event sources. * * Results: * None. * * Side effects: * Removes the timer and idle event sources and remaining events. * *---------------------------------------------------------------------- */ static void TimerExitProc( TCL_UNUSED(void *)) { ThreadSpecificData *tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); Tcl_DeleteEventSource(TimerSetupProc, TimerCheckProc, NULL); if (tsdPtr != NULL) { TimerHandler *timerHandlerPtr; timerHandlerPtr = tsdPtr->firstTimerHandlerPtr; while (timerHandlerPtr != NULL) { tsdPtr->firstTimerHandlerPtr = timerHandlerPtr->nextPtr; Tcl_Free(timerHandlerPtr); timerHandlerPtr = tsdPtr->firstTimerHandlerPtr; } } } /* *-------------------------------------------------------------- * * Tcl_CreateTimerHandler -- * * Arrange for a given function to be invoked at a particular time in the * future. * * Results: * The return value is a token for the timer event, which may be used to * delete the event before it fires. * * Side effects: * When milliseconds have elapsed, proc will be invoked exactly once. * *-------------------------------------------------------------- */ Tcl_TimerToken Tcl_CreateTimerHandler( int milliseconds, /* How many milliseconds to wait before * invoking proc. */ Tcl_TimerProc *proc, /* Function to invoke. */ void *clientData) /* Arbitrary data to pass to proc. */ { Tcl_Time time; /* * Compute when the event should fire. */ Tcl_GetTime(&time); time.sec += milliseconds/1000; time.usec += (milliseconds%1000)*1000; if (time.usec >= 1000000) { time.usec -= 1000000; time.sec += 1; } return TclCreateAbsoluteTimerHandler(&time, proc, clientData); } /* *-------------------------------------------------------------- * * TclCreateAbsoluteTimerHandler -- * * Arrange for a given function to be invoked at a particular time in the * future. * * Results: * The return value is a token for the timer event, which may be used to * delete the event before it fires. * * Side effects: * When the time in timePtr has been reached, proc will be invoked * exactly once. * *-------------------------------------------------------------- */ Tcl_TimerToken TclCreateAbsoluteTimerHandler( Tcl_Time *timePtr, Tcl_TimerProc *proc, void *clientData) { TimerHandler *timerHandlerPtr, *tPtr2, *prevPtr; ThreadSpecificData *tsdPtr = InitTimer(); timerHandlerPtr = (TimerHandler *)Tcl_Alloc(sizeof(TimerHandler)); /* * Fill in fields for the event. */ memcpy(&timerHandlerPtr->time, timePtr, sizeof(Tcl_Time)); timerHandlerPtr->proc = proc; timerHandlerPtr->clientData = clientData; tsdPtr->lastTimerId++; timerHandlerPtr->token = (Tcl_TimerToken) INT2PTR(tsdPtr->lastTimerId); /* * Add the event to the queue in the correct position (ordered by event * firing time). */ for (tPtr2 = tsdPtr->firstTimerHandlerPtr, prevPtr = NULL; tPtr2 != NULL; prevPtr = tPtr2, tPtr2 = tPtr2->nextPtr) { if (TCL_TIME_BEFORE(timerHandlerPtr->time, tPtr2->time)) { break; } } timerHandlerPtr->nextPtr = tPtr2; if (prevPtr == NULL) { tsdPtr->firstTimerHandlerPtr = timerHandlerPtr; } else { prevPtr->nextPtr = timerHandlerPtr; } TimerSetupProc(NULL, TCL_ALL_EVENTS); return timerHandlerPtr->token; } /* *-------------------------------------------------------------- * * Tcl_DeleteTimerHandler -- * * Delete a previously-registered timer handler. * * Results: * None. * * Side effects: * Destroy the timer callback identified by TimerToken, so that its * associated function will not be called. If the callback has already * fired, or if the given token doesn't exist, then nothing happens. * *-------------------------------------------------------------- */ void Tcl_DeleteTimerHandler( Tcl_TimerToken token) /* Result previously returned by * Tcl_DeleteTimerHandler. */ { TimerHandler *timerHandlerPtr, *prevPtr; ThreadSpecificData *tsdPtr = InitTimer(); if (token == NULL) { return; } for (timerHandlerPtr = tsdPtr->firstTimerHandlerPtr, prevPtr = NULL; timerHandlerPtr != NULL; prevPtr = timerHandlerPtr, timerHandlerPtr = timerHandlerPtr->nextPtr) { if (timerHandlerPtr->token != token) { continue; } if (prevPtr == NULL) { tsdPtr->firstTimerHandlerPtr = timerHandlerPtr->nextPtr; } else { prevPtr->nextPtr = timerHandlerPtr->nextPtr; } Tcl_Free(timerHandlerPtr); return; } } /* *---------------------------------------------------------------------- * * TimerSetupProc -- * * This function is called by Tcl_DoOneEvent to setup the timer event * source for before blocking. This routine checks both the idle and * after timer lists. * * Results: * None. * * Side effects: * May update the maximum notifier block time. * *---------------------------------------------------------------------- */ static void TimerSetupProc( TCL_UNUSED(void *), int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { Tcl_Time blockTime; ThreadSpecificData *tsdPtr = InitTimer(); if (((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList) || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending)) { /* * There is an idle handler or a pending timer event, so just poll. */ blockTime.sec = 0; blockTime.usec = 0; } else if ((flags & TCL_TIMER_EVENTS) && tsdPtr->firstTimerHandlerPtr) { /* * Compute the timeout for the next timer on the list. */ Tcl_GetTime(&blockTime); blockTime.sec = tsdPtr->firstTimerHandlerPtr->time.sec - blockTime.sec; blockTime.usec = tsdPtr->firstTimerHandlerPtr->time.usec - blockTime.usec; if (blockTime.usec < 0) { blockTime.sec -= 1; blockTime.usec += 1000000; } if (blockTime.sec < 0) { blockTime.sec = 0; blockTime.usec = 0; } } else { return; } Tcl_SetMaxBlockTime(&blockTime); } /* *---------------------------------------------------------------------- * * TimerCheckProc -- * * This function is called by Tcl_DoOneEvent to check the timer event * source for events. This routine checks both the idle and after timer * lists. * * Results: * None. * * Side effects: * May queue an event and update the maximum notifier block time. * *---------------------------------------------------------------------- */ static void TimerCheckProc( TCL_UNUSED(void *), int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { Tcl_Event *timerEvPtr; Tcl_Time blockTime; ThreadSpecificData *tsdPtr = InitTimer(); if ((flags & TCL_TIMER_EVENTS) && tsdPtr->firstTimerHandlerPtr) { /* * Compute the timeout for the next timer on the list. */ Tcl_GetTime(&blockTime); blockTime.sec = tsdPtr->firstTimerHandlerPtr->time.sec - blockTime.sec; blockTime.usec = tsdPtr->firstTimerHandlerPtr->time.usec - blockTime.usec; if (blockTime.usec < 0) { blockTime.sec -= 1; blockTime.usec += 1000000; } if (blockTime.sec < 0) { blockTime.sec = 0; blockTime.usec = 0; } /* * If the first timer has expired, stick an event on the queue. */ if (blockTime.sec == 0 && blockTime.usec == 0 && !tsdPtr->timerPending) { tsdPtr->timerPending = 1; timerEvPtr = (Tcl_Event *)Tcl_Alloc(sizeof(Tcl_Event)); timerEvPtr->proc = TimerHandlerEventProc; Tcl_QueueEvent(timerEvPtr, TCL_QUEUE_TAIL); } } } /* *---------------------------------------------------------------------- * * TimerHandlerEventProc -- * * This function is called by Tcl_ServiceEvent when a timer event reaches * the front of the event queue. This function handles the event by * invoking the callbacks for all timers that are ready. * * Results: * Returns 1 if the event was handled, meaning it should be removed from * the queue. Returns 0 if the event was not handled, meaning it should * stay on the queue. The only time the event isn't handled is if the * TCL_TIMER_EVENTS flag bit isn't set. * * Side effects: * Whatever the timer handler callback functions do. * *---------------------------------------------------------------------- */ static int TimerHandlerEventProc( TCL_UNUSED(Tcl_Event *), int flags) /* Flags that indicate what events to handle, * such as TCL_FILE_EVENTS. */ { TimerHandler *timerHandlerPtr, **nextPtrPtr; Tcl_Time time; int currentTimerId; ThreadSpecificData *tsdPtr = InitTimer(); /* * Do nothing if timers aren't enabled. This leaves the event on the * queue, so we will get to it as soon as ServiceEvents() is called with * timers enabled. */ if (!(flags & TCL_TIMER_EVENTS)) { return 0; } /* * The code below is trickier than it may look, for the following reasons: * * 1. New handlers can get added to the list while the current one is * being processed. If new ones get added, we don't want to process * them during this pass through the list to avoid starving other event * sources. This is implemented using the token number in the handler: * new handlers will have a newer token than any of the ones currently * on the list. * 2. The handler can call Tcl_DoOneEvent, so we have to remove the * handler from the list before calling it. Otherwise an infinite loop * could result. * 3. Tcl_DeleteTimerHandler can be called to remove an element from the * list while a handler is executing, so the list could change * structure during the call. * 4. Because we only fetch the current time before entering the loop, the * only way a new timer will even be considered runnable is if its * expiration time is within the same millisecond as the current time. * This is fairly likely on Windows, since it has a course granularity * clock. Since timers are placed on the queue in time order with the * most recently created handler appearing after earlier ones with the * same expiration time, we don't have to worry about newer generation * timers appearing before later ones. */ tsdPtr->timerPending = 0; currentTimerId = tsdPtr->lastTimerId; Tcl_GetTime(&time); while (1) { nextPtrPtr = &tsdPtr->firstTimerHandlerPtr; timerHandlerPtr = tsdPtr->firstTimerHandlerPtr; if (timerHandlerPtr == NULL) { break; } if (TCL_TIME_BEFORE(time, timerHandlerPtr->time)) { break; } /* * Bail out if the next timer is of a newer generation. */ if ((currentTimerId - PTR2INT(timerHandlerPtr->token)) < 0) { break; } /* * Remove the handler from the queue before invoking it, to avoid * potential reentrancy problems. */ *nextPtrPtr = timerHandlerPtr->nextPtr; timerHandlerPtr->proc(timerHandlerPtr->clientData); Tcl_Free(timerHandlerPtr); } TimerSetupProc(NULL, TCL_TIMER_EVENTS); return 1; } /* *-------------------------------------------------------------- * * Tcl_DoWhenIdle -- * * Arrange for proc to be invoked the next time the system is idle (i.e., * just before the next time that Tcl_DoOneEvent would have to wait for * something to happen). * * Results: * None. * * Side effects: * Proc will eventually be called, with clientData as argument. See the * manual entry for details. * *-------------------------------------------------------------- */ void Tcl_DoWhenIdle( Tcl_IdleProc *proc, /* Function to invoke. */ void *clientData) /* Arbitrary value to pass to proc. */ { IdleHandler *idlePtr; Tcl_Time blockTime; ThreadSpecificData *tsdPtr = InitTimer(); idlePtr = (IdleHandler *)Tcl_Alloc(sizeof(IdleHandler)); idlePtr->proc = proc; idlePtr->clientData = clientData; idlePtr->generation = tsdPtr->idleGeneration; idlePtr->nextPtr = NULL; if (tsdPtr->lastIdlePtr == NULL) { tsdPtr->idleList = idlePtr; } else { tsdPtr->lastIdlePtr->nextPtr = idlePtr; } tsdPtr->lastIdlePtr = idlePtr; blockTime.sec = 0; blockTime.usec = 0; Tcl_SetMaxBlockTime(&blockTime); } /* *---------------------------------------------------------------------- * * Tcl_CancelIdleCall -- * * If there are any when-idle calls requested to a given function with * given clientData, cancel all of them. * * Results: * None. * * Side effects: * If the proc/clientData combination were on the when-idle list, they * are removed so that they will never be called. * *---------------------------------------------------------------------- */ void Tcl_CancelIdleCall( Tcl_IdleProc *proc, /* Function that was previously registered. */ void *clientData) /* Arbitrary value to pass to proc. */ { IdleHandler *idlePtr, *prevPtr; IdleHandler *nextPtr; ThreadSpecificData *tsdPtr = InitTimer(); for (prevPtr = NULL, idlePtr = tsdPtr->idleList; idlePtr != NULL; prevPtr = idlePtr, idlePtr = idlePtr->nextPtr) { while ((idlePtr->proc == proc) && (idlePtr->clientData == clientData)) { nextPtr = idlePtr->nextPtr; Tcl_Free(idlePtr); idlePtr = nextPtr; if (prevPtr == NULL) { tsdPtr->idleList = idlePtr; } else { prevPtr->nextPtr = idlePtr; } if (idlePtr == NULL) { tsdPtr->lastIdlePtr = prevPtr; return; } } } } /* *---------------------------------------------------------------------- * * TclServiceIdle -- * * This function is invoked by the notifier when it becomes idle. It will * invoke all idle handlers that are present at the time the call is * invoked, but not those added during idle processing. * * Results: * The return value is 1 if TclServiceIdle found something to do, * otherwise return value is 0. * * Side effects: * Invokes all pending idle handlers. * *---------------------------------------------------------------------- */ int TclServiceIdle(void) { IdleHandler *idlePtr; int oldGeneration; Tcl_Time blockTime; ThreadSpecificData *tsdPtr = InitTimer(); if (tsdPtr->idleList == NULL) { return 0; } oldGeneration = tsdPtr->idleGeneration; tsdPtr->idleGeneration++; /* * The code below is trickier than it may look, for the following reasons: * * 1. New handlers can get added to the list while the current one is * being processed. If new ones get added, we don't want to process * them during this pass through the list (want to check for other work * to do first). This is implemented using the generation number in the * handler: new handlers will have a different generation than any of * the ones currently on the list. * 2. The handler can call Tcl_DoOneEvent, so we have to remove the * handler from the list before calling it. Otherwise an infinite loop * could result. * 3. Tcl_CancelIdleCall can be called to remove an element from the list * while a handler is executing, so the list could change structure * during the call. */ for (idlePtr = tsdPtr->idleList; ((idlePtr != NULL) && ((oldGeneration - idlePtr->generation) >= 0)); idlePtr = tsdPtr->idleList) { tsdPtr->idleList = idlePtr->nextPtr; if (tsdPtr->idleList == NULL) { tsdPtr->lastIdlePtr = NULL; } idlePtr->proc(idlePtr->clientData); Tcl_Free(idlePtr); } if (tsdPtr->idleList) { blockTime.sec = 0; blockTime.usec = 0; Tcl_SetMaxBlockTime(&blockTime); } return 1; } /* *---------------------------------------------------------------------- * * Tcl_AfterObjCmd -- * * This function is invoked to process the "after" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_AfterObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_WideInt ms = 0; /* Number of milliseconds to wait */ Tcl_Time wakeup; AfterInfo *afterPtr; AfterAssocData *assocPtr; Tcl_Size length; int index = -1; static const char *const afterSubCmds[] = { "cancel", "idle", "info", NULL }; enum afterSubCmdsEnum {AFTER_CANCEL, AFTER_IDLE, AFTER_INFO}; ThreadSpecificData *tsdPtr = InitTimer(); if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; } /* * Create the "after" information associated for this interpreter, if it * doesn't already exist. */ assocPtr = (AfterAssocData *)Tcl_GetAssocData(interp, "tclAfter", NULL); if (assocPtr == NULL) { assocPtr = (AfterAssocData *)Tcl_Alloc(sizeof(AfterAssocData)); assocPtr->interp = interp; assocPtr->firstAfterPtr = NULL; Tcl_SetAssocData(interp, "tclAfter", AfterCleanupProc, assocPtr); } /* * First lets see if the command was passed a number as the first argument. */ if (TclGetWideIntFromObj(NULL, objv[1], &ms) != TCL_OK) { if (Tcl_GetIndexFromObj(NULL, objv[1], afterSubCmds, "", 0, &index) != TCL_OK) { const char *arg = TclGetString(objv[1]); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad argument \"%s\": must be" " cancel, idle, info, or an integer", arg)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "INDEX", "argument", arg, (char *)NULL); return TCL_ERROR; } } /* * At this point, either index = -1 and ms contains the number of ms * to wait, or else index is the index of a subcommand. */ switch (index) { case -1: { if (ms < 0) { ms = 0; } if (objc == 2) { return AfterDelay(interp, ms); } afterPtr = (AfterInfo *)Tcl_Alloc(sizeof(AfterInfo)); afterPtr->assocPtr = assocPtr; if (objc == 3) { afterPtr->commandPtr = objv[2]; } else { afterPtr->commandPtr = Tcl_ConcatObj(objc-2, objv+2); } Tcl_IncrRefCount(afterPtr->commandPtr); /* * The variable below is used to generate unique identifiers for after * commands. This id can wrap around, which can potentially cause * problems. However, there are not likely to be problems in practice, * because after commands can only be requested to about a month in * the future, and wrap-around is unlikely to occur in less than about * 1-10 years. Thus it's unlikely that any old ids will still be * around when wrap-around occurs. */ afterPtr->id = tsdPtr->afterId; tsdPtr->afterId += 1; Tcl_GetTime(&wakeup); wakeup.sec += ms / 1000; wakeup.usec += ms % 1000 * 1000; if (wakeup.usec > 1000000) { wakeup.sec++; wakeup.usec -= 1000000; } afterPtr->token = TclCreateAbsoluteTimerHandler(&wakeup, AfterProc, afterPtr); afterPtr->nextPtr = assocPtr->firstAfterPtr; assocPtr->firstAfterPtr = afterPtr; Tcl_SetObjResult(interp, Tcl_ObjPrintf("after#%d", afterPtr->id)); return TCL_OK; } case AFTER_CANCEL: { Tcl_Obj *commandPtr; const char *command, *tempCommand; Tcl_Size tempLength; if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "id|command"); return TCL_ERROR; } if (objc == 3) { commandPtr = objv[2]; } else { commandPtr = Tcl_ConcatObj(objc-2, objv+2); } command = TclGetStringFromObj(commandPtr, &length); for (afterPtr = assocPtr->firstAfterPtr; afterPtr != NULL; afterPtr = afterPtr->nextPtr) { tempCommand = TclGetStringFromObj(afterPtr->commandPtr, &tempLength); if ((length == tempLength) && !memcmp(command, tempCommand, length)) { break; } } if (afterPtr == NULL) { afterPtr = GetAfterEvent(assocPtr, commandPtr); } if (objc != 3) { Tcl_DecrRefCount(commandPtr); } if (afterPtr != NULL) { if (afterPtr->token != NULL) { Tcl_DeleteTimerHandler(afterPtr->token); } else { Tcl_CancelIdleCall(AfterProc, afterPtr); } FreeAfterPtr(afterPtr); } break; } case AFTER_IDLE: if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "script ?script ...?"); return TCL_ERROR; } afterPtr = (AfterInfo *)Tcl_Alloc(sizeof(AfterInfo)); afterPtr->assocPtr = assocPtr; if (objc == 3) { afterPtr->commandPtr = objv[2]; } else { afterPtr->commandPtr = Tcl_ConcatObj(objc-2, objv+2); } Tcl_IncrRefCount(afterPtr->commandPtr); afterPtr->id = tsdPtr->afterId; tsdPtr->afterId += 1; afterPtr->token = NULL; afterPtr->nextPtr = assocPtr->firstAfterPtr; assocPtr->firstAfterPtr = afterPtr; Tcl_DoWhenIdle(AfterProc, afterPtr); Tcl_SetObjResult(interp, Tcl_ObjPrintf("after#%d", afterPtr->id)); break; case AFTER_INFO: if (objc == 2) { Tcl_Obj *resultObj; TclNewObj(resultObj); for (afterPtr = assocPtr->firstAfterPtr; afterPtr != NULL; afterPtr = afterPtr->nextPtr) { if (assocPtr->interp == interp) { Tcl_ListObjAppendElement(NULL, resultObj, Tcl_ObjPrintf( "after#%d", afterPtr->id)); } } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "?id?"); return TCL_ERROR; } afterPtr = GetAfterEvent(assocPtr, objv[2]); if (afterPtr == NULL) { const char *eventStr = TclGetString(objv[2]); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "event \"%s\" doesn't exist", eventStr)); Tcl_SetErrorCode(interp, "TCL","LOOKUP","EVENT", eventStr, (char *)NULL); return TCL_ERROR; } else { Tcl_Obj *resultListPtr; TclNewObj(resultListPtr); Tcl_ListObjAppendElement(interp, resultListPtr, afterPtr->commandPtr); Tcl_ListObjAppendElement(interp, resultListPtr, Tcl_NewStringObj( (afterPtr->token == NULL) ? "idle" : "timer", -1)); Tcl_SetObjResult(interp, resultListPtr); } break; default: Tcl_Panic("Tcl_AfterObjCmd: bad subcommand index to afterSubCmds"); } return TCL_OK; } /* *---------------------------------------------------------------------- * * AfterDelay -- * * Implements the blocking delay behaviour of [after $time]. Tricky * because it has to take into account any time limit that has been set. * * Results: * Standard Tcl result code (with error set if an error occurred due to a * time limit being exceeded or being canceled). * * Side effects: * May adjust the time limit granularity marker. * *---------------------------------------------------------------------- */ static int AfterDelay( Tcl_Interp *interp, Tcl_WideInt ms) { Interp *iPtr = (Interp *) interp; Tcl_Time endTime, now; Tcl_WideInt diff; Tcl_GetTime(&now); endTime = now; endTime.sec += (ms / 1000); endTime.usec += (ms % 1000) * 1000; if (endTime.usec >= 1000000) { endTime.sec++; endTime.usec -= 1000000; } do { if (Tcl_AsyncReady()) { if (Tcl_AsyncInvoke(interp, TCL_OK) != TCL_OK) { return TCL_ERROR; } } if (Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG) == TCL_ERROR) { return TCL_ERROR; } if (iPtr->limit.timeEvent != NULL && TCL_TIME_BEFORE(iPtr->limit.time, now)) { iPtr->limit.granularityTicker = 0; if (Tcl_LimitCheck(interp) != TCL_OK) { return TCL_ERROR; } } if (iPtr->limit.timeEvent == NULL || TCL_TIME_BEFORE(endTime, iPtr->limit.time)) { diff = TCL_TIME_DIFF_MS_CEILING(endTime, now); if (diff > TCL_TIME_MAXIMUM_SLICE) { diff = TCL_TIME_MAXIMUM_SLICE; } if (diff == 0 && TCL_TIME_BEFORE(now, endTime)) { diff = 1; } if (diff > 0) { Tcl_Sleep((int) diff); if (diff < SLEEP_OFFLOAD_GETTIMEOFDAY) { break; } } else { break; } } else { diff = TCL_TIME_DIFF_MS(iPtr->limit.time, now); if (diff > TCL_TIME_MAXIMUM_SLICE) { diff = TCL_TIME_MAXIMUM_SLICE; } if (diff > 0) { Tcl_Sleep((int) diff); } if (Tcl_AsyncReady()) { if (Tcl_AsyncInvoke(interp, TCL_OK) != TCL_OK) { return TCL_ERROR; } } if (Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG) == TCL_ERROR) { return TCL_ERROR; } if (Tcl_LimitCheck(interp) != TCL_OK) { return TCL_ERROR; } } Tcl_GetTime(&now); } while (TCL_TIME_BEFORE(now, endTime)); return TCL_OK; } /* *---------------------------------------------------------------------- * * GetAfterEvent -- * * This function parses an "after" id such as "after#4" and returns a * pointer to the AfterInfo structure. * * Results: * The return value is either a pointer to an AfterInfo structure, if one * is found that corresponds to "cmdString" and is for interp, or NULL if * no corresponding after event can be found. * * Side effects: * None. * *---------------------------------------------------------------------- */ static AfterInfo * GetAfterEvent( AfterAssocData *assocPtr, /* Points to "after"-related information for * this interpreter. */ Tcl_Obj *commandPtr) { const char *cmdString; /* Textual identifier for after event, such as * "after#6". */ AfterInfo *afterPtr; int id; char *end; cmdString = TclGetString(commandPtr); if (strncmp(cmdString, "after#", 6) != 0) { return NULL; } cmdString += 6; id = strtoul(cmdString, &end, 10); if ((end == cmdString) || (*end != 0)) { return NULL; } for (afterPtr = assocPtr->firstAfterPtr; afterPtr != NULL; afterPtr = afterPtr->nextPtr) { if (afterPtr->id == id) { return afterPtr; } } return NULL; } /* *---------------------------------------------------------------------- * * AfterProc -- * * Timer callback to execute commands registered with the "after" * command. * * Results: * None. * * Side effects: * Executes whatever command was specified. If the command returns an * error, then the command "bgerror" is invoked to process the error; if * bgerror fails then information about the error is output on stderr. * *---------------------------------------------------------------------- */ static void AfterProc( void *clientData) /* Describes command to execute. */ { AfterInfo *afterPtr = (AfterInfo *)clientData; AfterAssocData *assocPtr = afterPtr->assocPtr; AfterInfo *prevPtr; int result; Tcl_Interp *interp; /* * First remove the callback from our list of callbacks; otherwise someone * could delete the callback while it's being executed, which could cause * a core dump. */ if (assocPtr->firstAfterPtr == afterPtr) { assocPtr->firstAfterPtr = afterPtr->nextPtr; } else { for (prevPtr = assocPtr->firstAfterPtr; prevPtr->nextPtr != afterPtr; prevPtr = prevPtr->nextPtr) { /* Empty loop body. */ } prevPtr->nextPtr = afterPtr->nextPtr; } /* * Execute the callback. */ interp = assocPtr->interp; Tcl_Preserve(interp); result = Tcl_EvalObjEx(interp, afterPtr->commandPtr, TCL_EVAL_GLOBAL); if (result != TCL_OK) { Tcl_AddErrorInfo(interp, "\n (\"after\" script)"); Tcl_BackgroundException(interp, result); } Tcl_Release(interp); /* * Free the memory for the callback. */ Tcl_DecrRefCount(afterPtr->commandPtr); Tcl_Free(afterPtr); } /* *---------------------------------------------------------------------- * * FreeAfterPtr -- * * This function removes an "after" command from the list of those that * are pending and frees its resources. This function does *not* cancel * the timer handler; if that's needed, the caller must do it. * * Results: * None. * * Side effects: * The memory associated with afterPtr is released. * *---------------------------------------------------------------------- */ static void FreeAfterPtr( AfterInfo *afterPtr) /* Command to be deleted. */ { AfterInfo *prevPtr; AfterAssocData *assocPtr = afterPtr->assocPtr; if (assocPtr->firstAfterPtr == afterPtr) { assocPtr->firstAfterPtr = afterPtr->nextPtr; } else { for (prevPtr = assocPtr->firstAfterPtr; prevPtr->nextPtr != afterPtr; prevPtr = prevPtr->nextPtr) { /* Empty loop body. */ } prevPtr->nextPtr = afterPtr->nextPtr; } Tcl_DecrRefCount(afterPtr->commandPtr); Tcl_Free(afterPtr); } /* *---------------------------------------------------------------------- * * AfterCleanupProc -- * * This function is invoked whenever an interpreter is deleted * to cleanup the AssocData for "tclAfter". * * Results: * None. * * Side effects: * After commands are removed. * *---------------------------------------------------------------------- */ static void AfterCleanupProc( void *clientData, /* Points to AfterAssocData for the * interpreter. */ TCL_UNUSED(Tcl_Interp *)) { AfterAssocData *assocPtr = (AfterAssocData *)clientData; AfterInfo *afterPtr; while (assocPtr->firstAfterPtr != NULL) { afterPtr = assocPtr->firstAfterPtr; assocPtr->firstAfterPtr = afterPtr->nextPtr; if (afterPtr->token != NULL) { Tcl_DeleteTimerHandler(afterPtr->token); } else { Tcl_CancelIdleCall(AfterProc, afterPtr); } Tcl_DecrRefCount(afterPtr->commandPtr); Tcl_Free(afterPtr); } Tcl_Free(assocPtr); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * indent-tabs-mode: nil * End: */ tcl9.0.1/generic/tclTomMath.h0000644000175000017500000000272314726623136015423 0ustar sergeisergei#ifndef BN_TCL_H_ #define BN_TCL_H_ #include #if defined(TCL_NO_TOMMATH_H) typedef size_t mp_digit; typedef int mp_sign; # define MP_ZPOS 0 /* positive integer */ # define MP_NEG 1 /* negative */ typedef int mp_ord; # define MP_LT -1 /* less than */ # define MP_EQ 0 /* equal to */ # define MP_GT 1 /* greater than */ typedef int mp_err; # define MP_OKAY 0 /* no error */ # define MP_ERR -1 /* unknown error */ # define MP_MEM -2 /* out of mem */ # define MP_VAL -3 /* invalid input */ # define MP_ITER -4 /* maximum iterations reached */ # define MP_BUF -5 /* buffer overflow, supplied buffer too small */ typedef int mp_order; # define MP_LSB_FIRST -1 # define MP_MSB_FIRST 1 typedef int mp_endian; # define MP_LITTLE_ENDIAN -1 # define MP_NATIVE_ENDIAN 0 # define MP_BIG_ENDIAN 1 # define MP_DEPRECATED_PRAGMA(s) /* nothing */ # define MP_WUR /* nothing */ # define mp_iszero(a) ((a)->used == 0) # define mp_isneg(a) ((a)->sign != 0) /* the infamous mp_int structure */ # ifndef MP_INT_DECLARED # define MP_INT_DECLARED typedef struct mp_int mp_int; # endif struct mp_int { int used, alloc; mp_sign sign; mp_digit *dp; }; #elif !defined(BN_H_) /* If BN_H_ already defined, don't try to include tommath.h again. */ # include "tommath.h" #endif #include "tclTomMathDecls.h" #endif tcl9.0.1/generic/tclTomMathDecls.h0000644000175000017500000005573214731057471016405 0ustar sergeisergei/* *---------------------------------------------------------------------- * * tclTomMathDecls.h -- * * This file contains the declarations for the 'libtommath' * functions that are exported by the Tcl library. * * Copyright (c) 2005 by Kevin B. Kenny. All rights reserved. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #ifndef _TCLTOMMATHDECLS #define _TCLTOMMATHDECLS #include "tcl.h" #include #ifndef BN_H_ #include "tclTomMath.h" #endif /* * Define the version of the Stubs table that's exported for tommath */ #define TCLTOMMATH_EPOCH 0 #define TCLTOMMATH_REVISION 0 #define Tcl_TomMath_InitStubs(interp,version) \ (TclTomMathInitializeStubs((interp),(version),\ TCLTOMMATH_EPOCH,TCLTOMMATH_REVISION)) /* Define custom memory allocation for libtommath */ /* MODULE_SCOPE void* TclBNAlloc( size_t ); */ #define TclBNAlloc(s) Tcl_AttemptAlloc((size_t)(s)) /* MODULE_SCOPE void* TclBNCalloc( size_t, size_t ); */ #define TclBNCalloc(m,s) memset(Tcl_AttemptAlloc((size_t)(m)*(size_t)(s)),0,(size_t)(m)*(size_t)(s)) /* MODULE_SCOPE void* TclBNRealloc( void*, size_t ); */ #define TclBNRealloc(x,s) Tcl_AttemptRealloc((x),(size_t)(s)) /* MODULE_SCOPE void TclBNFree( void* ); */ #define TclBNFree(x) Tcl_Free(x) #undef MP_MALLOC #undef MP_CALLOC #undef MP_REALLOC #undef MP_FREE #define MP_MALLOC(size) TclBNAlloc(size) #define MP_CALLOC(nmemb, size) TclBNCalloc((nmemb), (size)) #define MP_REALLOC(mem, oldsize, newsize) TclBNRealloc((mem), ((void)(oldsize), (newsize))) #define MP_FREE(mem, size) TclBNFree(((void)(size), (mem))) #ifndef MODULE_SCOPE # ifdef __cplusplus # define MODULE_SCOPE extern "C" # else # define MODULE_SCOPE extern # endif #endif #ifdef __cplusplus extern "C" { #endif MODULE_SCOPE mp_err TclBN_mp_sqr(const mp_int *a, mp_int *b); MODULE_SCOPE mp_err TclBN_mp_balance_mul(const mp_int *a, const mp_int *b, mp_int *c); MODULE_SCOPE mp_err TclBN_mp_div_3(const mp_int *a, mp_int *c, mp_digit *d); MODULE_SCOPE mp_err TclBN_mp_karatsuba_mul(const mp_int *a, const mp_int *b, mp_int *c); MODULE_SCOPE mp_err TclBN_mp_karatsuba_sqr(const mp_int *a, mp_int *b); MODULE_SCOPE mp_err TclBN_mp_toom_mul(const mp_int *a, const mp_int *b, mp_int *c); MODULE_SCOPE mp_err TclBN_mp_toom_sqr(const mp_int *a, mp_int *b); MODULE_SCOPE mp_err TclBN_s_mp_add(const mp_int *a, const mp_int *b, mp_int *c); MODULE_SCOPE mp_err TclBN_mp_mul_digs(const mp_int *a, const mp_int *b, mp_int *c, int digs); MODULE_SCOPE mp_err TclBN_mp_mul_digs_fast(const mp_int *a, const mp_int *b, mp_int *c, int digs); MODULE_SCOPE void TclBN_mp_reverse(unsigned char *s, size_t len); MODULE_SCOPE mp_err TclBN_s_mp_sqr(const mp_int *a, mp_int *b); MODULE_SCOPE mp_err TclBN_mp_sqr_fast(const mp_int *a, mp_int *b); MODULE_SCOPE mp_err TclBN_s_mp_sub(const mp_int *a, const mp_int *b, mp_int *c); MODULE_SCOPE const char *const TclBN_mp_s_rmap; MODULE_SCOPE const uint8_t TclBN_mp_s_rmap_reverse[]; MODULE_SCOPE const size_t TclBN_mp_s_rmap_reverse_sz; MODULE_SCOPE mp_err TclBN_mp_set_int(mp_int *a, unsigned long b); #ifdef __cplusplus } #endif /* Rename the global symbols in libtommath to avoid linkage conflicts */ #ifndef TCL_WITH_EXTERNAL_TOMMATH #define bn_reverse TclBN_reverse #define mp_add TclBN_mp_add #define mp_add_d TclBN_mp_add_d #define mp_and TclBN_mp_and #define mp_clamp TclBN_mp_clamp #define mp_clear TclBN_mp_clear #define mp_clear_multi TclBN_mp_clear_multi #define mp_cmp TclBN_mp_cmp #define mp_cmp_d TclBN_mp_cmp_d #define mp_cmp_mag TclBN_mp_cmp_mag #define mp_cnt_lsb TclBN_mp_cnt_lsb #define mp_copy TclBN_mp_copy #define mp_count_bits TclBN_mp_count_bits #define mp_div TclBN_mp_div #define mp_div_d TclBN_mp_div_d #define mp_div_2 TclBN_mp_div_2 #define mp_div_2d TclBN_mp_div_2d #define mp_exch TclBN_mp_exch #define mp_expt_d TclBN_mp_expt_n #define mp_expt_n TclBN_mp_expt_n #define mp_get_mag_u64 TclBN_mp_get_mag_u64 #define mp_grow TclBN_mp_grow #define mp_init TclBN_mp_init #define mp_init_copy TclBN_mp_init_copy #define mp_init_i64 TclBN_mp_init_i64 #define mp_init_multi TclBN_mp_init_multi #define mp_init_set TclBN_mp_init_set #define mp_init_size TclBN_mp_init_size #define mp_init_u64 TclBN_mp_init_u64 #define mp_lshd TclBN_mp_lshd #define mp_mod TclBN_mp_mod #define mp_mod_2d TclBN_mp_mod_2d #define mp_mul TclBN_mp_mul #define mp_mul_d TclBN_mp_mul_d #define mp_mul_2 TclBN_mp_mul_2 #define mp_mul_2d TclBN_mp_mul_2d #define mp_neg TclBN_mp_neg #define mp_or TclBN_mp_or #define mp_pack TclBN_mp_pack #define mp_pack_count TclBN_mp_pack_count #define mp_radix_size TclBN_mp_radix_size #define mp_read_radix TclBN_mp_read_radix #define mp_rshd TclBN_mp_rshd #define mp_s_rmap TclBN_mp_s_rmap #define mp_s_rmap_reverse TclBN_mp_s_rmap_reverse #define mp_s_rmap_reverse_sz TclBN_mp_s_rmap_reverse_sz #define mp_set TclBN_mp_set #define mp_set_i64 TclBN_mp_set_i64 #define mp_set_u64 TclBN_mp_set_u64 #define mp_shrink TclBN_mp_shrink #define mp_sqr TclBN_mp_sqr #define mp_sqrt TclBN_mp_sqrt #define mp_sub TclBN_mp_sub #define mp_sub_d TclBN_mp_sub_d #define mp_signed_rsh TclBN_mp_signed_rsh #define mp_tc_and TclBN_mp_and #define mp_tc_div_2d TclBN_mp_signed_rsh #define mp_tc_or TclBN_mp_or #define mp_tc_xor TclBN_mp_xor #define mp_to_unsigned_bin TclBN_mp_to_unsigned_bin #define mp_to_unsigned_bin_n TclBN_mp_to_unsigned_bin_n #define mp_toradix_n TclBN_mp_toradix_n #define mp_to_radix TclBN_mp_to_radix #define mp_to_ubin TclBN_mp_to_ubin #define mp_ubin_size TclBN_mp_ubin_size #define mp_unpack TclBN_mp_unpack #define mp_xor TclBN_mp_xor #define mp_zero TclBN_mp_zero #define s_mp_add TclBN_s_mp_add #define s_mp_balance_mul TclBN_mp_balance_mul #define s_mp_div_3 TclBN_mp_div_3 #define s_mp_karatsuba_mul TclBN_mp_karatsuba_mul #define s_mp_karatsuba_sqr TclBN_mp_karatsuba_sqr #define s_mp_mul_digs TclBN_mp_mul_digs #define s_mp_mul_digs_fast TclBN_mp_mul_digs_fast #define s_mp_reverse TclBN_mp_reverse #define s_mp_sqr TclBN_s_mp_sqr #define s_mp_sqr_fast TclBN_mp_sqr_fast #define s_mp_sub TclBN_s_mp_sub #define s_mp_toom_mul TclBN_mp_toom_mul #define s_mp_toom_sqr TclBN_mp_toom_sqr #endif /* !TCL_WITH_EXTERNAL_TOMMATH */ #undef TCL_STORAGE_CLASS #ifdef BUILD_tcl # define TCL_STORAGE_CLASS DLLEXPORT #else # ifdef USE_TCL_STUBS # define TCL_STORAGE_CLASS # else # define TCL_STORAGE_CLASS DLLIMPORT # endif #endif /* * WARNING: This file is automatically generated by the tools/genStubs.tcl * script. Any modifications to the function declarations below should be made * in the generic/tclInt.decls script. */ /* !BEGIN!: Do not edit below this line. */ #ifdef __cplusplus extern "C" { #endif /* * Exported function declarations: */ /* 0 */ EXTERN int TclBN_epoch(void) MP_WUR; /* 1 */ EXTERN int TclBN_revision(void) MP_WUR; /* 2 */ EXTERN mp_err TclBN_mp_add(const mp_int *a, const mp_int *b, mp_int *c) MP_WUR; /* 3 */ EXTERN mp_err TclBN_mp_add_d(const mp_int *a, mp_digit b, mp_int *c) MP_WUR; /* 4 */ EXTERN mp_err TclBN_mp_and(const mp_int *a, const mp_int *b, mp_int *c) MP_WUR; /* 5 */ EXTERN void TclBN_mp_clamp(mp_int *a); /* 6 */ EXTERN void TclBN_mp_clear(mp_int *a); /* 7 */ EXTERN void TclBN_mp_clear_multi(mp_int *a, ...); /* 8 */ EXTERN mp_ord TclBN_mp_cmp(const mp_int *a, const mp_int *b) MP_WUR; /* 9 */ EXTERN mp_ord TclBN_mp_cmp_d(const mp_int *a, mp_digit b) MP_WUR; /* 10 */ EXTERN mp_ord TclBN_mp_cmp_mag(const mp_int *a, const mp_int *b) MP_WUR; /* 11 */ EXTERN mp_err TclBN_mp_copy(const mp_int *a, mp_int *b) MP_WUR; /* 12 */ EXTERN int TclBN_mp_count_bits(const mp_int *a) MP_WUR; /* 13 */ EXTERN mp_err TclBN_mp_div(const mp_int *a, const mp_int *b, mp_int *q, mp_int *r) MP_WUR; /* 14 */ EXTERN mp_err TclBN_mp_div_d(const mp_int *a, mp_digit b, mp_int *q, mp_digit *r) MP_WUR; /* 15 */ EXTERN mp_err TclBN_mp_div_2(const mp_int *a, mp_int *q) MP_WUR; /* 16 */ EXTERN mp_err TclBN_mp_div_2d(const mp_int *a, int b, mp_int *q, mp_int *r) MP_WUR; /* Slot 17 is reserved */ /* 18 */ EXTERN void TclBN_mp_exch(mp_int *a, mp_int *b); /* 19 */ EXTERN mp_err TclBN_mp_expt_n(const mp_int *a, int b, mp_int *c) MP_WUR; /* 20 */ EXTERN mp_err TclBN_mp_grow(mp_int *a, int size) MP_WUR; /* 21 */ EXTERN mp_err TclBN_mp_init(mp_int *a) MP_WUR; /* 22 */ EXTERN mp_err TclBN_mp_init_copy(mp_int *a, const mp_int *b) MP_WUR; /* 23 */ EXTERN mp_err TclBN_mp_init_multi(mp_int *a, ...) MP_WUR; /* 24 */ EXTERN mp_err TclBN_mp_init_set(mp_int *a, mp_digit b) MP_WUR; /* 25 */ EXTERN mp_err TclBN_mp_init_size(mp_int *a, int size) MP_WUR; /* 26 */ EXTERN mp_err TclBN_mp_lshd(mp_int *a, int shift) MP_WUR; /* 27 */ EXTERN mp_err TclBN_mp_mod(const mp_int *a, const mp_int *b, mp_int *r) MP_WUR; /* 28 */ EXTERN mp_err TclBN_mp_mod_2d(const mp_int *a, int b, mp_int *r) MP_WUR; /* 29 */ EXTERN mp_err TclBN_mp_mul(const mp_int *a, const mp_int *b, mp_int *p) MP_WUR; /* 30 */ EXTERN mp_err TclBN_mp_mul_d(const mp_int *a, mp_digit b, mp_int *p) MP_WUR; /* 31 */ EXTERN mp_err TclBN_mp_mul_2(const mp_int *a, mp_int *p) MP_WUR; /* 32 */ EXTERN mp_err TclBN_mp_mul_2d(const mp_int *a, int d, mp_int *p) MP_WUR; /* 33 */ EXTERN mp_err TclBN_mp_neg(const mp_int *a, mp_int *b) MP_WUR; /* 34 */ EXTERN mp_err TclBN_mp_or(const mp_int *a, const mp_int *b, mp_int *c) MP_WUR; /* 35 */ EXTERN mp_err TclBN_mp_radix_size(const mp_int *a, int radix, int *size) MP_WUR; /* 36 */ EXTERN mp_err TclBN_mp_read_radix(mp_int *a, const char *str, int radix) MP_WUR; /* 37 */ EXTERN void TclBN_mp_rshd(mp_int *a, int shift); /* 38 */ EXTERN mp_err TclBN_mp_shrink(mp_int *a) MP_WUR; /* Slot 39 is reserved */ /* Slot 40 is reserved */ /* 41 */ EXTERN mp_err TclBN_mp_sqrt(const mp_int *a, mp_int *b) MP_WUR; /* 42 */ EXTERN mp_err TclBN_mp_sub(const mp_int *a, const mp_int *b, mp_int *c) MP_WUR; /* 43 */ EXTERN mp_err TclBN_mp_sub_d(const mp_int *a, mp_digit b, mp_int *c) MP_WUR; /* Slot 44 is reserved */ /* Slot 45 is reserved */ /* Slot 46 is reserved */ /* 47 */ EXTERN size_t TclBN_mp_ubin_size(const mp_int *a) MP_WUR; /* 48 */ EXTERN mp_err TclBN_mp_xor(const mp_int *a, const mp_int *b, mp_int *c) MP_WUR; /* 49 */ EXTERN void TclBN_mp_zero(mp_int *a); /* Slot 50 is reserved */ /* Slot 51 is reserved */ /* Slot 52 is reserved */ /* Slot 53 is reserved */ /* Slot 54 is reserved */ /* Slot 55 is reserved */ /* Slot 56 is reserved */ /* Slot 57 is reserved */ /* Slot 58 is reserved */ /* Slot 59 is reserved */ /* Slot 60 is reserved */ /* Slot 61 is reserved */ /* Slot 62 is reserved */ /* 63 */ EXTERN int TclBN_mp_cnt_lsb(const mp_int *a) MP_WUR; /* Slot 64 is reserved */ /* 65 */ EXTERN int TclBN_mp_init_i64(mp_int *bignum, int64_t initVal) MP_WUR; /* 66 */ EXTERN int TclBN_mp_init_u64(mp_int *bignum, uint64_t initVal) MP_WUR; /* Slot 67 is reserved */ /* 68 */ EXTERN void TclBN_mp_set_u64(mp_int *a, uint64_t i); /* 69 */ EXTERN uint64_t TclBN_mp_get_mag_u64(const mp_int *a) MP_WUR; /* 70 */ EXTERN void TclBN_mp_set_i64(mp_int *a, int64_t i); /* 71 */ EXTERN mp_err TclBN_mp_unpack(mp_int *rop, size_t count, mp_order order, size_t size, mp_endian endian, size_t nails, const void *op) MP_WUR; /* 72 */ EXTERN mp_err TclBN_mp_pack(void *rop, size_t maxcount, size_t *written, mp_order order, size_t size, mp_endian endian, size_t nails, const mp_int *op) MP_WUR; /* Slot 73 is reserved */ /* Slot 74 is reserved */ /* Slot 75 is reserved */ /* 76 */ EXTERN mp_err TclBN_mp_signed_rsh(const mp_int *a, int b, mp_int *c) MP_WUR; /* 77 */ EXTERN size_t TclBN_mp_pack_count(const mp_int *a, size_t nails, size_t size) MP_WUR; /* 78 */ EXTERN int TclBN_mp_to_ubin(const mp_int *a, unsigned char *buf, size_t maxlen, size_t *written) MP_WUR; /* Slot 79 is reserved */ /* 80 */ EXTERN int TclBN_mp_to_radix(const mp_int *a, char *str, size_t maxlen, size_t *written, int radix) MP_WUR; typedef struct TclTomMathStubs { int magic; void *hooks; int (*tclBN_epoch) (void) MP_WUR; /* 0 */ int (*tclBN_revision) (void) MP_WUR; /* 1 */ mp_err (*tclBN_mp_add) (const mp_int *a, const mp_int *b, mp_int *c) MP_WUR; /* 2 */ mp_err (*tclBN_mp_add_d) (const mp_int *a, mp_digit b, mp_int *c) MP_WUR; /* 3 */ mp_err (*tclBN_mp_and) (const mp_int *a, const mp_int *b, mp_int *c) MP_WUR; /* 4 */ void (*tclBN_mp_clamp) (mp_int *a); /* 5 */ void (*tclBN_mp_clear) (mp_int *a); /* 6 */ void (*tclBN_mp_clear_multi) (mp_int *a, ...); /* 7 */ mp_ord (*tclBN_mp_cmp) (const mp_int *a, const mp_int *b) MP_WUR; /* 8 */ mp_ord (*tclBN_mp_cmp_d) (const mp_int *a, mp_digit b) MP_WUR; /* 9 */ mp_ord (*tclBN_mp_cmp_mag) (const mp_int *a, const mp_int *b) MP_WUR; /* 10 */ mp_err (*tclBN_mp_copy) (const mp_int *a, mp_int *b) MP_WUR; /* 11 */ int (*tclBN_mp_count_bits) (const mp_int *a) MP_WUR; /* 12 */ mp_err (*tclBN_mp_div) (const mp_int *a, const mp_int *b, mp_int *q, mp_int *r) MP_WUR; /* 13 */ mp_err (*tclBN_mp_div_d) (const mp_int *a, mp_digit b, mp_int *q, mp_digit *r) MP_WUR; /* 14 */ mp_err (*tclBN_mp_div_2) (const mp_int *a, mp_int *q) MP_WUR; /* 15 */ mp_err (*tclBN_mp_div_2d) (const mp_int *a, int b, mp_int *q, mp_int *r) MP_WUR; /* 16 */ void (*reserved17)(void); void (*tclBN_mp_exch) (mp_int *a, mp_int *b); /* 18 */ mp_err (*tclBN_mp_expt_n) (const mp_int *a, int b, mp_int *c) MP_WUR; /* 19 */ mp_err (*tclBN_mp_grow) (mp_int *a, int size) MP_WUR; /* 20 */ mp_err (*tclBN_mp_init) (mp_int *a) MP_WUR; /* 21 */ mp_err (*tclBN_mp_init_copy) (mp_int *a, const mp_int *b) MP_WUR; /* 22 */ mp_err (*tclBN_mp_init_multi) (mp_int *a, ...) MP_WUR; /* 23 */ mp_err (*tclBN_mp_init_set) (mp_int *a, mp_digit b) MP_WUR; /* 24 */ mp_err (*tclBN_mp_init_size) (mp_int *a, int size) MP_WUR; /* 25 */ mp_err (*tclBN_mp_lshd) (mp_int *a, int shift) MP_WUR; /* 26 */ mp_err (*tclBN_mp_mod) (const mp_int *a, const mp_int *b, mp_int *r) MP_WUR; /* 27 */ mp_err (*tclBN_mp_mod_2d) (const mp_int *a, int b, mp_int *r) MP_WUR; /* 28 */ mp_err (*tclBN_mp_mul) (const mp_int *a, const mp_int *b, mp_int *p) MP_WUR; /* 29 */ mp_err (*tclBN_mp_mul_d) (const mp_int *a, mp_digit b, mp_int *p) MP_WUR; /* 30 */ mp_err (*tclBN_mp_mul_2) (const mp_int *a, mp_int *p) MP_WUR; /* 31 */ mp_err (*tclBN_mp_mul_2d) (const mp_int *a, int d, mp_int *p) MP_WUR; /* 32 */ mp_err (*tclBN_mp_neg) (const mp_int *a, mp_int *b) MP_WUR; /* 33 */ mp_err (*tclBN_mp_or) (const mp_int *a, const mp_int *b, mp_int *c) MP_WUR; /* 34 */ mp_err (*tclBN_mp_radix_size) (const mp_int *a, int radix, int *size) MP_WUR; /* 35 */ mp_err (*tclBN_mp_read_radix) (mp_int *a, const char *str, int radix) MP_WUR; /* 36 */ void (*tclBN_mp_rshd) (mp_int *a, int shift); /* 37 */ mp_err (*tclBN_mp_shrink) (mp_int *a) MP_WUR; /* 38 */ void (*reserved39)(void); void (*reserved40)(void); mp_err (*tclBN_mp_sqrt) (const mp_int *a, mp_int *b) MP_WUR; /* 41 */ mp_err (*tclBN_mp_sub) (const mp_int *a, const mp_int *b, mp_int *c) MP_WUR; /* 42 */ mp_err (*tclBN_mp_sub_d) (const mp_int *a, mp_digit b, mp_int *c) MP_WUR; /* 43 */ void (*reserved44)(void); void (*reserved45)(void); void (*reserved46)(void); size_t (*tclBN_mp_ubin_size) (const mp_int *a) MP_WUR; /* 47 */ mp_err (*tclBN_mp_xor) (const mp_int *a, const mp_int *b, mp_int *c) MP_WUR; /* 48 */ void (*tclBN_mp_zero) (mp_int *a); /* 49 */ void (*reserved50)(void); void (*reserved51)(void); void (*reserved52)(void); void (*reserved53)(void); void (*reserved54)(void); void (*reserved55)(void); void (*reserved56)(void); void (*reserved57)(void); void (*reserved58)(void); void (*reserved59)(void); void (*reserved60)(void); void (*reserved61)(void); void (*reserved62)(void); int (*tclBN_mp_cnt_lsb) (const mp_int *a) MP_WUR; /* 63 */ void (*reserved64)(void); int (*tclBN_mp_init_i64) (mp_int *bignum, int64_t initVal) MP_WUR; /* 65 */ int (*tclBN_mp_init_u64) (mp_int *bignum, uint64_t initVal) MP_WUR; /* 66 */ void (*reserved67)(void); void (*tclBN_mp_set_u64) (mp_int *a, uint64_t i); /* 68 */ uint64_t (*tclBN_mp_get_mag_u64) (const mp_int *a) MP_WUR; /* 69 */ void (*tclBN_mp_set_i64) (mp_int *a, int64_t i); /* 70 */ mp_err (*tclBN_mp_unpack) (mp_int *rop, size_t count, mp_order order, size_t size, mp_endian endian, size_t nails, const void *op) MP_WUR; /* 71 */ mp_err (*tclBN_mp_pack) (void *rop, size_t maxcount, size_t *written, mp_order order, size_t size, mp_endian endian, size_t nails, const mp_int *op) MP_WUR; /* 72 */ void (*reserved73)(void); void (*reserved74)(void); void (*reserved75)(void); mp_err (*tclBN_mp_signed_rsh) (const mp_int *a, int b, mp_int *c) MP_WUR; /* 76 */ size_t (*tclBN_mp_pack_count) (const mp_int *a, size_t nails, size_t size) MP_WUR; /* 77 */ int (*tclBN_mp_to_ubin) (const mp_int *a, unsigned char *buf, size_t maxlen, size_t *written) MP_WUR; /* 78 */ void (*reserved79)(void); int (*tclBN_mp_to_radix) (const mp_int *a, char *str, size_t maxlen, size_t *written, int radix) MP_WUR; /* 80 */ } TclTomMathStubs; extern const TclTomMathStubs *tclTomMathStubsPtr; #ifdef __cplusplus } #endif #if defined(USE_TCL_STUBS) /* * Inline function declarations: */ #define TclBN_epoch \ (tclTomMathStubsPtr->tclBN_epoch) /* 0 */ #define TclBN_revision \ (tclTomMathStubsPtr->tclBN_revision) /* 1 */ #define TclBN_mp_add \ (tclTomMathStubsPtr->tclBN_mp_add) /* 2 */ #define TclBN_mp_add_d \ (tclTomMathStubsPtr->tclBN_mp_add_d) /* 3 */ #define TclBN_mp_and \ (tclTomMathStubsPtr->tclBN_mp_and) /* 4 */ #define TclBN_mp_clamp \ (tclTomMathStubsPtr->tclBN_mp_clamp) /* 5 */ #define TclBN_mp_clear \ (tclTomMathStubsPtr->tclBN_mp_clear) /* 6 */ #define TclBN_mp_clear_multi \ (tclTomMathStubsPtr->tclBN_mp_clear_multi) /* 7 */ #define TclBN_mp_cmp \ (tclTomMathStubsPtr->tclBN_mp_cmp) /* 8 */ #define TclBN_mp_cmp_d \ (tclTomMathStubsPtr->tclBN_mp_cmp_d) /* 9 */ #define TclBN_mp_cmp_mag \ (tclTomMathStubsPtr->tclBN_mp_cmp_mag) /* 10 */ #define TclBN_mp_copy \ (tclTomMathStubsPtr->tclBN_mp_copy) /* 11 */ #define TclBN_mp_count_bits \ (tclTomMathStubsPtr->tclBN_mp_count_bits) /* 12 */ #define TclBN_mp_div \ (tclTomMathStubsPtr->tclBN_mp_div) /* 13 */ #define TclBN_mp_div_d \ (tclTomMathStubsPtr->tclBN_mp_div_d) /* 14 */ #define TclBN_mp_div_2 \ (tclTomMathStubsPtr->tclBN_mp_div_2) /* 15 */ #define TclBN_mp_div_2d \ (tclTomMathStubsPtr->tclBN_mp_div_2d) /* 16 */ /* Slot 17 is reserved */ #define TclBN_mp_exch \ (tclTomMathStubsPtr->tclBN_mp_exch) /* 18 */ #define TclBN_mp_expt_n \ (tclTomMathStubsPtr->tclBN_mp_expt_n) /* 19 */ #define TclBN_mp_grow \ (tclTomMathStubsPtr->tclBN_mp_grow) /* 20 */ #define TclBN_mp_init \ (tclTomMathStubsPtr->tclBN_mp_init) /* 21 */ #define TclBN_mp_init_copy \ (tclTomMathStubsPtr->tclBN_mp_init_copy) /* 22 */ #define TclBN_mp_init_multi \ (tclTomMathStubsPtr->tclBN_mp_init_multi) /* 23 */ #define TclBN_mp_init_set \ (tclTomMathStubsPtr->tclBN_mp_init_set) /* 24 */ #define TclBN_mp_init_size \ (tclTomMathStubsPtr->tclBN_mp_init_size) /* 25 */ #define TclBN_mp_lshd \ (tclTomMathStubsPtr->tclBN_mp_lshd) /* 26 */ #define TclBN_mp_mod \ (tclTomMathStubsPtr->tclBN_mp_mod) /* 27 */ #define TclBN_mp_mod_2d \ (tclTomMathStubsPtr->tclBN_mp_mod_2d) /* 28 */ #define TclBN_mp_mul \ (tclTomMathStubsPtr->tclBN_mp_mul) /* 29 */ #define TclBN_mp_mul_d \ (tclTomMathStubsPtr->tclBN_mp_mul_d) /* 30 */ #define TclBN_mp_mul_2 \ (tclTomMathStubsPtr->tclBN_mp_mul_2) /* 31 */ #define TclBN_mp_mul_2d \ (tclTomMathStubsPtr->tclBN_mp_mul_2d) /* 32 */ #define TclBN_mp_neg \ (tclTomMathStubsPtr->tclBN_mp_neg) /* 33 */ #define TclBN_mp_or \ (tclTomMathStubsPtr->tclBN_mp_or) /* 34 */ #define TclBN_mp_radix_size \ (tclTomMathStubsPtr->tclBN_mp_radix_size) /* 35 */ #define TclBN_mp_read_radix \ (tclTomMathStubsPtr->tclBN_mp_read_radix) /* 36 */ #define TclBN_mp_rshd \ (tclTomMathStubsPtr->tclBN_mp_rshd) /* 37 */ #define TclBN_mp_shrink \ (tclTomMathStubsPtr->tclBN_mp_shrink) /* 38 */ /* Slot 39 is reserved */ /* Slot 40 is reserved */ #define TclBN_mp_sqrt \ (tclTomMathStubsPtr->tclBN_mp_sqrt) /* 41 */ #define TclBN_mp_sub \ (tclTomMathStubsPtr->tclBN_mp_sub) /* 42 */ #define TclBN_mp_sub_d \ (tclTomMathStubsPtr->tclBN_mp_sub_d) /* 43 */ /* Slot 44 is reserved */ /* Slot 45 is reserved */ /* Slot 46 is reserved */ #define TclBN_mp_ubin_size \ (tclTomMathStubsPtr->tclBN_mp_ubin_size) /* 47 */ #define TclBN_mp_xor \ (tclTomMathStubsPtr->tclBN_mp_xor) /* 48 */ #define TclBN_mp_zero \ (tclTomMathStubsPtr->tclBN_mp_zero) /* 49 */ /* Slot 50 is reserved */ /* Slot 51 is reserved */ /* Slot 52 is reserved */ /* Slot 53 is reserved */ /* Slot 54 is reserved */ /* Slot 55 is reserved */ /* Slot 56 is reserved */ /* Slot 57 is reserved */ /* Slot 58 is reserved */ /* Slot 59 is reserved */ /* Slot 60 is reserved */ /* Slot 61 is reserved */ /* Slot 62 is reserved */ #define TclBN_mp_cnt_lsb \ (tclTomMathStubsPtr->tclBN_mp_cnt_lsb) /* 63 */ /* Slot 64 is reserved */ #define TclBN_mp_init_i64 \ (tclTomMathStubsPtr->tclBN_mp_init_i64) /* 65 */ #define TclBN_mp_init_u64 \ (tclTomMathStubsPtr->tclBN_mp_init_u64) /* 66 */ /* Slot 67 is reserved */ #define TclBN_mp_set_u64 \ (tclTomMathStubsPtr->tclBN_mp_set_u64) /* 68 */ #define TclBN_mp_get_mag_u64 \ (tclTomMathStubsPtr->tclBN_mp_get_mag_u64) /* 69 */ #define TclBN_mp_set_i64 \ (tclTomMathStubsPtr->tclBN_mp_set_i64) /* 70 */ #define TclBN_mp_unpack \ (tclTomMathStubsPtr->tclBN_mp_unpack) /* 71 */ #define TclBN_mp_pack \ (tclTomMathStubsPtr->tclBN_mp_pack) /* 72 */ /* Slot 73 is reserved */ /* Slot 74 is reserved */ /* Slot 75 is reserved */ #define TclBN_mp_signed_rsh \ (tclTomMathStubsPtr->tclBN_mp_signed_rsh) /* 76 */ #define TclBN_mp_pack_count \ (tclTomMathStubsPtr->tclBN_mp_pack_count) /* 77 */ #define TclBN_mp_to_ubin \ (tclTomMathStubsPtr->tclBN_mp_to_ubin) /* 78 */ /* Slot 79 is reserved */ #define TclBN_mp_to_radix \ (tclTomMathStubsPtr->tclBN_mp_to_radix) /* 80 */ #endif /* defined(USE_TCL_STUBS) */ /* !END!: Do not edit above this line. */ #undef mp_get_ll #define mp_get_ll(a) ((long long)mp_get_i64(a)) #undef mp_set_ll #define mp_set_ll(a,b) mp_set_i64(a,b) #undef mp_init_ll #define mp_init_ll(a,b) mp_init_i64(a,b) #undef mp_get_ull #define mp_get_ull(a) ((unsigned long long)mp_get_i64(a)) #undef mp_set_ull #define mp_set_ull(a,b) mp_set_u64(a,b) #undef mp_init_ull #define mp_init_ull(a,b) mp_init_u64(a,b) #undef mp_set #define mp_set(a,b) mp_set_i64((a),(int32_t)(b)) #define mp_set_i32(a,b) mp_set_i64((a),(int32_t)(b)) #define mp_set_l(a,b) mp_set_i64((a),(long)(b)) #define mp_set_u32(a,b) mp_set_u64((a),(uint32_t)(b)) #define mp_set_ul(a,b) mp_set_u64((a),(unsigned long)(b)) #define mp_init_i32(a,b) mp_init_i64((a),(int32_t)(b)) #define mp_init_l(a,b) mp_init_i64((a),(long)(b)) #define mp_init_u32(a,b) mp_init_u64((a),(uint32_t)(b)) #define mp_init_ul(a,b) mp_init_u64((a),(unsigned long)(b)) #undef mp_iseven #undef mp_isodd #define mp_iseven(a) (!mp_isodd(a)) #define mp_isodd(a) (((a)->used != 0) && (((a)->dp[0] & 1) != 0)) #undef mp_sqr #define mp_sqr(a,b) mp_mul(a,a,b) #undef TCL_STORAGE_CLASS #define TCL_STORAGE_CLASS DLLIMPORT #endif /* _TCLINTDECLS */ tcl9.0.1/generic/tclTomMathInt.h0000644000175000017500000000010714726623136016070 0ustar sergeisergei#include "tclInt.h" #include "tclTomMath.h" #include "tommath_class.h" tcl9.0.1/generic/tclTomMathInterface.c0000644000175000017500000000435614726623136017243 0ustar sergeisergei/* *---------------------------------------------------------------------- * * tclTomMathInterface.c -- * * This file contains procedures that are used as a 'glue' layer between * Tcl and libtommath. * * Copyright © 2005 Kevin B. Kenny. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclTomMath.h" MODULE_SCOPE const TclTomMathStubs tclTomMathStubs; /* *---------------------------------------------------------------------- * * TclTommath_Init -- * * Initializes the TclTomMath 'package', which exists as a * placeholder so that the package data can be used to hold * a stub table pointer. * * Results: * Returns a standard Tcl result. * * Side effects: * Installs the stub table for tommath. * *---------------------------------------------------------------------- */ int TclTommath_Init( Tcl_Interp *interp) /* Tcl interpreter */ { /* TIP #268: Full patchlevel instead of just major.minor */ if (Tcl_PkgProvideEx(interp, "tcl::tommath", TCL_PATCH_LEVEL, &tclTomMathStubs) != TCL_OK) { return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclBN_epoch -- * * Return the epoch number of the TclTomMath stubs table * * Results: * Returns an arbitrary integer that does not decrease with * release. Stubs tables with different epochs are incompatible. * *---------------------------------------------------------------------- */ int TclBN_epoch(void) { return TCLTOMMATH_EPOCH; } /* *---------------------------------------------------------------------- * * TclBN_revision -- * * Returns the revision level of the TclTomMath stubs table * * Results: * Returns an arbitrary integer that increases with revisions. * If a client requires a given epoch and revision, any Stubs table * with the same epoch and an equal or higher revision satisfies * the request. * *---------------------------------------------------------------------- */ int TclBN_revision(void) { return TCLTOMMATH_REVISION; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclTomMathStubLib.c0000644000175000017500000000427414726623136016706 0ustar sergeisergei/* * tclTomMathStubLib.c -- * * Stub object that will be statically linked into extensions that want * to access Tcl. * * Copyright © 1998-1999 Scriptics Corporation. * Copyright © 1998 Paul Duffin. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclTomMath.h" MODULE_SCOPE const TclTomMathStubs *tclTomMathStubsPtr; const TclTomMathStubs *tclTomMathStubsPtr = NULL; /* *---------------------------------------------------------------------- * * TclTomMathInitStubs -- * * Initializes the Stubs table for Tcl's subset of libtommath * * Results: * Returns a standard Tcl result. * * This procedure should not be called directly, but rather through * the TclTomMath_InitStubs macro, to insure that the Stubs table * matches the header files used in compilation. * *---------------------------------------------------------------------- */ MODULE_SCOPE const char * TclTomMathInitializeStubs( Tcl_Interp *interp, /* Tcl interpreter */ const char *version, /* Tcl version needed */ int epoch, /* Stubs table epoch from the header files */ int revision) /* Stubs table revision number from the * header files */ { int exact = 0; const char *packageName = "tcl::tommath"; const char *errMsg = NULL; TclTomMathStubs *stubsPtr = NULL; const char *actualVersion = tclStubsPtr->tcl_PkgRequireEx(interp, packageName, version, exact, &stubsPtr); if (actualVersion == NULL) { return NULL; } if (stubsPtr == NULL) { errMsg = "missing stub table pointer"; } else if (stubsPtr->tclBN_epoch() != epoch) { errMsg = "epoch number mismatch"; } else if (stubsPtr->tclBN_revision() != revision) { errMsg = "requires a later revision"; } else { tclTomMathStubsPtr = stubsPtr; return actualVersion; } tclStubsPtr->tcl_ResetResult(interp); tclStubsPtr->tcl_AppendResult(interp, "Error loading ", packageName, " (requested version ", version, ", actual version ", actualVersion, "): ", errMsg, NULL); return NULL; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclTrace.c0000644000175000017500000026214614726623136015112 0ustar sergeisergei/* * tclTrace.c -- * * This file contains code to handle most trace management. * * Copyright © 1987-1993 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * Copyright © 1998-2000 Scriptics Corporation. * Copyright © 2002 ActiveState Corporation. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * Structures used to hold information about variable traces: */ typedef struct { int flags; /* Operations for which Tcl command is to be * invoked. */ Tcl_Size length; /* Number of non-NUL chars. in command. */ char command[TCLFLEXARRAY]; /* Space for Tcl command to invoke. Actual * size will be as large as necessary to hold * command. This field must be the last in the * structure, so that it can be larger than 1 * byte. */ } TraceVarInfo; typedef struct { VarTrace traceInfo; TraceVarInfo traceCmdInfo; } CombinedTraceVarInfo; /* * Structure used to hold information about command traces: */ typedef struct { int flags; /* Operations for which Tcl command is to be * invoked. */ Tcl_Size length; /* Number of non-NUL chars. in command. */ Tcl_Trace stepTrace; /* Used for execution traces, when tracing * inside the given command */ Tcl_Size startLevel; /* Used for bookkeeping with step execution * traces, store the level at which the step * trace was invoked */ char *startCmd; /* Used for bookkeeping with step execution * traces, store the command name which * invoked step trace */ int curFlags; /* Trace flags for the current command */ int curCode; /* Return code for the current command */ size_t refCount; /* Used to ensure this structure is not * deleted too early. Keeps track of how many * pieces of code have a pointer to this * structure. */ char command[TCLFLEXARRAY]; /* Space for Tcl command to invoke. Actual * size will be as large as necessary to hold * command. This field must be the last in the * structure, so that it can be larger than 1 * byte. */ } TraceCommandInfo; /* * Used by command execution traces. Note that we assume in the code that * TCL_TRACE_ENTER_DURING_EXEC == 4 * TCL_TRACE_ENTER_EXEC and that * TCL_TRACE_LEAVE_DURING_EXEC == 4 * TCL_TRACE_LEAVE_EXEC. * * TCL_TRACE_ENTER_DURING_EXEC - Trace each command inside the command * currently being traced, before execution. * TCL_TRACE_LEAVE_DURING_EXEC - Trace each command inside the command * currently being traced, after execution. * TCL_TRACE_ANY_EXEC - OR'd combination of all EXEC flags. * TCL_TRACE_EXEC_IN_PROGRESS - The callback function on this trace is * currently executing. Therefore we don't let * further traces execute. * TCL_TRACE_EXEC_DIRECT - This execution trace is triggered directly * by the command being traced, not because of * an internal trace. * The flag 'TCL_TRACE_DESTROYED' may also be used in command execution traces. */ #define TCL_TRACE_ENTER_DURING_EXEC 4 #define TCL_TRACE_LEAVE_DURING_EXEC 8 #define TCL_TRACE_ANY_EXEC 15 #define TCL_TRACE_EXEC_IN_PROGRESS 0x10 #define TCL_TRACE_EXEC_DIRECT 0x20 /* * Forward declarations for functions defined in this file: */ enum traceOptionsEnum { TRACE_ADD, TRACE_INFO, TRACE_REMOVE }; typedef int (Tcl_TraceTypeObjCmd)(Tcl_Interp *interp, enum traceOptionsEnum optionIndex, Tcl_Size objc, Tcl_Obj *const objv[]); static Tcl_TraceTypeObjCmd TraceVariableObjCmd; static Tcl_TraceTypeObjCmd TraceCommandObjCmd; static Tcl_TraceTypeObjCmd TraceExecutionObjCmd; /* * Each subcommand has a number of 'types' to which it can apply. Currently * 'execution', 'command' and 'variable' are the only types supported. These * three arrays MUST be kept in sync! In the future we may provide an API to * add to the list of supported trace types. */ static const char *const traceTypeOptions[] = { "execution", "command", "variable", NULL }; static Tcl_TraceTypeObjCmd *const traceSubCmds[] = { TraceExecutionObjCmd, TraceCommandObjCmd, TraceVariableObjCmd }; /* * Declarations for local functions to this file: */ static int CallTraceFunction(Tcl_Interp *interp, Trace *tracePtr, Command *cmdPtr, const char *command, Tcl_Size numChars, Tcl_Size objc, Tcl_Obj *const objv[]); static char * TraceVarProc(void *clientData, Tcl_Interp *interp, const char *name1, const char *name2, int flags); static void TraceCommandProc(void *clientData, Tcl_Interp *interp, const char *oldName, const char *newName, int flags); static Tcl_CmdObjTraceProc2 TraceExecutionProc; static int StringTraceProc(void *clientData, Tcl_Interp *interp, Tcl_Size level, const char *command, Tcl_Command commandInfo, Tcl_Size objc, Tcl_Obj *const objv[]); static void StringTraceDeleteProc(void *clientData); static void DisposeTraceResult(int flags, char *result); static int TraceVarEx(Tcl_Interp *interp, const char *part1, const char *part2, VarTrace *tracePtr); /* * The following structure holds the client data for string-based * trace procs */ typedef struct { void *clientData; /* Client data from Tcl_CreateTrace */ Tcl_CmdTraceProc *proc; /* Trace function from Tcl_CreateTrace */ } StringTraceData; /* * Convenience macros for iterating over the list of traces. Note that each of * these *must* be treated as a command, and *must* have a block following it. */ #define FOREACH_VAR_TRACE(interp, name, clientData) \ (clientData) = NULL; \ while (((clientData) = Tcl_VarTraceInfo2((interp), (name), NULL, \ 0, TraceVarProc, (clientData))) != NULL) #define FOREACH_COMMAND_TRACE(interp, name, clientData) \ (clientData) = NULL; \ while (((clientData) = Tcl_CommandTraceInfo((interp), (name), 0, \ TraceCommandProc, (clientData))) != NULL) /* *---------------------------------------------------------------------- * * Tcl_TraceObjCmd -- * * This function is invoked to process the "trace" Tcl command. See the * user documentation for details on what it does. * * Standard syntax as of Tcl 8.4 is: * trace {add|info|remove} {command|variable} name ops cmd * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. *---------------------------------------------------------------------- */ int Tcl_TraceObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { /* Main sub commands to 'trace' */ static const char *const traceOptions[] = { "add", "info", "remove", NULL }; enum traceOptionsEnum optionIndex; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], traceOptions, "option", 0, &optionIndex) != TCL_OK) { return TCL_ERROR; } switch (optionIndex) { case TRACE_ADD: case TRACE_REMOVE: { /* * All sub commands of trace add/remove must take at least one more * argument. Beyond that we let the subcommand itself control the * argument structure. */ int typeIndex; if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "type ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[2], traceTypeOptions, "option", 0, &typeIndex) != TCL_OK) { return TCL_ERROR; } return traceSubCmds[typeIndex](interp, optionIndex, objc, objv); } case TRACE_INFO: { /* * All sub commands of trace info must take exactly two more arguments * which name the type of thing being traced and the name of the thing * being traced. */ int typeIndex; if (objc < 3) { /* * Delegate other complaints to the type-specific code which can * give a better error message. */ Tcl_WrongNumArgs(interp, 2, objv, "type name"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[2], traceTypeOptions, "option", 0, &typeIndex) != TCL_OK) { return TCL_ERROR; } return traceSubCmds[typeIndex](interp, optionIndex, objc, objv); break; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TraceExecutionObjCmd -- * * Helper function for Tcl_TraceObjCmd; implements the [trace * {add|remove|info} execution ...] subcommands. See the user * documentation for details on what these do. * * Results: * Standard Tcl result. * * Side effects: * Depends on the operation (add, remove, or info) being performed; may * add or remove command traces on a command. * *---------------------------------------------------------------------- */ static int TraceExecutionObjCmd( Tcl_Interp *interp, /* Current interpreter. */ enum traceOptionsEnum optionIndex, /* Add, info or remove */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *name, *command; Tcl_Size length; static const char *const opStrings[] = { "enter", "leave", "enterstep", "leavestep", NULL }; enum operations { TRACE_EXEC_ENTER, TRACE_EXEC_LEAVE, TRACE_EXEC_ENTER_STEP, TRACE_EXEC_LEAVE_STEP } index; switch (optionIndex) { case TRACE_ADD: case TRACE_REMOVE: { int flags = 0, result; Tcl_Size i, listLen; Tcl_Obj **elemPtrs; if (objc != 6) { Tcl_WrongNumArgs(interp, 3, objv, "name opList command"); return TCL_ERROR; } /* * Make sure the ops argument is a list object; get its length and a * pointer to its array of element pointers. */ result = TclListObjLength(interp, objv[4], &listLen); if (result != TCL_OK) { return result; } if (listLen == 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad operation list \"\": must be one or more of" " enter, leave, enterstep, or leavestep", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "TRACE", "NOOPS", (char *)NULL); return TCL_ERROR; } result = TclListObjGetElements(interp, objv[4], &listLen, &elemPtrs); if (result != TCL_OK) { return result; } for (i = 0; i < listLen; i++) { if (Tcl_GetIndexFromObj(interp, elemPtrs[i], opStrings, "operation", TCL_EXACT, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case TRACE_EXEC_ENTER: flags |= TCL_TRACE_ENTER_EXEC; break; case TRACE_EXEC_LEAVE: flags |= TCL_TRACE_LEAVE_EXEC; break; case TRACE_EXEC_ENTER_STEP: flags |= TCL_TRACE_ENTER_DURING_EXEC; break; case TRACE_EXEC_LEAVE_STEP: flags |= TCL_TRACE_LEAVE_DURING_EXEC; break; } } command = TclGetStringFromObj(objv[5], &length); if (optionIndex == TRACE_ADD) { TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)Tcl_Alloc( offsetof(TraceCommandInfo, command) + 1 + length); tcmdPtr->flags = flags; tcmdPtr->stepTrace = NULL; tcmdPtr->startLevel = 0; tcmdPtr->startCmd = NULL; tcmdPtr->length = length; tcmdPtr->refCount = 1; flags |= TCL_TRACE_DELETE; if (flags & (TCL_TRACE_ENTER_DURING_EXEC | TCL_TRACE_LEAVE_DURING_EXEC)) { flags |= (TCL_TRACE_ENTER_EXEC | TCL_TRACE_LEAVE_EXEC); } memcpy(tcmdPtr->command, command, length+1); name = TclGetString(objv[3]); if (Tcl_TraceCommand(interp, name, flags, TraceCommandProc, tcmdPtr) != TCL_OK) { Tcl_Free(tcmdPtr); return TCL_ERROR; } } else { /* * Search through all of our traces on this command to see if * there's one with the given command. If so, then delete the * first one that matches. */ void *clientData; /* * First ensure the name given is valid. */ name = TclGetString(objv[3]); if (Tcl_FindCommand(interp,name,NULL,TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } FOREACH_COMMAND_TRACE(interp, name, clientData) { TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData; /* * In checking the 'flags' field we must remove any extraneous * flags which may have been temporarily added by various * pieces of the trace mechanism. */ if ((tcmdPtr->length == length) && ((tcmdPtr->flags & (TCL_TRACE_ANY_EXEC | TCL_TRACE_RENAME | TCL_TRACE_DELETE)) == flags) && (strncmp(command, tcmdPtr->command, length) == 0)) { flags |= TCL_TRACE_DELETE; if (flags & (TCL_TRACE_ENTER_DURING_EXEC | TCL_TRACE_LEAVE_DURING_EXEC)) { flags |= (TCL_TRACE_ENTER_EXEC | TCL_TRACE_LEAVE_EXEC); } Tcl_UntraceCommand(interp, name, flags, TraceCommandProc, clientData); if (tcmdPtr->stepTrace != NULL) { /* * We need to remove the interpreter-wide trace which * we created to allow 'step' traces. */ Tcl_DeleteTrace(interp, tcmdPtr->stepTrace); tcmdPtr->stepTrace = NULL; Tcl_Free(tcmdPtr->startCmd); } if (tcmdPtr->flags & TCL_TRACE_EXEC_IN_PROGRESS) { /* * Postpone deletion. */ tcmdPtr->flags = 0; } if (tcmdPtr->refCount-- <= 1) { Tcl_Free(tcmdPtr); } break; } } } break; } case TRACE_INFO: { void *clientData; Tcl_Obj *resultListPtr; if (objc != 4) { Tcl_WrongNumArgs(interp, 3, objv, "name"); return TCL_ERROR; } name = TclGetString(objv[3]); /* * First ensure the name given is valid. */ if (Tcl_FindCommand(interp, name, NULL, TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } resultListPtr = Tcl_NewListObj(0, NULL); FOREACH_COMMAND_TRACE(interp, name, clientData) { Tcl_Size numOps = 0; Tcl_Obj *opObj, *eachTraceObjPtr, *elemObjPtr; TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData; /* * Build a list with the ops list as the first obj element and the * tcmdPtr->command string as the second obj element. Append this * list (as an element) to the end of the result object list. */ elemObjPtr = Tcl_NewListObj(0, NULL); Tcl_IncrRefCount(elemObjPtr); if (tcmdPtr->flags & TCL_TRACE_ENTER_EXEC) { TclNewLiteralStringObj(opObj, "enter"); Tcl_ListObjAppendElement(NULL, elemObjPtr, opObj); } if (tcmdPtr->flags & TCL_TRACE_LEAVE_EXEC) { TclNewLiteralStringObj(opObj, "leave"); Tcl_ListObjAppendElement(NULL, elemObjPtr, opObj); } if (tcmdPtr->flags & TCL_TRACE_ENTER_DURING_EXEC) { TclNewLiteralStringObj(opObj, "enterstep"); Tcl_ListObjAppendElement(NULL, elemObjPtr, opObj); } if (tcmdPtr->flags & TCL_TRACE_LEAVE_DURING_EXEC) { TclNewLiteralStringObj(opObj, "leavestep"); Tcl_ListObjAppendElement(NULL, elemObjPtr, opObj); } TclListObjLength(NULL, elemObjPtr, &numOps); if (0 == numOps) { Tcl_DecrRefCount(elemObjPtr); continue; } eachTraceObjPtr = Tcl_NewListObj(0, NULL); Tcl_ListObjAppendElement(NULL, eachTraceObjPtr, elemObjPtr); Tcl_DecrRefCount(elemObjPtr); elemObjPtr = NULL; Tcl_ListObjAppendElement(NULL, eachTraceObjPtr, Tcl_NewStringObj(tcmdPtr->command, -1)); Tcl_ListObjAppendElement(interp, resultListPtr, eachTraceObjPtr); } Tcl_SetObjResult(interp, resultListPtr); break; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TraceCommandObjCmd -- * * Helper function for Tcl_TraceObjCmd; implements the [trace * {add|info|remove} command ...] subcommands. See the user documentation * for details on what these do. * * Results: * Standard Tcl result. * * Side effects: * Depends on the operation (add, remove, or info) being performed; may * add or remove command traces on a command. * *---------------------------------------------------------------------- */ static int TraceCommandObjCmd( Tcl_Interp *interp, /* Current interpreter. */ enum traceOptionsEnum optionIndex, /* Add, info or remove */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *name, *command; Tcl_Size length; static const char *const opStrings[] = { "delete", "rename", NULL }; enum operations { TRACE_CMD_DELETE, TRACE_CMD_RENAME } index; switch (optionIndex) { case TRACE_ADD: case TRACE_REMOVE: { int flags = 0, result; Tcl_Size i, listLen; Tcl_Obj **elemPtrs; if (objc != 6) { Tcl_WrongNumArgs(interp, 3, objv, "name opList command"); return TCL_ERROR; } /* * Make sure the ops argument is a list object; get its length and a * pointer to its array of element pointers. */ result = TclListObjLength(interp, objv[4], &listLen); if (result != TCL_OK) { return result; } if (listLen == 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad operation list \"\": must be one or more of" " delete or rename", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "TRACE", "NOOPS", (char *)NULL); return TCL_ERROR; } result = TclListObjGetElements(interp, objv[4], &listLen, &elemPtrs); if (result != TCL_OK) { return result; } for (i = 0; i < listLen; i++) { if (Tcl_GetIndexFromObj(interp, elemPtrs[i], opStrings, "operation", TCL_EXACT, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case TRACE_CMD_RENAME: flags |= TCL_TRACE_RENAME; break; case TRACE_CMD_DELETE: flags |= TCL_TRACE_DELETE; break; } } command = TclGetStringFromObj(objv[5], &length); if (optionIndex == TRACE_ADD) { TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)Tcl_Alloc( offsetof(TraceCommandInfo, command) + 1 + length); tcmdPtr->flags = flags; tcmdPtr->stepTrace = NULL; tcmdPtr->startLevel = 0; tcmdPtr->startCmd = NULL; tcmdPtr->length = length; tcmdPtr->refCount = 1; flags |= TCL_TRACE_DELETE; memcpy(tcmdPtr->command, command, length+1); name = TclGetString(objv[3]); if (Tcl_TraceCommand(interp, name, flags, TraceCommandProc, tcmdPtr) != TCL_OK) { Tcl_Free(tcmdPtr); return TCL_ERROR; } } else { /* * Search through all of our traces on this command to see if * there's one with the given command. If so, then delete the * first one that matches. */ void *clientData; /* * First ensure the name given is valid. */ name = TclGetString(objv[3]); if (Tcl_FindCommand(interp,name,NULL,TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } FOREACH_COMMAND_TRACE(interp, name, clientData) { TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData; if ((tcmdPtr->length == length) && (tcmdPtr->flags == flags) && (strncmp(command, tcmdPtr->command, length) == 0)) { Tcl_UntraceCommand(interp, name, flags | TCL_TRACE_DELETE, TraceCommandProc, clientData); tcmdPtr->flags |= TCL_TRACE_DESTROYED; if (tcmdPtr->refCount-- <= 1) { Tcl_Free(tcmdPtr); } break; } } } break; } case TRACE_INFO: { void *clientData; Tcl_Obj *resultListPtr; if (objc != 4) { Tcl_WrongNumArgs(interp, 3, objv, "name"); return TCL_ERROR; } /* * First ensure the name given is valid. */ name = TclGetString(objv[3]); if (Tcl_FindCommand(interp, name, NULL, TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } resultListPtr = Tcl_NewListObj(0, NULL); FOREACH_COMMAND_TRACE(interp, name, clientData) { Tcl_Size numOps = 0; Tcl_Obj *opObj, *eachTraceObjPtr, *elemObjPtr; TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData; /* * Build a list with the ops list as the first obj element and the * tcmdPtr->command string as the second obj element. Append this * list (as an element) to the end of the result object list. */ elemObjPtr = Tcl_NewListObj(0, NULL); Tcl_IncrRefCount(elemObjPtr); if (tcmdPtr->flags & TCL_TRACE_RENAME) { TclNewLiteralStringObj(opObj, "rename"); Tcl_ListObjAppendElement(NULL, elemObjPtr, opObj); } if (tcmdPtr->flags & TCL_TRACE_DELETE) { TclNewLiteralStringObj(opObj, "delete"); Tcl_ListObjAppendElement(NULL, elemObjPtr, opObj); } TclListObjLength(NULL, elemObjPtr, &numOps); if (0 == numOps) { Tcl_DecrRefCount(elemObjPtr); continue; } eachTraceObjPtr = Tcl_NewListObj(0, NULL); Tcl_ListObjAppendElement(NULL, eachTraceObjPtr, elemObjPtr); Tcl_DecrRefCount(elemObjPtr); elemObjPtr = Tcl_NewStringObj(tcmdPtr->command, -1); Tcl_ListObjAppendElement(NULL, eachTraceObjPtr, elemObjPtr); Tcl_ListObjAppendElement(interp, resultListPtr, eachTraceObjPtr); } Tcl_SetObjResult(interp, resultListPtr); break; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TraceVariableObjCmd -- * * Helper function for Tcl_TraceObjCmd; implements the [trace * {add|info|remove} variable ...] subcommands. See the user * documentation for details on what these do. * * Results: * Standard Tcl result. * * Side effects: * Depends on the operation (add, remove, or info) being performed; may * add or remove variable traces on a variable. * *---------------------------------------------------------------------- */ static int TraceVariableObjCmd( Tcl_Interp *interp, /* Current interpreter. */ enum traceOptionsEnum optionIndex, /* Add, info or remove */ Tcl_Size objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *name, *command; Tcl_Size length; void *clientData; static const char *const opStrings[] = { "array", "read", "unset", "write", NULL }; enum operations { TRACE_VAR_ARRAY, TRACE_VAR_READ, TRACE_VAR_UNSET, TRACE_VAR_WRITE } index; switch (optionIndex) { case TRACE_ADD: case TRACE_REMOVE: { int flags = 0, result; Tcl_Size i, listLen; Tcl_Obj **elemPtrs; if (objc != 6) { Tcl_WrongNumArgs(interp, 3, objv, "name opList command"); return TCL_ERROR; } /* * Make sure the ops argument is a list object; get its length and a * pointer to its array of element pointers. */ result = TclListObjLength(interp, objv[4], &listLen); if (result != TCL_OK) { return result; } if (listLen == 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "bad operation list \"\": must be one or more of" " array, read, unset, or write", -1)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "TRACE", "NOOPS", (char *)NULL); return TCL_ERROR; } result = TclListObjGetElements(interp, objv[4], &listLen, &elemPtrs); if (result != TCL_OK) { return result; } for (i = 0; i < listLen ; i++) { if (Tcl_GetIndexFromObj(interp, elemPtrs[i], opStrings, "operation", TCL_EXACT, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case TRACE_VAR_ARRAY: flags |= TCL_TRACE_ARRAY; break; case TRACE_VAR_READ: flags |= TCL_TRACE_READS; break; case TRACE_VAR_UNSET: flags |= TCL_TRACE_UNSETS; break; case TRACE_VAR_WRITE: flags |= TCL_TRACE_WRITES; break; } } command = TclGetStringFromObj(objv[5], &length); if (optionIndex == TRACE_ADD) { CombinedTraceVarInfo *ctvarPtr = (CombinedTraceVarInfo *)Tcl_Alloc( offsetof(CombinedTraceVarInfo, traceCmdInfo.command) + 1 + length); ctvarPtr->traceCmdInfo.flags = flags; ctvarPtr->traceCmdInfo.length = length; flags |= TCL_TRACE_UNSETS | TCL_TRACE_RESULT_OBJECT; memcpy(ctvarPtr->traceCmdInfo.command, command, length+1); ctvarPtr->traceInfo.traceProc = TraceVarProc; ctvarPtr->traceInfo.clientData = &ctvarPtr->traceCmdInfo; ctvarPtr->traceInfo.flags = flags; name = TclGetString(objv[3]); if (TraceVarEx(interp, name, NULL, (VarTrace *) ctvarPtr) != TCL_OK) { Tcl_Free(ctvarPtr); return TCL_ERROR; } } else { /* * Search through all of our traces on this variable to see if * there's one with the given command. If so, then delete the * first one that matches. */ name = TclGetString(objv[3]); FOREACH_VAR_TRACE(interp, name, clientData) { TraceVarInfo *tvarPtr = (TraceVarInfo *)clientData; if ((tvarPtr->length == length) && ((tvarPtr->flags)==flags) && (strncmp(command, tvarPtr->command, length) == 0)) { Tcl_UntraceVar2(interp, name, NULL, flags | TCL_TRACE_UNSETS | TCL_TRACE_RESULT_OBJECT, TraceVarProc, clientData); break; } } } break; } case TRACE_INFO: { Tcl_Obj *resultListPtr; if (objc != 4) { Tcl_WrongNumArgs(interp, 3, objv, "name"); return TCL_ERROR; } TclNewObj(resultListPtr); name = TclGetString(objv[3]); FOREACH_VAR_TRACE(interp, name, clientData) { Tcl_Obj *opObjPtr, *eachTraceObjPtr, *elemObjPtr; TraceVarInfo *tvarPtr = (TraceVarInfo *)clientData; /* * Build a list with the ops list as the first obj element and the * tcmdPtr->command string as the second obj element. Append this * list (as an element) to the end of the result object list. */ elemObjPtr = Tcl_NewListObj(0, NULL); if (tvarPtr->flags & TCL_TRACE_ARRAY) { TclNewLiteralStringObj(opObjPtr, "array"); Tcl_ListObjAppendElement(NULL, elemObjPtr, opObjPtr); } if (tvarPtr->flags & TCL_TRACE_READS) { TclNewLiteralStringObj(opObjPtr, "read"); Tcl_ListObjAppendElement(NULL, elemObjPtr, opObjPtr); } if (tvarPtr->flags & TCL_TRACE_WRITES) { TclNewLiteralStringObj(opObjPtr, "write"); Tcl_ListObjAppendElement(NULL, elemObjPtr, opObjPtr); } if (tvarPtr->flags & TCL_TRACE_UNSETS) { TclNewLiteralStringObj(opObjPtr, "unset"); Tcl_ListObjAppendElement(NULL, elemObjPtr, opObjPtr); } eachTraceObjPtr = Tcl_NewListObj(0, NULL); Tcl_ListObjAppendElement(NULL, eachTraceObjPtr, elemObjPtr); elemObjPtr = Tcl_NewStringObj(tvarPtr->command, -1); Tcl_ListObjAppendElement(NULL, eachTraceObjPtr, elemObjPtr); Tcl_ListObjAppendElement(interp, resultListPtr, eachTraceObjPtr); } Tcl_SetObjResult(interp, resultListPtr); break; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_CommandTraceInfo -- * * Return the clientData value associated with a trace on a command. * This function can also be used to step through all of the traces on a * particular command that have the same trace function. * * Results: * The return value is the clientData value associated with a trace on * the given command. Information will only be returned for a trace with * proc as trace function. If the clientData argument is NULL then the * first such trace is returned; otherwise, the next relevant one after * the one given by clientData will be returned. If the command doesn't * exist then an error message is left in the interpreter and NULL is * returned. Also, if there are no (more) traces for the given command, * NULL is returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ void * Tcl_CommandTraceInfo( Tcl_Interp *interp, /* Interpreter containing command. */ const char *cmdName, /* Name of command. */ TCL_UNUSED(int) /*flags*/, Tcl_CommandTraceProc *proc, /* Function assocated with trace. */ void *prevClientData) /* If non-NULL, gives last value returned by * this function, so this call will return the * next trace after that one. If NULL, this * call will return the first trace. */ { Command *cmdPtr; CommandTrace *tracePtr; cmdPtr = (Command *) Tcl_FindCommand(interp, cmdName, NULL, TCL_LEAVE_ERR_MSG); if (cmdPtr == NULL) { return NULL; } /* * Find the relevant trace, if any, and return its clientData. */ tracePtr = cmdPtr->tracePtr; if (prevClientData != NULL) { for (; tracePtr!=NULL ; tracePtr=tracePtr->nextPtr) { if ((tracePtr->clientData == prevClientData) && (tracePtr->traceProc == proc)) { tracePtr = tracePtr->nextPtr; break; } } } for (; tracePtr!=NULL ; tracePtr=tracePtr->nextPtr) { if (tracePtr->traceProc == proc) { return tracePtr->clientData; } } return NULL; } /* *---------------------------------------------------------------------- * * Tcl_TraceCommand -- * * Arrange for rename/deletes to a command to cause a function to be * invoked, which can monitor the operations. * * Also optionally arrange for execution of that command to cause a * function to be invoked. * * Results: * A standard Tcl return value. * * Side effects: * A trace is set up on the command given by cmdName, such that future * changes to the command will be mediated by proc. See the manual * entry for complete details on the calling sequence for proc. * *---------------------------------------------------------------------- */ int Tcl_TraceCommand( Tcl_Interp *interp, /* Interpreter in which command is to be * traced. */ const char *cmdName, /* Name of command. */ int flags, /* OR-ed collection of bits, including any of * TCL_TRACE_RENAME, TCL_TRACE_DELETE, and any * of the TRACE_*_EXEC flags */ Tcl_CommandTraceProc *proc, /* Function to call when specified ops are * invoked upon cmdName. */ void *clientData) /* Arbitrary argument to pass to proc. */ { Command *cmdPtr; CommandTrace *tracePtr; cmdPtr = (Command *) Tcl_FindCommand(interp, cmdName, NULL, TCL_LEAVE_ERR_MSG); if (cmdPtr == NULL) { return TCL_ERROR; } /* * Set up trace information. */ tracePtr = (CommandTrace *)Tcl_Alloc(sizeof(CommandTrace)); tracePtr->traceProc = proc; tracePtr->clientData = clientData; tracePtr->flags = flags & (TCL_TRACE_RENAME | TCL_TRACE_DELETE | TCL_TRACE_ANY_EXEC); tracePtr->nextPtr = cmdPtr->tracePtr; tracePtr->refCount = 1; cmdPtr->tracePtr = tracePtr; if (tracePtr->flags & TCL_TRACE_ANY_EXEC) { /* * Bug 3484621: up the interp's epoch if this is a BC'ed command */ if ((cmdPtr->compileProc != NULL) && !(cmdPtr->flags & CMD_HAS_EXEC_TRACES)){ Interp *iPtr = (Interp *) interp; iPtr->compileEpoch++; } cmdPtr->flags |= CMD_HAS_EXEC_TRACES; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_UntraceCommand -- * * Remove a previously-created trace for a command. * * Results: * None. * * Side effects: * If there exists a trace for the command given by cmdName with the * given flags, proc, and clientData, then that trace is removed. * *---------------------------------------------------------------------- */ void Tcl_UntraceCommand( Tcl_Interp *interp, /* Interpreter containing command. */ const char *cmdName, /* Name of command. */ int flags, /* OR-ed collection of bits, including any of * TCL_TRACE_RENAME, TCL_TRACE_DELETE, and any * of the TRACE_*_EXEC flags */ Tcl_CommandTraceProc *proc, /* Function assocated with trace. */ void *clientData) /* Arbitrary argument to pass to proc. */ { CommandTrace *tracePtr; CommandTrace *prevPtr; Command *cmdPtr; Interp *iPtr = (Interp *)interp; ActiveCommandTrace *activePtr; int hasExecTraces = 0; cmdPtr = (Command *) Tcl_FindCommand(interp, cmdName, NULL, TCL_LEAVE_ERR_MSG); if (cmdPtr == NULL) { return; } flags &= (TCL_TRACE_RENAME | TCL_TRACE_DELETE | TCL_TRACE_ANY_EXEC); for (tracePtr = cmdPtr->tracePtr, prevPtr = NULL; ; prevPtr = tracePtr, tracePtr = tracePtr->nextPtr) { if (tracePtr == NULL) { return; } if ((tracePtr->traceProc == proc) && ((tracePtr->flags & (TCL_TRACE_RENAME | TCL_TRACE_DELETE | TCL_TRACE_ANY_EXEC)) == flags) && (tracePtr->clientData == clientData)) { if (tracePtr->flags & TCL_TRACE_ANY_EXEC) { hasExecTraces = 1; } break; } } /* * The code below makes it possible to delete traces while traces are * active: it makes sure that the deleted trace won't be processed by * CallCommandTraces. */ for (activePtr = iPtr->activeCmdTracePtr; activePtr != NULL; activePtr = activePtr->nextPtr) { if (activePtr->nextTracePtr == tracePtr) { if (activePtr->reverseScan) { activePtr->nextTracePtr = prevPtr; } else { activePtr->nextTracePtr = tracePtr->nextPtr; } } } if (prevPtr == NULL) { cmdPtr->tracePtr = tracePtr->nextPtr; } else { prevPtr->nextPtr = tracePtr->nextPtr; } tracePtr->flags = 0; if (tracePtr->refCount-- <= 1) { Tcl_Free(tracePtr); } if (hasExecTraces) { for (tracePtr = cmdPtr->tracePtr, prevPtr = NULL; tracePtr != NULL ; prevPtr = tracePtr, tracePtr = tracePtr->nextPtr) { if (tracePtr->flags & TCL_TRACE_ANY_EXEC) { return; } } /* * None of the remaining traces on this command are execution traces. * We therefore remove this flag: */ cmdPtr->flags &= ~CMD_HAS_EXEC_TRACES; /* * Bug 3484621: up the interp's epoch if this is a BC'ed command */ if (cmdPtr->compileProc != NULL) { iPtr->compileEpoch++; } } } /* *---------------------------------------------------------------------- * * TraceCommandProc -- * * This function is called to handle command changes that have been * traced using the "trace" command, when using the 'rename' or 'delete' * options. * * Results: * None. * * Side effects: * Depends on the command associated with the trace. * *---------------------------------------------------------------------- */ static void TraceCommandProc( void *clientData, /* Information about the command trace. */ Tcl_Interp *interp, /* Interpreter containing command. */ const char *oldName, /* Name of command being changed. */ const char *newName, /* New name of command. Empty string or NULL * means command is being deleted (renamed to * ""). */ int flags) /* OR-ed bits giving operation and other * information. */ { TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData; int code; Tcl_DString cmd; tcmdPtr->refCount++; if ((tcmdPtr->flags & flags) && !Tcl_InterpDeleted(interp) && !Tcl_LimitExceeded(interp)) { /* * Generate a command to execute by appending list elements for the * old and new command name and the operation. */ Tcl_DStringInit(&cmd); Tcl_DStringAppend(&cmd, tcmdPtr->command, tcmdPtr->length); Tcl_DStringAppendElement(&cmd, oldName); Tcl_DStringAppendElement(&cmd, (newName ? newName : "")); if (flags & TCL_TRACE_RENAME) { TclDStringAppendLiteral(&cmd, " rename"); } else if (flags & TCL_TRACE_DELETE) { TclDStringAppendLiteral(&cmd, " delete"); } /* * Execute the command. We discard any object result the command * returns. * * Add the TCL_TRACE_DESTROYED flag to tcmdPtr to indicate to other * areas that this will be destroyed by us, otherwise a double-free * might occur depending on what the eval does. */ if (flags & TCL_TRACE_DESTROYED) { tcmdPtr->flags |= TCL_TRACE_DESTROYED; } code = Tcl_EvalEx(interp, Tcl_DStringValue(&cmd), Tcl_DStringLength(&cmd), 0); if (code != TCL_OK) { /* We ignore errors in these traced commands */ /*** QUESTION: Use Tcl_BackgroundException(interp, code); instead? ***/ } Tcl_DStringFree(&cmd); } /* * We delete when the trace was destroyed or if this is a delete trace, * because command deletes are unconditional, so the trace must go away. */ if (flags & (TCL_TRACE_DESTROYED | TCL_TRACE_DELETE)) { int untraceFlags = tcmdPtr->flags; Tcl_InterpState state; if (tcmdPtr->stepTrace != NULL) { Tcl_DeleteTrace(interp, tcmdPtr->stepTrace); tcmdPtr->stepTrace = NULL; Tcl_Free(tcmdPtr->startCmd); } if (tcmdPtr->flags & TCL_TRACE_EXEC_IN_PROGRESS) { /* * Postpone deletion, until exec trace returns. */ tcmdPtr->flags = 0; } /* * We need to construct the same flags for Tcl_UntraceCommand as were * passed to Tcl_TraceCommand. Reproduce the processing of [trace add * execution/command]. Be careful to keep this code in sync with that. */ if (untraceFlags & TCL_TRACE_ANY_EXEC) { untraceFlags |= TCL_TRACE_DELETE; if (untraceFlags & (TCL_TRACE_ENTER_DURING_EXEC | TCL_TRACE_LEAVE_DURING_EXEC)) { untraceFlags |= (TCL_TRACE_ENTER_EXEC | TCL_TRACE_LEAVE_EXEC); } } else if (untraceFlags & TCL_TRACE_RENAME) { untraceFlags |= TCL_TRACE_DELETE; } /* * Remove the trace since TCL_TRACE_DESTROYED tells us to, or the * command we're tracing has just gone away. Then decrement the * clientData refCount that was set up by trace creation. * * Note that we save the (return) state of the interpreter to prevent * bizarre error messages. */ state = Tcl_SaveInterpState(interp, TCL_OK); Tcl_UntraceCommand(interp, oldName, untraceFlags, TraceCommandProc, clientData); Tcl_RestoreInterpState(interp, state); tcmdPtr->refCount--; } if (tcmdPtr->refCount-- <= 1) { Tcl_Free(tcmdPtr); } } /* *---------------------------------------------------------------------- * * TclCheckExecutionTraces -- * * Checks on all current command execution traces, and invokes functions * which have been registered. This function can be used by other code * which performs execution to unify the tracing system, so that * execution traces will function for that other code. * * For instance extensions like [incr Tcl] which use their own execution * technique can make use of Tcl's tracing. * * This function is called by 'TclEvalObjvInternal' * * Results: * The return value is a standard Tcl completion code such as TCL_OK or * TCL_ERROR, etc. * * Side effects: * Those side effects made by any trace functions called. * *---------------------------------------------------------------------- */ int TclCheckExecutionTraces( Tcl_Interp *interp, /* The current interpreter. */ const char *command, /* Pointer to beginning of the current command * string. */ TCL_UNUSED(Tcl_Size) /*numChars*/, Command *cmdPtr, /* Points to command's Command struct. */ int code, /* The current result code. */ int traceFlags, /* Current tracing situation. */ Tcl_Size objc, /* Number of arguments for the command. */ Tcl_Obj *const objv[]) /* Pointers to Tcl_Obj of each argument. */ { Interp *iPtr = (Interp *) interp; CommandTrace *tracePtr, *lastTracePtr; ActiveCommandTrace active; Tcl_Size curLevel; int traceCode = TCL_OK; Tcl_InterpState state = NULL; if (cmdPtr->tracePtr == NULL) { return traceCode; } curLevel = iPtr->varFramePtr->level; active.nextPtr = iPtr->activeCmdTracePtr; iPtr->activeCmdTracePtr = &active; active.cmdPtr = cmdPtr; lastTracePtr = NULL; for (tracePtr = cmdPtr->tracePtr; (traceCode == TCL_OK) && (tracePtr != NULL); tracePtr = active.nextTracePtr) { if (traceFlags & TCL_TRACE_LEAVE_EXEC) { /* * Execute the trace command in order of creation for "leave". */ active.reverseScan = 1; active.nextTracePtr = NULL; tracePtr = cmdPtr->tracePtr; while (tracePtr->nextPtr != lastTracePtr) { active.nextTracePtr = tracePtr; tracePtr = tracePtr->nextPtr; } } else { active.reverseScan = 0; active.nextTracePtr = tracePtr->nextPtr; } if (tracePtr->traceProc == TraceCommandProc) { TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)tracePtr->clientData; if (tcmdPtr->flags != 0) { tcmdPtr->curFlags = traceFlags | TCL_TRACE_EXEC_DIRECT; tcmdPtr->curCode = code; tcmdPtr->refCount++; if (state == NULL) { state = Tcl_SaveInterpState(interp, code); } traceCode = TraceExecutionProc(tcmdPtr, interp, curLevel, command, (Tcl_Command) cmdPtr, objc, objv); if (tcmdPtr->refCount-- <= 1) { Tcl_Free(tcmdPtr); } } } if (active.nextTracePtr) { lastTracePtr = active.nextTracePtr->nextPtr; } } iPtr->activeCmdTracePtr = active.nextPtr; if (state) { if (traceCode == TCL_OK) { (void) Tcl_RestoreInterpState(interp, state); } else { Tcl_DiscardInterpState(state); } } return traceCode; } /* *---------------------------------------------------------------------- * * TclCheckInterpTraces -- * * Checks on all current traces, and invokes functions which have been * registered. This function can be used by other code which performs * execution to unify the tracing system. For instance extensions like * [incr Tcl] which use their own execution technique can make use of * Tcl's tracing. * * This function is called by 'TclEvalObjvInternal' * * Results: * The return value is a standard Tcl completion code such as TCL_OK or * TCL_ERROR, etc. * * Side effects: * Those side effects made by any trace functions called. * *---------------------------------------------------------------------- */ int TclCheckInterpTraces( Tcl_Interp *interp, /* The current interpreter. */ const char *command, /* Pointer to beginning of the current command * string. */ Tcl_Size numChars, /* The number of characters in 'command' which * are part of the command string. */ Command *cmdPtr, /* Points to command's Command struct. */ int code, /* The current result code. */ int traceFlags, /* Current tracing situation. */ Tcl_Size objc, /* Number of arguments for the command. */ Tcl_Obj *const objv[]) /* Pointers to Tcl_Obj of each argument. */ { Interp *iPtr = (Interp *) interp; Trace *tracePtr, *lastTracePtr; ActiveInterpTrace active; Tcl_Size curLevel; int traceCode = TCL_OK; Tcl_InterpState state = NULL; if ((iPtr->tracePtr == NULL) || (iPtr->flags & INTERP_TRACE_IN_PROGRESS)) { return(traceCode); } curLevel = iPtr->numLevels; active.nextPtr = iPtr->activeInterpTracePtr; iPtr->activeInterpTracePtr = &active; lastTracePtr = NULL; for (tracePtr = iPtr->tracePtr; (traceCode == TCL_OK) && (tracePtr != NULL); tracePtr = active.nextTracePtr) { if (traceFlags & TCL_TRACE_ENTER_EXEC) { /* * Execute the trace command in reverse order of creation for * "enterstep" operation. The order is changed for "enterstep" * instead of for "leavestep" as was done in * TclCheckExecutionTraces because for step traces, * Tcl_CreateObjTrace creates one more linked list of traces which * results in one more reversal of trace invocation. */ active.reverseScan = 1; active.nextTracePtr = NULL; tracePtr = iPtr->tracePtr; while (tracePtr->nextPtr != lastTracePtr) { active.nextTracePtr = tracePtr; tracePtr = tracePtr->nextPtr; } if (active.nextTracePtr) { lastTracePtr = active.nextTracePtr->nextPtr; } } else { active.reverseScan = 0; active.nextTracePtr = tracePtr->nextPtr; } if (tracePtr->level > 0 && curLevel > tracePtr->level) { continue; } if (!(tracePtr->flags & TCL_TRACE_EXEC_IN_PROGRESS)) { /* * The proc invoked might delete the traced command which * might try to free tracePtr. We want to use tracePtr until the * end of this if section, so we use Tcl_Preserve() and * Tcl_Release() to be sure it is not freed while we still need * it. */ Tcl_Preserve(tracePtr); tracePtr->flags |= TCL_TRACE_EXEC_IN_PROGRESS; if (state == NULL) { state = Tcl_SaveInterpState(interp, code); } if (tracePtr->flags & (TCL_TRACE_ENTER_EXEC | TCL_TRACE_LEAVE_EXEC)) { /* * New style trace. */ if (tracePtr->flags & traceFlags) { if (tracePtr->proc == TraceExecutionProc) { TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)tracePtr->clientData; tcmdPtr->curFlags = traceFlags; tcmdPtr->curCode = code; } traceCode = tracePtr->proc(tracePtr->clientData, interp, curLevel, command, (Tcl_Command) cmdPtr, objc, objv); } } else { /* * Old-style trace. */ if (traceFlags & TCL_TRACE_ENTER_EXEC) { /* * Old-style interpreter-wide traces only trigger before * the command is executed. */ traceCode = CallTraceFunction(interp, tracePtr, cmdPtr, command, numChars, objc, objv); } } tracePtr->flags &= ~TCL_TRACE_EXEC_IN_PROGRESS; Tcl_Release(tracePtr); } } iPtr->activeInterpTracePtr = active.nextPtr; if (state) { if (traceCode == TCL_OK) { Tcl_RestoreInterpState(interp, state); } else { Tcl_DiscardInterpState(state); } } return traceCode; } /* *---------------------------------------------------------------------- * * CallTraceFunction -- * * Invokes a trace function registered with an interpreter. These * functions trace command execution. Currently this trace function is * called with the address of the string-based Tcl_CmdProc for the * command, not the Tcl_ObjCmdProc. * * Results: * None. * * Side effects: * Those side effects made by the trace function. * *---------------------------------------------------------------------- */ static int CallTraceFunction( Tcl_Interp *interp, /* The current interpreter. */ Trace *tracePtr, /* Describes the trace function to call. */ Command *cmdPtr, /* Points to command's Command struct. */ const char *command, /* Points to the first character of the * command's source before substitutions. */ Tcl_Size numChars, /* The number of characters in the command's * source. */ Tcl_Size objc, /* Number of arguments for the command. */ Tcl_Obj *const objv[]) /* Pointers to Tcl_Obj of each argument. */ { Interp *iPtr = (Interp *) interp; char *commandCopy; int traceCode; /* * Copy the command characters into a new string. */ commandCopy = (char *)TclStackAlloc(interp, numChars + 1); memcpy(commandCopy, command, numChars); commandCopy[numChars] = '\0'; /* * Call the trace function then free allocated storage. */ traceCode = tracePtr->proc(tracePtr->clientData, (Tcl_Interp *) iPtr, iPtr->numLevels, commandCopy, (Tcl_Command) cmdPtr, objc, objv); TclStackFree(interp, commandCopy); return traceCode; } /* *---------------------------------------------------------------------- * * CommandObjTraceDeleted -- * * Ensure the trace is correctly deleted by decrementing its refCount and * only deleting if no other references exist. * * Results: * None. * * Side effects: * May release memory. * *---------------------------------------------------------------------- */ static void CommandObjTraceDeleted( void *clientData) { TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData; if (tcmdPtr->refCount-- <= 1) { Tcl_Free(tcmdPtr); } } /* *---------------------------------------------------------------------- * * TraceExecutionProc -- * * This function is invoked whenever code relevant to a 'trace execution' * command is executed. It is called in one of two ways in Tcl's core: * * (i) by the TclCheckExecutionTraces, when an execution trace has been * triggered. * (ii) by TclCheckInterpTraces, when a prior execution trace has created * a trace of the internals of a procedure, passing in this function as * the one to be called. * * Results: * The return value is a standard Tcl completion code such as TCL_OK or * TCL_ERROR, etc. * * Side effects: * May invoke an arbitrary Tcl procedure, and may create or delete an * interpreter-wide trace. * *---------------------------------------------------------------------- */ static int TraceExecutionProc( void *clientData, Tcl_Interp *interp, Tcl_Size level, const char *command, TCL_UNUSED(Tcl_Command), Tcl_Size objc, Tcl_Obj *const objv[]) { int call = 0; Interp *iPtr = (Interp *) interp; TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData; int flags = tcmdPtr->curFlags; int code = tcmdPtr->curCode; int traceCode = TCL_OK; if (tcmdPtr->flags & TCL_TRACE_EXEC_IN_PROGRESS) { /* * Inside any kind of execution trace callback, we do not allow any * further execution trace callbacks to be called for the same trace. */ return traceCode; } if (!Tcl_InterpDeleted(interp) && !Tcl_LimitExceeded(interp)) { /* * Check whether the current call is going to eval arbitrary Tcl code * with a generated trace, or whether we are only going to setup * interpreter-wide traces to implement the 'step' traces. This latter * situation can happen if we create a command trace without either * before or after operations, but with either of the step operations. */ if (flags & TCL_TRACE_EXEC_DIRECT) { call = flags & tcmdPtr->flags & (TCL_TRACE_ENTER_EXEC | TCL_TRACE_LEAVE_EXEC); } else { call = 1; } /* * First, if we have returned back to the level at which we created an * interpreter trace for enterstep and/or leavestep execution traces, * we remove it here. */ if ((flags & TCL_TRACE_LEAVE_EXEC) && (tcmdPtr->stepTrace != NULL) && (level == tcmdPtr->startLevel) && (strcmp(command, tcmdPtr->startCmd) == 0)) { Tcl_DeleteTrace(interp, tcmdPtr->stepTrace); tcmdPtr->stepTrace = NULL; Tcl_Free(tcmdPtr->startCmd); } /* * Second, create the tcl callback, if required. */ if (call) { Tcl_DString cmd, sub; Tcl_Size i; int saveInterpFlags; Tcl_DStringInit(&cmd); Tcl_DStringAppend(&cmd, tcmdPtr->command, tcmdPtr->length); /* * Append command with arguments. */ Tcl_DStringInit(&sub); for (i = 0; i < objc; i++) { Tcl_DStringAppendElement(&sub, TclGetString(objv[i])); } Tcl_DStringAppendElement(&cmd, Tcl_DStringValue(&sub)); Tcl_DStringFree(&sub); if (flags & TCL_TRACE_ENTER_EXEC) { /* * Append trace operation. */ if (flags & TCL_TRACE_EXEC_DIRECT) { Tcl_DStringAppendElement(&cmd, "enter"); } else { Tcl_DStringAppendElement(&cmd, "enterstep"); } } else if (flags & TCL_TRACE_LEAVE_EXEC) { Tcl_Obj *resultCode; const char *resultCodeStr; /* * Append result code. */ TclNewIntObj(resultCode, code); resultCodeStr = TclGetString(resultCode); Tcl_DStringAppendElement(&cmd, resultCodeStr); Tcl_DecrRefCount(resultCode); /* * Append result string. */ Tcl_DStringAppendElement(&cmd, Tcl_GetStringResult(interp)); /* * Append trace operation. */ if (flags & TCL_TRACE_EXEC_DIRECT) { Tcl_DStringAppendElement(&cmd, "leave"); } else { Tcl_DStringAppendElement(&cmd, "leavestep"); } } else { Tcl_Panic("TraceExecutionProc: bad flag combination"); } /* * Execute the command. We discard any object result the command * returns. */ saveInterpFlags = iPtr->flags; iPtr->flags |= INTERP_TRACE_IN_PROGRESS; tcmdPtr->flags |= TCL_TRACE_EXEC_IN_PROGRESS; tcmdPtr->refCount++; /* * This line can have quite arbitrary side-effects, including * deleting the trace, the command being traced, or even the * interpreter. */ traceCode = Tcl_EvalEx(interp, Tcl_DStringValue(&cmd), Tcl_DStringLength(&cmd), 0); tcmdPtr->flags &= ~TCL_TRACE_EXEC_IN_PROGRESS; /* * Restore the interp tracing flag to prevent cmd traces from * affecting interp traces. */ iPtr->flags = saveInterpFlags; if (tcmdPtr->flags == 0) { flags |= TCL_TRACE_DESTROYED; } Tcl_DStringFree(&cmd); } /* * Third, if there are any step execution traces for this proc, we * register an interpreter trace to invoke enterstep and/or leavestep * traces. We also need to save the current stack level and the proc * string in startLevel and startCmd so that we can delete this * interpreter trace when it reaches the end of this proc. */ if ((flags & TCL_TRACE_ENTER_EXEC) && (tcmdPtr->stepTrace == NULL) && (tcmdPtr->flags & (TCL_TRACE_ENTER_DURING_EXEC | TCL_TRACE_LEAVE_DURING_EXEC))) { unsigned len = strlen(command) + 1; tcmdPtr->startLevel = level; tcmdPtr->startCmd = (char *)Tcl_Alloc(len); memcpy(tcmdPtr->startCmd, command, len); tcmdPtr->refCount++; tcmdPtr->stepTrace = Tcl_CreateObjTrace2(interp, 0, (tcmdPtr->flags & TCL_TRACE_ANY_EXEC) >> 2, TraceExecutionProc, tcmdPtr, CommandObjTraceDeleted); } } if (flags & TCL_TRACE_DESTROYED) { if (tcmdPtr->stepTrace != NULL) { Tcl_DeleteTrace(interp, tcmdPtr->stepTrace); tcmdPtr->stepTrace = NULL; Tcl_Free(tcmdPtr->startCmd); } } if (call) { if (tcmdPtr->refCount-- <= 1) { Tcl_Free(tcmdPtr); } } return traceCode; } /* *---------------------------------------------------------------------- * * TraceVarProc -- * * This function is called to handle variable accesses that have been * traced using the "trace" command. * * Results: * Normally returns NULL. If the trace command returns an error, then * this function returns an error string. * * Side effects: * Depends on the command associated with the trace. * *---------------------------------------------------------------------- */ static char * TraceVarProc( void *clientData, /* Information about the variable trace. */ Tcl_Interp *interp, /* Interpreter containing variable. */ const char *name1, /* Name of variable or array. */ const char *name2, /* Name of element within array; NULL means * scalar variable is being referenced. */ int flags) /* OR-ed bits giving operation and other * information. */ { TraceVarInfo *tvarPtr = (TraceVarInfo *)clientData; char *result; int code, destroy = 0; Tcl_DString cmd; int rewind = ((Interp *)interp)->execEnvPtr->rewind; /* * We might call Tcl_EvalEx() below, and that might evaluate * [trace remove variable] which might try to free tvarPtr. We want to * use tvarPtr until the end of this function, so we use Tcl_Preserve() * and Tcl_Release() to be sure it is not freed while we still need it. */ result = NULL; if ((tvarPtr->flags & flags) && !Tcl_InterpDeleted(interp) && !Tcl_LimitExceeded(interp)) { if (tvarPtr->length) { /* * Generate a command to execute by appending list elements for * the two variable names and the operation. */ Tcl_DStringInit(&cmd); Tcl_DStringAppend(&cmd, tvarPtr->command, tvarPtr->length); Tcl_DStringAppendElement(&cmd, name1); Tcl_DStringAppendElement(&cmd, (name2 ? name2 : "")); if (flags & TCL_TRACE_ARRAY) { TclDStringAppendLiteral(&cmd, " array"); } else if (flags & TCL_TRACE_READS) { TclDStringAppendLiteral(&cmd, " read"); } else if (flags & TCL_TRACE_WRITES) { TclDStringAppendLiteral(&cmd, " write"); } else if (flags & TCL_TRACE_UNSETS) { TclDStringAppendLiteral(&cmd, " unset"); } /* * Execute the command. We discard any object result the command * returns. * * Add the TCL_TRACE_DESTROYED flag to tvarPtr to indicate to * other areas that this will be destroyed by us, otherwise a * double-free might occur depending on what the eval does. */ if ((flags & TCL_TRACE_DESTROYED) && !(tvarPtr->flags & TCL_TRACE_DESTROYED)) { destroy = 1; tvarPtr->flags |= TCL_TRACE_DESTROYED; } /* * Make sure that unset traces are rune even if the execEnv is * rewinding (coroutine deletion, [Bug 2093947] */ if (rewind && (flags & TCL_TRACE_UNSETS)) { ((Interp *)interp)->execEnvPtr->rewind = 0; } code = Tcl_EvalEx(interp, Tcl_DStringValue(&cmd), Tcl_DStringLength(&cmd), 0); if (rewind) { ((Interp *)interp)->execEnvPtr->rewind = rewind; } if (code != TCL_OK) { /* copy error msg to result */ Tcl_Obj *errMsgObj = Tcl_GetObjResult(interp); Tcl_IncrRefCount(errMsgObj); result = (char *) errMsgObj; } Tcl_DStringFree(&cmd); } } if (destroy && result != NULL) { Tcl_Obj *errMsgObj = (Tcl_Obj *) result; Tcl_DecrRefCount(errMsgObj); result = NULL; } return result; } /* *---------------------------------------------------------------------- * * Tcl_CreateObjTrace/Tcl_CreateObjTrace2 -- * * Arrange for a function to be called to trace command execution. * * Results: * The return value is a token for the trace, which may be passed to * Tcl_DeleteTrace to eliminate the trace. * * Side effects: * From now on, proc will be called just before a command function is * called to execute a Tcl command. Calls to proc will have the following * form: * * void proc(void * clientData, * Tcl_Interp * interp, * int level, * const char * command, * Tcl_Command commandInfo, * int objc, * Tcl_Obj *const objv[]); * * The 'clientData' and 'interp' arguments to 'proc' will be the same as * the arguments to Tcl_CreateObjTrace. The 'level' argument gives the * nesting depth of command interpretation within the interpreter. The * 'command' argument is the ASCII text of the command being evaluated - * before any substitutions are performed. The 'commandInfo' argument * gives a handle to the command procedure that will be evaluated. The * 'objc' and 'objv' parameters give the parameter vector that will be * passed to the command procedure. Proc does not return a value. * * The 'level' argument specifies the maximum nesting level of calls to * be traced. If the execution depth of the interpreter exceeds 'level', * the trace callback is not executed. * * The 'flags' argument is either zero or the value, * TCL_ALLOW_INLINE_COMPILATION. If the TCL_ALLOW_INLINE_COMPILATION flag * is not present, the bytecode compiler will not generate inline code * for Tcl's built-in commands. This behavior will have a significant * impact on performance, but will ensure that all command evaluations * are traced. If the TCL_ALLOW_INLINE_COMPILATION flag is present, the * bytecode compiler will have its normal behavior of compiling in-line * code for some of Tcl's built-in commands. In this case, the tracing * will be imprecise - in-line code will not be traced - but run-time * performance will be improved. The latter behavior is desired for many * applications such as profiling of run time. * * When the trace is deleted, the 'delProc' function will be invoked, * passing it the original client data. * *---------------------------------------------------------------------- */ typedef struct { Tcl_CmdObjTraceProc *proc; Tcl_CmdObjTraceDeleteProc *delProc; void *clientData; } TraceWrapperInfo; static int traceWrapperProc( void *clientData, Tcl_Interp *interp, Tcl_Size level, const char *command, Tcl_Command commandInfo, Tcl_Size objc, Tcl_Obj *const objv[]) { TraceWrapperInfo *info = (TraceWrapperInfo *)clientData; if (objc > INT_MAX) { objc = -1; /* Signal Tcl_CmdObjTraceProc that objc is out of range */ } return info->proc(info->clientData, interp, (int)level, command, commandInfo, objc, objv); } static void traceWrapperDelProc( void *clientData) { TraceWrapperInfo *info = (TraceWrapperInfo *)clientData; clientData = info->clientData; if (info->delProc) { info->delProc(clientData); } Tcl_Free(info); } Tcl_Trace Tcl_CreateObjTrace( Tcl_Interp *interp, /* Tcl interpreter */ Tcl_Size level, /* Maximum nesting level */ int flags, /* Flags, see above */ Tcl_CmdObjTraceProc *proc, /* Trace callback */ void *clientData, /* Client data for the callback */ Tcl_CmdObjTraceDeleteProc *delProc) /* Function to call when trace is deleted */ { TraceWrapperInfo *info = (TraceWrapperInfo *)Tcl_Alloc(sizeof(TraceWrapperInfo)); info->proc = proc; info->delProc = delProc; info->clientData = clientData; return Tcl_CreateObjTrace2(interp, level, flags, (proc ? traceWrapperProc : NULL), info, traceWrapperDelProc); } Tcl_Trace Tcl_CreateObjTrace2( Tcl_Interp *interp, /* Tcl interpreter */ Tcl_Size level, /* Maximum nesting level */ int flags, /* Flags, see above */ Tcl_CmdObjTraceProc2 *proc, /* Trace callback */ void *clientData, /* Client data for the callback */ Tcl_CmdObjTraceDeleteProc *delProc) /* Function to call when trace is deleted */ { Trace *tracePtr; Interp *iPtr = (Interp *) interp; /* * Test if this trace allows inline compilation of commands. */ if (!(flags & TCL_ALLOW_INLINE_COMPILATION)) { if (iPtr->tracesForbiddingInline == 0) { /* * When the first trace forbidding inline compilation is created, * invalidate existing compiled code for this interpreter and * arrange (by setting the DONT_COMPILE_CMDS_INLINE flag) that * when compiling new code, no commands will be compiled inline * (i.e., into an inline sequence of instructions). We do this * because commands that were compiled inline will never result in * a command trace being called. */ iPtr->compileEpoch++; iPtr->flags |= DONT_COMPILE_CMDS_INLINE; } iPtr->tracesForbiddingInline++; } tracePtr = (Trace *)Tcl_Alloc(sizeof(Trace)); tracePtr->level = level; tracePtr->proc = proc; tracePtr->clientData = clientData; tracePtr->delProc = delProc; tracePtr->nextPtr = iPtr->tracePtr; tracePtr->flags = flags; iPtr->tracePtr = tracePtr; return (Tcl_Trace) tracePtr; } /* *---------------------------------------------------------------------- * * Tcl_CreateTrace -- * * Arrange for a function to be called to trace command execution. * * Results: * The return value is a token for the trace, which may be passed to * Tcl_DeleteTrace to eliminate the trace. * * Side effects: * From now on, proc will be called just before a command procedure is * called to execute a Tcl command. Calls to proc will have the following * form: * * void * proc(clientData, interp, level, command, cmdProc, cmdClientData, * argc, argv) * void *clientData; * Tcl_Interp *interp; * int level; * char *command; * int (*cmdProc)(); * void *cmdClientData; * int argc; * char **argv; * { * } * * The clientData and interp arguments to proc will be the same as the * corresponding arguments to this function. Level gives the nesting * level of command interpretation for this interpreter (0 corresponds to * top level). Command gives the ASCII text of the raw command, cmdProc * and cmdClientData give the function that will be called to process the * command and the ClientData value it will receive, and argc and argv * give the arguments to the command, after any argument parsing and * substitution. Proc does not return a value. * *---------------------------------------------------------------------- */ Tcl_Trace Tcl_CreateTrace( Tcl_Interp *interp, /* Interpreter in which to create trace. */ Tcl_Size level, /* Only call proc for commands at nesting * level<=argument level (1=>top level). */ Tcl_CmdTraceProc *proc, /* Function to call before executing each * command. */ void *clientData) /* Arbitrary value word to pass to proc. */ { StringTraceData *data = (StringTraceData *)Tcl_Alloc(sizeof(StringTraceData)); data->clientData = clientData; data->proc = proc; return Tcl_CreateObjTrace2(interp, level, 0, StringTraceProc, data, StringTraceDeleteProc); } /* *---------------------------------------------------------------------- * * StringTraceProc -- * * Invoke a string-based trace function from an object-based callback. * * Results: * None. * * Side effects: * Whatever the string-based trace function does. * *---------------------------------------------------------------------- */ static int StringTraceProc( void *clientData, Tcl_Interp *interp, Tcl_Size level, const char *command, Tcl_Command commandInfo, Tcl_Size objc, Tcl_Obj *const *objv) { StringTraceData *data = (StringTraceData *)clientData; Command *cmdPtr = (Command *) commandInfo; const char **argv; /* Args to pass to string trace proc */ Tcl_Size i; /* * This is a bit messy because we have to emulate the old trace interface, * which uses strings for everything. */ argv = (const char **) TclStackAlloc(interp, (objc + 1) * sizeof(const char *)); for (i = 0; i < objc; i++) { argv[i] = TclGetString(objv[i]); } argv[objc] = 0; /* * Invoke the command function. Note that we cast away const-ness on two * parameters for compatibility with legacy code; the code MUST NOT modify * either command or argv. */ data->proc(data->clientData, interp, level, (char *) command, cmdPtr->proc, cmdPtr->clientData, objc, argv); TclStackFree(interp, (void *) argv); return TCL_OK; } /* *---------------------------------------------------------------------- * * StringTraceDeleteProc -- * * Clean up memory when a string-based trace is deleted. * * Results: * None. * * Side effects: * Allocated memory is returned to the system. * *---------------------------------------------------------------------- */ static void StringTraceDeleteProc( void *clientData) { Tcl_Free(clientData); } /* *---------------------------------------------------------------------- * * Tcl_DeleteTrace -- * * Remove a trace. * * Results: * None. * * Side effects: * From now on there will be no more calls to the function given in * trace. * *---------------------------------------------------------------------- */ void Tcl_DeleteTrace( Tcl_Interp *interp, /* Interpreter that contains trace. */ Tcl_Trace trace) /* Token for trace (returned previously by * Tcl_CreateTrace). */ { Interp *iPtr = (Interp *) interp; Trace *prevPtr, *tracePtr = (Trace *) trace; Trace **tracePtr2 = &iPtr->tracePtr; ActiveInterpTrace *activePtr; /* * Locate the trace entry in the interpreter's trace list, and remove it * from the list. */ prevPtr = NULL; while (*tracePtr2 != NULL && *tracePtr2 != tracePtr) { prevPtr = *tracePtr2; tracePtr2 = &prevPtr->nextPtr; } if (*tracePtr2 == NULL) { return; } *tracePtr2 = (*tracePtr2)->nextPtr; /* * The code below makes it possible to delete traces while traces are * active: it makes sure that the deleted trace won't be processed by * TclCheckInterpTraces. */ for (activePtr = iPtr->activeInterpTracePtr; activePtr != NULL; activePtr = activePtr->nextPtr) { if (activePtr->nextTracePtr == tracePtr) { if (activePtr->reverseScan) { activePtr->nextTracePtr = prevPtr; } else { activePtr->nextTracePtr = tracePtr->nextPtr; } } } /* * If the trace forbids bytecode compilation, change the interpreter's * state. If bytecode compilation is now permitted, flag the fact and * advance the compilation epoch so that procs will be recompiled to take * advantage of it. */ if (!(tracePtr->flags & TCL_ALLOW_INLINE_COMPILATION)) { iPtr->tracesForbiddingInline--; if (iPtr->tracesForbiddingInline == 0) { iPtr->flags &= ~DONT_COMPILE_CMDS_INLINE; iPtr->compileEpoch++; } } /* * Execute any delete callback. */ if (tracePtr->delProc != NULL) { tracePtr->delProc(tracePtr->clientData); } /* * Delete the trace object. */ Tcl_EventuallyFree((char *) tracePtr, TCL_DYNAMIC); } /* *---------------------------------------------------------------------- * * TclTraceVarExists -- * * This is called from info exists. We need to trigger read and/or array * traces because they may end up creating a variable that doesn't * currently exist. * * Results: * A pointer to the Var structure, or NULL. * * Side effects: * May fill in error messages in the interp. * *---------------------------------------------------------------------- */ Var * TclVarTraceExists( Tcl_Interp *interp, /* The interpreter */ const char *varName) /* The variable name */ { Var *varPtr, *arrayPtr; /* * The choice of "create" flag values is delicate here, and matches the * semantics of GetVar. Things are still not perfect, however, because if * you do "info exists x" you get a varPtr and therefore trigger traces. * However, if you do "info exists x(i)", then you only get a varPtr if x * is already known to be an array. Otherwise you get NULL, and no trace * is triggered. This matches Tcl 7.6 semantics. */ varPtr = TclLookupVar(interp, varName, NULL, 0, "access", /*createPart1*/ 0, /*createPart2*/ 1, &arrayPtr); if (varPtr == NULL) { return NULL; } if ((varPtr->flags & VAR_TRACED_READ) || (arrayPtr && (arrayPtr->flags & VAR_TRACED_READ))) { TclCallVarTraces((Interp *) interp, arrayPtr, varPtr, varName, NULL, TCL_TRACE_READS, /* leaveErrMsg */ 0); } /* * If the variable doesn't exist anymore and no-one's using it, then free * up the relevant structures and hash table entries. */ if (TclIsVarUndefined(varPtr)) { TclCleanupVar(varPtr, arrayPtr); return NULL; } return varPtr; } /* *---------------------------------------------------------------------- * * TclCheckArrayTraces -- * * This function is invoked to when we operate on an array variable, * to allow any array traces to fire. * * Results: * Returns TCL_OK to indicate normal operation. Returns TCL_ERROR if * invocation of a trace function indicated an error. When TCL_ERROR is * returned, then error information is left in interp. * * Side effects: * Almost anything can happen, depending on trace; this function itself * doesn't have any side effects. * *---------------------------------------------------------------------- */ int TclCheckArrayTraces( Tcl_Interp *interp, Var *varPtr, Var *arrayPtr, Tcl_Obj *name, int index) { int code = TCL_OK; if (varPtr && (varPtr->flags & VAR_TRACED_ARRAY) && (TclIsVarArray(varPtr) || TclIsVarUndefined(varPtr))) { Interp *iPtr = (Interp *)interp; code = TclObjCallVarTraces(iPtr, arrayPtr, varPtr, name, NULL, (TCL_NAMESPACE_ONLY|TCL_GLOBAL_ONLY| TCL_TRACE_ARRAY), /* leaveErrMsg */ 1, index); } return code; } /* *---------------------------------------------------------------------- * * TclCallVarTraces -- * * This function is invoked to find and invoke relevant trace functions * associated with a particular operation on a variable. This function * invokes traces both on the variable and on its containing array (where * relevant). * * Results: * Returns TCL_OK to indicate normal operation. Returns TCL_ERROR if * invocation of a trace function indicated an error. When TCL_ERROR is * returned and leaveErrMsg is true, then the errorInfo field of iPtr has * information about the error placed in it. * * Side effects: * Almost anything can happen, depending on trace; this function itself * doesn't have any side effects. * *---------------------------------------------------------------------- */ int TclObjCallVarTraces( Interp *iPtr, /* Interpreter containing variable. */ Var *arrayPtr, /* Pointer to array variable that contains the * variable, or NULL if the variable isn't an * element of an array. */ Var *varPtr, /* Variable whose traces are to be invoked. */ Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, /* Variable's two-part name. */ int flags, /* Flags passed to trace functions: indicates * what's happening to variable, plus maybe * TCL_GLOBAL_ONLY or TCL_NAMESPACE_ONLY */ int leaveErrMsg, /* If true, and one of the traces indicates an * error, then leave an error message and * stack trace information in *iPTr. */ int index) /* Index into the local variable table of the * variable, or -1. Only used when part1Ptr is * NULL. */ { const char *part1, *part2; if (!part1Ptr) { part1Ptr = localName(iPtr->varFramePtr, index); } if (!part1Ptr) { Tcl_Panic("Cannot trace a variable with no name"); } part1 = TclGetString(part1Ptr); part2 = part2Ptr? TclGetString(part2Ptr) : NULL; return TclCallVarTraces(iPtr, arrayPtr, varPtr, part1, part2, flags, leaveErrMsg); } int TclCallVarTraces( Interp *iPtr, /* Interpreter containing variable. */ Var *arrayPtr, /* Pointer to array variable that contains the * variable, or NULL if the variable isn't an * element of an array. */ Var *varPtr, /* Variable whose traces are to be invoked. */ const char *part1, const char *part2, /* Variable's two-part name. */ int flags, /* Flags passed to trace functions: indicates * what's happening to variable, plus maybe * TCL_GLOBAL_ONLY or TCL_NAMESPACE_ONLY */ int leaveErrMsg) /* If true, and one of the traces indicates an * error, then leave an error message and * stack trace information in *iPTr. */ { VarTrace *tracePtr; ActiveVarTrace active; char *result; const char *openParen, *p; Tcl_DString nameCopy; int copiedName; int code = TCL_OK; int disposeFlags = 0; Tcl_InterpState state = NULL; Tcl_HashEntry *hPtr; int traceflags = flags & VAR_ALL_TRACES; const char *element; /* * If there are already similar trace functions active for the variable, * don't call them again. */ if (TclIsVarTraceActive(varPtr)) { return code; } TclSetVarTraceActive(varPtr); if (TclIsVarInHash(varPtr)) { VarHashRefCount(varPtr)++; } if (arrayPtr && TclIsVarInHash(arrayPtr)) { VarHashRefCount(arrayPtr)++; } /* * If the variable name hasn't been parsed into array name and element, do * it here. If there really is an array element, make a copy of the * original name so that NULLs can be inserted into it to separate the * names (can't modify the name string in place, because the string might * get used by the callbacks we invoke). */ copiedName = 0; if (part2 == NULL) { for (p = part1; *p ; p++) { if (*p == '(') { openParen = p; do { p++; } while (*p != '\0'); p--; if (*p == ')') { int offset = (openParen - part1); char *newPart1; Tcl_DStringInit(&nameCopy); Tcl_DStringAppend(&nameCopy, part1, p-part1); newPart1 = Tcl_DStringValue(&nameCopy); newPart1[offset] = 0; part1 = newPart1; part2 = newPart1 + offset + 1; copiedName = 1; } break; } } } /* Keep the original pointer for possible use in an error message */ element = part2; if (part2 == NULL) { if (TclIsVarArrayElement(varPtr)) { Tcl_Obj *keyObj = VarHashGetKey(varPtr); part2 = Tcl_GetString(keyObj); } } else if ((flags & VAR_TRACED_UNSET) && !(flags & VAR_ARRAY_ELEMENT)) { /* On unset traces, part2 has already been set by the caller, and * the VAR_ARRAY_ELEMENT flag indicates whether the accessed * variable actually has a second part, or is a scalar */ element = NULL; } /* * Invoke traces on the array containing the variable, if relevant. */ result = NULL; active.nextPtr = iPtr->activeVarTracePtr; iPtr->activeVarTracePtr = &active; Tcl_Preserve(iPtr); if (arrayPtr && !TclIsVarTraceActive(arrayPtr) && (arrayPtr->flags & traceflags)) { hPtr = Tcl_FindHashEntry(&iPtr->varTraces, arrayPtr); active.varPtr = arrayPtr; for (tracePtr = (VarTrace *)Tcl_GetHashValue(hPtr); tracePtr != NULL; tracePtr = active.nextTracePtr) { active.nextTracePtr = tracePtr->nextPtr; if (!(tracePtr->flags & flags)) { continue; } Tcl_Preserve(tracePtr); if (state == NULL) { state = Tcl_SaveInterpState((Tcl_Interp *) iPtr, code); } result = tracePtr->traceProc(tracePtr->clientData, (Tcl_Interp *) iPtr, part1, part2, flags); if (result != NULL) { if (flags & TCL_TRACE_UNSETS) { /* * Ignore errors in unset traces. */ DisposeTraceResult(tracePtr->flags, result); } else { disposeFlags = tracePtr->flags; code = TCL_ERROR; } } Tcl_Release(tracePtr); if (code == TCL_ERROR) { goto done; } } } /* * Invoke traces on the variable itself. */ if (flags & TCL_TRACE_UNSETS) { flags |= TCL_TRACE_DESTROYED; } active.varPtr = varPtr; if (varPtr->flags & traceflags) { hPtr = Tcl_FindHashEntry(&iPtr->varTraces, varPtr); for (tracePtr = (VarTrace *)Tcl_GetHashValue(hPtr); tracePtr != NULL; tracePtr = active.nextTracePtr) { active.nextTracePtr = tracePtr->nextPtr; if (!(tracePtr->flags & flags)) { continue; } Tcl_Preserve(tracePtr); if (state == NULL) { state = Tcl_SaveInterpState((Tcl_Interp *) iPtr, code); } result = tracePtr->traceProc(tracePtr->clientData, (Tcl_Interp *) iPtr, part1, part2, flags); if (result != NULL) { if (flags & TCL_TRACE_UNSETS) { /* * Ignore errors in unset traces. */ DisposeTraceResult(tracePtr->flags, result); } else { disposeFlags = tracePtr->flags; code = TCL_ERROR; } } Tcl_Release(tracePtr); if (code == TCL_ERROR) { goto done; } } } /* * Restore the variable's flags, remove the record of our active traces, * and then return. */ done: if (code == TCL_ERROR) { if (leaveErrMsg) { const char *verb = ""; const char *type = ""; switch (flags&(TCL_TRACE_READS|TCL_TRACE_WRITES|TCL_TRACE_ARRAY)) { case TCL_TRACE_READS: verb = "read"; type = verb; break; case TCL_TRACE_WRITES: verb = "set"; type = "write"; break; case TCL_TRACE_ARRAY: verb = "trace array"; type = "array"; break; } if (disposeFlags & TCL_TRACE_RESULT_OBJECT) { Tcl_SetObjResult((Tcl_Interp *)iPtr, (Tcl_Obj *) result); } else { Tcl_SetObjResult((Tcl_Interp *)iPtr, Tcl_NewStringObj(result, -1)); } Tcl_AddErrorInfo((Tcl_Interp *)iPtr, ""); Tcl_AppendObjToErrorInfo((Tcl_Interp *)iPtr, Tcl_ObjPrintf( "\n (%s trace on \"%s%s%s%s\")", type, part1, (element ? "(" : ""), (element ? element : ""), (element ? ")" : "") )); if (disposeFlags & TCL_TRACE_RESULT_OBJECT) { TclVarErrMsg((Tcl_Interp *) iPtr, part1, element, verb, TclGetString((Tcl_Obj *) result)); } else { TclVarErrMsg((Tcl_Interp *) iPtr, part1, element, verb, result); } iPtr->flags &= ~(ERR_ALREADY_LOGGED); Tcl_DiscardInterpState(state); } else { Tcl_RestoreInterpState((Tcl_Interp *) iPtr, state); } DisposeTraceResult(disposeFlags,result); } else if (state) { if (code == TCL_OK) { code = Tcl_RestoreInterpState((Tcl_Interp *) iPtr, state); } else { Tcl_DiscardInterpState(state); } } if (arrayPtr && TclIsVarInHash(arrayPtr)) { VarHashRefCount(arrayPtr)--; } if (copiedName) { Tcl_DStringFree(&nameCopy); } TclClearVarTraceActive(varPtr); if (TclIsVarInHash(varPtr)) { VarHashRefCount(varPtr)--; } iPtr->activeVarTracePtr = active.nextPtr; Tcl_Release(iPtr); return code; } /* *---------------------------------------------------------------------- * * DisposeTraceResult-- * * This function is called to dispose of the result returned from a trace * function. The disposal method appropriate to the type of result is * determined by flags. * * Results: * None. * * Side effects: * The memory allocated for the trace result may be freed. * *---------------------------------------------------------------------- */ static void DisposeTraceResult( int flags, /* Indicates type of result to determine * proper disposal method. */ char *result) /* The result returned from a trace function * to be disposed. */ { if (flags & TCL_TRACE_RESULT_DYNAMIC) { Tcl_Free(result); } else if (flags & TCL_TRACE_RESULT_OBJECT) { Tcl_DecrRefCount((Tcl_Obj *) result); } } /* *---------------------------------------------------------------------- * * Tcl_UntraceVar2 -- * * Remove a previously-created trace for a variable. * * Results: * None. * * Side effects: * If there exists a trace for the variable given by part1 and part2 with * the given flags, proc, and clientData, then that trace is removed. * *---------------------------------------------------------------------- */ void Tcl_UntraceVar2( Tcl_Interp *interp, /* Interpreter containing variable. */ const char *part1, /* Name of variable or array. */ const char *part2, /* Name of element within array; NULL means * trace applies to scalar variable or array * as-a-whole. */ int flags, /* OR-ed collection of bits describing current * trace, including any of TCL_TRACE_READS, * TCL_TRACE_WRITES, TCL_TRACE_UNSETS, * TCL_GLOBAL_ONLY, and TCL_NAMESPACE_ONLY. */ Tcl_VarTraceProc *proc, /* Function associated with trace. */ void *clientData) /* Arbitrary argument to pass to proc. */ { VarTrace *tracePtr; VarTrace *prevPtr, *nextPtr; Var *varPtr, *arrayPtr; Interp *iPtr = (Interp *) interp; ActiveVarTrace *activePtr; int flagMask, allFlags = 0; Tcl_HashEntry *hPtr; /* * Set up a mask to mask out the parts of the flags that we are not * interested in now. */ flagMask = TCL_GLOBAL_ONLY | TCL_NAMESPACE_ONLY; varPtr = TclLookupVar(interp, part1, part2, flags & flagMask, /*msg*/ NULL, /*createPart1*/ 0, /*createPart2*/ 0, &arrayPtr); if (varPtr == NULL || !(varPtr->flags & VAR_ALL_TRACES & flags)) { return; } /* * Set up a mask to mask out the parts of the flags that we are not * interested in now. */ flagMask = TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS | TCL_TRACE_ARRAY | TCL_TRACE_RESULT_DYNAMIC | TCL_TRACE_RESULT_OBJECT; flags &= flagMask; hPtr = Tcl_FindHashEntry(&iPtr->varTraces, varPtr); for (tracePtr = (VarTrace *)Tcl_GetHashValue(hPtr), prevPtr = NULL; ; prevPtr = tracePtr, tracePtr = tracePtr->nextPtr) { if (tracePtr == NULL) { goto updateFlags; } if ((tracePtr->traceProc == proc) && (tracePtr->flags == flags) && (tracePtr->clientData == clientData)) { break; } allFlags |= tracePtr->flags; } /* * The code below makes it possible to delete traces while traces are * active: it makes sure that the deleted trace won't be processed by * TclCallVarTraces. * * Caveat (Bug 3062331): When an unset trace handler on a variable * tries to delete a different unset trace handler on the same variable, * the results may be surprising. When variable unset traces fire, the * traced variable is already gone. So the TclLookupVar() call above * will not find that variable, and not finding it will never reach here * to perform the deletion. This means callers of Tcl_UntraceVar*() * attempting to delete unset traces from within the handler of another * unset trace have to account for the possibility that their call to * Tcl_UntraceVar*() is a no-op. */ for (activePtr = iPtr->activeVarTracePtr; activePtr != NULL; activePtr = activePtr->nextPtr) { if (activePtr->nextTracePtr == tracePtr) { activePtr->nextTracePtr = tracePtr->nextPtr; } } nextPtr = tracePtr->nextPtr; if (prevPtr == NULL) { if (nextPtr) { Tcl_SetHashValue(hPtr, nextPtr); } else { Tcl_DeleteHashEntry(hPtr); } } else { prevPtr->nextPtr = nextPtr; } tracePtr->nextPtr = NULL; Tcl_EventuallyFree(tracePtr, TCL_DYNAMIC); for (tracePtr = nextPtr; tracePtr != NULL; tracePtr = tracePtr->nextPtr) { allFlags |= tracePtr->flags; } updateFlags: varPtr->flags &= ~VAR_ALL_TRACES; if (allFlags & VAR_ALL_TRACES) { varPtr->flags |= (allFlags & VAR_ALL_TRACES); } else if (TclIsVarUndefined(varPtr)) { /* * If this is the last trace on the variable, and the variable is * unset and unused, then free up the variable. */ TclCleanupVar(varPtr, NULL); } } /* *---------------------------------------------------------------------- * * Tcl_VarTraceInfo2 -- * * Same as Tcl_VarTraceInfo, except takes name in two pieces instead of * one. * * Results: * Same as Tcl_VarTraceInfo. * * Side effects: * None. * *---------------------------------------------------------------------- */ void * Tcl_VarTraceInfo2( Tcl_Interp *interp, /* Interpreter containing variable. */ const char *part1, /* Name of variable or array. */ const char *part2, /* Name of element within array; NULL means * trace applies to scalar variable or array * as-a-whole. */ int flags, /* OR-ed combination of TCL_GLOBAL_ONLY, * TCL_NAMESPACE_ONLY. */ Tcl_VarTraceProc *proc, /* Function associated with trace. */ void *prevClientData) /* If non-NULL, gives last value returned by * this function, so this call will return the * next trace after that one. If NULL, this * call will return the first trace. */ { Interp *iPtr = (Interp *) interp; Var *varPtr, *arrayPtr; Tcl_HashEntry *hPtr; varPtr = TclLookupVar(interp, part1, part2, flags & (TCL_GLOBAL_ONLY|TCL_NAMESPACE_ONLY), /*msg*/ NULL, /*createPart1*/ 0, /*createPart2*/ 0, &arrayPtr); if (varPtr == NULL) { return NULL; } /* * Find the relevant trace, if any, and return its clientData. */ hPtr = Tcl_FindHashEntry(&iPtr->varTraces, varPtr); if (hPtr) { VarTrace *tracePtr = (VarTrace *)Tcl_GetHashValue(hPtr); if (prevClientData != NULL) { for (; tracePtr != NULL; tracePtr = tracePtr->nextPtr) { if ((tracePtr->clientData == prevClientData) && (tracePtr->traceProc == proc)) { tracePtr = tracePtr->nextPtr; break; } } } for (; tracePtr != NULL ; tracePtr = tracePtr->nextPtr) { if (tracePtr->traceProc == proc) { return tracePtr->clientData; } } } return NULL; } /* *---------------------------------------------------------------------- * * Tcl_TraceVar2 -- * * Arrange for reads and/or writes to a variable to cause a function to * be invoked, which can monitor the operations and/or change their * actions. * * Results: * A standard Tcl return value. * * Side effects: * A trace is set up on the variable given by part1 and part2, such that * future references to the variable will be mediated by proc. See * the manual entry for complete details on the calling sequence for * proc. The variable's flags are updated. * *---------------------------------------------------------------------- */ int Tcl_TraceVar2( Tcl_Interp *interp, /* Interpreter in which variable is to be * traced. */ const char *part1, /* Name of scalar variable or array. */ const char *part2, /* Name of element within array; NULL means * trace applies to scalar variable or array * as-a-whole. */ int flags, /* OR-ed collection of bits, including any of * TCL_TRACE_READS, TCL_TRACE_WRITES, * TCL_TRACE_UNSETS, TCL_GLOBAL_ONLY, and * TCL_NAMESPACE_ONLY. */ Tcl_VarTraceProc *proc, /* Function to call when specified ops are * invoked upon varName. */ void *clientData) /* Arbitrary argument to pass to proc. */ { VarTrace *tracePtr; int result; tracePtr = (VarTrace *)Tcl_Alloc(sizeof(VarTrace)); tracePtr->traceProc = proc; tracePtr->clientData = clientData; tracePtr->flags = flags; result = TraceVarEx(interp, part1, part2, tracePtr); if (result != TCL_OK) { Tcl_Free(tracePtr); } return result; } /* *---------------------------------------------------------------------- * * TraceVarEx -- * * Arrange for reads and/or writes to a variable to cause a function to * be invoked, which can monitor the operations and/or change their * actions. * * Results: * A standard Tcl return value. * * Side effects: * A trace is set up on the variable given by part1 and part2, such that * future references to the variable will be mediated by the * traceProc listed in tracePtr. See the manual entry for complete * details on the calling sequence for proc. * *---------------------------------------------------------------------- */ static int TraceVarEx( Tcl_Interp *interp, /* Interpreter in which variable is to be * traced. */ const char *part1, /* Name of scalar variable or array. */ const char *part2, /* Name of element within array; NULL means * trace applies to scalar variable or array * as-a-whole. */ VarTrace *tracePtr)/* Structure containing flags, traceProc and * clientData fields. Others should be left * blank. Will be Tcl_Free()d (eventually) if * this function returns TCL_OK, and up to * caller to free if this function returns * TCL_ERROR. */ { Interp *iPtr = (Interp *) interp; Var *varPtr, *arrayPtr; int flagMask, isNew; Tcl_HashEntry *hPtr; /* * We strip 'flags' down to just the parts which are relevant to * TclLookupVar, to avoid conflicts between trace flags and internal * namespace flags such as 'TCL_FIND_ONLY_NS'. This can now occur since we * have trace flags with values 0x1000 and higher. */ flagMask = TCL_GLOBAL_ONLY | TCL_NAMESPACE_ONLY; varPtr = TclLookupVar(interp, part1, part2, (tracePtr->flags & flagMask) | TCL_LEAVE_ERR_MSG, "trace", /*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr); if (varPtr == NULL) { return TCL_ERROR; } /* * Check for a nonsense flag combination. Note that this is a Tcl_Panic() * because there should be no code path that ever sets both flags. */ if ((tracePtr->flags & TCL_TRACE_RESULT_DYNAMIC) && (tracePtr->flags & TCL_TRACE_RESULT_OBJECT)) { Tcl_Panic("bad result flag combination"); } /* * Set up trace information. */ flagMask = TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS | TCL_TRACE_ARRAY | TCL_TRACE_RESULT_DYNAMIC | TCL_TRACE_RESULT_OBJECT; tracePtr->flags = tracePtr->flags & flagMask; hPtr = Tcl_CreateHashEntry(&iPtr->varTraces, varPtr, &isNew); if (isNew) { tracePtr->nextPtr = NULL; } else { tracePtr->nextPtr = (VarTrace *)Tcl_GetHashValue(hPtr); } Tcl_SetHashValue(hPtr, tracePtr); /* * Mark the variable as traced so we know to call them. */ varPtr->flags |= (tracePtr->flags & VAR_ALL_TRACES); return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclUniData.c0000644000175000017500000036641114726623136015401 0ustar sergeisergei/* * tclUniData.c -- * * Declarations of Unicode character information tables. This file is * automatically generated by the tools/uniParse.tcl script. Do not * modify this file by hand. * * Copyright © 1998 Scriptics Corporation. * All rights reserved. */ /* * A 16-bit Unicode character is split into two parts in order to index * into the following tables. The lower OFFSET_BITS comprise an offset * into a page of characters. The upper bits comprise the page number. */ #define OFFSET_BITS 5 /* * The pageMap is indexed by page number and returns an alternate page number * that identifies a unique page of characters. Many Unicode characters map * to the same alternate page number. */ static const unsigned short pageMap[] = { 0, 32, 64, 96, 0, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 224, 480, 512, 544, 576, 608, 640, 672, 704, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992, 224, 1024, 224, 1056, 224, 224, 1088, 1120, 1152, 1184, 1216, 1248, 1280, 1312, 1344, 1376, 1408, 1344, 1344, 1440, 1472, 1504, 1536, 1568, 1344, 1344, 1600, 1632, 1664, 1696, 1728, 1760, 1792, 1824, 1344, 1856, 1888, 1920, 1952, 1984, 2016, 2048, 2080, 2112, 2144, 2176, 2208, 2240, 2272, 2304, 2336, 2368, 2400, 2432, 2464, 2496, 2528, 2560, 2592, 2624, 2656, 2688, 2720, 2752, 2784, 2816, 2848, 2880, 2912, 2944, 2976, 3008, 3040, 3072, 3104, 3136, 3168, 3200, 3232, 3264, 3296, 3328, 3360, 3392, 3296, 3424, 3456, 3488, 3520, 3552, 3584, 3616, 3296, 1344, 3648, 3680, 3712, 3744, 3776, 3808, 3840, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 3872, 1344, 3904, 3936, 3968, 1344, 4000, 1344, 4032, 4064, 4096, 4128, 4128, 4160, 4192, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 4224, 4256, 1344, 1344, 4288, 4320, 4352, 4384, 4416, 1344, 4448, 4480, 4512, 4544, 1344, 4576, 4608, 4640, 4672, 1344, 4704, 4736, 4768, 4800, 4832, 1344, 4864, 4896, 4928, 4960, 1344, 4992, 5024, 5056, 5088, 5120, 3296, 5152, 5184, 5216, 5248, 5280, 5312, 1344, 5344, 1344, 5376, 5408, 5440, 5472, 5504, 5536, 5568, 5600, 5632, 5664, 5696, 5728, 5664, 704, 704, 224, 224, 224, 224, 5760, 224, 224, 224, 5792, 5824, 5856, 5888, 5920, 5952, 5984, 6016, 6048, 6080, 6112, 6144, 6176, 6208, 6240, 6272, 6304, 6336, 6368, 6400, 6432, 6464, 6496, 6528, 6560, 6560, 6560, 6560, 6560, 6560, 6560, 6560, 6592, 6624, 4928, 6656, 6688, 6720, 6752, 6784, 4928, 6816, 6848, 6880, 6912, 6944, 6976, 7008, 4928, 4928, 4928, 4928, 4928, 7040, 7072, 7104, 4928, 4928, 4928, 7136, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 7168, 7200, 4928, 7232, 7264, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 6560, 6560, 6560, 6560, 7296, 6560, 7328, 7360, 6560, 6560, 6560, 6560, 6560, 6560, 6560, 6560, 4928, 7392, 7424, 7456, 7488, 4928, 4928, 4928, 7520, 7552, 7584, 7616, 224, 224, 224, 7648, 7680, 7712, 1344, 7744, 7776, 7808, 7808, 704, 7840, 7872, 7904, 3296, 7936, 4928, 4928, 7968, 4928, 4928, 4928, 4928, 4928, 4928, 8000, 8032, 8064, 8096, 3200, 1344, 8128, 4192, 1344, 8160, 8192, 8224, 1344, 1344, 8256, 1344, 4928, 8288, 8320, 8352, 8384, 4928, 8352, 8416, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 4928, 4928, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 8448, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 8480, 4928, 8512, 5440, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 8544, 8576, 224, 8608, 8640, 1344, 1344, 8672, 8704, 8736, 224, 8768, 8800, 8832, 8864, 8896, 8928, 8960, 1344, 8992, 9024, 9056, 9088, 9120, 1632, 9152, 9184, 9216, 1920, 9248, 9280, 9312, 1344, 9344, 9376, 9408, 1344, 9440, 9472, 9504, 9536, 9568, 9600, 9632, 9664, 9664, 1344, 9696, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 9728, 9760, 9792, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9824, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 9856, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 9888, 1344, 1344, 9920, 3296, 9952, 9984, 10016, 1344, 1344, 10048, 10080, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 10112, 10144, 1344, 10176, 1344, 10208, 10240, 10272, 10304, 10336, 10368, 1344, 1344, 1344, 10400, 10432, 64, 10464, 10496, 10528, 4736, 10560, 10592 #if TCL_UTF_MAX > 3 || TCL_MAJOR_VERSION > 8 || TCL_MINOR_VERSION > 6 ,10624, 10656, 10688, 3296, 1344, 1344, 1344, 10720, 10752, 10784, 10816, 10848, 10880, 10912, 8032, 10944, 3296, 3296, 3296, 3296, 9216, 1344, 10976, 11008, 1344, 11040, 11072, 11104, 11136, 1344, 11168, 3296, 11200, 11232, 11264, 1344, 11296, 11328, 11360, 11392, 1344, 11424, 1344, 11456, 11488, 11520, 1344, 11552, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 7776, 4704, 11584, 11616, 11648, 3296, 3296, 11680, 11712, 11744, 11776, 4736, 11808, 3296, 11840, 11872, 11904, 3296, 3296, 1344, 11936, 11968, 6880, 12000, 12032, 12064, 12096, 12128, 3296, 12160, 12192, 1344, 12224, 12256, 12288, 12320, 12352, 3296, 3296, 1344, 1344, 12384, 3296, 12416, 12448, 12480, 12512, 1344, 12544, 12576, 12608, 12640, 3296, 3296, 3296, 3296, 3296, 3296, 12672, 1344, 12704, 12736, 12768, 12128, 12800, 12832, 12864, 12896, 12864, 12928, 7776, 12960, 12992, 13024, 13056, 5280, 13088, 13120, 13152, 13184, 13216, 13248, 13280, 5280, 13312, 13344, 13376, 13408, 13440, 13472, 3296, 13504, 13536, 13568, 13600, 13632, 13664, 13696, 13728, 13760, 13792, 13824, 13856, 1344, 13888, 13920, 13952, 1344, 13984, 14016, 3296, 3296, 3296, 3296, 3296, 1344, 14048, 14080, 3296, 1344, 14112, 14144, 14176, 1344, 14208, 14240, 14272, 14304, 14336, 14368, 3296, 3296, 3296, 3296, 3296, 1344, 14400, 3296, 3296, 3296, 14432, 14464, 14496, 14528, 14560, 14592, 3296, 3296, 14624, 14656, 14688, 14720, 14752, 14784, 1344, 14816, 14848, 1344, 4608, 14880, 3296, 3296, 3296, 3296, 3296, 1344, 14912, 14944, 14976, 15008, 15040, 15072, 15104, 3296, 3296, 15136, 15168, 15200, 15232, 15264, 15296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 15328, 15360, 15392, 15424, 3296, 3296, 15456, 15488, 15520, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 9920, 3296, 3296, 3296, 10816, 10816, 10816, 15552, 1344, 1344, 1344, 1344, 1344, 1344, 15584, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 12864, 1344, 1344, 15616, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 15648, 15680, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 10720, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 14368, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 15712, 15744, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 4608, 4736, 15776, 1344, 4736, 15808, 15840, 1344, 15872, 15904, 15936, 15968, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 16000, 16032, 3296, 3296, 3296, 3296, 3296, 3296, 14432, 14464, 16064, 3296, 3296, 3296, 1344, 1344, 16096, 16128, 16160, 3296, 3296, 16192, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 16224, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 4704, 16256, 12384, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 16288, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 16320, 16352, 16384, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 9792, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 1344, 1344, 1344, 16416, 16448, 16480, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 16512, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 7968, 3296, 3296, 704, 16544, 16576, 4928, 4928, 4928, 16608, 3296, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 8000, 4928, 16640, 4928, 16672, 16704, 16736, 4928, 6848, 4928, 4928, 16768, 3296, 3296, 3296, 16800, 16800, 4928, 4928, 16832, 16864, 3296, 3296, 3296, 3296, 16896, 16928, 16960, 16992, 17024, 17056, 17088, 17120, 17152, 17184, 17216, 17248, 17280, 16896, 16928, 17312, 16992, 17344, 17376, 17408, 17120, 17440, 17472, 17504, 17536, 17568, 17600, 17632, 17664, 17696, 17728, 17760, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 704, 17792, 704, 17824, 17856, 17888, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 17920, 17952, 3296, 3296, 3296, 3296, 3296, 3296, 17984, 18016, 5664, 18048, 18080, 3296, 3296, 3296, 1344, 18112, 18144, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 12864, 18176, 1344, 18208, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 12864, 18240, 3296, 3296, 3296, 3296, 3296, 3296, 12864, 18272, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 18304, 1344, 1344, 1344, 1344, 1344, 1344, 18336, 3296, 18368, 18400, 18432, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 18464, 6880, 18496, 3296, 3296, 18528, 18560, 3296, 3296, 3296, 3296, 3296, 3296, 18592, 18624, 18656, 18688, 18720, 18752, 3296, 18784, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 4928, 18816, 4928, 4928, 7968, 18848, 18880, 8000, 18912, 4928, 4928, 4928, 4928, 18944, 3296, 18976, 19008, 19040, 19072, 19104, 3296, 3296, 3296, 3296, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 19136, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 19168, 19200, 4928, 4928, 4928, 19232, 4928, 4928, 19264, 19296, 18816, 4928, 19328, 4928, 19360, 19392, 19424, 3296, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 4928, 7968, 19456, 19488, 4928, 19520, 19552, 4928, 4928, 4928, 4928, 19584, 4928, 4928, 16512, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 3296, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 9920, 1344, 1344, 1344, 1344, 1344, 1344, 11296, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 19616, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 19648, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 11296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 11296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 3296, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1792, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 1344, 15968 #endif /* TCL_UTF_MAX > 3 */ }; /* * The groupMap is indexed by combining the alternate page number with * the page offset and returns a group number that identifies a unique * set of character attributes. */ static const unsigned char groupMap[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 4, 3, 3, 3, 5, 6, 3, 7, 3, 8, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 3, 7, 7, 7, 3, 3, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 5, 3, 6, 11, 12, 11, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 5, 7, 6, 7, 1, 2, 3, 4, 4, 4, 4, 14, 3, 11, 14, 15, 16, 7, 17, 14, 11, 14, 7, 18, 18, 11, 19, 3, 3, 11, 18, 15, 20, 18, 18, 18, 3, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 7, 10, 10, 10, 10, 10, 10, 10, 21, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 7, 13, 13, 13, 13, 13, 13, 13, 22, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 25, 26, 23, 24, 23, 24, 23, 24, 21, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 21, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 27, 23, 24, 23, 24, 23, 24, 28, 29, 30, 23, 24, 23, 24, 31, 23, 24, 32, 32, 23, 24, 21, 33, 34, 35, 23, 24, 32, 36, 37, 38, 39, 23, 24, 40, 41, 38, 42, 43, 44, 23, 24, 23, 24, 23, 24, 45, 23, 24, 45, 21, 21, 23, 24, 45, 23, 24, 46, 46, 23, 24, 23, 24, 47, 23, 24, 21, 15, 23, 24, 21, 48, 15, 15, 15, 15, 49, 50, 51, 49, 50, 51, 49, 50, 51, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 52, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 21, 49, 50, 51, 23, 24, 53, 54, 23, 24, 23, 24, 23, 24, 23, 24, 55, 21, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 21, 21, 21, 21, 21, 21, 56, 23, 24, 57, 58, 59, 59, 23, 24, 60, 61, 62, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 63, 64, 65, 66, 67, 21, 68, 68, 21, 69, 21, 70, 71, 21, 21, 21, 68, 72, 21, 73, 74, 75, 76, 21, 77, 78, 76, 79, 80, 21, 21, 78, 21, 81, 82, 21, 21, 83, 21, 21, 21, 21, 21, 21, 21, 84, 21, 21, 85, 21, 86, 85, 21, 21, 21, 87, 85, 88, 89, 89, 90, 21, 21, 21, 21, 21, 91, 21, 15, 21, 21, 21, 21, 21, 21, 21, 21, 92, 93, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 11, 11, 11, 11, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 94, 94, 94, 94, 94, 11, 11, 11, 11, 11, 11, 11, 94, 11, 94, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 96, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 23, 24, 23, 24, 94, 11, 23, 24, 0, 0, 94, 43, 43, 43, 3, 97, 0, 0, 0, 0, 11, 11, 98, 3, 99, 99, 99, 0, 100, 0, 101, 101, 21, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 102, 103, 103, 103, 21, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 104, 13, 13, 13, 13, 13, 13, 13, 13, 13, 105, 106, 106, 107, 108, 109, 110, 110, 110, 111, 112, 113, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 114, 115, 116, 117, 118, 119, 7, 23, 24, 120, 23, 24, 21, 55, 55, 55, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 23, 24, 14, 95, 95, 95, 95, 95, 122, 122, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 123, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 124, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 0, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 0, 0, 94, 3, 3, 3, 3, 3, 3, 21, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 21, 21, 3, 8, 0, 0, 14, 14, 4, 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 8, 95, 3, 95, 95, 3, 95, 95, 3, 95, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 15, 15, 15, 15, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 7, 7, 7, 3, 3, 4, 3, 3, 14, 14, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 3, 17, 3, 3, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 94, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 3, 3, 3, 15, 15, 95, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 3, 15, 95, 95, 95, 95, 95, 95, 95, 17, 14, 95, 95, 95, 95, 95, 95, 94, 94, 95, 95, 14, 95, 95, 95, 95, 15, 15, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 15, 15, 15, 14, 14, 15, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 17, 15, 95, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 95, 95, 95, 94, 94, 14, 3, 3, 3, 94, 0, 0, 95, 4, 4, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 94, 95, 95, 95, 95, 95, 95, 95, 95, 95, 94, 95, 95, 95, 94, 95, 95, 95, 95, 95, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 0, 0, 3, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 11, 15, 15, 15, 15, 15, 15, 0, 17, 17, 0, 0, 0, 0, 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 15, 15, 15, 15, 15, 15, 15, 15, 15, 94, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 17, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 127, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 127, 95, 15, 127, 127, 127, 95, 95, 95, 95, 95, 95, 95, 95, 127, 127, 127, 127, 95, 127, 127, 15, 95, 95, 95, 95, 95, 95, 95, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 94, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 127, 127, 0, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 0, 0, 0, 15, 15, 15, 15, 0, 0, 95, 15, 127, 127, 127, 95, 95, 95, 95, 0, 0, 127, 127, 0, 0, 127, 127, 95, 15, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 15, 15, 0, 15, 15, 15, 95, 95, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 15, 15, 4, 4, 18, 18, 18, 18, 18, 18, 14, 4, 15, 3, 95, 0, 0, 95, 95, 127, 0, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 0, 15, 15, 0, 15, 15, 0, 0, 95, 0, 127, 127, 127, 95, 95, 0, 0, 0, 0, 95, 95, 0, 0, 95, 95, 95, 0, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 0, 15, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 95, 95, 15, 15, 15, 95, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 95, 127, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 0, 15, 15, 15, 15, 15, 0, 0, 95, 15, 127, 127, 127, 95, 95, 95, 95, 95, 0, 95, 95, 127, 0, 127, 127, 95, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 95, 95, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 4, 0, 0, 0, 0, 0, 0, 0, 15, 95, 95, 95, 95, 95, 95, 0, 95, 127, 127, 0, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 0, 15, 15, 15, 15, 15, 0, 0, 95, 15, 127, 95, 127, 95, 95, 95, 95, 0, 0, 127, 127, 0, 0, 127, 127, 95, 0, 0, 0, 0, 0, 0, 0, 95, 95, 127, 0, 0, 0, 0, 15, 15, 0, 15, 15, 15, 95, 95, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 14, 15, 18, 18, 18, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 15, 0, 15, 15, 15, 15, 15, 15, 0, 0, 0, 15, 15, 15, 0, 15, 15, 15, 15, 0, 0, 0, 15, 15, 0, 15, 0, 15, 15, 0, 0, 0, 15, 15, 0, 0, 0, 15, 15, 15, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 127, 127, 95, 127, 127, 0, 0, 0, 127, 127, 127, 0, 127, 127, 127, 95, 0, 0, 15, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 18, 18, 18, 14, 14, 14, 14, 14, 14, 4, 14, 0, 0, 0, 0, 0, 95, 127, 127, 127, 95, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 95, 15, 95, 95, 95, 127, 127, 127, 127, 0, 95, 95, 95, 0, 95, 95, 95, 95, 0, 0, 0, 0, 0, 0, 0, 95, 95, 0, 15, 15, 15, 0, 0, 15, 0, 0, 15, 15, 95, 95, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 3, 18, 18, 18, 18, 18, 18, 18, 14, 15, 95, 127, 127, 3, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 0, 0, 95, 15, 127, 95, 127, 127, 127, 127, 127, 0, 95, 127, 127, 0, 127, 127, 95, 95, 0, 0, 0, 0, 0, 0, 0, 127, 127, 0, 0, 0, 0, 0, 0, 15, 15, 0, 15, 15, 95, 95, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 15, 15, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 95, 127, 127, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 15, 127, 127, 127, 95, 95, 95, 95, 0, 127, 127, 127, 0, 127, 127, 127, 95, 15, 14, 0, 0, 0, 0, 15, 15, 15, 127, 18, 18, 18, 18, 18, 18, 18, 15, 15, 15, 95, 95, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 18, 18, 18, 18, 18, 18, 18, 18, 18, 14, 15, 15, 15, 15, 15, 15, 0, 95, 127, 127, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 95, 0, 0, 0, 0, 127, 127, 127, 95, 95, 95, 0, 95, 0, 127, 127, 127, 127, 127, 127, 127, 127, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 127, 127, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 15, 15, 95, 95, 95, 95, 95, 95, 95, 0, 0, 0, 0, 4, 15, 15, 15, 15, 15, 15, 94, 95, 95, 95, 95, 95, 95, 95, 95, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 0, 15, 0, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 15, 15, 95, 95, 95, 95, 95, 95, 95, 95, 95, 15, 0, 0, 15, 15, 15, 15, 15, 0, 94, 0, 95, 95, 95, 95, 95, 95, 95, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 15, 15, 15, 15, 15, 14, 14, 14, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 14, 3, 14, 14, 14, 95, 95, 14, 14, 14, 14, 14, 14, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 14, 95, 14, 95, 14, 95, 5, 6, 5, 6, 127, 127, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 127, 95, 95, 95, 95, 95, 3, 95, 95, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 0, 14, 14, 14, 14, 14, 14, 14, 14, 95, 14, 14, 14, 14, 14, 14, 0, 14, 14, 3, 3, 3, 3, 3, 14, 14, 14, 14, 3, 3, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 95, 95, 95, 95, 127, 95, 95, 95, 95, 95, 95, 127, 95, 95, 127, 127, 95, 95, 15, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 3, 3, 3, 3, 3, 15, 15, 15, 15, 15, 15, 127, 127, 95, 95, 15, 15, 15, 15, 95, 95, 95, 15, 127, 127, 127, 15, 15, 127, 127, 127, 127, 127, 127, 127, 15, 15, 15, 95, 95, 95, 95, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 127, 127, 95, 95, 127, 127, 127, 127, 127, 127, 95, 15, 127, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 127, 127, 127, 95, 14, 14, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 0, 128, 0, 0, 0, 0, 0, 128, 0, 0, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 3, 94, 129, 129, 129, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 0, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 0, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 95, 95, 95, 3, 3, 3, 3, 3, 3, 3, 3, 3, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 130, 107, 107, 107, 107, 107, 107, 0, 0, 113, 113, 113, 113, 113, 113, 0, 0, 8, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 2, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 5, 6, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 3, 3, 3, 131, 131, 131, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 127, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 0, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 127, 95, 95, 95, 95, 95, 95, 95, 127, 127, 127, 127, 127, 127, 127, 127, 95, 127, 127, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 3, 3, 3, 94, 3, 3, 3, 4, 15, 95, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 8, 3, 3, 3, 3, 95, 95, 95, 17, 95, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 15, 15, 15, 94, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 95, 95, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 15, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 95, 95, 95, 127, 127, 127, 127, 95, 95, 127, 127, 127, 0, 0, 0, 0, 127, 127, 95, 127, 127, 127, 127, 127, 127, 95, 95, 95, 0, 0, 0, 0, 14, 0, 0, 0, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 18, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 127, 127, 95, 0, 0, 3, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 95, 127, 95, 95, 95, 95, 95, 95, 95, 0, 95, 127, 95, 127, 127, 95, 95, 95, 95, 95, 95, 95, 95, 127, 127, 127, 127, 127, 127, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 0, 0, 95, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 94, 3, 3, 3, 3, 3, 3, 0, 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 122, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 95, 95, 95, 127, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 127, 95, 95, 95, 95, 95, 127, 95, 127, 127, 127, 127, 127, 95, 127, 127, 15, 15, 15, 15, 15, 15, 15, 15, 0, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 3, 3, 3, 3, 3, 3, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 95, 95, 95, 95, 95, 95, 95, 95, 95, 14, 14, 14, 14, 14, 14, 14, 14, 14, 3, 3, 3, 95, 95, 127, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 95, 95, 95, 95, 127, 127, 95, 95, 127, 95, 95, 95, 15, 15, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 127, 95, 95, 127, 127, 127, 95, 127, 95, 95, 95, 127, 127, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 15, 15, 15, 15, 127, 127, 127, 127, 127, 127, 127, 127, 95, 95, 95, 95, 95, 95, 95, 95, 127, 127, 95, 95, 0, 0, 0, 3, 3, 3, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 15, 15, 15, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 94, 94, 94, 94, 94, 94, 3, 3, 132, 133, 134, 135, 135, 136, 137, 138, 139, 23, 24, 0, 0, 0, 0, 0, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 0, 0, 140, 140, 140, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 95, 95, 95, 3, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 127, 95, 95, 95, 95, 95, 95, 95, 15, 15, 15, 15, 95, 15, 15, 15, 15, 15, 15, 95, 15, 15, 127, 95, 95, 15, 0, 0, 0, 0, 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 94, 141, 21, 21, 21, 142, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 143, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 94, 94, 94, 94, 94, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 21, 21, 21, 21, 21, 144, 21, 21, 145, 21, 146, 146, 146, 146, 146, 146, 146, 146, 147, 147, 147, 147, 147, 147, 147, 147, 146, 146, 146, 146, 146, 146, 0, 0, 147, 147, 147, 147, 147, 147, 0, 0, 146, 146, 146, 146, 146, 146, 146, 146, 147, 147, 147, 147, 147, 147, 147, 147, 146, 146, 146, 146, 146, 146, 146, 146, 147, 147, 147, 147, 147, 147, 147, 147, 146, 146, 146, 146, 146, 146, 0, 0, 147, 147, 147, 147, 147, 147, 0, 0, 21, 146, 21, 146, 21, 146, 21, 146, 0, 147, 0, 147, 0, 147, 0, 147, 146, 146, 146, 146, 146, 146, 146, 146, 147, 147, 147, 147, 147, 147, 147, 147, 148, 148, 149, 149, 149, 149, 150, 150, 151, 151, 152, 152, 153, 153, 0, 0, 146, 146, 146, 146, 146, 146, 146, 146, 154, 154, 154, 154, 154, 154, 154, 154, 146, 146, 146, 146, 146, 146, 146, 146, 154, 154, 154, 154, 154, 154, 154, 154, 146, 146, 146, 146, 146, 146, 146, 146, 154, 154, 154, 154, 154, 154, 154, 154, 146, 146, 21, 155, 21, 0, 21, 21, 147, 147, 156, 156, 157, 11, 158, 11, 11, 11, 21, 155, 21, 0, 21, 21, 159, 159, 159, 159, 157, 11, 11, 11, 146, 146, 21, 21, 0, 0, 21, 21, 147, 147, 160, 160, 0, 11, 11, 11, 146, 146, 21, 21, 21, 116, 21, 21, 147, 147, 161, 161, 120, 11, 11, 11, 0, 0, 21, 155, 21, 0, 21, 21, 162, 162, 163, 163, 157, 11, 11, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 17, 17, 17, 17, 17, 8, 8, 8, 8, 8, 8, 3, 3, 16, 20, 5, 16, 16, 20, 5, 16, 3, 3, 3, 3, 3, 3, 3, 3, 164, 165, 17, 17, 17, 17, 17, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 16, 20, 3, 3, 3, 3, 12, 12, 3, 3, 3, 7, 5, 6, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 12, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 17, 17, 17, 17, 17, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 94, 0, 0, 18, 18, 18, 18, 18, 18, 7, 7, 7, 5, 6, 94, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 7, 7, 7, 5, 6, 0, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 122, 122, 122, 122, 95, 122, 122, 122, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 110, 14, 14, 14, 14, 110, 14, 14, 21, 110, 110, 110, 21, 21, 110, 110, 110, 21, 14, 110, 14, 14, 7, 110, 110, 110, 110, 110, 14, 14, 14, 14, 14, 14, 110, 14, 166, 14, 110, 14, 167, 168, 110, 110, 14, 21, 110, 110, 169, 110, 21, 15, 15, 15, 15, 21, 14, 14, 21, 21, 110, 110, 7, 7, 7, 7, 7, 110, 21, 21, 21, 21, 14, 7, 14, 14, 170, 14, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 131, 131, 131, 23, 24, 131, 131, 131, 131, 18, 14, 14, 0, 0, 0, 0, 7, 7, 7, 7, 7, 14, 14, 14, 14, 14, 7, 7, 14, 14, 14, 14, 7, 14, 14, 7, 14, 14, 7, 14, 14, 14, 14, 14, 14, 14, 7, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 7, 7, 14, 14, 7, 14, 7, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 14, 14, 14, 14, 14, 14, 14, 14, 5, 6, 5, 6, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 7, 7, 14, 14, 14, 14, 14, 14, 14, 5, 6, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 7, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 7, 7, 7, 7, 7, 7, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 174, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 7, 14, 14, 14, 14, 14, 14, 14, 14, 14, 7, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 7, 7, 7, 7, 7, 7, 7, 7, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 7, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 7, 7, 7, 7, 7, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 5, 6, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 5, 6, 7, 7, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 14, 14, 7, 7, 7, 7, 7, 7, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 23, 24, 175, 176, 177, 178, 179, 23, 24, 23, 24, 23, 24, 180, 181, 182, 183, 21, 23, 24, 21, 23, 24, 21, 21, 21, 21, 21, 94, 94, 184, 184, 23, 24, 23, 24, 21, 14, 14, 14, 14, 14, 14, 23, 24, 23, 24, 95, 95, 95, 23, 24, 0, 0, 0, 0, 0, 3, 3, 3, 3, 18, 3, 3, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 0, 185, 0, 0, 0, 0, 0, 185, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 94, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 0, 3, 3, 16, 20, 16, 20, 3, 3, 3, 16, 20, 3, 16, 20, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 3, 3, 8, 3, 16, 20, 3, 3, 16, 20, 5, 6, 5, 6, 5, 6, 5, 6, 3, 3, 3, 3, 3, 94, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 8, 3, 3, 3, 3, 8, 3, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 14, 14, 3, 3, 3, 5, 6, 5, 6, 5, 6, 5, 6, 8, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 2, 3, 3, 3, 14, 94, 15, 131, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 14, 14, 5, 6, 5, 6, 5, 6, 5, 6, 8, 5, 6, 6, 14, 131, 131, 131, 131, 131, 131, 131, 131, 131, 95, 95, 95, 95, 127, 127, 8, 94, 94, 94, 94, 94, 14, 14, 131, 131, 131, 94, 15, 3, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 95, 95, 11, 11, 94, 94, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 3, 94, 94, 94, 15, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 14, 14, 18, 18, 18, 18, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 18, 18, 18, 18, 18, 18, 18, 18, 14, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 94, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 94, 3, 3, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 15, 95, 122, 122, 122, 3, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 3, 94, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 94, 94, 95, 95, 15, 15, 15, 15, 15, 15, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 95, 95, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 94, 94, 94, 94, 94, 94, 94, 94, 94, 11, 11, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 21, 21, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 94, 21, 21, 21, 21, 21, 21, 21, 21, 23, 24, 23, 24, 186, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 94, 11, 11, 23, 24, 187, 21, 15, 23, 24, 23, 24, 188, 21, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 189, 190, 191, 192, 189, 21, 193, 194, 195, 196, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 197, 198, 199, 23, 24, 23, 24, 200, 23, 24, 0, 0, 23, 24, 0, 21, 0, 21, 23, 24, 23, 24, 23, 24, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94, 94, 94, 23, 24, 15, 94, 94, 21, 15, 15, 15, 15, 15, 15, 15, 95, 15, 15, 15, 95, 15, 15, 15, 15, 95, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 95, 95, 127, 14, 14, 14, 14, 95, 0, 0, 0, 18, 18, 18, 18, 18, 18, 14, 14, 4, 14, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 127, 127, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 15, 15, 15, 15, 15, 15, 3, 3, 3, 15, 3, 15, 15, 95, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 95, 95, 3, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 127, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 127, 127, 95, 95, 95, 95, 127, 127, 95, 95, 127, 127, 127, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 94, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 3, 3, 15, 15, 15, 15, 15, 95, 94, 15, 15, 15, 15, 15, 15, 15, 15, 15, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 127, 127, 95, 95, 127, 127, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 95, 15, 15, 15, 15, 15, 15, 15, 15, 95, 127, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 3, 3, 3, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 94, 15, 15, 15, 15, 15, 15, 14, 14, 14, 15, 127, 95, 127, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 15, 95, 95, 95, 15, 15, 95, 95, 15, 15, 15, 15, 15, 95, 95, 15, 95, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 94, 3, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 95, 95, 127, 127, 3, 3, 15, 94, 94, 127, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 202, 21, 21, 21, 21, 21, 21, 21, 11, 94, 94, 94, 94, 21, 21, 21, 21, 21, 21, 21, 21, 21, 94, 11, 11, 0, 0, 0, 0, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 15, 15, 15, 127, 127, 95, 127, 127, 95, 127, 127, 3, 127, 95, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 21, 21, 21, 21, 21, 21, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 21, 21, 21, 0, 0, 0, 0, 0, 15, 95, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 7, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 0, 15, 0, 15, 15, 0, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 6, 5, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 4, 14, 14, 14, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 3, 3, 3, 3, 3, 3, 3, 5, 6, 3, 0, 0, 0, 0, 0, 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 3, 8, 8, 12, 12, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 3, 3, 5, 6, 3, 3, 3, 3, 12, 12, 12, 3, 3, 3, 0, 3, 3, 3, 3, 8, 5, 6, 5, 6, 5, 6, 3, 3, 3, 7, 8, 7, 7, 7, 0, 3, 4, 3, 3, 0, 0, 0, 0, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 17, 0, 3, 3, 3, 4, 3, 3, 3, 5, 6, 3, 7, 3, 8, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 3, 7, 7, 7, 3, 11, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 5, 7, 6, 7, 5, 6, 3, 5, 6, 3, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 94, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 94, 94, 0, 0, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 15, 0, 0, 0, 4, 4, 7, 11, 14, 4, 4, 0, 14, 7, 7, 7, 7, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 14, 14, 0, 0 #if TCL_UTF_MAX > 3 || TCL_MAJOR_VERSION > 8 || TCL_MINOR_VERSION > 6 ,15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 18, 18, 18, 18, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 18, 18, 14, 14, 14, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 95, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 0, 18, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 131, 15, 15, 15, 15, 15, 15, 15, 15, 131, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 3, 15, 15, 15, 15, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 3, 131, 131, 131, 131, 131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 0, 0, 0, 0, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 0, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 0, 208, 208, 208, 208, 208, 208, 208, 0, 208, 208, 0, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 0, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 0, 209, 209, 209, 209, 209, 209, 209, 0, 209, 209, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94, 94, 94, 94, 94, 94, 0, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 0, 94, 94, 94, 94, 94, 94, 94, 94, 94, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 0, 0, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 0, 0, 0, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 3, 18, 18, 18, 18, 18, 18, 18, 18, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 14, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 18, 18, 18, 18, 18, 18, 0, 0, 0, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 18, 18, 15, 15, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 15, 95, 95, 95, 0, 95, 95, 0, 0, 0, 0, 0, 95, 95, 95, 95, 15, 15, 15, 15, 0, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 95, 95, 95, 0, 0, 0, 0, 95, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 18, 18, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 18, 18, 18, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 0, 0, 0, 0, 18, 18, 18, 18, 18, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 0, 0, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 15, 15, 15, 15, 95, 95, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 15, 15, 15, 15, 94, 15, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 95, 95, 95, 95, 95, 8, 94, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 95, 95, 8, 0, 0, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 95, 95, 95, 18, 18, 18, 18, 18, 18, 18, 15, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 18, 18, 18, 18, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 95, 127, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 95, 15, 15, 95, 95, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 127, 95, 95, 95, 95, 127, 127, 95, 95, 3, 3, 17, 3, 3, 3, 3, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 95, 95, 95, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 127, 95, 95, 95, 95, 95, 95, 95, 95, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 3, 3, 3, 15, 127, 127, 15, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 3, 3, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 127, 95, 95, 95, 95, 95, 95, 95, 95, 95, 127, 127, 15, 15, 15, 15, 3, 3, 3, 3, 95, 95, 95, 95, 3, 127, 95, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 15, 3, 15, 3, 3, 3, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 127, 95, 95, 95, 127, 127, 95, 127, 95, 95, 3, 3, 3, 3, 3, 3, 95, 15, 15, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 0, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 3, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 127, 127, 127, 95, 95, 95, 95, 95, 95, 95, 95, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 95, 95, 127, 127, 0, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 0, 15, 15, 15, 15, 15, 0, 95, 95, 15, 127, 127, 95, 127, 127, 127, 127, 0, 0, 127, 127, 0, 0, 127, 127, 127, 0, 0, 15, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 127, 127, 0, 0, 95, 95, 95, 95, 95, 95, 95, 0, 0, 0, 95, 95, 95, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 0, 0, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 127, 127, 127, 95, 95, 95, 95, 95, 95, 0, 127, 0, 0, 127, 0, 127, 127, 127, 127, 0, 127, 127, 95, 127, 95, 15, 95, 15, 3, 3, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 127, 95, 95, 95, 95, 95, 95, 95, 95, 127, 127, 95, 95, 95, 127, 95, 15, 15, 15, 15, 3, 3, 3, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 3, 3, 0, 3, 95, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 127, 95, 95, 95, 95, 95, 95, 127, 95, 127, 127, 127, 127, 95, 95, 127, 95, 95, 15, 15, 3, 15, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 127, 95, 95, 95, 95, 0, 0, 127, 127, 127, 127, 95, 95, 127, 95, 95, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 15, 15, 15, 15, 95, 95, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 127, 95, 95, 95, 95, 95, 95, 95, 95, 127, 127, 95, 127, 95, 95, 3, 3, 3, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 127, 95, 127, 127, 95, 95, 95, 95, 95, 95, 127, 95, 15, 3, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 95, 127, 95, 127, 127, 95, 95, 95, 95, 127, 95, 95, 95, 95, 95, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 18, 18, 3, 3, 3, 14, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 127, 95, 95, 95, 95, 95, 95, 95, 95, 95, 127, 95, 95, 3, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 127, 127, 127, 127, 0, 127, 127, 0, 0, 95, 95, 127, 95, 15, 127, 15, 127, 95, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 127, 95, 95, 95, 95, 0, 0, 95, 95, 127, 127, 127, 127, 95, 15, 3, 15, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 127, 15, 95, 95, 95, 95, 3, 3, 3, 3, 3, 3, 3, 3, 95, 0, 0, 0, 0, 0, 0, 0, 0, 15, 95, 95, 95, 95, 95, 95, 127, 127, 95, 95, 95, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 127, 95, 95, 3, 3, 3, 15, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 95, 95, 95, 95, 95, 95, 95, 0, 95, 95, 95, 95, 95, 95, 127, 95, 15, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 3, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 0, 127, 95, 95, 95, 95, 95, 95, 95, 127, 95, 95, 127, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 0, 0, 0, 95, 0, 95, 95, 0, 95, 95, 95, 95, 95, 95, 95, 15, 95, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 0, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 127, 127, 127, 0, 95, 95, 0, 127, 127, 95, 127, 95, 15, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 127, 127, 3, 3, 0, 0, 0, 0, 0, 0, 0, 95, 95, 15, 127, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 127, 127, 95, 95, 95, 95, 95, 0, 0, 0, 127, 127, 95, 127, 95, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 14, 14, 14, 14, 14, 14, 14, 14, 4, 4, 4, 4, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 95, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 127, 127, 127, 95, 95, 95, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 3, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 95, 95, 95, 95, 95, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 95, 95, 95, 3, 3, 3, 3, 3, 14, 14, 14, 14, 94, 94, 94, 94, 3, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 18, 18, 18, 18, 18, 18, 18, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94, 94, 94, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 94, 94, 3, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 3, 3, 3, 3, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 95, 15, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 0, 0, 0, 0, 0, 0, 0, 95, 95, 95, 95, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 3, 94, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94, 94, 94, 94, 0, 94, 94, 94, 94, 94, 94, 94, 0, 94, 94, 0, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 14, 95, 95, 3, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 0, 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 127, 127, 95, 95, 95, 14, 14, 14, 127, 127, 127, 127, 127, 127, 17, 17, 17, 17, 17, 17, 17, 17, 95, 95, 95, 95, 95, 95, 95, 95, 14, 14, 95, 95, 95, 95, 95, 95, 95, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 95, 95, 95, 95, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 95, 95, 95, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 21, 21, 21, 21, 21, 21, 21, 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 110, 0, 110, 110, 0, 0, 110, 0, 0, 110, 110, 0, 0, 110, 110, 110, 110, 0, 110, 110, 110, 110, 110, 110, 110, 110, 21, 21, 21, 21, 0, 21, 0, 21, 21, 21, 21, 21, 21, 21, 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 110, 110, 0, 110, 110, 110, 110, 0, 0, 110, 110, 110, 110, 110, 110, 110, 110, 0, 110, 110, 110, 110, 110, 110, 110, 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 110, 110, 0, 110, 110, 110, 110, 0, 110, 110, 110, 110, 110, 0, 110, 0, 0, 0, 110, 110, 110, 110, 110, 110, 110, 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 110, 110, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 21, 21, 21, 21, 21, 21, 0, 0, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 7, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 7, 21, 21, 21, 21, 21, 21, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 7, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 7, 21, 21, 21, 21, 21, 21, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 7, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 7, 21, 21, 21, 21, 21, 21, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 7, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 7, 21, 21, 21, 21, 21, 21, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 7, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 7, 21, 21, 21, 21, 21, 21, 110, 21, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 14, 14, 14, 14, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 14, 14, 14, 14, 14, 14, 14, 14, 95, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 95, 14, 14, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 95, 95, 95, 95, 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 15, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 0, 0, 0, 0, 0, 0, 21, 21, 21, 21, 21, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 95, 95, 95, 95, 95, 95, 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 0, 0, 95, 95, 95, 95, 95, 95, 95, 0, 95, 95, 0, 95, 95, 95, 95, 95, 0, 0, 0, 0, 0, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 95, 95, 95, 95, 95, 95, 95, 94, 94, 94, 94, 94, 94, 94, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 15, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 95, 95, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 4, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 94, 95, 95, 95, 95, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 95, 95, 15, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 3, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 0, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 95, 95, 95, 95, 95, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 210, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 95, 95, 95, 95, 95, 95, 95, 94, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 14, 18, 18, 18, 4, 18, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 14, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 0, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 0, 15, 0, 0, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 0, 15, 0, 15, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 15, 0, 15, 0, 15, 0, 15, 15, 15, 0, 15, 15, 0, 15, 0, 0, 15, 0, 15, 0, 15, 0, 15, 0, 15, 0, 15, 15, 0, 15, 0, 0, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 0, 15, 15, 15, 15, 0, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 15, 15, 15, 0, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 11, 11, 11, 11, 11, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 #endif /* TCL_UTF_MAX > 3 */ }; /* * Each group represents a unique set of character attributes. The attributes * are encoded into a 32-bit value as follows: * * Bits 0-4 Character category: see the constants listed below. * * Bits 5-7 Case delta type: 000 = identity * 010 = add delta for lower * 011 = add delta for lower, add 1 for title * 100 = subtract delta for title/upper * 101 = sub delta for upper, sub 1 for title * 110 = sub delta for upper, add delta for lower * 111 = subtract delta for upper * * Bits 8-31 Case delta: delta for case conversions. This should be the * highest field so we can easily sign extend. */ static const int groups[] = { 0, 15, 12, 25, 27, 21, 22, 26, 20, 9, 8257, 28, 19, 8322, 29, 5, 23, 16, 11, -190078, 24, 2, -30846, 321, 386, -50879, 59522, -30911, 76930, -49790, 53825, 52801, 52545, 20289, 51777, 52033, 53057, -24702, 54081, 53569, -41598, -10895486, 54593, -33150, 54849, 55873, 55617, 56129, -14206, 609, 451, 674, 20354, -24767, -14271, -33215, 2763585, -41663, 2762817, -2768510, -49855, 17729, 18241, -2760318, -2759550, -2760062, 53890, 52866, 52610, 51842, 52098, -10833534, -10832510, 53122, -10839678, -10823550, -10830718, 53634, 54146, -2750078, -10829950, -2751614, 54658, 54914, -2745982, 55938, -10830462, -10824062, 17794, 55682, 18306, 56194, -10818686, -10817918, 4, 6, -21370, 29761, 9793, 9537, 16449, 16193, 9858, 9602, 8066, 16514, 16258, 2113, 16002, 14722, 1, 12162, 13954, 2178, 22146, 20610, -1662, 29826, -15295, 24706, -1727, 20545, 7, 3905, 3970, 12353, 12418, 8, 1859649, -769822, 9949249, 10, 1601154, 1600898, 1598594, 1598082, 1598338, 1596546, 1582466, -9027966, -769983, -9044862, -976254, -9058174, 15234, -1949375, -1918, -1983, -18814, -21886, -25470, -32638, -28542, -32126, -1981, -2174, -18879, -2237, 1844610, -21951, -25535, -28607, -32703, -32191, 13, 14, -1924287, -2145983, -2115007, 7233, 7298, 4170, 4234, 6749, 6813, -2750143, -976319, -2746047, 2763650, 2762882, -2759615, -2751679, -2760383, -2760127, -2768575, 1859714, -9044927, -10823615, -12158, -10830783, -10833599, -10832575, -10830015, -10817983, -10824127, -10818751, 237633, -12223, -10830527, -9058239, -10839743, -10895551, 237698, 9949314, 18, 17, 10305, 10370, 10049, 10114, 8769, 8834 }; #if TCL_UTF_MAX > 3 || TCL_MAJOR_VERSION > 8 || TCL_MINOR_VERSION > 6 # define UNICODE_OUT_OF_RANGE(ch) (((ch) & 0x1FFFFF) >= 0x323C0) #else # define UNICODE_OUT_OF_RANGE(ch) (((ch) & 0x1F0000) != 0) #endif /* * The following constants are used to determine the category of a * Unicode character. */ enum { UNASSIGNED, UPPERCASE_LETTER, LOWERCASE_LETTER, TITLECASE_LETTER, MODIFIER_LETTER, OTHER_LETTER, NON_SPACING_MARK, ENCLOSING_MARK, COMBINING_SPACING_MARK, DECIMAL_DIGIT_NUMBER, LETTER_NUMBER, OTHER_NUMBER, SPACE_SEPARATOR, LINE_SEPARATOR, PARAGRAPH_SEPARATOR, CONTROL, FORMAT, PRIVATE_USE, SURROGATE, CONNECTOR_PUNCTUATION, DASH_PUNCTUATION, OPEN_PUNCTUATION, CLOSE_PUNCTUATION, INITIAL_QUOTE_PUNCTUATION, FINAL_QUOTE_PUNCTUATION, OTHER_PUNCTUATION, MATH_SYMBOL, CURRENCY_SYMBOL, MODIFIER_SYMBOL, OTHER_SYMBOL }; /* * The following macros extract the fields of the character info. The * GetDelta() macro is complicated because we can't rely on the C compiler * to do sign extension on right shifts. */ #define GetCaseType(info) (((info) & 0xE0) >> 5) #define GetCategory(ch) (GetUniCharInfo(ch) & 0x1F) #define GetDelta(info) ((info) >> 8) /* * This macro extracts the information about a character from the * Unicode character tables. */ #if TCL_UTF_MAX > 3 || TCL_MAJOR_VERSION > 8 || TCL_MINOR_VERSION > 6 # define GetUniCharInfo(ch) (groups[groupMap[pageMap[((ch) & 0x1FFFFF) >> OFFSET_BITS] | ((ch) & ((1 << OFFSET_BITS)-1))]]) #else # define GetUniCharInfo(ch) (groups[groupMap[pageMap[((ch) & 0xFFFF) >> OFFSET_BITS] | ((ch) & ((1 << OFFSET_BITS)-1))]]) #endif tcl9.0.1/generic/tclUtf.c0000644000175000017500000020431014731032403014562 0ustar sergeisergei/* * tclUtf.c -- * * Routines for manipulating UTF-8 strings. * * Copyright © 1997-1998 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" /* * Include the static character classification tables and macros. */ #include "tclUniData.c" /* * The following masks are used for fast character category tests. The x_BITS * values are shifted right by the category value to determine whether the * given category is included in the set. */ enum UnicodeCharacterCategoryMasks { ALPHA_BITS = (1 << UPPERCASE_LETTER) | (1 << LOWERCASE_LETTER) | (1 << TITLECASE_LETTER) | (1 << MODIFIER_LETTER) | (1 << OTHER_LETTER), CONTROL_BITS = (1 << CONTROL) | (1 << FORMAT), DIGIT_BITS = (1 << DECIMAL_DIGIT_NUMBER), SPACE_BITS = (1 << SPACE_SEPARATOR) | (1 << LINE_SEPARATOR) | (1 << PARAGRAPH_SEPARATOR), WORD_BITS = ALPHA_BITS | DIGIT_BITS | (1 << CONNECTOR_PUNCTUATION), PUNCT_BITS = (1 << CONNECTOR_PUNCTUATION) | (1 << DASH_PUNCTUATION) | (1 << OPEN_PUNCTUATION) | (1 << CLOSE_PUNCTUATION) | (1 << INITIAL_QUOTE_PUNCTUATION) | (1 << FINAL_QUOTE_PUNCTUATION) | (1 << OTHER_PUNCTUATION), GRAPH_BITS = WORD_BITS | PUNCT_BITS | (1 << NON_SPACING_MARK) | (1 << ENCLOSING_MARK) | (1 << COMBINING_SPACING_MARK) | (1 << LETTER_NUMBER) | (1 << OTHER_NUMBER) | (1 << MATH_SYMBOL) | (1 << CURRENCY_SYMBOL) | (1 << MODIFIER_SYMBOL) | (1 << OTHER_SYMBOL) }; /* * Unicode characters less than this value are represented by themselves in * UTF-8 strings. */ #define UNICODE_SELF 0x80 /* * The following structures are used when mapping between Unicode and * UTF-8. */ static const unsigned char totalBytes[256] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1 }; static const unsigned char complete[256] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* Tcl_UtfCharComplete() might point to 2nd byte of valid 4-byte sequence */ 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, /* End of "continuation byte section" */ 2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1 }; /* * Functions used only in this module. */ static int Invalid(const char *src); /* *--------------------------------------------------------------------------- * * TclUtfCount -- * * Find the number of bytes in the Utf character "ch". * * Results: * The return values is the number of bytes in the Utf character "ch". * * Side effects: * None. * *--------------------------------------------------------------------------- */ int TclUtfCount( int ch) /* The Unicode character whose size is returned. */ { if ((unsigned)(ch - 1) < (UNICODE_SELF - 1)) { return 1; } if (ch <= 0x7FF) { return 2; } if (((unsigned)(ch - 0x10000) <= 0xFFFFF)) { return 4; } return 3; } /* *--------------------------------------------------------------------------- * * Invalid -- * * Given a pointer to a two-byte prefix of a well-formed UTF-8 byte * sequence (a lead byte followed by a trail byte) this routine * examines those two bytes to determine whether the sequence is * invalid in UTF-8. This might be because it is an overlong * encoding, or because it encodes something out of the proper range. * * Given a pointer to the bytes \xF8 or \xFC , this routine will * try to read beyond the end of the "bounds" table. Callers must * prevent this. * * Given a pointer to something else (an ASCII byte, a trail byte, * or another byte that can never begin a valid byte sequence such * as \xF5) this routine returns false. That makes the routine poorly * named, as it does not detect and report all invalid sequences. * * Callers have to take care that this routine does something useful * for their needs. * * Results: * A boolean. *--------------------------------------------------------------------------- */ static const unsigned char bounds[28] = { 0x80, 0x80, /* \xC0 accepts \x80 only */ 0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF, /* (\xC4 - \xDC) -- all sequences valid */ 0xA0, 0xBF, /* \xE0\x80 through \xE0\x9F are invalid prefixes */ 0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF, /* (\xE4 - \xEC) -- all valid */ 0x90, 0xBF, /* \xF0\x80 through \xF0\x8F are invalid prefixes */ 0x80, 0x8F /* \xF4\x90 and higher are invalid prefixes */ }; static int Invalid( const char *src) /* Points to lead byte of a UTF-8 byte sequence */ { unsigned char byte = UCHAR(*src); int index; if ((byte & 0xC3) == 0xC0) { /* Only lead bytes 0xC0, 0xE0, 0xF0, 0xF4 need examination */ index = (byte - 0xC0) >> 1; if (UCHAR(src[1]) < bounds[index] || UCHAR(src[1]) > bounds[index+1]) { /* Out of bounds - report invalid. */ return 1; } } return 0; } /* *--------------------------------------------------------------------------- * * Tcl_UniCharToUtf -- * * Stores the given Tcl_UniChar as a sequence of UTF-8 bytes in the provided * buffer. Equivalent to Plan 9 runetochar(). * * Surrogate pairs are handled as follows: When ch is a high surrogate, * the first byte of the 4-byte UTF-8 sequence is stored in the buffer and * the function returns 1. If the function is called again with a low * surrogate and the same buffer, the remaining 3 bytes of the 4-byte * UTF-8 sequence are produced. * * If no low surrogate follows the high surrogate (which is actually illegal), * calling Tcl_UniCharToUtf again with ch being -1 produces a 3-byte UTF-8 * sequence representing the high surrogate. * * Results: * Returns the number of bytes stored into the buffer. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Size Tcl_UniCharToUtf( int ch, /* The Tcl_UniChar to be stored in the buffer. * Can be or'ed with flag TCL_COMBINE. */ char *buf) /* Buffer in which the UTF-8 representation of * ch is stored. Must be large enough to hold * the UTF-8 character (at most 4 bytes). */ { int flags = ch; if (ch >= TCL_COMBINE) { ch &= (TCL_COMBINE - 1); } if ((unsigned)(ch - 1) < (UNICODE_SELF - 1)) { buf[0] = (char) ch; return 1; } if (ch >= 0) { if (ch <= 0x7FF) { buf[1] = (char) (0x80 | (0x3F & ch)); buf[0] = (char) (0xC0 | (ch >> 6)); return 2; } if (ch <= 0xFFFF) { if ((flags & TCL_COMBINE) && ((ch & 0xF800) == 0xD800)) { if (ch & 0x0400) { /* Low surrogate */ if ( (0x80 == (0xC0 & buf[0])) && (0 == (0xCF & buf[1]))) { /* Previous Tcl_UniChar was a high surrogate, so combine */ buf[2] = (char) (0x80 | (0x3F & ch)); buf[1] |= (char) (0x80 | (0x0F & (ch >> 6))); return 3; } /* Previous Tcl_UniChar was not a high surrogate, so just output */ } else { /* High surrogate */ /* Add 0x10000 to the raw number encoded in the surrogate * pair in order to get the code point. */ ch += 0x40; /* Fill buffer with specific 3-byte (invalid) byte combination, * so following low surrogate can recognize it and combine */ buf[2] = (char) ((ch << 4) & 0x30); buf[1] = (char) (0x80 | (0x3F & (ch >> 2))); buf[0] = (char) (0xF0 | (0x07 & (ch >> 8))); return 1; } } goto three; } if (ch <= 0x10FFFF) { buf[3] = (char) (0x80 | (0x3F & ch)); buf[2] = (char) (0x80 | (0x3F & (ch >> 6))); buf[1] = (char) (0x80 | (0x3F & (ch >> 12))); buf[0] = (char) (0xF0 | (ch >> 18)); return 4; } } else if (ch == -1) { if ( (0x80 == (0xC0 & buf[0])) && (0 == (0xCF & buf[1])) && (0xF0 == (0xF8 & buf[-1]))) { ch = 0xD7C0 + ((0x07 & buf[-1]) << 8) + ((0x3F & buf[0]) << 2) + ((0x30 & buf[1]) >> 4); buf[1] = (char) (0x80 | (0x3F & ch)); buf[0] = (char) (0x80 | (0x3F & (ch >> 6))); buf[-1] = (char) (0xE0 | (ch >> 12)); return 2; } } ch = 0xFFFD; three: buf[2] = (char) (0x80 | (0x3F & ch)); buf[1] = (char) (0x80 | (0x3F & (ch >> 6))); buf[0] = (char) (0xE0 | (ch >> 12)); return 3; } /* *--------------------------------------------------------------------------- * * Tcl_UniCharToUtfDString -- * * Convert the given Unicode string to UTF-8. * * Results: * The return value is a pointer to the UTF-8 representation of the * Unicode string. Storage for the return value is appended to the end of * dsPtr. * * Side effects: * None. * *--------------------------------------------------------------------------- */ char * Tcl_UniCharToUtfDString( const int *uniStr, /* Unicode string to convert to UTF-8. */ Tcl_Size uniLength, /* Length of Unicode string. Negative for nul * terminated string */ Tcl_DString *dsPtr) /* UTF-8 representation of string is appended * to this previously initialized DString. */ { const int *w, *wEnd; char *p, *string; Tcl_Size oldLength; /* * UTF-8 string length in bytes will be <= Unicode string length * 4. */ if (uniStr == NULL) { return NULL; } if (uniLength < 0) { uniLength = 0; w = uniStr; while (*w != '\0') { uniLength++; w++; } } oldLength = Tcl_DStringLength(dsPtr); Tcl_DStringSetLength(dsPtr, oldLength + (uniLength + 1) * 4); string = Tcl_DStringValue(dsPtr) + oldLength; p = string; wEnd = uniStr + uniLength; for (w = uniStr; w < wEnd; ) { p += Tcl_UniCharToUtf(*w, p); w++; } Tcl_DStringSetLength(dsPtr, oldLength + (p - string)); return string; } char * Tcl_Char16ToUtfDString( const unsigned short *uniStr,/* Utf-16 string to convert to UTF-8. */ Tcl_Size uniLength, /* Length of Utf-16 string. */ Tcl_DString *dsPtr) /* UTF-8 representation of string is appended * to this previously initialized DString. */ { const unsigned short *w, *wEnd; char *p, *string; Tcl_Size oldLength; int len = 1; /* * UTF-8 string length in bytes will be <= Utf16 string length * 3. */ if (uniStr == NULL) { return NULL; } if (uniLength < 0) { uniLength = 0; w = uniStr; while (*w != '\0') { uniLength++; w++; } } oldLength = Tcl_DStringLength(dsPtr); Tcl_DStringSetLength(dsPtr, oldLength + (uniLength + 1) * 3); string = Tcl_DStringValue(dsPtr) + oldLength; p = string; wEnd = uniStr + uniLength; for (w = uniStr; w < wEnd; ) { if (!len && ((*w & 0xFC00) != 0xDC00)) { /* Special case for handling high surrogates. */ p += Tcl_UniCharToUtf(-1, p); } len = Tcl_UniCharToUtf(*w | TCL_COMBINE, p); p += len; if ((*w >= 0xD800) && (len < 3)) { len = 0; /* Indication that high surrogate was found */ } w++; } if (!len) { /* Special case for handling high surrogates. */ p += Tcl_UniCharToUtf(-1, p); } Tcl_DStringSetLength(dsPtr, oldLength + (p - string)); return string; } /* *--------------------------------------------------------------------------- * * Tcl_UtfToUniChar -- * * Extract the Tcl_UniChar represented by the UTF-8 string. Bad UTF-8 * sequences are converted to valid Tcl_UniChars and processing * continues. Equivalent to Plan 9 chartorune(). * * The caller must ensure that the source buffer is long enough that this * routine does not run off the end and dereference non-existent memory * looking for trail bytes. If the source buffer is known to be '\0' * terminated, this cannot happen. Otherwise, the caller should call * Tcl_UtfCharComplete() before calling this routine to ensure that * enough bytes remain in the string. * * Results: * *chPtr is filled with the Tcl_UniChar, and the return value is the * number of bytes from the UTF-8 string that were consumed. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static const unsigned short cp1252[32] = { 0x20AC, 0x81, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x8D, 0x017D, 0x8F, 0x90, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x2DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x9D, 0x017E, 0x0178 }; Tcl_Size Tcl_UtfToUniChar( const char *src, /* The UTF-8 string. */ int *chPtr) /* Filled with the Unicode character * represented by the UTF-8 string. */ { int byte; /* * Unroll 1 to 4 byte UTF-8 sequences. */ byte = *((unsigned char *) src); if (byte < 0xC0) { /* * Handles properly formed UTF-8 characters between 0x01 and 0x7F. * Treats naked trail bytes 0x80 to 0x9F as valid characters from * the cp1252 table. See: * Also treats \0 and other naked trail bytes 0xA0 to 0xBF as valid * characters representing themselves. */ if ((unsigned)(byte-0x80) < (unsigned)0x20) { *chPtr = cp1252[byte-0x80]; } else { *chPtr = byte; } return 1; } else if (byte < 0xE0) { if ((byte != 0xC1) && ((src[1] & 0xC0) == 0x80)) { /* * Two-byte-character lead-byte followed by a trail-byte. */ *chPtr = (((byte & 0x1F) << 6) | (src[1] & 0x3F)); if ((unsigned)(*chPtr - 1) >= (UNICODE_SELF - 1)) { return 2; } } /* * A two-byte-character lead-byte not followed by trail-byte * represents itself. */ } else if (byte < 0xF0) { if (((src[1] & 0xC0) == 0x80) && ((src[2] & 0xC0) == 0x80)) { /* * Three-byte-character lead byte followed by two trail bytes. */ *chPtr = (((byte & 0x0F) << 12) | ((src[1] & 0x3F) << 6) | (src[2] & 0x3F)); if (*chPtr > 0x7FF) { return 3; } } /* * A three-byte-character lead-byte not followed by two trail-bytes * represents itself. */ } else if (byte < 0xF5) { if (((src[1] & 0xC0) == 0x80) && ((src[2] & 0xC0) == 0x80) && ((src[3] & 0xC0) == 0x80)) { /* * Four-byte-character lead byte followed by three trail bytes. */ *chPtr = (((byte & 0x07) << 18) | ((src[1] & 0x3F) << 12) | ((src[2] & 0x3F) << 6) | (src[3] & 0x3F)); if ((unsigned)(*chPtr - 0x10000) <= 0xFFFFF) { return 4; } } /* * A four-byte-character lead-byte not followed by three trail-bytes * represents itself. */ } *chPtr = byte; return 1; } Tcl_Size Tcl_UtfToChar16( const char *src, /* The UTF-8 string. */ unsigned short *chPtr)/* Filled with the Tcl_UniChar represented by * the UTF-8 string. This could be a surrogate too. */ { unsigned short byte; /* * Unroll 1 to 4 byte UTF-8 sequences. */ byte = UCHAR(*src); if (byte < 0xC0) { /* * Handles properly formed UTF-8 characters between 0x01 and 0x7F. * Treats naked trail bytes 0x80 to 0x9F as valid characters from * the cp1252 table. See: * Also treats \0 and other naked trail bytes 0xA0 to 0xBF as valid * characters representing themselves. */ /* If *chPtr contains a high surrogate (produced by a previous * Tcl_UtfToUniChar() call) and the next 3 bytes are UTF-8 continuation * bytes, then we must produce a follow-up low surrogate. We only * do that if the high surrogate matches the bits we encounter. */ if (((byte & 0xC0) == 0x80) && ((src[1] & 0xC0) == 0x80) && ((src[2] & 0xC0) == 0x80) && (((((byte - 0x10) << 2) & 0xFC) | 0xD800) == (*chPtr & 0xFCFC)) && ((src[1] & 0xF0) == (((*chPtr << 4) & 0x30) | 0x80))) { *chPtr = ((src[1] & 0x0F) << 6) + (src[2] & 0x3F) + 0xDC00; return 3; } if ((unsigned)(byte-0x80) < (unsigned)0x20) { *chPtr = cp1252[byte-0x80]; } else { *chPtr = byte; } return 1; } else if (byte < 0xE0) { if ((byte != 0xC1) && ((src[1] & 0xC0) == 0x80)) { /* * Two-byte-character lead-byte followed by a trail-byte. */ *chPtr = (((byte & 0x1F) << 6) | (src[1] & 0x3F)); if ((unsigned)(*chPtr - 1) >= (UNICODE_SELF - 1)) { return 2; } } /* * A two-byte-character lead-byte not followed by trail-byte * represents itself. */ } else if (byte < 0xF0) { if (((src[1] & 0xC0) == 0x80) && ((src[2] & 0xC0) == 0x80)) { /* * Three-byte-character lead byte followed by two trail bytes. */ *chPtr = (((byte & 0x0F) << 12) | ((src[1] & 0x3F) << 6) | (src[2] & 0x3F)); if (*chPtr > 0x7FF) { return 3; } } /* * A three-byte-character lead-byte not followed by two trail-bytes * represents itself. */ } else if (byte < 0xF5) { if (((src[1] & 0xC0) == 0x80) && ((src[2] & 0xC0) == 0x80)) { /* * Four-byte-character lead byte followed by at least two trail bytes. * We don't test the validity of 3th trail byte, see [ed29806ba] */ Tcl_UniChar high = (((byte & 0x07) << 8) | ((src[1] & 0x3F) << 2) | ((src[2] & 0x3F) >> 4)) - 0x40; if (high < 0x400) { /* produce high surrogate, advance source pointer */ *chPtr = 0xD800 + high; return 1; } /* out of range, < 0x10000 or > 0x10FFFF */ } /* * A four-byte-character lead-byte not followed by three trail-bytes * represents itself. */ } *chPtr = byte; return 1; } /* *--------------------------------------------------------------------------- * * Tcl_UtfToUniCharDString -- * * Convert the UTF-8 string to Unicode. * * Results: * The return value is a pointer to the Unicode representation of the * UTF-8 string. Storage for the return value is appended to the end of * dsPtr. The Unicode string is terminated with a Unicode NULL character. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int * Tcl_UtfToUniCharDString( const char *src, /* UTF-8 string to convert to Unicode. */ Tcl_Size length, /* Length of UTF-8 string in bytes, or -1 for * strlen(). */ Tcl_DString *dsPtr) /* Unicode representation of string is * appended to this previously initialized * DString. */ { int ch = 0, *w, *wString; const char *p; Tcl_Size oldLength; /* Pointer to the end of string. Never read endPtr[0] */ const char *endPtr = src + length; /* Pointer to last byte where optimization still can be used */ const char *optPtr = endPtr - TCL_UTF_MAX; if (src == NULL) { return NULL; } if (length < 0) { length = strlen(src); } /* * Unicode string length in Tcl_UniChars will be <= UTF-8 string length in * bytes. */ oldLength = Tcl_DStringLength(dsPtr); Tcl_DStringSetLength(dsPtr, oldLength + ((length + 1) * sizeof(int))); wString = (int *) (Tcl_DStringValue(dsPtr) + oldLength); w = wString; p = src; endPtr = src + length; optPtr = endPtr - 4; while (p <= optPtr) { p += TclUtfToUniChar(p, &ch); *w++ = ch; } while ((p < endPtr) && Tcl_UtfCharComplete(p, endPtr-p)) { p += TclUtfToUniChar(p, &ch); *w++ = ch; } while (p < endPtr) { *w++ = UCHAR(*p++); } *w = '\0'; Tcl_DStringSetLength(dsPtr, oldLength + ((char *) w - (char *) wString)); return wString; } unsigned short * Tcl_UtfToChar16DString( const char *src, /* UTF-8 string to convert to Unicode. */ Tcl_Size length, /* Length of UTF-8 string in bytes, or -1 for * strlen(). */ Tcl_DString *dsPtr) /* Unicode representation of string is * appended to this previously initialized * DString. */ { unsigned short ch = 0, *w, *wString; const char *p; Tcl_Size oldLength; /* Pointer to the end of string. Never read endPtr[0] */ const char *endPtr = src + length; /* Pointer to last byte where optimization still can be used */ const char *optPtr = endPtr - TCL_UTF_MAX; if (src == NULL) { return NULL; } if (length < 0) { length = strlen(src); } /* * Unicode string length in WCHARs will be <= UTF-8 string length in * bytes. */ oldLength = Tcl_DStringLength(dsPtr); Tcl_DStringSetLength(dsPtr, oldLength + ((length + 1) * sizeof(unsigned short))); wString = (unsigned short *) (Tcl_DStringValue(dsPtr) + oldLength); w = wString; p = src; endPtr = src + length; optPtr = endPtr - 3; while (p <= optPtr) { p += Tcl_UtfToChar16(p, &ch); *w++ = ch; } while (p < endPtr) { if (Tcl_UtfCharComplete(p, endPtr-p)) { p += Tcl_UtfToChar16(p, &ch); *w++ = ch; } else { *w++ = UCHAR(*p++); } } *w = '\0'; Tcl_DStringSetLength(dsPtr, oldLength + ((char *) w - (char *) wString)); return wString; } /* *--------------------------------------------------------------------------- * * Tcl_UtfCharComplete -- * * Determine if the UTF-8 string of the given length is long enough to be * decoded by Tcl_UtfToUniChar(). This does not ensure that the UTF-8 * string is properly formed. Equivalent to Plan 9 fullrune(). * * Results: * The return value is 0 if the string is not long enough, non-zero * otherwise. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int Tcl_UtfCharComplete( const char *src, /* String to check if first few bytes contain * a complete UTF-8 character. */ Tcl_Size length) /* Length of above string in bytes. */ { return length >= complete[UCHAR(*src)]; } /* *--------------------------------------------------------------------------- * * Tcl_NumUtfChars -- * * Returns the number of characters (not bytes) in the UTF-8 string, not * including the terminating NULL byte. This is equivalent to Plan 9 * utflen() and utfnlen(). * * Results: * As above. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Size Tcl_NumUtfChars( const char *src, /* The UTF-8 string to measure. */ Tcl_Size length) /* The length of the string in bytes, or * negative value for strlen(src). */ { Tcl_UniChar ch = 0; Tcl_Size i = 0; if (length < 0) { /* string is NUL-terminated, so TclUtfToUniChar calls are safe. */ while (*src != '\0') { src += TclUtfToUniChar(src, &ch); i++; } } else { /* Will return value between 0 and length. No overflow checks. */ /* Pointer to the end of string. Never read endPtr[0] */ const char *endPtr = src + length; /* Pointer to last byte where optimization still can be used */ const char *optPtr = endPtr - 4; /* * Optimize away the call in this loop. Justified because... * when (src <= optPtr), (endPtr - src) >= (endPtr - optPtr) * By initialization above (endPtr - optPtr) = TCL_UTF_MAX * So (endPtr - src) >= TCL_UTF_MAX, and passing that to * Tcl_UtfCharComplete we know will cause return of 1. */ while (src <= optPtr /* && Tcl_UtfCharComplete(src, endPtr - src) */ ) { src += TclUtfToUniChar(src, &ch); i++; } /* Loop over the remaining string where call must happen */ while (src < endPtr) { if (Tcl_UtfCharComplete(src, endPtr - src)) { src += TclUtfToUniChar(src, &ch); } else { /* * src points to incomplete UTF-8 sequence * Treat first byte as character and count it */ src++; } i++; } } return i; } Tcl_Size TclNumUtfChars( const char *src, /* The UTF-8 string to measure. */ Tcl_Size length) /* The length of the string in bytes, or * negative for strlen(src). */ { unsigned short ch = 0; Tcl_Size i = 0; if (length < 0) { /* string is NUL-terminated, so TclUtfToUniChar calls are safe. */ while (*src != '\0') { src += Tcl_UtfToChar16(src, &ch); i++; } } else { /* Will return value between 0 and length. No overflow checks. */ /* Pointer to the end of string. Never read endPtr[0] */ const char *endPtr = src + length; /* Pointer to last byte where optimization still can be used */ const char *optPtr = endPtr - 4; /* * Optimize away the call in this loop. Justified because... * when (src <= optPtr), (endPtr - src) >= (endPtr - optPtr) * By initialization above (endPtr - optPtr) = TCL_UTF_MAX * So (endPtr - src) >= TCL_UTF_MAX, and passing that to * Tcl_UtfCharComplete we know will cause return of 1. */ while (src <= optPtr /* && Tcl_UtfCharComplete(src, endPtr - src) */ ) { src += Tcl_UtfToChar16(src, &ch); i++; } /* Loop over the remaining string where call must happen */ while (src < endPtr) { if (Tcl_UtfCharComplete(src, endPtr - src)) { src += Tcl_UtfToChar16(src, &ch); } else { /* * src points to incomplete UTF-8 sequence * Treat first byte as character and count it */ src++; } i++; } } return i; } /* *--------------------------------------------------------------------------- * * Tcl_UtfFindFirst -- * * Returns a pointer to the first occurrence of the given Unicode character * in the NULL-terminated UTF-8 string. The NULL terminator is considered * part of the UTF-8 string. Equivalent to Plan 9 utfrune(). * * Results: * As above. If the Unicode character does not exist in the given string, * the return value is NULL. * * Side effects: * None. * *--------------------------------------------------------------------------- */ const char * Tcl_UtfFindFirst( const char *src, /* The UTF-8 string to be searched. */ int ch) /* The Unicode character to search for. */ { while (1) { int find, len = TclUtfToUniChar(src, &find); if (find == ch) { return src; } if (*src == '\0') { return NULL; } src += len; } } /* *--------------------------------------------------------------------------- * * Tcl_UtfFindLast -- * * Returns a pointer to the last occurrence of the given Unicode character * in the NULL-terminated UTF-8 string. The NULL terminator is considered * part of the UTF-8 string. Equivalent to Plan 9 utfrrune(). * * Results: * As above. If the Unicode character does not exist in the given string, the * return value is NULL. * * Side effects: * None. * *--------------------------------------------------------------------------- */ const char * Tcl_UtfFindLast( const char *src, /* The UTF-8 string to be searched. */ int ch) /* The Unicode character to search for. */ { const char *last = NULL; while (1) { int find, len = TclUtfToUniChar(src, &find); if (find == ch) { last = src; } if (*src == '\0') { break; } src += len; } return last; } /* *--------------------------------------------------------------------------- * * Tcl_UtfNext -- * * Given a pointer to some location in a UTF-8 string, Tcl_UtfNext * returns a pointer to the next UTF-8 character in the string. * The caller must not ask for the next character after the last * character in the string if the string is not terminated by a null * character. * * Results: * The return value is the pointer to the next character in the UTF-8 * string. * * Side effects: * None. * *--------------------------------------------------------------------------- */ const char * Tcl_UtfNext( const char *src) /* The current location in the string. */ { int left; const char *next; if (((*src) & 0xC0) == 0x80) { /* Continuation byte, so we start 'inside' a (possible valid) UTF-8 * sequence. Since we are not allowed to access src[-1], we cannot * check if the sequence is actually valid, the best we can do is * just assume it is valid and locate the end. */ if ((((*++src) & 0xC0) == 0x80) && (((*++src) & 0xC0) == 0x80)) { ++src; } return src; } left = totalBytes[UCHAR(*src)]; next = src + 1; while (--left) { if ((*next & 0xC0) != 0x80) { /* * src points to non-trail byte; We ran out of trail bytes * before the needs of the lead byte were satisfied. * Let the (malformed) lead byte alone be a character */ return src + 1; } next++; } /* * Call Invalid() here only if required conditions are met: * src[0] is known a lead byte. * src[1] is known a trail byte. * Especially important to prevent calls when src[0] == '\xF8' or '\xFC' * See tests utf-6.37 through utf-6.43 through valgrind or similar tool. */ if ((next == src + 1) || Invalid(src)) { return src + 1; } return next; } /* *--------------------------------------------------------------------------- * * Tcl_UtfPrev -- * * Given a pointer to some current location in a UTF-8 string, move * backwards one character. This works correctly when the pointer is in * the middle of a UTF-8 character. * * Results: * The return value is a pointer to the previous character in the UTF-8 * string. If the current location was already at the beginning of the * string, the return value will also be a pointer to the beginning of * the string. * * Side effects: * None. * *--------------------------------------------------------------------------- */ const char * Tcl_UtfPrev( const char *src, /* A location in a UTF-8 string. */ const char *start) /* Pointer to the beginning of the string */ { int trailBytesSeen = 0; /* How many trail bytes have been verified? */ const char *fallback = src - 1; /* If we cannot find a lead byte that might * start a prefix of a valid UTF byte sequence, * we will fallback to a one-byte back step */ const char *look = fallback; /* Start search at the fallback position */ /* Quick boundary case exit. */ if (fallback <= start) { return start; } do { unsigned char byte = UCHAR(look[0]); if (byte < 0x80) { /* * Single byte character. Either this is a correct previous * character, or it is followed by at least one trail byte * which indicates a malformed sequence. In either case the * correct result is to return the fallback. */ return fallback; } if (byte >= 0xC0) { /* Non-trail byte; May be multibyte lead. */ if ((trailBytesSeen == 0) /* * We've seen no trailing context to use to check * anything. From what we know, this non-trail byte * is a prefix of a previous character, and accepting * it (the fallback) is correct. */ || (trailBytesSeen >= totalBytes[byte])) { /* * That is, (1 + trailBytesSeen > needed). * We've examined more bytes than needed to complete * this lead byte. No matter about well-formedness or * validity, the sequence starting with this lead byte * will never include the fallback location, so we must * return the fallback location. See test utf-7.17 */ return fallback; } /* * trailBytesSeen > 0, so we can examine look[1] safely. * Use that capability to screen out invalid sequences. */ if (Invalid(look)) { /* Reject */ return fallback; } return (const char *)look; } /* We saw a trail byte. */ trailBytesSeen++; if ((const char *)look == start) { /* * Do not read before the start of the string * * If we get here, we've examined bytes at every location * >= start and < src and all of them are trail bytes, * including (*start). We need to return our fallback * and exit this loop before we run past the start of the string. */ return fallback; } /* Continue the search backwards... */ look--; } while (trailBytesSeen < 4); /* * We've seen 4 trail bytes, so we know there will not be a * properly formed byte sequence to find, and we can stop looking, * accepting the fallback. */ return fallback; } /* *--------------------------------------------------------------------------- * * Tcl_UniCharAtIndex -- * * Returns the Unicode character represented at the specified character * (not byte) position in the UTF-8 string. * * Results: * As above. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int Tcl_UniCharAtIndex( const char *src, /* The UTF-8 string to dereference. */ Tcl_Size index) /* The position of the desired character. */ { Tcl_UniChar ch = 0; int i = 0; if (index < 0) { return -1; } while (index--) { i = TclUtfToUniChar(src, &ch); src += i; } TclUtfToUniChar(src, &i); return i; } /* *--------------------------------------------------------------------------- * * Tcl_UtfAtIndex -- * * Returns a pointer to the specified character (not byte) position in * the UTF-8 string. * * Results: * As above. * * Side effects: * None. * *--------------------------------------------------------------------------- */ const char * Tcl_UtfAtIndex( const char *src, /* The UTF-8 string. */ Tcl_Size index) /* The position of the desired character. */ { Tcl_UniChar ch = 0; while (index-- > 0) { src += TclUtfToUniChar(src, &ch); } return src; } const char * TclUtfAtIndex( const char *src, /* The UTF-8 string. */ Tcl_Size index) /* The position of the desired character. */ { unsigned short ch = 0; Tcl_Size len = 0; if (index > 0) { while (index--) { src += (len = Tcl_UtfToChar16(src, &ch)); } if ((ch >= 0xD800) && (len < 3)) { /* Index points at character following high Surrogate */ src += Tcl_UtfToChar16(src, &ch); } } return src; } /* *--------------------------------------------------------------------------- * * Tcl_UtfBackslash -- * * Figure out how to handle a backslash sequence. * * Results: * Stores the bytes represented by the backslash sequence in dst and * returns the number of bytes written to dst. At most 4 bytes * are written to dst; dst must have been large enough to accept those * bytes. If readPtr isn't NULL then it is filled in with a count of the * number of bytes in the backslash sequence. * * Side effects: * The maximum number of bytes it takes to represent a Unicode character * in UTF-8 is guaranteed to be less than the number of bytes used to * express the backslash sequence that represents that Unicode character. * If the target buffer into which the caller is going to store the bytes * that represent the Unicode character is at least as large as the * source buffer from which the backslashed sequence was extracted, no * buffer overruns should occur. * *--------------------------------------------------------------------------- */ Tcl_Size Tcl_UtfBackslash( const char *src, /* Points to the backslash character of a * backslash sequence. */ int *readPtr, /* Fill in with number of characters read from * src, unless NULL. */ char *dst) /* Filled with the bytes represented by the * backslash sequence. */ { #define LINE_LENGTH 128 Tcl_Size numRead; int result; result = TclParseBackslash(src, LINE_LENGTH, &numRead, dst); if (numRead == LINE_LENGTH) { /* * We ate a whole line. Pay the price of a strlen() */ result = TclParseBackslash(src, strlen(src), &numRead, dst); } if (readPtr != NULL) { *readPtr = numRead; } return result; } /* *---------------------------------------------------------------------- * * Tcl_UtfToUpper -- * * Convert lowercase characters to uppercase characters in a UTF string * in place. The conversion may shrink the UTF string. * * Results: * Returns the number of bytes in the resulting string excluding the * trailing null. * * Side effects: * Writes a terminating null after the last converted character. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_UtfToUpper( char *str) /* String to convert in place. */ { int ch, upChar; char *src, *dst; Tcl_Size len; /* * Iterate over the string until we hit the terminating null. */ src = dst = str; while (*src) { len = TclUtfToUniChar(src, &ch); upChar = Tcl_UniCharToUpper(ch); /* * To keep badly formed Utf strings from getting inflated by the * conversion (thereby causing a segfault), only copy the upper case * char to dst if its size is <= the original char. */ if (len < TclUtfCount(upChar)) { memmove(dst, src, len); dst += len; } else { dst += Tcl_UniCharToUtf(upChar, dst); } src += len; } *dst = '\0'; return (dst - str); } /* *---------------------------------------------------------------------- * * Tcl_UtfToLower -- * * Convert uppercase characters to lowercase characters in a UTF string * in place. The conversion may shrink the UTF string. * * Results: * Returns the number of bytes in the resulting string excluding the * trailing null. * * Side effects: * Writes a terminating null after the last converted character. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_UtfToLower( char *str) /* String to convert in place. */ { int ch, lowChar; char *src, *dst; Tcl_Size len; /* * Iterate over the string until we hit the terminating null. */ src = dst = str; while (*src) { len = TclUtfToUniChar(src, &ch); lowChar = Tcl_UniCharToLower(ch); /* * To keep badly formed Utf strings from getting inflated by the * conversion (thereby causing a segfault), only copy the lower case * char to dst if its size is <= the original char. */ if (len < TclUtfCount(lowChar)) { memmove(dst, src, len); dst += len; } else { dst += Tcl_UniCharToUtf(lowChar, dst); } src += len; } *dst = '\0'; return (dst - str); } /* *---------------------------------------------------------------------- * * Tcl_UtfToTitle -- * * Changes the first character of a UTF string to title case or uppercase * and the rest of the string to lowercase. The conversion happens in * place and may shrink the UTF string. * * Results: * Returns the number of bytes in the resulting string excluding the * trailing null. * * Side effects: * Writes a terminating null after the last converted character. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_UtfToTitle( char *str) /* String to convert in place. */ { int ch, titleChar, lowChar; char *src, *dst; Tcl_Size len; /* * Capitalize the first character and then lowercase the rest of the * characters until we get to a null. */ src = dst = str; if (*src) { len = TclUtfToUniChar(src, &ch); titleChar = Tcl_UniCharToTitle(ch); if (len < TclUtfCount(titleChar)) { memmove(dst, src, len); dst += len; } else { dst += Tcl_UniCharToUtf(titleChar, dst); } src += len; } while (*src) { len = TclUtfToUniChar(src, &ch); lowChar = ch; /* Special exception for Georgian Asomtavruli chars, no titlecase. */ if ((unsigned)(lowChar - 0x1C90) >= 0x30) { lowChar = Tcl_UniCharToLower(lowChar); } if (len < TclUtfCount(lowChar)) { memmove(dst, src, len); dst += len; } else { dst += Tcl_UniCharToUtf(lowChar, dst); } src += len; } *dst = '\0'; return (dst - str); } /* *---------------------------------------------------------------------- * * TclpUtfNcmp2 -- * * Compare at most numBytes bytes of utf-8 strings cs and ct. Both cs and * ct are assumed to be at least numBytes bytes long. * * Results: * Return <0 if cs < ct, 0 if cs == ct, or >0 if cs > ct. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclpUtfNcmp2( const void *csPtr, /* UTF string to compare to ct. */ const void *ctPtr, /* UTF string cs is compared to. */ size_t numBytes) /* Number of *bytes* to compare. */ { const char *cs = (const char *)csPtr; const char *ct = (const char *)ctPtr; /* * We can't simply call 'memcmp(cs, ct, numBytes);' because we need to * check for Tcl's \xC0\x80 non-utf-8 null encoding. Otherwise utf-8 lexes * fine in the strcmp manner. */ int result = 0; for ( ; numBytes != 0; numBytes--, cs++, ct++) { if (*cs != *ct) { result = UCHAR(*cs) - UCHAR(*ct); break; } } if (numBytes && ((UCHAR(*cs) == 0xC0) || (UCHAR(*ct) == 0xC0))) { unsigned char c1, c2; c1 = ((UCHAR(*cs) == 0xC0) && (UCHAR(cs[1]) == 0x80)) ? 0 : UCHAR(*cs); c2 = ((UCHAR(*ct) == 0xC0) && (UCHAR(ct[1]) == 0x80)) ? 0 : UCHAR(*ct); result = (c1 - c2); } return result; } /* *---------------------------------------------------------------------- * * Tcl_UtfNcmp -- * * Compare at most numChars chars (not bytes) of string cs to string ct. Both cs * and ct are assumed to be at least numChars chars long. * * Results: * Return <0 if cs < ct, 0 if cs == ct, or >0 if cs > ct. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclUtfNcmp( const char *cs, /* UTF string to compare to ct. */ const char *ct, /* UTF string cs is compared to. */ size_t numChars) /* Number of UTF-16 chars to compare. */ { unsigned short ch1 = 0, ch2 = 0; /* * Cannot use 'memcmp(cs, ct, n);' as byte representation of \u0000 (the * pair of bytes 0xC0,0x80) is larger than byte representation of \u0001 * (the byte 0x01.) */ while (numChars-- > 0) { /* * n must be interpreted as chars, not bytes. This should be called * only when both strings are of at least n UTF-16 chars long (no need for \0 * check) */ cs += Tcl_UtfToChar16(cs, &ch1); ct += Tcl_UtfToChar16(ct, &ch2); if (ch1 != ch2) { /* Surrogates always report higher than non-surrogates */ if (((ch1 & 0xFC00) == 0xD800)) { if ((ch2 & 0xFC00) != 0xD800) { return ch1; } } else if ((ch2 & 0xFC00) == 0xD800) { return -ch2; } return (ch1 - ch2); } } return 0; } int Tcl_UtfNcmp( const char *cs, /* UTF string to compare to ct. */ const char *ct, /* UTF string cs is compared to. */ size_t numChars) /* Number of chars to compare. */ { Tcl_UniChar ch1 = 0, ch2 = 0; /* * Cannot use 'memcmp(cs, ct, n);' as byte representation of \u0000 (the * pair of bytes 0xC0,0x80) is larger than byte representation of \u0001 * (the byte 0x01.) */ while (numChars-- > 0) { /* * n must be interpreted as chars, not bytes. This should be called * only when both strings are of at least n chars long (no need for \0 * check) */ cs += TclUtfToUniChar(cs, &ch1); ct += TclUtfToUniChar(ct, &ch2); if (ch1 != ch2) { return (ch1 - ch2); } } return 0; } /* *---------------------------------------------------------------------- * * Tcl_UtfNcasecmp -- * * Compare at most numChars chars (not bytes) of string cs to string ct case * insensitive. Both cs and ct are assumed to be at least numChars UTF * chars long. * * Results: * Return <0 if cs < ct, 0 if cs == ct, or >0 if cs > ct. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclUtfNcasecmp( const char *cs, /* UTF string to compare to ct. */ const char *ct, /* UTF string cs is compared to. */ size_t numChars) /* Number of UTF-16 chars to compare. */ { unsigned short ch1 = 0, ch2 = 0; while (numChars-- > 0) { /* * n must be interpreted as UTF-16 chars, not bytes. * This should be called only when both strings are of * at least n UTF-16 chars long (no need for \0 check) */ cs += Tcl_UtfToChar16(cs, &ch1); ct += Tcl_UtfToChar16(ct, &ch2); if (ch1 != ch2) { /* Surrogates always report higher than non-surrogates */ if (((ch1 & 0xFC00) == 0xD800)) { if ((ch2 & 0xFC00) != 0xD800) { return ch1; } } else if ((ch2 & 0xFC00) == 0xD800) { return -ch2; } ch1 = Tcl_UniCharToLower(ch1); ch2 = Tcl_UniCharToLower(ch2); if (ch1 != ch2) { return (ch1 - ch2); } } } return 0; } int Tcl_UtfNcasecmp( const char *cs, /* UTF string to compare to ct. */ const char *ct, /* UTF string cs is compared to. */ size_t numChars) /* Number of chars to compare. */ { Tcl_UniChar ch1 = 0, ch2 = 0; while (numChars-- > 0) { /* * n must be interpreted as chars, not bytes. * This should be called only when both strings are of * at least n chars long (no need for \0 check) */ cs += TclUtfToUniChar(cs, &ch1); ct += TclUtfToUniChar(ct, &ch2); if (ch1 != ch2) { ch1 = Tcl_UniCharToLower(ch1); ch2 = Tcl_UniCharToLower(ch2); if (ch1 != ch2) { return (ch1 - ch2); } } } return 0; } /* *---------------------------------------------------------------------- * * Tcl_UtfCmp -- * * Compare UTF chars of string cs to string ct case sensitively. * Replacement for strcmp in Tcl core, in places where UTF-8 should * be handled. * * Results: * Return <0 if cs < ct, 0 if cs == ct, or >0 if cs > ct. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclUtfCmp( const char *cs, /* UTF string to compare to ct. */ const char *ct) /* UTF string cs is compared to. */ { Tcl_UniChar ch1 = 0, ch2 = 0; while (*cs && *ct) { cs += TclUtfToUniChar(cs, &ch1); ct += TclUtfToUniChar(ct, &ch2); if (ch1 != ch2) { return ch1 - ch2; } } return UCHAR(*cs) - UCHAR(*ct); } /* *---------------------------------------------------------------------- * * TclUtfCasecmp -- * * Compare UTF chars of string cs to string ct case insensitively. * Replacement for strcasecmp in Tcl core, in places where UTF-8 should * be handled. * * Results: * Return <0 if cs < ct, 0 if cs == ct, or >0 if cs > ct. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclUtfCasecmp( const char *cs, /* UTF string to compare to ct. */ const char *ct) /* UTF string cs is compared to. */ { Tcl_UniChar ch1 = 0, ch2 = 0; while (*cs && *ct) { cs += TclUtfToUniChar(cs, &ch1); ct += TclUtfToUniChar(ct, &ch2); if (ch1 != ch2) { ch1 = Tcl_UniCharToLower(ch1); ch2 = Tcl_UniCharToLower(ch2); if (ch1 != ch2) { return ch1 - ch2; } } } return UCHAR(*cs) - UCHAR(*ct); } /* *---------------------------------------------------------------------- * * Tcl_UniCharToUpper -- * * Compute the uppercase equivalent of the given Unicode character. * * Results: * Returns the uppercase Unicode character. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharToUpper( int ch) /* Unicode character to convert. */ { if (!UNICODE_OUT_OF_RANGE(ch)) { int info = GetUniCharInfo(ch); if (GetCaseType(info) & 0x04) { ch -= GetDelta(info); } } /* Clear away extension bits, if any */ return ch & 0x1FFFFF; } /* *---------------------------------------------------------------------- * * Tcl_UniCharToLower -- * * Compute the lowercase equivalent of the given Unicode character. * * Results: * Returns the lowercase Unicode character. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharToLower( int ch) /* Unicode character to convert. */ { if (!UNICODE_OUT_OF_RANGE(ch)) { int info = GetUniCharInfo(ch); int mode = GetCaseType(info); if ((mode & 0x02) && (mode != 0x7)) { ch += GetDelta(info); } } /* Clear away extension bits, if any */ return ch & 0x1FFFFF; } /* *---------------------------------------------------------------------- * * Tcl_UniCharToTitle -- * * Compute the titlecase equivalent of the given Unicode character. * * Results: * Returns the titlecase Unicode character. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharToTitle( int ch) /* Unicode character to convert. */ { if (!UNICODE_OUT_OF_RANGE(ch)) { int info = GetUniCharInfo(ch); int mode = GetCaseType(info); if (mode & 0x1) { /* * Subtract or add one depending on the original case. */ if (mode != 0x7) { ch += ((mode & 0x4) ? -1 : 1); } } else if (mode == 0x4) { ch -= GetDelta(info); } } /* Clear away extension bits, if any */ return ch & 0x1FFFFF; } /* *---------------------------------------------------------------------- * * Tcl_Char16Len -- * * Find the length of a UniChar string. The str input must be null * terminated. * * Results: * Returns the length of str in UniChars (not bytes). * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_Char16Len( const unsigned short *uniStr) /* Unicode string to find length of. */ { Tcl_Size len = 0; while (*uniStr != '\0') { len++; uniStr++; } return len; } /* *---------------------------------------------------------------------- * * Tcl_UniCharLen -- * * Find the length of a UniChar string. The str input must be null * terminated. * * Results: * Returns the length of str in UniChars (not bytes). * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_UniCharLen( const int *uniStr) /* Unicode string to find length of. */ { Tcl_Size len = 0; while (*uniStr != '\0') { len++; uniStr++; } return len; } /* *---------------------------------------------------------------------- * * TclUniCharNcmp -- * * Compare at most numChars chars (not bytes) of string ucs to string uct. * Both ucs and uct are assumed to be at least numChars chars long. * * Results: * Return <0 if ucs < uct, 0 if ucs == uct, or >0 if ucs > uct. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclUniCharNcmp( const Tcl_UniChar *ucs, /* Unicode string to compare to uct. */ const Tcl_UniChar *uct, /* Unicode string ucs is compared to. */ size_t numChars) /* Number of chars to compare. */ { #if defined(WORDS_BIGENDIAN) /* * We are definitely on a big-endian machine; memcmp() is safe */ return memcmp(ucs, uct, numChars*sizeof(Tcl_UniChar)); #else /* !WORDS_BIGENDIAN */ /* * We can't simply call memcmp() because that is not lexically correct. */ for ( ; numChars != 0; ucs++, uct++, numChars--) { if (*ucs != *uct) { return (*ucs - *uct); } } return 0; #endif /* WORDS_BIGENDIAN */ } /* *---------------------------------------------------------------------- * * TclUniCharNcasecmp -- * * Compare at most numChars chars (not bytes) of string ucs to string uct case * insensitive. Both ucs and uct are assumed to be at least numChars * chars long. * * Results: * Return <0 if ucs < uct, 0 if ucs == uct, or >0 if ucs > uct. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclUniCharNcasecmp( const Tcl_UniChar *ucs, /* Unicode string to compare to uct. */ const Tcl_UniChar *uct, /* Unicode string ucs is compared to. */ size_t numChars) /* Number of chars to compare. */ { for ( ; numChars != 0; numChars--, ucs++, uct++) { if (*ucs != *uct) { Tcl_UniChar lcs = Tcl_UniCharToLower(*ucs); Tcl_UniChar lct = Tcl_UniCharToLower(*uct); if (lcs != lct) { return (lcs - lct); } } } return 0; } /* *---------------------------------------------------------------------- * * Tcl_UniCharIsAlnum -- * * Test if a character is an alphanumeric Unicode character. * * Results: * Returns 1 if character is alphanumeric. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharIsAlnum( int ch) /* Unicode character to test. */ { if (UNICODE_OUT_OF_RANGE(ch)) { return 0; } return (((ALPHA_BITS | DIGIT_BITS) >> GetCategory(ch)) & 1); } /* *---------------------------------------------------------------------- * * Tcl_UniCharIsAlpha -- * * Test if a character is an alphabetic Unicode character. * * Results: * Returns 1 if character is alphabetic. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharIsAlpha( int ch) /* Unicode character to test. */ { if (UNICODE_OUT_OF_RANGE(ch)) { return 0; } return ((ALPHA_BITS >> GetCategory(ch)) & 1); } /* *---------------------------------------------------------------------- * * Tcl_UniCharIsControl -- * * Test if a character is a Unicode control character. * * Results: * Returns non-zero if character is a control. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharIsControl( int ch) /* Unicode character to test. */ { if (UNICODE_OUT_OF_RANGE(ch)) { /* Clear away extension bits, if any */ ch &= 0x1FFFFF; return ((ch == 0xE0001) || ((unsigned)(ch - 0xE0020) <= 0x5F)); } return ((CONTROL_BITS >> GetCategory(ch)) & 1); } /* *---------------------------------------------------------------------- * * Tcl_UniCharIsDigit -- * * Test if a character is a numeric Unicode character. * * Results: * Returns non-zero if character is a digit. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharIsDigit( int ch) /* Unicode character to test. */ { if (UNICODE_OUT_OF_RANGE(ch)) { return 0; } return (GetCategory(ch) == DECIMAL_DIGIT_NUMBER); } /* *---------------------------------------------------------------------- * * Tcl_UniCharIsGraph -- * * Test if a character is any Unicode print character except space. * * Results: * Returns non-zero if character is printable, but not space. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharIsGraph( int ch) /* Unicode character to test. */ { if (UNICODE_OUT_OF_RANGE(ch)) { return ((unsigned)((ch & 0x1FFFFF) - 0xE0100) <= 0xEF); } return ((GRAPH_BITS >> GetCategory(ch)) & 1); } /* *---------------------------------------------------------------------- * * Tcl_UniCharIsLower -- * * Test if a character is a lowercase Unicode character. * * Results: * Returns non-zero if character is lowercase. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharIsLower( int ch) /* Unicode character to test. */ { if (UNICODE_OUT_OF_RANGE(ch)) { return 0; } return (GetCategory(ch) == LOWERCASE_LETTER); } /* *---------------------------------------------------------------------- * * Tcl_UniCharIsPrint -- * * Test if a character is a Unicode print character. * * Results: * Returns non-zero if character is printable. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharIsPrint( int ch) /* Unicode character to test. */ { if (UNICODE_OUT_OF_RANGE(ch)) { return ((unsigned)((ch & 0x1FFFFF) - 0xE0100) <= 0xEF); } return (((GRAPH_BITS|SPACE_BITS) >> GetCategory(ch)) & 1); } /* *---------------------------------------------------------------------- * * Tcl_UniCharIsPunct -- * * Test if a character is a Unicode punctuation character. * * Results: * Returns non-zero if character is punct. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharIsPunct( int ch) /* Unicode character to test. */ { if (UNICODE_OUT_OF_RANGE(ch)) { return 0; } return ((PUNCT_BITS >> GetCategory(ch)) & 1); } /* *---------------------------------------------------------------------- * * Tcl_UniCharIsSpace -- * * Test if a character is a whitespace Unicode character. * * Results: * Returns non-zero if character is a space. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharIsSpace( int ch) /* Unicode character to test. */ { /* Ignore upper 11 bits. */ ch &= 0x1FFFFF; /* * If the character is within the first 127 characters, just use the * standard C function, otherwise consult the Unicode table. */ if (ch < 0x80) { return TclIsSpaceProcM((char) ch); } else if (UNICODE_OUT_OF_RANGE(ch)) { return 0; } else if (ch == 0x0085 || ch == 0x180E || ch == 0x200B || ch == 0x202F || ch == 0x2060 || ch == 0xFEFF) { return 1; } else { return ((SPACE_BITS >> GetCategory(ch)) & 1); } } /* *---------------------------------------------------------------------- * * Tcl_UniCharIsUpper -- * * Test if a character is a uppercase Unicode character. * * Results: * Returns non-zero if character is uppercase. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharIsUpper( int ch) /* Unicode character to test. */ { if (UNICODE_OUT_OF_RANGE(ch)) { return 0; } return (GetCategory(ch) == UPPERCASE_LETTER); } /* *---------------------------------------------------------------------- * * Tcl_UniCharIsWordChar -- * * Test if a character is alphanumeric or a connector punctuation mark. * * Results: * Returns 1 if character is a word character. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_UniCharIsWordChar( int ch) /* Unicode character to test. */ { if (UNICODE_OUT_OF_RANGE(ch)) { return 0; } return ((WORD_BITS >> GetCategory(ch)) & 1); } /* *---------------------------------------------------------------------- * * TclUniCharCaseMatch -- * * See if a particular Unicode string matches a particular pattern. * Allows case insensitivity. This is the Unicode equivalent of the char* * Tcl_StringCaseMatch. The UniChar strings must be NULL-terminated. * This has no provision for counted UniChar strings, thus should not be * used where NULLs are expected in the UniChar string. Use * TclUniCharMatch where possible. * * Results: * The return value is 1 if string matches pattern, and 0 otherwise. The * matching operation permits the following special characters in the * pattern: *?\[] (see the manual entry for details on what these mean). * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclUniCharCaseMatch( const Tcl_UniChar *uniStr, /* Unicode String. */ const Tcl_UniChar *uniPattern, /* Pattern, which may contain special * characters. */ int nocase) /* 0 for case sensitive, 1 for insensitive */ { Tcl_UniChar ch1 = 0, p; while (1) { p = *uniPattern; /* * See if we're at the end of both the pattern and the string. If so, * we succeeded. If we're at the end of the pattern but not at the end * of the string, we failed. */ if (p == 0) { return (*uniStr == 0); } if ((*uniStr == 0) && (p != '*')) { return 0; } /* * Check for a "*" as the next pattern character. It matches any * substring. We handle this by skipping all the characters up to the * next matching one in the pattern, and then calling ourselves * recursively for each postfix of string, until either we match or we * reach the end of the string. */ if (p == '*') { /* * Skip all successive *'s in the pattern */ while (*(++uniPattern) == '*') { /* empty body */ } p = *uniPattern; if (p == 0) { return 1; } if (nocase) { p = Tcl_UniCharToLower(p); } while (1) { /* * Optimization for matching - cruise through the string * quickly if the next char in the pattern isn't a special * character */ if ((p != '[') && (p != '?') && (p != '\\')) { if (nocase) { while (*uniStr && (p != *uniStr) && (p != Tcl_UniCharToLower(*uniStr))) { uniStr++; } } else { while (*uniStr && (p != *uniStr)) { uniStr++; } } } if (TclUniCharCaseMatch(uniStr, uniPattern, nocase)) { return 1; } if (*uniStr == 0) { return 0; } uniStr++; } } /* * Check for a "?" as the next pattern character. It matches any * single character. */ if (p == '?') { uniPattern++; uniStr++; continue; } /* * Check for a "[" as the next pattern character. It is followed by a * list of characters that are acceptable, or by a range (two * characters separated by "-"). */ if (p == '[') { Tcl_UniChar startChar, endChar; uniPattern++; ch1 = (nocase ? Tcl_UniCharToLower(*uniStr) : *uniStr); uniStr++; while (1) { if ((*uniPattern == ']') || (*uniPattern == 0)) { return 0; } startChar = (nocase ? Tcl_UniCharToLower(*uniPattern) : *uniPattern); uniPattern++; if (*uniPattern == '-') { uniPattern++; if (*uniPattern == 0) { return 0; } endChar = (nocase ? Tcl_UniCharToLower(*uniPattern) : *uniPattern); uniPattern++; if (((startChar <= ch1) && (ch1 <= endChar)) || ((endChar <= ch1) && (ch1 <= startChar))) { /* * Matches ranges of form [a-z] or [z-a]. */ break; } } else if (startChar == ch1) { break; } } while (*uniPattern != ']') { if (*uniPattern == 0) { uniPattern--; break; } uniPattern++; } uniPattern++; continue; } /* * If the next pattern character is '\', just strip off the '\' so we * do exact matching on the character that follows. */ if (p == '\\') { if (*(++uniPattern) == '\0') { return 0; } } /* * There's no special character. Just make sure that the next bytes of * each string match. */ if (nocase) { if (Tcl_UniCharToLower(*uniStr) != Tcl_UniCharToLower(*uniPattern)) { return 0; } } else if (*uniStr != *uniPattern) { return 0; } uniStr++; uniPattern++; } } /* *---------------------------------------------------------------------- * * TclUniCharMatch -- * * See if a particular Unicode string matches a particular pattern. * Allows case insensitivity. This is the Unicode equivalent of the char* * Tcl_StringCaseMatch. This variant of TclUniCharCaseMatch uses counted * Strings, so embedded NULLs are allowed. * * Results: * The return value is 1 if string matches pattern, and 0 otherwise. The * matching operation permits the following special characters in the * pattern: *?\[] (see the manual entry for details on what these mean). * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclUniCharMatch( const Tcl_UniChar *string, /* Unicode String. */ Tcl_Size strLen, /* Length of String */ const Tcl_UniChar *pattern, /* Pattern, which may contain special * characters. */ Tcl_Size ptnLen, /* Length of Pattern */ int nocase) /* 0 for case sensitive, 1 for insensitive */ { const Tcl_UniChar *stringEnd, *patternEnd; Tcl_UniChar p; stringEnd = string + strLen; patternEnd = pattern + ptnLen; while (1) { /* * See if we're at the end of both the pattern and the string. If so, * we succeeded. If we're at the end of the pattern but not at the end * of the string, we failed. */ if (pattern == patternEnd) { return (string == stringEnd); } p = *pattern; if ((string == stringEnd) && (p != '*')) { return 0; } /* * Check for a "*" as the next pattern character. It matches any * substring. We handle this by skipping all the characters up to the * next matching one in the pattern, and then calling ourselves * recursively for each postfix of string, until either we match or we * reach the end of the string. */ if (p == '*') { /* * Skip all successive *'s in the pattern. */ while (*(++pattern) == '*') { /* empty body */ } if (pattern == patternEnd) { return 1; } p = *pattern; if (nocase) { p = Tcl_UniCharToLower(p); } while (1) { /* * Optimization for matching - cruise through the string * quickly if the next char in the pattern isn't a special * character. */ if ((p != '[') && (p != '?') && (p != '\\')) { if (nocase) { while ((string < stringEnd) && (p != *string) && (p != Tcl_UniCharToLower(*string))) { string++; } } else { while ((string < stringEnd) && (p != *string)) { string++; } } } if (TclUniCharMatch(string, stringEnd - string, pattern, patternEnd - pattern, nocase)) { return 1; } if (string == stringEnd) { return 0; } string++; } } /* * Check for a "?" as the next pattern character. It matches any * single character. */ if (p == '?') { pattern++; string++; continue; } /* * Check for a "[" as the next pattern character. It is followed by a * list of characters that are acceptable, or by a range (two * characters separated by "-"). */ if (p == '[') { Tcl_UniChar ch1, startChar, endChar; pattern++; ch1 = (nocase ? Tcl_UniCharToLower(*string) : *string); string++; while (1) { if ((*pattern == ']') || (pattern == patternEnd)) { return 0; } startChar = (nocase ? Tcl_UniCharToLower(*pattern) : *pattern); pattern++; if (*pattern == '-') { pattern++; if (pattern == patternEnd) { return 0; } endChar = (nocase ? Tcl_UniCharToLower(*pattern) : *pattern); pattern++; if (((startChar <= ch1) && (ch1 <= endChar)) || ((endChar <= ch1) && (ch1 <= startChar))) { /* * Matches ranges of form [a-z] or [z-a]. */ break; } } else if (startChar == ch1) { break; } } while (*pattern != ']') { if (pattern == patternEnd) { pattern--; break; } pattern++; } pattern++; continue; } /* * If the next pattern character is '\', just strip off the '\' so we * do exact matching on the character that follows. */ if (p == '\\') { if (++pattern == patternEnd) { return 0; } } /* * There's no special character. Just make sure that the next bytes of * each string match. */ if (nocase) { if (Tcl_UniCharToLower(*string) != Tcl_UniCharToLower(*pattern)) { return 0; } } else if (*string != *pattern) { return 0; } string++; pattern++; } } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclUtil.c0000644000175000017500000037673114731032403014762 0ustar sergeisergei/* * tclUtil.c -- * * This file contains utility functions that are used by many Tcl * commands. * * Copyright © 1987-1993 The Regents of the University of California. * Copyright © 1994-1998 Sun Microsystems, Inc. * Copyright © 2001 Kevin B. Kenny. All rights reserved. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include #include "tclInt.h" #include "tclParse.h" #include "tclStringTrim.h" #include "tclTomMath.h" #include /* * The absolute pathname of the executable in which this Tcl library is * running. */ static ProcessGlobalValue executableName = { 0, 0, NULL, NULL, NULL, NULL, NULL }; /* * The following values are used in the flags arguments of Tcl*Scan*Element * and Tcl*Convert*Element. The values TCL_DONT_USE_BRACES and * TCL_DONT_QUOTE_HASH are defined in tcl.h, like so: * #define TCL_DONT_USE_BRACES 1 #define TCL_DONT_QUOTE_HASH 8 * * Those are public flag bits which callers of the public routines * Tcl_Convert*Element() can use to indicate: * * TCL_DONT_USE_BRACES - 1 means the caller is insisting that brace * quoting not be used when converting the list * element. * TCL_DONT_QUOTE_HASH - 1 means the caller insists that a leading hash * character ('#') should *not* be quoted. This * is appropriate when the caller can guarantee * the element is not the first element of a * list, so [eval] cannot mis-parse the element * as a comment. * * The remaining values which can be carried by the flags of these routines * are for internal use only. Make sure they do not overlap with the public * values above. * * The Tcl*Scan*Element() routines make a determination which of 4 modes of * conversion is most appropriate for Tcl*Convert*Element() to perform, and * sets two bits of the flags value to indicate the mode selected. * * For more details, see the comments on the Tcl*Scan*Element and * Tcl*Convert*Element routines. */ enum ConvertFlags { CONVERT_NONE = 0, /* The element needs no quoting. Its literal * string is suitable as is. */ DONT_USE_BRACES = TCL_DONT_USE_BRACES, /* The caller is insisting that brace quoting * not be used when converting the list * element. */ CONVERT_BRACE = 2, /* The conversion should be enclosing the * literal string in braces. */ CONVERT_ESCAPE = 4, /* The conversion should be using backslashes * to escape any characters in the string that * require it. */ DONT_QUOTE_HASH = TCL_DONT_QUOTE_HASH, /* The caller insists that a leading hash * character ('#') should *not* be quoted. This * is appropriate when the caller can guarantee * the element is not the first element of a * list, so [eval] cannot mis-parse the element * as a comment.*/ CONVERT_MASK = CONVERT_BRACE | CONVERT_ESCAPE, /* A mask value used to extract the conversion * mode from the flags argument. * * Also indicates a strange conversion mode * where all special characters are escaped * with backslashes *except for braces*. This * is a strange and unnecessary case, but it's * part of the historical way in which lists * have been formatted in Tcl. To experiment * with removing this case, define the value of * COMPAT to be 0. */ CONVERT_ANY = 16 /* The caller of TclScanElement() declares it * can make no promise about what public flags * will be passed to the matching call of * TclConvertElement(). As such, * TclScanElement() has to determine the worst * case destination buffer length over all * possibilities, and in other cases this means * an overestimate of the required size. * * Used only by callers of TclScanElement(). * The flag value produced by a call to * Tcl*Scan*Element() will never leave this * bit set. */ }; #ifndef COMPAT #define COMPAT 1 #endif /* * Prototypes for functions defined later in this file. */ static void ClearHash(Tcl_HashTable *tablePtr); static void FreeProcessGlobalValue(void *clientData); static void FreeThreadHash(void *clientData); static int GetEndOffsetFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_WideInt endValue, Tcl_WideInt *indexPtr); static Tcl_HashTable * GetThreadHash(Tcl_ThreadDataKey *keyPtr); static int GetWideForIndex(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_WideInt endValue, Tcl_WideInt *widePtr); static int FindElement(Tcl_Interp *interp, const char *string, Tcl_Size stringLength, const char *typeStr, const char *typeCode, const char **elementPtr, const char **nextPtr, Tcl_Size *sizePtr, int *literalPtr); /* * The following is the Tcl object type definition for an object that * represents a list index in the form, "end-offset". It is used as a * performance optimization in Tcl_GetIntForIndex. The internal rep is * stored directly in the wideValue, so no memory management is required * for it. This is a caching internalrep, keeping the result of a parse * around. This type is only created from a pre-existing string, so an * updateStringProc will never be called and need not exist. The type * is unregistered, so has no need of a setFromAnyProc either. */ static const Tcl_ObjType endOffsetType = { "end-offset", /* name */ NULL, /* freeIntRepProc */ NULL, /* dupIntRepProc */ NULL, /* updateStringProc */ NULL, /* setFromAnyProc */ TCL_OBJTYPE_V1(TclLengthOne) }; Tcl_Size TclLengthOne( TCL_UNUSED(Tcl_Obj *)) { return 1; } /* * * STRING REPRESENTATION OF LISTS * * * * * The next several routines implement the conversions of strings to and from * Tcl lists. To understand their operation, the rules of parsing and * generating the string representation of lists must be known. Here we * describe them in one place. * * A list is made up of zero or more elements. Any string is a list if it is * made up of alternating substrings of element-separating ASCII whitespace * and properly formatted elements. * * The ASCII characters which can make up the whitespace between list elements * are: * * \u0009 \t TAB * \u000A \n NEWLINE * \u000B \v VERTICAL TAB * \u000C \f FORM FEED * \u000D \r CARRIAGE RETURN * \u0020 SPACE * * NOTE: differences between this and other places where Tcl defines a role * for "whitespace". * * * Unlike command parsing, here NEWLINE is just another whitespace * character; its role as a command terminator in a script has no * importance here. * * * Unlike command parsing, the BACKSLASH NEWLINE sequence is not * considered to be a whitespace character. * * * Other Unicode whitespace characters (recognized by [string is space] * or Tcl_UniCharIsSpace()) do not play any role as element separators * in Tcl lists. * * * The NUL byte ought not appear, as it is not in strings properly * encoded for Tcl, but if it is present, it is not treated as * separating whitespace, or a string terminator. It is just another * character in a list element. * * The interpretation of a formatted substring as a list element follows rules * similar to the parsing of the words of a command in a Tcl script. Backslash * substitution plays a key role, and is defined exactly as it is in command * parsing. The same routine, TclParseBackslash() is used in both command * parsing and list parsing. * * NOTE: This means that if and when backslash substitution rules ever change * for command parsing, the interpretation of strings as lists also changes. * * Backslash substitution replaces an "escape sequence" of one or more * characters starting with * \u005c \ BACKSLASH * with a single character. The one character escape sequence case happens only * when BACKSLASH is the last character in the string. In all other cases, the * escape sequence is at least two characters long. * * The formatted substrings are interpreted as element values according to the * following cases: * * * If the first character of a formatted substring is * \u007b { OPEN BRACE * then the end of the substring is the matching * \u007d } CLOSE BRACE * character, where matching is determined by counting nesting levels, and * not including any brace characters that are contained within a backslash * escape sequence in the nesting count. Having found the matching brace, * all characters between the braces are the string value of the element. * If no matching close brace is found before the end of the string, the * string is not a Tcl list. If the character following the close brace is * not an element separating whitespace character, or the end of the string, * then the string is not a Tcl list. * * NOTE: this differs from a brace-quoted word in the parsing of a Tcl * command only in its treatment of the backslash-newline sequence. In a * list element, the literal characters in the backslash-newline sequence * become part of the element value. In a script word, conversion to a * single SPACE character is done. * * NOTE: Most list element values can be represented by a formatted * substring using brace quoting. The exceptions are any element value that * includes an unbalanced brace not in a backslash escape sequence, and any * value that ends with a backslash not itself in a backslash escape * sequence. * * * If the first character of a formatted substring is * \u0022 " QUOTE * then the end of the substring is the next QUOTE character, not counting * any QUOTE characters that are contained within a backslash escape * sequence. If no next QUOTE is found before the end of the string, the * string is not a Tcl list. If the character following the closing QUOTE is * not an element separating whitespace character, or the end of the string, * then the string is not a Tcl list. Having found the limits of the * substring, the element value is produced by performing backslash * substitution on the character sequence between the open and close QUOTEs. * * NOTE: Any element value can be represented by this style of formatting, * given suitable choice of backslash escape sequences. * * * All other formatted substrings are terminated by the next element * separating whitespace character in the string. Having found the limits * of the substring, the element value is produced by performing backslash * substitution on it. * * NOTE: Any element value can be represented by this style of formatting, * given suitable choice of backslash escape sequences, with one exception. * The empty string cannot be represented as a list element without the use * of either braces or quotes to delimit it. * * This collection of parsing rules is implemented in the routine * FindElement(). * * In order to produce lists that can be parsed by these rules, we need the * ability to distinguish between characters that are part of a list element * value from characters providing syntax that define the structure of the * list. This means that our code that generates lists must at a minimum be * able to produce escape sequences for the 10 characters identified above * that have significance to a list parser. * * * * CANONICAL LISTS * * * * * * * In addition to the basic rules for parsing strings into Tcl lists, there * are additional properties to be met by the set of list values that are * generated by Tcl. Such list values are often said to be in "canonical * form": * * * When any canonical list is evaluated as a Tcl script, it is a script of * either zero commands (an empty list) or exactly one command. The command * word is exactly the first element of the list, and each argument word is * exactly one of the following elements of the list. This means that any * characters that have special meaning during script evaluation need * special treatment when canonical lists are produced: * * * Whitespace between elements may not include NEWLINE. * * The command terminating character, * \u003b ; SEMICOLON * must be BRACEd, QUOTEd, or escaped so that it does not terminate the * command prematurely. * * Any of the characters that begin substitutions in scripts, * \u0024 $ DOLLAR * \u005b [ OPEN BRACKET * \u005c \ BACKSLASH * need to be BRACEd or escaped. * * In any list where the first character of the first element is * \u0023 # HASH * that HASH character must be BRACEd, QUOTEd, or escaped so that it * does not convert the command into a comment. * * Any list element that contains the character sequence BACKSLASH * NEWLINE cannot be formatted with BRACEs. The BACKSLASH character * must be represented by an escape sequence, and unless QUOTEs are * used, the NEWLINE must be as well. * * * It is also guaranteed that one can use a canonical list as a building * block of a larger script within command substitution, as in this example: * set script "puts \[[list $cmd $arg]]"; eval $script * To support this usage, any appearance of the character * \u005d ] CLOSE BRACKET * in a list element must be BRACEd, QUOTEd, or escaped. * * * Finally it is guaranteed that enclosing a canonical list in braces * produces a new value that is also a canonical list. This new list has * length 1, and its only element is the original canonical list. This same * guarantee also makes it possible to construct scripts where an argument * word is given a list value by enclosing the canonical form of that list * in braces: * set script "puts {[list $one $two $three]}"; eval $script * This sort of coding was once fairly common, though it's become more * idiomatic to see the following instead: * set script [list puts [list $one $two $three]]; eval $script * In order to support this guarantee, every canonical list must have * balance when counting those braces that are not in escape sequences. * * Within these constraints, the canonical list generation routines * TclScanElement() and TclConvertElement() attempt to generate the string for * any list that is easiest to read. When an element value is itself * acceptable as the formatted substring, it is usually used (CONVERT_NONE). * When some quoting or escaping is required, use of BRACEs (CONVERT_BRACE) is * usually preferred over the use of escape sequences (CONVERT_ESCAPE). There * are some exceptions to both of these preferences for reasons of code * simplicity, efficiency, and continuation of historical habits. Canonical * lists never use the QUOTE formatting to delimit their elements because that * form of quoting does not nest, which makes construction of nested lists far * too much trouble. Canonical lists always use only a single SPACE character * for element-separating whitespace. * * * * FUTURE CONSIDERATIONS * * * * * When a list element requires quoting or escaping due to a CLOSE BRACKET * character or an internal QUOTE character, a strange formatting mode is * recommended. For example, if the value "a{b]c}d" is converted by the usual * modes: * * CONVERT_BRACE: a{b]c}d => {a{b]c}d} * CONVERT_ESCAPE: a{b]c}d => a\{b\]c\}d * * we get perfectly usable formatted list elements. However, this is not what * Tcl releases have been producing. Instead, we have: * * CONVERT_MASK: a{b]c}d => a{b\]c}d * * where the CLOSE BRACKET is escaped, but the BRACEs are not. The same effect * can be seen replacing ] with " in this example. There does not appear to be * any functional or aesthetic purpose for this strange additional mode. The * sole purpose I can see for preserving it is to keep generating the same * formatted lists programmers have become accustomed to, and perhaps written * tests to expect. That is, compatibility only. The additional code * complexity required to support this mode is significant. The lines of code * supporting it are delimited in the routines below with #if COMPAT * directives. This makes it easy to experiment with eliminating this * formatting mode simply with "#define COMPAT 0" above. I believe this is * worth considering. * * Another consideration is the treatment of QUOTE characters in list * elements. TclConvertElement() must have the ability to produce the escape * sequence \" so that when a list element begins with a QUOTE we do not * confuse that first character with a QUOTE used as list syntax to define * list structure. However, that is the only place where QUOTE characters need * quoting. In this way, handling QUOTE could really be much more like the way * we handle HASH which also needs quoting and escaping only in particular * situations. Following up this could increase the set of list elements that * can use the CONVERT_NONE formatting mode. * * More speculative is that the demands of canonical list form require brace * balance for the list as a whole, while the current implementation achieves * this by establishing brace balance for every element. * * Finally, a reminder that the rules for parsing and formatting lists are * closely tied together with the rules for parsing and evaluating scripts, * and will need to evolve in sync. */ /* *---------------------------------------------------------------------- * * TclMaxListLength -- * * Given 'bytes' pointing to 'numBytes' bytes, scan through them and * count the number of whitespace runs that could be list element * separators. If 'numBytes' is TCL_INDEX_NONE, scan to the terminating * '\0'. Not a full list parser. Typically used to get a quick and dirty * overestimate of length size in order to allocate space for an actual * list parser to operate with. * * Results: * Returns the largest number of list elements that could possibly be in * this string, interpreted as a Tcl list. If 'endPtr' is not NULL, * writes a pointer to the end of the string scanned there. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size TclMaxListLength( const char *bytes, Tcl_Size numBytes, const char **endPtr) { Tcl_Size count = 0; if ((numBytes == 0) || ((numBytes == TCL_INDEX_NONE) && (*bytes == '\0'))) { /* Empty string case - quick exit */ goto done; } /* * No list element before leading white space. */ count += 1 - TclIsSpaceProcM(*bytes); /* * Count white space runs as potential element separators. */ while (numBytes) { if ((numBytes == TCL_INDEX_NONE) && (*bytes == '\0')) { break; } if (TclIsSpaceProcM(*bytes)) { /* * Space run started; bump count. */ count++; do { bytes++; numBytes -= (numBytes != TCL_INDEX_NONE); } while (numBytes && TclIsSpaceProcM(*bytes)); if ((numBytes == 0) || ((numBytes == TCL_INDEX_NONE) && (*bytes == '\0'))) { break; } /* * (*bytes) is non-space; return to counting state. */ } bytes++; numBytes -= (numBytes != TCL_INDEX_NONE); } /* * No list element following trailing white space. */ count -= TclIsSpaceProcM(bytes[-1]); done: if (endPtr) { *endPtr = bytes; } return count; } /* *---------------------------------------------------------------------- * * TclFindElement -- * * Given a pointer into a Tcl list, locate the first (or next) element in * the list. * * Results: * The return value is normally TCL_OK, which means that the element was * successfully located. If TCL_ERROR is returned it means that list * didn't have proper list structure; the interp's result contains a more * detailed error message. * * If TCL_OK is returned, then *elementPtr will be set to point to the * first element of list, and *nextPtr will be set to point to the * character just after any white space following the last character * that's part of the element. If this is the last argument in the list, * then *nextPtr will point just after the last character in the list * (i.e., at the character at list+listLength). If sizePtr is non-NULL, * *sizePtr is filled in with the number of bytes in the element. If the * element is in braces, then *elementPtr will point to the character * after the opening brace and *sizePtr will not include either of the * braces. If there isn't an element in the list, *sizePtr will be zero, * and both *elementPtr and *nextPtr will point just after the last * character in the list. If literalPtr is non-NULL, *literalPtr is set * to a boolean value indicating whether the substring returned as the * values of **elementPtr and *sizePtr is the literal value of a list * element. If not, a call to TclCopyAndCollapse() is needed to produce * the actual value of the list element. Note: this function does NOT * collapse backslash sequences, but uses *literalPtr to tell callers * when it is required for them to do so. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclFindElement( Tcl_Interp *interp, /* Interpreter to use for error reporting. If * NULL, then no error message is left after * errors. */ const char *list, /* Points to the first byte of a string * containing a Tcl list with zero or more * elements (possibly in braces). */ Tcl_Size listLength, /* Number of bytes in the list's string. */ const char **elementPtr, /* Where to put address of first significant * character in first element of list. */ const char **nextPtr, /* Fill in with location of character just * after all white space following end of * argument (next arg or end of list). */ Tcl_Size *sizePtr, /* If non-zero, fill in with size of * element. */ int *literalPtr) /* If non-zero, fill in with non-zero/zero to * indicate that the substring of *sizePtr * bytes starting at **elementPtr is/is not * the literal list element and therefore * does not/does require a call to * TclCopyAndCollapse() by the caller. */ { return FindElement(interp, list, listLength, "list", "LIST", elementPtr, nextPtr, sizePtr, literalPtr); } int TclFindDictElement( Tcl_Interp *interp, /* Interpreter to use for error reporting. If * NULL, then no error message is left after * errors. */ const char *dict, /* Points to the first byte of a string * containing a Tcl dictionary with zero or * more keys and values (possibly in * braces). */ Tcl_Size dictLength, /* Number of bytes in the dict's string. */ const char **elementPtr, /* Where to put address of first significant * character in the first element (i.e., key * or value) of dict. */ const char **nextPtr, /* Fill in with location of character just * after all white space following end of * element (next arg or end of list). */ Tcl_Size *sizePtr, /* If non-zero, fill in with size of * element. */ int *literalPtr) /* If non-zero, fill in with non-zero/zero to * indicate that the substring of *sizePtr * bytes starting at **elementPtr is/is not * the literal key or value and therefore * does not/does require a call to * TclCopyAndCollapse() by the caller. */ { return FindElement(interp, dict, dictLength, "dict", "DICTIONARY", elementPtr, nextPtr, sizePtr, literalPtr); } static int FindElement( Tcl_Interp *interp, /* Interpreter to use for error reporting. If * NULL, then no error message is left after * errors. */ const char *string, /* Points to the first byte of a string * containing a Tcl list or dictionary with * zero or more elements (possibly in * braces). */ Tcl_Size stringLength, /* Number of bytes in the string. */ const char *typeStr, /* The name of the type of thing we are * parsing, for error messages. */ const char *typeCode, /* The type code for thing we are parsing, for * error messages. */ const char **elementPtr, /* Where to put address of first significant * character in first element. */ const char **nextPtr, /* Fill in with location of character just * after all white space following end of * argument (next arg or end of list/dict). */ Tcl_Size *sizePtr, /* If non-zero, fill in with size of * element. */ int *literalPtr) /* If non-zero, fill in with non-zero/zero to * indicate that the substring of *sizePtr * bytes starting at **elementPtr is/is not * the literal list/dict element and therefore * does not/does require a call to * TclCopyAndCollapse() by the caller. */ { const char *p = string; const char *elemStart; /* Points to first byte of first element. */ const char *limit; /* Points just after list/dict's last byte. */ Tcl_Size openBraces = 0; /* Brace nesting level during parse. */ int inQuotes = 0; Tcl_Size size = 0; Tcl_Size numChars; int literal = 1; const char *p2; /* * Skim off leading white space and check for an opening brace or quote. * We treat embedded NULLs in the list/dict as bytes belonging to a list * element (or dictionary key or value). */ limit = (string + stringLength); while ((p < limit) && (TclIsSpaceProcM(*p))) { p++; } if (p == limit) { /* no element found */ elemStart = limit; goto done; } if (*p == '{') { openBraces = 1; p++; } else if (*p == '"') { inQuotes = 1; p++; } elemStart = p; /* * Find element's end (a space, close brace, or the end of the string). */ while (p < limit) { switch (*p) { /* * Open brace: don't treat specially unless the element is in * braces. In this case, keep a nesting count. */ case '{': if (openBraces != 0) { openBraces++; } break; /* * Close brace: if element is in braces, keep nesting count and * quit when the last close brace is seen. */ case '}': if (openBraces > 1) { openBraces--; } else if (openBraces == 1) { size = (p - elemStart); p++; if ((p >= limit) || TclIsSpaceProcM(*p)) { goto done; } /* * Garbage after the closing brace; return an error. */ if (interp != NULL) { p2 = p; while ((p2 < limit) && (!TclIsSpaceProcM(*p2)) && (p2 < p+20)) { p2++; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s element in braces followed by \"%.*s\" " "instead of space", typeStr, (int) (p2-p), p)); Tcl_SetErrorCode(interp, "TCL", "VALUE", typeCode, "JUNK", (char *)NULL); } return TCL_ERROR; } break; /* * Backslash: skip over everything up to the end of the backslash * sequence. */ case '\\': if (openBraces == 0) { /* * A backslash sequence not within a brace quoted element * means the value of the element is different from the * substring we are parsing. A call to TclCopyAndCollapse() is * needed to produce the element value. Inform the caller. */ literal = 0; } TclParseBackslash(p, limit - p, &numChars, NULL); p += (numChars - 1); break; /* * Double-quote: if element is in quotes then terminate it. */ case '"': if (inQuotes) { size = (p - elemStart); p++; if ((p >= limit) || TclIsSpaceProcM(*p)) { goto done; } /* * Garbage after the closing quote; return an error. */ if (interp != NULL) { p2 = p; while ((p2 < limit) && (!TclIsSpaceProcM(*p2)) && (p2 < p+20)) { p2++; } Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s element in quotes followed by \"%.*s\" " "instead of space", typeStr, (int) (p2-p), p)); Tcl_SetErrorCode(interp, "TCL", "VALUE", typeCode, "JUNK", (char *)NULL); } return TCL_ERROR; } break; default: if (TclIsSpaceProcM(*p)) { /* * Space: ignore if element is in braces or quotes; * otherwise terminate element. */ if ((openBraces == 0) && !inQuotes) { size = (p - elemStart); goto done; } } break; } p++; } /* * End of list/dict: terminate element. */ if (p == limit) { if (openBraces != 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unmatched open brace in %s", typeStr)); Tcl_SetErrorCode(interp, "TCL", "VALUE", typeCode, "BRACE", (char *)NULL); } return TCL_ERROR; } else if (inQuotes) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unmatched open quote in %s", typeStr)); Tcl_SetErrorCode(interp, "TCL", "VALUE", typeCode, "QUOTE", (char *)NULL); } return TCL_ERROR; } size = (p - elemStart); } done: while ((p < limit) && (TclIsSpaceProcM(*p))) { p++; } *elementPtr = elemStart; *nextPtr = p; if (sizePtr != 0) { *sizePtr = size; } if (literalPtr != 0) { *literalPtr = literal; } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCopyAndCollapse -- * * Copy a string and substitute all backslash escape sequences * * Results: * Count bytes get copied from src to dst. Along the way, backslash * sequences are substituted in the copy. After scanning count bytes from * src, a null character is placed at the end of dst. Returns the number * of bytes that got written to dst. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size TclCopyAndCollapse( Tcl_Size count, /* Number of byte to copy from src. */ const char *src, /* Copy from here... */ char *dst) /* ... to here. */ { Tcl_Size newCount = 0; while (count > 0) { char c = *src; if (c == '\\') { char buf[4] = ""; Tcl_Size numRead; Tcl_Size backslashCount = TclParseBackslash(src, count, &numRead, buf); memcpy(dst, buf, backslashCount); dst += backslashCount; newCount += backslashCount; src += numRead; count -= numRead; } else { *dst = c; dst++; newCount++; src++; count--; } } *dst = 0; return newCount; } /* *---------------------------------------------------------------------- * * Tcl_SplitList -- * * Splits a list up into its constituent fields. * * Results * The return value is normally TCL_OK, which means that the list was * successfully split up. If TCL_ERROR is returned, it means that "list" * didn't have proper list structure; the interp's result will contain a * more detailed error message. * * *argvPtr will be filled in with the address of an array whose elements * point to the elements of list, in order. *argcPtr will get filled in * with the number of valid elements in the array. A single block of * memory is dynamically allocated to hold both the argv array and a copy * of the list (with backslashes and braces removed in the standard way). * The caller must eventually free this memory by calling free() on * *argvPtr. Note: *argvPtr and *argcPtr are only modified if the * function returns normally. * * Side effects: * Memory is allocated. * *---------------------------------------------------------------------- */ #undef Tcl_SplitList int Tcl_SplitList( Tcl_Interp *interp, /* Interpreter to use for error reporting. If * NULL, no error message is left. */ const char *list, /* Pointer to string with list structure. */ Tcl_Size *argcPtr, /* Pointer to location to fill in with the * number of elements in the list. */ const char ***argvPtr) /* Pointer to place to store pointer to array * of pointers to list elements. */ { const char **argv, *end, *element; char *p; int result; Tcl_Size length, size, i, elSize; /* * Allocate enough space to work in. A (const char *) for each (possible) * list element plus one more for terminating NULL, plus as many bytes as * in the original string value, plus one more for a terminating '\0'. * Space used to hold element separating white space in the original * string gets re-purposed to hold '\0' characters in the argv array. */ size = TclMaxListLength(list, TCL_INDEX_NONE, &end) + 1; length = end - list; argv = (const char **)Tcl_Alloc((size * sizeof(char *)) + length + 1); for (i = 0, p = ((char *) argv) + size*sizeof(char *); *list != 0; i++) { const char *prevList = list; int literal; result = TclFindElement(interp, list, length, &element, &list, &elSize, &literal); length -= (list - prevList); if (result != TCL_OK) { Tcl_Free((void *)argv); return result; } if (*element == 0) { break; } if (i >= size) { Tcl_Free((void *)argv); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "internal error in Tcl_SplitList", -1)); Tcl_SetErrorCode(interp, "TCL", "INTERNAL", "Tcl_SplitList", (char *)NULL); } return TCL_ERROR; } argv[i] = p; if (literal) { memcpy(p, element, elSize); p += elSize; *p = 0; p++; } else { p += 1 + TclCopyAndCollapse(elSize, element, p); } } argv[i] = NULL; *argvPtr = argv; *argcPtr = i; return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ScanElement -- * * This function is a companion function to Tcl_ConvertElement. It scans * a string to see what needs to be done to it (e.g. add backslashes or * enclosing braces) to make the string into a valid Tcl list element. * * Results: * The return value is an overestimate of the number of bytes that will * be needed by Tcl_ConvertElement to produce a valid list element from * src. The word at *flagPtr is filled in with a value needed by * Tcl_ConvertElement when doing the actual conversion. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_ScanElement( const char *src, /* String to convert to list element. */ int *flagPtr) /* Where to store information to guide * Tcl_ConvertCountedElement. */ { return Tcl_ScanCountedElement(src, TCL_INDEX_NONE, flagPtr); } /* *---------------------------------------------------------------------- * * Tcl_ScanCountedElement -- * * This function is a companion function to Tcl_ConvertCountedElement. It * scans a string to see what needs to be done to it (e.g. add * backslashes or enclosing braces) to make the string into a valid Tcl * list element. If length is TCL_INDEX_NONE, then the string is scanned * from src up to the first null byte. * * Results: * The return value is an overestimate of the number of bytes that will * be needed by Tcl_ConvertCountedElement to produce a valid list element * from src. The word at *flagPtr is filled in with a value needed by * Tcl_ConvertCountedElement when doing the actual conversion. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_ScanCountedElement( const char *src, /* String to convert to Tcl list element. */ Tcl_Size length, /* Number of bytes in src, or TCL_INDEX_NONE. */ int *flagPtr) /* Where to store information to guide * Tcl_ConvertElement. */ { char flags = CONVERT_ANY; Tcl_Size numBytes = TclScanElement(src, length, &flags); *flagPtr = flags; return numBytes; } /* *---------------------------------------------------------------------- * * TclScanElement -- * * This function is a companion function to TclConvertElement. It scans a * string to see what needs to be done to it (e.g. add backslashes or * enclosing braces) to make the string into a valid Tcl list element. If * length is TCL_INDEX_NONE, then the string is scanned from src up to the first null * byte. A NULL value for src is treated as an empty string. The incoming * value of *flagPtr is a report from the caller what additional flags it * will pass to TclConvertElement(). * * Results: * The recommended formatting mode for the element is determined and a * value is written to *flagPtr indicating that recommendation. This * recommendation is combined with the incoming flag values in *flagPtr * set by the caller to determine how many bytes will be needed by * TclConvertElement() in which to write the formatted element following * the recommendation modified by the flag values. This number of bytes * is the return value of the routine. In some situations it may be an * overestimate, but so long as the caller passes the same flags to * TclConvertElement(), it will be large enough. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size TclScanElement( const char *src, /* String to convert to Tcl list element. */ Tcl_Size length, /* Number of bytes in src, or TCL_INDEX_NONE. */ char *flagPtr) /* Where to store information to guide * Tcl_ConvertElement. */ { const char *p = src; Tcl_Size nestingLevel = 0; /* Brace nesting count */ int forbidNone = 0; /* Do not permit CONVERT_NONE mode. Something * needs protection or escape. */ int requireEscape = 0; /* Force use of CONVERT_ESCAPE mode. For some * reason bare or brace-quoted form fails. */ Tcl_Size extra = 0; /* Count of number of extra bytes needed for * formatted element, assuming we use escape * sequences in formatting. */ Tcl_Size bytesNeeded; /* Buffer length computed to complete the * element formatting in the selected mode. */ #if COMPAT int preferEscape = 0; /* Use preferences to track whether to use */ int preferBrace = 0; /* CONVERT_MASK mode. */ int braceCount = 0; /* Count of all braces '{' '}' seen. */ #endif /* COMPAT */ if ((p == NULL) || (length == 0) || ((*p == '\0') && (length == TCL_INDEX_NONE))) { /* * Empty string element must be brace quoted. */ *flagPtr = CONVERT_BRACE; return 2; } #if COMPAT /* * We have an established history in TclConvertElement() when quoting * because of a leading hash character to force what would be the * CONVERT_MASK mode into the CONVERT_BRACE mode. That is, we format * the element #{a"b} like this: * {#{a"b}} * and not like this: * \#{a\"b} * This is inconsistent with [list x{a"b}], but we will not change that now. * Set that preference here so that we compute a tight size requirement. */ if ((*src == '#') && !(*flagPtr & DONT_QUOTE_HASH)) { preferBrace = 1; } #endif if ((*p == '{') || (*p == '"')) { /* * Must escape or protect so leading character of value is not * misinterpreted as list element delimiting syntax. */ forbidNone = 1; #if COMPAT preferBrace = 1; #endif /* COMPAT */ } while (length) { if (CHAR_TYPE(*p) != TYPE_NORMAL) { switch (*p) { case '{': /* TYPE_BRACE */ #if COMPAT braceCount++; #endif /* COMPAT */ extra++; /* Escape '{' => '\{' */ nestingLevel++; break; case '}': /* TYPE_BRACE */ #if COMPAT braceCount++; #endif /* COMPAT */ extra++; /* Escape '}' => '\}' */ if (nestingLevel-- < 1) { /* * Unbalanced braces! Cannot format with brace quoting. */ requireEscape = 1; } break; case ']': /* TYPE_CLOSE_BRACK */ case '"': /* TYPE_SPACE */ #if COMPAT forbidNone = 1; extra++; /* Escapes all just prepend a backslash */ preferEscape = 1; break; #else /* FLOW THROUGH */ #endif /* COMPAT */ case '[': /* TYPE_SUBS */ case '$': /* TYPE_SUBS */ case ';': /* TYPE_COMMAND_END */ forbidNone = 1; extra++; /* Escape sequences all one byte longer. */ #if COMPAT preferBrace = 1; #endif /* COMPAT */ break; case '\\': /* TYPE_SUBS */ extra++; /* Escape '\' => '\\' */ if ((length == 1) || ((length == TCL_INDEX_NONE) && (p[1] == '\0'))) { /* * Final backslash. Cannot format with brace quoting. */ requireEscape = 1; break; } if (p[1] == '\n') { extra++; /* Escape newline => '\n', one byte longer */ /* * Backslash newline sequence. Brace quoting not permitted. */ requireEscape = 1; length -= (length > 0); p++; break; } if ((p[1] == '{') || (p[1] == '}') || (p[1] == '\\')) { extra++; /* Escape sequences all one byte longer. */ length -= (length > 0); p++; } forbidNone = 1; #if COMPAT preferBrace = 1; #endif /* COMPAT */ break; case '\0': /* TYPE_SUBS */ if (length == TCL_INDEX_NONE) { goto endOfString; } /* TODO: Panic on improper encoding? */ break; default: if (TclIsSpaceProcM(*p)) { forbidNone = 1; extra++; /* Escape sequences all one byte longer. */ #if COMPAT preferBrace = 1; #endif } break; } } length -= (length > 0); p++; } endOfString: if (nestingLevel > 0) { /* * Unbalanced braces! Cannot format with brace quoting. */ requireEscape = 1; } /* * We need at least as many bytes as are in the element value... */ bytesNeeded = p - src; if (requireEscape) { /* * We must use escape sequences. Add all the extra bytes needed to * have room to create them. */ bytesNeeded += extra; /* * Make room to escape leading #, if needed. */ if ((*src == '#') && !(*flagPtr & DONT_QUOTE_HASH)) { bytesNeeded++; } *flagPtr = CONVERT_ESCAPE; return bytesNeeded; } if (*flagPtr & CONVERT_ANY) { /* * The caller has not let us know what flags it will pass to * TclConvertElement() so compute the max size we might need for any * possible choice. Normally the formatting using escape sequences is * the longer one, and a minimum "extra" value of 2 makes sure we * don't request too small a buffer in those edge cases where that's * not true. */ if (extra < 2) { extra = 2; } *flagPtr &= ~CONVERT_ANY; *flagPtr |= DONT_USE_BRACES; } if (forbidNone) { /* * We must request some form of quoting of escaping... */ #if COMPAT if (preferEscape && !preferBrace) { /* * If we are quoting solely due to ] or internal " characters use * the CONVERT_MASK mode where we escape all special characters * except for braces. "extra" counted space needed to escape * braces too, so subtract "braceCount" to get our actual needs. */ bytesNeeded += (extra - braceCount); /* Make room to escape leading #, if needed. */ if ((*src == '#') && !(*flagPtr & DONT_QUOTE_HASH)) { bytesNeeded++; } /* * If the caller reports it will direct TclConvertElement() to * use full escapes on the element, add back the bytes needed to * escape the braces. */ if (*flagPtr & DONT_USE_BRACES) { bytesNeeded += braceCount; } *flagPtr = CONVERT_MASK; return bytesNeeded; } #endif /* COMPAT */ if (*flagPtr & DONT_USE_BRACES) { /* * If the caller reports it will direct TclConvertElement() to * use escapes, add the extra bytes needed to have room for them. */ bytesNeeded += extra; /* * Make room to escape leading #, if needed. */ if ((*src == '#') && !(*flagPtr & DONT_QUOTE_HASH)) { bytesNeeded++; } } else { /* * Add 2 bytes for room for the enclosing braces. */ bytesNeeded += 2; } *flagPtr = CONVERT_BRACE; return bytesNeeded; } /* * So far, no need to quote or escape anything. */ if ((*src == '#') && !(*flagPtr & DONT_QUOTE_HASH)) { /* * If we need to quote a leading #, make room to enclose in braces. */ bytesNeeded += 2; } *flagPtr = CONVERT_NONE; return bytesNeeded; } /* *---------------------------------------------------------------------- * * Tcl_ConvertElement -- * * This is a companion function to Tcl_ScanElement. Given the information * produced by Tcl_ScanElement, this function converts a string to a list * element equal to that string. * * Results: * Information is copied to *dst in the form of a list element identical * to src (i.e. if Tcl_SplitList is applied to dst it will produce a * string identical to src). The return value is a count of the number of * characters copied (not including the terminating NULL character). * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_ConvertElement( const char *src, /* Source information for list element. */ char *dst, /* Place to put list-ified element. */ int flags) /* Flags produced by Tcl_ScanElement. */ { return Tcl_ConvertCountedElement(src, TCL_INDEX_NONE, dst, flags); } /* *---------------------------------------------------------------------- * * Tcl_ConvertCountedElement -- * * This is a companion function to Tcl_ScanCountedElement. Given the * information produced by Tcl_ScanCountedElement, this function converts * a string to a list element equal to that string. * * Results: * Information is copied to *dst in the form of a list element identical * to src (i.e. if Tcl_SplitList is applied to dst it will produce a * string identical to src). The return value is a count of the number of * characters copied (not including the terminating NULL character). * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size Tcl_ConvertCountedElement( const char *src, /* Source information for list element. */ Tcl_Size length, /* Number of bytes in src, or TCL_INDEX_NONE. */ char *dst, /* Place to put list-ified element. */ int flags) /* Flags produced by Tcl_ScanElement. */ { Tcl_Size numBytes = TclConvertElement(src, length, dst, flags); dst[numBytes] = '\0'; return numBytes; } /* *---------------------------------------------------------------------- * * TclConvertElement -- * * This is a companion function to TclScanElement. Given the information * produced by TclScanElement, this function converts a string to a list * element equal to that string. * * Results: * Information is copied to *dst in the form of a list element identical * to src (i.e. if Tcl_SplitList is applied to dst it will produce a * string identical to src). The return value is a count of the number of * characters copied (not including the terminating NULL character). * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size TclConvertElement( const char *src, /* Source information for list element. */ Tcl_Size length, /* Number of bytes in src, or TCL_INDEX_NONE. */ char *dst, /* Place to put list-ified element. */ int flags) /* Flags produced by Tcl_ScanElement. */ { int conversion = flags & CONVERT_MASK; char *p = dst; /* * Let the caller demand we use escape sequences rather than braces. */ if ((flags & DONT_USE_BRACES) && (conversion & CONVERT_BRACE)) { conversion = CONVERT_ESCAPE; } /* * No matter what the caller demands, empty string must be braced! */ if ((src == NULL) || (length == 0) || (*src == '\0' && length == TCL_INDEX_NONE)) { p[0] = '{'; p[1] = '}'; return 2; } /* * Escape leading hash as needed and requested. */ if ((*src == '#') && !(flags & DONT_QUOTE_HASH)) { if (conversion == CONVERT_ESCAPE) { p[0] = '\\'; p[1] = '#'; p += 2; src++; length -= (length > 0); } else { conversion = CONVERT_BRACE; } } /* * No escape or quoting needed. Copy the literal string value. */ if (conversion == CONVERT_NONE) { if (length == TCL_INDEX_NONE) { /* TODO: INT_MAX overflow? */ while (*src) { *p++ = *src++; } return p - dst; } else { memcpy(dst, src, length); return length; } } /* * Formatted string is original string enclosed in braces. */ if (conversion == CONVERT_BRACE) { *p = '{'; p++; if (length == TCL_INDEX_NONE) { /* TODO: INT_MAX overflow? */ while (*src) { *p++ = *src++; } } else { memcpy(p, src, length); p += length; } *p = '}'; p++; return (p - dst); } /* conversion == CONVERT_ESCAPE or CONVERT_MASK */ /* * Formatted string is original string converted to escape sequences. */ for ( ; length; src++, length -= (length > 0)) { switch (*src) { case ']': case '[': case '$': case ';': case ' ': case '\\': case '"': *p = '\\'; p++; break; case '{': case '}': #if COMPAT if (conversion == CONVERT_ESCAPE) #endif /* COMPAT */ { *p = '\\'; p++; } break; case '\f': *p = '\\'; p++; *p = 'f'; p++; continue; case '\n': *p = '\\'; p++; *p = 'n'; p++; continue; case '\r': *p = '\\'; p++; *p = 'r'; p++; continue; case '\t': *p = '\\'; p++; *p = 't'; p++; continue; case '\v': *p = '\\'; p++; *p = 'v'; p++; continue; case '\0': if (length == TCL_INDEX_NONE) { return (p - dst); } /* * If we reach this point, there's an embedded NULL in the string * range being processed, which should not happen when the * encoding rules for Tcl strings are properly followed. If the * day ever comes when we stop tolerating such things, this is * where to put the Tcl_Panic(). */ break; } *p = *src; p++; } return (p - dst); } /* *---------------------------------------------------------------------- * * Tcl_Merge -- * * Given a collection of strings, merge them together into a single * string that has proper Tcl list structured (i.e. Tcl_SplitList may be * used to retrieve strings equal to the original elements, and Tcl_Eval * will parse the string back into its original elements). * * Results: * The return value is the address of a dynamically-allocated string * containing the merged list. * * Side effects: * None. * *---------------------------------------------------------------------- */ char * Tcl_Merge( Tcl_Size argc, /* How many strings to merge. */ const char *const *argv) /* Array of string values. */ { #define LOCAL_SIZE 64 char localFlags[LOCAL_SIZE], *flagPtr = NULL; Tcl_Size i; size_t bytesNeeded = 0; char *result, *dst; /* * Handle empty list case first, so logic of the general case can be * simpler. */ if (argc <= 0) { if (argc < 0) { Tcl_Panic("Tcl_Merge called with negative argc (%" TCL_SIZE_MODIFIER "d)", argc); } result = (char *)Tcl_Alloc(1); result[0] = '\0'; return result; } /* * Pass 1: estimate space, gather flags. */ if (argc <= LOCAL_SIZE) { flagPtr = localFlags; } else { flagPtr = (char *)Tcl_Alloc(argc); } for (i = 0; i < argc; i++) { flagPtr[i] = ( i ? DONT_QUOTE_HASH : 0 ); bytesNeeded += TclScanElement(argv[i], TCL_INDEX_NONE, &flagPtr[i]); } bytesNeeded += argc; /* * Pass two: copy into the result area. */ result = (char *)Tcl_Alloc(bytesNeeded); dst = result; for (i = 0; i < argc; i++) { flagPtr[i] |= ( i ? DONT_QUOTE_HASH : 0 ); dst += TclConvertElement(argv[i], TCL_INDEX_NONE, dst, flagPtr[i]); *dst = ' '; dst++; } dst[-1] = 0; if (flagPtr != localFlags) { Tcl_Free(flagPtr); } return result; } /* *---------------------------------------------------------------------- * * TclTrimRight -- * Takes two counted strings in the Tcl encoding. Conceptually * finds the sub string (offset) to trim from the right side of the * first string all characters found in the second string. * * Results: * The number of bytes to be removed from the end of the string. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size TclTrimRight( const char *bytes, /* String to be trimmed... */ Tcl_Size numBytes, /* ...and its length in bytes */ /* Calls to TclUtfToUniChar() in this routine * rely on (bytes[numBytes] == '\0'). */ const char *trim, /* String of trim characters... */ Tcl_Size numTrim) /* ...and its length in bytes */ /* Calls to TclUtfToUniChar() in this routine * rely on (trim[numTrim] == '\0'). */ { const char *pp, *p = bytes + numBytes; int ch1, ch2; /* Empty strings -> nothing to do */ if ((numBytes == 0) || (numTrim == 0)) { return 0; } /* * Outer loop: iterate over string to be trimmed. */ do { const char *q = trim; Tcl_Size pInc = 0, bytesLeft = numTrim; pp = Tcl_UtfPrev(p, bytes); do { pp += pInc; pInc = TclUtfToUniChar(pp, &ch1); } while (pp + pInc < p); /* * Inner loop: scan trim string for match to current character. */ do { pInc = TclUtfToUniChar(q, &ch2); if (ch1 == ch2) { break; } q += pInc; bytesLeft -= pInc; } while (bytesLeft); if (bytesLeft == 0) { /* * No match; trim task done; *p is last non-trimmed char. */ break; } p = pp; } while (p > bytes); return numBytes - (p - bytes); } /* *---------------------------------------------------------------------- * * TclTrimLeft -- * * Takes two counted strings in the Tcl encoding. Conceptually * finds the sub string (offset) to trim from the left side of the * first string all characters found in the second string. * * Results: * The number of bytes to be removed from the start of the string. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size TclTrimLeft( const char *bytes, /* String to be trimmed... */ Tcl_Size numBytes, /* ...and its length in bytes */ /* Calls to TclUtfToUniChar() in this routine * rely on (bytes[numBytes] == '\0'). */ const char *trim, /* String of trim characters... */ Tcl_Size numTrim) /* ...and its length in bytes */ /* Calls to TclUtfToUniChar() in this routine * rely on (trim[numTrim] == '\0'). */ { const char *p = bytes; int ch1, ch2; /* Empty strings -> nothing to do */ if ((numBytes == 0) || (numTrim == 0)) { return 0; } /* * Outer loop: iterate over string to be trimmed. */ do { Tcl_Size pInc = TclUtfToUniChar(p, &ch1); const char *q = trim; Tcl_Size bytesLeft = numTrim; /* * Inner loop: scan trim string for match to current character. */ do { Tcl_Size qInc = TclUtfToUniChar(q, &ch2); if (ch1 == ch2) { break; } q += qInc; bytesLeft -= qInc; } while (bytesLeft); if (bytesLeft == 0) { /* * No match; trim task done; *p is first non-trimmed char. */ break; } p += pInc; numBytes -= pInc; } while (numBytes > 0); return p - bytes; } /* *---------------------------------------------------------------------- * * TclTrim -- * Finds the sub string (offset) to trim from both sides of the * first string all characters found in the second string. * * Results: * The number of bytes to be removed from the start of the string * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Size TclTrim( const char *bytes, /* String to be trimmed... */ Tcl_Size numBytes, /* ...and its length in bytes */ /* Calls in this routine * rely on (bytes[numBytes] == '\0'). */ const char *trim, /* String of trim characters... */ Tcl_Size numTrim, /* ...and its length in bytes */ /* Calls in this routine * rely on (trim[numTrim] == '\0'). */ Tcl_Size *trimRightPtr) /* Offset from the end of the string. */ { Tcl_Size trimLeft = 0, trimRight = 0; /* Empty strings -> nothing to do */ if ((numBytes > 0) && (numTrim > 0)) { /* When bytes is NUL-terminated, returns 0 <= trimLeft <= numBytes */ trimLeft = TclTrimLeft(bytes, numBytes, trim, numTrim); numBytes -= trimLeft; /* If we did not trim the whole string, it starts with a character * that we will not trim. Skip over it. */ if (numBytes > 0) { int ch; const char *first = bytes + trimLeft; bytes += TclUtfToUniChar(first, &ch); numBytes -= (bytes - first); if (numBytes > 0) { /* When bytes is NUL-terminated, returns * 0 <= trimRight <= numBytes */ trimRight = TclTrimRight(bytes, numBytes, trim, numTrim); } } } *trimRightPtr = trimRight; return trimLeft; } /* *---------------------------------------------------------------------- * * Tcl_Concat -- * * Concatenate a set of strings into a single large string. * * Results: * The return value is dynamically-allocated string containing a * concatenation of all the strings in argv, with spaces between the * original argv elements. * * Side effects: * Memory is allocated for the result; the caller is responsible for * freeing the memory. * *---------------------------------------------------------------------- */ /* The whitespace characters trimmed during [concat] operations */ #define CONCAT_WS_SIZE (sizeof(CONCAT_TRIM_SET "") - 1) char * Tcl_Concat( Tcl_Size argc, /* Number of strings to concatenate. */ const char *const *argv) /* Array of strings to concatenate. */ { Tcl_Size i, needSpace = 0, bytesNeeded = 0; char *result, *p; /* * Dispose of the empty result corner case first to simplify later code. */ if (argc == 0) { result = (char *) Tcl_Alloc(1); result[0] = '\0'; return result; } /* * First allocate the result buffer at the size required. */ for (i = 0; i < argc; i++) { bytesNeeded += strlen(argv[i]); if (bytesNeeded < 0) { Tcl_Panic("Tcl_Concat: max size of Tcl value exceeded"); } } /* * All element bytes + (argc - 1) spaces + 1 terminating NULL. */ if (bytesNeeded + argc - 1 < 0) { /* * Panic test could be tighter, but not going to bother for this * legacy routine. */ Tcl_Panic("Tcl_Concat: max size of Tcl value exceeded"); } result = (char *)Tcl_Alloc(bytesNeeded + argc); for (p = result, i = 0; i < argc; i++) { Tcl_Size triml, trimr, elemLength; const char *element; element = argv[i]; elemLength = strlen(argv[i]); /* Trim away the leading/trailing whitespace. */ triml = TclTrim(element, elemLength, CONCAT_TRIM_SET, CONCAT_WS_SIZE, &trimr); element += triml; elemLength -= triml + trimr; /* Do not permit trimming to expose a final backslash character. */ elemLength += trimr && (element[elemLength - 1] == '\\'); /* * If we're left with empty element after trimming, do nothing. */ if (elemLength == 0) { continue; } /* * Append to the result with space if needed. */ if (needSpace) { *p++ = ' '; } memcpy(p, element, elemLength); p += elemLength; needSpace = 1; } *p = '\0'; return result; } /* *---------------------------------------------------------------------- * * Tcl_ConcatObj -- * * Concatenate the strings from a set of objects into a single string * object with spaces between the original strings. * * Results: * The return value is a new string object containing a concatenation of * the strings in objv. Its ref count is zero. * * Side effects: * A new object is created. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_ConcatObj( Tcl_Size objc, /* Number of objects to concatenate. */ Tcl_Obj *const objv[]) /* Array of objects to concatenate. */ { int needSpace = 0; Tcl_Size i, bytesNeeded = 0, elemLength; const char *element; Tcl_Obj *objPtr, *resPtr; /* * Check first to see if all the items are of list type or empty. If so, * we will concat them together as lists, and return a list object. This * is only valid when the lists are in canonical form. */ for (i = 0; i < objc; i++) { Tcl_Size length; objPtr = objv[i]; if (TclListObjIsCanonical(objPtr) || TclObjTypeHasProc(objPtr, indexProc)) { continue; } (void)TclGetStringFromObj(objPtr, &length); if (length > 0) { break; } } if (i == objc) { resPtr = NULL; for (i = 0; i < objc; i++) { objPtr = objv[i]; if (!TclListObjIsCanonical(objPtr) && !TclObjTypeHasProc(objPtr, indexProc)) { continue; } if (resPtr) { Tcl_Obj *elemPtr = NULL; Tcl_ListObjIndex(NULL, objPtr, 0, &elemPtr); if (elemPtr == NULL) { continue; } if (TclGetString(elemPtr)[0] == '#' || TCL_OK != Tcl_ListObjAppendList(NULL, resPtr, objPtr)) { /* Abandon ship! */ Tcl_DecrRefCount(resPtr); Tcl_BounceRefCount(elemPtr); // could be an abstract list element goto slow; } Tcl_BounceRefCount(elemPtr); // could be an an abstract list element } else { resPtr = TclListObjCopy(NULL, objPtr); } } if (!resPtr) { TclNewObj(resPtr); } return resPtr; } slow: /* * Something cannot be determined to be safe, so build the concatenation * the slow way, using the string representations. * * First try to preallocate the size required. */ for (i = 0; i < objc; i++) { element = TclGetStringFromObj(objv[i], &elemLength); if (bytesNeeded > (TCL_SIZE_MAX - elemLength)) { break; /* Overflow. Do not preallocate. See comment below. */ } bytesNeeded += elemLength; } /* * Does not matter if this fails, will simply try later to build up the * string with each Append reallocating as needed with the usual string * append algorithm. When that fails it will report the error. */ TclNewObj(resPtr); (void) Tcl_AttemptSetObjLength(resPtr, bytesNeeded + objc - 1); Tcl_SetObjLength(resPtr, 0); for (i = 0; i < objc; i++) { Tcl_Size triml, trimr; element = TclGetStringFromObj(objv[i], &elemLength); /* Trim away the leading/trailing whitespace. */ triml = TclTrim(element, elemLength, CONCAT_TRIM_SET, CONCAT_WS_SIZE, &trimr); element += triml; elemLength -= triml + trimr; /* Do not permit trimming to expose a final backslash character. */ elemLength += trimr && (element[elemLength - 1] == '\\'); /* * If we're left with empty element after trimming, do nothing. */ if (elemLength == 0) { continue; } /* * Append to the result with space if needed. */ if (needSpace) { Tcl_AppendToObj(resPtr, " ", 1); } Tcl_AppendToObj(resPtr, element, elemLength); needSpace = 1; } return resPtr; } /* *---------------------------------------------------------------------- * * Tcl_StringCaseMatch -- * * See if a particular string matches a particular pattern. Allows case * insensitivity. * * Results: * The return value is 1 if string matches pattern, and 0 otherwise. The * matching operation permits the following special characters in the * pattern: *?\[] (see the manual entry for details on what these mean). * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_StringCaseMatch( const char *str, /* String. */ const char *pattern, /* Pattern, which may contain special * characters. */ int nocase) /* 0 for case sensitive, 1 for insensitive */ { int p, charLen; int ch1 = 0, ch2 = 0; while (1) { p = *pattern; /* * See if we're at the end of both the pattern and the string. If so, * we succeeded. If we're at the end of the pattern but not at the end * of the string, we failed. */ if (p == '\0') { return (*str == '\0'); } if ((*str == '\0') && (p != '*')) { return 0; } /* * Check for a "*" as the next pattern character. It matches any * substring. We handle this by calling ourselves recursively for each * postfix of string, until either we match or we reach the end of the * string. */ if (p == '*') { /* * Skip all successive *'s in the pattern */ while (*(++pattern) == '*'); p = *pattern; if (p == '\0') { return 1; } /* * This is a special case optimization for single-byte utf. */ if (UCHAR(*pattern) < 0x80) { ch2 = (int) (nocase ? tolower(UCHAR(*pattern)) : UCHAR(*pattern)); } else { TclUtfToUniChar(pattern, &ch2); if (nocase) { ch2 = Tcl_UniCharToLower(ch2); } } while (1) { /* * Optimization for matching - cruise through the string * quickly if the next char in the pattern isn't a special * character */ if ((p != '[') && (p != '?') && (p != '\\')) { if (nocase) { while (*str) { charLen = TclUtfToUniChar(str, &ch1); if (ch2==ch1 || ch2==Tcl_UniCharToLower(ch1)) { break; } str += charLen; } } else { /* * There's no point in trying to make this code * shorter, as the number of bytes you want to compare * each time is non-constant. */ while (*str) { charLen = TclUtfToUniChar(str, &ch1); if (ch2 == ch1) { break; } str += charLen; } } } if (Tcl_StringCaseMatch(str, pattern, nocase)) { return 1; } if (*str == '\0') { return 0; } str += TclUtfToUniChar(str, &ch1); } } /* * Check for a "?" as the next pattern character. It matches any * single character. */ if (p == '?') { pattern++; str += TclUtfToUniChar(str, &ch1); continue; } /* * Check for a "[" as the next pattern character. It is followed by a * list of characters that are acceptable, or by a range (two * characters separated by "-"). */ if (p == '[') { int startChar = 0, endChar = 0; pattern++; if (UCHAR(*str) < 0x80) { ch1 = (int) (nocase ? tolower(UCHAR(*str)) : UCHAR(*str)); str++; } else { str += TclUtfToUniChar(str, &ch1); if (nocase) { ch1 = Tcl_UniCharToLower(ch1); } } while (1) { if ((*pattern == ']') || (*pattern == '\0')) { return 0; } if (UCHAR(*pattern) < 0x80) { startChar = (int) (nocase ? tolower(UCHAR(*pattern)) : UCHAR(*pattern)); pattern++; } else { pattern += TclUtfToUniChar(pattern, &startChar); if (nocase) { startChar = Tcl_UniCharToLower(startChar); } } if (*pattern == '-') { pattern++; if (*pattern == '\0') { return 0; } if (UCHAR(*pattern) < 0x80) { endChar = (int) (nocase ? tolower(UCHAR(*pattern)) : UCHAR(*pattern)); pattern++; } else { pattern += TclUtfToUniChar(pattern, &endChar); if (nocase) { endChar = Tcl_UniCharToLower(endChar); } } if (((startChar <= ch1) && (ch1 <= endChar)) || ((endChar <= ch1) && (ch1 <= startChar))) { /* * Matches ranges of form [a-z] or [z-a]. */ break; } } else if (startChar == ch1) { break; } } /* If we reach here, we matched. Need to move past closing ] */ while (*pattern != ']') { if (*pattern == '\0') { /* We ran out of pattern after matching something in * (unclosed!) brackets. So long as we ran out of string * at the same time, we have a match. Otherwise, not. */ return (*str == '\0'); } pattern++; } pattern++; continue; } /* * If the next pattern character is '\', just strip off the '\' so we * do exact matching on the character that follows. */ if (p == '\\') { pattern++; if (*pattern == '\0') { return 0; } } /* * There's no special character. Just make sure that the next bytes of * each string match. */ str += TclUtfToUniChar(str, &ch1); pattern += TclUtfToUniChar(pattern, &ch2); if (nocase) { if (Tcl_UniCharToLower(ch1) != Tcl_UniCharToLower(ch2)) { return 0; } } else if (ch1 != ch2) { return 0; } } } /* *---------------------------------------------------------------------- * * TclByteArrayMatch -- * * See if a particular string matches a particular pattern. Does not * allow for case insensitivity. * Parallels tclUtf.c:TclUniCharMatch, adjusted for char* and sans nocase. * * Results: * The return value is 1 if string matches pattern, and 0 otherwise. The * matching operation permits the following special characters in the * pattern: *?\[] (see the manual entry for details on what these mean). * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclByteArrayMatch( const unsigned char *string,/* String. */ Tcl_Size strLen, /* Length of String */ const unsigned char *pattern, /* Pattern, which may contain special * characters. */ Tcl_Size ptnLen, /* Length of Pattern */ TCL_UNUSED(int) /*flags*/) { const unsigned char *stringEnd, *patternEnd; unsigned char p; stringEnd = string + strLen; patternEnd = pattern + ptnLen; while (1) { /* * See if we're at the end of both the pattern and the string. If so, * we succeeded. If we're at the end of the pattern but not at the end * of the string, we failed. */ if (pattern == patternEnd) { return (string == stringEnd); } p = *pattern; if ((string == stringEnd) && (p != '*')) { return 0; } /* * Check for a "*" as the next pattern character. It matches any * substring. We handle this by skipping all the characters up to the * next matching one in the pattern, and then calling ourselves * recursively for each postfix of string, until either we match or we * reach the end of the string. */ if (p == '*') { /* * Skip all successive *'s in the pattern. */ while ((++pattern < patternEnd) && (*pattern == '*')) { /* empty body */ } if (pattern == patternEnd) { return 1; } p = *pattern; while (1) { /* * Optimization for matching - cruise through the string * quickly if the next char in the pattern isn't a special * character. */ if ((p != '[') && (p != '?') && (p != '\\')) { while ((string < stringEnd) && (p != *string)) { string++; } } if (TclByteArrayMatch(string, stringEnd - string, pattern, patternEnd - pattern, 0)) { return 1; } if (string == stringEnd) { return 0; } string++; } } /* * Check for a "?" as the next pattern character. It matches any * single character. */ if (p == '?') { pattern++; string++; continue; } /* * Check for a "[" as the next pattern character. It is followed by a * list of characters that are acceptable, or by a range (two * characters separated by "-"). */ if (p == '[') { unsigned char ch1, startChar, endChar; pattern++; ch1 = *string; string++; while (1) { if ((*pattern == ']') || (pattern == patternEnd)) { return 0; } startChar = *pattern; pattern++; if (*pattern == '-') { pattern++; if (pattern == patternEnd) { return 0; } endChar = *pattern; pattern++; if (((startChar <= ch1) && (ch1 <= endChar)) || ((endChar <= ch1) && (ch1 <= startChar))) { /* * Matches ranges of form [a-z] or [z-a]. */ break; } } else if (startChar == ch1) { break; } } while (*pattern != ']') { if (pattern == patternEnd) { pattern--; break; } pattern++; } pattern++; continue; } /* * If the next pattern character is '\', just strip off the '\' so we * do exact matching on the character that follows. */ if (p == '\\') { if (++pattern == patternEnd) { return 0; } } /* * There's no special character. Just make sure that the next bytes of * each string match. */ if (*string != *pattern) { return 0; } string++; pattern++; } } /* *---------------------------------------------------------------------- * * TclStringMatchObj -- * * See if a particular string matches a particular pattern. Allows case * insensitivity. This is the generic multi-type handler for the various * matching algorithms. * * Results: * The return value is 1 if string matches pattern, and 0 otherwise. The * matching operation permits the following special characters in the * pattern: *?\[] (see the manual entry for details on what these mean). * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclStringMatchObj( Tcl_Obj *strObj, /* string object. */ Tcl_Obj *ptnObj, /* pattern object. */ int flags) /* Only TCL_MATCH_NOCASE should be passed, or * 0. */ { int match; Tcl_Size length = 0, plen = 0; /* * Promote based on the type of incoming object. * XXX: Currently doesn't take advantage of exact-ness that * XXX: TclReToGlob tells us about trivial = nocase ? 0 : TclMatchIsTrivial(TclGetString(ptnObj)); */ if (TclHasInternalRep(strObj, &tclStringType) || (strObj->typePtr == NULL)) { Tcl_UniChar *udata, *uptn; udata = Tcl_GetUnicodeFromObj(strObj, &length); uptn = Tcl_GetUnicodeFromObj(ptnObj, &plen); match = TclUniCharMatch(udata, length, uptn, plen, flags); } else if (TclIsPureByteArray(strObj) && TclIsPureByteArray(ptnObj) && !flags) { unsigned char *data, *ptn; data = Tcl_GetBytesFromObj(NULL, strObj, &length); ptn = Tcl_GetBytesFromObj(NULL, ptnObj, &plen); match = TclByteArrayMatch(data, length, ptn, plen, 0); } else { match = Tcl_StringCaseMatch(TclGetString(strObj), TclGetString(ptnObj), flags); } return match; } /* *---------------------------------------------------------------------- * * Tcl_DStringInit -- * * Initializes a dynamic string, discarding any previous contents of the * string (Tcl_DStringFree should have been called already if the dynamic * string was previously in use). * * Results: * None. * * Side effects: * The dynamic string is initialized to be empty. * *---------------------------------------------------------------------- */ void Tcl_DStringInit( Tcl_DString *dsPtr) /* Pointer to structure for dynamic string. */ { dsPtr->string = dsPtr->staticSpace; dsPtr->length = 0; dsPtr->spaceAvl = TCL_DSTRING_STATIC_SIZE; dsPtr->staticSpace[0] = '\0'; } /* *---------------------------------------------------------------------- * * Tcl_DStringAppend -- * * Append more bytes to the current value of a dynamic string. * * Results: * The return value is a pointer to the dynamic string's new value. * * Side effects: * Length bytes from "bytes" (or all of "bytes" if length is less than * zero) are added to the current value of the string. Memory gets * reallocated if needed to accomodate the string's new size. * *---------------------------------------------------------------------- */ char * Tcl_DStringAppend( Tcl_DString *dsPtr, /* Structure describing dynamic string. */ const char *bytes, /* String to append. If length is * TCL_INDEX_NONE then this must be * null-terminated. */ Tcl_Size length) /* Number of bytes from "bytes" to append. If * TCL_INDEX_NONE, then append all of bytes, up * to null at end. */ { Tcl_Size newSize; if (length < 0) { length = strlen(bytes); } if (length > (TCL_SIZE_MAX - dsPtr->length - 1)) { Tcl_Panic("max size for a Tcl value (%" TCL_SIZE_MODIFIER "d bytes) exceeded", TCL_SIZE_MAX); return NULL; /* NOTREACHED */ } newSize = length + dsPtr->length + 1; if (newSize > dsPtr->spaceAvl) { if (dsPtr->string == dsPtr->staticSpace) { char *newString; newString = (char *) TclAllocEx(newSize, &dsPtr->spaceAvl); memcpy(newString, dsPtr->string, dsPtr->length); dsPtr->string = newString; } else { Tcl_Size offset = -1; /* See [16896d49fd] */ if (bytes >= dsPtr->string && bytes <= dsPtr->string + dsPtr->length) { /* Source string is within this DString. Note offset */ offset = bytes - dsPtr->string; } dsPtr->string = (char *)TclReallocEx(dsPtr->string, newSize, &dsPtr->spaceAvl); if (offset >= 0) { bytes = dsPtr->string + offset; } } } /* * Copy the new string into the buffer at the end of the old one. */ memcpy(dsPtr->string + dsPtr->length, bytes, length); dsPtr->length += length; dsPtr->string[dsPtr->length] = '\0'; return dsPtr->string; } /* *---------------------------------------------------------------------- * * TclDStringAppendObj, TclDStringAppendDString -- * * Simple wrappers round Tcl_DStringAppend that make it easier to append * from particular sources of strings. * *---------------------------------------------------------------------- */ char * TclDStringAppendObj( Tcl_DString *dsPtr, Tcl_Obj *objPtr) { Tcl_Size length; const char *bytes = TclGetStringFromObj(objPtr, &length); return Tcl_DStringAppend(dsPtr, bytes, length); } char * TclDStringAppendDString( Tcl_DString *dsPtr, Tcl_DString *toAppendPtr) { return Tcl_DStringAppend(dsPtr, Tcl_DStringValue(toAppendPtr), Tcl_DStringLength(toAppendPtr)); } /* *---------------------------------------------------------------------- * * Tcl_DStringAppendElement -- * * Append a list element to the current value of a dynamic string. * * Results: * The return value is a pointer to the dynamic string's new value. * * Side effects: * String is reformatted as a list element and added to the current value * of the string. Memory gets reallocated if needed to accomodate the * string's new size. * *---------------------------------------------------------------------- */ char * Tcl_DStringAppendElement( Tcl_DString *dsPtr, /* Structure describing dynamic string. */ const char *element) /* String to append. Must be * null-terminated. */ { char *dst = dsPtr->string + dsPtr->length; int needSpace = TclNeedSpace(dsPtr->string, dst); char flags = 0; int quoteHash = 1; Tcl_Size newSize; if (needSpace) { /* * If we need a space to separate the new element from something * already ending the string, we're not appending the first element * of any list, so we need not quote any leading hash character. */ quoteHash = 0; } else { /* * We don't need a space, maybe because there's some already there. * Checking whether we might be appending a first element is a bit * more involved. * * Backtrack over all whitespace. */ while ((--dst >= dsPtr->string) && TclIsSpaceProcM(*dst)) { } /* Call again without whitespace to confound things. */ quoteHash = !TclNeedSpace(dsPtr->string, dst+1); } if (!quoteHash) { flags |= DONT_QUOTE_HASH; } newSize = dsPtr->length + needSpace + TclScanElement(element, TCL_INDEX_NONE, &flags); if (!quoteHash) { flags |= DONT_QUOTE_HASH; } /* * Allocate a larger buffer for the string if the current one isn't large * enough. Allocate extra space in the new buffer so that there will be * room to grow before we have to allocate again. SPECIAL NOTE: must use * memcpy, not strcpy, to copy the string to a larger buffer, since there * may be embedded NULLs in the string in some cases. */ newSize += 1; /* For terminating nul */ if (newSize > dsPtr->spaceAvl) { if (dsPtr->string == dsPtr->staticSpace) { char *newString = (char *) TclAllocEx(newSize, &dsPtr->spaceAvl); memcpy(newString, dsPtr->string, dsPtr->length); dsPtr->string = newString; } else { int offset = -1; /* See [16896d49fd] */ if (element >= dsPtr->string && element <= dsPtr->string + dsPtr->length) { /* Source string is within this DString. Note offset */ offset = element - dsPtr->string; } dsPtr->string = (char *)TclReallocEx(dsPtr->string, newSize, &dsPtr->spaceAvl); if (offset >= 0) { element = dsPtr->string + offset; } } } dst = dsPtr->string + dsPtr->length; /* * Convert the new string to a list element and copy it into the buffer at * the end, with a space, if needed. */ if (needSpace) { *dst = ' '; dst++; dsPtr->length++; } dsPtr->length += TclConvertElement(element, TCL_INDEX_NONE, dst, flags); dsPtr->string[dsPtr->length] = '\0'; return dsPtr->string; } /* *---------------------------------------------------------------------- * * Tcl_DStringSetLength -- * * Change the length of a dynamic string. This can cause the string to * either grow or shrink, depending on the value of length. * * Results: * None. * * Side effects: * The length of dsPtr is changed to length and a null byte is stored at * that position in the string. * *---------------------------------------------------------------------- */ void Tcl_DStringSetLength( Tcl_DString *dsPtr, /* Structure describing dynamic string. */ Tcl_Size length) /* New length for dynamic string. */ { Tcl_Size newsize; if (length < 0) { length = 0; } if (length >= dsPtr->spaceAvl) { /* * There are two interesting cases here. In the first case, the user * may be trying to allocate a large buffer of a specific size. It * would be wasteful to overallocate that buffer, so we just allocate * enough for the requested size plus the trailing null byte. In the * second case, we are growing the buffer incrementally, so we need * behavior similar to Tcl_DStringAppend. * TODO - the above makes no sense to me. How does the code below * translate into distinguishing the two cases above? IMO, if caller * specifically sets the length, there is no cause for overallocation. */ if (length >= TCL_SIZE_MAX) { Tcl_Panic("Tcl_Concat: max size of Tcl value exceeded"); } newsize = TclUpsizeAlloc(dsPtr->spaceAvl, length + 1, TCL_SIZE_MAX); if (length < newsize) { dsPtr->spaceAvl = newsize; } else { dsPtr->spaceAvl = length + 1; } if (dsPtr->string == dsPtr->staticSpace) { char *newString = (char *)Tcl_Alloc(dsPtr->spaceAvl); memcpy(newString, dsPtr->string, dsPtr->length); dsPtr->string = newString; } else { dsPtr->string = (char *)Tcl_Realloc(dsPtr->string, dsPtr->spaceAvl); } } dsPtr->length = length; dsPtr->string[length] = 0; } /* *---------------------------------------------------------------------- * * Tcl_DStringFree -- * * Frees up any memory allocated for the dynamic string and reinitializes * the string to an empty state. * * Results: * None. * * Side effects: * The previous contents of the dynamic string are lost, and the new * value is an empty string. * *---------------------------------------------------------------------- */ void Tcl_DStringFree( Tcl_DString *dsPtr) /* Structure describing dynamic string. */ { if (dsPtr->string != dsPtr->staticSpace) { Tcl_Free(dsPtr->string); } dsPtr->string = dsPtr->staticSpace; dsPtr->length = 0; dsPtr->spaceAvl = TCL_DSTRING_STATIC_SIZE; dsPtr->staticSpace[0] = '\0'; } /* *---------------------------------------------------------------------- * * Tcl_DStringResult -- * * This function moves the value of a dynamic string into an interpreter * as its string result. Afterwards, the dynamic string is reset to an * empty string. * * Results: * None. * * Side effects: * The string is "moved" to interp's result, and any existing string * result for interp is freed. dsPtr is reinitialized to an empty string. * *---------------------------------------------------------------------- */ void Tcl_DStringResult( Tcl_Interp *interp, /* Interpreter whose result is to be reset. */ Tcl_DString *dsPtr) /* Dynamic string that is to become the * result of interp. */ { Tcl_SetObjResult(interp, Tcl_DStringToObj(dsPtr)); } /* *---------------------------------------------------------------------- * * Tcl_DStringGetResult -- * * This function moves an interpreter's result into a dynamic string. * * Results: * None. * * Side effects: * The interpreter's string result is cleared, and the previous contents * of dsPtr are freed. * * If the string result is empty, the object result is moved to the * string result, then the object result is reset. * *---------------------------------------------------------------------- */ void Tcl_DStringGetResult( Tcl_Interp *interp, /* Interpreter whose result is to be reset. */ Tcl_DString *dsPtr) /* Dynamic string that is to become the result * of interp. */ { Tcl_Obj *obj = Tcl_GetObjResult(interp); const char *bytes = TclGetString(obj); Tcl_DStringFree(dsPtr); Tcl_DStringAppend(dsPtr, bytes, obj->length); Tcl_ResetResult(interp); } /* *---------------------------------------------------------------------- * * Tcl_DStringToObj -- * * This function moves a dynamic string's contents to a new Tcl_Obj. Be * aware that this function does *not* check that the encoding of the * contents of the dynamic string is correct; this is the caller's * responsibility to enforce. * * Results: * The newly-allocated untyped (i.e., typePtr==NULL) Tcl_Obj with a * reference count of zero. * * Side effects: * The string is "moved" to the object. dsPtr is reinitialized to an * empty string; it does not need to be Tcl_DStringFree'd after this if * not used further. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_DStringToObj( Tcl_DString *dsPtr) { Tcl_Obj *result; if (dsPtr->string == dsPtr->staticSpace) { if (dsPtr->length == 0) { TclNewObj(result); } else { /* * Static buffer, so must copy. */ TclNewStringObj(result, dsPtr->string, dsPtr->length); } } else { /* * Dynamic buffer, so transfer ownership and reset. */ TclNewObj(result); result->bytes = dsPtr->string; result->length = dsPtr->length; } /* * Re-establish the DString as empty with no buffer allocated. */ dsPtr->string = dsPtr->staticSpace; dsPtr->spaceAvl = TCL_DSTRING_STATIC_SIZE; dsPtr->length = 0; dsPtr->staticSpace[0] = '\0'; return result; } /* *---------------------------------------------------------------------- * * Tcl_DStringStartSublist -- * * This function adds the necessary information to a dynamic string * (e.g. " {") to start a sublist. Future element appends will be in the * sublist rather than the main list. * * Results: * None. * * Side effects: * Characters get added to the dynamic string. * *---------------------------------------------------------------------- */ void Tcl_DStringStartSublist( Tcl_DString *dsPtr) /* Dynamic string. */ { if (TclNeedSpace(dsPtr->string, dsPtr->string + dsPtr->length)) { TclDStringAppendLiteral(dsPtr, " {"); } else { TclDStringAppendLiteral(dsPtr, "{"); } } /* *---------------------------------------------------------------------- * * Tcl_DStringEndSublist -- * * This function adds the necessary characters to a dynamic string to end * a sublist (e.g. "}"). Future element appends will be in the enclosing * (sub)list rather than the current sublist. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_DStringEndSublist( Tcl_DString *dsPtr) /* Dynamic string. */ { TclDStringAppendLiteral(dsPtr, "}"); } /* *---------------------------------------------------------------------- * * Tcl_PrintDouble -- * * Given a floating-point value, this function converts it to an ASCII * string using. * * Results: * The ASCII equivalent of "value" is written at "dst". It is guaranteed * to contain a decimal point or exponent, so that it looks like a * floating-point value and not an integer. * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_PrintDouble( TCL_UNUSED(Tcl_Interp *), double value, /* Value to print as string. */ char *dst) /* Where to store converted value; must have * at least TCL_DOUBLE_SPACE characters. */ { char *p, c; int exponent; int signum; char *digits; char *end; /* * Handle NaN. */ if (isnan(value)) { TclFormatNaN(value, dst); return; } /* * Handle infinities. */ if (isinf(value)) { /* * Remember to copy the terminating NUL too. */ if (value < 0) { memcpy(dst, "-Inf", 5); } else { memcpy(dst, "Inf", 4); } return; } /* * Ordinary (normal and denormal) values. */ digits = TclDoubleDigits(value, -1, TCL_DD_SHORTEST, &exponent, &signum, &end); if (signum) { *dst++ = '-'; } p = digits; if (exponent < -4 || exponent > 16) { /* * E format for numbers < 1e-3 or >= 1e17. */ *dst++ = *p++; c = *p; if (c != '\0') { *dst++ = '.'; while (c != '\0') { *dst++ = c; c = *++p; } } snprintf(dst, TCL_DOUBLE_SPACE, "e%+d", exponent); } else { /* * F format for others. */ if (exponent < 0) { *dst++ = '0'; } c = *p; while (exponent-- >= 0) { if (c != '\0') { *dst++ = c; c = *++p; } else { *dst++ = '0'; } } *dst++ = '.'; if (c == '\0') { *dst++ = '0'; } else { while (++exponent < -1) { *dst++ = '0'; } while (c != '\0') { *dst++ = c; c = *++p; } } *dst++ = '\0'; } Tcl_Free(digits); } /* *---------------------------------------------------------------------- * * TclNeedSpace -- * * This function checks to see whether it is appropriate to add a space * before appending a new list element to an existing string. * * Results: * The return value is 1 if a space is appropriate, 0 otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclNeedSpace( const char *start, /* First character in string. */ const char *end) /* End of string (place where space will be * added, if appropriate). */ { /* * A space is needed unless either: * (a) we're at the start of the string, or * * (NOTE: This check is now absorbed into the loop below.) * if (end == start) { return 0; } * */ /* * (b) we're at the start of a nested list-element, quoted with an open * curly brace; we can be nested arbitrarily deep, so long as the * first curly brace starts an element, so backtrack over open curly * braces that are trailing characters of the string; and * * (NOTE: Every character our parser is looking for is a proper * single-byte encoding of an ASCII value. It does not accept * overlong encodings. Given that, there's no benefit using * Tcl_UtfPrev. If it would find what we seek, so would byte-by-byte * backward scan. Save routine call overhead and risk of wrong * results should the behavior of Tcl_UtfPrev change in unexpected ways. * Reconsider this if we ever start treating non-ASCII Unicode * characters as meaningful list syntax, expanded Unicode spaces as * element separators, for example.) * end = Tcl_UtfPrev(end, start); while (*end == '{') { if (end == start) { return 0; } end = Tcl_UtfPrev(end, start); } * */ while ((--end >= start) && (*end == '{')) { } if (end < start) { return 0; } /* * (c) the trailing character of the string is already a list-element * separator, Use the same testing routine as TclFindElement to * enforce consistency. */ if (TclIsSpaceProcM(*end)) { int result = 0; /* * Trailing whitespace might be part of a backslash escape * sequence. Handle that possibility. */ while ((--end >= start) && (*end == '\\')) { result = !result; } return result; } return 1; } /* *---------------------------------------------------------------------- * * TclFormatInt -- * * This procedure formats an integer into a sequence of decimal digit * characters in a buffer. If the integer is negative, a minus sign is * inserted at the start of the buffer. A null character is inserted at * the end of the formatted characters. It is the caller's responsibility * to ensure that enough storage is available. This procedure has the * effect of sprintf(buffer, "%ld", n) but is faster as proven in * benchmarks. This is key to UpdateStringOfInt, which is a common path * for a lot of code (e.g. int-indexed arrays). * * Results: * An integer representing the number of characters formatted, not * including the terminating \0. * * Side effects: * The formatted characters are written into the storage pointer to by * the "buffer" argument. * *---------------------------------------------------------------------- */ Tcl_Size TclFormatInt( char *buffer, /* Points to the storage into which the * formatted characters are written. */ Tcl_WideInt n) /* The integer to format. */ { Tcl_WideUInt intVal; int i = 0, numFormatted, j; static const char digits[] = "0123456789"; /* * Generate the characters of the result backwards in the buffer. */ intVal = (n < 0 ? -(Tcl_WideUInt)n : (Tcl_WideUInt)n); do { buffer[i++] = digits[intVal % 10]; intVal = intVal / 10; } while (intVal > 0); if (n < 0) { buffer[i++] = '-'; } buffer[i] = '\0'; numFormatted = i--; /* * Now reverse the characters. */ for (j = 0; j < i; j++, i--) { char tmp = buffer[i]; buffer[i] = buffer[j]; buffer[j] = tmp; } return numFormatted; } /* *---------------------------------------------------------------------- * * GetWideForIndex -- * * This function produces a wide integer value corresponding to the * index value held in *objPtr. The parsing supports all values * recognized as any size of integer, and the syntaxes end[-+]$integer * and $integer[-+]$integer. The argument endValue is used to give * the meaning of the literal index value "end". Index arithmetic * on arguments outside the wide integer range are only accepted * when interp is a working interpreter, not NULL. * * Results: * When parsing of *objPtr successfully recognizes an index value, * TCL_OK is returned, and the wide integer value corresponding to * the recognized index value is written to *widePtr. When parsing * fails, TCL_ERROR is returned and error information is written to * interp, if non-NULL. * * Side effects: * The type of *objPtr may change. * *---------------------------------------------------------------------- */ static int GetWideForIndex( Tcl_Interp *interp, /* Interpreter to use for error reporting. If * NULL, then no error message is left after * errors. */ Tcl_Obj *objPtr, /* Points to the value to be parsed */ Tcl_WideInt endValue, /* The value to be stored at *widePtr if * objPtr holds "end". * NOTE: this value may be TCL_INDEX_NONE. */ Tcl_WideInt *widePtr) /* Location filled in with a wide integer * representing an index. */ { int numType; void *cd; int code = Tcl_GetNumberFromObj(NULL, objPtr, &cd, &numType); if (code == TCL_OK) { if (numType == TCL_NUMBER_INT) { /* objPtr holds an integer in the signed wide range */ *widePtr = *(Tcl_WideInt *)cd; if ((*widePtr < 0)) { *widePtr = (endValue == -1) ? WIDE_MIN : -1; } return TCL_OK; } if (numType == TCL_NUMBER_BIG) { /* objPtr holds an integer outside the signed wide range */ /* Truncate to the signed wide range. */ *widePtr = ((mp_isneg((mp_int *)cd)) ? WIDE_MIN : WIDE_MAX); return TCL_OK; } } /* objPtr does not hold a number, check the end+/- format... */ return GetEndOffsetFromObj(interp, objPtr, endValue, widePtr); } /* *---------------------------------------------------------------------- * * Tcl_GetIntForIndex -- * * Provides an integer corresponding to the list index held in a Tcl * object. The string value 'objPtr' is expected have the format * integer([+-]integer)? or end([+-]integer)?. * * If the computed index lies within the valid range of Tcl indices * (0..TCL_SIZE_MAX) it is returned. Higher values are returned as * TCL_SIZE_MAX. Negative values are returned as TCL_INDEX_NONE (-1). * * Callers should pass reasonable values for endValue - one in the * valid index range or TCL_INDEX_NONE (-1), for example for an empty * list. * * Results: * TCL_OK * * The index is stored at the address given by 'indexPtr'. * * TCL_ERROR * * The value of 'objPtr' does not have one of the expected formats. If * 'interp' is non-NULL, an error message is left in the interpreter's * result object. * * Side effects: * * The internal representation contained within objPtr may shimmer. * *---------------------------------------------------------------------- */ int Tcl_GetIntForIndex( Tcl_Interp *interp, /* Interpreter to use for error reporting. If * NULL, then no error message is left after * errors. */ Tcl_Obj *objPtr, /* Points to an object containing either "end" * or an integer. */ Tcl_Size endValue, /* The value corresponding to the "end" index */ Tcl_Size *indexPtr) /* Location filled in with an integer * representing an index. May be NULL.*/ { Tcl_WideInt wide; if (GetWideForIndex(interp, objPtr, endValue, &wide) == TCL_ERROR) { return TCL_ERROR; } if (indexPtr != NULL) { /* Note: check against TCL_SIZE_MAX needed for 32-bit builds */ if (wide >= 0 && wide <= TCL_SIZE_MAX) { *indexPtr = (Tcl_Size)wide; /* A valid index */ } else if (wide > TCL_SIZE_MAX) { *indexPtr = TCL_SIZE_MAX; /* Beyond max possible index */ } else if (wide < -1-TCL_SIZE_MAX) { *indexPtr = -1-TCL_SIZE_MAX; /* Below most negative index */ } else if ((wide < 0) && (endValue >= 0)) { *indexPtr = TCL_INDEX_NONE; /* No clue why this special case */ } else { *indexPtr = (Tcl_Size) wide; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * GetEndOffsetFromObj -- * * Look for a string of the form "end[+-]offset" or "offset[+-]offset" and * convert it to an internal representation. * * The internal representation (wideValue) uses the following encoding: * * WIDE_MIN: Index value TCL_INDEX_NONE (or -1) * WIDE_MIN+1: Index value n, for any n < -1 (usually same effect as -1) * -$n: Index "end-[expr {$n-1}]" * -2: Index "end-1" * -1: Index "end" * 0: Index "0" * WIDE_MAX-1: Index "end+n", for any n > 1. Distinguish from end+1 for * commands like lset. * WIDE_MAX: Index "end+1" * * Results: * Tcl return code. * * Side effects: * May store a Tcl_ObjType. * *---------------------------------------------------------------------- */ static int GetEndOffsetFromObj( Tcl_Interp *interp, Tcl_Obj *objPtr, /* Pointer to the object to parse */ Tcl_WideInt endValue, /* The value to be stored at "widePtr" if * "objPtr" holds "end". */ Tcl_WideInt *widePtr) /* Location filled in with an integer * representing an index. */ { Tcl_ObjInternalRep *irPtr; Tcl_WideInt offset = -1; /* Offset in the "end-offset" expression - 1 */ void *cd; while ((irPtr = TclFetchInternalRep(objPtr, &endOffsetType)) == NULL) { Tcl_ObjInternalRep ir; Tcl_Size length; const char *bytes = TclGetStringFromObj(objPtr, &length); if (*bytes != 'e') { int numType; const char *opPtr; int t1 = 0, t2 = 0; /* Value doesn't start with "e" */ /* If we reach here, the string rep of objPtr exists. */ /* * The valid index syntax does not include any value that is * a list of more than one element. This is necessary so that * lists of index values can be reliably distinguished from any * single index value. */ /* * Quick scan to see if multi-value list is even possible. * This relies on TclGetString() returning a NUL-terminated string. */ if ((TclMaxListLength(bytes, TCL_INDEX_NONE, NULL) > 1) /* If it's possible, do the full list parse. */ && (TCL_OK == TclListObjLength(NULL, objPtr, &length)) && (length > 1)) { goto parseError; } /* Passed the list screen, so parse for index arithmetic expression */ if (TCL_OK == TclParseNumber(NULL, objPtr, NULL, NULL, TCL_INDEX_NONE, &opPtr, TCL_PARSE_INTEGER_ONLY)) { Tcl_WideInt w1=0, w2=0; /* value starts with valid integer... */ if ((*opPtr == '-') || (*opPtr == '+')) { /* ... value continues with [-+] ... */ /* Save first integer as wide if possible */ Tcl_GetNumberFromObj(NULL, objPtr, &cd, &t1); if (t1 == TCL_NUMBER_INT) { w1 = (*(Tcl_WideInt *)cd); } if (TCL_OK == TclParseNumber(NULL, objPtr, NULL, opPtr + 1, TCL_INDEX_NONE, NULL, TCL_PARSE_INTEGER_ONLY)) { /* ... value concludes with second valid integer */ /* Save second integer as wide if possible */ Tcl_GetNumberFromObj(NULL, objPtr, &cd, &t2); if (t2 == TCL_NUMBER_INT) { w2 = (*(Tcl_WideInt *)cd); } } } /* Clear invalid internalreps left by TclParseNumber */ TclFreeInternalRep(objPtr); if (t1 && t2) { /* We have both integer values */ if ((t1 == TCL_NUMBER_INT) && (t2 == TCL_NUMBER_INT)) { /* Both are wide, do wide-integer math */ if (*opPtr == '-') { if (w2 == WIDE_MIN) { goto extreme; } w2 = -w2; } if ((w1 ^ w2) < 0) { /* Different signs, sum cannot overflow */ offset = w1 + w2; } else if (w1 >= 0) { if (w1 < WIDE_MAX - w2) { offset = w1 + w2; } else { offset = WIDE_MAX; } } else { if (w1 > WIDE_MIN - w2) { offset = w1 + w2; } else { offset = WIDE_MIN; } } } else { /* * At least one is big, do bignum math. Little reason to * value performance here. Re-use code. Parse has verified * objPtr is an expression. Compute it. */ Tcl_Obj *sum; extreme: if (interp) { Tcl_ExprObj(interp, objPtr, &sum); } else { Tcl_Interp *compute = Tcl_CreateInterp(); Tcl_ExprObj(compute, objPtr, &sum); Tcl_DeleteInterp(compute); } Tcl_GetNumberFromObj(NULL, sum, &cd, &numType); if (numType == TCL_NUMBER_INT) { /* sum holds an integer in the signed wide range */ offset = *(Tcl_WideInt *)cd; } else { /* sum holds an integer outside the signed wide range */ /* Truncate to the signed wide range. */ if (mp_isneg((mp_int *)cd)) { offset = WIDE_MIN; } else { offset = WIDE_MAX; } } Tcl_DecrRefCount(sum); } if (offset < 0) { offset = (offset == -1) ? WIDE_MIN : WIDE_MIN+1; } goto parseOK; } } goto parseError; } if ((length < 3) || (length == 4) || (strncmp(bytes, "end", 3) != 0)) { /* Doesn't start with "end" */ goto parseError; } if (length > 4) { int t; /* Parse for the "end-..." or "end+..." formats */ if ((bytes[3] != '-') && (bytes[3] != '+')) { /* No operator where we need one */ goto parseError; } if (TclIsSpaceProc(bytes[4])) { /* Space after + or - not permitted. */ goto parseError; } /* Parse the integer offset */ if (TCL_OK != TclParseNumber(NULL, objPtr, NULL, bytes + 4, length - 4, NULL, TCL_PARSE_INTEGER_ONLY)) { /* Not a recognized integer format */ goto parseError; } /* Got an integer offset; pull it from where parser left it. */ Tcl_GetNumberFromObj(NULL, objPtr, &cd, &t); if (t == TCL_NUMBER_BIG) { /* Truncate to the signed wide range. */ if (mp_isneg((mp_int *)cd)) { offset = (bytes[3] == '-') ? WIDE_MAX : WIDE_MIN; } else { offset = (bytes[3] == '-') ? WIDE_MIN : WIDE_MAX; } } else { /* assert (t == TCL_NUMBER_INT); */ offset = (*(Tcl_WideInt *)cd); if (bytes[3] == '-') { offset = (offset == WIDE_MIN) ? WIDE_MAX : -offset; } if (offset == 1) { offset = WIDE_MAX; /* "end+1" */ } else if (offset > 1) { offset = WIDE_MAX - 1; /* "end+n", out of range */ } else if (offset != WIDE_MIN) { offset--; } } } parseOK: /* Success. Store the new internal rep. */ ir.wideValue = offset; Tcl_StoreInternalRep(objPtr, &endOffsetType, &ir); } offset = irPtr->wideValue; if (offset == WIDE_MAX) { /* * Encodes end+1. This is distinguished from end+n as noted * in function header. * NOTE: this may wrap around if the caller passes (as lset does) * listLen-1 as endValue and listLen is 0. The -1 will be * interpreted as FF...FF and adding 1 will result in 0 which * is what we want. Callers like lset which pass in listLen-1 == -1 * as endValue will have to adjust accordingly. */ *widePtr = (endValue == -1) ? WIDE_MAX : endValue + 1; } else if (offset == WIDE_MIN) { *widePtr = (endValue == -1) ? WIDE_MIN : -1; } else if (offset < 0) { /* end-(n-1) - Different signs, sum cannot overflow */ *widePtr = endValue + offset + 1; } else { /* 0:WIDE_MAX - plain old index. */ *widePtr = offset; } return TCL_OK; /* Report a parse error. */ parseError: if (interp != NULL) { char * bytes = TclGetString(objPtr); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad index \"%s\": must be integer?[+-]integer? or" " end?[+-]integer?", bytes)); if (!strncmp(bytes, "end-", 4)) { bytes += 4; } Tcl_SetErrorCode(interp, "TCL", "VALUE", "INDEX", (char *)NULL); } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TclIndexEncode -- * IMPORTANT: function only encodes indices in the range that fits within * an "int" type. Do NOT change this as the byte code compiler and engine * which call this function cannot handle wider index types. Indices * outside the range will result in the function returning an error. * * Parse objPtr to determine if it is an index value. Two cases * are possible. The value objPtr might be parsed as an absolute * index value in the Tcl_Size range. Note that this includes * index values that are integers as presented and it includes index * arithmetic expressions. * * The largest string supported in Tcl 8 has byte length TCL_SIZE_MAX. * This means the largest supported character length is also TCL_SIZE_MAX, * and the index of the last character in a string of length TCL_SIZE_MAX * is TCL_SIZE_MAX-1. Thus the absolute index values that can be * directly meaningful as an index into either a list or a string are * integer values in the range 0 to TCL_SIZE_MAX - 1. * * This function however can only handle integer indices in the range * 0 : INT_MAX-1. * * Any absolute index value parsed outside that range is encoded * using the before and after values passed in by the * caller as the encoding to use for indices that are either * less than or greater than the usable index range. TCL_INDEX_NONE * is available as a good choice for most callers to use for * after. Likewise, the value TCL_INDEX_NONE is good for * most callers to use for before. Other values are possible * when the caller knows it is helpful in producing its own behavior * for indices before and after the indexed item. * * A token can also be parsed as an end-relative index expression. * All end-relative expressions that indicate an index larger * than end (end+2, end--5) point beyond the end of the indexed * collection, and can be encoded as after. The end-relative * expressions that indicate an index less than or equal to end * are encoded relative to the value TCL_INDEX_END (-2). The * index "end" is encoded as -2, down to the index "end-0x7FFFFFFE" * which is encoded as INT_MIN. Since the largest index into a * string possible in Tcl 8 is 0x7FFFFFFE, the interpretation of * "end-0x7FFFFFFE" for that largest string would be 0. Thus, * if the tokens "end-0x7FFFFFFF" or "end+-0x80000000" are parsed, * they can be encoded with the before value. * * Returns: * TCL_OK if parsing succeeded, and TCL_ERROR if it failed or the * index does not fit in an int type. * * Side effects: * When TCL_OK is returned, the encoded index value is written * to *indexPtr. * *---------------------------------------------------------------------- */ int TclIndexEncode( Tcl_Interp *interp, /* For error reporting, may be NULL */ Tcl_Obj *objPtr, /* Index value to parse */ int before, /* Value to return for index before beginning */ int after, /* Value to return for index after end */ int *indexPtr) /* Where to write the encoded answer, not NULL */ { Tcl_WideInt wide; int idx; const Tcl_WideInt ENDVALUE = 2 * (Tcl_WideInt) INT_MAX; assert(ENDVALUE < WIDE_MAX); if (TCL_OK != GetWideForIndex(interp, objPtr, ENDVALUE, &wide)) { return TCL_ERROR; } /* * We passed 2*INT_MAX as the "end value" to GetWideForIndex. The computed * index will be in one of the following ranges that need to be * distinguished for encoding purposes in the following code. * (1) 0:INT_MAX when * (a) objPtr was a pure non-negative numeric value in that range * (b) objPtr was a numeric computation M+/-N with a result in that range * (c) objPtr was of the form end-N where N was in range INT_MAX:2*INT_MAX * (2) INT_MAX+1:2*INT_MAX when * (a,b) as above * (c) objPtr was of the form end-N where N was in range 0:INT_MAX-1 * (3) 2*INT_MAX:WIDE_MAX when * (a,b) as above * (c) objPtr was of the form end+N * (4) (2*INT_MAX)-TCL_SIZE_MAX : -1 when * (a,b) as above * (c) objPtr was of the form end-N where N was in the range 0:TCL_SIZE_MAX * (5) WIDE_MIN:(2*INT_MAX)-TCL_SIZE_MAX * (a,b) as above * (c) objPtr was of the form end-N where N was > TCL_SIZE_MAX * * For all cases (b) and (c), the internal representation of objPtr * will be shimmered to endOffsetType. That allows us to distinguish between * (for example) 1a (encodable) and 1c (not encodable) though the computed * index value is the same. * * Further note, the values TCL_SIZE_MAX < N < WIDE_MAX come into play * only in the 32-bit builds as TCL_SIZE_MAX == WIDE_MAX for 64-bits. */ const Tcl_ObjInternalRep *irPtr = TclFetchInternalRep(objPtr, &endOffsetType); if (irPtr && irPtr->wideValue >= 0) { /* * "int[+-]int" syntax, works the same here as "int". * Note same does not hold for negative integers. * Distinguishes 1b and 1c where wide will be in 0:INT_MAX for * both but irPtr->wideValue will be negative for 1c. */ irPtr = NULL; } if (irPtr == NULL) { /* objPtr can be treated as a purely numeric value. */ /* * On 64-bit systems, indices in the range INT_MAX:TCL_SIZE_MAX are * valid indices but are not in the encodable range. Thus an * error is raised. On 32-bit systems, indices in that range indicate * the position after the end and so do not raise an error. */ if ((sizeof(int) != sizeof(Tcl_Size)) && (wide > INT_MAX) && (wide < WIDE_MAX-1)) { /* 2(a,b) on 64-bit systems*/ goto rangeerror; } if (wide > INT_MAX) { /* * 3(a,b) on 64-bit systems and 2(a,b), 3(a,b) on 32-bit systems * Because of the check above, this case holds for indices * greater than INT_MAX on 32-bit systems and > TCL_SIZE_MAX * on 64-bit systems. Always maps to the element after the end. */ idx = after; } else if (wide < 0) { /* 4(a,b) (32-bit systems), 5(a,b) - before the beginning */ idx = before; } else { /* 1(a,b) Encodable range */ idx = (int)wide; } } else { /* objPtr is not purely numeric (end etc.) */ /* * On 64-bit systems, indices in the range end-LIST_MAX:end-INT_MAX * are valid indices (with max size strings/lists) but are not in * the encodable range. Thus an error is raised. On 32-bit systems, * indices in that range indicate the position before the beginning * and so do not raise an error. */ if ((sizeof(int) != sizeof(Tcl_Size)) && (wide > (ENDVALUE - LIST_MAX)) && (wide <= INT_MAX)) { /* 1(c), 4(a,b) on 64-bit systems */ goto rangeerror; } if (wide > ENDVALUE) { /* * 2(c) (32-bit systems), 3(c) * All end+positive or end-negative expressions * always indicate "after the end". * Note we will not reach here for a pure numeric value in this * range because irPtr will be NULL in that case. */ idx = after; } else if (wide <= INT_MAX) { /* 1(c) (32-bit systems), 4(c) (32-bit systems), 5(c) */ idx = before; } else { /* 2(c) Encodable end-positive (or end+negative) */ idx = (int)wide; } } *indexPtr = idx; return TCL_OK; rangeerror: if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("index \"%s\" out of range", TclGetString(objPtr))); Tcl_SetErrorCode(interp, "TCL", "VALUE", "INDEX", "OUTOFRANGE", (char *)NULL); } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TclIndexDecode -- * * Decodes a value previously encoded by TclIndexEncode. The argument * endValue indicates what value of "end" should be used in the * decoding. * * Results: * The decoded index value. * *---------------------------------------------------------------------- */ Tcl_Size TclIndexDecode( int encoded, /* Value to decode */ Tcl_Size endValue) /* Meaning of "end" to use, > TCL_INDEX_END */ { if (encoded > TCL_INDEX_END) { return encoded; } endValue += encoded - TCL_INDEX_END; if (endValue >= 0) { return endValue; } return TCL_INDEX_NONE; } /* *------------------------------------------------------------------------ * * TclCommandWordLimitErrpr -- * * Generates an error message limit on number of command words exceeded. * * Results: * Always return TCL_ERROR. * * Side effects: * If interp is not-NULL, an error message is stored in it. * *------------------------------------------------------------------------ */ int TclCommandWordLimitError( Tcl_Interp *interp, /* May be NULL */ Tcl_Size count) /* If <= 0, "unknown" */ { if (interp) { if (count > 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Number of words (%" TCL_SIZE_MODIFIER "d) in command exceeds limit %" TCL_SIZE_MODIFIER "d.", count, (Tcl_Size)INT_MAX)); } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Number of words in command exceeds limit %" TCL_SIZE_MODIFIER "d.", (Tcl_Size)INT_MAX)); } } return TCL_ERROR; /* Always */ } /* *---------------------------------------------------------------------- * * ClearHash -- * * Remove all the entries in the hash table *tablePtr. * *---------------------------------------------------------------------- */ static void ClearHash( Tcl_HashTable *tablePtr) { Tcl_HashSearch search; Tcl_HashEntry *hPtr; for (hPtr = Tcl_FirstHashEntry(tablePtr, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { Tcl_Obj *objPtr = (Tcl_Obj *)Tcl_GetHashValue(hPtr); Tcl_DecrRefCount(objPtr); Tcl_DeleteHashEntry(hPtr); } } /* *---------------------------------------------------------------------- * * GetThreadHash -- * * Get a thread-specific (Tcl_HashTable *) associated with a thread data * key. * * Results: * The Tcl_HashTable * corresponding to *keyPtr. * * Side effects: * The first call on a keyPtr in each thread creates a new Tcl_HashTable, * and registers a thread exit handler to dispose of it. * *---------------------------------------------------------------------- */ static Tcl_HashTable * GetThreadHash( Tcl_ThreadDataKey *keyPtr) { Tcl_HashTable **tablePtrPtr = (Tcl_HashTable **)Tcl_GetThreadData(keyPtr, sizeof(Tcl_HashTable *)); if (NULL == *tablePtrPtr) { *tablePtrPtr = (Tcl_HashTable *)Tcl_Alloc(sizeof(Tcl_HashTable)); Tcl_CreateThreadExitHandler(FreeThreadHash, *tablePtrPtr); Tcl_InitHashTable(*tablePtrPtr, TCL_ONE_WORD_KEYS); } return *tablePtrPtr; } /* *---------------------------------------------------------------------- * * FreeThreadHash -- * * Thread exit handler used by GetThreadHash to dispose of a thread hash * table. * * Side effects: * Frees a Tcl_HashTable. * *---------------------------------------------------------------------- */ static void FreeThreadHash( void *clientData) { Tcl_HashTable *tablePtr = (Tcl_HashTable *)clientData; ClearHash(tablePtr); Tcl_DeleteHashTable(tablePtr); Tcl_Free(tablePtr); } /* *---------------------------------------------------------------------- * * FreeProcessGlobalValue -- * * Exit handler used by Tcl(Set|Get)ProcessGlobalValue to cleanup a * ProcessGlobalValue at exit. * *---------------------------------------------------------------------- */ static void FreeProcessGlobalValue( void *clientData) { ProcessGlobalValue *pgvPtr = (ProcessGlobalValue *)clientData; pgvPtr->epoch++; pgvPtr->numBytes = 0; Tcl_Free(pgvPtr->value); pgvPtr->value = NULL; if (pgvPtr->encoding) { Tcl_FreeEncoding(pgvPtr->encoding); pgvPtr->encoding = NULL; } Tcl_MutexFinalize(&pgvPtr->mutex); } /* *---------------------------------------------------------------------- * * TclSetProcessGlobalValue -- * * Utility routine to set a global value shared by all threads in the * process while keeping a thread-local copy as well. * *---------------------------------------------------------------------- */ void TclSetProcessGlobalValue( ProcessGlobalValue *pgvPtr, Tcl_Obj *newValue) { const char *bytes; Tcl_HashTable *cacheMap; Tcl_HashEntry *hPtr; int dummy; Tcl_DString ds; Tcl_MutexLock(&pgvPtr->mutex); /* * Fill the global string value. */ pgvPtr->epoch++; if (NULL != pgvPtr->value) { Tcl_Free(pgvPtr->value); } else { Tcl_CreateExitHandler(FreeProcessGlobalValue, pgvPtr); } bytes = TclGetString(newValue); pgvPtr->numBytes = newValue->length; Tcl_UtfToExternalDStringEx(NULL, NULL, bytes, pgvPtr->numBytes, TCL_ENCODING_PROFILE_TCL8, &ds, NULL); pgvPtr->numBytes = Tcl_DStringLength(&ds); pgvPtr->value = (char *)Tcl_Alloc(pgvPtr->numBytes + 1); memcpy(pgvPtr->value, Tcl_DStringValue(&ds), pgvPtr->numBytes + 1); Tcl_DStringFree(&ds); if (pgvPtr->encoding) { Tcl_FreeEncoding(pgvPtr->encoding); } pgvPtr->encoding = NULL; /* * Fill the local thread copy directly with the Tcl_Obj value to avoid * loss of the internalrep. Increment newValue refCount early to handle case * where we set a PGV to itself. */ Tcl_IncrRefCount(newValue); cacheMap = GetThreadHash(&pgvPtr->key); ClearHash(cacheMap); hPtr = Tcl_CreateHashEntry(cacheMap, INT2PTR(pgvPtr->epoch), &dummy); Tcl_SetHashValue(hPtr, newValue); Tcl_MutexUnlock(&pgvPtr->mutex); } /* *---------------------------------------------------------------------- * * TclGetProcessGlobalValue -- * * Retrieve a global value shared among all threads of the process, * preferring a thread-local copy as long as it remains valid. * * Results: * Returns a (Tcl_Obj *) that holds a copy of the global value. * *---------------------------------------------------------------------- */ Tcl_Obj * TclGetProcessGlobalValue( ProcessGlobalValue *pgvPtr) { Tcl_Obj *value = NULL; Tcl_HashTable *cacheMap; Tcl_HashEntry *hPtr; Tcl_Size epoch = pgvPtr->epoch; Tcl_DString newValue; if (pgvPtr->encoding) { Tcl_Encoding current = Tcl_GetEncoding(NULL, NULL); if (pgvPtr->encoding != current) { /* * The system encoding has changed since the global string value * was saved. Convert the global value to be based on the new * system encoding. */ Tcl_DString native; Tcl_MutexLock(&pgvPtr->mutex); epoch = ++pgvPtr->epoch; Tcl_UtfToExternalDStringEx(NULL, pgvPtr->encoding, pgvPtr->value, pgvPtr->numBytes, TCL_ENCODING_PROFILE_TCL8, &native, NULL); Tcl_ExternalToUtfDStringEx(NULL, current, Tcl_DStringValue(&native), Tcl_DStringLength(&native), TCL_ENCODING_PROFILE_TCL8, &newValue, NULL); Tcl_DStringFree(&native); Tcl_Free(pgvPtr->value); pgvPtr->value = (char *)Tcl_Alloc(Tcl_DStringLength(&newValue) + 1); memcpy(pgvPtr->value, Tcl_DStringValue(&newValue), Tcl_DStringLength(&newValue) + 1); Tcl_DStringFree(&newValue); Tcl_FreeEncoding(pgvPtr->encoding); pgvPtr->encoding = current; Tcl_MutexUnlock(&pgvPtr->mutex); } else { Tcl_FreeEncoding(current); } } cacheMap = GetThreadHash(&pgvPtr->key); hPtr = Tcl_FindHashEntry(cacheMap, INT2PTR(epoch)); if (NULL == hPtr) { int dummy; /* * No cache for the current epoch - must be a new one. * * First, clear the cacheMap, as anything in it must refer to some * expired epoch. */ ClearHash(cacheMap); /* * If no thread has set the shared value, call the initializer. */ Tcl_MutexLock(&pgvPtr->mutex); if ((NULL == pgvPtr->value) && (pgvPtr->proc)) { pgvPtr->epoch++; pgvPtr->proc(&pgvPtr->value,&pgvPtr->numBytes,&pgvPtr->encoding); if (pgvPtr->value == NULL) { Tcl_Panic("PGV Initializer did not initialize"); } Tcl_CreateExitHandler(FreeProcessGlobalValue, pgvPtr); } /* * Store a copy of the shared value (but then in utf-8) * in our epoch-indexed cache. */ Tcl_ExternalToUtfDString(NULL, pgvPtr->value, pgvPtr->numBytes, &newValue); value = Tcl_DStringToObj(&newValue); hPtr = Tcl_CreateHashEntry(cacheMap, INT2PTR(pgvPtr->epoch), &dummy); Tcl_MutexUnlock(&pgvPtr->mutex); Tcl_SetHashValue(hPtr, value); Tcl_IncrRefCount(value); } return (Tcl_Obj *)Tcl_GetHashValue(hPtr); } /* *---------------------------------------------------------------------- * * TclSetObjNameOfExecutable -- * * This function stores the absolute pathname of the executable file * (normally as computed by TclpFindExecutable). * * Starting with Tcl 9.0, encoding parameter is not used any more. * * Results: * None. * * Side effects: * Stores the executable name. * *---------------------------------------------------------------------- */ void TclSetObjNameOfExecutable( Tcl_Obj *name, TCL_UNUSED(Tcl_Encoding)) { TclSetProcessGlobalValue(&executableName, name); } /* *---------------------------------------------------------------------- * * TclGetObjNameOfExecutable -- * * This function retrieves the absolute pathname of the application in * which the Tcl library is running, usually as previously stored by * TclpFindExecutable(). This function call is the C API equivalent to * the "info nameofexecutable" command. * * Results: * A pointer to an "fsPath" Tcl_Obj, or to an empty Tcl_Obj if the * pathname of the application is unknown. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj * TclGetObjNameOfExecutable(void) { return TclGetProcessGlobalValue(&executableName); } /* *---------------------------------------------------------------------- * * Tcl_GetNameOfExecutable -- * * This function retrieves the absolute pathname of the application in * which the Tcl library is running, and returns it in string form. * * The returned string belongs to Tcl and should be copied if the caller * plans to keep it, to guard against it becoming invalid. * * Results: * A pointer to the internal string or NULL if the internal full path * name has not been computed or unknown. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * Tcl_GetNameOfExecutable(void) { Tcl_Obj *obj = TclGetObjNameOfExecutable(); const char *bytes = TclGetString(obj); if (obj->length == 0) { return NULL; } return bytes; } /* *---------------------------------------------------------------------- * * TclGetPlatform -- * * This is a kludge that allows the test library to get access the * internal tclPlatform variable. * * Results: * Returns a pointer to the tclPlatform variable. * * Side effects: * None. * *---------------------------------------------------------------------- */ TclPlatformType * TclGetPlatform(void) { return &tclPlatform; } /* *---------------------------------------------------------------------- * * TclReToGlob -- * * Attempt to convert a regular expression to an equivalent glob pattern. * * Results: * Returns TCL_OK on success, TCL_ERROR on failure. If interp is not * NULL, an error message is placed in the result. On success, the * DString will contain an exact equivalent glob pattern. The caller is * responsible for calling Tcl_DStringFree on success. If exactPtr is not * NULL, it will be 1 if an exact match qualifies. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclReToGlob( Tcl_Interp *interp, const char *reStr, Tcl_Size reStrLen, Tcl_DString *dsPtr, int *exactPtr, int *quantifiersFoundPtr) { int anchorLeft, anchorRight, lastIsStar, numStars; char *dsStr, *dsStrStart; const char *msg, *p, *strEnd, *code; strEnd = reStr + reStrLen; Tcl_DStringInit(dsPtr); if (quantifiersFoundPtr != NULL) { *quantifiersFoundPtr = 0; } /* * "***=xxx" == "*xxx*", watch for glob-sensitive chars. */ if ((reStrLen >= 4) && (memcmp("***=", reStr, 4) == 0)) { /* * At most, the glob pattern has length 2*reStrLen + 2 to backslash * escape every character and have * at each end. */ Tcl_DStringSetLength(dsPtr, reStrLen + 2); dsStr = dsStrStart = Tcl_DStringValue(dsPtr); *dsStr++ = '*'; for (p = reStr + 4; p < strEnd; p++) { switch (*p) { case '\\': case '*': case '[': case ']': case '?': /* Only add \ where necessary for glob */ *dsStr++ = '\\'; /* fall through */ default: *dsStr++ = *p; break; } } *dsStr++ = '*'; Tcl_DStringSetLength(dsPtr, dsStr - dsStrStart); if (exactPtr) { *exactPtr = 0; } return TCL_OK; } /* * At most, the glob pattern has length reStrLen + 2 to account for * possible * at each end. */ Tcl_DStringSetLength(dsPtr, reStrLen + 2); dsStr = dsStrStart = Tcl_DStringValue(dsPtr); /* * Check for anchored REs (ie ^foo$), so we can use string equal if * possible. Do not alter the start of str so we can free it correctly. * * Keep track of the last char being an unescaped star to prevent multiple * instances. Simpler than checking that the last star may be escaped. */ msg = NULL; code = NULL; p = reStr; anchorRight = 0; lastIsStar = 0; numStars = 0; if (*p == '^') { anchorLeft = 1; p++; } else { anchorLeft = 0; *dsStr++ = '*'; lastIsStar = 1; } for ( ; p < strEnd; p++) { switch (*p) { case '\\': p++; switch (*p) { case 'a': *dsStr++ = '\a'; break; case 'b': *dsStr++ = '\b'; break; case 'f': *dsStr++ = '\f'; break; case 'n': *dsStr++ = '\n'; break; case 'r': *dsStr++ = '\r'; break; case 't': *dsStr++ = '\t'; break; case 'v': *dsStr++ = '\v'; break; case 'B': case '\\': *dsStr++ = '\\'; *dsStr++ = '\\'; anchorLeft = 0; /* prevent exact match */ break; case '*': case '[': case ']': case '?': /* Only add \ where necessary for glob */ *dsStr++ = '\\'; anchorLeft = 0; /* prevent exact match */ /* fall through */ case '{': case '}': case '(': case ')': case '+': case '.': case '|': case '^': case '$': *dsStr++ = *p; break; default: msg = "invalid escape sequence"; code = "BADESCAPE"; goto invalidGlob; } break; case '.': if (quantifiersFoundPtr != NULL) { *quantifiersFoundPtr = 1; } anchorLeft = 0; /* prevent exact match */ if (p+1 < strEnd) { if (p[1] == '*') { p++; if (!lastIsStar) { *dsStr++ = '*'; lastIsStar = 1; numStars++; } continue; } else if (p[1] == '+') { p++; *dsStr++ = '?'; *dsStr++ = '*'; lastIsStar = 1; numStars++; continue; } } *dsStr++ = '?'; break; case '$': if (p+1 != strEnd) { msg = "$ not anchor"; code = "NONANCHOR"; goto invalidGlob; } anchorRight = 1; break; case '*': case '+': case '?': case '|': case '^': case '{': case '}': case '(': case ')': case '[': case ']': msg = "unhandled RE special char"; code = "UNHANDLED"; goto invalidGlob; default: *dsStr++ = *p; break; } lastIsStar = 0; } if (numStars > 1) { /* * Heuristic: if >1 non-anchoring *, the risk is large that glob * matching is slower than the RE engine, so report invalid. */ msg = "excessive recursive glob backtrack potential"; code = "OVERCOMPLEX"; goto invalidGlob; } if (!anchorRight && !lastIsStar) { *dsStr++ = '*'; } Tcl_DStringSetLength(dsPtr, dsStr - dsStrStart); if (exactPtr) { *exactPtr = (anchorLeft && anchorRight); } return TCL_OK; invalidGlob: if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj(msg, -1)); Tcl_SetErrorCode(interp, "TCL", "RE2GLOB", code, (char *)NULL); } Tcl_DStringFree(dsPtr); return TCL_ERROR; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclVar.c0000644000175000017500000062013414726623136014577 0ustar sergeisergei/* * tclVar.c -- * * This file contains routines that implement Tcl variables (both scalars * and arrays). * * The implementation of arrays is modelled after an initial * implementation by Mark Diekhans and Karl Lehenbauer. * * Copyright © 1987-1994 The Regents of the University of California. * Copyright © 1994-1997 Sun Microsystems, Inc. * Copyright © 1998-1999 Scriptics Corporation. * Copyright © 2001 Kevin B. Kenny. All rights reserved. * Copyright © 2007 Miguel Sofer * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclOOInt.h" /* * Prototypes for the variable hash key methods. */ static Tcl_HashEntry * AllocVarEntry(Tcl_HashTable *tablePtr, void *keyPtr); static void FreeVarEntry(Tcl_HashEntry *hPtr); static int CompareVarKeys(void *keyPtr, Tcl_HashEntry *hPtr); static const Tcl_HashKeyType tclVarHashKeyType = { TCL_HASH_KEY_TYPE_VERSION, /* version */ TCL_HASH_KEY_DIRECT_COMPARE,/* allows compare keys by pointers */ TclHashObjKey, /* hashKeyProc */ CompareVarKeys, /* compareKeysProc */ AllocVarEntry, /* allocEntryProc */ FreeVarEntry /* freeEntryProc */ }; static inline Var * VarHashCreateVar(TclVarHashTable *tablePtr, Tcl_Obj *key, int *newPtr); static inline Var * VarHashFirstVar(TclVarHashTable *tablePtr, Tcl_HashSearch *searchPtr); static inline Var * VarHashNextVar(Tcl_HashSearch *searchPtr); static inline void CleanupVar(Var *varPtr, Var *arrayPtr); #define VarHashGetValue(hPtr) \ ((Var *) ((char *)hPtr - offsetof(VarInHash, entry))) /* * NOTE: VarHashCreateVar increments the recount of its key argument. * All callers that will call Tcl_DecrRefCount on that argument must * call Tcl_IncrRefCount on it before passing it in. This requirement * can bubble up to callers of callers .... etc. */ static inline Var * VarHashCreateVar( TclVarHashTable *tablePtr, Tcl_Obj *key, int *newPtr) { Tcl_HashEntry *hPtr = Tcl_CreateHashEntry(&tablePtr->table, key, newPtr); if (!hPtr) { return NULL; } return VarHashGetValue(hPtr); } #define VarHashFindVar(tablePtr, key) \ VarHashCreateVar((tablePtr), (key), NULL) #define VarHashInvalidateEntry(varPtr) \ ((varPtr)->flags |= VAR_DEAD_HASH) #define VarHashDeleteEntry(varPtr) \ Tcl_DeleteHashEntry(&(((VarInHash *) varPtr)->entry)) #define VarHashFirstEntry(tablePtr, searchPtr) \ Tcl_FirstHashEntry(&(tablePtr)->table, (searchPtr)) #define VarHashNextEntry(searchPtr) \ Tcl_NextHashEntry((searchPtr)) static inline Var * VarHashFirstVar( TclVarHashTable *tablePtr, Tcl_HashSearch *searchPtr) { Tcl_HashEntry *hPtr = VarHashFirstEntry(tablePtr, searchPtr); if (!hPtr) { return NULL; } return VarHashGetValue(hPtr); } static inline Var * VarHashNextVar( Tcl_HashSearch *searchPtr) { Tcl_HashEntry *hPtr = VarHashNextEntry(searchPtr); if (!hPtr) { return NULL; } return VarHashGetValue(hPtr); } #define VarHashDeleteTable(tablePtr) \ Tcl_DeleteHashTable(&(tablePtr)->table) /* * The strings below are used to indicate what went wrong when a variable * access is denied. */ static const char NOSUCHVAR[] = "no such variable"; static const char ISARRAY[] = "variable is array"; static const char NEEDARRAY[] = "variable isn't array"; static const char NOSUCHELEMENT[] = "no such element in array"; static const char DANGLINGELEMENT[] = "upvar refers to element in deleted array"; static const char DANGLINGVAR[] = "upvar refers to variable in deleted namespace"; static const char BADNAMESPACE[] = "parent namespace doesn't exist"; static const char MISSINGNAME[] = "missing variable name"; static const char ISARRAYELEMENT[] = "name refers to an element in an array"; static const char ISCONST[] = "variable is a constant"; static const char EXISTS[] = "variable already exists"; /* * A test to see if we are in a call frame that has local variables. This is * true if we are inside a procedure body. */ #define HasLocalVars(framePtr) ((framePtr)->isProcCallFrame & FRAME_IS_PROC) /* * The following structure describes an enumerative search in progress on an * array variable; this are invoked with options to the "array" command. */ typedef struct ArraySearch { Tcl_Obj *name; /* Name of this search */ int id; /* Integer id used to distinguish among * multiple concurrent searches for the same * array. */ struct Var *varPtr; /* Pointer to array variable that's being * searched. */ Tcl_HashSearch search; /* Info kept by the hash module about progress * through the array. */ Tcl_HashEntry *nextEntry; /* Non-null means this is the next element to * be enumerated (it's leftover from the * Tcl_FirstHashEntry call or from an "array * anymore" command). NULL means must call * Tcl_NextHashEntry to get value to * return. */ struct ArraySearch *nextPtr;/* Next in list of all active searches for * this variable, or NULL if this is the last * one. */ } ArraySearch; /* * TIP #508: [array default] * * The following structure extends the regular TclVarHashTable used by array * variables to store their optional default value. */ typedef struct ArrayVarHashTable { TclVarHashTable table; Tcl_Obj *defaultObj; } ArrayVarHashTable; /* * Forward references to functions defined later in this file: */ static void AppendLocals(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Obj *patternPtr, int includeLinks, int justConstants); static void ArrayPopulateSearch(Tcl_Interp *interp, Tcl_Obj *arrayNameObj, Var *varPtr, ArraySearch *searchPtr); static void ArrayDoneSearch(Interp *iPtr, Var *varPtr, ArraySearch *searchPtr); static Tcl_NRPostProc ArrayForLoopCallback; static Tcl_ObjCmdProc ArrayForNRCmd; static void DeleteSearches(Interp *iPtr, Var *arrayVarPtr); static void DeleteArray(Interp *iPtr, Tcl_Obj *arrayNamePtr, Var *varPtr, int flags, int index); static int LocateArray(Tcl_Interp *interp, Tcl_Obj *name, Var **varPtrPtr, int *isArrayPtr); static int NotArrayError(Tcl_Interp *interp, Tcl_Obj *name); static Tcl_Var ObjFindNamespaceVar(Tcl_Interp *interp, Tcl_Obj *namePtr, Tcl_Namespace *contextNsPtr, int flags); static int ObjMakeUpvar(Tcl_Interp *interp, CallFrame *framePtr, Tcl_Obj *otherP1Ptr, const char *otherP2, int otherFlags, Tcl_Obj *myNamePtr, int myFlags, int index); static ArraySearch * ParseSearchId(Tcl_Interp *interp, const Var *varPtr, Tcl_Obj *varNamePtr, Tcl_Obj *handleObj); static void UnsetVarStruct(Var *varPtr, Var *arrayPtr, Interp *iPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags, int index); /* * TIP #508: [array default] */ static Tcl_ObjCmdProc ArrayDefaultCmd; static void DeleteArrayVar(Var *arrayPtr); static void SetArrayDefault(Var *arrayPtr, Tcl_Obj *defaultObj); /* * Functions defined in this file that may be exported in the future for use * by the bytecode compiler and engine or to the public interface. */ MODULE_SCOPE Var * TclLookupSimpleVar(Tcl_Interp *interp, Tcl_Obj *varNamePtr, int flags, int create, const char **errMsgPtr, int *indexPtr); static Tcl_DupInternalRepProc DupLocalVarName; static Tcl_FreeInternalRepProc FreeLocalVarName; static Tcl_FreeInternalRepProc FreeParsedVarName; static Tcl_DupInternalRepProc DupParsedVarName; /* * Types of Tcl_Objs used to cache variable lookups. * * localVarName - INTERNALREP DEFINITION: * twoPtrValue.ptr1: pointer to name obj in varFramePtr->localCache * or NULL if it is this same obj * twoPtrValue.ptr2: index into locals table * * parsedVarName - INTERNALREP DEFINITION: * twoPtrValue.ptr1: pointer to the array name Tcl_Obj, or NULL if it is a * scalar variable * twoPtrValue.ptr2: pointer to the element name string (owned by this * Tcl_Obj), or NULL if it is a scalar variable */ static const Tcl_ObjType localVarNameType = { "localVarName", FreeLocalVarName, DupLocalVarName, NULL, NULL, TCL_OBJTYPE_V0 }; #define LocalSetInternalRep(objPtr, index, namePtr) \ do { \ Tcl_ObjInternalRep ir; \ Tcl_Obj *ptr = (namePtr); \ if (ptr) {Tcl_IncrRefCount(ptr);} \ ir.twoPtrValue.ptr1 = ptr; \ ir.twoPtrValue.ptr2 = INT2PTR(index); \ Tcl_StoreInternalRep((objPtr), &localVarNameType, &ir); \ } while (0) #define LocalGetInternalRep(objPtr, index, name) \ do { \ const Tcl_ObjInternalRep *irPtr; \ irPtr = TclFetchInternalRep((objPtr), &localVarNameType); \ (name) = irPtr ? (Tcl_Obj *)irPtr->twoPtrValue.ptr1 : NULL; \ (index) = irPtr ? PTR2INT(irPtr->twoPtrValue.ptr2) : TCL_INDEX_NONE; \ } while (0) static const Tcl_ObjType parsedVarNameType = { "parsedVarName", FreeParsedVarName, DupParsedVarName, NULL, NULL, TCL_OBJTYPE_V0 }; #define ParsedSetInternalRep(objPtr, arrayPtr, elem) \ do { \ Tcl_ObjInternalRep ir; \ Tcl_Obj *ptr1 = (arrayPtr); \ Tcl_Obj *ptr2 = (elem); \ if (ptr1) {Tcl_IncrRefCount(ptr1);} \ if (ptr2) {Tcl_IncrRefCount(ptr2);} \ ir.twoPtrValue.ptr1 = ptr1; \ ir.twoPtrValue.ptr2 = ptr2; \ Tcl_StoreInternalRep((objPtr), &parsedVarNameType, &ir); \ } while (0) #define ParsedGetInternalRep(objPtr, parsed, array, elem) \ do { \ const Tcl_ObjInternalRep *irPtr; \ irPtr = TclFetchInternalRep((objPtr), &parsedVarNameType); \ (parsed) = (irPtr != NULL); \ (array) = irPtr ? (Tcl_Obj *)irPtr->twoPtrValue.ptr1 : NULL; \ (elem) = irPtr ? (Tcl_Obj *)irPtr->twoPtrValue.ptr2 : NULL; \ } while (0) Var * TclVarHashCreateVar( TclVarHashTable *tablePtr, const char *key, int *newPtr) { Tcl_Obj *keyPtr; Var *varPtr; keyPtr = Tcl_NewStringObj(key, -1); Tcl_IncrRefCount(keyPtr); varPtr = VarHashCreateVar(tablePtr, keyPtr, newPtr); Tcl_DecrRefCount(keyPtr); return varPtr; } static int LocateArray( Tcl_Interp *interp, Tcl_Obj *name, Var **varPtrPtr, int *isArrayPtr) { Var *arrayPtr, *varPtr = TclObjLookupVarEx(interp, name, NULL, /*flags*/ 0, /*msg*/ 0, /*createPart1*/ 0, /*createPart2*/ 0, &arrayPtr); if (TclCheckArrayTraces(interp, varPtr, arrayPtr, name, -1) == TCL_ERROR) { return TCL_ERROR; } if (varPtrPtr) { *varPtrPtr = varPtr; } if (isArrayPtr) { *isArrayPtr = varPtr && !TclIsVarUndefined(varPtr) && TclIsVarArray(varPtr); } return TCL_OK; } static int NotArrayError( Tcl_Interp *interp, Tcl_Obj *name) { const char *nameStr = TclGetString(name); Tcl_SetObjResult(interp, Tcl_ObjPrintf("\"%s\" isn't an array", nameStr)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "ARRAY", nameStr, (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TclCleanupVar -- * * This function is called when it looks like it may be OK to free up a * variable's storage. If the variable is in a hashtable, its Var * structure and hash table entry will be freed along with those of its * containing array, if any. This function is called, for example, when * a trace on a variable deletes a variable. * * Results: * None. * * Side effects: * If the variable (or its containing array) really is dead and in a * hashtable, then its Var structure, and possibly its hash table entry, * is freed up. * *---------------------------------------------------------------------- */ static inline void CleanupVar( Var *varPtr, /* Pointer to variable that may be a candidate * for being expunged. */ Var *arrayPtr) /* Array that contains the variable, or NULL * if this variable isn't an array element. */ { if (TclIsVarUndefined(varPtr) && TclIsVarInHash(varPtr) && !TclIsVarTraced(varPtr) && (VarHashRefCount(varPtr) == (Tcl_Size) !TclIsVarDeadHash(varPtr))) { if (VarHashRefCount(varPtr) == 0) { Tcl_Free(varPtr); } else { VarHashDeleteEntry(varPtr); } } if (arrayPtr != NULL && TclIsVarUndefined(arrayPtr) && TclIsVarInHash(arrayPtr) && !TclIsVarTraced(arrayPtr) && (VarHashRefCount(arrayPtr) == (Tcl_Size) !TclIsVarDeadHash(arrayPtr))) { if (VarHashRefCount(arrayPtr) == 0) { Tcl_Free(arrayPtr); } else { VarHashDeleteEntry(arrayPtr); } } } void TclCleanupVar( Var *varPtr, /* Pointer to variable that may be a candidate * for being expunged. */ Var *arrayPtr) /* Array that contains the variable, or NULL * if this variable isn't an array element. */ { CleanupVar(varPtr, arrayPtr); } /* *---------------------------------------------------------------------- * * TclLookupVar -- * * This function is used to locate a variable given its name(s). It has * been mostly superseded by TclObjLookupVar, it is now only used by the * trace code. It is kept in tcl9.0 mainly because it is in the internal * stubs table, so that some extension may be calling it. * * Results: * The return value is a pointer to the variable structure indicated by * part1 and part2, or NULL if the variable couldn't be found. If the * variable is found, *arrayPtrPtr is filled in with the address of the * variable structure for the array that contains the variable (or NULL * if the variable is a scalar). If the variable can't be found and * either createPart1 or createPart2 are 1, a new as-yet-undefined * (VAR_UNDEFINED) variable structure is created, entered into a hash * table, and returned. * * If the variable isn't found and creation wasn't specified, or some * other error occurs, NULL is returned and an error message is left in * the interp's result if TCL_LEAVE_ERR_MSG is set in flags. * * Note: it's possible for the variable returned to be VAR_UNDEFINED even * if createPart1 or createPart2 are 1 (these only cause the hash table * entry or array to be created). For example, the variable might be a * global that has been unset but is still referenced by a procedure, or * a variable that has been unset but it only being kept in existence (if * VAR_UNDEFINED) by a trace. * * Side effects: * New hashtable entries may be created if createPart1 or createPart2 * are 1. * *---------------------------------------------------------------------- */ Var * TclLookupVar( Tcl_Interp *interp, /* Interpreter to use for lookup. */ const char *part1, /* If part2 isn't NULL, this is the name of an * array. Otherwise, this is a full variable * name that could include a parenthesized * array element. */ const char *part2, /* Name of element within array, or NULL. */ int flags, /* Only TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * and TCL_LEAVE_ERR_MSG bits matter. */ const char *msg, /* Verb to use in error messages, e.g. "read" * or "set". Only needed if TCL_LEAVE_ERR_MSG * is set in flags. */ int createPart1, /* If 1, create hash table entry for part 1 of * name, if it doesn't already exist. If 0, * return error if it doesn't exist. */ int createPart2, /* If 1, create hash table entry for part 2 of * name, if it doesn't already exist. If 0, * return error if it doesn't exist. */ Var **arrayPtrPtr) /* If the name refers to an element of an * array, *arrayPtrPtr gets filled in with * address of array variable. Otherwise this * is set to NULL. */ { Var *varPtr; Tcl_Obj *part1Ptr = Tcl_NewStringObj(part1, -1); if (createPart1) { Tcl_IncrRefCount(part1Ptr); } varPtr = TclObjLookupVar(interp, part1Ptr, part2, flags, msg, createPart1, createPart2, arrayPtrPtr); TclDecrRefCount(part1Ptr); return varPtr; } /* *---------------------------------------------------------------------- * * TclObjLookupVar, TclObjLookupVarEx -- * * This function is used by virtually all of the variable code to locate * a variable given its name(s). The parsing into array/element * components and (if possible) the lookup results are cached in * part1Ptr, which is converted to one of the varNameTypes. * * Results: * The return value is a pointer to the variable structure indicated by * part1Ptr and part2, or NULL if the variable couldn't be found. If * * the variable is found, *arrayPtrPtr is filled with the address of the * variable structure for the array that contains the variable (or NULL * if the variable is a scalar). If the variable can't be found and * either createPart1 or createPart2 are 1, a new as-yet-undefined * (VAR_UNDEFINED) variable structure is created, entered into a hash * table, and returned. * * If the variable isn't found and creation wasn't specified, or some * other error occurs, NULL is returned and an error message is left in * the interp's result if TCL_LEAVE_ERR_MSG is set in flags. * * Note: it's possible for the variable returned to be VAR_UNDEFINED even * if createPart1 or createPart2 are 1 (these only cause the hash table * entry or array to be created). For example, the variable might be a * global that has been unset but is still referenced by a procedure, or * a variable that has been unset but it only being kept in existence (if * VAR_UNDEFINED) by a trace. * * Side effects: * New hashtable entries may be created if createPart1 or createPart2 * are 1. The object part1Ptr is converted to one of localVarNameType * or parsedVarNameType and caches as much of the lookup as it can. * When createPart1 is 1, callers must IncrRefCount part1Ptr if they * plan to DecrRefCount it. * *---------------------------------------------------------------------- */ Var * TclObjLookupVar( Tcl_Interp *interp, /* Interpreter to use for lookup. */ Tcl_Obj *part1Ptr, /* If part2 isn't NULL, this is the name of an * array. Otherwise, this is a full variable * name that could include a parenthesized * array element. */ const char *part2, /* Name of element within array, or NULL. */ int flags, /* Only TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * and TCL_LEAVE_ERR_MSG bits matter. */ const char *msg, /* Verb to use in error messages, e.g. "read" * or "set". Only needed if TCL_LEAVE_ERR_MSG * is set in flags. */ int createPart1, /* If 1, create hash table entry for part 1 of * name, if it doesn't already exist. If 0, * return error if it doesn't exist. */ int createPart2, /* If 1, create hash table entry for part 2 of * name, if it doesn't already exist. If 0, * return error if it doesn't exist. */ Var **arrayPtrPtr) /* If the name refers to an element of an * array, *arrayPtrPtr gets filled in with * address of array variable. Otherwise this * is set to NULL. */ { Tcl_Obj *part2Ptr = NULL; Var *resPtr; if (part2) { part2Ptr = Tcl_NewStringObj(part2, -1); if (createPart2) { Tcl_IncrRefCount(part2Ptr); } } resPtr = TclObjLookupVarEx(interp, part1Ptr, part2Ptr, flags, msg, createPart1, createPart2, arrayPtrPtr); if (part2Ptr) { Tcl_DecrRefCount(part2Ptr); } return resPtr; } /* * When createPart1 is 1, callers must IncrRefCount part1Ptr if they * plan to DecrRefCount it. * When createPart2 is 1, callers must IncrRefCount part2Ptr if they * plan to DecrRefCount it. */ Var * TclObjLookupVarEx( Tcl_Interp *interp, /* Interpreter to use for lookup. */ Tcl_Obj *part1Ptr, /* If part2Ptr isn't NULL, this is the name of * an array. Otherwise, this is a full * variable name that could include a * parenthesized array element. */ Tcl_Obj *part2Ptr, /* Name of element within array, or NULL. */ int flags, /* Only TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * and TCL_LEAVE_ERR_MSG bits matter. */ const char *msg, /* Verb to use in error messages, e.g. "read" * or "set". Only needed if TCL_LEAVE_ERR_MSG * is set in flags. */ int createPart1, /* If 1, create hash table entry for part 1 of * name, if it doesn't already exist. If 0, * return error if it doesn't exist. */ int createPart2, /* If 1, create hash table entry for part 2 of * name, if it doesn't already exist. If 0, * return error if it doesn't exist. */ Var **arrayPtrPtr) /* If the name refers to an element of an * array, *arrayPtrPtr gets filled in with * address of array variable. Otherwise this * is set to NULL. */ { Interp *iPtr = (Interp *) interp; CallFrame *varFramePtr = iPtr->varFramePtr; Var *varPtr; /* Points to the variable's in-frame Var * structure. */ const char *errMsg = NULL; int index, parsed = 0; Tcl_Size localIndex; Tcl_Obj *namePtr, *arrayPtr, *elem; *arrayPtrPtr = NULL; restart: LocalGetInternalRep(part1Ptr, localIndex, namePtr); if (localIndex >= 0) { if (HasLocalVars(varFramePtr) && !(flags & (TCL_GLOBAL_ONLY | TCL_NAMESPACE_ONLY)) && (localIndex < varFramePtr->numCompiledLocals)) { /* * Use the cached index if the names coincide. */ Tcl_Obj *checkNamePtr = localName(varFramePtr, localIndex); if ((!namePtr && (checkNamePtr == part1Ptr)) || (namePtr && (checkNamePtr == namePtr))) { varPtr = (Var *) &(varFramePtr->compiledLocals[localIndex]); goto donePart1; } } goto doneParsing; } /* * If part1Ptr is a parsedVarNameType, retrieve the preparsed parts. */ ParsedGetInternalRep(part1Ptr, parsed, arrayPtr, elem); if (parsed && arrayPtr) { if (part2Ptr != NULL) { /* * ERROR: part1Ptr is already an array element, cannot specify * a part2. */ if (flags & TCL_LEAVE_ERR_MSG) { TclObjVarErrMsg(interp, part1Ptr, part2Ptr, msg, NOSUCHVAR, -1); Tcl_SetErrorCode(interp, "TCL", "VALUE", "VARNAME", (char *)NULL); } return NULL; } part2Ptr = elem; part1Ptr = arrayPtr; goto restart; } if (!parsed) { /* * part1Ptr is possibly an unparsed array element. */ Tcl_Size len; const char *part1 = TclGetStringFromObj(part1Ptr, &len); if ((len > 1) && (part1[len - 1] == ')')) { const char *part2 = strchr(part1, '('); if (part2) { if (part2Ptr != NULL) { if (flags & TCL_LEAVE_ERR_MSG) { TclObjVarErrMsg(interp, part1Ptr, part2Ptr, msg, NEEDARRAY, -1); Tcl_SetErrorCode(interp, "TCL", "VALUE", "VARNAME", (char *)NULL); } return NULL; } arrayPtr = Tcl_NewStringObj(part1, (part2 - part1)); part2Ptr = Tcl_NewStringObj(part2 + 1, len - (part2 - part1) - 2); ParsedSetInternalRep(part1Ptr, arrayPtr, part2Ptr); part1Ptr = arrayPtr; } } } doneParsing: /* * part1Ptr is not an array element; look it up, and convert it to one of * the cached types if possible. */ varPtr = TclLookupSimpleVar(interp, part1Ptr, flags, createPart1, &errMsg, &index); if (varPtr == NULL) { if ((errMsg != NULL) && (flags & TCL_LEAVE_ERR_MSG)) { TclObjVarErrMsg(interp, part1Ptr, part2Ptr, msg, errMsg, -1); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "VARNAME", TclGetString(part1Ptr), (char *)NULL); } return NULL; } /* * Cache the newly found variable if possible. */ if (index >= 0) { /* * An indexed local variable. */ Tcl_Obj *cachedNamePtr = localName(varFramePtr, index); if (part1Ptr == cachedNamePtr) { LocalSetInternalRep(part1Ptr, index, NULL); } else { /* * [80304238ac] Trickiness here. We will store and incr the * refcount on cachedNamePtr. Trouble is that it's possible * (see test var-22.1) for cachedNamePtr to have an internalrep * that contains a stored and refcounted part1Ptr. This * would be a reference cycle which leads to a memory leak. * * The solution here is to wipe away all internalrep(s) in * cachedNamePtr and leave it as string only. This is * radical and destructive, so a better idea would be welcome. */ /* * Firstly set cached local var reference (avoid free before set, * see [45b9faf103f2]) */ LocalSetInternalRep(part1Ptr, index, cachedNamePtr); /* Then wipe it */ TclFreeInternalRep(cachedNamePtr); /* * Now go ahead and convert it to the "localVarName" type, * since we suspect at least some use of the value as a * varname and we want to resolve it quickly. */ LocalSetInternalRep(cachedNamePtr, index, NULL); } } else { /* * At least mark part1Ptr as already parsed. */ ParsedSetInternalRep(part1Ptr, NULL, NULL); } donePart1: while (TclIsVarLink(varPtr)) { varPtr = varPtr->value.linkPtr; } if (part2Ptr != NULL) { /* * Array element sought: look it up. */ *arrayPtrPtr = varPtr; varPtr = TclLookupArrayElement(interp, part1Ptr, part2Ptr, flags, msg, createPart1, createPart2, varPtr, -1); } return varPtr; } /* *---------------------------------------------------------------------- * * TclLookupSimpleVar -- * * This function is used by to locate a simple variable (i.e., not an * array element) given its name. * * Results: * The return value is a pointer to the variable structure indicated by * varName, or NULL if the variable couldn't be found. If the variable * can't be found and create is 1, a new as-yet-undefined (VAR_UNDEFINED) * variable structure is created, entered into a hash table, and * returned. * * If the current CallFrame corresponds to a proc and the variable found * is one of the compiledLocals, its index is placed in *indexPtr. * Otherwise, *indexPtr will be set to (according to the needs of * TclObjLookupVar): * -1 a global reference * -2 a reference to a namespace variable * -3 a non-cacheable reference, i.e., one of: * . non-indexed local var * . a reference of unknown origin; * . resolution by a namespace or interp resolver * * If the variable isn't found and creation wasn't specified, or some * other error occurs, NULL is returned and the corresponding error * message is left in *errMsgPtr. * * Note: it's possible for the variable returned to be VAR_UNDEFINED even * if create is 1 (this only causes the hash table entry to be created). * For example, the variable might be a global that has been unset but is * still referenced by a procedure, or a variable that has been unset but * it only being kept in existence (if VAR_UNDEFINED) by a trace. * * Side effects: * A new hashtable entry may be created if create is 1. * Callers must Incr varNamePtr if they plan to Decr it if create is 1. * *---------------------------------------------------------------------- */ Var * TclLookupSimpleVar( Tcl_Interp *interp, /* Interpreter to use for lookup. */ Tcl_Obj *varNamePtr, /* This is a simple variable name that could * represent a scalar or an array. */ int flags, /* Only TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * TCL_AVOID_RESOLVERS and TCL_LEAVE_ERR_MSG * bits matter. */ int create, /* If 1, create hash table entry for varname, * if it doesn't already exist. If 0, return * error if it doesn't exist. */ const char **errMsgPtr, int *indexPtr) { Interp *iPtr = (Interp *) interp; CallFrame *varFramePtr = iPtr->varFramePtr; /* Points to the procedure call frame whose * variables are currently in use. Same as the * current procedure's frame, if any, unless * an "uplevel" is executing. */ TclVarHashTable *tablePtr; /* Points to the hashtable, if any, in which * to look up the variable. */ Tcl_Var var; /* Used to search for global names. */ Var *varPtr; /* Points to the Var structure returned for * the variable. */ Namespace *varNsPtr, *cxtNsPtr, *dummy1Ptr, *dummy2Ptr; ResolverScheme *resPtr; int isNew, result; Tcl_Size i, varLen; const char *varName = TclGetStringFromObj(varNamePtr, &varLen); varPtr = NULL; varNsPtr = NULL; /* Set non-NULL if a nonlocal variable. */ *indexPtr = -3; if (flags & TCL_GLOBAL_ONLY) { cxtNsPtr = iPtr->globalNsPtr; } else { cxtNsPtr = iPtr->varFramePtr->nsPtr; } /* * If this namespace has a variable resolver, then give it first crack at * the variable resolution. It may return a Tcl_Var value, it may signal * to continue onward, or it may signal an error. */ if ((cxtNsPtr->varResProc != NULL || iPtr->resolverPtr != NULL) && !(flags & TCL_AVOID_RESOLVERS)) { resPtr = iPtr->resolverPtr; if (cxtNsPtr->varResProc) { result = cxtNsPtr->varResProc(interp, varName, (Tcl_Namespace *) cxtNsPtr, flags, &var); } else { result = TCL_CONTINUE; } while (result == TCL_CONTINUE && resPtr) { if (resPtr->varResProc) { result = resPtr->varResProc(interp, varName, (Tcl_Namespace *) cxtNsPtr, flags, &var); } resPtr = resPtr->nextPtr; } if (result == TCL_OK) { return (Var *) var; } else if (result != TCL_CONTINUE) { return NULL; } } /* * Look up varName. Look it up as either a namespace variable or as a * local variable in a procedure call frame (varFramePtr). Interpret * varName as a namespace variable if: * 1) so requested by a TCL_GLOBAL_ONLY or TCL_NAMESPACE_ONLY flag, * 2) there is no active frame (we're at the global :: scope), * 3) the active frame was pushed to define the namespace context for a * "namespace eval" or "namespace inscope" command, * 4) the name has namespace qualifiers ("::"s). * Otherwise, if varName is a local variable, search first in the frame's * array of compiler-allocated local variables, then in its hashtable for * runtime-created local variables. * * If create and the variable isn't found, create the variable and, if * necessary, create varFramePtr's local var hashtable. */ if (((flags & (TCL_GLOBAL_ONLY | TCL_NAMESPACE_ONLY)) != 0) || !HasLocalVars(varFramePtr) || (strstr(varName, "::") != NULL)) { const char *tail; int lookGlobal = (flags & TCL_GLOBAL_ONLY) || (cxtNsPtr == iPtr->globalNsPtr) || ((varName[0] == ':') && (varName[1] == ':')); if (lookGlobal) { *indexPtr = -1; flags = (flags | TCL_GLOBAL_ONLY) & ~TCL_NAMESPACE_ONLY; } else { flags = (flags | TCL_NAMESPACE_ONLY); *indexPtr = -2; } /* * Don't pass TCL_LEAVE_ERR_MSG, we may yet create the variable, or * otherwise generate our own error! */ varPtr = (Var *) ObjFindNamespaceVar(interp, varNamePtr, (Tcl_Namespace *) cxtNsPtr, (flags | TCL_AVOID_RESOLVERS) & ~TCL_LEAVE_ERR_MSG); if (varPtr == NULL) { Tcl_Obj *tailPtr; if (!create) { /* Var wasn't found and not to create it. */ *errMsgPtr = NOSUCHVAR; return NULL; } /* * Var wasn't found so create it. */ TclGetNamespaceForQualName(interp, varName, cxtNsPtr, flags, &varNsPtr, &dummy1Ptr, &dummy2Ptr, &tail); if (varNsPtr == NULL) { *errMsgPtr = BADNAMESPACE; return NULL; } else if (tail == NULL) { *errMsgPtr = MISSINGNAME; return NULL; } if (tail != varName) { tailPtr = Tcl_NewStringObj(tail, -1); } else { tailPtr = varNamePtr; } varPtr = VarHashCreateVar(&varNsPtr->varTable, tailPtr, &isNew); if (lookGlobal) { /* * The variable was created starting from the global * namespace: a global reference is returned even if it wasn't * explicitly requested. */ *indexPtr = -1; } else { *indexPtr = -2; } } } else { /* Local var: look in frame varFramePtr. */ Tcl_Size localCt = varFramePtr->numCompiledLocals; if (localCt > 0) { Tcl_Obj **objPtrPtr = &varFramePtr->localCachePtr->varName0; const char *localNameStr; Tcl_Size localLen; for (i=0 ; icompiledLocals[i]; } } } } tablePtr = varFramePtr->varTablePtr; if (create) { if (tablePtr == NULL) { tablePtr = (TclVarHashTable *)Tcl_Alloc(sizeof(TclVarHashTable)); TclInitVarHashTable(tablePtr, NULL); tablePtr->arrayPtr = varPtr; varFramePtr->varTablePtr = tablePtr; } varPtr = VarHashCreateVar(tablePtr, varNamePtr, &isNew); } else { varPtr = NULL; if (tablePtr != NULL) { varPtr = VarHashFindVar(tablePtr, varNamePtr); } if (varPtr == NULL) { *errMsgPtr = NOSUCHVAR; } } } return varPtr; } /* *---------------------------------------------------------------------- * * TclLookupArrayElement -- * * This function is used to locate a variable which is in an array's * hashtable given a pointer to the array's Var structure and the * element's name. * * Results: * The return value is a pointer to the variable structure , or NULL if * the variable couldn't be found. * * If arrayPtr points to a variable that isn't an array and createPart1 * is 1, the corresponding variable will be converted to an array. * Otherwise, NULL is returned and an error message is left in the * interp's result if TCL_LEAVE_ERR_MSG is set in flags. * * If the variable is not found and createPart2 is 1, the variable is * created. Otherwise, NULL is returned and an error message is left in * the interp's result if TCL_LEAVE_ERR_MSG is set in flags. * * Note: it's possible for the variable returned to be VAR_UNDEFINED even * if createPart1 or createPart2 are 1 (these only cause the hash table * entry or array to be created). For example, the variable might be a * global that has been unset but is still referenced by a procedure, or * a variable that has been unset but it only being kept in existence (if * VAR_UNDEFINED) by a trace. * * Side effects: * The variable at arrayPtr may be converted to be an array if * createPart1 is 1. A new hashtable entry may be created if createPart2 * is 1. * When createElem is 1, callers must incr elNamePtr if they plan * to decr it. * *---------------------------------------------------------------------- */ Var * TclLookupArrayElement( Tcl_Interp *interp, /* Interpreter to use for lookup. */ Tcl_Obj *arrayNamePtr, /* This is the name of the array, or NULL if * index>= 0. */ Tcl_Obj *elNamePtr, /* Name of element within array. */ int flags, /* Only TCL_LEAVE_ERR_MSG bit matters. */ const char *msg, /* Verb to use in error messages, e.g. "read" * or "set". Only needed if TCL_LEAVE_ERR_MSG * is set in flags. */ int createArray, /* If 1, transform arrayName to be an array if * it isn't one yet and the transformation is * possible. If 0, return error if it isn't * already an array. */ int createElem, /* If 1, create hash table entry for the * element, if it doesn't already exist. If 0, * return error if it doesn't exist. */ Var *arrayPtr, /* Pointer to the array's Var structure. */ int index) /* If >=0, the index of the local array. */ { int isNew; Var *varPtr; /* * We're dealing with an array element. Make sure the variable is an array * and look up the element (create the element if desired). */ if (TclIsVarUndefined(arrayPtr) && !TclIsVarArrayElement(arrayPtr)) { if (!createArray) { if (flags & TCL_LEAVE_ERR_MSG) { TclObjVarErrMsg(interp, arrayNamePtr, elNamePtr, msg, NOSUCHVAR, index); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "VARNAME", arrayNamePtr?TclGetString(arrayNamePtr):NULL, (char *)NULL); } return NULL; } /* * Make sure we are not resurrecting a namespace variable from a * deleted namespace! */ if (TclIsVarDeadHash(arrayPtr)) { if (flags & TCL_LEAVE_ERR_MSG) { TclObjVarErrMsg(interp, arrayNamePtr, elNamePtr, msg, DANGLINGVAR, index); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "VARNAME", arrayNamePtr?TclGetString(arrayNamePtr):NULL, (char *)NULL); } return NULL; } TclInitArrayVar(arrayPtr); } else if (!TclIsVarArray(arrayPtr)) { if (flags & TCL_LEAVE_ERR_MSG) { TclObjVarErrMsg(interp, arrayNamePtr, elNamePtr, msg, NEEDARRAY, index); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "VARNAME", arrayNamePtr?TclGetString(arrayNamePtr):NULL, (char *)NULL); } return NULL; } if (createElem) { varPtr = VarHashCreateVar(arrayPtr->value.tablePtr, elNamePtr, &isNew); if (isNew) { if (arrayPtr->flags & VAR_SEARCH_ACTIVE) { DeleteSearches((Interp *) interp, arrayPtr); } TclSetVarArrayElement(varPtr); } } else { varPtr = VarHashFindVar(arrayPtr->value.tablePtr, elNamePtr); if (varPtr == NULL) { if (flags & TCL_LEAVE_ERR_MSG) { TclObjVarErrMsg(interp, arrayNamePtr, elNamePtr, msg, NOSUCHELEMENT, index); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "ELEMENT", TclGetString(elNamePtr), (char *)NULL); } } } return varPtr; } /* *---------------------------------------------------------------------- * * Tcl_GetVar2 -- * * Return the value of a Tcl variable as a string, given a two-part name * consisting of array name and element within array. * * Results: * The return value points to the current value of the variable given by * part1 and part2 as a string. If the specified variable doesn't exist, * or if there is a clash in array usage, then NULL is returned and a * message will be left in the interp's result if the TCL_LEAVE_ERR_MSG * flag is set. Note: the return value is only valid up until the next * change to the variable; if you depend on the value lasting longer than * that, then make yourself a private copy. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char * Tcl_GetVar2( Tcl_Interp *interp, /* Command interpreter in which variable is to * be looked up. */ const char *part1, /* Name of an array (if part2 is non-NULL) or * the name of a variable. */ const char *part2, /* If non-NULL, gives the name of an element * in the array part1. */ int flags) /* OR-ed combination of TCL_GLOBAL_ONLY, * TCL_NAMESPACE_ONLY and TCL_LEAVE_ERR_MSG * * bits. */ { Tcl_Obj *resultPtr; Tcl_Obj *part2Ptr = NULL, *part1Ptr = Tcl_NewStringObj(part1, -1); if (part2) { part2Ptr = Tcl_NewStringObj(part2, -1); Tcl_IncrRefCount(part2Ptr); } resultPtr = Tcl_ObjGetVar2(interp, part1Ptr, part2Ptr, flags); Tcl_DecrRefCount(part1Ptr); if (part2Ptr) { Tcl_DecrRefCount(part2Ptr); } if (resultPtr == NULL) { return NULL; } return TclGetString(resultPtr); } /* *---------------------------------------------------------------------- * * Tcl_GetVar2Ex -- * * Return the value of a Tcl variable as a Tcl object, given a two-part * name consisting of array name and element within array. * * Results: * The return value points to the current object value of the variable * given by part1Ptr and part2Ptr. If the specified variable doesn't * exist, or if there is a clash in array usage, then NULL is returned * and a message will be left in the interpreter's result if the * TCL_LEAVE_ERR_MSG flag is set. * * Side effects: * The ref count for the returned object is _not_ incremented to reflect * the returned reference; if you want to keep a reference to the object * you must increment its ref count yourself. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_GetVar2Ex( Tcl_Interp *interp, /* Command interpreter in which variable is to * be looked up. */ const char *part1, /* Name of an array (if part2 is non-NULL) or * the name of a variable. */ const char *part2, /* If non-NULL, gives the name of an element * in the array part1. */ int flags) /* OR-ed combination of TCL_GLOBAL_ONLY, and * TCL_LEAVE_ERR_MSG bits. */ { Tcl_Obj *resPtr, *part2Ptr = NULL, *part1Ptr = Tcl_NewStringObj(part1, -1); if (part2) { part2Ptr = Tcl_NewStringObj(part2, -1); Tcl_IncrRefCount(part2Ptr); } resPtr = Tcl_ObjGetVar2(interp, part1Ptr, part2Ptr, flags); Tcl_DecrRefCount(part1Ptr); if (part2Ptr) { Tcl_DecrRefCount(part2Ptr); } return resPtr; } /* *---------------------------------------------------------------------- * * Tcl_ObjGetVar2 -- * * Return the value of a Tcl variable as a Tcl object, given a two-part * name consisting of array name and element within array. * * Results: * The return value points to the current object value of the variable * given by part1Ptr and part2Ptr. If the specified variable doesn't * exist, or if there is a clash in array usage, then NULL is returned * and a message will be left in the interpreter's result if the * TCL_LEAVE_ERR_MSG flag is set. * * Side effects: * The ref count for the returned object is _not_ incremented to reflect * the returned reference; if you want to keep a reference to the object * you must increment its ref count yourself. * * Callers must incr part2Ptr if they plan to decr it. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_ObjGetVar2( Tcl_Interp *interp, /* Command interpreter in which variable is to * be looked up. */ Tcl_Obj *part1Ptr, /* Points to an object holding the name of an * array (if part2 is non-NULL) or the name of * a variable. */ Tcl_Obj *part2Ptr, /* If non-null, points to an object holding * the name of an element in the array * part1Ptr. */ int flags) /* OR-ed combination of TCL_GLOBAL_ONLY and * TCL_LEAVE_ERR_MSG bits. */ { Var *varPtr, *arrayPtr; /* * Filter to pass through only the flags this interface supports. */ flags &= (TCL_GLOBAL_ONLY|TCL_NAMESPACE_ONLY|TCL_LEAVE_ERR_MSG); varPtr = TclObjLookupVarEx(interp, part1Ptr, part2Ptr, flags, "read", /*createPart1*/ 0, /*createPart2*/ 1, &arrayPtr); if (varPtr == NULL) { return NULL; } return TclPtrGetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, flags, -1); } /* *---------------------------------------------------------------------- * * TclPtrGetVar -- * * Return the value of a Tcl variable as a Tcl object, given the pointers * to the variable's (and possibly containing array's) VAR structure. * * Results: * The return value points to the current object value of the variable * given by varPtr. If the specified variable doesn't exist, or if there * is a clash in array usage, then NULL is returned and a message will be * left in the interpreter's result if the TCL_LEAVE_ERR_MSG flag is set. * * Side effects: * The ref count for the returned object is _not_ incremented to reflect * the returned reference; if you want to keep a reference to the object * you must increment its ref count yourself. * *---------------------------------------------------------------------- */ Tcl_Obj * TclPtrGetVar( Tcl_Interp *interp, /* Command interpreter in which variable is to * be looked up. */ Tcl_Var varPtr, /* The variable to be read.*/ Tcl_Var arrayPtr, /* NULL for scalar variables, pointer to the * containing array otherwise. */ Tcl_Obj *part1Ptr, /* Name of an array (if part2 is non-NULL) or * the name of a variable. */ Tcl_Obj *part2Ptr, /* If non-NULL, gives the name of an element * in the array part1. */ int flags) /* OR-ed combination of TCL_GLOBAL_ONLY, and * TCL_LEAVE_ERR_MSG bits. */ { if (varPtr == NULL) { Tcl_Panic("varPtr must not be NULL"); } if (part1Ptr == NULL) { Tcl_Panic("part1Ptr must not be NULL"); } return TclPtrGetVarIdx(interp, (Var *) varPtr, (Var *) arrayPtr, part1Ptr, part2Ptr, flags, -1); } /* *---------------------------------------------------------------------- * * TclPtrGetVarIdx -- * * Return the value of a Tcl variable as a Tcl object, given the pointers * to the variable's (and possibly containing array's) VAR structure. * * Results: * The return value points to the current object value of the variable * given by varPtr. If the specified variable doesn't exist, or if there * is a clash in array usage, then NULL is returned and a message will be * left in the interpreter's result if the TCL_LEAVE_ERR_MSG flag is set. * * Side effects: * The ref count for the returned object is _not_ incremented to reflect * the returned reference; if you want to keep a reference to the object * you must increment its ref count yourself. * *---------------------------------------------------------------------- */ Tcl_Obj * TclPtrGetVarIdx( Tcl_Interp *interp, /* Command interpreter in which variable is to * be looked up. */ Var *varPtr, /* The variable to be read.*/ Var *arrayPtr, /* NULL for scalar variables, pointer to the * containing array otherwise. */ Tcl_Obj *part1Ptr, /* Name of an array (if part2 is non-NULL) or * the name of a variable. */ Tcl_Obj *part2Ptr, /* If non-NULL, gives the name of an element * in the array part1. */ int flags, /* OR-ed combination of TCL_GLOBAL_ONLY, and * TCL_LEAVE_ERR_MSG bits. */ int index) /* Index into the local variable table of the * variable, or -1. Only used when part1Ptr is * NULL. */ { Interp *iPtr = (Interp *) interp; const char *msg; Var *initialArrayPtr = arrayPtr; TclVarFindHiddenArray(varPtr, arrayPtr); /* * Invoke any read traces that have been set for the variable. */ if ((varPtr->flags & VAR_TRACED_READ) || (arrayPtr && (arrayPtr->flags & VAR_TRACED_READ))) { if (TCL_ERROR == TclObjCallVarTraces(iPtr, arrayPtr, varPtr, part1Ptr, part2Ptr, (flags & (TCL_NAMESPACE_ONLY|TCL_GLOBAL_ONLY)) | TCL_TRACE_READS, (flags & TCL_LEAVE_ERR_MSG), index)) { goto errorReturn; } } /* * Return the element if it's an existing scalar variable. */ if (TclIsVarScalar(varPtr) && !TclIsVarUndefined(varPtr)) { return varPtr->value.objPtr; } /* * Return the array default value if any. */ if (arrayPtr && TclIsVarArray(arrayPtr) && TclGetArrayDefault(arrayPtr)) { return TclGetArrayDefault(arrayPtr); } if (TclIsVarArrayElement(varPtr) && !arrayPtr) { /* * UGLY! Peek inside the implementation of things. This lets us get * the default of an array even when we've been [upvar]ed to just an * element of the array. */ ArrayVarHashTable *avhtPtr = (ArrayVarHashTable *) ((VarInHash *) varPtr)->entry.tablePtr; if (avhtPtr->defaultObj) { return avhtPtr->defaultObj; } } if (flags & TCL_LEAVE_ERR_MSG) { if (TclIsVarUndefined(varPtr) && initialArrayPtr && !TclIsVarUndefined(initialArrayPtr)) { msg = NOSUCHELEMENT; } else if (TclIsVarArray(varPtr)) { msg = ISARRAY; } else { msg = NOSUCHVAR; } TclObjVarErrMsg(interp, part1Ptr, part2Ptr, "read", msg, index); } /* * An error. If the variable doesn't exist anymore and no-one's using it, * then free up the relevant structures and hash table entries. */ errorReturn: Tcl_SetErrorCode(interp, "TCL", "READ", "VARNAME", (char *)NULL); if (TclIsVarUndefined(varPtr)) { TclCleanupVar(varPtr, arrayPtr); } return NULL; } /* *---------------------------------------------------------------------- * * Tcl_SetObjCmd -- * * This function is invoked to process the "set" Tcl command. See the * user documentation for details on what it does. * * Results: * A standard Tcl result value. * * Side effects: * A variable's value may be changed. * *---------------------------------------------------------------------- */ int Tcl_SetObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *varValueObj; if (objc == 2) { varValueObj = Tcl_ObjGetVar2(interp, objv[1], NULL,TCL_LEAVE_ERR_MSG); if (varValueObj == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, varValueObj); return TCL_OK; } else if (objc == 3) { varValueObj = Tcl_ObjSetVar2(interp, objv[1], NULL, objv[2], TCL_LEAVE_ERR_MSG); if (varValueObj == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, varValueObj); return TCL_OK; } else { Tcl_WrongNumArgs(interp, 1, objv, "varName ?newValue?"); return TCL_ERROR; } } /* *---------------------------------------------------------------------- * * Tcl_SetVar2 -- * * Given a two-part variable name, which may refer either to a scalar * variable or an element of an array, change the value of the variable. * If the named scalar or array or element doesn't exist then create one. * * Results: * Returns a pointer to the malloc'ed string which is the character * representation of the variable's new value. The caller must not modify * this string. If the write operation was disallowed because an array * was expected but not found (or vice versa), then NULL is returned; if * the TCL_LEAVE_ERR_MSG flag is set, then an explanatory message will be * left in the interp's result. Note that the returned string may not be * the same as newValue; this is because variable traces may modify the * variable's value. * * Side effects: * The value of the given variable is set. If either the array or the * entry didn't exist then a new one is created. * *---------------------------------------------------------------------- */ const char * Tcl_SetVar2( Tcl_Interp *interp, /* Command interpreter in which variable is to * be looked up. */ const char *part1, /* If part2 is NULL, this is name of scalar * variable. Otherwise it is the name of an * array. */ const char *part2, /* Name of an element within an array, or * NULL. */ const char *newValue, /* New value for variable. */ int flags) /* Various flags that tell how to set value: * any of TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * TCL_APPEND_VALUE, TCL_LIST_ELEMENT, or * TCL_LEAVE_ERR_MSG. */ { Tcl_Obj *varValuePtr = Tcl_SetVar2Ex(interp, part1, part2, Tcl_NewStringObj(newValue, -1), flags); if (varValuePtr == NULL) { return NULL; } return TclGetString(varValuePtr); } /* *---------------------------------------------------------------------- * * Tcl_SetVar2Ex -- * * Given a two-part variable name, which may refer either to a scalar * variable or an element of an array, change the value of the variable * to a new Tcl object value. If the named scalar or array or element * doesn't exist then create one. * * Results: * Returns a pointer to the Tcl_Obj holding the new value of the * variable. If the write operation was disallowed because an array was * expected but not found (or vice versa), then NULL is returned; if the * TCL_LEAVE_ERR_MSG flag is set, then an explanatory message will be * left in the interpreter's result. Note that the returned object may * not be the same one referenced by newValuePtr; this is because * variable traces may modify the variable's value. * * Side effects: * The value of the given variable is set. If either the array or the * entry didn't exist then a new variable is created. * * The reference count is decremented for any old value of the variable * and incremented for its new value. If the new value for the variable * is not the same one referenced by newValuePtr (perhaps as a result of * a variable trace), then newValuePtr's ref count is left unchanged by * Tcl_SetVar2Ex. newValuePtr's ref count is also left unchanged if we * are appending it as a string value: that is, if "flags" includes * TCL_APPEND_VALUE but not TCL_LIST_ELEMENT. * * The reference count for the returned object is _not_ incremented: if * you want to keep a reference to the object you must increment its ref * count yourself. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_SetVar2Ex( Tcl_Interp *interp, /* Command interpreter in which variable is to * be found. */ const char *part1, /* Name of an array (if part2 is non-NULL) or * the name of a variable. */ const char *part2, /* If non-NULL, gives the name of an element * in the array part1. */ Tcl_Obj *newValuePtr, /* New value for variable. */ int flags) /* Various flags that tell how to set value: * any of TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * TCL_APPEND_VALUE, TCL_LIST_ELEMENT or * TCL_LEAVE_ERR_MSG. */ { Tcl_Obj *resPtr, *part2Ptr = NULL, *part1Ptr = Tcl_NewStringObj(part1, -1); Tcl_IncrRefCount(part1Ptr); if (part2) { part2Ptr = Tcl_NewStringObj(part2, -1); Tcl_IncrRefCount(part2Ptr); } resPtr = Tcl_ObjSetVar2(interp, part1Ptr, part2Ptr, newValuePtr, flags); Tcl_DecrRefCount(part1Ptr); if (part2Ptr) { Tcl_DecrRefCount(part2Ptr); } return resPtr; } /* *---------------------------------------------------------------------- * * Tcl_ObjSetVar2 -- * * This function is the same as Tcl_SetVar2Ex above, except the variable * names are passed in Tcl object instead of strings. * * Results: * Returns a pointer to the Tcl_Obj holding the new value of the * variable. If the write operation was disallowed because an array was * expected but not found (or vice versa), then NULL is returned; if the * TCL_LEAVE_ERR_MSG flag is set, then an explanatory message will be * left in the interpreter's result. Note that the returned object may * not be the same one referenced by newValuePtr; this is because * variable traces may modify the variable's value. * * Side effects: * The value of the given variable is set. If either the array or the * entry didn't exist then a new variable is created. * Callers must Incr part1Ptr if they plan to Decr it. * Callers must Incr part2Ptr if they plan to Decr it. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_ObjSetVar2( Tcl_Interp *interp, /* Command interpreter in which variable is to * be found. */ Tcl_Obj *part1Ptr, /* Points to an object holding the name of an * array (if part2 is non-NULL) or the name of * a variable. */ Tcl_Obj *part2Ptr, /* If non-NULL, points to an object holding * the name of an element in the array * part1Ptr. */ Tcl_Obj *newValuePtr, /* New value for variable. */ int flags) /* Various flags that tell how to set value: * any of TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * TCL_APPEND_VALUE, TCL_LIST_ELEMENT, or * TCL_LEAVE_ERR_MSG. */ { Var *varPtr, *arrayPtr; /* * Filter to pass through only the flags this interface supports. */ flags &= (TCL_GLOBAL_ONLY|TCL_NAMESPACE_ONLY|TCL_LEAVE_ERR_MSG |TCL_APPEND_VALUE|TCL_LIST_ELEMENT); varPtr = TclObjLookupVarEx(interp, part1Ptr, part2Ptr, flags, "set", /*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr); if (varPtr == NULL) { if (newValuePtr->refCount == 0) { Tcl_DecrRefCount(newValuePtr); } return NULL; } return TclPtrSetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, newValuePtr, flags, -1); } /* *---------------------------------------------------------------------- * * TclPtrSetVar -- * * This function is the same as Tcl_SetVar2Ex above, except that it * requires pointers to the variable's Var structs in addition to the * variable names. * * Results: * Returns a pointer to the Tcl_Obj holding the new value of the * variable. If the write operation was disallowed because an array was * expected but not found (or vice versa), then NULL is returned; if the * TCL_LEAVE_ERR_MSG flag is set, then an explanatory message will be * left in the interpreter's result. Note that the returned object may * not be the same one referenced by newValuePtr; this is because * variable traces may modify the variable's value. * * Side effects: * The value of the given variable is set. If either the array or the * entry didn't exist then a new variable is created. * *---------------------------------------------------------------------- */ Tcl_Obj * TclPtrSetVar( Tcl_Interp *interp, /* Command interpreter in which variable is to * be looked up. */ Tcl_Var varPtr, /* Reference to the variable to set. */ Tcl_Var arrayPtr, /* Reference to the array containing the * variable, or NULL if the variable is a * scalar. */ Tcl_Obj *part1Ptr, /* Name of an array (if part2 is non-NULL) or * the name of a variable. */ Tcl_Obj *part2Ptr, /* If non-NULL, gives the name of an element * in the array part1. */ Tcl_Obj *newValuePtr, /* New value for variable. */ int flags) /* OR-ed combination of TCL_GLOBAL_ONLY, and * TCL_LEAVE_ERR_MSG bits. */ { if (varPtr == NULL) { Tcl_Panic("varPtr must not be NULL"); } if (part1Ptr == NULL) { Tcl_Panic("part1Ptr must not be NULL"); } if (newValuePtr == NULL) { Tcl_Panic("newValuePtr must not be NULL"); } return TclPtrSetVarIdx(interp, (Var *) varPtr, (Var *) arrayPtr, part1Ptr, part2Ptr, newValuePtr, flags, -1); } /* *---------------------------------------------------------------------- * * ListAppendInVar, StringAppendInVar -- * * Support functions for TclPtrSetVarIdx that implement various types of * appending operations. * * Results: * ListAppendInVar returns a Tcl result code (from the core list append * operation). StringAppendInVar has no return value. * * Side effects: * The variable or element of the array is updated. This may make the * variable/element exist. Reference counts of values may be updated. * *---------------------------------------------------------------------- */ static inline int ListAppendInVar( Tcl_Interp *interp, Var *varPtr, Var *arrayPtr, Tcl_Obj *oldValuePtr, Tcl_Obj *newValuePtr) { if (oldValuePtr == NULL) { /* * No previous value. Check for defaults if there's an array we can * ask this of. */ if (arrayPtr) { Tcl_Obj *defValuePtr = TclGetArrayDefault(arrayPtr); if (defValuePtr) { oldValuePtr = Tcl_DuplicateObj(defValuePtr); } } if (oldValuePtr == NULL) { /* * No default. [lappend] semantics say this is like being an empty * string. */ TclNewObj(oldValuePtr); } varPtr->value.objPtr = oldValuePtr; Tcl_IncrRefCount(oldValuePtr); /* Since var is referenced. */ } else if (Tcl_IsShared(oldValuePtr)) { varPtr->value.objPtr = Tcl_DuplicateObj(oldValuePtr); TclDecrRefCount(oldValuePtr); oldValuePtr = varPtr->value.objPtr; Tcl_IncrRefCount(oldValuePtr); /* Since var is referenced. */ } return Tcl_ListObjAppendElement(interp, oldValuePtr, newValuePtr); } static inline void StringAppendInVar( Var *varPtr, Var *arrayPtr, Tcl_Obj *oldValuePtr, Tcl_Obj *newValuePtr) { /* * If there was no previous value, either we use the array's default (if * this is an array with a default at all) or we treat this as a simple * set. */ if (oldValuePtr == NULL) { if (arrayPtr) { Tcl_Obj *defValuePtr = TclGetArrayDefault(arrayPtr); if (defValuePtr) { /* * This is *almost* the same as the shared path below, except * that the original value reference in defValuePtr is not * decremented. */ Tcl_Obj *valuePtr = Tcl_DuplicateObj(defValuePtr); varPtr->value.objPtr = valuePtr; TclContinuationsCopy(valuePtr, defValuePtr); Tcl_IncrRefCount(valuePtr); Tcl_AppendObjToObj(valuePtr, newValuePtr); if (newValuePtr->refCount == 0) { Tcl_DecrRefCount(newValuePtr); } return; } } varPtr->value.objPtr = newValuePtr; Tcl_IncrRefCount(newValuePtr); return; } /* * We append newValuePtr's bytes but don't change its ref count. Unless * the reference is shared, when we have to duplicate in order to be safe * to modify at all. */ if (Tcl_IsShared(oldValuePtr)) { /* Append to copy. */ varPtr->value.objPtr = Tcl_DuplicateObj(oldValuePtr); TclContinuationsCopy(varPtr->value.objPtr, oldValuePtr); TclDecrRefCount(oldValuePtr); oldValuePtr = varPtr->value.objPtr; Tcl_IncrRefCount(oldValuePtr); /* Since var is ref */ } Tcl_AppendObjToObj(oldValuePtr, newValuePtr); if (newValuePtr->refCount == 0) { Tcl_DecrRefCount(newValuePtr); } } /* *---------------------------------------------------------------------- * * TclPtrSetVarIdx -- * * This function is the same as Tcl_SetVar2Ex above, except that it * requires pointers to the variable's Var structs in addition to the * variable names. * * Results: * Returns a pointer to the Tcl_Obj holding the new value of the * variable. If the write operation was disallowed because an array was * expected but not found (or vice versa), then NULL is returned; if the * TCL_LEAVE_ERR_MSG flag is set, then an explanatory message will be * left in the interpreter's result. Note that the returned object may * not be the same one referenced by newValuePtr; this is because * variable traces may modify the variable's value. * * Side effects: * The value of the given variable is set. If either the array or the * entry didn't exist then a new variable is created. * *---------------------------------------------------------------------- */ Tcl_Obj * TclPtrSetVarIdx( Tcl_Interp *interp, /* Command interpreter in which variable is to * be looked up. */ Var *varPtr, /* Reference to the variable to set. */ Var *arrayPtr, /* Reference to the array containing the * variable, or NULL if the variable is a * scalar. */ Tcl_Obj *part1Ptr, /* Name of an array (if part2 is non-NULL) or * the name of a variable. NULL if the 'index' * parameter is >= 0 */ Tcl_Obj *part2Ptr, /* If non-NULL, gives the name of an element * in the array part1. */ Tcl_Obj *newValuePtr, /* New value for variable. */ int flags, /* OR-ed combination of TCL_GLOBAL_ONLY, and * TCL_LEAVE_ERR_MSG bits. */ int index) /* Index of local var where part1 is to be * found. */ { Interp *iPtr = (Interp *) interp; Tcl_Obj *oldValuePtr; Tcl_Obj *resultPtr = NULL; int result; int cleanupOnEarlyError = (newValuePtr->refCount == 0); /* * If the variable is in a hashtable and its hPtr field is NULL, then we * may have an upvar to an array element where the array was deleted or an * upvar to a namespace variable whose namespace was deleted. Generate an * error (allowing the variable to be reset would screw up our storage * allocation and is meaningless anyway). */ if (TclIsVarDeadHash(varPtr)) { if (flags & TCL_LEAVE_ERR_MSG) { if (TclIsVarArrayElement(varPtr)) { TclObjVarErrMsg(interp, part1Ptr, part2Ptr, "set", DANGLINGELEMENT, index); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "ELEMENT", (char *)NULL); } else { TclObjVarErrMsg(interp, part1Ptr, part2Ptr, "set", DANGLINGVAR, index); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "VARNAME", (char *)NULL); } } goto earlyError; } /* * It's an error to try to set a constant. */ if (TclIsVarConstant(varPtr)) { if (flags & TCL_LEAVE_ERR_MSG) { TclObjVarErrMsg(interp, part1Ptr, part2Ptr, "set", ISCONST,index); Tcl_SetErrorCode(interp, "TCL", "WRITE", "CONST", (char *)NULL); } goto earlyError; } /* * It's an error to try to set an array variable itself. */ if (TclIsVarArray(varPtr)) { if (flags & TCL_LEAVE_ERR_MSG) { TclObjVarErrMsg(interp, part1Ptr, part2Ptr, "set", ISARRAY,index); Tcl_SetErrorCode(interp, "TCL", "WRITE", "ARRAY", (char *)NULL); } goto earlyError; } TclVarFindHiddenArray(varPtr, arrayPtr); /* * Invoke any read traces that have been set for the variable if it is * requested. This was done for INST_LAPPEND_* but that was inconsistent * with the non-bc instruction, and would cause failures trying to * lappend to any non-existing ::env var, which is inconsistent with * documented behavior. [Bug #3057639]. */ if ((flags & TCL_TRACE_READS) && ((varPtr->flags & VAR_TRACED_READ) || (arrayPtr && (arrayPtr->flags & VAR_TRACED_READ)))) { if (TCL_ERROR == TclObjCallVarTraces(iPtr, arrayPtr, varPtr, part1Ptr, part2Ptr, TCL_TRACE_READS, (flags & TCL_LEAVE_ERR_MSG), index)) { goto earlyError; } } /* * Set the variable's new value. If appending, append the new value to the * variable, either as a list element or as a string. Also, if appending, * then if the variable's old value is unshared we can modify it directly, * otherwise we must create a new copy to modify: this is "copy on write". */ oldValuePtr = varPtr->value.objPtr; if (flags & TCL_LIST_ELEMENT && !(flags & TCL_APPEND_VALUE)) { varPtr->value.objPtr = NULL; } if (flags & (TCL_APPEND_VALUE|TCL_LIST_ELEMENT)) { if (flags & TCL_LIST_ELEMENT) { /* Append list element. */ result = ListAppendInVar(interp, varPtr, arrayPtr, oldValuePtr, newValuePtr); if (result != TCL_OK) { goto earlyError; } } else { /* Append string. */ StringAppendInVar(varPtr, arrayPtr, oldValuePtr, newValuePtr); } } else if (newValuePtr != oldValuePtr) { /* * In this case we are replacing the value, so we don't need to do * more than swap the objects. */ varPtr->value.objPtr = newValuePtr; Tcl_IncrRefCount(newValuePtr); /* Var is another ref. */ if (oldValuePtr != NULL) { TclDecrRefCount(oldValuePtr); /* Discard old value. */ } } /* * Invoke any write traces for the variable. */ if ((varPtr->flags & VAR_TRACED_WRITE) || (arrayPtr && (arrayPtr->flags & VAR_TRACED_WRITE))) { if (TCL_ERROR == TclObjCallVarTraces(iPtr, arrayPtr, varPtr, part1Ptr, part2Ptr, (flags & (TCL_GLOBAL_ONLY|TCL_NAMESPACE_ONLY)) | TCL_TRACE_WRITES, (flags & TCL_LEAVE_ERR_MSG), index)) { goto cleanup; } } /* * Return the variable's value unless the variable was changed in some * gross way by a trace (e.g. it was unset and then recreated as an * array). */ if (TclIsVarScalar(varPtr) && !TclIsVarUndefined(varPtr)) { return varPtr->value.objPtr; } /* * A trace changed the value in some gross way. Return an empty string * object. */ resultPtr = iPtr->emptyObjPtr; /* * If the variable doesn't exist anymore and no-one's using it, then free * up the relevant structures and hash table entries. */ cleanup: if (resultPtr == NULL) { Tcl_SetErrorCode(interp, "TCL", "WRITE", "VARNAME", (char *)NULL); } if (TclIsVarUndefined(varPtr)) { TclCleanupVar(varPtr, arrayPtr); } return resultPtr; earlyError: if (cleanupOnEarlyError) { Tcl_DecrRefCount(newValuePtr); } goto cleanup; } /* *---------------------------------------------------------------------- * * TclIncrObjVar2 -- * * Given a two-part variable name, which may refer either to a scalar * variable or an element of an array, increment the Tcl object value of * the variable by a specified Tcl_Obj increment value. * * Results: * Returns a pointer to the Tcl_Obj holding the new value of the * variable. If the specified variable doesn't exist, or there is a clash * in array usage, or an error occurs while executing variable traces, * then NULL is returned and a message will be left in the interpreter's * result. * * Side effects: * The value of the given variable is incremented by the specified * amount. If either the array or the entry didn't exist then a new * variable is created. The ref count for the returned object is _not_ * incremented to reflect the returned reference; if you want to keep a * reference to the object you must increment its ref count yourself. * Callers must Incr part1Ptr if they plan to Decr it. * Callers must Incr part2Ptr if they plan to Decr it. * *---------------------------------------------------------------------- */ Tcl_Obj * TclIncrObjVar2( Tcl_Interp *interp, /* Command interpreter in which variable is to * be found. */ Tcl_Obj *part1Ptr, /* Points to an object holding the name of an * array (if part2 is non-NULL) or the name of * a variable. */ Tcl_Obj *part2Ptr, /* If non-null, points to an object holding * the name of an element in the array * part1Ptr. */ Tcl_Obj *incrPtr, /* Amount to be added to variable. */ int flags) /* Various flags that tell how to incr value: * any of TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * TCL_APPEND_VALUE, TCL_LIST_ELEMENT, * TCL_LEAVE_ERR_MSG. */ { Var *varPtr, *arrayPtr; varPtr = TclObjLookupVarEx(interp, part1Ptr, part2Ptr, flags, "read", 1, 1, &arrayPtr); if (varPtr == NULL) { Tcl_AddErrorInfo(interp, "\n (reading value of variable to increment)"); return NULL; } return TclPtrIncrObjVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, incrPtr, flags, -1); } /* *---------------------------------------------------------------------- * * TclPtrIncrObjVar -- * * Given the pointers to a variable and possible containing array, * increment the Tcl object value of the variable by a Tcl_Obj increment. * * Results: * Returns a pointer to the Tcl_Obj holding the new value of the * variable. If the specified variable doesn't exist, or there is a clash * in array usage, or an error occurs while executing variable traces, * then NULL is returned and a message will be left in the interpreter's * result. * * Side effects: * The value of the given variable is incremented by the specified * amount. If either the array or the entry didn't exist then a new * variable is created. The ref count for the returned object is _not_ * incremented to reflect the returned reference; if you want to keep a * reference to the object you must increment its ref count yourself. * *---------------------------------------------------------------------- */ Tcl_Obj * TclPtrIncrObjVar( Tcl_Interp *interp, /* Command interpreter in which variable is to * be found. */ Tcl_Var varPtr, /* Reference to the variable to set. */ Tcl_Var arrayPtr, /* Reference to the array containing the * variable, or NULL if the variable is a * scalar. */ Tcl_Obj *part1Ptr, /* Points to an object holding the name of an * array (if part2 is non-NULL) or the name of * a variable. */ Tcl_Obj *part2Ptr, /* If non-null, points to an object holding * the name of an element in the array * part1Ptr. */ Tcl_Obj *incrPtr, /* Increment value. */ /* TODO: Which of these flag values really make sense? */ int flags) /* Various flags that tell how to incr value: * any of TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * TCL_APPEND_VALUE, TCL_LIST_ELEMENT, * TCL_LEAVE_ERR_MSG. */ { if (varPtr == NULL) { Tcl_Panic("varPtr must not be NULL"); } if (part1Ptr == NULL) { Tcl_Panic("part1Ptr must not be NULL"); } return TclPtrIncrObjVarIdx(interp, (Var *) varPtr, (Var *) arrayPtr, part1Ptr, part2Ptr, incrPtr, flags, -1); } /* *---------------------------------------------------------------------- * * TclPtrIncrObjVarIdx -- * * Given the pointers to a variable and possible containing array, * increment the Tcl object value of the variable by a Tcl_Obj increment. * * Results: * Returns a pointer to the Tcl_Obj holding the new value of the * variable. If the specified variable doesn't exist, or there is a clash * in array usage, or an error occurs while executing variable traces, * then NULL is returned and a message will be left in the interpreter's * result. * * Side effects: * The value of the given variable is incremented by the specified * amount. If either the array or the entry didn't exist then a new * variable is created. The ref count for the returned object is _not_ * incremented to reflect the returned reference; if you want to keep a * reference to the object you must increment its ref count yourself. * *---------------------------------------------------------------------- */ Tcl_Obj * TclPtrIncrObjVarIdx( Tcl_Interp *interp, /* Command interpreter in which variable is to * be found. */ Var *varPtr, /* Reference to the variable to set. */ Var *arrayPtr, /* Reference to the array containing the * variable, or NULL if the variable is a * scalar. */ Tcl_Obj *part1Ptr, /* Points to an object holding the name of an * array (if part2 is non-NULL) or the name of * a variable. */ Tcl_Obj *part2Ptr, /* If non-null, points to an object holding * the name of an element in the array * part1Ptr. */ Tcl_Obj *incrPtr, /* Increment value. */ /* TODO: Which of these flag values really make sense? */ int flags, /* Various flags that tell how to incr value: * any of TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * TCL_APPEND_VALUE, TCL_LIST_ELEMENT, * TCL_LEAVE_ERR_MSG. */ int index) /* Index into the local variable table of the * variable, or -1. Only used when part1Ptr is * NULL. */ { Tcl_Obj *varValuePtr; /* * It's an error to try to increment a constant. */ if (TclIsVarConstant(varPtr)) { if (flags & TCL_LEAVE_ERR_MSG) { TclObjVarErrMsg(interp, part1Ptr, part2Ptr, "incr", ISCONST,index); Tcl_SetErrorCode(interp, "TCL", "WRITE", "CONST", (char *)NULL); } return NULL; } if (TclIsVarInHash(varPtr)) { VarHashRefCount(varPtr)++; } varValuePtr = TclPtrGetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, flags, index); if (TclIsVarInHash(varPtr)) { VarHashRefCount(varPtr)--; } if (varValuePtr == NULL) { TclNewIntObj(varValuePtr, 0); } if (Tcl_IsShared(varValuePtr)) { /* Copy on write */ varValuePtr = Tcl_DuplicateObj(varValuePtr); if (TCL_OK == TclIncrObj(interp, varValuePtr, incrPtr)) { return TclPtrSetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, varValuePtr, flags, index); } else { Tcl_DecrRefCount(varValuePtr); return NULL; } } else { /* Unshared - can Incr in place */ if (TCL_OK == TclIncrObj(interp, varValuePtr, incrPtr)) { /* * This seems dumb to write the incremeted value into the var * after we just adjusted the value in place, but the spec for * [incr] requires that write traces fire, and making this call * is the way to make that happen. */ return TclPtrSetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, varValuePtr, flags, index); } else { return NULL; } } } /* *---------------------------------------------------------------------- * * Tcl_UnsetVar2 -- * * Delete a variable, given a 2-part name. * * Results: * Returns TCL_OK if the variable was successfully deleted, TCL_ERROR if * the variable can't be unset. In the event of an error, if the * TCL_LEAVE_ERR_MSG flag is set then an error message is left in the * interp's result. * * Side effects: * If part1 and part2 indicate a local or global variable in interp, it * is deleted. If part1 is an array name and part2 is NULL, then the * whole array is deleted. * *---------------------------------------------------------------------- */ int Tcl_UnsetVar2( Tcl_Interp *interp, /* Command interpreter in which varName is to * be looked up. */ const char *part1, /* Name of variable or array. */ const char *part2, /* Name of element within array or NULL. */ int flags) /* OR-ed combination of any of * TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * TCL_LEAVE_ERR_MSG. */ { int result; Tcl_Obj *part2Ptr = NULL, *part1Ptr = Tcl_NewStringObj(part1, -1); if (part2) { part2Ptr = Tcl_NewStringObj(part2, -1); } /* * Filter to pass through only the flags this interface supports. */ flags &= (TCL_GLOBAL_ONLY|TCL_NAMESPACE_ONLY|TCL_LEAVE_ERR_MSG); result = TclObjUnsetVar2(interp, part1Ptr, part2Ptr, flags); Tcl_DecrRefCount(part1Ptr); if (part2Ptr) { Tcl_DecrRefCount(part2Ptr); } return result; } /* *---------------------------------------------------------------------- * * TclObjUnsetVar2 -- * * Delete a variable, given a 2-object name. * * Results: * Returns TCL_OK if the variable was successfully deleted, TCL_ERROR if * the variable can't be unset. In the event of an error, if the * TCL_LEAVE_ERR_MSG flag is set then an error message is left in the * interp's result. * * Side effects: * If part1ptr and part2Ptr indicate a local or global variable in * interp, it is deleted. If part1Ptr is an array name and part2Ptr is * NULL, then the whole array is deleted. * *---------------------------------------------------------------------- */ int TclObjUnsetVar2( Tcl_Interp *interp, /* Command interpreter in which varName is to * be looked up. */ Tcl_Obj *part1Ptr, /* Name of variable or array. */ Tcl_Obj *part2Ptr, /* Name of element within array or NULL. */ int flags) /* OR-ed combination of any of * TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * TCL_LEAVE_ERR_MSG. */ { Var *varPtr, *arrayPtr; varPtr = TclObjLookupVarEx(interp, part1Ptr, part2Ptr, flags, "unset", /*createPart1*/ 0, /*createPart2*/ 0, &arrayPtr); if (varPtr == NULL) { return TCL_ERROR; } return TclPtrUnsetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr, flags, -1); } /* *---------------------------------------------------------------------- * * TclPtrUnsetVar -- * * Delete a variable, given the pointers to the variable's (and possibly * containing array's) VAR structure. * * Results: * Returns TCL_OK if the variable was successfully deleted, TCL_ERROR if * the variable can't be unset. In the event of an error, if the * TCL_LEAVE_ERR_MSG flag is set then an error message is left in the * interp's result. * * Side effects: * If varPtr and arrayPtr indicate a local or global variable in interp, * it is deleted. If varPtr is an array reference and part2Ptr is NULL, * then the whole array is deleted. * *---------------------------------------------------------------------- */ int TclPtrUnsetVar( Tcl_Interp *interp, /* Command interpreter in which varName is to * be looked up. */ Tcl_Var varPtr, /* The variable to be unset. */ Tcl_Var arrayPtr, /* NULL for scalar variables, pointer to the * containing array otherwise. */ Tcl_Obj *part1Ptr, /* Name of an array (if part2 is non-NULL) or * the name of a variable. */ Tcl_Obj *part2Ptr, /* If non-NULL, gives the name of an element * in the array part1. */ int flags) /* OR-ed combination of any of * TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * TCL_LEAVE_ERR_MSG. */ { if (varPtr == NULL) { Tcl_Panic("varPtr must not be NULL"); } if (part1Ptr == NULL) { Tcl_Panic("part1Ptr must not be NULL"); } return TclPtrUnsetVarIdx(interp, (Var *) varPtr, (Var *) arrayPtr, part1Ptr, part2Ptr, flags, -1); } /* *---------------------------------------------------------------------- * * TclPtrUnsetVarIdx -- * * Delete a variable, given the pointers to the variable's (and possibly * containing array's) VAR structure. * * Results: * Returns TCL_OK if the variable was successfully deleted, TCL_ERROR if * the variable can't be unset. In the event of an error, if the * TCL_LEAVE_ERR_MSG flag is set then an error message is left in the * interp's result. * * Side effects: * If varPtr and arrayPtr indicate a local or global variable in interp, * it is deleted. If varPtr is an array reference and part2Ptr is NULL, * then the whole array is deleted. * *---------------------------------------------------------------------- */ int TclPtrUnsetVarIdx( Tcl_Interp *interp, /* Command interpreter in which varName is to * be looked up. */ Var *varPtr, /* The variable to be unset. */ Var *arrayPtr, /* NULL for scalar variables, pointer to the * containing array otherwise. */ Tcl_Obj *part1Ptr, /* Name of an array (if part2 is non-NULL) or * the name of a variable. */ Tcl_Obj *part2Ptr, /* If non-NULL, gives the name of an element * in the array part1. */ int flags, /* OR-ed combination of any of * TCL_GLOBAL_ONLY, TCL_NAMESPACE_ONLY, * TCL_LEAVE_ERR_MSG. */ int index) /* Index into the local variable table of the * variable, or -1. Only used when part1Ptr is * NULL. */ { Interp *iPtr = (Interp *) interp; int result = (TclIsVarUndefined(varPtr)? TCL_ERROR : TCL_OK); Var *initialArrayPtr = arrayPtr; /* * It's an error to try to unset a constant. */ if (TclIsVarConstant(varPtr)) { if (flags & TCL_LEAVE_ERR_MSG) { TclObjVarErrMsg(interp, part1Ptr, part2Ptr, "unset", ISCONST,index); Tcl_SetErrorCode(interp, "TCL", "UNSET", "CONST", (char *)NULL); } return TCL_ERROR; } /* * Keep the variable alive until we're done with it. We used to * increase/decrease the refCount for each operation, making it hard to * find [Bug 735335] - caused by unsetting the variable whose value was * the variable's name. */ if (TclIsVarInHash(varPtr)) { VarHashRefCount(varPtr)++; } TclVarFindHiddenArray(varPtr, arrayPtr); UnsetVarStruct(varPtr, arrayPtr, iPtr, part1Ptr, part2Ptr, flags, index); /* * It's an error to unset an undefined variable. */ if (result != TCL_OK) { if (flags & TCL_LEAVE_ERR_MSG) { TclObjVarErrMsg(interp, part1Ptr, part2Ptr, "unset", ((initialArrayPtr == NULL) ? NOSUCHVAR : NOSUCHELEMENT), index); Tcl_SetErrorCode(interp, "TCL", "UNSET", "VARNAME", (char *)NULL); } } /* * Finally, if the variable is truly not in use then free up its Var * structure and remove it from its hash table, if any. The ref count of * its value object, if any, was decremented above. */ if (TclIsVarInHash(varPtr)) { VarHashRefCount(varPtr)--; CleanupVar(varPtr, arrayPtr); } return result; } /* *---------------------------------------------------------------------- * * UnsetVarStruct -- * * Unset and delete a variable. This does the internal work for * TclObjUnsetVar2 and TclDeleteNamespaceVars, which call here for each * variable to be unset and deleted. * * Results: * None. * * Side effects: * If the arguments indicate a local or global variable in iPtr, it is * unset and deleted. * *---------------------------------------------------------------------- */ static void UnsetVarStruct( Var *varPtr, Var *arrayPtr, Interp *iPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags, int index) { Var dummyVar; int traced = TclIsVarTraced(varPtr) || (arrayPtr && (arrayPtr->flags & VAR_TRACED_UNSET)); if (arrayPtr && (arrayPtr->flags & VAR_SEARCH_ACTIVE)) { DeleteSearches(iPtr, arrayPtr); } else if (varPtr->flags & VAR_SEARCH_ACTIVE) { DeleteSearches(iPtr, varPtr); } /* * The code below is tricky, because of the possibility that a trace * function might try to access a variable being deleted. To handle this * situation gracefully, do things in three steps: * 1. Copy the contents of the variable to a dummy variable structure, and * mark the original Var structure as undefined. * 2. Invoke traces and clean up the variable, using the dummy copy. * 3. If at the end of this the original variable is still undefined and * has no outstanding references, then delete it (but it could have * gotten recreated by a trace). */ dummyVar = *varPtr; dummyVar.flags &= ~VAR_ALL_HASH; TclSetVarUndefined(varPtr); /* * Call trace functions for the variable being deleted. Then delete its * traces. Be sure to abort any other traces for the variable that are * still pending. Special tricks: * 1. We need to increment varPtr's refCount around this: TclCallVarTraces * will use dummyVar so it won't increment varPtr's refCount itself. * 2. Turn off the VAR_TRACE_ACTIVE flag in dummyVar: we want to call * unset traces even if other traces are pending. */ if (traced) { VarTrace *tracePtr = NULL; Tcl_HashEntry *tPtr; if (TclIsVarTraced(&dummyVar)) { /* * Transfer any existing traces on var, IF there are unset traces. * Otherwise just delete them. */ int isNew; tPtr = Tcl_FindHashEntry(&iPtr->varTraces, varPtr); tracePtr = (VarTrace *)Tcl_GetHashValue(tPtr); varPtr->flags &= ~VAR_ALL_TRACES; Tcl_DeleteHashEntry(tPtr); if (dummyVar.flags & VAR_TRACED_UNSET) { tPtr = Tcl_CreateHashEntry(&iPtr->varTraces, &dummyVar, &isNew); Tcl_SetHashValue(tPtr, tracePtr); } } if ((dummyVar.flags & VAR_TRACED_UNSET) || (arrayPtr && (arrayPtr->flags & VAR_TRACED_UNSET))) { /* * Pass the array element name to TclObjCallVarTraces(), because * it cannot be determined from dummyVar. Alternatively, indicate * via flags whether the variable involved in the code that caused * the trace to be triggered was an array element, for the correct * formatting of error messages. */ if (part2Ptr) { flags |= VAR_ARRAY_ELEMENT; } else if (TclIsVarArrayElement(varPtr)) { part2Ptr = VarHashGetKey(varPtr); } dummyVar.flags &= ~VAR_TRACE_ACTIVE; TclObjCallVarTraces(iPtr, arrayPtr, &dummyVar, part1Ptr, part2Ptr, (flags & (TCL_GLOBAL_ONLY|TCL_NAMESPACE_ONLY|VAR_ARRAY_ELEMENT)) | TCL_TRACE_UNSETS, /* leaveErrMsg */ 0, index); /* * The traces that we just called may have triggered a change in * the set of traces. If so, reload the traces to manipulate. */ tracePtr = NULL; if (TclIsVarTraced(&dummyVar)) { tPtr = Tcl_FindHashEntry(&iPtr->varTraces, &dummyVar); if (tPtr) { tracePtr = (VarTrace *)Tcl_GetHashValue(tPtr); Tcl_DeleteHashEntry(tPtr); } } } if (tracePtr) { ActiveVarTrace *activePtr; while (tracePtr) { VarTrace *prevPtr = tracePtr; tracePtr = tracePtr->nextPtr; prevPtr->nextPtr = NULL; Tcl_EventuallyFree(prevPtr, TCL_DYNAMIC); } for (activePtr = iPtr->activeVarTracePtr; activePtr != NULL; activePtr = activePtr->nextPtr) { if (activePtr->varPtr == varPtr) { activePtr->nextTracePtr = NULL; } } dummyVar.flags &= ~VAR_ALL_TRACES; } } if (TclIsVarScalar(&dummyVar) && (dummyVar.value.objPtr != NULL)) { /* * Decrement the ref count of the var's value. */ Tcl_Obj *objPtr = dummyVar.value.objPtr; TclDecrRefCount(objPtr); } else if (TclIsVarArray(&dummyVar)) { /* * If the variable is an array, delete all of its elements. This must * be done after calling and deleting the traces on the array, above * (that's the way traces are defined). If the array name is not * present and is required for a trace on some element, it will be * computed at DeleteArray. */ DeleteArray(iPtr, part1Ptr, (Var *) &dummyVar, (flags & (TCL_GLOBAL_ONLY|TCL_NAMESPACE_ONLY)) | TCL_TRACE_UNSETS, index); } else if (TclIsVarLink(&dummyVar)) { /* * For global/upvar variables referenced in procedures, decrement the * reference count on the variable referred to, and free the * referenced variable if it's no longer needed. */ Var *linkPtr = dummyVar.value.linkPtr; if (TclIsVarInHash(linkPtr)) { VarHashRefCount(linkPtr)--; CleanupVar(linkPtr, NULL); } } /* * If the variable was a namespace variable, decrement its reference * count. */ TclClearVarNamespaceVar(varPtr); } /* *---------------------------------------------------------------------- * * Tcl_UnsetObjCmd -- * * This object-based function is invoked to process the "unset" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result value. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_UnsetObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int i, flags = TCL_LEAVE_ERR_MSG; const char *name; if (objc == 1) { /* * Do nothing if no arguments supplied, so as to match command * documentation. */ return TCL_OK; } /* * Simple, restrictive argument parsing. The only options are -- and * -nocomplain (which must come first and be given exactly to be an * option). */ i = 1; name = TclGetString(objv[i]); if (name[0] == '-') { if (strcmp("-nocomplain", name) == 0) { i++; if (i == objc) { return TCL_OK; } flags = 0; name = TclGetString(objv[i]); } if (strcmp("--", name) == 0) { i++; } } for (; i < objc; i++) { if ((TclObjUnsetVar2(interp, objv[i], NULL, flags) != TCL_OK) && (flags == TCL_LEAVE_ERR_MSG)) { return TCL_ERROR; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_AppendObjCmd -- * * This object-based function is invoked to process the "append" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result value. * * Side effects: * A variable's value may be changed. * *---------------------------------------------------------------------- */ int Tcl_AppendObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Var *varPtr, *arrayPtr; Tcl_Obj *varValuePtr = NULL; /* Initialized to avoid compiler warning. */ int i; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "varName ?value ...?"); return TCL_ERROR; } if (objc == 2) { varValuePtr = Tcl_ObjGetVar2(interp, objv[1], NULL,TCL_LEAVE_ERR_MSG); if (varValuePtr == NULL) { return TCL_ERROR; } } else { varPtr = TclObjLookupVarEx(interp, objv[1], NULL, TCL_LEAVE_ERR_MSG, "set", /*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr); if (varPtr == NULL) { return TCL_ERROR; } for (i=2 ; iemptyObjPtr)) { return TCL_ERROR; } } } Tcl_SetObjResult(interp, varValuePtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_LappendObjCmd -- * * This object-based function is invoked to process the "lappend" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result value. * * Side effects: * A variable's value may be changed. * *---------------------------------------------------------------------- */ int Tcl_LappendObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *varValuePtr, *newValuePtr; Tcl_Size numElems; Var *varPtr, *arrayPtr; int result, createdNewObj; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "varName ?value ...?"); return TCL_ERROR; } if (objc == 2) { newValuePtr = Tcl_ObjGetVar2(interp, objv[1], NULL, 0); if (newValuePtr == NULL) { /* * The variable doesn't exist yet. Just create it with an empty * initial value. */ TclNewObj(varValuePtr); newValuePtr = Tcl_ObjSetVar2(interp, objv[1], NULL, varValuePtr, TCL_LEAVE_ERR_MSG); if (newValuePtr == NULL) { return TCL_ERROR; } } else { result = TclListObjLength(interp, newValuePtr, &numElems); if (result != TCL_OK) { return result; } } } else { /* * We have arguments to append. We used to call Tcl_SetVar2 to append * each argument one at a time to ensure that traces were run for each * append step. We now append the arguments all at once because it's * faster. Note that a read trace and a write trace for the variable * will now each only be called once. Also, if the variable's old * value is unshared we modify it directly, otherwise we create a new * copy to modify: this is "copy on write". */ createdNewObj = 0; /* * Protect the variable pointers around the TclPtrGetVarIdx call * to insure that they remain valid even if the variable was undefined * and unused. */ varPtr = TclObjLookupVarEx(interp, objv[1], NULL, TCL_LEAVE_ERR_MSG, "set", /*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr); if (varPtr == NULL) { return TCL_ERROR; } if (TclIsVarInHash(varPtr)) { VarHashRefCount(varPtr)++; } if (arrayPtr && TclIsVarInHash(arrayPtr)) { VarHashRefCount(arrayPtr)++; } varValuePtr = TclPtrGetVarIdx(interp, varPtr, arrayPtr, objv[1], NULL, TCL_LEAVE_ERR_MSG, -1); if (TclIsVarInHash(varPtr)) { VarHashRefCount(varPtr)--; } if (arrayPtr && TclIsVarInHash(arrayPtr)) { VarHashRefCount(arrayPtr)--; } if (varValuePtr == NULL) { /* * We couldn't read the old value: either the var doesn't yet * exist or it's an array element. If it's new, we will try to * create it with Tcl_ObjSetVar2 below. */ TclNewObj(varValuePtr); createdNewObj = 1; } else if (Tcl_IsShared(varValuePtr)) { varValuePtr = Tcl_DuplicateObj(varValuePtr); createdNewObj = 1; } result = TclListObjLength(interp, varValuePtr, &numElems); if (result == TCL_OK) { result = Tcl_ListObjReplace(interp, varValuePtr, numElems, 0, (objc-2), (objv+2)); } if (result != TCL_OK) { if (createdNewObj) { TclDecrRefCount(varValuePtr); /* Free unneeded obj. */ } return result; } /* * Now store the list object back into the variable. If there is an * error setting the new value, decrement its ref count if it was new * and we didn't create the variable. */ newValuePtr = TclPtrSetVarIdx(interp, varPtr, arrayPtr, objv[1], NULL, varValuePtr, TCL_LEAVE_ERR_MSG, -1); if (newValuePtr == NULL) { return TCL_ERROR; } } /* * Set the interpreter's object result to refer to the variable's value * object. */ Tcl_SetObjResult(interp, newValuePtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * ArrayForObjCmd, ArrayForNRCmd, ArrayForLoopCallback, ArrayObjNext -- * * These functions implement the "array for" Tcl command. * array for {k v} a {} * The array for command iterates over the array, setting the * specified loop variables, and executing the body each iteration. * * ArrayForObjCmd() is the standard wrapper around ArrayForNRCmd(). * * ArrayForNRCmd() sets up the ArraySearch structure, sets arrayNamePtr * inside the structure and calls VarHashFirstEntry to start the hash * iteration. * * ArrayForNRCmd() does not execute the body or set the loop variables, * it only initializes the iterator. * * ArrayForLoopCallback() iterates over the entire array, executing the * body each time. * *---------------------------------------------------------------------- */ static int ArrayObjNext( Tcl_Interp *interp, Tcl_Obj *arrayNameObj, /* array */ Var *varPtr, /* array */ ArraySearch *searchPtr, Tcl_Obj **keyPtrPtr, /* Pointer to a variable to have the key * written into, or NULL. */ Tcl_Obj **valuePtrPtr) /* Pointer to a variable to have the * value written into, or NULL.*/ { Tcl_Obj *keyObj; Tcl_Obj *valueObj = NULL; int gotValue; int donerc; donerc = TCL_BREAK; if ((varPtr->flags & VAR_SEARCH_ACTIVE) != VAR_SEARCH_ACTIVE) { donerc = TCL_ERROR; return donerc; } gotValue = 0; while (1) { Tcl_HashEntry *hPtr = searchPtr->nextEntry; if (hPtr != NULL) { searchPtr->nextEntry = NULL; } else { hPtr = Tcl_NextHashEntry(&searchPtr->search); if (hPtr == NULL) { gotValue = 0; break; } } varPtr = VarHashGetValue(hPtr); if (!TclIsVarUndefined(varPtr)) { gotValue = 1; break; } } if (!gotValue) { return donerc; } donerc = TCL_CONTINUE; keyObj = VarHashGetKey(varPtr); *keyPtrPtr = keyObj; valueObj = Tcl_ObjGetVar2(interp, arrayNameObj, keyObj, TCL_LEAVE_ERR_MSG); *valuePtrPtr = valueObj; return donerc; } static int ArrayForObjCmd( void *clientData, Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { return Tcl_NRCallObjProc(interp, ArrayForNRCmd, clientData, objc, objv); } static int ArrayForNRCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) { Tcl_Obj *varListObj, *arrayNameObj, *scriptObj; ArraySearch *searchPtr = NULL; Var *varPtr; int isArray; Tcl_Size numVars; /* * array for {k v} a body */ if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "{key value} arrayName script"); return TCL_ERROR; } /* * Parse arguments. */ if (TclListObjLength(interp, objv[1], &numVars) != TCL_OK) { return TCL_ERROR; } if (numVars != 2) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "must have two variable names", -1)); Tcl_SetErrorCode(interp, "TCL", "SYNTAX", "array", "for", (char *)NULL); return TCL_ERROR; } arrayNameObj = objv[2]; if (TCL_ERROR == LocateArray(interp, arrayNameObj, &varPtr, &isArray)) { return TCL_ERROR; } if (!isArray) { return NotArrayError(interp, arrayNameObj); } /* * Make a new array search, put it on the stack. */ searchPtr = (ArraySearch *)Tcl_Alloc(sizeof(ArraySearch)); ArrayPopulateSearch(interp, arrayNameObj, varPtr, searchPtr); /* * Make sure that these objects (which we need throughout the body of the * loop) don't vanish. */ varListObj = TclListObjCopy(NULL, objv[1]); if (!varListObj) { return TCL_ERROR; } scriptObj = objv[3]; Tcl_IncrRefCount(scriptObj); /* * Run the script. */ TclNRAddCallback(interp, ArrayForLoopCallback, searchPtr, varListObj, arrayNameObj, scriptObj); return TCL_OK; } static int ArrayForLoopCallback( void *data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; ArraySearch *searchPtr = (ArraySearch *)data[0]; Tcl_Obj *varListObj = (Tcl_Obj *)data[1]; Tcl_Obj *arrayNameObj = (Tcl_Obj *)data[2]; Tcl_Obj *scriptObj = (Tcl_Obj *)data[3]; Tcl_Obj **varv; Tcl_Obj *keyObj, *valueObj; Var *varPtr; Var *arrayPtr; int done; Tcl_Size varc; /* * Process the result from the previous execution of the script body. */ done = TCL_ERROR; if (result == TCL_CONTINUE) { result = TCL_OK; } else if (result != TCL_OK) { if (result == TCL_BREAK) { Tcl_ResetResult(interp); result = TCL_OK; } else if (result == TCL_ERROR) { Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (\"array for\" body line %d)", Tcl_GetErrorLine(interp))); } goto arrayfordone; } /* * Get the next mapping from the array. */ keyObj = NULL; valueObj = NULL; varPtr = TclObjLookupVarEx(interp, arrayNameObj, NULL, /*flags*/ 0, /*msg*/ 0, /*createPart1*/ 0, /*createPart2*/ 0, &arrayPtr); if (varPtr == NULL) { done = TCL_ERROR; } else { done = ArrayObjNext(interp, arrayNameObj, varPtr, searchPtr, &keyObj, &valueObj); } result = TCL_OK; if (done != TCL_CONTINUE) { Tcl_ResetResult(interp); if (done == TCL_ERROR) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "array changed during iteration", -1)); Tcl_SetErrorCode(interp, "TCL", "READ", "array", "for", (char *)NULL); varPtr->flags |= TCL_LEAVE_ERR_MSG; result = done; } goto arrayfordone; } result = TclListObjGetElements(NULL, varListObj, &varc, &varv); if (result != TCL_OK) { goto arrayfordone; } if (Tcl_ObjSetVar2(interp, varv[0], NULL, keyObj, TCL_LEAVE_ERR_MSG) == NULL) { result = TCL_ERROR; goto arrayfordone; } if (valueObj != NULL) { if (Tcl_ObjSetVar2(interp, varv[1], NULL, valueObj, TCL_LEAVE_ERR_MSG) == NULL) { result = TCL_ERROR; goto arrayfordone; } } /* * Run the script. */ TclNRAddCallback(interp, ArrayForLoopCallback, searchPtr, varListObj, arrayNameObj, scriptObj); return TclNREvalObjEx(interp, scriptObj, 0, iPtr->cmdFramePtr, 3); /* * For unwinding everything once the iterating is done. */ arrayfordone: if (done != TCL_ERROR) { /* * If the search was terminated by an array change, the * VAR_SEARCH_ACTIVE flag will no longer be set. */ ArrayDoneSearch(iPtr, varPtr, searchPtr); Tcl_DecrRefCount(searchPtr->name); Tcl_Free(searchPtr); } TclDecrRefCount(varListObj); TclDecrRefCount(scriptObj); return result; } /* * ArrayPopulateSearch */ static void ArrayPopulateSearch( Tcl_Interp *interp, Tcl_Obj *arrayNameObj, Var *varPtr, ArraySearch *searchPtr) { Interp *iPtr = (Interp *) interp; Tcl_HashEntry *hPtr; int isNew; hPtr = Tcl_CreateHashEntry(&iPtr->varSearches, varPtr, &isNew); if (isNew) { searchPtr->id = 1; varPtr->flags |= VAR_SEARCH_ACTIVE; searchPtr->nextPtr = NULL; } else { searchPtr->id = ((ArraySearch *) Tcl_GetHashValue(hPtr))->id + 1; searchPtr->nextPtr = (ArraySearch *)Tcl_GetHashValue(hPtr); } searchPtr->varPtr = varPtr; searchPtr->nextEntry = VarHashFirstEntry(varPtr->value.tablePtr, &searchPtr->search); Tcl_SetHashValue(hPtr, searchPtr); searchPtr->name = Tcl_ObjPrintf("s-%d-%s", searchPtr->id, TclGetString(arrayNameObj)); Tcl_IncrRefCount(searchPtr->name); } /* *---------------------------------------------------------------------- * * ArrayStartSearchCmd -- * * This object-based function is invoked to process the "array * startsearch" Tcl command. See the user documentation for details on * what it does. * * Results: * A standard Tcl result object. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int ArrayStartSearchCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Var *varPtr; int isArray; ArraySearch *searchPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "arrayName"); return TCL_ERROR; } if (TCL_ERROR == LocateArray(interp, objv[1], &varPtr, &isArray)) { return TCL_ERROR; } if (!isArray) { return NotArrayError(interp, objv[1]); } /* * Make a new array search with a free name. */ searchPtr = (ArraySearch *)Tcl_Alloc(sizeof(ArraySearch)); ArrayPopulateSearch(interp, objv[1], varPtr, searchPtr); Tcl_SetObjResult(interp, searchPtr->name); return TCL_OK; } /* *---------------------------------------------------------------------- * * ArrayDoneSearch -- * * Removes the search from the hash of active searches. * *---------------------------------------------------------------------- */ static void ArrayDoneSearch( Interp *iPtr, Var *varPtr, ArraySearch *searchPtr) { Tcl_HashEntry *hPtr; ArraySearch *prevPtr; /* * Unhook the search from the list of searches associated with the * variable. */ hPtr = Tcl_FindHashEntry(&iPtr->varSearches, varPtr); if (hPtr == NULL) { return; } if (searchPtr == Tcl_GetHashValue(hPtr)) { if (searchPtr->nextPtr) { Tcl_SetHashValue(hPtr, searchPtr->nextPtr); } else { varPtr->flags &= ~VAR_SEARCH_ACTIVE; Tcl_DeleteHashEntry(hPtr); } } else { for (prevPtr = (ArraySearch *)Tcl_GetHashValue(hPtr); ; prevPtr=prevPtr->nextPtr) { if (prevPtr->nextPtr == searchPtr) { prevPtr->nextPtr = searchPtr->nextPtr; break; } } } } /* *---------------------------------------------------------------------- * * ArrayAnyMoreCmd -- * * This object-based function is invoked to process the "array anymore" * Tcl command. See the user documentation for details on what it does. * * Results: * A standard Tcl result object. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int ArrayAnyMoreCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Interp *iPtr = (Interp *) interp; Var *varPtr; Tcl_Obj *varNameObj, *searchObj; int gotValue, isArray; ArraySearch *searchPtr; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "arrayName searchId"); return TCL_ERROR; } varNameObj = objv[1]; searchObj = objv[2]; if (TCL_ERROR == LocateArray(interp, varNameObj, &varPtr, &isArray)) { return TCL_ERROR; } if (!isArray) { return NotArrayError(interp, varNameObj); } /* * Get the search. */ searchPtr = ParseSearchId(interp, varPtr, varNameObj, searchObj); if (searchPtr == NULL) { return TCL_ERROR; } /* * Scan forward to find if there are any further elements in the array * that are defined. */ while (1) { if (searchPtr->nextEntry != NULL) { varPtr = VarHashGetValue(searchPtr->nextEntry); if (!TclIsVarUndefined(varPtr)) { gotValue = 1; break; } } searchPtr->nextEntry = Tcl_NextHashEntry(&searchPtr->search); if (searchPtr->nextEntry == NULL) { gotValue = 0; break; } } Tcl_SetObjResult(interp, iPtr->execEnvPtr->constants[gotValue]); return TCL_OK; } /* *---------------------------------------------------------------------- * * ArrayNextElementCmd -- * * This object-based function is invoked to process the "array * nextelement" Tcl command. See the user documentation for details on * what it does. * * Results: * A standard Tcl result object. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int ArrayNextElementCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Var *varPtr; Tcl_Obj *varNameObj, *searchObj; ArraySearch *searchPtr; int isArray; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "arrayName searchId"); return TCL_ERROR; } varNameObj = objv[1]; searchObj = objv[2]; if (TCL_ERROR == LocateArray(interp, varNameObj, &varPtr, &isArray)) { return TCL_ERROR; } if (!isArray) { return NotArrayError(interp, varNameObj); } /* * Get the search. */ searchPtr = ParseSearchId(interp, varPtr, varNameObj, searchObj); if (searchPtr == NULL) { return TCL_ERROR; } /* * Get the next element from the search, or the empty string on * exhaustion. Note that the [array anymore] command may well have already * pulled a value from the hash enumeration, so we have to check the cache * there first. */ while (1) { Tcl_HashEntry *hPtr = searchPtr->nextEntry; if (hPtr == NULL) { hPtr = Tcl_NextHashEntry(&searchPtr->search); if (hPtr == NULL) { return TCL_OK; } } else { searchPtr->nextEntry = NULL; } varPtr = VarHashGetValue(hPtr); if (!TclIsVarUndefined(varPtr)) { Tcl_SetObjResult(interp, VarHashGetKey(varPtr)); return TCL_OK; } } } /* *---------------------------------------------------------------------- * * ArrayDoneSearchCmd -- * * This object-based function is invoked to process the "array * donesearch" Tcl command. See the user documentation for details on * what it does. * * Results: * A standard Tcl result object. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int ArrayDoneSearchCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Interp *iPtr = (Interp *) interp; Var *varPtr; Tcl_Obj *varNameObj, *searchObj; ArraySearch *searchPtr; int isArray; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "arrayName searchId"); return TCL_ERROR; } varNameObj = objv[1]; searchObj = objv[2]; if (TCL_ERROR == LocateArray(interp, varNameObj, &varPtr, &isArray)) { return TCL_ERROR; } if (!isArray) { return NotArrayError(interp, varNameObj); } /* * Get the search. */ searchPtr = ParseSearchId(interp, varPtr, varNameObj, searchObj); if (searchPtr == NULL) { return TCL_ERROR; } ArrayDoneSearch(iPtr, varPtr, searchPtr); Tcl_DecrRefCount(searchPtr->name); Tcl_Free(searchPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * ArrayExistsCmd -- * * This object-based function is invoked to process the "array exists" * Tcl command. See the user documentation for details on what it does. * * Results: * A standard Tcl result object. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int ArrayExistsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Interp *iPtr = (Interp *)interp; int isArray; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "arrayName"); return TCL_ERROR; } if (TCL_ERROR == LocateArray(interp, objv[1], NULL, &isArray)) { return TCL_ERROR; } Tcl_SetObjResult(interp, iPtr->execEnvPtr->constants[isArray]); return TCL_OK; } /* *---------------------------------------------------------------------- * * ArrayGetCmd -- * * This object-based function is invoked to process the "array get" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl result object. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int ArrayGetCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Var *varPtr, *varPtr2; Tcl_Obj *varNameObj, *nameObj, *valueObj, *nameLstObj, *tmpResObj; Tcl_Obj **nameObjPtr, *patternObj; Tcl_HashSearch search; const char *pattern; Tcl_Size i, count; int result, isArray; switch (objc) { case 2: varNameObj = objv[1]; patternObj = NULL; break; case 3: varNameObj = objv[1]; patternObj = objv[2]; break; default: Tcl_WrongNumArgs(interp, 1, objv, "arrayName ?pattern?"); return TCL_ERROR; } if (TCL_ERROR == LocateArray(interp, varNameObj, &varPtr, &isArray)) { return TCL_ERROR; } /* If not an array, it's an empty result. */ if (!isArray) { return TCL_OK; } pattern = (patternObj ? TclGetString(patternObj) : NULL); /* * Store the array names in a new object. */ TclNewObj(nameLstObj); Tcl_IncrRefCount(nameLstObj); if ((patternObj != NULL) && TclMatchIsTrivial(pattern)) { varPtr2 = VarHashFindVar(varPtr->value.tablePtr, patternObj); if (varPtr2 == NULL) { goto searchDone; } if (TclIsVarUndefined(varPtr2)) { goto searchDone; } result = Tcl_ListObjAppendElement(interp, nameLstObj, VarHashGetKey(varPtr2)); if (result != TCL_OK) { TclDecrRefCount(nameLstObj); return result; } goto searchDone; } for (varPtr2 = VarHashFirstVar(varPtr->value.tablePtr, &search); varPtr2; varPtr2 = VarHashNextVar(&search)) { if (TclIsVarUndefined(varPtr2)) { continue; } nameObj = VarHashGetKey(varPtr2); if (patternObj && !Tcl_StringMatch(TclGetString(nameObj), pattern)) { continue; /* Element name doesn't match pattern. */ } result = Tcl_ListObjAppendElement(interp, nameLstObj, nameObj); if (result != TCL_OK) { TclDecrRefCount(nameLstObj); return result; } } /* * Make sure the Var structure of the array is not removed by a trace * while we're working. */ searchDone: if (TclIsVarInHash(varPtr)) { VarHashRefCount(varPtr)++; } /* * Get the array values corresponding to each element name. */ TclNewObj(tmpResObj); result = TclListObjGetElements(interp, nameLstObj, &count, &nameObjPtr); if (result != TCL_OK) { goto errorInArrayGet; } for (i=0 ; i 4)) { Tcl_WrongNumArgs(interp, 1, objv, "arrayName ?mode? ?pattern?"); return TCL_ERROR; } patternObj = (objc > 2 ? objv[objc-1] : NULL); if (TCL_ERROR == LocateArray(interp, objv[1], &varPtr, &isArray)) { return TCL_ERROR; } /* * Finish parsing the arguments. */ if ((objc == 4) && Tcl_GetIndexFromObj(interp, objv[2], options, "option", 0, &mode) != TCL_OK) { return TCL_ERROR; } /* If not an array, the result is empty. */ if (!isArray) { return TCL_OK; } /* * Check for the trivial cases where we can use a direct lookup. */ TclNewObj(resultObj); if (patternObj) { pattern = TclGetString(patternObj); } if ((mode==OPT_GLOB && patternObj && TclMatchIsTrivial(pattern)) || (mode==OPT_EXACT)) { varPtr2 = VarHashFindVar(varPtr->value.tablePtr, patternObj); if ((varPtr2 != NULL) && !TclIsVarUndefined(varPtr2)) { /* * This can't fail; lappending to an empty object always works. */ Tcl_ListObjAppendElement(NULL, resultObj, VarHashGetKey(varPtr2)); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* * Must scan the array to select the elements. */ for (varPtr2=VarHashFirstVar(varPtr->value.tablePtr, &search); varPtr2!=NULL ; varPtr2=VarHashNextVar(&search)) { if (TclIsVarUndefined(varPtr2)) { continue; } nameObj = VarHashGetKey(varPtr2); if (patternObj) { const char *name = TclGetString(nameObj); int matched = 0; switch (mode) { case OPT_EXACT: Tcl_Panic("exact matching shouldn't get here"); case OPT_GLOB: matched = Tcl_StringMatch(name, pattern); break; case OPT_REGEXP: matched = Tcl_RegExpMatchObj(interp, nameObj, patternObj); if (matched < 0) { TclDecrRefCount(resultObj); return TCL_ERROR; } break; } if (matched == 0) { continue; } } Tcl_ListObjAppendElement(NULL, resultObj, nameObj); } Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclFindArrayPtrElements -- * * Fill out a hash table (which *must* use Tcl_Obj* keys) with an entry * for each existing element of the given array. The provided hash table * is assumed to be initially empty. * * Result: * none * * Side effects: * The keys of the array gain an extra reference. The supplied hash table * has elements added to it. * *---------------------------------------------------------------------- */ void TclFindArrayPtrElements( Var *arrayPtr, Tcl_HashTable *tablePtr) { Var *varPtr; Tcl_HashSearch search; if ((arrayPtr == NULL) || !TclIsVarArray(arrayPtr) || TclIsVarUndefined(arrayPtr)) { return; } for (varPtr=VarHashFirstVar(arrayPtr->value.tablePtr, &search); varPtr!=NULL ; varPtr=VarHashNextVar(&search)) { Tcl_HashEntry *hPtr; Tcl_Obj *nameObj; int dummy; if (TclIsVarUndefined(varPtr)) { continue; } nameObj = VarHashGetKey(varPtr); hPtr = Tcl_CreateHashEntry(tablePtr, nameObj, &dummy); Tcl_SetHashValue(hPtr, nameObj); } } /* *---------------------------------------------------------------------- * * ArraySetCmd -- * * This object-based function is invoked to process the "array set" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl result object. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int ArraySetCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_Obj *arrayNameObj; Tcl_Obj *arrayElemObj; Var *varPtr, *arrayPtr; int result; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "arrayName list"); return TCL_ERROR; } if (TCL_ERROR == LocateArray(interp, objv[1], NULL, NULL)) { return TCL_ERROR; } arrayNameObj = objv[1]; varPtr = TclObjLookupVarEx(interp, arrayNameObj, NULL, /*flags*/ TCL_LEAVE_ERR_MSG, /*msg*/ "set", /*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr); if (varPtr == NULL) { return TCL_ERROR; } if (arrayPtr) { CleanupVar(varPtr, arrayPtr); TclObjVarErrMsg(interp, arrayNameObj, NULL, "set", NEEDARRAY, -1); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "VARNAME", TclGetString(arrayNameObj), (char *)NULL); return TCL_ERROR; } /* * Install the contents of the dictionary or list into the array. */ arrayElemObj = objv[2]; if (TclHasInternalRep(arrayElemObj, &tclDictType) && arrayElemObj->bytes == NULL) { Tcl_Obj *keyPtr, *valuePtr; Tcl_DictSearch search; int done; Tcl_Size size; if (Tcl_DictObjSize(interp, arrayElemObj, &size) != TCL_OK) { return TCL_ERROR; } if (size == 0) { /* * Empty, so we'll just force the array to be properly existing * instead. */ goto ensureArray; } /* * Don't need to look at result of Tcl_DictObjFirst as we've just * successfully used a dictionary operation on the same object. */ for (Tcl_DictObjFirst(interp, arrayElemObj, &search, &keyPtr, &valuePtr, &done) ; !done ; Tcl_DictObjNext(&search, &keyPtr, &valuePtr, &done)) { /* * At this point, it would be nice if the key was directly usable * by the array. This isn't the case though. */ Var *elemVarPtr = TclLookupArrayElement(interp, arrayNameObj, keyPtr, TCL_LEAVE_ERR_MSG, "set", 1, 1, varPtr, -1); if ((elemVarPtr == NULL) || (TclPtrSetVarIdx(interp, elemVarPtr, varPtr, arrayNameObj, keyPtr, valuePtr, TCL_LEAVE_ERR_MSG, -1) == NULL)) { Tcl_DictObjDone(&search); return TCL_ERROR; } } return TCL_OK; } else { /* * Not a dictionary, so assume (and convert to, for backward- * -compatibility reasons) a list. */ Tcl_Size elemLen; Tcl_Obj **elemPtrs, *copyListObj; Tcl_Size i; result = TclListObjLength(interp, arrayElemObj, &elemLen); if (result != TCL_OK) { return result; } if (elemLen & 1) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "list must have an even number of elements", -1)); Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "FORMAT", (char *)NULL); return TCL_ERROR; } if (elemLen == 0) { goto ensureArray; } result = TclListObjGetElements(interp, arrayElemObj, &elemLen, &elemPtrs); if (result != TCL_OK) { return result; } /* * We needn't worry about traces invalidating arrayPtr: should that be * the case, TclPtrSetVarIdx will return NULL so that we break out of * the loop and return an error. */ copyListObj = TclListObjCopy(NULL, arrayElemObj); if (!copyListObj) { return TCL_ERROR; } for (i=0 ; ivalue.tablePtr, &search); varPtr2!=NULL ; varPtr2=VarHashNextVar(&search)) { if (!TclIsVarUndefined(varPtr2)) { size++; } } } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(size)); return TCL_OK; } /* *---------------------------------------------------------------------- * * ArrayStatsCmd -- * * This object-based function is invoked to process the "array * statistics" Tcl command. See the user documentation for details on * what it does. * * Results: * A standard Tcl result object. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int ArrayStatsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Var *varPtr; Tcl_Obj *varNameObj; char *stats; int isArray; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "arrayName"); return TCL_ERROR; } varNameObj = objv[1]; if (TCL_ERROR == LocateArray(interp, varNameObj, &varPtr, &isArray)) { return TCL_ERROR; } if (!isArray) { return NotArrayError(interp, varNameObj); } stats = Tcl_HashStats((Tcl_HashTable *) varPtr->value.tablePtr); if (stats == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "error reading array statistics", -1)); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewStringObj(stats, -1)); Tcl_Free(stats); return TCL_OK; } /* *---------------------------------------------------------------------- * * ArrayUnsetCmd -- * * This object-based function is invoked to process the "array unset" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl result object. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int ArrayUnsetCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Var *varPtr, *varPtr2, *protectedVarPtr; Tcl_Obj *varNameObj, *patternObj, *nameObj; Tcl_HashSearch search; const char *pattern; int unsetFlags = 0; /* Should this be TCL_LEAVE_ERR_MSG? */ int isArray; switch (objc) { case 2: varNameObj = objv[1]; patternObj = NULL; break; case 3: varNameObj = objv[1]; patternObj = objv[2]; break; default: Tcl_WrongNumArgs(interp, 1, objv, "arrayName ?pattern?"); return TCL_ERROR; } if (TCL_ERROR == LocateArray(interp, varNameObj, &varPtr, &isArray)) { return TCL_ERROR; } if (!isArray) { return TCL_OK; } if (!patternObj) { /* * When no pattern is given, just unset the whole array. */ return TclObjUnsetVar2(interp, varNameObj, NULL, 0); } /* * With a trivial pattern, we can just unset. */ pattern = TclGetString(patternObj); if (TclMatchIsTrivial(pattern)) { varPtr2 = VarHashFindVar(varPtr->value.tablePtr, patternObj); if (!varPtr2 || TclIsVarUndefined(varPtr2)) { return TCL_OK; } return TclPtrUnsetVarIdx(interp, varPtr2, varPtr, varNameObj, patternObj, unsetFlags, -1); } /* * Non-trivial case (well, deeply tricky really). We peek inside the hash * iterator in order to allow us to guarantee that the following element * in the array will not be scrubbed until we have dealt with it. This * stops the overall iterator from ending up pointing into deallocated * memory. [Bug 2939073] */ protectedVarPtr = NULL; for (varPtr2=VarHashFirstVar(varPtr->value.tablePtr, &search); varPtr2!=NULL ; varPtr2=VarHashNextVar(&search)) { /* * Drop the extra ref immediately. We don't need to free it at this * point though; we'll be unsetting it if necessary soon. */ if (varPtr2 == protectedVarPtr) { VarHashRefCount(varPtr2)--; } /* * Guard the next (peeked) item in the search chain by incrementing * its refcount. This guarantees that the hash table iterator won't be * dangling on the next time through the loop. */ if (search.nextEntryPtr != NULL) { protectedVarPtr = VarHashGetValue(search.nextEntryPtr); VarHashRefCount(protectedVarPtr)++; } else { protectedVarPtr = NULL; } /* * If the variable is undefined, clean it out as it has been hit by * something else (i.e., an unset trace). */ if (TclIsVarUndefined(varPtr2)) { CleanupVar(varPtr2, varPtr); continue; } nameObj = VarHashGetKey(varPtr2); if (Tcl_StringMatch(TclGetString(nameObj), pattern) && TclPtrUnsetVarIdx(interp, varPtr2, varPtr, varNameObj, nameObj, unsetFlags, -1) != TCL_OK) { /* * If we incremented a refcount, we must decrement it here as we * will not be coming back properly due to the error. */ if (protectedVarPtr) { VarHashRefCount(protectedVarPtr)--; CleanupVar(protectedVarPtr, varPtr); } return TCL_ERROR; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclInitArrayCmd -- * * This creates the ensemble for the "array" command. * * Results: * The handle for the created ensemble. * * Side effects: * Creates a command in the global namespace. * *---------------------------------------------------------------------- */ Tcl_Command TclInitArrayCmd( Tcl_Interp *interp) /* Current interpreter. */ { static const EnsembleImplMap arrayImplMap[] = { {"anymore", ArrayAnyMoreCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"default", ArrayDefaultCmd, TclCompileBasic2Or3ArgCmd, NULL, NULL, 0}, {"donesearch", ArrayDoneSearchCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"exists", ArrayExistsCmd, TclCompileArrayExistsCmd, NULL, NULL, 0}, {"for", ArrayForObjCmd, TclCompileBasic3ArgCmd, ArrayForNRCmd, NULL, 0}, {"get", ArrayGetCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0}, {"names", ArrayNamesCmd, TclCompileBasic1To3ArgCmd, NULL, NULL, 0}, {"nextelement", ArrayNextElementCmd, TclCompileBasic2ArgCmd, NULL, NULL, 0}, {"set", ArraySetCmd, TclCompileArraySetCmd, NULL, NULL, 0}, {"size", ArraySizeCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"startsearch", ArrayStartSearchCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"statistics", ArrayStatsCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"unset", ArrayUnsetCmd, TclCompileArrayUnsetCmd, NULL, NULL, 0}, {NULL, NULL, NULL, NULL, NULL, 0} }; return TclMakeEnsemble(interp, "array", arrayImplMap); } /* *---------------------------------------------------------------------- * * ObjMakeUpvar -- * * This function does all of the work of the "global" and "upvar" * commands. * * Results: * A standard Tcl completion code. If an error occurs then an error * message is left in interp. * * Side effects: * The variable given by myName is linked to the variable in framePtr * given by otherP1 and otherP2, so that references to myName are * redirected to the other variable like a symbolic link. * Callers must Incr myNamePtr if they plan to Decr it. * Callers must Incr otherP1Ptr if they plan to Decr it. * *---------------------------------------------------------------------- */ static int ObjMakeUpvar( Tcl_Interp *interp, /* Interpreter containing variables. Used for * error messages, too. */ CallFrame *framePtr, /* Call frame containing "other" variable. * NULL means use global :: context. */ Tcl_Obj *otherP1Ptr, const char *otherP2, /* Two-part name of variable in framePtr. */ int otherFlags, /* 0, TCL_GLOBAL_ONLY or TCL_NAMESPACE_ONLY: * indicates scope of "other" variable. */ Tcl_Obj *myNamePtr, /* Name of variable which will refer to * otherP1/otherP2. Must be a scalar. */ int myFlags, /* 0, TCL_GLOBAL_ONLY or TCL_NAMESPACE_ONLY: * indicates scope of myName. */ int index) /* If the variable to be linked is an indexed * scalar, this is its index. Otherwise, -1 */ { Interp *iPtr = (Interp *) interp; Var *otherPtr, *arrayPtr; CallFrame *varFramePtr; /* * Find "other" in "framePtr". If not looking up other in just the current * namespace, temporarily replace the current var frame pointer in the * interpreter in order to use TclObjLookupVar. */ if (framePtr == NULL) { framePtr = iPtr->rootFramePtr; } varFramePtr = iPtr->varFramePtr; if (!(otherFlags & TCL_NAMESPACE_ONLY)) { iPtr->varFramePtr = framePtr; } otherPtr = TclObjLookupVar(interp, otherP1Ptr, otherP2, (otherFlags | TCL_LEAVE_ERR_MSG), "access", /*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr); if (!(otherFlags & TCL_NAMESPACE_ONLY)) { iPtr->varFramePtr = varFramePtr; } if (otherPtr == NULL) { return TCL_ERROR; } /* * Check that we are not trying to create a namespace var linked to a * local variable in a procedure. If we allowed this, the local * variable in the shorter-lived procedure frame could go away leaving * the namespace var's reference invalid. */ if (index < 0) { if (!(arrayPtr != NULL ? (TclIsVarInHash(arrayPtr) && TclGetVarNsPtr(arrayPtr)) : (TclIsVarInHash(otherPtr) && TclGetVarNsPtr(otherPtr))) && ((myFlags & (TCL_GLOBAL_ONLY | TCL_NAMESPACE_ONLY)) || (varFramePtr == NULL) || !HasLocalVars(varFramePtr) || (strstr(TclGetString(myNamePtr), "::") != NULL))) { Tcl_SetObjResult((Tcl_Interp *) iPtr, Tcl_ObjPrintf( "bad variable name \"%s\": can't create namespace " "variable that refers to procedure variable", TclGetString(myNamePtr))); Tcl_SetErrorCode(interp, "TCL", "UPVAR", "INVERTED", (char *)NULL); return TCL_ERROR; } } return TclPtrObjMakeUpvarIdx(interp, otherPtr, myNamePtr, myFlags, index); } /* *---------------------------------------------------------------------- * * TclPtrMakeUpvar -- * * This procedure does all of the work of the "global" and "upvar" * commands. * * Results: * A standard Tcl completion code. If an error occurs then an error * message is left in interp. * * Side effects: * The variable given by myName is linked to the variable in framePtr * given by otherP1 and otherP2, so that references to myName are * redirected to the other variable like a symbolic link. * *---------------------------------------------------------------------- */ int TclPtrMakeUpvar( Tcl_Interp *interp, /* Interpreter containing variables. Used for * error messages, too. */ Var *otherPtr, /* Pointer to the variable being linked-to. */ const char *myName, /* Name of variable which will refer to * otherP1/otherP2. Must be a scalar. */ int myFlags, /* 0, TCL_GLOBAL_ONLY or TCL_NAMESPACE_ONLY: * indicates scope of myName. */ int index) /* If the variable to be linked is an indexed * scalar, this is its index. Otherwise, -1 */ { Tcl_Obj *myNamePtr = NULL; int result; if (myName) { myNamePtr = Tcl_NewStringObj(myName, -1); Tcl_IncrRefCount(myNamePtr); } result = TclPtrObjMakeUpvarIdx(interp, otherPtr, myNamePtr, myFlags, index); if (myNamePtr) { Tcl_DecrRefCount(myNamePtr); } return result; } int TclPtrObjMakeUpvar( Tcl_Interp *interp, /* Interpreter containing variables. Used for * error messages, too. */ Tcl_Var otherPtr, /* Pointer to the variable being linked-to. */ Tcl_Obj *myNamePtr, /* Name of variable which will refer to * otherP1/otherP2. Must be a scalar. */ int myFlags) /* 0, TCL_GLOBAL_ONLY or TCL_NAMESPACE_ONLY: * indicates scope of myName. */ { return TclPtrObjMakeUpvarIdx(interp, (Var *) otherPtr, myNamePtr, myFlags, -1); } /* Callers must Incr myNamePtr if they plan to Decr it. */ int TclPtrObjMakeUpvarIdx( Tcl_Interp *interp, /* Interpreter containing variables. Used for * error messages, too. */ Var *otherPtr, /* Pointer to the variable being linked-to. */ Tcl_Obj *myNamePtr, /* Name of variable which will refer to * otherP1/otherP2. Must be a scalar. */ int myFlags, /* 0, TCL_GLOBAL_ONLY or TCL_NAMESPACE_ONLY: * indicates scope of myName. */ int index) /* If the variable to be linked is an indexed * scalar, this is its index. Otherwise, -1 */ { Interp *iPtr = (Interp *) interp; CallFrame *varFramePtr = iPtr->varFramePtr; const char *errMsg, *p, *myName; Var *varPtr; if (index >= 0) { if (!HasLocalVars(varFramePtr)) { Tcl_Panic("ObjMakeUpvar called with an index outside from a proc"); } varPtr = (Var *) &(varFramePtr->compiledLocals[index]); myNamePtr = localName(iPtr->varFramePtr, index); myName = myNamePtr? TclGetString(myNamePtr) : NULL; } else { /* * Do not permit the new variable to look like an array reference, as * it will not be reachable in that case [Bug 600812, TIP 184]. The * "definition" of what "looks like an array reference" is consistent * (and must remain consistent) with the code in TclObjLookupVar(). */ myName = TclGetString(myNamePtr); p = strstr(myName, "("); if (p != NULL) { p += strlen(p)-1; if (*p == ')') { /* * myName looks like an array reference. */ Tcl_SetObjResult((Tcl_Interp *) iPtr, Tcl_ObjPrintf( "bad variable name \"%s\": can't create a scalar " "variable that looks like an array element", myName)); Tcl_SetErrorCode(interp, "TCL", "UPVAR", "LOCAL_ELEMENT", (char *)NULL); return TCL_ERROR; } } /* * Lookup and eventually create the new variable. Set the flag bit * TCL_AVOID_RESOLVERS to indicate the special resolution rules for * upvar purposes: * - Bug #696893 - variable is either proc-local or in the current * namespace; never follow the second (global) resolution path. * - Bug #631741 - do not use special namespace or interp resolvers. */ varPtr = TclLookupSimpleVar(interp, myNamePtr, myFlags|TCL_AVOID_RESOLVERS, /* create */ 1, &errMsg, &index); if (varPtr == NULL) { TclObjVarErrMsg(interp, myNamePtr, NULL, "create", errMsg, -1); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "VARNAME", TclGetString(myNamePtr), (char *)NULL); return TCL_ERROR; } } if (varPtr == otherPtr) { Tcl_SetObjResult((Tcl_Interp *) iPtr, Tcl_NewStringObj( "can't upvar from variable to itself", -1)); Tcl_SetErrorCode(interp, "TCL", "UPVAR", "SELF", (char *)NULL); return TCL_ERROR; } if (TclIsVarTraced(varPtr)) { Tcl_SetObjResult((Tcl_Interp *) iPtr, Tcl_ObjPrintf( "variable \"%s\" has traces: can't use for upvar", myName)); Tcl_SetErrorCode(interp, "TCL", "UPVAR", "TRACED", (char *)NULL); return TCL_ERROR; } else if (!TclIsVarUndefined(varPtr)) { Var *linkPtr; /* * The variable already existed. Make sure this variable "varPtr" * isn't the same as "otherPtr" (avoid circular links). Also, if it's * not an upvar then it's an error. If it is an upvar, then just * disconnect it from the thing it currently refers to. */ if (!TclIsVarLink(varPtr)) { Tcl_SetObjResult((Tcl_Interp *) iPtr, Tcl_ObjPrintf( "variable \"%s\" already exists", myName)); Tcl_SetErrorCode(interp, "TCL", "UPVAR", "EXISTS", (char *)NULL); return TCL_ERROR; } linkPtr = varPtr->value.linkPtr; if (linkPtr == otherPtr) { return TCL_OK; } if (TclIsVarInHash(linkPtr)) { VarHashRefCount(linkPtr)--; if (TclIsVarUndefined(linkPtr)) { CleanupVar(linkPtr, NULL); } } } TclSetVarLink(varPtr); varPtr->value.linkPtr = otherPtr; if (TclIsVarInHash(otherPtr)) { VarHashRefCount(otherPtr)++; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_UpVar2 -- * * This function links one variable to another, just like the "upvar" * command. * * Results: * A standard Tcl completion code. If an error occurs then an error * message is left in the interp's result. * * Side effects: * The variable in frameName whose name is given by part1 and part2 * becomes accessible under the name localNameStr, so that references to * localNameStr are redirected to the other variable like a symbolic * link. * *---------------------------------------------------------------------- */ int Tcl_UpVar2( Tcl_Interp *interp, /* Interpreter containing variables. Used for * error messages too. */ const char *frameName, /* Name of the frame containing the source * variable, such as "1" or "#0". */ const char *part1, const char *part2, /* Two parts of source variable name to link * to. */ const char *localNameStr, /* Name of link variable. */ int flags) /* 0, TCL_GLOBAL_ONLY or TCL_NAMESPACE_ONLY: * indicates scope of localNameStr. */ { int result; CallFrame *framePtr; Tcl_Obj *part1Ptr, *localNamePtr; if (TclGetFrame(interp, frameName, &framePtr) == -1) { return TCL_ERROR; } part1Ptr = Tcl_NewStringObj(part1, -1); Tcl_IncrRefCount(part1Ptr); localNamePtr = Tcl_NewStringObj(localNameStr, -1); Tcl_IncrRefCount(localNamePtr); result = ObjMakeUpvar(interp, framePtr, part1Ptr, part2, 0, localNamePtr, flags, -1); Tcl_DecrRefCount(part1Ptr); Tcl_DecrRefCount(localNamePtr); return result; } /* *---------------------------------------------------------------------- * * Tcl_GetVariableFullName -- * * Given a Tcl_Var token returned by Tcl_FindNamespaceVar, this function * appends to an object the namespace variable's full name, qualified by * a sequence of parent namespace names. * * Results: * None. * * Side effects: * The variable's fully-qualified name is appended to the string * representation of objPtr. * *---------------------------------------------------------------------- */ void Tcl_GetVariableFullName( Tcl_Interp *interp, /* Interpreter containing the variable. */ Tcl_Var variable, /* Token for the variable returned by a * previous call to Tcl_FindNamespaceVar. */ Tcl_Obj *objPtr) /* Points to the object onto which the * variable's full name is appended. */ { Interp *iPtr = (Interp *) interp; Var *varPtr = (Var *) variable; Tcl_Obj *namePtr; Namespace *nsPtr; if (!varPtr || TclIsVarArrayElement(varPtr)) { return; } /* * Add the full name of the containing namespace (if any), followed by the * "::" separator, then the variable name. */ nsPtr = TclGetVarNsPtr(varPtr); if (nsPtr) { Tcl_AppendToObj(objPtr, nsPtr->fullName, -1); if (nsPtr != iPtr->globalNsPtr) { Tcl_AppendToObj(objPtr, "::", 2); } } if (TclIsVarInHash(varPtr)) { if (!TclIsVarDeadHash(varPtr)) { namePtr = VarHashGetKey(varPtr); Tcl_AppendObjToObj(objPtr, namePtr); } } else if (iPtr->varFramePtr->procPtr) { Tcl_Size index = varPtr - iPtr->varFramePtr->compiledLocals; if (index < iPtr->varFramePtr->numCompiledLocals) { namePtr = localName(iPtr->varFramePtr, index); Tcl_AppendObjToObj(objPtr, namePtr); } } } /* *---------------------------------------------------------------------- * * Tcl_ConstObjCmd -- * * This function is invoked to process the "const" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl object result value. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_ConstObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Var *varPtr, *arrayPtr; Tcl_Obj *part1Ptr; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "varName value"); return TCL_ERROR; } part1Ptr = objv[1]; varPtr = TclObjLookupVarEx(interp, part1Ptr, NULL, TCL_LEAVE_ERR_MSG, "const", /*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr); if (TclIsVarArray(varPtr)) { TclObjVarErrMsg(interp, part1Ptr, NULL, "make constant", ISARRAY, -1); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CONST", (char *)NULL); return TCL_ERROR; } if (TclIsVarArrayElement(varPtr)) { if (TclIsVarUndefined(varPtr)) { CleanupVar(varPtr, arrayPtr); } TclObjVarErrMsg(interp, part1Ptr, NULL, "make constant", ISARRAYELEMENT, -1); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CONST", (char *)NULL); return TCL_ERROR; } /* * If already exists, either a constant (no problem) or an error. */ if (!TclIsVarUndefined(varPtr)) { if (TclIsVarConstant(varPtr)) { return TCL_OK; } TclObjVarErrMsg(interp, part1Ptr, NULL, "make constant", EXISTS, -1); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CONST", (char *)NULL); return TCL_ERROR; } /* * Make the variable and flag it as a constant. */ if (TclPtrSetVar(interp, (Tcl_Var) varPtr, NULL, objv[1], NULL, objv[2], TCL_LEAVE_ERR_MSG) == NULL) { if (TclIsVarUndefined(varPtr)) { CleanupVar(varPtr, arrayPtr); } return TCL_ERROR; }; TclSetVarConstant(varPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_GlobalObjCmd -- * * This object-based function is invoked to process the "global" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result value. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_GlobalObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; Tcl_Obj *objPtr, *tailPtr; const char *varName; const char *tail; int result, i; /* * If we are not executing inside a Tcl procedure, just return. */ if (!HasLocalVars(iPtr->varFramePtr)) { return TCL_OK; } for (i=1 ; i varName) && ((tail[0] != ':') || (tail[-1] != ':'))) { tail--; } if ((*tail == ':') && (tail > varName)) { tail++; } if (tail == varName) { tailPtr = objPtr; } else { tailPtr = Tcl_NewStringObj(tail, -1); Tcl_IncrRefCount(tailPtr); } /* * Link to the variable "varName" in the global :: namespace. */ result = ObjMakeUpvar(interp, NULL, objPtr, NULL, TCL_GLOBAL_ONLY, /*myName*/ tailPtr, /*myFlags*/ 0, -1); if (tail != varName) { Tcl_DecrRefCount(tailPtr); } if (result != TCL_OK) { return result; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_VariableObjCmd -- * * Invoked to implement the "variable" command that creates one or more * global variables. Handles the following syntax: * * variable ?name value...? name ?value? * * One or more variables can be created. The variables are initialized * with the specified values. The value for the last variable is * optional. * * If the variable does not exist, it is created and given the optional * value. If it already exists, it is simply set to the optional value. * Normally, "name" is an unqualified name, so it is created in the * current namespace. If it includes namespace qualifiers, it can be * created in another namespace. * * If the variable command is executed inside a Tcl procedure, it creates * a local variable linked to the newly-created namespace variable. * * Results: * Returns TCL_OK if the variable is found or created. Returns TCL_ERROR * if anything goes wrong. * * Side effects: * If anything goes wrong, this function returns an error message as the * result in the interpreter's result object. * *---------------------------------------------------------------------- */ int Tcl_VariableObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; const char *varName, *tail, *cp; Var *varPtr, *arrayPtr; Tcl_Obj *varValuePtr; int i, result; Tcl_Obj *varNamePtr, *tailPtr; for (i=1 ; ivarFramePtr)) { /* * varName might have a scope qualifier, but the name for the * local "link" variable must be the simple name at the tail. * * Locate tail in one pass: drop any prefix after two *or more* * consecutive ":" characters). */ for (tail=cp=varName ; *cp!='\0' ;) { if (*cp++ == ':') { while (*cp == ':') { tail = ++cp; } } } /* * Create a local link "tail" to the variable "varName" in the * current namespace. */ if (tail == varName) { tailPtr = varNamePtr; } else { tailPtr = Tcl_NewStringObj(tail, -1); Tcl_IncrRefCount(tailPtr); } result = ObjMakeUpvar(interp, NULL, varNamePtr, /*otherP2*/ NULL, /*otherFlags*/ TCL_NAMESPACE_ONLY, /*myName*/ tailPtr, /*myFlags*/ 0, -1); if (tail != varName) { Tcl_DecrRefCount(tailPtr); } if (result != TCL_OK) { return result; } } } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_UpvarObjCmd -- * * This object-based function is invoked to process the "upvar" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result value. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tcl_UpvarObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { CallFrame *framePtr; int result, hasLevel; Tcl_Obj *levelObj; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "?level? otherVar localVar ?otherVar localVar ...?"); return TCL_ERROR; } if (objc & 1) { /* * Even number of arguments, so use the default level of "1" by * passing NULL to TclObjGetFrame. */ levelObj = NULL; hasLevel = 0; } else { /* * Odd number of arguments, so objv[1] must contain the level. */ levelObj = objv[1]; hasLevel = 1; } /* * Find the call frame containing each of the "other variables" to be * linked to. */ result = TclObjGetFrame(interp, levelObj, &framePtr); if (result == -1) { return TCL_ERROR; } if ((result == 0) && hasLevel) { /* * Synthesize an error message since TclObjGetFrame doesn't do this * for this particular case. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad level \"%s\"", TclGetString(levelObj))); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "LEVEL", TclGetString(levelObj), (char *)NULL); return TCL_ERROR; } /* * We've now finished with parsing levels; skip to the variable names. */ objc -= hasLevel + 1; objv += hasLevel + 1; /* * Iterate over each (other variable, local variable) pair. Divide the * other variable name into two parts, then call MakeUpvar to do all the * work of linking it to the local variable. */ for (; objc>0 ; objc-=2, objv+=2) { result = ObjMakeUpvar(interp, framePtr, /* othervarName */ objv[0], NULL, 0, /* myVarName */ objv[1], /*flags*/ 0, -1); if (result != TCL_OK) { return TCL_ERROR; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * ParseSearchId -- * * This function translates from a tcl object to a pointer to an active * array search (if there is one that matches the string). * * Results: * The return value is a pointer to the array search indicated by string, * or NULL if there isn't one. If NULL is returned, the interp's result * contains an error message. * *---------------------------------------------------------------------- */ static ArraySearch * ParseSearchId( Tcl_Interp *interp, /* Interpreter containing variable. */ const Var *varPtr, /* Array variable search is for. */ Tcl_Obj *varNamePtr, /* Name of array variable that search is * supposed to be for. */ Tcl_Obj *handleObj) /* Object containing id of search. Must have * form "search-num-var" where "num" is a * decimal number and "var" is a variable * name. */ { Interp *iPtr = (Interp *) interp; ArraySearch *searchPtr; const char *handle = TclGetString(handleObj); char *end; if (varPtr->flags & VAR_SEARCH_ACTIVE) { Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&iPtr->varSearches, varPtr); /* First look for same (Tcl_Obj *) */ for (searchPtr = (ArraySearch *)Tcl_GetHashValue(hPtr); searchPtr != NULL; searchPtr = searchPtr->nextPtr) { if (searchPtr->name == handleObj) { return searchPtr; } } /* Fallback: do string compares. */ for (searchPtr = (ArraySearch *)Tcl_GetHashValue(hPtr); searchPtr != NULL; searchPtr = searchPtr->nextPtr) { if (strcmp(TclGetString(searchPtr->name), handle) == 0) { return searchPtr; } } } if ((handle[0] != 's') || (handle[1] != '-') || (strtoul(handle + 2, &end, 10), end == (handle + 2)) || (*end != '-')) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "illegal search identifier \"%s\"", handle)); } else if (strcmp(end + 1, TclGetString(varNamePtr)) != 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "search identifier \"%s\" isn't for variable \"%s\"", handle, TclGetString(varNamePtr))); } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't find search \"%s\"", handle)); } Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "ARRAYSEARCH", handle, (char *)NULL); return NULL; } /* *---------------------------------------------------------------------- * * DeleteSearches -- * * This function is called to free up all of the searches associated * with an array variable. * * Results: * None. * * Side effects: * Memory is released to the storage allocator. * *---------------------------------------------------------------------- */ static void DeleteSearches( Interp *iPtr, Var *arrayVarPtr) /* Variable whose searches are to be * deleted. */ { ArraySearch *searchPtr, *nextPtr; Tcl_HashEntry *sPtr; if (arrayVarPtr->flags & VAR_SEARCH_ACTIVE) { sPtr = Tcl_FindHashEntry(&iPtr->varSearches, arrayVarPtr); for (searchPtr = (ArraySearch *)Tcl_GetHashValue(sPtr); searchPtr != NULL; searchPtr = nextPtr) { nextPtr = searchPtr->nextPtr; Tcl_DecrRefCount(searchPtr->name); Tcl_Free(searchPtr); } arrayVarPtr->flags &= ~VAR_SEARCH_ACTIVE; Tcl_DeleteHashEntry(sPtr); } } /* *---------------------------------------------------------------------- * * TclDeleteNamespaceVars -- * * This function is called to recycle all the storage space associated * with a namespace's table of variables. * * Results: * None. * * Side effects: * Variables are deleted and trace functions are invoked, if any are * declared. * *---------------------------------------------------------------------- */ void TclDeleteNamespaceVars( Namespace *nsPtr) { TclVarHashTable *tablePtr = &nsPtr->varTable; Tcl_Interp *interp = nsPtr->interp; Interp *iPtr = (Interp *)interp; Tcl_HashSearch search; int flags = 0; Var *varPtr; /* * Determine what flags to pass to the trace callback functions. */ if (nsPtr == iPtr->globalNsPtr) { flags = TCL_GLOBAL_ONLY; } else if (nsPtr == (Namespace *) TclGetCurrentNamespace(interp)) { flags = TCL_NAMESPACE_ONLY; } for (varPtr = VarHashFirstVar(tablePtr, &search); varPtr != NULL; varPtr = VarHashFirstVar(tablePtr, &search)) { Tcl_Obj *objPtr; TclNewObj(objPtr); VarHashRefCount(varPtr)++; /* Make sure we get to remove from * hash. */ Tcl_GetVariableFullName(interp, (Tcl_Var) varPtr, objPtr); UnsetVarStruct(varPtr, NULL, iPtr, /* part1 */ objPtr, NULL, flags, -1); /* * We just unset the variable. However, an unset trace might * have re-set it, or might have re-established traces on it. * This namespace and its vartable are going away unconditionally, * so we cannot let such things linger. That would be a leak. * * First we destroy all traces. ... */ if (TclIsVarTraced(varPtr)) { Tcl_HashEntry *tPtr = Tcl_FindHashEntry(&iPtr->varTraces, varPtr); VarTrace *tracePtr = (VarTrace *)Tcl_GetHashValue(tPtr); ActiveVarTrace *activePtr; while (tracePtr) { VarTrace *prevPtr = tracePtr; tracePtr = tracePtr->nextPtr; prevPtr->nextPtr = NULL; Tcl_EventuallyFree(prevPtr, TCL_DYNAMIC); } Tcl_DeleteHashEntry(tPtr); varPtr->flags &= ~VAR_ALL_TRACES; for (activePtr = iPtr->activeVarTracePtr; activePtr != NULL; activePtr = activePtr->nextPtr) { if (activePtr->varPtr == varPtr) { activePtr->nextTracePtr = NULL; } } } /* * ...and then, if the variable still holds a value, we unset it * again. This time with no traces left, we're sure it goes away. */ if (!TclIsVarUndefined(varPtr)) { UnsetVarStruct(varPtr, NULL, iPtr, /* part1 */ objPtr, NULL, flags, -1); } Tcl_DecrRefCount(objPtr); /* free no longer needed obj */ VarHashRefCount(varPtr)--; VarHashDeleteEntry(varPtr); } VarHashDeleteTable(tablePtr); } /* *---------------------------------------------------------------------- * * TclDeleteVars -- * * This function is called to recycle all the storage space associated * with a table of variables. For this function to work correctly, it * must not be possible for any of the variables in the table to be * accessed from Tcl commands (e.g. from trace functions). * * Results: * None. * * Side effects: * Variables are deleted and trace functions are invoked, if any are * declared. * *---------------------------------------------------------------------- */ void TclDeleteVars( Interp *iPtr, /* Interpreter to which variables belong. */ TclVarHashTable *tablePtr) /* Hash table containing variables to * delete. */ { Tcl_Interp *interp = (Tcl_Interp *) iPtr; Tcl_HashSearch search; Var *varPtr; int flags; Namespace *currNsPtr = (Namespace *) TclGetCurrentNamespace(interp); /* * Determine what flags to pass to the trace callback functions. */ flags = TCL_TRACE_UNSETS; if (tablePtr == &iPtr->globalNsPtr->varTable) { flags |= TCL_GLOBAL_ONLY; } else if (tablePtr == &currNsPtr->varTable) { flags |= TCL_NAMESPACE_ONLY; } for (varPtr = VarHashFirstVar(tablePtr, &search); varPtr != NULL; varPtr = VarHashFirstVar(tablePtr, &search)) { UnsetVarStruct(varPtr, NULL, iPtr, VarHashGetKey(varPtr), NULL, flags, -1); VarHashDeleteEntry(varPtr); } VarHashDeleteTable(tablePtr); } /* *---------------------------------------------------------------------- * * TclDeleteCompiledLocalVars -- * * This function is called to recycle storage space associated with the * compiler-allocated array of local variables in a procedure call frame. * This function resembles TclDeleteVars above except that each variable * is stored in a call frame and not a hash table. For this function to * work correctly, it must not be possible for any of the variable in the * table to be accessed from Tcl commands (e.g. from trace functions). * * Results: * None. * * Side effects: * Variables are deleted and trace functions are invoked, if any are * declared. * *---------------------------------------------------------------------- */ void TclDeleteCompiledLocalVars( Interp *iPtr, /* Interpreter to which variables belong. */ CallFrame *framePtr) /* Procedure call frame containing compiler- * assigned local variables to delete. */ { Var *varPtr; Tcl_Size numLocals, i; Tcl_Obj **namePtrPtr; numLocals = framePtr->numCompiledLocals; varPtr = framePtr->compiledLocals; namePtrPtr = &localName(framePtr, 0); for (i=0 ; inumCompiledLocals = 0; } /* *---------------------------------------------------------------------- * * DeleteArray -- * * This function is called to free up everything in an array variable. * It's the caller's responsibility to make sure that the array is no * longer accessible before this function is called. * * Results: * None. * * Side effects: * All storage associated with varPtr's array elements is deleted * (including the array's hash table). Deletion trace functions for * array elements are invoked, then deleted. Any pending traces for array * elements are also deleted. * *---------------------------------------------------------------------- */ static void DeleteArray( Interp *iPtr, /* Interpreter containing array. */ Tcl_Obj *arrayNamePtr, /* Name of array (used for trace callbacks), * or NULL if it is to be computed on * demand. */ Var *varPtr, /* Pointer to variable structure. */ int flags, /* Flags to pass to TclCallVarTraces: * TCL_TRACE_UNSETS and sometimes * TCL_NAMESPACE_ONLY or TCL_GLOBAL_ONLY. */ int index) { Tcl_HashSearch search; Tcl_HashEntry *tPtr; Var *elPtr; ActiveVarTrace *activePtr; Tcl_Obj *objPtr; VarTrace *tracePtr; for (elPtr = VarHashFirstVar(varPtr->value.tablePtr, &search); elPtr != NULL; elPtr = VarHashNextVar(&search)) { if (TclIsVarScalar(elPtr) && (elPtr->value.objPtr != NULL)) { objPtr = elPtr->value.objPtr; TclDecrRefCount(objPtr); elPtr->value.objPtr = NULL; } /* * Lie about the validity of the hashtable entry. In this way the * variables will be deleted by VarHashDeleteTable. */ VarHashInvalidateEntry(elPtr); if (TclIsVarTraced(elPtr)) { /* * Compute the array name if it was not supplied. */ if (elPtr->flags & VAR_TRACED_UNSET) { Tcl_Obj *elNamePtr = VarHashGetKey(elPtr); elPtr->flags &= ~VAR_TRACE_ACTIVE; TclObjCallVarTraces(iPtr, NULL, elPtr, arrayNamePtr, elNamePtr, flags,/* leaveErrMsg */ 0, index); } tPtr = Tcl_FindHashEntry(&iPtr->varTraces, elPtr); tracePtr = (VarTrace *)Tcl_GetHashValue(tPtr); while (tracePtr) { VarTrace *prevPtr = tracePtr; tracePtr = tracePtr->nextPtr; prevPtr->nextPtr = NULL; Tcl_EventuallyFree(prevPtr, TCL_DYNAMIC); } Tcl_DeleteHashEntry(tPtr); elPtr->flags &= ~VAR_ALL_TRACES; for (activePtr = iPtr->activeVarTracePtr; activePtr != NULL; activePtr = activePtr->nextPtr) { if (activePtr->varPtr == elPtr) { activePtr->nextTracePtr = NULL; } } } TclSetVarUndefined(elPtr); /* * Even though array elements are not supposed to be namespace * variables, some combinations of [upvar] and [variable] may create * such beasts - see [Bug 604239]. This is necessary to avoid leaking * the corresponding Var struct, and is otherwise harmless. */ TclClearVarNamespaceVar(elPtr); } DeleteArrayVar(varPtr); } /* *---------------------------------------------------------------------- * * TclObjVarErrMsg -- * * Generate a reasonable error message describing why a variable * operation failed. * * Results: * None. * * Side effects: * The interp's result is set to hold a message identifying the variable * given by part1 and part2 and describing why the variable operation * failed. * *---------------------------------------------------------------------- */ void TclVarErrMsg( Tcl_Interp *interp, /* Interpreter in which to record message. */ const char *part1, const char *part2, /* Variable's two-part name. */ const char *operation, /* String describing operation that failed, * e.g. "read", "set", or "unset". */ const char *reason) /* String describing why operation failed. */ { Tcl_Obj *part2Ptr = NULL, *part1Ptr = Tcl_NewStringObj(part1, -1); if (part2) { part2Ptr = Tcl_NewStringObj(part2, -1); } TclObjVarErrMsg(interp, part1Ptr, part2Ptr, operation, reason, -1); Tcl_DecrRefCount(part1Ptr); if (part2Ptr) { Tcl_DecrRefCount(part2Ptr); } } void TclObjVarErrMsg( Tcl_Interp *interp, /* Interpreter in which to record message. */ Tcl_Obj *part1Ptr, /* (may be NULL, if index >= 0) */ Tcl_Obj *part2Ptr, /* Variable's two-part name. */ const char *operation, /* String describing operation that failed, * e.g. "read", "set", or "unset". */ const char *reason, /* String describing why operation failed. */ int index) /* Index into the local variable table of the * variable, or -1. Only used when part1Ptr is * NULL. */ { if (!part1Ptr) { if (index == -1) { Tcl_Panic("invalid part1Ptr and invalid index together"); } part1Ptr = localName(((Interp *)interp)->varFramePtr, index); } Tcl_SetObjResult(interp, Tcl_ObjPrintf("can't %s \"%s%s%s%s\": %s", operation, TclGetString(part1Ptr), (part2Ptr ? "(" : ""), (part2Ptr ? TclGetString(part2Ptr) : ""), (part2Ptr ? ")" : ""), reason)); } /* *---------------------------------------------------------------------- * * Internal functions for variable name object types -- * *---------------------------------------------------------------------- */ /* * localVarName - * * INTERNALREP DEFINITION: * twoPtrValue.ptr1: pointer to name obj in varFramePtr->localCache * or NULL if it is this same obj * twoPtrValue.ptr2: index into locals table */ static void FreeLocalVarName( Tcl_Obj *objPtr) { Tcl_Size index; Tcl_Obj *namePtr; LocalGetInternalRep(objPtr, index, namePtr); index++; /* Compiler warning bait. */ if (namePtr) { Tcl_DecrRefCount(namePtr); } } static void DupLocalVarName( Tcl_Obj *srcPtr, Tcl_Obj *dupPtr) { Tcl_Size index; Tcl_Obj *namePtr; LocalGetInternalRep(srcPtr, index, namePtr); if (!namePtr) { namePtr = srcPtr; } LocalSetInternalRep(dupPtr, index, namePtr); } /* * parsedVarName - * * INTERNALREP DEFINITION: * twoPtrValue.ptr1 = pointer to the array name Tcl_Obj (NULL if scalar) * twoPtrValue.ptr2 = pointer to the element name string (owned by this * Tcl_Obj), or NULL if it is a scalar variable */ static void FreeParsedVarName( Tcl_Obj *objPtr) { Tcl_Obj *arrayPtr, *elem; int parsed; ParsedGetInternalRep(objPtr, parsed, arrayPtr, elem); parsed++; /* Silence compiler. */ if (arrayPtr != NULL) { TclDecrRefCount(arrayPtr); TclDecrRefCount(elem); } } static void DupParsedVarName( Tcl_Obj *srcPtr, Tcl_Obj *dupPtr) { Tcl_Obj *arrayPtr, *elem; int parsed; ParsedGetInternalRep(srcPtr, parsed, arrayPtr, elem); parsed++; /* Silence compiler. */ ParsedSetInternalRep(dupPtr, arrayPtr, elem); } /* *---------------------------------------------------------------------- * * Tcl_FindNamespaceVar -- MOVED OVER from tclNamesp.c * * Searches for a namespace variable, a variable not local to a * procedure. The variable can be either a scalar or an array, but may * not be an element of an array. * * Results: * Returns a token for the variable if it is found. Otherwise, if it * can't be found or there is an error, returns NULL and leaves an error * message in the interpreter's result object if "flags" contains * TCL_LEAVE_ERR_MSG. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Var Tcl_FindNamespaceVar( Tcl_Interp *interp, /* The interpreter in which to find the * variable. */ const char *name, /* Variable's name. If it starts with "::", * will be looked up in global namespace. * Else, looked up first in contextNsPtr * (current namespace if contextNsPtr is * NULL), then in global namespace. */ Tcl_Namespace *contextNsPtr,/* Ignored if TCL_GLOBAL_ONLY flag set. * Otherwise, points to namespace in which to * resolve name. If NULL, look up name in the * current namespace. */ int flags) /* An OR'd combination of: * TCL_AVOID_RESOLVERS, TCL_GLOBAL_ONLY (look * up name only in global namespace), * TCL_NAMESPACE_ONLY (look up only in * contextNsPtr, or the current namespace if * contextNsPtr is NULL), and * TCL_LEAVE_ERR_MSG. If both TCL_GLOBAL_ONLY * and TCL_NAMESPACE_ONLY are given, * TCL_GLOBAL_ONLY is ignored. */ { Tcl_Obj *namePtr = Tcl_NewStringObj(name, -1); Tcl_Var var; var = ObjFindNamespaceVar(interp, namePtr, contextNsPtr, flags); Tcl_DecrRefCount(namePtr); return var; } static Tcl_Var ObjFindNamespaceVar( Tcl_Interp *interp, /* The interpreter in which to find the * variable. */ Tcl_Obj *namePtr, /* Variable's name. If it starts with "::", * will be looked up in global namespace. * Else, looked up first in contextNsPtr * (current namespace if contextNsPtr is * NULL), then in global namespace. */ Tcl_Namespace *contextNsPtr,/* Ignored if TCL_GLOBAL_ONLY flag set. * Otherwise, points to namespace in which to * resolve name. If NULL, look up name in the * current namespace. */ int flags) /* An OR'd combination of: * TCL_AVOID_RESOLVERS, TCL_GLOBAL_ONLY (look * up name only in global namespace), * TCL_NAMESPACE_ONLY (look up only in * contextNsPtr, or the current namespace if * contextNsPtr is NULL), and * TCL_LEAVE_ERR_MSG. If both TCL_GLOBAL_ONLY * and TCL_NAMESPACE_ONLY are given, * TCL_GLOBAL_ONLY is ignored. */ { Interp *iPtr = (Interp *) interp; ResolverScheme *resPtr; Namespace *nsPtr[2], *cxtNsPtr; const char *simpleName; Var *varPtr; int search; int result; Tcl_Var var; Tcl_Obj *simpleNamePtr; const char *name = TclGetString(namePtr); /* * If this namespace has a variable resolver, then give it first crack at * the variable resolution. It may return a Tcl_Var value, it may signal * to continue onward, or it may signal an error. */ if ((flags & TCL_GLOBAL_ONLY) != 0) { cxtNsPtr = (Namespace *) TclGetGlobalNamespace(interp); } else if (contextNsPtr != NULL) { cxtNsPtr = (Namespace *) contextNsPtr; } else { cxtNsPtr = (Namespace *) TclGetCurrentNamespace(interp); } if (!(flags & TCL_AVOID_RESOLVERS) && (cxtNsPtr->varResProc != NULL || iPtr->resolverPtr != NULL)) { resPtr = iPtr->resolverPtr; if (cxtNsPtr->varResProc) { result = cxtNsPtr->varResProc(interp, name, (Tcl_Namespace *) cxtNsPtr, flags, &var); } else { result = TCL_CONTINUE; } while (result == TCL_CONTINUE && resPtr) { if (resPtr->varResProc) { result = resPtr->varResProc(interp, name, (Tcl_Namespace *) cxtNsPtr, flags, &var); } resPtr = resPtr->nextPtr; } if (result == TCL_OK) { return var; } else if (result != TCL_CONTINUE) { return NULL; } } /* * Find the namespace(s) that contain the variable. */ if (!(flags & TCL_GLOBAL_ONLY)) { flags |= TCL_NAMESPACE_ONLY; } TclGetNamespaceForQualName(interp, name, (Namespace *) contextNsPtr, flags, &nsPtr[0], &nsPtr[1], &cxtNsPtr, &simpleName); /* * Look for the variable in the variable table of its namespace. Be sure * to check both possible search paths: from the specified namespace * context and from the global namespace. */ varPtr = NULL; if (simpleName != name) { simpleNamePtr = Tcl_NewStringObj(simpleName, -1); } else { simpleNamePtr = namePtr; } for (search = 0; (search < 2) && (varPtr == NULL); search++) { if ((nsPtr[search] != NULL) && (simpleName != NULL)) { varPtr = VarHashFindVar(&nsPtr[search]->varTable, simpleNamePtr); } } if (simpleName != name) { Tcl_DecrRefCount(simpleNamePtr); } if ((varPtr == NULL) && (flags & TCL_LEAVE_ERR_MSG)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown variable \"%s\"", name)); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "VARIABLE", name, (char *)NULL); } return (Tcl_Var) varPtr; } /* *---------------------------------------------------------------------- * * InfoVarsCmd -- (moved over from tclCmdIL.c) * * Called to implement the "info vars" command that returns the list of * variables in the interpreter that match an optional pattern. The * pattern, if any, consists of an optional sequence of namespace names * separated by "::" qualifiers, which is followed by a glob-style * pattern that restricts which variables are returned. Handles the * following syntax: * * info vars ?pattern? * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ int TclInfoVarsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; const char *varName, *pattern, *simplePattern; Tcl_HashSearch search; Var *varPtr; Namespace *nsPtr; Namespace *currNsPtr = (Namespace *) Tcl_GetCurrentNamespace(interp); Tcl_Obj *listPtr, *elemObjPtr, *varNamePtr; int specificNsInPattern = 0;/* Init. to avoid compiler warning. */ Tcl_Obj *simplePatternPtr = NULL; /* * Get the pattern and find the "effective namespace" in which to list * variables. We only use this effective namespace if there's no active * Tcl procedure frame. */ if (objc == 1) { simplePattern = NULL; nsPtr = currNsPtr; specificNsInPattern = 0; } else if (objc == 2) { /* * From the pattern, get the effective namespace and the simple * pattern (no namespace qualifiers or ::'s) at the end. If an error * was found while parsing the pattern, return it. Otherwise, if the * namespace wasn't found, just leave nsPtr NULL: we will return an * empty list since no variables there can be found. */ Namespace *dummy1NsPtr, *dummy2NsPtr; pattern = TclGetString(objv[1]); TclGetNamespaceForQualName(interp, pattern, NULL, /*flags*/ 0, &nsPtr, &dummy1NsPtr, &dummy2NsPtr, &simplePattern); if (nsPtr != NULL) { /* We successfully found the pattern's ns. */ specificNsInPattern = (strcmp(simplePattern, pattern) != 0); if (simplePattern == pattern) { simplePatternPtr = objv[1]; } else { simplePatternPtr = Tcl_NewStringObj(simplePattern, -1); } Tcl_IncrRefCount(simplePatternPtr); } } else { Tcl_WrongNumArgs(interp, 1, objv, "?pattern?"); return TCL_ERROR; } /* * If the namespace specified in the pattern wasn't found, just return. */ if (nsPtr == NULL) { return TCL_OK; } listPtr = Tcl_NewListObj(0, NULL); if (!HasLocalVars(iPtr->varFramePtr) || specificNsInPattern) { /* * There is no frame pointer, the frame pointer was pushed only to * activate a namespace, or we are in a procedure call frame but a * specific namespace was specified. Create a list containing only the * variables in the effective namespace's variable table. */ if (simplePattern && TclMatchIsTrivial(simplePattern)) { /* * If we can just do hash lookups, that simplifies things a lot. */ varPtr = VarHashFindVar(&nsPtr->varTable, simplePatternPtr); if (varPtr) { if (!TclIsVarUndefined(varPtr) || TclIsVarNamespaceVar(varPtr)) { if (specificNsInPattern) { TclNewObj(elemObjPtr); Tcl_GetVariableFullName(interp, (Tcl_Var) varPtr, elemObjPtr); } else { elemObjPtr = VarHashGetKey(varPtr); } Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr); } } } else { /* * Have to scan the tables of variables. */ varPtr = VarHashFirstVar(&nsPtr->varTable, &search); while (varPtr) { if (!TclIsVarUndefined(varPtr) || TclIsVarNamespaceVar(varPtr)) { varNamePtr = VarHashGetKey(varPtr); varName = TclGetString(varNamePtr); if ((simplePattern == NULL) || Tcl_StringMatch(varName, simplePattern)) { if (specificNsInPattern) { TclNewObj(elemObjPtr); Tcl_GetVariableFullName(interp, (Tcl_Var) varPtr, elemObjPtr); } else { elemObjPtr = varNamePtr; } Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr); } } varPtr = VarHashNextVar(&search); } } } else if (iPtr->varFramePtr->procPtr != NULL) { AppendLocals(interp, listPtr, simplePatternPtr, 1, 0); } if (simplePatternPtr) { Tcl_DecrRefCount(simplePatternPtr); } Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * InfoGlobalsCmd -- (moved over from tclCmdIL.c) * * Called to implement the "info globals" command that returns the list * of global variables matching an optional pattern. Handles the * following syntax: * * info globals ?pattern? * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ int TclInfoGlobalsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *varName, *pattern; Namespace *globalNsPtr = (Namespace *) Tcl_GetGlobalNamespace(interp); Tcl_HashSearch search; Var *varPtr; Tcl_Obj *listPtr, *varNamePtr, *patternPtr; if (objc == 1) { pattern = NULL; } else if (objc == 2) { pattern = TclGetString(objv[1]); /* * Strip leading global-namespace qualifiers. [Bug 1057461] */ if (pattern[0] == ':' && pattern[1] == ':') { while (*pattern == ':') { pattern++; } } } else { Tcl_WrongNumArgs(interp, 1, objv, "?pattern?"); return TCL_ERROR; } /* * Scan through the global :: namespace's variable table and create a list * of all global variables that match the pattern. */ listPtr = Tcl_NewListObj(0, NULL); if (pattern != NULL && TclMatchIsTrivial(pattern)) { if (pattern == TclGetString(objv[1])) { patternPtr = objv[1]; } else { patternPtr = Tcl_NewStringObj(pattern, -1); } Tcl_IncrRefCount(patternPtr); varPtr = VarHashFindVar(&globalNsPtr->varTable, patternPtr); if (varPtr) { if (!TclIsVarUndefined(varPtr)) { Tcl_ListObjAppendElement(interp, listPtr, VarHashGetKey(varPtr)); } } Tcl_DecrRefCount(patternPtr); } else { for (varPtr = VarHashFirstVar(&globalNsPtr->varTable, &search); varPtr != NULL; varPtr = VarHashNextVar(&search)) { if (TclIsVarUndefined(varPtr)) { continue; } varNamePtr = VarHashGetKey(varPtr); varName = TclGetString(varNamePtr); if ((pattern == NULL) || Tcl_StringMatch(varName, pattern)) { Tcl_ListObjAppendElement(interp, listPtr, varNamePtr); } } } Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclInfoLocalsCmd -- (moved over from tclCmdIl.c) * * Called to implement the "info locals" command to return a list of * local variables that match an optional pattern. Handles the following * syntax: * * info locals ?pattern? * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ int TclInfoLocalsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; Tcl_Obj *patternPtr, *listPtr; if (objc == 1) { patternPtr = NULL; } else if (objc == 2) { patternPtr = objv[1]; } else { Tcl_WrongNumArgs(interp, 1, objv, "?pattern?"); return TCL_ERROR; } if (!HasLocalVars(iPtr->varFramePtr)) { return TCL_OK; } /* * Return a list containing names of first the compiled locals (i.e. the * ones stored in the call frame), then the variables in the local hash * table (if one exists). */ listPtr = Tcl_NewListObj(0, NULL); AppendLocals(interp, listPtr, patternPtr, 0, 0); Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * TclInfoConstsCmd -- * * Called to implement the "info consts" command that returns the list of * constants in the interpreter that match an optional pattern. The * pattern, if any, consists of an optional sequence of namespace names * separated by "::" qualifiers, which is followed by a glob-style * pattern that restricts which variables are returned. Handles the * following syntax: * * info consts ?pattern? * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ int TclInfoConstsCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Interp *iPtr = (Interp *) interp; const char *varName, *pattern, *simplePattern; Tcl_HashSearch search; Var *varPtr; Namespace *nsPtr; Namespace *globalNsPtr = (Namespace *) Tcl_GetGlobalNamespace(interp); Namespace *currNsPtr = (Namespace *) Tcl_GetCurrentNamespace(interp); Tcl_Obj *listPtr, *elemObjPtr, *varNamePtr; int specificNsInPattern = 0;/* Init. to avoid compiler warning. */ Tcl_Obj *simplePatternPtr = NULL; /* * Get the pattern and find the "effective namespace" in which to list * variables. We only use this effective namespace if there's no active * Tcl procedure frame. */ if (objc == 1) { simplePattern = NULL; nsPtr = currNsPtr; specificNsInPattern = 0; } else if (objc == 2) { /* * From the pattern, get the effective namespace and the simple * pattern (no namespace qualifiers or ::'s) at the end. If an error * was found while parsing the pattern, return it. Otherwise, if the * namespace wasn't found, just leave nsPtr NULL: we will return an * empty list since no variables there can be found. */ Namespace *dummy1NsPtr, *dummy2NsPtr; pattern = TclGetString(objv[1]); TclGetNamespaceForQualName(interp, pattern, NULL, /*flags*/ 0, &nsPtr, &dummy1NsPtr, &dummy2NsPtr, &simplePattern); if (nsPtr != NULL) { /* We successfully found the pattern's ns. */ specificNsInPattern = (strcmp(simplePattern, pattern) != 0); if (simplePattern == pattern) { simplePatternPtr = objv[1]; } else { simplePatternPtr = Tcl_NewStringObj(simplePattern, -1); } Tcl_IncrRefCount(simplePatternPtr); } } else { Tcl_WrongNumArgs(interp, 1, objv, "?pattern?"); return TCL_ERROR; } /* * If the namespace specified in the pattern wasn't found, just return. */ if (nsPtr == NULL) { return TCL_OK; } listPtr = Tcl_NewListObj(0, NULL); if (!HasLocalVars(iPtr->varFramePtr) || specificNsInPattern) { /* * There is no frame pointer, the frame pointer was pushed only to * activate a namespace, or we are in a procedure call frame but a * specific namespace was specified. Create a list containing only the * variables in the effective namespace's variable table. */ if (simplePattern && TclMatchIsTrivial(simplePattern)) { /* * If we can just do hash lookups, that simplifies things a lot. */ varPtr = VarHashFindVar(&nsPtr->varTable, simplePatternPtr); if (varPtr && TclIsVarConstant(varPtr)) { if (!TclIsVarUndefined(varPtr) || TclIsVarNamespaceVar(varPtr)) { if (specificNsInPattern) { TclNewObj(elemObjPtr); Tcl_GetVariableFullName(interp, (Tcl_Var) varPtr, elemObjPtr); } else { elemObjPtr = VarHashGetKey(varPtr); } Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr); } } else if ((nsPtr != globalNsPtr) && !specificNsInPattern) { varPtr = VarHashFindVar(&globalNsPtr->varTable, simplePatternPtr); if (varPtr && TclIsVarConstant(varPtr)) { if (!TclIsVarUndefined(varPtr) || TclIsVarNamespaceVar(varPtr)) { Tcl_ListObjAppendElement(interp, listPtr, VarHashGetKey(varPtr)); } } } } else { /* * Have to scan the tables of variables. */ varPtr = VarHashFirstVar(&nsPtr->varTable, &search); while (varPtr) { if (TclIsVarConstant(varPtr) && (!TclIsVarUndefined(varPtr) || TclIsVarNamespaceVar(varPtr))) { varNamePtr = VarHashGetKey(varPtr); varName = TclGetString(varNamePtr); if ((simplePattern == NULL) || Tcl_StringMatch(varName, simplePattern)) { if (specificNsInPattern) { TclNewObj(elemObjPtr); Tcl_GetVariableFullName(interp, (Tcl_Var) varPtr, elemObjPtr); } else { elemObjPtr = varNamePtr; } Tcl_ListObjAppendElement(interp, listPtr, elemObjPtr); } } varPtr = VarHashNextVar(&search); } /* * If the effective namespace isn't the global :: namespace, and a * specific namespace wasn't requested in the pattern (i.e., the * pattern only specifies variable names), then add in all global * :: variables that match the simple pattern. Of course, add in * only those variables that aren't hidden by a variable in the * effective namespace. */ if ((nsPtr != globalNsPtr) && !specificNsInPattern) { varPtr = VarHashFirstVar(&globalNsPtr->varTable, &search); while (varPtr) { if (TclIsVarConstant(varPtr) && (!TclIsVarUndefined(varPtr) || TclIsVarNamespaceVar(varPtr))) { varNamePtr = VarHashGetKey(varPtr); varName = TclGetString(varNamePtr); if ((simplePattern == NULL) || Tcl_StringMatch(varName, simplePattern)) { if (VarHashFindVar(&nsPtr->varTable, varNamePtr) == NULL) { Tcl_ListObjAppendElement(interp, listPtr, varNamePtr); } } } varPtr = VarHashNextVar(&search); } } } } else if (iPtr->varFramePtr->procPtr != NULL) { AppendLocals(interp, listPtr, simplePatternPtr, 1, 1); } if (simplePatternPtr) { Tcl_DecrRefCount(simplePatternPtr); } Tcl_SetObjResult(interp, listPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * * AppendLocals -- * * Append the local variables for the current frame to the specified list * object. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int ContextObjectContainsConstant( Tcl_ObjectContext context, Tcl_Obj *varNamePtr) { /* * Helper for AppendLocals to check if an object contains a variable * that is a constant. It's too complicated without factoring this * check out! */ Object *oPtr = (Object *) Tcl_ObjectContextObject(context); Namespace *nsPtr = (Namespace *) oPtr->namespacePtr; Var *varPtr = VarHashFindVar(&nsPtr->varTable, varNamePtr); return !TclIsVarUndefined(varPtr) && TclIsVarConstant(varPtr); } static void AppendLocals( Tcl_Interp *interp, /* Current interpreter. */ Tcl_Obj *listPtr, /* List object to append names to. */ Tcl_Obj *patternPtr, /* Pattern to match against. */ int includeLinks, /* 1 if upvars should be included, else 0. */ int justConstants) /* 1 if just constants should be included. */ { Interp *iPtr = (Interp *) interp; Var *varPtr; Tcl_Size i, localVarCt; int added; Tcl_Obj *objNamePtr; const char *varName; TclVarHashTable *localVarTablePtr; Tcl_HashSearch search; Tcl_HashTable addedTable; const char *pattern = patternPtr? TclGetString(patternPtr) : NULL; localVarCt = iPtr->varFramePtr->numCompiledLocals; varPtr = iPtr->varFramePtr->compiledLocals; localVarTablePtr = iPtr->varFramePtr->varTablePtr; if (includeLinks) { Tcl_InitObjHashTable(&addedTable); } if (localVarCt > 0) { Tcl_Obj **varNamePtr = &iPtr->varFramePtr->localCachePtr->varName0; for (i = 0; i < localVarCt; i++, varNamePtr++) { /* * Skip nameless (temporary) variables and undefined variables. */ if (*varNamePtr && !TclIsVarUndefined(varPtr) && (includeLinks || !TclIsVarLink(varPtr))) { varName = TclGetString(*varNamePtr); if ((pattern == NULL) || Tcl_StringMatch(varName, pattern)) { if (!justConstants || TclIsVarConstant(varPtr)) { Tcl_ListObjAppendElement(interp, listPtr, *varNamePtr); } if (includeLinks) { Tcl_CreateHashEntry(&addedTable, *varNamePtr, &added); } } } varPtr++; } } /* * Do nothing if no local variables. */ if (localVarTablePtr == NULL) { goto objectVars; } /* * Check for the simple and fast case. */ if ((pattern != NULL) && TclMatchIsTrivial(pattern)) { varPtr = VarHashFindVar(localVarTablePtr, patternPtr); if (varPtr != NULL) { if (!TclIsVarUndefined(varPtr) && (includeLinks || !TclIsVarLink(varPtr))) { if ((!justConstants || TclIsVarConstant(varPtr))) { Tcl_ListObjAppendElement(interp, listPtr, VarHashGetKey(varPtr)); } if (includeLinks) { Tcl_CreateHashEntry(&addedTable, VarHashGetKey(varPtr), &added); } } } goto objectVars; } /* * Scan over and process all local variables. */ for (varPtr = VarHashFirstVar(localVarTablePtr, &search); varPtr != NULL; varPtr = VarHashNextVar(&search)) { if (!TclIsVarUndefined(varPtr) && (includeLinks || !TclIsVarLink(varPtr))) { objNamePtr = VarHashGetKey(varPtr); varName = TclGetString(objNamePtr); if ((pattern == NULL) || Tcl_StringMatch(varName, pattern)) { if (!justConstants || TclIsVarConstant(varPtr)) { Tcl_ListObjAppendElement(interp, listPtr, objNamePtr); } if (includeLinks) { Tcl_CreateHashEntry(&addedTable, objNamePtr, &added); } } } } objectVars: if (!includeLinks) { return; } if (iPtr->varFramePtr->isProcCallFrame & FRAME_IS_METHOD) { Tcl_ObjectContext context = (Tcl_ObjectContext) iPtr->varFramePtr->clientData; Method *mPtr = (Method *) Tcl_ObjectContextMethod(context); PrivateVariableMapping *privatePtr; if (mPtr->declaringObjectPtr) { Object *oPtr = mPtr->declaringObjectPtr; FOREACH(objNamePtr, oPtr->variables) { Tcl_CreateHashEntry(&addedTable, objNamePtr, &added); if (justConstants && !ContextObjectContainsConstant(context, objNamePtr)) { continue; } if (added && (!pattern || Tcl_StringMatch(TclGetString(objNamePtr), pattern))) { Tcl_ListObjAppendElement(interp, listPtr, objNamePtr); } } FOREACH_STRUCT(privatePtr, oPtr->privateVariables) { Tcl_CreateHashEntry(&addedTable, privatePtr->variableObj, &added); if (justConstants && !ContextObjectContainsConstant(context, privatePtr->fullNameObj)) { continue; } if (added && (!pattern || Tcl_StringMatch(TclGetString(privatePtr->variableObj), pattern))) { Tcl_ListObjAppendElement(interp, listPtr, privatePtr->variableObj); } } } else { Class *clsPtr = mPtr->declaringClassPtr; FOREACH(objNamePtr, clsPtr->variables) { Tcl_CreateHashEntry(&addedTable, objNamePtr, &added); if (justConstants && !ContextObjectContainsConstant(context, objNamePtr)) { continue; } if (added && (!pattern || Tcl_StringMatch(TclGetString(objNamePtr), pattern))) { Tcl_ListObjAppendElement(interp, listPtr, objNamePtr); } } FOREACH_STRUCT(privatePtr, clsPtr->privateVariables) { Tcl_CreateHashEntry(&addedTable, privatePtr->variableObj, &added); if (justConstants && !ContextObjectContainsConstant(context, privatePtr->fullNameObj)) { continue; } if (added && (!pattern || Tcl_StringMatch(TclGetString(privatePtr->variableObj), pattern))) { Tcl_ListObjAppendElement(interp, listPtr, privatePtr->variableObj); } } } } Tcl_DeleteHashTable(&addedTable); } /* *---------------------------------------------------------------------- * * TclInfoConstantCmd -- * * Called to implement the "info constant" command that tests whether a * specific variable is a constant. Handles the following syntax: * * info constant varName * * Results: * Returns TCL_OK if successful and TCL_ERROR if there is an error. * * Side effects: * Returns a result in the interpreter's result object. If there is an * error, the result is an error message. * *---------------------------------------------------------------------- */ int TclInfoConstantCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Var *varPtr, *arrayPtr; int result; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "varName"); return TCL_ERROR; } varPtr = TclObjLookupVar(interp, objv[1], NULL, 0, "lookup", 0, 0, &arrayPtr); result = (varPtr && TclIsVarConstant(varPtr)); Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result)); return TCL_OK; } /* * Hash table implementation - first, just copy and adapt the obj key stuff */ void TclInitVarHashTable( TclVarHashTable *tablePtr, Namespace *nsPtr) { Tcl_InitCustomHashTable(&tablePtr->table, TCL_CUSTOM_TYPE_KEYS, &tclVarHashKeyType); tablePtr->nsPtr = nsPtr; tablePtr->arrayPtr = NULL; } static Tcl_HashEntry * AllocVarEntry( TCL_UNUSED(Tcl_HashTable *), void *keyPtr) /* Key to store in the hash table entry. */ { Tcl_Obj *objPtr = (Tcl_Obj *)keyPtr; Tcl_HashEntry *hPtr; Var *varPtr; varPtr = (Var *)Tcl_Alloc(sizeof(VarInHash)); varPtr->flags = VAR_IN_HASHTABLE; varPtr->value.objPtr = NULL; VarHashRefCount(varPtr) = 1; hPtr = &(((VarInHash *) varPtr)->entry); Tcl_SetHashValue(hPtr, varPtr); hPtr->key.objPtr = objPtr; Tcl_IncrRefCount(objPtr); return hPtr; } static void FreeVarEntry( Tcl_HashEntry *hPtr) { Var *varPtr = VarHashGetValue(hPtr); Tcl_Obj *objPtr = hPtr->key.objPtr; if (TclIsVarUndefined(varPtr) && !TclIsVarTraced(varPtr) && (VarHashRefCount(varPtr) == 1)) { Tcl_Free(varPtr); } else { VarHashInvalidateEntry(varPtr); TclSetVarUndefined(varPtr); VarHashRefCount(varPtr)--; } Tcl_DecrRefCount(objPtr); } static int CompareVarKeys( void *keyPtr, /* New key to compare. */ Tcl_HashEntry *hPtr) /* Existing key to compare. */ { Tcl_Obj *objPtr1 = (Tcl_Obj *)keyPtr; Tcl_Obj *objPtr2 = hPtr->key.objPtr; const char *p1, *p2; size_t l1, l2; /* * If the object pointers are the same then they match. * OPT: this comparison was moved to the caller * * if (objPtr1 == objPtr2) return 1; */ /* * Don't use Tcl_GetStringFromObj as it would prevent l1 and l2 being in a * register. */ p1 = TclGetString(objPtr1); l1 = objPtr1->length; p2 = TclGetString(objPtr2); l2 = objPtr2->length; /* * Only compare string representations of the same length. */ return ((l1 == l2) && !memcmp(p1, p2, l1)); } /*---------------------------------------------------------------------- * * ArrayDefaultCmd -- * * This function implements the 'array default' Tcl command. * Refer to the user documentation for details on what it does. * * Results: * Returns a standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ static int ArrayDefaultCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const options[] = { "get", "set", "exists", "unset", NULL }; enum arrayDefaultOptionsEnum { OPT_GET, OPT_SET, OPT_EXISTS, OPT_UNSET } option; Tcl_Obj *arrayNameObj, *defaultValueObj; Var *varPtr, *arrayPtr; int isArray; /* * Parse arguments. */ if (objc != 3 && objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "option arrayName ?value?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], options, "option", 0, &option) != TCL_OK) { return TCL_ERROR; } arrayNameObj = objv[2]; if (TCL_ERROR == LocateArray(interp, arrayNameObj, &varPtr, &isArray)) { return TCL_ERROR; } switch (option) { case OPT_GET: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "arrayName"); return TCL_ERROR; } if (!varPtr || TclIsVarUndefined(varPtr) || !isArray) { return NotArrayError(interp, arrayNameObj); } defaultValueObj = TclGetArrayDefault(varPtr); if (!defaultValueObj) { /* Array default must exist. */ Tcl_SetObjResult(interp, Tcl_NewStringObj( "array has no default value", -1)); Tcl_SetErrorCode(interp, "TCL", "READ", "ARRAY", "DEFAULT", (char *)NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, defaultValueObj); return TCL_OK; case OPT_SET: if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "arrayName value"); return TCL_ERROR; } /* * Attempt to create array if needed. */ varPtr = TclObjLookupVarEx(interp, arrayNameObj, NULL, /*flags*/ TCL_LEAVE_ERR_MSG, /*msg*/ "array default set", /*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr); if (varPtr == NULL) { return TCL_ERROR; } if (arrayPtr) { /* * Not a valid array name. */ CleanupVar(varPtr, arrayPtr); TclObjVarErrMsg(interp, arrayNameObj, NULL, "array default set", NEEDARRAY, -1); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "VARNAME", TclGetString(arrayNameObj), (char *)NULL); return TCL_ERROR; } if (!TclIsVarArray(varPtr) && !TclIsVarUndefined(varPtr)) { /* * Not an array. */ TclObjVarErrMsg(interp, arrayNameObj, NULL, "array default set", NEEDARRAY, -1); Tcl_SetErrorCode(interp, "TCL", "WRITE", "ARRAY", (char *)NULL); return TCL_ERROR; } if (!TclIsVarArray(varPtr)) { TclInitArrayVar(varPtr); } defaultValueObj = objv[3]; SetArrayDefault(varPtr, defaultValueObj); return TCL_OK; case OPT_EXISTS: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "arrayName"); return TCL_ERROR; } /* * Undefined variables (whether or not they have storage allocated) do * not have defaults, and this is not an error case. */ if (!varPtr || TclIsVarUndefined(varPtr)) { Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0)); } else if (!isArray) { return NotArrayError(interp, arrayNameObj); } else { defaultValueObj = TclGetArrayDefault(varPtr); Tcl_SetObjResult(interp, Tcl_NewBooleanObj(!!defaultValueObj)); } return TCL_OK; case OPT_UNSET: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "arrayName"); return TCL_ERROR; } if (varPtr && !TclIsVarUndefined(varPtr)) { if (!isArray) { return NotArrayError(interp, arrayNameObj); } SetArrayDefault(varPtr, NULL); } return TCL_OK; } /* Unreached */ return TCL_ERROR; } /* * Initialize array variable. */ void TclInitArrayVar( Var *arrayPtr) { ArrayVarHashTable *tablePtr = (ArrayVarHashTable *)Tcl_Alloc(sizeof(ArrayVarHashTable)); /* * Mark the variable as an array. */ TclSetVarArray(arrayPtr); /* * Regular TclVarHashTable initialization. */ arrayPtr->value.tablePtr = (TclVarHashTable *) tablePtr; TclInitVarHashTable(arrayPtr->value.tablePtr, TclGetVarNsPtr(arrayPtr)); arrayPtr->value.tablePtr->arrayPtr = arrayPtr; /* * Default value initialization. */ tablePtr->defaultObj = NULL; } /* * Cleanup array variable. */ static void DeleteArrayVar( Var *arrayPtr) { ArrayVarHashTable *tablePtr = (ArrayVarHashTable *) arrayPtr->value.tablePtr; /* * Default value cleanup. */ SetArrayDefault(arrayPtr, NULL); /* * Regular TclVarHashTable cleanup. */ VarHashDeleteTable(arrayPtr->value.tablePtr); Tcl_Free(tablePtr); } /* * Get array default value if any. */ Tcl_Obj * TclGetArrayDefault( Var *arrayPtr) { ArrayVarHashTable *tablePtr = (ArrayVarHashTable *) arrayPtr->value.tablePtr; return tablePtr->defaultObj; } /* * Set/replace/unset array default value. */ static void SetArrayDefault( Var *arrayPtr, Tcl_Obj *defaultObj) { ArrayVarHashTable *tablePtr = (ArrayVarHashTable *) arrayPtr->value.tablePtr; /* * Increment/decrement refcount twice to ensure that the object is shared, * so that it doesn't get modified accidentally by the folling code: * * array default set v 1 * lappend v(a) 2; # returns a new object {1 2} * set v(b); # returns the original default object "1" */ if (tablePtr->defaultObj) { Tcl_DecrRefCount(tablePtr->defaultObj); Tcl_DecrRefCount(tablePtr->defaultObj); } tablePtr->defaultObj = defaultObj; if (tablePtr->defaultObj) { Tcl_IncrRefCount(tablePtr->defaultObj); Tcl_IncrRefCount(tablePtr->defaultObj); } } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclZipfs.c0000644000175000017500000054310214731032403015124 0ustar sergeisergei/* * tclZipfs.c -- * * Implementation of the ZIP filesystem used in TIP 430 * Adapted from the implementation for AndroWish. * * Copyright © 2016-2017 Sean Woods * Copyright © 2013-2015 Christian Werner * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * * This file is distributed in two ways: * generic/tclZipfs.c file in the TIP430-enabled Tcl cores. * compat/tclZipfs.c file in the tclconfig (TEA) file system, for pre-tip430 * projects. * * Helpful docs: * https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.9.TXT * https://libzip.org/specifications/appnote_iz.txt */ #include "tclInt.h" #include "tclFileSystem.h" #include #ifndef _WIN32 #include #endif /* _WIN32*/ #ifndef MAP_FILE #define MAP_FILE 0 #endif /* !MAP_FILE */ #define NOBYFOUR #ifndef TBLS #define TBLS 1 #endif #if !defined(_WIN32) && !defined(NO_DLFCN_H) #include #endif /* * Macros to report errors only if an interp is present. */ #define ZIPFS_ERROR(interp,errstr) \ do { \ if (interp) { \ Tcl_SetObjResult(interp, Tcl_NewStringObj(errstr, -1)); \ } \ } while (0) #define ZIPFS_MEM_ERROR(interp) \ do { \ if (interp) { \ Tcl_SetObjResult(interp, Tcl_NewStringObj( \ "out of memory", -1)); \ Tcl_SetErrorCode(interp, "TCL", "MALLOC", (char *)NULL); \ } \ } while (0) #define ZIPFS_POSIX_ERROR(interp,errstr) \ do { \ if (interp) { \ Tcl_SetObjResult(interp, Tcl_ObjPrintf( \ "%s: %s", errstr, Tcl_PosixError(interp))); \ } \ } while (0) #define ZIPFS_ERROR_CODE(interp,errcode) \ do { \ if (interp) { \ Tcl_SetErrorCode(interp, \ "TCL", "ZIPFS", errcode, (char *)NULL); \ } \ } while (0) #include "zlib.h" #include "crypt.h" #include "zutil.h" #include "crc32.h" static const z_crc_t* crc32tab; /* ** We are compiling as part of the core. ** TIP430 style zipfs prefix */ #define ZIPFS_VOLUME "//zipfs:/" #define ZIPFS_ROOTDIR_DEPTH 3 /* Number of / in root mount */ #define ZIPFS_VOLUME_LEN 9 #define ZIPFS_APP_MOUNT ZIPFS_VOLUME "app" #define ZIPFS_ZIP_MOUNT ZIPFS_VOLUME "lib/tcl" #define ZIPFS_FALLBACK_ENCODING "cp437" /* * Various constants and offsets found in ZIP archive files */ #define ZIP_SIG_LEN 4 /* * Local header of ZIP archive member (at very beginning of each member). * C can't express this structure type even close to portably (thanks for * nothing, Clang and MSVC). */ enum ZipLocalEntryOffsets { ZIP_LOCAL_SIG_OFFS = 0, /* sig field offset */ ZIP_LOCAL_VERSION_OFFS = 4, /* version field offset */ ZIP_LOCAL_FLAGS_OFFS = 6, /* flags field offset */ ZIP_LOCAL_COMPMETH_OFFS = 8, /* compMethod field offset */ ZIP_LOCAL_MTIME_OFFS = 10, /* modTime field offset */ ZIP_LOCAL_MDATE_OFFS = 12, /* modDate field offset */ ZIP_LOCAL_CRC32_OFFS = 14, /* crc32 field offset */ ZIP_LOCAL_COMPLEN_OFFS = 18, /* compLen field offset */ ZIP_LOCAL_UNCOMPLEN_OFFS = 22, /* uncompLen field offset */ ZIP_LOCAL_PATHLEN_OFFS = 26, /* pathLen field offset */ ZIP_LOCAL_EXTRALEN_OFFS = 28, /* extraLen field offset */ ZIP_LOCAL_HEADER_LEN = 30 /* header part length */ }; #if 0 /* Recent enough GCC can do this. */ #define PACKED_LITTLE_ENDIAN \ __attribute__((packed, scalar_storage_order("little-endian"))) #else #undef PACKED_LITTLE_ENDIAN /* Really don't support this yet! */ #endif #ifdef PACKED_LITTLE_ENDIAN /* * Local header of ZIP archive member (at very beginning of each member). */ struct PACKED_LITTLE_ENDIAN ZipLocalEntry { uint32_t sig; // == ZIP_LOCAL_HEADER_SIG uint16_t version; uint16_t flags; uint16_t compMethod; uint16_t modTime; uint16_t modDate; uint32_t crc32; uint32_t compLen; uint32_t uncompLen; uint16_t pathLen; uint16_t extraLen; }; #endif #define ZIP_LOCAL_HEADER_SIG 0x04034b50 enum ZipLocalFlags { ZIP_LOCAL_FLAGS_UTF8 = 0x0800 }; /* * Central header of ZIP archive member at end of ZIP file. * C can't express this structure type even close to portably (thanks for * nothing, Clang and MSVC). */ enum ZipCentralEntryOffsets { ZIP_CENTRAL_SIG_OFFS = 0, /* sig field offset */ ZIP_CENTRAL_VERSIONMADE_OFFS = 4, /* versionMade field offset */ ZIP_CENTRAL_VERSION_OFFS = 6, /* version field offset */ ZIP_CENTRAL_FLAGS_OFFS = 8, /* flags field offset */ ZIP_CENTRAL_COMPMETH_OFFS = 10, /* compMethod field offset */ ZIP_CENTRAL_MTIME_OFFS = 12, /* modTime field offset */ ZIP_CENTRAL_MDATE_OFFS = 14, /* modDate field offset */ ZIP_CENTRAL_CRC32_OFFS = 16, /* crc32 field offset */ ZIP_CENTRAL_COMPLEN_OFFS = 20, /* compLen field offset */ ZIP_CENTRAL_UNCOMPLEN_OFFS = 24, /* uncompLen field offset */ ZIP_CENTRAL_PATHLEN_OFFS = 28, /* pathLen field offset */ ZIP_CENTRAL_EXTRALEN_OFFS = 30, /* extraLen field offset */ ZIP_CENTRAL_FCOMMENTLEN_OFFS = 32, /* commentLen field offset */ ZIP_CENTRAL_DISKFILE_OFFS = 34, /* diskFile field offset */ ZIP_CENTRAL_IATTR_OFFS = 36, /* intAttr field offset */ ZIP_CENTRAL_EATTR_OFFS = 38, /* extAttr field offset */ ZIP_CENTRAL_LOCALHDR_OFFS = 42, /* localHeaderOffset field offset */ ZIP_CENTRAL_HEADER_LEN = 46 /* header part length */ }; #ifdef PACKED_LITTLE_ENDIAN /* * Central header of ZIP archive member at end of ZIP file. */ struct PACKED_LITTLE_ENDIAN ZipCentralEntry { uint32_t sig; // == ZIP_CENTRAL_HEADER_SIG uint16_t versionMade; uint16_t version; uint16_t flags; uint16_t compMethod; uint16_t modTime; uint16_t modDate; uint32_t crc32; uint32_t compLen; uint32_t uncompLen; uint16_t pathLen; uint16_t extraLen; uint16_t commentLen; uint16_t diskFile; uint16_t intAttr; uint32_t extAttr; uint32_t localHeaderOffset; }; #endif #define ZIP_CENTRAL_HEADER_SIG 0x02014b50 /* * Central end signature at very end of ZIP file. * C can't express this structure type even close to portably (thanks for * nothing, Clang and MSVC). */ enum ZipCentralMainOffsets { ZIP_CENTRAL_END_SIG_OFFS = 0, /* sig field offset */ ZIP_CENTRAL_DISKNO_OFFS = 4, /* diskNum field offset */ ZIP_CENTRAL_DISKDIR_OFFS = 6, /* diskDir field offset */ ZIP_CENTRAL_ENTS_OFFS = 8, /* entriesOffset field offset */ ZIP_CENTRAL_TOTALENTS_OFFS = 10, /* totalEntries field offset */ ZIP_CENTRAL_DIRSIZE_OFFS = 12, /* dirSize field offset */ ZIP_CENTRAL_DIRSTART_OFFS = 16, /* dirStart field offset */ ZIP_CENTRAL_COMMENTLEN_OFFS = 20, /* commentLen field offset */ ZIP_CENTRAL_END_LEN = 22 /* header part length */ }; #ifdef PACKED_LITTLE_ENDIAN /* * Central end signature at very end of ZIP file. */ struct PACKED_LITTLE_ENDIAN ZipCentralMain { uint32_t sig; // == ZIP_CENTRAL_END_SIG uint16_t diskNum; uint16_t diskDir; uint16_t entriesOffset; uint16_t totalEntries; uint32_t dirSize; uint32_t dirStart; uint16_t commentLen; } #endif #define ZIP_CENTRAL_END_SIG 0x06054b50 #define ZIP_MIN_VERSION 20 enum ZipCompressionMethods { ZIP_COMPMETH_STORED = 0, ZIP_COMPMETH_DEFLATED = 8 }; #define ZIP_PASSWORD_END_SIG 0x5a5a4b50 #define ZIP_CRYPT_HDR_LEN 12 #define ZIP_MAX_FILE_SIZE INT_MAX #define DEFAULT_WRITE_MAX_SIZE ZIP_MAX_FILE_SIZE /* * Mutex to protect localtime(3) when no reentrant version available. */ #if !defined(_WIN32) && !defined(HAVE_LOCALTIME_R) && TCL_THREADS TCL_DECLARE_MUTEX(localtimeMutex) #endif /* !_WIN32 && !HAVE_LOCALTIME_R && TCL_THREADS */ /* * Forward declaration. */ struct ZipEntry; /* * In-core description of mounted ZIP archive file. */ typedef struct ZipFile { char *name; /* Archive name */ size_t nameLength; /* Length of archive name */ char isMemBuffer; /* When true, not a file but a memory buffer */ Tcl_Channel chan; /* Channel handle or NULL */ unsigned char *data; /* Memory mapped or malloc'ed file */ size_t length; /* Length of memory mapped file */ void *ptrToFree; /* Non-NULL if malloc'ed file */ size_t numFiles; /* Number of files in archive */ size_t baseOffset; /* Archive start */ size_t passOffset; /* Password start */ size_t directoryOffset; /* Archive directory start */ size_t directorySize; /* Size of archive directory */ unsigned char passBuf[264]; /* Password buffer */ size_t numOpen; /* Number of open files on archive */ struct ZipEntry *entries; /* List of files in archive */ struct ZipEntry *topEnts; /* List of top-level dirs in archive */ char *mountPoint; /* Mount point name */ Tcl_Size mountPointLen; /* Length of mount point name */ #ifdef _WIN32 HANDLE mountHandle; /* Handle used for direct file access. */ #endif /* _WIN32 */ } ZipFile; /* * In-core description of file contained in mounted ZIP archive. */ typedef struct ZipEntry { char *name; /* The full pathname of the virtual file */ ZipFile *zipFilePtr; /* The ZIP file holding this virtual file */ size_t offset; /* Data offset into memory mapped ZIP file */ int numBytes; /* Uncompressed size of the virtual file. * -1 for zip64 */ int numCompressedBytes; /* Compressed size of the virtual file. * -1 for zip64 */ int compressMethod; /* Compress method */ int isDirectory; /* 0 if file, 1 if directory, -1 if root */ int depth; /* Number of slashes in path. */ int crc32; /* CRC-32 as stored in ZIP */ int timestamp; /* Modification time */ int isEncrypted; /* True if data is encrypted */ int flags; /* See ZipEntryFlags for bit definitions. */ unsigned char *data; /* File data if written */ struct ZipEntry *next; /* Next file in the same archive */ struct ZipEntry *tnext; /* Next top-level dir in archive */ } ZipEntry; enum ZipEntryFlags { ZE_F_CRC_COMPARED = 1, /* If 1, the CRC has been compared. */ ZE_F_CRC_CORRECT = 2, /* Only meaningful if ZE_F_CRC_COMPARED is 1 */ ZE_F_VOLUME = 4 /* Entry corresponds to //zipfs:/ */ }; /* * File channel for file contained in mounted ZIP archive. * * Regarding data buffers: * For READ-ONLY files that are not encrypted and not compressed (zip STORE * method), ubuf points directly to the mapped zip file data in memory. No * additional storage is allocated and so ubufToFree is NULL. * * In all other combinations of compression and encryption or if channel is * writable, storage is allocated for the decrypted and/or uncompressed data * and a pointer to it is stored in ubufToFree and ubuf. When channel is * closed, ubufToFree is freed if not NULL. ubuf is irrelevant since it may * or may not point to allocated storage as above. */ typedef struct ZipChannel { ZipFile *zipFilePtr; /* The ZIP file holding this channel */ ZipEntry *zipEntryPtr; /* Pointer back to virtual file */ Tcl_Size maxWrite; /* Maximum size for write */ Tcl_Size numBytes; /* Number of bytes of uncompressed data */ Tcl_Size cursor; /* Seek position for next read or write*/ unsigned char *ubuf; /* Pointer to the uncompressed data */ unsigned char *ubufToFree; /* NULL if ubuf points to memory that does not * need freeing. Else memory to free (ubuf * may point *inside* the block) */ Tcl_Size ubufSize; /* Size of allocated ubufToFree */ int iscompr; /* True if data is compressed */ int isDirectory; /* Set to 1 if directory, or -1 if root */ int isEncrypted; /* True if data is encrypted */ int mode; /* O_WRITE, O_APPEND, O_TRUNC etc.*/ unsigned long keys[3]; /* Key for decryption */ } ZipChannel; static inline int ZipChannelWritable( ZipChannel *info) { return (info->mode & (O_WRONLY | O_RDWR)) != 0; } /* * Global variables. * * Most are kept in single ZipFS struct. When build with threading support * this struct is protected by the ZipFSMutex (see below). * * The "fileHash" component is the process-wide global table of all known ZIP * archive members in all mounted ZIP archives. * * The "zipHash" components is the process wide global table of all mounted * ZIP archive files. */ static struct { int initialized; /* True when initialized */ int lock; /* RW lock, see below */ int waiters; /* RW lock, see below */ int wrmax; /* Maximum write size of a file; only written * to from Tcl code in a trusted interpreter, * so NOT protected by mutex. */ char *fallbackEntryEncoding;/* The fallback encoding for ZIP entries when * they are believed to not be UTF-8; only * written to from Tcl code in a trusted * interpreter, so not protected by mutex. */ int idCount; /* Counter for channel names */ Tcl_HashTable fileHash; /* File name to ZipEntry mapping */ Tcl_HashTable zipHash; /* Mount to ZipFile mapping */ } ZipFS = { 0, 0, 0, DEFAULT_WRITE_MAX_SIZE, NULL, 0, {0,{0,0,0,0},0,0,0,0,0,0,0,0,0}, {0,{0,0,0,0},0,0,0,0,0,0,0,0,0} }; /* * For password rotation. */ static const char pwrot[17] = "\x00\x80\x40\xC0\x20\xA0\x60\xE0" "\x10\x90\x50\xD0\x30\xB0\x70\xF0"; static const char *zipfs_literal_tcl_library = NULL; /* Function prototypes */ static int CopyImageFile(Tcl_Interp *interp, const char *imgName, Tcl_Channel out); static int DescribeMounted(Tcl_Interp *interp, const char *mountPoint); static int InitReadableChannel(Tcl_Interp *interp, ZipChannel *info, ZipEntry *z); static int InitWritableChannel(Tcl_Interp *interp, ZipChannel *info, ZipEntry *z, int trunc); static int ListMountPoints(Tcl_Interp *interp); static int ContainsMountPoint(const char *path, int pathLen); static void CleanupMount(ZipFile *zf); static Tcl_Obj * ScriptLibrarySetup(const char *dirName); static void SerializeCentralDirectoryEntry( const unsigned char *start, const unsigned char *end, unsigned char *buf, ZipEntry *z, size_t nameLength, long long dataStartOffset); static void SerializeCentralDirectorySuffix( const unsigned char *start, const unsigned char *end, unsigned char *buf, int entryCount, long long dataStartOffset, long long directoryStartOffset, long long suffixStartOffset); static void SerializeLocalEntryHeader( const unsigned char *start, const unsigned char *end, unsigned char *buf, ZipEntry *z, int nameLength, int align); static int IsCryptHeaderValid(ZipEntry *z, unsigned char cryptHdr[ZIP_CRYPT_HDR_LEN]); static int DecodeCryptHeader(Tcl_Interp *interp, ZipEntry *z, unsigned long keys[3], unsigned char cryptHdr[ZIP_CRYPT_HDR_LEN]); #if !defined(STATIC_BUILD) static int ZipfsAppHookFindTclInit(const char *archive); #endif static int ZipFSPathInFilesystemProc(Tcl_Obj *pathPtr, void **clientDataPtr); static Tcl_Obj * ZipFSFilesystemPathTypeProc(Tcl_Obj *pathPtr); static Tcl_Obj * ZipFSFilesystemSeparatorProc(Tcl_Obj *pathPtr); static int ZipFSStatProc(Tcl_Obj *pathPtr, Tcl_StatBuf *buf); static int ZipFSAccessProc(Tcl_Obj *pathPtr, int mode); static Tcl_Channel ZipFSOpenFileChannelProc(Tcl_Interp *interp, Tcl_Obj *pathPtr, int mode, int permissions); static int ZipFSMatchInDirectoryProc(Tcl_Interp *interp, Tcl_Obj *result, Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types); static void ZipFSMatchMountPoints(Tcl_Obj *result, Tcl_Obj *normPathPtr, const char *pattern, Tcl_DString *prefix); static Tcl_Obj * ZipFSListVolumesProc(void); static const char *const *ZipFSFileAttrStringsProc(Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); static int ZipFSFileAttrsGetProc(Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); static int ZipFSFileAttrsSetProc(Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj *objPtr); static int ZipFSLoadFile(Tcl_Interp *interp, Tcl_Obj *path, Tcl_LoadHandle *loadHandle, Tcl_FSUnloadFileProc **unloadProcPtr, int flags); static int ZipMapArchive(Tcl_Interp *interp, ZipFile *zf, void *handle); static void ZipfsSetup(void); static int ZipChannelClose(void *instanceData, Tcl_Interp *interp, int flags); static Tcl_DriverGetHandleProc ZipChannelGetFile; static int ZipChannelRead(void *instanceData, char *buf, int toRead, int *errloc); static long long ZipChannelWideSeek(void *instanceData, long long offset, int mode, int *errloc); static void ZipChannelWatchChannel(void *instanceData, int mask); static int ZipChannelWrite(void *instanceData, const char *buf, int toWrite, int *errloc); /* * Define the ZIP filesystem dispatch table. */ static const Tcl_Filesystem zipfsFilesystem = { "zipfs", sizeof(Tcl_Filesystem), TCL_FILESYSTEM_VERSION_2, ZipFSPathInFilesystemProc, NULL, /* dupInternalRepProc */ NULL, /* freeInternalRepProc */ NULL, /* internalToNormalizedProc */ NULL, /* createInternalRepProc */ NULL, /* normalizePathProc */ ZipFSFilesystemPathTypeProc, ZipFSFilesystemSeparatorProc, ZipFSStatProc, ZipFSAccessProc, ZipFSOpenFileChannelProc, ZipFSMatchInDirectoryProc, NULL, /* utimeProc */ NULL, /* linkProc */ ZipFSListVolumesProc, ZipFSFileAttrStringsProc, ZipFSFileAttrsGetProc, ZipFSFileAttrsSetProc, NULL, /* createDirectoryProc */ NULL, /* removeDirectoryProc */ NULL, /* deleteFileProc */ NULL, /* copyFileProc */ NULL, /* renameFileProc */ NULL, /* copyDirectoryProc */ NULL, /* lstatProc */ (Tcl_FSLoadFileProc *) (void *) ZipFSLoadFile, NULL, /* getCwdProc */ NULL, /* chdirProc */ }; /* * The channel type/driver definition used for ZIP archive members. */ static const Tcl_ChannelType zipChannelType = { "zip", TCL_CHANNEL_VERSION_5, NULL, /* Deprecated. */ ZipChannelRead, ZipChannelWrite, NULL, /* Deprecated. */ NULL, /* Set options proc. */ NULL, /* Get options proc. */ ZipChannelWatchChannel, ZipChannelGetFile, ZipChannelClose, NULL, /* Set blocking mode for raw channel. */ NULL, /* Function to flush channel. */ NULL, /* Function to handle bubbled events. */ ZipChannelWideSeek, NULL, /* Thread action function. */ NULL, /* Truncate function. */ }; /* *------------------------------------------------------------------------ * * TclIsZipfsPath -- * * Checks if the passed path has a zipfs volume prefix. * * Results: * 0 if not a zipfs path * else the length of the zipfs volume prefix * * Side effects: * None. * *------------------------------------------------------------------------ */ int TclIsZipfsPath( const char *path) { #ifdef _WIN32 return strncmp(path, ZIPFS_VOLUME, ZIPFS_VOLUME_LEN) ? 0 : ZIPFS_VOLUME_LEN; #else int i; for (i = 0; i < ZIPFS_VOLUME_LEN; ++i) { if (path[i] != ZIPFS_VOLUME[i] && (path[i] != '\\' || ZIPFS_VOLUME[i] != '/')) { return 0; } } return ZIPFS_VOLUME_LEN; #endif } /* *------------------------------------------------------------------------- * * ZipReadInt, ZipReadShort, ZipWriteInt, ZipWriteShort -- * * Inline functions to read and write little-endian 16 and 32 bit * integers from/to buffers representing parts of ZIP archives. * * These take bufferStart and bufferEnd pointers, which are used to * maintain a guarantee that out-of-bounds accesses don't happen when * reading or writing critical directory structures. * *------------------------------------------------------------------------- */ static inline unsigned int ZipReadInt( const unsigned char *bufferStart, const unsigned char *bufferEnd, const unsigned char *ptr) { if (ptr < bufferStart || ptr + 4 > bufferEnd) { Tcl_Panic("out of bounds read(4): start=%p, end=%p, ptr=%p", bufferStart, bufferEnd, ptr); } return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | ((unsigned int)ptr[3] << 24); } static inline unsigned short ZipReadShort( const unsigned char *bufferStart, const unsigned char *bufferEnd, const unsigned char *ptr) { if (ptr < bufferStart || ptr + 2 > bufferEnd) { Tcl_Panic("out of bounds read(2): start=%p, end=%p, ptr=%p", bufferStart, bufferEnd, ptr); } return ptr[0] | (ptr[1] << 8); } static inline void ZipWriteInt( const unsigned char *bufferStart, const unsigned char *bufferEnd, unsigned char *ptr, unsigned int value) { if (ptr < bufferStart || ptr + 4 > bufferEnd) { Tcl_Panic("out of bounds write(4): start=%p, end=%p, ptr=%p", bufferStart, bufferEnd, ptr); } ptr[0] = value & 0xff; ptr[1] = (value >> 8) & 0xff; ptr[2] = (value >> 16) & 0xff; ptr[3] = (value >> 24) & 0xff; } static inline void ZipWriteShort( const unsigned char *bufferStart, const unsigned char *bufferEnd, unsigned char *ptr, unsigned short value) { if (ptr < bufferStart || ptr + 2 > bufferEnd) { Tcl_Panic("out of bounds write(2): start=%p, end=%p, ptr=%p", bufferStart, bufferEnd, ptr); } ptr[0] = value & 0xff; ptr[1] = (value >> 8) & 0xff; } /* *------------------------------------------------------------------------- * * ReadLock, WriteLock, Unlock -- * * POSIX like rwlock functions to support multiple readers and single * writer on internal structs. * * Limitations: * - a read lock cannot be promoted to a write lock * - a write lock may not be nested * *------------------------------------------------------------------------- */ TCL_DECLARE_MUTEX(ZipFSMutex) #if TCL_THREADS static Tcl_Condition ZipFSCond; static inline void ReadLock(void) { Tcl_MutexLock(&ZipFSMutex); while (ZipFS.lock < 0) { ZipFS.waiters++; Tcl_ConditionWait(&ZipFSCond, &ZipFSMutex, NULL); ZipFS.waiters--; } ZipFS.lock++; Tcl_MutexUnlock(&ZipFSMutex); } static inline void WriteLock(void) { Tcl_MutexLock(&ZipFSMutex); while (ZipFS.lock != 0) { ZipFS.waiters++; Tcl_ConditionWait(&ZipFSCond, &ZipFSMutex, NULL); ZipFS.waiters--; } ZipFS.lock = -1; Tcl_MutexUnlock(&ZipFSMutex); } static inline void Unlock(void) { Tcl_MutexLock(&ZipFSMutex); if (ZipFS.lock > 0) { --ZipFS.lock; } else if (ZipFS.lock < 0) { ZipFS.lock = 0; } if ((ZipFS.lock == 0) && (ZipFS.waiters > 0)) { Tcl_ConditionNotify(&ZipFSCond); } Tcl_MutexUnlock(&ZipFSMutex); } #else /* !TCL_THREADS */ #define ReadLock() do {} while (0) #define WriteLock() do {} while (0) #define Unlock() do {} while (0) #endif /* TCL_THREADS */ /* *------------------------------------------------------------------------- * * DosTimeDate, ToDosTime, ToDosDate -- * * Functions to perform conversions between DOS time stamps and POSIX * time_t. * *------------------------------------------------------------------------- */ static time_t DosTimeDate( int dosDate, int dosTime) { struct tm tm; time_t ret; memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; /* let mktime() deal with DST */ tm.tm_year = ((dosDate & 0xfe00) >> 9) + 80; tm.tm_mon = ((dosDate & 0x1e0) >> 5) - 1; tm.tm_mday = dosDate & 0x1f; tm.tm_hour = (dosTime & 0xf800) >> 11; tm.tm_min = (dosTime & 0x7e0) >> 5; tm.tm_sec = (dosTime & 0x1f) << 1; ret = mktime(&tm); if (ret == (time_t) -1) { /* fallback to 1980-01-01T00:00:00+00:00 (DOS epoch) */ ret = (time_t) 315532800; } return ret; } static int ToDosTime( time_t when) { struct tm *tmp, tm; #if !TCL_THREADS || defined(_WIN32) /* Not threaded, or on Win32 which uses thread local storage */ tmp = localtime(&when); tm = *tmp; #elif defined(HAVE_LOCALTIME_R) /* Threaded, have reentrant API */ tmp = &tm; localtime_r(&when, tmp); #else /* TCL_THREADS && !_WIN32 && !HAVE_LOCALTIME_R */ /* Only using a mutex is safe. */ Tcl_MutexLock(&localtimeMutex); tmp = localtime(&when); tm = *tmp; Tcl_MutexUnlock(&localtimeMutex); #endif return (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); } static int ToDosDate( time_t when) { struct tm *tmp, tm; #if !TCL_THREADS || defined(_WIN32) /* Not threaded, or on Win32 which uses thread local storage */ tmp = localtime(&when); tm = *tmp; #elif /* TCL_THREADS && !_WIN32 && */ defined(HAVE_LOCALTIME_R) /* Threaded, have reentrant API */ tmp = &tm; localtime_r(&when, tmp); #else /* TCL_THREADS && !_WIN32 && !HAVE_LOCALTIME_R */ /* Only using a mutex is safe. */ Tcl_MutexLock(&localtimeMutex); tmp = localtime(&when); tm = *tmp; Tcl_MutexUnlock(&localtimeMutex); #endif return ((tm.tm_year - 80) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday; } /* *------------------------------------------------------------------------- * * CountSlashes -- * * This function counts the number of slashes in a pathname string. * * Results: * Number of slashes found in string. * * Side effects: * None. * *------------------------------------------------------------------------- */ static inline size_t CountSlashes( const char *string) { size_t count = 0; const char *p = string; while (*p != '\0') { if (*p == '/') { count++; } p++; } return count; } /* *------------------------------------------------------------------------ * * IsCryptHeaderValid -- * * Computes the validity of the encryption header CRC for a ZipEntry. * * Results: * Returns 1 if the header is valid else 0. * * Side effects: * None. * *------------------------------------------------------------------------ */ static int IsCryptHeaderValid( ZipEntry *z, unsigned char cryptHeader[ZIP_CRYPT_HDR_LEN]) { /* * There are multiple possibilities. The last one or two bytes of the * encryption header should match the last one or two bytes of the * CRC of the file. Or the last byte of the encryption header should * be the high order byte of the file time. Depending on the archiver * and version, any of the might be in used. We follow libzip in checking * only one byte against both the crc and the time. Note that by design * the check generates high number of false positives in any case. * Also, in case a check is passed when it should not, the final CRC * calculation will (should) catch it. Only difference is it will be * reported as a corruption error instead of incorrect password. */ int dosTime = ToDosTime(z->timestamp); if (cryptHeader[11] == (unsigned char)(dosTime >> 8)) { /* Infozip style - Tested with test-password.zip */ return 1; } /* DOS time did not match, may be CRC does */ if (z->crc32) { /* Pkware style - Tested with test-password2.zip */ return (cryptHeader[11] == (unsigned char)(z->crc32 >> 24)); } /* No CRC, no way to verify. Assume valid */ return 1; } /* *------------------------------------------------------------------------ * * DecodeCryptHeader -- * * Decodes the crypt header and validates it. * * Results: * TCL_OK on success, TCL_ERROR on failure. * * Side effects: * On success, keys[] are updated. On failure, an error message is * left in interp if not NULL. * *------------------------------------------------------------------------ */ static int DecodeCryptHeader( Tcl_Interp *interp, ZipEntry *z, unsigned long keys[3], /* Updated on success. Must have been * initialized by caller. */ unsigned char cryptHeader[ZIP_CRYPT_HDR_LEN]) /* From zip file content */ { int i; int ch; int len = z->zipFilePtr->passBuf[0] & 0xFF; char passBuf[260]; for (i = 0; i < len; i++) { ch = z->zipFilePtr->passBuf[len - i]; passBuf[i] = (ch & 0x0f) | pwrot[(ch >> 4) & 0x0f]; } passBuf[i] = '\0'; init_keys(passBuf, keys, crc32tab); memset(passBuf, 0, sizeof(passBuf)); unsigned char encheader[ZIP_CRYPT_HDR_LEN]; memcpy(encheader, cryptHeader, ZIP_CRYPT_HDR_LEN); for (i = 0; i < ZIP_CRYPT_HDR_LEN; i++) { ch = cryptHeader[i]; ch ^= decrypt_byte(keys, crc32tab); encheader[i] = ch; update_keys(keys, crc32tab, ch); } if (!IsCryptHeaderValid(z, encheader)) { ZIPFS_ERROR(interp, "invalid password"); ZIPFS_ERROR_CODE(interp, "PASSWORD"); return TCL_ERROR; } return TCL_OK; } /* *------------------------------------------------------------------------- * * DecodeZipEntryText -- * * Given a sequence of bytes from an entry in a ZIP central directory, * convert that into a Tcl string. This is complicated because we don't * actually know what encoding is in use! So we try to use UTF-8, and if * that goes wrong, we fall back to a user-specified encoding, or to an * encoding we specify (Windows code page 437), or to ISO 8859-1 if * absolutely nothing else works. * * During Tcl startup, we skip the user-specified encoding and cp437, as * we may well not have any loadable encodings yet. Tcl's own library * files ought to be using ASCII filenames. * * Results: * The decoded filename; the filename is owned by the argument DString. * * Side effects: * Updates dstPtr. * *------------------------------------------------------------------------- */ static char * DecodeZipEntryText( const unsigned char *inputBytes, unsigned int inputLength, Tcl_DString *dstPtr) /* Must have been initialized by caller! */ { Tcl_Encoding encoding; const char *src; char *dst; int dstLen, srcLen = inputLength, flags; Tcl_EncodingState state; if (inputLength < 1) { return Tcl_DStringValue(dstPtr); } /* * We can't use Tcl_ExternalToUtfDString at this point; it has no way to * fail. So we use this modified version of it that can report encoding * errors to us (so we can fall back to something else). * * The utf-8 encoding is implemented internally, and so is guaranteed to * be present. */ src = (const char *) inputBytes; dst = Tcl_DStringValue(dstPtr); dstLen = dstPtr->spaceAvl - 1; flags = TCL_ENCODING_START | TCL_ENCODING_END; /* Special flag! */ while (1) { int srcRead, dstWrote; int result = Tcl_ExternalToUtf(NULL, tclUtf8Encoding, src, srcLen, flags, &state, dst, dstLen, &srcRead, &dstWrote, NULL); int soFar = dst + dstWrote - Tcl_DStringValue(dstPtr); if (result == TCL_OK) { Tcl_DStringSetLength(dstPtr, soFar); return Tcl_DStringValue(dstPtr); } else if (result != TCL_CONVERT_NOSPACE) { break; } flags &= ~TCL_ENCODING_START; src += srcRead; srcLen -= srcRead; if (Tcl_DStringLength(dstPtr) == 0) { Tcl_DStringSetLength(dstPtr, dstLen); } Tcl_DStringSetLength(dstPtr, 2 * Tcl_DStringLength(dstPtr) + 1); dst = Tcl_DStringValue(dstPtr) + soFar; dstLen = Tcl_DStringLength(dstPtr) - soFar - 1; } /* * Something went wrong. Fall back to another encoding. Those *can* use * Tcl_ExternalToUtfDString(). */ encoding = NULL; if (ZipFS.fallbackEntryEncoding) { encoding = Tcl_GetEncoding(NULL, ZipFS.fallbackEntryEncoding); } if (!encoding) { encoding = Tcl_GetEncoding(NULL, ZIPFS_FALLBACK_ENCODING); } if (!encoding) { /* * Fallback to internal encoding that always converts all bytes. * Should only happen when a filename isn't UTF-8 and we've not got * our encodings initialised for some reason. */ encoding = Tcl_GetEncoding(NULL, "iso8859-1"); } char *converted = Tcl_ExternalToUtfDString(encoding, (const char *) inputBytes, inputLength, dstPtr); Tcl_FreeEncoding(encoding); return converted; } /* *------------------------------------------------------------------------ * * NormalizeMountPoint -- * * Converts the passed path into a normalized zipfs mount point * of the form //zipfs:/some/path. On Windows any \ path separators * are converted to /. * * Mount points with a volume will raise an error unless the volume is * zipfs root. Thus D:/foo is not a valid mount point. * * Relative paths and absolute paths without a volume are mapped under * the zipfs root. * * The empty string is mapped to the zipfs root. * * dsPtr is initialized by the function and must be cleared by caller * on a successful return. * * Results: * TCL_OK on success with normalized mount path in dsPtr * TCL_ERROR on fail with error message in interp if not NULL * *------------------------------------------------------------------------ */ static int NormalizeMountPoint( Tcl_Interp *interp, const char *mountPath, Tcl_DString *dsPtr) /* Must be initialized by caller! */ { const char *joiner[2]; char *joinedPath; Tcl_Obj *unnormalizedObj; Tcl_Obj *normalizedObj; const char *normalizedPath; Tcl_Size normalizedLen; Tcl_DString dsJoin; /* * Several things need to happen here * - Absolute paths containing volumes (drive letter or UNC) raise error * except of course if the volume is zipfs root * - \ -> / and // -> / conversions (except if UNC which is error) * - . and .. have to be dealt with * The first is explicitly checked, the others are dealt with a * combination file join and normalize. Easier than doing it ourselves * and not performance sensitive anyways. */ joiner[0] = ZIPFS_VOLUME; joiner[1] = mountPath; Tcl_DStringInit(&dsJoin); joinedPath = Tcl_JoinPath(2, joiner, &dsJoin); /* Now joinedPath has all \ -> / and // -> / (except UNC) converted. */ if (!strncmp(ZIPFS_VOLUME, joinedPath, ZIPFS_VOLUME_LEN)) { unnormalizedObj = Tcl_DStringToObj(&dsJoin); } else { if (joinedPath[0] != '/' || joinedPath[1] == '/') { /* mount path was D:/x, D:x or //unc */ goto invalidMountPath; } unnormalizedObj = Tcl_ObjPrintf(ZIPFS_VOLUME "%s", joinedPath + 1); } Tcl_IncrRefCount(unnormalizedObj); normalizedObj = Tcl_FSGetNormalizedPath(interp, unnormalizedObj); if (normalizedObj == NULL) { Tcl_DecrRefCount(unnormalizedObj); goto errorReturn; } Tcl_IncrRefCount(normalizedObj); /* BEFORE DecrRefCount on unnormalizedObj */ Tcl_DecrRefCount(unnormalizedObj); /* normalizedObj owned by Tcl!! Do NOT DecrRef without an IncrRef */ normalizedPath = TclGetStringFromObj(normalizedObj, &normalizedLen); Tcl_DStringFree(&dsJoin); Tcl_DStringAppend(dsPtr, normalizedPath, normalizedLen); Tcl_DecrRefCount(normalizedObj); return TCL_OK; invalidMountPath: if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Invalid mount path \"%s\"", mountPath)); ZIPFS_ERROR_CODE(interp, "MOUNT_PATH"); } errorReturn: Tcl_DStringFree(&dsJoin); return TCL_ERROR; } /* *------------------------------------------------------------------------ * * MapPathToZipfs -- * * Maps a path as stored in a zip archive to its normalized location * under a given zipfs mount point. Relative paths and Unix style * absolute paths go directly under the mount point. Volume relative * paths and absolute paths that have a volume (drive or UNC) are * stripped of the volume before joining the mount point. * * Results: * Pointer to normalized path. * * Side effects: * Stores mapped path in dsPtr. * *------------------------------------------------------------------------ */ static char * MapPathToZipfs( Tcl_Interp *interp, const char *mountPath, /* Must be fully normalized */ const char *path, /* Archive content path to map */ Tcl_DString *dsPtr) /* Must be initialized and cleared * by caller */ { const char *joiner[2]; char *joinedPath; Tcl_Obj *unnormalizedObj; Tcl_Obj *normalizedObj; const char *normalizedPath; Tcl_Size normalizedLen; Tcl_DString dsJoin; assert(TclIsZipfsPath(mountPath)); joiner[0] = mountPath; joiner[1] = path; #ifndef _WIN32 /* On Unix C:/foo/bat is not treated as absolute by JoinPath so check ourself */ if (path[0] && path[1] == ':') { joiner[1] += 2; } #endif Tcl_DStringInit(&dsJoin); joinedPath = Tcl_JoinPath(2, joiner, &dsJoin); if (strncmp(ZIPFS_VOLUME, joinedPath, ZIPFS_VOLUME_LEN)) { /* path was not relative. Strip off the volume (e.g. UNC) */ Tcl_Size numParts; const char **partsPtr; Tcl_SplitPath(path, &numParts, &partsPtr); Tcl_DStringFree(&dsJoin); partsPtr[0] = mountPath; (void)Tcl_JoinPath(numParts, partsPtr, &dsJoin); Tcl_Free(partsPtr); } unnormalizedObj = Tcl_DStringToObj(&dsJoin); /* Also resets dsJoin */ Tcl_IncrRefCount(unnormalizedObj); normalizedObj = Tcl_FSGetNormalizedPath(interp, unnormalizedObj); if (normalizedObj == NULL) { /* Should not happen but continue... */ normalizedObj = unnormalizedObj; } Tcl_IncrRefCount(normalizedObj); /* BEFORE DecrRefCount on unnormalizedObj */ Tcl_DecrRefCount(unnormalizedObj); /* normalizedObj owned by Tcl!! Do NOT DecrRef without an IncrRef */ normalizedPath = TclGetStringFromObj(normalizedObj, &normalizedLen); Tcl_DStringAppend(dsPtr, normalizedPath, normalizedLen); Tcl_DecrRefCount(normalizedObj); return Tcl_DStringValue(dsPtr); } /* *------------------------------------------------------------------------- * * ZipFSLookup -- * * This function returns the ZIP entry struct corresponding to the ZIP * archive member of the given file name. Caller must hold the right * lock. * * Results: * Returns the pointer to ZIP entry struct or NULL if the the given file * name could not be found in the global list of ZIP archive members. * * Side effects: * None. * *------------------------------------------------------------------------- */ static inline ZipEntry * ZipFSLookup( const char *filename) { Tcl_HashEntry *hPtr; ZipEntry *z = NULL; hPtr = Tcl_FindHashEntry(&ZipFS.fileHash, filename); if (hPtr) { z = (ZipEntry *) Tcl_GetHashValue(hPtr); } return z; } /* *------------------------------------------------------------------------- * * ZipFSLookupZip -- * * This function gets the structure for a mounted ZIP archive. * * Results: * Returns a pointer to the structure, or NULL if the file is ZIP file is * unknown/not mounted. * * Side effects: * None. * *------------------------------------------------------------------------- */ static inline ZipFile * ZipFSLookupZip( const char *mountPoint) { Tcl_HashEntry *hPtr; ZipFile *zf = NULL; hPtr = Tcl_FindHashEntry(&ZipFS.zipHash, mountPoint); if (hPtr) { zf = (ZipFile *) Tcl_GetHashValue(hPtr); } return zf; } /* *------------------------------------------------------------------------ * * ContainsMountPoint -- * * Check if there is a mount point anywhere under the specified path. * Although the function will work for any path, for efficiency reasons * it should be called only after checking ZipFSLookup does not find * the path. * * Caller must hold read lock before calling. * * Results: * 1 - there is at least one mount point under the path * 0 - otherwise * * Side effects: * None. * *------------------------------------------------------------------------ */ static int ContainsMountPoint( const char *path, int pathLen) { Tcl_HashEntry *hPtr; Tcl_HashSearch search; if (ZipFS.zipHash.numEntries == 0) { return 0; } if (pathLen < 0) { pathLen = strlen(path); } /* * We are looking for the case where the path is //zipfs:/a/b * and there is a mount point //zipfs:/a/b/c/.. below it */ for (hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &search); hPtr; hPtr = Tcl_NextHashEntry(&search)) { ZipFile *zf = (ZipFile *) Tcl_GetHashValue(hPtr); if (zf->mountPointLen == 0) { /* * Enumerate the contents of the ZIP; it's mounted on the root. * TODO - a holdover from androwish? Tcl does not allow mounting * outside of the //zipfs:/ area. */ ZipEntry *z; for (z = zf->topEnts; z; z = z->tnext) { int lenz = (int) strlen(z->name); if ((lenz >= pathLen) && (z->name[pathLen] == '/' || z->name[pathLen] == '\0') && (strncmp(z->name, path, pathLen) == 0)) { return 1; } } } else if ((zf->mountPointLen >= pathLen) && (zf->mountPoint[pathLen] == '/' || zf->mountPoint[pathLen] == '\0' || pathLen == ZIPFS_VOLUME_LEN) && (strncmp(zf->mountPoint, path, pathLen) == 0)) { /* Matched standard mount */ return 1; } } return 0; } /* *------------------------------------------------------------------------- * * AllocateZipFile, AllocateZipEntry, AllocateZipChannel -- * * Allocates the memory for a datastructure. Always ensures that it is * zeroed out for safety. * * Returns: * The allocated structure, or NULL if allocate fails. * * Side effects: * The interpreter result may be written to on error. Which might fail * (for ZipFile) in a low-memory situation. Always panics if ZipEntry * allocation fails. * *------------------------------------------------------------------------- */ static inline ZipFile * AllocateZipFile( Tcl_Interp *interp, size_t mountPointNameLength) { size_t size = sizeof(ZipFile) + mountPointNameLength + 1; ZipFile *zf = (ZipFile *) Tcl_AttemptAlloc(size); if (!zf) { ZIPFS_MEM_ERROR(interp); } else { memset(zf, 0, size); } return zf; } static inline ZipEntry * AllocateZipEntry(void) { ZipEntry *z = (ZipEntry *) Tcl_Alloc(sizeof(ZipEntry)); memset(z, 0, sizeof(ZipEntry)); return z; } static inline ZipChannel * AllocateZipChannel( Tcl_Interp *interp) { ZipChannel *zc = (ZipChannel *) Tcl_AttemptAlloc(sizeof(ZipChannel)); if (!zc) { ZIPFS_MEM_ERROR(interp); } else { memset(zc, 0, sizeof(ZipChannel)); } return zc; } /* *------------------------------------------------------------------------- * * ZipFSCloseArchive -- * * This function closes a mounted ZIP archive file. * * Results: * None. * * Side effects: * A memory mapped ZIP archive is unmapped, allocated memory is released. * The ZipFile pointer is *NOT* deallocated by this function. * *------------------------------------------------------------------------- */ static void ZipFSCloseArchive( Tcl_Interp *interp, /* Current interpreter. */ ZipFile *zf) { if (zf->nameLength) { Tcl_Free(zf->name); } if (zf->isMemBuffer) { /* Pointer to memory */ if (zf->ptrToFree) { Tcl_Free(zf->ptrToFree); zf->ptrToFree = NULL; } zf->data = NULL; return; } /* * Remove the memory mapping, if we have one. */ #ifdef _WIN32 if (zf->data && !zf->ptrToFree) { UnmapViewOfFile(zf->data); zf->data = NULL; } if (zf->mountHandle != INVALID_HANDLE_VALUE) { CloseHandle(zf->mountHandle); } #else /* !_WIN32 */ if ((zf->data != MAP_FAILED) && !zf->ptrToFree) { munmap(zf->data, zf->length); zf->data = (unsigned char *) MAP_FAILED; } #endif /* _WIN32 */ if (zf->ptrToFree) { Tcl_Free(zf->ptrToFree); zf->ptrToFree = NULL; } if (zf->chan) { Tcl_Close(interp, zf->chan); zf->chan = NULL; } } /* *------------------------------------------------------------------------- * * ZipFSFindTOC -- * * This function takes a memory mapped zip file and indexes the contents. * When "needZip" is zero an embedded ZIP archive in an executable file * is accepted. Note that we do not support ZIP64. * * Results: * TCL_OK on success, TCL_ERROR otherwise with an error message placed * into the given "interp" if it is not NULL. * * Side effects: * The given ZipFile struct is filled with information about the ZIP * archive file. On error, ZipFSCloseArchive is called on zf but * it is not freed. * *------------------------------------------------------------------------- */ static int ZipFSFindTOC( Tcl_Interp *interp, /* Current interpreter. NULLable. */ int needZip, ZipFile *zf) { size_t i, minoff; const unsigned char *eocdPtr; /* End of Central Directory Record */ const unsigned char *start = zf->data; const unsigned char *end = zf->data + zf->length; /* * Scan backwards from the end of the file for the signature. This is * necessary because ZIP archives aren't the only things that get tagged * on the end of executables; digital signatures can also go there. */ eocdPtr = zf->data + zf->length - ZIP_CENTRAL_END_LEN; while (eocdPtr >= start) { if (*eocdPtr == (ZIP_CENTRAL_END_SIG & 0xFF)) { if (ZipReadInt(start, end, eocdPtr) == ZIP_CENTRAL_END_SIG) { break; } eocdPtr -= ZIP_SIG_LEN; } else { --eocdPtr; } } if (eocdPtr < zf->data) { /* * Didn't find it (or not enough space for a central directory!); not * a ZIP archive. This might be OK or a problem. */ if (!needZip) { zf->baseOffset = zf->passOffset = zf->length; return TCL_OK; } ZIPFS_ERROR(interp, "archive directory end signature not found"); ZIPFS_ERROR_CODE(interp, "END_SIG"); error: ZipFSCloseArchive(interp, zf); return TCL_ERROR; } /* * eocdPtr -> End of Central Directory (EOCD) record at this point. * Note this is not same as "end of Central Directory" :-) as EOCD * is a record/structure in the ZIP spec terminology */ /* * How many files in the archive? If that's bogus, we're done here. */ zf->numFiles = ZipReadShort(start, end, eocdPtr + ZIP_CENTRAL_ENTS_OFFS); if (zf->numFiles == 0) { if (!needZip) { zf->baseOffset = zf->passOffset = zf->length; return TCL_OK; } ZIPFS_ERROR(interp, "empty archive"); ZIPFS_ERROR_CODE(interp, "EMPTY"); goto error; } /* * The Central Directory (CD) is a series of Central Directory File * Header (CDFH) records preceding the EOCD (but not necessarily * immediately preceding). cdirZipOffset is the offset into the * *archive* to the CD (first CDFH). The size of the CD is given by * cdirSize. NOTE: offset into archive does NOT mean offset into * (zf->data) as other data may precede the archive in the file. */ ptrdiff_t eocdDataOffset = eocdPtr - zf->data; unsigned int cdirZipOffset = ZipReadInt(start, end, eocdPtr + ZIP_CENTRAL_DIRSTART_OFFS); unsigned int cdirSize = ZipReadInt(start, end, eocdPtr + ZIP_CENTRAL_DIRSIZE_OFFS); /* * As computed above, * eocdDataOffset < zf->length. * In addition, the following consistency checks must be met * (1) cdirZipOffset <= eocdDataOffset (to prevent under flow in computation of (2)) * (2) cdirZipOffset + cdirSize <= eocdDataOffset. Else the CD will be overlapping * the EOCD. Note this automatically means cdirZipOffset+cdirSize < zf->length. */ if (!(cdirZipOffset <= (size_t)eocdDataOffset && cdirSize <= eocdDataOffset - cdirZipOffset)) { if (!needZip) { /* Simply point to end od data */ zf->directoryOffset = zf->baseOffset = zf->passOffset = zf->length; return TCL_OK; } ZIPFS_ERROR(interp, "archive directory truncated"); ZIPFS_ERROR_CODE(interp, "NO_DIR"); goto error; } /* * Calculate the offset of the CD in the *data*. If there was no extra * "junk" preceding the archive, this would just be cdirZipOffset but * otherwise we have to account for it. */ if (eocdDataOffset - cdirSize > cdirZipOffset) { zf->baseOffset = eocdDataOffset - cdirSize - cdirZipOffset; } else { zf->baseOffset = 0; } zf->passOffset = zf->baseOffset; zf->directoryOffset = cdirZipOffset + zf->baseOffset; zf->directorySize = cdirSize; /* * Read the central directory. */ const unsigned char *const cdirStart = eocdPtr - cdirSize; /* Start of CD */ const unsigned char *dirEntry; minoff = zf->length; for (dirEntry = cdirStart, i = 0; i < zf->numFiles; i++) { if ((dirEntry-cdirStart) + ZIP_CENTRAL_HEADER_LEN > (ptrdiff_t)zf->directorySize) { ZIPFS_ERROR(interp, "truncated directory"); ZIPFS_ERROR_CODE(interp, "TRUNC_DIR"); goto error; } if (ZipReadInt(start, end, dirEntry) != ZIP_CENTRAL_HEADER_SIG) { ZIPFS_ERROR(interp, "wrong header signature"); ZIPFS_ERROR_CODE(interp, "HDR_SIG"); goto error; } int pathlen = ZipReadShort(start, end, dirEntry + ZIP_CENTRAL_PATHLEN_OFFS); int comlen = ZipReadShort(start, end, dirEntry + ZIP_CENTRAL_FCOMMENTLEN_OFFS); int extra = ZipReadShort(start, end, dirEntry + ZIP_CENTRAL_EXTRALEN_OFFS); size_t localhdr_off = ZipReadInt(start, end, dirEntry + ZIP_CENTRAL_LOCALHDR_OFFS); const unsigned char *localP = zf->data + zf->baseOffset + localhdr_off; if (localP > (cdirStart - ZIP_LOCAL_HEADER_LEN) || ZipReadInt(start, end, localP) != ZIP_LOCAL_HEADER_SIG) { ZIPFS_ERROR(interp, "Failed to find local header"); ZIPFS_ERROR_CODE(interp, "LCL_HDR"); goto error; } if (localhdr_off < minoff) { minoff = localhdr_off; } dirEntry += pathlen + comlen + extra + ZIP_CENTRAL_HEADER_LEN; } if ((dirEntry-cdirStart) < (ptrdiff_t) zf->directorySize) { /* file count and dir size do not match */ ZIPFS_ERROR(interp, "short file count"); ZIPFS_ERROR_CODE(interp, "FILE_COUNT"); goto error; } zf->passOffset = minoff + zf->baseOffset; /* * If there's also an encoded password, extract that too (but don't decode * yet). * TODO - is this even part of the ZIP "standard". The idea of storing * a password with the archive seems absurd, encoded or not. */ unsigned char *q = zf->data + zf->passOffset; if ((zf->passOffset >= 6) && (start < q-4) && (ZipReadInt(start, end, q - 4) == ZIP_PASSWORD_END_SIG)) { const unsigned char *passPtr; i = q[-5]; passPtr = q - 5 - i; if (passPtr >= start && passPtr + i < end) { zf->passBuf[0] = i; memcpy(zf->passBuf + 1, passPtr, i); zf->passOffset -= i ? (5 + i) : 0; } } return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipFSOpenArchive -- * * This function opens a ZIP archive file for reading. An attempt is made * to memory map that file. Otherwise it is read into an allocated memory * buffer. The ZIP archive header is verified and must be valid for the * function to succeed. When "needZip" is zero an embedded ZIP archive in * an executable file is accepted. * * Results: * TCL_OK on success, TCL_ERROR otherwise with an error message placed * into the given "interp" if it is not NULL. On error, ZipFSCloseArchive * is called on zf but it is not freed. * * Side effects: * ZIP archive is memory mapped or read into allocated memory, the given * ZipFile struct is filled with information about the ZIP archive file. * *------------------------------------------------------------------------- */ static int ZipFSOpenArchive( Tcl_Interp *interp, /* Current interpreter. NULLable. */ const char *zipname, /* Path to ZIP file to open. */ int needZip, ZipFile *zf) { size_t i; void *handle; zf->nameLength = 0; zf->isMemBuffer = 0; #ifdef _WIN32 zf->data = NULL; zf->mountHandle = INVALID_HANDLE_VALUE; #else /* !_WIN32 */ zf->data = (unsigned char *) MAP_FAILED; #endif /* _WIN32 */ zf->length = 0; zf->numFiles = 0; zf->baseOffset = zf->passOffset = 0; zf->ptrToFree = NULL; zf->passBuf[0] = 0; /* * Actually open the file. */ zf->chan = Tcl_OpenFileChannel(interp, zipname, "rb", 0); if (!zf->chan) { return TCL_ERROR; } /* * See if we can get the OS handle. If we can, we can use that to memory * map the file, which is nice and efficient. However, it totally depends * on the filename pointing to a real regular OS file. * * Opening real filesystem entities that are not files will lead to an * error. */ if (Tcl_GetChannelHandle(zf->chan, TCL_READABLE, &handle) == TCL_OK) { if (ZipMapArchive(interp, zf, handle) != TCL_OK) { goto error; } } else { /* * Not an OS file, but rather something in a Tcl VFS. Must copy into * memory. */ zf->length = Tcl_Seek(zf->chan, 0, SEEK_END); if (zf->length == (size_t)TCL_INDEX_NONE) { ZIPFS_POSIX_ERROR(interp, "seek error"); goto error; } /* What's the magic about 64 * 1024 * 1024 ? */ if ((zf->length <= ZIP_CENTRAL_END_LEN) || (zf->length - ZIP_CENTRAL_END_LEN) > (64 * 1024 * 1024 - ZIP_CENTRAL_END_LEN)) { ZIPFS_ERROR(interp, "illegal file size"); ZIPFS_ERROR_CODE(interp, "FILE_SIZE"); goto error; } if (Tcl_Seek(zf->chan, 0, SEEK_SET) == -1) { ZIPFS_POSIX_ERROR(interp, "seek error"); goto error; } zf->ptrToFree = zf->data = (unsigned char *) Tcl_AttemptAlloc(zf->length); if (!zf->ptrToFree) { ZIPFS_MEM_ERROR(interp); goto error; } i = Tcl_Read(zf->chan, (char *) zf->data, zf->length); if (i != zf->length) { ZIPFS_POSIX_ERROR(interp, "file read error"); goto error; } } /* * Close the Tcl channel. If the file was mapped, the mapping is * unaffected. It is important to close the channel otherwise there is a * potential chicken and egg issue at finalization time as the channels * are closed before the file systems are dismounted. */ Tcl_Close(interp, zf->chan); zf->chan = NULL; return ZipFSFindTOC(interp, needZip, zf); /* * Handle errors by closing the archive. This includes closing the channel * handle for the archive file. */ error: ZipFSCloseArchive(interp, zf); return TCL_ERROR; } /* *------------------------------------------------------------------------- * * ZipMapArchive -- * * Wrapper around the platform-specific parts of mmap() (and Windows's * equivalent) because it's not part of the standard channel API. * *------------------------------------------------------------------------- */ static int ZipMapArchive( Tcl_Interp *interp, /* Interpreter for error reporting. */ ZipFile *zf, /* The archive descriptor structure. */ void *handle) /* The OS handle to the open archive. */ { #ifdef _WIN32 HANDLE hFile = (HANDLE) handle; int readSuccessful; /* * Determine the file size. */ readSuccessful = GetFileSizeEx(hFile, (PLARGE_INTEGER) &zf->length) != 0; if (!readSuccessful) { Tcl_WinConvertError(GetLastError()); ZIPFS_POSIX_ERROR(interp, "failed to retrieve file size"); return TCL_ERROR; } if (zf->length < ZIP_CENTRAL_END_LEN) { Tcl_SetErrno(EINVAL); ZIPFS_POSIX_ERROR(interp, "truncated file"); return TCL_ERROR; } if (zf->length > TCL_SIZE_MAX) { Tcl_SetErrno(EFBIG); ZIPFS_POSIX_ERROR(interp, "zip archive too big"); return TCL_ERROR; } /* * Map the file. */ zf->mountHandle = CreateFileMappingW(hFile, 0, PAGE_READONLY, 0, zf->length, 0); if (zf->mountHandle == INVALID_HANDLE_VALUE) { Tcl_WinConvertError(GetLastError()); ZIPFS_POSIX_ERROR(interp, "file mapping failed"); return TCL_ERROR; } zf->data = (unsigned char *) MapViewOfFile(zf->mountHandle, FILE_MAP_READ, 0, 0, zf->length); if (!zf->data) { Tcl_WinConvertError(GetLastError()); ZIPFS_POSIX_ERROR(interp, "file mapping failed"); return TCL_ERROR; } #else /* !_WIN32 */ int fd = PTR2INT(handle); /* * Determine the file size. */ zf->length = lseek(fd, 0, SEEK_END); if (zf->length == (size_t)-1) { ZIPFS_POSIX_ERROR(interp, "failed to retrieve file size"); return TCL_ERROR; } if (zf->length < ZIP_CENTRAL_END_LEN) { Tcl_SetErrno(EINVAL); ZIPFS_POSIX_ERROR(interp, "truncated file"); return TCL_ERROR; } lseek(fd, 0, SEEK_SET); zf->data = (unsigned char *) mmap(0, zf->length, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0); if (zf->data == MAP_FAILED) { ZIPFS_POSIX_ERROR(interp, "file mapping failed"); return TCL_ERROR; } #endif /* _WIN32 */ return TCL_OK; } /* *------------------------------------------------------------------------- * * IsPasswordValid -- * * Basic test for whether a passowrd is valid. If the test fails, sets an * error message in the interpreter. * * Returns: * TCL_OK if the test passes, TCL_ERROR if it fails. * *------------------------------------------------------------------------- */ static inline int IsPasswordValid( Tcl_Interp *interp, const char *passwd, size_t pwlen) { if ((pwlen > 255) || strchr(passwd, 0xff)) { ZIPFS_ERROR(interp, "illegal password"); ZIPFS_ERROR_CODE(interp, "BAD_PASS"); return TCL_ERROR; } return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipFSCatalogFilesystem -- * * This function generates the root node for a ZIPFS filesystem by * reading the ZIP's central directory. * * Results: * TCL_OK on success, TCL_ERROR otherwise with an error message placed * into the given "interp" if it is not NULL. On error, frees zf!! * * Side effects: * Will acquire and release the write lock. * *------------------------------------------------------------------------- */ static int ZipFSCatalogFilesystem( Tcl_Interp *interp, /* Current interpreter. NULLable. */ ZipFile *zf, /* Temporary buffer hold archive descriptors */ const char *mountPoint, /* Mount point path. Must be fully normalized */ const char *passwd, /* Password for opening the ZIP, or NULL if * the ZIP is unprotected. */ const char *zipname) /* Path to ZIP file to build a catalog of. */ { int isNew; size_t i, pwlen; ZipFile *zf0; ZipEntry *z; Tcl_HashEntry *hPtr; Tcl_DString ds, fpBuf; unsigned char *q; assert(TclIsZipfsPath(mountPoint)); /* Caller should have normalized */ Tcl_DStringInit(&ds); /* * Basic verification of the password for sanity. */ pwlen = 0; if (passwd) { pwlen = strlen(passwd); if (IsPasswordValid(interp, passwd, pwlen) != TCL_OK) { ZipFSCloseArchive(interp, zf); Tcl_Free(zf); return TCL_ERROR; } } /* * Validate the TOC data. If that's bad, things fall apart. */ if (zf->baseOffset >= zf->length || zf->passOffset >= zf->length || zf->directoryOffset >= zf->length) { ZIPFS_ERROR(interp, "bad zip data"); ZIPFS_ERROR_CODE(interp, "BAD_ZIP"); ZipFSCloseArchive(interp, zf); Tcl_Free(zf); return TCL_ERROR; } WriteLock(); hPtr = Tcl_CreateHashEntry(&ZipFS.zipHash, mountPoint, &isNew); if (!isNew) { if (interp) { zf0 = (ZipFile *) Tcl_GetHashValue(hPtr); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s is already mounted on %s", zf0->name, mountPoint)); ZIPFS_ERROR_CODE(interp, "MOUNTED"); } Unlock(); ZipFSCloseArchive(interp, zf); Tcl_DStringFree(&ds); Tcl_Free(zf); return TCL_ERROR; } /* * Convert to a real archive descriptor. */ zf->mountPoint = (char *) Tcl_GetHashKey(&ZipFS.zipHash, hPtr); zf->mountPointLen = strlen(zf->mountPoint); zf->nameLength = strlen(zipname); zf->name = (char *) Tcl_Alloc(zf->nameLength + 1); memcpy(zf->name, zipname, zf->nameLength + 1); Tcl_SetHashValue(hPtr, zf); if ((zf->passBuf[0] == 0) && pwlen) { int k = 0; zf->passBuf[k++] = pwlen; for (i = pwlen; i-- > 0 ;) { zf->passBuf[k++] = (passwd[i] & 0x0f) | pwrot[(passwd[i] >> 4) & 0x0f]; } zf->passBuf[k] = '\0'; } /* TODO - is this test necessary? When will mountPoint[0] be \0 ? */ if (mountPoint[0] != '\0') { hPtr = Tcl_CreateHashEntry(&ZipFS.fileHash, mountPoint, &isNew); if (isNew) { z = AllocateZipEntry(); Tcl_SetHashValue(hPtr, z); z->depth = CountSlashes(mountPoint); assert(z->depth >= ZIPFS_ROOTDIR_DEPTH); z->zipFilePtr = zf; z->isDirectory = (zf->baseOffset == 0) ? 1 : -1; /* root marker */ z->offset = zf->baseOffset; z->compressMethod = ZIP_COMPMETH_STORED; z->name = (char *) Tcl_GetHashKey(&ZipFS.fileHash, hPtr); if (!strcmp(z->name, ZIPFS_VOLUME)) { z->flags |= ZE_F_VOLUME; /* Mark as root volume */ } Tcl_Time t; Tcl_GetTime(&t); z->timestamp = t.sec; z->next = zf->entries; zf->entries = z; } } q = zf->data + zf->directoryOffset; Tcl_DStringInit(&fpBuf); for (i = 0; i < zf->numFiles; i++) { const unsigned char *start = zf->data; const unsigned char *end = zf->data + zf->length; int extra, isdir = 0, dosTime, dosDate, nbcompr; size_t offs, pathlen, comlen; unsigned char *lq, *gq = NULL; char *fullpath, *path; pathlen = ZipReadShort(start, end, q + ZIP_CENTRAL_PATHLEN_OFFS); comlen = ZipReadShort(start, end, q + ZIP_CENTRAL_FCOMMENTLEN_OFFS); extra = ZipReadShort(start, end, q + ZIP_CENTRAL_EXTRALEN_OFFS); Tcl_DStringSetLength(&ds, 0); path = DecodeZipEntryText(q + ZIP_CENTRAL_HEADER_LEN, pathlen, &ds); if ((pathlen > 0) && (path[pathlen - 1] == '/')) { Tcl_DStringSetLength(&ds, pathlen - 1); path = Tcl_DStringValue(&ds); isdir = 1; } if ((strcmp(path, ".") == 0) || (strcmp(path, "..") == 0)) { goto nextent; } lq = zf->data + zf->baseOffset + ZipReadInt(start, end, q + ZIP_CENTRAL_LOCALHDR_OFFS); if ((lq < start) || (lq + ZIP_LOCAL_HEADER_LEN > end)) { goto nextent; } nbcompr = ZipReadInt(start, end, lq + ZIP_LOCAL_COMPLEN_OFFS); if (!isdir && (nbcompr == 0) && (ZipReadInt(start, end, lq + ZIP_LOCAL_UNCOMPLEN_OFFS) == 0) && (ZipReadInt(start, end, lq + ZIP_LOCAL_CRC32_OFFS) == 0)) { gq = q; nbcompr = ZipReadInt(start, end, gq + ZIP_CENTRAL_COMPLEN_OFFS); } offs = (lq - zf->data) + ZIP_LOCAL_HEADER_LEN + ZipReadShort(start, end, lq + ZIP_LOCAL_PATHLEN_OFFS) + ZipReadShort(start, end, lq + ZIP_LOCAL_EXTRALEN_OFFS); if (offs + nbcompr > zf->length) { goto nextent; } if (!isdir && (mountPoint[0] == '\0') && !CountSlashes(path)) { #ifdef ANDROID /* * When mounting the ZIP archive on the root directory try to * remap top level regular files of the archive to * /assets/.root/... since this directory should not be in a valid * APK due to the leading dot in the file name component. This * trick should make the files AndroidManifest.xml, * resources.arsc, and classes.dex visible to Tcl. */ Tcl_DString ds2; Tcl_DStringInit(&ds2); Tcl_DStringAppend(&ds2, "assets/.root/", -1); Tcl_DStringAppend(&ds2, path, -1); if (ZipFSLookup(Tcl_DStringValue(&ds2))) { /* should not happen but skip it anyway */ Tcl_DStringFree(&ds2); goto nextent; } Tcl_DStringSetLength(&ds, 0); Tcl_DStringAppend(&ds, Tcl_DStringValue(&ds2), Tcl_DStringLength(&ds2)); path = Tcl_DStringValue(&ds); Tcl_DStringFree(&ds2); #else /* !ANDROID */ /* * Regular files skipped when mounting on root. */ goto nextent; #endif /* ANDROID */ } Tcl_DStringSetLength(&fpBuf, 0); fullpath = MapPathToZipfs(interp, mountPoint, path, &fpBuf); z = AllocateZipEntry(); z->depth = CountSlashes(fullpath); assert(z->depth >= ZIPFS_ROOTDIR_DEPTH); z->zipFilePtr = zf; z->isDirectory = isdir; z->isEncrypted = (ZipReadShort(start, end, lq + ZIP_LOCAL_FLAGS_OFFS) & 1) && (nbcompr > ZIP_CRYPT_HDR_LEN); z->offset = offs; if (gq) { z->crc32 = ZipReadInt(start, end, gq + ZIP_CENTRAL_CRC32_OFFS); dosDate = ZipReadShort(start, end, gq + ZIP_CENTRAL_MDATE_OFFS); dosTime = ZipReadShort(start, end, gq + ZIP_CENTRAL_MTIME_OFFS); z->timestamp = DosTimeDate(dosDate, dosTime); z->numBytes = ZipReadInt(start, end, gq + ZIP_CENTRAL_UNCOMPLEN_OFFS); z->compressMethod = ZipReadShort(start, end, gq + ZIP_CENTRAL_COMPMETH_OFFS); } else { z->crc32 = ZipReadInt(start, end, lq + ZIP_LOCAL_CRC32_OFFS); dosDate = ZipReadShort(start, end, lq + ZIP_LOCAL_MDATE_OFFS); dosTime = ZipReadShort(start, end, lq + ZIP_LOCAL_MTIME_OFFS); z->timestamp = DosTimeDate(dosDate, dosTime); z->numBytes = ZipReadInt(start, end, lq + ZIP_LOCAL_UNCOMPLEN_OFFS); z->compressMethod = ZipReadShort(start, end, lq + ZIP_LOCAL_COMPMETH_OFFS); } z->numCompressedBytes = nbcompr; hPtr = Tcl_CreateHashEntry(&ZipFS.fileHash, fullpath, &isNew); if (!isNew) { /* should not happen but skip it anyway */ Tcl_Free(z); goto nextent; } Tcl_SetHashValue(hPtr, z); z->name = (char *) Tcl_GetHashKey(&ZipFS.fileHash, hPtr); z->next = zf->entries; zf->entries = z; if (isdir && (mountPoint[0] == '\0') && (z->depth == ZIPFS_ROOTDIR_DEPTH)) { z->tnext = zf->topEnts; zf->topEnts = z; } /* * Make any directory nodes we need. ZIPs are not consistent about * containing directory nodes. */ if (!z->isDirectory && (z->depth > ZIPFS_ROOTDIR_DEPTH)) { char *dir, *endPtr; ZipEntry *zd; Tcl_DStringSetLength(&ds, strlen(z->name) + 8); Tcl_DStringSetLength(&ds, 0); Tcl_DStringAppend(&ds, z->name, -1); dir = Tcl_DStringValue(&ds); for (endPtr = strrchr(dir, '/'); endPtr && (endPtr != dir); endPtr = strrchr(dir, '/')) { Tcl_DStringSetLength(&ds, endPtr - dir); hPtr = Tcl_CreateHashEntry(&ZipFS.fileHash, dir, &isNew); if (!isNew) { /* * Already made. That's fine. */ break; } zd = AllocateZipEntry(); zd->depth = CountSlashes(dir); assert(zd->depth > ZIPFS_ROOTDIR_DEPTH); zd->zipFilePtr = zf; zd->isDirectory = 1; zd->offset = z->offset; zd->timestamp = z->timestamp; zd->compressMethod = ZIP_COMPMETH_STORED; Tcl_SetHashValue(hPtr, zd); zd->name = (char *) Tcl_GetHashKey(&ZipFS.fileHash, hPtr); zd->next = zf->entries; zf->entries = zd; if ((mountPoint[0] == '\0') && (zd->depth == ZIPFS_ROOTDIR_DEPTH)) { zd->tnext = zf->topEnts; zf->topEnts = zd; } } } nextent: q += pathlen + comlen + extra + ZIP_CENTRAL_HEADER_LEN; } Unlock(); Tcl_DStringFree(&fpBuf); Tcl_DStringFree(&ds); Tcl_FSMountsChanged(NULL); return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipfsSetup -- * * Common initialisation code. ZipFS.initialized must *not* be set prior * to the call. * *------------------------------------------------------------------------- */ static void ZipfsSetup(void) { #if TCL_THREADS static const Tcl_Time t = { 0, 0 }; /* * Inflate condition variable. */ Tcl_MutexLock(&ZipFSMutex); Tcl_ConditionWait(&ZipFSCond, &ZipFSMutex, &t); Tcl_MutexUnlock(&ZipFSMutex); #endif /* TCL_THREADS */ crc32tab = get_crc_table(); Tcl_FSRegister(NULL, &zipfsFilesystem); Tcl_InitHashTable(&ZipFS.fileHash, TCL_STRING_KEYS); Tcl_InitHashTable(&ZipFS.zipHash, TCL_STRING_KEYS); ZipFS.idCount = 1; ZipFS.wrmax = DEFAULT_WRITE_MAX_SIZE; ZipFS.fallbackEntryEncoding = (char *) Tcl_Alloc(strlen(ZIPFS_FALLBACK_ENCODING) + 1); strcpy(ZipFS.fallbackEntryEncoding, ZIPFS_FALLBACK_ENCODING); ZipFS.initialized = 1; } /* *------------------------------------------------------------------------- * * ListMountPoints -- * * This procedure lists the mount points and what's mounted there, or * reports whether there are any mounts (if there's no interpreter). The * read lock must be held by the caller. * * Results: * A standard Tcl result. TCL_OK (or TCL_BREAK if no mounts and no * interpreter). * * Side effects: * Interpreter result may be updated. * *------------------------------------------------------------------------- */ static int ListMountPoints( Tcl_Interp *interp) { Tcl_HashEntry *hPtr; Tcl_HashSearch search; ZipFile *zf; Tcl_Obj *resultList; if (!interp) { /* * Are there any entries in the zipHash? Don't need to enumerate them * all to know. */ return (ZipFS.zipHash.numEntries ? TCL_OK : TCL_BREAK); } TclNewObj(resultList); for (hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &search); hPtr; hPtr = Tcl_NextHashEntry(&search)) { zf = (ZipFile *) Tcl_GetHashValue(hPtr); Tcl_ListObjAppendElement(NULL, resultList, Tcl_NewStringObj( zf->mountPoint, -1)); Tcl_ListObjAppendElement(NULL, resultList, Tcl_NewStringObj( zf->name, -1)); } Tcl_SetObjResult(interp, resultList); return TCL_OK; } /* *------------------------------------------------------------------------ * * CleanupMount -- * * Releases all resources associated with a mounted archive. There * must not be any open files in the archive. * * Caller MUST be holding WriteLock() before calling this function. * * Results: * None. * * Side effects: * Memory associated with the mounted archive is deallocated. *------------------------------------------------------------------------ */ static void CleanupMount( ZipFile *zf) /* Mount point */ { ZipEntry *z, *znext; Tcl_HashEntry *hPtr; for (z = zf->entries; z; z = znext) { znext = z->next; hPtr = Tcl_FindHashEntry(&ZipFS.fileHash, z->name); if (hPtr) { Tcl_DeleteHashEntry(hPtr); } if (z->data) { Tcl_Free(z->data); } Tcl_Free(z); } zf->entries = NULL; } /* *------------------------------------------------------------------------- * * DescribeMounted -- * * This procedure describes what is mounted at the given the mount point. * The interpreter result is not updated if there is nothing mounted at * the given point. The read lock must be held by the caller. * * Results: * A standard Tcl result. TCL_OK (or TCL_BREAK if nothing mounted there * and no interpreter). * * Side effects: * Interpreter result may be updated. * *------------------------------------------------------------------------- */ static int DescribeMounted( Tcl_Interp *interp, const char *mountPoint) { if (interp) { ZipFile *zf = ZipFSLookupZip(mountPoint); if (zf) { Tcl_SetObjResult(interp, Tcl_NewStringObj(zf->name, -1)); return TCL_OK; } } return (interp ? TCL_OK : TCL_BREAK); } /* *------------------------------------------------------------------------- * * TclZipfs_Mount -- * * This procedure is invoked to mount a given ZIP archive file on a given * mountpoint with optional ZIP password. * * Results: * A standard Tcl result. * * Side effects: * A ZIP archive file is read, analyzed and mounted, resources are * allocated. * *------------------------------------------------------------------------- */ int TclZipfs_Mount( Tcl_Interp *interp, /* Current interpreter. NULLable. */ const char *zipname, /* Path to ZIP file to mount */ const char *mountPoint, /* Mount point path. */ const char *passwd) /* Password for opening the ZIP, or NULL if * the ZIP is unprotected. */ { ZipFile *zf; int ret; ReadLock(); if (!ZipFS.initialized) { ZipfsSetup(); } /* * No mount point, so list all mount points and what is mounted there. */ if (mountPoint == NULL) { ret = ListMountPoints(interp); Unlock(); return ret; } Tcl_DString ds; Tcl_DStringInit(&ds); ret = NormalizeMountPoint(interp, mountPoint, &ds); if (ret != TCL_OK) { Unlock(); return ret; } mountPoint = Tcl_DStringValue(&ds); if (!zipname) { /* * Mount point but no file, so describe what is mounted at that mount * point. */ ret = DescribeMounted(interp, mountPoint); Unlock(); } else { /* Have both a mount point and a file (name) to mount there. */ Tcl_Obj *zipPathObj; Tcl_Obj *normZipPathObj; Unlock(); zipPathObj = Tcl_NewStringObj(zipname, -1); Tcl_IncrRefCount(zipPathObj); normZipPathObj = Tcl_FSGetNormalizedPath(interp, zipPathObj); if (normZipPathObj == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not normalize zip filename \"%s\"", zipname)); Tcl_SetErrorCode(interp, "TCL", "OPERATION", "NORMALIZE", (char *)NULL); ret = TCL_ERROR; } else { Tcl_IncrRefCount(normZipPathObj); const char *normPath = Tcl_GetString(normZipPathObj); if (passwd == NULL || (ret = IsPasswordValid(interp, passwd, strlen(passwd))) == TCL_OK) { zf = AllocateZipFile(interp, strlen(mountPoint)); if (zf == NULL) { ret = TCL_ERROR; } else { ret = ZipFSOpenArchive(interp, normPath, 1, zf); if (ret != TCL_OK) { Tcl_Free(zf); } else { ret = ZipFSCatalogFilesystem( interp, zf, mountPoint, passwd, normPath); /* Note zf is already freed on error! */ } } } Tcl_DecrRefCount(normZipPathObj); if (ret == TCL_OK && interp) { Tcl_DStringResult(interp, &ds); } } Tcl_DecrRefCount(zipPathObj); } Tcl_DStringFree(&ds); return ret; } /* *------------------------------------------------------------------------- * * TclZipfs_MountBuffer -- * * This procedure is invoked to mount a given ZIP archive file on a given * mountpoint. * * Results: * A standard Tcl result. * * Side effects: * A ZIP archive file is read, analyzed and mounted, resources are * allocated. * *------------------------------------------------------------------------- */ int TclZipfs_MountBuffer( Tcl_Interp *interp, /* Current interpreter. NULLable. */ const void *data, size_t datalen, const char *mountPoint, /* Mount point path. */ int copy) { ZipFile *zf; int ret; if (mountPoint == NULL || data == NULL) { ZIPFS_ERROR(interp, "mount point and/or data are null"); return TCL_ERROR; } /* TODO - how come a *read* lock suffices for initialzing ? */ ReadLock(); if (!ZipFS.initialized) { ZipfsSetup(); } Tcl_DString ds; Tcl_DStringInit(&ds); ret = NormalizeMountPoint(interp, mountPoint, &ds); if (ret != TCL_OK) { Unlock(); return ret; } mountPoint = Tcl_DStringValue(&ds); Unlock(); /* * Have both a mount point and data to mount there. * What's the magic about 64 * 1024 * 1024 ? */ ret = TCL_ERROR; if ((datalen <= ZIP_CENTRAL_END_LEN) || (datalen - ZIP_CENTRAL_END_LEN) > (64 * 1024 * 1024 - ZIP_CENTRAL_END_LEN)) { ZIPFS_ERROR(interp, "illegal file size"); ZIPFS_ERROR_CODE(interp, "FILE_SIZE"); goto done; } zf = AllocateZipFile(interp, strlen(mountPoint)); if (zf == NULL) { goto done; } zf->isMemBuffer = 1; zf->length = datalen; if (copy) { zf->data = (unsigned char *)Tcl_AttemptAlloc(datalen); if (zf->data == NULL) { ZipFSCloseArchive(interp, zf); Tcl_Free(zf); ZIPFS_MEM_ERROR(interp); goto done; } memcpy(zf->data, data, datalen); zf->ptrToFree = zf->data; } else { zf->data = (unsigned char *)data; zf->ptrToFree = NULL; } ret = ZipFSFindTOC(interp, 1, zf); if (ret != TCL_OK) { Tcl_Free(zf); } else { /* Note ZipFSCatalogFilesystem will free zf on error */ ret = ZipFSCatalogFilesystem( interp, zf, mountPoint, NULL, "Memory Buffer"); } if (ret == TCL_OK && interp) { Tcl_DStringResult(interp, &ds); } done: Tcl_DStringFree(&ds); return ret; } /* *------------------------------------------------------------------------- * * TclZipfs_Unmount -- * * This procedure is invoked to unmount a given ZIP archive. * * Results: * A standard Tcl result. * * Side effects: * A mounted ZIP archive file is unmounted, resources are free'd. * *------------------------------------------------------------------------- */ int TclZipfs_Unmount( Tcl_Interp *interp, /* Current interpreter. NULLable. */ const char *mountPoint) /* Mount point path. */ { ZipFile *zf; Tcl_HashEntry *hPtr; Tcl_DString dsm; int ret = TCL_OK, unmounted = 0; Tcl_DStringInit(&dsm); WriteLock(); if (!ZipFS.initialized) { goto done; } /* * Mount point sometimes is a relative or otherwise denormalized path. * But an absolute name is needed as mount point here. */ if (NormalizeMountPoint(interp, mountPoint, &dsm) != TCL_OK) { goto done; } mountPoint = Tcl_DStringValue(&dsm); hPtr = Tcl_FindHashEntry(&ZipFS.zipHash, mountPoint); /* don't report no-such-mount as an error */ if (!hPtr) { goto done; } zf = (ZipFile *) Tcl_GetHashValue(hPtr); if (zf->numOpen > 0) { ZIPFS_ERROR(interp, "filesystem is busy"); ZIPFS_ERROR_CODE(interp, "BUSY"); ret = TCL_ERROR; goto done; } Tcl_DeleteHashEntry(hPtr); /* * Now no longer mounted - the rest of the code won't find it - but we're * still cleaning things up. */ CleanupMount(zf); ZipFSCloseArchive(interp, zf); Tcl_Free(zf); unmounted = 1; done: Unlock(); Tcl_DStringFree(&dsm); if (unmounted) { Tcl_FSMountsChanged(NULL); } return ret; } /* *------------------------------------------------------------------------- * * ZipFSMountObjCmd -- * * This procedure is invoked to process the [zipfs mount] command. * * Results: * A standard Tcl result. * * Side effects: * A ZIP archive file is mounted, resources are allocated. * *------------------------------------------------------------------------- */ static int ZipFSMountObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *mountPoint = NULL, *zipFile = NULL, *password = NULL; int result; if (objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "?zipfile? ?mountpoint? ?password?"); return TCL_ERROR; } /* * A single argument is treated as the mountpoint. Two arguments * are treated as zipfile and mountpoint. */ if (objc > 1) { if (objc == 2) { mountPoint = Tcl_GetString(objv[1]); } else { /* 2 < objc < 4 */ zipFile = Tcl_GetString(objv[1]); mountPoint = Tcl_GetString(objv[2]); if (objc > 3) { password = Tcl_GetString(objv[3]); } } } result = TclZipfs_Mount(interp, zipFile, mountPoint, password); return result; } /* *------------------------------------------------------------------------- * * ZipFSMountBufferObjCmd -- * * This procedure is invoked to process the [zipfs mountdata] command. * * Results: * A standard Tcl result. * * Side effects: * A ZIP archive file is mounted, resources are allocated. * *------------------------------------------------------------------------- */ static int ZipFSMountBufferObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *mountPoint = NULL; /* Mount point path. */ unsigned char *data = NULL; Tcl_Size length; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "data mountpoint"); return TCL_ERROR; } data = Tcl_GetBytesFromObj(interp, objv[1], &length); mountPoint = Tcl_GetString(objv[2]); if (data == NULL) { return TCL_ERROR; } return TclZipfs_MountBuffer(interp, data, length, mountPoint, 1); } /* *------------------------------------------------------------------------- * * ZipFSRootObjCmd -- * * This procedure is invoked to process the [zipfs root] command. It * returns the root that all zipfs file systems are mounted under. * * Results: * A standard Tcl result. * * Side effects: * *------------------------------------------------------------------------- */ static int ZipFSRootObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, Tcl_Obj *const *objv) { if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewStringObj(ZIPFS_VOLUME, -1)); return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipFSUnmountObjCmd -- * * This procedure is invoked to process the [zipfs unmount] command. * * Results: * A standard Tcl result. * * Side effects: * A mounted ZIP archive file is unmounted, resources are free'd. * *------------------------------------------------------------------------- */ static int ZipFSUnmountObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "mountpoint"); return TCL_ERROR; } return TclZipfs_Unmount(interp, TclGetString(objv[1])); } /* *------------------------------------------------------------------------- * * ZipFSMkKeyObjCmd -- * * This procedure is invoked to process the [zipfs mkkey] command. It * produces a rotated password to be embedded into an image file. * * Results: * A standard Tcl result. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSMkKeyObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Size len, i = 0; const char *pw; Tcl_Obj *passObj; unsigned char *passBuf; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "password"); return TCL_ERROR; } pw = TclGetStringFromObj(objv[1], &len); if (len == 0) { return TCL_OK; } if (IsPasswordValid(interp, pw, len) != TCL_OK) { return TCL_ERROR; } passObj = Tcl_NewByteArrayObj(NULL, 264); passBuf = Tcl_GetBytesFromObj(NULL, passObj, (Tcl_Size *)NULL); while (len > 0) { int ch = pw[len - 1]; passBuf[i++] = (ch & 0x0f) | pwrot[(ch >> 4) & 0x0f]; len--; } passBuf[i] = i; i++; ZipWriteInt(passBuf, passBuf + 264, passBuf + i, ZIP_PASSWORD_END_SIG); Tcl_SetByteArrayLength(passObj, i + 4); Tcl_SetObjResult(interp, passObj); return TCL_OK; } /* *------------------------------------------------------------------------- * * RandomChar -- * * Worker for ZipAddFile(). Picks a random character (range: 0..255) * using Tcl's standard PRNG. * * Returns: * Tcl result code. Updates chPtr with random character on success. * * Side effects: * Advances the PRNG state. May reenter the Tcl interpreter if the user * has replaced the PRNG. * *------------------------------------------------------------------------- */ static int RandomChar( Tcl_Interp *interp, int step, int *chPtr) { double r; Tcl_Obj *ret; if (Tcl_EvalEx(interp, "::tcl::mathfunc::rand", TCL_INDEX_NONE, 0) != TCL_OK) { goto failed; } ret = Tcl_GetObjResult(interp); if (Tcl_GetDoubleFromObj(interp, ret, &r) != TCL_OK) { goto failed; } *chPtr = (int) (r * 256); return TCL_OK; failed: Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (evaluating PRNG step %d for password encoding)", step)); return TCL_ERROR; } /* *------------------------------------------------------------------------- * * ZipAddFile -- * * This procedure is used by ZipFSMkZipOrImg() to add a single file to * the output ZIP archive file being written. A ZipEntry struct about the * input file is added to the given fileHash table for later creation of * the central ZIP directory. * * Tcl *always* encodes filenames in the ZIP as UTF-8. Similarly, it * would always encode comments as UTF-8, if it supported comments. * * Results: * A standard Tcl result. * * Side effects: * Input file is read and (compressed and) written to the output ZIP * archive file. * *------------------------------------------------------------------------- */ static int ZipAddFile( Tcl_Interp *interp, /* Current interpreter. */ Tcl_Obj *pathObj, /* Actual name of the file to add. */ const char *name, /* Name to use in the ZIP archive, in Tcl's * internal encoding. */ Tcl_Channel out, /* The open ZIP archive being built. */ const char *passwd, /* Password for encoding the file, or NULL if * the file is to be unprotected. */ char *buf, /* Working buffer. */ int bufsize, /* Size of buf */ Tcl_HashTable *fileHash) /* Where to record ZIP entry metdata so we can * built the central directory. */ { const unsigned char *start = (unsigned char *) buf; const unsigned char *end = (unsigned char *) buf + bufsize; Tcl_Channel in; Tcl_HashEntry *hPtr; ZipEntry *z; z_stream stream; Tcl_DString zpathDs; /* Buffer for the encoded filename. */ const char *zpathExt; /* Filename in external encoding (true * UTF-8). */ const char *zpathTcl; /* Filename in Tcl's internal encoding. */ int crc, flush, zpathlen; size_t nbyte, nbytecompr; Tcl_Size len, olen, align = 0; long long headerStartOffset, dataStartOffset, dataEndOffset; int mtime = 0, isNew, compMeth; unsigned long keys[3], keys0[3]; char obuf[4096]; /* * Trim leading '/' characters. If this results in an empty string, we've * nothing to do. */ zpathTcl = name; while (zpathTcl && zpathTcl[0] == '/') { zpathTcl++; } if (!zpathTcl || (zpathTcl[0] == '\0')) { return TCL_OK; } /* * Convert to encoded form. Note that we use strlen() here; if someone's * crazy enough to embed NULs in filenames, they deserve what they get! */ if (Tcl_UtfToExternalDStringEx(interp, tclUtf8Encoding, zpathTcl, TCL_INDEX_NONE, 0, &zpathDs, NULL) != TCL_OK) { Tcl_DStringFree(&zpathDs); return TCL_ERROR; } zpathExt = Tcl_DStringValue(&zpathDs); zpathlen = strlen(zpathExt); if (zpathlen + ZIP_CENTRAL_HEADER_LEN > bufsize) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "path too long for \"%s\"", TclGetString(pathObj))); ZIPFS_ERROR_CODE(interp, "PATH_LEN"); Tcl_DStringFree(&zpathDs); return TCL_ERROR; } in = Tcl_FSOpenFileChannel(interp, pathObj, "rb", 0); if (!in) { Tcl_DStringFree(&zpathDs); #ifdef _WIN32 /* hopefully a directory */ if (strcmp("permission denied", Tcl_PosixError(interp)) == 0) { Tcl_Close(interp, in); return TCL_OK; } #endif /* _WIN32 */ Tcl_Close(interp, in); return TCL_ERROR; } else { Tcl_StatBuf statBuf; if (Tcl_FSStat(pathObj, &statBuf) != -1) { mtime = statBuf.st_mtime; } } Tcl_ResetResult(interp); /* * Compute the CRC. */ crc = 0; nbyte = nbytecompr = 0; while (1) { len = Tcl_Read(in, buf, bufsize); if (len < 0) { Tcl_DStringFree(&zpathDs); if (nbyte == 0 && errno == EISDIR) { Tcl_Close(interp, in); return TCL_OK; } readErrorWithChannelOpen: Tcl_SetObjResult(interp, Tcl_ObjPrintf("read error on \"%s\": %s", TclGetString(pathObj), Tcl_PosixError(interp))); Tcl_Close(interp, in); return TCL_ERROR; } if (len == 0) { break; } crc = crc32(crc, (unsigned char *) buf, len); nbyte += len; } if (Tcl_Seek(in, 0, SEEK_SET) == -1) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("seek error on \"%s\": %s", TclGetString(pathObj), Tcl_PosixError(interp))); Tcl_Close(interp, in); Tcl_DStringFree(&zpathDs); return TCL_ERROR; } /* * Remember where we've got to so far so we can write the header (after * writing the file). */ headerStartOffset = Tcl_Tell(out); /* * Reserve space for the per-file header. Includes writing the file name * as we already know that. */ memset(buf, '\0', ZIP_LOCAL_HEADER_LEN); memcpy(buf + ZIP_LOCAL_HEADER_LEN, zpathExt, zpathlen); len = zpathlen + ZIP_LOCAL_HEADER_LEN; if (Tcl_Write(out, buf, len) != len) { writeErrorWithChannelOpen: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "write error on \"%s\": %s", TclGetString(pathObj), Tcl_PosixError(interp))); Tcl_Close(interp, in); Tcl_DStringFree(&zpathDs); return TCL_ERROR; } /* * Align payload to next 4-byte boundary (if necessary) using a dummy * extra entry similar to the zipalign tool from Android's SDK. */ if ((len + headerStartOffset) & 3) { unsigned char abuf[8]; const unsigned char *astart = abuf; const unsigned char *aend = abuf + 8; align = 4 + ((len + headerStartOffset) & 3); ZipWriteShort(astart, aend, abuf, 0xffff); ZipWriteShort(astart, aend, abuf + 2, align - 4); ZipWriteInt(astart, aend, abuf + 4, 0x03020100); if (Tcl_Write(out, (const char *) abuf, align) != align) { goto writeErrorWithChannelOpen; } } /* * Set up encryption if we were asked to. */ if (passwd) { int i, ch, tmp; unsigned char kvbuf[2*ZIP_CRYPT_HDR_LEN]; init_keys(passwd, keys, crc32tab); for (i = 0; i < ZIP_CRYPT_HDR_LEN - 2; i++) { if (RandomChar(interp, i, &ch) != TCL_OK) { Tcl_Close(interp, in); return TCL_ERROR; } kvbuf[i + ZIP_CRYPT_HDR_LEN] = UCHAR(zencode(keys, crc32tab, ch, tmp)); } Tcl_ResetResult(interp); init_keys(passwd, keys, crc32tab); for (i = 0; i < ZIP_CRYPT_HDR_LEN - 2; i++) { kvbuf[i] = UCHAR(zencode(keys, crc32tab, kvbuf[i + ZIP_CRYPT_HDR_LEN], tmp)); } kvbuf[i++] = UCHAR(zencode(keys, crc32tab, crc >> 16, tmp)); kvbuf[i++] = UCHAR(zencode(keys, crc32tab, crc >> 24, tmp)); len = Tcl_Write(out, (char *) kvbuf, ZIP_CRYPT_HDR_LEN); memset(kvbuf, 0, sizeof(kvbuf)); if (len != ZIP_CRYPT_HDR_LEN) { goto writeErrorWithChannelOpen; } memcpy(keys0, keys, sizeof(keys0)); nbytecompr += ZIP_CRYPT_HDR_LEN; } /* * Save where we've got to in case we need to just store this file. */ Tcl_Flush(out); dataStartOffset = Tcl_Tell(out); /* * Compress the stream. */ compMeth = ZIP_COMPMETH_DEFLATED; memset(&stream, 0, sizeof(z_stream)); stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; if (deflateInit2(&stream, 9, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) != Z_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "compression init error on \"%s\"", TclGetString(pathObj))); ZIPFS_ERROR_CODE(interp, "DEFLATE_INIT"); Tcl_Close(interp, in); Tcl_DStringFree(&zpathDs); return TCL_ERROR; } do { len = Tcl_Read(in, buf, bufsize); if (len < 0) { deflateEnd(&stream); goto readErrorWithChannelOpen; } stream.avail_in = len; stream.next_in = (unsigned char *) buf; flush = Tcl_Eof(in) ? Z_FINISH : Z_NO_FLUSH; do { stream.avail_out = sizeof(obuf); stream.next_out = (unsigned char *) obuf; len = deflate(&stream, flush); if (len == Z_STREAM_ERROR) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "deflate error on \"%s\"", TclGetString(pathObj))); ZIPFS_ERROR_CODE(interp, "DEFLATE"); deflateEnd(&stream); Tcl_Close(interp, in); Tcl_DStringFree(&zpathDs); return TCL_ERROR; } olen = sizeof(obuf) - stream.avail_out; if (passwd) { Tcl_Size i; int tmp; for (i = 0; i < olen; i++) { obuf[i] = (char) zencode(keys, crc32tab, obuf[i], tmp); } } if (olen && (Tcl_Write(out, obuf, olen) != olen)) { deflateEnd(&stream); goto writeErrorWithChannelOpen; } nbytecompr += olen; } while (stream.avail_out == 0); } while (flush != Z_FINISH); deflateEnd(&stream); /* * Work out where we've got to. */ Tcl_Flush(out); dataEndOffset = Tcl_Tell(out); if (nbyte - nbytecompr <= 0) { /* * Compressed file larger than input, write it again uncompressed. */ if (Tcl_Seek(in, 0, SEEK_SET) != 0) { goto seekErr; } if (Tcl_Seek(out, dataStartOffset, SEEK_SET) != dataStartOffset) { seekErr: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "seek error: %s", Tcl_PosixError(interp))); Tcl_Close(interp, in); Tcl_DStringFree(&zpathDs); return TCL_ERROR; } nbytecompr = (passwd ? ZIP_CRYPT_HDR_LEN : 0); while (1) { len = Tcl_Read(in, buf, bufsize); if (len < 0) { goto readErrorWithChannelOpen; } else if (len == 0) { break; } if (passwd) { Tcl_Size i; int tmp; for (i = 0; i < len; i++) { buf[i] = (char) zencode(keys0, crc32tab, buf[i], tmp); } } if (Tcl_Write(out, buf, len) != len) { goto writeErrorWithChannelOpen; } nbytecompr += len; } compMeth = ZIP_COMPMETH_STORED; /* * Chop off everything after this; it's the over-large compressed data * and we don't know if it is going to get overwritten otherwise. */ Tcl_Flush(out); dataEndOffset = Tcl_Tell(out); Tcl_TruncateChannel(out, dataEndOffset); } Tcl_Close(interp, in); Tcl_DStringFree(&zpathDs); zpathExt = NULL; hPtr = Tcl_CreateHashEntry(fileHash, zpathTcl, &isNew); if (!isNew) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "non-unique path name \"%s\"", TclGetString(pathObj))); ZIPFS_ERROR_CODE(interp, "DUPLICATE_PATH"); return TCL_ERROR; } /* * Remember that we've written the file (for central directory generation) * and generate the local (per-file) header in the space that we reserved * earlier. */ z = AllocateZipEntry(); Tcl_SetHashValue(hPtr, z); z->isEncrypted = (passwd ? 1 : 0); z->offset = headerStartOffset; z->crc32 = crc; z->timestamp = mtime; z->numBytes = nbyte; z->numCompressedBytes = nbytecompr; z->compressMethod = compMeth; z->name = (char *) Tcl_GetHashKey(fileHash, hPtr); /* * Write final local header information. */ SerializeLocalEntryHeader(start, end, (unsigned char *) buf, z, zpathlen, align); if (Tcl_Seek(out, headerStartOffset, SEEK_SET) != headerStartOffset) { Tcl_DeleteHashEntry(hPtr); Tcl_Free(z); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "seek error: %s", Tcl_PosixError(interp))); return TCL_ERROR; } if (Tcl_Write(out, buf, ZIP_LOCAL_HEADER_LEN) != ZIP_LOCAL_HEADER_LEN) { Tcl_DeleteHashEntry(hPtr); Tcl_Free(z); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "write error: %s", Tcl_PosixError(interp))); return TCL_ERROR; } Tcl_Flush(out); if (Tcl_Seek(out, dataEndOffset, SEEK_SET) != dataEndOffset) { Tcl_DeleteHashEntry(hPtr); Tcl_Free(z); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "seek error: %s", Tcl_PosixError(interp))); return TCL_ERROR; } return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipFSFind -- * * Worker for ZipFSMkZipOrImg() that discovers the list of files to add. * Simple wrapper around [zipfs find]. * *------------------------------------------------------------------------- */ static Tcl_Obj * ZipFSFind( Tcl_Interp *interp, Tcl_Obj *dirRoot) { Tcl_Obj *cmd[2]; int result; cmd[0] = Tcl_NewStringObj("::tcl::zipfs::find", -1); cmd[1] = dirRoot; Tcl_IncrRefCount(cmd[0]); result = Tcl_EvalObjv(interp, 2, cmd, 0); Tcl_DecrRefCount(cmd[0]); if (result != TCL_OK) { return NULL; } return Tcl_GetObjResult(interp); } /* *------------------------------------------------------------------------- * * ComputeNameInArchive -- * * Helper for ZipFSMkZipOrImg() that computes what the actual name of a * file in the ZIP archive should be, stripping a prefix (if appropriate) * and any leading slashes. If the result is an empty string, the entry * should be skipped. * * Returns: * Pointer to the name (in Tcl's internal encoding), which will be in * memory owned by one of the argument objects. * * Side effects: * None (if Tcl_Objs have string representations) * *------------------------------------------------------------------------- */ static inline const char * ComputeNameInArchive( Tcl_Obj *pathObj, /* The path to the origin file */ Tcl_Obj *directNameObj, /* User-specified name for use in the ZIP * archive */ const char *strip, /* A prefix to strip; may be NULL if no * stripping need be done. */ Tcl_Size slen) /* The length of the prefix; must be 0 if no * stripping need be done. */ { const char *name; Tcl_Size len; if (directNameObj) { name = TclGetString(directNameObj); } else { name = TclGetStringFromObj(pathObj, &len); if (slen > 0) { if ((len <= slen) || (strncmp(strip, name, slen) != 0)) { /* * Guaranteed to be a NUL at the end, which will make this * entry be skipped. */ return name + len; } name += slen; } } while (name[0] == '/') { ++name; } return name; } /* *------------------------------------------------------------------------- * * ZipFSMkZipOrImg -- * * This procedure is creates a new ZIP archive file or image file given * output filename, input directory of files to be archived, optional * password, and optional image to be prepended to the output ZIP archive * file. It's the core of the implementation of [zipfs mkzip], [zipfs * mkimg], [zipfs lmkzip] and [zipfs lmkimg]. * * Tcl *always* encodes filenames in the ZIP as UTF-8. Similarly, it * would always encode comments as UTF-8, if it supported comments. * * Results: * A standard Tcl result. * * Side effects: * A new ZIP archive file or image file is written. * *------------------------------------------------------------------------- */ static int ZipFSMkZipOrImg( Tcl_Interp *interp, /* Current interpreter. */ int isImg, /* Are we making an image? */ Tcl_Obj *targetFile, /* What file are we making? */ Tcl_Obj *dirRoot, /* What directory do we take files from? Do * not specify at the same time as * mappingList (one must be NULL). */ Tcl_Obj *mappingList, /* What files are we putting in, and with what * names? Do not specify at the same time as * dirRoot (one must be NULL). */ Tcl_Obj *originFile, /* If we're making an image, what file does * the non-ZIP part of the image come from? */ Tcl_Obj *stripPrefix, /* Are we going to strip a prefix from * filenames found beneath dirRoot? If NULL, * do not strip anything (except for dirRoot * itself). */ Tcl_Obj *passwordObj) /* The password for encoding things. NULL if * there's no password protection. */ { Tcl_Channel out; int count, ret = TCL_ERROR; Tcl_Size pwlen = 0, slen = 0, len, i = 0; Tcl_Size lobjc; long long dataStartOffset; /* The overall file offset of the start of the * data section of the file. */ long long directoryStartOffset; /* The overall file offset of the start of the * central directory. */ long long suffixStartOffset;/* The overall file offset of the start of the * suffix of the central directory (i.e., * where this data will be written). */ Tcl_Obj **lobjv, *list = mappingList; ZipEntry *z; Tcl_HashEntry *hPtr; Tcl_HashSearch search; Tcl_HashTable fileHash; char *strip = NULL, *pw = NULL, passBuf[264], buf[4096]; unsigned char *start = (unsigned char *) buf; unsigned char *end = start + sizeof(buf); /* * Caller has verified that the number of arguments is correct. */ passBuf[0] = 0; if (passwordObj != NULL) { pw = TclGetStringFromObj(passwordObj, &pwlen); if (IsPasswordValid(interp, pw, pwlen) != TCL_OK) { return TCL_ERROR; } if (pwlen == 0) { pw = NULL; } } if (dirRoot != NULL) { list = ZipFSFind(interp, dirRoot); if (!list) { return TCL_ERROR; } } Tcl_IncrRefCount(list); if (TclListObjLength(interp, list, &lobjc) != TCL_OK) { Tcl_DecrRefCount(list); return TCL_ERROR; } if (mappingList && (lobjc % 2)) { Tcl_DecrRefCount(list); ZIPFS_ERROR(interp, "need even number of elements"); ZIPFS_ERROR_CODE(interp, "LIST_LENGTH"); return TCL_ERROR; } if (lobjc == 0) { Tcl_DecrRefCount(list); ZIPFS_ERROR(interp, "empty archive"); ZIPFS_ERROR_CODE(interp, "EMPTY"); return TCL_ERROR; } if (TclListObjGetElements(interp, list, &lobjc, &lobjv) != TCL_OK) { Tcl_DecrRefCount(list); return TCL_ERROR; } out = Tcl_FSOpenFileChannel(interp, targetFile, "wb", 0755); if (out == NULL) { Tcl_DecrRefCount(list); return TCL_ERROR; } /* * Copy the existing contents from the image if it is an executable image. * Care must be taken because this might include an existing ZIP, which * needs to be stripped. */ if (isImg) { ZipFile *zf, zf0; int isMounted = 0; const char *imgName; // TODO: normalize the origin file name imgName = (originFile != NULL) ? TclGetString(originFile) : Tcl_GetNameOfExecutable(); if (pwlen) { i = 0; for (len = pwlen; len-- > 0;) { int ch = pw[len]; passBuf[i] = (ch & 0x0f) | pwrot[(ch >> 4) & 0x0f]; i++; } passBuf[i] = i; ++i; passBuf[i++] = (char) ZIP_PASSWORD_END_SIG; passBuf[i++] = (char) (ZIP_PASSWORD_END_SIG >> 8); passBuf[i++] = (char) (ZIP_PASSWORD_END_SIG >> 16); passBuf[i++] = (char) (ZIP_PASSWORD_END_SIG >> 24); passBuf[i] = '\0'; } /* * Check for mounted image. */ WriteLock(); for (hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &search); hPtr; hPtr = Tcl_NextHashEntry(&search)) { zf = (ZipFile *) Tcl_GetHashValue(hPtr); if (strcmp(zf->name, imgName) == 0) { isMounted = 1; zf->numOpen++; break; } } Unlock(); if (!isMounted) { zf = &zf0; memset(&zf0, 0, sizeof(ZipFile)); } if (isMounted || ZipFSOpenArchive(interp, imgName, 0, zf) == TCL_OK) { /* * Copy everything up to the ZIP-related suffix. */ if ((size_t)Tcl_Write(out, (char *) zf->data, zf->passOffset) != zf->passOffset) { memset(passBuf, 0, sizeof(passBuf)); Tcl_DecrRefCount(list); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "write error: %s", Tcl_PosixError(interp))); Tcl_Close(interp, out); if (zf == &zf0) { ZipFSCloseArchive(interp, zf); } else { WriteLock(); zf->numOpen--; Unlock(); } return TCL_ERROR; } if (zf == &zf0) { ZipFSCloseArchive(interp, zf); } else { WriteLock(); zf->numOpen--; Unlock(); } } else { /* * Fall back to read it as plain file which hopefully is a static * tclsh or wish binary with proper zipfs infrastructure built in. */ if (CopyImageFile(interp, imgName, out) != TCL_OK) { memset(passBuf, 0, sizeof(passBuf)); Tcl_DecrRefCount(list); Tcl_Close(interp, out); return TCL_ERROR; } } /* * Store the password so that the automounter can find it. */ len = strlen(passBuf); if (len > 0) { i = Tcl_Write(out, passBuf, len); if (i != len) { Tcl_DecrRefCount(list); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "write error: %s", Tcl_PosixError(interp))); Tcl_Close(interp, out); return TCL_ERROR; } } memset(passBuf, 0, sizeof(passBuf)); Tcl_Flush(out); dataStartOffset = Tcl_Tell(out); } else { dataStartOffset = 0; } /* * Prepare the contents of the ZIP archive. */ Tcl_InitHashTable(&fileHash, TCL_STRING_KEYS); if (mappingList == NULL && stripPrefix != NULL) { strip = TclGetStringFromObj(stripPrefix, &slen); if (!slen) { strip = NULL; } } for (i = 0; i < lobjc; i += (mappingList ? 2 : 1)) { Tcl_Obj *pathObj = lobjv[i]; const char *name = ComputeNameInArchive(pathObj, (mappingList ? lobjv[i + 1] : NULL), strip, slen); if (name[0] == '\0') { continue; } if (ZipAddFile(interp, pathObj, name, out, pw, buf, sizeof(buf), &fileHash) != TCL_OK) { goto done; } } /* * Construct the contents of the ZIP central directory. */ directoryStartOffset = Tcl_Tell(out); count = 0; for (i = 0; i < lobjc; i += (mappingList ? 2 : 1)) { const char *name = ComputeNameInArchive(lobjv[i], (mappingList ? lobjv[i + 1] : NULL), strip, slen); Tcl_DString ds; hPtr = Tcl_FindHashEntry(&fileHash, name); if (!hPtr) { continue; } z = (ZipEntry *) Tcl_GetHashValue(hPtr); if (Tcl_UtfToExternalDStringEx(interp, tclUtf8Encoding, z->name, TCL_INDEX_NONE, 0, &ds, NULL) != TCL_OK) { ret = TCL_ERROR; goto done; } name = Tcl_DStringValue(&ds); len = Tcl_DStringLength(&ds); SerializeCentralDirectoryEntry(start, end, (unsigned char *) buf, z, len, dataStartOffset); if ((Tcl_Write(out, buf, ZIP_CENTRAL_HEADER_LEN) != ZIP_CENTRAL_HEADER_LEN) || (Tcl_Write(out, name, len) != len)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "write error: %s", Tcl_PosixError(interp))); Tcl_DStringFree(&ds); goto done; } Tcl_DStringFree(&ds); count++; } /* * Finalize the central directory. */ Tcl_Flush(out); suffixStartOffset = Tcl_Tell(out); SerializeCentralDirectorySuffix(start, end, (unsigned char *) buf, count, dataStartOffset, directoryStartOffset, suffixStartOffset); if (Tcl_Write(out, buf, ZIP_CENTRAL_END_LEN) != ZIP_CENTRAL_END_LEN) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "write error: %s", Tcl_PosixError(interp))); goto done; } Tcl_Flush(out); ret = TCL_OK; done: if (ret == TCL_OK) { ret = Tcl_Close(interp, out); } else { Tcl_Close(interp, out); } Tcl_DecrRefCount(list); for (hPtr = Tcl_FirstHashEntry(&fileHash, &search); hPtr; hPtr = Tcl_NextHashEntry(&search)) { z = (ZipEntry *) Tcl_GetHashValue(hPtr); Tcl_Free(z); Tcl_DeleteHashEntry(hPtr); } Tcl_DeleteHashTable(&fileHash); return ret; } /* * --------------------------------------------------------------------- * * CopyImageFile -- * * A simple file copy function that is used (by ZipFSMkZipOrImg) for * anything that is not an image with a ZIP appended. * * Returns: * A Tcl result code. * * Side effects: * Writes to an output channel. * * --------------------------------------------------------------------- */ static int CopyImageFile( Tcl_Interp *interp, /* For error reporting. */ const char *imgName, /* Where to copy from. */ Tcl_Channel out) /* Where to copy to; already open for writing * binary data. */ { Tcl_WideInt i, k; Tcl_Size m, n; Tcl_Channel in; char buf[4096]; const char *errMsg; Tcl_ResetResult(interp); in = Tcl_OpenFileChannel(interp, imgName, "rb", 0644); if (!in) { return TCL_ERROR; } /* * Get the length of the file (and exclude non-files). */ i = Tcl_Seek(in, 0, SEEK_END); if (i == -1) { errMsg = "seek error"; goto copyError; } Tcl_Seek(in, 0, SEEK_SET); /* * Copy the whole file, 8 blocks at a time (reasonably efficient). Note * that this totally ignores things like Windows's Alternate File Streams. */ for (k = 0; k < i; k += m) { m = i - k; if (m > (Tcl_Size) sizeof(buf)) { m = sizeof(buf); } n = Tcl_Read(in, buf, m); if (n == -1) { errMsg = "read error"; goto copyError; } else if (n == 0) { break; } m = Tcl_Write(out, buf, n); if (m != n) { errMsg = "write error"; goto copyError; } } Tcl_Close(interp, in); return TCL_OK; copyError: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%s: %s", errMsg, Tcl_PosixError(interp))); Tcl_Close(interp, in); return TCL_ERROR; } /* * --------------------------------------------------------------------- * * SerializeLocalEntryHeader, SerializeCentralDirectoryEntry, * SerializeCentralDirectorySuffix -- * * Create serialized forms of the structures that make up the ZIP * metadata. Note that the both the local entry and the central directory * entry need to have the name of the entry written directly afterwards. * * We could write these as structs except we need to guarantee that we * are writing these out as little-endian values. * * Side effects: * Both update their buffer arguments, but otherwise change nothing. * * --------------------------------------------------------------------- */ static void SerializeLocalEntryHeader( const unsigned char *start, /* The start of writable memory. */ const unsigned char *end, /* The end of writable memory. */ unsigned char *buf, /* Where to serialize to */ ZipEntry *z, /* The description of what to serialize. */ int nameLength, /* The length of the name. */ int align) /* The number of alignment bytes. */ { ZipWriteInt(start, end, buf + ZIP_LOCAL_SIG_OFFS, ZIP_LOCAL_HEADER_SIG); ZipWriteShort(start, end, buf + ZIP_LOCAL_VERSION_OFFS, ZIP_MIN_VERSION); ZipWriteShort(start, end, buf + ZIP_LOCAL_FLAGS_OFFS, z->isEncrypted + ZIP_LOCAL_FLAGS_UTF8); ZipWriteShort(start, end, buf + ZIP_LOCAL_COMPMETH_OFFS, z->compressMethod); ZipWriteShort(start, end, buf + ZIP_LOCAL_MTIME_OFFS, ToDosTime(z->timestamp)); ZipWriteShort(start, end, buf + ZIP_LOCAL_MDATE_OFFS, ToDosDate(z->timestamp)); ZipWriteInt(start, end, buf + ZIP_LOCAL_CRC32_OFFS, z->crc32); ZipWriteInt(start, end, buf + ZIP_LOCAL_COMPLEN_OFFS, z->numCompressedBytes); ZipWriteInt(start, end, buf + ZIP_LOCAL_UNCOMPLEN_OFFS, z->numBytes); ZipWriteShort(start, end, buf + ZIP_LOCAL_PATHLEN_OFFS, nameLength); ZipWriteShort(start, end, buf + ZIP_LOCAL_EXTRALEN_OFFS, align); } static void SerializeCentralDirectoryEntry( const unsigned char *start, /* The start of writable memory. */ const unsigned char *end, /* The end of writable memory. */ unsigned char *buf, /* Where to serialize to */ ZipEntry *z, /* The description of what to serialize. */ size_t nameLength, /* The length of the name. */ long long dataStartOffset) /* The overall file offset of the start of the * data section of the file. */ { ZipWriteInt(start, end, buf + ZIP_CENTRAL_SIG_OFFS, ZIP_CENTRAL_HEADER_SIG); ZipWriteShort(start, end, buf + ZIP_CENTRAL_VERSIONMADE_OFFS, ZIP_MIN_VERSION); ZipWriteShort(start, end, buf + ZIP_CENTRAL_VERSION_OFFS, ZIP_MIN_VERSION); ZipWriteShort(start, end, buf + ZIP_CENTRAL_FLAGS_OFFS, z->isEncrypted + ZIP_LOCAL_FLAGS_UTF8); ZipWriteShort(start, end, buf + ZIP_CENTRAL_COMPMETH_OFFS, z->compressMethod); ZipWriteShort(start, end, buf + ZIP_CENTRAL_MTIME_OFFS, ToDosTime(z->timestamp)); ZipWriteShort(start, end, buf + ZIP_CENTRAL_MDATE_OFFS, ToDosDate(z->timestamp)); ZipWriteInt(start, end, buf + ZIP_CENTRAL_CRC32_OFFS, z->crc32); ZipWriteInt(start, end, buf + ZIP_CENTRAL_COMPLEN_OFFS, z->numCompressedBytes); ZipWriteInt(start, end, buf + ZIP_CENTRAL_UNCOMPLEN_OFFS, z->numBytes); ZipWriteShort(start, end, buf + ZIP_CENTRAL_PATHLEN_OFFS, nameLength); ZipWriteShort(start, end, buf + ZIP_CENTRAL_EXTRALEN_OFFS, 0); ZipWriteShort(start, end, buf + ZIP_CENTRAL_FCOMMENTLEN_OFFS, 0); ZipWriteShort(start, end, buf + ZIP_CENTRAL_DISKFILE_OFFS, 0); ZipWriteShort(start, end, buf + ZIP_CENTRAL_IATTR_OFFS, 0); ZipWriteInt(start, end, buf + ZIP_CENTRAL_EATTR_OFFS, 0); ZipWriteInt(start, end, buf + ZIP_CENTRAL_LOCALHDR_OFFS, z->offset - dataStartOffset); } static void SerializeCentralDirectorySuffix( const unsigned char *start, /* The start of writable memory. */ const unsigned char *end, /* The end of writable memory. */ unsigned char *buf, /* Where to serialize to */ int entryCount, /* The number of entries in the directory */ long long dataStartOffset, /* The overall file offset of the start of the * data file. */ long long directoryStartOffset, /* The overall file offset of the start of the * central directory. */ long long suffixStartOffset)/* The overall file offset of the start of the * suffix of the central directory (i.e., * where this data will be written). */ { ZipWriteInt(start, end, buf + ZIP_CENTRAL_END_SIG_OFFS, ZIP_CENTRAL_END_SIG); ZipWriteShort(start, end, buf + ZIP_CENTRAL_DISKNO_OFFS, 0); ZipWriteShort(start, end, buf + ZIP_CENTRAL_DISKDIR_OFFS, 0); ZipWriteShort(start, end, buf + ZIP_CENTRAL_ENTS_OFFS, entryCount); ZipWriteShort(start, end, buf + ZIP_CENTRAL_TOTALENTS_OFFS, entryCount); ZipWriteInt(start, end, buf + ZIP_CENTRAL_DIRSIZE_OFFS, suffixStartOffset - directoryStartOffset); ZipWriteInt(start, end, buf + ZIP_CENTRAL_DIRSTART_OFFS, directoryStartOffset - dataStartOffset); ZipWriteShort(start, end, buf + ZIP_CENTRAL_COMMENTLEN_OFFS, 0); } /* *------------------------------------------------------------------------- * * ZipFSMkZipObjCmd, ZipFSLMkZipObjCmd -- * * These procedures are invoked to process the [zipfs mkzip] and [zipfs * lmkzip] commands. See description of ZipFSMkZipOrImg(). * * Results: * A standard Tcl result. * * Side effects: * See description of ZipFSMkZipOrImg(). * *------------------------------------------------------------------------- */ static int ZipFSMkZipObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *stripPrefix, *password; if (objc < 3 || objc > 5) { Tcl_WrongNumArgs(interp, 1, objv, "outfile indir ?strip? ?password?"); return TCL_ERROR; } if (Tcl_IsSafe(interp)) { ZIPFS_ERROR(interp, "operation not permitted in a safe interpreter"); ZIPFS_ERROR_CODE(interp, "SAFE_INTERP"); return TCL_ERROR; } stripPrefix = (objc > 3 ? objv[3] : NULL); password = (objc > 4 ? objv[4] : NULL); return ZipFSMkZipOrImg(interp, 0, objv[1], objv[2], NULL, NULL, stripPrefix, password); } static int ZipFSLMkZipObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *password; if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "outfile inlist ?password?"); return TCL_ERROR; } if (Tcl_IsSafe(interp)) { ZIPFS_ERROR(interp, "operation not permitted in a safe interpreter"); ZIPFS_ERROR_CODE(interp, "SAFE_INTERP"); return TCL_ERROR; } password = (objc > 3 ? objv[3] : NULL); return ZipFSMkZipOrImg(interp, 0, objv[1], NULL, objv[2], NULL, NULL, password); } /* *------------------------------------------------------------------------- * * ZipFSMkImgObjCmd, ZipFSLMkImgObjCmd -- * * These procedures are invoked to process the [zipfs mkimg] and [zipfs * lmkimg] commands. See description of ZipFSMkZipOrImg(). * * Results: * A standard Tcl result. * * Side effects: * See description of ZipFSMkZipOrImg(). * *------------------------------------------------------------------------- */ static int ZipFSMkImgObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *originFile, *stripPrefix, *password; if (objc < 3 || objc > 6) { Tcl_WrongNumArgs(interp, 1, objv, "outfile indir ?strip? ?password? ?infile?"); return TCL_ERROR; } if (Tcl_IsSafe(interp)) { ZIPFS_ERROR(interp, "operation not permitted in a safe interpreter"); ZIPFS_ERROR_CODE(interp, "SAFE_INTERP"); return TCL_ERROR; } originFile = (objc > 5 ? objv[5] : NULL); stripPrefix = (objc > 3 ? objv[3] : NULL); password = (objc > 4 ? objv[4] : NULL); return ZipFSMkZipOrImg(interp, 1, objv[1], objv[2], NULL, originFile, stripPrefix, password); } static int ZipFSLMkImgObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_Obj *originFile, *password; if (objc < 3 || objc > 5) { Tcl_WrongNumArgs(interp, 1, objv, "outfile inlist ?password? ?infile?"); return TCL_ERROR; } if (Tcl_IsSafe(interp)) { ZIPFS_ERROR(interp, "operation not permitted in a safe interpreter"); ZIPFS_ERROR_CODE(interp, "SAFE_INTERP"); return TCL_ERROR; } originFile = (objc > 4 ? objv[4] : NULL); password = (objc > 3 ? objv[3] : NULL); return ZipFSMkZipOrImg(interp, 1, objv[1], NULL, objv[2], originFile, NULL, password); } /* *------------------------------------------------------------------------- * * ZipFSCanonicalObjCmd -- * * This procedure is invoked to process the [zipfs canonical] command. * It returns the canonical name for a file within zipfs * * Results: * Always TCL_OK provided the right number of arguments are supplied. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSCanonicalObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { const char *mntPoint = NULL; Tcl_DString dsPath, dsMount; if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "?mountpoint? filename"); return TCL_ERROR; } Tcl_DStringInit(&dsPath); Tcl_DStringInit(&dsMount); if (objc == 2) { mntPoint = ZIPFS_VOLUME; } else { if (NormalizeMountPoint(interp, Tcl_GetString(objv[1]), &dsMount) != TCL_OK) { return TCL_ERROR; } mntPoint = Tcl_DStringValue(&dsMount); } (void)MapPathToZipfs(interp, mntPoint, Tcl_GetString(objv[objc - 1]), &dsPath); Tcl_SetObjResult(interp, Tcl_DStringToObj(&dsPath)); return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipFSExistsObjCmd -- * * This procedure is invoked to process the [zipfs exists] command. It * tests for the existence of a file in the ZIP filesystem and places a * boolean into the interp's result. * * Results: * Always TCL_OK provided the right number of arguments are supplied. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSExistsObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { char *filename; int exists; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "filename"); return TCL_ERROR; } filename = TclGetString(objv[1]); ReadLock(); exists = ZipFSLookup(filename) != NULL; if (!exists) { /* An ancestor directory of a file ? */ exists = ContainsMountPoint(filename, -1); } Unlock(); Tcl_SetObjResult(interp, Tcl_NewBooleanObj(exists)); return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipFSInfoObjCmd -- * * This procedure is invoked to process the [zipfs info] command. On * success, it returns a Tcl list made up of name of ZIP archive file, * size uncompressed, size compressed, and archive offset of a file in * the ZIP filesystem. * * Results: * A standard Tcl result. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSInfoObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { char *filename; ZipEntry *z; int ret; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "filename"); return TCL_ERROR; } filename = TclGetString(objv[1]); ReadLock(); z = ZipFSLookup(filename); if (z) { Tcl_Obj *result = Tcl_GetObjResult(interp); Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(z->zipFilePtr->name, -1)); Tcl_ListObjAppendElement(interp, result, Tcl_NewWideIntObj(z->numBytes)); Tcl_ListObjAppendElement(interp, result, Tcl_NewWideIntObj(z->numCompressedBytes)); Tcl_ListObjAppendElement(interp, result, Tcl_NewWideIntObj(z->offset)); ret = TCL_OK; } else { Tcl_SetErrno(ENOENT); if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "path \"%s\" not found in any zipfs volume", filename)); } ret = TCL_ERROR; } Unlock(); return ret; } /* *------------------------------------------------------------------------- * * ZipFSListObjCmd -- * * This procedure is invoked to process the [zipfs list] command. On * success, it returns a Tcl list of files of the ZIP filesystem which * match a search pattern (glob or regexp). * * Results: * A standard Tcl result. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSListObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { char *pattern = NULL; Tcl_RegExp regexp = NULL; Tcl_HashEntry *hPtr; Tcl_HashSearch search; Tcl_Obj *result = Tcl_GetObjResult(interp); const char *options[] = {"-glob", "-regexp", NULL}; enum list_options { OPT_GLOB, OPT_REGEXP }; /* * Parse arguments. */ if (objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "?(-glob|-regexp)? ?pattern?"); return TCL_ERROR; } if (objc == 3) { int idx; if (Tcl_GetIndexFromObj(interp, objv[1], options, "option", 0, &idx) != TCL_OK) { return TCL_ERROR; } switch (idx) { case OPT_GLOB: pattern = TclGetString(objv[2]); break; case OPT_REGEXP: regexp = Tcl_RegExpCompile(interp, TclGetString(objv[2])); if (!regexp) { return TCL_ERROR; } break; } } else if (objc == 2) { pattern = TclGetString(objv[1]); } /* * Scan for matching entries. */ ReadLock(); if (pattern) { for (hPtr = Tcl_FirstHashEntry(&ZipFS.fileHash, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { ZipEntry *z = (ZipEntry *) Tcl_GetHashValue(hPtr); if (Tcl_StringMatch(z->name, pattern)) { Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(z->name, -1)); } } } else if (regexp) { for (hPtr = Tcl_FirstHashEntry(&ZipFS.fileHash, &search); hPtr; hPtr = Tcl_NextHashEntry(&search)) { ZipEntry *z = (ZipEntry *) Tcl_GetHashValue(hPtr); if (Tcl_RegExpExec(interp, regexp, z->name, z->name)) { Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(z->name, -1)); } } } else { for (hPtr = Tcl_FirstHashEntry(&ZipFS.fileHash, &search); hPtr; hPtr = Tcl_NextHashEntry(&search)) { ZipEntry *z = (ZipEntry *) Tcl_GetHashValue(hPtr); Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(z->name, -1)); } } Unlock(); return TCL_OK; } /* *------------------------------------------------------------------------- * * TclZipfs_TclLibrary -- * * This procedure gets (and possibly finds) the root that Tcl's library * files are mounted under. * * Results: * A Tcl object holding the location (with zero refcount), or NULL if no * Tcl library can be found. * * Side effects: * May initialise the cache of where such library files are to be found. * This cache is never cleared. * *------------------------------------------------------------------------- */ /* Utility routine to centralize housekeeping */ static Tcl_Obj * ScriptLibrarySetup( const char *dirName) { Tcl_Obj *libDirObj = Tcl_NewStringObj(dirName, -1); Tcl_Obj *subDirObj, *searchPathObj; TclNewLiteralStringObj(subDirObj, "encoding"); Tcl_IncrRefCount(subDirObj); TclNewObj(searchPathObj); Tcl_ListObjAppendElement(NULL, searchPathObj, Tcl_FSJoinToPath(libDirObj, 1, &subDirObj)); Tcl_DecrRefCount(subDirObj); Tcl_IncrRefCount(searchPathObj); Tcl_SetEncodingSearchPath(searchPathObj); Tcl_DecrRefCount(searchPathObj); /* Bug [fccb9f322f]. Reinit system encoding after setting search path */ TclpSetInitialEncodings(); return libDirObj; } Tcl_Obj * TclZipfs_TclLibrary(void) { Tcl_Obj *vfsInitScript; int found; #if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(STATIC_BUILD) # define LIBRARY_SIZE 64 HMODULE hModule; WCHAR wName[MAX_PATH + LIBRARY_SIZE]; char dllName[(MAX_PATH + LIBRARY_SIZE) * 3]; #endif /* _WIN32 */ /* * Use the cached value if that has been set; we don't want to repeat the * searching and mounting. */ if (zipfs_literal_tcl_library) { return ScriptLibrarySetup(zipfs_literal_tcl_library); } /* * Look for the library file system within the executable. */ vfsInitScript = Tcl_NewStringObj(ZIPFS_APP_MOUNT "/tcl_library/init.tcl", -1); Tcl_IncrRefCount(vfsInitScript); found = Tcl_FSAccess(vfsInitScript, F_OK); Tcl_DecrRefCount(vfsInitScript); if (found == TCL_OK) { zipfs_literal_tcl_library = ZIPFS_APP_MOUNT "/tcl_library"; return ScriptLibrarySetup(zipfs_literal_tcl_library); } /* * Look for the library file system within the DLL/shared library. Note * that we must mount the zip file and dll before releasing to search. */ #if !defined(STATIC_BUILD) #if defined(_WIN32) || defined(__CYGWIN__) hModule = (HMODULE)TclWinGetTclInstance(); GetModuleFileNameW(hModule, wName, MAX_PATH); #ifdef __CYGWIN__ cygwin_conv_path(3, wName, dllName, sizeof(dllName)); #else WideCharToMultiByte(CP_UTF8, 0, wName, -1, dllName, sizeof(dllName), NULL, NULL); #endif if (ZipfsAppHookFindTclInit(dllName) == TCL_OK) { return ScriptLibrarySetup(zipfs_literal_tcl_library); } #elif !defined(NO_DLFCN_H) Dl_info dlinfo; if (dladdr((const void *)TclZipfs_TclLibrary, &dlinfo) && (dlinfo.dli_fname != NULL) && (ZipfsAppHookFindTclInit(dlinfo.dli_fname) == TCL_OK)) { return ScriptLibrarySetup(zipfs_literal_tcl_library); } #else if (ZipfsAppHookFindTclInit(CFG_RUNTIME_LIBDIR "/" CFG_RUNTIME_DLLFILE) == TCL_OK) { return ScriptLibrarySetup(zipfs_literal_tcl_library); } #endif /* _WIN32 */ #endif /* !defined(STATIC_BUILD) */ /* * If anything set the cache (but subsequently failed) go with that * anyway. */ if (zipfs_literal_tcl_library) { return ScriptLibrarySetup(zipfs_literal_tcl_library); } return NULL; } /* *------------------------------------------------------------------------- * * ZipFSTclLibraryObjCmd -- * * This procedure is invoked to process the * [::tcl::zipfs::tcl_library_init] command, usually called during the * execution of Tcl's interpreter startup. It returns the root that Tcl's * library files are mounted under. * * Results: * A standard Tcl result. * * Side effects: * May initialise the cache of where such library files are to be found. * This cache is never cleared. * *------------------------------------------------------------------------- */ static int ZipFSTclLibraryObjCmd( TCL_UNUSED(void *), Tcl_Interp *interp, /* Current interpreter. */ TCL_UNUSED(int) /*objc*/, TCL_UNUSED(Tcl_Obj *const *)) /*objv*/ { if (!Tcl_IsSafe(interp)) { Tcl_Obj *pResult = TclZipfs_TclLibrary(); if (!pResult) { TclNewObj(pResult); } Tcl_SetObjResult(interp, pResult); } return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipChannelClose -- * * This function is called to close a channel. * * Results: * Always TCL_OK. * * Side effects: * Resources are free'd. * *------------------------------------------------------------------------- */ static int ZipChannelClose( void *instanceData, TCL_UNUSED(Tcl_Interp *), int flags) { ZipChannel *info = (ZipChannel *) instanceData; if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) { return EINVAL; } if (info->isEncrypted) { info->isEncrypted = 0; memset(info->keys, 0, sizeof(info->keys)); } WriteLock(); if (ZipChannelWritable(info)) { /* * Copy channel data back into original file in archive. */ ZipEntry *z = info->zipEntryPtr; assert(info->ubufToFree && info->ubuf); unsigned char *newdata; newdata = (unsigned char *) Tcl_AttemptRealloc( info->ubufToFree, info->numBytes ? info->numBytes : 1); /* Bug [23dd83ce7c] */ if (newdata == NULL) { /* Could not reallocate, keep existing buffer */ newdata = info->ubufToFree; } info->ubufToFree = NULL; /* Now newdata! */ info->ubuf = NULL; info->ubufSize = 0; /* Replace old content */ if (z->data) { Tcl_Free(z->data); } z->data = newdata; /* May be NULL when ubufToFree was NULL */ z->numBytes = z->numCompressedBytes = info->numBytes; assert(z->data || z->numBytes == 0); z->compressMethod = ZIP_COMPMETH_STORED; z->timestamp = time(NULL); z->isDirectory = 0; z->isEncrypted = 0; z->offset = 0; z->crc32 = 0; } info->zipFilePtr->numOpen--; Unlock(); if (info->ubufToFree) { assert(info->ubuf); Tcl_Free(info->ubufToFree); info->ubuf = NULL; info->ubufToFree = NULL; info->ubufSize = 0; } Tcl_Free(info); return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipChannelRead -- * * This function is called to read data from channel. * * Results: * Number of bytes read or -1 on error with error number set. * * Side effects: * Data is read and file pointer is advanced. * *------------------------------------------------------------------------- */ static int ZipChannelRead( void *instanceData, char *buf, int toRead, int *errloc) { ZipChannel *info = (ZipChannel *) instanceData; Tcl_Size nextpos; if (info->isDirectory < 0) { /* * Special case: when executable combined with ZIP archive file read * data in front of ZIP, i.e. the executable itself. */ nextpos = info->cursor + toRead; if ((size_t)nextpos > info->zipFilePtr->baseOffset) { toRead = info->zipFilePtr->baseOffset - info->cursor; nextpos = info->zipFilePtr->baseOffset; } if (toRead == 0) { return 0; } memcpy(buf, info->zipFilePtr->data, toRead); info->cursor = nextpos; *errloc = 0; return toRead; } if (info->isDirectory) { *errloc = EISDIR; return -1; } nextpos = info->cursor + toRead; if (nextpos > info->numBytes) { toRead = info->numBytes - info->cursor; nextpos = info->numBytes; } if (toRead == 0) { return 0; } if (info->isEncrypted) { int i; /* * TODO - when is this code ever exercised? Cannot reach it from * tests. In particular, decryption is always done at channel open * to allow for seeks and random reads. */ for (i = 0; i < toRead; i++) { int ch = info->ubuf[i + info->cursor]; buf[i] = zdecode(info->keys, crc32tab, ch); } } else { memcpy(buf, info->ubuf + info->cursor, toRead); } info->cursor = nextpos; *errloc = 0; return toRead; } /* *------------------------------------------------------------------------- * * ZipChannelWrite -- * * This function is called to write data into channel. * * Results: * Number of bytes written or -1 on error with error number set. * * Side effects: * Data is written and file pointer is advanced. * *------------------------------------------------------------------------- */ static int ZipChannelWrite( void *instanceData, const char *buf, int toWrite, int *errloc) { ZipChannel *info = (ZipChannel *) instanceData; unsigned long nextpos; if (!ZipChannelWritable(info)) { *errloc = EINVAL; return -1; } assert(info->ubuf == info->ubufToFree); assert(info->ubufToFree && info->ubufSize > 0); assert(info->ubufSize <= info->maxWrite); assert(info->numBytes <= info->ubufSize); assert(info->cursor <= info->numBytes); if (toWrite == 0) { *errloc = 0; return 0; } if (info->mode & O_APPEND) { info->cursor = info->numBytes; } if (toWrite > (info->maxWrite - info->cursor)) { /* File would grow beyond max size permitted */ /* Don't do partial writes in error case. Or should we? */ *errloc = EFBIG; return -1; } if (toWrite > (info->ubufSize - info->cursor)) { /* grow the buffer. We have already checked will not exceed maxWrite */ Tcl_Size needed = info->cursor + toWrite; /* Tack on a bit for future growth. */ if (needed < (info->maxWrite - needed/2)) { needed += needed / 2; } else { needed = info->maxWrite; } unsigned char *newBuf = (unsigned char *) Tcl_AttemptRealloc(info->ubufToFree, needed); if (newBuf == NULL) { *errloc = ENOMEM; return -1; } info->ubufToFree = newBuf; info->ubuf = info->ubufToFree; info->ubufSize = needed; } nextpos = info->cursor + toWrite; memcpy(info->ubuf + info->cursor, buf, toWrite); info->cursor = nextpos; if (info->cursor > info->numBytes) { info->numBytes = info->cursor; } *errloc = 0; return toWrite; } /* *------------------------------------------------------------------------- * * ZipChannelSeek/ZipChannelWideSeek -- * * This function is called to position file pointer of channel. * * Results: * New file position or -1 on error with error number set. * * Side effects: * File pointer is repositioned according to offset and mode. * *------------------------------------------------------------------------- */ static long long ZipChannelWideSeek( void *instanceData, long long offset, int mode, int *errloc) { ZipChannel *info = (ZipChannel *) instanceData; Tcl_Size end; if (!ZipChannelWritable(info) && (info->isDirectory < 0)) { /* * Special case: when executable combined with ZIP archive file, seek * within front of ZIP, i.e. the executable itself. */ end = info->zipFilePtr->baseOffset; } else if (info->isDirectory) { *errloc = EINVAL; return -1; } else { end = info->numBytes; } switch (mode) { case SEEK_CUR: offset += info->cursor; break; case SEEK_END: offset += end; break; case SEEK_SET: break; default: *errloc = EINVAL; return -1; } if (offset < 0 || offset > TCL_SIZE_MAX) { *errloc = EINVAL; return -1; } if (ZipChannelWritable(info)) { if (offset > info->maxWrite) { *errloc = EINVAL; return -1; } if (offset > info->numBytes) { info->numBytes = offset; } } else if (offset > end) { *errloc = EINVAL; return -1; } info->cursor = (Tcl_Size) offset; return info->cursor; } /* *------------------------------------------------------------------------- * * ZipChannelWatchChannel -- * * This function is called for event notifications on channel. Does * nothing. * * Results: * None. * * Side effects: * None. * *------------------------------------------------------------------------- */ static void ZipChannelWatchChannel( TCL_UNUSED(void *), TCL_UNUSED(int) /*mask*/) { return; } /* *------------------------------------------------------------------------- * * ZipChannelGetFile -- * * This function is called to retrieve OS handle for channel. * * Results: * Always TCL_ERROR since there's never an OS handle for a file within a * ZIP archive. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipChannelGetFile( TCL_UNUSED(void *), TCL_UNUSED(int) /*direction*/, TCL_UNUSED(void **) /*handlePtr*/) { return TCL_ERROR; } /* *------------------------------------------------------------------------- * * ZipChannelOpen -- * * This function opens a Tcl_Channel on a file from a mounted ZIP archive * according to given open mode (already parsed by caller). * * Results: * Tcl_Channel on success, or NULL on error. * * Side effects: * Memory is allocated, the file from the ZIP archive is uncompressed. * *------------------------------------------------------------------------- */ static Tcl_Channel ZipChannelOpen( Tcl_Interp *interp, /* Current interpreter. */ char *filename, /* What are we opening. */ int mode) /* O_WRONLY O_RDWR O_TRUNC flags */ { ZipEntry *z; ZipChannel *info; int flags = 0; char cname[128]; int wr = (mode & (O_WRONLY | O_RDWR)) != 0; /* Check for unsupported modes. */ if ((ZipFS.wrmax <= 0) && wr) { Tcl_SetErrno(EACCES); if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "writes not permitted: %s", Tcl_PosixError(interp))); } return NULL; } if ((mode & (O_APPEND|O_TRUNC)) && !wr) { Tcl_SetErrno(EINVAL); if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "Invalid flags 0x%x. O_APPEND and " "O_TRUNC require write access: %s", mode, Tcl_PosixError(interp))); } return NULL; } /* * Is the file there? */ WriteLock(); z = ZipFSLookup(filename); if (!z) { Tcl_SetErrno(wr ? ENOTSUP : ENOENT); if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "file \"%s\" not %s: %s", filename, wr ? "created" : "found", Tcl_PosixError(interp))); } goto error; } if (z->numBytes < 0 || z->numCompressedBytes < 0 || z->offset >= z->zipFilePtr->length) { /* Normally this should only happen for zip64. */ ZIPFS_ERROR(interp, "file size error (may be zip64)"); ZIPFS_ERROR_CODE(interp, "FILE_SIZE"); goto error; } /* Do we support opening the file that way? */ if (wr && z->isDirectory) { Tcl_SetErrno(EISDIR); if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unsupported file type: %s", Tcl_PosixError(interp))); } goto error; } if ((z->compressMethod != ZIP_COMPMETH_STORED) && (z->compressMethod != ZIP_COMPMETH_DEFLATED)) { ZIPFS_ERROR(interp, "unsupported compression method"); ZIPFS_ERROR_CODE(interp, "COMP_METHOD"); goto error; } if (wr) { if ((mode & O_TRUNC) == 0 && !z->data && (z->numBytes > ZipFS.wrmax)) { Tcl_SetErrno(EFBIG); ZIPFS_POSIX_ERROR(interp, "file size exceeds max writable"); goto error; } flags = TCL_WRITABLE; if (mode & O_RDWR) { flags |= TCL_READABLE; } } else { /* Read-only */ flags |= TCL_READABLE; } if (z->isEncrypted) { if (z->numCompressedBytes < ZIP_CRYPT_HDR_LEN) { ZIPFS_ERROR(interp, "decryption failed: truncated decryption header"); ZIPFS_ERROR_CODE(interp, "DECRYPT"); goto error; } if (z->zipFilePtr->passBuf[0] == 0) { ZIPFS_ERROR(interp, "decryption failed - no password provided"); ZIPFS_ERROR_CODE(interp, "DECRYPT"); goto error; } } info = AllocateZipChannel(interp); if (!info) { goto error; } info->zipFilePtr = z->zipFilePtr; info->zipEntryPtr = z; if (wr) { /* Set up a writable channel. */ if (InitWritableChannel(interp, info, z, mode) == TCL_ERROR) { Tcl_Free(info); goto error; } } else if (z->data) { /* Set up a readable channel for direct data. */ info->numBytes = z->numBytes; info->ubuf = z->data; info->ubufToFree = NULL; /* Not dynamically allocated */ info->ubufSize = 0; } else { /* * Set up a readable channel. */ if (InitReadableChannel(interp, info, z) == TCL_ERROR) { Tcl_Free(info); goto error; } } if (z->crc32) { if (!(z->flags & ZE_F_CRC_COMPARED)) { int crc = crc32(0, NULL, info->numBytes); crc = crc32(crc, info->ubuf, info->numBytes); z->flags |= ZE_F_CRC_COMPARED; if (crc == z->crc32) { z->flags |= ZE_F_CRC_CORRECT; } } if (!(z->flags & ZE_F_CRC_CORRECT)) { ZIPFS_ERROR(interp, "invalid CRC"); ZIPFS_ERROR_CODE(interp, "CRC_FAILED"); if (info->ubufToFree) { Tcl_Free(info->ubufToFree); info->ubufSize = 0; } Tcl_Free(info); goto error; } } /* * Wrap the ZipChannel into a Tcl_Channel. */ snprintf(cname, sizeof(cname), "zipfs_%" TCL_Z_MODIFIER "x_%d", z->offset, ZipFS.idCount++); z->zipFilePtr->numOpen++; Unlock(); return Tcl_CreateChannel(&zipChannelType, cname, info, flags); error: Unlock(); return NULL; } /* *------------------------------------------------------------------------- * * InitWritableChannel -- * * Assistant for ZipChannelOpen() that sets up a writable channel. It's * up to the caller to actually register the channel. * * Returns: * Tcl result code. * * Side effects: * Allocates memory for the implementation of the channel. Writes to the * interpreter's result on error. * *------------------------------------------------------------------------- */ static int InitWritableChannel( Tcl_Interp *interp, /* Current interpreter, or NULL (when errors * will be silent). */ ZipChannel *info, /* The channel to set up. */ ZipEntry *z, /* The zipped file that the channel will write * to. */ int mode) /* O_APPEND, O_TRUNC */ { int i, ch; unsigned char *cbuf = NULL; /* * Set up a writable channel. */ info->mode = mode; info->maxWrite = ZipFS.wrmax; info->ubufSize = z->numBytes ? z->numBytes : 1; info->ubufToFree = (unsigned char *)Tcl_AttemptAlloc(info->ubufSize); info->ubuf = info->ubufToFree; if (info->ubufToFree == NULL) { goto memoryError; } if (z->isEncrypted) { assert(z->numCompressedBytes >= ZIP_CRYPT_HDR_LEN); /* caller should have checked*/ if (DecodeCryptHeader(interp, z, info->keys, z->zipFilePtr->data + z->offset) != TCL_OK) { goto error_cleanup; } } if (mode & O_TRUNC) { /* * Truncate; nothing there. */ info->numBytes = 0; z->crc32 = 0; /* Truncated, CRC no longer applicable */ } else if (z->data) { /* * Already got uncompressed data. */ assert(info->ubufSize >= z->numBytes); memcpy(info->ubuf, z->data, z->numBytes); info->numBytes = z->numBytes; } else { /* * Need to uncompress the existing data. */ unsigned char *zbuf = z->zipFilePtr->data + z->offset; if (z->isEncrypted) { zbuf += ZIP_CRYPT_HDR_LEN; } if (z->compressMethod == ZIP_COMPMETH_DEFLATED) { z_stream stream; int err; memset(&stream, 0, sizeof(z_stream)); stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; stream.avail_in = z->numCompressedBytes; if (z->isEncrypted) { unsigned int j; /* Min length ZIP_CRYPT_HDR_LEN for keys should already been checked. */ assert(stream.avail_in >= ZIP_CRYPT_HDR_LEN); stream.avail_in -= ZIP_CRYPT_HDR_LEN; cbuf = (unsigned char *) Tcl_AttemptAlloc(stream.avail_in ? stream.avail_in : 1); if (!cbuf) { goto memoryError; } for (j = 0; j < stream.avail_in; j++) { ch = zbuf[j]; cbuf[j] = zdecode(info->keys, crc32tab, ch); } stream.next_in = cbuf; } else { stream.next_in = zbuf; } stream.next_out = info->ubuf; stream.avail_out = info->ubufSize; if (inflateInit2(&stream, -15) != Z_OK) { goto corruptionError; } err = inflate(&stream, Z_SYNC_FLUSH); inflateEnd(&stream); if ((err != Z_STREAM_END) && ((err != Z_OK) || (stream.avail_in != 0))) { goto corruptionError; } /* Even if decompression succeeded, counts should be as expected */ if ((int) stream.total_out != z->numBytes) { goto corruptionError; } info->numBytes = z->numBytes; if (cbuf) { Tcl_Free(cbuf); } } else if (z->isEncrypted) { /* * Need to decrypt some otherwise-simple stored data. */ if (z->numCompressedBytes <= ZIP_CRYPT_HDR_LEN || (z->numCompressedBytes - ZIP_CRYPT_HDR_LEN) != z->numBytes) { goto corruptionError; } int len = z->numCompressedBytes - ZIP_CRYPT_HDR_LEN; assert(len <= info->ubufSize); for (i = 0; i < len; i++) { ch = zbuf[i]; info->ubuf[i] = zdecode(info->keys, crc32tab, ch); } info->numBytes = len; } else { /* * Simple stored data. Copy into our working buffer. */ assert(info->ubufSize >= z->numBytes); memcpy(info->ubuf, zbuf, z->numBytes); info->numBytes = z->numBytes; } memset(info->keys, 0, sizeof(info->keys)); } if (mode & O_APPEND) { info->cursor = info->numBytes; } return TCL_OK; memoryError: ZIPFS_MEM_ERROR(interp); goto error_cleanup; corruptionError: if (cbuf) { memset(info->keys, 0, sizeof(info->keys)); Tcl_Free(cbuf); } ZIPFS_ERROR(interp, "decompression error"); ZIPFS_ERROR_CODE(interp, "CORRUPT"); error_cleanup: if (info->ubufToFree) { Tcl_Free(info->ubufToFree); info->ubufToFree = NULL; info->ubuf = NULL; info->ubufSize = 0; } return TCL_ERROR; } /* *------------------------------------------------------------------------- * * InitReadableChannel -- * * Assistant for ZipChannelOpen() that sets up a readable channel. It's * up to the caller to actually register the channel. Caller should have * validated the passed ZipEntry (byte counts in particular) * * Returns: * Tcl result code. * * Side effects: * Allocates memory for the implementation of the channel. Writes to the * interpreter's result on error. * *------------------------------------------------------------------------- */ static int InitReadableChannel( Tcl_Interp *interp, /* Current interpreter, or NULL (when errors * will be silent). */ ZipChannel *info, /* The channel to set up. */ ZipEntry *z) /* The zipped file that the channel will read * from. */ { unsigned char *ubuf = NULL; int ch; info->iscompr = (z->compressMethod == ZIP_COMPMETH_DEFLATED); info->ubuf = z->zipFilePtr->data + z->offset; info->ubufToFree = NULL; /* ubuf memory not allocated */ info->ubufSize = 0; info->isDirectory = z->isDirectory; info->isEncrypted = z->isEncrypted; info->mode = O_RDONLY; /* Caller must validate - bug [6ed3447a7e] */ assert(z->numBytes >= 0 && z->numCompressedBytes >= 0); info->numBytes = z->numBytes; if (info->isEncrypted) { assert(z->numCompressedBytes >= ZIP_CRYPT_HDR_LEN); /* caller should have checked*/ if (DecodeCryptHeader(interp, z, info->keys, info->ubuf) != TCL_OK) { goto error_cleanup; } info->ubuf += ZIP_CRYPT_HDR_LEN; } if (info->iscompr) { z_stream stream; int err; unsigned int j; /* * Data to decode is compressed, and possibly encrpyted too. If * encrypted, local variable ubuf is used to hold the decrypted but * still compressed data. */ memset(&stream, 0, sizeof(z_stream)); stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; stream.avail_in = z->numCompressedBytes; if (info->isEncrypted) { assert(stream.avail_in >= ZIP_CRYPT_HDR_LEN); stream.avail_in -= ZIP_CRYPT_HDR_LEN; ubuf = (unsigned char *) Tcl_AttemptAlloc(stream.avail_in ? stream.avail_in : 1); if (!ubuf) { goto memoryError; } for (j = 0; j < stream.avail_in; j++) { ch = info->ubuf[j]; ubuf[j] = zdecode(info->keys, crc32tab, ch); } stream.next_in = ubuf; } else { stream.next_in = info->ubuf; } info->ubufSize = info->numBytes ? info->numBytes : 1; info->ubufToFree = (unsigned char *)Tcl_AttemptAlloc(info->ubufSize); info->ubuf = info->ubufToFree; stream.next_out = info->ubuf; if (!info->ubuf) { goto memoryError; } stream.avail_out = info->numBytes; if (inflateInit2(&stream, -15) != Z_OK) { goto corruptionError; } err = inflate(&stream, Z_SYNC_FLUSH); inflateEnd(&stream); /* * Decompression was successful if we're either in the END state, or * in the OK state with no buffered bytes. */ if ((err != Z_STREAM_END) && ((err != Z_OK) || (stream.avail_in != 0))) { goto corruptionError; } /* Even if decompression succeeded, counts should be as expected */ if ((int) stream.total_out != z->numBytes) { goto corruptionError; } if (ubuf) { info->isEncrypted = 0; memset(info->keys, 0, sizeof(info->keys)); Tcl_Free(ubuf); } } else if (info->isEncrypted) { unsigned int j, len; /* * Decode encrypted but uncompressed file, since we support Tcl_Seek() * on it, and it can be randomly accessed later. */ if (z->numCompressedBytes <= ZIP_CRYPT_HDR_LEN || (z->numCompressedBytes - ZIP_CRYPT_HDR_LEN) != z->numBytes) { goto corruptionError; } len = z->numCompressedBytes - ZIP_CRYPT_HDR_LEN; ubuf = (unsigned char *) Tcl_AttemptAlloc(len); if (ubuf == NULL) { goto memoryError; } for (j = 0; j < len; j++) { ch = info->ubuf[j]; ubuf[j] = zdecode(info->keys, crc32tab, ch); } info->ubufSize = len; info->ubufToFree = ubuf; info->ubuf = info->ubufToFree; ubuf = NULL; /* So it does not inadvertently get free on future changes */ info->isEncrypted = 0; } return TCL_OK; corruptionError: ZIPFS_ERROR(interp, "decompression error"); ZIPFS_ERROR_CODE(interp, "CORRUPT"); goto error_cleanup; memoryError: ZIPFS_MEM_ERROR(interp); error_cleanup: if (ubuf) { memset(info->keys, 0, sizeof(info->keys)); Tcl_Free(ubuf); } if (info->ubufToFree) { Tcl_Free(info->ubufToFree); info->ubufToFree = NULL; info->ubuf = NULL; info->ubufSize = 0; } return TCL_ERROR; } /* *------------------------------------------------------------------------- * * ZipEntryStat -- * * This function implements the ZIP filesystem specific version of the * library version of stat. * * Results: * See stat documentation. * * Side effects: * See stat documentation. * *------------------------------------------------------------------------- */ static int ZipEntryStat( char *path, Tcl_StatBuf *buf) { ZipEntry *z; int ret; ReadLock(); z = ZipFSLookup(path); if (z) { memset(buf, 0, sizeof(Tcl_StatBuf)); if (z->isDirectory) { buf->st_mode = S_IFDIR | 0555; } else { buf->st_mode = S_IFREG | 0555; } buf->st_size = z->numBytes; buf->st_mtime = z->timestamp; buf->st_ctime = z->timestamp; buf->st_atime = z->timestamp; ret = 0; } else if (ContainsMountPoint(path, -1)) { /* An intermediate dir under which a mount exists */ memset(buf, 0, sizeof(Tcl_StatBuf)); Tcl_Time t; Tcl_GetTime(&t); buf->st_atime = buf->st_mtime = buf->st_ctime = t.sec; buf->st_mode = S_IFDIR | 0555; ret = 0; } else { Tcl_SetErrno(ENOENT); ret = -1; } Unlock(); return ret; } /* *------------------------------------------------------------------------- * * ZipEntryAccess -- * * This function implements the ZIP filesystem specific version of the * library version of access. * * Results: * See access documentation. * * Side effects: * See access documentation. * *------------------------------------------------------------------------- */ static int ZipEntryAccess( char *path, int mode) { if (mode & X_OK) { return -1; } ReadLock(); int access; ZipEntry *z = ZipFSLookup(path); if (z) { /* Currently existing files read/write but dirs are read-only */ access = (z->isDirectory && (mode & W_OK)) ? -1 : 0; } else { if (mode & W_OK) { access = -1; } else { /* * Even if entry does not exist, could be intermediate dir * containing a mount point */ access = ContainsMountPoint(path, -1) ? 0 : -1; } } Unlock(); return access; } /* *------------------------------------------------------------------------- * * ZipFSOpenFileChannelProc -- * * Open a channel to a file in a mounted ZIP archive. Delegates to * ZipChannelOpen(). * * Results: * Tcl_Channel on success, or NULL on error. * * Side effects: * Allocates memory. * *------------------------------------------------------------------------- */ static Tcl_Channel ZipFSOpenFileChannelProc( Tcl_Interp *interp, /* Current interpreter. */ Tcl_Obj *pathPtr, int mode, TCL_UNUSED(int) /* permissions */) { pathPtr = Tcl_FSGetNormalizedPath(NULL, pathPtr); if (!pathPtr) { return NULL; } return ZipChannelOpen(interp, Tcl_GetString(pathPtr), mode); } /* *------------------------------------------------------------------------- * * ZipFSStatProc -- * * This function implements the ZIP filesystem specific version of the * library version of stat. * * Results: * See stat documentation. * * Side effects: * See stat documentation. * *------------------------------------------------------------------------- */ static int ZipFSStatProc( Tcl_Obj *pathPtr, Tcl_StatBuf *buf) { pathPtr = Tcl_FSGetNormalizedPath(NULL, pathPtr); if (!pathPtr) { return -1; } return ZipEntryStat(TclGetString(pathPtr), buf); } /* *------------------------------------------------------------------------- * * ZipFSAccessProc -- * * This function implements the ZIP filesystem specific version of the * library version of access. * * Results: * See access documentation. * * Side effects: * See access documentation. * *------------------------------------------------------------------------- */ static int ZipFSAccessProc( Tcl_Obj *pathPtr, int mode) { pathPtr = Tcl_FSGetNormalizedPath(NULL, pathPtr); if (!pathPtr) { return -1; } return ZipEntryAccess(TclGetString(pathPtr), mode); } /* *------------------------------------------------------------------------- * * ZipFSFilesystemSeparatorProc -- * * This function returns the separator to be used for a given path. The * object returned should have a refCount of zero * * Results: * A Tcl object, with a refCount of zero. If the caller needs to retain a * reference to the object, it should call Tcl_IncrRefCount, and should * otherwise free the object. * * Side effects: * None. * *------------------------------------------------------------------------- */ static Tcl_Obj * ZipFSFilesystemSeparatorProc( TCL_UNUSED(Tcl_Obj *) /*pathPtr*/) { return Tcl_NewStringObj("/", -1); } /* *------------------------------------------------------------------------- * * AppendWithPrefix -- * * Worker for ZipFSMatchInDirectoryProc() that is a wrapper around * Tcl_ListObjAppendElement() which knows about handling prefixes. * *------------------------------------------------------------------------- */ static inline void AppendWithPrefix( Tcl_Obj *result, /* Where to append a list element to. */ Tcl_DString *prefix, /* The prefix to add to the element, or NULL * for don't do that. */ const char *name, /* The name to append. */ size_t nameLen) /* The length of the name. May be TCL_INDEX_NONE for * append-up-to-NUL-byte. */ { if (prefix) { size_t prefixLength = Tcl_DStringLength(prefix); Tcl_DStringAppend(prefix, name, nameLen); Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj( Tcl_DStringValue(prefix), Tcl_DStringLength(prefix))); Tcl_DStringSetLength(prefix, prefixLength); } else { Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(name, nameLen)); } } /* *------------------------------------------------------------------------- * * ZipFSMatchInDirectoryProc -- * * This routine is used by the globbing code to search a directory for * all files which match a given pattern. * * Results: * The return value is a standard Tcl result indicating whether an error * occurred in globbing. Errors are left in interp, good results are * lappend'ed to result (which must be a valid object). * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSMatchInDirectoryProc( Tcl_Interp *interp, Tcl_Obj *result, /* Where to append matched items to. */ Tcl_Obj *pathPtr, /* Where we are looking. */ const char *pattern, /* What names we are looking for. */ Tcl_GlobTypeData *types) /* What types we are looking for. */ { Tcl_Obj *normPathPtr = Tcl_FSGetNormalizedPath(NULL, pathPtr); int scnt, l; Tcl_Size prefixLen, len, strip = 0; char *pat, *prefix, *path; Tcl_DString dsPref, *prefixBuf = NULL; int foundInHash, notDuplicate; ZipEntry *z; int wanted; /* TCL_GLOB_TYPE* */ if (!normPathPtr) { return -1; } if (types) { wanted = types->type; if ((wanted & TCL_GLOB_TYPE_MOUNT) && (wanted != TCL_GLOB_TYPE_MOUNT)) { if (interp) { ZIPFS_ERROR(interp, "Internal error: TCL_GLOB_TYPE_MOUNT should not " "be set in conjunction with other glob types."); } return TCL_ERROR; } if ((wanted & (TCL_GLOB_TYPE_DIR | TCL_GLOB_TYPE_FILE | TCL_GLOB_TYPE_MOUNT)) == 0) { /* Not looking for files,dirs,mounts. zipfs cannot have others */ return TCL_OK; } wanted &= (TCL_GLOB_TYPE_DIR | TCL_GLOB_TYPE_FILE | TCL_GLOB_TYPE_MOUNT); } else { wanted = TCL_GLOB_TYPE_DIR | TCL_GLOB_TYPE_FILE; } /* * The prefix that gets prepended to results. */ prefix = TclGetStringFromObj(pathPtr, &prefixLen); /* * The (normalized) path we're searching. */ path = TclGetStringFromObj(normPathPtr, &len); Tcl_DStringInit(&dsPref); if (strcmp(prefix, path) == 0) { prefixBuf = NULL; } else { /* * We need to strip the normalized prefix of the filenames and replace * it with the official prefix that we were expecting to get. */ strip = len + 1; Tcl_DStringAppend(&dsPref, prefix, prefixLen); Tcl_DStringAppend(&dsPref, "/", 1); prefix = Tcl_DStringValue(&dsPref); prefixBuf = &dsPref; } ReadLock(); /* * Are we globbing the mount points? */ if (wanted & TCL_GLOB_TYPE_MOUNT) { ZipFSMatchMountPoints(result, normPathPtr, pattern, prefixBuf); goto end; } /* Should not reach here unless at least one of DIR or FILE is set */ assert(wanted & (TCL_GLOB_TYPE_DIR | TCL_GLOB_TYPE_FILE)); /* Does the path exist in the hash table? */ z = ZipFSLookup(path); if (z) { /* * Can we skip the complexity of actual globbing? Without a pattern, * yes; it's a directory existence test. */ if (!pattern || (pattern[0] == '\0')) { /* TODO - can't seem to get to this code from script for tests. */ /* Follow logic of what tclUnixFile.c does */ if ((wanted == (TCL_GLOB_TYPE_DIR | TCL_GLOB_TYPE_FILE)) || (wanted == TCL_GLOB_TYPE_DIR && z->isDirectory) || (wanted == TCL_GLOB_TYPE_FILE && !z->isDirectory)) { Tcl_ListObjAppendElement(NULL, result, pathPtr); } goto end; } } else { /* Not in the hash table but could be an intermediate dir in a mount */ if (!pattern || (pattern[0] == '\0')) { /* TODO - can't seem to get to this code from script for tests. */ if ((wanted & TCL_GLOB_TYPE_DIR) && ContainsMountPoint(path, len)) { Tcl_ListObjAppendElement(NULL, result, pathPtr); } goto end; } } foundInHash = (z != NULL); /* * We've got to work for our supper and do the actual globbing. And all * we've got really is an undifferentiated pile of all the filenames we've * got from all our ZIP mounts. */ l = strlen(pattern); pat = (char *) Tcl_Alloc(len + l + 2); memcpy(pat, path, len); while ((len > 1) && (pat[len - 1] == '/')) { --len; } if ((len > 1) || (pat[0] != '/')) { pat[len] = '/'; ++len; } memcpy(pat + len, pattern, l + 1); scnt = CountSlashes(pat); Tcl_HashTable duplicates; notDuplicate = 0; Tcl_InitHashTable(&duplicates, TCL_STRING_KEYS); Tcl_HashEntry *hPtr; Tcl_HashSearch search; if (foundInHash) { for (hPtr = Tcl_FirstHashEntry(&ZipFS.fileHash, &search); hPtr; hPtr = Tcl_NextHashEntry(&search)) { z = (ZipEntry *)Tcl_GetHashValue(hPtr); if ((wanted == (TCL_GLOB_TYPE_DIR | TCL_GLOB_TYPE_FILE)) || (wanted == TCL_GLOB_TYPE_DIR && z->isDirectory) || (wanted == TCL_GLOB_TYPE_FILE && !z->isDirectory)) { if ((z->depth == scnt) && ((z->flags & ZE_F_VOLUME) == 0) /* Bug 14db54d81e */ && Tcl_StringCaseMatch(z->name, pat, 0)) { Tcl_CreateHashEntry(&duplicates, z->name + strip, ¬Duplicate); assert(notDuplicate); AppendWithPrefix(result, prefixBuf, z->name + strip, -1); } } } } if (wanted & TCL_GLOB_TYPE_DIR) { /* * Also check paths that are ancestors of a mount. e.g. glob * //zipfs:/a/? with mount at //zipfs:/a/b/c. Also have to be * careful about duplicates, such as when another mount is * //zipfs:/a/b/d */ Tcl_DString ds; Tcl_DStringInit(&ds); for (hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &search); hPtr; hPtr = Tcl_NextHashEntry(&search)) { ZipFile *zf = (ZipFile *)Tcl_GetHashValue(hPtr); if (Tcl_StringCaseMatch(zf->mountPoint, pat, 0)) { const char *tail = zf->mountPoint + len; if (*tail == '\0') { continue; } const char *end = strchr(tail, '/'); Tcl_DStringAppend(&ds, zf->mountPoint + strip, end ? (Tcl_Size)(end - zf->mountPoint) : -1); const char *matchedPath = Tcl_DStringValue(&ds); (void)Tcl_CreateHashEntry( &duplicates, matchedPath, ¬Duplicate); if (notDuplicate) { AppendWithPrefix( result, prefixBuf, matchedPath, Tcl_DStringLength(&ds)); } Tcl_DStringFree(&ds); } } } Tcl_DeleteHashTable(&duplicates); Tcl_Free(pat); end: Unlock(); Tcl_DStringFree(&dsPref); return TCL_OK; } /* *------------------------------------------------------------------------- * * ZipFSMatchMountPoints -- * * This routine is a worker for ZipFSMatchInDirectoryProc, used by the * globbing code to search for all mount points files which match a given * pattern. * * Results: * None. * * Side effects: * Adds the matching mounts to the list in result, uses prefix as working * space if it is non-NULL. * *------------------------------------------------------------------------- */ static void ZipFSMatchMountPoints( Tcl_Obj *result, /* The list of matches being built. */ Tcl_Obj *normPathPtr, /* Where we're looking from. */ const char *pattern, /* What we're looking for. NULL for a full * list. */ Tcl_DString *prefix) /* Workspace filled with a prefix for all the * filenames, or NULL if no prefix is to be * used. */ { Tcl_HashEntry *hPtr; Tcl_HashSearch search; int l; Tcl_Size normLength; const char *path = TclGetStringFromObj(normPathPtr, &normLength); Tcl_Size len = normLength; if (len < 1) { /* * Shouldn't happen. But "shouldn't"... */ return; } l = CountSlashes(path); if (path[len - 1] == '/') { len--; } else { l++; } if (!pattern || (pattern[0] == '\0')) { pattern = "*"; } for (hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &search); hPtr; hPtr = Tcl_NextHashEntry(&search)) { ZipFile *zf = (ZipFile *) Tcl_GetHashValue(hPtr); if (zf->mountPointLen == 0) { ZipEntry *z; /* * Enumerate the contents of the ZIP; it's mounted on the root. * TODO - a holdover from androwish? Tcl does not allow mounting * outside of the //zipfs:/ area. */ for (z = zf->topEnts; z; z = z->tnext) { Tcl_Size lenz = strlen(z->name); if ((lenz > len + 1) && (strncmp(z->name, path, len) == 0) && (z->name[len] == '/') && ((int) CountSlashes(z->name) == l) && Tcl_StringCaseMatch(z->name + len + 1, pattern, 0)) { AppendWithPrefix(result, prefix, z->name, lenz); } } } else if ((zf->mountPointLen > len + 1) && (strncmp(zf->mountPoint, path, len) == 0) && (zf->mountPoint[len] == '/') && ((int) CountSlashes(zf->mountPoint) == l) && Tcl_StringCaseMatch(zf->mountPoint + len + 1, pattern, 0)) { /* * Standard mount; append if it matches. */ AppendWithPrefix(result, prefix, zf->mountPoint, zf->mountPointLen); } } } /* *------------------------------------------------------------------------- * * ZipFSPathInFilesystemProc -- * * This function determines if the given path object is in the ZIP * filesystem. * * Results: * TCL_OK when the path object is in the ZIP filesystem, -1 otherwise. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSPathInFilesystemProc( Tcl_Obj *pathPtr, TCL_UNUSED(void **)) { Tcl_Size len; char *path; int ret, decrRef = 0; if (TclFSCwdIsNative() || Tcl_FSGetPathType(pathPtr) == TCL_PATH_ABSOLUTE) { /* * The cwd is native (or path is absolute), use the translated path * without worrying about normalization (this will also usually be * shorter so the utf-to-external conversion will be somewhat faster). */ pathPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr); if (pathPtr == NULL) { return -1; } decrRef = 1; /* Tcl_FSGetTranslatedPath increases refCount */ } else { /* * Make sure the normalized path is set. */ pathPtr = Tcl_FSGetNormalizedPath(NULL, pathPtr); if (!pathPtr) { return -1; } /* Tcl_FSGetNormalizedPath doesn't increase refCount */ } path = TclGetStringFromObj(pathPtr, &len); /* * Claim any path under ZIPFS_VOLUME as ours. This is both a necessary * and sufficient condition as zipfs mounts at arbitrary paths are * not permitted (unlike Androwish). */ ret = ( (len < ZIPFS_VOLUME_LEN) || strncmp(path, ZIPFS_VOLUME, ZIPFS_VOLUME_LEN) ) ? -1 : TCL_OK; if (decrRef) { Tcl_DecrRefCount(pathPtr); } return ret; } /* *------------------------------------------------------------------------- * * ZipFSListVolumesProc -- * * Lists the currently mounted ZIP filesystem volumes. * * Results: * The list of volumes. * * Side effects: * None * *------------------------------------------------------------------------- */ static Tcl_Obj * ZipFSListVolumesProc(void) { return Tcl_NewStringObj(ZIPFS_VOLUME, -1); } /* *------------------------------------------------------------------------- * * ZipFSFileAttrStringsProc -- * * This function implements the ZIP filesystem dependent 'file * attributes' subcommand, for listing the set of possible attribute * strings. * * Results: * An array of strings * * Side effects: * None. * *------------------------------------------------------------------------- */ enum ZipFileAttrs { ZIP_ATTR_UNCOMPSIZE, ZIP_ATTR_COMPSIZE, ZIP_ATTR_OFFSET, ZIP_ATTR_MOUNT, ZIP_ATTR_ARCHIVE, ZIP_ATTR_PERMISSIONS, ZIP_ATTR_CRC }; static const char *const * ZipFSFileAttrStringsProc( TCL_UNUSED(Tcl_Obj *) /*pathPtr*/, TCL_UNUSED(Tcl_Obj **) /*objPtrRef*/) { /* * Must match up with ZipFileAttrs enum above. */ static const char *const attrs[] = { "-uncompsize", "-compsize", "-offset", "-mount", "-archive", "-permissions", "-crc", NULL, }; return attrs; } /* *------------------------------------------------------------------------- * * ZipFSFileAttrsGetProc -- * * This function implements the ZIP filesystem specific 'file attributes' * subcommand, for 'get' operations. * * Results: * Standard Tcl return code. The object placed in objPtrRef (if TCL_OK * was returned) is likely to have a refCount of zero. Either way we must * either store it somewhere (e.g. the Tcl result), or Incr/Decr its * refCount to ensure it is properly freed. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSFileAttrsGetProc( Tcl_Interp *interp, /* Current interpreter. */ int index, Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef) { Tcl_Size len; int ret = TCL_OK; char *path; ZipEntry *z; pathPtr = Tcl_FSGetNormalizedPath(NULL, pathPtr); if (!pathPtr) { return -1; } path = TclGetStringFromObj(pathPtr, &len); ReadLock(); z = ZipFSLookup(path); if (!z && !ContainsMountPoint(path, -1)) { Tcl_SetErrno(ENOENT); ZIPFS_POSIX_ERROR(interp, "file not found"); ret = TCL_ERROR; goto done; } /* z == NULL for intermediate directories that are ancestors of mounts */ switch (index) { case ZIP_ATTR_UNCOMPSIZE: TclNewIntObj(*objPtrRef, z ? z->numBytes : 0); break; case ZIP_ATTR_COMPSIZE: TclNewIntObj(*objPtrRef, z ? z->numCompressedBytes : 0); break; case ZIP_ATTR_OFFSET: TclNewIntObj(*objPtrRef, z ? z->offset : 0); break; case ZIP_ATTR_MOUNT: if (z) { *objPtrRef = Tcl_NewStringObj(z->zipFilePtr->mountPoint, z->zipFilePtr->mountPointLen); } else { *objPtrRef = Tcl_NewStringObj("", 0); } break; case ZIP_ATTR_ARCHIVE: *objPtrRef = Tcl_NewStringObj(z ? z->zipFilePtr->name : "", -1); break; case ZIP_ATTR_PERMISSIONS: *objPtrRef = Tcl_NewStringObj("0o555", -1); break; case ZIP_ATTR_CRC: TclNewIntObj(*objPtrRef, z ? z->crc32 : 0); break; default: ZIPFS_ERROR(interp, "unknown attribute"); ZIPFS_ERROR_CODE(interp, "FILE_ATTR"); ret = TCL_ERROR; } done: Unlock(); return ret; } /* *------------------------------------------------------------------------- * * ZipFSFileAttrsSetProc -- * * This function implements the ZIP filesystem specific 'file attributes' * subcommand, for 'set' operations. * * Results: * Standard Tcl return code. * * Side effects: * None. * *------------------------------------------------------------------------- */ static int ZipFSFileAttrsSetProc( Tcl_Interp *interp, /* Current interpreter. */ TCL_UNUSED(int) /*index*/, TCL_UNUSED(Tcl_Obj *) /*pathPtr*/, TCL_UNUSED(Tcl_Obj *) /*objPtr*/) { ZIPFS_ERROR(interp, "unsupported operation"); ZIPFS_ERROR_CODE(interp, "UNSUPPORTED_OP"); return TCL_ERROR; } /* *------------------------------------------------------------------------- * * ZipFSFilesystemPathTypeProc -- * * Results: * * Side effects: * *------------------------------------------------------------------------- */ static Tcl_Obj * ZipFSFilesystemPathTypeProc( TCL_UNUSED(Tcl_Obj *) /*pathPtr*/) { return Tcl_NewStringObj("zip", -1); } /* *------------------------------------------------------------------------- * * ZipFSLoadFile -- * * This functions deals with loading native object code. If the given * path object refers to a file within the ZIP filesystem, an approriate * error code is returned to delegate loading to the caller (by copying * the file to temp store and loading from there). As fallback when the * file refers to the ZIP file system but is not present, it is looked up * relative to the executable and loaded from there when available. * * Results: * TCL_OK on success, TCL_ERROR otherwise with error message left. * * Side effects: * Loads native code into the process address space. * *------------------------------------------------------------------------- */ static int ZipFSLoadFile( Tcl_Interp *interp, /* Current interpreter. */ Tcl_Obj *path, Tcl_LoadHandle *loadHandle, Tcl_FSUnloadFileProc **unloadProcPtr, int flags) { Tcl_FSLoadFileProc2 *loadFileProc; #ifdef ANDROID /* * Force loadFileProc to native implementation since the package manager * already extracted the shared libraries from the APK at install time. */ loadFileProc = (Tcl_FSLoadFileProc2 *) tclNativeFilesystem.loadFileProc; if (loadFileProc) { return loadFileProc(interp, path, loadHandle, unloadProcPtr, flags); } Tcl_SetErrno(ENOENT); ZIPFS_ERROR(interp, Tcl_PosixError(interp)); return TCL_ERROR; #else /* !ANDROID */ Tcl_Obj *altPath = NULL; int ret = TCL_ERROR; Tcl_Obj *objs[2] = { NULL, NULL }; if (Tcl_FSAccess(path, R_OK) == 0) { /* * EXDEV should trigger loading by copying to temp store. */ Tcl_SetErrno(EXDEV); ZIPFS_ERROR(interp, Tcl_PosixError(interp)); return ret; } objs[1] = TclPathPart(interp, path, TCL_PATH_DIRNAME); if (objs[1] && (ZipFSAccessProc(objs[1], R_OK) == 0)) { const char *execName = Tcl_GetNameOfExecutable(); /* * Shared object is not in ZIP but its path prefix is, thus try to * load from directory where the executable came from. */ TclDecrRefCount(objs[1]); objs[1] = TclPathPart(interp, path, TCL_PATH_TAIL); /* * Get directory name of executable manually to deal with cases where * [file dirname [info nameofexecutable]] is equal to [info * nameofexecutable] due to VFS effects. */ if (execName) { const char *p = strrchr(execName, '/'); if (p && p > execName + 1) { --p; objs[0] = Tcl_NewStringObj(execName, p - execName); } } if (!objs[0]) { objs[0] = TclPathPart(interp, TclGetObjNameOfExecutable(), TCL_PATH_DIRNAME); } if (objs[0]) { altPath = TclJoinPath(2, objs, 0); if (altPath) { Tcl_IncrRefCount(altPath); if (Tcl_FSAccess(altPath, R_OK) == 0) { path = altPath; } } } } if (objs[0]) { Tcl_DecrRefCount(objs[0]); } if (objs[1]) { Tcl_DecrRefCount(objs[1]); } loadFileProc = (Tcl_FSLoadFileProc2 *) (void *) tclNativeFilesystem.loadFileProc; if (loadFileProc) { ret = loadFileProc(interp, path, loadHandle, unloadProcPtr, flags); } else { Tcl_SetErrno(ENOENT); ZIPFS_ERROR(interp, Tcl_PosixError(interp)); } if (altPath) { Tcl_DecrRefCount(altPath); } return ret; #endif /* ANDROID */ } /* *------------------------------------------------------------------------- * * TclZipfs_Init -- * * Perform per interpreter initialization of this module. * * Results: * The return value is a standard Tcl result. * * Side effects: * Initializes this module if not already initialized, and adds module * related commands to the given interpreter. * *------------------------------------------------------------------------- */ int TclZipfs_Init( Tcl_Interp *interp) /* Current interpreter. */ { static const EnsembleImplMap initMap[] = { {"mkimg", ZipFSMkImgObjCmd, NULL, NULL, NULL, 1}, {"mkzip", ZipFSMkZipObjCmd, NULL, NULL, NULL, 1}, {"lmkimg", ZipFSLMkImgObjCmd, NULL, NULL, NULL, 1}, {"lmkzip", ZipFSLMkZipObjCmd, NULL, NULL, NULL, 1}, {"mount", ZipFSMountObjCmd, NULL, NULL, NULL, 1}, {"mountdata", ZipFSMountBufferObjCmd, NULL, NULL, NULL, 1}, {"unmount", ZipFSUnmountObjCmd, NULL, NULL, NULL, 1}, {"mkkey", ZipFSMkKeyObjCmd, NULL, NULL, NULL, 1}, {"exists", ZipFSExistsObjCmd, NULL, NULL, NULL, 1}, {"info", ZipFSInfoObjCmd, NULL, NULL, NULL, 1}, {"list", ZipFSListObjCmd, NULL, NULL, NULL, 1}, {"canonical", ZipFSCanonicalObjCmd, NULL, NULL, NULL, 1}, {"root", ZipFSRootObjCmd, NULL, NULL, NULL, 1}, {NULL, NULL, NULL, NULL, NULL, 0} }; static const char findproc[] = "namespace eval ::tcl::zipfs {}\n" "proc ::tcl::zipfs::Find dir {\n" " set result {}\n" " if {[catch {\n" " concat [glob -directory $dir -nocomplain *] [glob -directory $dir -types hidden -nocomplain *]\n" " } list]} {\n" " return $result\n" " }\n" " foreach file $list {\n" " if {[file tail $file] in {. ..}} {\n" " continue\n" " }\n" " lappend result $file {*}[Find $file]\n" " }\n" " return $result\n" "}\n" "proc ::tcl::zipfs::find {directoryName} {\n" " return [lsort [Find $directoryName]]\n" "}\n"; /* * One-time initialization. */ WriteLock(); if (!ZipFS.initialized) { ZipfsSetup(); } Unlock(); if (interp) { Tcl_Command ensemble; Tcl_Obj *mapObj; Tcl_EvalEx(interp, findproc, TCL_INDEX_NONE, TCL_EVAL_GLOBAL); if (!Tcl_IsSafe(interp)) { Tcl_LinkVar(interp, "::tcl::zipfs::wrmax", (char *) &ZipFS.wrmax, TCL_LINK_INT); Tcl_LinkVar(interp, "::tcl::zipfs::fallbackEntryEncoding", (char *) &ZipFS.fallbackEntryEncoding, TCL_LINK_STRING); } ensemble = TclMakeEnsemble(interp, "zipfs", Tcl_IsSafe(interp) ? (initMap + 4) : initMap); /* * Add the [zipfs find] subcommand. */ Tcl_GetEnsembleMappingDict(NULL, ensemble, &mapObj); TclDictPutString(NULL, mapObj, "find", "::tcl::zipfs::find"); Tcl_CreateObjCommand(interp, "::tcl::zipfs::tcl_library_init", ZipFSTclLibraryObjCmd, NULL, NULL); } return TCL_OK; } #if !defined(STATIC_BUILD) static int ZipfsAppHookFindTclInit( const char *archive) { Tcl_Obj *vfsInitScript; int found; if (zipfs_literal_tcl_library) { return TCL_ERROR; } if (TclZipfs_Mount(NULL, archive, ZIPFS_ZIP_MOUNT, NULL)) { /* Either the file doesn't exist or it is not a zip archive */ return TCL_ERROR; } TclNewLiteralStringObj(vfsInitScript, ZIPFS_ZIP_MOUNT "/init.tcl"); Tcl_IncrRefCount(vfsInitScript); found = Tcl_FSAccess(vfsInitScript, F_OK); Tcl_DecrRefCount(vfsInitScript); if (found == 0) { zipfs_literal_tcl_library = ZIPFS_ZIP_MOUNT; return TCL_OK; } TclNewLiteralStringObj(vfsInitScript, ZIPFS_ZIP_MOUNT "/tcl_library/init.tcl"); Tcl_IncrRefCount(vfsInitScript); found = Tcl_FSAccess(vfsInitScript, F_OK); Tcl_DecrRefCount(vfsInitScript); if (found == 0) { zipfs_literal_tcl_library = ZIPFS_ZIP_MOUNT "/tcl_library"; return TCL_OK; } return TCL_ERROR; } #endif /* *------------------------------------------------------------------------ * * TclZipfsFinalize -- * * Frees all zipfs resources IRRESPECTIVE of open channels (there should * not be any!) etc. To be called at process exit time (from * Tcl_Finalize->TclFinalizeFilesystem) * * Results: * None. * * Side effects: * Frees up archives loaded into memory. * *------------------------------------------------------------------------ */ void TclZipfsFinalize(void) { WriteLock(); if (!ZipFS.initialized) { Unlock(); return; } Tcl_HashEntry *hPtr; Tcl_HashSearch zipSearch; for (hPtr = Tcl_FirstHashEntry(&ZipFS.zipHash, &zipSearch); hPtr; hPtr = Tcl_NextHashEntry(&zipSearch)) { ZipFile *zf = (ZipFile *) Tcl_GetHashValue(hPtr); Tcl_DeleteHashEntry(hPtr); CleanupMount(zf); /* Frees file entries belonging to the archive */ ZipFSCloseArchive(NULL, zf); Tcl_Free(zf); } Tcl_FSUnregister(&zipfsFilesystem); Tcl_DeleteHashTable(&ZipFS.fileHash); Tcl_DeleteHashTable(&ZipFS.zipHash); if (ZipFS.fallbackEntryEncoding) { Tcl_Free(ZipFS.fallbackEntryEncoding); ZipFS.fallbackEntryEncoding = NULL; } ZipFS.initialized = 0; Unlock(); } /* *------------------------------------------------------------------------- * * TclZipfs_AppHook -- * * Performs the argument munging for the shell * *------------------------------------------------------------------------- */ const char * TclZipfs_AppHook( #ifdef SUPPORT_BUILTIN_ZIP_INSTALL int *argcPtr, /* Pointer to argc */ #else TCL_UNUSED(int *), /*argcPtr*/ #endif #ifdef _WIN32 TCL_UNUSED(WCHAR ***)) /* argvPtr */ #else /* !_WIN32 */ char ***argvPtr) /* Pointer to argv */ #endif /* _WIN32 */ { const char *archive; const char *result; #ifdef _WIN32 result = Tcl_FindExecutable(NULL); #else result = Tcl_FindExecutable((*argvPtr)[0]); #endif archive = Tcl_GetNameOfExecutable(); TclZipfs_Init(NULL); /* * Look for init.tcl in one of the locations mounted later in this * function. */ if (!TclZipfs_Mount(NULL, archive, ZIPFS_APP_MOUNT, NULL)) { int found; Tcl_Obj *vfsInitScript; TclNewLiteralStringObj(vfsInitScript, ZIPFS_APP_MOUNT "/main.tcl"); Tcl_IncrRefCount(vfsInitScript); if (Tcl_FSAccess(vfsInitScript, F_OK) == 0) { /* * Startup script should be set before calling Tcl_AppInit */ Tcl_SetStartupScript(vfsInitScript, NULL); } else { Tcl_DecrRefCount(vfsInitScript); } /* * Set Tcl Encodings */ if (!zipfs_literal_tcl_library) { TclNewLiteralStringObj(vfsInitScript, ZIPFS_APP_MOUNT "/tcl_library/init.tcl"); Tcl_IncrRefCount(vfsInitScript); found = Tcl_FSAccess(vfsInitScript, F_OK); Tcl_DecrRefCount(vfsInitScript); if (found == TCL_OK) { zipfs_literal_tcl_library = ZIPFS_APP_MOUNT "/tcl_library"; return result; } } #ifdef SUPPORT_BUILTIN_ZIP_INSTALL } else if (*argcPtr > 1) { /* * If the first argument is "install", run the supplied installer * script. */ #ifdef _WIN32 Tcl_DString ds; Tcl_DStringInit(&ds); archive = Tcl_WCharToUtfDString((*argvPtr)[1], TCL_INDEX_NONE, &ds); #else /* !_WIN32 */ archive = (*argvPtr)[1]; #endif /* _WIN32 */ if (strcmp(archive, "install") == 0) { Tcl_Obj *vfsInitScript; /* * Run this now to ensure the file is present by the time Tcl_Main * wants it. */ TclZipfs_TclLibrary(); TclNewLiteralStringObj(vfsInitScript, ZIPFS_ZIP_MOUNT "/tcl_library/install.tcl"); Tcl_IncrRefCount(vfsInitScript); if (Tcl_FSAccess(vfsInitScript, F_OK) == 0) { Tcl_SetStartupScript(vfsInitScript, NULL); } return result; } else if (!TclZipfs_Mount(NULL, archive, ZIPFS_APP_MOUNT, NULL)) { int found; Tcl_Obj *vfsInitScript; TclNewLiteralStringObj(vfsInitScript, ZIPFS_APP_MOUNT "/main.tcl"); Tcl_IncrRefCount(vfsInitScript); if (Tcl_FSAccess(vfsInitScript, F_OK) == 0) { /* * Startup script should be set before calling Tcl_AppInit */ Tcl_SetStartupScript(vfsInitScript, NULL); } else { Tcl_DecrRefCount(vfsInitScript); } /* Set Tcl Encodings */ TclNewLiteralStringObj(vfsInitScript, ZIPFS_APP_MOUNT "/tcl_library/init.tcl"); Tcl_IncrRefCount(vfsInitScript); found = Tcl_FSAccess(vfsInitScript, F_OK); Tcl_DecrRefCount(vfsInitScript); if (found == TCL_OK) { zipfs_literal_tcl_library = ZIPFS_APP_MOUNT "/tcl_library"; return result; } } #ifdef _WIN32 Tcl_DStringFree(&ds); #endif /* _WIN32 */ #endif /* SUPPORT_BUILTIN_ZIP_INSTALL */ } return result; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tclZlib.c0000644000175000017500000032503414726623136014750 0ustar sergeisergei/* * tclZlib.c -- * * This file provides the interface to the Zlib library. * * Copyright © 2004-2005 Pascal Scheffers * Copyright © 2005 Unitas Software B.V. * Copyright © 2008-2012 Donal K. Fellows * * Parts written by Jean-Claude Wippler, as part of Tclkit, placed in the * public domain March 2003. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "zlib.h" #include "tclIO.h" /* * The version of the zlib "package" that this implements. Note that this * thoroughly supersedes the versions included with tclkit, which are "1.1", * so this is at least "2.0" (there's no general *commitment* to have the same * interface, even if that is mostly true). */ #define TCL_ZLIB_VERSION "2.0.1" /* * Magic flags used with wbits fields to indicate that we're handling the gzip * format or automatic detection of format. Putting it here is slightly less * gross! */ enum WBitsFlags { WBITS_RAW = (-MAX_WBITS), /* RAW compressed data */ WBITS_ZLIB = (MAX_WBITS), /* Zlib-format compressed data */ WBITS_GZIP = (MAX_WBITS | 16), /* Gzip-format compressed data */ WBITS_AUTODETECT = (MAX_WBITS | 32) /* Auto-detect format from its header */ }; /* * Structure used for handling gzip headers that are generated from a * dictionary. It comprises the header structure itself plus some working * space that it is very convenient to have attached. */ #define MAX_COMMENT_LEN 256 typedef struct { gz_header header; char nativeFilenameBuf[MAXPATHLEN]; char nativeCommentBuf[MAX_COMMENT_LEN]; } GzipHeader; /* * Structure used for the Tcl_ZlibStream* commands and [zlib stream ...] */ typedef struct { Tcl_Interp *interp; z_stream stream; /* The interface to the zlib library. */ int streamEnd; /* If we've got to end-of-stream. */ Tcl_Obj *inData, *outData; /* Input / output buffers (lists) */ Tcl_Obj *currentInput; /* Pointer to what is currently being * inflated. */ Tcl_Size outPos; /* Index into output buffer to write to next. */ int mode; /* Either TCL_ZLIB_STREAM_DEFLATE or * TCL_ZLIB_STREAM_INFLATE. */ int format; /* Flags from the TCL_ZLIB_FORMAT_* */ int level; /* Default 5, 0-9 */ int flush; /* Stores the flush param for deferred the * decompression. */ int wbits; /* The encoded compression mode, so we can * restart the stream if necessary. */ Tcl_Command cmd; /* Token for the associated Tcl command. */ Tcl_Obj *compDictObj; /* Byte-array object containing compression * dictionary (not dictObj!) to use if * necessary. */ int flags; /* Miscellaneous flag bits. */ GzipHeader *gzHeaderPtr; /* If we've allocated a gzip header * structure. */ } ZlibStreamHandle; enum ZlibStreamHandleFlags { DICT_TO_SET = 0x1 /* If we need to set a compression dictionary * in the low-level engine at the next * opportunity. */ }; /* * Macros to make it clearer in some of the twiddlier accesses what is * happening. */ #define IsRawStream(zshPtr) ((zshPtr)->format == TCL_ZLIB_FORMAT_RAW) #define HaveDictToSet(zshPtr) ((zshPtr)->flags & DICT_TO_SET) #define DictWasSet(zshPtr) ((zshPtr)->flags |= ~DICT_TO_SET) /* * Structure used for stacked channel compression and decompression. */ typedef struct { Tcl_Channel chan; /* Reference to the channel itself. */ Tcl_Channel parent; /* The underlying source and sink of bytes. */ int flags; /* General flag bits, see below... */ int mode; /* Either the value TCL_ZLIB_STREAM_DEFLATE * for compression on output, or * TCL_ZLIB_STREAM_INFLATE for decompression * on input. */ int format; /* What format of data is going on the wire. * Needed so that the correct [fconfigure] * options can be enabled. */ unsigned int readAheadLimit;/* The maximum number of bytes to read from * the underlying stream in one go. */ z_stream inStream; /* Structure used by zlib for decompression of * input. */ z_stream outStream; /* Structure used by zlib for compression of * output. */ char *inBuffer, *outBuffer; /* Working buffers. */ size_t inAllocated, outAllocated; /* Sizes of working buffers. */ GzipHeader inHeader; /* Header read from input stream, when * decompressing a gzip stream. */ GzipHeader outHeader; /* Header to write to an output stream, when * compressing a gzip stream. */ Tcl_TimerToken timer; /* Timer used for keeping events fresh. */ Tcl_Obj *compDictObj; /* Byte-array object containing compression * dictionary (not dictObj!) to use if * necessary. */ } ZlibChannelData; /* * Value bits for the ZlibChannelData::flags field. */ enum ZlibChannelDataFlags { ASYNC = 0x01, /* Set if this is an asynchronous channel. */ IN_HEADER = 0x02, /* Set if the inHeader field has been * registered with the input compressor. */ OUT_HEADER = 0x04, /* Set if the outputHeader field has been * registered with the output decompressor. */ STREAM_DECOMPRESS = 0x08, /* Set to signal decompress pending data. */ STREAM_DONE = 0x10 /* Set to signal stream end up to transform * input. */ }; /* * Size of buffers allocated by default, and the range it can be set to. The * same sorts of values apply to streams, except with different limits (they * permit byte-level activity). Channels always use bytes unless told to use * larger buffers. */ #define DEFAULT_BUFFER_SIZE 4096 #define MIN_NONSTREAM_BUFFER_SIZE 16 #define MAX_BUFFER_SIZE 65536 /* * Prototypes for private procedures defined later in this file: */ static Tcl_CmdDeleteProc ZlibStreamCmdDelete; static Tcl_DriverBlockModeProc ZlibTransformBlockMode; static Tcl_DriverClose2Proc ZlibTransformClose; static Tcl_DriverGetHandleProc ZlibTransformGetHandle; static Tcl_DriverGetOptionProc ZlibTransformGetOption; static Tcl_DriverHandlerProc ZlibTransformEventHandler; static Tcl_DriverInputProc ZlibTransformInput; static Tcl_DriverOutputProc ZlibTransformOutput; static Tcl_DriverSetOptionProc ZlibTransformSetOption; static Tcl_DriverWatchProc ZlibTransformWatch; static Tcl_ObjCmdProc ZlibCmd; static Tcl_ObjCmdProc ZlibStreamCmd; static Tcl_ObjCmdProc ZlibStreamAddCmd; static Tcl_ObjCmdProc ZlibStreamHeaderCmd; static Tcl_ObjCmdProc ZlibStreamPutCmd; static void ConvertError(Tcl_Interp *interp, int code, uLong adler); static Tcl_Obj * ConvertErrorToList(int code, uLong adler); static inline int Deflate(z_streamp strm, void *bufferPtr, size_t bufferSize, int flush, size_t *writtenPtr); static void ExtractHeader(gz_header *headerPtr, Tcl_Obj *dictObj); static int GenerateHeader(Tcl_Interp *interp, Tcl_Obj *dictObj, GzipHeader *headerPtr, int *extraSizePtr); static int ZlibPushSubcmd(Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static int ResultDecompress(ZlibChannelData *chanDataPtr, char *buf, int toRead, int flush, int *errorCodePtr); static Tcl_Channel ZlibStackChannelTransform(Tcl_Interp *interp, int mode, int format, int level, int limit, Tcl_Channel channel, Tcl_Obj *gzipHeaderDictPtr, Tcl_Obj *compDictObj); static void ZlibStreamCleanup(ZlibStreamHandle *zshPtr); static int ZlibStreamSubcmd(Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static inline void ZlibTransformEventTimerKill( ZlibChannelData *chanDataPtr); static void ZlibTransformTimerRun(void *clientData); /* * Type of zlib-based compressing and decompressing channels. */ static const Tcl_ChannelType zlibChannelType = { "zlib", TCL_CHANNEL_VERSION_5, NULL, /* Deprecated. */ ZlibTransformInput, ZlibTransformOutput, NULL, /* Deprecated. */ ZlibTransformSetOption, ZlibTransformGetOption, ZlibTransformWatch, ZlibTransformGetHandle, ZlibTransformClose, ZlibTransformBlockMode, NULL, /* Flush proc. */ ZlibTransformEventHandler, NULL, /* Seek proc. */ NULL, /* Thread action proc. */ NULL /* Truncate proc. */ }; /* *---------------------------------------------------------------------- * * Latin1 -- * Helper to definitely get the ISO 8859-1 encoding. It's internally * defined by Tcl so this operation should always succeed. * *---------------------------------------------------------------------- */ static inline Tcl_Encoding Latin1(void) { Tcl_Encoding latin1enc = Tcl_GetEncoding(NULL, "iso8859-1"); if (latin1enc == NULL) { Tcl_Panic("no latin-1 encoding"); } return latin1enc; } /* *---------------------------------------------------------------------- * * ConvertError -- * * Utility function for converting a zlib error into a Tcl error. * * Results: * None. * * Side effects: * Updates the interpreter result and errorcode. * *---------------------------------------------------------------------- */ static void ConvertError( Tcl_Interp *interp, /* Interpreter to store the error in. May be * NULL, in which case nothing happens. */ int code, /* The zlib error code. */ uLong adler) /* The checksum expected (for Z_NEED_DICT) */ { const char *codeStr, *codeStr2 = NULL; char codeStrBuf[TCL_INTEGER_SPACE]; if (interp == NULL) { return; } switch (code) { /* * Firstly, the case that is *different* because it's really coming * from the OS and is just being reported via zlib. It should be * really uncommon because Tcl handles all I/O rather than delegating * it to zlib, but proving it can't happen is hard. */ case Z_ERRNO: Tcl_SetObjResult(interp, Tcl_NewStringObj( Tcl_PosixError(interp), TCL_AUTO_LENGTH)); return; /* * Normal errors/conditions, some of which have additional detail and * some which don't. (This is not defined by array lookup because zlib * error codes are sometimes negative.) */ case Z_STREAM_ERROR: codeStr = "STREAM"; break; case Z_DATA_ERROR: codeStr = "DATA"; break; case Z_MEM_ERROR: codeStr = "MEM"; break; case Z_BUF_ERROR: codeStr = "BUF"; break; case Z_VERSION_ERROR: codeStr = "VERSION"; break; case Z_NEED_DICT: codeStr = "NEED_DICT"; codeStr2 = codeStrBuf; snprintf(codeStrBuf, sizeof(codeStrBuf), "%lu", adler); break; /* * These should _not_ happen! This function is for dealing with error * cases, not non-errors! */ case Z_OK: Tcl_Panic("unexpected zlib result in error handler: Z_OK"); case Z_STREAM_END: Tcl_Panic("unexpected zlib result in error handler: Z_STREAM_END"); /* * Anything else is bad news; it's unexpected. Convert to generic * error. */ default: codeStr = "UNKNOWN"; codeStr2 = codeStrBuf; snprintf(codeStrBuf, sizeof(codeStrBuf), "%d", code); break; } Tcl_SetObjResult(interp, Tcl_NewStringObj(zError(code), TCL_AUTO_LENGTH)); /* * Tricky point! We might pass NULL twice here (and will when the error * type is known). */ Tcl_SetErrorCode(interp, "TCL", "ZLIB", codeStr, codeStr2, (char *)NULL); } static Tcl_Obj * ConvertErrorToList( int code, /* The zlib error code. */ uLong adler) /* The checksum expected (for Z_NEED_DICT) */ { Tcl_Obj *objv[4]; TclNewLiteralStringObj(objv[0], "TCL"); TclNewLiteralStringObj(objv[1], "ZLIB"); switch (code) { case Z_STREAM_ERROR: TclNewLiteralStringObj(objv[2], "STREAM"); return Tcl_NewListObj(3, objv); case Z_DATA_ERROR: TclNewLiteralStringObj(objv[2], "DATA"); return Tcl_NewListObj(3, objv); case Z_MEM_ERROR: TclNewLiteralStringObj(objv[2], "MEM"); return Tcl_NewListObj(3, objv); case Z_BUF_ERROR: TclNewLiteralStringObj(objv[2], "BUF"); return Tcl_NewListObj(3, objv); case Z_VERSION_ERROR: TclNewLiteralStringObj(objv[2], "VERSION"); return Tcl_NewListObj(3, objv); case Z_ERRNO: TclNewLiteralStringObj(objv[2], "POSIX"); objv[3] = Tcl_NewStringObj(Tcl_ErrnoId(), TCL_AUTO_LENGTH); return Tcl_NewListObj(4, objv); case Z_NEED_DICT: TclNewLiteralStringObj(objv[2], "NEED_DICT"); TclNewIntObj(objv[3], (Tcl_WideInt) adler); return Tcl_NewListObj(4, objv); /* * These should _not_ happen! This function is for dealing with error * cases, not non-errors! */ case Z_OK: Tcl_Panic("unexpected zlib result in error handler: Z_OK"); case Z_STREAM_END: Tcl_Panic("unexpected zlib result in error handler: Z_STREAM_END"); /* * Catch-all. Should be unreachable because all cases are already * listed above. */ default: TclNewLiteralStringObj(objv[2], "UNKNOWN"); TclNewIntObj(objv[3], code); return Tcl_NewListObj(4, objv); } } /* *---------------------------------------------------------------------- * * GenerateHeader -- * * Function for creating a gzip header from the contents of a dictionary * (as described in the documentation). * * Results: * A Tcl result code. * * Side effects: * Updates the fields of the given gz_header structure. Adds amount of * extra space required for the header to the variable referenced by the * extraSizePtr argument. * *---------------------------------------------------------------------- */ static int GenerateHeader( Tcl_Interp *interp, /* Where to put error messages. */ Tcl_Obj *dictObj, /* The dictionary whose contents are to be * parsed. */ GzipHeader *headerPtr, /* Where to store the parsed-out values. */ int *extraSizePtr) /* Variable to add the length of header * strings (filename, comment) to. */ { Tcl_Obj *value; int len, result = TCL_ERROR; Tcl_Size length; Tcl_WideInt wideValue = 0; const char *valueStr; Tcl_Encoding latin1enc = Latin1(); static const char *const types[] = { "binary", "text" }; if (TclDictGet(interp, dictObj, "comment", &value) != TCL_OK) { goto error; } else if (value != NULL) { Tcl_EncodingState state; valueStr = TclGetStringFromObj(value, &length); result = Tcl_UtfToExternal(NULL, latin1enc, valueStr, length, TCL_ENCODING_START|TCL_ENCODING_END|TCL_ENCODING_PROFILE_STRICT, &state, headerPtr->nativeCommentBuf, MAX_COMMENT_LEN - 1, NULL, &len, NULL); if (result != TCL_OK) { if (interp) { if (result == TCL_CONVERT_UNKNOWN) { Tcl_AppendResult(interp, "Comment contains characters > 0xFF", (char *)NULL); } else { Tcl_AppendResult(interp, "Comment too large for zip", (char *)NULL); } } result = TCL_ERROR; /* TCL_CONVERT_* -> TCL_ERROR */ goto error; } headerPtr->nativeCommentBuf[len] = '\0'; headerPtr->header.comment = (Bytef *) headerPtr->nativeCommentBuf; if (extraSizePtr != NULL) { *extraSizePtr += len; } } if (TclDictGet(interp, dictObj, "crc", &value) != TCL_OK) { goto error; } else if (value != NULL && Tcl_GetBooleanFromObj(interp, value, &headerPtr->header.hcrc)) { goto error; } if (TclDictGet(interp, dictObj, "filename", &value) != TCL_OK) { goto error; } else if (value != NULL) { Tcl_EncodingState state; valueStr = TclGetStringFromObj(value, &length); result = Tcl_UtfToExternal(NULL, latin1enc, valueStr, length, TCL_ENCODING_START|TCL_ENCODING_END|TCL_ENCODING_PROFILE_STRICT, &state, headerPtr->nativeFilenameBuf, MAXPATHLEN - 1, NULL, &len, NULL); if (result != TCL_OK) { if (interp) { if (result == TCL_CONVERT_UNKNOWN) { Tcl_AppendResult(interp, "Filename contains characters > 0xFF", (char *)NULL); } else { Tcl_AppendResult(interp, "Filename too large for zip", (char *)NULL); } } result = TCL_ERROR; /* TCL_CONVERT_* -> TCL_ERROR */ goto error; } headerPtr->nativeFilenameBuf[len] = '\0'; headerPtr->header.name = (Bytef *) headerPtr->nativeFilenameBuf; if (extraSizePtr != NULL) { *extraSizePtr += len; } } if (TclDictGet(interp, dictObj, "os", &value) != TCL_OK) { goto error; } else if (value != NULL && Tcl_GetIntFromObj(interp, value, &headerPtr->header.os) != TCL_OK) { goto error; } /* * Ignore the 'size' field, since that is controlled by the size of the * input data. */ if (TclDictGet(interp, dictObj, "time", &value) != TCL_OK) { goto error; } else if (value != NULL && TclGetWideIntFromObj(interp, value, &wideValue) != TCL_OK) { goto error; } headerPtr->header.time = wideValue; if (TclDictGet(interp, dictObj, "type", &value) != TCL_OK) { goto error; } else if (value != NULL && Tcl_GetIndexFromObj(interp, value, types, "type", TCL_EXACT, &headerPtr->header.text) != TCL_OK) { goto error; } result = TCL_OK; error: Tcl_FreeEncoding(latin1enc); return result; } /* *---------------------------------------------------------------------- * * ExtractHeader -- * * Take the values out of a gzip header and store them in a dictionary. * * Results: * None. * * Side effects: * Updates the dictionary, which must be writable (i.e. refCount < 2). * *---------------------------------------------------------------------- */ static void ExtractHeader( gz_header *headerPtr, /* The gzip header to extract from. */ Tcl_Obj *dictObj) /* The dictionary to store in. */ { Tcl_Encoding latin1enc = NULL; /* RFC 1952 says that header strings are in * ISO 8859-1 (LATIN-1). */ Tcl_DString tmp; if (headerPtr->comment != Z_NULL) { latin1enc = Latin1(); (void) Tcl_ExternalToUtfDString(latin1enc, (char *) headerPtr->comment, TCL_AUTO_LENGTH, &tmp); TclDictPut(NULL, dictObj, "comment", Tcl_DStringToObj(&tmp)); } TclDictPut(NULL, dictObj, "crc", Tcl_NewBooleanObj(headerPtr->hcrc)); if (headerPtr->name != Z_NULL) { if (latin1enc == NULL) { latin1enc = Latin1(); } (void) Tcl_ExternalToUtfDString(latin1enc, (char *) headerPtr->name, TCL_AUTO_LENGTH, &tmp); TclDictPut(NULL, dictObj, "filename", Tcl_DStringToObj(&tmp)); } if (headerPtr->os != 255) { TclDictPut(NULL, dictObj, "os", Tcl_NewWideIntObj(headerPtr->os)); } if (headerPtr->time != 0 /* magic - no time */) { TclDictPut(NULL, dictObj, "time", Tcl_NewWideIntObj(headerPtr->time)); } if (headerPtr->text != Z_UNKNOWN) { TclDictPutString(NULL, dictObj, "type", headerPtr->text ? "text" : "binary"); } if (latin1enc != NULL) { Tcl_FreeEncoding(latin1enc); } } /* * Disentangle the worst of how the zlib API is used. */ static int SetInflateDictionary( z_streamp strm, Tcl_Obj *compDictObj) { if (compDictObj != NULL) { Tcl_Size length = 0; unsigned char *bytes = Tcl_GetBytesFromObj(NULL, compDictObj, &length); if (bytes == NULL) { return Z_DATA_ERROR; } return inflateSetDictionary(strm, bytes, length); } return Z_OK; } static int SetDeflateDictionary( z_streamp strm, Tcl_Obj *compDictObj) { if (compDictObj != NULL) { Tcl_Size length = 0; unsigned char *bytes = Tcl_GetBytesFromObj(NULL, compDictObj, &length); if (bytes == NULL) { return Z_DATA_ERROR; } return deflateSetDictionary(strm, bytes, length); } return Z_OK; } static inline int Deflate( z_streamp strm, void *bufferPtr, size_t bufferSize, int flush, size_t *writtenPtr) { strm->next_out = (Bytef *) bufferPtr; strm->avail_out = bufferSize; int e = deflate(strm, flush); if (writtenPtr != NULL) { *writtenPtr = bufferSize - strm->avail_out; } return e; } static inline void AppendByteArray( Tcl_Obj *listObj, void *buffer, size_t size) { if (size > 0) { Tcl_Obj *baObj = Tcl_NewByteArrayObj((unsigned char *) buffer, size); Tcl_ListObjAppendElement(NULL, listObj, baObj); } } /* *---------------------------------------------------------------------- * * Tcl_ZlibStreamInit -- * * This command initializes a (de)compression context/handle for * (de)compressing data in chunks. * * Results: * A standard Tcl result. * * Side effects: * The variable pointed to by zshandlePtr is initialised and memory * allocated for internal state. Additionally, if interp is not null, a * Tcl command is created and its name placed in the interp result obj. * * Note: * At least one of interp and zshandlePtr should be non-NULL or the * reference to the stream will be completely lost. * *---------------------------------------------------------------------- */ int Tcl_ZlibStreamInit( Tcl_Interp *interp, int mode, /* Either TCL_ZLIB_STREAM_INFLATE or * TCL_ZLIB_STREAM_DEFLATE. */ int format, /* Flags from the TCL_ZLIB_FORMAT_* set. */ int level, /* 0-9 or TCL_ZLIB_COMPRESS_DEFAULT. */ Tcl_Obj *dictObj, /* Dictionary containing headers for gzip. */ Tcl_ZlibStream *zshandlePtr) { int wbits = 0; int e; ZlibStreamHandle *zshPtr = NULL; Tcl_DString cmdname; GzipHeader *gzHeaderPtr = NULL; switch (mode) { case TCL_ZLIB_STREAM_DEFLATE: /* * Compressed format is specified by the wbits parameter. See zlib.h * for details. */ switch (format) { case TCL_ZLIB_FORMAT_RAW: wbits = WBITS_RAW; break; case TCL_ZLIB_FORMAT_GZIP: wbits = WBITS_GZIP; if (dictObj) { gzHeaderPtr = (GzipHeader *) Tcl_Alloc(sizeof(GzipHeader)); memset(gzHeaderPtr, 0, sizeof(GzipHeader)); if (GenerateHeader(interp, dictObj, gzHeaderPtr, NULL) != TCL_OK) { Tcl_Free(gzHeaderPtr); return TCL_ERROR; } } break; case TCL_ZLIB_FORMAT_ZLIB: wbits = WBITS_ZLIB; break; default: Tcl_Panic("incorrect zlib data format, must be " "TCL_ZLIB_FORMAT_ZLIB, TCL_ZLIB_FORMAT_GZIP or " "TCL_ZLIB_FORMAT_RAW"); } if (level < -1 || level > 9) { Tcl_Panic("compression level should be between 0 (no compression)" " and 9 (best compression) or -1 for default compression " "level"); } break; case TCL_ZLIB_STREAM_INFLATE: /* * wbits are the same as DEFLATE, but FORMAT_AUTO is valid too. */ switch (format) { case TCL_ZLIB_FORMAT_RAW: wbits = WBITS_RAW; break; case TCL_ZLIB_FORMAT_GZIP: wbits = WBITS_GZIP; gzHeaderPtr = (GzipHeader *) Tcl_Alloc(sizeof(GzipHeader)); memset(gzHeaderPtr, 0, sizeof(GzipHeader)); gzHeaderPtr->header.name = (Bytef *) gzHeaderPtr->nativeFilenameBuf; gzHeaderPtr->header.name_max = MAXPATHLEN - 1; gzHeaderPtr->header.comment = (Bytef *) gzHeaderPtr->nativeCommentBuf; gzHeaderPtr->header.name_max = MAX_COMMENT_LEN - 1; break; case TCL_ZLIB_FORMAT_ZLIB: wbits = WBITS_ZLIB; break; case TCL_ZLIB_FORMAT_AUTO: wbits = WBITS_AUTODETECT; break; default: Tcl_Panic("incorrect zlib data format, must be " "TCL_ZLIB_FORMAT_ZLIB, TCL_ZLIB_FORMAT_GZIP, " "TCL_ZLIB_FORMAT_RAW or TCL_ZLIB_FORMAT_AUTO"); } break; default: Tcl_Panic("bad mode, must be TCL_ZLIB_STREAM_DEFLATE or" " TCL_ZLIB_STREAM_INFLATE"); } zshPtr = (ZlibStreamHandle *) Tcl_Alloc(sizeof(ZlibStreamHandle)); zshPtr->interp = interp; zshPtr->mode = mode; zshPtr->format = format; zshPtr->level = level; zshPtr->wbits = wbits; zshPtr->currentInput = NULL; zshPtr->streamEnd = 0; zshPtr->compDictObj = NULL; zshPtr->flags = 0; zshPtr->gzHeaderPtr = gzHeaderPtr; memset(&zshPtr->stream, 0, sizeof(z_stream)); zshPtr->stream.adler = 1; /* * No output buffer available yet */ if (mode == TCL_ZLIB_STREAM_DEFLATE) { e = deflateInit2(&zshPtr->stream, level, Z_DEFLATED, wbits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); if (e == Z_OK && zshPtr->gzHeaderPtr) { e = deflateSetHeader(&zshPtr->stream, &zshPtr->gzHeaderPtr->header); } } else { e = inflateInit2(&zshPtr->stream, wbits); if (e == Z_OK && zshPtr->gzHeaderPtr) { e = inflateGetHeader(&zshPtr->stream, &zshPtr->gzHeaderPtr->header); } } if (e != Z_OK) { ConvertError(interp, e, zshPtr->stream.adler); goto error; } /* * I could do all this in C, but this is easier. */ if (interp != NULL) { if (Tcl_EvalEx(interp, "::incr ::tcl::zlib::cmdcounter", TCL_AUTO_LENGTH, 0) != TCL_OK) { goto error; } Tcl_DStringInit(&cmdname); TclDStringAppendLiteral(&cmdname, "::tcl::zlib::streamcmd_"); TclDStringAppendObj(&cmdname, Tcl_GetObjResult(interp)); if (Tcl_FindCommand(interp, Tcl_DStringValue(&cmdname), NULL, 0) != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "BUG: Stream command name already exists", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "BUG", "EXISTING_CMD", (char *)NULL); Tcl_DStringFree(&cmdname); goto error; } Tcl_ResetResult(interp); /* * Create the command. */ zshPtr->cmd = Tcl_CreateObjCommand(interp, Tcl_DStringValue(&cmdname), ZlibStreamCmd, zshPtr, ZlibStreamCmdDelete); Tcl_DStringFree(&cmdname); if (zshPtr->cmd == NULL) { goto error; } } else { zshPtr->cmd = NULL; } /* * Prepare the buffers for use. */ zshPtr->inData = Tcl_NewListObj(0, NULL); Tcl_IncrRefCount(zshPtr->inData); zshPtr->outData = Tcl_NewListObj(0, NULL); Tcl_IncrRefCount(zshPtr->outData); zshPtr->outPos = 0; /* * Now set the variable pointed to by *zshandlePtr to the pointer to the * zsh struct. */ if (zshandlePtr) { *zshandlePtr = (Tcl_ZlibStream) zshPtr; } return TCL_OK; error: if (zshPtr->compDictObj) { Tcl_DecrRefCount(zshPtr->compDictObj); } if (zshPtr->gzHeaderPtr) { Tcl_Free(zshPtr->gzHeaderPtr); } Tcl_Free(zshPtr); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * ZlibStreamCmdDelete -- * * This is the delete command which Tcl invokes when a zlibstream command * is deleted from the interpreter (on stream close, usually). * * Results: * None * * Side effects: * Invalidates the zlib stream handle as obtained from Tcl_ZlibStreamInit * *---------------------------------------------------------------------- */ static void ZlibStreamCmdDelete( void *clientData) { ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) clientData; zshPtr->cmd = NULL; ZlibStreamCleanup(zshPtr); } /* *---------------------------------------------------------------------- * * Tcl_ZlibStreamClose -- * * This procedure must be called after (de)compression is done to ensure * memory is freed and the command is deleted from the interpreter (if * any). * * Results: * A standard Tcl result. * * Side effects: * Invalidates the zlib stream handle as obtained from Tcl_ZlibStreamInit * *---------------------------------------------------------------------- */ int Tcl_ZlibStreamClose( Tcl_ZlibStream zshandle) /* As obtained from Tcl_ZlibStreamInit. */ { ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; /* * If the interp is set, deleting the command will trigger * ZlibStreamCleanup in ZlibStreamCmdDelete. If no interp is set, call * ZlibStreamCleanup directly. */ if (zshPtr->interp && zshPtr->cmd) { Tcl_DeleteCommandFromToken(zshPtr->interp, zshPtr->cmd); } else { ZlibStreamCleanup(zshPtr); } return TCL_OK; } /* *---------------------------------------------------------------------- * * ZlibStreamCleanup -- * * This procedure is called by either Tcl_ZlibStreamClose or * ZlibStreamCmdDelete to cleanup the stream context. * * Results: * None * * Side effects: * Invalidates the zlib stream handle. * *---------------------------------------------------------------------- */ void ZlibStreamCleanup( ZlibStreamHandle *zshPtr) { if (!zshPtr->streamEnd) { if (zshPtr->mode == TCL_ZLIB_STREAM_DEFLATE) { deflateEnd(&zshPtr->stream); } else { inflateEnd(&zshPtr->stream); } } if (zshPtr->inData) { Tcl_DecrRefCount(zshPtr->inData); } if (zshPtr->outData) { Tcl_DecrRefCount(zshPtr->outData); } if (zshPtr->currentInput) { Tcl_DecrRefCount(zshPtr->currentInput); } if (zshPtr->compDictObj) { Tcl_DecrRefCount(zshPtr->compDictObj); } if (zshPtr->gzHeaderPtr) { Tcl_Free(zshPtr->gzHeaderPtr); } Tcl_Free(zshPtr); } /* *---------------------------------------------------------------------- * * Tcl_ZlibStreamReset -- * * This procedure will reinitialize an existing stream handle. * * Results: * A standard Tcl result. * * Side effects: * Any data left in the (de)compression buffer is lost. * *---------------------------------------------------------------------- */ int Tcl_ZlibStreamReset( Tcl_ZlibStream zshandle) /* As obtained from Tcl_ZlibStreamInit */ { ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; int e; if (!zshPtr->streamEnd) { if (zshPtr->mode == TCL_ZLIB_STREAM_DEFLATE) { deflateEnd(&zshPtr->stream); } else { inflateEnd(&zshPtr->stream); } } Tcl_SetByteArrayLength(zshPtr->inData, 0); Tcl_SetByteArrayLength(zshPtr->outData, 0); if (zshPtr->currentInput) { Tcl_DecrRefCount(zshPtr->currentInput); zshPtr->currentInput = NULL; } zshPtr->outPos = 0; zshPtr->streamEnd = 0; memset(&zshPtr->stream, 0, sizeof(z_stream)); /* * No output buffer available yet. */ if (zshPtr->mode == TCL_ZLIB_STREAM_DEFLATE) { e = deflateInit2(&zshPtr->stream, zshPtr->level, Z_DEFLATED, zshPtr->wbits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); if (e == Z_OK && HaveDictToSet(zshPtr)) { e = SetDeflateDictionary(&zshPtr->stream, zshPtr->compDictObj); if (e == Z_OK) { DictWasSet(zshPtr); } } } else { e = inflateInit2(&zshPtr->stream, zshPtr->wbits); if (IsRawStream(zshPtr) && HaveDictToSet(zshPtr) && e == Z_OK) { e = SetInflateDictionary(&zshPtr->stream, zshPtr->compDictObj); if (e == Z_OK) { DictWasSet(zshPtr); } } } if (e != Z_OK) { ConvertError(zshPtr->interp, e, zshPtr->stream.adler); /* TODO:cleanup */ return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ZlibStreamGetCommandName -- * * This procedure will return the command name associated with the * stream. * * Results: * A Tcl_Obj with the name of the Tcl command or NULL if no command is * associated with the stream. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_ZlibStreamGetCommandName( Tcl_ZlibStream zshandle) /* As obtained from Tcl_ZlibStreamInit */ { ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; Tcl_Obj *objPtr; if (!zshPtr->interp) { return NULL; } TclNewObj(objPtr); Tcl_GetCommandFullName(zshPtr->interp, zshPtr->cmd, objPtr); return objPtr; } /* *---------------------------------------------------------------------- * * Tcl_ZlibStreamEof -- * * This procedure This function returns 0 or 1 depending on the state of * the (de)compressor. For decompression, eof is reached when the entire * compressed stream has been decompressed. For compression, eof is * reached when the stream has been flushed with TCL_ZLIB_FINALIZE. * * Results: * Integer. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_ZlibStreamEof( Tcl_ZlibStream zshandle) /* As obtained from Tcl_ZlibStreamInit */ { ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; return zshPtr->streamEnd; } /* *---------------------------------------------------------------------- * * Tcl_ZlibStreamChecksum -- * * Return the checksum of the uncompressed data seen so far by the * stream. * *---------------------------------------------------------------------- */ int Tcl_ZlibStreamChecksum( Tcl_ZlibStream zshandle) /* As obtained from Tcl_ZlibStreamInit */ { ZlibStreamHandle *zshPtr = (ZlibStreamHandle *)zshandle; return zshPtr->stream.adler; } /* *---------------------------------------------------------------------- * * Tcl_ZlibStreamSetCompressionDictionary -- * * Sets the compression dictionary for a stream. This will be used as * appropriate for the next compression or decompression action performed * on the stream. * *---------------------------------------------------------------------- */ void Tcl_ZlibStreamSetCompressionDictionary( Tcl_ZlibStream zshandle, Tcl_Obj *compressionDictionaryObj) { ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; if (compressionDictionaryObj && (NULL == Tcl_GetBytesFromObj(NULL, compressionDictionaryObj, (Tcl_Size *)NULL))) { /* Missing or invalid compression dictionary */ compressionDictionaryObj = NULL; } if (compressionDictionaryObj != NULL) { if (Tcl_IsShared(compressionDictionaryObj)) { compressionDictionaryObj = Tcl_DuplicateObj(compressionDictionaryObj); } Tcl_IncrRefCount(compressionDictionaryObj); zshPtr->flags |= DICT_TO_SET; } else { zshPtr->flags &= ~DICT_TO_SET; } if (zshPtr->compDictObj != NULL) { Tcl_DecrRefCount(zshPtr->compDictObj); } zshPtr->compDictObj = compressionDictionaryObj; } /* *---------------------------------------------------------------------- * * Tcl_ZlibStreamPut -- * * Add data to the stream for compression or decompression from a * bytearray Tcl_Obj. * *---------------------------------------------------------------------- */ #define BUFFER_SIZE_LIMIT 0xFFFF int Tcl_ZlibStreamPut( Tcl_ZlibStream zshandle, /* As obtained from Tcl_ZlibStreamInit */ Tcl_Obj *data, /* Data to compress/decompress */ int flush) /* TCL_ZLIB_NO_FLUSH, TCL_ZLIB_FLUSH, * TCL_ZLIB_FULLFLUSH, or TCL_ZLIB_FINALIZE */ { ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; char *dataTmp = NULL; int e; Tcl_Size size = 0; size_t outSize, toStore; unsigned char *bytes; if (zshPtr->streamEnd) { if (zshPtr->interp) { Tcl_SetObjResult(zshPtr->interp, Tcl_NewStringObj( "already past compressed stream end", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(zshPtr->interp, "TCL", "ZIP", "CLOSED", (char *)NULL); } return TCL_ERROR; } bytes = Tcl_GetBytesFromObj(zshPtr->interp, data, &size); if (bytes == NULL) { return TCL_ERROR; } if (zshPtr->mode == TCL_ZLIB_STREAM_DEFLATE) { zshPtr->stream.next_in = bytes; zshPtr->stream.avail_in = size; /* * Must not do a zero-length compress unless finalizing. [Bug 25842c161] */ if (size == 0 && flush != Z_FINISH) { return TCL_OK; } if (HaveDictToSet(zshPtr)) { e = SetDeflateDictionary(&zshPtr->stream, zshPtr->compDictObj); if (e != Z_OK) { ConvertError(zshPtr->interp, e, zshPtr->stream.adler); return TCL_ERROR; } DictWasSet(zshPtr); } /* * deflateBound() doesn't seem to take various header sizes into * account, so we add 100 extra bytes. However, we can also loop * around again so we also set an upper bound on the output buffer * size. */ outSize = deflateBound(&zshPtr->stream, size) + 100; if (outSize > BUFFER_SIZE_LIMIT) { outSize = BUFFER_SIZE_LIMIT; } dataTmp = (char *) Tcl_Alloc(outSize); while (1) { e = Deflate(&zshPtr->stream, dataTmp, outSize, flush, &toStore); /* * Test if we've filled the buffer up and have to ask deflate() to * give us some more. Note that the condition for needing to * repeat a buffer transfer when the result is Z_OK is whether * there is no more space in the buffer we provided; the zlib * library does not necessarily return a different code in that * case. [Bug b26e38a3e4] [Tk Bug 10f2e7872b] */ if ((e != Z_BUF_ERROR) && (e != Z_OK || toStore < outSize)) { if ((e == Z_OK) || (flush == Z_FINISH && e == Z_STREAM_END)) { break; } ConvertError(zshPtr->interp, e, zshPtr->stream.adler); return TCL_ERROR; } /* * Output buffer too small to hold the data being generated or we * are doing the end-of-stream flush (which can spit out masses of * data). This means we need to put a new buffer into place after * saving the old generated data to the outData list. */ AppendByteArray(zshPtr->outData, dataTmp, outSize); if (outSize < BUFFER_SIZE_LIMIT) { outSize = BUFFER_SIZE_LIMIT; /* There may be *lots* of data left to output... */ dataTmp = (char *) Tcl_Realloc(dataTmp, outSize); } } /* * And append the final data block to the outData list. */ AppendByteArray(zshPtr->outData, dataTmp, toStore); Tcl_Free(dataTmp); } else { /* * This is easy. Just append to the inData list. */ Tcl_ListObjAppendElement(NULL, zshPtr->inData, data); /* * and we'll need the flush parameter for the Inflate call. */ zshPtr->flush = flush; } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ZlibStreamGet -- * * Retrieve data (now compressed or decompressed) from the stream into a * bytearray Tcl_Obj. * *---------------------------------------------------------------------- */ int Tcl_ZlibStreamGet( Tcl_ZlibStream zshandle, /* As obtained from Tcl_ZlibStreamInit */ Tcl_Obj *data, /* A place to append the data. */ Tcl_Size count) /* Number of bytes to grab as a maximum, you * may get less! */ { ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) zshandle; int e; Tcl_Size listLen, i, itemLen = 0, dataPos = 0; Tcl_Obj *itemObj; unsigned char *dataPtr, *itemPtr; Tcl_Size existing = 0; /* * Getting beyond the of stream, just return empty string. */ if (zshPtr->streamEnd) { return TCL_OK; } if (NULL == Tcl_GetBytesFromObj(zshPtr->interp, data, &existing)) { return TCL_ERROR; } if (zshPtr->mode == TCL_ZLIB_STREAM_INFLATE) { if (count < 0) { /* * The only safe thing to do is restict to 65k. We might cause a * panic for out of memory if we just kept growing the buffer. */ count = MAX_BUFFER_SIZE; } /* * Prepare the place to store the data. */ dataPtr = Tcl_SetByteArrayLength(data, existing + count); dataPtr += existing; zshPtr->stream.next_out = dataPtr; zshPtr->stream.avail_out = count; if (zshPtr->stream.avail_in == 0) { /* * zlib will probably need more data to decompress. */ if (zshPtr->currentInput) { Tcl_DecrRefCount(zshPtr->currentInput); zshPtr->currentInput = NULL; } TclListObjLength(NULL, zshPtr->inData, &listLen); if (listLen > 0) { /* * There is more input available, get it from the list and * give it to zlib. At this point, the data must not be shared * since we require the bytearray representation to not vanish * under our feet. [Bug 3081008] */ Tcl_ListObjIndex(NULL, zshPtr->inData, 0, &itemObj); if (Tcl_IsShared(itemObj)) { itemObj = Tcl_DuplicateObj(itemObj); } itemPtr = Tcl_GetBytesFromObj(NULL, itemObj, &itemLen); Tcl_IncrRefCount(itemObj); zshPtr->currentInput = itemObj; zshPtr->stream.next_in = itemPtr; zshPtr->stream.avail_in = itemLen; /* * And remove it from the list */ Tcl_ListObjReplace(NULL, zshPtr->inData, 0, 1, 0, NULL); } } /* * When dealing with a raw stream, we set the dictionary here, once. * (You can't do it in response to getting Z_NEED_DATA as raw streams * don't ever issue that.) */ if (IsRawStream(zshPtr) && HaveDictToSet(zshPtr)) { e = SetInflateDictionary(&zshPtr->stream, zshPtr->compDictObj); if (e != Z_OK) { ConvertError(zshPtr->interp, e, zshPtr->stream.adler); return TCL_ERROR; } DictWasSet(zshPtr); } e = inflate(&zshPtr->stream, zshPtr->flush); if (e == Z_NEED_DICT && HaveDictToSet(zshPtr)) { e = SetInflateDictionary(&zshPtr->stream, zshPtr->compDictObj); if (e == Z_OK) { DictWasSet(zshPtr); e = inflate(&zshPtr->stream, zshPtr->flush); } }; TclListObjLength(NULL, zshPtr->inData, &listLen); while ((zshPtr->stream.avail_out > 0) && (e == Z_OK || e == Z_BUF_ERROR) && (listLen > 0)) { /* * State: We have not satisfied the request yet and there may be * more to inflate. */ if (zshPtr->stream.avail_in > 0) { if (zshPtr->interp) { Tcl_SetObjResult(zshPtr->interp, Tcl_NewStringObj( "unexpected zlib internal state during" " decompression", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(zshPtr->interp, "TCL", "ZIP", "STATE", (char *)NULL); } Tcl_SetByteArrayLength(data, existing); return TCL_ERROR; } if (zshPtr->currentInput) { Tcl_DecrRefCount(zshPtr->currentInput); zshPtr->currentInput = 0; } /* * Get the next block of data to go to inflate. At this point, the * data must not be shared since we require the bytearray * representation to not vanish under our feet. [Bug 3081008] */ Tcl_ListObjIndex(zshPtr->interp, zshPtr->inData, 0, &itemObj); if (Tcl_IsShared(itemObj)) { itemObj = Tcl_DuplicateObj(itemObj); } itemPtr = Tcl_GetBytesFromObj(NULL, itemObj, &itemLen); Tcl_IncrRefCount(itemObj); zshPtr->currentInput = itemObj; zshPtr->stream.next_in = itemPtr; zshPtr->stream.avail_in = itemLen; /* * Remove it from the list. */ Tcl_ListObjReplace(NULL, zshPtr->inData, 0, 1, 0, NULL); listLen--; /* * And call inflate again. */ do { e = inflate(&zshPtr->stream, zshPtr->flush); if (e != Z_NEED_DICT || !HaveDictToSet(zshPtr)) { break; } e = SetInflateDictionary(&zshPtr->stream, zshPtr->compDictObj); DictWasSet(zshPtr); } while (e == Z_OK); } if (zshPtr->stream.avail_out > 0) { Tcl_SetByteArrayLength(data, existing + count - zshPtr->stream.avail_out); } if (!(e==Z_OK || e==Z_STREAM_END || e==Z_BUF_ERROR)) { Tcl_SetByteArrayLength(data, existing); ConvertError(zshPtr->interp, e, zshPtr->stream.adler); return TCL_ERROR; } if (e == Z_STREAM_END) { zshPtr->streamEnd = 1; if (zshPtr->currentInput) { Tcl_DecrRefCount(zshPtr->currentInput); zshPtr->currentInput = 0; } inflateEnd(&zshPtr->stream); } } else { TclListObjLength(NULL, zshPtr->outData, &listLen); if (count < 0) { count = 0; for (i=0; ioutData, i, &itemObj); (void) Tcl_GetBytesFromObj(NULL, itemObj, &itemLen); if (i == 0) { count += itemLen - zshPtr->outPos; } else { count += itemLen; } } } /* * Prepare the place to store the data. */ dataPtr = Tcl_SetByteArrayLength(data, existing + count); dataPtr += existing; while ((count > dataPos) && (TclListObjLength(NULL, zshPtr->outData, &listLen) == TCL_OK) && (listLen > 0)) { /* * Get the next chunk off our list of chunks and grab the data out * of it. */ Tcl_ListObjIndex(NULL, zshPtr->outData, 0, &itemObj); itemPtr = Tcl_GetBytesFromObj(NULL, itemObj, &itemLen); if ((itemLen - zshPtr->outPos) >= (count - dataPos)) { Tcl_Size len = count - dataPos; memcpy(dataPtr + dataPos, itemPtr + zshPtr->outPos, len); zshPtr->outPos += len; dataPos += len; if (zshPtr->outPos == itemLen) { zshPtr->outPos = 0; } } else { Tcl_Size len = itemLen - zshPtr->outPos; memcpy(dataPtr + dataPos, itemPtr + zshPtr->outPos, len); dataPos += len; zshPtr->outPos = 0; } if (zshPtr->outPos == 0) { Tcl_ListObjReplace(NULL, zshPtr->outData, 0, 1, 0, NULL); listLen--; } } Tcl_SetByteArrayLength(data, existing + dataPos); } return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_ZlibDeflate -- * * Compress the contents of Tcl_Obj *data with compression level in * output format, producing the compressed data in the interpreter * result. * *---------------------------------------------------------------------- */ int Tcl_ZlibDeflate( Tcl_Interp *interp, int format, Tcl_Obj *data, int level, Tcl_Obj *gzipHeaderDictObj) { int wbits = 0, e = 0, extraSize = 0; Tcl_Size inLen = 0; Byte *inData = NULL; z_stream stream; GzipHeader header; gz_header *headerPtr = NULL; Tcl_Obj *obj; if (!interp) { return TCL_ERROR; } /* * Obtain the pointer to the byte array, we'll pass this pointer straight * to the deflate command. */ inData = Tcl_GetBytesFromObj(interp, data, &inLen); if (inData == NULL) { return TCL_ERROR; } /* * Compressed format is specified by the wbits parameter. See zlib.h for * details. */ if (format == TCL_ZLIB_FORMAT_RAW) { wbits = WBITS_RAW; } else if (format == TCL_ZLIB_FORMAT_GZIP) { wbits = WBITS_GZIP; /* * Need to allocate extra space for the gzip header and footer. The * amount of space is (a bit less than) 32 bytes, plus a byte for each * byte of string that we add. Note that over-allocation is not a * problem. [Bug 2419061] */ extraSize = 32; if (gzipHeaderDictObj) { headerPtr = &header.header; memset(headerPtr, 0, sizeof(gz_header)); if (GenerateHeader(interp, gzipHeaderDictObj, &header, &extraSize) != TCL_OK) { return TCL_ERROR; } } } else if (format == TCL_ZLIB_FORMAT_ZLIB) { wbits = WBITS_ZLIB; } else { Tcl_Panic("incorrect zlib data format, must be TCL_ZLIB_FORMAT_ZLIB, " "TCL_ZLIB_FORMAT_GZIP or TCL_ZLIB_FORMAT_ZLIB"); } if (level < -1 || level > 9) { Tcl_Panic("compression level should be between 0 (uncompressed) and " "9 (best compression) or -1 for default compression level"); } /* * Allocate some space to store the output. */ TclNewObj(obj); memset(&stream, 0, sizeof(z_stream)); stream.avail_in = inLen; stream.next_in = inData; /* * No output buffer available yet, will alloc after deflateInit2. */ e = deflateInit2(&stream, level, Z_DEFLATED, wbits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); if (e != Z_OK) { goto error; } if (headerPtr != NULL) { e = deflateSetHeader(&stream, headerPtr); if (e != Z_OK) { goto error; } } /* * Allocate the output buffer from the value of deflateBound(). This is * probably too much space. Before returning to the caller, we will reduce * it back to the actual compressed size. */ stream.avail_out = deflateBound(&stream, inLen) + extraSize; stream.next_out = Tcl_SetByteArrayLength(obj, stream.avail_out); /* * Perform the compression, Z_FINISH means do it in one go. */ e = deflate(&stream, Z_FINISH); if (e != Z_STREAM_END) { e = deflateEnd(&stream); /* * deflateEnd() returns Z_OK when there are bytes left to compress, at * this point we consider that an error, although we could continue by * allocating more memory and calling deflate() again. */ if (e == Z_OK) { e = Z_BUF_ERROR; } } else { e = deflateEnd(&stream); } if (e != Z_OK) { goto error; } /* * Reduce the ByteArray length to the actual data length produced by * deflate. */ Tcl_SetByteArrayLength(obj, stream.total_out); Tcl_SetObjResult(interp, obj); return TCL_OK; error: ConvertError(interp, e, stream.adler); TclDecrRefCount(obj); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_ZlibInflate -- * * Decompress data in an object into the interpreter result. * *---------------------------------------------------------------------- */ int Tcl_ZlibInflate( Tcl_Interp *interp, int format, Tcl_Obj *data, Tcl_Size bufferSize, Tcl_Obj *gzipHeaderDictObj) { int wbits = 0, e = 0; Tcl_Size inLen = 0, newBufferSize; Byte *inData = NULL, *outData = NULL, *newOutData = NULL; z_stream stream; gz_header header, *headerPtr = NULL; Tcl_Obj *obj; char *nameBuf = NULL, *commentBuf = NULL; if (!interp) { return TCL_ERROR; } inData = Tcl_GetBytesFromObj(interp, data, &inLen); if (inData == NULL) { return TCL_ERROR; } /* * Compressed format is specified by the wbits parameter. See zlib.h for * details. */ switch (format) { case TCL_ZLIB_FORMAT_RAW: wbits = WBITS_RAW; gzipHeaderDictObj = NULL; break; case TCL_ZLIB_FORMAT_ZLIB: wbits = WBITS_ZLIB; gzipHeaderDictObj = NULL; break; case TCL_ZLIB_FORMAT_GZIP: wbits = WBITS_GZIP; break; case TCL_ZLIB_FORMAT_AUTO: wbits = WBITS_AUTODETECT; break; default: Tcl_Panic("incorrect zlib data format, must be TCL_ZLIB_FORMAT_ZLIB, " "TCL_ZLIB_FORMAT_GZIP, TCL_ZLIB_FORMAT_RAW or " "TCL_ZLIB_FORMAT_AUTO"); } if (gzipHeaderDictObj) { headerPtr = &header; memset(headerPtr, 0, sizeof(gz_header)); nameBuf = (char *) Tcl_Alloc(MAXPATHLEN); header.name = (Bytef *) nameBuf; header.name_max = MAXPATHLEN - 1; commentBuf = (char *) Tcl_Alloc(MAX_COMMENT_LEN); header.comment = (Bytef *) commentBuf; header.comm_max = MAX_COMMENT_LEN - 1; } if (bufferSize < 1) { /* * Start with a buffer (up to) 3 times the size of the input data. */ if (inLen < 32 * 1024 * 1024) { bufferSize = 3 * inLen; } else if (inLen < 256 * 1024 * 1024) { bufferSize = 2 * inLen; } else { bufferSize = inLen; } } TclNewObj(obj); outData = Tcl_SetByteArrayLength(obj, bufferSize); memset(&stream, 0, sizeof(z_stream)); stream.avail_in = inLen+1; /* +1 because zlib can "over-request" * input (but ignore it!) */ stream.next_in = inData; stream.avail_out = bufferSize; stream.next_out = outData; /* * Initialize zlib for decompression. */ e = inflateInit2(&stream, wbits); if (e != Z_OK) { goto error; } if (headerPtr) { e = inflateGetHeader(&stream, headerPtr); if (e != Z_OK) { inflateEnd(&stream); goto error; } } /* * Start the decompression cycle. */ while (1) { e = inflate(&stream, Z_FINISH); if (e != Z_BUF_ERROR) { break; } /* * Not enough room in the output buffer. Increase it by five times the * bytes still in the input buffer. (Because 3 times didn't do the * trick before, 5 times is what we do next.) Further optimization * should be done by the user, specify the decompressed size! */ if ((stream.avail_in == 0) && (stream.avail_out > 0)) { e = Z_STREAM_ERROR; break; } newBufferSize = bufferSize + 5 * stream.avail_in; if (newBufferSize == bufferSize) { newBufferSize = bufferSize + 1000; } newOutData = Tcl_SetByteArrayLength(obj, newBufferSize); /* * Set next out to the same offset in the new location. */ stream.next_out = newOutData + stream.total_out; /* * And increase avail_out with the number of new bytes allocated. */ stream.avail_out += newBufferSize - bufferSize; outData = newOutData; bufferSize = newBufferSize; } if (e != Z_STREAM_END) { inflateEnd(&stream); goto error; } e = inflateEnd(&stream); if (e != Z_OK) { goto error; } /* * Reduce the BA length to the actual data length produced by deflate. */ Tcl_SetByteArrayLength(obj, stream.total_out); if (headerPtr != NULL) { ExtractHeader(&header, gzipHeaderDictObj); TclDictPut(NULL, gzipHeaderDictObj, "size", Tcl_NewWideIntObj(stream.total_out)); Tcl_Free(nameBuf); Tcl_Free(commentBuf); } Tcl_SetObjResult(interp, obj); return TCL_OK; error: TclDecrRefCount(obj); ConvertError(interp, e, stream.adler); if (nameBuf) { Tcl_Free(nameBuf); } if (commentBuf) { Tcl_Free(commentBuf); } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tcl_ZlibCRC32, Tcl_ZlibAdler32 -- * * Access to the checksumming engines. * *---------------------------------------------------------------------- */ unsigned int Tcl_ZlibCRC32( unsigned int crc, const unsigned char *buf, Tcl_Size len) { /* Nothing much to do, just wrap the crc32(). */ return crc32(crc, (Bytef *) buf, len); } unsigned int Tcl_ZlibAdler32( unsigned int adler, const unsigned char *buf, Tcl_Size len) { return adler32(adler, (Bytef *) buf, len); } /* *---------------------------------------------------------------------- * * ZlibCmd -- * * Implementation of the [zlib] command. * * TODO: Convert this to an ensemble. * *---------------------------------------------------------------------- */ static int ZlibCmd( TCL_UNUSED(void *), Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int i, option, level = -1; size_t buffersize = 0; Tcl_Size dlen = 0; unsigned int start; Tcl_WideInt wideLen; Byte *data; Tcl_Obj *headerDictObj; const char *extraInfoStr = NULL; static const char *const commands[] = { "adler32", "compress", "crc32", "decompress", "deflate", "gunzip", "gzip", "inflate", "push", "stream", NULL }; enum zlibCommands { CMD_ADLER, CMD_COMPRESS, CMD_CRC, CMD_DECOMPRESS, CMD_DEFLATE, CMD_GUNZIP, CMD_GZIP, CMD_INFLATE, CMD_PUSH, CMD_STREAM } command; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "command arg ?...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], commands, "command", 0, &command) != TCL_OK) { return TCL_ERROR; } switch (command) { case CMD_ADLER: /* adler32 str ?startvalue? * -> checksum */ if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 2, objv, "data ?startValue?"); return TCL_ERROR; } data = Tcl_GetBytesFromObj(interp, objv[2], &dlen); if (data == NULL) { return TCL_ERROR; } if (objc > 3 && Tcl_GetIntFromObj(interp, objv[3], (int *) &start) != TCL_OK) { return TCL_ERROR; } if (objc < 4) { start = Tcl_ZlibAdler32(0, NULL, 0); } Tcl_SetObjResult(interp, Tcl_NewWideIntObj( Tcl_ZlibAdler32(start, data, dlen))); return TCL_OK; case CMD_CRC: /* crc32 str ?startvalue? * -> checksum */ if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 2, objv, "data ?startValue?"); return TCL_ERROR; } data = Tcl_GetBytesFromObj(interp, objv[2], &dlen); if (data == NULL) { return TCL_ERROR; } if (objc > 3 && Tcl_GetIntFromObj(interp, objv[3], (int *) &start) != TCL_OK) { return TCL_ERROR; } if (objc < 4) { start = Tcl_ZlibCRC32(0, NULL, 0); } Tcl_SetObjResult(interp, Tcl_NewWideIntObj( Tcl_ZlibCRC32(start, data, dlen))); return TCL_OK; case CMD_DEFLATE: /* deflate data ?level? * -> rawCompressedData */ if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 2, objv, "data ?level?"); return TCL_ERROR; } if (objc > 3) { if (Tcl_GetIntFromObj(interp, objv[3], &level) != TCL_OK) { return TCL_ERROR; } if (level < 0 || level > 9) { goto badLevel; } } return Tcl_ZlibDeflate(interp, TCL_ZLIB_FORMAT_RAW, objv[2], level, NULL); case CMD_COMPRESS: /* compress data ?level? * -> zlibCompressedData */ if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 2, objv, "data ?level?"); return TCL_ERROR; } if (objc > 3) { if (Tcl_GetIntFromObj(interp, objv[3], &level) != TCL_OK) { return TCL_ERROR; } if (level < 0 || level > 9) { goto badLevel; } } return Tcl_ZlibDeflate(interp, TCL_ZLIB_FORMAT_ZLIB, objv[2], level, NULL); case CMD_GZIP: /* gzip data ?level? * -> gzippedCompressedData */ headerDictObj = NULL; /* * Legacy argument format support. */ if (objc == 4 && Tcl_GetIntFromObj(interp, objv[3], &level) == TCL_OK) { if (level < 0 || level > 9) { extraInfoStr = "\n (in -level option)"; goto badLevel; } return Tcl_ZlibDeflate(interp, TCL_ZLIB_FORMAT_GZIP, objv[2], level, NULL); } if (objc < 3 || objc > 7 || ((objc & 1) == 0)) { Tcl_WrongNumArgs(interp, 2, objv, "data ?-level level? ?-header header?"); return TCL_ERROR; } for (i=3 ; i 9) { extraInfoStr = "\n (in -level option)"; goto badLevel; } break; } } return Tcl_ZlibDeflate(interp, TCL_ZLIB_FORMAT_GZIP, objv[2], level, headerDictObj); case CMD_INFLATE: /* inflate rawcomprdata ?bufferSize? * -> decompressedData */ if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 2, objv, "data ?bufferSize?"); return TCL_ERROR; } if (objc > 3) { if (TclGetWideIntFromObj(interp, objv[3], &wideLen) != TCL_OK) { return TCL_ERROR; } if (wideLen < MIN_NONSTREAM_BUFFER_SIZE || wideLen > MAX_BUFFER_SIZE) { goto badBuffer; } buffersize = wideLen; } return Tcl_ZlibInflate(interp, TCL_ZLIB_FORMAT_RAW, objv[2], buffersize, NULL); case CMD_DECOMPRESS: /* decompress zlibcomprdata ?bufferSize? * -> decompressedData */ if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 2, objv, "data ?bufferSize?"); return TCL_ERROR; } if (objc > 3) { if (TclGetWideIntFromObj(interp, objv[3], &wideLen) != TCL_OK) { return TCL_ERROR; } if (wideLen < MIN_NONSTREAM_BUFFER_SIZE || wideLen > MAX_BUFFER_SIZE) { goto badBuffer; } buffersize = wideLen; } return Tcl_ZlibInflate(interp, TCL_ZLIB_FORMAT_ZLIB, objv[2], buffersize, NULL); case CMD_GUNZIP: { /* gunzip gzippeddata ?-headerVar varName? * -> decompressedData */ Tcl_Obj *headerVarObj; if (objc < 3 || objc > 5 || ((objc & 1) == 0)) { Tcl_WrongNumArgs(interp, 2, objv, "data ?-headerVar varName?"); return TCL_ERROR; } headerDictObj = headerVarObj = NULL; for (i=3 ; i MAX_BUFFER_SIZE) { goto badBuffer; } buffersize = wideLen; break; case 1: headerVarObj = objv[i + 1]; TclNewObj(headerDictObj); break; } } if (Tcl_ZlibInflate(interp, TCL_ZLIB_FORMAT_GZIP, objv[2], buffersize, headerDictObj) != TCL_OK) { if (headerDictObj) { TclDecrRefCount(headerDictObj); } return TCL_ERROR; } if (headerVarObj != NULL && Tcl_ObjSetVar2(interp, headerVarObj, NULL, headerDictObj, TCL_LEAVE_ERR_MSG) == NULL) { return TCL_ERROR; } return TCL_OK; } case CMD_STREAM: /* stream deflate/inflate/...gunzip options... * -> handleCmd */ return ZlibStreamSubcmd(interp, objc, objv); case CMD_PUSH: /* push mode channel options... * -> channel */ return ZlibPushSubcmd(interp, objc, objv); } return TCL_ERROR; badLevel: Tcl_SetObjResult(interp, Tcl_NewStringObj( "level must be 0 to 9", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMPRESSIONLEVEL", (char *)NULL); if (extraInfoStr) { Tcl_AddErrorInfo(interp, extraInfoStr); } return TCL_ERROR; badBuffer: Tcl_SetObjResult(interp, Tcl_ObjPrintf( "buffer size must be %d to %d", MIN_NONSTREAM_BUFFER_SIZE, MAX_BUFFER_SIZE)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "BUFFERSIZE", (char *)NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * ZlibStreamSubcmd -- * * Implementation of the [zlib stream] subcommand. * *---------------------------------------------------------------------- */ static int ZlibStreamSubcmd( Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { static const char *const stream_formats[] = { "compress", "decompress", "deflate", "gunzip", "gzip", "inflate", NULL }; enum zlibFormats { FMT_COMPRESS, FMT_DECOMPRESS, FMT_DEFLATE, FMT_GUNZIP, FMT_GZIP, FMT_INFLATE } fmt; int i, format, mode = 0, option, level; enum objIndices { OPT_COMPRESSION_DICTIONARY = 0, OPT_GZIP_HEADER = 1, OPT_COMPRESSION_LEVEL = 2, OPT_END = -1 }; Tcl_Obj *obj[3] = { NULL, NULL, NULL }; #define compDictObj obj[OPT_COMPRESSION_DICTIONARY] #define gzipHeaderObj obj[OPT_GZIP_HEADER] #define levelObj obj[OPT_COMPRESSION_LEVEL] typedef struct { const char *name; enum objIndices offset; } OptDescriptor; static const OptDescriptor compressionOpts[] = { { "-dictionary", OPT_COMPRESSION_DICTIONARY }, { "-level", OPT_COMPRESSION_LEVEL }, { NULL, OPT_END } }; static const OptDescriptor gzipOpts[] = { { "-header", OPT_GZIP_HEADER }, { "-level", OPT_COMPRESSION_LEVEL }, { NULL, OPT_END } }; static const OptDescriptor expansionOpts[] = { { "-dictionary", OPT_COMPRESSION_DICTIONARY }, { NULL, OPT_END } }; static const OptDescriptor gunzipOpts[] = { { NULL, OPT_END } }; const OptDescriptor *desc = NULL; Tcl_ZlibStream zh; if (objc < 3 || !(objc & 1)) { Tcl_WrongNumArgs(interp, 2, objv, "mode ?-option value...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[2], stream_formats, "mode", 0, &fmt) != TCL_OK) { return TCL_ERROR; } /* * The format determines the compression mode and the options that may be * specified. */ switch (fmt) { case FMT_DEFLATE: desc = compressionOpts; mode = TCL_ZLIB_STREAM_DEFLATE; format = TCL_ZLIB_FORMAT_RAW; break; case FMT_INFLATE: desc = expansionOpts; mode = TCL_ZLIB_STREAM_INFLATE; format = TCL_ZLIB_FORMAT_RAW; break; case FMT_COMPRESS: desc = compressionOpts; mode = TCL_ZLIB_STREAM_DEFLATE; format = TCL_ZLIB_FORMAT_ZLIB; break; case FMT_DECOMPRESS: desc = expansionOpts; mode = TCL_ZLIB_STREAM_INFLATE; format = TCL_ZLIB_FORMAT_ZLIB; break; case FMT_GZIP: desc = gzipOpts; mode = TCL_ZLIB_STREAM_DEFLATE; format = TCL_ZLIB_FORMAT_GZIP; break; case FMT_GUNZIP: desc = gunzipOpts; mode = TCL_ZLIB_STREAM_INFLATE; format = TCL_ZLIB_FORMAT_GZIP; break; default: Tcl_Panic("should be unreachable"); } /* * Parse the options. */ for (i=3 ; i 9) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "level must be 0 to 9", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMPRESSIONLEVEL", (char *)NULL); Tcl_AddErrorInfo(interp, "\n (in -level option)"); return TCL_ERROR; } if (compDictObj) { if (NULL == Tcl_GetBytesFromObj(interp, compDictObj, (Tcl_Size *)NULL)) { return TCL_ERROR; } } /* * Construct the stream now we know its configuration. */ if (Tcl_ZlibStreamInit(interp, mode, format, level, gzipHeaderObj, &zh) != TCL_OK) { return TCL_ERROR; } if (compDictObj != NULL) { Tcl_ZlibStreamSetCompressionDictionary(zh, compDictObj); } Tcl_SetObjResult(interp, Tcl_ZlibStreamGetCommandName(zh)); return TCL_OK; #undef compDictObj #undef gzipHeaderObj #undef levelObj } /* *---------------------------------------------------------------------- * * ZlibPushSubcmd -- * * Implementation of the [zlib push] subcommand. * *---------------------------------------------------------------------- */ static int ZlibPushSubcmd( Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { static const char *const stream_formats[] = { "compress", "decompress", "deflate", "gunzip", "gzip", "inflate", NULL }; enum zlibFormats { FMT_COMPRESS, FMT_DECOMPRESS, FMT_DEFLATE, FMT_GUNZIP, FMT_GZIP, FMT_INFLATE } fmt; Tcl_Channel chan; int chanMode, format, mode = 0, level, i; static const char *const pushCompressOptions[] = { "-dictionary", "-header", "-level", NULL }; static const char *const pushDecompressOptions[] = { "-dictionary", "-header", "-level", "-limit", NULL }; const char *const *pushOptions = pushDecompressOptions; enum pushOptionsEnum {poDictionary, poHeader, poLevel, poLimit} option; Tcl_Obj *headerObj = NULL, *compDictObj = NULL; int limit = DEFAULT_BUFFER_SIZE; Tcl_Size dummy; if (objc < 4) { Tcl_WrongNumArgs(interp, 2, objv, "mode channel ?options...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[2], stream_formats, "mode", 0, &fmt) != TCL_OK) { return TCL_ERROR; } switch (fmt) { case FMT_DEFLATE: mode = TCL_ZLIB_STREAM_DEFLATE; format = TCL_ZLIB_FORMAT_RAW; pushOptions = pushCompressOptions; break; case FMT_INFLATE: mode = TCL_ZLIB_STREAM_INFLATE; format = TCL_ZLIB_FORMAT_RAW; break; case FMT_COMPRESS: mode = TCL_ZLIB_STREAM_DEFLATE; format = TCL_ZLIB_FORMAT_ZLIB; pushOptions = pushCompressOptions; break; case FMT_DECOMPRESS: mode = TCL_ZLIB_STREAM_INFLATE; format = TCL_ZLIB_FORMAT_ZLIB; break; case FMT_GZIP: mode = TCL_ZLIB_STREAM_DEFLATE; format = TCL_ZLIB_FORMAT_GZIP; pushOptions = pushCompressOptions; break; case FMT_GUNZIP: mode = TCL_ZLIB_STREAM_INFLATE; format = TCL_ZLIB_FORMAT_GZIP; break; default: Tcl_Panic("should be unreachable"); } if (TclGetChannelFromObj(interp, objv[3], &chan, &chanMode, 0) != TCL_OK) { return TCL_ERROR; } /* * Sanity checks. */ if (mode == TCL_ZLIB_STREAM_DEFLATE && !(chanMode & TCL_WRITABLE)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "compression may only be applied to writable channels", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ZIP", "UNWRITABLE", (char *)NULL); return TCL_ERROR; } if (mode == TCL_ZLIB_STREAM_INFLATE && !(chanMode & TCL_READABLE)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "decompression may only be applied to readable channels", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ZIP", "UNREADABLE", (char *)NULL); return TCL_ERROR; } /* * Parse options. */ level = Z_DEFAULT_COMPRESSION; for (i=4 ; i objc - 1) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "value missing for %s option", pushOptions[option])); Tcl_SetErrorCode(interp, "TCL", "ZIP", "NOVAL", (char *)NULL); return TCL_ERROR; } switch (option) { case poHeader: /* -header headerDict */ headerObj = objv[i]; if (Tcl_DictObjSize(interp, headerObj, &dummy) != TCL_OK) { goto genericOptionError; } break; case poLevel: /* -level compLevel */ if (Tcl_GetIntFromObj(interp, objv[i], (int *) &level) != TCL_OK) { goto genericOptionError; } if (level < 0 || level > 9) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "level must be 0 to 9", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMPRESSIONLEVEL", (char *)NULL); goto genericOptionError; } break; case poLimit: /* -limit numBytes */ if (Tcl_GetIntFromObj(interp, objv[i], (int *) &limit) != TCL_OK) { goto genericOptionError; } if (limit < 1 || limit > MAX_BUFFER_SIZE) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "read ahead limit must be 1 to %d", MAX_BUFFER_SIZE)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "BUFFERSIZE", (char *)NULL); goto genericOptionError; } break; case poDictionary: /* -dictionary compDict */ if (format == TCL_ZLIB_FORMAT_GZIP) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "a compression dictionary may not be set in the " "gzip format", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ZIP", "BADOPT", (char *)NULL); goto genericOptionError; } compDictObj = objv[i]; break; } } if (compDictObj && (NULL == Tcl_GetBytesFromObj(interp, compDictObj, (Tcl_Size *)NULL))) { return TCL_ERROR; } if (ZlibStackChannelTransform(interp, mode, format, level, limit, chan, headerObj, compDictObj) == NULL) { return TCL_ERROR; } Tcl_SetObjResult(interp, objv[3]); return TCL_OK; genericOptionError: Tcl_AddErrorInfo(interp, "\n (in "); Tcl_AddErrorInfo(interp, pushOptions[option]); Tcl_AddErrorInfo(interp, " option)"); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * ZlibStreamCmd -- * * Implementation of the commands returned by [zlib stream]. * *---------------------------------------------------------------------- */ static int ZlibStreamCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_ZlibStream zstream = (Tcl_ZlibStream) clientData; int count, code; Tcl_Obj *obj; static const char *const cmds[] = { "add", "checksum", "close", "eof", "finalize", "flush", "fullflush", "get", "header", "put", "reset", NULL }; enum zlibStreamCommands { zs_add, zs_checksum, zs_close, zs_eof, zs_finalize, zs_flush, zs_fullflush, zs_get, zs_header, zs_put, zs_reset } command; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option data ?...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], cmds, "option", 0, &command) != TCL_OK) { return TCL_ERROR; } switch (command) { case zs_add: /* $strm add ?$flushopt? $data */ return ZlibStreamAddCmd(zstream, interp, objc, objv); case zs_header: /* $strm header */ return ZlibStreamHeaderCmd(zstream, interp, objc, objv); case zs_put: /* $strm put ?$flushopt? $data */ return ZlibStreamPutCmd(zstream, interp, objc, objv); case zs_get: /* $strm get ?count? */ if (objc > 3) { Tcl_WrongNumArgs(interp, 2, objv, "?count?"); return TCL_ERROR; } count = -1; if (objc >= 3) { if (Tcl_GetIntFromObj(interp, objv[2], &count) != TCL_OK) { return TCL_ERROR; } } TclNewObj(obj); code = Tcl_ZlibStreamGet(zstream, obj, count); if (code == TCL_OK) { Tcl_SetObjResult(interp, obj); } else { TclDecrRefCount(obj); } return code; case zs_flush: /* $strm flush */ if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } TclNewObj(obj); Tcl_IncrRefCount(obj); code = Tcl_ZlibStreamPut(zstream, obj, Z_SYNC_FLUSH); TclDecrRefCount(obj); return code; case zs_fullflush: /* $strm fullflush */ if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } TclNewObj(obj); Tcl_IncrRefCount(obj); code = Tcl_ZlibStreamPut(zstream, obj, Z_FULL_FLUSH); TclDecrRefCount(obj); return code; case zs_finalize: /* $strm finalize */ if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } /* * The flush commands slightly abuse the empty result obj as input * data. */ TclNewObj(obj); Tcl_IncrRefCount(obj); code = Tcl_ZlibStreamPut(zstream, obj, Z_FINISH); TclDecrRefCount(obj); return code; case zs_close: /* $strm close */ if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } return Tcl_ZlibStreamClose(zstream); case zs_eof: /* $strm eof */ if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(Tcl_ZlibStreamEof(zstream))); return TCL_OK; case zs_checksum: /* $strm checksum */ if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj( (uint32_t) Tcl_ZlibStreamChecksum(zstream))); return TCL_OK; case zs_reset: /* $strm reset */ if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } return Tcl_ZlibStreamReset(zstream); } return TCL_OK; } static int ZlibStreamAddCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_ZlibStream zstream = (Tcl_ZlibStream) clientData; int code, buffersize = -1, flush = -1, i; Tcl_Obj *obj, *compDictObj = NULL; static const char *const add_options[] = { "-buffer", "-dictionary", "-finalize", "-flush", "-fullflush", NULL }; enum addOptions { ao_buffer, ao_dictionary, ao_finalize, ao_flush, ao_fullflush } index; for (i=2; i= 0) { flush = -2; } else { flush = Z_SYNC_FLUSH; } break; case ao_fullflush: /* -fullflush */ if (flush >= 0) { flush = -2; } else { flush = Z_FULL_FLUSH; } break; case ao_finalize: /* -finalize */ if (flush >= 0) { flush = -2; } else { flush = Z_FINISH; } break; case ao_buffer: /* -buffer bufferSize */ if (i == objc - 2) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "\"-buffer\" option must be followed by integer " "decompression buffersize", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ZIP", "NOVAL", (char *)NULL); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[++i], &buffersize) != TCL_OK) { return TCL_ERROR; } if (buffersize < 1 || buffersize > MAX_BUFFER_SIZE) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "buffer size must be 1 to %d", MAX_BUFFER_SIZE)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "BUFFERSIZE", (char *)NULL); return TCL_ERROR; } break; case ao_dictionary: /* -dictionary compDict */ if (i == objc - 2) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "\"-dictionary\" option must be followed by" " compression dictionary bytes", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ZIP", "NOVAL", (char *)NULL); return TCL_ERROR; } compDictObj = objv[++i]; break; } if (flush == -2) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "\"-flush\", \"-fullflush\" and \"-finalize\" options" " are mutually exclusive", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ZIP", "EXCLUSIVE", (char *)NULL); return TCL_ERROR; } } if (flush == -1) { flush = 0; } /* * Set the compression dictionary if requested. */ if (compDictObj != NULL) { Tcl_Size len = 0; if (NULL == Tcl_GetBytesFromObj(interp, compDictObj, &len)) { return TCL_ERROR; } if (len == 0) { compDictObj = NULL; } Tcl_ZlibStreamSetCompressionDictionary(zstream, compDictObj); } /* * Send the data to the stream core, along with any flushing directive. */ if (Tcl_ZlibStreamPut(zstream, objv[objc - 1], flush) != TCL_OK) { return TCL_ERROR; } /* * Get such data out as we can (up to the requested length). */ TclNewObj(obj); code = Tcl_ZlibStreamGet(zstream, obj, buffersize); if (code == TCL_OK) { Tcl_SetObjResult(interp, obj); } else { TclDecrRefCount(obj); } return code; } static int ZlibStreamPutCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Tcl_ZlibStream zstream = (Tcl_ZlibStream) clientData; int flush = -1, i; Tcl_Obj *compDictObj = NULL; static const char *const put_options[] = { "-dictionary", "-finalize", "-flush", "-fullflush", NULL }; enum putOptions { po_dictionary, po_finalize, po_flush, po_fullflush } index; for (i=2; i= 0) { flush = -2; } else { flush = Z_SYNC_FLUSH; } break; case po_fullflush: /* -fullflush */ if (flush >= 0) { flush = -2; } else { flush = Z_FULL_FLUSH; } break; case po_finalize: /* -finalize */ if (flush >= 0) { flush = -2; } else { flush = Z_FINISH; } break; case po_dictionary: /* -dictionary compDict */ if (i == objc - 2) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "\"-dictionary\" option must be followed by" " compression dictionary bytes", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ZIP", "NOVAL", (char *)NULL); return TCL_ERROR; } compDictObj = objv[++i]; break; } if (flush == -2) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "\"-flush\", \"-fullflush\" and \"-finalize\" options" " are mutually exclusive", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ZIP", "EXCLUSIVE", (char *)NULL); return TCL_ERROR; } } if (flush == -1) { flush = 0; } /* * Set the compression dictionary if requested. */ if (compDictObj != NULL) { Tcl_Size len = 0; if (NULL == Tcl_GetBytesFromObj(interp, compDictObj, &len)) { return TCL_ERROR; } if (len == 0) { compDictObj = NULL; } Tcl_ZlibStreamSetCompressionDictionary(zstream, compDictObj); } /* * Send the data to the stream core, along with any flushing directive. */ return Tcl_ZlibStreamPut(zstream, objv[objc - 1], flush); } static int ZlibStreamHeaderCmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { ZlibStreamHandle *zshPtr = (ZlibStreamHandle *) clientData; Tcl_Obj *resultObj; if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } else if (zshPtr->mode != TCL_ZLIB_STREAM_INFLATE || zshPtr->format != TCL_ZLIB_FORMAT_GZIP) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "only gunzip streams can produce header information", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "ZIP", "BADOP", (char *)NULL); return TCL_ERROR; } TclNewObj(resultObj); ExtractHeader(&zshPtr->gzHeaderPtr->header, resultObj); Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* *---------------------------------------------------------------------- * Set of functions to support channel stacking. *---------------------------------------------------------------------- */ static inline int HaveFlag( ZlibChannelData *chanDataPtr, int flag) { return (chanDataPtr->flags & flag) != 0; } /* * * ZlibTransformClose -- * * How to shut down a stacked compressing/decompressing transform. * *---------------------------------------------------------------------- */ static int ZlibTransformClose( void *instanceData, Tcl_Interp *interp, int flags) { ZlibChannelData *chanDataPtr = (ZlibChannelData *) instanceData; int e, result = TCL_OK; size_t written; if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) { return EINVAL; } /* * Delete the support timer. */ ZlibTransformEventTimerKill(chanDataPtr); /* * Flush any data waiting to be compressed. */ if (chanDataPtr->mode == TCL_ZLIB_STREAM_DEFLATE) { chanDataPtr->outStream.avail_in = 0; do { e = Deflate(&chanDataPtr->outStream, chanDataPtr->outBuffer, chanDataPtr->outAllocated, Z_FINISH, &written); /* * Can't be sure that deflate() won't declare the buffer to be * full (with Z_BUF_ERROR) so handle that case. */ if (e == Z_BUF_ERROR) { e = Z_OK; written = chanDataPtr->outAllocated; } if (e != Z_OK && e != Z_STREAM_END) { /* TODO: is this the right way to do errors on close? */ if (!TclInThreadExit()) { ConvertError(interp, e, chanDataPtr->outStream.adler); } result = TCL_ERROR; break; } if (written && Tcl_WriteRaw(chanDataPtr->parent, chanDataPtr->outBuffer, written) == TCL_IO_FAILURE) { /* TODO: is this the right way to do errors on close? * Note: when close is called from FinalizeIOSubsystem then * interp may be NULL */ if (!TclInThreadExit() && interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error while finalizing file: %s", Tcl_PosixError(interp))); } result = TCL_ERROR; break; } } while (e != Z_STREAM_END); (void) deflateEnd(&chanDataPtr->outStream); } else { /* * If we have unused bytes from the read input (overshot by * Z_STREAM_END or on possible error), unget them back to the parent * channel, so that they appear as not being read yet. */ if (chanDataPtr->inStream.avail_in) { Tcl_Ungets(chanDataPtr->parent, (char *) chanDataPtr->inStream.next_in, chanDataPtr->inStream.avail_in, 0); } (void) inflateEnd(&chanDataPtr->inStream); } /* * Release all memory. */ if (chanDataPtr->compDictObj) { Tcl_DecrRefCount(chanDataPtr->compDictObj); chanDataPtr->compDictObj = NULL; } if (chanDataPtr->inBuffer) { Tcl_Free(chanDataPtr->inBuffer); chanDataPtr->inBuffer = NULL; } if (chanDataPtr->outBuffer) { Tcl_Free(chanDataPtr->outBuffer); chanDataPtr->outBuffer = NULL; } Tcl_Free(chanDataPtr); return result; } /* *---------------------------------------------------------------------- * * ZlibTransformInput -- * * Reader filter that does decompression. * *---------------------------------------------------------------------- */ static int ZlibTransformInput( void *instanceData, char *buf, int toRead, int *errorCodePtr) { ZlibChannelData *chanDataPtr = (ZlibChannelData *) instanceData; Tcl_DriverInputProc *inProc = Tcl_ChannelInputProc(Tcl_GetChannelType(chanDataPtr->parent)); int readBytes, gotBytes; if (chanDataPtr->mode == TCL_ZLIB_STREAM_DEFLATE) { return inProc(Tcl_GetChannelInstanceData(chanDataPtr->parent), buf, toRead, errorCodePtr); } gotBytes = 0; readBytes = chanDataPtr->inStream.avail_in; /* how many bytes in buffer now */ while (!HaveFlag(chanDataPtr, STREAM_DONE) && toRead > 0) { unsigned int n; int decBytes; /* if starting from scratch or continuation after full decompression */ if (!chanDataPtr->inStream.avail_in) { /* buffer to start, we can read to whole available buffer */ chanDataPtr->inStream.next_in = (Bytef *) chanDataPtr->inBuffer; } /* * If done - no read needed anymore, check we have to copy rest of * decompressed data, otherwise return with size (or 0 for Eof) */ if (HaveFlag(chanDataPtr, STREAM_DECOMPRESS)) { goto copyDecompressed; } /* * The buffer is exhausted, but the caller wants even more. We now * have to go to the underlying channel, get more bytes and then * transform them for delivery. We may not get what we want (full EOF * or temporarily out of data). */ /* Check free buffer size and adjust size of next chunk to read. */ n = chanDataPtr->inAllocated - ((char *) chanDataPtr->inStream.next_in - chanDataPtr->inBuffer); if (n <= 0) { /* Normally unreachable: not enough input buffer to uncompress. * Todo: firstly try to realloc inBuffer upto MAX_BUFFER_SIZE. */ *errorCodePtr = ENOBUFS; return -1; } if (n > chanDataPtr->readAheadLimit) { n = chanDataPtr->readAheadLimit; } readBytes = Tcl_ReadRaw(chanDataPtr->parent, (char *) chanDataPtr->inStream.next_in, n); /* * Three cases here: * 1. Got some data from the underlying channel (readBytes > 0) so * it should be fed through the decompression engine. * 2. Got an error (readBytes == -1) which we should report up except * for the case where we can convert it to a short read. * 3. Got an end-of-data from EOF or blocking (readBytes == 0). If * it is EOF, try flushing the data out of the decompressor. */ if (readBytes == -1) { /* See ReflectInput() in tclIORTrans.c */ if (Tcl_InputBlocked(chanDataPtr->parent) && (gotBytes > 0)) { break; } *errorCodePtr = Tcl_GetErrno(); return -1; } /* more bytes (or Eof if readBytes == 0) */ chanDataPtr->inStream.avail_in += readBytes; copyDecompressed: /* * Transform the read chunk, if not empty. Anything we get * back is a transformation result to be put into our buffers, and * the next iteration will put it into the result. * For the case readBytes is 0 which signaling Eof in parent, the * partial data waiting is converted and returned. */ decBytes = ResultDecompress(chanDataPtr, buf, toRead, (readBytes != 0) ? Z_NO_FLUSH : Z_SYNC_FLUSH, errorCodePtr); if (decBytes == -1) { return -1; } gotBytes += decBytes; buf += decBytes; toRead -= decBytes; if ((decBytes == 0) || HaveFlag(chanDataPtr, STREAM_DECOMPRESS)) { /* * The drain delivered nothing (or buffer too small to decompress). * Time to deliver what we've got. */ if (!gotBytes && !HaveFlag(chanDataPtr, STREAM_DONE)) { /* if no-data, but not ready - avoid signaling Eof, * continue in blocking mode, otherwise EAGAIN */ if (Tcl_InputBlocked(chanDataPtr->parent)) { continue; } *errorCodePtr = EAGAIN; return -1; } break; } /* * Loop until the request is satisfied (or no data available from * above, possibly EOF). */ } return gotBytes; } /* *---------------------------------------------------------------------- * * ZlibTransformOutput -- * * Writer filter that does compression. * *---------------------------------------------------------------------- */ static int ZlibTransformOutput( void *instanceData, const char *buf, int toWrite, int *errorCodePtr) { ZlibChannelData *chanDataPtr = (ZlibChannelData *) instanceData; Tcl_DriverOutputProc *outProc = Tcl_ChannelOutputProc(Tcl_GetChannelType(chanDataPtr->parent)); int e; size_t produced; Tcl_Obj *errObj; if (chanDataPtr->mode == TCL_ZLIB_STREAM_INFLATE) { return outProc(Tcl_GetChannelInstanceData(chanDataPtr->parent), buf, toWrite, errorCodePtr); } /* * No zero-length writes. Flushes must be explicit. */ if (toWrite == 0) { return 0; } chanDataPtr->outStream.next_in = (Bytef *) buf; chanDataPtr->outStream.avail_in = toWrite; while (chanDataPtr->outStream.avail_in > 0) { e = Deflate(&chanDataPtr->outStream, chanDataPtr->outBuffer, chanDataPtr->outAllocated, Z_NO_FLUSH, &produced); if (e != Z_OK || produced == 0) { break; } if (Tcl_WriteRaw(chanDataPtr->parent, chanDataPtr->outBuffer, produced) == TCL_IO_FAILURE) { *errorCodePtr = Tcl_GetErrno(); return -1; } } if (e == Z_OK) { return toWrite - chanDataPtr->outStream.avail_in; } errObj = Tcl_NewListObj(0, NULL); Tcl_ListObjAppendElement(NULL, errObj, Tcl_NewStringObj( "-errorcode", TCL_AUTO_LENGTH)); Tcl_ListObjAppendElement(NULL, errObj, ConvertErrorToList(e, chanDataPtr->outStream.adler)); Tcl_ListObjAppendElement(NULL, errObj, Tcl_NewStringObj(chanDataPtr->outStream.msg, TCL_AUTO_LENGTH)); Tcl_SetChannelError(chanDataPtr->parent, errObj); *errorCodePtr = EINVAL; return -1; } /* *---------------------------------------------------------------------- * * ZlibTransformFlush -- * * How to perform a flush of a compressing transform. * *---------------------------------------------------------------------- */ static int ZlibTransformFlush( Tcl_Interp *interp, ZlibChannelData *chanDataPtr, int flushType) { int e; size_t len; chanDataPtr->outStream.avail_in = 0; do { /* * Get the bytes to go out of the compression engine. */ e = Deflate(&chanDataPtr->outStream, chanDataPtr->outBuffer, chanDataPtr->outAllocated, flushType, &len); if (e != Z_OK && e != Z_BUF_ERROR) { ConvertError(interp, e, chanDataPtr->outStream.adler); return TCL_ERROR; } /* * Write the bytes we've received to the next layer. */ if (len > 0 && Tcl_WriteRaw(chanDataPtr->parent, chanDataPtr->outBuffer, len) == TCL_IO_FAILURE) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "problem flushing channel: %s", Tcl_PosixError(interp))); return TCL_ERROR; } /* * If we get to this point, either we're in the Z_OK or the * Z_BUF_ERROR state. In the former case, we're done. In the latter * case, it's because there's more bytes to go than would fit in the * buffer we provided, and we need to go round again to get some more. * * We also stop the loop if we would have done a zero-length write. * Those can cause problems at the OS level. */ } while (len > 0 && e == Z_BUF_ERROR); return TCL_OK; } /* *---------------------------------------------------------------------- * * ZlibTransformSetOption -- * * Writing side of [fconfigure] on our channel. * *---------------------------------------------------------------------- */ static int ZlibTransformSetOption( /* not used */ void *instanceData, Tcl_Interp *interp, const char *optionName, const char *value) { ZlibChannelData *chanDataPtr = (ZlibChannelData *) instanceData; Tcl_DriverSetOptionProc *setOptionProc = Tcl_ChannelSetOptionProc(Tcl_GetChannelType(chanDataPtr->parent)); static const char *compressChanOptions = "dictionary flush"; static const char *gzipChanOptions = "flush"; static const char *decompressChanOptions = "dictionary limit"; static const char *gunzipChanOptions = "flush limit"; int haveFlushOpt = (chanDataPtr->mode == TCL_ZLIB_STREAM_DEFLATE); if (optionName && (strcmp(optionName, "-dictionary") == 0) && (chanDataPtr->format != TCL_ZLIB_FORMAT_GZIP)) { Tcl_Obj *compDictObj; int code; TclNewStringObj(compDictObj, value, strlen(value)); Tcl_IncrRefCount(compDictObj); if (NULL == Tcl_GetBytesFromObj(interp, compDictObj, (Tcl_Size *)NULL)) { Tcl_DecrRefCount(compDictObj); return TCL_ERROR; } if (chanDataPtr->compDictObj) { TclDecrRefCount(chanDataPtr->compDictObj); } chanDataPtr->compDictObj = compDictObj; code = Z_OK; if (chanDataPtr->mode == TCL_ZLIB_STREAM_DEFLATE) { code = SetDeflateDictionary(&chanDataPtr->outStream, compDictObj); if (code != Z_OK) { ConvertError(interp, code, chanDataPtr->outStream.adler); return TCL_ERROR; } } else if (chanDataPtr->format == TCL_ZLIB_FORMAT_RAW) { code = SetInflateDictionary(&chanDataPtr->inStream, compDictObj); if (code != Z_OK) { ConvertError(interp, code, chanDataPtr->inStream.adler); return TCL_ERROR; } } return TCL_OK; } if (haveFlushOpt) { if (optionName && strcmp(optionName, "-flush") == 0) { int flushType; if (value[0] == 'f' && strcmp(value, "full") == 0) { flushType = Z_FULL_FLUSH; } else if (value[0] == 's' && strcmp(value, "sync") == 0) { flushType = Z_SYNC_FLUSH; } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown -flush type \"%s\": must be full or sync", value)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "FLUSH", (char *)NULL); return TCL_ERROR; } /* * Try to actually do the flush now. */ return ZlibTransformFlush(interp, chanDataPtr, flushType); } } else { if (optionName && strcmp(optionName, "-limit") == 0) { int newLimit; if (Tcl_GetInt(interp, value, &newLimit) != TCL_OK) { return TCL_ERROR; } else if (newLimit < 1 || newLimit > MAX_BUFFER_SIZE) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "-limit must be between 1 and 65536", TCL_AUTO_LENGTH)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "READLIMIT", (char *)NULL); return TCL_ERROR; } } } if (setOptionProc == NULL) { if (chanDataPtr->format == TCL_ZLIB_FORMAT_GZIP) { return Tcl_BadChannelOption(interp, optionName, (chanDataPtr->mode == TCL_ZLIB_STREAM_DEFLATE) ? gzipChanOptions : gunzipChanOptions); } else { return Tcl_BadChannelOption(interp, optionName, (chanDataPtr->mode == TCL_ZLIB_STREAM_DEFLATE) ? compressChanOptions : decompressChanOptions); } } /* * Pass all unknown options down, to deeper transforms and/or the base * channel. */ return setOptionProc(Tcl_GetChannelInstanceData(chanDataPtr->parent), interp, optionName, value); } /* *---------------------------------------------------------------------- * * ZlibTransformGetOption -- * * Reading side of [fconfigure] on our channel. * *---------------------------------------------------------------------- */ static int ZlibTransformGetOption( void *instanceData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr) { ZlibChannelData *chanDataPtr = (ZlibChannelData *) instanceData; Tcl_DriverGetOptionProc *getOptionProc = Tcl_ChannelGetOptionProc(Tcl_GetChannelType(chanDataPtr->parent)); static const char *compressChanOptions = "checksum dictionary"; static const char *gzipChanOptions = "checksum"; static const char *decompressChanOptions = "checksum dictionary limit"; static const char *gunzipChanOptions = "checksum header limit"; /* * The "crc" option reports the current CRC (calculated with the Adler32 * or CRC32 algorithm according to the format) given the data that has * been processed so far. */ if (optionName == NULL || strcmp(optionName, "-checksum") == 0) { uLong crc; char buf[12]; if (chanDataPtr->mode == TCL_ZLIB_STREAM_DEFLATE) { crc = chanDataPtr->outStream.adler; } else { crc = chanDataPtr->inStream.adler; } snprintf(buf, sizeof(buf), "%lu", crc); if (optionName == NULL) { Tcl_DStringAppendElement(dsPtr, "-checksum"); Tcl_DStringAppendElement(dsPtr, buf); } else { Tcl_DStringAppend(dsPtr, buf, TCL_AUTO_LENGTH); return TCL_OK; } } if ((chanDataPtr->format != TCL_ZLIB_FORMAT_GZIP) && (optionName == NULL || strcmp(optionName, "-dictionary") == 0)) { /* * Embedded NUL bytes are ok; they'll be C080-encoded. */ if (optionName == NULL) { Tcl_DStringAppendElement(dsPtr, "-dictionary"); if (chanDataPtr->compDictObj) { Tcl_DStringAppendElement(dsPtr, TclGetString(chanDataPtr->compDictObj)); } else { Tcl_DStringAppendElement(dsPtr, ""); } } else { if (chanDataPtr->compDictObj) { Tcl_Size length; const char *str = TclGetStringFromObj(chanDataPtr->compDictObj, &length); Tcl_DStringAppend(dsPtr, str, length); } return TCL_OK; } } /* * The "header" option, which is only valid on inflating gzip channels, * reports the header that has been read from the start of the stream. */ if (HaveFlag(chanDataPtr, IN_HEADER) && ((optionName == NULL) || (strcmp(optionName, "-header") == 0))) { Tcl_Obj *tmpObj; TclNewObj(tmpObj); ExtractHeader(&chanDataPtr->inHeader.header, tmpObj); if (optionName == NULL) { Tcl_DStringAppendElement(dsPtr, "-header"); Tcl_DStringAppendElement(dsPtr, TclGetString(tmpObj)); Tcl_DecrRefCount(tmpObj); } else { TclDStringAppendObj(dsPtr, tmpObj); Tcl_DecrRefCount(tmpObj); return TCL_OK; } } /* * Now we do the standard processing of the stream we wrapped. */ if (getOptionProc) { return getOptionProc(Tcl_GetChannelInstanceData(chanDataPtr->parent), interp, optionName, dsPtr); } if (optionName == NULL) { return TCL_OK; } if (chanDataPtr->format == TCL_ZLIB_FORMAT_GZIP) { return Tcl_BadChannelOption(interp, optionName, (chanDataPtr->mode == TCL_ZLIB_STREAM_DEFLATE) ? gzipChanOptions : gunzipChanOptions); } else { return Tcl_BadChannelOption(interp, optionName, (chanDataPtr->mode == TCL_ZLIB_STREAM_DEFLATE) ? compressChanOptions : decompressChanOptions); } } /* *---------------------------------------------------------------------- * * ZlibTransformWatch, ZlibTransformEventHandler -- * * If we have data pending, trigger a readable event after a short time * (in order to allow a real event to catch up). * *---------------------------------------------------------------------- */ static void ZlibTransformWatch( void *instanceData, int mask) { ZlibChannelData *chanDataPtr = (ZlibChannelData *) instanceData; Tcl_DriverWatchProc *watchProc; /* * This code is based on the code in tclIORTrans.c */ watchProc = Tcl_ChannelWatchProc(Tcl_GetChannelType(chanDataPtr->parent)); watchProc(Tcl_GetChannelInstanceData(chanDataPtr->parent), mask); if (!(mask & TCL_READABLE) || !HaveFlag(chanDataPtr, STREAM_DECOMPRESS)) { ZlibTransformEventTimerKill(chanDataPtr); } else if (chanDataPtr->timer == NULL) { chanDataPtr->timer = Tcl_CreateTimerHandler(SYNTHETIC_EVENT_TIME, ZlibTransformTimerRun, chanDataPtr); } } static int ZlibTransformEventHandler( void *instanceData, int interestMask) { ZlibChannelData *chanDataPtr = (ZlibChannelData *) instanceData; ZlibTransformEventTimerKill(chanDataPtr); return interestMask; } static inline void ZlibTransformEventTimerKill( ZlibChannelData *chanDataPtr) { if (chanDataPtr->timer != NULL) { Tcl_DeleteTimerHandler(chanDataPtr->timer); chanDataPtr->timer = NULL; } } static void ZlibTransformTimerRun( void *clientData) { ZlibChannelData *chanDataPtr = (ZlibChannelData *) clientData; chanDataPtr->timer = NULL; Tcl_NotifyChannel(chanDataPtr->chan, TCL_READABLE); } /* *---------------------------------------------------------------------- * * ZlibTransformGetHandle -- * * Anything that needs the OS handle is told to get it from what we are * stacked on top of. * *---------------------------------------------------------------------- */ static int ZlibTransformGetHandle( void *instanceData, int direction, void **handlePtr) { ZlibChannelData *chanDataPtr = (ZlibChannelData *) instanceData; return Tcl_GetChannelHandle(chanDataPtr->parent, direction, handlePtr); } /* *---------------------------------------------------------------------- * * ZlibTransformBlockMode -- * * We need to keep track of the blocking mode; it changes our behavior. * *---------------------------------------------------------------------- */ static int ZlibTransformBlockMode( void *instanceData, int mode) { ZlibChannelData *chanDataPtr = (ZlibChannelData *) instanceData; if (mode == TCL_MODE_NONBLOCKING) { chanDataPtr->flags |= ASYNC; } else { chanDataPtr->flags &= ~ASYNC; } return TCL_OK; } /* *---------------------------------------------------------------------- * * ZlibStackChannelTransform -- * * Stacks either compression or decompression onto a channel. * * Results: * The stacked channel, or NULL if there was an error. * *---------------------------------------------------------------------- */ static Tcl_Channel ZlibStackChannelTransform( Tcl_Interp *interp, /* Where to write error messages. */ int mode, /* Whether this is a compressing transform * (TCL_ZLIB_STREAM_DEFLATE) or a * decompressing transform * (TCL_ZLIB_STREAM_INFLATE). Note that * compressing transforms require that the * channel is writable, and decompressing * transforms require that the channel is * readable. */ int format, /* One of the TCL_ZLIB_FORMAT_* values that * indicates what compressed format to allow. * TCL_ZLIB_FORMAT_AUTO is only supported for * decompressing transforms. */ int level, /* What compression level to use. Ignored for * decompressing transforms. */ int limit, /* The limit on the number of bytes to read * ahead; always at least 1. */ Tcl_Channel channel, /* The channel to attach to. */ Tcl_Obj *gzipHeaderDictPtr, /* A description of header to use, or NULL to * use a default. Ignored if not compressing * to produce gzip-format data. */ Tcl_Obj *compDictObj) /* Byte-array object containing compression * dictionary (not dictObj!) to use if * necessary. */ { ZlibChannelData *chanDataPtr = (ZlibChannelData *) Tcl_Alloc(sizeof(ZlibChannelData)); Tcl_Channel chan; int wbits = 0; if (mode != TCL_ZLIB_STREAM_DEFLATE && mode != TCL_ZLIB_STREAM_INFLATE) { Tcl_Panic("unknown mode: %d", mode); } memset(chanDataPtr, 0, sizeof(ZlibChannelData)); chanDataPtr->mode = mode; chanDataPtr->format = format; chanDataPtr->readAheadLimit = limit; if (format == TCL_ZLIB_FORMAT_GZIP || format == TCL_ZLIB_FORMAT_AUTO) { if (mode == TCL_ZLIB_STREAM_DEFLATE) { if (gzipHeaderDictPtr) { chanDataPtr->flags |= OUT_HEADER; if (GenerateHeader(interp, gzipHeaderDictPtr, &chanDataPtr->outHeader, NULL) != TCL_OK) { goto error; } } } else { chanDataPtr->flags |= IN_HEADER; chanDataPtr->inHeader.header.name = (Bytef *) &chanDataPtr->inHeader.nativeFilenameBuf; chanDataPtr->inHeader.header.name_max = MAXPATHLEN - 1; chanDataPtr->inHeader.header.comment = (Bytef *) &chanDataPtr->inHeader.nativeCommentBuf; chanDataPtr->inHeader.header.comm_max = MAX_COMMENT_LEN - 1; } } if (compDictObj != NULL) { chanDataPtr->compDictObj = Tcl_DuplicateObj(compDictObj); Tcl_IncrRefCount(chanDataPtr->compDictObj); Tcl_GetBytesFromObj(NULL, chanDataPtr->compDictObj, (Tcl_Size *)NULL); } switch (format) { case TCL_ZLIB_FORMAT_RAW: wbits = WBITS_RAW; break; case TCL_ZLIB_FORMAT_ZLIB: wbits = WBITS_ZLIB; break; case TCL_ZLIB_FORMAT_GZIP: wbits = WBITS_GZIP; break; case TCL_ZLIB_FORMAT_AUTO: wbits = WBITS_AUTODETECT; break; default: Tcl_Panic("bad format: %d", format); } /* * Initialize input inflater or the output deflater. */ if (mode == TCL_ZLIB_STREAM_INFLATE) { if (inflateInit2(&chanDataPtr->inStream, wbits) != Z_OK) { goto error; } chanDataPtr->inAllocated = DEFAULT_BUFFER_SIZE; if (chanDataPtr->inAllocated < chanDataPtr->readAheadLimit) { chanDataPtr->inAllocated = chanDataPtr->readAheadLimit; } chanDataPtr->inBuffer = (char *) Tcl_Alloc(chanDataPtr->inAllocated); if (HaveFlag(chanDataPtr, IN_HEADER)) { if (inflateGetHeader(&chanDataPtr->inStream, &chanDataPtr->inHeader.header) != Z_OK) { goto error; } } if (chanDataPtr->format == TCL_ZLIB_FORMAT_RAW && chanDataPtr->compDictObj) { if (SetInflateDictionary(&chanDataPtr->inStream, chanDataPtr->compDictObj) != Z_OK) { goto error; } } } else { if (deflateInit2(&chanDataPtr->outStream, level, Z_DEFLATED, wbits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) { goto error; } chanDataPtr->outAllocated = DEFAULT_BUFFER_SIZE; chanDataPtr->outBuffer = (char *) Tcl_Alloc(chanDataPtr->outAllocated); if (HaveFlag(chanDataPtr, OUT_HEADER)) { if (deflateSetHeader(&chanDataPtr->outStream, &chanDataPtr->outHeader.header) != Z_OK) { goto error; } } if (chanDataPtr->compDictObj) { if (SetDeflateDictionary(&chanDataPtr->outStream, chanDataPtr->compDictObj) != Z_OK) { goto error; } } } chan = Tcl_StackChannel(interp, &zlibChannelType, chanDataPtr, Tcl_GetChannelMode(channel), channel); if (chan == NULL) { goto error; } chanDataPtr->chan = chan; chanDataPtr->parent = Tcl_GetStackedChannel(chan); Tcl_SetObjResult(interp, Tcl_NewStringObj( Tcl_GetChannelName(chan), TCL_AUTO_LENGTH)); return chan; error: if (chanDataPtr->inBuffer) { Tcl_Free(chanDataPtr->inBuffer); inflateEnd(&chanDataPtr->inStream); } if (chanDataPtr->outBuffer) { Tcl_Free(chanDataPtr->outBuffer); deflateEnd(&chanDataPtr->outStream); } if (chanDataPtr->compDictObj) { Tcl_DecrRefCount(chanDataPtr->compDictObj); } Tcl_Free(chanDataPtr); return NULL; } /* *---------------------------------------------------------------------- * * ResultDecompress -- * * Extract uncompressed bytes from the compression engine and store them * in our buffer (buf) up to toRead bytes. * * Result: * Number of bytes decompressed or -1 if error (with *errorCodePtr updated * with reason). * * Side effects: * After execution it updates chanDataPtr->inStream (next_in, avail_in) to * reflect the data that has been decompressed. * *---------------------------------------------------------------------- */ static int ResultDecompress( ZlibChannelData *chanDataPtr, char *buf, int toRead, int flush, int *errorCodePtr) { int e, written, resBytes = 0; Tcl_Obj *errObj; chanDataPtr->flags &= ~STREAM_DECOMPRESS; chanDataPtr->inStream.next_out = (Bytef *) buf; chanDataPtr->inStream.avail_out = toRead; while (chanDataPtr->inStream.avail_out > 0) { e = inflate(&chanDataPtr->inStream, flush); /* * Apply a compression dictionary if one is needed and we have one. */ if (e == Z_NEED_DICT && chanDataPtr->compDictObj) { e = SetInflateDictionary(&chanDataPtr->inStream, chanDataPtr->compDictObj); if (e == Z_OK) { /* * A repetition of Z_NEED_DICT now is just an error. */ e = inflate(&chanDataPtr->inStream, flush); } } /* * avail_out is now the left over space in the output. Therefore * "toRead - avail_out" is the amount of bytes generated. */ written = toRead - chanDataPtr->inStream.avail_out; /* * The cases where we're definitely done. */ if (e == Z_STREAM_END) { chanDataPtr->flags |= STREAM_DONE; resBytes += written; break; } if (e == Z_OK) { if (written == 0) { break; } resBytes += written; } if ((flush == Z_SYNC_FLUSH) && (e == Z_BUF_ERROR)) { break; } /* * Z_BUF_ERROR can be ignored as per http://www.zlib.net/zlib_how.html * * Just indicates that the zlib couldn't consume input/produce output, * and is fixed by supplying more input. * * Otherwise, we've got errors and need to report to higher-up. */ if ((e != Z_OK) && (e != Z_BUF_ERROR)) { goto handleError; } /* * Check if the inflate stopped early. */ if (chanDataPtr->inStream.avail_in <= 0 && flush != Z_SYNC_FLUSH) { break; } } if (!HaveFlag(chanDataPtr, STREAM_DONE)) { /* if we have pending input data, but no available output buffer */ if (chanDataPtr->inStream.avail_in && !chanDataPtr->inStream.avail_out) { /* next time try to decompress it got readable (new output buffer) */ chanDataPtr->flags |= STREAM_DECOMPRESS; } } return resBytes; handleError: errObj = Tcl_NewListObj(0, NULL); Tcl_ListObjAppendElement(NULL, errObj, Tcl_NewStringObj( "-errorcode", TCL_AUTO_LENGTH)); Tcl_ListObjAppendElement(NULL, errObj, ConvertErrorToList(e, chanDataPtr->inStream.adler)); Tcl_ListObjAppendElement(NULL, errObj, Tcl_NewStringObj(chanDataPtr->inStream.msg, TCL_AUTO_LENGTH)); Tcl_SetChannelError(chanDataPtr->parent, errObj); *errorCodePtr = EINVAL; return -1; } /* *---------------------------------------------------------------------- * Finally, the TclZlibInit function. Used to install the zlib API. *---------------------------------------------------------------------- */ int TclZlibInit( Tcl_Interp *interp) { Tcl_Config cfg[2]; /* * This does two things. It creates a counter used in the creation of * stream commands, and it creates the namespace that will contain those * commands. */ Tcl_EvalEx(interp, "namespace eval ::tcl::zlib {variable cmdcounter 0}", TCL_AUTO_LENGTH, 0); /* * Create the public scripted interface to this file's functionality. */ Tcl_CreateObjCommand(interp, "zlib", ZlibCmd, 0, 0); /* * Store the underlying configuration information. * * TODO: Describe whether we're using the system version of the library or * a compatibility version built into Tcl? */ cfg[0].key = "zlibVersion"; cfg[0].value = zlibVersion(); cfg[1].key = NULL; Tcl_RegisterConfig(interp, "zlib", cfg, "utf-8"); /* * Allow command type introspection to do something sensible with streams. */ TclRegisterCommandTypeName(ZlibStreamCmd, "zlibStream"); /* * Formally provide the package as a Tcl built-in. */ return Tcl_PkgProvideEx(interp, "tcl::zlib", TCL_ZLIB_VERSION, NULL); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/generic/tcl.decls0000644000175000017500000020101214726623136014764 0ustar sergeisergei# tcl.decls -- # # This file contains the declarations for all supported public # functions that are exported by the Tcl library via the stubs table. # This file is used to generate the tclDecls.h, tclPlatDecls.h # and tclStubInit.c files. # # Copyright © 1998-1999 Scriptics Corporation. # Copyright © 2001, 2002 Kevin B. Kenny. All rights reserved. # Copyright © 2007 Daniel A. Steffen # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. library tcl # Define the tcl interface with several sub interfaces: # tclPlat - platform specific public # tclInt - generic private # tclPlatInt - platform specific private interface tcl hooks {tclPlat tclInt tclIntPlat} scspec EXTERN # Declare each of the functions in the public Tcl interface. Note that # the an index should never be reused for a different function in order # to preserve backwards compatibility. declare 0 { int Tcl_PkgProvideEx(Tcl_Interp *interp, const char *name, const char *version, const void *clientData) } declare 1 { const char *Tcl_PkgRequireEx(Tcl_Interp *interp, const char *name, const char *version, int exact, void *clientDataPtr) } declare 2 { TCL_NORETURN void Tcl_Panic(const char *format, ...) } declare 3 { void *Tcl_Alloc(TCL_HASH_TYPE size) } declare 4 { void Tcl_Free(void *ptr) } declare 5 { void *Tcl_Realloc(void *ptr, TCL_HASH_TYPE size) } declare 6 { void *Tcl_DbCkalloc(TCL_HASH_TYPE size, const char *file, int line) } declare 7 { void Tcl_DbCkfree(void *ptr, const char *file, int line) } declare 8 { void *Tcl_DbCkrealloc(void *ptr, TCL_HASH_TYPE size, const char *file, int line) } # Tcl_CreateFileHandler and Tcl_DeleteFileHandler are only available on Unix, # but they are part of the old generic interface, so we include them here for # compatibility reasons. declare 9 { void Tcl_CreateFileHandler(int fd, int mask, Tcl_FileProc *proc, void *clientData) } declare 10 { void Tcl_DeleteFileHandler(int fd) } declare 11 { void Tcl_SetTimer(const Tcl_Time *timePtr) } declare 12 { void Tcl_Sleep(int ms) } declare 13 { int Tcl_WaitForEvent(const Tcl_Time *timePtr) } declare 14 { int Tcl_AppendAllObjTypes(Tcl_Interp *interp, Tcl_Obj *objPtr) } declare 15 { void Tcl_AppendStringsToObj(Tcl_Obj *objPtr, ...) } declare 16 { void Tcl_AppendToObj(Tcl_Obj *objPtr, const char *bytes, Tcl_Size length) } declare 17 { Tcl_Obj *Tcl_ConcatObj(Tcl_Size objc, Tcl_Obj *const objv[]) } declare 18 { int Tcl_ConvertToType(Tcl_Interp *interp, Tcl_Obj *objPtr, const Tcl_ObjType *typePtr) } declare 19 { void Tcl_DbDecrRefCount(Tcl_Obj *objPtr, const char *file, int line) } declare 20 { void Tcl_DbIncrRefCount(Tcl_Obj *objPtr, const char *file, int line) } declare 21 { int Tcl_DbIsShared(Tcl_Obj *objPtr, const char *file, int line) } declare 23 { Tcl_Obj *Tcl_DbNewByteArrayObj(const unsigned char *bytes, Tcl_Size numBytes, const char *file, int line) } declare 24 { Tcl_Obj *Tcl_DbNewDoubleObj(double doubleValue, const char *file, int line) } declare 25 { Tcl_Obj *Tcl_DbNewListObj(Tcl_Size objc, Tcl_Obj *const *objv, const char *file, int line) } declare 27 { Tcl_Obj *Tcl_DbNewObj(const char *file, int line) } declare 28 { Tcl_Obj *Tcl_DbNewStringObj(const char *bytes, Tcl_Size length, const char *file, int line) } declare 29 { Tcl_Obj *Tcl_DuplicateObj(Tcl_Obj *objPtr) } declare 30 { void TclFreeObj(Tcl_Obj *objPtr) } declare 31 { int Tcl_GetBoolean(Tcl_Interp *interp, const char *src, int *intPtr) } declare 32 { int Tcl_GetBooleanFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *intPtr) } # Only available in Tcl 8.x, NULL in Tcl 9.0 declare 33 { unsigned char *Tcl_GetByteArrayFromObj(Tcl_Obj *objPtr, Tcl_Size *numBytesPtr) } declare 34 { int Tcl_GetDouble(Tcl_Interp *interp, const char *src, double *doublePtr) } declare 35 { int Tcl_GetDoubleFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, double *doublePtr) } declare 37 { int Tcl_GetInt(Tcl_Interp *interp, const char *src, int *intPtr) } declare 38 { int Tcl_GetIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *intPtr) } declare 39 { int Tcl_GetLongFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, long *longPtr) } declare 40 { const Tcl_ObjType *Tcl_GetObjType(const char *typeName) } declare 41 { char *TclGetStringFromObj(Tcl_Obj *objPtr, void *lengthPtr) } declare 42 { void Tcl_InvalidateStringRep(Tcl_Obj *objPtr) } declare 43 { int Tcl_ListObjAppendList(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Obj *elemListPtr) } declare 44 { int Tcl_ListObjAppendElement(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Obj *objPtr) } declare 45 { int TclListObjGetElements(Tcl_Interp *interp, Tcl_Obj *listPtr, void *objcPtr, Tcl_Obj ***objvPtr) } declare 46 { int Tcl_ListObjIndex(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size index, Tcl_Obj **objPtrPtr) } declare 47 { int TclListObjLength(Tcl_Interp *interp, Tcl_Obj *listPtr, void *lengthPtr) } declare 48 { int Tcl_ListObjReplace(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size first, Tcl_Size count, Tcl_Size objc, Tcl_Obj *const objv[]) } declare 50 { Tcl_Obj *Tcl_NewByteArrayObj(const unsigned char *bytes, Tcl_Size numBytes) } declare 51 { Tcl_Obj *Tcl_NewDoubleObj(double doubleValue) } declare 53 { Tcl_Obj *Tcl_NewListObj(Tcl_Size objc, Tcl_Obj *const objv[]) } declare 55 { Tcl_Obj *Tcl_NewObj(void) } declare 56 { Tcl_Obj *Tcl_NewStringObj(const char *bytes, Tcl_Size length) } declare 58 { unsigned char *Tcl_SetByteArrayLength(Tcl_Obj *objPtr, Tcl_Size numBytes) } declare 59 { void Tcl_SetByteArrayObj(Tcl_Obj *objPtr, const unsigned char *bytes, Tcl_Size numBytes) } declare 60 { void Tcl_SetDoubleObj(Tcl_Obj *objPtr, double doubleValue) } declare 62 { void Tcl_SetListObj(Tcl_Obj *objPtr, Tcl_Size objc, Tcl_Obj *const objv[]) } declare 64 { void Tcl_SetObjLength(Tcl_Obj *objPtr, Tcl_Size length) } declare 65 { void Tcl_SetStringObj(Tcl_Obj *objPtr, const char *bytes, Tcl_Size length) } declare 68 { void Tcl_AllowExceptions(Tcl_Interp *interp) } declare 69 { void Tcl_AppendElement(Tcl_Interp *interp, const char *element) } declare 70 { void Tcl_AppendResult(Tcl_Interp *interp, ...) } declare 71 { Tcl_AsyncHandler Tcl_AsyncCreate(Tcl_AsyncProc *proc, void *clientData) } declare 72 { void Tcl_AsyncDelete(Tcl_AsyncHandler async) } declare 73 { int Tcl_AsyncInvoke(Tcl_Interp *interp, int code) } declare 74 { void Tcl_AsyncMark(Tcl_AsyncHandler async) } declare 75 { int Tcl_AsyncReady(void) } declare 78 { int Tcl_BadChannelOption(Tcl_Interp *interp, const char *optionName, const char *optionList) } declare 79 { void Tcl_CallWhenDeleted(Tcl_Interp *interp, Tcl_InterpDeleteProc *proc, void *clientData) } declare 80 { void Tcl_CancelIdleCall(Tcl_IdleProc *idleProc, void *clientData) } # Only available in Tcl 8.x, NULL in Tcl 9.0 declare 81 { int Tcl_Close(Tcl_Interp *interp, Tcl_Channel chan) } declare 82 { int Tcl_CommandComplete(const char *cmd) } declare 83 { char *Tcl_Concat(Tcl_Size argc, const char *const *argv) } declare 84 { Tcl_Size Tcl_ConvertElement(const char *src, char *dst, int flags) } declare 85 { Tcl_Size Tcl_ConvertCountedElement(const char *src, Tcl_Size length, char *dst, int flags) } declare 86 { int Tcl_CreateAlias(Tcl_Interp *childInterp, const char *childCmd, Tcl_Interp *target, const char *targetCmd, Tcl_Size argc, const char *const *argv) } declare 87 { int Tcl_CreateAliasObj(Tcl_Interp *childInterp, const char *childCmd, Tcl_Interp *target, const char *targetCmd, Tcl_Size objc, Tcl_Obj *const objv[]) } declare 88 { Tcl_Channel Tcl_CreateChannel(const Tcl_ChannelType *typePtr, const char *chanName, void *instanceData, int mask) } declare 89 { void Tcl_CreateChannelHandler(Tcl_Channel chan, int mask, Tcl_ChannelProc *proc, void *clientData) } declare 90 { void Tcl_CreateCloseHandler(Tcl_Channel chan, Tcl_CloseProc *proc, void *clientData) } declare 91 { Tcl_Command Tcl_CreateCommand(Tcl_Interp *interp, const char *cmdName, Tcl_CmdProc *proc, void *clientData, Tcl_CmdDeleteProc *deleteProc) } declare 92 { void Tcl_CreateEventSource(Tcl_EventSetupProc *setupProc, Tcl_EventCheckProc *checkProc, void *clientData) } declare 93 { void Tcl_CreateExitHandler(Tcl_ExitProc *proc, void *clientData) } declare 94 { Tcl_Interp *Tcl_CreateInterp(void) } declare 96 { Tcl_Command Tcl_CreateObjCommand(Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc *proc, void *clientData, Tcl_CmdDeleteProc *deleteProc) } declare 97 { Tcl_Interp *Tcl_CreateChild(Tcl_Interp *interp, const char *name, int isSafe) } declare 98 { Tcl_TimerToken Tcl_CreateTimerHandler(int milliseconds, Tcl_TimerProc *proc, void *clientData) } declare 99 { Tcl_Trace Tcl_CreateTrace(Tcl_Interp *interp, Tcl_Size level, Tcl_CmdTraceProc *proc, void *clientData) } declare 100 { void Tcl_DeleteAssocData(Tcl_Interp *interp, const char *name) } declare 101 { void Tcl_DeleteChannelHandler(Tcl_Channel chan, Tcl_ChannelProc *proc, void *clientData) } declare 102 { void Tcl_DeleteCloseHandler(Tcl_Channel chan, Tcl_CloseProc *proc, void *clientData) } declare 103 { int Tcl_DeleteCommand(Tcl_Interp *interp, const char *cmdName) } declare 104 { int Tcl_DeleteCommandFromToken(Tcl_Interp *interp, Tcl_Command command) } declare 105 { void Tcl_DeleteEvents(Tcl_EventDeleteProc *proc, void *clientData) } declare 106 { void Tcl_DeleteEventSource(Tcl_EventSetupProc *setupProc, Tcl_EventCheckProc *checkProc, void *clientData) } declare 107 { void Tcl_DeleteExitHandler(Tcl_ExitProc *proc, void *clientData) } declare 108 { void Tcl_DeleteHashEntry(Tcl_HashEntry *entryPtr) } declare 109 { void Tcl_DeleteHashTable(Tcl_HashTable *tablePtr) } declare 110 { void Tcl_DeleteInterp(Tcl_Interp *interp) } declare 111 { void Tcl_DetachPids(Tcl_Size numPids, Tcl_Pid *pidPtr) } declare 112 { void Tcl_DeleteTimerHandler(Tcl_TimerToken token) } declare 113 { void Tcl_DeleteTrace(Tcl_Interp *interp, Tcl_Trace trace) } declare 114 { void Tcl_DontCallWhenDeleted(Tcl_Interp *interp, Tcl_InterpDeleteProc *proc, void *clientData) } declare 115 { int Tcl_DoOneEvent(int flags) } declare 116 { void Tcl_DoWhenIdle(Tcl_IdleProc *proc, void *clientData) } declare 117 { char *Tcl_DStringAppend(Tcl_DString *dsPtr, const char *bytes, Tcl_Size length) } declare 118 { char *Tcl_DStringAppendElement(Tcl_DString *dsPtr, const char *element) } declare 119 { void Tcl_DStringEndSublist(Tcl_DString *dsPtr) } declare 120 { void Tcl_DStringFree(Tcl_DString *dsPtr) } declare 121 { void Tcl_DStringGetResult(Tcl_Interp *interp, Tcl_DString *dsPtr) } declare 122 { void Tcl_DStringInit(Tcl_DString *dsPtr) } declare 123 { void Tcl_DStringResult(Tcl_Interp *interp, Tcl_DString *dsPtr) } declare 124 { void Tcl_DStringSetLength(Tcl_DString *dsPtr, Tcl_Size length) } declare 125 { void Tcl_DStringStartSublist(Tcl_DString *dsPtr) } declare 126 { int Tcl_Eof(Tcl_Channel chan) } declare 127 { const char *Tcl_ErrnoId(void) } declare 128 { const char *Tcl_ErrnoMsg(int err) } declare 130 { int Tcl_EvalFile(Tcl_Interp *interp, const char *fileName) } declare 132 { void Tcl_EventuallyFree(void *clientData, Tcl_FreeProc *freeProc) } declare 133 { TCL_NORETURN void Tcl_Exit(int status) } declare 134 { int Tcl_ExposeCommand(Tcl_Interp *interp, const char *hiddenCmdToken, const char *cmdName) } declare 135 { int Tcl_ExprBoolean(Tcl_Interp *interp, const char *expr, int *ptr) } declare 136 { int Tcl_ExprBooleanObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *ptr) } declare 137 { int Tcl_ExprDouble(Tcl_Interp *interp, const char *expr, double *ptr) } declare 138 { int Tcl_ExprDoubleObj(Tcl_Interp *interp, Tcl_Obj *objPtr, double *ptr) } declare 139 { int Tcl_ExprLong(Tcl_Interp *interp, const char *expr, long *ptr) } declare 140 { int Tcl_ExprLongObj(Tcl_Interp *interp, Tcl_Obj *objPtr, long *ptr) } declare 141 { int Tcl_ExprObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj **resultPtrPtr) } declare 142 { int Tcl_ExprString(Tcl_Interp *interp, const char *expr) } declare 143 { void Tcl_Finalize(void) } declare 145 { Tcl_HashEntry *Tcl_FirstHashEntry(Tcl_HashTable *tablePtr, Tcl_HashSearch *searchPtr) } declare 146 { int Tcl_Flush(Tcl_Channel chan) } declare 149 { int TclGetAliasObj(Tcl_Interp *interp, const char *childCmd, Tcl_Interp **targetInterpPtr, const char **targetCmdPtr, int *objcPtr, Tcl_Obj ***objvPtr) } declare 150 { void *Tcl_GetAssocData(Tcl_Interp *interp, const char *name, Tcl_InterpDeleteProc **procPtr) } declare 151 { Tcl_Channel Tcl_GetChannel(Tcl_Interp *interp, const char *chanName, int *modePtr) } declare 152 { Tcl_Size Tcl_GetChannelBufferSize(Tcl_Channel chan) } declare 153 { int Tcl_GetChannelHandle(Tcl_Channel chan, int direction, void **handlePtr) } declare 154 { void *Tcl_GetChannelInstanceData(Tcl_Channel chan) } declare 155 { int Tcl_GetChannelMode(Tcl_Channel chan) } declare 156 { const char *Tcl_GetChannelName(Tcl_Channel chan) } declare 157 { int Tcl_GetChannelOption(Tcl_Interp *interp, Tcl_Channel chan, const char *optionName, Tcl_DString *dsPtr) } declare 158 { const Tcl_ChannelType *Tcl_GetChannelType(Tcl_Channel chan) } declare 159 { int Tcl_GetCommandInfo(Tcl_Interp *interp, const char *cmdName, Tcl_CmdInfo *infoPtr) } declare 160 { const char *Tcl_GetCommandName(Tcl_Interp *interp, Tcl_Command command) } declare 161 { int Tcl_GetErrno(void) } declare 162 { const char *Tcl_GetHostName(void) } declare 163 { int Tcl_GetInterpPath(Tcl_Interp *interp, Tcl_Interp *childInterp) } declare 164 { Tcl_Interp *Tcl_GetParent(Tcl_Interp *interp) } declare 165 { const char *Tcl_GetNameOfExecutable(void) } declare 166 { Tcl_Obj *Tcl_GetObjResult(Tcl_Interp *interp) } # Tcl_GetOpenFile is only available on unix, but it is a part of the old # generic interface, so we include it here for compatibility reasons. declare 167 { int Tcl_GetOpenFile(Tcl_Interp *interp, const char *chanID, int forWriting, int checkUsage, void **filePtr) } # Obsolete. Should now use Tcl_FSGetPathType which is objectified # and therefore usually faster. declare 168 { Tcl_PathType Tcl_GetPathType(const char *path) } declare 169 { Tcl_Size Tcl_Gets(Tcl_Channel chan, Tcl_DString *dsPtr) } declare 170 { Tcl_Size Tcl_GetsObj(Tcl_Channel chan, Tcl_Obj *objPtr) } declare 171 { int Tcl_GetServiceMode(void) } declare 172 { Tcl_Interp *Tcl_GetChild(Tcl_Interp *interp, const char *name) } declare 173 { Tcl_Channel Tcl_GetStdChannel(int type) } declare 176 { const char *Tcl_GetVar2(Tcl_Interp *interp, const char *part1, const char *part2, int flags) } declare 179 { int Tcl_HideCommand(Tcl_Interp *interp, const char *cmdName, const char *hiddenCmdToken) } declare 180 { int Tcl_Init(Tcl_Interp *interp) } declare 181 { void Tcl_InitHashTable(Tcl_HashTable *tablePtr, int keyType) } declare 182 { int Tcl_InputBlocked(Tcl_Channel chan) } declare 183 { int Tcl_InputBuffered(Tcl_Channel chan) } declare 184 { int Tcl_InterpDeleted(Tcl_Interp *interp) } declare 185 { int Tcl_IsSafe(Tcl_Interp *interp) } # Obsolete, use Tcl_FSJoinPath declare 186 { char *Tcl_JoinPath(Tcl_Size argc, const char *const *argv, Tcl_DString *resultPtr) } declare 187 { int Tcl_LinkVar(Tcl_Interp *interp, const char *varName, void *addr, int type) } # This slot is reserved for use by the plus patch: # declare 188 { # Tcl_MainLoop # } declare 189 { Tcl_Channel Tcl_MakeFileChannel(void *handle, int mode) } declare 191 { Tcl_Channel Tcl_MakeTcpClientChannel(void *tcpSocket) } declare 192 { char *Tcl_Merge(Tcl_Size argc, const char *const *argv) } declare 193 { Tcl_HashEntry *Tcl_NextHashEntry(Tcl_HashSearch *searchPtr) } declare 194 { void Tcl_NotifyChannel(Tcl_Channel channel, int mask) } declare 195 { Tcl_Obj *Tcl_ObjGetVar2(Tcl_Interp *interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags) } declare 196 { Tcl_Obj *Tcl_ObjSetVar2(Tcl_Interp *interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *newValuePtr, int flags) } declare 197 { Tcl_Channel Tcl_OpenCommandChannel(Tcl_Interp *interp, Tcl_Size argc, const char **argv, int flags) } # This is obsolete, use Tcl_FSOpenFileChannel declare 198 { Tcl_Channel Tcl_OpenFileChannel(Tcl_Interp *interp, const char *fileName, const char *modeString, int permissions) } declare 199 { Tcl_Channel Tcl_OpenTcpClient(Tcl_Interp *interp, int port, const char *address, const char *myaddr, int myport, int flags) } declare 200 { Tcl_Channel Tcl_OpenTcpServer(Tcl_Interp *interp, int port, const char *host, Tcl_TcpAcceptProc *acceptProc, void *callbackData) } declare 201 { void Tcl_Preserve(void *data) } declare 202 { void Tcl_PrintDouble(Tcl_Interp *interp, double value, char *dst) } declare 203 { int Tcl_PutEnv(const char *assignment) } declare 204 { const char *Tcl_PosixError(Tcl_Interp *interp) } declare 205 { void Tcl_QueueEvent(Tcl_Event *evPtr, int position) } declare 206 { Tcl_Size Tcl_Read(Tcl_Channel chan, char *bufPtr, Tcl_Size toRead) } declare 207 { void Tcl_ReapDetachedProcs(void) } declare 208 { int Tcl_RecordAndEval(Tcl_Interp *interp, const char *cmd, int flags) } declare 209 { int Tcl_RecordAndEvalObj(Tcl_Interp *interp, Tcl_Obj *cmdPtr, int flags) } declare 210 { void Tcl_RegisterChannel(Tcl_Interp *interp, Tcl_Channel chan) } declare 211 { void Tcl_RegisterObjType(const Tcl_ObjType *typePtr) } declare 212 { Tcl_RegExp Tcl_RegExpCompile(Tcl_Interp *interp, const char *pattern) } declare 213 { int Tcl_RegExpExec(Tcl_Interp *interp, Tcl_RegExp regexp, const char *text, const char *start) } declare 214 { int Tcl_RegExpMatch(Tcl_Interp *interp, const char *text, const char *pattern) } declare 215 { void Tcl_RegExpRange(Tcl_RegExp regexp, Tcl_Size index, const char **startPtr, const char **endPtr) } declare 216 { void Tcl_Release(void *clientData) } declare 217 { void Tcl_ResetResult(Tcl_Interp *interp) } declare 218 { Tcl_Size Tcl_ScanElement(const char *src, int *flagPtr) } declare 219 { Tcl_Size Tcl_ScanCountedElement(const char *src, Tcl_Size length, int *flagPtr) } declare 221 { int Tcl_ServiceAll(void) } declare 222 { int Tcl_ServiceEvent(int flags) } declare 223 { void Tcl_SetAssocData(Tcl_Interp *interp, const char *name, Tcl_InterpDeleteProc *proc, void *clientData) } declare 224 { void Tcl_SetChannelBufferSize(Tcl_Channel chan, Tcl_Size sz) } declare 225 { int Tcl_SetChannelOption(Tcl_Interp *interp, Tcl_Channel chan, const char *optionName, const char *newValue) } declare 226 { int Tcl_SetCommandInfo(Tcl_Interp *interp, const char *cmdName, const Tcl_CmdInfo *infoPtr) } declare 227 { void Tcl_SetErrno(int err) } declare 228 { void Tcl_SetErrorCode(Tcl_Interp *interp, ...) } declare 229 { void Tcl_SetMaxBlockTime(const Tcl_Time *timePtr) } declare 231 { Tcl_Size Tcl_SetRecursionLimit(Tcl_Interp *interp, Tcl_Size depth) } declare 233 { int Tcl_SetServiceMode(int mode) } declare 234 { void Tcl_SetObjErrorCode(Tcl_Interp *interp, Tcl_Obj *errorObjPtr) } declare 235 { void Tcl_SetObjResult(Tcl_Interp *interp, Tcl_Obj *resultObjPtr) } declare 236 { void Tcl_SetStdChannel(Tcl_Channel channel, int type) } declare 238 { const char *Tcl_SetVar2(Tcl_Interp *interp, const char *part1, const char *part2, const char *newValue, int flags) } declare 239 { const char *Tcl_SignalId(int sig) } declare 240 { const char *Tcl_SignalMsg(int sig) } declare 241 { void Tcl_SourceRCFile(Tcl_Interp *interp) } declare 242 { int TclSplitList(Tcl_Interp *interp, const char *listStr, void *argcPtr, const char ***argvPtr) } # Obsolete, use Tcl_FSSplitPath declare 243 { void TclSplitPath(const char *path, void *argcPtr, const char ***argvPtr) } declare 248 { int Tcl_TraceVar2(Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *proc, void *clientData) } declare 249 { char *Tcl_TranslateFileName(Tcl_Interp *interp, const char *name, Tcl_DString *bufferPtr) } declare 250 { Tcl_Size Tcl_Ungets(Tcl_Channel chan, const char *str, Tcl_Size len, int atHead) } declare 251 { void Tcl_UnlinkVar(Tcl_Interp *interp, const char *varName) } declare 252 { int Tcl_UnregisterChannel(Tcl_Interp *interp, Tcl_Channel chan) } declare 254 { int Tcl_UnsetVar2(Tcl_Interp *interp, const char *part1, const char *part2, int flags) } declare 256 { void Tcl_UntraceVar2(Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *proc, void *clientData) } declare 257 { void Tcl_UpdateLinkedVar(Tcl_Interp *interp, const char *varName) } declare 259 { int Tcl_UpVar2(Tcl_Interp *interp, const char *frameName, const char *part1, const char *part2, const char *localName, int flags) } declare 260 { int Tcl_VarEval(Tcl_Interp *interp, ...) } declare 262 { void *Tcl_VarTraceInfo2(Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *procPtr, void *prevClientData) } declare 263 { Tcl_Size Tcl_Write(Tcl_Channel chan, const char *s, Tcl_Size slen) } declare 264 { void Tcl_WrongNumArgs(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], const char *message) } declare 265 { int Tcl_DumpActiveMemory(const char *fileName) } declare 266 { void Tcl_ValidateAllMemory(const char *file, int line) } declare 269 { char *Tcl_HashStats(Tcl_HashTable *tablePtr) } declare 270 { const char *Tcl_ParseVar(Tcl_Interp *interp, const char *start, const char **termPtr) } declare 272 { const char *Tcl_PkgPresentEx(Tcl_Interp *interp, const char *name, const char *version, int exact, void *clientDataPtr) } declare 277 { Tcl_Pid Tcl_WaitPid(Tcl_Pid pid, int *statPtr, int options) } declare 279 { void Tcl_GetVersion(int *major, int *minor, int *patchLevel, int *type) } declare 280 { void Tcl_InitMemory(Tcl_Interp *interp) } # Andreas Kupries , 03/21/1999 # "Trf-Patch for filtering channels" # # C-Level API for (un)stacking of channels. This allows the introduction # of filtering channels with relatively little changes to the core. # This patch was created in cooperation with Jan Nijtmans j.nijtmans@chello.nl # and is therefore part of his plus-patches too. # # It would have been possible to place the following definitions according # to the alphabetical order used elsewhere in this file, but I decided # against that to ease the maintenance of the patch across new tcl versions # (patch usually has no problems to integrate the patch file for the last # version into the new one). declare 281 { Tcl_Channel Tcl_StackChannel(Tcl_Interp *interp, const Tcl_ChannelType *typePtr, void *instanceData, int mask, Tcl_Channel prevChan) } declare 282 { int Tcl_UnstackChannel(Tcl_Interp *interp, Tcl_Channel chan) } declare 283 { Tcl_Channel Tcl_GetStackedChannel(Tcl_Channel chan) } # 284 was reserved, but added in 8.4a2 declare 284 { void Tcl_SetMainLoop(Tcl_MainLoopProc *proc) } declare 285 { int Tcl_GetAliasObj(Tcl_Interp *interp, const char *childCmd, Tcl_Interp **targetInterpPtr, const char **targetCmdPtr, Tcl_Size *objcPtr, Tcl_Obj ***objvPtr) } # Added in 8.1: declare 286 { void Tcl_AppendObjToObj(Tcl_Obj *objPtr, Tcl_Obj *appendObjPtr) } declare 287 { Tcl_Encoding Tcl_CreateEncoding(const Tcl_EncodingType *typePtr) } declare 288 { void Tcl_CreateThreadExitHandler(Tcl_ExitProc *proc, void *clientData) } declare 289 { void Tcl_DeleteThreadExitHandler(Tcl_ExitProc *proc, void *clientData) } declare 291 { int Tcl_EvalEx(Tcl_Interp *interp, const char *script, Tcl_Size numBytes, int flags) } declare 292 { int Tcl_EvalObjv(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags) } declare 293 { int Tcl_EvalObjEx(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags) } declare 294 { TCL_NORETURN void Tcl_ExitThread(int status) } declare 295 { int Tcl_ExternalToUtf(Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, Tcl_Size dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr) } declare 296 { char *Tcl_ExternalToUtfDString(Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, Tcl_DString *dsPtr) } declare 297 { void Tcl_FinalizeThread(void) } declare 298 { void Tcl_FinalizeNotifier(void *clientData) } declare 299 { void Tcl_FreeEncoding(Tcl_Encoding encoding) } declare 300 { Tcl_ThreadId Tcl_GetCurrentThread(void) } declare 301 { Tcl_Encoding Tcl_GetEncoding(Tcl_Interp *interp, const char *name) } declare 302 { const char *Tcl_GetEncodingName(Tcl_Encoding encoding) } declare 303 { void Tcl_GetEncodingNames(Tcl_Interp *interp) } declare 304 { int Tcl_GetIndexFromObjStruct(Tcl_Interp *interp, Tcl_Obj *objPtr, const void *tablePtr, Tcl_Size offset, const char *msg, int flags, void *indexPtr) } declare 305 { void *Tcl_GetThreadData(Tcl_ThreadDataKey *keyPtr, Tcl_Size size) } declare 306 { Tcl_Obj *Tcl_GetVar2Ex(Tcl_Interp *interp, const char *part1, const char *part2, int flags) } declare 307 { void *Tcl_InitNotifier(void) } declare 308 { void Tcl_MutexLock(Tcl_Mutex *mutexPtr) } declare 309 { void Tcl_MutexUnlock(Tcl_Mutex *mutexPtr) } declare 310 { void Tcl_ConditionNotify(Tcl_Condition *condPtr) } declare 311 { void Tcl_ConditionWait(Tcl_Condition *condPtr, Tcl_Mutex *mutexPtr, const Tcl_Time *timePtr) } declare 312 { Tcl_Size TclNumUtfChars(const char *src, Tcl_Size length) } declare 313 { Tcl_Size Tcl_ReadChars(Tcl_Channel channel, Tcl_Obj *objPtr, Tcl_Size charsToRead, int appendFlag) } declare 316 { int Tcl_SetSystemEncoding(Tcl_Interp *interp, const char *name) } declare 317 { Tcl_Obj *Tcl_SetVar2Ex(Tcl_Interp *interp, const char *part1, const char *part2, Tcl_Obj *newValuePtr, int flags) } declare 318 { void Tcl_ThreadAlert(Tcl_ThreadId threadId) } declare 319 { void Tcl_ThreadQueueEvent(Tcl_ThreadId threadId, Tcl_Event *evPtr, int position) } declare 320 { int Tcl_UniCharAtIndex(const char *src, Tcl_Size index) } declare 321 { int Tcl_UniCharToLower(int ch) } declare 322 { int Tcl_UniCharToTitle(int ch) } declare 323 { int Tcl_UniCharToUpper(int ch) } declare 324 { Tcl_Size Tcl_UniCharToUtf(int ch, char *buf) } declare 325 { const char *TclUtfAtIndex(const char *src, Tcl_Size index) } declare 326 { int TclUtfCharComplete(const char *src, Tcl_Size length) } declare 327 { Tcl_Size Tcl_UtfBackslash(const char *src, int *readPtr, char *dst) } declare 328 { const char *Tcl_UtfFindFirst(const char *src, int ch) } declare 329 { const char *Tcl_UtfFindLast(const char *src, int ch) } declare 330 { const char *TclUtfNext(const char *src) } declare 331 { const char *TclUtfPrev(const char *src, const char *start) } declare 332 { int Tcl_UtfToExternal(Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, Tcl_Size dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr) } declare 333 { char *Tcl_UtfToExternalDString(Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, Tcl_DString *dsPtr) } declare 334 { Tcl_Size Tcl_UtfToLower(char *src) } declare 335 { Tcl_Size Tcl_UtfToTitle(char *src) } declare 336 { Tcl_Size Tcl_UtfToChar16(const char *src, unsigned short *chPtr) } declare 337 { Tcl_Size Tcl_UtfToUpper(char *src) } declare 338 { Tcl_Size Tcl_WriteChars(Tcl_Channel chan, const char *src, Tcl_Size srcLen) } declare 339 { Tcl_Size Tcl_WriteObj(Tcl_Channel chan, Tcl_Obj *objPtr) } declare 340 { char *Tcl_GetString(Tcl_Obj *objPtr) } declare 343 { void Tcl_AlertNotifier(void *clientData) } declare 344 { void Tcl_ServiceModeHook(int mode) } declare 345 { int Tcl_UniCharIsAlnum(int ch) } declare 346 { int Tcl_UniCharIsAlpha(int ch) } declare 347 { int Tcl_UniCharIsDigit(int ch) } declare 348 { int Tcl_UniCharIsLower(int ch) } declare 349 { int Tcl_UniCharIsSpace(int ch) } declare 350 { int Tcl_UniCharIsUpper(int ch) } declare 351 { int Tcl_UniCharIsWordChar(int ch) } declare 352 { Tcl_Size Tcl_Char16Len(const unsigned short *uniStr) } declare 354 { char *Tcl_Char16ToUtfDString(const unsigned short *uniStr, Tcl_Size uniLength, Tcl_DString *dsPtr) } declare 355 { unsigned short *Tcl_UtfToChar16DString(const char *src, Tcl_Size length, Tcl_DString *dsPtr) } declare 356 { Tcl_RegExp Tcl_GetRegExpFromObj(Tcl_Interp *interp, Tcl_Obj *patObj, int flags) } declare 358 { void Tcl_FreeParse(Tcl_Parse *parsePtr) } declare 359 { void Tcl_LogCommandInfo(Tcl_Interp *interp, const char *script, const char *command, Tcl_Size length) } declare 360 { int Tcl_ParseBraces(Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr, int append, const char **termPtr) } declare 361 { int Tcl_ParseCommand(Tcl_Interp *interp, const char *start, Tcl_Size numBytes, int nested, Tcl_Parse *parsePtr) } declare 362 { int Tcl_ParseExpr(Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr) } declare 363 { int Tcl_ParseQuotedString(Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr, int append, const char **termPtr) } declare 364 { int Tcl_ParseVarName(Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr, int append) } # These 4 functions are obsolete, use Tcl_FSGetCwd, Tcl_FSChdir, # Tcl_FSAccess and Tcl_FSStat declare 365 { char *Tcl_GetCwd(Tcl_Interp *interp, Tcl_DString *cwdPtr) } declare 366 { int Tcl_Chdir(const char *dirName) } declare 367 { int Tcl_Access(const char *path, int mode) } declare 368 { int Tcl_Stat(const char *path, struct stat *bufPtr) } declare 369 { int TclUtfNcmp(const char *s1, const char *s2, size_t n) } declare 370 { int TclUtfNcasecmp(const char *s1, const char *s2, size_t n) } declare 371 { int Tcl_StringCaseMatch(const char *str, const char *pattern, int nocase) } declare 372 { int Tcl_UniCharIsControl(int ch) } declare 373 { int Tcl_UniCharIsGraph(int ch) } declare 374 { int Tcl_UniCharIsPrint(int ch) } declare 375 { int Tcl_UniCharIsPunct(int ch) } declare 376 { int Tcl_RegExpExecObj(Tcl_Interp *interp, Tcl_RegExp regexp, Tcl_Obj *textObj, Tcl_Size offset, Tcl_Size nmatches, int flags) } declare 377 { void Tcl_RegExpGetInfo(Tcl_RegExp regexp, Tcl_RegExpInfo *infoPtr) } declare 378 { Tcl_Obj *Tcl_NewUnicodeObj(const Tcl_UniChar *unicode, Tcl_Size numChars) } declare 379 { void Tcl_SetUnicodeObj(Tcl_Obj *objPtr, const Tcl_UniChar *unicode, Tcl_Size numChars) } declare 380 { Tcl_Size TclGetCharLength(Tcl_Obj *objPtr) } declare 381 { int TclGetUniChar(Tcl_Obj *objPtr, Tcl_Size index) } declare 383 { Tcl_Obj *TclGetRange(Tcl_Obj *objPtr, Tcl_Size first, Tcl_Size last) } declare 384 { void Tcl_AppendUnicodeToObj(Tcl_Obj *objPtr, const Tcl_UniChar *unicode, Tcl_Size length) } declare 385 { int Tcl_RegExpMatchObj(Tcl_Interp *interp, Tcl_Obj *textObj, Tcl_Obj *patternObj) } declare 386 { void Tcl_SetNotifier(const Tcl_NotifierProcs *notifierProcPtr) } declare 387 { Tcl_Mutex *Tcl_GetAllocMutex(void) } declare 388 { int Tcl_GetChannelNames(Tcl_Interp *interp) } declare 389 { int Tcl_GetChannelNamesEx(Tcl_Interp *interp, const char *pattern) } declare 390 { int Tcl_ProcObjCmd(void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[]) } declare 391 { void Tcl_ConditionFinalize(Tcl_Condition *condPtr) } declare 392 { void Tcl_MutexFinalize(Tcl_Mutex *mutex) } declare 393 { int Tcl_CreateThread(Tcl_ThreadId *idPtr, Tcl_ThreadCreateProc *proc, void *clientData, TCL_HASH_TYPE stackSize, int flags) } # Introduced in 8.3.2 declare 394 { Tcl_Size Tcl_ReadRaw(Tcl_Channel chan, char *dst, Tcl_Size bytesToRead) } declare 395 { Tcl_Size Tcl_WriteRaw(Tcl_Channel chan, const char *src, Tcl_Size srcLen) } declare 396 { Tcl_Channel Tcl_GetTopChannel(Tcl_Channel chan) } declare 397 { int Tcl_ChannelBuffered(Tcl_Channel chan) } declare 398 { const char *Tcl_ChannelName(const Tcl_ChannelType *chanTypePtr) } declare 399 { Tcl_ChannelTypeVersion Tcl_ChannelVersion( const Tcl_ChannelType *chanTypePtr) } declare 400 { Tcl_DriverBlockModeProc *Tcl_ChannelBlockModeProc( const Tcl_ChannelType *chanTypePtr) } declare 402 { Tcl_DriverClose2Proc *Tcl_ChannelClose2Proc( const Tcl_ChannelType *chanTypePtr) } declare 403 { Tcl_DriverInputProc *Tcl_ChannelInputProc( const Tcl_ChannelType *chanTypePtr) } declare 404 { Tcl_DriverOutputProc *Tcl_ChannelOutputProc( const Tcl_ChannelType *chanTypePtr) } declare 406 { Tcl_DriverSetOptionProc *Tcl_ChannelSetOptionProc( const Tcl_ChannelType *chanTypePtr) } declare 407 { Tcl_DriverGetOptionProc *Tcl_ChannelGetOptionProc( const Tcl_ChannelType *chanTypePtr) } declare 408 { Tcl_DriverWatchProc *Tcl_ChannelWatchProc( const Tcl_ChannelType *chanTypePtr) } declare 409 { Tcl_DriverGetHandleProc *Tcl_ChannelGetHandleProc( const Tcl_ChannelType *chanTypePtr) } declare 410 { Tcl_DriverFlushProc *Tcl_ChannelFlushProc( const Tcl_ChannelType *chanTypePtr) } declare 411 { Tcl_DriverHandlerProc *Tcl_ChannelHandlerProc( const Tcl_ChannelType *chanTypePtr) } # Introduced in 8.4a2 declare 412 { int Tcl_JoinThread(Tcl_ThreadId threadId, int *result) } declare 413 { int Tcl_IsChannelShared(Tcl_Channel channel) } declare 414 { int Tcl_IsChannelRegistered(Tcl_Interp *interp, Tcl_Channel channel) } declare 415 { void Tcl_CutChannel(Tcl_Channel channel) } declare 416 { void Tcl_SpliceChannel(Tcl_Channel channel) } declare 417 { void Tcl_ClearChannelHandlers(Tcl_Channel channel) } declare 418 { int Tcl_IsChannelExisting(const char *channelName) } declare 423 { void Tcl_InitCustomHashTable(Tcl_HashTable *tablePtr, int keyType, const Tcl_HashKeyType *typePtr) } declare 424 { void Tcl_InitObjHashTable(Tcl_HashTable *tablePtr) } declare 425 { void *Tcl_CommandTraceInfo(Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *procPtr, void *prevClientData) } declare 426 { int Tcl_TraceCommand(Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *proc, void *clientData) } declare 427 { void Tcl_UntraceCommand(Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *proc, void *clientData) } declare 428 { void *Tcl_AttemptAlloc(TCL_HASH_TYPE size) } declare 429 { void *Tcl_AttemptDbCkalloc(TCL_HASH_TYPE size, const char *file, int line) } declare 430 { void *Tcl_AttemptRealloc(void *ptr, TCL_HASH_TYPE size) } declare 431 { void *Tcl_AttemptDbCkrealloc(void *ptr, TCL_HASH_TYPE size, const char *file, int line) } declare 432 { int Tcl_AttemptSetObjLength(Tcl_Obj *objPtr, Tcl_Size length) } # TIP#10 (thread-aware channels) akupries declare 433 { Tcl_ThreadId Tcl_GetChannelThread(Tcl_Channel channel) } # introduced in 8.4a3 declare 434 { Tcl_UniChar *TclGetUnicodeFromObj(Tcl_Obj *objPtr, void *lengthPtr) } # TIP#36 (better access to 'subst') dkf declare 437 { Tcl_Obj *Tcl_SubstObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags) } # TIP#17 (virtual filesystem layer) vdarley declare 438 { int Tcl_DetachChannel(Tcl_Interp *interp, Tcl_Channel channel) } declare 439 { int Tcl_IsStandardChannel(Tcl_Channel channel) } declare 440 { int Tcl_FSCopyFile(Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr) } declare 441 { int Tcl_FSCopyDirectory(Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr) } declare 442 { int Tcl_FSCreateDirectory(Tcl_Obj *pathPtr) } declare 443 { int Tcl_FSDeleteFile(Tcl_Obj *pathPtr) } declare 444 { int Tcl_FSLoadFile(Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *sym1, const char *sym2, Tcl_LibraryInitProc **proc1Ptr, Tcl_LibraryInitProc **proc2Ptr, Tcl_LoadHandle *handlePtr, Tcl_FSUnloadFileProc **unloadProcPtr) } declare 445 { int Tcl_FSMatchInDirectory(Tcl_Interp *interp, Tcl_Obj *result, Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types) } declare 446 { Tcl_Obj *Tcl_FSLink(Tcl_Obj *pathPtr, Tcl_Obj *toPtr, int linkAction) } declare 447 { int Tcl_FSRemoveDirectory(Tcl_Obj *pathPtr, int recursive, Tcl_Obj **errorPtr) } declare 448 { int Tcl_FSRenameFile(Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr) } declare 449 { int Tcl_FSLstat(Tcl_Obj *pathPtr, Tcl_StatBuf *buf) } declare 450 { int Tcl_FSUtime(Tcl_Obj *pathPtr, struct utimbuf *tval) } declare 451 { int Tcl_FSFileAttrsGet(Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef) } declare 452 { int Tcl_FSFileAttrsSet(Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj *objPtr) } declare 453 { const char *const *Tcl_FSFileAttrStrings(Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef) } declare 454 { int Tcl_FSStat(Tcl_Obj *pathPtr, Tcl_StatBuf *buf) } declare 455 { int Tcl_FSAccess(Tcl_Obj *pathPtr, int mode) } declare 456 { Tcl_Channel Tcl_FSOpenFileChannel(Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *modeString, int permissions) } declare 457 { Tcl_Obj *Tcl_FSGetCwd(Tcl_Interp *interp) } declare 458 { int Tcl_FSChdir(Tcl_Obj *pathPtr) } declare 459 { int Tcl_FSConvertToPathType(Tcl_Interp *interp, Tcl_Obj *pathPtr) } declare 460 { Tcl_Obj *Tcl_FSJoinPath(Tcl_Obj *listObj, Tcl_Size elements) } declare 461 { Tcl_Obj *TclFSSplitPath(Tcl_Obj *pathPtr, void *lenPtr) } declare 462 { int Tcl_FSEqualPaths(Tcl_Obj *firstPtr, Tcl_Obj *secondPtr) } declare 463 { Tcl_Obj *Tcl_FSGetNormalizedPath(Tcl_Interp *interp, Tcl_Obj *pathPtr) } declare 464 { Tcl_Obj *Tcl_FSJoinToPath(Tcl_Obj *pathPtr, Tcl_Size objc, Tcl_Obj *const objv[]) } declare 465 { void *Tcl_FSGetInternalRep(Tcl_Obj *pathPtr, const Tcl_Filesystem *fsPtr) } declare 466 { Tcl_Obj *Tcl_FSGetTranslatedPath(Tcl_Interp *interp, Tcl_Obj *pathPtr) } declare 467 { int Tcl_FSEvalFile(Tcl_Interp *interp, Tcl_Obj *fileName) } declare 468 { Tcl_Obj *Tcl_FSNewNativePath(const Tcl_Filesystem *fromFilesystem, void *clientData) } declare 469 { const void *Tcl_FSGetNativePath(Tcl_Obj *pathPtr) } declare 470 { Tcl_Obj *Tcl_FSFileSystemInfo(Tcl_Obj *pathPtr) } declare 471 { Tcl_Obj *Tcl_FSPathSeparator(Tcl_Obj *pathPtr) } declare 472 { Tcl_Obj *Tcl_FSListVolumes(void) } declare 473 { int Tcl_FSRegister(void *clientData, const Tcl_Filesystem *fsPtr) } declare 474 { int Tcl_FSUnregister(const Tcl_Filesystem *fsPtr) } declare 475 { void *Tcl_FSData(const Tcl_Filesystem *fsPtr) } declare 476 { const char *Tcl_FSGetTranslatedStringPath(Tcl_Interp *interp, Tcl_Obj *pathPtr) } declare 477 { const Tcl_Filesystem *Tcl_FSGetFileSystemForPath(Tcl_Obj *pathPtr) } declare 478 { Tcl_PathType Tcl_FSGetPathType(Tcl_Obj *pathPtr) } # TIP#49 (detection of output buffering) akupries declare 479 { int Tcl_OutputBuffered(Tcl_Channel chan) } declare 480 { void Tcl_FSMountsChanged(const Tcl_Filesystem *fsPtr) } # TIP#56 (evaluate a parsed script) msofer declare 481 { int Tcl_EvalTokensStandard(Tcl_Interp *interp, Tcl_Token *tokenPtr, Tcl_Size count) } # TIP#73 (access to current time) kbk declare 482 { void Tcl_GetTime(Tcl_Time *timeBuf) } # TIP#32 (object-enabled traces) kbk declare 483 { Tcl_Trace Tcl_CreateObjTrace(Tcl_Interp *interp, Tcl_Size level, int flags, Tcl_CmdObjTraceProc *objProc, void *clientData, Tcl_CmdObjTraceDeleteProc *delProc) } declare 484 { int Tcl_GetCommandInfoFromToken(Tcl_Command token, Tcl_CmdInfo *infoPtr) } declare 485 { int Tcl_SetCommandInfoFromToken(Tcl_Command token, const Tcl_CmdInfo *infoPtr) } ### New functions on 64-bit dev branch ### # TIP#72 (64-bit values) dkf declare 486 { Tcl_Obj *Tcl_DbNewWideIntObj(Tcl_WideInt wideValue, const char *file, int line) } declare 487 { int Tcl_GetWideIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_WideInt *widePtr) } declare 488 { Tcl_Obj *Tcl_NewWideIntObj(Tcl_WideInt wideValue) } declare 489 { void Tcl_SetWideIntObj(Tcl_Obj *objPtr, Tcl_WideInt wideValue) } declare 490 { Tcl_StatBuf *Tcl_AllocStatBuf(void) } declare 491 { long long Tcl_Seek(Tcl_Channel chan, long long offset, int mode) } declare 492 { long long Tcl_Tell(Tcl_Channel chan) } # TIP#91 (back-compat enhancements for channels) dkf declare 493 { Tcl_DriverWideSeekProc *Tcl_ChannelWideSeekProc( const Tcl_ChannelType *chanTypePtr) } # ----- BASELINE -- FOR -- 8.4.0 ----- # # TIP#111 (dictionaries) dkf declare 494 { int Tcl_DictObjPut(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr, Tcl_Obj *valuePtr) } declare 495 { int Tcl_DictObjGet(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr, Tcl_Obj **valuePtrPtr) } declare 496 { int Tcl_DictObjRemove(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr) } declare 497 { int TclDictObjSize(Tcl_Interp *interp, Tcl_Obj *dictPtr, void *sizePtr) } declare 498 { int Tcl_DictObjFirst(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_DictSearch *searchPtr, Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr, int *donePtr) } declare 499 { void Tcl_DictObjNext(Tcl_DictSearch *searchPtr, Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr, int *donePtr) } declare 500 { void Tcl_DictObjDone(Tcl_DictSearch *searchPtr) } declare 501 { int Tcl_DictObjPutKeyList(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size keyc, Tcl_Obj *const *keyv, Tcl_Obj *valuePtr) } declare 502 { int Tcl_DictObjRemoveKeyList(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size keyc, Tcl_Obj *const *keyv) } declare 503 { Tcl_Obj *Tcl_NewDictObj(void) } declare 504 { Tcl_Obj *Tcl_DbNewDictObj(const char *file, int line) } # TIP#59 (configuration reporting) akupries declare 505 { void Tcl_RegisterConfig(Tcl_Interp *interp, const char *pkgName, const Tcl_Config *configuration, const char *valEncoding) } # TIP #139 (partial exposure of namespace API - transferred from tclInt.decls) # dkf, API by Brent Welch? declare 506 { Tcl_Namespace *Tcl_CreateNamespace(Tcl_Interp *interp, const char *name, void *clientData, Tcl_NamespaceDeleteProc *deleteProc) } declare 507 { void Tcl_DeleteNamespace(Tcl_Namespace *nsPtr) } declare 508 { int Tcl_AppendExportList(Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *objPtr) } declare 509 { int Tcl_Export(Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern, int resetListFirst) } declare 510 { int Tcl_Import(Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern, int allowOverwrite) } declare 511 { int Tcl_ForgetImport(Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern) } declare 512 { Tcl_Namespace *Tcl_GetCurrentNamespace(Tcl_Interp *interp) } declare 513 { Tcl_Namespace *Tcl_GetGlobalNamespace(Tcl_Interp *interp) } declare 514 { Tcl_Namespace *Tcl_FindNamespace(Tcl_Interp *interp, const char *name, Tcl_Namespace *contextNsPtr, int flags) } declare 515 { Tcl_Command Tcl_FindCommand(Tcl_Interp *interp, const char *name, Tcl_Namespace *contextNsPtr, int flags) } declare 516 { Tcl_Command Tcl_GetCommandFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr) } declare 517 { void Tcl_GetCommandFullName(Tcl_Interp *interp, Tcl_Command command, Tcl_Obj *objPtr) } # TIP#137 (encoding-aware source command) dgp for Anton Kovalenko declare 518 { int Tcl_FSEvalFileEx(Tcl_Interp *interp, Tcl_Obj *fileName, const char *encodingName) } # TIP#143 (resource limits) dkf declare 520 { void Tcl_LimitAddHandler(Tcl_Interp *interp, int type, Tcl_LimitHandlerProc *handlerProc, void *clientData, Tcl_LimitHandlerDeleteProc *deleteProc) } declare 521 { void Tcl_LimitRemoveHandler(Tcl_Interp *interp, int type, Tcl_LimitHandlerProc *handlerProc, void *clientData) } declare 522 { int Tcl_LimitReady(Tcl_Interp *interp) } declare 523 { int Tcl_LimitCheck(Tcl_Interp *interp) } declare 524 { int Tcl_LimitExceeded(Tcl_Interp *interp) } declare 525 { void Tcl_LimitSetCommands(Tcl_Interp *interp, Tcl_Size commandLimit) } declare 526 { void Tcl_LimitSetTime(Tcl_Interp *interp, Tcl_Time *timeLimitPtr) } declare 527 { void Tcl_LimitSetGranularity(Tcl_Interp *interp, int type, int granularity) } declare 528 { int Tcl_LimitTypeEnabled(Tcl_Interp *interp, int type) } declare 529 { int Tcl_LimitTypeExceeded(Tcl_Interp *interp, int type) } declare 530 { void Tcl_LimitTypeSet(Tcl_Interp *interp, int type) } declare 531 { void Tcl_LimitTypeReset(Tcl_Interp *interp, int type) } declare 532 { int Tcl_LimitGetCommands(Tcl_Interp *interp) } declare 533 { void Tcl_LimitGetTime(Tcl_Interp *interp, Tcl_Time *timeLimitPtr) } declare 534 { int Tcl_LimitGetGranularity(Tcl_Interp *interp, int type) } # TIP#226 (interpreter result state management) dgp declare 535 { Tcl_InterpState Tcl_SaveInterpState(Tcl_Interp *interp, int status) } declare 536 { int Tcl_RestoreInterpState(Tcl_Interp *interp, Tcl_InterpState state) } declare 537 { void Tcl_DiscardInterpState(Tcl_InterpState state) } # TIP#227 (return options interface) dgp declare 538 { int Tcl_SetReturnOptions(Tcl_Interp *interp, Tcl_Obj *options) } declare 539 { Tcl_Obj *Tcl_GetReturnOptions(Tcl_Interp *interp, int result) } # TIP#235 (ensembles) dkf declare 540 { int Tcl_IsEnsemble(Tcl_Command token) } declare 541 { Tcl_Command Tcl_CreateEnsemble(Tcl_Interp *interp, const char *name, Tcl_Namespace *namespacePtr, int flags) } declare 542 { Tcl_Command Tcl_FindEnsemble(Tcl_Interp *interp, Tcl_Obj *cmdNameObj, int flags) } declare 543 { int Tcl_SetEnsembleSubcommandList(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *subcmdList) } declare 544 { int Tcl_SetEnsembleMappingDict(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *mapDict) } declare 545 { int Tcl_SetEnsembleUnknownHandler(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *unknownList) } declare 546 { int Tcl_SetEnsembleFlags(Tcl_Interp *interp, Tcl_Command token, int flags) } declare 547 { int Tcl_GetEnsembleSubcommandList(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **subcmdListPtr) } declare 548 { int Tcl_GetEnsembleMappingDict(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **mapDictPtr) } declare 549 { int Tcl_GetEnsembleUnknownHandler(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **unknownListPtr) } declare 550 { int Tcl_GetEnsembleFlags(Tcl_Interp *interp, Tcl_Command token, int *flagsPtr) } declare 551 { int Tcl_GetEnsembleNamespace(Tcl_Interp *interp, Tcl_Command token, Tcl_Namespace **namespacePtrPtr) } # TIP#233 (virtualized time) akupries declare 552 { void Tcl_SetTimeProc(Tcl_GetTimeProc *getProc, Tcl_ScaleTimeProc *scaleProc, void *clientData) } declare 553 { void Tcl_QueryTimeProc(Tcl_GetTimeProc **getProc, Tcl_ScaleTimeProc **scaleProc, void **clientData) } # TIP#218 (driver thread actions) davygrvy/akupries ChannelType ver 4 declare 554 { Tcl_DriverThreadActionProc *Tcl_ChannelThreadActionProc( const Tcl_ChannelType *chanTypePtr) } # TIP#237 (arbitrary-precision integers) kbk declare 555 { Tcl_Obj *Tcl_NewBignumObj(void *value) } declare 556 { Tcl_Obj *Tcl_DbNewBignumObj(void *value, const char *file, int line) } declare 557 { void Tcl_SetBignumObj(Tcl_Obj *obj, void *value) } declare 558 { int Tcl_GetBignumFromObj(Tcl_Interp *interp, Tcl_Obj *obj, void *value) } declare 559 { int Tcl_TakeBignumFromObj(Tcl_Interp *interp, Tcl_Obj *obj, void *value) } # TIP #208 ('chan' command) jeffh declare 560 { int Tcl_TruncateChannel(Tcl_Channel chan, long long length) } declare 561 { Tcl_DriverTruncateProc *Tcl_ChannelTruncateProc( const Tcl_ChannelType *chanTypePtr) } # TIP#219 (channel reflection api) akupries declare 562 { void Tcl_SetChannelErrorInterp(Tcl_Interp *interp, Tcl_Obj *msg) } declare 563 { void Tcl_GetChannelErrorInterp(Tcl_Interp *interp, Tcl_Obj **msg) } declare 564 { void Tcl_SetChannelError(Tcl_Channel chan, Tcl_Obj *msg) } declare 565 { void Tcl_GetChannelError(Tcl_Channel chan, Tcl_Obj **msg) } # TIP #237 (additional conversion functions for bignum support) kbk/dgp declare 566 { int Tcl_InitBignumFromDouble(Tcl_Interp *interp, double initval, void *toInit) } # TIP#181 (namespace unknown command) dgp for Neil Madden declare 567 { Tcl_Obj *Tcl_GetNamespaceUnknownHandler(Tcl_Interp *interp, Tcl_Namespace *nsPtr) } declare 568 { int Tcl_SetNamespaceUnknownHandler(Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *handlerPtr) } # TIP#258 (enhanced interface for encodings) dgp declare 569 { int Tcl_GetEncodingFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Encoding *encodingPtr) } declare 570 { Tcl_Obj *Tcl_GetEncodingSearchPath(void) } declare 571 { int Tcl_SetEncodingSearchPath(Tcl_Obj *searchPath) } declare 572 { const char *Tcl_GetEncodingNameFromEnvironment(Tcl_DString *bufPtr) } # TIP#268 (extended version numbers and requirements) akupries declare 573 { int Tcl_PkgRequireProc(Tcl_Interp *interp, const char *name, Tcl_Size objc, Tcl_Obj *const objv[], void *clientDataPtr) } # TIP#270 (utility C routines for string formatting) dgp declare 574 { void Tcl_AppendObjToErrorInfo(Tcl_Interp *interp, Tcl_Obj *objPtr) } declare 575 { void Tcl_AppendLimitedToObj(Tcl_Obj *objPtr, const char *bytes, Tcl_Size length, Tcl_Size limit, const char *ellipsis) } declare 576 { Tcl_Obj *Tcl_Format(Tcl_Interp *interp, const char *format, Tcl_Size objc, Tcl_Obj *const objv[]) } declare 577 { int Tcl_AppendFormatToObj(Tcl_Interp *interp, Tcl_Obj *objPtr, const char *format, Tcl_Size objc, Tcl_Obj *const objv[]) } declare 578 { Tcl_Obj *Tcl_ObjPrintf(const char *format, ...) } declare 579 { void Tcl_AppendPrintfToObj(Tcl_Obj *objPtr, const char *format, ...) } # ----- BASELINE -- FOR -- 8.5.0 ----- # # TIP #285 (script cancellation support) jmistachkin declare 580 { int Tcl_CancelEval(Tcl_Interp *interp, Tcl_Obj *resultObjPtr, void *clientData, int flags) } declare 581 { int Tcl_Canceled(Tcl_Interp *interp, int flags) } # TIP#304 (chan pipe) aferrieux declare 582 { int Tcl_CreatePipe(Tcl_Interp *interp, Tcl_Channel *rchan, Tcl_Channel *wchan, int flags) } # TIP #322 (NRE public interface) msofer declare 583 { Tcl_Command Tcl_NRCreateCommand(Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc *proc, Tcl_ObjCmdProc *nreProc, void *clientData, Tcl_CmdDeleteProc *deleteProc) } declare 584 { int Tcl_NREvalObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags) } declare 585 { int Tcl_NREvalObjv(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags) } declare 586 { int Tcl_NRCmdSwap(Tcl_Interp *interp, Tcl_Command cmd, Tcl_Size objc, Tcl_Obj *const objv[], int flags) } declare 587 { void Tcl_NRAddCallback(Tcl_Interp *interp, Tcl_NRPostProc *postProcPtr, void *data0, void *data1, void *data2, void *data3) } # For use by NR extenders, to have a simple way to also provide a (required!) # classic objProc declare 588 { int Tcl_NRCallObjProc(Tcl_Interp *interp, Tcl_ObjCmdProc *objProc, void *clientData, Tcl_Size objc, Tcl_Obj *const objv[]) } # TIP#316 (Tcl_StatBuf reader functions) dkf declare 589 { unsigned Tcl_GetFSDeviceFromStat(const Tcl_StatBuf *statPtr) } declare 590 { unsigned Tcl_GetFSInodeFromStat(const Tcl_StatBuf *statPtr) } declare 591 { unsigned Tcl_GetModeFromStat(const Tcl_StatBuf *statPtr) } declare 592 { int Tcl_GetLinkCountFromStat(const Tcl_StatBuf *statPtr) } declare 593 { int Tcl_GetUserIdFromStat(const Tcl_StatBuf *statPtr) } declare 594 { int Tcl_GetGroupIdFromStat(const Tcl_StatBuf *statPtr) } declare 595 { int Tcl_GetDeviceTypeFromStat(const Tcl_StatBuf *statPtr) } declare 596 { long long Tcl_GetAccessTimeFromStat(const Tcl_StatBuf *statPtr) } declare 597 { long long Tcl_GetModificationTimeFromStat(const Tcl_StatBuf *statPtr) } declare 598 { long long Tcl_GetChangeTimeFromStat(const Tcl_StatBuf *statPtr) } declare 599 { unsigned long long Tcl_GetSizeFromStat(const Tcl_StatBuf *statPtr) } declare 600 { unsigned long long Tcl_GetBlocksFromStat(const Tcl_StatBuf *statPtr) } declare 601 { unsigned Tcl_GetBlockSizeFromStat(const Tcl_StatBuf *statPtr) } # TIP#314 (ensembles with parameters) dkf for Lars Hellstr"om declare 602 { int Tcl_SetEnsembleParameterList(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *paramList) } declare 603 { int Tcl_GetEnsembleParameterList(Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **paramListPtr) } # TIP#265 (option parser) dkf for Sam Bromley declare 604 { int TclParseArgsObjv(Tcl_Interp *interp, const Tcl_ArgvInfo *argTable, void *objcPtr, Tcl_Obj *const *objv, Tcl_Obj ***remObjv) } # TIP#336 (manipulate the error line) dgp declare 605 { int Tcl_GetErrorLine(Tcl_Interp *interp) } declare 606 { void Tcl_SetErrorLine(Tcl_Interp *interp, int lineNum) } # TIP#307 (move results between interpreters) dkf declare 607 { void Tcl_TransferResult(Tcl_Interp *sourceInterp, int code, Tcl_Interp *targetInterp) } # TIP#335 (detect if interpreter in use) jmistachkin declare 608 { int Tcl_InterpActive(Tcl_Interp *interp) } # TIP#337 (log exception for background processing) dgp declare 609 { void Tcl_BackgroundException(Tcl_Interp *interp, int code) } # TIP#234 (zlib interface) dkf/Pascal Scheffers declare 610 { int Tcl_ZlibDeflate(Tcl_Interp *interp, int format, Tcl_Obj *data, int level, Tcl_Obj *gzipHeaderDictObj) } declare 611 { int Tcl_ZlibInflate(Tcl_Interp *interp, int format, Tcl_Obj *data, Tcl_Size buffersize, Tcl_Obj *gzipHeaderDictObj) } declare 612 { unsigned int Tcl_ZlibCRC32(unsigned int crc, const unsigned char *buf, Tcl_Size len) } declare 613 { unsigned int Tcl_ZlibAdler32(unsigned int adler, const unsigned char *buf, Tcl_Size len) } declare 614 { int Tcl_ZlibStreamInit(Tcl_Interp *interp, int mode, int format, int level, Tcl_Obj *dictObj, Tcl_ZlibStream *zshandle) } declare 615 { Tcl_Obj *Tcl_ZlibStreamGetCommandName(Tcl_ZlibStream zshandle) } declare 616 { int Tcl_ZlibStreamEof(Tcl_ZlibStream zshandle) } declare 617 { int Tcl_ZlibStreamChecksum(Tcl_ZlibStream zshandle) } declare 618 { int Tcl_ZlibStreamPut(Tcl_ZlibStream zshandle, Tcl_Obj *data, int flush) } declare 619 { int Tcl_ZlibStreamGet(Tcl_ZlibStream zshandle, Tcl_Obj *data, Tcl_Size count) } declare 620 { int Tcl_ZlibStreamClose(Tcl_ZlibStream zshandle) } declare 621 { int Tcl_ZlibStreamReset(Tcl_ZlibStream zshandle) } # TIP 338 (control over startup script) dgp declare 622 { void Tcl_SetStartupScript(Tcl_Obj *path, const char *encoding) } declare 623 { Tcl_Obj *Tcl_GetStartupScript(const char **encodingPtr) } # TIP#332 (half-close made public) aferrieux declare 624 { int Tcl_CloseEx(Tcl_Interp *interp, Tcl_Channel chan, int flags) } # TIP #353 (NR-enabled expressions) dgp declare 625 { int Tcl_NRExprObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj *resultPtr) } # TIP #356 (NR-enabled substitution) dgp declare 626 { int Tcl_NRSubstObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags) } # TIP #357 (Export TclLoadFile and TclpFindSymbol) kbk declare 627 { int Tcl_LoadFile(Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *const symv[], int flags, void *procPtrs, Tcl_LoadHandle *handlePtr) } declare 628 { void *Tcl_FindSymbol(Tcl_Interp *interp, Tcl_LoadHandle handle, const char *symbol) } declare 629 { int Tcl_FSUnloadFile(Tcl_Interp *interp, Tcl_LoadHandle handlePtr) } # TIP #400 declare 630 { void Tcl_ZlibStreamSetCompressionDictionary(Tcl_ZlibStream zhandle, Tcl_Obj *compressionDictionaryObj) } # ----- BASELINE -- FOR -- 8.6.0 ----- # # TIP #456/#468 declare 631 { Tcl_Channel Tcl_OpenTcpServerEx(Tcl_Interp *interp, const char *service, const char *host, unsigned int flags, int backlog, Tcl_TcpAcceptProc *acceptProc, void *callbackData) } # TIP #430 declare 632 { int TclZipfs_Mount(Tcl_Interp *interp, const char *zipname, const char *mountPoint, const char *passwd) } declare 633 { int TclZipfs_Unmount(Tcl_Interp *interp, const char *mountPoint) } declare 634 { Tcl_Obj *TclZipfs_TclLibrary(void) } declare 635 { int TclZipfs_MountBuffer(Tcl_Interp *interp, const void *data, size_t datalen, const char *mountPoint, int copy) } # TIP #445 declare 636 { void Tcl_FreeInternalRep(Tcl_Obj *objPtr) } declare 637 { char *Tcl_InitStringRep(Tcl_Obj *objPtr, const char *bytes, TCL_HASH_TYPE numBytes) } declare 638 { Tcl_ObjInternalRep *Tcl_FetchInternalRep(Tcl_Obj *objPtr, const Tcl_ObjType *typePtr) } declare 639 { void Tcl_StoreInternalRep(Tcl_Obj *objPtr, const Tcl_ObjType *typePtr, const Tcl_ObjInternalRep *irPtr) } declare 640 { int Tcl_HasStringRep(Tcl_Obj *objPtr) } # TIP #506 declare 641 { void Tcl_IncrRefCount(Tcl_Obj *objPtr) } declare 642 { void Tcl_DecrRefCount(Tcl_Obj *objPtr) } declare 643 { int Tcl_IsShared(Tcl_Obj *objPtr) } # TIP#312 New Tcl_LinkArray() function declare 644 { int Tcl_LinkArray(Tcl_Interp *interp, const char *varName, void *addr, int type, Tcl_Size size) } declare 645 { int Tcl_GetIntForIndex(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size endValue, Tcl_Size *indexPtr) } # TIP #548 declare 646 { Tcl_Size Tcl_UtfToUniChar(const char *src, int *chPtr) } declare 647 { char *Tcl_UniCharToUtfDString(const int *uniStr, Tcl_Size uniLength, Tcl_DString *dsPtr) } declare 648 { int *Tcl_UtfToUniCharDString(const char *src, Tcl_Size length, Tcl_DString *dsPtr) } # TIP #568 declare 649 { unsigned char *TclGetBytesFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, void *numBytesPtr) } declare 650 { unsigned char *Tcl_GetBytesFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size *numBytesPtr) } # TIP #481 declare 651 { char *Tcl_GetStringFromObj(Tcl_Obj *objPtr, Tcl_Size *lengthPtr) } declare 652 { Tcl_UniChar *Tcl_GetUnicodeFromObj(Tcl_Obj *objPtr, Tcl_Size *lengthPtr) } # TIP 660 declare 653 { int Tcl_GetSizeIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size *sizePtr) } # TIP #575 declare 654 { int Tcl_UtfCharComplete(const char *src, Tcl_Size length) } declare 655 { const char *Tcl_UtfNext(const char *src) } declare 656 { const char *Tcl_UtfPrev(const char *src, const char *start) } # TIP 701 declare 657 { int Tcl_FSTildeExpand(Tcl_Interp *interp, const char *path, Tcl_DString *dsPtr) } # TIP 656 declare 658 { int Tcl_ExternalToUtfDStringEx(Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr, Tcl_Size *errorLocationPtr) } declare 659 { int Tcl_UtfToExternalDStringEx(Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr, Tcl_Size *errorLocationPtr) } # TIP #511 declare 660 { int Tcl_AsyncMarkFromSignal(Tcl_AsyncHandler async, int sigNumber) } # TIP #616 declare 661 { int Tcl_ListObjGetElements(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size *objcPtr, Tcl_Obj ***objvPtr) } declare 662 { int Tcl_ListObjLength(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size *lengthPtr) } declare 663 { int Tcl_DictObjSize(Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size *sizePtr) } declare 664 { int Tcl_SplitList(Tcl_Interp *interp, const char *listStr, Tcl_Size *argcPtr, const char ***argvPtr) } declare 665 { void Tcl_SplitPath(const char *path, Tcl_Size *argcPtr, const char ***argvPtr) } declare 666 { Tcl_Obj *Tcl_FSSplitPath(Tcl_Obj *pathPtr, Tcl_Size *lenPtr) } declare 667 { int Tcl_ParseArgsObjv(Tcl_Interp *interp, const Tcl_ArgvInfo *argTable, Tcl_Size *objcPtr, Tcl_Obj *const *objv, Tcl_Obj ***remObjv) } # TIP #617 declare 668 { Tcl_Size Tcl_UniCharLen(const int *uniStr) } declare 669 { Tcl_Size Tcl_NumUtfChars(const char *src, Tcl_Size length) } declare 670 { Tcl_Size Tcl_GetCharLength(Tcl_Obj *objPtr) } declare 671 { const char *Tcl_UtfAtIndex(const char *src, Tcl_Size index) } declare 672 { Tcl_Obj *Tcl_GetRange(Tcl_Obj *objPtr, Tcl_Size first, Tcl_Size last) } declare 673 { int Tcl_GetUniChar(Tcl_Obj *objPtr, Tcl_Size index) } declare 674 { int Tcl_GetBool(Tcl_Interp *interp, const char *src, int flags, char *charPtr) } declare 675 { int Tcl_GetBoolFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags, char *charPtr) } declare 676 { Tcl_Command Tcl_CreateObjCommand2(Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc2 *proc2, void *clientData, Tcl_CmdDeleteProc *deleteProc) } declare 677 { Tcl_Trace Tcl_CreateObjTrace2(Tcl_Interp *interp, Tcl_Size level, int flags, Tcl_CmdObjTraceProc2 *objProc2, void *clientData, Tcl_CmdObjTraceDeleteProc *delProc) } declare 678 { Tcl_Command Tcl_NRCreateCommand2(Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc2 *proc, Tcl_ObjCmdProc2 *nreProc2, void *clientData, Tcl_CmdDeleteProc *deleteProc) } declare 679 { int Tcl_NRCallObjProc2(Tcl_Interp *interp, Tcl_ObjCmdProc2 *objProc2, void *clientData, Tcl_Size objc, Tcl_Obj *const objv[]) } # TIP #638. declare 680 { int Tcl_GetNumberFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, void **clientDataPtr, int *typePtr) } declare 681 { int Tcl_GetNumber(Tcl_Interp *interp, const char *bytes, Tcl_Size numBytes, void **clientDataPtr, int *typePtr) } # TIP #220. declare 682 { int Tcl_RemoveChannelMode(Tcl_Interp *interp, Tcl_Channel chan, int mode) } # TIP 643 declare 683 { Tcl_Size Tcl_GetEncodingNulLength(Tcl_Encoding encoding) } # TIP #650 declare 684 { int Tcl_GetWideUIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_WideUInt *uwidePtr) } # TIP 651 declare 685 { Tcl_Obj *Tcl_DStringToObj(Tcl_DString *dsPtr) } declare 686 { int Tcl_UtfNcmp(const char *s1, const char *s2, size_t n) } declare 687 { int Tcl_UtfNcasecmp(const char *s1, const char *s2, size_t n) } # TIP #648 declare 688 { Tcl_Obj *Tcl_NewWideUIntObj(Tcl_WideUInt wideValue) } declare 689 { void Tcl_SetWideUIntObj(Tcl_Obj *objPtr, Tcl_WideUInt uwideValue) } # ----- BASELINE -- FOR -- 8.7.0 / 9.0.0 ----- # declare 690 { void TclUnusedStubEntry(void) } ############################################################################## # Define the platform specific public Tcl interface. These functions are only # available on the designated platform. interface tclPlat ################################ # Unix specific functions # (none) ################################ # Mac OS X specific functions declare 1 { int Tcl_MacOSXOpenVersionedBundleResources(Tcl_Interp *interp, const char *bundleName, const char *bundleVersion, int hasResourceFile, Tcl_Size maxPathLen, char *libraryPath) } declare 2 { void Tcl_MacOSXNotifierAddRunLoopMode(const void *runLoopMode) } ################################ # Windows specific functions declare 3 { void Tcl_WinConvertError(unsigned errCode) } ############################################################################## # Public functions that are not accessible via the stubs table. export { TCL_NORETURN void Tcl_MainEx(Tcl_Size argc, char **argv, Tcl_AppInitProc *appInitProc, Tcl_Interp *interp) } export { void Tcl_StaticLibrary(Tcl_Interp *interp, const char *prefix, Tcl_LibraryInitProc *initProc, Tcl_LibraryInitProc *safeInitProc) } export { const char *Tcl_SetPanicProc(Tcl_PanicProc *panicProc) } export { Tcl_ExitProc *Tcl_SetExitProc(Tcl_ExitProc *proc) } export { const char *Tcl_FindExecutable(const char *argv0) } export { const char *Tcl_InitStubs(Tcl_Interp *interp, const char *version, int exact) } export { const char *TclTomMathInitializeStubs(Tcl_Interp* interp, const char* version, int epoch, int revision) } export { const char *Tcl_PkgInitStubsCheck(Tcl_Interp *interp, const char *version, int exact) } export { void Tcl_GetMemoryInfo(Tcl_DString *dsPtr) } export { const char *Tcl_InitSubsystems(void) } export { const char *TclZipfs_AppHook(int *argc, char ***argv) } # Local Variables: # mode: tcl # End: tcl9.0.1/generic/tclInt.decls0000644000175000017500000004712514726623136015454 0ustar sergeisergei# tclInt.decls -- # # This file contains the declarations for all unsupported # functions that are exported by the Tcl library. This file # is used to generate the tclIntDecls.h, tclIntPlatDecls.h # and tclStubInit.c files # # Copyright © 1998-1999 Scriptics Corporation. # Copyright © 2001 Kevin B. Kenny. All rights reserved. # Copyright © 2007 Daniel A. Steffen # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. library tcl # Define the unsupported generic interfaces. interface tclInt scspec EXTERN # Declare each of the functions in the unsupported internal Tcl # interface. These interfaces are allowed to changed between versions. # Use at your own risk. Note that the position of functions should not # be changed between versions to avoid gratuitous incompatibilities. declare 3 { void TclAllocateFreeObjects(void) } declare 5 { int TclCleanupChildren(Tcl_Interp *interp, Tcl_Size numPids, Tcl_Pid *pidPtr, Tcl_Channel errorChan) } declare 6 { void TclCleanupCommand(Command *cmdPtr) } declare 7 { Tcl_Size TclCopyAndCollapse(Tcl_Size count, const char *src, char *dst) } # TclCreatePipeline unofficially exported for use by BLT. declare 9 { Tcl_Size TclCreatePipeline(Tcl_Interp *interp, Tcl_Size argc, const char **argv, Tcl_Pid **pidArrayPtr, TclFile *inPipePtr, TclFile *outPipePtr, TclFile *errFilePtr) } declare 10 { int TclCreateProc(Tcl_Interp *interp, Namespace *nsPtr, const char *procName, Tcl_Obj *argsPtr, Tcl_Obj *bodyPtr, Proc **procPtrPtr) } declare 11 { void TclDeleteCompiledLocalVars(Interp *iPtr, CallFrame *framePtr) } declare 12 { void TclDeleteVars(Interp *iPtr, TclVarHashTable *tablePtr) } declare 14 { int TclDumpMemoryInfo(void *clientData, int flags) } declare 16 { void TclExprFloatError(Tcl_Interp *interp, double value) } declare 22 { int TclFindElement(Tcl_Interp *interp, const char *listStr, Tcl_Size listLength, const char **elementPtr, const char **nextPtr, Tcl_Size *sizePtr, int *bracePtr) } declare 23 { Proc *TclFindProc(Interp *iPtr, const char *procName) } # Replaced with macro (see tclInt.h) in Tcl 8.5.0, restored in 8.5.10 declare 24 { Tcl_Size TclFormatInt(char *buffer, Tcl_WideInt n) } declare 25 { void TclFreePackageInfo(Interp *iPtr) } declare 28 { Tcl_Channel TclpGetDefaultStdChannel(int type) } declare 31 { const char *TclGetExtension(const char *name) } declare 32 { int TclGetFrame(Tcl_Interp *interp, const char *str, CallFrame **framePtrPtr) } # Removed in 9.0: #declare 34 { # int TclGetIntForIndex(Tcl_Interp *interp, Tcl_Obj *objPtr, # int endValue, int *indexPtr) #} #declare 37 { # int TclGetLoadedPackages(Tcl_Interp *interp, const char *targetName) #} declare 38 { int TclGetNamespaceForQualName(Tcl_Interp *interp, const char *qualName, Namespace *cxtNsPtr, int flags, Namespace **nsPtrPtr, Namespace **altNsPtrPtr, Namespace **actualCxtPtrPtr, const char **simpleNamePtr) } declare 39 { Tcl_ObjCmdProc *TclGetObjInterpProc(void) } declare 40 { int TclGetOpenMode(Tcl_Interp *interp, const char *str, int *modeFlagsPtr) } declare 41 { Tcl_Command TclGetOriginalCommand(Tcl_Command command) } declare 42 { const char *TclpGetUserHome(const char *name, Tcl_DString *bufferPtr) } declare 43 { Tcl_ObjCmdProc2 *TclGetObjInterpProc2(void) } # Removed in 9.0: #declare 44 { # int TclGuessPackageName(const char *fileName, Tcl_DString *bufPtr) #} declare 45 { int TclHideUnsafeCommands(Tcl_Interp *interp) } declare 46 { int TclInExit(void) } # Removed in 9.0: #declare 50 { # void TclInitCompiledLocals(Tcl_Interp *interp, CallFrame *framePtr, # Namespace *nsPtr) #} declare 51 { int TclInterpInit(Tcl_Interp *interp) } # Removed in 9.0 #declare 53 { # int TclInvokeObjectCommand(void *clientData, Tcl_Interp *interp, # Tcl_Size argc, const char **argv) #} #declare 54 { # int TclInvokeStringCommand(void *clientData, Tcl_Interp *interp, # Tcl_Size objc, Tcl_Obj *const objv[]) #} declare 55 { Proc *TclIsProc(Command *cmdPtr) } declare 58 { Var *TclLookupVar(Tcl_Interp *interp, const char *part1, const char *part2, int flags, const char *msg, int createPart1, int createPart2, Var **arrayPtrPtr) } declare 60 { int TclNeedSpace(const char *start, const char *end) } declare 61 { Tcl_Obj *TclNewProcBodyObj(Proc *procPtr) } declare 62 { int TclObjCommandComplete(Tcl_Obj *cmdPtr) } # Removed in 9.0: #declare 63 { # int TclObjInterpProc(void *clientData, Tcl_Interp *interp, # Tcl_Size objc, Tcl_Obj *const objv[]) #} declare 64 { int TclObjInvoke(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags) } declare 69 { void *TclpAlloc(TCL_HASH_TYPE size) } declare 74 { void TclpFree(void *ptr) } declare 75 { unsigned long long TclpGetClicks(void) } declare 76 { unsigned long long TclpGetSeconds(void) } # Removed in 9.0: #declare 77 { # void TclpGetTime(Tcl_Time *time) #} declare 81 { void *TclpRealloc(void *ptr, TCL_HASH_TYPE size) } # Removed in 9.0: #declare 88 { # char *TclPrecTraceProc(void *clientData, Tcl_Interp *interp, # const char *name1, const char *name2, int flags) #} declare 89 { int TclPreventAliasLoop(Tcl_Interp *interp, Tcl_Interp *cmdInterp, Tcl_Command cmd) } declare 91 { void TclProcCleanupProc(Proc *procPtr) } declare 92 { int TclProcCompileProc(Tcl_Interp *interp, Proc *procPtr, Tcl_Obj *bodyPtr, Namespace *nsPtr, const char *description, const char *procName) } declare 93 { void TclProcDeleteProc(void *clientData) } declare 96 { int TclRenameCommand(Tcl_Interp *interp, const char *oldName, const char *newName) } declare 97 { void TclResetShadowedCmdRefs(Tcl_Interp *interp, Command *newCmdPtr) } declare 98 { int TclServiceIdle(void) } # Removed in 9.0: #declare 101 { # const char *TclSetPreInitScript(const char *string) #} declare 102 { void TclSetupEnv(Tcl_Interp *interp) } declare 103 { int TclSockGetPort(Tcl_Interp *interp, const char *str, const char *proto, int *portPtr) } # Removed in 9.0: #declare 104 { # int TclSockMinimumBuffersOld(int sock, int size) #} declare 108 { void TclTeardownNamespace(Namespace *nsPtr) } declare 109 { int TclUpdateReturnInfo(Interp *iPtr) } declare 110 { int TclSockMinimumBuffers(void *sock, Tcl_Size size) } # Procedures used in conjunction with Tcl namespaces. They are # defined here instead of in tcl.decls since they are not stable yet. declare 111 { void Tcl_AddInterpResolvers(Tcl_Interp *interp, const char *name, Tcl_ResolveCmdProc *cmdProc, Tcl_ResolveVarProc *varProc, Tcl_ResolveCompiledVarProc *compiledVarProc) } # Removed in 9.0: #declare 112 { # int TclAppendExportList(Tcl_Interp *interp, Tcl_Namespace *nsPtr, # Tcl_Obj *objPtr) #} #declare 113 { # Tcl_Namespace *TclCreateNamespace(Tcl_Interp *interp, const char *name, # void *clientData, Tcl_NamespaceDeleteProc *deleteProc) #} #declare 114 { # void TclDeleteNamespace(Tcl_Namespace *nsPtr) #} #declare 115 { # int TclExport(Tcl_Interp *interp, Tcl_Namespace *nsPtr, # const char *pattern, int resetListFirst) #} #declare 116 { # Tcl_Command TclFindCommand(Tcl_Interp *interp, const char *name, # Tcl_Namespace *contextNsPtr, int flags) #} #declare 117 { # Tcl_Namespace *TclFindNamespace(Tcl_Interp *interp, const char *name, # Tcl_Namespace *contextNsPtr, int flags) #} declare 118 { int Tcl_GetInterpResolvers(Tcl_Interp *interp, const char *name, Tcl_ResolverInfo *resInfo) } declare 119 { int Tcl_GetNamespaceResolvers(Tcl_Namespace *namespacePtr, Tcl_ResolverInfo *resInfo) } declare 120 { Tcl_Var Tcl_FindNamespaceVar(Tcl_Interp *interp, const char *name, Tcl_Namespace *contextNsPtr, int flags) } declare 126 { void Tcl_GetVariableFullName(Tcl_Interp *interp, Tcl_Var variable, Tcl_Obj *objPtr) } declare 128 { void Tcl_PopCallFrame(Tcl_Interp *interp) } declare 129 { int Tcl_PushCallFrame(Tcl_Interp *interp, Tcl_CallFrame *framePtr, Tcl_Namespace *nsPtr, int isProcCallFrame) } declare 130 { int Tcl_RemoveInterpResolvers(Tcl_Interp *interp, const char *name) } declare 131 { void Tcl_SetNamespaceResolvers(Tcl_Namespace *namespacePtr, Tcl_ResolveCmdProc *cmdProc, Tcl_ResolveVarProc *varProc, Tcl_ResolveCompiledVarProc *compiledVarProc) } declare 138 { const char *TclGetEnv(const char *name, Tcl_DString *valuePtr) } # This is used by TclX, but should otherwise be considered private declare 141 { const char *TclpGetCwd(Tcl_Interp *interp, Tcl_DString *cwdPtr) } declare 142 { int TclSetByteCodeFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr, CompileHookProc *hookProc, void *clientData) } declare 143 { int TclAddLiteralObj(struct CompileEnv *envPtr, Tcl_Obj *objPtr, LiteralEntry **litPtrPtr) } declare 144 { void TclHideLiteral(Tcl_Interp *interp, struct CompileEnv *envPtr, int index) } declare 145 { const struct AuxDataType *TclGetAuxDataType(const char *typeName) } declare 146 { TclHandle TclHandleCreate(void *ptr) } declare 147 { void TclHandleFree(TclHandle handle) } declare 148 { TclHandle TclHandlePreserve(TclHandle handle) } declare 149 { void TclHandleRelease(TclHandle handle) } declare 150 { int TclRegAbout(Tcl_Interp *interp, Tcl_RegExp re) } declare 151 { void TclRegExpRangeUniChar(Tcl_RegExp re, Tcl_Size index, Tcl_Size *startPtr, Tcl_Size *endPtr) } declare 156 { void TclRegError(Tcl_Interp *interp, const char *msg, int status) } declare 157 { Var *TclVarTraceExists(Tcl_Interp *interp, const char *varName) } declare 161 { int TclChannelTransform(Tcl_Interp *interp, Tcl_Channel chan, Tcl_Obj *cmdObjPtr) } declare 162 { void TclChannelEventScriptInvoker(void *clientData, int flags) } # ALERT: The result of 'TclGetInstructionTable' is actually a # "const InstructionDesc*" but we do not want to describe this structure in # "tclInt.h". It is described in "tclCompile.h". Use a cast to the # correct type when calling this procedure. declare 163 { const void *TclGetInstructionTable(void) } # ALERT: The argument of 'TclExpandCodeArray' is actually a # "CompileEnv*" but we do not want to describe this structure in # "tclInt.h". It is described in "tclCompile.h". declare 164 { void TclExpandCodeArray(void *envPtr) } # These functions are vfs aware, but are generally only useful internally. declare 165 { void TclpSetInitialEncodings(void) } # New function due to TIP #33 declare 166 { int TclListObjSetElement(Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size index, Tcl_Obj *valuePtr) } # variant of Tcl_UtfNcmp that takes n as bytes, not chars declare 169 { int TclpUtfNcmp2(const void *s1, const void *s2, size_t n) } declare 170 { int TclCheckInterpTraces(Tcl_Interp *interp, const char *command, Tcl_Size numChars, Command *cmdPtr, int result, int traceFlags, Tcl_Size objc, Tcl_Obj *const objv[]) } declare 171 { int TclCheckExecutionTraces(Tcl_Interp *interp, const char *command, Tcl_Size numChars, Command *cmdPtr, int result, int traceFlags, Tcl_Size objc, Tcl_Obj *const objv[]) } declare 172 { int TclInThreadExit(void) } declare 173 { int TclUniCharMatch(const Tcl_UniChar *string, Tcl_Size strLen, const Tcl_UniChar *pattern, Tcl_Size ptnLen, int flags) } declare 175 { int TclCallVarTraces(Interp *iPtr, Var *arrayPtr, Var *varPtr, const char *part1, const char *part2, int flags, int leaveErrMsg) } declare 176 { void TclCleanupVar(Var *varPtr, Var *arrayPtr) } declare 177 { void TclVarErrMsg(Tcl_Interp *interp, const char *part1, const char *part2, const char *operation, const char *reason) } declare 198 { int TclObjGetFrame(Tcl_Interp *interp, Tcl_Obj *objPtr, CallFrame **framePtrPtr) } # 200-208 exported for use by the test suite [Bug 1054748] declare 200 { int TclpObjRemoveDirectory(Tcl_Obj *pathPtr, int recursive, Tcl_Obj **errorPtr) } declare 201 { int TclpObjCopyDirectory(Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr) } declare 202 { int TclpObjCreateDirectory(Tcl_Obj *pathPtr) } declare 203 { int TclpObjDeleteFile(Tcl_Obj *pathPtr) } declare 204 { int TclpObjCopyFile(Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr) } declare 205 { int TclpObjRenameFile(Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr) } declare 206 { int TclpObjStat(Tcl_Obj *pathPtr, Tcl_StatBuf *buf) } declare 207 { int TclpObjAccess(Tcl_Obj *pathPtr, int mode) } declare 208 { Tcl_Channel TclpOpenFileChannel(Tcl_Interp *interp, Tcl_Obj *pathPtr, int mode, int permissions) } declare 212 { void TclpFindExecutable(const char *argv0) } declare 213 { Tcl_Obj *TclGetObjNameOfExecutable(void) } declare 214 { void TclSetObjNameOfExecutable(Tcl_Obj *name, Tcl_Encoding encoding) } declare 215 { void *TclStackAlloc(Tcl_Interp *interp, TCL_HASH_TYPE numBytes) } declare 216 { void TclStackFree(Tcl_Interp *interp, void *freePtr) } declare 217 { int TclPushStackFrame(Tcl_Interp *interp, Tcl_CallFrame **framePtrPtr, Tcl_Namespace *namespacePtr, int isProcCallFrame) } declare 218 { void TclPopStackFrame(Tcl_Interp *interp) } # TIP 431: temporary directory creation function declare 219 { Tcl_Obj *TclpCreateTemporaryDirectory(Tcl_Obj *dirObj, Tcl_Obj *basenameObj) } # for use in tclTest.c # TIP 625: for unit testing - create list objects with span declare 221 { Tcl_Obj *TclListTestObj(size_t length, size_t leadingSpace, size_t endSpace) } # TIP 625: for unit testing - check list invariants declare 222 { void TclListObjValidate(Tcl_Interp *interp, Tcl_Obj *listObj) } # Bug 7371b6270b declare 223 { void *TclGetCStackPtr(void) } declare 224 { TclPlatformType *TclGetPlatform(void) } declare 225 { Tcl_Obj *TclTraceDictPath(Tcl_Interp *interp, Tcl_Obj *rootPtr, Tcl_Size keyc, Tcl_Obj *const keyv[], int flags) } declare 226 { int TclObjBeingDeleted(Tcl_Obj *objPtr) } declare 227 { void TclSetNsPath(Namespace *nsPtr, Tcl_Size pathLength, Tcl_Namespace *pathAry[]) } declare 229 { int TclPtrMakeUpvar(Tcl_Interp *interp, Var *otherP1Ptr, const char *myName, int myFlags, int index) } declare 230 { Var *TclObjLookupVar(Tcl_Interp *interp, Tcl_Obj *part1Ptr, const char *part2, int flags, const char *msg, int createPart1, int createPart2, Var **arrayPtrPtr) } declare 231 { int TclGetNamespaceFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Namespace **nsPtrPtr) } # Bits and pieces of TIP#280's guts declare 232 { int TclEvalObjEx(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags, const CmdFrame *invoker, int word) } declare 233 { void TclGetSrcInfoForPc(CmdFrame *contextPtr) } # Exports for VarReform compat: Itcl, XOTcl like to peek into our varTables :( declare 234 { Var *TclVarHashCreateVar(TclVarHashTable *tablePtr, const char *key, int *newPtr) } declare 235 { void TclInitVarHashTable(TclVarHashTable *tablePtr, Namespace *nsPtr) } # TIP #285: Script cancellation support. declare 237 { int TclResetCancellation(Tcl_Interp *interp, int force) } # NRE functions for "rogue" extensions to exploit NRE; they will need to # include NRE.h too. declare 238 { int TclNRInterpProc(void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[]) } declare 239 { int TclNRInterpProcCore(Tcl_Interp *interp, Tcl_Obj *procNameObj, Tcl_Size skip, ProcErrorProc *errorProc) } declare 240 { int TclNRRunCallbacks(Tcl_Interp *interp, int result, struct NRE_callback *rootPtr) } declare 241 { int TclNREvalObjEx(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags, const CmdFrame *invoker, int word) } declare 242 { int TclNREvalObjv(Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags, Command *cmdPtr) } # Tcl_Obj leak detection support. declare 243 { void TclDbDumpActiveObjects(FILE *outFile) } # Functions to make things better for itcl declare 244 { Tcl_HashTable *TclGetNamespaceChildTable(Tcl_Namespace *nsPtr) } declare 245 { Tcl_HashTable *TclGetNamespaceCommandTable(Tcl_Namespace *nsPtr) } declare 246 { int TclInitRewriteEnsemble(Tcl_Interp *interp, Tcl_Size numRemoved, Tcl_Size numInserted, Tcl_Obj *const *objv) } declare 247 { void TclResetRewriteEnsemble(Tcl_Interp *interp, int isRootEnsemble) } declare 248 { int TclCopyChannel(Tcl_Interp *interp, Tcl_Channel inChan, Tcl_Channel outChan, long long toRead, Tcl_Obj *cmdPtr) } declare 249 { char *TclDoubleDigits(double dv, int ndigits, int flags, int *decpt, int *signum, char **endPtr) } # TIP #285: Script cancellation support. declare 250 { void TclSetChildCancelFlags(Tcl_Interp *interp, int flags, int force) } # Allow extensions for optimization declare 251 { int TclRegisterLiteral(void *envPtr, const char *bytes, Tcl_Size length, int flags) } # Exporting of the internal API to variables. declare 252 { Tcl_Obj *TclPtrGetVar(Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags) } declare 253 { Tcl_Obj *TclPtrSetVar(Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *newValuePtr, int flags) } declare 254 { Tcl_Obj *TclPtrIncrObjVar(Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *incrPtr, int flags) } declare 255 { int TclPtrObjMakeUpvar(Tcl_Interp *interp, Tcl_Var otherPtr, Tcl_Obj *myNamePtr, int myFlags) } declare 256 { int TclPtrUnsetVar(Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags) } declare 257 { void TclStaticLibrary(Tcl_Interp *interp, const char *prefix, Tcl_LibraryInitProc *initProc, Tcl_LibraryInitProc *safeInitProc) } declare 261 { void TclUnusedStubEntry(void) } ############################################################################## # Define the platform specific internal Tcl interface. These functions are # only available on the designated platform. interface tclIntPlat ################################ # Platform specific functions declare 1 { int TclpCloseFile(TclFile file) } declare 2 { Tcl_Channel TclpCreateCommandChannel(TclFile readFile, TclFile writeFile, TclFile errorFile, size_t numPids, Tcl_Pid *pidPtr) } declare 3 { int TclpCreatePipe(TclFile *readPipe, TclFile *writePipe) } declare 4 { void *TclWinGetTclInstance(void) } declare 5 { int TclUnixWaitForFile(int fd, int mask, int timeout) } declare 6 { TclFile TclpMakeFile(Tcl_Channel channel, int direction) } declare 7 { TclFile TclpOpenFile(const char *fname, int mode) } declare 8 { Tcl_Size TclpGetPid(Tcl_Pid pid) } declare 9 { TclFile TclpCreateTempFile(const char *contents) } declare 11 { void TclGetAndDetachPids(Tcl_Interp *interp, Tcl_Channel chan) } declare 15 { int TclpCreateProcess(Tcl_Interp *interp, size_t argc, const char **argv, TclFile inputFile, TclFile outputFile, TclFile errorFile, Tcl_Pid *pidPtr) } declare 16 { int TclpIsAtty(int fd) } declare 17 { int TclUnixCopyFile(const char *src, const char *dst, const Tcl_StatBuf *statBufPtr, int dontCopyAtts) } declare 20 { void TclWinAddProcess(void *hProcess, Tcl_Size id) } declare 24 { char *TclWinNoBackslash(char *path) } declare 27 { void TclWinFlushDirtyChannels(void) } declare 29 { int TclWinCPUID(int index, int *regs) } declare 30 { int TclUnixOpenTemporaryFile(Tcl_Obj *dirObj, Tcl_Obj *basenameObj, Tcl_Obj *extensionObj, Tcl_Obj *resultingNameObj) } # Local Variables: # mode: tcl # End: tcl9.0.1/generic/tclOO.decls0000644000175000017500000001630314726623136015231 0ustar sergeisergei# tclOO.decls -- # # This file contains the declarations for all supported public functions # that are exported by the TclOO package that is embedded within the Tcl # library via the stubs table. This file is used to generate the # tclOODecls.h, tclOOIntDecls.h and tclOOStubInit.c files. # # Copyright © 2008-2013 Donal K. Fellows. # # See the file "license.terms" for information on usage and redistribution of # this file, and for a DISCLAIMER OF ALL WARRANTIES. library tclOO ###################################################################### # Public API, exposed for general users of TclOO. # interface tclOO hooks tclOOInt scspec TCLAPI declare 0 { Tcl_Object Tcl_CopyObjectInstance(Tcl_Interp *interp, Tcl_Object sourceObject, const char *targetName, const char *targetNamespaceName) } declare 1 { Tcl_Object Tcl_GetClassAsObject(Tcl_Class clazz) } declare 2 { Tcl_Class Tcl_GetObjectAsClass(Tcl_Object object) } declare 3 { Tcl_Command Tcl_GetObjectCommand(Tcl_Object object) } declare 4 { Tcl_Object Tcl_GetObjectFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr) } declare 5 { Tcl_Namespace *Tcl_GetObjectNamespace(Tcl_Object object) } declare 6 { Tcl_Class Tcl_MethodDeclarerClass(Tcl_Method method) } declare 7 { Tcl_Object Tcl_MethodDeclarerObject(Tcl_Method method) } declare 8 { int Tcl_MethodIsPublic(Tcl_Method method) } declare 9 { int Tcl_MethodIsType(Tcl_Method method, const Tcl_MethodType *typePtr, void **clientDataPtr) } declare 10 { Tcl_Obj *Tcl_MethodName(Tcl_Method method) } declare 11 { Tcl_Method Tcl_NewInstanceMethod(Tcl_Interp *interp, Tcl_Object object, Tcl_Obj *nameObj, int flags, const Tcl_MethodType *typePtr, void *clientData) } declare 12 { Tcl_Method Tcl_NewMethod(Tcl_Interp *interp, Tcl_Class cls, Tcl_Obj *nameObj, int flags, const Tcl_MethodType *typePtr, void *clientData) } declare 13 { Tcl_Object Tcl_NewObjectInstance(Tcl_Interp *interp, Tcl_Class cls, const char *nameStr, const char *nsNameStr, Tcl_Size objc, Tcl_Obj *const *objv, Tcl_Size skip) } declare 14 { int Tcl_ObjectDeleted(Tcl_Object object) } declare 15 { int Tcl_ObjectContextIsFiltering(Tcl_ObjectContext context) } declare 16 { Tcl_Method Tcl_ObjectContextMethod(Tcl_ObjectContext context) } declare 17 { Tcl_Object Tcl_ObjectContextObject(Tcl_ObjectContext context) } declare 18 { Tcl_Size Tcl_ObjectContextSkippedArgs(Tcl_ObjectContext context) } declare 19 { void *Tcl_ClassGetMetadata(Tcl_Class clazz, const Tcl_ObjectMetadataType *typePtr) } declare 20 { void Tcl_ClassSetMetadata(Tcl_Class clazz, const Tcl_ObjectMetadataType *typePtr, void *metadata) } declare 21 { void *Tcl_ObjectGetMetadata(Tcl_Object object, const Tcl_ObjectMetadataType *typePtr) } declare 22 { void Tcl_ObjectSetMetadata(Tcl_Object object, const Tcl_ObjectMetadataType *typePtr, void *metadata) } declare 23 { int Tcl_ObjectContextInvokeNext(Tcl_Interp *interp, Tcl_ObjectContext context, Tcl_Size objc, Tcl_Obj *const *objv, Tcl_Size skip) } declare 24 { Tcl_ObjectMapMethodNameProc *Tcl_ObjectGetMethodNameMapper( Tcl_Object object) } declare 25 { void Tcl_ObjectSetMethodNameMapper(Tcl_Object object, Tcl_ObjectMapMethodNameProc *mapMethodNameProc) } declare 26 { void Tcl_ClassSetConstructor(Tcl_Interp *interp, Tcl_Class clazz, Tcl_Method method) } declare 27 { void Tcl_ClassSetDestructor(Tcl_Interp *interp, Tcl_Class clazz, Tcl_Method method) } declare 28 { Tcl_Obj *Tcl_GetObjectName(Tcl_Interp *interp, Tcl_Object object) } declare 29 { int Tcl_MethodIsPrivate(Tcl_Method method) } declare 30 { Tcl_Class Tcl_GetClassOfObject(Tcl_Object object) } declare 31 { Tcl_Obj *Tcl_GetObjectClassName(Tcl_Interp *interp, Tcl_Object object) } declare 32 { int Tcl_MethodIsType2(Tcl_Method method, const Tcl_MethodType2 *typePtr, void **clientDataPtr) } declare 33 { Tcl_Method Tcl_NewInstanceMethod2(Tcl_Interp *interp, Tcl_Object object, Tcl_Obj *nameObj, int flags, const Tcl_MethodType2 *typePtr, void *clientData) } declare 34 { Tcl_Method Tcl_NewMethod2(Tcl_Interp *interp, Tcl_Class cls, Tcl_Obj *nameObj, int flags, const Tcl_MethodType2 *typePtr, void *clientData) } ###################################################################### # Private API, exposed to support advanced OO systems that plug in on top of # TclOO; not intended for general use and does not have any commitment to # long-term support. # interface tclOOInt declare 0 { Tcl_Object TclOOGetDefineCmdContext(Tcl_Interp *interp) } declare 1 { Tcl_Method TclOOMakeProcInstanceMethod(Tcl_Interp *interp, Object *oPtr, int flags, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, const Tcl_MethodType *typePtr, void *clientData, Proc **procPtrPtr) } declare 2 { Tcl_Method TclOOMakeProcMethod(Tcl_Interp *interp, Class *clsPtr, int flags, Tcl_Obj *nameObj, const char *namePtr, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, const Tcl_MethodType *typePtr, void *clientData, Proc **procPtrPtr) } declare 3 { Method *TclOONewProcInstanceMethod(Tcl_Interp *interp, Object *oPtr, int flags, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, ProcedureMethod **pmPtrPtr) } declare 4 { Method *TclOONewProcMethod(Tcl_Interp *interp, Class *clsPtr, int flags, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, ProcedureMethod **pmPtrPtr) } declare 5 { int TclOOObjectCmdCore(Object *oPtr, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv, int publicOnly, Class *startCls) } declare 6 { int TclOOIsReachable(Class *targetPtr, Class *startPtr) } declare 7 { Method *TclOONewForwardMethod(Tcl_Interp *interp, Class *clsPtr, int isPublic, Tcl_Obj *nameObj, Tcl_Obj *prefixObj) } declare 8 { Method *TclOONewForwardInstanceMethod(Tcl_Interp *interp, Object *oPtr, int isPublic, Tcl_Obj *nameObj, Tcl_Obj *prefixObj) } declare 9 { Tcl_Method TclOONewProcInstanceMethodEx(Tcl_Interp *interp, Tcl_Object oPtr, TclOO_PreCallProc *preCallPtr, TclOO_PostCallProc *postCallPtr, ProcErrorProc *errProc, void *clientData, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, int flags, void **internalTokenPtr) } declare 10 { Tcl_Method TclOONewProcMethodEx(Tcl_Interp *interp, Tcl_Class clsPtr, TclOO_PreCallProc *preCallPtr, TclOO_PostCallProc *postCallPtr, ProcErrorProc *errProc, void *clientData, Tcl_Obj *nameObj, Tcl_Obj *argsObj, Tcl_Obj *bodyObj, int flags, void **internalTokenPtr) } declare 11 { int TclOOInvokeObject(Tcl_Interp *interp, Tcl_Object object, Tcl_Class startCls, int publicPrivate, Tcl_Size objc, Tcl_Obj *const *objv) } declare 12 { void TclOOObjectSetFilters(Object *oPtr, Tcl_Size numFilters, Tcl_Obj *const *filters) } declare 13 { void TclOOClassSetFilters(Tcl_Interp *interp, Class *classPtr, Tcl_Size numFilters, Tcl_Obj *const *filters) } declare 14 { void TclOOObjectSetMixins(Object *oPtr, Tcl_Size numMixins, Class *const *mixins) } declare 15 { void TclOOClassSetMixins(Tcl_Interp *interp, Class *classPtr, Tcl_Size numMixins, Class *const *mixins) } return # Local Variables: # mode: tcl # End: tcl9.0.1/generic/tclTomMath.decls0000644000175000017500000001231514726623136016264 0ustar sergeisergei# tclTomMath.decls -- # # This file contains the declarations for the functions in 'libtommath' # that are contained within the Tcl library. This file is used to # generate the 'tclTomMathDecls.h' and 'tclStubInit.c' files. # # If you edit this file, advance the revision number (and the epoch # if the new stubs are not backward compatible) in tclTomMathDecls.h # # Copyright © 2005 Kevin B. Kenny. All rights reserved. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. library tcl # Define the unsupported generic interfaces. interface tclTomMath scspec EXTERN # Declare each of the functions in the Tcl tommath interface declare 0 { int MP_WUR TclBN_epoch(void) } declare 1 { int MP_WUR TclBN_revision(void) } declare 2 { mp_err MP_WUR TclBN_mp_add(const mp_int *a, const mp_int *b, mp_int *c) } declare 3 { mp_err MP_WUR TclBN_mp_add_d(const mp_int *a, mp_digit b, mp_int *c) } declare 4 { mp_err MP_WUR TclBN_mp_and(const mp_int *a, const mp_int *b, mp_int *c) } declare 5 { void TclBN_mp_clamp(mp_int *a) } declare 6 { void TclBN_mp_clear(mp_int *a) } declare 7 { void TclBN_mp_clear_multi(mp_int *a, ...) } declare 8 { mp_ord MP_WUR TclBN_mp_cmp(const mp_int *a, const mp_int *b) } declare 9 { mp_ord MP_WUR TclBN_mp_cmp_d(const mp_int *a, mp_digit b) } declare 10 { mp_ord MP_WUR TclBN_mp_cmp_mag(const mp_int *a, const mp_int *b) } declare 11 { mp_err MP_WUR TclBN_mp_copy(const mp_int *a, mp_int *b) } declare 12 { int MP_WUR TclBN_mp_count_bits(const mp_int *a) } declare 13 { mp_err MP_WUR TclBN_mp_div(const mp_int *a, const mp_int *b, mp_int *q, mp_int *r) } declare 14 { mp_err MP_WUR TclBN_mp_div_d(const mp_int *a, mp_digit b, mp_int *q, mp_digit *r) } declare 15 { mp_err MP_WUR TclBN_mp_div_2(const mp_int *a, mp_int *q) } declare 16 { mp_err MP_WUR TclBN_mp_div_2d(const mp_int *a, int b, mp_int *q, mp_int *r) } declare 18 { void TclBN_mp_exch(mp_int *a, mp_int *b) } declare 19 { mp_err MP_WUR TclBN_mp_expt_n(const mp_int *a, int b, mp_int *c) } declare 20 { mp_err MP_WUR TclBN_mp_grow(mp_int *a, int size) } declare 21 { mp_err MP_WUR TclBN_mp_init(mp_int *a) } declare 22 { mp_err MP_WUR TclBN_mp_init_copy(mp_int *a, const mp_int *b) } declare 23 { mp_err MP_WUR TclBN_mp_init_multi(mp_int *a, ...) } declare 24 { mp_err MP_WUR TclBN_mp_init_set(mp_int *a, mp_digit b) } declare 25 { mp_err MP_WUR TclBN_mp_init_size(mp_int *a, int size) } declare 26 { mp_err MP_WUR TclBN_mp_lshd(mp_int *a, int shift) } declare 27 { mp_err MP_WUR TclBN_mp_mod(const mp_int *a, const mp_int *b, mp_int *r) } declare 28 { mp_err MP_WUR TclBN_mp_mod_2d(const mp_int *a, int b, mp_int *r) } declare 29 { mp_err MP_WUR TclBN_mp_mul(const mp_int *a, const mp_int *b, mp_int *p) } declare 30 { mp_err MP_WUR TclBN_mp_mul_d(const mp_int *a, mp_digit b, mp_int *p) } declare 31 { mp_err MP_WUR TclBN_mp_mul_2(const mp_int *a, mp_int *p) } declare 32 { mp_err MP_WUR TclBN_mp_mul_2d(const mp_int *a, int d, mp_int *p) } declare 33 { mp_err MP_WUR TclBN_mp_neg(const mp_int *a, mp_int *b) } declare 34 { mp_err MP_WUR TclBN_mp_or(const mp_int *a, const mp_int *b, mp_int *c) } declare 35 { mp_err MP_WUR TclBN_mp_radix_size(const mp_int *a, int radix, int *size) } declare 36 { mp_err MP_WUR TclBN_mp_read_radix(mp_int *a, const char *str, int radix) } declare 37 { void TclBN_mp_rshd(mp_int *a, int shift) } declare 38 { mp_err MP_WUR TclBN_mp_shrink(mp_int *a) } declare 41 { mp_err MP_WUR TclBN_mp_sqrt(const mp_int *a, mp_int *b) } declare 42 { mp_err MP_WUR TclBN_mp_sub(const mp_int *a, const mp_int *b, mp_int *c) } declare 43 { mp_err MP_WUR TclBN_mp_sub_d(const mp_int *a, mp_digit b, mp_int *c) } declare 47 { size_t MP_WUR TclBN_mp_ubin_size(const mp_int *a) } declare 48 { mp_err MP_WUR TclBN_mp_xor(const mp_int *a, const mp_int *b, mp_int *c) } declare 49 { void TclBN_mp_zero(mp_int *a) } declare 63 { int MP_WUR TclBN_mp_cnt_lsb(const mp_int *a) } declare 65 { int MP_WUR TclBN_mp_init_i64(mp_int *bignum, int64_t initVal) } declare 66 { int MP_WUR TclBN_mp_init_u64(mp_int *bignum, uint64_t initVal) } declare 68 { void TclBN_mp_set_u64(mp_int *a, uint64_t i) } declare 69 { uint64_t MP_WUR TclBN_mp_get_mag_u64(const mp_int *a) } declare 70 { void TclBN_mp_set_i64(mp_int *a, int64_t i) } declare 71 { mp_err MP_WUR TclBN_mp_unpack(mp_int *rop, size_t count, mp_order order, size_t size, mp_endian endian, size_t nails, const void *op) } declare 72 { mp_err MP_WUR TclBN_mp_pack(void *rop, size_t maxcount, size_t *written, mp_order order, size_t size, mp_endian endian, size_t nails, const mp_int *op) } declare 76 { mp_err MP_WUR TclBN_mp_signed_rsh(const mp_int *a, int b, mp_int *c) } declare 77 { size_t MP_WUR TclBN_mp_pack_count(const mp_int *a, size_t nails, size_t size) } # Added in libtommath 1.2.0 declare 78 { int MP_WUR TclBN_mp_to_ubin(const mp_int *a, unsigned char *buf, size_t maxlen, size_t *written) } declare 80 { int MP_WUR TclBN_mp_to_radix(const mp_int *a, char *str, size_t maxlen, size_t *written, int radix) } # Local Variables: # mode: tcl # End: tcl9.0.1/generic/README0000644000175000017500000000030714726623136014052 0ustar sergeisergeiThis directory contains Tcl source files that work on all the platforms where Tcl runs (e.g. UNIX, PCs, and MacOSX). Platform-specific sources are in the directories ../unix, ../win, and ../macosx. tcl9.0.1/generic/tclGetDate.y0000644000175000017500000006212014726623136015405 0ustar sergeisergei/* * tclGetDate.y -- * * Contains yacc grammar for parsing date and time strings. The output of * this file should be the file tclDate.c which is used directly in the * Tcl sources. Note that this file is largely obsolete in Tcl 8.5; it is * only used when doing free-form date parsing, an ill-defined process * anyway. * * Copyright © 1992-1995 Karl Lehenbauer & Mark Diekhans. * Copyright © 1995-1997 Sun Microsystems, Inc. * Copyright © 2015 Sergey G. Brester aka sebres. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ %parse-param {DateInfo* info} %lex-param {DateInfo* info} %define api.pure /* %error-verbose would be nice, but our token names are meaningless */ %locations %{ /* * tclDate.c -- * * This file is generated from a yacc grammar defined in the file * tclGetDate.y. It should not be edited directly. * * Copyright © 1992-1995 Karl Lehenbauer & Mark Diekhans. * Copyright © 1995-1997 Sun Microsystems, Inc. * Copyright © 2015 Sergey G. Brester aka sebres. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * */ #include "tclInt.h" /* * Bison generates several labels that happen to be unused. MS Visual C++ * doesn't like that, and complains. Tell it to shut up. */ #ifdef _MSC_VER #pragma warning( disable : 4102 ) #endif /* _MSC_VER */ #if 0 #define YYDEBUG 1 #endif /* * yyparse will accept a 'struct DateInfo' as its parameter; that's where the * parsed fields will be returned. */ #include "tclDate.h" #define YYMALLOC Tcl_Alloc #define YYFREE(x) (Tcl_Free((void*) (x))) #define EPOCH 1970 #define START_OF_TIME 1902 #define END_OF_TIME 2037 /* * The offset of tm_year of struct tm returned by localtime, gmtime, etc. * Posix requires 1900. */ #define TM_YEAR_BASE 1900 #define HOUR(x) ((60 * (int)(x))) #define IsLeapYear(x) (((x) % 4 == 0) && ((x) % 100 != 0 || (x) % 400 == 0)) #define yyIncrFlags(f) \ do { \ info->errFlags |= (info->flags & (f)); \ if (info->errFlags) { YYABORT; } \ info->flags |= (f); \ } while (0); /* * An entry in the lexical lookup table. */ typedef struct { const char *name; int type; int value; } TABLE; /* * Daylight-savings mode: on, off, or not yet known. */ typedef enum _DSTMODE { DSTon, DSToff, DSTmaybe } DSTMODE; %} %union { Tcl_WideInt Number; enum _MERIDIAN Meridian; } %{ /* * Prototypes of internal functions. */ static int LookupWord(YYSTYPE* yylvalPtr, char *buff); static void TclDateerror(YYLTYPE* location, DateInfo* info, const char *s); static int TclDatelex(YYSTYPE* yylvalPtr, YYLTYPE* location, DateInfo* info); MODULE_SCOPE int yyparse(DateInfo*); %} %token tAGO %token tDAY %token tDAYZONE %token tID %token tMERIDIAN %token tMONTH %token tMONTH_UNIT %token tSTARDATE %token tSEC_UNIT %token tUNUMBER %token tZONE %token tZONEwO4 %token tZONEwO2 %token tEPOCH %token tDST %token tISOBAS8 %token tISOBAS6 %token tISOBASL %token tDAY_UNIT %token tNEXT %token SP %type tDAY %type tDAYZONE %type tMONTH %type tMONTH_UNIT %type tDST %type tSEC_UNIT %type tUNUMBER %type INTNUM %type tZONE %type tZONEwO4 %type tZONEwO2 %type tISOBAS8 %type tISOBAS6 %type tISOBASL %type tDAY_UNIT %type unit %type sign %type tNEXT %type tSTARDATE %type tMERIDIAN %type o_merid %% spec : /* NULL */ | spec item /* | spec SP item */ ; item : time { yyIncrFlags(CLF_TIME); } | zone { yyIncrFlags(CLF_ZONE); } | date { yyIncrFlags(CLF_HAVEDATE); } | ordMonth { yyIncrFlags(CLF_ORDINALMONTH); } | day { yyIncrFlags(CLF_DAYOFWEEK); } | relspec { info->flags |= CLF_RELCONV; } | iso { yyIncrFlags(CLF_TIME|CLF_HAVEDATE); } | trek { yyIncrFlags(CLF_TIME|CLF_HAVEDATE); info->flags |= CLF_RELCONV; } | numitem ; iextime : tUNUMBER ':' tUNUMBER ':' tUNUMBER { yyHour = $1; yyMinutes = $3; yySeconds = $5; } | tUNUMBER ':' tUNUMBER { yyHour = $1; yyMinutes = $3; yySeconds = 0; } ; time : tUNUMBER tMERIDIAN { yyHour = $1; yyMinutes = 0; yySeconds = 0; yyMeridian = $2; } | iextime o_merid { yyMeridian = $2; } ; zone : tZONE tDST { yyTimezone = $1; yyDSTmode = DSTon; } | tZONE { yyTimezone = $1; yyDSTmode = DSToff; } | tDAYZONE { yyTimezone = $1; yyDSTmode = DSTon; } | tZONEwO4 sign INTNUM { /* GMT+0100, GMT-1000, etc. */ yyTimezone = $1 - $2*($3 % 100 + ($3 / 100) * 60); yyDSTmode = DSToff; } | tZONEwO2 sign INTNUM { /* GMT+1, GMT-10, etc. */ yyTimezone = $1 - $2*($3 * 60); yyDSTmode = DSToff; } | sign INTNUM { /* +0100, -0100 */ yyTimezone = -$1*($2 % 100 + ($2 / 100) * 60); yyDSTmode = DSToff; } ; comma : ',' | ',' SP ; day : tDAY { yyDayOrdinal = 1; yyDayOfWeek = $1; } | tDAY comma { yyDayOrdinal = 1; yyDayOfWeek = $1; } | tUNUMBER tDAY { yyDayOrdinal = $1; yyDayOfWeek = $2; } | sign SP tUNUMBER tDAY { yyDayOrdinal = $1 * $3; yyDayOfWeek = $4; } | sign tUNUMBER tDAY { yyDayOrdinal = $1 * $2; yyDayOfWeek = $3; } | tNEXT tDAY { yyDayOrdinal = 2; yyDayOfWeek = $2; } ; iexdate : tUNUMBER '-' tUNUMBER '-' tUNUMBER { yyMonth = $3; yyDay = $5; yyYear = $1; } ; date : tUNUMBER '/' tUNUMBER { yyMonth = $1; yyDay = $3; } | tUNUMBER '/' tUNUMBER '/' tUNUMBER { yyMonth = $1; yyDay = $3; yyYear = $5; } | isodate | tUNUMBER '-' tMONTH '-' tUNUMBER { yyDay = $1; yyMonth = $3; yyYear = $5; } | tMONTH tUNUMBER { yyMonth = $1; yyDay = $2; } | tMONTH tUNUMBER comma tUNUMBER { yyMonth = $1; yyDay = $2; yyYear = $4; } | tUNUMBER tMONTH { yyMonth = $2; yyDay = $1; } | tEPOCH { yyMonth = 1; yyDay = 1; yyYear = EPOCH; } | tUNUMBER tMONTH tUNUMBER { yyMonth = $2; yyDay = $1; yyYear = $3; } ; ordMonth: tNEXT tMONTH { yyMonthOrdinalIncr = 1; yyMonthOrdinal = $2; } | tNEXT tUNUMBER tMONTH { yyMonthOrdinalIncr = $2; yyMonthOrdinal = $3; } ; isosep : 'T'|SP ; isodate : tISOBAS8 { /* YYYYMMDD */ yyYear = $1 / 10000; yyMonth = ($1 % 10000)/100; yyDay = $1 % 100; } | tISOBAS6 { /* YYMMDD */ yyYear = $1 / 10000; yyMonth = ($1 % 10000)/100; yyDay = $1 % 100; } | iexdate ; isotime : tISOBAS6 { yyHour = $1 / 10000; yyMinutes = ($1 % 10000)/100; yySeconds = $1 % 100; } | iextime ; iso : isodate isosep isotime | tISOBASL tISOBAS6 { /* YYYYMMDDhhmmss */ yyYear = $1 / 10000; yyMonth = ($1 % 10000)/100; yyDay = $1 % 100; yyHour = $2 / 10000; yyMinutes = ($2 % 10000)/100; yySeconds = $2 % 100; } | tISOBASL tUNUMBER { /* YYYYMMDDhhmm */ if (yyDigitCount != 4) YYABORT; /* normally unreached */ yyYear = $1 / 10000; yyMonth = ($1 % 10000)/100; yyDay = $1 % 100; yyHour = $2 / 100; yyMinutes = ($2 % 100); yySeconds = 0; } ; trek : tSTARDATE INTNUM '.' tUNUMBER { /* * Offset computed year by -377 so that the returned years will be * in a range accessible with a 32 bit clock seconds value. */ yyYear = $2/1000 + 2323 - 377; yyDay = 1; yyMonth = 1; yyRelDay += (($2%1000)*(365 + IsLeapYear(yyYear)))/1000; yyRelSeconds += $4 * (144LL * 60LL); } ; relspec : relunits tAGO { yyRelSeconds *= -1; yyRelMonth *= -1; yyRelDay *= -1; } | relunits ; relunits : sign SP INTNUM unit { *yyRelPointer += $1 * $3 * $4; } | sign INTNUM unit { *yyRelPointer += $1 * $2 * $3; } | INTNUM unit { *yyRelPointer += $1 * $2; } | tNEXT unit { *yyRelPointer += $2; } | tNEXT INTNUM unit { *yyRelPointer += $2 * $3; } | unit { *yyRelPointer += $1; } ; sign : '-' { $$ = -1; } | '+' { $$ = 1; } ; unit : tSEC_UNIT { $$ = $1; yyRelPointer = &yyRelSeconds; } | tDAY_UNIT { $$ = $1; yyRelPointer = &yyRelDay; } | tMONTH_UNIT { $$ = $1; yyRelPointer = &yyRelMonth; } ; INTNUM : tUNUMBER { $$ = $1; } | tISOBAS6 { $$ = $1; } | tISOBAS8 { $$ = $1; } ; numitem : tUNUMBER { if ((info->flags & (CLF_TIME|CLF_HAVEDATE|CLF_RELCONV)) == (CLF_TIME|CLF_HAVEDATE)) { yyYear = $1; } else { yyIncrFlags(CLF_TIME); if (yyDigitCount <= 2) { yyHour = $1; yyMinutes = 0; } else { yyHour = $1 / 100; yyMinutes = $1 % 100; } yySeconds = 0; yyMeridian = MER24; } } ; o_merid : /* NULL */ { $$ = MER24; } | tMERIDIAN { $$ = $1; } ; %% /* * Month and day table. */ static const TABLE MonthDayTable[] = { { "january", tMONTH, 1 }, { "february", tMONTH, 2 }, { "march", tMONTH, 3 }, { "april", tMONTH, 4 }, { "may", tMONTH, 5 }, { "june", tMONTH, 6 }, { "july", tMONTH, 7 }, { "august", tMONTH, 8 }, { "september", tMONTH, 9 }, { "sept", tMONTH, 9 }, { "october", tMONTH, 10 }, { "november", tMONTH, 11 }, { "december", tMONTH, 12 }, { "sunday", tDAY, 7 }, { "monday", tDAY, 1 }, { "tuesday", tDAY, 2 }, { "tues", tDAY, 2 }, { "wednesday", tDAY, 3 }, { "wednes", tDAY, 3 }, { "thursday", tDAY, 4 }, { "thur", tDAY, 4 }, { "thurs", tDAY, 4 }, { "friday", tDAY, 5 }, { "saturday", tDAY, 6 }, { NULL, 0, 0 } }; /* * Time units table. */ static const TABLE UnitsTable[] = { { "year", tMONTH_UNIT, 12 }, { "month", tMONTH_UNIT, 1 }, { "fortnight", tDAY_UNIT, 14 }, { "week", tDAY_UNIT, 7 }, { "day", tDAY_UNIT, 1 }, { "hour", tSEC_UNIT, 60 * 60 }, { "minute", tSEC_UNIT, 60 }, { "min", tSEC_UNIT, 60 }, { "second", tSEC_UNIT, 1 }, { "sec", tSEC_UNIT, 1 }, { NULL, 0, 0 } }; /* * Assorted relative-time words. */ static const TABLE OtherTable[] = { { "tomorrow", tDAY_UNIT, 1 }, { "yesterday", tDAY_UNIT, -1 }, { "today", tDAY_UNIT, 0 }, { "now", tSEC_UNIT, 0 }, { "last", tUNUMBER, -1 }, { "this", tSEC_UNIT, 0 }, { "next", tNEXT, 1 }, { "ago", tAGO, 1 }, { "epoch", tEPOCH, 0 }, { "stardate", tSTARDATE, 0 }, { NULL, 0, 0 } }; /* * The timezone table. (Note: This table was modified to not use any floating * point constants to work around an SGI compiler bug). */ static const TABLE TimezoneTable[] = { { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */ { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */ { "utc", tZONE, HOUR( 0) }, { "uct", tZONE, HOUR( 0) }, /* Universal Coordinated Time */ { "wet", tZONE, HOUR( 0) }, /* Western European */ { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */ { "wat", tZONE, HOUR( 1) }, /* West Africa */ { "at", tZONE, HOUR( 2) }, /* Azores */ #if 0 /* For completeness. BST is also British Summer, and GST is * also Guam Standard. */ { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */ { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */ #endif { "nft", tZONE, HOUR( 7/2) }, /* Newfoundland */ { "nst", tZONE, HOUR( 7/2) }, /* Newfoundland Standard */ { "ndt", tDAYZONE, HOUR( 7/2) }, /* Newfoundland Daylight */ { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */ { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */ { "est", tZONE, HOUR( 5) }, /* Eastern Standard */ { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */ { "cst", tZONE, HOUR( 6) }, /* Central Standard */ { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */ { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */ { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */ { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */ { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */ { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */ { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */ { "akst", tZONE, HOUR( 9) }, /* Alaska Standard */ { "akdt", tDAYZONE, HOUR( 9) }, /* Alaska Daylight */ { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */ { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */ { "cat", tZONE, HOUR(10) }, /* Central Alaska */ { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */ { "nt", tZONE, HOUR(11) }, /* Nome */ { "idlw", tZONE, HOUR(12) }, /* International Date Line West */ { "cet", tZONE, -HOUR( 1) }, /* Central European */ { "cest", tDAYZONE, -HOUR( 1) }, /* Central European Summer */ { "met", tZONE, -HOUR( 1) }, /* Middle European */ { "mewt", tZONE, -HOUR( 1) }, /* Middle European Winter */ { "mest", tDAYZONE, -HOUR( 1) }, /* Middle European Summer */ { "swt", tZONE, -HOUR( 1) }, /* Swedish Winter */ { "sst", tDAYZONE, -HOUR( 1) }, /* Swedish Summer */ { "fwt", tZONE, -HOUR( 1) }, /* French Winter */ { "fst", tDAYZONE, -HOUR( 1) }, /* French Summer */ { "eet", tZONE, -HOUR( 2) }, /* Eastern Europe, USSR Zone 1 */ { "bt", tZONE, -HOUR( 3) }, /* Baghdad, USSR Zone 2 */ { "it", tZONE, -HOUR( 7/2) }, /* Iran */ { "zp4", tZONE, -HOUR( 4) }, /* USSR Zone 3 */ { "zp5", tZONE, -HOUR( 5) }, /* USSR Zone 4 */ { "ist", tZONE, -HOUR(11/2) }, /* Indian Standard */ { "zp6", tZONE, -HOUR( 6) }, /* USSR Zone 5 */ #if 0 /* For completeness. NST is also Newfoundland Standard, and SST is * also Swedish Summer. */ { "nst", tZONE, -HOUR(13/2) }, /* North Sumatra */ { "sst", tZONE, -HOUR( 7) }, /* South Sumatra, USSR Zone 6 */ #endif /* 0 */ { "wast", tZONE, -HOUR( 7) }, /* West Australian Standard */ { "wadt", tDAYZONE, -HOUR( 7) }, /* West Australian Daylight */ { "jt", tZONE, -HOUR(15/2) }, /* Java (3pm in Cronusland!) */ { "cct", tZONE, -HOUR( 8) }, /* China Coast, USSR Zone 7 */ { "jst", tZONE, -HOUR( 9) }, /* Japan Standard, USSR Zone 8 */ { "jdt", tDAYZONE, -HOUR( 9) }, /* Japan Daylight */ { "kst", tZONE, -HOUR( 9) }, /* Korea Standard */ { "kdt", tDAYZONE, -HOUR( 9) }, /* Korea Daylight */ { "cast", tZONE, -HOUR(19/2) }, /* Central Australian Standard */ { "cadt", tDAYZONE, -HOUR(19/2) }, /* Central Australian Daylight */ { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */ { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */ { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */ { "nzt", tZONE, -HOUR(12) }, /* New Zealand */ { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */ { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */ { "idle", tZONE, -HOUR(12) }, /* International Date Line East */ /* ADDED BY Marco Nijdam */ { "dst", tDST, HOUR( 0) }, /* DST on (hour is ignored) */ /* End ADDED */ { NULL, 0, 0 } }; /* * Military timezone table. */ static const TABLE MilitaryTable[] = { { "a", tZONE, -HOUR( 1) }, { "b", tZONE, -HOUR( 2) }, { "c", tZONE, -HOUR( 3) }, { "d", tZONE, -HOUR( 4) }, { "e", tZONE, -HOUR( 5) }, { "f", tZONE, -HOUR( 6) }, { "g", tZONE, -HOUR( 7) }, { "h", tZONE, -HOUR( 8) }, { "i", tZONE, -HOUR( 9) }, { "k", tZONE, -HOUR(10) }, { "l", tZONE, -HOUR(11) }, { "m", tZONE, -HOUR(12) }, { "n", tZONE, HOUR( 1) }, { "o", tZONE, HOUR( 2) }, { "p", tZONE, HOUR( 3) }, { "q", tZONE, HOUR( 4) }, { "r", tZONE, HOUR( 5) }, { "s", tZONE, HOUR( 6) }, { "t", tZONE, HOUR( 7) }, { "u", tZONE, HOUR( 8) }, { "v", tZONE, HOUR( 9) }, { "w", tZONE, HOUR( 10) }, { "x", tZONE, HOUR( 11) }, { "y", tZONE, HOUR( 12) }, { "z", tZONE, HOUR( 0) }, { NULL, 0, 0 } }; static inline const char * bypassSpaces( const char *s) { while (TclIsSpaceProc(*s)) { s++; } return s; } /* * Dump error messages in the bit bucket. */ static void TclDateerror( YYLTYPE* location, DateInfo* infoPtr, const char *s) { Tcl_Obj* t; if (!infoPtr->messages) { TclNewObj(infoPtr->messages); } Tcl_AppendToObj(infoPtr->messages, infoPtr->separatrix, -1); Tcl_AppendToObj(infoPtr->messages, s, -1); Tcl_AppendToObj(infoPtr->messages, " (characters ", -1); TclNewIntObj(t, location->first_column); Tcl_IncrRefCount(t); Tcl_AppendObjToObj(infoPtr->messages, t); Tcl_DecrRefCount(t); Tcl_AppendToObj(infoPtr->messages, "-", -1); TclNewIntObj(t, location->last_column); Tcl_IncrRefCount(t); Tcl_AppendObjToObj(infoPtr->messages, t); Tcl_DecrRefCount(t); Tcl_AppendToObj(infoPtr->messages, ")", -1); infoPtr->separatrix = "\n"; } int ToSeconds( int Hours, int Minutes, int Seconds, MERIDIAN Meridian) { switch (Meridian) { case MER24: return (Hours * 60 + Minutes) * 60 + Seconds; case MERam: return (((Hours / 24) * 24 + (Hours % 12)) * 60 + Minutes) * 60 + Seconds; case MERpm: return (((Hours / 24) * 24 + (Hours % 12) + 12) * 60 + Minutes) * 60 + Seconds; } return -1; /* Should never be reached */ } static int LookupWord( YYSTYPE* yylvalPtr, char *buff) { char *p; char *q; const TABLE *tp; int i, abbrev; /* * Make it lowercase. */ Tcl_UtfToLower(buff); if (*buff == 'a' && (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0)) { yylvalPtr->Meridian = MERam; return tMERIDIAN; } if (*buff == 'p' && (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0)) { yylvalPtr->Meridian = MERpm; return tMERIDIAN; } /* * See if we have an abbreviation for a month. */ if (strlen(buff) == 3) { abbrev = 1; } else if (strlen(buff) == 4 && buff[3] == '.') { abbrev = 1; buff[3] = '\0'; } else { abbrev = 0; } for (tp = MonthDayTable; tp->name; tp++) { if (abbrev) { if (strncmp(buff, tp->name, 3) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } else if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } for (tp = TimezoneTable; tp->name; tp++) { if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } for (tp = UnitsTable; tp->name; tp++) { if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } /* * Strip off any plural and try the units table again. */ i = strlen(buff) - 1; if (i > 0 && buff[i] == 's') { buff[i] = '\0'; for (tp = UnitsTable; tp->name; tp++) { if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } } for (tp = OtherTable; tp->name; tp++) { if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } /* * Military timezones. */ if (buff[1] == '\0' && !(*buff & 0x80) && isalpha(UCHAR(*buff))) { /* INTL: ISO only */ for (tp = MilitaryTable; tp->name; tp++) { if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } } /* * Drop out any periods and try the timezone table again. */ for (i = 0, p = q = buff; *q; q++) { if (*q != '.') { *p++ = *q; } else { i++; } } *p = '\0'; if (i) { for (tp = TimezoneTable; tp->name; tp++) { if (strcmp(buff, tp->name) == 0) { yylvalPtr->Number = tp->value; return tp->type; } } } return tID; } static int TclDatelex( YYSTYPE* yylvalPtr, YYLTYPE* location, DateInfo *info) { char c; char *p; char buff[20]; int Count; const char *tokStart; location->first_column = yyInput - info->dateStart; for ( ; ; ) { if (isspace(UCHAR(*yyInput))) { yyInput = bypassSpaces(yyInput); /* ignore space at end of text and before some words */ c = *yyInput; if (c != '\0' && !isalpha(UCHAR(c))) { return SP; } } tokStart = yyInput; if (isdigit(UCHAR(c = *yyInput))) { /* INTL: digit */ /* * Count the number of digits. */ p = (char *)yyInput; while (isdigit(UCHAR(*++p))) {}; yyDigitCount = p - yyInput; /* * A number with 12 or 14 digits is considered an ISO 8601 date. */ if (yyDigitCount == 14 || yyDigitCount == 12) { /* long form of ISO 8601 (without separator), either * YYYYMMDDhhmmss or YYYYMMDDhhmm, so reduce to date * (8 chars is isodate) */ p = (char *)yyInput+8; if (TclAtoWIe(&yylvalPtr->Number, yyInput, p, 1) != TCL_OK) { return tID; /* overflow*/ } yyDigitCount = 8; yyInput = p; location->last_column = yyInput - info->dateStart - 1; return tISOBASL; } /* * Convert the string into a number */ if (TclAtoWIe(&yylvalPtr->Number, yyInput, p, 1) != TCL_OK) { return tID; /* overflow*/ } yyInput = p; /* * A number with 6 or more digits is considered an ISO 8601 base. */ location->last_column = yyInput - info->dateStart - 1; if (yyDigitCount >= 6) { if (yyDigitCount == 8) { return tISOBAS8; } if (yyDigitCount == 6) { return tISOBAS6; } } /* ignore spaces after digits (optional) */ yyInput = bypassSpaces(yyInput); return tUNUMBER; } if (!(c & 0x80) && isalpha(UCHAR(c))) { /* INTL: ISO only. */ int ret; for (p = buff; isalpha(UCHAR(c = *yyInput++)) /* INTL: ISO only. */ || c == '.'; ) { if (p < &buff[sizeof(buff) - 1]) { *p++ = c; } } *p = '\0'; yyInput--; location->last_column = yyInput - info->dateStart - 1; ret = LookupWord(yylvalPtr, buff); /* * lookahead: * for spaces to consider word boundaries (for instance * literal T in isodateTisotimeZ is not a TZ, but Z is UTC); * for +/- digit, to differentiate between "GMT+1000 day" and "GMT +1000 day"; * bypass spaces after token (but ignore by TZ+OFFS), because should * recognize next SP token, if TZ only. */ if (ret == tZONE || ret == tDAYZONE) { c = *yyInput; if (isdigit(UCHAR(c))) { /* literal not a TZ */ yyInput = tokStart; return *yyInput++; } if ((c == '+' || c == '-') && isdigit(UCHAR(*(yyInput+1)))) { if ( !isdigit(UCHAR(*(yyInput+2))) || !isdigit(UCHAR(*(yyInput+3)))) { /* GMT+1, GMT-10, etc. */ return tZONEwO2; } if ( isdigit(UCHAR(*(yyInput+4))) && !isdigit(UCHAR(*(yyInput+5)))) { /* GMT+1000, etc. */ return tZONEwO4; } } } yyInput = bypassSpaces(yyInput); return ret; } if (c != '(') { location->last_column = yyInput - info->dateStart; return *yyInput++; } Count = 0; do { c = *yyInput++; if (c == '\0') { location->last_column = yyInput - info->dateStart - 1; return c; } else if (c == '(') { Count++; } else if (c == ')') { Count--; } } while (Count > 0); } } int TclClockFreeScan( Tcl_Interp *interp, /* Tcl interpreter */ DateInfo *info) /* Input and result parameters */ { int status; #if YYDEBUG /* enable debugging if compiled with YYDEBUG */ yydebug = 1; #endif /* * yyInput = stringToParse; * * ClockInitDateInfo(info) should be executed to pre-init info; */ yyDSTmode = DSTmaybe; info->separatrix = ""; info->dateStart = yyInput; /* ignore spaces at begin */ yyInput = bypassSpaces(yyInput); /* parse */ status = yyparse(info); if (status == 1) { const char *msg = NULL; if (info->errFlags & CLF_HAVEDATE) { msg = "more than one date in string"; } else if (info->errFlags & CLF_TIME) { msg = "more than one time of day in string"; } else if (info->errFlags & CLF_ZONE) { msg = "more than one time zone in string"; } else if (info->errFlags & CLF_DAYOFWEEK) { msg = "more than one weekday in string"; } else if (info->errFlags & CLF_ORDINALMONTH) { msg = "more than one ordinal month in string"; } if (msg) { Tcl_SetObjResult(interp, Tcl_NewStringObj(msg, -1)); Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "MULTIPLE", (char *)NULL); } else { Tcl_SetObjResult(interp, info->messages ? info->messages : Tcl_NewObj()); info->messages = NULL; Tcl_SetErrorCode(interp, "TCL", "VALUE", "DATE", "PARSE", (char *)NULL); } status = TCL_ERROR; } else if (status == 2) { Tcl_SetObjResult(interp, Tcl_NewStringObj("memory exhausted", -1)); Tcl_SetErrorCode(interp, "TCL", "MEMORY", (char *)NULL); status = TCL_ERROR; } else if (status != 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj("Unknown status returned " "from date parser. Please " "report this error as a " "bug in Tcl.", -1)); Tcl_SetErrorCode(interp, "TCL", "BUG", (char *)NULL); status = TCL_ERROR; } if (info->messages) { Tcl_DecrRefCount(info->messages); } return status; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */ tcl9.0.1/changes.md0000644000175000017500000002253614731047047013515 0ustar sergeisergei The source code for Tcl is managed by fossil. Tcl developers coordinate all changes to the Tcl source code at > [Tcl Source Code](https://core.tcl-lang.org/tcl/timeline) Release Tcl 9.0.1 arises from the check-in with tag `core-9-0-1`. Tcl patch releases have the primary purpose of delivering bug fixes to the userbase. As the first patch release in the Tcl 9.0.\* series, Tcl 9.0.1 also includes a small number of interface changes that complete some incomplete features first delivered in Tcl 9.0.0. # Completed 9.0 Features and Interfaces - [TIP 701 - Tcl_FSTildeExpand C API](https://core.tcl-lang.org/tips/doc/trunk/tip/701.md) - [TIP 707 - ptrAndSize internal rep in Tcl_Obj](https://core.tcl-lang.org/tips/doc/trunk/tip/707.md) - [Size modifiers j, q, z, t not implemented]( https://core.tcl-lang.org/tcl/info/c4f365) # Bug fixes - [regression in tzdata, %z instead of offset TZ-name](https://core.tcl-lang.org/tcl/tktview/2c237b) - [Tcl will not start properly if there is an init.tcl file in the current dir](https://core.tcl-lang.org/tcl/tktview/43c94f) - [clock scan "24:00", ISO-8601 compatibility](https://core.tcl-lang.org/tcl/tktview/aee9f2) - [Temporary folder with file "tcl9registry13.dll" remains after "exit"](https://core.tcl-lang.org/tcl/tktview/6ce3c0) - [Wrong result by "lsearch -stride -subindices -inline -all"](https://core.tcl-lang.org/tcl/info/5a1aaa) - [TIP 609 - required Tcl_ThreadAlert() skipped with nested event loop](https://core.tcl-lang.org/tcl/info/c7e4c4) - [buffer overwrite for non-BMP characters in utf-16](https://core.tcl-lang.org/tcl/tktview/66da4d) - [zipfs info on mountpoint of executable returns zero offset in field 4"](https://core.tcl-lang.org/tcl/info/aaa84f) - [zlib-8.8, zlib-8.16 fail on Fedora 40, gcc 14.1.1](https://core.tcl-lang.org/tcl/tktview/73d5cb) - [install registry and dde in $INSTALL_DIR\lib always](https://core.tcl-lang.org/tcl/tktview/364bd9) - [cannot build .chm help file (Windows)](https://core.tcl-lang.org/tcl/tktview/bb110c) # Incompatibilities - No known incompatibilities with the Tcl 9.0.0 public interface. # Updated bundled packages, libraries, standards, data - Itcl 4.3.2 - sqlite3 3.47.2 - Thread 3.0.1 - TDBC\* 1.1.10 - tcltest 2.5.9 - tzdata 2024b, corrected Release Tcl 9.0.0 arises from the check-in with tag `core-9-0-0`. Highlighted differences between Tcl 9.0 and Tcl 8.6 are summarized below, with focus on changes important to programmers using the Tcl library and writing Tcl scripts. # Major Features ## 64-bit capacity: Data values larger than 2Gb - Strings can be any length (that fits in your available memory) - Lists and dictionaries can have very large numbers of elements ## Internationalization of text - Full Unicode range of codepoints - New encodings: `utf-16`/`utf-32`/`ucs-2`(`le`|`be`), `CESU-8`, etc. - `encoding` options `-profile`, `-failindex` manage encoding of I/O. - `msgcat` supports custom locale search list - `source` defaults to `-encoding utf-8` ## Zip filesystems and attached archives. - Packaging of the Tcl script library with the Tcl binary library, meaning that the `TCL_LIBRARY` environment variable is usually not required. - Packaging of an application into a virtual filesystem is now a supported core Tcl feature. ## Unix notifiers available using `epoll()` or `kqueue()` - This relieves limits on file descriptors imposed by legacy `select()` and fixes a performance bottleneck. # Incompatibilities ## Notable incompatibilities - Unqualified varnames resolved in current namespace, not global. Note that in almost all cases where this causes a change, the change is actually the removal of a latent bug. - No `--disable-threads` build option. Always thread-enabled. - I/O malencoding default response: raise error (`-profile strict`) - Windows platform needs Windows 7 or Windows Server 2008 R2 or later - Ended interpretation of `~` as home directory in pathnames. (See `file home` and `file tildeexpand` for replacements when you need them.) - Removed the `identity` encoding. (There were only ever very few valid use cases for this; almost all uses were systematically wrong.) - Removed the encoding alias `binary` to `iso8859-1`. - `$::tcl_precision` no longer controls string generation of doubles. (If you need a particular precision, use `format`.) - Removed pre-Tcl 8 legacies: `case`, `puts` and `read` variant syntaxes. - Removed subcommands [`trace variable`|`vdelete`|`vinfo`] - Removed `-eofchar` option for write channels. - On Windows 10+ (Version 1903 or higher), system encoding is always utf-8. - `%b`/`%d`/`%o`/`%x` format modifiers (without size modifier) for `format` and `scan` always truncate to 32-bits on all platforms. - `%L` size modifier for `scan` no longer truncates to 64-bit. - Removed command `::tcl::unsupported::inject`. (See `coroinject` and `coroprobe` for supported commands with significantly more comprehensible semantics.) ## Incompatibilities in C public interface - Extensions built against Tcl 8.6 and before will not work with Tcl 9.0; ABI compatibility was a non-goal for 9.0. In _most_ cases, rebuilding against Tcl 9.0 should work except when a removed API function is used. - Many arguments expanded type from `int` to `Tcl_Size`, a signed integer type large enough to support 64-bit sized memory objects. The constant `TCL_AUTO_LENGTH` is a value of that type that indicates that the length should be obtained using an appropriate function (typically `strlen()` for `char *` values). - Ended support for `Tcl_ChannelTypeVersion` less than 5 - Introduced versioning of the `Tcl_ObjType` struct - Removed macros `CONST*`: Tcl 9 support means dropping Tcl 8.3 support. (Replaced with standard C `const` keyword going forward.) - Removed registration of several `Tcl_ObjType`s. - Removed API functions: `Tcl_Backslash()`, `Tcl_*VA()`, `Tcl_*MathFunc*()`, `Tcl_MakeSafe()`, `Tcl_(Save|Restore|Discard|Free)Result()`, `Tcl_EvalTokens()`, `Tcl_(Get|Set)DefaultEncodingDir()`, `Tcl_UniCharN(case)cmp()`, `Tcl_UniCharCaseMatch()` - Revised many internals; beware reliance on undocumented behaviors. # New Features ## New commands - `array default` — Specify default values for arrays (note that this alters the behaviour of `append`, `incr`, `lappend`). - `array for` — Cheap iteration over an array's contents. - `chan isbinary` — Test if a channel is configured to work with binary data. - `coroinject`, `coroprobe` — Interact with paused coroutines. - `clock add weekdays` — Clock arithmetic with week days. - `const`, `info const*` — Commands for defining constants (variables that can't be modified). - `dict getwithdefault` — Define a fallback value to use when `dict get` would otherwise fail. - `file home` — Get the user home directory. - `file tempdir` — Create a temporary directory. - `file tildeexpand` — Expand a file path containing a `~`. - `info commandtype` — Introspection for the kinds of commands. - `ledit` — Equivalent to `lreplace` but on a list in a variable. - `lpop` — Remove an item from a list in a variable. - `lremove` — Remove a sublist from a list in a variable. - `lseq` — Generate a list of numbers in a sequence. - `package files` — Describe the contents of a package. - `string insert` — Insert a string as a substring of another string. - `string is dict` — Test whether a string is a dictionary. - `tcl::process` — Commands for working with subprocesses. - `*::build-info` — Obtain information about the build of Tcl. - `readFile`, `writeFile`, `foreachLine` — Simple procedures for basic working with files. - `tcl::idna::*` — Commands for working with encoded DNS names. ## New command options - `chan configure ... -inputmode ...` — Support for raw terminal input and reading passwords. - `clock scan ... -validate ...` - `info loaded ... ?prefix?` - `lsearch ... -stride ...` — Search a list by groups of items. - `regsub ... -command ...` — Generate the replacement for a regular expression by calling a command. - `socket ... -nodelay ... -keepalive ...` - `vwait` controlled by several new options - `expr` string comparators `lt`, `gt`, `le`, `ge` - `expr` supports comments inside expressions ## Numbers - 0NNN format is no longer octal interpretation. Use 0oNNN. - 0dNNNN format to compel decimal interpretation. - NN_NNN_NNN, underscores in numbers for optional readability - Functions: `isinf()`, `isnan()`, `isnormal()`, `issubnormal()`, `isunordered()` - Command: `fpclassify` - Function `int()` no longer truncates to word size ## TclOO facilities - private variables and methods - class variables and methods - abstract and singleton classes - configurable properties - `method -export`, `method -unexport` # Known bugs - [changed behaviour wrt command names, namespaces and resolution](https://core.tcl-lang.org/tcl/tktview/f14b33) - [windows dos device paths inconsistencies and missing functionality](https://core.tcl-lang.org/tcl/tktview/d8f121) - [load library (dll) from zipfs-library causes a leak in temporary folder](https://core.tcl-lang.org/tcl/tktview/a8e4f7) - [lsearch -sorted -inline -subindices incorrect result](https://core.tcl-lang.org/tcl/tktview/bc4ac0) - ["No error" when load fails due to a missing secondary DLL](https://core.tcl-lang.org/tcl/tktview/bc4ac0) tcl9.0.1/README.md0000644000175000017500000001751114726623136013042 0ustar sergeisergei# README: Tcl This is the **Tcl 9.0.1** source distribution. You can get any source release of Tcl from [our distribution site](https://sourceforge.net/projects/tcl/files/Tcl/). 9.0 (production release, daily build) [![Build Status](https://github.com/tcltk/tcl/actions/workflows/linux-build.yml/badge.svg?branch=main)](https://github.com/tcltk/tcl/actions/workflows/linux-build.yml?query=branch%3Amain) [![Build Status](https://github.com/tcltk/tcl/actions/workflows/win-build.yml/badge.svg?branch=main)](https://github.com/tcltk/tcl/actions/workflows/win-build.yml?query=branch%3Amain) [![Build Status](https://github.com/tcltk/tcl/actions/workflows/mac-build.yml/badge.svg?branch=main)](https://github.com/tcltk/tcl/actions/workflows/mac-build.yml?query=branch%3Amain)
8.7 (in development, daily build) [![Build Status](https://github.com/tcltk/tcl/actions/workflows/linux-build.yml/badge.svg?branch=core-8-branch)](https://github.com/tcltk/tcl/actions/workflows/linux-build.yml?query=branch%3Acore-8-branch) [![Build Status](https://github.com/tcltk/tcl/actions/workflows/win-build.yml/badge.svg?branch=core-8-branch)](https://github.com/tcltk/tcl/actions/workflows/win-build.yml?query=branch%3Acore-8-branch) [![Build Status](https://github.com/tcltk/tcl/actions/workflows/mac-build.yml/badge.svg?branch=core-8-branch)](https://github.com/tcltk/tcl/actions/workflows/mac-build.yml?query=branch%3Acore-8-branch) ## Contents 1. [Introduction](#intro) 2. [Documentation](#doc) 3. [Compiling and installing Tcl](#build) 4. [Development tools](#devtools) 5. [Tcl newsgroup](#complangtcl) 6. [The Tcler's Wiki](#wiki) 7. [Mailing lists](#email) 8. [Support and Training](#support) 9. [Tracking Development](#watch) 10. [Thank You](#thanks) ## 1. Introduction Tcl provides a powerful platform for creating integration applications that tie together diverse applications, protocols, devices, and frameworks. When paired with the Tk toolkit, Tcl provides the fastest and most powerful way to create GUI applications that run on PCs, Unix, and Mac OS X. Tcl can also be used for a variety of web-related tasks and for creating powerful command languages for applications. Tcl is maintained, enhanced, and distributed freely by the Tcl community. Source code development and tracking of bug reports and feature requests take place at [core.tcl-lang.org](https://core.tcl-lang.org/). Tcl/Tk release and mailing list services are [hosted by SourceForge](https://sourceforge.net/projects/tcl/) with the Tcl Developer Xchange hosted at [www.tcl-lang.org](https://www.tcl-lang.org). Tcl is a freely available open-source package. You can do virtually anything you like with it, such as modifying it, redistributing it, and selling it either in whole or in part. See the file `license.terms` for complete information. ## 2. Documentation Extensive documentation is available on our website. The home page for this release, including new features, is [here](https://www.tcl-lang.org/software/tcltk/9.0.html). Detailed release notes can be found at the [file distributions page](https://sourceforge.net/projects/tcl/files/Tcl/) by clicking on the relevant version. Information about Tcl itself can be found at the [Developer Xchange](https://www.tcl-lang.org/about/). There have been many Tcl books on the market. Many are mentioned in [the Wiki](https://wiki.tcl-lang.org/_/ref?N=25206). The complete set of reference manual entries for Tcl 9.0 is [online, here](https://www.tcl-lang.org/man/tcl9.0/). ### 2a. Unix Documentation The `doc` subdirectory in this release contains a complete set of reference manual entries for Tcl. Files with extension "`.1`" are for programs (for example, `tclsh.1`); files with extension "`.3`" are for C library procedures; and files with extension "`.n`" describe Tcl commands. The file "`doc/Tcl.n`" gives a quick summary of the Tcl language syntax. To print any of the man pages on Unix, cd to the "doc" directory and invoke your favorite variant of troff using the normal -man macros, for example groff -man -Tpdf Tcl.n >output.pdf to print Tcl.n to PDF. If Tcl has been installed correctly and your "man" program supports it, you should be able to access the Tcl manual entries using the normal "man" mechanisms, such as man Tcl ### 2b. Windows Documentation The "doc" subdirectory in this release contains a complete set of Windows help files for Tcl. Once you install this Tcl release, a shortcut to the Windows help Tcl documentation will appear in the "Start" menu: Start | Programs | Tcl | Tcl Help ## 3. Compiling and installing Tcl There are brief notes in the `unix/README`, `win/README`, and `macosx/README` about compiling on these different platforms. There is additional information about building Tcl from sources [online](https://www.tcl-lang.org/doc/howto/compile.html). ## 4. Development tools ActiveState produces a high-quality set of commercial quality development tools that is available to accelerate your Tcl application development. Tcl Dev Kit builds on the earlier TclPro toolset and provides a debugger, static code checker, single-file wrapping utility, bytecode compiler, and more. More information can be found at https://www.activestate.com/products/tcl/ ## 5. Tcl newsgroup There is a USENET newsgroup, "`comp.lang.tcl`", intended for the exchange of information about Tcl, Tk, and related applications. The newsgroup is a great place to ask general information questions. For bug reports, please see the "Support and bug fixes" section below. ## 6. Tcl'ers Wiki There is a [wiki-based open community site](https://wiki.tcl-lang.org/) covering all aspects of Tcl/Tk. It is dedicated to the Tcl programming language and its extensions. A wealth of useful information can be found there. It contains code snippets, references to papers, books, and FAQs, as well as pointers to development tools, extensions, and applications. You can also recommend additional URLs by editing the wiki yourself. ## 7. Mailing lists Several mailing lists are hosted at SourceForge to discuss development or use issues (like Macintosh and Windows topics). For more information and to subscribe, visit [here](https://sourceforge.net/projects/tcl/) and go to the Mailing Lists page. ## 8. Support and Training We are very interested in receiving bug reports, patches, and suggestions for improvements. We prefer that you send this information to us as tickets entered into [our issue tracker](https://core.tcl-lang.org/tcl/reportlist). We will log and follow-up on each bug, although we cannot promise a specific turn-around time. Enhancements may take longer and may not happen at all unless there is widespread support for them (we're trying to slow the rate at which Tcl/Tk turns into a kitchen sink). It's very difficult to make incompatible changes to Tcl/Tk at this point, due to the size of the installed base. The Tcl community is too large for us to provide much individual support for users. If you need help we suggest that you post questions to `comp.lang.tcl` or ask a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/tcl). We read the newsgroup and will attempt to answer esoteric questions for which no one else is likely to know the answer. In addition, see the wiki for [links to other organizations](https://wiki.tcl-lang.org/training) that offer Tcl/Tk training. ## 9. Tracking Development Tcl is developed in public. You can keep an eye on how Tcl is changing at [core.tcl-lang.org](https://core.tcl-lang.org/). ## 10. Thank You We'd like to express our thanks to the Tcl community for all the helpful suggestions, bug reports, and patches we have received. Tcl/Tk has improved vastly and will continue to do so with your help. tcl9.0.1/license.terms0000644000175000017500000000431714726623136014261 0ustar sergeisergeiThis software is copyrighted by the Regents of the University of California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState Corporation and other parties. The following terms apply to all files associated with the software unless explicitly disclaimed in individual files. The authors hereby grant permission to use, copy, modify, distribute, and license this software and its documentation for any purpose, provided that existing copyright notices are retained in all copies and that this notice is included verbatim in any distributions. No written agreement, license, or royalty fee is required for any of the authorized uses. Modifications to this software may be copyrighted by their authors and need not follow the licensing terms described here, provided that the new terms are clearly indicated on the first page of each file where they apply. IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. GOVERNMENT USE: If you are acquiring this software on behalf of the U.S. government, the Government shall have only "Restricted Rights" in the software and related documentation as defined in the Federal Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you are acquiring the software on behalf of the Department of Defense, the software shall be classified as "Commercial Computer Software" and the Government shall have only "Restricted Rights" as defined in Clause 252.227-7014 (b) (3) of DFARs. Notwithstanding the foregoing, the authors grant the U.S. Government and others acting in its behalf permission to use and distribute the software in accordance with the terms specified in this license. tcl9.0.1/library/0000755000175000017500000000000014731057546013224 5ustar sergeisergeitcl9.0.1/library/license.terms0000644000175000017500000000431714726623136015725 0ustar sergeisergeiThis software is copyrighted by the Regents of the University of California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState Corporation and other parties. The following terms apply to all files associated with the software unless explicitly disclaimed in individual files. The authors hereby grant permission to use, copy, modify, distribute, and license this software and its documentation for any purpose, provided that existing copyright notices are retained in all copies and that this notice is included verbatim in any distributions. No written agreement, license, or royalty fee is required for any of the authorized uses. Modifications to this software may be copyrighted by their authors and need not follow the licensing terms described here, provided that the new terms are clearly indicated on the first page of each file where they apply. IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. GOVERNMENT USE: If you are acquiring this software on behalf of the U.S. government, the Government shall have only "Restricted Rights" in the software and related documentation as defined in the Federal Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you are acquiring the software on behalf of the Department of Defense, the software shall be classified as "Commercial Computer Software" and the Government shall have only "Restricted Rights" as defined in Clause 252.227-7014 (b) (3) of DFARs. Notwithstanding the foregoing, the authors grant the U.S. Government and others acting in its behalf permission to use and distribute the software in accordance with the terms specified in this license. tcl9.0.1/library/auto.tcl0000644000175000017500000005462414726623136014711 0ustar sergeisergei# auto.tcl -- # # utility procs formerly in init.tcl dealing with auto execution of commands # and can be auto loaded themselves. # # Copyright © 1991-1993 The Regents of the University of California. # Copyright © 1994-1998 Sun Microsystems, Inc. # # See the file "license.terms" for information on usage and redistribution of # this file, and for a DISCLAIMER OF ALL WARRANTIES. # # auto_reset -- # # Destroy all cached information for auto-loading and auto-execution, so that # the information gets recomputed the next time it's needed. Also delete any # commands that are listed in the auto-load index. # # Arguments: # None. proc auto_reset {} { global auto_execs auto_index auto_path if {[array exists auto_index]} { foreach cmdName [array names auto_index] { set fqcn [namespace which $cmdName] if {$fqcn eq ""} { continue } rename $fqcn {} } } unset -nocomplain auto_execs auto_index ::tcl::auto_oldpath if {[catch {llength $auto_path}]} { set auto_path [list [info library]] } elseif {[info library] ni $auto_path} { lappend auto_path [info library] } } # tcl_findLibrary -- # # This is a utility for extensions that searches for a library directory # using a canonical searching algorithm. A side effect is to source the # initialization script and set a global library variable. # # Arguments: # basename Prefix of the directory name, (e.g., "tk") # version Version number of the package, (e.g., "8.0") # patch Patchlevel of the package, (e.g., "8.0.3") # initScript Initialization script to source (e.g., tk.tcl) # enVarName environment variable to honor (e.g., TK_LIBRARY) # varName Global variable to set when done (e.g., tk_library) proc tcl_findLibrary {basename version patch initScript enVarName varName} { upvar #0 $varName the_library global auto_path env tcl_platform set dirs {} set errors {} # The C application may have hardwired a path, which we honor if {[info exists the_library] && $the_library ne ""} { lappend dirs $the_library } else { # Do the canonical search # 1. From an environment variable, if it exists. Placing this first # gives the end-user ultimate control to work-around any bugs, or # to customize. if {[info exists env($enVarName)]} { lappend dirs $env($enVarName) } catch { set found 0 set root [zipfs root] set mountpoint [file join $root lib $basename] lappend dirs [file join $root app ${basename}_library] lappend dirs [file join $root lib ${basename} ${basename}_library] lappend dirs [file join $root lib ${basename}] if {![zipfs exists [file join $root app ${basename}_library]] \ && ![zipfs exists $mountpoint]} { set found 0 foreach pkgdat [info loaded] { lassign $pkgdat dllfile dllpkg if {$dllpkg ne $basename} continue if {$dllfile eq {}} { # Loaded statically break } set found 1 zipfs mount $dllfile $mountpoint break } if {!$found} { set paths {} if {![catch {::${basename}::pkgconfig get libdir,runtime} dir]} { lappend paths $dir } else { catch {lappend paths [::tcl::pkgconfig get libdir,runtime]} } if {![catch {::${basename}::pkgconfig get bindir,runtime} dir]} { lappend paths $dir } else { catch {lappend paths [::tcl::pkgconfig get bindir,runtime]} } if {[catch {::${basename}::pkgconfig get dllfile,runtime} dllfile]} { set dllfile "libtcl9${basename}${version}[info sharedlibextension]" } set dir [file dirname [file join [pwd] [info nameofexecutable]]] lappend paths $dir lappend paths [file join [file dirname $dir] lib] foreach path $paths { set archive [file join $path $dllfile] if {![file exists $archive]} { continue } zipfs mount $archive $mountpoint if {[zipfs exists [file join $mountpoint ${basename}_library $initScript]]} { lappend dirs [file join $mountpoint ${basename}_library] set found 1 break } elseif {[zipfs exists [file join $mountpoint $initScript]]} { lappend dirs [file join $mountpoint $initScript] set found 1 break } else { catch {zipfs unmount $mountpoint} } } } } } # 2. In the package script directory registered within the # configuration of the package itself. catch { lappend dirs [::${basename}::pkgconfig get scriptdir,runtime] } # 3. Relative to auto_path directories. This checks relative to the # Tcl library as well as allowing loading of libraries added to the # auto_path that is not relative to the core library or binary paths. foreach d $auto_path { lappend dirs [file join $d $basename$version] if {$tcl_platform(platform) eq "unix" && $tcl_platform(os) eq "Darwin"} { # 4. On MacOSX, check the Resources/Scripts subdir too lappend dirs [file join $d $basename$version Resources Scripts] } } # 3. Various locations relative to the executable # ../lib/foo1.0 (From bin directory in install hierarchy) # ../../lib/foo1.0 (From bin/arch directory in install hierarchy) # ../library (From unix directory in build hierarchy) # # Remaining locations are out of date (when relevant, they ought to be # covered by the $::auto_path seach above) and disabled. # # ../../library (From unix/arch directory in build hierarchy) # ../../foo1.0.1/library # (From unix directory in parallel build hierarchy) # ../../../foo1.0.1/library # (From unix/arch directory in parallel build hierarchy) set parentDir [file dirname [file dirname [info nameofexecutable]]] set grandParentDir [file dirname $parentDir] lappend dirs [file join $parentDir lib $basename$version] lappend dirs [file join $grandParentDir lib $basename$version] lappend dirs [file join $parentDir library] if {0} { lappend dirs [file join $grandParentDir library] lappend dirs [file join $grandParentDir $basename$patch library] lappend dirs [file join [file dirname $grandParentDir] \ $basename$patch library] } } # make $dirs unique, preserving order array set seen {} foreach i $dirs { # Make sure $i is unique under normalization. Avoid repeated [source]. if {[interp issafe]} { # Safe interps have no [file normalize]. set norm $i } else { set norm [file normalize $i] } if {[info exists seen($norm)]} { continue } set seen($norm) {} set the_library $i set file [file join $i $initScript] # source everything when in a safe interpreter because we have a # source command, but no file exists command if {[interp issafe] || [file exists $file]} { if {![catch {uplevel #0 [list source $file]} msg opts]} { return } append errors "$file: $msg\n" append errors [dict get $opts -errorinfo]\n } } unset -nocomplain the_library set msg "Can't find a usable $initScript in the following directories: \n" append msg " $dirs\n\n" append msg "$errors\n\n" append msg "This probably means that $basename wasn't installed properly.\n" error $msg } # ---------------------------------------------------------------------- # auto_mkindex # ---------------------------------------------------------------------- # The following procedures are used to generate the tclIndex file from Tcl # source files. They use a special safe interpreter to parse Tcl source # files, writing out index entries as "proc" commands are encountered. This # implementation won't work in a safe interpreter, since a safe interpreter # can't create the special parser and mess with its commands. if {[interp issafe]} { return ;# Stop sourcing the file here } # auto_mkindex -- # Regenerate a tclIndex file from Tcl source files. Takes as argument the # name of the directory in which the tclIndex file is to be placed, followed # by any number of glob patterns to use in that directory to locate all of the # relevant files. # # Arguments: # dir - Name of the directory in which to create an index. # args - Any number of additional arguments giving the names of files # within dir. If no additional are given auto_mkindex will look # for *.tcl. proc auto_mkindex {dir args} { if {[interp issafe]} { error "can't generate index within safe interpreter" } set oldDir [pwd] cd $dir append index "# Tcl autoload index file, version 2.0\n" append index "# This file is generated by the \"auto_mkindex\" command\n" append index "# and sourced to set up indexing information for one or\n" append index "# more commands. Typically each line is a command that\n" append index "# sets an element in the auto_index array, where the\n" append index "# element name is the name of a command and the value is\n" append index "# a script that loads the command.\n\n" if {![llength $args]} { set args *.tcl } auto_mkindex_parser::init foreach file [lsort [glob -- {*}$args]] { try { append index [auto_mkindex_parser::mkindex $file] } on error {msg opts} { cd $oldDir return -options $opts $msg } } auto_mkindex_parser::cleanup set fid [open "tclIndex" w] fconfigure $fid -encoding utf-8 -translation lf puts -nonewline $fid $index close $fid cd $oldDir } # Original version of auto_mkindex that just searches the source code for # "proc" at the beginning of the line. proc auto_mkindex_old {dir args} { set oldDir [pwd] cd $dir set dir [pwd] append index "# Tcl autoload index file, version 2.0\n" append index "# This file is generated by the \"auto_mkindex\" command\n" append index "# and sourced to set up indexing information for one or\n" append index "# more commands. Typically each line is a command that\n" append index "# sets an element in the auto_index array, where the\n" append index "# element name is the name of a command and the value is\n" append index "# a script that loads the command.\n\n" if {![llength $args]} { set args *.tcl } foreach file [lsort [glob -- {*}$args]] { set f "" set error [catch { set f [open $file] fconfigure $f -encoding utf-8 -eofchar \x1A while {[gets $f line] >= 0} { if {[regexp {^proc[ ]+([^ ]*)} $line match procName]} { set procName [lindex [auto_qualify $procName "::"] 0] append index "set [list auto_index($procName)]" append index " \[list source -encoding utf-8 \[file join \$dir [list $file]\]\]\n" } } close $f } msg opts] if {$error} { catch {close $f} cd $oldDir return -options $opts $msg } } set f "" set error [catch { set f [open tclIndex w] fconfigure $f -encoding utf-8 -translation lf puts -nonewline $f $index close $f cd $oldDir } msg opts] if {$error} { catch {close $f} cd $oldDir error $msg $info $code return -options $opts $msg } } # Create a safe interpreter that can be used to parse Tcl source files # generate a tclIndex file for autoloading. This interp contains commands for # things that need index entries. Each time a command is executed, it writes # an entry out to the index file. namespace eval auto_mkindex_parser { variable parser "" ;# parser used to build index variable index "" ;# maintains index as it is built variable scriptFile "" ;# name of file being processed variable contextStack "" ;# stack of namespace scopes variable imports "" ;# keeps track of all imported cmds variable initCommands ;# list of commands that create aliases if {![info exists initCommands]} { set initCommands [list] } proc init {} { variable parser variable initCommands if {![interp issafe]} { set parser [interp create -safe] $parser hide info $parser hide rename $parser hide proc $parser hide namespace $parser hide eval $parser hide puts foreach ns [$parser invokehidden namespace children ::] { # MUST NOT DELETE "::tcl" OR BAD THINGS HAPPEN! if {$ns eq "::tcl"} continue $parser invokehidden namespace delete $ns } foreach cmd [$parser invokehidden info commands ::*] { $parser invokehidden rename $cmd {} } $parser invokehidden proc unknown {args} {} # We'll need access to the "namespace" command within the # interp. Put it back, but move it out of the way. $parser expose namespace $parser invokehidden rename namespace _%@namespace $parser expose eval $parser invokehidden rename eval _%@eval # Install all the registered pseudo-command implementations foreach cmd $initCommands { eval $cmd } } } proc cleanup {} { variable parser interp delete $parser unset parser } } # auto_mkindex_parser::mkindex -- # # Used by the "auto_mkindex" command to create a "tclIndex" file for the given # Tcl source file. Executes the commands in the file, and handles things like # the "proc" command by adding an entry for the index file. Returns a string # that represents the index file. # # Arguments: # file Name of Tcl source file to be indexed. proc auto_mkindex_parser::mkindex {file} { variable parser variable index variable scriptFile variable contextStack variable imports set scriptFile $file set fid [open $file] fconfigure $fid -encoding utf-8 -eofchar \x1A set contents [read $fid] close $fid # There is one problem with sourcing files into the safe interpreter: # references like "$x" will fail since code is not really being executed # and variables do not really exist. To avoid this, we replace all $ with # \0 (literally, the null char) later, when getting proc names we will # have to reverse this replacement, in case there were any $ in the proc # name. This will cause a problem if somebody actually tries to have a \0 # in their proc name. Too bad for them. set contents [string map [list \$ \0] $contents] set index "" set contextStack "" set imports "" $parser eval $contents foreach name $imports { catch {$parser eval [list _%@namespace forget $name]} } return $index } # auto_mkindex_parser::hook command # # Registers a Tcl command to evaluate when initializing the child interpreter # used by the mkindex parser. The command is evaluated in the parent # interpreter, and can use the variable auto_mkindex_parser::parser to get to # the child proc auto_mkindex_parser::hook {cmd} { variable initCommands lappend initCommands $cmd } # auto_mkindex_parser::childhook command # # Registers a Tcl command to evaluate when initializing the child interpreter # used by the mkindex parser. The command is evaluated in the child # interpreter. proc auto_mkindex_parser::childhook {cmd} { variable initCommands # The $parser variable is defined to be the name of the child interpreter # when this command is used later. lappend initCommands "\$parser eval [list $cmd]" } # auto_mkindex_parser::command -- # # Registers a new command with the "auto_mkindex_parser" interpreter that # parses Tcl files. These commands are fake versions of things like the # "proc" command. When you execute them, they simply write out an entry to a # "tclIndex" file for auto-loading. # # This procedure allows extensions to register their own commands with the # auto_mkindex facility. For example, a package like [incr Tcl] might # register a "class" command so that class definitions could be added to a # "tclIndex" file for auto-loading. # # Arguments: # name Name of command recognized in Tcl files. # arglist Argument list for command. # body Implementation of command to handle indexing. proc auto_mkindex_parser::command {name arglist body} { hook [list auto_mkindex_parser::commandInit $name $arglist $body] } # auto_mkindex_parser::commandInit -- # # This does the actual work set up by auto_mkindex_parser::command. This is # called when the interpreter used by the parser is created. # # Arguments: # name Name of command recognized in Tcl files. # arglist Argument list for command. # body Implementation of command to handle indexing. proc auto_mkindex_parser::commandInit {name arglist body} { variable parser set ns [namespace qualifiers $name] set tail [namespace tail $name] if {$ns eq ""} { set fakeName [namespace current]::_%@fake_$tail } else { set fakeName [namespace current]::[string map {:: _} _%@fake_$name] } proc $fakeName $arglist $body # YUK! Tcl won't let us alias fully qualified command names, so we can't # handle names like "::itcl::class". Instead, we have to build procs with # the fully qualified names, and have the procs point to the aliases. if {[string match *::* $name]} { set exportCmd [list _%@namespace export [namespace tail $name]] $parser eval [list _%@namespace eval $ns $exportCmd] # The following proc definition does not work if you want to tolerate # space or something else diabolical in the procedure name, (i.e., # space in $alias). The following does not work: # "_%@eval {$alias} \$args" # because $alias gets concat'ed to $args. The following does not work # because $cmd is somehow undefined # "set cmd {$alias} \; _%@eval {\$cmd} \$args" # A gold star to someone that can make test autoMkindex-3.3 work # properly set alias [namespace tail $fakeName] $parser invokehidden proc $name {args} "_%@eval {$alias} \$args" $parser alias $alias $fakeName } else { $parser alias $name $fakeName } return } # auto_mkindex_parser::fullname -- # # Used by commands like "proc" within the auto_mkindex parser. Returns the # qualified namespace name for the "name" argument. If the "name" does not # start with "::", elements are added from the current namespace stack to # produce a qualified name. Then, the name is examined to see whether or not # it should really be qualified. If the name has more than the leading "::", # it is returned as a fully qualified name. Otherwise, it is returned as a # simple name. That way, the Tcl autoloader will recognize it properly. # # Arguments: # name - Name that is being added to index. proc auto_mkindex_parser::fullname {name} { variable contextStack if {![string match ::* $name]} { foreach ns $contextStack { set name "${ns}::$name" if {[string match ::* $name]} { break } } } if {[namespace qualifiers $name] eq ""} { set name [namespace tail $name] } elseif {![string match ::* $name]} { set name "::$name" } # Earlier, mkindex replaced all $'s with \0. Now, we have to reverse that # replacement. return [string map [list \0 \$] $name] } # auto_mkindex_parser::indexEntry -- # # Used by commands like "proc" within the auto_mkindex parser to add a # correctly-quoted entry to the index. This is shared code so it is done # *right*, in one place. # # Arguments: # name - Name that is being added to index. proc auto_mkindex_parser::indexEntry {name} { variable index variable scriptFile # We convert all metacharacters to their backslashed form, and pre-split # the file name that we know about (which will be a proper list, and so # correctly quoted). set name [string range [list \}[fullname $name]] 2 end] set filenameParts [file split $scriptFile] append index [format \ {set auto_index(%s) [list source -encoding utf-8 [file join $dir %s]]%s} \ $name $filenameParts \n] return } if {[llength $::auto_mkindex_parser::initCommands]} { return } # Register all of the procedures for the auto_mkindex parser that will build # the "tclIndex" file. # AUTO MKINDEX: proc name arglist body # Adds an entry to the auto index list for the given procedure name. auto_mkindex_parser::command proc {name args} { indexEntry $name } # Conditionally add support for Tcl byte code files. There are some tricky # details here. First, we need to get the tbcload library initialized in the # current interpreter. We cannot load tbcload into the child until we have # done so because it needs access to the tcl_patchLevel variable. Second, # because the package index file may defer loading the library until we invoke # a command, we need to explicitly invoke auto_load to force it to be loaded. # This should be a noop if the package has already been loaded auto_mkindex_parser::hook { try { package require tbcload } on error {} { # OK, don't have it so do nothing } on ok {} { if {[namespace which -command tbcload::bcproc] eq ""} { auto_load tbcload::bcproc } load {} tbcload $auto_mkindex_parser::parser # AUTO MKINDEX: tbcload::bcproc name arglist body # Adds an entry to the auto index list for the given precompiled # procedure name. auto_mkindex_parser::commandInit tbcload::bcproc {name args} { indexEntry $name } } } # AUTO MKINDEX: namespace eval name command ?arg arg...? # Adds the namespace name onto the context stack and evaluates the associated # body of commands. # # AUTO MKINDEX: namespace import ?-force? pattern ?pattern...? # Performs the "import" action in the parser interpreter. This is important # for any commands contained in a namespace that affect the index. For # example, a script may say "itcl::class ...", or it may import "itcl::*" and # then say "class ...". This procedure does the import operation, but keeps # track of imported patterns so we can remove the imports later. auto_mkindex_parser::command namespace {op args} { switch -- $op { eval { variable parser variable contextStack set name [lindex $args 0] set args [lrange $args 1 end] set contextStack [linsert $contextStack 0 $name] $parser eval [list _%@namespace eval $name] $args set contextStack [lrange $contextStack 1 end] } import { variable parser variable imports foreach pattern $args { if {$pattern ne "-force"} { lappend imports $pattern } } catch {$parser eval "_%@namespace import $args"} } ensemble { variable parser variable contextStack if {[lindex $args 0] eq "create"} { set name ::[join [lreverse $contextStack] ::] catch { set name [dict get [lrange $args 1 end] -command] if {![string match ::* $name]} { set name ::[join [lreverse $contextStack] ::]$name } regsub -all ::+ $name :: name } # create artificial proc to force an entry in the tclIndex $parser eval [list ::proc $name {} {}] } } } } # AUTO MKINDEX: oo::class create name ?definition? # Adds an entry to the auto index list for the given class name. auto_mkindex_parser::command oo::class {op name {body ""}} { if {$op eq "create"} { indexEntry $name } } auto_mkindex_parser::command class {op name {body ""}} { if {$op eq "create"} { indexEntry $name } } return tcl9.0.1/library/clock.tcl0000644000175000017500000016613414726623136015034 0ustar sergeisergei#---------------------------------------------------------------------- # # clock.tcl -- # # This file implements the portions of the [clock] ensemble that are # coded in Tcl. Refer to the users' manual to see the description of # the [clock] command and its subcommands. # # #---------------------------------------------------------------------- # # Copyright © 2004-2007 Kevin B. Kenny # Copyright © 2015 Sergey G. Brester aka sebres. # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # #---------------------------------------------------------------------- # msgcat 1.7 features are used. package require msgcat 1.7 # Put the library directory into the namespace for the ensemble so that the # library code can find message catalogs and time zone definition files. namespace eval ::tcl::clock \ [list variable LibDir [info library]] #---------------------------------------------------------------------- # # clock -- # # Manipulate times. # # The 'clock' command manipulates time. Refer to the user documentation for # the available subcommands and what they do. # #---------------------------------------------------------------------- namespace eval ::tcl::clock { # Export the subcommands namespace export format namespace export clicks namespace export microseconds namespace export milliseconds namespace export scan namespace export seconds namespace export add # Import the message catalog commands that we use. namespace import ::msgcat::mclocale namespace import ::msgcat::mcpackagelocale } #---------------------------------------------------------------------- # # ::tcl::clock::Initialize -- # # Finish initializing the 'clock' subsystem # # Results: # None. # # Side effects: # Namespace variable in the 'clock' subsystem are initialized. # # The '::tcl::clock::Initialize' procedure initializes the namespace variables # and root locale message catalog for the 'clock' subsystem. It is broken # into a procedure rather than simply evaluated as a script so that it will be # able to use local variables, avoiding the dangers of 'creative writing' as # in Bug 1185933. # #---------------------------------------------------------------------- proc ::tcl::clock::Initialize {} { rename ::tcl::clock::Initialize {} variable LibDir # Define the Greenwich time zone proc InitTZData {} { variable TZData array unset TZData set TZData(:Etc/GMT) { {-9223372036854775808 0 0 GMT} } set TZData(:GMT) $TZData(:Etc/GMT) set TZData(:Etc/UTC) { {-9223372036854775808 0 0 UTC} } set TZData(:UTC) $TZData(:Etc/UTC) set TZData(:localtime) {} } InitTZData mcpackagelocale set {} ::msgcat::mcpackageconfig set mcfolder [file join $LibDir msgs] ::msgcat::mcpackageconfig set unknowncmd "" ::msgcat::mcpackageconfig set changecmd ChangeCurrentLocale # Define the message catalog for the root locale. ::msgcat::mcmset {} { AM {am} BCE {B.C.E.} CE {C.E.} DATE_FORMAT {%m/%d/%Y} DATE_TIME_FORMAT {%a %b %e %H:%M:%S %Y} DAYS_OF_WEEK_ABBREV { Sun Mon Tue Wed Thu Fri Sat } DAYS_OF_WEEK_FULL { Sunday Monday Tuesday Wednesday Thursday Friday Saturday } GREGORIAN_CHANGE_DATE 2299161 LOCALE_DATE_FORMAT {%m/%d/%Y} LOCALE_DATE_TIME_FORMAT {%a %b %e %H:%M:%S %Y} LOCALE_ERAS {} LOCALE_NUMERALS { 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 } LOCALE_TIME_FORMAT {%H:%M:%S} LOCALE_YEAR_FORMAT {%EC%Ey} MONTHS_ABBREV { Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec } MONTHS_FULL { January February March April May June July August September October November December } PM {pm} TIME_FORMAT {%H:%M:%S} TIME_FORMAT_12 {%I:%M:%S %P} TIME_FORMAT_24 {%H:%M} TIME_FORMAT_24_SECS {%H:%M:%S} } # Define a few Gregorian change dates for other locales. In most cases # the change date follows a language, because a nation's colonies changed # at the same time as the nation itself. In many cases, different # national boundaries existed; the dominating rule is to follow the # nation's capital. # Italy, Spain, Portugal, Poland ::msgcat::mcset it GREGORIAN_CHANGE_DATE 2299161 ::msgcat::mcset es GREGORIAN_CHANGE_DATE 2299161 ::msgcat::mcset pt GREGORIAN_CHANGE_DATE 2299161 ::msgcat::mcset pl GREGORIAN_CHANGE_DATE 2299161 # France, Austria ::msgcat::mcset fr GREGORIAN_CHANGE_DATE 2299227 # For Belgium, we follow Southern Netherlands; Liege Diocese changed # several weeks later. ::msgcat::mcset fr_BE GREGORIAN_CHANGE_DATE 2299238 ::msgcat::mcset nl_BE GREGORIAN_CHANGE_DATE 2299238 # Austria ::msgcat::mcset de_AT GREGORIAN_CHANGE_DATE 2299527 # Hungary ::msgcat::mcset hu GREGORIAN_CHANGE_DATE 2301004 # Germany, Norway, Denmark (Catholic Germany changed earlier) ::msgcat::mcset de_DE GREGORIAN_CHANGE_DATE 2342032 ::msgcat::mcset nb GREGORIAN_CHANGE_DATE 2342032 ::msgcat::mcset nn GREGORIAN_CHANGE_DATE 2342032 ::msgcat::mcset no GREGORIAN_CHANGE_DATE 2342032 ::msgcat::mcset da GREGORIAN_CHANGE_DATE 2342032 # Holland (Brabant, Gelderland, Flanders, Friesland, etc. changed at # various times) ::msgcat::mcset nl GREGORIAN_CHANGE_DATE 2342165 # Protestant Switzerland (Catholic cantons changed earlier) ::msgcat::mcset fr_CH GREGORIAN_CHANGE_DATE 2361342 ::msgcat::mcset it_CH GREGORIAN_CHANGE_DATE 2361342 ::msgcat::mcset de_CH GREGORIAN_CHANGE_DATE 2361342 # English speaking countries ::msgcat::mcset en GREGORIAN_CHANGE_DATE 2361222 # Sweden (had several changes onto and off of the Gregorian calendar) ::msgcat::mcset sv GREGORIAN_CHANGE_DATE 2361390 # Russia ::msgcat::mcset ru GREGORIAN_CHANGE_DATE 2421639 # Romania (Transylvania changed earlier - perhaps de_RO should show the # earlier date?) ::msgcat::mcset ro GREGORIAN_CHANGE_DATE 2422063 # Greece ::msgcat::mcset el GREGORIAN_CHANGE_DATE 2423480 #------------------------------------------------------------------ # # CONSTANTS # #------------------------------------------------------------------ # Paths at which binary time zone data for the Olson libraries are known # to reside on various operating systems variable ZoneinfoPaths {} foreach path { /usr/share/zoneinfo /usr/share/lib/zoneinfo /usr/lib/zoneinfo /usr/local/etc/zoneinfo } { if { [file isdirectory $path] } { lappend ZoneinfoPaths $path } } # Define the directories for time zone data and message catalogs. variable DataDir [file join $LibDir tzdata] # Number of days in the months, in common years and leap years. variable DaysInRomanMonthInCommonYear \ { 31 28 31 30 31 30 31 31 30 31 30 31 } variable DaysInRomanMonthInLeapYear \ { 31 29 31 30 31 30 31 31 30 31 30 31 } variable DaysInPriorMonthsInCommonYear [list 0] variable DaysInPriorMonthsInLeapYear [list 0] set i 0 foreach j $DaysInRomanMonthInCommonYear { lappend DaysInPriorMonthsInCommonYear [incr i $j] } set i 0 foreach j $DaysInRomanMonthInLeapYear { lappend DaysInPriorMonthsInLeapYear [incr i $j] } # Another epoch (Hi, Jeff!) variable Roddenberry 1946 # Integer ranges variable MINWIDE -9223372036854775808 variable MAXWIDE 9223372036854775807 # Day before Leap Day variable FEB_28 58 # Default configuration ::tcl::unsupported::clock::configure -current-locale [mclocale] #::tcl::unsupported::clock::configure -default-locale C #::tcl::unsupported::clock::configure -year-century 2000 \ # -century-switch 38 # Translation table to map Windows TZI onto cities, so that the Olson # rules can apply. In some cases the mapping is ambiguous, so it's wise # to specify $::env(TCL_TZ) rather than simply depending on the system # time zone. # The keys are long lists of values obtained from the time zone # information in the Registry. In order, the list elements are: # Bias StandardBias DaylightBias # StandardDate.wYear StandardDate.wMonth StandardDate.wDayOfWeek # StandardDate.wDay StandardDate.wHour StandardDate.wMinute # StandardDate.wSecond StandardDate.wMilliseconds # DaylightDate.wYear DaylightDate.wMonth DaylightDate.wDayOfWeek # DaylightDate.wDay DaylightDate.wHour DaylightDate.wMinute # DaylightDate.wSecond DaylightDate.wMilliseconds # The values are the names of time zones where those rules apply. There # is considerable ambiguity in certain zones; an attempt has been made to # make a reasonable guess, but this table needs to be taken with a grain # of salt. variable WinZoneInfo [dict create {*}{ {-43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Kwajalein {-39600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Midway {-36000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Honolulu {-32400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Anchorage {-28800 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Los_Angeles {-28800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Tijuana {-25200 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Denver {-25200 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Chihuahua {-25200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Phoenix {-21600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Regina {-21600 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Chicago {-21600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Mexico_City {-18000 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/New_York {-18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Indianapolis {-14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Caracas {-14400 0 3600 0 3 6 2 23 59 59 999 0 10 6 2 23 59 59 999} :America/Santiago {-14400 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Manaus {-14400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Halifax {-12600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/St_Johns {-10800 0 3600 0 2 0 2 2 0 0 0 0 10 0 3 2 0 0 0} :America/Sao_Paulo {-10800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Godthab {-10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Buenos_Aires {-10800 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Bahia {-10800 0 3600 0 3 0 2 2 0 0 0 0 10 0 1 2 0 0 0} :America/Montevideo {-7200 0 3600 0 9 0 5 2 0 0 0 0 3 0 5 2 0 0 0} :America/Noronha {-3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Atlantic/Azores {-3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Atlantic/Cape_Verde {0 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :UTC {0 0 3600 0 10 0 5 2 0 0 0 0 3 0 5 1 0 0 0} :Europe/London {3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa/Kinshasa {3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :CET {7200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa/Harare {7200 0 3600 0 9 4 5 23 59 59 0 0 4 4 5 23 59 59 0} :Africa/Cairo {7200 0 3600 0 10 0 5 4 0 0 0 0 3 0 5 3 0 0 0} :Europe/Helsinki {7200 0 3600 0 9 0 3 2 0 0 0 0 3 5 5 2 0 0 0} :Asia/Jerusalem {7200 0 3600 0 9 0 5 1 0 0 0 0 3 0 5 0 0 0 0} :Europe/Bucharest {7200 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe/Athens {7200 0 3600 0 9 5 5 1 0 0 0 0 3 4 5 0 0 0 0} :Asia/Amman {7200 0 3600 0 10 6 5 23 59 59 999 0 3 0 5 0 0 0 0} :Asia/Beirut {7200 0 -3600 0 4 0 1 2 0 0 0 0 9 0 1 2 0 0 0} :Africa/Windhoek {10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Riyadh {10800 0 3600 0 10 0 1 4 0 0 0 0 4 0 1 3 0 0 0} :Asia/Baghdad {10800 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe/Moscow {12600 0 3600 0 9 2 4 2 0 0 0 0 3 0 1 2 0 0 0} :Asia/Tehran {14400 0 3600 0 10 0 5 5 0 0 0 0 3 0 5 4 0 0 0} :Asia/Baku {14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Muscat {14400 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Tbilisi {16200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Kabul {18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Karachi {18000 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Yekaterinburg {19800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Calcutta {20700 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Katmandu {21600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Dhaka {21600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Novosibirsk {23400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Rangoon {25200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Bangkok {25200 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Krasnoyarsk {28800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Chongqing {28800 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Irkutsk {32400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Tokyo {32400 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Yakutsk {34200 0 3600 0 3 0 5 3 0 0 0 0 10 0 5 2 0 0 0} :Australia/Adelaide {34200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Australia/Darwin {36000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Australia/Brisbane {36000 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Vladivostok {36000 0 3600 0 3 0 5 3 0 0 0 0 10 0 1 2 0 0 0} :Australia/Hobart {36000 0 3600 0 3 0 5 3 0 0 0 0 10 0 5 2 0 0 0} :Australia/Sydney {39600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Noumea {43200 0 3600 0 3 0 3 3 0 0 0 0 10 0 1 2 0 0 0} :Pacific/Auckland {43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Fiji {46800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Tongatapu }] # Legacy time zones, used primarily for parsing RFC822 dates. variable LegacyTimeZone [dict create \ gmt +0000 \ ut +0000 \ utc +0000 \ bst +0100 \ wet +0000 \ wat -0100 \ at -0200 \ nft -0330 \ nst -0330 \ ndt -0230 \ ast -0400 \ adt -0300 \ est -0500 \ edt -0400 \ cst -0600 \ cdt -0500 \ mst -0700 \ mdt -0600 \ pst -0800 \ pdt -0700 \ yst -0900 \ ydt -0800 \ akst -0900 \ akdt -0800 \ hst -1000 \ hdt -0900 \ cat -1000 \ ahst -1000 \ nt -1100 \ idlw -1200 \ cet +0100 \ cest +0200 \ met +0100 \ mewt +0100 \ mest +0200 \ swt +0100 \ sst +0200 \ fwt +0100 \ fst +0200 \ eet +0200 \ eest +0300 \ bt +0300 \ it +0330 \ zp4 +0400 \ zp5 +0500 \ ist +0530 \ zp6 +0600 \ wast +0700 \ wadt +0800 \ jt +0730 \ cct +0800 \ jst +0900 \ kst +0900 \ cast +0930 \ jdt +1000 \ kdt +1000 \ cadt +1030 \ east +1000 \ eadt +1030 \ gst +1000 \ nzt +1200 \ nzst +1200 \ nzdt +1300 \ idle +1200 \ a +0100 \ b +0200 \ c +0300 \ d +0400 \ e +0500 \ f +0600 \ g +0700 \ h +0800 \ i +0900 \ k +1000 \ l +1100 \ m +1200 \ n -0100 \ o -0200 \ p -0300 \ q -0400 \ r -0500 \ s -0600 \ t -0700 \ u -0800 \ v -0900 \ w -1000 \ x -1100 \ y -1200 \ z +0000 \ ] # Caches variable LocFmtMap [dict create]; # Dictionary with localized format maps variable TimeZoneBad [dict create]; # Dictionary whose keys are time zone # names and whose values are 1 if # the time zone is unknown and 0 # if it is known. variable TZData; # Array whose keys are time zone names # and whose values are lists of quads # comprising start time, UTC offset, # Daylight Saving Time indicator, and # time zone abbreviation. variable mcLocales [dict create]; # Dictionary with loaded locales variable mcMergedCat [dict create]; # Dictionary with merged locale catalogs } ::tcl::clock::Initialize #---------------------------------------------------------------------- # mcget -- # # Return the merged translation catalog for the ::tcl::clock namespace # Searching of catalog is similar to "msgcat::mc". # # Contrary to "msgcat::mc" may additionally load a package catalog # on demand. # # Arguments: # loc The locale used for translation. # # Results: # Returns the dictionary object as whole catalog of the package/locale. # proc ::tcl::clock::mcget {loc} { variable mcMergedCat switch -- $loc system { set loc [GetSystemLocale] } current { set loc [mclocale] } if {$loc ne {}} { set loc [string tolower $loc] } # try to retrieve now if already available: if {[dict exists $mcMergedCat $loc]} { return [dict get $mcMergedCat $loc] } # get locales list for given locale (de_de -> {de_de de {}}) variable mcLocales if {[dict exists $mcLocales $loc]} { set loclist [dict get $mcLocales $loc] } else { # save current locale: set prevloc [mclocale] # lazy load catalog on demand (set it will load the catalog) mcpackagelocale set $loc set loclist [msgcat::mcutil::getpreferences $loc] dict set $mcLocales $loc $loclist # restore: if {$prevloc ne $loc} { mcpackagelocale set $prevloc } } # get whole catalog: mcMerge $loclist } # mcMerge -- # # Merge message catalog dictionaries to one dictionary. # # Arguments: # locales List of locales to merge. # # Results: # Returns the (weak pointer) to merged dictionary of message catalog. # proc ::tcl::clock::mcMerge {locales} { variable mcMergedCat if {[dict exists $mcMergedCat [set loc [lindex $locales 0]]]} { return [dict get $mcMergedCat $loc] } # package msgcat currently does not provide possibility to get whole catalog: upvar ::msgcat::Msgs Msgs set ns ::tcl::clock # Merge sequential locales (in reverse order, e. g. {} -> en -> en_en): if {[llength $locales] > 1} { set mrgcat [mcMerge [lrange $locales 1 end]] if {[dict exists $Msgs $ns $loc]} { set mrgcat [dict merge $mrgcat [dict get $Msgs $ns $loc]] dict set mrgcat L $loc } else { # be sure a duplicate is created, don't overwrite {} (common) locale: set mrgcat [dict merge $mrgcat [dict create L $loc]] } } else { if {[dict exists $Msgs $ns $loc]} { set mrgcat [dict get $Msgs $ns $loc] dict set mrgcat L $loc } else { # be sure a duplicate is created, don't overwrite {} (common) locale: set mrgcat [dict create L $loc] } } dict set mcMergedCat $loc $mrgcat # return smart reference (shared dict as object with exact one ref-counter) return $mrgcat } #---------------------------------------------------------------------- # # GetSystemLocale -- # # Determines the system locale, which corresponds to "system" # keyword for locale parameter of 'clock' command. # # Parameters: # None. # # Results: # Returns the system locale. # # Side effects: # None. # #---------------------------------------------------------------------- proc ::tcl::clock::GetSystemLocale {} { if { $::tcl_platform(platform) ne {windows} } { # On a non-windows platform, the 'system' locale is the same as # the 'current' locale return [mclocale] } # On a windows platform, the 'system' locale is adapted from the # 'current' locale by applying the date and time formats from the # Control Panel. First, load the 'current' locale if it's not yet # loaded mcpackagelocale set [mclocale] # Make a new locale string for the system locale, and get the # Control Panel information set locale [mclocale]_windows if { ! [mcpackagelocale present $locale] } { LoadWindowsDateTimeFormats $locale } return $locale } #---------------------------------------------------------------------- # # EnterLocale -- # # Switch [mclocale] to a given locale if necessary # # Parameters: # locale -- Desired locale # # Results: # Returns the locale that was previously current. # # Side effects: # Does [mclocale]. If necessary, loades the designated locale's files. # #---------------------------------------------------------------------- proc ::tcl::clock::EnterLocale { locale } { switch -- $locale system { set locale [GetSystemLocale] } current { set locale [mclocale] } # Select the locale, eventually load it mcpackagelocale set $locale return $locale } #---------------------------------------------------------------------- # # _hasRegistry -- # # Helper that checks whether registry module is available (Windows only) # and loads it on demand. # #---------------------------------------------------------------------- proc ::tcl::clock::_hasRegistry {} { set res 0 if { $::tcl_platform(platform) eq {windows} } { if { [catch { package require registry 1.3 }] } { # try to load registry directly from root (if uninstalled / development env): if {[regexp {[/\\]library$} [info library]]} {catch { load [lindex \ [glob -tails -directory [file dirname [info nameofexecutable]] \ tcl9registry*[expr {[::tcl::pkgconfig get debug] ? {g} : {}}].dll] 0 \ ] Registry }} } if { [namespace which -command ::registry] ne "" } { set res 1 } } proc ::tcl::clock::_hasRegistry {} [list return $res] return $res } #---------------------------------------------------------------------- # # LoadWindowsDateTimeFormats -- # # Load the date/time formats from the Control Panel in Windows and # convert them so that they're usable by Tcl. # # Parameters: # locale - Name of the locale in whose message catalog # the converted formats are to be stored. # # Results: # None. # # Side effects: # Updates the given message catalog with the locale strings. # # Presumes that on entry, [mclocale] is set to the current locale, so that # default strings can be obtained if the Registry query fails. # #---------------------------------------------------------------------- proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } { # Bail out if we can't find the Registry if { ![_hasRegistry] } return if { ![catch { registry get "HKEY_CURRENT_USER\\Control Panel\\International" \ sShortDate } string] } { set quote {} set datefmt {} foreach { unquoted quoted } [split $string '] { append datefmt $quote [string map { dddd %A ddd %a dd %d d %e MMMM %B MMM %b MM %m M %N yyyy %Y yy %y y %y gg {} } $unquoted] if { $quoted eq {} } { set quote ' } else { set quote $quoted } } ::msgcat::mcset $locale DATE_FORMAT $datefmt } if { ![catch { registry get "HKEY_CURRENT_USER\\Control Panel\\International" \ sLongDate } string] } { set quote {} set ldatefmt {} foreach { unquoted quoted } [split $string '] { append ldatefmt $quote [string map { dddd %A ddd %a dd %d d %e MMMM %B MMM %b MM %m M %N yyyy %Y yy %y y %y gg {} } $unquoted] if { $quoted eq {} } { set quote ' } else { set quote $quoted } } ::msgcat::mcset $locale LOCALE_DATE_FORMAT $ldatefmt } if { ![catch { registry get "HKEY_CURRENT_USER\\Control Panel\\International" \ sTimeFormat } string] } { set quote {} set timefmt {} foreach { unquoted quoted } [split $string '] { append timefmt $quote [string map { HH %H H %k hh %I h %l mm %M m %M ss %S s %S tt %p t %p } $unquoted] if { $quoted eq {} } { set quote ' } else { set quote $quoted } } ::msgcat::mcset $locale TIME_FORMAT $timefmt } catch { ::msgcat::mcset $locale DATE_TIME_FORMAT "$datefmt $timefmt" } catch { ::msgcat::mcset $locale LOCALE_DATE_TIME_FORMAT "$ldatefmt $timefmt" } return } #---------------------------------------------------------------------- # # LocalizeFormat -- # # Map away locale-dependent format groups in a clock format. # # Parameters: # locale -- Current [mclocale] locale, supplied to avoid # an extra call # format -- Format supplied to [clock scan] or [clock format] # mcd -- Message catalog dictionary for current locale (read-only, # don't store it to avoid shared references). # # Results: # Returns the string with locale-dependent composite format groups # substituted out. # # Side effects: # None. # #---------------------------------------------------------------------- proc ::tcl::clock::LocalizeFormat { locale format mcd } { variable LocFmtMap # get map list cached or build it: if {[dict exists $LocFmtMap $locale]} { set mlst [dict get $LocFmtMap $locale] } else { # Handle locale-dependent format groups by mapping them out of the format # string. Note that the order of the [string map] operations is # significant because later formats can refer to later ones; for example # %c can refer to %X, which in turn can refer to %T. set mlst { %% %% %D %m/%d/%Y %+ {%a %b %e %H:%M:%S %Z %Y} } lappend mlst %EY [string map $mlst [dict get $mcd LOCALE_YEAR_FORMAT]] lappend mlst %T [string map $mlst [dict get $mcd TIME_FORMAT_24_SECS]] lappend mlst %R [string map $mlst [dict get $mcd TIME_FORMAT_24]] lappend mlst %r [string map $mlst [dict get $mcd TIME_FORMAT_12]] lappend mlst %X [string map $mlst [dict get $mcd TIME_FORMAT]] lappend mlst %EX [string map $mlst [dict get $mcd LOCALE_TIME_FORMAT]] lappend mlst %x [string map $mlst [dict get $mcd DATE_FORMAT]] lappend mlst %Ex [string map $mlst [dict get $mcd LOCALE_DATE_FORMAT]] lappend mlst %c [string map $mlst [dict get $mcd DATE_TIME_FORMAT]] lappend mlst %Ec [string map $mlst [dict get $mcd LOCALE_DATE_TIME_FORMAT]] dict set LocFmtMap $locale $mlst } # translate copy of format (don't use format object here, because otherwise # it can lose its internal representation (string map - convert to unicode) set locfmt [string map $mlst [string range " $format" 1 end]] # Save original format as long as possible, because of internal # representation (performance). # Note that in this case such format will be never localized (also # using another locales). To prevent this return a duplicate (but # it may be slower). if {$locfmt eq $format} { set locfmt $format } return $locfmt } #---------------------------------------------------------------------- # # GetSystemTimeZone -- # # Determines the system time zone, which is the default for the # 'clock' command if no other zone is supplied. # # Parameters: # None. # # Results: # Returns the system time zone. # # Side effects: # Stores the system time zone in engine configuration, since # determining it may be an expensive process. # #---------------------------------------------------------------------- proc ::tcl::clock::GetSystemTimeZone {} { variable TimeZoneBad if {[set result [getenv TCL_TZ]] ne {}} { set timezone $result } elseif {[set result [getenv TZ]] ne {}} { set timezone $result } else { # ask engine for the cached timezone: set timezone [::tcl::unsupported::clock::configure -system-tz] if { $timezone ne "" } { return $timezone } if { $::tcl_platform(platform) eq {windows} } { set timezone [GuessWindowsTimeZone] } elseif { [file exists /etc/localtime] && ![catch {ReadZoneinfoFile \ Tcl/Localtime /etc/localtime}] } { set timezone :Tcl/Localtime } else { set timezone :localtime } } if { ![dict exists $TimeZoneBad $timezone] } { catch {set timezone [SetupTimeZone $timezone]} } if { [dict exists $TimeZoneBad $timezone] } { set timezone :localtime } # tell backend - current system timezone: ::tcl::unsupported::clock::configure -system-tz $timezone return $timezone } #---------------------------------------------------------------------- # # SetupTimeZone -- # # Given the name or specification of a time zone, sets up its in-memory # data. # # Parameters: # tzname - Name of a time zone # # Results: # Unless the time zone is ':localtime', sets the TZData array to contain # the lookup table for local<->UTC conversion. Returns an error if the # time zone cannot be parsed. # #---------------------------------------------------------------------- proc ::tcl::clock::SetupTimeZone { timezone {alias {}} } { variable TZData if {! [info exists TZData($timezone)] } { variable TimeZoneBad if { [dict exists $TimeZoneBad $timezone] } { return -code error \ -errorcode [list CLOCK badTimeZone $timezone] \ "time zone \"$timezone\" not found" } variable MINWIDE if { [regexp {^([-+])(\d\d)(?::?(\d\d)(?::?(\d\d))?)?} $timezone \ -> s hh mm ss] } then { # Make a fixed offset ::scan $hh %d hh if { $mm eq {} } { set mm 0 } else { ::scan $mm %d mm } if { $ss eq {} } { set ss 0 } else { ::scan $ss %d ss } set offset [expr { ( $hh * 60 + $mm ) * 60 + $ss }] if { $s eq {-} } { set offset [expr { - $offset }] } set TZData($timezone) [list [list $MINWIDE $offset -1 $timezone]] } elseif { [string index $timezone 0] eq {:} } { # Convert using a time zone file if { [catch { LoadTimeZoneFile [string range $timezone 1 end] }] && [catch { LoadZoneinfoFile [string range $timezone 1 end] } ret opts] } then { dict unset opts -errorinfo if {[lindex [dict get $opts -errorcode] 0] ne "CLOCK"} { dict set opts -errorcode [list CLOCK badTimeZone $timezone] set ret "time zone \"$timezone\" not found: $ret" } dict set TimeZoneBad $timezone 1 return -options $opts $ret } } elseif { ![catch {ParsePosixTimeZone $timezone} tzfields] } { # This looks like a POSIX time zone - try to process it if { [catch {ProcessPosixTimeZone $tzfields} ret opts] } { dict unset opts -errorinfo if {[lindex [dict get $opts -errorcode] 0] ne "CLOCK"} { dict set opts -errorcode [list CLOCK badTimeZone $timezone] set ret "time zone \"$timezone\" not found: $ret" } dict set TimeZoneBad $timezone 1 return -options $opts $ret } else { set TZData($timezone) $ret } } else { variable LegacyTimeZone # We couldn't parse this as a POSIX time zone. Try again with a # time zone file - this time without a colon if { [catch { LoadTimeZoneFile $timezone }] && [catch { LoadZoneinfoFile $timezone } ret opts] } { # Check may be a legacy zone: if { $alias eq {} && ![catch { set tzname [dict get $LegacyTimeZone [string tolower $timezone]] }] } { set tzname [::tcl::clock::SetupTimeZone $tzname $timezone] set TZData($timezone) $TZData($tzname) # tell backend - timezone is initialized and return shared timezone object: return [::tcl::unsupported::clock::configure -setup-tz $timezone] } dict unset opts -errorinfo if {[lindex [dict get $opts -errorcode] 0] ne "CLOCK"} { dict set opts -errorcode [list CLOCK badTimeZone $timezone] set ret "time zone \"$timezone\" not found: $ret" } dict set TimeZoneBad $timezone 1 return -options $opts $ret } set TZData($timezone) $TZData(:$timezone) } } # tell backend - timezone is initialized and return shared timezone object: ::tcl::unsupported::clock::configure -setup-tz $timezone } #---------------------------------------------------------------------- # # GuessWindowsTimeZone -- # # Determines the system time zone on windows. # # Parameters: # None. # # Results: # Returns a time zone specifier that corresponds to the system time zone # information found in the Registry. # # Bugs: # Fixed dates for DST change are unimplemented at present, because no # time zone information supplied with Windows actually uses them! # # On a Windows system where neither $env(TCL_TZ) nor $env(TZ) is specified, # GuessWindowsTimeZone looks in the Registry for the system time zone # information. It then attempts to find an entry in WinZoneInfo for a time # zone that uses the same rules. If it finds one, it returns it; otherwise, # it constructs a Posix-style time zone string and returns that. # #---------------------------------------------------------------------- proc ::tcl::clock::GuessWindowsTimeZone {} { variable WinZoneInfo variable TimeZoneBad if { ![_hasRegistry] } { return :localtime } # Dredge time zone information out of the registry if { [catch { set rpath HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\TimeZoneInformation set data [list \ [expr { -60 * [registry get $rpath Bias] }] \ [expr { -60 * [registry get $rpath StandardBias] }] \ [expr { -60 \ * [registry get $rpath DaylightBias] }]] set stdtzi [registry get $rpath StandardStart] foreach ind {0 2 14 4 6 8 10 12} { binary scan $stdtzi @${ind}s val lappend data $val } set daytzi [registry get $rpath DaylightStart] foreach ind {0 2 14 4 6 8 10 12} { binary scan $daytzi @${ind}s val lappend data $val } }] } { # Missing values in the Registry - bail out return :localtime } # Make up a Posix time zone specifier if we can't find one. Check here # that the tzdata file exists, in case we're running in an environment # (e.g. starpack) where tzdata is incomplete. (Bug 1237907) if { [dict exists $WinZoneInfo $data] } { set tzname [dict get $WinZoneInfo $data] if { ! [dict exists $TimeZoneBad $tzname] } { catch {set tzname [SetupTimeZone $tzname]} } } else { set tzname {} } if { $tzname eq {} || [dict exists $TimeZoneBad $tzname] } { lassign $data \ bias stdBias dstBias \ stdYear stdMonth stdDayOfWeek stdDayOfMonth \ stdHour stdMinute stdSecond stdMillisec \ dstYear dstMonth dstDayOfWeek dstDayOfMonth \ dstHour dstMinute dstSecond dstMillisec set stdDelta [expr { $bias + $stdBias }] set dstDelta [expr { $bias + $dstBias }] if { $stdDelta <= 0 } { set stdSignum + set stdDelta [expr { - $stdDelta }] set dispStdSignum - } else { set stdSignum - set dispStdSignum + } set hh [::format %02d [expr { $stdDelta / 3600 }]] set mm [::format %02d [expr { ($stdDelta / 60 ) % 60 }]] set ss [::format %02d [expr { $stdDelta % 60 }]] set tzname {} append tzname < $dispStdSignum $hh $mm > $stdSignum $hh : $mm : $ss if { $stdMonth >= 0 } { if { $dstDelta <= 0 } { set dstSignum + set dstDelta [expr { - $dstDelta }] set dispDstSignum - } else { set dstSignum - set dispDstSignum + } set hh [::format %02d [expr { $dstDelta / 3600 }]] set mm [::format %02d [expr { ($dstDelta / 60 ) % 60 }]] set ss [::format %02d [expr { $dstDelta % 60 }]] append tzname < $dispDstSignum $hh $mm > $dstSignum $hh : $mm : $ss if { $dstYear == 0 } { append tzname ,M $dstMonth . $dstDayOfMonth . $dstDayOfWeek } else { # I have not been able to find any locale on which Windows # converts time zone on a fixed day of the year, hence don't # know how to interpret the fields. If someone can inform me, # I'd be glad to code it up. For right now, we bail out in # such a case. return :localtime } append tzname / [::format %02d $dstHour] \ : [::format %02d $dstMinute] \ : [::format %02d $dstSecond] if { $stdYear == 0 } { append tzname ,M $stdMonth . $stdDayOfMonth . $stdDayOfWeek } else { # I have not been able to find any locale on which Windows # converts time zone on a fixed day of the year, hence don't # know how to interpret the fields. If someone can inform me, # I'd be glad to code it up. For right now, we bail out in # such a case. return :localtime } append tzname / [::format %02d $stdHour] \ : [::format %02d $stdMinute] \ : [::format %02d $stdSecond] } dict set WinZoneInfo $data $tzname } return [dict get $WinZoneInfo $data] } #---------------------------------------------------------------------- # # LoadTimeZoneFile -- # # Load the data file that specifies the conversion between a # given time zone and Greenwich. # # Parameters: # fileName -- Name of the file to load # # Results: # None. # # Side effects: # TZData(:fileName) contains the time zone data # #---------------------------------------------------------------------- proc ::tcl::clock::LoadTimeZoneFile { fileName } { variable DataDir variable TZData if { [info exists TZData($fileName)] } { return } # Since an unsafe interp uses the [clock] command in the parent, this code # is security sensitive. Make sure that the path name cannot escape the # given directory. if { [regexp {^[/\\]|^[a-zA-Z]+:|(?:^|[/\\])\.\.} $fileName] } { return -code error \ -errorcode [list CLOCK badTimeZone :$fileName] \ "time zone \":$fileName\" not valid" } try { source [file join $DataDir $fileName] } on error {} { return -code error \ -errorcode [list CLOCK badTimeZone :$fileName] \ "time zone \":$fileName\" not found" } return } #---------------------------------------------------------------------- # # LoadZoneinfoFile -- # # Loads a binary time zone information file in Olson format. # # Parameters: # fileName - Relative path name of the file to load. # # Results: # Returns an empty result normally; returns an error if no Olson file # was found or the file was malformed in some way. # # Side effects: # TZData(:fileName) contains the time zone data # #---------------------------------------------------------------------- proc ::tcl::clock::LoadZoneinfoFile { fileName } { variable ZoneinfoPaths # Since an unsafe interp uses the [clock] command in the parent, this code # is security sensitive. Make sure that the path name cannot escape the # given directory. if { [regexp {^[/\\]|^[a-zA-Z]+:|(?:^|[/\\])\.\.} $fileName] } { return -code error \ -errorcode [list CLOCK badTimeZone :$fileName] \ "time zone \":$fileName\" not valid" } set fname "" foreach d $ZoneinfoPaths { set fname [file join $d $fileName] if { [file readable $fname] && [file isfile $fname] } { break } set fname "" } if {$fname eq ""} { return -code error \ -errorcode [list CLOCK badTimeZone :$fileName] \ "time zone \":$fileName\" not found" } ReadZoneinfoFile $fileName $fname } #---------------------------------------------------------------------- # # ReadZoneinfoFile -- # # Loads a binary time zone information file in Olson format. # # Parameters: # fileName - Name of the time zone (relative path name of the # file). # fname - Absolute path name of the file. # # Results: # Returns an empty result normally; returns an error if no Olson file # was found or the file was malformed in some way. # # Side effects: # TZData(:fileName) contains the time zone data # #---------------------------------------------------------------------- proc ::tcl::clock::ReadZoneinfoFile {fileName fname} { variable MINWIDE variable TZData if { ![file exists $fname] } { return -code error "$fileName not found" } if { [file size $fname] > 262144 } { return -code error "$fileName too big" } # Suck in all the data from the file set f [open $fname r] fconfigure $f -translation binary set d [read $f] close $f # The file begins with a magic number, sixteen reserved bytes, and then # six 4-byte integers giving counts of fields in the file. binary scan $d a4a1x15IIIIII \ magic version nIsGMT nIsStd nLeap nTime nType nChar set seek 44 set ilen 4 set iformat I if { $magic != {TZif} } { return -code error "$fileName not a time zone information file" } if { $nType > 255 } { return -code error "$fileName contains too many time types" } # Accept only Posix-style zoneinfo. Sorry, 'leaps' bigots. if { $nLeap != 0 } { return -code error "$fileName contains leap seconds" } # In a version 2 file, we use the second part of the file, which contains # 64-bit transition times. if {$version eq "2"} { set seek [expr { 44 + 5 * $nTime + 6 * $nType + 4 * $nLeap + $nIsStd + $nIsGMT + $nChar }] binary scan $d @${seek}a4a1x15IIIIII \ magic version nIsGMT nIsStd nLeap nTime nType nChar if {$magic ne {TZif}} { return -code error "seek address $seek miscomputed, magic = $magic" } set iformat W set ilen 8 incr seek 44 } # Next come ${nTime} transition times, followed by ${nTime} time type # codes. The type codes are unsigned 1-byte quantities. We insert an # arbitrary start time in front of the transitions. binary scan $d @${seek}${iformat}${nTime}c${nTime} times tempCodes incr seek [expr { ($ilen + 1) * $nTime }] set times [linsert $times 0 $MINWIDE] set codes {} foreach c $tempCodes { lappend codes [expr { $c & 0xFF }] } set codes [linsert $codes 0 0] # Next come ${nType} time type descriptions, each of which has an offset # (seconds east of GMT), a DST indicator, and an index into the # abbreviation text. for { set i 0 } { $i < $nType } { incr i } { binary scan $d @${seek}Icc gmtOff isDst abbrInd lappend types [list $gmtOff $isDst $abbrInd] incr seek 6 } # Next come $nChar characters of time zone name abbreviations, which are # null-terminated. # We build them up into a dictionary indexed by character index, because # that's what's in the indices above. binary scan $d @${seek}a${nChar} abbrs incr seek ${nChar} set abbrList [split $abbrs \0] set i 0 set abbrevs {} foreach a $abbrList { for {set j 0} {$j <= [string length $a]} {incr j} { dict set abbrevs $i [string range $a $j end] incr i } } # Package up a list of tuples, each of which contains transition time, # seconds east of Greenwich, DST flag and time zone abbreviation. set r {} set lastTime $MINWIDE foreach t $times c $codes { if { $t < $lastTime } { return -code error "$fileName has times out of order" } set lastTime $t lassign [lindex $types $c] gmtoff isDst abbrInd set abbrev [dict get $abbrevs $abbrInd] lappend r [list $t $gmtoff $isDst $abbrev] } # In a version 2 file, there is also a POSIX-style time zone description # at the very end of the file. To get to it, skip over nLeap leap second # values (8 bytes each), # nIsStd standard/DST indicators and nIsGMT UTC/local indicators. if {$version eq {2}} { set seek [expr {$seek + 8 * $nLeap + $nIsStd + $nIsGMT + 1}] set last [string first \n $d $seek] set posix [string range $d $seek [expr {$last-1}]] if {[llength $posix] > 0} { set posixFields [ParsePosixTimeZone $posix] foreach tuple [ProcessPosixTimeZone $posixFields] { lassign $tuple t gmtoff isDst abbrev if {$t > $lastTime} { lappend r $tuple } } } } set TZData(:$fileName) $r return } #---------------------------------------------------------------------- # # ParsePosixTimeZone -- # # Parses the TZ environment variable in Posix form # # Parameters: # tz Time zone specifier to be interpreted # # Results: # Returns a dictionary whose values contain the various pieces of the # time zone specification. # # Side effects: # None. # # Errors: # Throws an error if the syntax of the time zone is incorrect. # # The following keys are present in the dictionary: # stdName - Name of the time zone when Daylight Saving Time # is not in effect. # stdSignum - Sign (+, -, or empty) of the offset from Greenwich # to the given (non-DST) time zone. + and the empty # string denote zones west of Greenwich, - denotes east # of Greenwich; this is contrary to the ISO convention # but follows Posix. # stdHours - Hours part of the offset from Greenwich to the given # (non-DST) time zone. # stdMinutes - Minutes part of the offset from Greenwich to the # given (non-DST) time zone. Empty denotes zero. # stdSeconds - Seconds part of the offset from Greenwich to the # given (non-DST) time zone. Empty denotes zero. # dstName - Name of the time zone when DST is in effect, or the # empty string if the time zone does not observe Daylight # Saving Time. # dstSignum, dstHours, dstMinutes, dstSeconds - # Fields corresponding to stdSignum, stdHours, stdMinutes, # stdSeconds for the Daylight Saving Time version of the # time zone. If dstHours is empty, it is presumed to be 1. # startDayOfYear - The ordinal number of the day of the year on which # Daylight Saving Time begins. If this field is # empty, then DST begins on a given month-week-day, # as below. # startJ - The letter J, or an empty string. If a J is present in # this field, then startDayOfYear does not count February 29 # even in leap years. # startMonth - The number of the month in which Daylight Saving Time # begins, supplied if startDayOfYear is empty. If both # startDayOfYear and startMonth are empty, then US rules # are presumed. # startWeekOfMonth - The number of the week in the month in which # Daylight Saving Time begins, in the range 1-5. # 5 denotes the last week of the month even in a # 4-week month. # startDayOfWeek - The number of the day of the week (Sunday=0, # Saturday=6) on which Daylight Saving Time begins. # startHours - The hours part of the time of day at which Daylight # Saving Time begins. An empty string is presumed to be 2. # startMinutes - The minutes part of the time of day at which DST begins. # An empty string is presumed zero. # startSeconds - The seconds part of the time of day at which DST begins. # An empty string is presumed zero. # endDayOfYear, endJ, endMonth, endWeekOfMonth, endDayOfWeek, # endHours, endMinutes, endSeconds - # Specify the end of DST in the same way that the start* fields # specify the beginning of DST. # # This procedure serves only to break the time specifier into fields. No # attempt is made to canonicalize the fields or supply default values. # #---------------------------------------------------------------------- proc ::tcl::clock::ParsePosixTimeZone { tz } { if {[regexp -expanded -nocase -- { ^ # 1 - Standard time zone name ([[:alpha:]]+ | <[-+[:alnum:]]+>) # 2 - Standard time zone offset, signum ([-+]?) # 3 - Standard time zone offset, hours ([[:digit:]]{1,2}) (?: # 4 - Standard time zone offset, minutes : ([[:digit:]]{1,2}) (?: # 5 - Standard time zone offset, seconds : ([[:digit:]]{1,2} ) )? )? (?: # 6 - DST time zone name ([[:alpha:]]+ | <[-+[:alnum:]]+>) (?: (?: # 7 - DST time zone offset, signum ([-+]?) # 8 - DST time zone offset, hours ([[:digit:]]{1,2}) (?: # 9 - DST time zone offset, minutes : ([[:digit:]]{1,2}) (?: # 10 - DST time zone offset, seconds : ([[:digit:]]{1,2}) )? )? )? (?: , (?: # 11 - Optional J in n and Jn form 12 - Day of year ( J ? ) ( [[:digit:]]+ ) | M # 13 - Month number 14 - Week of month 15 - Day of week ( [[:digit:]] + ) [.] ( [[:digit:]] + ) [.] ( [[:digit:]] + ) ) (?: # 16 - Start time of DST - hours / ( [[:digit:]]{1,2} ) (?: # 17 - Start time of DST - minutes : ( [[:digit:]]{1,2} ) (?: # 18 - Start time of DST - seconds : ( [[:digit:]]{1,2} ) )? )? )? , (?: # 19 - Optional J in n and Jn form 20 - Day of year ( J ? ) ( [[:digit:]]+ ) | M # 21 - Month number 22 - Week of month 23 - Day of week ( [[:digit:]] + ) [.] ( [[:digit:]] + ) [.] ( [[:digit:]] + ) ) (?: # 24 - End time of DST - hours / ( [[:digit:]]{1,2} ) (?: # 25 - End time of DST - minutes : ( [[:digit:]]{1,2} ) (?: # 26 - End time of DST - seconds : ( [[:digit:]]{1,2} ) )? )? )? )? )? )? $ } $tz -> x(stdName) x(stdSignum) x(stdHours) x(stdMinutes) x(stdSeconds) \ x(dstName) x(dstSignum) x(dstHours) x(dstMinutes) x(dstSeconds) \ x(startJ) x(startDayOfYear) \ x(startMonth) x(startWeekOfMonth) x(startDayOfWeek) \ x(startHours) x(startMinutes) x(startSeconds) \ x(endJ) x(endDayOfYear) \ x(endMonth) x(endWeekOfMonth) x(endDayOfWeek) \ x(endHours) x(endMinutes) x(endSeconds)] } { # it's a good timezone return [array get x] } return -code error\ -errorcode [list CLOCK badTimeZone $tz] \ "unable to parse time zone specification \"$tz\"" } #---------------------------------------------------------------------- # # ProcessPosixTimeZone -- # # Handle a Posix time zone after it's been broken out into fields. # # Parameters: # z - Dictionary returned from 'ParsePosixTimeZone' # # Results: # Returns time zone information for the 'TZData' array. # # Side effects: # None. # #---------------------------------------------------------------------- proc ::tcl::clock::ProcessPosixTimeZone { z } { variable MINWIDE variable TZData # Determine the standard time zone name and seconds east of Greenwich set stdName [dict get $z stdName] if { [string index $stdName 0] eq {<} } { set stdName [string range $stdName 1 end-1] } if { [dict get $z stdSignum] eq {-} } { set stdSignum +1 } else { set stdSignum -1 } set stdHours [lindex [::scan [dict get $z stdHours] %d] 0] if { [dict get $z stdMinutes] ne {} } { set stdMinutes [lindex [::scan [dict get $z stdMinutes] %d] 0] } else { set stdMinutes 0 } if { [dict get $z stdSeconds] ne {} } { set stdSeconds [lindex [::scan [dict get $z stdSeconds] %d] 0] } else { set stdSeconds 0 } set stdOffset [expr { (($stdHours * 60 + $stdMinutes) * 60 + $stdSeconds) * $stdSignum }] set data [list [list $MINWIDE $stdOffset 0 $stdName]] # If there's no daylight zone, we're done set dstName [dict get $z dstName] if { $dstName eq {} } { return $data } if { [string index $dstName 0] eq {<} } { set dstName [string range $dstName 1 end-1] } # Determine the daylight name if { [dict get $z dstSignum] eq {-} } { set dstSignum +1 } else { set dstSignum -1 } if { [dict get $z dstHours] eq {} } { set dstOffset [expr { 3600 + $stdOffset }] } else { set dstHours [lindex [::scan [dict get $z dstHours] %d] 0] if { [dict get $z dstMinutes] ne {} } { set dstMinutes [lindex [::scan [dict get $z dstMinutes] %d] 0] } else { set dstMinutes 0 } if { [dict get $z dstSeconds] ne {} } { set dstSeconds [lindex [::scan [dict get $z dstSeconds] %d] 0] } else { set dstSeconds 0 } set dstOffset [expr { (($dstHours*60 + $dstMinutes) * 60 + $dstSeconds) * $dstSignum }] } # Fill in defaults for European or US DST rules # US start time is the second Sunday in March # EU start time is the last Sunday in March # US end time is the first Sunday in November. # EU end time is the last Sunday in October if { [dict get $z startDayOfYear] eq {} && [dict get $z startMonth] eq {} } then { if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} { # EU dict set z startWeekOfMonth 5 if {$stdHours>2} { dict set z startHours 2 } else { dict set z startHours [expr {$stdHours+1}] } } else { # US dict set z startWeekOfMonth 2 dict set z startHours 2 } dict set z startMonth 3 dict set z startDayOfWeek 0 dict set z startMinutes 0 dict set z startSeconds 0 } if { [dict get $z endDayOfYear] eq {} && [dict get $z endMonth] eq {} } then { if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} { # EU dict set z endMonth 10 dict set z endWeekOfMonth 5 if {$stdHours>2} { dict set z endHours 3 } else { dict set z endHours [expr {$stdHours+2}] } } else { # US dict set z endMonth 11 dict set z endWeekOfMonth 1 dict set z endHours 2 } dict set z endDayOfWeek 0 dict set z endMinutes 0 dict set z endSeconds 0 } # Put DST in effect in all years from 1916 to 2099. for { set y 1916 } { $y < 2100 } { incr y } { set startTime [DeterminePosixDSTTime $z start $y] incr startTime [expr { - wide($stdOffset) }] set endTime [DeterminePosixDSTTime $z end $y] incr endTime [expr { - wide($dstOffset) }] if { $startTime < $endTime } { lappend data \ [list $startTime $dstOffset 1 $dstName] \ [list $endTime $stdOffset 0 $stdName] } else { lappend data \ [list $endTime $stdOffset 0 $stdName] \ [list $startTime $dstOffset 1 $dstName] } } return $data } #---------------------------------------------------------------------- # # DeterminePosixDSTTime -- # # Determines the time that Daylight Saving Time starts or ends from a # Posix time zone specification. # # Parameters: # z - Time zone data returned from ParsePosixTimeZone. # Missing fields are expected to be filled in with # default values. # bound - The word 'start' or 'end' # y - The year for which the transition time is to be determined. # # Results: # Returns the transition time as a count of seconds from the epoch. The # time is relative to the wall clock, not UTC. # #---------------------------------------------------------------------- proc ::tcl::clock::DeterminePosixDSTTime { z bound y } { variable FEB_28 # Determine the start or end day of DST set date [dict create era CE year $y gregorian 1] set doy [dict get $z ${bound}DayOfYear] if { $doy ne {} } { # Time was specified as a day of the year if { [dict get $z ${bound}J] ne {} && [IsGregorianLeapYear $date] && ( $doy > $FEB_28 ) } { incr doy } dict set date dayOfYear $doy set date [GetJulianDayFromEraYearDay $date[set date {}] 2361222] } else { # Time was specified as a day of the week within a month dict set date month [dict get $z ${bound}Month] dict set date dayOfWeek [dict get $z ${bound}DayOfWeek] set dowim [dict get $z ${bound}WeekOfMonth] if { $dowim >= 5 } { set dowim -1 } dict set date dayOfWeekInMonth $dowim set date [GetJulianDayFromEraYearMonthWeekDay $date[set date {}] 2361222] } set jd [dict get $date julianDay] set seconds [expr { wide($jd) * wide(86400) - wide(210866803200) }] set h [dict get $z ${bound}Hours] if { $h eq {} } { set h 2 } else { set h [lindex [::scan $h %d] 0] } set m [dict get $z ${bound}Minutes] if { $m eq {} } { set m 0 } else { set m [lindex [::scan $m %d] 0] } set s [dict get $z ${bound}Seconds] if { $s eq {} } { set s 0 } else { set s [lindex [::scan $s %d] 0] } set tod [expr { ( $h * 60 + $m ) * 60 + $s }] return [expr { $seconds + $tod }] } #---------------------------------------------------------------------- # # GetJulianDayFromEraYearDay -- # # Given a year, month and day on the Gregorian calendar, determines # the Julian Day Number beginning at noon on that date. # # Parameters: # date -- A dictionary in which the 'era', 'year', and # 'dayOfYear' slots are populated. The calendar in use # is determined by the date itself relative to: # changeover -- Julian day on which the Gregorian calendar was # adopted in the current locale. # # Results: # Returns the given dictionary augmented with a 'julianDay' key whose # value is the desired Julian Day Number, and a 'gregorian' key that # specifies whether the calendar is Gregorian (1) or Julian (0). # # Side effects: # None. # # Bugs: # This code needs to be moved to the C layer. # #---------------------------------------------------------------------- proc ::tcl::clock::GetJulianDayFromEraYearDay {date changeover} { # Get absolute year number from the civil year switch -exact -- [dict get $date era] { BCE { set year [expr { 1 - [dict get $date year] }] } CE { set year [dict get $date year] } } set ym1 [expr { $year - 1 }] # Try the Gregorian calendar first. dict set date gregorian 1 set jd [expr { 1721425 + [dict get $date dayOfYear] + ( 365 * $ym1 ) + ( $ym1 / 4 ) - ( $ym1 / 100 ) + ( $ym1 / 400 ) }] # If the date is before the Gregorian change, use the Julian calendar. if { $jd < $changeover } { dict set date gregorian 0 set jd [expr { 1721423 + [dict get $date dayOfYear] + ( 365 * $ym1 ) + ( $ym1 / 4 ) }] } dict set date julianDay $jd return $date } #---------------------------------------------------------------------- # # GetJulianDayFromEraYearMonthWeekDay -- # # Determines the Julian Day number corresponding to the nth given # day-of-the-week in a given month. # # Parameters: # date - Dictionary containing the keys, 'era', 'year', 'month' # 'weekOfMonth', 'dayOfWeek', and 'dayOfWeekInMonth'. # changeover - Julian Day of adoption of the Gregorian calendar # # Results: # Returns the given dictionary, augmented with a 'julianDay' key. # # Side effects: # None. # # Bugs: # This code needs to be moved to the C layer. # #---------------------------------------------------------------------- proc ::tcl::clock::GetJulianDayFromEraYearMonthWeekDay {date changeover} { # Come up with a reference day; either the zeroeth day of the given month # (dayOfWeekInMonth >= 0) or the seventh day of the following month # (dayOfWeekInMonth < 0) set date2 $date set week [dict get $date dayOfWeekInMonth] if { $week >= 0 } { dict set date2 dayOfMonth 0 } else { dict incr date2 month dict set date2 dayOfMonth 7 } set date2 [GetJulianDayFromEraYearMonthDay $date2[set date2 {}] \ $changeover] set wd0 [WeekdayOnOrBefore [dict get $date dayOfWeek] \ [dict get $date2 julianDay]] dict set date julianDay [expr { $wd0 + 7 * $week }] return $date } #---------------------------------------------------------------------- # # IsGregorianLeapYear -- # # Determines whether a given date represents a leap year in the # Gregorian calendar. # # Parameters: # date -- The date to test. The fields, 'era', 'year' and 'gregorian' # must be set. # # Results: # Returns 1 if the year is a leap year, 0 otherwise. # # Side effects: # None. # #---------------------------------------------------------------------- proc ::tcl::clock::IsGregorianLeapYear { date } { switch -exact -- [dict get $date era] { BCE { set year [expr { 1 - [dict get $date year]}] } CE { set year [dict get $date year] } } if { $year % 4 != 0 } { return 0 } elseif { ![dict get $date gregorian] } { return 1 } elseif { $year % 400 == 0 } { return 1 } elseif { $year % 100 == 0 } { return 0 } else { return 1 } } #---------------------------------------------------------------------- # # WeekdayOnOrBefore -- # # Determine the nearest day of week (given by the 'weekday' parameter, # Sunday==0) on or before a given Julian Day. # # Parameters: # weekday -- Day of the week # j -- Julian Day number # # Results: # Returns the Julian Day Number of the desired date. # # Side effects: # None. # #---------------------------------------------------------------------- proc ::tcl::clock::WeekdayOnOrBefore { weekday j } { set k [expr { ( $weekday + 6 ) % 7 }] return [expr { $j - ( $j - $k ) % 7 }] } #---------------------------------------------------------------------- # # ChangeCurrentLocale -- # # The global locale was changed within msgcat. # Clears the buffered parse functions of the current locale. # # Parameters: # loclist (ignored) # # Results: # None. # # Side effects: # Buffered parse functions are cleared. # #---------------------------------------------------------------------- proc ::tcl::clock::ChangeCurrentLocale {args} { ::tcl::unsupported::clock::configure -current-locale [lindex $args 0] } #---------------------------------------------------------------------- # # ClearCaches -- # # Clears all caches to reclaim the memory used in [clock] # # Parameters: # None. # # Results: # None. # # Side effects: # Caches are cleared. # #---------------------------------------------------------------------- proc ::tcl::clock::ClearCaches {} { variable LocFmtMap variable mcMergedCat variable TimeZoneBad # tell backend - should invalidate: ::tcl::unsupported::clock::configure -clear # clear msgcat cache: set mcMergedCat [dict create] set LocFmtMap {} set TimeZoneBad {} InitTZData } tcl9.0.1/library/foreachline.tcl0000644000175000017500000000114014726623136016201 0ustar sergeisergei# foreachLine: # Iterate over the contents of a file, a line at a time. # The body script is run for each, with variable varName set to the line # contents. # # Copyright © 2023 Donal K Fellows. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # proc foreachLine {varName filename body} { upvar 1 $varName line set f [open $filename "r"] try { while {[gets $f line] >= 0} { uplevel 1 $body } } on return {msg opt} { dict incr opt -level return -options $opt $msg } finally { close $f } } tcl9.0.1/library/history.tcl0000644000175000017500000001733314726623136015436 0ustar sergeisergei# history.tcl -- # # Implementation of the history command. # # Copyright © 1997 Sun Microsystems, Inc. # # See the file "license.terms" for information on usage and redistribution of # this file, and for a DISCLAIMER OF ALL WARRANTIES. # # The tcl::history array holds the history list and some additional # bookkeeping variables. # # nextid the index used for the next history list item. # keep the max size of the history list # oldest the index of the oldest item in the history. namespace eval ::tcl { variable history if {![info exists history]} { array set history { nextid 0 keep 20 oldest -20 } } namespace ensemble create -command ::tcl::history -map { add ::tcl::HistAdd change ::tcl::HistChange clear ::tcl::HistClear event ::tcl::HistEvent info ::tcl::HistInfo keep ::tcl::HistKeep nextid ::tcl::HistNextID redo ::tcl::HistRedo } } # history -- # # This is the main history command. See the man page for its interface. # This does some argument checking and calls the helper ensemble in the # tcl namespace. proc ::history {args} { # If no command given, we're doing 'history info'. Can't be done with an # ensemble unknown handler, as those don't fire when no subcommand is # given at all. if {![llength $args]} { set args info } # Tricky stuff needed to make stack and errors come out right! tailcall apply {arglist {tailcall ::tcl::history {*}$arglist} ::tcl} $args } # (unnamed) -- # # Callback when [::history] is destroyed. Destroys the implementation. # # Parameters: # oldName what the command was called. # newName what the command is now called (an empty string). # op the operation (= delete). # # Results: # none # # Side Effects: # The implementation of the [::history] command ceases to exist. trace add command ::history delete [list apply {{oldName newName op} { variable history unset -nocomplain history foreach c [info procs ::tcl::Hist*] { rename $c {} } rename ::tcl::history {} } ::tcl}] # tcl::HistAdd -- # # Add an item to the history, and optionally eval it at the global scope # # Parameters: # event the command to add # exec (optional) a substring of "exec" causes the command to # be evaled. # Results: # If executing, then the results of the command are returned # # Side Effects: # Adds to the history list proc ::tcl::HistAdd {event {exec {}}} { variable history if { [prefix longest {exec {}} $exec] eq "" && [llength [info level 0]] == 3 } then { return -code error "bad argument \"$exec\": should be \"exec\"" } # Do not add empty commands to the history if {[string trim $event] eq ""} { return "" } # Maintain the history set history([incr history(nextid)]) $event unset -nocomplain history([incr history(oldest)]) # Only execute if 'exec' (or non-empty prefix of it) given if {$exec eq ""} { return "" } tailcall eval $event } # tcl::HistKeep -- # # Set or query the limit on the length of the history list # # Parameters: # limit (optional) the length of the history list # # Results: # If no limit is specified, the current limit is returned # # Side Effects: # Updates history(keep) if a limit is specified proc ::tcl::HistKeep {{count {}}} { variable history if {[llength [info level 0]] == 1} { return $history(keep) } if {![string is integer -strict $count] || ($count < 0)} { return -code error "illegal keep count \"$count\"" } set oldold $history(oldest) set history(oldest) [expr {$history(nextid) - $count}] for {} {$oldold <= $history(oldest)} {incr oldold} { unset -nocomplain history($oldold) } set history(keep) $count } # tcl::HistClear -- # # Erase the history list # # Parameters: # none # # Results: # none # # Side Effects: # Resets the history array, except for the keep limit proc ::tcl::HistClear {} { variable history set keep $history(keep) unset history array set history [list \ nextid 0 \ keep $keep \ oldest -$keep \ ] } # tcl::HistInfo -- # # Return a pretty-printed version of the history list # # Parameters: # num (optional) the length of the history list to return # # Results: # A formatted history list proc ::tcl::HistInfo {{count {}}} { variable history if {[llength [info level 0]] == 1} { set count [expr {$history(keep) + 1}] } elseif {![string is integer -strict $count]} { return -code error "bad integer \"$count\"" } set result {} set newline "" for {set i [expr {$history(nextid) - $count + 1}]} \ {$i <= $history(nextid)} {incr i} { if {![info exists history($i)]} { continue } set cmd [string map [list \n \n\t] [string trimright $history($i) \ \n]] append result $newline[format "%6d %s" $i $cmd] set newline \n } return $result } # tcl::HistRedo -- # # Fetch the previous or specified event, execute it, and then replace # the current history item with that event. # # Parameters: # event (optional) index of history item to redo. Defaults to -1, # which means the previous event. # # Results: # Those of the command being redone. # # Side Effects: # Replaces the current history list item with the one being redone. proc ::tcl::HistRedo {{event -1}} { variable history set i [HistIndex $event] if {$i == $history(nextid)} { return -code error "cannot redo the current event" } set cmd $history($i) HistChange $cmd 0 tailcall eval $cmd } # tcl::HistIndex -- # # Map from an event specifier to an index in the history list. # # Parameters: # event index of history item to redo. # If this is a positive number, it is used directly. # If it is a negative number, then it counts back to a previous # event, where -1 is the most recent event. # A string can be matched, either by being the prefix of a # command or by matching a command with string match. # # Results: # The index into history, or an error if the index didn't match. proc ::tcl::HistIndex {event} { variable history if {![string is integer -strict $event]} { for {set i [expr {$history(nextid)-1}]} {[info exists history($i)]} \ {incr i -1} { if {[string match $event* $history($i)]} { return $i } if {[string match $event $history($i)]} { return $i } } return -code error "no event matches \"$event\"" } elseif {$event <= 0} { set i [expr {$history(nextid) + $event}] } else { set i $event } if {$i <= $history(oldest)} { return -code error "event \"$event\" is too far in the past" } if {$i > $history(nextid)} { return -code error "event \"$event\" hasn't occurred yet" } return $i } # tcl::HistEvent -- # # Map from an event specifier to the value in the history list. # # Parameters: # event index of history item to redo. See index for a description of # possible event patterns. # # Results: # The value from the history list. proc ::tcl::HistEvent {{event -1}} { variable history set i [HistIndex $event] if {![info exists history($i)]} { return "" } return [string trimright $history($i) \ \n] } # tcl::HistChange -- # # Replace a value in the history list. # # Parameters: # newValue The new value to put into the history list. # event (optional) index of history item to redo. See index for a # description of possible event patterns. This defaults to 0, # which specifies the current event. # # Side Effects: # Changes the history list. proc ::tcl::HistChange {newValue {event 0}} { variable history set i [HistIndex $event] set history($i) $newValue } # tcl::HistNextID -- # # Returns the number of the next history event. # # Parameters: # None. # # Side Effects: # None. proc ::tcl::HistNextID {} { variable history return [expr {$history(nextid) + 1}] } return # Local Variables: # mode: tcl # fill-column: 78 # End: tcl9.0.1/library/icu.tcl0000644000175000017500000000742514726623136014516 0ustar sergeisergei#---------------------------------------------------------------------- # # icu.tcl -- # # This file implements the portions of the [tcl::unsupported::icu] # ensemble that are coded in Tcl. # #---------------------------------------------------------------------- # # Copyright © 2024 Ashok P. Nadkarni # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # #---------------------------------------------------------------------- ::tcl::unsupported::loadIcu namespace eval ::tcl::unsupported::icu { # Map Tcl encoding names to ICU and back. Note ICU has multiple aliases # for the same encoding. variable tclToIcu variable icuToTcl proc LogError {message} { puts stderr $message } proc Init {} { variable tclToIcu variable icuToTcl # There are some special cases where names do not line up # at all. Map Tcl -> ICU array set specialCases { ebcdic ebcdic-cp-us macCentEuro maccentraleurope utf16 UTF16_PlatformEndian utf-16be UnicodeBig utf-16le UnicodeLittle utf32 UTF32_PlatformEndian } # Ignore all errors. Do not want to hold up Tcl # if ICU not available if {[catch { foreach tclName [encoding names] { if {[catch { set icuNames [aliases $tclName] } erMsg]} { LogError "Could not get aliases for $tclName: $erMsg" continue } if {[llength $icuNames] == 0} { # E.g. macGreek -> x-MacGreek set icuNames [aliases x-$tclName] if {[llength $icuNames] == 0} { # Still no joy, check for special cases if {[info exists specialCases($tclName)]} { set icuNames [aliases $specialCases($tclName)] } } } # If the Tcl name is also an ICU name use it else use # the first name which is the canonical ICU name set pos [lsearch -exact -nocase $icuNames $tclName] if {$pos >= 0} { lappend tclToIcu($tclName) [lindex $icuNames $pos] {*}[lreplace $icuNames $pos $pos] } else { set tclToIcu($tclName) $icuNames } foreach icuName $icuNames { lappend icuToTcl($icuName) $tclName } } } errMsg]} { LogError $errMsg } array default set tclToIcu "" array default set icuToTcl "" # Redefine ourselves to no-op. proc Init {} {} } # Primarily used during development proc MappedIcuNames {{pat *}} { Init variable icuToTcl return [array names icuToTcl $pat] } # Primarily used during development proc UnmappedIcuNames {{pat *}} { Init variable icuToTcl set unmappedNames {} foreach icuName [converters] { if {[llength [icuToTcl $icuName]] == 0} { lappend unmappedNames $icuName } foreach alias [aliases $icuName] { if {[llength [icuToTcl $alias]] == 0} { lappend unmappedNames $alias } } } # Aliases can be duplicates. Remove return [lsort -unique [lsearch -inline -all $unmappedNames $pat]] } # Primarily used during development proc UnmappedTclNames {{pat *}} { Init variable tclToIcu set unmappedNames {} foreach tclName [encoding names] { # Note entry will always exist. Check if empty if {[llength [tclToIcu $tclName]] == 0} { lappend unmappedNames $tclName } } return [lsearch -inline -all $unmappedNames $pat] } # Returns the Tcl equivalent of an ICU encoding name or # the empty string in case not found. proc icuToTcl {icuName} { Init proc icuToTcl {icuName} { variable icuToTcl return [lindex $icuToTcl($icuName) 0] } icuToTcl $icuName } # Returns the ICU equivalent of an Tcl encoding name or # the empty string in case not found. proc tclToIcu {tclName} { Init proc tclToIcu {tclName} { variable tclToIcu return [lindex $tclToIcu($tclName) 0] } tclToIcu $tclName } namespace export {[a-z]*} namespace ensemble create } tcl9.0.1/library/init.tcl0000644000175000017500000005572514726623136014707 0ustar sergeisergei# init.tcl -- # # Default system startup file for Tcl-based applications. Defines # "unknown" procedure and auto-load facilities. # # Copyright © 1991-1993 The Regents of the University of California. # Copyright © 1994-1996 Sun Microsystems, Inc. # Copyright © 1998-1999 Scriptics Corporation. # Copyright © 2004 Kevin B. Kenny. # Copyright © 2018 Sean Woods # # All rights reserved. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # package require -exact tcl 9.0.1 # Compute the auto path to use in this interpreter. # The values on the path come from several locations: # # The environment variable TCLLIBPATH # # tcl_library, which is the directory containing this init.tcl script. # [tclInit] (Tcl_Init()) searches around for the directory containing this # init.tcl and defines tcl_library to that location before sourcing it. # # The parent directory of tcl_library. Adding the parent # means that packages in peer directories will be found automatically. # # Also add the directory ../lib relative to the directory where the # executable is located. This is meant to find binary packages for the # same architecture as the current executable. # # tcl_pkgPath, which is set by the platform-specific initialization routines # On UNIX it is compiled in # On Windows, it is not used # # (Ticket 41c9857bdd) In a safe interpreter, this file does not set # ::auto_path (other than to {} if it is undefined). The caller, typically # a Safe Base command, is responsible for setting ::auto_path. if {![info exists auto_path]} { if {[info exists env(TCLLIBPATH)] && (![interp issafe])} { set auto_path [apply {{} { lmap path $::env(TCLLIBPATH) { # Paths relative to unresolvable home dirs are ignored if {[catch {file tildeexpand $path} expanded_path]} { continue } set expanded_path } }}] } else { set auto_path "" } } namespace eval tcl { if {![interp issafe]} { variable Dir foreach Dir [list $::tcl_library [file dirname $::tcl_library]] { if {$Dir ni $::auto_path} { lappend ::auto_path $Dir } } set Dir [file join [file dirname [file dirname \ [info nameofexecutable]]] lib] if {$Dir ni $::auto_path} { lappend ::auto_path $Dir } if {[info exists ::tcl_pkgPath]} { catch { foreach Dir $::tcl_pkgPath { if {$Dir ni $::auto_path} { lappend ::auto_path $Dir } } }} variable Path [encoding dirs] set Dir [file join $::tcl_library encoding] if {$Dir ni $Path} { lappend Path $Dir encoding dirs $Path } unset Dir Path } } namespace eval tcl::Pkg {} # Setup the unknown package handler if {[interp issafe]} { package unknown {::tcl::tm::UnknownHandler ::tclPkgUnknown} } else { # Set up search for Tcl Modules (TIP #189). # and setup platform specific unknown package handlers if {$tcl_platform(os) eq "Darwin" && $tcl_platform(platform) eq "unix"} { package unknown {::tcl::tm::UnknownHandler \ {::tcl::MacOSXPkgUnknown ::tclPkgUnknown}} } else { package unknown {::tcl::tm::UnknownHandler ::tclPkgUnknown} } # Set up the 'clock' ensemble apply {{} { set cmdmap [dict create] foreach cmd {add clicks format microseconds milliseconds scan seconds} { dict set cmdmap $cmd ::tcl::clock::$cmd } namespace inscope ::tcl::clock [list namespace ensemble create -command \ ::clock -map $cmdmap] ::tcl::unsupported::clock::configure -init-complete }} } # Conditionalize for presence of exec. if {[namespace which -command exec] eq ""} { # Some machines do not have exec. Also, on all # platforms, safe interpreters do not have exec. set auto_noexec 1 } # Define a log command (which can be overwritten to log errors # differently, specially when stderr is not available) if {[namespace which -command tclLog] eq ""} { proc tclLog {string} { catch {puts stderr $string} } } # unknown -- # This procedure is called when a Tcl command is invoked that doesn't # exist in the interpreter. It takes the following steps to make the # command available: # # 1. See if the autoload facility can locate the command in a # Tcl script file. If so, load it and execute it. # 2. If the command was invoked interactively at top-level: # (a) see if the command exists as an executable UNIX program. # If so, "exec" the command. # (b) see if the command requests csh-like history substitution # in one of the common forms !!, !, or ^old^new. If # so, emulate csh's history substitution. # (c) see if the command is a unique abbreviation for another # command. If so, invoke the command. # # Arguments: # args - A list whose elements are the words of the original # command, including the command name. proc unknown args { variable ::tcl::UnknownPending global auto_noexec auto_noload env tcl_interactive errorInfo errorCode if {[info exists errorInfo]} { set savedErrorInfo $errorInfo } if {[info exists errorCode]} { set savedErrorCode $errorCode } set name [lindex $args 0] if {![info exists auto_noload]} { # # Make sure we're not trying to load the same proc twice. # if {[info exists UnknownPending($name)]} { return -code error "self-referential recursion\ in \"unknown\" for command \"$name\"" } set UnknownPending($name) pending set ret [catch { auto_load $name [uplevel 1 {::namespace current}] } msg opts] unset UnknownPending($name) if {$ret != 0} { dict append opts -errorinfo "\n (autoloading \"$name\")" return -options $opts $msg } if {![array size UnknownPending]} { unset UnknownPending } if {$msg} { if {[info exists savedErrorCode]} { set ::errorCode $savedErrorCode } else { unset -nocomplain ::errorCode } if {[info exists savedErrorInfo]} { set errorInfo $savedErrorInfo } else { unset -nocomplain errorInfo } set code [catch {uplevel 1 $args} msg opts] if {$code == 1} { # # Compute stack trace contribution from the [uplevel]. # Note the dependence on how Tcl_AddErrorInfo, etc. # construct the stack trace. # set errInfo [dict get $opts -errorinfo] set errCode [dict get $opts -errorcode] set cinfo $args if {[string length [encoding convertto utf-8 $cinfo]] > 150} { set cinfo [string range $cinfo 0 150] while {[string length [encoding convertto utf-8 $cinfo]] > 150} { set cinfo [string range $cinfo 0 end-1] } append cinfo ... } set tail "\n (\"uplevel\" body line 1)\n invoked\ from within\n\"uplevel 1 \$args\"" set expect "$msg\n while executing\n\"$cinfo\"$tail" if {$errInfo eq $expect} { # # The stack has only the eval from the expanded command # Do not generate any stack trace here. # dict unset opts -errorinfo dict incr opts -level return -options $opts $msg } # # Stack trace is nested, trim off just the contribution # from the extra "eval" of $args due to the "catch" above. # set last [string last $tail $errInfo] if {$last + [string length $tail] != [string length $errInfo]} { # Very likely cannot happen return -options $opts $msg } set errInfo [string range $errInfo 0 $last-1] set tail "\"$cinfo\"" set last [string last $tail $errInfo] if {$last < 0 || $last + [string length $tail] != [string length $errInfo]} { return -code error -errorcode $errCode \ -errorinfo $errInfo $msg } set errInfo [string range $errInfo 0 $last-1] set tail "\n invoked from within\n" set last [string last $tail $errInfo] if {$last + [string length $tail] == [string length $errInfo]} { return -code error -errorcode $errCode \ -errorinfo [string range $errInfo 0 $last-1] $msg } set tail "\n while executing\n" set last [string last $tail $errInfo] if {$last + [string length $tail] == [string length $errInfo]} { return -code error -errorcode $errCode \ -errorinfo [string range $errInfo 0 $last-1] $msg } return -options $opts $msg } else { dict incr opts -level return -options $opts $msg } } } if {([info level] == 1) && ([info script] eq "") && [info exists tcl_interactive] && $tcl_interactive} { if {![info exists auto_noexec]} { set new [auto_execok $name] if {$new ne ""} { set redir "" if {[namespace which -command console] eq ""} { set redir ">&@stdout <@stdin" } uplevel 1 [list ::catch \ [concat exec $redir $new [lrange $args 1 end]] \ ::tcl::UnknownResult ::tcl::UnknownOptions] dict incr ::tcl::UnknownOptions -level return -options $::tcl::UnknownOptions $::tcl::UnknownResult } } if {$name eq "!!"} { set newcmd [history event] } elseif {[regexp {^!(.+)$} $name -> event]} { set newcmd [history event $event] } elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $name -> old new]} { set newcmd [history event -1] catch {regsub -all -- $old $newcmd $new newcmd} } if {[info exists newcmd]} { tclLog $newcmd history change $newcmd 0 uplevel 1 [list ::catch $newcmd \ ::tcl::UnknownResult ::tcl::UnknownOptions] dict incr ::tcl::UnknownOptions -level return -options $::tcl::UnknownOptions $::tcl::UnknownResult } set ret [catch [list uplevel 1 [list info commands $name*]] candidates] if {$name eq "::"} { set name "" } if {$ret != 0} { dict append opts -errorinfo \ "\n (expanding command prefix \"$name\" in unknown)" return -options $opts $candidates } # Filter out bogus matches when $name contained # a glob-special char [Bug 946952] if {$name eq ""} { # Handle empty $name separately due to strangeness # in [string first] (See RFE 1243354) set cmds $candidates } else { set cmds [list] foreach x $candidates { if {[string first $name $x] == 0} { lappend cmds $x } } } if {[llength $cmds] == 1} { uplevel 1 [list ::catch [lreplace $args 0 0 [lindex $cmds 0]] \ ::tcl::UnknownResult ::tcl::UnknownOptions] dict incr ::tcl::UnknownOptions -level return -options $::tcl::UnknownOptions $::tcl::UnknownResult } if {[llength $cmds]} { return -code error "ambiguous command name \"$name\": [lsort $cmds]" } } return -code error -errorcode [list TCL LOOKUP COMMAND $name] \ "invalid command name \"$name\"" } # auto_load -- # Checks a collection of library directories to see if a procedure # is defined in one of them. If so, it sources the appropriate # library file to create the procedure. Returns 1 if it successfully # loaded the procedure, 0 otherwise. # # Arguments: # cmd - Name of the command to find and load. # namespace (optional) The namespace where the command is being used - must be # a canonical namespace as returned [namespace current] # for instance. If not given, namespace current is used. proc auto_load {cmd {namespace {}}} { global auto_index auto_path # qualify names: if {$namespace eq ""} { set namespace [uplevel 1 [list ::namespace current]] } set nameList [auto_qualify $cmd $namespace] # workaround non canonical auto_index entries that might be around # from older auto_mkindex versions if {$cmd ni $nameList} {lappend nameList $cmd} # try to load (and create sub-cmd handler "_sub_load_cmd" for further usage): foreach name $nameList [set _sub_load_cmd { # via auto_index: if {[info exists auto_index($name)]} { namespace inscope :: $auto_index($name) # There's a couple of ways to look for a command of a given # name. One is to use # info commands $name # Unfortunately, if the name has glob-magic chars in it like * # or [], it may not match. For our purposes here, a better # route is to use # namespace which -command $name if {[namespace which -command $name] ne ""} { return 1 } } }] # load auto_index if possible: if {![info exists auto_path]} { return 0 } if {![auto_load_index]} { return 0 } # try again (something new could be loaded): foreach name $nameList $_sub_load_cmd return 0 } # ::tcl::Pkg::source -- # This procedure provides an alternative "source" command, which doesn't # register the file for the "package files" command. Safe interpreters # don't have to do anything special. # # Arguments: # filename proc ::tcl::Pkg::source {filename} { if {[interp issafe]} { uplevel 1 [list ::source $filename] } else { uplevel 1 [list ::source -nopkg $filename] } } # auto_load_index -- # Loads the contents of tclIndex files on the auto_path directory # list. This is usually invoked within auto_load to load the index # of available commands. Returns 1 if the index is loaded, and 0 if # the index is already loaded and up to date. # # Arguments: # None. proc auto_load_index {} { variable ::tcl::auto_oldpath global auto_index auto_path if {[info exists auto_oldpath] && ($auto_oldpath eq $auto_path)} { return 0 } set auto_oldpath $auto_path # Check if we are a safe interpreter. In that case, we support only # newer format tclIndex files. set issafe [interp issafe] for {set i [expr {[llength $auto_path] - 1}]} {$i >= 0} {incr i -1} { set dir [lindex $auto_path $i] set f "" if {$issafe} { catch {source [file join $dir tclIndex]} } elseif {[catch {set f [open [file join $dir tclIndex]]}]} { continue } else { set error [catch { fconfigure $f -encoding utf-8 -eofchar \x1A set id [gets $f] if {$id eq "# Tcl autoload index file, version 2.0"} { eval [read $f] } elseif {$id eq "# Tcl autoload index file: each line identifies a Tcl"} { while {[gets $f line] >= 0} { if {([string index $line 0] eq "#") \ || ([llength $line] != 2)} { continue } set name [lindex $line 0] set auto_index($name) \ "::tcl::Pkg::source [file join $dir [lindex $line 1]]" } } else { error "[file join $dir tclIndex] isn't a proper Tcl index file" } } msg opts] if {$f ne ""} { close $f } if {$error} { return -options $opts $msg } } } return 1 } # auto_qualify -- # # Compute a fully qualified names list for use in the auto_index array. # For historical reasons, commands in the global namespace do not have leading # :: in the index key. The list has two elements when the command name is # relative (no leading ::) and the namespace is not the global one. Otherwise # only one name is returned (and searched in the auto_index). # # Arguments - # cmd The command name. Can be any name accepted for command # invocations (Like "foo::::bar"). # namespace The namespace where the command is being used - must be # a canonical namespace as returned by [namespace current] # for instance. proc auto_qualify {cmd namespace} { # count separators and clean them up # (making sure that foo:::::bar will be treated as foo::bar) set n [regsub -all {::+} $cmd :: cmd] # Ignore namespace if the name starts with :: # Handle special case of only leading :: # Before each return case we give an example of which category it is # with the following form : # (inputCmd, inputNameSpace) -> output if {[string match ::* $cmd]} { if {$n > 1} { # (::foo::bar , *) -> ::foo::bar return [list $cmd] } else { # (::global , *) -> global return [list [string range $cmd 2 end]] } } # Potentially returning 2 elements to try : # (if the current namespace is not the global one) if {$n == 0} { if {$namespace eq "::"} { # (nocolons , ::) -> nocolons return [list $cmd] } else { # (nocolons , ::sub) -> ::sub::nocolons nocolons return [list ${namespace}::$cmd $cmd] } } elseif {$namespace eq "::"} { # (foo::bar , ::) -> ::foo::bar return [list ::$cmd] } else { # (foo::bar , ::sub) -> ::sub::foo::bar ::foo::bar return [list ${namespace}::$cmd ::$cmd] } } # auto_import -- # # Invoked during "namespace import" to make see if the imported commands # reside in an autoloaded library. If so, the commands are loaded so # that they will be available for the import links. If not, then this # procedure does nothing. # # Arguments - # pattern The pattern of commands being imported (like "foo::*") # a canonical namespace as returned by [namespace current] proc auto_import {pattern} { global auto_index # If no namespace is specified, this will be an error case if {![string match *::* $pattern]} { return } set ns [uplevel 1 [list ::namespace current]] set patternList [auto_qualify $pattern $ns] auto_load_index foreach pattern $patternList { foreach name [array names auto_index $pattern] { if {([namespace which -command $name] eq "") && ([namespace qualifiers $pattern] eq [namespace qualifiers $name])} { namespace inscope :: $auto_index($name) } } } } # auto_execok -- # # Returns string that indicates name of program to execute if # name corresponds to a shell builtin or an executable in the # Windows search path, or "" otherwise. Builds an associative # array auto_execs that caches information about previous checks, # for speed. # # Arguments: # name - Name of a command. if {$tcl_platform(platform) eq "windows"} { # Windows version. # # Note that file executable doesn't work under Windows, so we have to # look for files with .exe, .com, or .bat extensions. Also, the path # may be in the Path or PATH environment variables, and path # components are separated with semicolons, not colons as under Unix. # proc auto_execok name { global auto_execs env tcl_platform if {[info exists auto_execs($name)]} { return $auto_execs($name) } set auto_execs($name) "" set shellBuiltins [list assoc cls copy date del dir echo erase exit ftype \ md mkdir mklink move rd ren rename rmdir start time type ver vol] if {[info exists env(PATHEXT)]} { # Add an initial ; to have the {} extension check first. set execExtensions [split ";$env(PATHEXT)" ";"] } else { set execExtensions [list {} .com .exe .bat .cmd] } if {[string tolower $name] in $shellBuiltins} { # When this is command.com for some reason on Win2K, Tcl won't # exec it unless the case is right, which this corrects. COMSPEC # may not point to a real file, so do the check. set cmd $env(COMSPEC) if {[file exists $cmd]} { set cmd [file attributes $cmd -shortname] } return [set auto_execs($name) [list $cmd /c $name]] } if {[llength [file split $name]] != 1} { foreach ext $execExtensions { set file ${name}${ext} if {[file exists $file] && ![file isdirectory $file]} { return [set auto_execs($name) [list $file]] } } return "" } set path "[file dirname [info nameofexecutable]];.;" if {[info exists env(SystemRoot)]} { set windir $env(SystemRoot) } elseif {[info exists env(WINDIR)]} { set windir $env(WINDIR) } if {[info exists windir]} { append path "$windir/system32;$windir/system;$windir;" } foreach var {PATH Path path} { if {[info exists env($var)]} { append path ";$env($var)" } } foreach ext $execExtensions { unset -nocomplain checked foreach dir [split $path {;}] { # Skip already checked directories if {[info exists checked($dir)] || ($dir eq "")} { continue } set checked($dir) {} set file [file join $dir ${name}${ext}] if {[file exists $file] && ![file isdirectory $file]} { return [set auto_execs($name) [list $file]] } } } return "" } } else { # Unix version. # proc auto_execok name { global auto_execs env if {[info exists auto_execs($name)]} { return $auto_execs($name) } set auto_execs($name) "" if {[llength [file split $name]] != 1} { if {[file executable $name] && ![file isdirectory $name]} { set auto_execs($name) [list $name] } return $auto_execs($name) } foreach dir [split $env(PATH) :] { if {$dir eq ""} { set dir . } set file [file join $dir $name] if {[file executable $file] && ![file isdirectory $file]} { set auto_execs($name) [list $file] return $auto_execs($name) } } return "" } } # ::tcl::CopyDirectory -- # # This procedure is called by Tcl's core when attempts to call the # filesystem's copydirectory function fail. The semantics of the call # are that 'dest' does not yet exist, i.e. dest should become the exact # image of src. If dest does exist, we throw an error. # # Note that making changes to this procedure can change the results # of running Tcl's tests. # # Arguments: # action - "renaming" or "copying" # src - source directory # dest - destination directory proc tcl::CopyDirectory {action src dest} { set nsrc [file normalize $src] set ndest [file normalize $dest] if {$action eq "renaming"} { # Can't rename volumes. We could give a more precise # error message here, but that would break the test suite. if {$nsrc in [file volumes]} { return -code error "error $action \"$src\" to\ \"$dest\": trying to rename a volume or move a directory\ into itself" } } if {[file exists $dest]} { if {$nsrc eq $ndest} { return -code error "error $action \"$src\" to\ \"$dest\": trying to rename a volume or move a directory\ into itself" } if {$action eq "copying"} { # We used to throw an error here, but, looking more closely # at the core copy code in tclFCmd.c, if the destination # exists, then we should only call this function if -force # is true, which means we just want to over-write. So, # the following code is now commented out. # # return -code error "error $action \"$src\" to\ # \"$dest\": file exists" } else { # Depending on the platform, and on the current # working directory, the directories '.', '..' # can be returned in various combinations. Anyway, # if any other file is returned, we must signal an error. set existing [glob -nocomplain -directory $dest * .*] lappend existing {*}[glob -nocomplain -directory $dest \ -type hidden * .*] foreach s $existing { if {[file tail $s] ni {. ..}} { return -code error "error $action \"$src\" to\ \"$dest\": file exists" } } } } else { if {[string first $nsrc $ndest] >= 0} { set srclen [expr {[llength [file split $nsrc]] - 1}] set ndest [lindex [file split $ndest] $srclen] if {$ndest eq [file tail $nsrc]} { return -code error "error $action \"$src\" to\ \"$dest\": trying to rename a volume or move a directory\ into itself" } } file mkdir $dest } # Have to be careful to capture both visible and hidden files. # We will also be more generous to the file system and not # assume the hidden and non-hidden lists are non-overlapping. # # On Unix 'hidden' files begin with '.'. On other platforms # or filesystems hidden files may have other interpretations. set filelist [concat [glob -nocomplain -directory $src *] \ [glob -nocomplain -directory $src -types hidden *]] foreach s [lsort -unique $filelist] { if {[file tail $s] ni {. ..}} { file copy -force -- $s [file join $dest [file tail $s]] } } return } tcl9.0.1/library/install.tcl0000644000175000017500000001636514726623136015407 0ustar sergeisergei### # Installer actions built into tclsh and invoked # if the first command line argument is "install" ### if {[llength $argv] < 2} { exit 0 } namespace eval ::practcl {} ### # Installer tools ### proc ::practcl::_isdirectory name { return [file isdirectory $name] } ### # Return true if the pkgindex file contains # any statement other than "package ifneeded" # and/or if any package ifneeded loads a DLL ### proc ::practcl::_pkgindex_directory {path} { set buffer {} set pkgidxfile [file join $path pkgIndex.tcl] if {![file exists $pkgidxfile]} { # No pkgIndex file, read the source foreach file [glob -nocomplain $path/*.tm] { set file [file normalize $file] set fname [file rootname [file tail $file]] ### # We used to be able to ... Assume the package is correct in the filename # No hunt for a "package provides" ### set package [lindex [split $fname -] 0] set version [lindex [split $fname -] 1] ### # Read the file, and override assumptions as needed ### set fin [open $file r] fconfigure $fin -encoding utf-8 -eofchar \x1A set dat [read $fin] close $fin # Look for a teapot style Package statement foreach line [split $dat \n] { set line [string trim $line] if { [string range $line 0 9] != "# Package " } continue set package [lindex $line 2] set version [lindex $line 3] break } # Look for a package provide statement foreach line [split $dat \n] { set line [string trim $line] if { [string range $line 0 14] != "package provide" } continue set package [lindex $line 2] set version [lindex $line 3] break } append buffer "package ifneeded $package $version \[list source \[file join \$dir [file tail $file]\]\]" \n } foreach file [glob -nocomplain $path/*.tcl] { if { [file tail $file] == "version_info.tcl" } continue set fin [open $file r] fconfigure $fin -encoding utf-8 -eofchar \x1A set dat [read $fin] close $fin if {![regexp "package provide" $dat]} continue set fname [file rootname [file tail $file]] # Look for a package provide statement foreach line [split $dat \n] { set line [string trim $line] if { [string range $line 0 14] != "package provide" } continue set package [lindex $line 2] set version [lindex $line 3] if {[string index $package 0] in "\$ \[ @"} continue if {[string index $version 0] in "\$ \[ @"} continue append buffer "package ifneeded $package $version \[list source \[file join \$dir [file tail $file]\]\]" \n break } } return $buffer } set fin [open $pkgidxfile r] fconfigure $fin -encoding utf-8 -eofchar \x1A set dat [read $fin] close $fin set trace 0 #if {[file tail $path] eq "tool"} { # set trace 1 #} set thisline {} foreach line [split $dat \n] { append thisline $line \n if {![info complete $thisline]} continue set line [string trim $line] if {[string length $line]==0} { set thisline {} ; continue } if {[string index $line 0] eq "#"} { set thisline {} ; continue } if {[regexp "if.*catch.*package.*Tcl.*return" $thisline]} { if {$trace} {puts "[file dirname $pkgidxfile] Ignoring $thisline"} set thisline {} ; continue } if {[regexp "if.*package.*vsatisfies.*package.*provide.*return" $thisline]} { if {$trace} { puts "[file dirname $pkgidxfile] Ignoring $thisline" } set thisline {} ; continue } if {![regexp "package.*ifneeded" $thisline]} { # This package index contains arbitrary code # source instead of trying to add it to the main # package index if {$trace} { puts "[file dirname $pkgidxfile] Arbitrary code $thisline" } return {source [file join $dir pkgIndex.tcl]} } append buffer $thisline \n set thisline {} } if {$trace} {puts [list [file dirname $pkgidxfile] $buffer]} return $buffer } proc ::practcl::_pkgindex_path_subdir {path} { set result {} foreach subpath [glob -nocomplain [file join $path *]] { if {[file isdirectory $subpath]} { lappend result $subpath {*}[_pkgindex_path_subdir $subpath] } } return $result } ### # Index all paths given as though they will end up in the same # virtual file system ### proc ::practcl::pkgindex_path args { set stack {} set buffer { lappend ::PATHSTACK $dir } foreach base $args { set base [file normalize $base] set paths {} foreach dir [glob -nocomplain [file join $base *]] { if {[file tail $dir] eq "teapot"} continue lappend paths $dir {*}[::practcl::_pkgindex_path_subdir $dir] } set i [string length $base] # Build a list of all of the paths if {[llength $paths]} { foreach path $paths { if {$path eq $base} continue set path_indexed($path) 0 } } else { puts [list WARNING: NO PATHS FOUND IN $base] } set path_indexed($base) 1 set path_indexed([file join $base boot tcl]) 1 foreach teapath [glob -nocomplain [file join $base teapot *]] { set pkg [file tail $teapath] append buffer [list set pkg $pkg] append buffer { set pkginstall [file join $::g(HOME) teapot $pkg] if {![file exists $pkginstall]} { installDir [file join $dir teapot $pkg] $pkginstall } } } foreach path $paths { if {$path_indexed($path)} continue set thisdir [file_relative $base $path] set idxbuf [::practcl::_pkgindex_directory $path] if {[string length $idxbuf]} { incr path_indexed($path) append buffer "set dir \[set PKGDIR \[file join \[lindex \$::PATHSTACK end\] $thisdir\]\]" \n append buffer [string map {$dir $PKGDIR} [string trimright $idxbuf]] \n } } } append buffer { set dir [lindex $::PATHSTACK end] set ::PATHSTACK [lrange $::PATHSTACK 0 end-1] } return $buffer } ### # topic: 64319f4600fb63c82b2258d908f9d066 # description: Script to build the VFS file system ### proc ::practcl::installDir {d1 d2} { puts [format {%*sCreating %s} [expr {4 * [info level]}] {} [file tail $d2]] file delete -force -- $d2 file mkdir $d2 foreach ftail [glob -directory $d1 -nocomplain -tails *] { set f [file join $d1 $ftail] if {[file isdirectory $f] && [string compare CVS $ftail]} { installDir $f [file join $d2 $ftail] } elseif {[file isfile $f]} { file copy -force $f [file join $d2 $ftail] if {$::tcl_platform(platform) eq {unix}} { file attributes [file join $d2 $ftail] -permissions 0o644 } else { file attributes [file join $d2 $ftail] -readonly 1 } } } if {$::tcl_platform(platform) eq {unix}} { file attributes $d2 -permissions 0o755 } else { file attributes $d2 -readonly 1 } } proc ::practcl::copyDir {d1 d2 {toplevel 1}} { #if {$toplevel} { # puts [list ::practcl::copyDir $d1 -> $d2] #} #file delete -force -- $d2 file mkdir $d2 foreach ftail [glob -directory $d1 -nocomplain -tails *] { set f [file join $d1 $ftail] if {[file isdirectory $f] && [string compare CVS $ftail]} { copyDir $f [file join $d2 $ftail] 0 } elseif {[file isfile $f]} { file copy -force $f [file join $d2 $ftail] } } } switch [lindex $argv 1] { mkzip { zipfs mkzip {*}[lrange $argv 2 end] } mkzip { zipfs mkimg {*}[lrange $argv 2 end] } default { ::practcl::[lindex $argv 1] {*}[lrange $argv 2 end] } } exit 0 tcl9.0.1/library/package.tcl0000644000175000017500000005630514726623136015332 0ustar sergeisergei# package.tcl -- # # utility procs formerly in init.tcl which can be loaded on demand # for package management. # # Copyright © 1991-1993 The Regents of the University of California. # Copyright © 1994-1998 Sun Microsystems, Inc. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # namespace eval tcl::Pkg {} # ::tcl::Pkg::CompareExtension -- # # Used internally by pkg_mkIndex to compare the extension of a file to a given # extension. On Windows, it uses a case-insensitive comparison because the # file system can be file insensitive. # # Arguments: # fileName name of a file whose extension is compared # ext (optional) The extension to compare against; you must # provide the starting dot. # Defaults to [info sharedlibextension] # # Results: # Returns 1 if the extension matches, 0 otherwise proc tcl::Pkg::CompareExtension {fileName {ext {}}} { global tcl_platform if {$ext eq ""} {set ext [info sharedlibextension]} if {$tcl_platform(platform) eq "windows"} { return [string equal -nocase [file extension $fileName] $ext] } else { # Some unices add trailing numbers after the .so, so # we could have something like '.so.1.2'. set root $fileName while {1} { set currExt [file extension $root] if {$currExt eq $ext} { return 1 } # The current extension does not match; if it is not a numeric # value, quit, as we are only looking to ignore version number # extensions. Otherwise we might return 1 in this case: # tcl::Pkg::CompareExtension foo.so.bar .so # which should not match. if {![string is integer -strict [string range $currExt 1 end]]} { return 0 } set root [file rootname $root] } } } # pkg_mkIndex -- # This procedure creates a package index in a given directory. The package # index consists of a "pkgIndex.tcl" file whose contents are a Tcl script that # sets up package information with "package require" commands. The commands # describe all of the packages defined by the files given as arguments. # # Arguments: # -direct (optional) If this flag is present, the generated # code in pkgMkIndex.tcl will cause the package to be # loaded when "package require" is executed, rather # than lazily when the first reference to an exported # procedure in the package is made. # -verbose (optional) Verbose output; the name of each file that # was successfully processed is printed out. Additionally, # if processing of a file failed a message is printed. # -load pat (optional) Preload any packages whose names match # the pattern. Used to handle DLLs that depend on # other packages during their Init procedure. # dir - Name of the directory in which to create the index. # args - Any number of additional arguments, each giving # a glob pattern that matches the names of one or # more shared libraries or Tcl script files in # dir. proc pkg_mkIndex {args} { set usage {"pkg_mkIndex ?-direct? ?-lazy? ?-load pattern? ?-verbose? ?--? dir ?pattern ...?"} set argCount [llength $args] if {$argCount < 1} { return -code error "wrong # args: should be\n$usage" } set more "" set direct 1 set doVerbose 0 set loadPat "" for {set idx 0} {$idx < $argCount} {incr idx} { set flag [lindex $args $idx] switch -glob -- $flag { -- { # done with the flags incr idx break } -verbose { set doVerbose 1 } -lazy { set direct 0 append more " -lazy" } -direct { append more " -direct" } -load { incr idx set loadPat [lindex $args $idx] append more " -load $loadPat" } -* { return -code error "unknown flag $flag: should be\n$usage" } default { # done with the flags break } } } set dir [lindex $args $idx] set patternList [lrange $args [expr {$idx + 1}] end] if {![llength $patternList]} { set patternList [list "*.tcl" "*[info sharedlibextension]"] } try { set fileList [glob -directory $dir -tails -types {r f} -- \ {*}$patternList] } on error {msg opt} { return -options $opt $msg } if {[llength $fileList] == 0} { return -code error "no files matched glob pattern \"$patternList\"" } foreach file $fileList { # For each file, figure out what commands and packages it provides. # To do this, create a child interpreter, load the file into the # interpreter, and get a list of the new commands and packages that # are defined. if {$file eq "pkgIndex.tcl"} { continue } set c [interp create] # Load into the child any packages currently loaded in the parent # interpreter that match the -load pattern. if {$loadPat ne ""} { if {$doVerbose} { tclLog "currently loaded packages: '[info loaded]'" tclLog "trying to load all packages matching $loadPat" } if {![llength [info loaded]]} { tclLog "warning: no packages are currently loaded, nothing" tclLog "can possibly match '$loadPat'" } } foreach pkg [info loaded] { if {![string match -nocase $loadPat [lindex $pkg 1]]} { continue } if {$doVerbose} { tclLog "package [lindex $pkg 1] matches '$loadPat'" } try { load [lindex $pkg 0] [lindex $pkg 1] $c } on error err { if {$doVerbose} { tclLog "warning: load [lindex $pkg 0]\ [lindex $pkg 1]\nfailed with: $err" } } on ok {} { if {$doVerbose} { tclLog "loaded [lindex $pkg 0] [lindex $pkg 1]" } } if {[lindex $pkg 1] eq "Tk"} { # Withdraw . if Tk was loaded, to avoid showing a window. $c eval [list wm withdraw .] } } $c eval { # Stub out the package command so packages can require other # packages. rename package __package_orig proc package {what args} { switch -- $what { require { return; # Ignore transitive requires } default { __package_orig $what {*}$args } } } proc tclPkgUnknown args {} package unknown tclPkgUnknown # Stub out the unknown command so package can call into each other # during their initialization. proc unknown {args} {} # Stub out the auto_import mechanism proc auto_import {args} {} # reserve the ::tcl namespace for support procs and temporary # variables. This might make it awkward to generate a # pkgIndex.tcl file for the ::tcl namespace. namespace eval ::tcl { variable dir ;# Current directory being processed variable file ;# Current file being processed variable direct ;# -direct flag value variable x ;# Loop variable variable debug ;# For debugging variable type ;# "load" or "source", for -direct variable namespaces ;# Existing namespaces (e.g., ::tcl) variable packages ;# Existing packages (e.g., Tcl) variable origCmds ;# Existing commands variable newCmds ;# Newly created commands variable newPkgs {} ;# Newly created packages } } $c eval [list set ::tcl::dir $dir] $c eval [list set ::tcl::file $file] $c eval [list set ::tcl::direct $direct] # Download needed procedures into the child because we've just deleted # the unknown procedure. This doesn't handle procedures with default # arguments. foreach p {::tcl::Pkg::CompareExtension} { $c eval [list namespace eval [namespace qualifiers $p] {}] $c eval [list proc $p [info args $p] [info body $p]] } try { $c eval { set ::tcl::debug "loading or sourcing" # we need to track command defined by each package even in the # -direct case, because they are needed internally by the # "partial pkgIndex.tcl" step above. proc ::tcl::GetAllNamespaces {{root ::}} { set list $root foreach ns [namespace children $root] { lappend list {*}[::tcl::GetAllNamespaces $ns] } return $list } # init the list of existing namespaces, packages, commands foreach ::tcl::x [::tcl::GetAllNamespaces] { set ::tcl::namespaces($::tcl::x) 1 } foreach ::tcl::x [package names] { if {[package provide $::tcl::x] ne ""} { set ::tcl::packages($::tcl::x) 1 } } set ::tcl::origCmds [info commands] # Try to load the file if it has the shared library extension, # otherwise source it. It's important not to try to load # files that aren't shared libraries, because on some systems # (like SunOS) the loader will abort the whole application # when it gets an error. if {[::tcl::Pkg::CompareExtension $::tcl::file [info sharedlibextension]]} { # The "file join ." command below is necessary. Without # it, if the file name has no \'s and we're on UNIX, the # load command will invoke the LD_LIBRARY_PATH search # mechanism, which could cause the wrong file to be used. set ::tcl::debug loading load [file join $::tcl::dir $::tcl::file] set ::tcl::type load } else { set ::tcl::debug sourcing source [file join $::tcl::dir $::tcl::file] set ::tcl::type source } # As a performance optimization, if we are creating direct # load packages, don't bother figuring out the set of commands # created by the new packages. We only need that list for # setting up the autoloading used in the non-direct case. if {!$::tcl::direct} { # See what new namespaces appeared, and import commands # from them. Only exported commands go into the index. foreach ::tcl::x [::tcl::GetAllNamespaces] { if {![info exists ::tcl::namespaces($::tcl::x)]} { namespace import -force ${::tcl::x}::* } # Figure out what commands appeared foreach ::tcl::x [info commands] { set ::tcl::newCmds($::tcl::x) 1 } foreach ::tcl::x $::tcl::origCmds { unset -nocomplain ::tcl::newCmds($::tcl::x) } foreach ::tcl::x [array names ::tcl::newCmds] { # determine which namespace a command comes from set ::tcl::abs [namespace origin $::tcl::x] # special case so that global names have no # leading ::, this is required by the unknown # command set ::tcl::abs \ [lindex [auto_qualify $::tcl::abs ::] 0] if {$::tcl::x ne $::tcl::abs} { # Name changed during qualification set ::tcl::newCmds($::tcl::abs) 1 unset ::tcl::newCmds($::tcl::x) } } } } # Look through the packages that appeared, and if there is a # version provided, then record it foreach ::tcl::x [package names] { if {[package provide $::tcl::x] ne "" && ![info exists ::tcl::packages($::tcl::x)]} { lappend ::tcl::newPkgs \ [list $::tcl::x [package provide $::tcl::x]] } } } } on error msg { set what [$c eval set ::tcl::debug] if {$doVerbose} { tclLog "warning: error while $what $file: $msg" } } on ok {} { set what [$c eval set ::tcl::debug] if {$doVerbose} { tclLog "successful $what of $file" } set type [$c eval set ::tcl::type] set cmds [lsort [$c eval array names ::tcl::newCmds]] set pkgs [$c eval set ::tcl::newPkgs] if {$doVerbose} { if {!$direct} { tclLog "commands provided were $cmds" } tclLog "packages provided were $pkgs" } if {[llength $pkgs] > 1} { tclLog "warning: \"$file\" provides more than one package ($pkgs)" } foreach pkg $pkgs { # cmds is empty/not used in the direct case lappend files($pkg) [list $file $type $cmds] } if {$doVerbose} { tclLog "processed $file" } } interp delete $c } append index "# Tcl package index file, version 1.1\n" append index "# This file is generated by the \"pkg_mkIndex$more\" command\n" append index "# and sourced either when an application starts up or\n" append index "# by a \"package unknown\" script. It invokes the\n" append index "# \"package ifneeded\" command to set up package-related\n" append index "# information so that packages will be loaded automatically\n" append index "# in response to \"package require\" commands. When this\n" append index "# script is sourced, the variable \$dir must contain the\n" append index "# full path name of this file's directory.\n" foreach pkg [lsort [array names files]] { set cmd {} lassign $pkg name version lappend cmd ::tcl::Pkg::Create -name $name -version $version foreach spec [lsort -index 0 $files($pkg)] { foreach {file type procs} $spec { if {$direct} { set procs {} } lappend cmd "-$type" [list $file $procs] } } append index "\n[eval $cmd]" } set f [open [file join $dir pkgIndex.tcl] w] fconfigure $f -encoding utf-8 -translation lf puts $f $index close $f } # tclPkgSetup -- # This is a utility procedure use by pkgIndex.tcl files. It is invoked as # part of a "package ifneeded" script. It calls "package provide" to indicate # that a package is available, then sets entries in the auto_index array so # that the package's files will be auto-loaded when the commands are used. # # Arguments: # dir - Directory containing all the files for this package. # pkg - Name of the package (no version number). # version - Version number for the package, such as 2.1.3. # files - List of files that constitute the package. Each # element is a sub-list with three elements. The first # is the name of a file relative to $dir, the second is # "load" or "source", indicating whether the file is a # loadable binary or a script to source, and the third # is a list of commands defined by this file. proc tclPkgSetup {dir pkg version files} { global auto_index package provide $pkg $version foreach fileInfo $files { set f [lindex $fileInfo 0] set type [lindex $fileInfo 1] foreach cmd [lindex $fileInfo 2] { if {$type eq "load"} { set auto_index($cmd) [list load [file join $dir $f] $pkg] } else { set auto_index($cmd) [list source [file join $dir $f]] } } } } # tclPkgUnknown -- # This procedure provides the default for the "package unknown" function. It # is invoked when a package that's needed can't be found. It scans the # auto_path directories and their immediate children looking for pkgIndex.tcl # files and sources any such files that are found to setup the package # database. As it searches, it will recognize changes to the auto_path and # scan any new directories. # # Arguments: # name - Name of desired package. Not used. # version - Version of desired package. Not used. # exact - Either "-exact" or omitted. Not used. proc tclPkgUnknown {name args} { global auto_path env if {![info exists auto_path]} { return } # Cache the auto_path, because it may change while we run through the # first set of pkgIndex.tcl files set old_path [set use_path $auto_path] while {[llength $use_path]} { set dir [lindex $use_path end] # Make sure we only scan each directory one time. if {[info exists tclSeenPath($dir)]} { set use_path [lrange $use_path 0 end-1] continue } set tclSeenPath($dir) 1 # Get the pkgIndex.tcl files in subdirectories of auto_path directories. # - Safe Base interpreters have a restricted "glob" command that # works in this case. # - The "catch" was essential when there was no safe glob and every # call in a safe interp failed; it is retained only for corner # cases in which the eventual call to glob returns an error. catch { foreach file [glob -directory $dir -join -nocomplain \ * pkgIndex.tcl] { set dir [file dirname $file] if {![info exists procdDirs($dir)]} { try { ::tcl::Pkg::source $file } trap {POSIX EACCES} {} { # $file was not readable; silently ignore continue } on error msg { if {[regexp {version conflict for package} $msg]} { # In case of version conflict, silently ignore continue } tclLog "error reading package index file $file: $msg" } on ok {} { set procdDirs($dir) 1 } } } } set dir [lindex $use_path end] if {![info exists procdDirs($dir)]} { set file [file join $dir pkgIndex.tcl] # safe interps usually don't have "file exists", if {([interp issafe] || [file exists $file])} { try { ::tcl::Pkg::source $file } trap {POSIX EACCES} {} { # $file was not readable; silently ignore continue } on error msg { if {[regexp {version conflict for package} $msg]} { # In case of version conflict, silently ignore continue } tclLog "error reading package index file $file: $msg" } on ok {} { set procdDirs($dir) 1 } } } set use_path [lrange $use_path 0 end-1] # Check whether any of the index scripts we [source]d above set a new # value for $::auto_path. If so, then find any new directories on the # $::auto_path, and lappend them to the $use_path we are working from. # This gives index scripts the (arguably unwise) power to expand the # index script search path while the search is in progress. set index 0 if {[llength $old_path] == [llength $auto_path]} { foreach dir $auto_path old $old_path { if {$dir ne $old} { # This entry in $::auto_path has changed. break } incr index } } # $index now points to the first element of $auto_path that has # changed, or the beginning if $auto_path has changed length Scan the # new elements of $auto_path for directories to add to $use_path. # Don't add directories we've already seen, or ones already on the # $use_path. foreach dir [lrange $auto_path $index end] { if {![info exists tclSeenPath($dir)] && ($dir ni $use_path)} { lappend use_path $dir } } set old_path $auto_path } } # tcl::MacOSXPkgUnknown -- # This procedure extends the "package unknown" function for MacOSX. It scans # the Resources/Scripts directories of the immediate children of the auto_path # directories for pkgIndex files. # # Arguments: # original - original [package unknown] procedure # name - Name of desired package. Not used. # version - Version of desired package. Not used. # exact - Either "-exact" or omitted. Not used. proc tcl::MacOSXPkgUnknown {original name args} { # First do the cross-platform default search uplevel 1 $original [linsert $args 0 $name] # Now do MacOSX specific searching global auto_path if {![info exists auto_path]} { return } # Cache the auto_path, because it may change while we run through the # first set of pkgIndex.tcl files set old_path [set use_path $auto_path] while {[llength $use_path]} { set dir [lindex $use_path end] # Make sure we only scan each directory one time. if {[info exists tclSeenPath($dir)]} { set use_path [lrange $use_path 0 end-1] continue } set tclSeenPath($dir) 1 # get the pkgIndex files out of the subdirectories # Safe interpreters do not use tcl::MacOSXPkgUnknown - see init.tcl. foreach file [glob -directory $dir -join -nocomplain \ * Resources Scripts pkgIndex.tcl] { set dir [file dirname $file] if {![info exists procdDirs($dir)]} { try { ::tcl::Pkg::source $file } trap {POSIX EACCES} {} { # $file was not readable; silently ignore continue } on error msg { if {[regexp {version conflict for package} $msg]} { # In case of version conflict, silently ignore continue } tclLog "error reading package index file $file: $msg" } on ok {} { set procdDirs($dir) 1 } } } set use_path [lrange $use_path 0 end-1] # Check whether any of the index scripts we [source]d above set a new # value for $::auto_path. If so, then find any new directories on the # $::auto_path, and lappend them to the $use_path we are working from. # This gives index scripts the (arguably unwise) power to expand the # index script search path while the search is in progress. set index 0 if {[llength $old_path] == [llength $auto_path]} { foreach dir $auto_path old $old_path { if {$dir ne $old} { # This entry in $::auto_path has changed. break } incr index } } # $index now points to the first element of $auto_path that has # changed, or the beginning if $auto_path has changed length Scan the # new elements of $auto_path for directories to add to $use_path. # Don't add directories we've already seen, or ones already on the # $use_path. foreach dir [lrange $auto_path $index end] { if {![info exists tclSeenPath($dir)] && ($dir ni $use_path)} { lappend use_path $dir } } set old_path $auto_path } } # ::tcl::Pkg::Create -- # # Given a package specification generate a "package ifneeded" statement # for the package, suitable for inclusion in a pkgIndex.tcl file. # # Arguments: # args arguments used by the Create function: # -name packageName # -version packageVersion # -load {filename ?{procs}?} # ... # -source {filename ?{procs}?} # ... # # Any number of -load and -source parameters may be # specified, so long as there is at least one -load or # -source parameter. If the procs component of a module # specifier is left off, that module will be set up for # direct loading; otherwise, it will be set up for lazy # loading. If both -source and -load are specified, the # -load'ed files will be loaded first, followed by the # -source'd files. # # Results: # An appropriate "package ifneeded" statement for the package. proc ::tcl::Pkg::Create {args} { append err(usage) "[lindex [info level 0] 0] " append err(usage) "-name packageName -version packageVersion" append err(usage) "?-load {filename ?{procs}?}? ... " append err(usage) "?-source {filename ?{procs}?}? ..." set err(wrongNumArgs) "wrong # args: should be \"$err(usage)\"" set err(valueMissing) "value for \"%s\" missing: should be \"$err(usage)\"" set err(unknownOpt) "unknown option \"%s\": should be \"$err(usage)\"" set err(noLoadOrSource) "at least one of -load and -source must be given" # process arguments set len [llength $args] if {$len < 6} { error $err(wrongNumArgs) } # Initialize parameters array set opts {-name {} -version {} -source {} -load {}} # process parameters for {set i 0} {$i < $len} {incr i} { set flag [lindex $args $i] incr i switch -glob -- $flag { "-name" - "-version" { if {$i >= $len} { error [format $err(valueMissing) $flag] } set opts($flag) [lindex $args $i] } "-source" - "-load" { if {$i >= $len} { error [format $err(valueMissing) $flag] } lappend opts($flag) [lindex $args $i] } default { error [format $err(unknownOpt) [lindex $args $i]] } } } # Validate the parameters if {![llength $opts(-name)]} { error [format $err(valueMissing) "-name"] } if {![llength $opts(-version)]} { error [format $err(valueMissing) "-version"] } if {!([llength $opts(-source)] || [llength $opts(-load)])} { error $err(noLoadOrSource) } # OK, now everything is good. Generate the package ifneeded statement. set cmdline "package ifneeded $opts(-name) $opts(-version) " set cmdList {} set lazyFileList {} # Handle -load and -source specs foreach key {load source} { foreach filespec $opts(-$key) { lassign $filespec filename proclist if { [llength $proclist] == 0 } { set cmd "\[list $key \[file join \$dir [list $filename]\]\]" lappend cmdList $cmd } else { lappend lazyFileList [list $filename $key $proclist] } } } if {[llength $lazyFileList]} { lappend cmdList "\[list tclPkgSetup \$dir $opts(-name)\ $opts(-version) [list $lazyFileList]\]" } append cmdline [join $cmdList "\\n"] return $cmdline } interp alias {} ::pkg::create {} ::tcl::Pkg::Create tcl9.0.1/library/parray.tcl0000644000175000017500000000145614726623136015232 0ustar sergeisergei# parray: # Print the contents of a global array on stdout. # # Copyright © 1991-1993 The Regents of the University of California. # Copyright © 1994 Sun Microsystems, Inc. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # proc parray {a {pattern *}} { upvar 1 $a array if {![array exists array]} { return -code error "\"$a\" isn't an array" } set maxl 0 set names [lsort [array names array $pattern]] foreach name $names { if {[string length $name] > $maxl} { set maxl [string length $name] } } set maxl [expr {$maxl + [string length $a] + 2}] foreach name $names { set nameString [format %s(%s) $a $name] puts stdout [format "%-*s = %s" $maxl $nameString $array($name)] } } tcl9.0.1/library/readfile.tcl0000644000175000017500000000112714726623136015502 0ustar sergeisergei# readFile: # Read the contents of a file. # # Copyright © 2023 Donal K Fellows. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # proc readFile {filename {mode text}} { # Parse the arguments set MODES {binary text} set ERR [list -level 1 -errorcode [list TCL LOOKUP MODE $mode]] set mode [tcl::prefix match -message "mode" -error $ERR $MODES $mode] # Read the file set f [open $filename [dict get {text r binary rb} $mode]] try { return [read $f] } finally { close $f } } tcl9.0.1/library/safe.tcl0000644000175000017500000013362414726623136014655 0ustar sergeisergei# safe.tcl -- # # This file provide a safe loading/sourcing mechanism for safe interpreters. # It implements a virtual path mecanism to hide the real pathnames from the # child. It runs in a parent interpreter and sets up data structure and # aliases that will be invoked when used from a child interpreter. # # See the safe.n man page for details. # # Copyright © 1996-1997 Sun Microsystems, Inc. # # See the file "license.terms" for information on usage and redistribution of # this file, and for a DISCLAIMER OF ALL WARRANTIES. # # The implementation is based on namespaces. These naming conventions are # followed: # Private procs starts with uppercase. # Public procs are exported and starts with lowercase # # Needed utilities package package require opt 0.4.9 # Create the safe namespace namespace eval ::safe { # Exported API: namespace export interpCreate interpInit interpConfigure interpDelete \ interpAddToAccessPath interpFindInAccessPath setLogCmd } # Helper function to resolve the dual way of specifying staticsok (either # by -noStatics or -statics 0) proc ::safe::InterpStatics {} { foreach v {Args statics noStatics} { upvar $v $v } set flag [::tcl::OptProcArgGiven -noStatics] if {$flag && (!$noStatics == !$statics) && ([::tcl::OptProcArgGiven -statics])} { return -code error\ "conflicting values given for -statics and -noStatics" } if {$flag} { return [expr {!$noStatics}] } else { return $statics } } # Helper function to resolve the dual way of specifying nested loading # (either by -nestedLoadOk or -nested 1) proc ::safe::InterpNested {} { foreach v {Args nested nestedLoadOk} { upvar $v $v } set flag [::tcl::OptProcArgGiven -nestedLoadOk] # note that the test here is the opposite of the "InterpStatics" one # (it is not -noNested... because of the wanted default value) if {$flag && (!$nestedLoadOk != !$nested) && ([::tcl::OptProcArgGiven -nested])} { return -code error\ "conflicting values given for -nested and -nestedLoadOk" } if {$flag} { # another difference with "InterpStatics" return $nestedLoadOk } else { return $nested } } #### # # API entry points that needs argument parsing : # #### # Interface/entry point function and front end for "Create" proc ::safe::interpCreate {args} { variable AutoPathSync if {$AutoPathSync} { set autoPath {} } set Args [::tcl::OptKeyParse ::safe::interpCreate $args] RejectExcessColons $child set withAutoPath [::tcl::OptProcArgGiven -autoPath] InterpCreate $child $accessPath \ [InterpStatics] [InterpNested] $deleteHook $autoPath $withAutoPath } proc ::safe::interpInit {args} { variable AutoPathSync if {$AutoPathSync} { set autoPath {} } set Args [::tcl::OptKeyParse ::safe::interpIC $args] if {![::interp exists $child]} { return -code error "\"$child\" is not an interpreter" } RejectExcessColons $child set withAutoPath [::tcl::OptProcArgGiven -autoPath] InterpInit $child $accessPath \ [InterpStatics] [InterpNested] $deleteHook $autoPath $withAutoPath } # Check that the given child is "one of us" proc ::safe::CheckInterp {child} { namespace upvar ::safe [VarName $child] state if {![info exists state] || ![::interp exists $child]} { return -code error \ "\"$child\" is not an interpreter managed by ::safe::" } } # Interface/entry point function and front end for "Configure". This code # is awfully pedestrian because it would need more coupling and support # between the way we store the configuration values in safe::interp's and # the Opt package. Obviously we would like an OptConfigure to avoid # duplicating all this code everywhere. # -> TODO (the app should share or access easily the program/value stored # by opt) # This is even more complicated by the boolean flags with no values that # we had the bad idea to support for the sake of user simplicity in # create/init but which makes life hard in configure... # So this will be hopefully written and some integrated with opt1.0 # (hopefully for tcl9.0 ?) proc ::safe::interpConfigure {args} { variable AutoPathSync switch [llength $args] { 1 { # If we have exactly 1 argument the semantic is to return all # the current configuration. We still call OptKeyParse though # we know that "child" is our given argument because it also # checks for the "-help" option. set Args [::tcl::OptKeyParse ::safe::interpIC $args] CheckInterp $child namespace upvar ::safe [VarName $child] state set TMP [list \ [list -accessPath $state(access_path)] \ [list -statics $state(staticsok)] \ [list -nested $state(nestedok)] \ [list -deleteHook $state(cleanupHook)] \ ] if {!$AutoPathSync} { lappend TMP [list -autoPath $state(auto_path)] } return [join $TMP] } 2 { # If we have exactly 2 arguments the semantic is a "configure # get" lassign $args child arg # get the flag sub program (we 'know' about Opt's internal # representation of data) set desc [lindex [::tcl::OptKeyGetDesc ::safe::interpIC] 2] set hits [::tcl::OptHits desc $arg] if {$hits > 1} { return -code error [::tcl::OptAmbigous $desc $arg] } elseif {$hits == 0} { return -code error [::tcl::OptFlagUsage $desc $arg] } CheckInterp $child namespace upvar ::safe [VarName $child] state set item [::tcl::OptCurDesc $desc] set name [::tcl::OptName $item] switch -exact -- $name { -accessPath { return [list -accessPath $state(access_path)] } -autoPath { if {$AutoPathSync} { return -code error "unknown flag $name (bug)" } else { return [list -autoPath $state(auto_path)] } } -statics { return [list -statics $state(staticsok)] } -nested { return [list -nested $state(nestedok)] } -deleteHook { return [list -deleteHook $state(cleanupHook)] } -noStatics { # it is most probably a set in fact but we would need # then to jump to the set part and it is not *sure* # that it is a set action that the user want, so force # it to use the unambiguous -statics ?value? instead: return -code error\ "ambigous query (get or set -noStatics ?)\ use -statics instead" } -nestedLoadOk { return -code error\ "ambigous query (get or set -nestedLoadOk ?)\ use -nested instead" } default { return -code error "unknown flag $name (bug)" } } } default { # Otherwise we want to parse the arguments like init and # create did set Args [::tcl::OptKeyParse ::safe::interpIC $args] CheckInterp $child namespace upvar ::safe [VarName $child] state # Get the current (and not the default) values of whatever has # not been given: if {![::tcl::OptProcArgGiven -accessPath]} { set doreset 0 set accessPath $state(access_path) } else { set doreset 1 } if {(!$AutoPathSync) && (![::tcl::OptProcArgGiven -autoPath])} { set autoPath $state(auto_path) } elseif {$AutoPathSync} { set autoPath {} } else { } if { ![::tcl::OptProcArgGiven -statics] && ![::tcl::OptProcArgGiven -noStatics] } then { set statics $state(staticsok) } else { set statics [InterpStatics] } if { [::tcl::OptProcArgGiven -nested] || [::tcl::OptProcArgGiven -nestedLoadOk] } then { set nested [InterpNested] } else { set nested $state(nestedok) } if {![::tcl::OptProcArgGiven -deleteHook]} { set deleteHook $state(cleanupHook) } # Now reconfigure set withAutoPath [::tcl::OptProcArgGiven -autoPath] InterpSetConfig $child $accessPath $statics $nested $deleteHook $autoPath $withAutoPath # auto_reset the child (to completely sync the new access_path) tests safe-9.8 safe-9.9 if {$doreset} { if {[catch {::interp eval $child {auto_reset}} msg]} { Log $child "auto_reset failed: $msg" } else { Log $child "successful auto_reset" NOTICE } # Sync the paths used to search for Tcl modules. ::interp eval $child {tcl::tm::path remove {*}[tcl::tm::list]} if {[llength $state(tm_path_child)] > 0} { ::interp eval $child [list \ ::tcl::tm::add {*}[lreverse $state(tm_path_child)]] } # Remove stale "package ifneeded" data for non-loaded packages. # - Not for loaded packages, because "package forget" erases # data from "package provide" as well as "package ifneeded". # - This is OK because the script cannot reload any version of # the package unless it first does "package forget". foreach pkg [::interp eval $child {package names}] { if {[::interp eval $child [list package provide $pkg]] eq ""} { ::interp eval $child [list package forget $pkg] } } } return } } } #### # # Functions that actually implements the exported APIs # #### # # safe::InterpCreate : doing the real job # # This procedure creates a safe interpreter and initializes it with the safe # base aliases. # NB: child name must be simple alphanumeric string, no spaces, no (), no # {},... {because the state array is stored as part of the name} # # Returns the child name. # # Optional Arguments : # + child name : if empty, generated name will be used # + access_path: path list controlling where load/source can occur, # if empty: the parent auto_path and its subdirectories will be # used. # + staticsok : flag, if 0 :no static package can be loaded (load {} Xxx) # if 1 :static packages are ok. # + nestedok : flag, if 0 :no loading to sub-sub interps (load xx xx sub) # if 1 : multiple levels are ok. # use the full name and no indent so auto_mkIndex can find us proc ::safe::InterpCreate { child access_path staticsok nestedok deletehook autoPath withAutoPath } { # Create the child. # If evaluated in ::safe, the interpreter command for foo is ::foo; # but for foo::bar is safe::foo::bar. So evaluate in :: instead. if {$child ne ""} { namespace eval :: [list ::interp create -safe $child] } else { # empty argument: generate child name set child [::interp create -safe] } Log $child "Created" NOTICE # Initialize it. (returns child name) InterpInit $child $access_path $staticsok $nestedok $deletehook $autoPath $withAutoPath } # # InterpSetConfig (was setAccessPath) : # Sets up child virtual access path and corresponding structure within # the parent. Also sets the tcl_library in the child to be the first # directory in the path. # NB: If you change the path after the child has been initialized you # probably need to call "auto_reset" in the child in order that it gets # the right auto_index() array values. # # It is the caller's responsibility, if it supplies a non-empty value for # access_path, to make the first directory in the path suitable for use as # tcl_library, and (if ![setSyncMode]), to set the child's ::auto_path. proc ::safe::InterpSetConfig {child access_path staticsok nestedok deletehook autoPath withAutoPath} { global auto_path variable AutoPathSync # determine and store the access path if empty if {$access_path eq ""} { set access_path $auto_path # Make sure that tcl_library is in auto_path and at the first # position (needed by setAccessPath) set where [lsearch -exact $access_path [info library]] if {$where < 0} { # not found, add it. set access_path [linsert $access_path 0 [info library]] Log $child "tcl_library was not in auto_path,\ added it to child's access_path" NOTICE } elseif {$where != 0} { # not first, move it first set access_path [linsert \ [lreplace $access_path $where $where] \ 0 [info library]] Log $child "tcl_libray was not in first in auto_path,\ moved it to front of child's access_path" NOTICE } set raw_auto_path $access_path # Add 1st level subdirs (will searched by auto loading from tcl # code in the child using glob and thus fail, so we add them here # so by default it works the same). set access_path [AddSubDirs $access_path] } else { set raw_auto_path $autoPath } if {$withAutoPath} { set raw_auto_path $autoPath } Log $child "Setting accessPath=($access_path) staticsok=$staticsok\ nestedok=$nestedok deletehook=($deletehook)" NOTICE if {!$AutoPathSync} { Log $child "Setting auto_path=($raw_auto_path)" NOTICE } namespace upvar ::safe [VarName $child] state # clear old autopath if it existed # build new one # Extend the access list with the paths used to look for Tcl Modules. # We save the virtual form separately as well, as syncing it with the # child has to be defered until the necessary commands are present for # setup. set norm_access_path {} set child_access_path {} set map_access_path {} set remap_access_path {} set child_tm_path {} set i 0 foreach dir $access_path { set token [PathToken $i] lappend child_access_path $token lappend map_access_path $token $dir lappend remap_access_path $dir $token lappend norm_access_path [file normalize $dir] incr i } # Set the child auto_path to a tokenized raw_auto_path. # Silently ignore any directories that are not in the access path. # If [setSyncMode], SyncAccessPath will overwrite this value with the # full access path. # If ![setSyncMode], Safe Base code will not change this value. set tokens_auto_path {} foreach dir $raw_auto_path { if {[dict exists $remap_access_path $dir]} { lappend tokens_auto_path [dict get $remap_access_path $dir] } } ::interp eval $child [list set auto_path $tokens_auto_path] # Add the tcl::tm directories to the access path. set morepaths [::tcl::tm::list] set firstpass 1 while {[llength $morepaths]} { set addpaths $morepaths set morepaths {} foreach dir $addpaths { # Prevent the addition of dirs on the tm list to the # result if they are already known. if {[dict exists $remap_access_path $dir]} { if {$firstpass} { # $dir is in [::tcl::tm::list] and belongs in the child_tm_path. # Later passes handle subdirectories, which belong in the # access path but not in the module path. lappend child_tm_path [dict get $remap_access_path $dir] } continue } set token [PathToken $i] lappend access_path $dir lappend child_access_path $token lappend map_access_path $token $dir lappend remap_access_path $dir $token lappend norm_access_path [file normalize $dir] if {$firstpass} { # $dir is in [::tcl::tm::list] and belongs in the child_tm_path. # Later passes handle subdirectories, which belong in the # access path but not in the module path. lappend child_tm_path $token } incr i # [Bug 2854929] # Recursively find deeper paths which may contain # modules. Required to handle modules with names like # 'platform::shell', which translate into # 'platform/shell-X.tm', i.e arbitrarily deep # subdirectories. lappend morepaths {*}[glob -nocomplain -directory $dir -type d *] } set firstpass 0 } set state(access_path) $access_path set state(access_path,map) $map_access_path set state(access_path,remap) $remap_access_path set state(access_path,norm) $norm_access_path set state(access_path,child) $child_access_path set state(tm_path_child) $child_tm_path set state(staticsok) $staticsok set state(nestedok) $nestedok set state(cleanupHook) $deletehook if {!$AutoPathSync} { set state(auto_path) $raw_auto_path } SyncAccessPath $child return } # # DetokPath: # Convert tokens to directories where possible. # Leave undefined tokens unconverted. They are # nonsense in both the child and the parent. # proc ::safe::DetokPath {child tokenPath} { namespace upvar ::safe [VarName $child] state set childPath {} foreach token $tokenPath { if {[dict exists $state(access_path,map) $token]} { lappend childPath [dict get $state(access_path,map) $token] } else { lappend childPath $token } } return $childPath } # # # interpFindInAccessPath: # Search for a real directory and returns its virtual Id (including the # "$") # # When debugging, use TranslatePath for the inverse operation. proc ::safe::interpFindInAccessPath {child path} { CheckInterp $child namespace upvar ::safe [VarName $child] state if {![dict exists $state(access_path,remap) $path]} { return -code error "$path not found in access path" } return [dict get $state(access_path,remap) $path] } # # addToAccessPath: # add (if needed) a real directory to access path and return its # virtual token (including the "$"). proc ::safe::interpAddToAccessPath {child path} { # first check if the directory is already in there # (inlined interpFindInAccessPath). CheckInterp $child namespace upvar ::safe [VarName $child] state if {[dict exists $state(access_path,remap) $path]} { return [dict get $state(access_path,remap) $path] } # new one, add it: set token [PathToken [llength $state(access_path)]] lappend state(access_path) $path lappend state(access_path,child) $token lappend state(access_path,map) $token $path lappend state(access_path,remap) $path $token lappend state(access_path,norm) [file normalize $path] SyncAccessPath $child return $token } # This procedure applies the initializations to an already existing # interpreter. It is useful when you want to install the safe base aliases # into a preexisting safe interpreter. proc ::safe::InterpInit { child access_path staticsok nestedok deletehook autoPath withAutoPath } { # Configure will generate an access_path when access_path is empty. InterpSetConfig $child $access_path $staticsok $nestedok $deletehook $autoPath $withAutoPath # NB we need to add [namespace current], aliases are always absolute # paths. # These aliases let the child load files to define new commands # This alias lets the child use the encoding names, convertfrom, # convertto, and system, but not "encoding system " to set the # system encoding. # Handling Tcl Modules, we need a restricted form of Glob. # This alias interposes on the 'exit' command and cleanly terminates # the child. foreach {command alias} { source AliasSource load AliasLoad exit interpDelete glob AliasGlob } { ::interp alias $child $command {} [namespace current]::$alias $child } # UGLY POINT! These commands are safe (they're ensembles with unsafe # subcommands), but is assumed to not be by existing policies so it is # hidden by default. Hack it... foreach command {encoding file} { ::interp alias $child $command {} interp invokehidden $child $command } # This alias lets the child have access to a subset of the 'file' # command functionality. foreach subcommand {dirname extension rootname tail} { ::interp alias $child ::tcl::file::$subcommand {} \ ::safe::AliasFileSubcommand $child $subcommand } # Subcommand of 'encoding' that has special handling; [encoding system] is # OK provided it has no other arguments passed to it. ::interp alias $child ::tcl::encoding::system {} \ ::safe::AliasEncodingSystem $child # Subcommands of info ::interp alias $child ::tcl::info::nameofexecutable {} \ ::safe::AliasExeName $child # Source init.tcl and tm.tcl into the child, to get auto_load and # other procedures defined: if {[catch {::interp eval $child { source [file join $tcl_library init.tcl] }} msg opt]} { Log $child "can't source init.tcl ($msg)" return -options $opt "can't source init.tcl into child $child ($msg)" } if {[catch {::interp eval $child { source [file join $tcl_library tm.tcl] }} msg opt]} { Log $child "can't source tm.tcl ($msg)" return -options $opt "can't source tm.tcl into child $child ($msg)" } # Sync the paths used to search for Tcl modules. This can be done only # now, after tm.tcl was loaded. namespace upvar ::safe [VarName $child] state if {[llength $state(tm_path_child)] > 0} { ::interp eval $child [list \ ::tcl::tm::add {*}[lreverse $state(tm_path_child)]] } return $child } # Add (only if needed, avoid duplicates) 1 level of sub directories to an # existing path list. Also removes non directories from the returned # list. proc ::safe::AddSubDirs {pathList} { set res {} foreach dir $pathList { if {[file isdirectory $dir]} { # check that we don't have it yet as a children of a previous # dir if {$dir ni $res} { lappend res $dir } foreach sub [glob -directory $dir -nocomplain *] { if {[file isdirectory $sub] && ($sub ni $res)} { # new sub dir, add it ! lappend res $sub } } } } return $res } # This procedure deletes a safe interpreter managed by Safe Tcl and cleans up # associated state. # - The command will also delete non-Safe-Base interpreters. # - This is regrettable, but to avoid breaking existing code this should be # amended at the next major revision by uncommenting "CheckInterp". proc ::safe::interpDelete {child} { Log $child "About to delete" NOTICE # CheckInterp $child namespace upvar ::safe [VarName $child] state # When an interpreter is deleted with [interp delete], any sub-interpreters # are deleted automatically, but this leaves behind their data in the Safe # Base. To clean up properly, we call safe::interpDelete recursively on each # Safe Base sub-interpreter, so each one is deleted cleanly and not by # the automatic mechanism built into [interp delete]. foreach sub [interp children $child] { if {[info exists ::safe::[VarName [list $child $sub]]]} { ::safe::interpDelete [list $child $sub] } } # If the child has a cleanup hook registered, call it. Check the # existence because we might be called to delete an interp which has # not been registered with us at all if {[info exists state(cleanupHook)]} { set hook $state(cleanupHook) if {[llength $hook]} { # remove the hook now, otherwise if the hook calls us somehow, # we'll loop unset state(cleanupHook) try { {*}$hook $child } on error err { Log $child "Delete hook error ($err)" } } } # Discard the global array of state associated with the child, and # delete the interpreter. if {[info exists state]} { unset state } # if we have been called twice, the interp might have been deleted # already if {[::interp exists $child]} { ::interp delete $child Log $child "Deleted" NOTICE } return } # Set (or get) the logging mechanism proc ::safe::setLogCmd {args} { variable Log set la [llength $args] if {$la == 0} { return $Log } elseif {$la == 1} { set Log [lindex $args 0] } else { set Log $args } if {$Log eq ""} { # Disable logging completely. Calls to it will be compiled out # of all users. proc ::safe::Log {args} {} } else { # Activate logging, define proper command. proc ::safe::Log {child msg {type ERROR}} { variable Log {*}$Log "$type for child $child : $msg" return } } } # ------------------- END OF PUBLIC METHODS ------------ # # Sets the child auto_path to its recorded access path. Also sets # tcl_library to the first token of the access path. # proc ::safe::SyncAccessPath {child} { variable AutoPathSync namespace upvar ::safe [VarName $child] state set child_access_path $state(access_path,child) if {$AutoPathSync} { ::interp eval $child [list set auto_path $child_access_path] Log $child "auto_path in $child has been set to $child_access_path"\ NOTICE } # This code assumes that info library is the first element in the # list of access path's. See -> InterpSetConfig for the code which # ensures this condition. ::interp eval $child [list \ set tcl_library [lindex $child_access_path 0]] return } # Returns the virtual token for directory number N. proc ::safe::PathToken {n} { # We need to have a ":" in the token string so [file join] on the # mac won't turn it into a relative path. return "\$p(:$n:)" ;# Form tested by case 7.2 } # # translate virtual path into real path # proc ::safe::TranslatePath {child path} { namespace upvar ::safe [VarName $child] state # somehow strip the namespaces 'functionality' out (the danger is that # we would strip valid macintosh "../" queries... : if {[string match "*::*" $path] || [string match "*..*" $path]} { return -code error "invalid characters in path $path" } # Use a cached map instead of computed local vars and subst. return [string map $state(access_path,map) $path] } # file name control (limit access to files/resources that should be a # valid tcl source file) proc ::safe::CheckFileName {child file} { # This used to limit what can be sourced to ".tcl" and forbid files # with more than 1 dot and longer than 14 chars, but I changed that # for 8.4 as a safe interp has enough internal protection already to # allow sourcing anything. - hobbs if {![file exists $file]} { # don't tell the file path return -code error "no such file or directory" } if {![file readable $file]} { # don't tell the file path return -code error "not readable" } } # AliasFileSubcommand handles selected subcommands of [file] in safe # interpreters that are *almost* safe. In particular, it just acts to # prevent discovery of what home directories exist. proc ::safe::AliasFileSubcommand {child subcommand name} { tailcall ::interp invokehidden $child tcl:file:$subcommand $name } # AliasGlob is the target of the "glob" alias in safe interpreters. proc ::safe::AliasGlob {child args} { variable AutoPathSync Log $child "GLOB ! $args" NOTICE set cmd {} set at 0 array set got { -directory 0 -nocomplain 0 -join 0 -tails 0 -- 0 } if {$::tcl_platform(platform) eq "windows"} { set dirPartRE {^(.*)[\\/]([^\\/]*)$} } else { set dirPartRE {^(.*)/([^/]*)$} } set dir {} set virtualdir {} while {$at < [llength $args]} { switch -glob -- [set opt [lindex $args $at]] { -nocomplain - -- - -tails { lappend cmd $opt set got($opt) 1 incr at } -join { set got($opt) 1 incr at } -types - -type { lappend cmd -types [lindex $args [incr at]] incr at } -directory { if {$got($opt)} { return -code error \ {"-directory" cannot be used with "-path"} } set got($opt) 1 set virtualdir [lindex $args [incr at]] incr at } -* { Log $child "Safe base rejecting glob option '$opt'" return -code error "Safe base rejecting glob option '$opt'" # unsafe/unnecessary options rejected: -path } default { break } } if {$got(--)} break } # Get the real path from the virtual one and check that the path is in the # access path of that child. Done after basic argument processing so that # we know if -nocomplain is set. if {$got(-directory)} { try { set dir [TranslatePath $child $virtualdir] DirInAccessPath $child $dir } on error msg { Log $child $msg if {$got(-nocomplain)} return return -code error "permission denied" } if {$got(--)} { set cmd [linsert $cmd end-1 -directory $dir] } else { lappend cmd -directory $dir } } else { # The code after this "if ... else" block would conspire to return with # no results in this case, if it were allowed to proceed. Instead, # return now and reduce the number of cases to be considered later. Log $child {option -directory must be supplied} if {$got(-nocomplain)} return return -code error "permission denied" } # Apply the -join semantics ourselves (hence -join not copied to $cmd) if {$got(-join)} { set args [lreplace $args $at end [join [lrange $args $at end] "/"]] } # Process the pattern arguments. If we've done a join there is only one # pattern argument. set firstPattern [llength $cmd] foreach opt [lrange $args $at end] { if {![regexp $dirPartRE $opt -> thedir thefile]} { set thedir . # The *.tm search comes here. } # "Special" treatment for (joined) argument {*/pkgIndex.tcl}. # Do the expansion of "*" here, and filter out any directories that are # not in the access path. The outcome is to lappend to cmd a path of # the form $virtualdir/subdir/pkgIndex.tcl for each subdirectory subdir, # after removing any subdir that are not in the access path. if {($thedir eq "*") && ($thefile eq "pkgIndex.tcl")} { set mapped 0 foreach d [glob -directory [TranslatePath $child $virtualdir] \ -types d -tails *] { catch { DirInAccessPath $child \ [TranslatePath $child [file join $virtualdir $d]] lappend cmd [file join $d $thefile] set mapped 1 } } if {$mapped} continue # Don't [continue] if */pkgIndex.tcl has no matches in the access # path. The pattern will now receive the same treatment as a # "non-special" pattern (and will fail because it includes a "*" in # the directory name). } # Any directory pattern that is not an exact (i.e. non-glob) match to a # directory in the access path will be rejected here. # - Rejections include any directory pattern that has glob matching # patterns "*", "?", backslashes, braces or square brackets, (UNLESS # it corresponds to a genuine directory name AND that directory is in # the access path). # - The only "special matching characters" that remain in patterns for # processing by glob are in the filename tail. # - [file join $anything ~${foo}] is ~${foo}, which is not an exact # match to any directory in the access path. Hence directory patterns # that begin with "~" are rejected here. Tests safe-16.[5-8] check # that "file join" remains as required and does not expand ~${foo}. # - Bug [3529949] relates to unwanted expansion of ~${foo} and this is # how the present code avoids the bug. All tests safe-16.* relate. try { DirInAccessPath $child [TranslatePath $child \ [file join $virtualdir $thedir]] } on error msg { Log $child $msg if {$got(-nocomplain)} continue return -code error "permission denied" } lappend cmd $opt } Log $child "GLOB = $cmd" NOTICE if {$got(-nocomplain) && [llength $cmd] eq $firstPattern} { return } try { # >>>>>>>>>> HERE'S THE CALL TO SAFE INTERP GLOB <<<<<<<<<< # - Pattern arguments added to cmd have NOT been translated from tokens. # Only the virtualdir is translated (to dir). # - In the pkgIndex.tcl case, there is no "*" in the pattern arguments, # which are a list of names each with tail pkgIndex.tcl. The purpose # of the call to glob is to remove the names for which the file does # not exist. set entries [::interp invokehidden $child glob {*}$cmd] } on error msg { # This is the only place that a call with -nocomplain and no invalid # "dash-options" can return an error. Log $child $msg return -code error "script error" } Log $child "GLOB < $entries" NOTICE # Translate path back to what the child should see. set res {} set l [string length $dir] foreach p $entries { if {[string equal -length $l $dir $p]} { set p [string replace $p 0 [expr {$l-1}] $virtualdir] } lappend res $p } Log $child "GLOB > $res" NOTICE return $res } # AliasSource is the target of the "source" alias in safe interpreters. proc ::safe::AliasSource {child args} { set argc [llength $args] # Extended for handling of Tcl Modules to allow not only "source # filename", but "source -encoding E filename" as well. if {[lindex $args 0] eq "-encoding"} { incr argc -2 set encoding [lindex $args 1] set at 2 if {$encoding eq "identity"} { Log $child "attempt to use the identity encoding" return -code error "permission denied" } } else { set at 0 set encoding utf-8 } if {$argc != 1} { set msg "wrong # args: should be \"source ?-encoding E? fileName\"" Log $child "$msg ($args)" return -code error $msg } set file [lindex $args $at] # get the real path from the virtual one. if {[catch { set realfile [TranslatePath $child $file] } msg]} { Log $child $msg return -code error "permission denied" } # check that the path is in the access path of that child if {[catch { FileInAccessPath $child $realfile } msg]} { Log $child $msg return -code error "permission denied" } # Check that the filename exists and is readable. If it is not, deliver # this -errorcode so that caller in tclPkgUnknown does not write a message # to tclLog. Has no effect on other callers of ::source, which are in # "package ifneeded" scripts. if {[catch { CheckFileName $child $realfile } msg]} { Log $child "$realfile:$msg" return -code error -errorcode {POSIX EACCES} $msg } # Passed all the tests, lets source it. Note that we do this all manually # because we want to control [info script] in the child so information # doesn't leak so much. [Bug 2913625] set old [::interp eval $child {info script}] set replacementMsg "script error" set code [catch { set f [open $realfile] fconfigure $f -encoding $encoding -eofchar \x1A set contents [read $f] close $f ::interp eval $child [list info script $file] } msg opt] if {$code == 0} { # See [Bug 1d26e580cf] if {[string index $contents 0] eq "\uFEFF"} { set contents [string range $contents 1 end] } set code [catch {::interp eval $child $contents} msg opt] set replacementMsg $msg } catch {interp eval $child [list info script $old]} # Note that all non-errors are fine result codes from [source], so we must # take a little care to do it properly. [Bug 2923613] if {$code == 1} { Log $child $msg return -code error $replacementMsg } return -code $code -options $opt $msg } # AliasLoad is the target of the "load" alias in safe interpreters. proc ::safe::AliasLoad {child file args} { set argc [llength $args] if {$argc > 2} { set msg "load error: too many arguments" Log $child "$msg ($argc) {$file $args}" return -code error $msg } # prefix (can be empty if file is not). set prefix [lindex $args 0] namespace upvar ::safe [VarName $child] state # Determine where to load. load use a relative interp path and {} # means self, so we can directly and safely use passed arg. set target [lindex $args 1] if {$target ne ""} { # we will try to load into a sub sub interp; check that we want to # authorize that. if {!$state(nestedok)} { Log $child "loading to a sub interp (nestedok)\ disabled (trying to load $prefix to $target)" return -code error "permission denied (nested load)" } } # Determine what kind of load is requested if {$file eq ""} { # static loading if {$prefix eq ""} { set msg "load error: empty filename and no prefix" Log $child $msg return -code error $msg } if {!$state(staticsok)} { Log $child "static loading disabled\ (trying to load $prefix to $target)" return -code error "permission denied (static library)" } } else { # file loading # get the real path from the virtual one. try { set file [TranslatePath $child $file] } on error msg { Log $child $msg return -code error "permission denied" } # check the translated path try { FileInAccessPath $child $file } on error msg { Log $child $msg return -code error "permission denied (path)" } } try { return [::interp invokehidden $child load $file $prefix $target] } on error msg { # Some libraries return no error message. set msg0 "load of library for prefix $prefix failed" if {$msg eq {}} { set msg $msg0 } else { set msg "$msg0: $msg" } Log $child $msg return -code error $msg } } # FileInAccessPath raises an error if the file is not found in the list of # directories contained in the (parent side recorded) child's access path. # the security here relies on "file dirname" answering the proper # result... needs checking ? proc ::safe::FileInAccessPath {child file} { namespace upvar ::safe [VarName $child] state set access_path $state(access_path) if {[file isdirectory $file]} { return -code error "\"$file\": is a directory" } set parent [file dirname $file] # Normalize paths for comparison since lsearch knows nothing of # potential pathname anomalies. set norm_parent [file normalize $parent] namespace upvar ::safe [VarName $child] state if {$norm_parent ni $state(access_path,norm)} { return -code error "\"$file\": not in access_path" } } proc ::safe::DirInAccessPath {child dir} { namespace upvar ::safe [VarName $child] state set access_path $state(access_path) if {[file isfile $dir]} { return -code error "\"$dir\": is a file" } # Normalize paths for comparison since lsearch knows nothing of # potential pathname anomalies. set norm_dir [file normalize $dir] namespace upvar ::safe [VarName $child] state if {$norm_dir ni $state(access_path,norm)} { return -code error "\"$dir\": not in access_path" } } # This procedure is used to report an attempt to use an unsafe member of an # ensemble command. proc ::safe::BadSubcommand {child command subcommand args} { set msg "not allowed to invoke subcommand $subcommand of $command" Log $child $msg return -code error -errorcode {TCL SAFE SUBCOMMAND} $msg } # AliasEncodingSystem is the target of the "encoding system" alias in safe # interpreters. proc ::safe::AliasEncodingSystem {child args} { try { # Must not pass extra arguments; safe interpreters may not set the # system encoding but they may read it. if {[llength $args]} { return -code error -errorcode {TCL WRONGARGS} \ "wrong # args: should be \"encoding system\"" } } on error {msg options} { Log $child $msg return -options $options $msg } tailcall ::interp invokehidden $child tcl:encoding:system } # Various minor hiding of platform features. [Bug 2913625] proc ::safe::AliasExeName {child} { return "" } # ------------------------------------------------------------------------------ # Using Interpreter Names with Namespace Qualifiers # ------------------------------------------------------------------------------ # (1) We wish to preserve compatibility with existing code, in which Safe Base # interpreter names have no namespace qualifiers. # (2) safe::interpCreate and the rest of the Safe Base previously could not # accept namespace qualifiers in an interpreter name. # (3) The interp command will accept namespace qualifiers in an interpreter # name, but accepts distinct interpreters that will have the same command # name (e.g. foo, ::foo, and :::foo) (bug 66c2e8c974). # (4) To satisfy these constraints, Safe Base interpreter names will be fully # qualified namespace names with no excess colons and with the leading "::" # omitted. # (5) Trailing "::" implies a namespace tail {}, which interp reads as {{}}. # Reject such names. # (6) We could: # (a) EITHER reject usable but non-compliant names (e.g. excess colons) in # interpCreate, interpInit; # (b) OR accept such names and then translate to a compliant name in every # command. # The problem with (b) is that the user will expect to use the name with the # interp command and will find that it is not recognised. # E.g "interpCreate ::foo" creates interpreter "foo", and the user's name # "::foo" works with all the Safe Base commands, but "interp eval ::foo" # fails. # So we choose (a). # (7) The command # namespace upvar ::safe S$child state # becomes # namespace upvar ::safe [VarName $child] state # ------------------------------------------------------------------------------ proc ::safe::RejectExcessColons {child} { set stripped [regsub -all -- {:::*} $child ::] if {[string range $stripped end-1 end] eq {::}} { return -code error {interpreter name must not end in "::"} } if {$stripped ne $child} { set msg {interpreter name has excess colons in namespace separators} return -code error $msg } if {[string range $stripped 0 1] eq {::}} { return -code error {interpreter name must not begin "::"} } return } proc ::safe::VarName {child} { # return S$child return S[string map {:: @N @ @A} $child] } proc ::safe::Setup {} { #### # # Setup the arguments parsing # #### variable AutoPathSync # Share the descriptions set OptList { {-accessPath -list {} "access path for the child"} {-noStatics "prevent loading of statically linked pkgs"} {-statics true "loading of statically linked pkgs"} {-nestedLoadOk "allow nested loading"} {-nested false "nested loading"} {-deleteHook -script {} "delete hook"} } if {!$AutoPathSync} { lappend OptList {-autoPath -list {} "::auto_path for the child"} } set temp [::tcl::OptKeyRegister $OptList] # create case (child is optional) ::tcl::OptKeyRegister { {?child? -name {} "name of the child (optional)"} } ::safe::interpCreate # adding the flags sub programs to the command program (relying on Opt's # internal implementation details) lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp) # init and configure (child is needed) ::tcl::OptKeyRegister { {child -name {} "name of the child"} } ::safe::interpIC # adding the flags sub programs to the command program (relying on Opt's # internal implementation details) lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp) # temp not needed anymore ::tcl::OptKeyDelete $temp #### # # Default: No logging. # #### setLogCmd {} # Log eventually. # To enable error logging, set Log to {puts stderr} for instance, # via setLogCmd. return } # Accessor method for ::safe::AutoPathSync # Usage: ::safe::setSyncMode ?newValue? # Respond to changes by calling Setup again, preserving any # caller-defined logging. This allows complete equivalence with # prior Safe Base behavior if AutoPathSync is true. # # >>> WARNING <<< # # DO NOT CHANGE AutoPathSync EXCEPT BY THIS COMMAND - IT IS VITAL THAT WHENEVER # THE VALUE CHANGES, THE EXISTING PARSE TOKENS ARE DELETED AND Setup IS CALLED # AGAIN. # (The initialization of AutoPathSync at the end of this file is acceptable # because Setup has not yet been called.) proc ::safe::setSyncMode {args} { variable AutoPathSync if {[llength $args] == 0} { } elseif {[llength $args] == 1} { set newValue [lindex $args 0] if {![string is boolean -strict $newValue]} { return -code error "new value must be a valid boolean" } set args [expr {$newValue && $newValue}] if {([info vars ::safe::S*] ne {}) && ($args != $AutoPathSync)} { return -code error \ "cannot set new value while Safe Base child interpreters exist" } if {($args != $AutoPathSync)} { set AutoPathSync {*}$args ::tcl::OptKeyDelete ::safe::interpCreate ::tcl::OptKeyDelete ::safe::interpIC set TmpLog [setLogCmd] Setup setLogCmd $TmpLog } } else { set msg {wrong # args: should be "safe::setSyncMode ?newValue?"} return -code error $msg } return $AutoPathSync } namespace eval ::safe { # internal variables (must not begin with "S") # AutoPathSync # # Set AutoPathSync to 0 to give a child's ::auto_path the same meaning as # for an unsafe interpreter: the package command will search its directories # and first-level subdirectories for pkgIndex.tcl files; the auto-loader # will search its directories for tclIndex files. The access path and # module path will be maintained as separate values, and ::auto_path will # not be updated when the user calls ::safe::interpAddToAccessPath to add to # the access path. If the user specifies an access path when calling # interpCreate, interpInit or interpConfigure, it is the user's # responsibility to define the child's auto_path. If these commands are # called with no (or empty) access path, the child's auto_path will be set # to a tokenized form of the parent's auto_path, and these directories and # their first-level subdirectories will be added to the access path. # # Set to 1 for "traditional" behavior: a child's entire access path and # module path are copied to its ::auto_path, which is updated whenever # the user calls ::safe::interpAddToAccessPath to add to the access path. variable AutoPathSync 0 # Log command, set via 'setLogCmd'. Logging is disabled when empty. variable Log {} # The package maintains a state array per child interp under its # control. The name of this array is S. This array is # brought into scope where needed, using 'namespace upvar'. The S # prefix is used to avoid that a child interp called "Log" smashes # the "Log" variable. # # The array's elements are: # # access_path : List of paths accessible to the child. # access_path,norm : Ditto, in normalized form. # access_path,child : Ditto, as the path tokens as seen by the child. # access_path,map : dict ( token -> path ) # access_path,remap : dict ( path -> token ) # auto_path : List of paths requested by the caller as child's ::auto_path. # tm_path_child : List of TM root directories, as tokens seen by the child. # staticsok : Value of option -statics # nestedok : Value of option -nested # cleanupHook : Value of option -deleteHook # # In principle, the child can change its value of ::auto_path - # - a package might add a path (that is already in the access path) for # access to tclIndex files; # - the script might remove some elements of the auto_path. # However, this is really the business of the parent, and the auto_path will # be reset whenever the token mapping changes (i.e. when option -accessPath is # used to change the access path). # -autoPath is now stored in the array and is no longer obtained from # the child. } ::safe::Setup tcl9.0.1/library/tm.tcl0000644000175000017500000002720014726623136014347 0ustar sergeisergei# -*- tcl -*- # # Searching for Tcl Modules. Defines a procedure, declares it as the primary # command for finding packages, however also uses the former 'package unknown' # command as a fallback. # # Locates all possible packages in a directory via a less restricted glob. The # targeted directory is derived from the name of the requested package, i.e. # the TM scan will look only at directories which can contain the requested # package. It will register all packages it found in the directory so that # future requests have a higher chance of being fulfilled by the ifneeded # database without having to come to us again. # # We do not remember where we have been and simply rescan targeted directories # when invoked again. The reasoning is this: # # - The only way we get back to the same directory is if someone is trying to # [package require] something that wasn't there on the first scan. # # Either # 1) It is there now: If we rescan, you get it; if not you don't. # # This covers the possibility that the application asked for a package # late, and the package was actually added to the installation after the # application was started. It should still be able to find it. # # 2) It still is not there: Either way, you don't get it, but the rescan # takes time. This is however an error case and we don't care that much # about it # # 3) It was there the first time; but for some reason a "package forget" has # been run, and "package" doesn't know about it anymore. # # This can be an indication that the application wishes to reload some # functionality. And should work as well. # # Note that this also strikes a balance between doing a glob targeting a # single package, and thus most likely requiring multiple globs of the same # directory when the application is asking for many packages, and trying to # glob for _everything_ in all subdirectories when looking for a package, # which comes with a heavy startup cost. # # We scan for regular packages only if no satisfying module was found. namespace eval ::tcl::tm { # Default paths. None yet. variable paths {} # The regex pattern a file name has to match to make it a Tcl Module. set pkgpattern {^([_[:alpha:]][:_[:alnum:]]*)-([[:digit:]].*)[.]tm$} # Export the public API namespace export path namespace ensemble create -command path -subcommands {add remove list} } # ::tcl::tm::path implementations -- # # Public API to the module path. See specification. # # Arguments # cmd - The subcommand to execute # args - The paths to add/remove. Must not appear querying the # path with 'list'. # # Results # No result for subcommands 'add' and 'remove'. A list of paths for # 'list'. # # Side effects # The subcommands 'add' and 'remove' manipulate the list of paths to # search for Tcl Modules. The subcommand 'list' has no side effects. proc ::tcl::tm::add {args} { # PART OF THE ::tcl::tm::path ENSEMBLE # # The path is added at the head to the list of module paths. # # The command enforces the restriction that no path may be an ancestor # directory of any other path on the list. If the new path violates this # restriction an error will be raised. # # If the path is already present as is no error will be raised and no # action will be taken. variable paths # We use a copy of the path as source during validation, and extend it as # well. Because we not only have to detect if the new paths are bogus with # respect to the existing paths, but also between themselves. Otherwise we # can still add bogus paths, by specifying them in a single call. This # makes the use of the new paths simpler as well, a trivial assignment of # the collected paths to the official state var. set newpaths $paths foreach p $args { if {($p eq "") || ($p in $newpaths)} { # Ignore any path which is empty or already on the list. continue } # Search for paths which are subdirectories of the new one. If there # are any then the new path violates the restriction about ancestors. set pos [lsearch -glob $newpaths ${p}/*] # Cannot use "in", we need the position for the message. if {$pos >= 0} { return -code error \ "$p is ancestor of existing module path [lindex $newpaths $pos]." } # Now look for existing paths which are ancestors of the new one. This # reverse question forces us to loop over the existing paths, as each # element is the pattern, not the new path :( foreach ep $newpaths { if {[string match ${ep}/* $p]} { return -code error \ "$p is subdirectory of existing module path $ep." } } set newpaths [linsert $newpaths 0 $p] } # The validation of the input is complete and successful, and everything # in newpaths is either an old path, or added. We can now extend the # official list of paths, a simple assignment is sufficient. set paths $newpaths return } proc ::tcl::tm::remove {args} { # PART OF THE ::tcl::tm::path ENSEMBLE # # Removes the path from the list of module paths. The command is silently # ignored if the path is not on the list. variable paths foreach p $args { set pos [lsearch -exact $paths $p] if {$pos >= 0} { set paths [lreplace $paths $pos $pos] } } } proc ::tcl::tm::list {} { # PART OF THE ::tcl::tm::path ENSEMBLE variable paths return $paths } # ::tcl::tm::UnknownHandler -- # # Unknown handler for Tcl Modules, i.e. packages in module form. # # Arguments # original - Original [package unknown] procedure. # name - Name of desired package. # version - Version of desired package. Can be the # empty string. # exact - Either -exact or omitted. # # Name, version, and exact are used to determine satisfaction. The # original is called iff no satisfaction was achieved. The name is also # used to compute the directory to target in the search. # # Results # None. # # Side effects # May populate the package ifneeded database with additional provide # scripts. proc ::tcl::tm::UnknownHandler {original name args} { # Import the list of paths to search for packages in module form. # Import the pattern used to check package names in detail. variable paths variable pkgpattern # Without paths to search we can do nothing. (Except falling back to the # regular search). if {[llength $paths]} { set pkgpath [string map {:: /} $name] set pkgroot [file dirname $pkgpath] if {$pkgroot eq "."} { set pkgroot "" } # We don't remember a copy of the paths while looping. Tcl Modules are # unable to change the list while we are searching for them. This also # simplifies the loop, as we cannot get additional directories while # iterating over the list. A simple foreach is sufficient. set satisfied 0 foreach path $paths { if {![interp issafe] && ![file exists $path]} { continue } set currentsearchpath [file join $path $pkgroot] if {![interp issafe] && ![file exists $currentsearchpath]} { continue } set strip [llength [file split $path]] # Get the module files out of the subdirectories. # - Safe Base interpreters have a restricted "glob" command that # works in this case. # - The "catch" was essential when there was no safe glob and every # call in a safe interp failed; it is retained only for corner # cases in which the eventual call to glob returns an error. catch { # We always look for _all_ possible modules in the current # path, to get the max result out of the glob. foreach file [glob -nocomplain -directory $currentsearchpath *.tm] { set pkgfilename [join [lrange [file split $file] $strip end] ::] if {![regexp -- $pkgpattern $pkgfilename --> pkgname pkgversion]} { # Ignore everything not matching our pattern for # package names. continue } try { package vcompare $pkgversion 0 } on error {} { # Ignore everything where the version part is not # acceptable to "package vcompare". continue } if {([package ifneeded $pkgname $pkgversion] ne {}) && (![interp issafe]) } { # There's already a provide script registered for # this version of this package. Since all units of # code claiming to be the same version of the same # package ought to be identical, just stick with # the one we already have. # This does not apply to Safe Base interpreters because # the token-to-directory mapping may have changed. continue } # We have found a candidate, generate a "provide script" # for it, and remember it. Note that we are using ::list # to do this; locally [list] means something else without # the namespace specifier. # NOTE. When making changes to the format of the provide # command generated below CHECK that the 'LOCATE' # procedure in core file 'platform/shell.tcl' still # understands it, or, if not, update its implementation # appropriately. # # Right now LOCATE's implementation assumes that the path # of the package file is the last element in the list. package ifneeded $pkgname $pkgversion \ "[::list package provide $pkgname $pkgversion];[::list source $file]" # We abort in this unknown handler only if we got a # satisfying candidate for the requested package. # Otherwise we still have to fallback to the regular # package search to complete the processing. if {($pkgname eq $name) && [package vsatisfies $pkgversion {*}$args]} { set satisfied 1 # We do not abort the loop, and keep adding provide # scripts for every candidate in the directory, just # remember to not fall back to the regular search # anymore. } } } } if {$satisfied} { return } } # Fallback to previous command, if existing. See comment above about # ::list... if {[llength $original]} { uplevel 1 $original [::linsert $args 0 $name] } } # ::tcl::tm::Defaults -- # # Determines the default search paths. # # Arguments # None # # Results # None. # # Side effects # May add paths to the list of defaults. proc ::tcl::tm::Defaults {} { global env tcl_platform regexp {^(\d+)\.(\d+)} [package provide tcl] - major minor set exe [file normalize [info nameofexecutable]] # Note that we're using [::list], not [list] because [list] means # something other than [::list] in this namespace. roots [::list \ [file dirname [info library]] \ [file join [file dirname [file dirname $exe]] lib] \ ] for {set n $minor} {$n >= 0} {incr n -1} { foreach ev [::list \ TCL${major}.${n}_TM_PATH \ TCL${major}_${n}_TM_PATH \ ] { if {![info exists env($ev)]} continue foreach p [split $env($ev) $::tcl_platform(pathSeparator)] { # Paths relative to unresolvable home dirs are ignored if {![catch {file tildeexpand $p} expanded_path]} { path add $expanded_path } } } } return } # ::tcl::tm::roots -- # # Public API to the module path. See specification. # # Arguments # paths - List of 'root' paths to derive search paths from. # # Results # No result. # # Side effects # Calls 'path add' to paths to the list of module search paths. proc ::tcl::tm::roots {paths} { regexp {^(\d+)\.(\d+)} [package provide tcl] - major minor foreach pa $paths { set p [file join $pa tcl$major] for {set n $minor} {$n >= 0} {incr n -1} { set px [file join $p ${major}.${n}] if {![interp issafe]} {set px [file normalize $px]} path add $px } set px [file join $p site-tcl] if {![interp issafe]} {set px [file normalize $px]} path add $px } return } # Initialization. Set up the default paths, then insert the new handler into # the chain. if {![interp issafe]} {::tcl::tm::Defaults} tcl9.0.1/library/word.tcl0000644000175000017500000001123414726623136014702 0ustar sergeisergei# word.tcl -- # # This file defines various procedures for computing word boundaries in # strings. This file is primarily needed so Tk text and entry widgets behave # properly for different platforms. # # Copyright © 1996 Sun Microsystems, Inc. # Copyright © 1998 Scriptics Corporation. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # The following variables are used to determine which characters are # interpreted as word characters. See bug [f1253530cdd8]. Will # probably be removed in Tcl 9. if {![info exists ::tcl_wordchars]} { set ::tcl_wordchars {\w} } if {![info exists ::tcl_nonwordchars]} { set ::tcl_nonwordchars {\W} } # Arrange for caches of the real matcher REs to be kept, which enables the REs # themselves to be cached for greater performance (and somewhat greater # clarity too). namespace eval ::tcl { variable WordBreakRE array set WordBreakRE {} proc UpdateWordBreakREs args { # Ignores the arguments global tcl_wordchars tcl_nonwordchars variable WordBreakRE # To keep the RE strings short... set letter $tcl_wordchars set space $tcl_nonwordchars set WordBreakRE(after) "$letter$space|$space$letter" set WordBreakRE(before) "^.*($letter$space|$space$letter)" set WordBreakRE(end) "$space*$letter+$space" set WordBreakRE(next) "$letter*$space+$letter" set WordBreakRE(previous) "$space*($letter+)$space*\$" } # Initialize the cache UpdateWordBreakREs trace add variable ::tcl_wordchars write ::tcl::UpdateWordBreakREs trace add variable ::tcl_nonwordchars write ::tcl::UpdateWordBreakREs } # tcl_wordBreakAfter -- # # This procedure returns the index of the first word boundary after the # starting point in the given string, or -1 if there are no more boundaries in # the given string. The index returned refers to the first character of the # pair that comprises a boundary. # # Arguments: # str - String to search. # start - Index into string specifying starting point. proc tcl_wordBreakAfter {str start} { variable ::tcl::WordBreakRE set result {-1 -1} if {$start < 0} { set start 0; } regexp -indices -start $start -- $WordBreakRE(after) $str result return [lindex $result 1] } # tcl_wordBreakBefore -- # # This procedure returns the index of the first word boundary before the # starting point in the given string, or -1 if there are no more boundaries in # the given string. The index returned refers to the second character of the # pair that comprises a boundary. # # Arguments: # str - String to search. # start - Index into string specifying starting point. proc tcl_wordBreakBefore {str start} { variable ::tcl::WordBreakRE set result {-1 -1} if {$start >= 0} { regexp -indices -- $WordBreakRE(before) [string range $str 0 $start] result } return [lindex $result 1] } # tcl_endOfWord -- # # This procedure returns the index of the first end-of-word location after a # starting index in the given string. An end-of-word location is defined to be # the first whitespace character following the first non-whitespace character # after the starting point. Returns -1 if there are no more words after the # starting point. # # Arguments: # str - String to search. # start - Index into string specifying starting point. proc tcl_endOfWord {str start} { variable ::tcl::WordBreakRE set result {-1 -1} if {$start < 0} { set start 0 } regexp -indices -start $start -- $WordBreakRE(end) $str result return [lindex $result 1] } # tcl_startOfNextWord -- # # This procedure returns the index of the first start-of-word location after a # starting index in the given string. A start-of-word location is defined to # be a non-whitespace character following a whitespace character. Returns -1 # if there are no more start-of-word locations after the starting point. # # Arguments: # str - String to search. # start - Index into string specifying starting point. proc tcl_startOfNextWord {str start} { variable ::tcl::WordBreakRE set result {-1 -1} if {$start < 0} { set start 0 } regexp -indices -start $start -- $WordBreakRE(next) $str result return [lindex $result 1] } # tcl_startOfPreviousWord -- # # This procedure returns the index of the first start-of-word location before # a starting index in the given string. # # Arguments: # str - String to search. # start - Index into string specifying starting point. proc tcl_startOfPreviousWord {str start} { variable ::tcl::WordBreakRE set word {-1 -1} if {$start > 0} { regexp -indices -- $WordBreakRE(previous) [string range [string range $str 0 $start] 0 end-1] \ result word } return [lindex $word 0] } tcl9.0.1/library/writefile.tcl0000644000175000017500000000161714726623136015725 0ustar sergeisergei# writeFile: # Write the contents of a file. # # Copyright © 2023 Donal K Fellows. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # proc writeFile {args} { # Parse the arguments switch [llength $args] { 2 { lassign $args filename data set mode text } 3 { lassign $args filename mode data set MODES {binary text} set ERR [list -level 1 -errorcode [list TCL LOOKUP MODE $mode]] set mode [tcl::prefix match -message "mode" -error $ERR $MODES $mode] } default { set COMMAND [lindex [info level 0] 0] return -code error -errorcode {TCL WRONGARGS} \ "wrong # args: should be \"$COMMAND filename ?mode? data\"" } } # Write the file set f [open $filename [dict get {text w binary wb} $mode]] try { puts -nonewline $f $data } finally { close $f } } tcl9.0.1/library/manifest.txt0000644000175000017500000000127614726623136015577 0ustar sergeisergei### # Package manifest for all Tcl packages included in the /library file system ### apply {{dir} { set isafe [interp issafe] foreach {safe package version file} { 0 http 2.10.0 {http http.tcl} 1 msgcat 1.7.1 {msgcat msgcat.tcl} 1 opt 0.4.9 {opt optparse.tcl} 0 cookiejar 0.2.0 {cookiejar cookiejar.tcl} 0 tcl::idna 1.0.1 {cookiejar idna.tcl} 0 platform 1.0.19 {platform platform.tcl} 0 platform::shell 1.1.4 {platform shell.tcl} 1 tcltest 2.5.9 {tcltest tcltest.tcl} } { if {$isafe && !$safe} continue package ifneeded $package $version [list source [file join $dir {*}$file]] } }} $dir tcl9.0.1/library/tclIndex0000644000175000017500000002321214726623136014717 0ustar sergeisergei# Tcl autoload index file, version 2.0 # This file is generated by the "auto_mkindex" command # and sourced to set up indexing information for one or # more commands. Typically each line is a command that # sets an element in the auto_index array, where the # element name is the name of a command and the value is # a script that loads the command. set auto_index(auto_reset) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(tcl_findLibrary) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(auto_mkindex) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(auto_mkindex_old) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(::auto_mkindex_parser::init) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(::auto_mkindex_parser::cleanup) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(::auto_mkindex_parser::mkindex) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(::auto_mkindex_parser::hook) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(::auto_mkindex_parser::childhook) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(::auto_mkindex_parser::command) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(::auto_mkindex_parser::commandInit) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(::auto_mkindex_parser::fullname) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(::auto_mkindex_parser::indexEntry) [list ::tcl::Pkg::source [file join $dir auto.tcl]] set auto_index(::tcl::clock::Initialize) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::mcget) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::mcMerge) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::GetSystemLocale) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::EnterLocale) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::_hasRegistry) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::LoadWindowsDateTimeFormats) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::LocalizeFormat) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::GetSystemTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::SetupTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::GuessWindowsTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::LoadTimeZoneFile) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::LoadZoneinfoFile) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::ReadZoneinfoFile) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::ParsePosixTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::ProcessPosixTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::DeterminePosixDSTTime) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::GetJulianDayFromEraYearDay) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::GetJulianDayFromEraYearMonthWeekDay) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::IsGregorianLeapYear) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::WeekdayOnOrBefore) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::ChangeCurrentLocale) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(::tcl::clock::ClearCaches) [list ::tcl::Pkg::source [file join $dir clock.tcl]] set auto_index(foreachLine) [list ::tcl::Pkg::source [file join $dir foreachline.tcl]] set auto_index(::tcl::history) [list ::tcl::Pkg::source [file join $dir history.tcl]] set auto_index(history) [list ::tcl::Pkg::source [file join $dir history.tcl]] set auto_index(::tcl::HistAdd) [list ::tcl::Pkg::source [file join $dir history.tcl]] set auto_index(::tcl::HistKeep) [list ::tcl::Pkg::source [file join $dir history.tcl]] set auto_index(::tcl::HistClear) [list ::tcl::Pkg::source [file join $dir history.tcl]] set auto_index(::tcl::HistInfo) [list ::tcl::Pkg::source [file join $dir history.tcl]] set auto_index(::tcl::HistRedo) [list ::tcl::Pkg::source [file join $dir history.tcl]] set auto_index(::tcl::HistIndex) [list ::tcl::Pkg::source [file join $dir history.tcl]] set auto_index(::tcl::HistEvent) [list ::tcl::Pkg::source [file join $dir history.tcl]] set auto_index(::tcl::HistChange) [list ::tcl::Pkg::source [file join $dir history.tcl]] set auto_index(::tcl::HistNextID) [list ::tcl::Pkg::source [file join $dir history.tcl]] set auto_index(::tcl::Pkg::CompareExtension) [list ::tcl::Pkg::source [file join $dir package.tcl]] set auto_index(pkg_mkIndex) [list ::tcl::Pkg::source [file join $dir package.tcl]] set auto_index(tclPkgSetup) [list ::tcl::Pkg::source [file join $dir package.tcl]] set auto_index(tclPkgUnknown) [list ::tcl::Pkg::source [file join $dir package.tcl]] set auto_index(::tcl::MacOSXPkgUnknown) [list ::tcl::Pkg::source [file join $dir package.tcl]] set auto_index(::pkg::create) [list ::tcl::Pkg::source [file join $dir package.tcl]] set auto_index(parray) [list ::tcl::Pkg::source [file join $dir parray.tcl]] set auto_index(readFile) [list ::tcl::Pkg::source [file join $dir readfile.tcl]] set auto_index(::safe::InterpStatics) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::InterpNested) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::interpCreate) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::interpInit) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::CheckInterp) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::interpConfigure) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::InterpCreate) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::InterpSetConfig) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::DetokPath) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::interpFindInAccessPath) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::interpAddToAccessPath) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::InterpInit) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::AddSubDirs) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::interpDelete) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::setLogCmd) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::SyncAccessPath) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::PathToken) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::TranslatePath) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::Log) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::CheckFileName) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::AliasFileSubcommand) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::AliasGlob) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::AliasSource) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::AliasLoad) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::FileInAccessPath) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::DirInAccessPath) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::BadSubcommand) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::AliasEncodingSystem) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::AliasExeName) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::RejectExcessColons) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::VarName) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::Setup) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::safe::setSyncMode) [list ::tcl::Pkg::source [file join $dir safe.tcl]] set auto_index(::tcl::tm::path) [list ::tcl::Pkg::source [file join $dir tm.tcl]] set auto_index(::tcl::tm::add) [list ::tcl::Pkg::source [file join $dir tm.tcl]] set auto_index(::tcl::tm::remove) [list ::tcl::Pkg::source [file join $dir tm.tcl]] set auto_index(::tcl::tm::list) [list ::tcl::Pkg::source [file join $dir tm.tcl]] set auto_index(::tcl::tm::UnknownHandler) [list ::tcl::Pkg::source [file join $dir tm.tcl]] set auto_index(::tcl::tm::Defaults) [list ::tcl::Pkg::source [file join $dir tm.tcl]] set auto_index(::tcl::tm::roots) [list ::tcl::Pkg::source [file join $dir tm.tcl]] set auto_index(::tcl::UpdateWordBreakREs) [list ::tcl::Pkg::source [file join $dir word.tcl]] set auto_index(tcl_wordBreakAfter) [list ::tcl::Pkg::source [file join $dir word.tcl]] set auto_index(tcl_wordBreakBefore) [list ::tcl::Pkg::source [file join $dir word.tcl]] set auto_index(tcl_endOfWord) [list ::tcl::Pkg::source [file join $dir word.tcl]] set auto_index(tcl_startOfNextWord) [list ::tcl::Pkg::source [file join $dir word.tcl]] set auto_index(tcl_startOfPreviousWord) [list ::tcl::Pkg::source [file join $dir word.tcl]] set auto_index(writeFile) [list ::tcl::Pkg::source [file join $dir writefile.tcl]] set auto_index(::tcl::unsupported::icu) [list ::tcl::Pkg::source [file join $dir icu.tcl]]tcl9.0.1/library/cookiejar/0000755000175000017500000000000014731057544015170 5ustar sergeisergeitcl9.0.1/library/cookiejar/cookiejar.tcl0000644000175000017500000005152214726623136017647 0ustar sergeisergei# cookiejar.tcl -- # # Implementation of an HTTP cookie storage engine using SQLite. The # implementation is done as a TclOO class, and includes a punycode # encoder and decoder (though only the encoder is currently used). # # See the file "license.terms" for information on usage and redistribution of # this file, and for a DISCLAIMER OF ALL WARRANTIES. # Dependencies package require Tcl 8.6- package require http 2.8.4 package require sqlite3 package require tcl::idna 1.0 # # Configuration for the cookiejar package, plus basic support procedures. # # This is the class that we are creating if {![llength [info commands ::http::cookiejar]]} { ::oo::class create ::http::cookiejar } namespace eval [info object namespace ::http::cookiejar] { proc setInt {*var val} { upvar 1 ${*var} var if {[catch {incr dummy $val} msg]} { return -code error $msg } set var $val } proc setInterval {trigger *var val} { upvar 1 ${*var} var if {![string is integer -strict $val] || $val < 1} { return -code error "expected positive integer but got \"$val\"" } set var $val {*}$trigger } proc setBool {*var val} { upvar 1 ${*var} var if {[catch {if {$val} {}} msg]} { return -code error $msg } set var [expr {!!$val}] } proc setLog {*var val} { upvar 1 ${*var} var set var [::tcl::prefix match -message "log level" \ {debug info warn error} $val] } # Keep this in sync with pkgIndex.tcl and with the install directories in # Makefiles variable version 0.2.0 variable domainlist \ https://publicsuffix.org/list/public_suffix_list.dat variable domainfile \ [file join [file dirname [info script]] public_suffix_list.dat.gz] # The list is directed to from http://publicsuffix.org/list/ variable loglevel info variable vacuumtrigger 200 variable retainlimit 100 variable offline false variable purgeinterval 60000 variable refreshinterval 10000000 variable domaincache {} # Some support procedures, none particularly useful in general namespace eval support { # Set up a logger if the http package isn't actually loaded yet. if {![llength [info commands ::http::Log]]} { proc ::http::Log args { # Do nothing by default... } } namespace export * proc locn {secure domain path {key ""}} { if {$key eq ""} { format "%s://%s%s" [expr {$secure?"https":"http"}] \ [::tcl::idna encode $domain] $path } else { format "%s://%s%s?%s" \ [expr {$secure?"https":"http"}] [::tcl::idna encode $domain] \ $path $key } } proc splitDomain domain { set pieces [split $domain "."] for {set i [llength $pieces]} {[incr i -1] >= 0} {} { lappend result [join [lrange $pieces $i end] "."] } return $result } proc splitPath path { set pieces [split [string trimleft $path "/"] "/"] set result / for {set j 0} {$j < [llength $pieces]} {incr j} { lappend result /[join [lrange $pieces 0 $j] "/"] } return $result } proc isoNow {} { set ms [clock milliseconds] set ts [expr {$ms / 1000}] set ms [format %03d [expr {$ms % 1000}]] clock format $ts -format "%Y%m%dT%H%M%S.${ms}Z" -gmt 1 } proc log {level msg args} { namespace upvar [info object namespace ::http::cookiejar] \ loglevel loglevel set who [uplevel 1 self class] set mth [uplevel 1 self method] set map {debug 0 info 1 warn 2 error 3} if {[string map $map $level] >= [string map $map $loglevel]} { set msg [format $msg {*}$args] set LVL [string toupper $level] ::http::Log "[isoNow] $LVL $who $mth - $msg" } } } } # Now we have enough information to provide the package. package provide cookiejar \ [set [info object namespace ::http::cookiejar]::version] # The implementation of the cookiejar package ::oo::define ::http::cookiejar { self { method configure {{optionName "\x00\x00"} {optionValue "\x00\x00"}} { set tbl { -domainfile {domainfile set} -domainlist {domainlist set} -domainrefresh {refreshinterval setInterval} -loglevel {loglevel setLog} -offline {offline setBool} -purgeold {purgeinterval setInterval} -retain {retainlimit setInt} -vacuumtrigger {vacuumtrigger setInt} } dict lappend tbl -domainrefresh [namespace code { my IntervalTrigger PostponeRefresh }] dict lappend tbl -purgeold [namespace code { my IntervalTrigger PostponePurge }] if {$optionName eq "\x00\x00"} { return [dict keys $tbl] } set opt [::tcl::prefix match -message "option" \ [dict keys $tbl] $optionName] set setter [lassign [dict get $tbl $opt] varname] namespace upvar [namespace current] $varname var if {$optionValue ne "\x00\x00"} { {*}$setter var $optionValue } return $var } method IntervalTrigger {method} { # TODO: handle subclassing foreach obj [info class instances [self]] { [info object namespace $obj]::my $method } } } variable purgeTimer deletions refreshTimer constructor {{path ""}} { namespace import [info object namespace [self class]]::support::* if {$path eq ""} { sqlite3 [namespace current]::db :memory: set storeorigin "constructed cookie store in memory" } else { sqlite3 [namespace current]::db $path db timeout 500 set storeorigin "loaded cookie store from $path" } set deletions 0 db transaction { db eval { --;# Store the persistent cookies in this table. --;# Deletion policy: once they expire, or if explicitly --;# killed. CREATE TABLE IF NOT EXISTS persistentCookies ( id INTEGER PRIMARY KEY, secure INTEGER NOT NULL, domain TEXT NOT NULL COLLATE NOCASE, path TEXT NOT NULL, key TEXT NOT NULL, value TEXT NOT NULL, originonly INTEGER NOT NULL, expiry INTEGER NOT NULL, lastuse INTEGER NOT NULL, creation INTEGER NOT NULL); CREATE UNIQUE INDEX IF NOT EXISTS persistentUnique ON persistentCookies (domain, path, key); CREATE INDEX IF NOT EXISTS persistentLookup ON persistentCookies (domain, path); --;# Store the session cookies in this table. --;# Deletion policy: at cookiejar instance deletion, if --;# explicitly killed, or if the number of session cookies is --;# too large and the cookie has not been used recently. CREATE TEMP TABLE sessionCookies ( id INTEGER PRIMARY KEY, secure INTEGER NOT NULL, domain TEXT NOT NULL COLLATE NOCASE, path TEXT NOT NULL, key TEXT NOT NULL, originonly INTEGER NOT NULL, value TEXT NOT NULL, lastuse INTEGER NOT NULL, creation INTEGER NOT NULL); CREATE UNIQUE INDEX sessionUnique ON sessionCookies (domain, path, key); CREATE INDEX sessionLookup ON sessionCookies (domain, path); --;# View to allow for simple looking up of a cookie. --;# Deletion policy: NOT SUPPORTED via this view. CREATE TEMP VIEW cookies AS SELECT id, domain, ( CASE originonly WHEN 1 THEN path ELSE '.' || path END ) AS path, key, value, secure, 1 AS persistent FROM persistentCookies UNION SELECT id, domain, ( CASE originonly WHEN 1 THEN path ELSE '.' || path END ) AS path, key, value, secure, 0 AS persistent FROM sessionCookies; --;# Encoded domain permission policy; if forbidden is 1, no --;# cookie may be ever set for the domain, and if forbidden --;# is 0, cookies *may* be created for the domain (overriding --;# the forbiddenSuper table). --;# Deletion policy: normally not modified. CREATE TABLE IF NOT EXISTS domains ( domain TEXT PRIMARY KEY NOT NULL, forbidden INTEGER NOT NULL); --;# Domains that may not have a cookie defined for direct --;# child domains of them. --;# Deletion policy: normally not modified. CREATE TABLE IF NOT EXISTS forbiddenSuper ( domain TEXT PRIMARY KEY); --;# When we last retrieved the domain list. CREATE TABLE IF NOT EXISTS domainCacheMetadata ( id INTEGER PRIMARY KEY, retrievalDate INTEGER, installDate INTEGER); } set cookieCount "no" db eval { SELECT COUNT(*) AS cookieCount FROM persistentCookies } log info "%s with %s entries" $storeorigin $cookieCount my PostponePurge if {$path ne ""} { if {[db exists {SELECT 1 FROM domains}]} { my RefreshDomains } else { my InitDomainList my PostponeRefresh } } else { set data [my GetDomainListOffline metadata] my InstallDomainData $data $metadata my PostponeRefresh } } } method PostponePurge {} { namespace upvar [info object namespace [self class]] \ purgeinterval interval catch {after cancel $purgeTimer} set purgeTimer [after $interval [namespace code {my PurgeCookies}]] } method PostponeRefresh {} { namespace upvar [info object namespace [self class]] \ refreshinterval interval catch {after cancel $refreshTimer} set refreshTimer [after $interval [namespace code {my RefreshDomains}]] } method RefreshDomains {} { # TODO: domain list refresh policy my PostponeRefresh } method HttpGet {url {timeout 0} {maxRedirects 5}} { for {set r 0} {$r < $maxRedirects} {incr r} { set tok [::http::geturl $url -timeout $timeout] try { if {[::http::status $tok] eq "timeout"} { return -code error "connection timed out" } elseif {[::http::ncode $tok] == 200} { return [::http::data $tok] } elseif {[::http::ncode $tok] >= 400} { return -code error [::http::error $tok] } elseif {[dict exists [::http::meta $tok] Location]} { set url [dict get [::http::meta $tok] Location] continue } return -code error \ "unexpected state: [::http::code $tok]" } finally { ::http::cleanup $tok } } return -code error "too many redirects" } method GetDomainListOnline {metaVar} { upvar 1 $metaVar meta namespace upvar [info object namespace [self class]] \ domainlist url domaincache cache lassign $cache when data if {$when > [clock seconds] - 3600} { log debug "using cached value created at %s" \ [clock format $when -format {%Y%m%dT%H%M%SZ} -gmt 1] dict set meta retrievalDate $when return $data } log debug "loading domain list from %s" $url try { set when [clock seconds] set data [my HttpGet $url] set cache [list $when $data] # TODO: Should we use the Last-Modified header instead? dict set meta retrievalDate $when return $data } on error msg { log error "failed to fetch list of forbidden cookie domains from %s: %s" \ $url $msg return {} } } method GetDomainListOffline {metaVar} { upvar 1 $metaVar meta namespace upvar [info object namespace [self class]] \ domainfile filename log debug "loading domain list from %s" $filename try { set f [open $filename] try { if {[string match *.gz $filename]} { zlib push gunzip $f } fconfigure $f -encoding utf-8 dict set meta retrievalDate [file mtime $filename] return [read $f] } finally { close $f } } on error {msg opt} { log error "failed to read list of forbidden cookie domains from %s: %s" \ $filename $msg return -options $opt $msg } } method InitDomainList {} { namespace upvar [info object namespace [self class]] \ offline offline if {!$offline} { try { set data [my GetDomainListOnline metadata] if {[string length $data]} { my InstallDomainData $data $metadata return } } on error {} { log warn "attempting to fall back to built in version" } } set data [my GetDomainListOffline metadata] my InstallDomainData $data $metadata } method InstallDomainData {data meta} { set n [db total_changes] db transaction { foreach line [split $data "\n"] { if {[string trim $line] eq ""} { continue } elseif {[string match //* $line]} { continue } elseif {[string match !* $line]} { set line [string range $line 1 end] set idna [string tolower [::tcl::idna encode $line]] set utf [::tcl::idna decode [string tolower $line]] db eval { INSERT OR REPLACE INTO domains (domain, forbidden) VALUES ($utf, 0); } if {$idna ne $utf} { db eval { INSERT OR REPLACE INTO domains (domain, forbidden) VALUES ($idna, 0); } } } else { if {[string match {\*.*} $line]} { set line [string range $line 2 end] set idna [string tolower [::tcl::idna encode $line]] set utf [::tcl::idna decode [string tolower $line]] db eval { INSERT OR REPLACE INTO forbiddenSuper (domain) VALUES ($utf); } if {$idna ne $utf} { db eval { INSERT OR REPLACE INTO forbiddenSuper (domain) VALUES ($idna); } } } else { set idna [string tolower [::tcl::idna encode $line]] set utf [::tcl::idna decode [string tolower $line]] } db eval { INSERT OR REPLACE INTO domains (domain, forbidden) VALUES ($utf, 1); } if {$idna ne $utf} { db eval { INSERT OR REPLACE INTO domains (domain, forbidden) VALUES ($idna, 1); } } } if {$utf ne [::tcl::idna decode [string tolower $idna]]} { log warn "mismatch in IDNA handling for %s (%d, %s, %s)" \ $idna $line $utf [::tcl::idna decode $idna] } } dict with meta { set installDate [clock seconds] db eval { INSERT OR REPLACE INTO domainCacheMetadata (id, retrievalDate, installDate) VALUES (1, $retrievalDate, $installDate); } } } set n [expr {[db total_changes] - $n}] log info "constructed domain info with %d entries" $n } # This forces the rebuild of the domain data, loading it from method forceLoadDomainData {} { db transaction { db eval { DELETE FROM domains; DELETE FROM forbiddenSuper; INSERT OR REPLACE INTO domainCacheMetadata (id, retrievalDate, installDate) VALUES (1, -1, -1); } my InitDomainList } } destructor { catch { after cancel $purgeTimer } catch { after cancel $refreshTimer } catch { db close } return } method GetCookiesForHostAndPath {listVar secure host path fullhost} { upvar 1 $listVar result log debug "check for cookies for %s" [locn $secure $host $path] set exact [expr {$host eq $fullhost}] db eval { SELECT key, value FROM persistentCookies WHERE domain = $host AND path = $path AND secure <= $secure AND (NOT originonly OR domain = $fullhost) AND originonly = $exact } { lappend result $key $value db eval { UPDATE persistentCookies SET lastuse = $now WHERE id = $id } } set now [clock seconds] db eval { SELECT id, key, value FROM sessionCookies WHERE domain = $host AND path = $path AND secure <= $secure AND (NOT originonly OR domain = $fullhost) AND originonly = $exact } { lappend result $key $value db eval { UPDATE sessionCookies SET lastuse = $now WHERE id = $id } } } method getCookies {proto host path} { set result {} set paths [splitPath $path] if {[regexp {[^0-9.]} $host]} { set domains [splitDomain [string tolower [::tcl::idna encode $host]]] } else { # Ugh, it's a numeric domain! Restrict it to just itself... set domains [list $host] } set secure [string equal -nocase $proto "https"] # Open question: how to move these manipulations into the database # engine (if that's where they *should* be). # # Suggestion from kbk: #LENGTH(theColumn) <= LENGTH($queryStr) AND #SUBSTR(theColumn, LENGTH($queryStr) LENGTH(theColumn)+1) = $queryStr # # However, we instead do most of the work in Tcl because that lets us # do the splitting exactly right, and it's far easier to work with # strings in Tcl than in SQL. db transaction { foreach domain $domains { foreach p $paths { my GetCookiesForHostAndPath result $secure $domain $p $host } } return $result } } method BadDomain options { if {![dict exists $options domain]} { log error "no domain present in options" return 0 } dict with options {} if {$domain ne $origin} { log debug "cookie domain varies from origin (%s, %s)" \ $domain $origin if {[string match .* $domain]} { set dotd $domain } else { set dotd .$domain } if {![string equal -length [string length $dotd] \ [string reverse $dotd] [string reverse $origin]]} { log warn "bad cookie: domain not suffix of origin" return 1 } } if {![regexp {[^0-9.]} $domain]} { if {$domain eq $origin} { # May set for itself return 0 } log warn "bad cookie: for a numeric address" return 1 } db eval { SELECT forbidden FROM domains WHERE domain = $domain } { if {$forbidden} { log warn "bad cookie: for a forbidden address" } return $forbidden } if {[regexp {^[^.]+\.(.+)$} $domain -> super] && [db exists { SELECT 1 FROM forbiddenSuper WHERE domain = $super }]} then { log warn "bad cookie: for a forbidden address" return 1 } return 0 } # A defined extension point to allow users to easily impose extra policies # on whether to accept cookies from a particular domain and path. method policyAllow {operation domain path} { return true } method storeCookie {options} { db transaction { if {[my BadDomain $options]} { return } set now [clock seconds] set persistent [dict exists $options expires] dict with options {} if {!$persistent} { if {![my policyAllow session $domain $path]} { log warn "bad cookie: $domain prohibited by user policy" return } db eval { INSERT OR REPLACE INTO sessionCookies ( secure, domain, path, key, value, originonly, creation, lastuse) VALUES ($secure, $domain, $path, $key, $value, $hostonly, $now, $now); DELETE FROM persistentCookies WHERE domain = $domain AND path = $path AND key = $key AND secure <= $secure AND originonly = $hostonly } incr deletions [db changes] log debug "defined session cookie for %s" \ [locn $secure $domain $path $key] } elseif {$expires < $now} { if {![my policyAllow delete $domain $path]} { log warn "bad cookie: $domain prohibited by user policy" return } db eval { DELETE FROM persistentCookies WHERE domain = $domain AND path = $path AND key = $key AND secure <= $secure AND originonly = $hostonly } set del [db changes] db eval { DELETE FROM sessionCookies WHERE domain = $domain AND path = $path AND key = $key AND secure <= $secure AND originonly = $hostonly } incr deletions [incr del [db changes]] log debug "deleted %d cookies for %s" \ $del [locn $secure $domain $path $key] } else { if {![my policyAllow set $domain $path]} { log warn "bad cookie: $domain prohibited by user policy" return } db eval { INSERT OR REPLACE INTO persistentCookies ( secure, domain, path, key, value, originonly, expiry, creation, lastuse) VALUES ($secure, $domain, $path, $key, $value, $hostonly, $expires, $now, $now); DELETE FROM sessionCookies WHERE domain = $domain AND path = $path AND key = $key AND secure <= $secure AND originonly = $hostonly } incr deletions [db changes] log debug "defined persistent cookie for %s, expires at %s" \ [locn $secure $domain $path $key] \ [clock format $expires] } } } method PurgeCookies {} { namespace upvar [info object namespace [self class]] \ vacuumtrigger trigger retainlimit retain my PostponePurge set now [clock seconds] log debug "purging cookies that expired before %s" [clock format $now] db transaction { db eval { DELETE FROM persistentCookies WHERE expiry < $now } incr deletions [db changes] db eval { DELETE FROM persistentCookies WHERE id IN ( SELECT id FROM persistentCookies ORDER BY lastuse ASC LIMIT -1 OFFSET $retain) } incr deletions [db changes] db eval { DELETE FROM sessionCookies WHERE id IN ( SELECT id FROM sessionCookies ORDER BY lastuse LIMIT -1 OFFSET $retain) } incr deletions [db changes] } # Once we've deleted a fair bit, vacuum the database. Must be done # outside a transaction. if {$deletions > $trigger} { set deletions 0 log debug "vacuuming cookie database" catch { db eval { VACUUM } } } } forward Database db method lookup {{host ""} {key ""}} { set host [string tolower [::tcl::idna encode $host]] db transaction { if {$host eq ""} { set result {} db eval { SELECT DISTINCT domain FROM cookies ORDER BY domain } { lappend result [::tcl::idna decode [string tolower $domain]] } return $result } elseif {$key eq ""} { set result {} db eval { SELECT DISTINCT key FROM cookies WHERE domain = $host ORDER BY key } { lappend result $key } return $result } else { db eval { SELECT value FROM cookies WHERE domain = $host AND key = $key LIMIT 1 } { return $value } return -code error "no such key for that host" } } } } # Local variables: # mode: tcl # fill-column: 78 # End: tcl9.0.1/library/cookiejar/idna.tcl0000644000175000017500000001643514726623136016620 0ustar sergeisergei# idna.tcl -- # # Implementation of IDNA (Internationalized Domain Names for # Applications) encoding/decoding system, built on a punycode engine # developed directly from the code in RFC 3492, Appendix C (with # substantial modifications). # # This implementation includes code from that RFC, translated to Tcl; the # other parts are: # Copyright © 2014 Donal K. Fellows # # See the file "license.terms" for information on usage and redistribution of # this file, and for a DISCLAIMER OF ALL WARRANTIES. namespace eval ::tcl::idna { namespace ensemble create -command puny -map { encode punyencode decode punydecode } namespace ensemble create -command ::tcl::idna -map { encode IDNAencode decode IDNAdecode puny puny version {::apply {{} {package present tcl::idna} ::}} } proc IDNAencode hostname { set parts {} # Split term from RFC 3490, Sec 3.1 foreach part [split $hostname "\x2E\u3002\uFF0E\uFF61"] { if {[regexp {[^-A-Za-z0-9]} $part]} { if {[regexp {[^-A-Za-z0-9\xA1-\uFFFF]} $part ch]} { scan $ch %c c if {$ch < "!" || $ch > "~"} { set ch [format "\\u%04x" $c] } throw [list IDNA INVALID_NAME_CHARACTER $ch] \ "bad character \"$ch\" in DNS name" } set part xn--[punyencode $part] # Length restriction from RFC 5890, Sec 2.3.1 if {[string length $part] > 63} { throw [list IDNA OVERLONG_PART $part] \ "hostname part too long" } } lappend parts $part } return [join $parts .] } proc IDNAdecode hostname { set parts {} # Split term from RFC 3490, Sec 3.1 foreach part [split $hostname "\x2E\u3002\uFF0E\uFF61"] { if {[string match -nocase "xn--*" $part]} { set part [punydecode [string range $part 4 end]] } lappend parts $part } return [join $parts .] } variable digits [split "abcdefghijklmnopqrstuvwxyz0123456789" ""] # Bootstring parameters for Punycode variable base 36 variable tmin 1 variable tmax 26 variable skew 38 variable damp 700 variable initial_bias 72 variable initial_n 0x80 variable max_codepoint 0x10FFFF proc adapt {delta first numchars} { variable base variable tmin variable tmax variable damp variable skew set delta [expr {$delta / ($first ? $damp : 2)}] incr delta [expr {$delta / $numchars}] set k 0 while {$delta > ($base - $tmin) * $tmax / 2} { set delta [expr {$delta / ($base-$tmin)}] incr k $base } return [expr {$k + ($base-$tmin+1) * $delta / ($delta+$skew)}] } # Main punycode encoding function proc punyencode {string {case ""}} { variable digits variable tmin variable tmax variable base variable initial_n variable initial_bias if {![string is boolean $case]} { return -code error "\"$case\" must be boolean" } set in {} foreach char [set string [split $string ""]] { scan $char "%c" ch lappend in $ch } set output {} # Initialize the state: set n $initial_n set delta 0 set bias $initial_bias # Handle the basic code points: foreach ch $string { if {$ch < "\x80"} { if {$case eq ""} { append output $ch } elseif {[string is true $case]} { append output [string toupper $ch] } elseif {[string is false $case]} { append output [string tolower $ch] } } } set b [string length $output] # h is the number of code points that have been handled, b is the # number of basic code points. if {$b > 0} { append output "-" } # Main encoding loop: for {set h $b} {$h < [llength $in]} {incr delta; incr n} { # All non-basic code points < n have been handled already. Find # the next larger one: set m inf foreach ch $in { if {$ch >= $n && $ch < $m} { set m $ch } } # Increase delta enough to advance the decoder's state to # , but guard against overflow: if {$m-$n > (0xFFFFFFFF-$delta)/($h+1)} { throw {PUNYCODE OVERFLOW} "overflow in delta computation" } incr delta [expr {($m-$n) * ($h+1)}] set n $m foreach ch $in { if {$ch < $n && ([incr delta] & 0xFFFFFFFF) == 0} { throw {PUNYCODE OVERFLOW} "overflow in delta computation" } if {$ch != $n} { continue } # Represent delta as a generalized variable-length integer: for {set q $delta; set k $base} true {incr k $base} { set t [expr {min(max($k-$bias, $tmin), $tmax)}] if {$q < $t} { break } append output \ [lindex $digits [expr {$t + ($q-$t)%($base-$t)}]] set q [expr {($q-$t) / ($base-$t)}] } append output [lindex $digits $q] set bias [adapt $delta [expr {$h==$b}] [expr {$h+1}]] set delta 0 incr h } } return $output } # Main punycode decode function proc punydecode {string {case ""}} { variable tmin variable tmax variable base variable initial_n variable initial_bias variable max_codepoint if {![string is boolean $case]} { return -code error "\"$case\" must be boolean" } # Initialize the state: set n $initial_n set i 0 set first 1 set bias $initial_bias # Split the string into the "real" ASCII characters and the ones to # feed into the main decoder. Note that we don't need to check the # result of [regexp] because that RE will technically match any string # at all. regexp {^(?:(.*)-)?([^-]*)$} $string -> pre post if {[string is true -strict $case]} { set pre [string toupper $pre] } elseif {[string is false -strict $case]} { set pre [string tolower $pre] } set output [split $pre ""] set out [llength $output] # Main decoding loop: for {set in 0} {$in < [string length $post]} {incr in} { # Decode a generalized variable-length integer into delta, which # gets added to i. The overflow checking is easier if we increase # i as we go, then subtract off its starting value at the end to # obtain delta. for {set oldi $i; set w 1; set k $base} 1 {incr in} { if {[set ch [string index $post $in]] eq ""} { throw {PUNYCODE BAD_INPUT LENGTH} "exceeded input data" } if {[string match -nocase {[a-z]} $ch]} { scan [string toupper $ch] %c digit incr digit -65 } elseif {[string match {[0-9]} $ch]} { set digit [expr {$ch + 26}] } else { throw {PUNYCODE BAD_INPUT CHAR} \ "bad decode character \"$ch\"" } incr i [expr {$digit * $w}] set t [expr {min(max($tmin, $k-$bias), $tmax)}] if {$digit < $t} { set bias [adapt [expr {$i-$oldi}] $first [incr out]] set first 0 break } if {[set w [expr {$w * ($base - $t)}]] > 0x7FFFFFFF} { throw {PUNYCODE OVERFLOW} \ "excessively large integer computed in digit decode" } incr k $base } # i was supposed to wrap around from out+1 to 0, incrementing n # each time, so we'll fix that now: if {[incr n [expr {$i / $out}]] > 0x7FFFFFFF} { throw {PUNYCODE OVERFLOW} \ "excessively large integer computed in character choice" } elseif {$n > $max_codepoint} { if {$n >= 0x00D800 && $n < 0x00E000} { # Bare surrogate?! throw {PUNYCODE NON_BMP} \ [format "unsupported character U+%06x" $n] } throw {PUNYCODE NON_UNICODE} "bad codepoint $n" } set i [expr {$i % $out}] # Insert n at position i of the output: set output [linsert $output $i [format "%c" $n]] incr i } return [join $output ""] } } package provide tcl::idna 1.0.1 # Local variables: # mode: tcl # fill-column: 78 # End: tcl9.0.1/library/cookiejar/pkgIndex.tcl0000644000175000017500000000032614726623136017446 0ustar sergeisergeiif {![package vsatisfies [package provide Tcl] 8.6-]} {return} package ifneeded cookiejar 0.2.0 [list source [file join $dir cookiejar.tcl]] package ifneeded tcl::idna 1.0.1 [list source [file join $dir idna.tcl]] tcl9.0.1/library/cookiejar/public_suffix_list.dat.gz0000644000175000017500000021226314726623136022204 0ustar sergeisergei`public_suffix_list.dat[OrwѮe1,ymI'YrZIvw^=3s1@NrgyȯgfW!bXUVK0Dm˗P]YʼeKewmuX>fY/.7uJݡϏߗ[Jri\UCܺFLZ0Z2TY9 oKu}bde DWn,UɖжLl1NY=(7 zP R|OK"*%:S_^+6-!ua-I4fPQl1wȶt߇Y,xg{Q Jǹ@~<ѯK?߽y[y۷U@]'jt?:*Y3j†PZBBtM?Ott3:Yj,tMwBGZVu|ݺUuyW &1vEw@Y)se ՁI )M'mev8LL|(VtIMV{K|Ǐ M5h7*4ǪnX3D;VLcEU5(=TH!EҦ$w} |_7$ہRtC^(Jm?coZ0aفz/xMqڜs$&UhA= mVLqS!hOJ[)\3d= LOB"V J)UI)f ?J ?bzBҨ 56̻ƾYhO*Ѵf`M$L{lo؉;PjpA4W8jT(slN'N5jlnǦDNcpG=t70z=>ܙQٸxO{׆q%SK=Z(:U0"$ɮNy쒴#1-ٸ=I0di)y(sXclw3)}P_6k4:2rƳh5=YzmJ0ɵXZΞZlr-JͯPjV})sS(D!m~#gP#ܑ^nWFQ8!].?^=-={H9Pı? %) M/f7`6|3G*p“]ˬ?5*tffu1pOe`w_%ѓA~Dk΀zBΌT (?mH{ƶA(HbO <~$WoS8e\7̦O[F\ifmHCU#hy|5ٌno4X0c#w7F87CS>{#'&ZI~?+i=y廵W(^#LK>z,AyacR%Ֆc@gTg,|"g,nG,Az"QD"![t;m?nz_ًQ˞FFڏ/ ]S7"T=!bRwOJXt@](pkk Ӟ X)f"hd xĐU']UM X&!A*,0R(UA&R!s#tF\ݕ=ۇOCJܪ1 D,Z%.YL&yre38U JbUhH MSފHT4C 4+ZO  D ll xH9<"9xJzpX֢OkQ(ZnAwz<Jpn++-G 3 p5U? "⠁M6Pw׷lp~qm^\T4:xxxG3=Onz**/tB /t *O'gSaQn`W l.nQا&m)/:ujEIXO1[d87҄BC=C9*MfsqYՁm50bZ%r@9߱R3Gk' p"TEnxVJGaED{ }z?X%qGG/T@I'ItJ%jFj`YHd!d=  w U0+[{1xg#dgD^2>|E Vvi_%g=lqenM**-;[|N-|ŹD0c D;ٽL"7:4ʽ.;tp.yvtJJǍ5rQ,d,\ۄwJߟU44V#]lF?:>(S(VrZn /k-N$tVEߪ9p{dBR*IT$3PIgd5bx56nlѯmI';.{5Öab' k;t~ǏƭLw҄bXlHфos!eV3 3.mޟB:1}ڌq8tМ_Pp(Yl 'Csf"Qo^ɂQz0͘4Вђђќ W(;- czRO|7|㥼 +ii_Iv bCoCX=##, 1g\t顠?[ir "&-z3!ݙsvS~o眧uGx>lB=s/vχF, л_YNȯw.0߆T.zC};^vFā=j! i;Nk.BI f8lx\f݁AӅaW''O,aɧ“óȽ6W q\/EL.sl43̇ #ǙsHnHgľqd@t61Fl[h%?F#hfF|t#ιW݈snUb-W#a~XMnW)-U?pPlTU\H@|Xyc T#>;'D VZyjm*[BZV\`q`z Yzqap7Š]n]E2O98Bx*^m +/Dc Ј0V?Yy #ģQʉdO_?%1јOxV'@^sd$b%}bgDG6V'V&V&V)@dD y1IX3Ša9|./~/e$[Я |Y |q,NQ?y_+ WE'(_ )k<?_g+g@"C}E|g5EFz^05X1=Pմ+:$XGgWCdvOD{IWkjuVaW5ヿsS/2>Z\(dyՔ&62WNKXm;W3ִ-6+(Т Y+hۨG1(&h5G3,*ıgA#WiLbAUWk.gmVTX.%gjwĖAVae墐N)F7S+rhF*QE#YޙF?(ۺ܆6ܣ8C_)J][ŕE#/JY NLccǧQ%jjkkjH4_) #`F>y5{voahe\dҪ5Ob >O7r:6r:6r,6Oܿ6Ir\9~R EtW 5ggWFG#2m P"E53Cj%(_b|l{x|pßU՜{ {ǏBٔW*mEc,鉶с@ʚn!i˜F\4FslV,Q\jбY/F@G :T$0ц*8 dIV&)ەlejK֡j{* IY \ѤRh_ʀf)sz4 r'dDmSNZ:ekUi,-*ۅ4_3R]'HNNTzrOzuzi}D'ʜ2AŁGXh kSer>:i*C37RGI0XqM@<$PqFDZ!Hry=2 Y{E9?.f;M񑉅y(jLњtnQ%jnñC(Lrdx^УMÔHfV&Úz&>T *r5ɐNgm֨~ &+1W ʕr U5Ub PU^Klhŧ߮W}^zWTrMkae%UUVW_W*98hl< u0^erMqpJfJ_ ÎvC;  7h;'o<; `?UcQl~6l6~I{fl6(zM~SG V~e@yXVxvX>hHg{ d y@Zyb"_ oJa_7K!ںTJ][(E"&%:Q0"1eE x3 9kIl@O ր@TZz۱LJR)1#\5C6&"5 &/I/k(r%1) ZQ…BԦz"7Ah()PC*TA"624K%ktPk4iFgnM}`5G(/~_6G_">_??(ǯ!?zoc/Ą> o-Aoۿ|roCI?J1ƇB?_}W}Lÿ:_?R[߲%?ڒџ ?jwz?ϡ?ġ7,w!y"wM?M}ޔ/P0y|OLT~?B?}ïP|6_f_~A3C=8 Fh9vj omrjv(@ zd5q=!õ@DdC>=6_qEDYɆc>+˒a盱n ǯ{&>R Vh=oxЎ:MqLvt#-"Hk, H!6<&U# lv |U0ZEYLҰ&*Vs]2y0nr5 cb〺 BGqAf6cQ'< x ="!EԄ߇8E =N|"m@1@RT^b2̵tc%&QgAH! >S9,p $ 0*}|p T# WC <`nVx ΂ND&G( 8<;=aQ:7(8 -Bg!TDt=dHFnhá̑"P"[BvfA$7k( ڠ( >yCCK8,'M5(g =C P:v`~G(zd a"08}@)C9` "7 pۙ?{ݹq$ᦧlH5P;RuQ5ە\tXX1Kh8&XqeEE4!8gUuG0QA Z[4lS/A~Х,'qGԋ` X`pd`J-`EsFWmy2CFB$ !DwRNDi{W #@/GvhibvjaqV,Mk O8'yDqE`&F (F0.$uZE/YmyQ68}@Q1[f)`q3h\eнQ*m\n8sņ0S@}]f]Ϩgُ p H;G: DBw :e`(wPlLjJIe^p#3;n<tВV-ut&o uJ(g7Tٵ;8Vmi킊Sėcwq5 FPymP,}Z c&]j7ɱ\W/R6mVP(48n 6vA\آA<3Za.]%u{n >"Giu#ĕr=׾I 5yI. r-@e*ݷat20Qjxr.@:@]$)8LAf4mMfcC;Fy$twU5{XPk= #pљ(tv :9؈(s.%ޒJb~Y h |`Em49iGaM_fZemD'y-6(AQQx E GhirpqMN!&.[(ׅ`G!EC }\Q{\;5}Xcؽe9{}{DpH|탃dË8o4f.X"~:8=FCtzZY1-p+֠a S_1PR35+@]j~@ +;\ dEL `nEx/$3)n3 $䫁1@-5^bjz";oEH=dN`j6SRu'-*9hPɊ R˳"tX jGm7CX*Tם*e3MfnZYv菡!Bע5}BbdSu0ANRI Q24e6@5Z a[ÞdlIjAfs t䏸\ת%`$Z\tw@;؆3-5A"<)NB!m"P KvK'O N1֎# 1!g/w&mxج\VVkަY)h\DY-0IEAH_q!KN}]i4'm51PZv`) C4AK0N&.b1Jb8*:r4hPN0xjGaVHhmBDl!1 A 86DF"\p*ᎃt@x`-АK} Dp-ԁo)ii<9Md)с7F~$~gC%(d6Ye-ůt'OTAd7p$4G2UrYq4yyA]Y1 REP 4 G9܈ꀸb2qdSW05~d}M )؅NV7&U'ZǵRw;HdӛӜՈ%`gC݊fӊN  4Q_>aצ[FC'.+ UaxSC(ࢶ\\#V4fm*  Z ZNt\d+(s%R7k>ޒ#Qǥ0N9 C$HYI\40H#@LJc*CIA"bsf!078`%)tAC@IѨ=Y#Ѽzv>[OX-a4Sj*hk Aw[Q4Ĩ| h)HC(kWAڢǼL s 8p;|duŀ.N| $w>HcX7泊4a~x@P.-I3)ktp\|@)<&Jh6Fs4aX 1'\ {U$^  YDqZ2f% ׬@0I=W6J݄b@.:c2ΤƆ-A;ɐG0wƋZVkf"HW&|T:O:m':?6h$ KiPwEᑫ uU8v'-CkqUCt8!ظ롈 rBHj;Jb,;\&$ kȭo!GKMQ:Z:XR;Hc>.Ђ:@93Œk n-492tEBWfb0:1 o$0Iƌfֲe!3l ReZ :Gw., V܊r6 ^xXq\#5 Ja? t)HPU偙 Hҳ2Uw}u{ `GfxEφ0حHE* tʟ{Rt9#`B n frq\F iH 3J̤CϥPDz=pҒ"6x.!ӏ@wQc9s8Q]qQI/F(0pU6:@Z>!VSq|U1 \Aܫ<^HIvan`ۊ"xy&#"iqEJP}@c(”l1u@TD{IDHB}B_i!xUq.RV`Mڥ`^< "k '- dAxS?l;|E BlD@c)}y>!- `xu%+n='[ҥ@w.Ёd8bw$ KN>hٹ;Sw+*ʆag . :wUȺr)sʤ'jcۅe<GE|sMa >g(fgDO 0bi%Q1l7x#ӨN^'CYkb,&hXa_2͉ʉ@,Kx9YtC؉oi睸Q`NE7Oz$_1Ꮜ܅%Rs Oeh1M;g7ҁVm)|=G<ѽk7jfҭDT>/`&UU.)V uA[`ŭGmmlis({$z$Q`s/\tbcfh GsLLD.6C0 # qeQ ? Vd&Ѯ!HǨ84 ^]EXƼe4i(Y|뼖 /= @!N C1 ɣ::RCZ 閛փ[L8&n:ibsAmxAMj#ŭU @"B ¸¶A&|r:fh,*qf6~v 0JE cm[͡. .7{,xn$4v82 :'sRD; 4LBh:dJՒ1Kq{EB4s)7AF (`ˀBrZ2TbCPXojfUb)Uy!Kѽ-)ogBi/CsMFPJ5E Vbq7[%#0d_:*{q[!FF1-"\ÝفLQyd+L_2=G'FueKMZS@rYD@XGDq{-! 4AˢgXOu@aF6`-_ 2` yz sJtHm$S{mGZ9j'KCniURD/Ra9-<6YhF&SĉmNF61k"+zIYTϔJwu#jcXMzjh;Bq \N"j[_6xsD+QE"1]>&zM$^j>7_!Ex (^M4ڹNo^gx\>Ixxw/^Jupg~5DkL:TFq MAeR\BŅQTqɷOg_Pu9^M}fMHx7e'|Py|4\A9ǐ!e_Tl<^dut 0 Le/h\mcK}k1XS21dZ߆U]7DP/cJXm+YPt*X3뤥JV(N R+@gd}/ NL8^u7)9JNJNזh4)BZBUgv0#@&L4"k(RE#]+UoLIԧA  WRzh כm> iL頝0p"$8pSQ33=$tmA96lSpl9L2 ZꖾL+fAc"˜Δ:S3sۻ"j# ylo200CPf+f\yܻԐIī9W=Hyms%p=:y嵳6.&s.ۜ 5Ϲ4s.ۜ 8E\윮6te!Ղ\wjAbnŤb ePY*@ aFO,<4XnxP(BUHϕuJ \dp\&L7>ӪX72#8R*_,$d7G\dJŭSd8J@^[C^5}CM" ׌{9/2 S c)'Ž[*R79ɜd,4gn3{͑˙Ywt`D&b$LD>gkLR Kqy2I*lE*!7uvV+)yYYYYug٩Q!5 ؚ)59UmbD,Z]3MgK$Ow9Op .:skkԝ^݃*\<<<}<} ǝPgm>V&bAUm]/E ~-`>^c=/6*BF*|AaOHREDk+8W<! >յABu0V%0>2\ Zك,ƞ ARD%@}hPVbqQt0J5bBgPUCߊC5UnQ\aeL顛bڏ |`$o_c:K„5ta5!Џ ks]`Hwp}e9/ңlxKF_?cH2ÁS98 } ^C@&&$giRCg%KxVIľk(j ]NRs=q~8laE7*!U$;DQR0Pda2FI~-*qχՙԣmC9f2`)P: Ӌ}Rxc :2V&qʭ/A bhYI0&ZP"$#EjY6l /-#m;)[(FM$1~fUG5 P( At`yƱ(Y\dK" PLC__װh#`m b*?bO&g3:prHD(@M\y)>nQ/'d FXŃrc!a-@K|/u PWS>ĂB+pW $l5o ?9A$׭F CdݙA Jl"G" `>VK>%. ־tl#(u)DG[Zt_ e`睙,تއ/jnpQ2(J_C.d) 2bn#Y#u@ ZKBxF:QbolzsZUu)ؠpK@+z1FW#)AFL4JIID򺬠T%9xUk>gֹ` FQm~=ʴ_iH_㚲N'h?$y(kb _$c,Ŀ(V!BŃۥ&T⻂}(>vM@WWP]ƃ H8 pf"Ȧ©tg $ת7{-@̄wH,T$Ї} !K%}8-QP-S6EIE4J|=A : T@Wl ݓKWgE5E2U,X 2Sp&SN\w1F$M(G]a0TAD*T#͡0d: JXEnX4M j@*?_SX` d8 I 5q3tBX\u\uUۯG^&Lx O,1XPThk=m8S|Y}6w`M֒6OP' `g W*zj(ψ*(ʵdWr>K ?ZM3kcrTU @iOQ9i=MAlk߂f"|B/+ӫxwa0@E@#b0MEA1ʒLYnՎ p7C zN= mzOVB߽;8YLOa3ȄӏU1͉P@*$9_tSS>)@f[AB3%kݜMY&9ƒv f%!c3ל-`QrQʞ2;gd4*Kgr>ϸ$3W>󱅜-G+ c5Bwsl/?2qGx>T#sg7cݜmws6ل7g]xPMU{El/@1&|‚j~)>flC·r>ԐI?| kY-Pv]Q?}Y4!YB8.u0s >|LG+D-Na\]dI3pxpPVIl \S@q1-p/~q#,+j" !pTPhHQyqes1|l kL# 4dzF9z *%>,4 L<CoG;QQOZ)Oy)ΐ7}ZI[ 74ѮrMe4]a4hPqCqF!T\J>`S)qeNiDു THL[?:A؉HLV@A\AKUA_>r)|(\#  z]P~ L׋}KC6Ҕ 0o,tg%}莲 `<}`0S _ u ?}WBbՖ.b66f5%w_?T(JENe{ V _0,)A5'2͹ * Cs^,E/ @L8 B,/2dR *bu!G6\]P5SXW$M FbU"E<&aIDJS~3ܗԇ|#RY{Qf:1ZWbXx x-$9 WkEPXg_AAJ.9%Sdi&| 0I⒊}DF--5![~\4 }"G`K>#L{ 4Mz&Gqa(^NЦi5`KD65=[tMp\o ݏKA|>6:M/UY"I1,]Cσ`^a[v)j[dm0dRA#4VOi^HAu3\ >V-?[#̌EGW('L V.j,9w7\P@1[$K1ρ?̐EmKL zb Js陎5:A‰cl7MwLY/p\VJV̛>2 P/7,$~Ղab DI蝼It*kBE.]`4˴2<|aBDh"1whSOfZ1uQ/29vňp!,tΏ453.KAhwA[Y\Z34!`yA\5M[S`(=s{F:ːn 01/6E[džLr2Lp兔]M Abl&drMFؗmXSra-i Z` %6 f׹J#QU,䳩2W(^f yX)i൮m&C != /";e*pe2c5Z`qE'cצ 0>@j742N;SKmp*LT8 ؼḯO 7RtGlDoD@gn i{GJ`oE'aVv+`ę~ 1b%'Npa PdE [&i!`ڠ a@aa6]bT6ҵ oSkq<"6<4O Q=SumRҚȰtim#.=QHA|00vT AaJ=^ (hX%m&N\DXMb^EƲ$b@cRH3{@ָT44tÔW -ڦԇu6kJoz>wA=.,@-~Vxkb'VU{WM̔^ys\0/V* Eu N0ZazUY4wZ iaHrZ$ƚ*֎i?OQV@'yFWI],?ء,JEx@2.GC(g{EŖR1by;-Slb v&[?状W?1_p(/dXM#"s[K ? z)b۳R3›g.4 a$Q|x##x Me?Q6]a A3y(hH)>m-^ +dOx0 Kv]mHh|`; < c >o[|=B\ɉI>%{-%ɖlY\3 kq~l JzĹyfy-S#i΅~os]qP&I4#nUc.ۘ?tEStLA4)ɅrbM u8z%{-ulɮcK' wJ][Yz؄?J~ W2.3 S4W;ْǖ8LڣQc:M~OJ0'gCyF>ك-|*|M ̰q"K9_=S> ')Q"=xBnx, V(PqAěrbM b3SQCΌl rKC]&.TPB # g pl0S}Hyh<jх]oip!Ci6ܺrTԧjGVWC{.\̔p1p~"WJ5H.V!xdW[6(}JZp:LwI !k6ma!kMN.s!G'k\,^HL-`!_\f3Qڶ kDg]4\cPi3,⚓%#lR:,M: N g׀%LQG/ى?<<Ы >)elOhv/J:%[WGd7%KfJ/z|ܿzBz嘕 cJ|t䃬%`-kɇ\K>z/P%E=Ae=^bi3gFr/ R|Ugy2gBeÑ\Kd=T٨Q OvVvx]_ڽ41.'*+u|<~+எ>jY9'9ú tWXeL:]YK]Tq7WxRCWVm><%bٞa>2"+$fhTUtycvF[36k!|Ѝ㹻HzWPѹBeNW|DT>^GCBX8d].WsYZ;At=p;I>nr\qAzljjj^qR={j,jHj>\'q /135E0eZB cΥMa>($\ˑp9}/8UN)N\┢@D()U B$K[N31I)>R 4*z5Ex+2@n?ANtDOEr,9Jε PD[;ǟP5/6]9=v^($ ![鑶*8_Tk>]|(xnfe=^ћgլY Yfmd͊ǚu).NªyyOqT]jfE-7&# S:NE5 v TB0 uWr,$ƒ(K5بRbA!$6uH}M+cw6ɄMϠC,|UajvSOI^gxTȠٳK}t7PS{x=B-+S4_y?w\9]W,[,[[H[[H[H[b?^t(K~tںiƚ7Sܢ { )Dl`6de)9@dgt $ͨfUsͪ&]k0KS\،kJ55cZ@7g{)BGip_ &K8\a5!iR/v4x@ A|JrAA5r)NxɝA{yjIISyѧT}L/ƫaz:1Ou _反Z ?j9t @]N2/>;wwwn'LUo0!3z`< n4&k&K1ؤ,#l +G95 RQCk=,=GtvWa-T֥# ^!24 tTAҘOnH%ذ҈(`y Cٰ$xRȹKrF"HÒCs 7s͔4$Q7ha V?Bg;8뫢oْ`J%f&t#گfk\k kx*!7E<05*w(~-qނ-:Fz^7%8EZև,궧1+bk"?W`5KvzBEPR YxMvV`Bm [lYlOi%@wx48>8%!&N藨~6 WR~DNʡ0Jy2ߌpr%̜)s7'r/( /{H9NrV7jr,)\Q^5YS QRS™rhgK5 F?N260aQ*R Nc[ebIܥd +#Ƞ!y2GK%wF!^>=w)x𙏣1qv\+Spҷ@ k(=W۳+g PBSr߫v]SJܗ;qn1yU"y%ßոeUca3RA!Bt%tqпZ\C룸HC &ph 2l IahF qi#`jx@' ({;,uLl1&̙]6ftr[&Mp 60[0mUm+E--%mͽeujbSřrsgj`ڛB U.T†)]$Hꣻu<-ꡰb,p=<@3Viwĉ;:s)>K9\ySW"Q.!rn։>RE![].w1 2 PBSr%ՎBծKW*WڕveiwpPJ?uSsy|r,k⥳X7E-XdI@Jv)q/pM?"|԰QpK`Z-c>Oj>>J ?GCY6tb4;/ns)Re&o~7sf{~"3W&( #/ܼGό]kXXu+әDŲyWB8n ʅ -']d w#iSgT y  igm0":=>q"$4Gjژ]XfeO#|0]D WB^BLVJ+ h65(_I-tVw HT(yeTzWSlsMmc1~8L,g{S6v˔\l)ۼLymړ5T>j7l5E1!Q0sb )أg*9b#oF# } ot@7YKEW5-%{xXqD{0#?> 5ѸϚE|xF2QfGZ/c]5V%jIo ,;H%¬G{_ _=c=#/}eh6DrJ)qf+L,w؝>7tm,7wyٜ:ܹaAbezAZrCǤd5΋w?qNߤ؅KSnM2NxwtrBhw 5ؿ {`鱋WBi5_|u;ƕs)s oa}'1Bx%>.3C;0W%sWgΝ}~?7u{ ׾bϏ=;6?}bkb7auxXzFqإn =7~ύoWjLo>O~/ yE}w(|W~xs}NNe>Jc)7͟>|oo=GI\N5W'CJt7EX m>I[_{[o>???>O#8?ڸm ҹwl{fG(j(h?i܋ ~f lķ_!ZTRZbIDvJt\&3sާϾIxs.o}vFFT.nho/Ƃ'N|9yvp%w% F& w{/|o:gޤQXАZɘp?}ݷ^tS|rCcoyȗ٤~^{O4V";#q] ʤ}gWb-]PV%qQ;ڢЯD <$Z[T[[ww;~@֜-aibzi-WGl`6Hi;ݺ RZċ>eh#¢~O_|2͕(䅤Fb@k$W@Z{߅_ 3w[0pA Z.W^DЍ@bg<z?z}}U`f9{vDj+H/6OjS$<|.{ǝ}+ܪսE8 F6jmưrkm=ݑf˂Խ Fw?-tDڻ]!T'}o}r݆ ػ߽=cöPPje5 /v>>8yYZO2!@WIU =+AB"򼸐ZE M1<~HЗ? sオ2˗ҕݐ S0J^6`xRj+ϯ/%6_}\J#?Tf#a\oזu- x*}^]qVI g4>{ۏ>w 0g8;L7ŕttp ?ֿ'N#:z.Y 9<%>`+-$܀sN}n8;z0}p1'?[g=|Wxl~x@}'?_>>DY4?lB 2XݩՃμ߀(WLj~P3D^̌cJ$HOSf58:x urz^>.s/9]g x!rvRu_Os3IdMwWvX2f|saKvωOCPxFlA-.(]tⅰ>i)/VA}Bad >`d,yQ K.X14>R"*8!9>4֛$ς_$ǯx">[)9m-,h΂,͂v|6gv|h:R7`7\^- JOg⹈ٍKkg76/tA'lݿ`ÃJQuc9( ~J{M0𶣤S@VgEAKS(t5a .\jsxq5gg fӇ mp˞а?! kg76/p2FLVl#y*͍;x{Qa9ƸvlyH F~53X&lK4_8yujf"h:B8<0hZ;(BK8U2.0 0ƻB;("I_Ig.&ShWX[Hf+ݖЭ`Il1m2ss^ u\Ci E"9'unmcR_UdFE7on !B2m%]/mg:p {ɒȸdY wSC],!r)!Ԯ.oѲXAg0|p-/\$L qidtڿ63`DdQ0@+Vkmj<{v:|k[O0 Fy+xFkĻZ$n|ifM0\ ۷<5.31L]& `.j;)DgaJm)G(·"sqmo aљ#nBn7#kmR-tQfh|[<`5hvA k_"')b2%Ll+S.'ַoߢ*M%mWd H bѝUR vf *v4cZb? ^\FE9qWG7-W56y?dЅEF%)DEIkPϋ ҵe O%zA iZCvEU~QۿCPCt%#XaNKzZq-aJB]Y,Cn 2'Jͫd5vR3&Noh(S^z2czqe5!(jQzŠ-/ u^e0BK0&6;xe(K8F64A4`znm_ݽyuvOloߺ{}g`3zH Ӑ_l(I&G{l8UG <6250-M8syz!UO>", 2: "2UQp\;woupkjyU ߉`pvЫW_VdDCU 눮Ό"P2ݑM!sZ^.a}ů;c$8id`<Pwz.}8򗕹aPBitx@gWp2D1ȡBi$ٍGJ<&Y!V'3~I?_Ƕi<_(|>24tKs1::0,rM-%1 &U]05 ,{ լӭ%+RрyJbN(a`pi]Yޝ~8LEQvzckZݰ ^Յ$Lr*&}\0`<^r{fRC,u#6Dջdsj$ĝD[|ktJ(TtK6 K'H(WkUԆ,C"VЏ[W) h5 FeHF!Y;DpN!v|e(k0Enf Yű~{ۘrz"AU 3d*I;kzILsf[fr{,^{[ &ji'2Zn"1%-% M pHZ~O P5IHsmo} ŬgMDJ 8I1D&T4M;&0*Kb|'I\J!cʪz2$5D2$'V j@4[;!.A ? [h=C;gu:Pf1 +b)xF,{څPL"4t =;@7'6L!/-bCրSwǢw04*CjРz B)Dmd2uYh[l,Rj?vJ&fS&-ñjN8jEv!ݩzJP6zsw#dVK7/F5m#{CXԸe`, ͰXi8IBXr:BIB`$K0( |-U֒V3#bL8YJA *E8XA&zn<1!a:f+t*ҡ'LjM$U=q? à}Eow^hCU :$AH uCY@޼D$6@f3i 4"8LUgh '\u HѵP@CEY*N"*?q :p;eHT`$g#/Z{Lǣ;mͦgynnKAª{tϕ`L$m!TV-Lwv%*`p_߾UX۷l^o%Ji a3~bJVp^D1ȣƊ "e2A߇ 7w E֩ׯ> Zfl"3(=Gɢ3f 0Tb+KG۱y Kf~hț>Vlm)\E**W7eDKU eIj7wJF1EOZEg%-PWL#5yQn]vf1lY&A_5yo0ОvkM8 |"hmr BClH?U4yQUk{_rBs&3.DP{/!Y=wv/swfޝ}?w^A Þ!DJS/WHv;y!w*e6 ?@VE~'@m*#qVCJ}^B'5fwa0"4 ,x}mb@f@;ZU҅9S9Q3 ycz}{br5-Tʯx?%EC.DO6*E>f #1A=GVZ\C5U[T@< D7n^;_{;;ֈc3伳]7m˵=`Ç]HhҨϾ/'ũNEsOx%dΐIΏ iB+n\ǁ3%;pH`@^:ҫO#J3JXH;:7hF Kqsm- :.0(fQ(>~]:dA{I]5G`gX# $`r@/\[=~͘*1)5*"E]*@4i1@d²v}G#2[-vhLk֡ PLt!7;gLjn2'c^< ?yدG= ]>j K h;'W.5V?=&gI: !Q'#!PTtLf 1%&$'<=O`+:ANӑ |̴ ϰ0a|-v)lѫi1@y~vBohq8k J Cc̬xd~F0$1sgCOP&wJjjv#A=o mU*/>Ʌq#=olLLDd*2G #MtGU23\T6Ó:-7ؒgcm(uoׄŭ  *t[ Pz."rB0H:I' Q`um8o]Qc9RP,Kx֎Q,,1h71;vw?Ug=B *կG7Iߒd :o޵Şqu"Ư,LV 34UʉYə\#sr[qI!p$iR06$lA-!ND؀:_DvB dG`KV]Qq,~y5|әW9:yօKa v$f#4y/TtdPP#6I j?QIa0u*Hҿ}=ʢMu=ΒDTsB3g(N K3ߩRh q{TqGp"Q̮>Nk{7-)}ݰϵbyV8bTXF^o Q3F1#Ba칊RsRŜx Pw~>ofg&#u>0nxGH2VЇ,@mK˫DruީA0uF*lPw\NҙPB9=lo8ɓ3a`c>UAnS ] k?K/TZów78 ؆*3oߺsf%?qXIRdbd R@2tR:Zb] >Rhq⧆jnqB7@RrxuPgW D1ӮϥnD ħY:\(cg"騺B tKxiBB1Od֝f6=Mm3lrG] `s S(vфJtKEI\ŝN!q0;0F RuB1NDt^kgh2ojs1jI\#Rr[7v[vnY Y8K>:^2E  (/׿S{]Ц_uBk'Daˠ;c/.CwxwJilԥOxh=5i%b d|sBcklb|,`,INg.Vq]wbUyP :16ơ3S0%k}'<MZi߬vDLáp~i~cw>u|lتDаbVyx >n1u,r_|NS6g0@HZ33 MCbȋTM\W!1mGYҙ@ۏc3;QDZ/]_yi;vmm{ݭ{vv0߹az+Dts] p&J YVjz]Czy-@u/ mϢ*:q [\[;ݽ{TٰHbDI;S-IU9d E-: Âm̙g8OaiDo0X&> fe!7MB`w_^ں {nv\D^!K}p Q\,%'$CDokS63!tɠR͆3>ObGN,B PeQC膸H:6"7dh IUˠ6OV ɊtYFX$2H5 ϩjYU} MdHc״U9OxQj߽3ȞT~kTCFM÷MB_'E#ؿmwn? OP'6ˎA42@w7D_IH\4M' ]ma|{0n`7Ds`3aq:6paж mS0Iɐ5>aimZ ^2uΎmT7<6PH玒V=1D:݉ ܣ\cwO{t~4;H`(eUψPYtј0=2vN'xl x<E6KU1 ZשԩcٔCD.K/v<Ffqg%P4 { ZVu.öZ1Ðn%$)%qT^L/&w-;/vNR_+JCMJMx%HvvحgeQSiDdwǀYkH2xexҲ̲Z>^*5vgttTMC^·ɜ>ӕ9;Dv,I,8{jSطb ri4[-:u* Eʮg{gnow_݃\CD Fa$QGm_vm7TqeUåCܶ$> <QzRqT>wۓ8Q L1AKbl%b Fq)a=*nΟ[UA,2^hǚH2{+JP7ñ Z !w^Q\D7vBΛK 0u.'xaD5,I<ܺYmkwvԊ񉗧{I~Bʃhi*=vU nсA4DMmBWzۡ-<:l\(tݻv[aW/Kn>t/3L:ۭ% +45a;ۑWi7X +FQӝDfj&3Foj ^Y aڵ_k<!cZX#ikyup[N3et=+QPa³E3(&įILgZ8hd$6:rK7o ,kxTﮣQxD`q#3/&eH&'Hq%SYֹf%G ѐP˟0L0ޢjq}05"+, I¯ &VXdVuD 9Cm)ICY`s&7ݛ+j*e]Nr~y,gwi69wS}AfeTb@yF4'VanLgQGU@o9e܀J ߘ>gϯ5[+14s@_[e Gfd<>a67)ʬ\lefq;'qeMe5 XK~dqn 4B&9 t&n&>1$]oBc{N3X6pH&H4BOzm*s J"}E=3$Gl(չ Vԭ!M>8L\LdP-8x, g;/? X+': f;>߷+ Z@j| wdl^XJ&\`/Px$V["B=cqפx5%2)4K`_m6X>K֕GSr?NQˎskC;b3i$ur>V4",M0!!dzB$p DZ4ֿyIk&dIGP U>bkK^}9 wBOM1Cz?ķROwl$Cqs4̠Sb-$<35CDCQ 'yc^B~97~Ib oɆVn1U\hJ%W"W8qb" Ckw5jlHOp=_nuX*~>5NeZ6Mݏ\\9X5u*`Aۡ\C]lij}{ rCso Gomw^ N_q ʈvݱQsg6M2Фx&OFם[ u ]xw50ׅaC/Ű@X iv&P,re%{7;A bweSkƘئӤ"ء,5#zh,t[ \IN[+bT mN+݃kXXd݄;Q4DF3A{ rb· S[2C<}yKO- ̓VLj␡Ҫ_eIgјN:|븩:MW>3 јӑF^<),$D`zx&[/C;)S)笴wMp)\|8`@f)n AsѺD3Xvp;w?&6@&<  tmLuwX;w=)KsO eɅlRU}\4әcL%xǾ(8PC>) ^EIBR6fIFpЀ0rM>Rsjt[=R ߽sz͝wo]؝v^l<<.Q́= ?6P3e:&e{Gȧ9 ?=(VծS'z@`܋ ]RyǹL;\8'lTOȲU4c2Db&*< Sd;m]P`̀(9;/oX1"vPbP z֑W>': ߟfgE)ξBҁ%[m)6U]o@]"8"'|M7Ba=Gӿi9)q갻՝[v^w,Х,s֯;) 3FAёi6jlFEvᮽeLa"bϑ/dTCSZE*N z%Xxf؊ =6iaz:tRN\(cgOJ.T iU5H|#ZǕkNť=_-,#9GZPJ&dCB:yz13!4gAK#ޕ,xxw%Tyë4{ H-z; BPQ<ᱏЭ5C׳ B%ݐ)4tA6n9VJx o=^m5BXp]I Oq V~֠;Θq]HnsF3+CsatU(ڪj ! <84 i$ -ul9E/0xM&6Ys־~&em" bѹ|38=RLBȝK'r&Fx샐RN1F(ĥbMbm> V [,Kza_3(a\;{VS8bgaMz|`*&I`KܖPt0P0E1i !xxn :6w`y#T-~]wdG$Tvzo0C8QCP5Æ,$?&( ~ ' =QɆ0oÝŶaW,z^qvԵ"GԓFto j0b 3nyC>Al-EyRS^in.g~ +ZY > TͽkGf훦(Z9zv}^-|7Bӹ#ym2-OH>,~m[)Υ\0Rszs)3KTxY)!"C:]ifdpgifw$Yxzaplc{LGB68־ 5R4 w U)O !HBҷ;'M] !Рb[iW̦o/yW@5 ڱpWnpU#wn Y]Xt/bqKqwCֶn|2F'= G-!/b.d=9Rxe!Pi 76Z4oe(+r¤` A|=đq,yLcѭ@Cd Ut'FʣC%tvbN'@dB`eQ/A&{e8-3dS7!]ހрs D.]=K54&_U"C|Iu0P m`oDlsk]9-eH0 K ,o_ȖnYw 7'(M۔Ny]StI6VaU=pk= tܽb3bMrr7!Ig'!Px zPͺ;x?%lݘb$¯ƒ&Qg(f΋wo_Ҏ 4R~XA)E.XD$ ,%`@D Z= $ *2D_>g7Wai$;lR6*S7hG ɢbҲ#dׯ;Hbu,t@芿IKuL+~LȦ8K^ V $O7U%x[,KzD۽qvEwoݾy+(б%(SXbߖ+} A2pۄF}7O!4CSJ17@շd*اPS}D7|;a AlFm!궞Dw]YK?<8Aw_ڌbjBBdlWĵxeO.S墊NA/}`⼵ufJD1*{¥[nЛW.y郷o9d:c+v5c^|S$p(s*+Ǣ-ǘh35-fzԐDjGTG7]K0ӧf-6bxq߶uбIMl(kЏ:0 sEzI[.o?iZSՑ.qi &H#ea9|U~&.apam?Ix坃kTt73.D$e(%0~;,z"-PS)ϵshw%__[TOpʇ">b?ݥb / G(k -zt 4ka)Qb[pN.i%bCPX EFpȑ> 'ӖI5r'Pb0j($blx aq^=ce; LLޡV:-Q.LM5Tx6XjXd~ Cή22Hd4H`)Sw;4$Ch'X)_ċ/LѨ_iψFFo(zKf&-XOm]̓QXwxe_յ.@LSXuʢ:^?34~8t|I}cJۗF6ױB6Ǩ*Yn>~*DC z@ D稏/)R*є%ؒ47}:NkϑyR)Z5M%sk$|d[ g!?K@b)$naxf]ʺ6jgW2F3GǏi@z15Fvz\$JX-t;42b2het}dob:4 j@ Y&GP^С;#O o:FQq 4r-![Vj#!0N‘}cߌh٪H EH Jx^8@ x(Z{޹59ØJIGcG#d~5 !*CU\4*_=AEYʶ4x-M[a\q ٩i |reO4 RTW5z2z[$,p\ A.c~p?U#J=#ٕrj9Sw{խ2!jDz`Ų$S{]9{29Ic# 1FM7fL:b ZNƳȦԭk)#M;-3=bjZڼiC,twQpq*( m=n|"mm4&׃KۻvwV[w󇃁Hr*/;ө:/^i]B;ЛǨ,W'a{_0wӵZወӲ]sgt0?O{̝=s%@lٖu6/_o_0wӸǧ~|!*D,'}t+%O}Cie*ꀇ}T N=nbJ}AgI%]~&F5[t&KEːujS%Og, c?FɈݝ-W/ޥ[f^VR׫,%g>HM"_b]tcO]U&iv1I"Ip ze0:>^e55VSU&ya93;^B{{a-,'9f(DzxÃ, PN;W+D{l WW+QסxSpAt1y$tQr.QlRX{K{8՘?%n"yOG4:]DA P=`2ҝJeu Y}peްcY(tLaHd/8G A <3ߤ'gf3\Neh x-Ysz4fc]ÿx:1MxYA:u39D=+/>F lQ,r_ӈș\MYOxmt;qrkvUPk}{莈 (hZ8ئ qVyܚ 3 B|UVq Sy% P)Gf\k58?<Q]ok7oydSn,Qܻ7<׹:|LDʋbg{ϵ'_|oTϝK Γ1VGiO:gV!4ëz*X_G_2ϟ?z)=ot{l:̣/}w{8w{OGo~?_+><9U?x|p3"?7]1 8HFڻ.W}}Χ_jDv̥ X2Ui~ҸpfXlH xp= _\х$f;,fUSa4{Omn?>>Ѻ=wʹ_ ï?G}"A>5oV®H[6~o>vI\U˛a]q_ѣ}ܻw,0ƱZIg˟KY'+=̿wOλy06D0gAO=fVmN7#@m@.I5hk2l4< 1̗b3mBy퟽Oѻ}',X-ljDwl߾t?ʷ?hg P@<8{\ǏïէO{dpoX/_/x\lcV~ww퀩 I|$ G?֣?Dg/d兜nڿ7>|gdcpE\H*y~d_ko~Ww] 1McXW?n@9.O_{<|o= LsO:qL/>__Ƈ@80f25T=w Svպ^ږC~ DtT qU/?ri+Of[}FT {|uos8xx|/>kH~ wlN?{.2:wilǑRڷ?4܅ %z3ż\NSouQ_}s||xyttrx^sWOb8΍Ύ/^}{)͆ &0w;059# J`C\7٧W6uVyglϊK*.ғK(JK. !G /v~=~vF{eviWΣ}9pZm\?QѿKb2.]J6w6d>ÿ}W4qqj _񵐬9W9qtw`m/sWu4t Ήs0V%^{} x^ȥcC~???d|vrQ~X+X4h"SrlB1r׀;!hPA<^4\b?~P\JIk0hb [D_o:/ OW*? 0?`͹d^g~+8ݞ??<{V |Otߛo^qN;S_~nKW.d觟r z OEwۺy ?{g~>(͕K'}/U˺ouGo~{߽+y;Cɥ_|ڿŧ<ϻ72&Bko*ߛ^yYX\|ˊ/?/9…~z/Y(O'Isc5 On '3y/^L5+Gⳏ|-nX (:n +ݾcuk.\!O#]H`('z,rO+2XDCՆzenLr.x.Ϟ4)&ohsna!Rw,oO(0#8Eka5*~3Fa|LJ./E+W?wOmsOoc;;;{[{{kom̼s}wKxZ?~Ԟno/g L |%Y jЂqT̃(l+ZO{gQGRЍXWJ|MmJ~օ1V%7,.vINR WWz5ίy\KpޮvuO9N#0]srFB|= d!k4໩nȽ cYCVێR7gOhZ-sD]}3 f&FL+J>Ca~rXൻ:m/_N uY֗j^}9dOhDDJn=y4])|xoUic^|PwmT}d%^ JE7l5yo] \ FϳLNHWLR{S^uaLlΦU=+ oPGuɋ f o웨wGJaNka}⦩~{«9X_IpqNt.Kx9ɐ@=C !WH͏ej U~6DiMTx/ZB\13f`$_X)4k,9 n,y\@c :{rɸ,2 !`e' W2t!*)ء{\Np$XYL7tth?bWsy^ FwhOr0;4$"k^3a$\+k['TF%AW, J(,S %!C]"1L0Ad(*"hHii6 g{uWL &q iFkAG7ޝ~ ѩ.G@Ч{jhg)6iT#O SvP|w9I ] S\/^%IDм~+agDXE`˼33F@:F?n'e1G^*34 1uBo4eE@rv9 LX3e_vs1=ٗy W :z`nn-8 5 #ԛ3!/T'6RIy&L.dtlrO*bw0( зu1`VAj[=v#/; 9ސ#l~SaHk|0GOՀut5*3պd`lG($ jƕegKQdUyTpHwt ڏI269e .i>bv52É>gdg K f}܁qՑ0Ͽ`V@+6O_fNδRR.{e[p9c qFA+$¨o"FUSp@ voaXO7}oiȤfc (/YG6奣PgٸO|Z@-œ U$B]8RlГ~UQEQƗMx.P”~(WP,Ll3DB:Ft]/{pd035`i;wONt/h"swaZ/$R3 b:wg]bK=7N꼦m_d@.Î}bϠKV 4EᑣtāL[5:/g>tgpm+W kf4m;iqF:mn, +?q޴<ȧLHCgYu \Ί_ f eef1 6^K˂b!QO݊c :&@yOItaݟPaq@5>2#Re$? Գ/xP浳 *%Ct)\=DH] gA>B2t[cpҞ5+N"fN=<~2SeZHC^hRr~f2L=;$m"(ֺuȇ%h^)`<=9+{ԋ^ Jiw`fz<:!L L=LOz$ 2#Tn&@ǩ$#}hs8dCU`YZ CfcD6c5?)$-SsUS [e o}>X݅N(ڃF>V9qL$#{c-Ǣm |uK=7 Qf xN# CLO>^ F~dRWsiKI`fzV6h|FE[5zl)/_ԡMQ +XjZUwܚ~` B`ԧRXwΕNU#(p—9{95/ )\K]gLJD٣0biL)j\8.hp5|Er!m&w:_¦K~ǧD,cmن4j D@i3p[b9O"ڠ|f&rȘK';0.;~9@0 H/# /tvbE@EqJi.Mf٘b M~8Aye .wM4_qU^*h6(cл\:ȧ#<e!Y,.}kym}4nY 2&[0}s`iJ*S$*(CMÂCU;nEc? ʲSl4刢X@<&a ;(Gæa. H#׳Bbb3:0V Oi!(UUZ7f:`IC]grA+g{>I*m1a#j=ba`!9hZDŽxJ:ml?w@4g =^IQ1E9O .Of)"l/a1cS;͠E>$&.iΣ-ћ6xk'&;իcnB٤t}3C ߥ)I_8>ReЛ C lx1?kD%P&İ+ޑ),x7!Gjvi a,$a7J@)HM4S 4$J)ԜN,U?ϏA 0I8̄ĭJ'!:A;lCҊFd0Od(øOrp9Ը ܠ9v hA ϮˎSKQ1:8WPS5[jqֲМEq~dڐ0ܝR'rՠB񔚩ԂUE {d%ac"sB7 M`Ҫ7x)tYќhnH[4=tVк톘Z UشQ#c-͊K^XQHY xq6h%DhkdoX/_!MG0MeNmND (a`OaXK;G#.C% RhY O}-m94_(E&䖆h(\6%υ= |'`4ڸצgC.A[Rgu0JicΎj]L[rY7]7̈́ɴʚbIA؅/{D\4++FNY٩Vp;jh_ArݪW~m[U'8_YOшn8=r7ހCqmӴI X,[}P}/̩i[ QqEOyBvegC]ݥzޡqkק}QO8{.*.-YwkC۪OH%񂅀3݌jJe:@y9(`)#)N;a]?,X)Yi%CM@c:cM̦C h fz"ۛނ {!v篃G=mf6u?g/nEhu}V8Rhwgh(p?wYZJ~bI7DxY%:a'+$ PThՀL $| B G?'"vDjzQ;JDfkײ8\ m|ltz˳zdfegTcw H>C/ njq#1" "L3uB,UGʽ0k]l`R`hKҤ;uQױ20ok 9J5awN(2MR1[eiOzTDNSIA w[9v"ܑ:wSrZ5ɩrFpn:4i}HF}72S"q{slV۽K1@Oږ>B;$0R[ w`Iܫ$(y&KgQP>z&qٰc8W^^vU gV z1^Nfub~wL`;pQոÝB01"uKuٌbRv| CB<%8 Uȇ/_$Ge+L#3v>i7ܽkar#hJ{-s5nT!`;PDN`+XFB ko\荭ը Iå4s_Ic%IT$ .d;y$W'j_92G~dյ%n<_^A(ؙe+#ƦLS.h%iUYmݻُW bЙ>zV:$G%7D7̻I~A=@:_UBgPd[s62]⤵y$lHϙ=#{A[gpgz,7A o&ke7l:Zog-|0m5M+5̓[EhO:^]NǿYv!IKʳw-azRf+CA?Bڈ"͞ 8ZHBȠk(7bäw$t7J:5S+[iQxy8d%jo6˫^}x+r{r'8Xir%m.ڎFF-׹FP63 IL.M_pBbLӉ@3IT ;/~$R PI_`Csf;6ƍ4ki|qM ){|ymzyepˉ>sP +S>&'_QԠ@ܖ@_?R/q"]ɯ,GowƻRܚbf `څxa nq|W,h~F){+S߾W2{[֝5՜/-lm'$ CJ vϛFGh$)TM֮C]gЊ⹚?~҅Hʷû@<4 } N6JxjQęqҰcXcB}|㉝ZOoxюrԧ p$m&|wӐ 7e֡΂<>y>*qQu+|ڧ Q[Կ #8ux?A= }a&[i sv%;\^70/,K2S$@YZa{" c,'jzF] ~5qT.ً(SbP[FM?pi倁= R+B-ZDF[7PEA&*6}BL#)t {q&u$Z!w#;eœbD `j6zyӥT g[E=z@LQ> ӵ9u \Ǟ 0)YwR;JRnTʧѐ*`ض JԗSSS&AV{]nbq+EwCO)noݿ×w$ I&m@rD,s?jӣ>~A&k\bs [?B ]]lU}I8)]k=V.,V1Y)oj(~ @&WAO4IL|xmiٚɞֈfӱv+4 ʩ IM1[0oR"Xз:w;_K6ULyYA{Ц=}OtaH{k{޳M (ggȽ[?OFxH)3$F#lp%6^>䊥ܧhmW]/<4 [@`Z O_/di*)rqSKظN HPCb!R)zHY=G>]zUqeTjՏ=TP_n=Kk97k{^N>L+//}v!IT} E#0{s8Y`S-y$tl;u`0(sMBx '{*8VxxWۍ}j2ħ1y~ZU4}]G۶:[~x療8~ }R;fޔr1b8V~Dԧi˯NQvVP! vSII[wň3ڃ.{88;D7m[Iy,h40bɠaRV {)au9(!㼕qM33zljƁ ?~6L`H| 3h`3\/vE–݇d  SXEC!׋7=B4L2ԏ'Tj뼫[G`.[UgxFC@h$4Xve>W\0909([Hቼ>ǍignuezI츞ΒyBς$3I t}$L `%WN Jx|?ZiDӏb֋M"O:f$wlFn#t3fxKf[S虐ʽ3" \Qgtq}PXW4OFHh -7"Ch]i J-1OZhXyQ_GKX{R"$=/iBEN>AbH3]ns4(HOsQ oɠtof#_nGmNlT=!D"F胀\Df@OBWK.9]pDo:g3"*9#}MInFTϳE *{iHf $r߼]~y|}D$yYjZ H5YE3JQZLr@z_'C#I'jszo˪Fs6]c sA.cT ShhI :o4P1|Igrk~D~F4̀]gH(t;d"LԾtpP_yρ85DcgZN 1<%ӽٴ ᕩ7B :d2(/ u")诨&yO,,?L4ay8|`]I|T Wn,WnӢAul8.2ɲɩ/`^F x0rz[Ѳ\~\5j!hU"IᑡӖ}˿{ װ:$,i;d J"s7m{Bðz2X\zKSB|$ M*ƣ+`՘a{zWO]]ݣIG=Q,rYg[EkW^9ލvnÈ@ba%>>Hb$j-1ESEƚC_a-2%'V@cŨk1{V`TV5\]%v4I8ͷ)nUަӡk D9*Ohkz,7KQ(8.*Aq:qDl]Va/HAU8jkEbџgS:w3Firʖ)qAPc'VNy~UG8B(t)%D/p=R(l|31yxg\% B1^\ H[ xxJt7GWvMr #@&/ϥ51b?mpRK?02F|@wӤ.!9ΰ'wK  G0Ĥ߹Iv2Yp(G8 Ц*b7ajS'*<%/A:NJl_9ŷ4#A9k\^[=75S R "q.nwC꼦6RZHf\^/2HRd6SMIM5i K@$^OЊ (NJ{@YY޿x@͒r$|\ġQ/i7{T @$<XjxGNų*?LLvyNjZ=͘a0?2ȏ\CJȍ`!y? ɥg&<+)j+3A-cuÏF jHfd>2J.N~{hAF%_'!ٺtwTQ DDmW8ĤRN8ufG{91W|n_a6 JM|Y`am/C|1҈DS&{LK,ej7v¢z1~WJoW o厾{+ kTk]O~zQϗ|%p{4C2 LS`y*+r^U% \R8,Ӽ_U g|n'7T]ּ<fp`" ~z b /*VE03iByIu9 QV6OřBb& ;}Nx֋nI|鶤CKnٻWoyESZerTcgR&x=e/bU.4vjjCnJ C$%e}ߘQ_=auGT%^Y }"JTvmmX]tfLvf\}Tj'u8$M 2R$Ëdyn7߼!Q=%ǟt  ykr-6nwGZmoζF*m9aec\?wע:Tde."kbj՚>oʺ?$_#- 7,U$;,Qedl84ڗ tZSСx_i˧ꎎ8)ȕM]iaWXit-,ԥÊΚ;#\U|XS6 !\B\I3_ BT9j5~ZchUNÔN =-rĺLw"!p"ן`=F(te!XWy ~7k*YKJwޭnD^gA>Zej-`&n~Ph 5u3|kx>QztA( F_JZ}]`-}5VMpsLDŽX0ms+X#gvlUą].wVqQaΌq·j#;F&ЇS"AX5g=cP?elg;Tz1W=_ . *@\q?Qw4ȫTL^'V<pآ`LwV!p 8j=4T8ҚX*Xo݉'94UuQ 2niM m? JF93żkjsL~ޏ"LS+N+)v~`䛱xݥjӽp&vvǧ@q!S*f`6&y^]anVU5jGmNx["r1/inTHG(C&ۀ781rZ(A&, C9!jR)UuCz$=B"2Qgl'ӝj&J nō13 L fe&!z_rayPC&':5EߕF?Ġj(Ī.8qF`(!^4>SI CSW_տ%Ulso'1ӓ c~C90dAЈK;CtY~IkYտOa5͕nœmhW`hӞ\ئ{]9UD-!i-DS"xlBrȈp*oNZXThwu0!l2AbpRsfV{taG-v qԺp_I',ױU WR_kLT%)S Gt-;Zt I@GbVh-޵Sy'-\SzxٲzmW(c#}]6|I^$%YE`,pf'2|TyЋ~Y+wUoSh`2Nv#|V4qY݃3w3 ?aƩ#d%Ǭ*>~34%n ˌLגCAxC]…u xU>m87t{V)c*Irv6xЍ۾]vNbd@V bm'-gImН9o-ˑpDݺL$e*ت73|$I; Yk;mS0BW@4hˑDՐ'=fKowI6 t被bzԈ!T?vu&xq=;ŻG}99P v<<+J}>QV^]J/WfǾ+m~B(]4{d0$|x"Ë2>@\g>9K+Jj#+S_ rR8vKXѫx#ӣFHu+pan.`aJ$4<U=nG5c cUZB8#iuD=kqgՈV̚-<vu~eĦy Qw?tCqC?6?@wlxe*e=S0+qXAq.v34N ~v?JQ}Ի2aS#"Mkt}sH#2_ l~.;i#Gl> ䷶]Lf8KfXѧږH : sv}a-T'mNQ-M_}e7=Pwpcb@2jwGSҙtFZtvb\6]h{).H٫XW0VрGL^D#ITx92;pŵd{ae:}FXR #M1I/%KŌag={?uϬyqopq=6z(̳[~>3O܀rpԈ'*{"NC&2d\w*KdBm;)4$W٥4= ?~>nn/?/VwwtLtcl9.0.1/library/http/0000755000175000017500000000000014731057544014201 5ustar sergeisergeitcl9.0.1/library/http/http.tcl0000644000175000017500000054102414726623136015672 0ustar sergeisergei# http.tcl -- # # Client-side HTTP for GET, POST, and HEAD commands. These routines can # be used in untrusted code that uses the Safesock security policy. # These procedures use a callback interface to avoid using vwait, which # is not defined in the safe base. # # See the file "license.terms" for information on usage and redistribution of # this file, and for a DISCLAIMER OF ALL WARRANTIES. package require Tcl 8.6- # Keep this in sync with pkgIndex.tcl and with the install directories in # Makefiles package provide http 2.10.0 namespace eval http { # Allow resourcing to not clobber existing data variable http if {![info exists http]} { array set http { -accept */* -cookiejar {} -pipeline 1 -postfresh 0 -proxyhost {} -proxyport {} -proxyfilter http::ProxyRequired -proxynot {} -proxyauth {} -repost 0 -threadlevel 0 -urlencoding utf-8 -zip 1 } # We need a useragent string of this style or various servers will # refuse to send us compressed content even when we ask for it. This # follows the de-facto layout of user-agent strings in current browsers. # Safe interpreters do not have ::tcl_platform(os) or # ::tcl_platform(osVersion). if {[interp issafe]} { set http(-useragent) "Mozilla/5.0\ (Windows; U;\ Windows NT 10.0)\ http/[package provide http] Tcl/[package provide Tcl]" } else { set http(-useragent) "Mozilla/5.0\ ([string totitle $::tcl_platform(platform)]; U;\ $::tcl_platform(os) $::tcl_platform(osVersion))\ http/[package provide http] Tcl/[package provide Tcl]" } } proc init {} { # Set up the map for quoting chars. RFC3986 Section 2.3 say percent # encode all except: "... percent-encoded octets in the ranges of # ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), period # (%2E), underscore (%5F), or tilde (%7E) should not be created by URI # producers ..." for {set i 0} {$i <= 256} {incr i} { set c [format %c $i] if {![string match {[-._~a-zA-Z0-9]} $c]} { set map($c) %[format %.2X $i] } } # These are handled specially set map(\n) %0D%0A variable formMap [array get map] # Create a map for HTTP/1.1 open sockets variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId if {[info exists socketMapping]} { # Close open sockets on re-init. Do not permit retries. foreach {url sock} [array get socketMapping] { unset -nocomplain socketClosing($url) unset -nocomplain socketPlayCmd($url) CloseSocket $sock } } # CloseSocket should have unset the socket* arrays, one element at # a time. Now unset anything that was overlooked. # Traces on "unset socketRdState(*)" will call CancelReadPipeline and # cancel any queued responses. # Traces on "unset socketWrState(*)" will call CancelWritePipeline and # cancel any queued requests. array unset socketMapping array unset socketRdState array unset socketWrState array unset socketRdQueue array unset socketWrQueue array unset socketPhQueue array unset socketClosing array unset socketPlayCmd array unset socketCoEvent array unset socketProxyId array set socketMapping {} array set socketRdState {} array set socketWrState {} array set socketRdQueue {} array set socketWrQueue {} array set socketPhQueue {} array set socketClosing {} array set socketPlayCmd {} array set socketCoEvent {} array set socketProxyId {} return } init variable urlTypes if {![info exists urlTypes]} { set urlTypes(http) [list 80 ::http::AltSocket {} 1 0] } variable encodings [string tolower [encoding names]] # This can be changed, but iso8859-1 is the RFC standard. variable defaultCharset if {![info exists defaultCharset]} { set defaultCharset "iso8859-1" } # Force RFC 3986 strictness in geturl url verification? variable strict if {![info exists strict]} { set strict 1 } # Let user control default keepalive for compatibility variable defaultKeepalive if {![info exists defaultKeepalive]} { set defaultKeepalive 0 } # Regular expression used to parse cookies variable CookieRE {(?x) # EXPANDED SYNTAX \s* # Ignore leading spaces ([^][\u0000- ()<>@,;:\\""/?={}\u007f-\uffff]+) # Match the name = # LITERAL: Equal sign ([!\u0023-+\u002D-:<-\u005B\u005D-~]*) # Match the value (?: \s* ; \s* # LITERAL: semicolon ([^\u0000]+) # Match the options )? } variable TmpSockCounter 0 variable ThreadCounter 0 variable reasonDict [dict create {*}{ 100 Continue 101 {Switching Protocols} 102 Processing 103 {Early Hints} 200 OK 201 Created 202 Accepted 203 {Non-Authoritative Information} 204 {No Content} 205 {Reset Content} 206 {Partial Content} 207 Multi-Status 208 {Already Reported} 226 {IM Used} 300 {Multiple Choices} 301 {Moved Permanently} 302 Found 303 {See Other} 304 {Not Modified} 305 {Use Proxy} 306 (Unused) 307 {Temporary Redirect} 308 {Permanent Redirect} 400 {Bad Request} 401 Unauthorized 402 {Payment Required} 403 Forbidden 404 {Not Found} 405 {Method Not Allowed} 406 {Not Acceptable} 407 {Proxy Authentication Required} 408 {Request Timeout} 409 Conflict 410 Gone 411 {Length Required} 412 {Precondition Failed} 413 {Content Too Large} 414 {URI Too Long} 415 {Unsupported Media Type} 416 {Range Not Satisfiable} 417 {Expectation Failed} 418 (Unused) 421 {Misdirected Request} 422 {Unprocessable Content} 423 Locked 424 {Failed Dependency} 425 {Too Early} 426 {Upgrade Required} 428 {Precondition Required} 429 {Too Many Requests} 431 {Request Header Fields Too Large} 451 {Unavailable For Legal Reasons} 500 {Internal Server Error} 501 {Not Implemented} 502 {Bad Gateway} 503 {Service Unavailable} 504 {Gateway Timeout} 505 {HTTP Version Not Supported} 506 {Variant Also Negotiates} 507 {Insufficient Storage} 508 {Loop Detected} 510 {Not Extended (OBSOLETED)} 511 {Network Authentication Required} }] variable failedProxyValues { binary body charset coding connection connectionRespFlag currentsize host http httpResponse meta method querylength queryoffset reasonPhrase requestHeaders requestLine responseCode state status tid totalsize transfer type } namespace export geturl config reset wait formatQuery postError quoteString namespace export register unregister registerError namespace export requestLine requestHeaders requestHeaderValue namespace export responseLine responseHeaders responseHeaderValue namespace export responseCode responseBody responseInfo reasonPhrase # - Legacy aliases, were never exported: # data, code, mapReply, meta, ncode # - Callable from outside (e.g. from TLS) by fully-qualified name, but # not exported: # socket # - Useful, but never exported (and likely to have naming collisions): # size, status, cleanup, error, init # Comments suggest that "init" can be used for re-initialisation, # although the command is undocumented. # - Never exported, renamed from lower-case names: # GetTextLine, MakeTransformationChunked. } # http::Log -- # # Debugging output -- define this to observe HTTP/1.1 socket usage. # Should echo any args received. # # Arguments: # msg Message to output # if {[info command http::Log] eq {}} {proc http::Log {args} {}} # http::register -- # # See documentation for details. # # Arguments: # proto URL protocol prefix, e.g. https # port Default port for protocol # command Command to use to create socket # socketCmdVarName (optional) name of variable provided by the protocol # handler whose value is the callback used by argument # "command" to open a socket. The default value "::socket" # will be overwritten by http. # useSockThread (optional, boolean) # endToEndProxy (optional, boolean) # Results: # list of port, command, variable name, (boolean) threadability, # and (boolean) endToEndProxy that was registered. proc http::register {proto port command {socketCmdVarName {}} {useSockThread 0} {endToEndProxy 0}} { variable urlTypes set lower [string tolower $proto] if {[info exists urlTypes($lower)]} { unregister $lower } set urlTypes($lower) [list $port $command $socketCmdVarName $useSockThread $endToEndProxy] # If the external handler for protocol $proto has given $socketCmdVarName the expected # value "::socket", overwrite it with the new value. if {($socketCmdVarName ne {}) && ([set $socketCmdVarName] eq {::socket})} { set $socketCmdVarName ::http::socketAsCallback } return $urlTypes($lower) } # http::unregister -- # # Unregisters URL protocol handler # # Arguments: # proto URL protocol prefix, e.g. https # Results: # list of port, command, variable name, (boolean) useSockThread, # and (boolean) endToEndProxy that was unregistered. proc http::unregister {proto} { variable urlTypes set lower [string tolower $proto] if {![info exists urlTypes($lower)]} { return -code error "unsupported url type \"$proto\"" } set old $urlTypes($lower) # Restore the external handler's original value for $socketCmdVarName. lassign $old defport defcmd socketCmdVarName useSockThread endToEndProxy if {($socketCmdVarName ne {}) && ([set $socketCmdVarName] eq {::http::socketAsCallback})} { set $socketCmdVarName ::socket } unset urlTypes($lower) return $old } # http::config -- # # See documentation for details. # # Arguments: # args Options parsed by the procedure. # Results: # TODO proc http::config {args} { variable http set options [lsort [array names http -*]] set usage [join $options ", "] if {[llength $args] == 0} { set result {} foreach name $options { lappend result $name $http($name) } return $result } set options [string map {- ""} $options] set pat ^-(?:[join $options |])$ if {[llength $args] == 1} { set flag [lindex $args 0] if {![regexp -- $pat $flag]} { return -code error "Unknown option $flag, must be: $usage" } return $http($flag) } elseif {[llength $args] % 2} { return -code error "If more than one argument is supplied, the\ number of arguments must be even" } else { foreach {flag value} $args { if {![regexp -- $pat $flag]} { return -code error "Unknown option $flag, must be: $usage" } if {($flag eq {-threadlevel}) && ($value ni {0 1 2})} { return -code error {Option -threadlevel must be 0, 1 or 2} } set http($flag) $value } return } } # ------------------------------------------------------------------------------ # Proc http::reasonPhrase # ------------------------------------------------------------------------------ # Command to return the IANA-recommended "reason phrase" for a HTTP Status Code. # Information obtained from: # https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml # # Arguments: # code - A valid HTTP Status Code (integer from 100 to 599) # # Return Value: the reason phrase # ------------------------------------------------------------------------------ proc http::reasonPhrase {code} { variable reasonDict if {![regexp -- {^[1-5][0-9][0-9]$} $code]} { set msg {argument must be a three-digit integer from 100 to 599} return -code error $msg } if {[dict exists $reasonDict $code]} { set reason [dict get $reasonDict $code] } else { set reason Unassigned } return $reason } # http::Finish -- # # Clean up the socket and eval close time callbacks # # Arguments: # token Connection token. # errormsg (optional) If set, forces status to error. # skipCB (optional) If set, don't call the -command callback. This # is useful when geturl wants to throw an exception instead # of calling the callback. That way, the same error isn't # reported to two places. # # Side Effects: # May close the socket. proc http::Finish {token {errormsg ""} {skipCB 0}} { variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId variable $token upvar 0 $token state global errorInfo errorCode set closeQueue 0 if {$errormsg ne ""} { set state(error) [list $errormsg $errorInfo $errorCode] set state(status) "error" } if {[info commands ${token}--EventCoroutine] ne {}} { rename ${token}--EventCoroutine {} } if {[info commands ${token}--SocketCoroutine] ne {}} { rename ${token}--SocketCoroutine {} } if {[info exists state(socketcoro)]} { Log $token Cancel socket after-idle event (Finish) after cancel $state(socketcoro) unset state(socketcoro) } # Is this an upgrade request/response? set upgradeResponse \ [expr { [info exists state(upgradeRequest)] && $state(upgradeRequest) && [info exists state(http)] && ([ncode $token] eq {101}) && [info exists state(connection)] && ("upgrade" in $state(connection)) && [info exists state(upgrade)] && ("" ne $state(upgrade)) }] if { ($state(status) eq "timeout") || ($state(status) eq "error") || ($state(status) eq "eof") } { set closeQueue 1 set connId $state(socketinfo) if {[info exists state(sock)]} { set sock $state(sock) CloseSocket $state(sock) $token } else { # When opening the socket and calling http::reset # immediately, the socket may not yet exist. # Test http-4.11 may come here. } if {$state(tid) ne {}} { # When opening the socket in a thread, and calling http::reset # immediately, the thread may still exist. # Test http-4.11 may come here. thread::release $state(tid) set state(tid) {} } else { } } elseif {$upgradeResponse} { # Special handling for an upgrade request/response. # - geturl ensures that this is not a "persistent" socket used for # multiple HTTP requests, so a call to KeepSocket is not needed. # - Leave socket open, so a call to CloseSocket is not needed either. # - Remove fileevent bindings. The caller will set its own bindings. # - THE CALLER MUST PROCESS THE UPGRADED SOCKET IN THE CALLBACK COMMAND # PASSED TO http::geturl AS -command callback. catch {fileevent $state(sock) readable {}} catch {fileevent $state(sock) writable {}} } elseif {([info exists state(-keepalive)] && !$state(-keepalive)) || ([info exists state(connection)] && ("close" in $state(connection))) } { set closeQueue 1 set connId $state(socketinfo) if {[info exists state(sock)]} { set sock $state(sock) CloseSocket $state(sock) $token } else { # When opening the socket and calling http::reset # immediately, the socket may not yet exist. # Test http-4.11 may come here. } } elseif { ([info exists state(-keepalive)] && $state(-keepalive)) && ([info exists state(connection)] && ("close" ni $state(connection))) } { KeepSocket $token } if {[info exists state(after)]} { after cancel $state(after) unset state(after) } if {[info exists state(-command)] && (!$skipCB) && (![info exists state(done-command-cb)])} { set state(done-command-cb) yes if { [catch {namespace eval :: $state(-command) $token} err] && ($errormsg eq "") } { set state(error) [list $err $errorInfo $errorCode] set state(status) error } } if { $closeQueue && [info exists socketMapping($connId)] && ($socketMapping($connId) eq $sock) } { http::CloseQueuedQueries $connId $token # This calls Unset. Other cases do not need the call. } return } # http::KeepSocket - # # Keep a socket in the persistent sockets table and connect it to its next # queued task if possible. Otherwise leave it idle and ready for its next # use. # # If $socketClosing(*), then ("close" in $state(connection)) and therefore # this command will not be called by Finish. # # Arguments: # token Connection token. proc http::KeepSocket {token} { variable http variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId variable $token upvar 0 $token state set tk [namespace tail $token] # Keep this socket open for another request ("Keep-Alive"). # React if the server half-closes the socket. # Discussion is in http::geturl. catch {fileevent $state(sock) readable [list http::CheckEof $state(sock)]} # The line below should not be changed in production code. # It is edited by the test suite. set TEST_EOF 0 if {$TEST_EOF} { # ONLY for testing reaction to server eof. # No server timeouts will be caught. catch {fileevent $state(sock) readable {}} } if { [info exists state(socketinfo)] && [info exists socketMapping($state(socketinfo))] } { set connId $state(socketinfo) # The value "Rready" is set only here. set socketRdState($connId) Rready if { $state(-pipeline) && [info exists socketRdQueue($connId)] && [llength $socketRdQueue($connId)] } { # The usual case for pipelined responses - if another response is # queued, arrange to read it. set token3 [lindex $socketRdQueue($connId) 0] set socketRdQueue($connId) [lrange $socketRdQueue($connId) 1 end] #Log pipelined, GRANT read access to $token3 in KeepSocket set socketRdState($connId) $token3 ReceiveResponse $token3 # Other pipelined cases. # - The test above ensures that, for the pipelined cases in the two # tests below, the read queue is empty. # - In those two tests, check whether the next write will be # nonpipeline. } elseif { $state(-pipeline) && [info exists socketWrState($connId)] && ($socketWrState($connId) eq "peNding") && [info exists socketWrQueue($connId)] && [llength $socketWrQueue($connId)] && (![set token3 [lindex $socketWrQueue($connId) 0] set ${token3}(-pipeline) ] ) } { # This case: # - Now it the time to run the "pending" request. # - The next token in the write queue is nonpipeline, and # socketWrState has been marked "pending" (in # http::NextPipelinedWrite or http::geturl) so a new pipelined # request cannot jump the queue. # # Tests: # - In this case the read queue (tested above) is empty and this # "pending" write token is in front of the rest of the write # queue. # - The write state is not Wready and therefore appears to be busy, # but because it is "pending" we know that it is reserved for the # first item in the write queue, a non-pipelined request that is # waiting for the read queue to empty. That has now happened: so # give that request read and write access. set conn [set ${token3}(connArgs)] #Log nonpipeline, GRANT r/w access to $token3 in KeepSocket set socketRdState($connId) $token3 set socketWrState($connId) $token3 set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end] # Connect does its own fconfigure. fileevent $state(sock) writable [list http::Connect $token3 {*}$conn] #Log ---- $state(sock) << conn to $token3 for HTTP request (c) } elseif { $state(-pipeline) && [info exists socketWrState($connId)] && ($socketWrState($connId) eq "peNding") } { # Should not come here. The second block in the previous "elseif" # test should be tautologous (but was needed in an earlier # implementation) and will be removed after testing. # If we get here, the value "pending" was assigned in error. # This error would block the queue for ever. Log ^X$tk <<<<< Error in queueing of requests >>>>> - token $token } elseif { $state(-pipeline) && [info exists socketWrState($connId)] && ($socketWrState($connId) eq "Wready") && [info exists socketWrQueue($connId)] && [llength $socketWrQueue($connId)] && (![set token3 [lindex $socketWrQueue($connId) 0] set ${token3}(-pipeline) ] ) } { # This case: # - The next token in the write queue is nonpipeline, and # socketWrState is Wready. Get the next event from socketWrQueue. # Tests: # - In this case the read state (tested above) is Rready and the # write state (tested here) is Wready - there is no "pending" # request. # Code: # - The code is the same as the code below for the nonpipelined # case with a queued request. set conn [set ${token3}(connArgs)] #Log nonpipeline, GRANT r/w access to $token3 in KeepSocket set socketRdState($connId) $token3 set socketWrState($connId) $token3 set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end] # Connect does its own fconfigure. fileevent $state(sock) writable [list http::Connect $token3 {*}$conn] #Log ---- $state(sock) << conn to $token3 for HTTP request (c) } elseif { (!$state(-pipeline)) && [info exists socketWrQueue($connId)] && [llength $socketWrQueue($connId)] && ("close" ni $state(connection)) } { # If not pipelined, (socketRdState eq Rready) tells us that we are # ready for the next write - there is no need to check # socketWrState. Write the next request, if one is waiting. # If the next request is pipelined, it receives premature read # access to the socket. This is not a problem. set token3 [lindex $socketWrQueue($connId) 0] set conn [set ${token3}(connArgs)] #Log nonpipeline, GRANT r/w access to $token3 in KeepSocket set socketRdState($connId) $token3 set socketWrState($connId) $token3 set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end] # Connect does its own fconfigure. fileevent $state(sock) writable [list http::Connect $token3 {*}$conn] #Log ---- $state(sock) << conn to $token3 for HTTP request (d) } elseif {(!$state(-pipeline))} { set socketWrState($connId) Wready # Rready and Wready and idle: nothing to do. } } else { CloseSocket $state(sock) $token # There is no socketMapping($state(socketinfo)), so it does not matter # that CloseQueuedQueries is not called. } return } # http::CheckEof - # # Read from a socket and close it if eof. # The command is bound to "fileevent readable" on an idle socket, and # "eof" is the only event that should trigger the binding, occurring when # the server times out and half-closes the socket. # # A read is necessary so that [eof] gives a meaningful result. # Any bytes sent are junk (or a bug). proc http::CheckEof {sock} { set junk [read $sock] set n [string length $junk] if {$n} { Log "WARNING: $n bytes received but no HTTP request sent" } if {[catch {eof $sock} res] || $res} { # The server has half-closed the socket. # If a new write has started, its transaction will fail and # will then be error-handled. CloseSocket $sock } return } # http::CloseSocket - # # Close a socket and remove it from the persistent sockets table. If # possible an http token is included here but when we are called from a # fileevent on remote closure we need to find the correct entry - hence # the "else" block of the first "if" command. proc http::CloseSocket {s {token {}}} { variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId set tk [namespace tail $token] catch {fileevent $s readable {}} set connId {} if {$token ne ""} { variable $token upvar 0 $token state if {[info exists state(socketinfo)]} { set connId $state(socketinfo) } } else { set map [array get socketMapping] set ndx [lsearch -exact $map $s] if {$ndx >= 0} { incr ndx -1 set connId [lindex $map $ndx] } } if { ($connId ne {}) && [info exists socketMapping($connId)] && ($socketMapping($connId) eq $s) } { Log "Closing connection $connId (sock $socketMapping($connId))" if {[catch {close $socketMapping($connId)} err]} { Log "Error closing connection: $err" } else { } if {$token eq {}} { # Cases with a non-empty token are handled by Finish, so the tokens # are finished in connection order. http::CloseQueuedQueries $connId } else { } } else { Log "Closing socket $s (no connection info)" if {[catch {close $s} err]} { Log "Error closing socket: $err" } else { } } return } # http::CloseQueuedQueries # # connId - identifier "domain:port" for the connection # token - (optional) used only for logging # # Called from http::CloseSocket and http::Finish, after a connection is closed, # to clear the read and write queues if this has not already been done. proc http::CloseQueuedQueries {connId {token {}}} { variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId ##Log CloseQueuedQueries $connId $token if {![info exists socketMapping($connId)]} { # Command has already been called. # Don't come here again - especially recursively. return } # Used only for logging. if {$token eq {}} { set tk {} } else { set tk [namespace tail $token] } if { [info exists socketPlayCmd($connId)] && ($socketPlayCmd($connId) ne {ReplayIfClose Wready {} {}}) } { # Before unsetting, there is some unfinished business. # - If the server sent "Connection: close", we have stored the command # for retrying any queued requests in socketPlayCmd, so copy that # value for execution below. socketClosing(*) was also set. # - Also clear the queues to prevent calls to Finish that would set the # state for the requests that will be retried to "finished with error # status". # - At this stage socketPhQueue is empty. set unfinished $socketPlayCmd($connId) set socketRdQueue($connId) {} set socketWrQueue($connId) {} } else { set unfinished {} } Unset $connId if {$unfinished ne {}} { Log ^R$tk Any unfinished transactions (excluding $token) failed \ - token $token - unfinished $unfinished {*}$unfinished # Calls ReplayIfClose. } return } # http::Unset # # The trace on "unset socketRdState(*)" will call CancelReadPipeline # and cancel any queued responses. # The trace on "unset socketWrState(*)" will call CancelWritePipeline # and cancel any queued requests. proc http::Unset {connId} { variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId unset socketMapping($connId) unset socketRdState($connId) unset socketWrState($connId) unset -nocomplain socketRdQueue($connId) unset -nocomplain socketWrQueue($connId) unset -nocomplain socketClosing($connId) unset -nocomplain socketPlayCmd($connId) unset -nocomplain socketProxyId($connId) return } # http::reset -- # # See documentation for details. # # Arguments: # token Connection token. # why Status info. # # Side Effects: # See Finish proc http::reset {token {why reset}} { variable $token upvar 0 $token state set state(status) $why catch {fileevent $state(sock) readable {}} catch {fileevent $state(sock) writable {}} Finish $token if {[info exists state(error)]} { set errorlist $state(error) unset state eval ::error $errorlist # i.e. error msg errorInfo errorCode } return } # http::geturl -- # # Establishes a connection to a remote url via http. # # Arguments: # url The http URL to goget. # args Option value pairs. Valid options include: # -blocksize, -validate, -headers, -timeout # Results: # Returns a token for this connection. This token is the name of an # array that the caller should unset to garbage collect the state. proc http::geturl {url args} { variable urlTypes # - If ::tls::socketCmd has its default value "::socket", change it to the # new value ::http::socketAsCallback. # - If the old value is different, then it has been modified either by the # script or by the Tcl installation, and replaced by a new command. The # script or installation that modified ::tls::socketCmd is also # responsible for integrating ::http::socketAsCallback into its own "new" # command, if it wishes to do so. # - Commands that open a socket: # - ::socket - basic # - ::http::AltSocket - can use a thread to avoid blockage by slow # DNS lookup. See http::config option # -threadlevel. # - ::http::socketAsCallback - as ::http::AltSocket, but can also open a # socket for HTTPS/TLS through a proxy. set token [CreateToken $url {*}$args] variable $token upvar 0 $token state AsyncTransaction $token # -------------------------------------------------------------------------- # Synchronous Call to http::geturl # -------------------------------------------------------------------------- # - If the call to http::geturl is asynchronous, it is now complete (apart # from delivering the return value). # - If the call to http::geturl is synchronous, the command must now wait # for the HTTP transaction to be completed. The call to http::wait uses # vwait, which may be inappropriate if the caller makes other HTTP # requests in the background. # -------------------------------------------------------------------------- if {![info exists state(-command)]} { # geturl does EVERYTHING asynchronously, so if the user # calls it synchronously, we just do a wait here. http::wait $token if {![info exists state]} { # If we timed out then Finish has been called and the users # command callback may have cleaned up the token. If so we end up # here with nothing left to do. return $token } elseif {$state(status) eq "error"} { # Something went wrong while trying to establish the connection. # Clean up after events and such, but DON'T call the command # callback (if available) because we're going to throw an # exception from here instead. set err [lindex $state(error) 0] cleanup $token return -code error $err } } return $token } # ------------------------------------------------------------------------------ # Proc http::CreateToken # ------------------------------------------------------------------------------ # Command to convert arguments into an initialised request token. # The return value is the variable name of the token. # # Other effects: # - Sets ::http::http(usingThread) if not already done # - Sets ::http::http(uid) if not already done # - Increments ::http::http(uid) # - May increment ::http::TmpSockCounter # - Alters ::http::socketPlayCmd, ::http::socketWrQueue if a -keepalive 1 # request is appended to the queue of a persistent socket that is already # scheduled to close. # This also sets state(alreadyQueued) to 1. # - Alters ::http::socketPhQueue if a -keepalive 1 request is appended to the # queue of a persistent socket that has not yet been created (and is therefore # represented by a placeholder). # This also sets state(ReusingPlaceholder) to 1. # ------------------------------------------------------------------------------ proc http::CreateToken {url args} { variable http variable urlTypes variable defaultCharset variable defaultKeepalive variable strict variable TmpSockCounter # Initialize the state variable, an array. We'll return the name of this # array as the token for the transaction. if {![info exists http(usingThread)]} { set http(usingThread) 0 } if {![info exists http(uid)]} { set http(uid) 0 } set token [namespace current]::[incr http(uid)] ##Log Starting http::geturl - token $token variable $token upvar 0 $token state set tk [namespace tail $token] reset $token Log ^A$tk URL $url - token $token # Process command options. array set state { -binary false -blocksize 8192 -queryblocksize 8192 -validate 0 -headers {} -timeout 0 -type application/x-www-form-urlencoded -queryprogress {} -protocol 1.1 -guesstype 0 binary 0 state created meta {} method {} coding {} currentsize 0 totalsize 0 querylength 0 queryoffset 0 type application/octet-stream body {} status "" http "" httpResponse {} responseCode {} reasonPhrase {} connection keep-alive tid {} requestHeaders {} requestLine {} transfer {} proxyUsed none protoSockThread 0 protoProxyConn 0 } set state(-keepalive) $defaultKeepalive set state(-strict) $strict # These flags have their types verified [Bug 811170] array set type { -binary boolean -blocksize integer -guesstype boolean -queryblocksize integer -strict boolean -timeout integer -validate boolean -headers list } set state(charset) $defaultCharset set options { -binary -blocksize -channel -command -guesstype -handler -headers -keepalive -method -myaddr -progress -protocol -query -queryblocksize -querychannel -queryprogress -strict -timeout -type -validate } set usage [join [lsort $options] ", "] set options [string map {- ""} $options] set pat ^-(?:[join $options |])$ foreach {flag value} $args { if {[regexp -- $pat $flag]} { # Validate numbers if { [info exists type($flag)] && (![string is $type($flag) -strict $value]) } { unset $token return -code error \ "Bad value for $flag ($value), must be $type($flag)" } if {($flag eq "-headers") && ([llength $value] % 2 != 0)} { unset $token return -code error "Bad value for $flag ($value), number\ of list elements must be even" } set state($flag) $value } else { unset $token return -code error "Unknown option $flag, can be: $usage" } } # Make sure -query and -querychannel aren't both specified set isQueryChannel [info exists state(-querychannel)] set isQuery [info exists state(-query)] if {$isQuery && $isQueryChannel} { unset $token return -code error "Can't combine -query and -querychannel options!" } # Validate URL, determine the server host and port, and check proxy case # Recognize user:pass@host URLs also, although we do not do anything with # that info yet. # URLs have basically four parts. # First, before the colon, is the protocol scheme (e.g. http) # Second, for HTTP-like protocols, is the authority # The authority is preceded by // and lasts up to (but not including) # the following / or ? and it identifies up to four parts, of which # only one, the host, is required (if an authority is present at all). # All other parts of the authority (user name, password, port number) # are optional. # Third is the resource name, which is split into two parts at a ? # The first part (from the single "/" up to "?") is the path, and the # second part (from that "?" up to "#") is the query. *HOWEVER*, we do # not need to separate them; we send the whole lot to the server. # Both, path and query are allowed to be missing, including their # delimiting character. # Fourth is the fragment identifier, which is everything after the first # "#" in the URL. The fragment identifier MUST NOT be sent to the server # and indeed, we don't bother to validate it (it could be an error to # pass it in here, but it's cheap to strip). # # An example of a URL that has all the parts: # # http://jschmoe:xyzzy@www.bogus.net:8000/foo/bar.tml?q=foo#changes # # The "http" is the protocol, the user is "jschmoe", the password is # "xyzzy", the host is "www.bogus.net", the port is "8000", the path is # "/foo/bar.tml", the query is "q=foo", and the fragment is "changes". # # Note that the RE actually combines the user and password parts, as # recommended in RFC 3986. Indeed, that RFC states that putting passwords # in URLs is a Really Bad Idea, something with which I would agree utterly. # RFC 9110 Sec 4.2.4 goes further than this, and deprecates the format # "user:password@". It is retained here for backward compatibility, # but its use is not recommended. # # From a validation perspective, we need to ensure that the parts of the # URL that are going to the server are correctly encoded. This is only # done if $state(-strict) is true (inherited from $::http::strict). set URLmatcher {(?x) # this is _expanded_ syntax ^ (?: (\w+) : ) ? # (?: // (?: ( [^@/\#?]+ # ) @ )? ( # [^/:\#?]+ | # host name or IPv4 address \[ [^/\#?]+ \] # IPv6 address in square brackets ) (?: : (\d+) )? # )? ( [/\?] [^\#]*)? # (including query) (?: \# (.*) )? # $ } # Phase one: parse if {![regexp -- $URLmatcher $url -> proto user host port srvurl]} { unset $token return -code error "Unsupported URL: $url" } # Phase two: validate set host [string trim $host {[]}]; # strip square brackets from IPv6 address if {$host eq ""} { # Caller has to provide a host name; we do not have a "default host" # that would enable us to handle relative URLs. unset $token return -code error "Missing host part: $url" # Note that we don't check the hostname for validity here; if it's # invalid, we'll simply fail to resolve it later on. } if {$port ne "" && $port > 65535} { unset $token return -code error "Invalid port number: $port" } # The user identification and resource identification parts of the URL can # have encoded characters in them; take care! if {$user ne ""} { # Check for validity according to RFC 3986, Appendix A set validityRE {(?xi) ^ (?: [-\w.~!$&'()*+,;=:] | %[0-9a-f][0-9a-f] )+ $ } if {$state(-strict) && ![regexp -- $validityRE $user]} { unset $token # Provide a better error message in this error case if {[regexp {(?i)%(?![0-9a-f][0-9a-f]).?.?} $user bad]} { return -code error \ "Illegal encoding character usage \"$bad\" in URL user" } return -code error "Illegal characters in URL user" } } if {$srvurl ne ""} { # RFC 3986 allows empty paths (not even a /), but servers # return 400 if the path in the HTTP request doesn't start # with / , so add it here if needed. if {[string index $srvurl 0] ne "/"} { set srvurl /$srvurl } # Check for validity according to RFC 3986, Appendix A set validityRE {(?xi) ^ # Path part (already must start with / character) (?: [-\w.~!$&'()*+,;=:@/] | %[0-9a-f][0-9a-f] )* # Query part (optional, permits ? characters) (?: \? (?: [-\w.~!$&'()*+,;=:@/?] | %[0-9a-f][0-9a-f] )* )? $ } if {$state(-strict) && ![regexp -- $validityRE $srvurl]} { unset $token # Provide a better error message in this error case if {[regexp {(?i)%(?![0-9a-f][0-9a-f])..} $srvurl bad]} { return -code error \ "Illegal encoding character usage \"$bad\" in URL path" } return -code error "Illegal characters in URL path" } if {![regexp {^[^?#]+} $srvurl state(path)]} { set state(path) / } } else { set srvurl / set state(path) / } if {$proto eq ""} { set proto http } set lower [string tolower $proto] if {![info exists urlTypes($lower)]} { unset $token return -code error "Unsupported URL type \"$proto\"" } lassign $urlTypes($lower) defport defcmd socketCmdVarName useSockThread end2EndProxy # If the external handler for protocol $proto has given $socketCmdVarName the expected # value "::socket", overwrite it with the new value. if {($socketCmdVarName ne {}) && ([set $socketCmdVarName] eq {::socket})} { set $socketCmdVarName ::http::socketAsCallback } set state(protoSockThread) $useSockThread set state(protoProxyConn) $end2EndProxy if {$port eq ""} { set port $defport } if {![catch {$http(-proxyfilter) $host} proxy]} { set phost [lindex $proxy 0] set pport [lindex $proxy 1] } else { set phost {} set pport {} } # OK, now reassemble into a full URL set url ${proto}:// if {$user ne ""} { append url $user append url @ } append url $host if {$port != $defport} { append url : $port } append url $srvurl # Don't append the fragment! RFC 7230 Sec 5.1 set state(url) $url # Proxy connections aren't shared among different hosts. set state(socketinfo) $host:$port # Save the accept types at this point to prevent a race condition. [Bug # c11a51c482] set state(accept-types) $http(-accept) # Check whether this is an Upgrade request. set connectionValues [SplitCommaSeparatedFieldValue \ [GetFieldValue $state(-headers) Connection]] set connectionValues [string tolower $connectionValues] set upgradeValues [SplitCommaSeparatedFieldValue \ [GetFieldValue $state(-headers) Upgrade]] set state(upgradeRequest) [expr { "upgrade" in $connectionValues && [llength $upgradeValues] >= 1}] set state(connectionValues) $connectionValues if {$isQuery || $isQueryChannel} { # It's a POST. # A client wishing to send a non-idempotent request SHOULD wait to send # that request until it has received the response status for the # previous request. if {$http(-postfresh)} { # Override -keepalive for a POST. Use a new connection, and thus # avoid the small risk of a race against server timeout. set state(-keepalive) 0 } else { # Allow -keepalive but do not -pipeline - wait for the previous # transaction to finish. # There is a small risk of a race against server timeout. set state(-pipeline) 0 } } elseif {$state(upgradeRequest)} { # It's an upgrade request. Method must be GET (untested). # Force -keepalive to 0 so the connection is not made over a persistent # socket, i.e. one used for multiple HTTP requests. set state(-keepalive) 0 } else { # It's a non-upgrade GET or HEAD. set state(-pipeline) $http(-pipeline) } # We cannot handle chunked encodings with -handler, so force HTTP/1.0 # until we can manage this. if {[info exists state(-handler)]} { set state(-protocol) 1.0 } # RFC 7320 A.1 - HTTP/1.0 Keep-Alive is problematic. We do not support it. if {$state(-protocol) eq "1.0"} { set state(connection) close set state(-keepalive) 0 } # Handle proxy requests here for http:// but not for https:// # The proxying for https is done in the ::http::socketAsCallback command. # A proxy request for http:// needs the full URL in the HTTP request line, # including the server name. # The *tls* test below attempts to describe protocols in addition to # "https on port 443" that use HTTP over TLS. if {($phost ne "") && (!$end2EndProxy)} { set srvurl $url set targetAddr [list $phost $pport] set state(proxyUsed) HttpProxy # The value of state(proxyUsed) none|HttpProxy depends only on the # all-transactions http::config settings and on the target URL. # Even if this is a persistent socket there is no need to change the # value of state(proxyUsed) for other transactions that use the socket: # they have the same value already. } else { set targetAddr [list $host $port] } set sockopts [list -async] # Pass -myaddr directly to the socket command if {[info exists state(-myaddr)]} { lappend sockopts -myaddr $state(-myaddr) } if {$useSockThread} { set targs [list -type $token] } else { set targs {} } set state(connArgs) [list $proto $phost $srvurl] set state(openCmd) [list {*}$defcmd {*}$sockopts {*}$targs {*}$targetAddr] # See if we are supposed to use a previously opened channel. # - In principle, ANY call to http::geturl could use a previously opened # channel if it is available - the "Connection: keep-alive" header is a # request to leave the channel open AFTER completion of this call. # - In fact, we try to use an existing channel only if -keepalive 1 -- this # means that at most one channel is left open for each value of # $state(socketinfo). This property simplifies the mapping of open # channels. set reusing 0 set state(alreadyQueued) 0 set state(ReusingPlaceholder) 0 if {$state(-keepalive)} { variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId if {[info exists socketMapping($state(socketinfo))]} { # - If the connection is idle, it has a "fileevent readable" binding # to http::CheckEof, in case the server times out and half-closes # the socket (http::CheckEof closes the other half). # - We leave this binding in place until just before the last # puts+flush in http::Connected (GET/HEAD) or http::Write (POST), # after which the HTTP response might be generated. if { [info exists socketClosing($state(socketinfo))] && $socketClosing($state(socketinfo)) } { # socketClosing(*) is set because the server has sent a # "Connection: close" header. # Do not use the persistent socket again. # Since we have only one persistent socket per server, and the # old socket is not yet dead, add the request to the write queue # of the dying socket, which will be replayed by ReplayIfClose. # Also add it to socketWrQueue(*) which is used only if an error # causes a call to Finish. set reusing 1 set sock $socketMapping($state(socketinfo)) set state(proxyUsed) $socketProxyId($state(socketinfo)) Log "reusing closing socket $sock for $state(socketinfo) - token $token" set state(alreadyQueued) 1 lassign $socketPlayCmd($state(socketinfo)) com0 com1 com2 com3 lappend com3 $token set socketPlayCmd($state(socketinfo)) [list $com0 $com1 $com2 $com3] lappend socketWrQueue($state(socketinfo)) $token ##Log socketPlayCmd($state(socketinfo)) is $socketPlayCmd($state(socketinfo)) ##Log socketWrQueue($state(socketinfo)) is $socketWrQueue($state(socketinfo)) } elseif { [catch {fconfigure $socketMapping($state(socketinfo))}] && (![SockIsPlaceHolder $socketMapping($state(socketinfo))]) } { ###Log "Socket $socketMapping($state(socketinfo)) for $state(socketinfo)" # FIXME Is it still possible for this code to be executed? If # so, this could be another place to call TestForReplay, # rather than discarding the queued transactions. Log "WARNING: socket for $state(socketinfo) was closed\ - token $token" Log "WARNING - if testing, pay special attention to this\ case (GH) which is seldom executed - token $token" # This will call CancelReadPipeline, CancelWritePipeline, and # cancel any queued requests, responses. Unset $state(socketinfo) } else { # Use the persistent socket. # - The socket may not be ready to write: an earlier request might # still be still writing (in the pipelined case) or # writing/reading (in the nonpipeline case). This possibility # is handled by socketWrQueue later in this command. # - The socket may not yet exist, and be defined with a placeholder. set reusing 1 set sock $socketMapping($state(socketinfo)) set state(proxyUsed) $socketProxyId($state(socketinfo)) if {[SockIsPlaceHolder $sock]} { set state(ReusingPlaceholder) 1 lappend socketPhQueue($sock) $token } else { } Log "reusing open socket $sock for $state(socketinfo) - token $token" } # Do not automatically close the connection socket. set state(connection) keep-alive } } set state(reusing) $reusing unset reusing if {![info exists sock]} { # N.B. At this point ([info exists sock] == $state(reusing)). # This will no longer be true after we set a value of sock here. # Give the socket a placeholder name. set sock HTTP_PLACEHOLDER_[incr TmpSockCounter] } set state(sock) $sock if {$state(reusing)} { # Define these for use (only) by http::ReplayIfDead if the persistent # connection has died. set state(tmpConnArgs) $state(connArgs) set state(tmpState) [array get state] set state(tmpOpenCmd) $state(openCmd) } return $token } # ------------------------------------------------------------------------------ # Proc ::http::SockIsPlaceHolder # ------------------------------------------------------------------------------ # Command to return 0 if the argument is a genuine socket handle, or 1 if is a # placeholder value generated by geturl or ReplayCore before the real socket is # created. # # Arguments: # sock - either a valid socket handle or a placeholder value # # Return Value: 0 or 1 # ------------------------------------------------------------------------------ proc http::SockIsPlaceHolder {sock} { expr {[string range $sock 0 16] eq {HTTP_PLACEHOLDER_}} } # ------------------------------------------------------------------------------ # state(reusing) # ------------------------------------------------------------------------------ # - state(reusing) is set by geturl, ReplayCore # - state(reusing) is used by geturl, AsyncTransaction, OpenSocket, # ConfigureNewSocket, and ScheduleRequest when creating and configuring the # connection. # - state(reusing) is used by Connect, Connected, Event x 2 when deciding # whether to call TestForReplay. # - Other places where state(reusing) is used: # - Connected - if reusing and not pipelined, start the state(-timeout) # timeout (when writing). # - DoneRequest - if reusing and pipelined, send the next pipelined write # - Event - if reusing and pipelined, start the state(-timeout) # timeout (when reading). # - Event - if (not reusing) and pipelined, send the next pipelined # write. # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Proc http::AsyncTransaction # ------------------------------------------------------------------------------ # This command is called by geturl and ReplayCore to prepare the HTTP # transaction prescribed by a suitably prepared token. # # Arguments: # token - connection token (name of an array) # # Return Value: none # ------------------------------------------------------------------------------ proc http::AsyncTransaction {token} { variable $token upvar 0 $token state set tk [namespace tail $token] variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId set sock $state(sock) # See comments above re the start of this timeout in other cases. if {(!$state(reusing)) && ($state(-timeout) > 0)} { set state(after) [after $state(-timeout) \ [list http::reset $token timeout]] } if { $state(-keepalive) && (![info exists socketMapping($state(socketinfo))]) } { # This code is executed only for the first -keepalive request on a # socket. It makes the socket persistent. ##Log " PreparePersistentConnection" $token -- $sock -- DO set DoLater [PreparePersistentConnection $token] } else { ##Log " PreparePersistentConnection" $token -- $sock -- SKIP set DoLater {-traceread 0 -tracewrite 0} } if {$state(ReusingPlaceholder)} { # - This request was added to the socketPhQueue of a persistent # connection. # - But the connection has not yet been created and is a placeholder; # - And the placeholder was created by an earlier request. # - When that earlier request calls OpenSocket, its placeholder is # replaced with a true socket, and it then executes the equivalent of # OpenSocket for any subsequent requests that have # $state(ReusingPlaceholder). Log >J$tk after idle coro NO - ReusingPlaceholder } elseif {$state(alreadyQueued)} { # - This request was added to the socketWrQueue and socketPlayCmd # of a persistent connection that will close at the end of its current # read operation. Log >J$tk after idle coro NO - alreadyQueued } else { Log >J$tk after idle coro YES set CoroName ${token}--SocketCoroutine set cancel [after idle [list coroutine $CoroName ::http::OpenSocket \ $token $DoLater]] dict set socketCoEvent($state(socketinfo)) $token $cancel set state(socketcoro) $cancel } return } # ------------------------------------------------------------------------------ # Proc http::PreparePersistentConnection # ------------------------------------------------------------------------------ # This command is called by AsyncTransaction to initialise a "persistent # connection" based upon a socket placeholder. It is called the first time the # socket is associated with a "-keepalive" request. # # Arguments: # token - connection token (name of an array) # # Return Value: - DoLater, a dictionary of boolean values listing unfinished # tasks; to be passed to ConfigureNewSocket via OpenSocket. # ------------------------------------------------------------------------------ proc http::PreparePersistentConnection {token} { variable $token upvar 0 $token state variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId set DoLater {-traceread 0 -tracewrite 0} set socketMapping($state(socketinfo)) $state(sock) set socketProxyId($state(socketinfo)) $state(proxyUsed) # - The value of state(proxyUsed) was set in http::CreateToken to either # "none" or "HttpProxy". # - $token is the first transaction to use this placeholder, so there are # no other tokens whose (proxyUsed) must be modified. if {![info exists socketRdState($state(socketinfo))]} { set socketRdState($state(socketinfo)) {} # set varName ::http::socketRdState($state(socketinfo)) # trace add variable $varName unset ::http::CancelReadPipeline dict set DoLater -traceread 1 } if {![info exists socketWrState($state(socketinfo))]} { set socketWrState($state(socketinfo)) {} # set varName ::http::socketWrState($state(socketinfo)) # trace add variable $varName unset ::http::CancelWritePipeline dict set DoLater -tracewrite 1 } if {$state(-pipeline)} { #Log new, init for pipelined, GRANT write access to $token in geturl # Also grant premature read access to the socket. This is OK. set socketRdState($state(socketinfo)) $token set socketWrState($state(socketinfo)) $token } else { # socketWrState is not used by this non-pipelined transaction. # We cannot leave it as "Wready" because the next call to # http::geturl with a pipelined transaction would conclude that the # socket is available for writing. #Log new, init for nonpipeline, GRANT r/w access to $token in geturl set socketRdState($state(socketinfo)) $token set socketWrState($state(socketinfo)) $token } # Value of socketPhQueue() may have already been set by ReplayCore. if {![info exists socketPhQueue($state(sock))]} { set socketPhQueue($state(sock)) {} } set socketRdQueue($state(socketinfo)) {} set socketWrQueue($state(socketinfo)) {} set socketClosing($state(socketinfo)) 0 set socketPlayCmd($state(socketinfo)) {ReplayIfClose Wready {} {}} set socketCoEvent($state(socketinfo)) {} set socketProxyId($state(socketinfo)) {} return $DoLater } # ------------------------------------------------------------------------------ # Proc ::http::OpenSocket # ------------------------------------------------------------------------------ # This command is called as a coroutine idletask to start the asynchronous HTTP # transaction in most cases. For the exceptions, see the calling code in # command AsyncTransaction. # # Arguments: # token - connection token (name of an array) # DoLater - dictionary of boolean values listing unfinished tasks # # Return Value: none # ------------------------------------------------------------------------------ proc http::OpenSocket {token DoLater} { variable $token upvar 0 $token state set tk [namespace tail $token] variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId Log >K$tk Start OpenSocket coroutine if {![info exists state(-keepalive)]} { # The request has already been cancelled by the calling script. return } set sockOld $state(sock) dict unset socketCoEvent($state(socketinfo)) $token unset -nocomplain state(socketcoro) if {[catch { if {$state(reusing)} { # If ($state(reusing)) is true, then we do not need to create a new # socket, even if $sockOld is only a placeholder for a socket. set sock $sockOld } else { # set sock in the [catch] below. set pre [clock milliseconds] ##Log pre socket opened, - token $token ##Log $state(openCmd) - token $token set sock [namespace eval :: $state(openCmd)] set state(sock) $sock # Normal return from $state(openCmd) always returns a valid socket. # A TLS proxy connection with 407 or other failure from the # proxy server raises an error. # Initialisation of a new socket. ##Log post socket opened, - token $token ##Log socket opened, now fconfigure - token $token set delay [expr {[clock milliseconds] - $pre}] if {$delay > 3000} { Log socket delay $delay - token $token } fconfigure $sock -translation {auto crlf} \ -buffersize $state(-blocksize) if {[package vsatisfies [package provide Tcl] 9.0-]} { fconfigure $sock -profile replace } ##Log socket opened, DONE fconfigure - token $token } Log "Using $sock for $state(socketinfo) - token $token" \ [expr {$state(-keepalive)?"keepalive":""}] # Code above has set state(sock) $sock ConfigureNewSocket $token $sockOld $DoLater ##Log OpenSocket success $sock - token $token } result errdict]} { ##Log OpenSocket failed $result - token $token # There may be other requests in the socketPhQueue. # Prepare socketPlayCmd so that Finish will replay them. if { ($state(-keepalive)) && (!$state(reusing)) && [info exists socketPhQueue($sockOld)] && ($socketPhQueue($sockOld) ne {}) } { if {$socketMapping($state(socketinfo)) ne $sockOld} { Log "WARNING: this code should not be reached.\ {$socketMapping($state(socketinfo)) ne $sockOld}" } set socketPlayCmd($state(socketinfo)) [list ReplayIfClose Wready {} $socketPhQueue($sockOld)] set socketPhQueue($sockOld) {} } if {[string range $result 0 20] eq {proxy connect failed:}} { # - The HTTPS proxy did not create a socket. The pre-existing value # (a "placeholder socket") is unchanged. # - The proxy returned a valid HTTP response to the failed CONNECT # request, and http::SecureProxyConnect copied this to $token, # and also set ${token}(connection) set to "close". # - Remove the error message $result so that Finish delivers this # HTTP response to the caller. set result {} } Finish $token $result # Because socket creation failed, the placeholder "socket" must be # "closed" and (if persistent) removed from the persistent sockets # table. In the {proxy connect failed:} case Finish does this because # the value of ${token}(connection) is "close". In the other cases here, # it does so because $result is non-empty. } ##Log Leaving http::OpenSocket coroutine [info coroutine] - token $token return } # ------------------------------------------------------------------------------ # Proc ::http::ConfigureNewSocket # ------------------------------------------------------------------------------ # Command to initialise a newly-created socket. Called only from OpenSocket. # # This command is called by OpenSocket whenever a genuine socket (sockNew) has # been opened for for use by HTTP. It does two things: # (1) If $token uses a placeholder socket, this command replaces the placeholder # socket with the real socket, not only in $token but in all other requests # that use the same placeholder. # (2) It calls ScheduleRequest to schedule each request that uses the socket. # # # Value of sockOld/sockNew can be "sock" (genuine socket) or "ph" (placeholder). # sockNew is ${token}(sock) # sockOld sockNew CASES # sock sock (if $reusing, and sockOld is sock) # ph sock (if (not $reusing), and sockOld is ph) # ph ph (if $reusing, and sockOld is ph) - not called in this case # sock ph (cannot occur unless a bug) - not called in this case # (if (not $reusing), and sockOld is sock) - illogical # # Arguments: # token - connection token (name of an array) # sockOld - handle or placeholder used for a socket before the call to # OpenSocket # DoLater - dictionary of boolean values listing unfinished tasks # # Return Value: none # ------------------------------------------------------------------------------ proc http::ConfigureNewSocket {token sockOld DoLater} { variable $token upvar 0 $token state set tk [namespace tail $token] variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId set reusing $state(reusing) set sock $state(sock) set proxyUsed $state(proxyUsed) ##Log " ConfigureNewSocket" $token $sockOld ... -- $reusing $sock $proxyUsed if {(!$reusing) && ($sock ne $sockOld)} { # Replace the placeholder value sockOld with sock. if { [info exists socketMapping($state(socketinfo))] && ($socketMapping($state(socketinfo)) eq $sockOld) } { set socketMapping($state(socketinfo)) $sock set socketProxyId($state(socketinfo)) $proxyUsed # tokens that use the placeholder $sockOld are updated below. ##Log set socketMapping($state(socketinfo)) $sock } # Now finish any tasks left over from PreparePersistentConnection on # the connection. # # The "unset" traces are fired by init (clears entire arrays), and # by http::Unset. # Unset is called by CloseQueuedQueries and (possibly never) by geturl. # # CancelReadPipeline, CancelWritePipeline call http::Finish for each # token. # # FIXME If Finish is placeholder-aware, these traces can be set earlier, # in PreparePersistentConnection. if {[dict get $DoLater -traceread]} { set varName ::http::socketRdState($state(socketinfo)) trace add variable $varName unset ::http::CancelReadPipeline } if {[dict get $DoLater -tracewrite]} { set varName ::http::socketWrState($state(socketinfo)) trace add variable $varName unset ::http::CancelWritePipeline } } # Do this in all cases. ScheduleRequest $token # Now look at all other tokens that use the placeholder $sockOld. if { (!$reusing) && ($sock ne $sockOld) && [info exists socketPhQueue($sockOld)] } { ##Log " ConfigureNewSocket" $token scheduled, now do $socketPhQueue($sockOld) foreach tok $socketPhQueue($sockOld) { # 1. Amend the token's (sock). ##Log set ${tok}(sock) $sock set ${tok}(sock) $sock set ${tok}(proxyUsed) $proxyUsed # 2. Schedule the token's HTTP request. # Every token in socketPhQueue(*) has reusing 1 alreadyQueued 0. set ${tok}(reusing) 1 set ${tok}(alreadyQueued) 0 ScheduleRequest $tok } set socketPhQueue($sockOld) {} } ##Log " ConfigureNewSocket" $token DONE return } # ------------------------------------------------------------------------------ # The values of array variables socketMapping etc. # ------------------------------------------------------------------------------ # connId "$host:$port" # socketMapping($connId) the handle or placeholder for the socket that is used # for "-keepalive 1" requests to $connId. # socketRdState($connId) the token that is currently reading from the socket. # Other values: Rready (ready for next token to read). # socketWrState($connId) the token that is currently writing to the socket. # Other values: Wready (ready for next token to write), # peNding (would be ready for next write, except that # the integrity of a non-pipelined transaction requires # waiting until the read(s) in progress are finished). # socketRdQueue($connId) List of tokens that are queued for reading later. # socketWrQueue($connId) List of tokens that are queued for writing later. # socketPhQueue($sock) List of tokens that are queued to use a placeholder # socket, when the real socket has not yet been created. # socketClosing($connId) (boolean) true iff a server response header indicates # that the server will close the connection at the end of # the current response. # socketPlayCmd($connId) The command to execute to replay pending and # part-completed transactions if the socket closes early. # socketCoEvent($connId) Identifier for the "after idle" event that will launch # an OpenSocket coroutine to open or re-use a socket. # socketProxyId($connId) The type of proxy that this socket uses: values are # those of state(proxyUsed) i.e. none, HttpProxy, # SecureProxy, and SecureProxyFailed. # The value is not used for anything by http, its purpose # is to set the value of state() for caller information. # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Using socketWrState(*), socketWrQueue(*), socketRdState(*), socketRdQueue(*) # ------------------------------------------------------------------------------ # The element socketWrState($connId) has a value which is either the name of # the token that is permitted to write to the socket, or "Wready" if no # token is permitted to write. # # The code that sets the value to Wready immediately calls # http::NextPipelinedWrite, which examines socketWrQueue($connId) and # processes the next request in the queue, if there is one. The value # Wready is not found when the interpreter is in the event loop unless the # socket is idle. # # The element socketRdState($connId) has a value which is either the name of # the token that is permitted to read from the socket, or "Rready" if no # token is permitted to read. # # The code that sets the value to Rready then examines # socketRdQueue($connId) and processes the next request in the queue, if # there is one. The value Rready is not found when the interpreter is in # the event loop unless the socket is idle. # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Proc http::ScheduleRequest # ------------------------------------------------------------------------------ # Command to either begin the HTTP request, or add it to the appropriate queue. # Called from two places in ConfigureNewSocket. # # Arguments: # token - connection token (name of an array) # # Return Value: none # ------------------------------------------------------------------------------ proc http::ScheduleRequest {token} { variable $token upvar 0 $token state set tk [namespace tail $token] Log >L$tk ScheduleRequest variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId set Unfinished 0 set reusing $state(reusing) set sockNew $state(sock) # The "if" tests below: must test against the current values of # socketWrState, socketRdState, and so the tests must be done here, # not earlier in PreparePersistentConnection. if {$state(alreadyQueued)} { # The request has been appended to the queue of a persistent socket # (that is scheduled to close and have its queue replayed). # # A write may or may not be in progress. There is no need to set # socketWrState to prevent another call stealing write access - all # subsequent calls on this socket will come here because the socket # will close after the current read, and its # socketClosing($connId) is 1. ##Log "HTTP request for token $token is queued" } elseif { $reusing && $state(-pipeline) && ($socketWrState($state(socketinfo)) ne "Wready") } { ##Log "HTTP request for token $token is queued for pipelined use" lappend socketWrQueue($state(socketinfo)) $token } elseif { $reusing && (!$state(-pipeline)) && ($socketWrState($state(socketinfo)) ne "Wready") } { # A write is queued or in progress. Lappend to the write queue. ##Log "HTTP request for token $token is queued for nonpipeline use" lappend socketWrQueue($state(socketinfo)) $token } elseif { $reusing && (!$state(-pipeline)) && ($socketWrState($state(socketinfo)) eq "Wready") && ($socketRdState($state(socketinfo)) ne "Rready") } { # A read is queued or in progress, but not a write. Cannot start the # nonpipeline transaction, but must set socketWrState to prevent a # pipelined request jumping the queue. ##Log "HTTP request for token $token is queued for nonpipeline use" #Log re-use nonpipeline, GRANT delayed write access to $token in geturl set socketWrState($state(socketinfo)) peNding lappend socketWrQueue($state(socketinfo)) $token } else { if {$reusing && $state(-pipeline)} { #Log new, init for pipelined, GRANT write access to $token in geturl # DO NOT grant premature read access to the socket. # set socketRdState($state(socketinfo)) $token set socketWrState($state(socketinfo)) $token } elseif {$reusing} { # socketWrState is not used by this non-pipelined transaction. # We cannot leave it as "Wready" because the next call to # http::geturl with a pipelined transaction would conclude that the # socket is available for writing. #Log new, init for nonpipeline, GRANT r/w access to $token in geturl set socketRdState($state(socketinfo)) $token set socketWrState($state(socketinfo)) $token } else { } # Process the request now. # - Command is not called unless $state(sock) is a real socket handle # and not a placeholder. # - All (!$reusing) cases come here. # - Some $reusing cases come here too if the connection is # marked as ready. Those $reusing cases are: # $reusing && ($socketWrState($state(socketinfo)) eq "Wready") && # EITHER !$pipeline && ($socketRdState($state(socketinfo)) eq "Rready") # OR $pipeline # #Log ---- $state(socketinfo) << conn to $token for HTTP request (a) ##Log " ScheduleRequest" $token -- fileevent $state(sock) writable for $token # Connect does its own fconfigure. lassign $state(connArgs) proto phost srvurl if {[catch { fileevent $state(sock) writable \ [list http::Connect $token $proto $phost $srvurl] } res opts]} { # The socket no longer exists. ##Log bug -- socket gone -- $res -- $opts } } return } # ------------------------------------------------------------------------------ # Proc http::SendHeader # ------------------------------------------------------------------------------ # Command to send a request header, and keep a copy in state(requestHeaders) # for debugging purposes. # # Arguments: # token - connection token (name of an array) # key - header name # value - header value # # Return Value: none # ------------------------------------------------------------------------------ proc http::SendHeader {token key value} { variable $token upvar 0 $token state set tk [namespace tail $token] set sock $state(sock) lappend state(requestHeaders) [string tolower $key] $value puts $sock "$key: $value" return } # http::Connected -- # # Callback used when the connection to the HTTP server is actually # established. # # Arguments: # token State token. # proto What protocol (http, https, etc.) was used to connect. # phost Are we using keep-alive? Non-empty if yes. # srvurl Service-local URL that we're requesting # Results: # None. proc http::Connected {token proto phost srvurl} { variable http variable urlTypes variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId variable $token upvar 0 $token state set tk [namespace tail $token] if {$state(reusing) && (!$state(-pipeline)) && ($state(-timeout) > 0)} { set state(after) [after $state(-timeout) \ [list http::reset $token timeout]] } # Set back the variables needed here. set sock $state(sock) set isQueryChannel [info exists state(-querychannel)] set isQuery [info exists state(-query)] regexp {^(.+):([^:]+)$} $state(socketinfo) {} host port set lower [string tolower $proto] set defport [lindex $urlTypes($lower) 0] # Send data in cr-lf format, but accept any line terminators. # Initialisation to {auto *} now done in geturl, KeepSocket and DoneRequest. # We are concerned here with the request (write) not the response (read). lassign [fconfigure $sock -translation] trRead trWrite fconfigure $sock -translation [list $trRead crlf] \ -buffersize $state(-blocksize) if {[package vsatisfies [package provide Tcl] 9.0-]} { fconfigure $sock -profile replace } # The following is disallowed in safe interpreters, but the socket is # already in non-blocking mode in that case. catch {fconfigure $sock -blocking off} set how GET if {$isQuery} { set state(querylength) [string length $state(-query)] if {$state(querylength) > 0} { set how POST set contDone 0 } else { # There's no query data. unset state(-query) set isQuery 0 } } elseif {$state(-validate)} { set how HEAD } elseif {$isQueryChannel} { set how POST # The query channel must be blocking for the async Write to # work properly. fconfigure $state(-querychannel) -blocking 1 -translation binary set contDone 0 } if {[info exists state(-method)] && ($state(-method) ne "")} { set how $state(-method) } set accept_types_seen 0 Log ^B$tk begin sending request - token $token if {[catch { if {[info exists state(bypass)]} { set state(method) [lindex [split $state(bypass) { }] 0] set state(requestHeaders) {} set state(requestLine) $state(bypass) } else { set state(method) $how set state(requestHeaders) {} set state(requestLine) "$how $srvurl HTTP/$state(-protocol)" } puts $sock $state(requestLine) set hostValue [GetFieldValue $state(-headers) Host] if {$hostValue ne {}} { # Allow Host spoofing. [Bug 928154] regexp {^[^:]+} $hostValue state(host) SendHeader $token Host $hostValue } elseif {$port == $defport} { # Don't add port in this case, to handle broken servers. [Bug # #504508] set state(host) $host SendHeader $token Host $host } else { set state(host) $host SendHeader $token Host "$host:$port" } SendHeader $token User-Agent $http(-useragent) if {($state(-protocol) > 1.0) && $state(-keepalive)} { # Send this header, because a 1.1 server is not compelled to treat # this as the default. set ConnVal keep-alive } elseif {($state(-protocol) > 1.0)} { # RFC2616 sec 8.1.2.1 set ConnVal close } else { # ($state(-protocol) <= 1.0) # RFC7230 A.1 # Some server implementations of HTTP/1.0 have a faulty # implementation of RFC 2068 Keep-Alive. # Don't leave this to chance. # For HTTP/1.0 we have already "set state(connection) close" # and "state(-keepalive) 0". set ConnVal close } # Proxy authorisation (cf. mod by Anders Ramdahl to autoproxy by # Pat Thoyts). if {($http(-proxyauth) ne {}) && ($state(proxyUsed) eq {HttpProxy})} { SendHeader $token Proxy-Authorization $http(-proxyauth) } # RFC7230 A.1 - "clients are encouraged not to send the # Proxy-Connection header field in any requests" set accept_encoding_seen 0 set content_type_seen 0 set connection_seen 0 foreach {key value} $state(-headers) { set value [string map [list \n "" \r ""] $value] set key [string map {" " -} [string trim $key]] if {[string equal -nocase $key "host"]} { continue } if {[string equal -nocase $key "accept-encoding"]} { set accept_encoding_seen 1 } if {[string equal -nocase $key "accept"]} { set accept_types_seen 1 } if {[string equal -nocase $key "content-type"]} { set content_type_seen 1 } if {[string equal -nocase $key "content-length"]} { set contDone 1 set state(querylength) $value } if { [string equal -nocase $key "connection"] && [info exists state(bypass)] } { # Value supplied in -headers overrides $ConnVal. set connection_seen 1 } elseif {[string equal -nocase $key "connection"]} { # Remove "close" or "keep-alive" and use our own value. # In an upgrade request, the upgrade is not guaranteed. # Value "close" or "keep-alive" tells the server what to do # if it refuses the upgrade. We send a single "Connection" # header because some websocket servers, e.g. civetweb, reject # multiple headers. Bug [d01de3281f] of tcllib/websocket. set connection_seen 1 set listVal $state(connectionValues) if {[set pos [lsearch $listVal close]] != -1} { set listVal [lreplace $listVal $pos $pos] } if {[set pos [lsearch $listVal keep-alive]] != -1} { set listVal [lreplace $listVal $pos $pos] } lappend listVal $ConnVal set value [join $listVal {, }] } if {[string length $key]} { SendHeader $token $key $value } } # Allow overriding the Accept header on a per-connection basis. Useful # for working with REST services. [Bug c11a51c482] if {!$accept_types_seen} { SendHeader $token Accept $state(accept-types) } if { (!$accept_encoding_seen) && (![info exists state(-handler)]) && $http(-zip) } { SendHeader $token Accept-Encoding gzip,deflate } elseif {!$accept_encoding_seen} { SendHeader $token Accept-Encoding identity } else { } if {!$connection_seen} { SendHeader $token Connection $ConnVal } if {$isQueryChannel && ($state(querylength) == 0)} { # Try to determine size of data in channel. If we cannot seek, the # surrounding catch will trap us set start [tell $state(-querychannel)] seek $state(-querychannel) 0 end set state(querylength) \ [expr {[tell $state(-querychannel)] - $start}] seek $state(-querychannel) $start } # Note that we don't do Cookie2; that's much nastier and not normally # observed in practice either. It also doesn't fix the multitude of # bugs in the basic cookie spec. if {$http(-cookiejar) ne ""} { set cookies "" set separator "" foreach {key value} [{*}$http(-cookiejar) \ getCookies $proto $host $state(path)] { append cookies $separator $key = $value set separator "; " } if {$cookies ne ""} { SendHeader $token Cookie $cookies } } # Flush the request header and set up the fileevent that will either # push the POST data or read the response. # # fileevent note: # # It is possible to have both the read and write fileevents active at # this point. The only scenario it seems to affect is a server that # closes the connection without reading the POST data. (e.g., early # versions TclHttpd in various error cases). Depending on the # platform, the client may or may not be able to get the response from # the server because of the error it will get trying to write the post # data. Having both fileevents active changes the timing and the # behavior, but no two platforms (among Solaris, Linux, and NT) behave # the same, and none behave all that well in any case. Servers should # always read their POST data if they expect the client to read their # response. if {$isQuery || $isQueryChannel} { # POST method. if {!$content_type_seen} { SendHeader $token Content-Type $state(-type) } if {!$contDone} { SendHeader $token Content-Length $state(querylength) } puts $sock "" flush $sock # Flush flushes the error in the https case with a bad handshake: # else the socket never becomes writable again, and hangs until # timeout (if any). lassign [fconfigure $sock -translation] trRead trWrite fconfigure $sock -translation [list $trRead binary] fileevent $sock writable [list http::Write $token] # The http::Write command decides when to make the socket readable, # using the same test as the GET/HEAD case below. } else { # GET or HEAD method. if { (![catch {fileevent $sock readable} binding]) && ($binding eq [list http::CheckEof $sock]) } { # Remove the "fileevent readable" binding of an idle persistent # socket to http::CheckEof. We can no longer treat bytes # received as junk. The server might still time out and # half-close the socket if it has not yet received the first # "puts". fileevent $sock readable {} } puts $sock "" flush $sock Log ^C$tk end sending request - token $token # End of writing (GET/HEAD methods). The request has been sent. DoneRequest $token } } err]} { # The socket probably was never connected, OR the connection dropped # later, OR https handshake error, which may be discovered as late as # the "flush" command above... Log "WARNING - if testing, pay special attention to this\ case (GI) which is seldom executed - token $token" if {[info exists state(reusing)] && $state(reusing)} { # The socket was closed at the server end, and closed at # this end by http::CheckEof. if {[TestForReplay $token write $err a]} { return } else { Finish $token {failed to re-use socket} } # else: # This is NOT a persistent socket that has been closed since its # last use. # If any other requests are in flight or pipelined/queued, they will # be discarded. } elseif {$state(status) eq ""} { # https handshake errors come here, for # Tcl 8.7 without http::SecureProxyConnect, and for Tcl 8.6. set msg [registerError $sock] registerError $sock {} if {$msg eq {}} { set msg {failed to use socket} } Finish $token $msg } elseif {$state(status) ne "error"} { Finish $token $err } } return } # http::registerError # # Called (for example when processing TclTLS activity) to register # an error for a connection on a specific socket. This helps # http::Connected to deliver meaningful error messages, e.g. when a TLS # certificate fails verification. # # Usage: http::registerError socket ?newValue? # # "set" semantics, except that a "get" (a call without a new value) for a # non-existent socket returns {}, not an error. proc http::registerError {sock args} { variable registeredErrors if { ([llength $args] == 0) && (![info exists registeredErrors($sock)]) } { return } elseif { ([llength $args] == 1) && ([lindex $args 0] eq {}) } { unset -nocomplain registeredErrors($sock) return } set registeredErrors($sock) {*}$args } # http::DoneRequest -- # # Command called when a request has been sent. It will arrange the # next request and/or response as appropriate. # # If this command is called when $socketClosing(*), the request $token # that calls it must be pipelined and destined to fail. proc http::DoneRequest {token} { variable http variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId variable $token upvar 0 $token state set tk [namespace tail $token] set sock $state(sock) # If pipelined, connect the next HTTP request to the socket. if {$state(reusing) && $state(-pipeline)} { # Enable next token (if any) to write. # The value "Wready" is set only here, and # in http::Event after reading the response-headers of a # non-reusing transaction. # Previous value is $token. It cannot be pending. set socketWrState($state(socketinfo)) Wready # Now ready to write the next pipelined request (if any). http::NextPipelinedWrite $token } else { # If pipelined, this is the first transaction on this socket. We wait # for the response headers to discover whether the connection is # persistent. (If this is not done and the connection is not # persistent, we SHOULD retry and then MUST NOT pipeline before knowing # that we have a persistent connection # (rfc2616 8.1.2.2)). } # Connect to receive the response, unless the socket is pipelined # and another response is being sent. # This code block is separate from the code below because there are # cases where socketRdState already has the value $token. if { $state(-keepalive) && $state(-pipeline) && [info exists socketRdState($state(socketinfo))] && ($socketRdState($state(socketinfo)) eq "Rready") } { #Log pipelined, GRANT read access to $token in Connected set socketRdState($state(socketinfo)) $token } if { $state(-keepalive) && $state(-pipeline) && [info exists socketRdState($state(socketinfo))] && ($socketRdState($state(socketinfo)) ne $token) } { # Do not read from the socket until it is ready. ##Log "HTTP response for token $token is queued for pipelined use" # If $socketClosing(*), then the caller will be a pipelined write and # execution will come here. # This token has already been recorded as "in flight" for writing. # When the socket is closed, the read queue will be cleared in # CloseQueuedQueries and so the "lappend" here has no effect. lappend socketRdQueue($state(socketinfo)) $token } else { # In the pipelined case, connection for reading depends on the # value of socketRdState. # In the nonpipeline case, connection for reading always occurs. ReceiveResponse $token } return } # http::ReceiveResponse # # Connects token to its socket for reading. proc http::ReceiveResponse {token} { variable $token upvar 0 $token state set tk [namespace tail $token] set sock $state(sock) #Log ---- $state(socketinfo) >> conn to $token for HTTP response lassign [fconfigure $sock -translation] trRead trWrite fconfigure $sock -translation [list auto $trWrite] \ -buffersize $state(-blocksize) if {[package vsatisfies [package provide Tcl] 9.0-]} { fconfigure $sock -profile replace } Log ^D$tk begin receiving response - token $token coroutine ${token}--EventCoroutine http::Event $sock $token if {[info exists state(-handler)] || [info exists state(-progress)]} { fileevent $sock readable [list http::EventGateway $sock $token] } else { fileevent $sock readable ${token}--EventCoroutine } return } # http::EventGateway # # Bug [c2dc1da315]. # - Recursive launch of the coroutine can occur if a -handler or -progress # callback is used, and the callback command enters the event loop. # - To prevent this, the fileevent "binding" is disabled while the # coroutine is in flight. # - If a recursive call occurs despite these precautions, it is not # trapped and discarded here, because it is better to report it as a # bug. # - Although this solution is believed to be sufficiently general, it is # used only if -handler or -progress is specified. In other cases, # the coroutine is called directly. proc http::EventGateway {sock token} { variable $token upvar 0 $token state fileevent $sock readable {} catch {${token}--EventCoroutine} res opts if {[info commands ${token}--EventCoroutine] ne {}} { # The coroutine can be deleted by completion (a non-yield return), by # http::Finish (when there is a premature end to the transaction), by # http::reset or http::cleanup, or if the caller set option -channel # but not option -handler: in the last case reading from the socket is # now managed by commands ::http::Copy*, http::ReceiveChunked, and # http::MakeTransformationChunked. # # Catch in case the coroutine has closed the socket. catch {fileevent $sock readable [list http::EventGateway $sock $token]} } # If there was an error, re-throw it. return -options $opts $res } # http::NextPipelinedWrite # # - Connecting a socket to a token for writing is done by this command and by # command KeepSocket. # - If another request has a pipelined write scheduled for $token's socket, # and if the socket is ready to accept it, connect the write and update # the queue accordingly. # - This command is called from http::DoneRequest and http::Event, # IF $state(-pipeline) AND (the current transfer has reached the point at # which the socket is ready for the next request to be written). # - This command is called when a token has write access and is pipelined and # keep-alive, and sets socketWrState to Wready. # - The command need not consider the case where socketWrState is set to a token # that does not yet have write access. Such a token is waiting for Rready, # and the assignment of the connection to the token will be done elsewhere (in # http::KeepSocket). # - This command cannot be called after socketWrState has been set to a # "pending" token value (that is then overwritten by the caller), because that # value is set by this command when it is called by an earlier token when it # relinquishes its write access, and the pending token is always the next in # line to write. proc http::NextPipelinedWrite {token} { variable http variable socketRdState variable socketWrState variable socketWrQueue variable socketClosing variable $token upvar 0 $token state set connId $state(socketinfo) if { [info exists socketClosing($connId)] && $socketClosing($connId) } { # socketClosing(*) is set because the server has sent a # "Connection: close" header. # Behave as if the queues are empty - so do nothing. } elseif { $state(-pipeline) && [info exists socketWrState($connId)] && ($socketWrState($connId) eq "Wready") && [info exists socketWrQueue($connId)] && [llength $socketWrQueue($connId)] && ([set token2 [lindex $socketWrQueue($connId) 0] set ${token2}(-pipeline) ] ) } { # - The usual case for a pipelined connection, ready for a new request. #Log pipelined, GRANT write access to $token2 in NextPipelinedWrite set conn [set ${token2}(connArgs)] set socketWrState($connId) $token2 set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end] # Connect does its own fconfigure. fileevent $state(sock) writable [list http::Connect $token2 {*}$conn] #Log ---- $connId << conn to $token2 for HTTP request (b) # In the tests below, the next request will be nonpipeline. } elseif { $state(-pipeline) && [info exists socketWrState($connId)] && ($socketWrState($connId) eq "Wready") && [info exists socketWrQueue($connId)] && [llength $socketWrQueue($connId)] && (![ set token3 [lindex $socketWrQueue($connId) 0] set ${token3}(-pipeline) ] ) && [info exists socketRdState($connId)] && ($socketRdState($connId) eq "Rready") } { # The case in which the next request will be non-pipelined, and the read # and write queues is ready: which is the condition for a non-pipelined # write. set conn [set ${token3}(connArgs)] #Log nonpipeline, GRANT r/w access to $token3 in NextPipelinedWrite set socketRdState($connId) $token3 set socketWrState($connId) $token3 set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end] # Connect does its own fconfigure. fileevent $state(sock) writable [list http::Connect $token3 {*}$conn] #Log ---- $state(sock) << conn to $token3 for HTTP request (c) } elseif { $state(-pipeline) && [info exists socketWrState($connId)] && ($socketWrState($connId) eq "Wready") && [info exists socketWrQueue($connId)] && [llength $socketWrQueue($connId)] && (![set token2 [lindex $socketWrQueue($connId) 0] set ${token2}(-pipeline) ] ) } { # - The case in which the next request will be non-pipelined, but the # read queue is NOT ready. # - A read is queued or in progress, but not a write. Cannot start the # nonpipeline transaction, but must set socketWrState to prevent a new # pipelined request (in http::geturl) jumping the queue. # - Because socketWrState($connId) is not set to Wready, the assignment # of the connection to $token2 will be done elsewhere - by command # http::KeepSocket when $socketRdState($connId) is set to "Rready". #Log re-use nonpipeline, GRANT delayed write access to $token in NextP.. set socketWrState($connId) peNding } return } # http::CancelReadPipeline # # Cancel pipelined responses on a closing "Keep-Alive" socket. # # - Called by a variable trace on "unset socketRdState($connId)". # - The variable relates to a Keep-Alive socket, which has been closed. # - Cancels all pipelined responses. The requests have been sent, # the responses have not yet been received. # - This is a hard cancel that ends each transaction with error status, # and closes the connection. Do not use it if you want to replay failed # transactions. # - N.B. Always delete ::http::socketRdState($connId) before deleting # ::http::socketRdQueue($connId), or this command will do nothing. # # Arguments # As for a trace command on a variable. proc http::CancelReadPipeline {name1 connId op} { variable socketRdQueue ##Log CancelReadPipeline $name1 $connId $op if {[info exists socketRdQueue($connId)]} { set msg {the connection was closed by CancelReadPipeline} foreach token $socketRdQueue($connId) { set tk [namespace tail $token] Log ^X$tk end of response "($msg)" - token $token set ${token}(status) eof Finish $token ;#$msg } set socketRdQueue($connId) {} } return } # http::CancelWritePipeline # # Cancel queued events on a closing "Keep-Alive" socket. # # - Called by a variable trace on "unset socketWrState($connId)". # - The variable relates to a Keep-Alive socket, which has been closed. # - In pipelined or nonpipeline case: cancels all queued requests. The # requests have not yet been sent, the responses are not due. # - This is a hard cancel that ends each transaction with error status, # and closes the connection. Do not use it if you want to replay failed # transactions. # - N.B. Always delete ::http::socketWrState($connId) before deleting # ::http::socketWrQueue($connId), or this command will do nothing. # # Arguments # As for a trace command on a variable. proc http::CancelWritePipeline {name1 connId op} { variable socketWrQueue ##Log CancelWritePipeline $name1 $connId $op if {[info exists socketWrQueue($connId)]} { set msg {the connection was closed by CancelWritePipeline} foreach token $socketWrQueue($connId) { set tk [namespace tail $token] Log ^X$tk end of response "($msg)" - token $token set ${token}(status) eof Finish $token ;#$msg } set socketWrQueue($connId) {} } return } # http::ReplayIfDead -- # # - A query on a re-used persistent socket failed at the earliest opportunity, # because the socket had been closed by the server. Keep the token, tidy up, # and try to connect on a fresh socket. # - The connection is monitored for eof by the command http::CheckEof. Thus # http::ReplayIfDead is needed only when a server event (half-closing an # apparently idle connection), and a client event (sending a request) occur at # almost the same time, and neither client nor server detects the other's # action before performing its own (an "asynchronous close event"). # - To simplify testing of http::ReplayIfDead, set TEST_EOF 1 in # http::KeepSocket, and then http::ReplayIfDead will be called if http::geturl # is called at any time after the server timeout. # # Arguments: # token Connection token. # # Side Effects: # Use the same token, but try to open a new socket. proc http::ReplayIfDead {token doing} { variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId variable $token upvar 0 $token state Log running http::ReplayIfDead for $token $doing # 1. Merge the tokens for transactions in flight, the read (response) queue, # and the write (request) queue. set InFlightR {} set InFlightW {} # Obtain the tokens for transactions in flight. if {$state(-pipeline)} { # Two transactions may be in flight. The "read" transaction was first. # It is unlikely that the server would close the socket if a response # was pending; however, an earlier request (as well as the present # request) may have been sent and ignored if the socket was half-closed # by the server. if { [info exists socketRdState($state(socketinfo))] && ($socketRdState($state(socketinfo)) ne "Rready") } { lappend InFlightR $socketRdState($state(socketinfo)) } elseif {($doing eq "read")} { lappend InFlightR $token } if { [info exists socketWrState($state(socketinfo))] && $socketWrState($state(socketinfo)) ni {Wready peNding} } { lappend InFlightW $socketWrState($state(socketinfo)) } elseif {($doing eq "write")} { lappend InFlightW $token } # Report any inconsistency of $token with socket*state. if { ($doing eq "read") && [info exists socketRdState($state(socketinfo))] && ($token ne $socketRdState($state(socketinfo))) } { Log WARNING - ReplayIfDead pipelined token $token $doing \ ne socketRdState($state(socketinfo)) \ $socketRdState($state(socketinfo)) } elseif { ($doing eq "write") && [info exists socketWrState($state(socketinfo))] && ($token ne $socketWrState($state(socketinfo))) } { Log WARNING - ReplayIfDead pipelined token $token $doing \ ne socketWrState($state(socketinfo)) \ $socketWrState($state(socketinfo)) } } else { # One transaction should be in flight. # socketRdState, socketWrQueue are used. # socketRdQueue should be empty. # Report any inconsistency of $token with socket*state. if {$token ne $socketRdState($state(socketinfo))} { Log WARNING - ReplayIfDead nonpipeline token $token $doing \ ne socketRdState($state(socketinfo)) \ $socketRdState($state(socketinfo)) } # Report the inconsistency that socketRdQueue is non-empty. if { [info exists socketRdQueue($state(socketinfo))] && ($socketRdQueue($state(socketinfo)) ne {}) } { Log WARNING - ReplayIfDead nonpipeline token $token $doing \ has read queue socketRdQueue($state(socketinfo)) \ $socketRdQueue($state(socketinfo)) ne {} } lappend InFlightW $socketRdState($state(socketinfo)) set socketRdQueue($state(socketinfo)) {} } set newQueue {} lappend newQueue {*}$InFlightR lappend newQueue {*}$socketRdQueue($state(socketinfo)) lappend newQueue {*}$InFlightW lappend newQueue {*}$socketWrQueue($state(socketinfo)) # 2. Tidy up token. This is a cut-down form of Finish/CloseSocket. # Do not change state(status). # No need to after cancel state(after) - either this is done in # ReplayCore/ReInit, or Finish is called. catch {close $state(sock)} Unset $state(socketinfo) # 2a. Tidy the tokens in the queues - this is done in ReplayCore/ReInit. # - Transactions, if any, that are awaiting responses cannot be completed. # They are listed for re-sending in newQueue. # - All tokens are preserved for re-use by ReplayCore, and their variables # will be re-initialised by calls to ReInit. # - The relevant element of socketMapping, socketRdState, socketWrState, # socketRdQueue, socketWrQueue, socketClosing, socketPlayCmd will be set # to new values in ReplayCore. ReplayCore $newQueue return } # http::ReplayIfClose -- # # A request on a socket that was previously "Connection: keep-alive" has # received a "Connection: close" response header. The server supplies # that response correctly, but any later requests already queued on this # connection will be lost when the socket closes. # # This command takes arguments that represent the socketWrState, # socketRdQueue and socketWrQueue for this connection. The socketRdState # is not needed because the server responds in full to the request that # received the "Connection: close" response header. # # Existing request tokens $token (::http::$n) are preserved. The caller # will be unaware that the request was processed this way. proc http::ReplayIfClose {Wstate Rqueue Wqueue} { Log running http::ReplayIfClose for $Wstate $Rqueue $Wqueue if {$Wstate in $Rqueue || $Wstate in $Wqueue} { Log WARNING duplicate token in http::ReplayIfClose - token $Wstate set Wstate Wready } # 1. Create newQueue set InFlightW {} if {$Wstate ni {Wready peNding}} { lappend InFlightW $Wstate } ##Log $Rqueue -- $InFlightW -- $Wqueue set newQueue {} lappend newQueue {*}$Rqueue lappend newQueue {*}$InFlightW lappend newQueue {*}$Wqueue # 2. Cleanup - none needed, done by the caller. ReplayCore $newQueue return } # http::ReInit -- # # Command to restore a token's state to a condition that # makes it ready to replay a request. # # Command http::geturl stores extra state in state(tmp*) so # we don't need to do the argument processing again. # # The caller must: # - Set state(reusing) and state(sock) to their new values after calling # this command. # - Unset state(tmpState), state(tmpOpenCmd) if future calls to ReplayCore # or ReInit are inappropriate for this token. Typically only one retry # is allowed. # The caller may also unset state(tmpConnArgs) if this value (and the # token) will be used immediately. The value is needed by tokens that # will be stored in a queue. # # Arguments: # token Connection token. # # Return Value: (boolean) true iff the re-initialisation was successful. proc http::ReInit {token} { variable $token upvar 0 $token state if {!( [info exists state(tmpState)] && [info exists state(tmpOpenCmd)] && [info exists state(tmpConnArgs)] ) } { Log FAILED in http::ReInit via ReplayCore - NO tmp vars for $token return 0 } if {[info exists state(after)]} { after cancel $state(after) unset state(after) } if {[info exists state(socketcoro)]} { Log $token Cancel socket after-idle event (ReInit) after cancel $state(socketcoro) unset state(socketcoro) } # Don't alter state(status) - this would trigger http::wait if it is in use. set tmpState $state(tmpState) set tmpOpenCmd $state(tmpOpenCmd) set tmpConnArgs $state(tmpConnArgs) foreach name [array names state] { if {$name ne "status"} { unset state($name) } } # Don't alter state(status). # Restore state(tmp*) - the caller may decide to unset them. # Restore state(tmpConnArgs) which is needed for connection. # state(tmpState), state(tmpOpenCmd) are needed only for retries. dict unset tmpState status array set state $tmpState set state(tmpState) $tmpState set state(tmpOpenCmd) $tmpOpenCmd set state(tmpConnArgs) $tmpConnArgs return 1 } # http::ReplayCore -- # # Command to replay a list of requests, using existing connection tokens. # # Abstracted from http::geturl which stores extra state in state(tmp*) so # we don't need to do the argument processing again. # # Arguments: # newQueue List of connection tokens. # # Side Effects: # Use existing tokens, but try to open a new socket. proc http::ReplayCore {newQueue} { variable TmpSockCounter variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId if {[llength $newQueue] == 0} { # Nothing to do. return } ##Log running ReplayCore for {*}$newQueue set newToken [lindex $newQueue 0] set newQueue [lrange $newQueue 1 end] # 3. Use newToken, and restore its values of state(*). Do not restore # elements tmp* - we try again only once. set token $newToken variable $token upvar 0 $token state if {![ReInit $token]} { Log FAILED in http::ReplayCore - NO tmp vars Log ReplayCore reject $token Finish $token {cannot send this request again} return } set tmpState $state(tmpState) set tmpOpenCmd $state(tmpOpenCmd) set tmpConnArgs $state(tmpConnArgs) unset state(tmpState) unset state(tmpOpenCmd) unset state(tmpConnArgs) set state(reusing) 0 set state(ReusingPlaceholder) 0 set state(alreadyQueued) 0 Log ReplayCore replay $token # Give the socket a placeholder name before it is created. set sock HTTP_PLACEHOLDER_[incr TmpSockCounter] set state(sock) $sock # Move the $newQueue into the placeholder socket's socketPhQueue. set socketPhQueue($sock) {} foreach tok $newQueue { if {[ReInit $tok]} { set ${tok}(reusing) 1 set ${tok}(sock) $sock lappend socketPhQueue($sock) $tok Log ReplayCore replay $tok } else { Log ReplayCore reject $tok set ${tok}(reusing) 1 set ${tok}(sock) NONE Finish $tok {cannot send this request again} } } AsyncTransaction $token return } # Data access functions: # Data - the URL data # Status - the transaction status: ok, reset, eof, timeout, error # Code - the HTTP transaction code, e.g., 200 # Size - the size of the URL data proc http::responseBody {token} { variable $token upvar 0 $token state return $state(body) } proc http::status {token} { if {![info exists $token]} { return "error" } variable $token upvar 0 $token state return $state(status) } proc http::responseLine {token} { variable $token upvar 0 $token state return $state(http) } proc http::requestLine {token} { variable $token upvar 0 $token state return $state(requestLine) } proc http::responseCode {token} { variable $token upvar 0 $token state if {[regexp {[0-9]{3}} $state(http) numeric_code]} { return $numeric_code } else { return $state(http) } } proc http::size {token} { variable $token upvar 0 $token state return $state(currentsize) } proc http::requestHeaders {token args} { set lenny [llength $args] if {$lenny > 1} { return -code error {usage: ::http::requestHeaders token ?headerName?} } else { return [Meta $token request {*}$args] } } proc http::responseHeaders {token args} { set lenny [llength $args] if {$lenny > 1} { return -code error {usage: ::http::responseHeaders token ?headerName?} } else { return [Meta $token response {*}$args] } } proc http::requestHeaderValue {token header} { Meta $token request $header VALUE } proc http::responseHeaderValue {token header} { Meta $token response $header VALUE } proc http::Meta {token who args} { variable $token upvar 0 $token state if {$who eq {request}} { set whom requestHeaders } elseif {$who eq {response}} { set whom meta } else { return -code error {usage: ::http::Meta token request|response ?headerName ?VALUE??} } set header [string tolower [lindex $args 0]] set how [string tolower [lindex $args 1]] set lenny [llength $args] if {$lenny == 0} { return $state($whom) } elseif {($lenny > 2) || (($lenny == 2) && ($how ne {value}))} { return -code error {usage: ::http::Meta token request|response ?headerName ?VALUE??} } else { set result {} set combined {} foreach {key value} $state($whom) { if {$key eq $header} { lappend result $key $value append combined $value {, } } } if {$lenny == 1} { return $result } else { return [string range $combined 0 end-2] } } } # ------------------------------------------------------------------------------ # Proc http::responseInfo # ------------------------------------------------------------------------------ # Command to return a dictionary of the most useful metadata of a HTTP # response. # # Arguments: # token - connection token (name of an array) # # Return Value: a dict. See man page http(n) for a description of each item. # ------------------------------------------------------------------------------ proc http::responseInfo {token} { variable $token upvar 0 $token state set result {} foreach {key origin name} { stage STATE state status STATE status responseCode STATE responseCode reasonPhrase STATE reasonPhrase contentType STATE type binary STATE binary redirection RESP location upgrade STATE upgrade error ERROR - postError STATE posterror method STATE method charset STATE charset compression STATE coding httpRequest STATE -protocol httpResponse STATE httpResponse url STATE url connectionRequest REQ connection connectionResponse RESP connection connectionActual STATE connection transferEncoding STATE transfer totalPost STATE querylength currentPost STATE queryoffset totalSize STATE totalsize currentSize STATE currentsize proxyUsed STATE proxyUsed } { if {$origin eq {STATE}} { if {[info exists state($name)]} { dict set result $key $state($name) } else { # Should never come here dict set result $key {} } } elseif {$origin eq {REQ}} { dict set result $key [requestHeaderValue $token $name] } elseif {$origin eq {RESP}} { dict set result $key [responseHeaderValue $token $name] } elseif {$origin eq {ERROR}} { # Don't flood the dict with data. The command ::http::error is # available. if {[info exists state(error)]} { set msg [lindex $state(error) 0] } else { set msg {} } dict set result $key $msg } else { # Should never come here dict set result $key {} } } return $result } proc http::error {token} { variable $token upvar 0 $token state if {[info exists state(error)]} { return $state(error) } return } proc http::postError {token} { variable $token upvar 0 $token state if {[info exists state(postErrorFull)]} { return $state(postErrorFull) } return } # http::cleanup # # Garbage collect the state associated with a transaction # # Arguments # token The token returned from http::geturl # # Side Effects # Unsets the state array. proc http::cleanup {token} { variable $token upvar 0 $token state if {[info commands ${token}--EventCoroutine] ne {}} { rename ${token}--EventCoroutine {} } if {[info commands ${token}--SocketCoroutine] ne {}} { rename ${token}--SocketCoroutine {} } if {[info exists state(after)]} { after cancel $state(after) unset state(after) } if {[info exists state(socketcoro)]} { Log $token Cancel socket after-idle event (cleanup) after cancel $state(socketcoro) unset state(socketcoro) } if {[info exists state]} { unset state } return } # http::Connect # # This callback is made when an asynchronous connection completes. # # Arguments # token The token returned from http::geturl # # Side Effects # Sets the status of the connection, which unblocks # the waiting geturl call proc http::Connect {token proto phost srvurl} { variable $token upvar 0 $token state set tk [namespace tail $token] if {[catch {eof $state(sock)} tmp] || $tmp} { set err "due to unexpected EOF" } elseif {[set err [fconfigure $state(sock) -error]] ne ""} { # set err is done in test } else { # All OK set state(state) connecting fileevent $state(sock) writable {} ::http::Connected $token $proto $phost $srvurl return } # Error cases. Log "WARNING - if testing, pay special attention to this\ case (GJ) which is seldom executed - token $token" if {[info exists state(reusing)] && $state(reusing)} { # The socket was closed at the server end, and closed at # this end by http::CheckEof. if {[TestForReplay $token write $err b]} { return } # else: # This is NOT a persistent socket that has been closed since its # last use. # If any other requests are in flight or pipelined/queued, they will # be discarded. } Finish $token "connect failed: $err" return } # http::Write # # Write POST query data to the socket # # Arguments # token The token for the connection # # Side Effects # Write the socket and handle callbacks. proc http::Write {token} { variable http variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId variable $token upvar 0 $token state set tk [namespace tail $token] set sock $state(sock) # Output a block. Tcl will buffer this if the socket blocks set done 0 if {[catch { # Catch I/O errors on dead sockets if {[info exists state(-query)]} { # Chop up large query strings so queryprogress callback can give # smooth feedback. if { $state(queryoffset) + $state(-queryblocksize) >= $state(querylength) } { # This will be the last puts for the request-body. if { (![catch {fileevent $sock readable} binding]) && ($binding eq [list http::CheckEof $sock]) } { # Remove the "fileevent readable" binding of an idle # persistent socket to http::CheckEof. We can no longer # treat bytes received as junk. The server might still time # out and half-close the socket if it has not yet received # the first "puts". fileevent $sock readable {} } } puts -nonewline $sock \ [string range $state(-query) $state(queryoffset) \ [expr {$state(queryoffset) + $state(-queryblocksize) - 1}]] incr state(queryoffset) $state(-queryblocksize) if {$state(queryoffset) >= $state(querylength)} { set state(queryoffset) $state(querylength) set done 1 } } else { # Copy blocks from the query channel set outStr [read $state(-querychannel) $state(-queryblocksize)] if {[eof $state(-querychannel)]} { # This will be the last puts for the request-body. if { (![catch {fileevent $sock readable} binding]) && ($binding eq [list http::CheckEof $sock]) } { # Remove the "fileevent readable" binding of an idle # persistent socket to http::CheckEof. We can no longer # treat bytes received as junk. The server might still time # out and half-close the socket if it has not yet received # the first "puts". fileevent $sock readable {} } } puts -nonewline $sock $outStr incr state(queryoffset) [string length $outStr] if {[eof $state(-querychannel)]} { set done 1 } } } err opts]} { # Do not call Finish here, but instead let the read half of the socket # process whatever server reply there is to get. set state(posterror) $err set info [dict get $opts -errorinfo] set code [dict get $opts -code] set state(postErrorFull) [list $err $info $code] set done 1 } if {$done} { catch {flush $sock} fileevent $sock writable {} Log ^C$tk end sending request - token $token # End of writing (POST method). The request has been sent. DoneRequest $token } # Callback to the client after we've completely handled everything. if {[string length $state(-queryprogress)]} { namespace eval :: $state(-queryprogress) \ [list $token $state(querylength) $state(queryoffset)] } return } # http::Event # # Handle input on the socket. This command is the core of # the coroutine commands ${token}--EventCoroutine that are # bound to "fileevent $sock readable" and process input. # # Arguments # sock The socket receiving input. # token The token returned from http::geturl # # Side Effects # Read the socket and handle callbacks. proc http::Event {sock token} { variable http variable socketMapping variable socketRdState variable socketWrState variable socketRdQueue variable socketWrQueue variable socketPhQueue variable socketClosing variable socketPlayCmd variable socketCoEvent variable socketProxyId variable $token upvar 0 $token state set tk [namespace tail $token] while 1 { yield ##Log Event call - token $token if {![info exists state]} { Log "Event $sock with invalid token '$token' - remote close?" if {!([catch {eof $sock} tmp] || $tmp)} { if {[set d [read $sock]] ne ""} { Log "WARNING: additional data left on closed socket\ - token $token" } else { } } else { } Log ^X$tk end of response (token error) - token $token CloseSocket $sock return } else { } if {$state(state) eq "connecting"} { ##Log - connecting - token $token if { $state(reusing) && $state(-pipeline) && ($state(-timeout) > 0) && (![info exists state(after)]) } { set state(after) [after $state(-timeout) \ [list http::reset $token timeout]] } else { } if {[catch {gets $sock state(http)} nsl]} { Log "WARNING - if testing, pay special attention to this\ case (GK) which is seldom executed - token $token" if {[info exists state(reusing)] && $state(reusing)} { # The socket was closed at the server end, and closed at # this end by http::CheckEof. if {[TestForReplay $token read $nsl c]} { return } else { } # else: # This is NOT a persistent socket that has been closed since # its last use. # If any other requests are in flight or pipelined/queued, # they will be discarded. } else { # https handshake errors come here, for # Tcl 8.7 with http::SecureProxyConnect. set msg [registerError $sock] registerError $sock {} if {$msg eq {}} { set msg $nsl } Log ^X$tk end of response (error) - token $token Finish $token $msg return } } elseif {$nsl >= 0} { ##Log - connecting 1 - token $token set state(state) "header" } elseif { ([catch {eof $sock} tmp] || $tmp) && [info exists state(reusing)] && $state(reusing) } { # The socket was closed at the server end, and we didn't notice. # This is the first read - where the closure is usually first # detected. if {[TestForReplay $token read {} d]} { return } else { } # else: # This is NOT a persistent socket that has been closed since its # last use. # If any other requests are in flight or pipelined/queued, they # will be discarded. } else { } } elseif {$state(state) eq "header"} { if {[catch {gets $sock line} nhl]} { ##Log header failed - token $token Log ^X$tk end of response (error) - token $token Finish $token $nhl return } elseif {$nhl == 0} { ##Log header done - token $token Log ^E$tk end of response headers - token $token # We have now read all headers # We ignore HTTP/1.1 100 Continue returns. RFC2616 sec 8.2.3 if { ($state(http) == "") || ([regexp {^\S+\s(\d+)} $state(http) {} x] && $x == 100) } { set state(state) "connecting" continue # This was a "return" in the pre-coroutine code. } else { } # We have $state(http) so let's split it into its components. if {[regexp {^HTTP/(\S+) ([0-9]{3}) (.*)$} $state(http) \ -> httpResponse responseCode reasonPhrase] } { set state(httpResponse) $httpResponse set state(responseCode) $responseCode set state(reasonPhrase) $reasonPhrase } else { set state(httpResponse) $state(http) set state(responseCode) $state(http) set state(reasonPhrase) $state(http) } if { ([info exists state(connection)]) && ([info exists socketMapping($state(socketinfo))]) && ("keep-alive" in $state(connection)) && ($state(-keepalive)) && (!$state(reusing)) && ($state(-pipeline)) } { # Response headers received for first request on a # persistent socket. Now ready for pipelined writes (if # any). # Previous value is $token. It cannot be "pending". set socketWrState($state(socketinfo)) Wready http::NextPipelinedWrite $token } else { } # Once a "close" has been signaled, the client MUST NOT send any # more requests on that connection. # # If either the client or the server sends the "close" token in # the Connection header, that request becomes the last one for # the connection. if { ([info exists state(connection)]) && ([info exists socketMapping($state(socketinfo))]) && ("close" in $state(connection)) && ($state(-keepalive)) } { # The server warns that it will close the socket after this # response. ##Log WARNING - socket will close after response for $token # Prepare data for a call to ReplayIfClose. Log $token socket will close after this transaction # 1. Cancel socket-assignment coro events that have not yet # launched, and add the tokens to the write queue. if {[info exists socketCoEvent($state(socketinfo))]} { foreach {tok can} $socketCoEvent($state(socketinfo)) { lappend socketWrQueue($state(socketinfo)) $tok unset -nocomplain ${tok}(socketcoro) after cancel $can Log $tok Cancel socket after-idle event (Event) Log Move $tok from socketCoEvent to socketWrQueue and cancel its after idle coro } set socketCoEvent($state(socketinfo)) {} } else { } if { ($socketRdQueue($state(socketinfo)) ne {}) || ($socketWrQueue($state(socketinfo)) ne {}) || ($socketWrState($state(socketinfo)) ni [list Wready peNding $token]) } { set InFlightW $socketWrState($state(socketinfo)) if {$InFlightW in [list Wready peNding $token]} { set InFlightW Wready } else { set msg "token ${InFlightW} is InFlightW" ##Log $msg - token $token } set socketPlayCmd($state(socketinfo)) \ [list ReplayIfClose $InFlightW \ $socketRdQueue($state(socketinfo)) \ $socketWrQueue($state(socketinfo))] # - All tokens are preserved for re-use by ReplayCore. # - Queues are preserved in case of Finish with error, # but are not used for anything else because # socketClosing(*) is set below. # - Cancel the state(after) timeout events. foreach tokenVal $socketRdQueue($state(socketinfo)) { if {[info exists ${tokenVal}(after)]} { after cancel [set ${tokenVal}(after)] unset ${tokenVal}(after) } else { } # Tokens in the read queue have no (socketcoro) to # cancel. } } else { set socketPlayCmd($state(socketinfo)) \ {ReplayIfClose Wready {} {}} } # Do not allow further connections on this socket (but # geturl can add new requests to the replay). set socketClosing($state(socketinfo)) 1 } else { } set state(state) body # According to # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection # any comma-separated "Connection:" list implies keep-alive, but I # don't see this in the RFC so we'll play safe and # scan any list for "close". # Done here to support combining duplicate header field's values. if { [info exists state(connection)] && ("close" ni $state(connection)) && ("keep-alive" ni $state(connection)) } { lappend state(connection) "keep-alive" } else { } # If doing a HEAD, then we won't get any body if {$state(-validate)} { Log ^F$tk end of response for HEAD request - token $token set state(state) complete Eot $token return } elseif { ($state(method) eq {CONNECT}) && [string is integer -strict $state(responseCode)] && ($state(responseCode) >= 200) && ($state(responseCode) < 300) } { # A successful CONNECT response has no body. # (An unsuccessful CONNECT has headers and body.) # The code below is abstracted from Eot/Finish, but # keeps the socket open. catch {fileevent $state(sock) readable {}} catch {fileevent $state(sock) writable {}} set state(state) complete set state(status) ok if {[info commands ${token}--EventCoroutine] ne {}} { rename ${token}--EventCoroutine {} } if {[info commands ${token}--SocketCoroutine] ne {}} { rename ${token}--SocketCoroutine {} } if {[info exists state(socketcoro)]} { Log $token Cancel socket after-idle event (Finish) after cancel $state(socketcoro) unset state(socketcoro) } if {[info exists state(after)]} { after cancel $state(after) unset state(after) } if { [info exists state(-command)] && (![info exists state(done-command-cb)]) } { set state(done-command-cb) yes if {[catch {namespace eval :: $state(-command) $token} err]} { set state(error) [list $err $errorInfo $errorCode] set state(status) error } } return } else { } # - For non-chunked transfer we may have no body - in this case # we may get no further file event if the connection doesn't # close and no more data is sent. We can tell and must finish # up now - not later - the alternative would be to wait until # the server times out. # - In this case, the server has NOT told the client it will # close the connection, AND it has NOT indicated the resource # length EITHER by setting the Content-Length (totalsize) OR # by using chunked Transfer-Encoding. # - Do not worry here about the case (Connection: close) because # the server should close the connection. # - IF (NOT Connection: close) AND (NOT chunked encoding) AND # (totalsize == 0). if { (!( [info exists state(connection)] && ("close" in $state(connection)) ) ) && ($state(transfer) eq {}) && ($state(totalsize) == 0) } { set msg {body size is 0 and no events likely - complete} Log "$msg - token $token" set msg {(length unknown, set to 0)} Log ^F$tk end of response body {*}$msg - token $token set state(state) complete Eot $token return } else { } # We have to use binary translation to count bytes properly. lassign [fconfigure $sock -translation] trRead trWrite fconfigure $sock -translation [list binary $trWrite] if { $state(-binary) || [IsBinaryContentType $state(type)] } { # Turn off conversions for non-text data. set state(binary) 1 } else { } if {[info exists state(-channel)]} { if {$state(binary) || [llength [ContentEncoding $token]]} { fconfigure $state(-channel) -translation binary } else { } if {![info exists state(-handler)]} { # Initiate a sequence of background fcopies. fileevent $sock readable {} rename ${token}--EventCoroutine {} CopyStart $sock $token return } else { } } else { } } elseif {$nhl > 0} { # Process header lines. ##Log header - token $token - $line if {[regexp -nocase {^([^:]+):(.+)$} $line x key value]} { set key [string tolower $key] switch -- $key { content-type { set state(type) [string trim [string tolower $value]] # Grab the optional charset information. if {[regexp -nocase \ {charset\s*=\s*\"((?:[^""]|\\\")*)\"} \ $state(type) -> cs]} { set state(charset) [string map {{\"} \"} $cs] } else { regexp -nocase {charset\s*=\s*(\S+?);?} \ $state(type) -> state(charset) } } content-length { set state(totalsize) [string trim $value] } content-encoding { set state(coding) [string trim $value] } transfer-encoding { set state(transfer) \ [string trim [string tolower $value]] } proxy-connection - connection { # RFC 7230 Section 6.1 states that a comma-separated # list is an acceptable value. if {![info exists state(connectionRespFlag)]} { # This is the first "Connection" response header. # Scrub the earlier value set by iniitialisation. set state(connectionRespFlag) {} set state(connection) {} } foreach el [SplitCommaSeparatedFieldValue $value] { lappend state(connection) [string tolower $el] } } upgrade { set state(upgrade) [string trim $value] } set-cookie { if {$http(-cookiejar) ne ""} { ParseCookie $token [string trim $value] } else { } } } lappend state(meta) $key [string trim $value] } else { } } else { } } else { # Now reading body ##Log body - token $token if {[catch { if {[info exists state(-handler)]} { set n [namespace eval :: $state(-handler) [list $sock $token]] ##Log handler $n - token $token # N.B. the protocol has been set to 1.0 because the -handler # logic is not expected to handle chunked encoding. # FIXME Allow -handler with 1.1 on dechunked stacked chan. if {$state(totalsize) == 0} { # We know the transfer is complete only when the server # closes the connection - i.e. eof is not an error. set state(state) complete } else { } if {![string is integer -strict $n]} { if 1 { # Do not tolerate bad -handler - fail with error # status. set msg {the -handler command for http::geturl must\ return an integer (the number of bytes\ read)} Log ^X$tk end of response (handler error) -\ token $token Eot $token $msg } else { # Tolerate the bad -handler, and continue. The # penalty: # (a) Because the handler returns nonsense, we know # the transfer is complete only when the server # closes the connection - i.e. eof is not an # error. # (b) http::size will not be accurate. # (c) The transaction is already downgraded to 1.0 # to avoid chunked transfer encoding. It MUST # also be forced to "Connection: close" or the # HTTP/1.0 equivalent; or it MUST fail (as # above) if the server sends # "Connection: keep-alive" or the HTTP/1.0 # equivalent. set n 0 set state(state) complete } } else { } } elseif {[info exists state(transfer_final)]} { # This code forgives EOF in place of the final CRLF. set line [GetTextLine $sock] set n [string length $line] set state(state) complete if {$n > 0} { # - HTTP trailers (late response headers) are permitted # by Chunked Transfer-Encoding, and can be safely # ignored. # - Do not count these bytes in the total received for # the response body. Log "trailer of $n bytes after final chunk -\ token $token" append state(transfer_final) $line set n 0 } else { Log ^F$tk end of response body (chunked) - token $token Log "final chunk part - token $token" Eot $token } } elseif { [info exists state(transfer)] && ($state(transfer) eq "chunked") } { ##Log chunked - token $token set size 0 set hexLenChunk [GetTextLine $sock] #set ntl [string length $hexLenChunk] if {[string trim $hexLenChunk] ne ""} { scan $hexLenChunk %x size if {$size != 0} { ##Log chunk-measure $size - token $token set chunk [BlockingRead $sock $size] set n [string length $chunk] if {$n >= 0} { append state(body) $chunk incr state(log_size) [string length $chunk] ##Log chunk $n cumul $state(log_size) -\ token $token } else { } if {$size != [string length $chunk]} { Log "WARNING: mis-sized chunk:\ was [string length $chunk], should be\ $size - token $token" set n 0 set state(connection) close Log ^X$tk end of response (chunk error) \ - token $token set msg {error in chunked encoding - fetch\ terminated} Eot $token $msg } else { } # CRLF that follows chunk. # If eof, this is handled at the end of this proc. GetTextLine $sock } else { set n 0 set state(transfer_final) {} } } else { # Line expected to hold chunk length is empty, or eof. ##Log bad-chunk-measure - token $token set n 0 set state(connection) close Log ^X$tk end of response (chunk error) - token $token Eot $token {error in chunked encoding -\ fetch terminated} } } else { ##Log unchunked - token $token if {$state(totalsize) == 0} { # We know the transfer is complete only when the server # closes the connection. set state(state) complete set reqSize $state(-blocksize) } else { # Ask for the whole of the unserved response-body. # This works around a problem with a tls::socket - for # https in keep-alive mode, and a request for # $state(-blocksize) bytes, the last part of the # resource does not get read until the server times out. set reqSize [expr { $state(totalsize) - $state(currentsize)}] # The workaround fails if reqSize is # capped at $state(-blocksize). # set reqSize [expr {min($reqSize, $state(-blocksize))}] } set c $state(currentsize) set t $state(totalsize) ##Log non-chunk currentsize $c of totalsize $t -\ token $token set block [read $sock $reqSize] set n [string length $block] if {$n >= 0} { append state(body) $block ##Log non-chunk [string length $state(body)] -\ token $token } else { } } # This calculation uses n from the -handler, chunked, or # unchunked case as appropriate. if {[info exists state]} { if {$n >= 0} { incr state(currentsize) $n set c $state(currentsize) set t $state(totalsize) ##Log another $n currentsize $c totalsize $t -\ token $token } else { } # If Content-Length - check for end of data. if { ($state(totalsize) > 0) && ($state(currentsize) >= $state(totalsize)) } { Log ^F$tk end of response body (unchunked) -\ token $token set state(state) complete Eot $token } else { } } else { } } err]} { Log ^X$tk end of response (error ${err}) - token $token Finish $token $err return } else { if {[info exists state(-progress)]} { namespace eval :: $state(-progress) \ [list $token $state(totalsize) $state(currentsize)] } else { } } } # catch as an Eot above may have closed the socket already # $state(state) may be connecting, header, body, or complete if {(![catch {eof $sock} eof]) && $eof} { # [eof sock] succeeded and the result was 1 ##Log eof - token $token if {[info exists $token]} { set state(connection) close if {$state(state) eq "complete"} { # This includes all cases in which the transaction # can be completed by eof. # The value "complete" is set only in http::Event, and it is # used only in the test above. Log ^F$tk end of response body (unchunked, eof) -\ token $token Eot $token } else { # Premature eof. Log ^X$tk end of response (unexpected eof) - token $token Eot $token eof } } else { # open connection closed on a token that has been cleaned up. Log ^X$tk end of response (token error) - token $token CloseSocket $sock } } else { # EITHER [eof sock] failed - presumed done by Eot # OR [eof sock] succeeded and the result was 0 } } return } # http::TestForReplay # # Command called if eof is discovered when a socket is first used for a # new transaction. Typically this occurs if a persistent socket is used # after a period of idleness and the server has half-closed the socket. # # token - the connection token returned by http::geturl # doing - "read" or "write" # err - error message, if any # caller - code to identify the caller - used only in logging # # Return Value: boolean, true iff the command calls http::ReplayIfDead. proc http::TestForReplay {token doing err caller} { variable http variable $token upvar 0 $token state set tk [namespace tail $token] if {$doing eq "read"} { set code Q set action response set ing reading } else { set code P set action request set ing writing } if {$err eq {}} { set err "detect eof when $ing (server timed out?)" } if {$state(method) eq "POST" && !$http(-repost)} { # No Replay. # The present transaction will end when Finish is called. # That call to Finish will abort any other transactions # currently in the write queue. # For calls from http::Event this occurs when execution # reaches the code block at the end of that proc. set msg {no retry for POST with http::config -repost 0} Log reusing socket failed "($caller)" - $msg - token $token Log error - $err - token $token Log ^X$tk end of $action (error) - token $token return 0 } else { # Replay. set msg {try a new socket} Log reusing socket failed "($caller)" - $msg - token $token Log error - $err - token $token Log ^$code$tk Any unfinished (incl this one) failed - token $token ReplayIfDead $token $doing return 1 } } # http::IsBinaryContentType -- # # Determine if the content-type means that we should definitely transfer # the data as binary. [Bug 838e99a76d] # # Arguments # type The content-type of the data. # # Results: # Boolean, true if we definitely should be binary. proc http::IsBinaryContentType {type} { lassign [split [string tolower $type] "/;"] major minor if {$major eq "text"} { return false } # There's a bunch of XML-as-application-format things about. See RFC 3023 # and so on. if {$major eq "application"} { set minor [string trimright $minor] if {$minor in {"json" "xml" "xml-external-parsed-entity" "xml-dtd"}} { return false } } # Not just application/foobar+xml but also image/svg+xml, so let us not # restrict things for now... if {[string match "*+xml" $minor]} { return false } return true } proc http::ParseCookie {token value} { variable http variable CookieRE variable $token upvar 0 $token state if {![regexp $CookieRE $value -> cookiename cookieval opts]} { # Bad cookie! No biscuit! return } # Convert the options into a list before feeding into the cookie store; # ugly, but quite easy. set realopts {hostonly 1 path / secure 0 httponly 0} dict set realopts origin $state(host) dict set realopts domain $state(host) foreach option [split [regsub -all {;\s+} $opts \u0000] \u0000] { regexp {^(.*?)(?:=(.*))?$} $option -> optname optval switch -exact -- [string tolower $optname] { expires { if {[catch { #Sun, 06 Nov 1994 08:49:37 GMT dict set realopts expires \ [clock scan $optval -format "%a, %d %b %Y %T %Z"] }] && [catch { # Google does this one #Mon, 01-Jan-1990 00:00:00 GMT dict set realopts expires \ [clock scan $optval -format "%a, %d-%b-%Y %T %Z"] }] && [catch { # This is in the RFC, but it is also in the original # Netscape cookie spec, now online at: # #Sunday, 06-Nov-94 08:49:37 GMT dict set realopts expires \ [clock scan $optval -format "%A, %d-%b-%y %T %Z"] }]} {catch { #Sun Nov 6 08:49:37 1994 dict set realopts expires \ [clock scan $optval -gmt 1 -format "%a %b %d %T %Y"] }} } max-age { # Normalize if {[string is integer -strict $optval]} { dict set realopts expires [expr {[clock seconds] + $optval}] } } domain { # From the domain-matches definition [RFC 2109, section 2]: # Host A's name domain-matches host B's if [...] # A is a FQDN string and has the form NB, where N is a # non-empty name string, B has the form .B', and B' is a # FQDN string. (So, x.y.com domain-matches .y.com but # not y.com.) if {$optval ne "" && ![string match *. $optval]} { dict set realopts domain [string trimleft $optval "."] dict set realopts hostonly [expr { ! [string match .* $optval] }] } } path { if {[string match /* $optval]} { dict set realopts path $optval } } secure - httponly { dict set realopts [string tolower $optname] 1 } } } dict set realopts key $cookiename dict set realopts value $cookieval {*}$http(-cookiejar) storeCookie $realopts } # http::GetTextLine -- # # Get one line with the stream in crlf mode. # Used if Transfer-Encoding is chunked, to read the line that # reports the size of the following chunk. # Empty line is not distinguished from eof. The caller must # be able to handle this. # # Arguments # sock The socket receiving input. # # Results: # The line of text, without trailing newline proc http::GetTextLine {sock} { set tr [fconfigure $sock -translation] lassign $tr trRead trWrite fconfigure $sock -translation [list crlf $trWrite] set r [BlockingGets $sock] fconfigure $sock -translation $tr return $r } # http::BlockingRead # # Replacement for a blocking read. # The caller must be a coroutine. # Used when we expect to read a chunked-encoding # chunk of known size. proc http::BlockingRead {sock size} { if {$size < 1} { return } set result {} while 1 { set need [expr {$size - [string length $result]}] set block [read $sock $need] set eof [expr {[catch {eof $sock} tmp] || $tmp}] append result $block if {[string length $result] >= $size || $eof} { return $result } else { yield } } } # http::BlockingGets # # Replacement for a blocking gets. # The caller must be a coroutine. # Empty line is not distinguished from eof. The caller must # be able to handle this. proc http::BlockingGets {sock} { while 1 { set count [gets $sock line] set eof [expr {[catch {eof $sock} tmp] || $tmp}] if {$count >= 0 || $eof} { return $line } else { yield } } } # http::CopyStart # # Error handling wrapper around fcopy # # Arguments # sock The socket to copy from # token The token returned from http::geturl # # Side Effects # This closes the connection upon error proc http::CopyStart {sock token {initial 1}} { upvar 0 $token state if {[info exists state(transfer)] && $state(transfer) eq "chunked"} { foreach coding [ContentEncoding $token] { if {$coding eq {deflateX}} { # Use the standards-compliant choice. set coding2 decompress } else { set coding2 $coding } lappend state(zlib) [zlib stream $coding2] } MakeTransformationChunked $sock [namespace code [list CopyChunk $token]] } else { if {$initial} { foreach coding [ContentEncoding $token] { if {$coding eq {deflateX}} { # Use the standards-compliant choice. set coding2 decompress } else { set coding2 $coding } zlib push $coding2 $sock } } if {[catch { # FIXME Keep-Alive on https tls::socket with unchunked transfer # hangs until the server times out. A workaround is possible, as for # the case without -channel, but it does not use the neat "fcopy" # solution. fcopy $sock $state(-channel) -size $state(-blocksize) -command \ [list http::CopyDone $token] } err]} { Finish $token $err } } return } proc http::CopyChunk {token chunk} { upvar 0 $token state if {[set count [string length $chunk]]} { incr state(currentsize) $count if {[info exists state(zlib)]} { foreach stream $state(zlib) { set chunk [$stream add $chunk] } } puts -nonewline $state(-channel) $chunk if {[info exists state(-progress)]} { namespace eval :: [linsert $state(-progress) end \ $token $state(totalsize) $state(currentsize)] } } else { Log "CopyChunk Finish - token $token" if {[info exists state(zlib)]} { set excess "" foreach stream $state(zlib) { catch { $stream put -finalize $excess set excess "" set overflood "" while {[set overflood [$stream get]] ne ""} { append excess $overflood } } } puts -nonewline $state(-channel) $excess foreach stream $state(zlib) { $stream close } unset state(zlib) } Eot $token ;# FIX ME: pipelining. } return } # http::CopyDone # # fcopy completion callback # # Arguments # token The token returned from http::geturl # count The amount transferred # # Side Effects # Invokes callbacks proc http::CopyDone {token count {error {}}} { variable $token upvar 0 $token state set sock $state(sock) incr state(currentsize) $count if {[info exists state(-progress)]} { namespace eval :: $state(-progress) \ [list $token $state(totalsize) $state(currentsize)] } # At this point the token may have been reset. if {[string length $error]} { Finish $token $error } elseif {[catch {eof $sock} iseof] || $iseof} { Eot $token } else { CopyStart $sock $token 0 } return } # http::Eot # # Called when either: # a. An eof condition is detected on the socket. # b. The client decides that the response is complete. # c. The client detects an inconsistency and aborts the transaction. # # Does: # 1. Set state(status) # 2. Reverse any Content-Encoding # 3. Convert charset encoding and line ends if necessary # 4. Call http::Finish # # Arguments # token The token returned from http::geturl # force (previously) optional, has no effect # reason - "eof" means premature EOF (not EOF as the natural end of # the response) # - "" means completion of response, with or without EOF # - anything else describes an error condition other than # premature EOF. # # Side Effects # Clean up the socket proc http::Eot {token {reason {}}} { variable $token upvar 0 $token state if {$reason eq "eof"} { # Premature eof. set state(status) eof set reason {} } elseif {$reason ne ""} { # Abort the transaction. set state(status) $reason } else { # The response is complete. set state(status) ok } if {[string length $state(body)] > 0} { if {[catch { foreach coding [ContentEncoding $token] { if {$coding eq {deflateX}} { # First try the standards-compliant choice. set coding2 decompress if {[catch {zlib $coding2 $state(body)} result]} { # If that fails, try the MS non-compliant choice. set coding2 inflate set state(body) [zlib $coding2 $state(body)] } else { # error {failed at standards-compliant deflate} set state(body) $result } } else { set state(body) [zlib $coding $state(body)] } } } err]} { Log "error doing decompression for token $token: $err" Finish $token $err return } if {!$state(binary)} { # If we are getting text, set the incoming channel's encoding # correctly. iso8859-1 is the RFC default, but this could be any # IANA charset. However, we only know how to convert what we have # encodings for. set enc [CharsetToEncoding $state(charset)] if {$enc ne "binary"} { if {[package vsatisfies [package provide Tcl] 9.0-]} { set state(body) [encoding convertfrom -profile replace $enc $state(body)] } else { set state(body) [encoding convertfrom $enc $state(body)] } } # Translate text line endings. set state(body) [string map {\r\n \n \r \n} $state(body)] } if {[info exists state(-guesstype)] && $state(-guesstype)} { GuessType $token } } Finish $token $reason return } # ------------------------------------------------------------------------------ # Proc http::GuessType # ------------------------------------------------------------------------------ # Command to attempt limited analysis of a resource with undetermined # Content-Type, i.e. "application/octet-stream". This value can be set for two # reasons: # (a) by the server, in a Content-Type header # (b) by http::geturl, as the default value if the server does not supply a # Content-Type header. # # This command converts a resource if: # (1) it has type application/octet-stream # (2) it begins with an XML declaration "?" # (3) one tag is named "encoding" and has a recognised value; or no "encoding" # tag exists (defaulting to utf-8) # # RFC 9110 Sec. 8.3 states: # "If a Content-Type header field is not present, the recipient MAY either # assume a media type of "application/octet-stream" ([RFC2046], Section 4.5.1) # or examine the data to determine its type." # # The RFC goes on to describe the pitfalls of "MIME sniffing", including # possible security risks. # # Arguments: # token - connection token # # Return Value: (boolean) true iff a change has been made # ------------------------------------------------------------------------------ proc http::GuessType {token} { variable $token upvar 0 $token state if {$state(type) ne {application/octet-stream}} { return 0 } set body $state(body) # e.g. { ...} if {![regexp -nocase -- {^<[?]xml[[:space:]][^>?]*[?]>} $body match]} { return 0 } # e.g. {} set contents [regsub -- {[[:space:]]+} $match { }] set contents [string range [string tolower $contents] 6 end-2] # e.g. {version="1.0" encoding="utf-8"} # without excess whitespace or upper-case letters if {![regexp -- {^([^=" ]+="[^"]+" )+$} "$contents "]} { return 0 } # The application/xml default encoding: set res utf-8 set tagList [regexp -all -inline -- {[^=" ]+="[^"]+"} $contents] foreach tag $tagList { regexp -- {([^=" ]+)="([^"]+)"} $tag -> name value if {$name eq {encoding}} { set res $value } } set enc [CharsetToEncoding $res] if {$enc eq "binary"} { return 0 } if {[package vsatisfies [package provide Tcl] 9.0-]} { set state(body) [encoding convertfrom -profile replace $enc $state(body)] } else { set state(body) [encoding convertfrom $enc $state(body)] } set state(body) [string map {\r\n \n \r \n} $state(body)] set state(type) application/xml set state(binary) 0 set state(charset) $res return 1 } # http::wait -- # # See documentation for details. # # Arguments: # token Connection token. # # Results: # The status after the wait. proc http::wait {token} { variable $token upvar 0 $token state if {![info exists state(status)] || $state(status) eq ""} { # We must wait on the original variable name, not the upvar alias vwait ${token}(status) } return [status $token] } # http::formatQuery -- # # See documentation for details. Call http::formatQuery with an even # number of arguments, where the first is a name, the second is a value, # the third is another name, and so on. # # Arguments: # args A list of name-value pairs. # # Results: # TODO proc http::formatQuery {args} { if {[llength $args] % 2} { return \ -code error \ -errorcode [list HTTP BADARGCNT $args] \ {Incorrect number of arguments, must be an even number.} } set result "" set sep "" foreach i $args { append result $sep [quoteString $i] if {$sep eq "="} { set sep & } else { set sep = } } return $result } # http::quoteString -- # # Do x-www-urlencoded character mapping # # Arguments: # string The string the needs to be encoded # # Results: # The encoded string proc http::quoteString {string} { variable http variable formMap # The spec says: "non-alphanumeric characters are replaced by '%HH'". Use # a pre-computed map and [string map] to do the conversion (much faster # than [regsub]/[subst]). [Bug 1020491] if {[package vsatisfies [package provide Tcl] 9.0-]} { set string [encoding convertto -profile replace $http(-urlencoding) $string] } else { set string [encoding convertto $http(-urlencoding) $string] } return [string map $formMap $string] } # http::ProxyRequired -- # Default proxy filter. # # Arguments: # host The destination host # # Results: # The current proxy settings proc http::ProxyRequired {host} { variable http if {(![info exists http(-proxyhost)]) || ($http(-proxyhost) eq {})} { return } if {![info exists http(-proxyport)] || ($http(-proxyport) eq {})} { set port 8080 } else { set port $http(-proxyport) } # Simple test (cf. autoproxy) for hosts that must be accessed directly, # not through the proxy server. foreach domain $http(-proxynot) { if {[string match -nocase $domain $host]} { return {} } } return [list $http(-proxyhost) $port] } # http::CharsetToEncoding -- # # Tries to map a given IANA charset to a tcl encoding. If no encoding # can be found, returns binary. # proc http::CharsetToEncoding {charset} { variable encodings set charset [string tolower $charset] if {[regexp {iso-?8859-([0-9]+)} $charset -> num]} { set encoding "iso8859-$num" } elseif {[regexp {iso-?2022-(jp|kr)} $charset -> ext]} { set encoding "iso2022-$ext" } elseif {[regexp {shift[-_]?jis} $charset]} { set encoding "shiftjis" } elseif {[regexp {(?:windows|cp)-?([0-9]+)} $charset -> num]} { set encoding "cp$num" } elseif {$charset eq "us-ascii"} { set encoding "ascii" } elseif {[regexp {(?:iso-?)?lat(?:in)?-?([0-9]+)} $charset -> num]} { switch -- $num { 5 {set encoding "iso8859-9"} 1 - 2 - 3 { set encoding "iso8859-$num" } default { set encoding "binary" } } } else { # other charset, like euc-xx, utf-8,... may directly map to encoding set encoding $charset } set idx [lsearch -exact $encodings $encoding] if {$idx >= 0} { return $encoding } else { return "binary" } } # ------------------------------------------------------------------------------ # Proc http::ContentEncoding # ------------------------------------------------------------------------------ # Return the list of content-encoding transformations we need to do in order. # # -------------------------------------------------------------------------- # Options for Accept-Encoding, Content-Encoding: the switch command # -------------------------------------------------------------------------- # The symbol deflateX allows http to attempt both versions of "deflate", # unless there is a -channel - for a -channel, only "decompress" is tried. # Alternative/extra lines for switch: # The standards-compliant version of "deflate" can be chosen with: # deflate { lappend r decompress } # The Microsoft non-compliant version of "deflate" can be chosen with: # deflate { lappend r inflate } # The previously used implementation of "compress", which appears to be # incorrect and is rarely used by web servers, can be chosen with: # compress - x-compress { lappend r decompress } # -------------------------------------------------------------------------- # # Arguments: # token - Connection token. # # Return Value: list # ------------------------------------------------------------------------------ proc http::ContentEncoding {token} { upvar 0 $token state set r {} if {[info exists state(coding)]} { foreach coding [split $state(coding) ,] { switch -exact -- $coding { deflate { lappend r deflateX } gzip - x-gzip { lappend r gunzip } identity {} br { return -code error\ "content-encoding \"br\" not implemented" } default { Log "unknown content-encoding \"$coding\" ignored" } } } } return $r } proc http::ReceiveChunked {chan command} { set data "" set size -1 yield while {1} { chan configure $chan -translation {crlf binary} while {[gets $chan line] < 1} { yield } chan configure $chan -translation {binary binary} if {[scan $line %x size] != 1} { return -code error "invalid size: \"$line\"" } set chunk "" while {$size && ![chan eof $chan]} { set part [chan read $chan $size] incr size -[string length $part] append chunk $part } if {[catch { uplevel #0 [linsert $command end $chunk] }]} { http::Log "Error in callback: $::errorInfo" } if {[string length $chunk] == 0} { # channel might have been closed in the callback catch {chan event $chan readable {}} return } } } # http::SplitCommaSeparatedFieldValue -- # Return the individual values of a comma-separated field value. # # Arguments: # fieldValue Comma-separated header field value. # # Results: # List of values. proc http::SplitCommaSeparatedFieldValue {fieldValue} { set r {} foreach el [split $fieldValue ,] { lappend r [string trim $el] } return $r } # http::GetFieldValue -- # Return the value of a header field. # # Arguments: # headers Headers key-value list # fieldName Name of header field whose value to return. # # Results: # The value of the fieldName header field # # Field names are matched case-insensitively (RFC 7230 Section 3.2). # # If the field is present multiple times, it is assumed that the field is # defined as a comma-separated list and the values are combined (by separating # them with commas, see RFC 7230 Section 3.2.2) and returned at once. proc http::GetFieldValue {headers fieldName} { set r {} foreach {field value} $headers { if {[string equal -nocase $fieldName $field]} { if {$r eq {}} { set r $value } else { append r ", $value" } } } return $r } proc http::MakeTransformationChunked {chan command} { coroutine [namespace current]::dechunk$chan ::http::ReceiveChunked $chan $command chan event $chan readable [namespace current]::dechunk$chan return } interp alias {} http::data {} http::responseBody interp alias {} http::code {} http::responseLine interp alias {} http::mapReply {} http::quoteString interp alias {} http::meta {} http::responseHeaders interp alias {} http::metaValue {} http::responseHeaderValue interp alias {} http::ncode {} http::responseCode # ------------------------------------------------------------------------------ # Proc http::socketAsCallback # ------------------------------------------------------------------------------ # Command to use in place of ::socket as the value of ::tls::socketCmd. # This command does the same as http::AltSocket, and also handles https # connections through a proxy server. # # Notes. # - The proxy server works differently for https and http. This implementation # is for https. The proxy for http is implemented in http::CreateToken (in # code that was previously part of http::geturl). # - This code implicitly uses the tls options set for https in a call to # http::register, and does not need to call commands tls::*. This simple # implementation is possible because tls uses a callback to ::socket that can # be redirected by changing the value of ::tls::socketCmd. # # Arguments: # args - as for ::socket # # Return Value: a socket identifier # ------------------------------------------------------------------------------ proc http::socketAsCallback {args} { variable http set targ [lsearch -exact $args -type] if {$targ != -1} { set token [lindex $args $targ+1] upvar 0 ${token} state set protoProxyConn $state(protoProxyConn) } else { set protoProxyConn 0 } set host [lindex $args end-1] set port [lindex $args end] if { ($http(-proxyfilter) ne {}) && (![catch {$http(-proxyfilter) $host} proxy]) && $protoProxyConn } { set phost [lindex $proxy 0] set pport [lindex $proxy 1] } else { set phost {} set pport {} } if {$phost eq ""} { set sock [::http::AltSocket {*}$args] } else { set sock [::http::SecureProxyConnect {*}$args $phost $pport] } return $sock } # ------------------------------------------------------------------------------ # Proc http::SecureProxyConnect # ------------------------------------------------------------------------------ # Command to open a socket through a proxy server to a remote server for use by # tls. The caller must perform the tls handshake. # # Notes # - Based on patch supplied by Melissa Chawla in ticket 1173760, and # Proxy-Authorization header cf. autoproxy by Pat Thoyts. # - Rewritten as a call to http::geturl, because response headers and body are # needed if the CONNECT request fails. CONNECT is implemented for this case # only, by state(bypass). # - FUTURE WORK: give http::geturl a -connect option for a general CONNECT. # - The request header Proxy-Connection is discouraged in RFC 7230 (June 2014), # RFC 9112 (June 2022). # # Arguments: # args - as for ::socket, ending in host, port; with proxy host, proxy # port appended. # # Return Value: a socket identifier # ------------------------------------------------------------------------------ proc http::SecureProxyConnect {args} { variable http variable ConnectVar variable ConnectCounter variable failedProxyValues set varName ::http::ConnectVar([incr ConnectCounter]) # Extract (non-proxy) target from args. set host [lindex $args end-3] set port [lindex $args end-2] set args [lreplace $args end-3 end-2] # Proxy server URL for connection. # This determines where the socket is opened. set phost [lindex $args end-1] set pport [lindex $args end] if {[string first : $phost] != -1} { # IPv6 address, wrap it in [] so we can append :pport set phost "\[${phost}\]" } set url http://${phost}:${pport} # Elements of args other than host and port are not used when # AsyncTransaction opens a socket. Those elements are -async and the # -type $tokenName for the https transaction. Option -async is used by # AsyncTransaction anyway, and -type $tokenName should not be # propagated: the proxy request adds its own -type value. set targ [lsearch -exact $args -type] if {$targ != -1} { # Record in the token that this is a proxy call. set token [lindex $args $targ+1] upvar 0 ${token} state set tim $state(-timeout) set state(proxyUsed) SecureProxyFailed # This value is overwritten with "SecureProxy" below if the CONNECT is # successful. If it is unsuccessful, the socket will be closed # below, and so in this unsuccessful case there are no other transactions # whose (proxyUsed) must be updated. } else { set tim 0 } if {$tim == 0} { # Do not use infinite timeout for the proxy. set tim 30000 } # Prepare and send a CONNECT request to the proxy, using # code similar to http::geturl. set requestHeaders [list Host $host] lappend requestHeaders Connection keep-alive if {$http(-proxyauth) != {}} { lappend requestHeaders Proxy-Authorization $http(-proxyauth) } set token2 [CreateToken $url -keepalive 0 -timeout $tim \ -headers $requestHeaders -command [list http::AllDone $varName]] variable $token2 upvar 0 $token2 state2 # Kludges: # Setting this variable overrides the HTTP request line and also allows # -headers to override the Connection: header set by -keepalive. # The arguments "-keepalive 0" ensure that when Finish is called for an # unsuccessful request, the socket is always closed. set state2(bypass) "CONNECT $host:$port HTTP/1.1" AsyncTransaction $token2 if {[info coroutine] ne {}} { # All callers in the http package are coroutines launched by # the event loop. # The cwait command requires a coroutine because it yields # to the caller; $varName is traced and the coroutine resumes # when the variable is written. cwait $varName } else { return -code error {code must run in a coroutine} # For testing with a non-coroutine caller outside the http package. # vwait $varName } unset $varName if { ($state2(state) ne "complete") || ($state2(status) ne "ok") || (![string is integer -strict $state2(responseCode)]) } { set msg {the HTTP request to the proxy server did not return a valid\ and complete response} if {[info exists state2(error)]} { append msg ": " [lindex $state2(error) 0] } cleanup $token2 return -code error $msg } set code $state2(responseCode) if {($code >= 200) && ($code < 300)} { # All OK. The caller in package tls will now call "tls::import $sock". # The cleanup command does not close $sock. # Other tidying was done in http::Event. # If this is a persistent socket, any other transactions that are # already marked to use the socket will have their (proxyUsed) updated # when http::OpenSocket calls http::ConfigureNewSocket. set state(proxyUsed) SecureProxy set sock $state2(sock) cleanup $token2 return $sock } if {$targ != -1} { # Non-OK HTTP status code; token is known because option -type # (cf. targ) was passed through tcltls, and so the useful # parts of the proxy's response can be copied to state(*). # Do not copy state2(sock). # Return the proxy response to the caller of geturl. foreach name $failedProxyValues { if {[info exists state2($name)]} { set state($name) $state2($name) } } set state(connection) close set msg "proxy connect failed: $code" # - This error message will be detected by http::OpenSocket and will # cause it to present the proxy's HTTP response as that of the # original $token transaction, identified only by state(proxyUsed) # as the response of the proxy. # - The cases where this would mislead the caller of http::geturl are # given a different value of msg (below) so that http::OpenSocket will # treat them as errors, but will preserve the $token array for # inspection by the caller. # - Status code 305 (Proxy Required) was deprecated for security reasons # in RFC 2616 (June 1999) and in any case should never be served by a # proxy. # - Other 3xx responses from the proxy are inappropriate, and should not # occur. # - A 401 response from the proxy is inappropriate, and should not # occur. It would be confusing if returned to the caller. if {($code >= 300) && ($code < 400)} { set msg "the proxy server responded to the HTTP request with an\ inappropriate $code redirect" set loc [responseHeaderValue $token2 location] if {$loc ne {}} { append msg "to " $loc } } elseif {($code == 401)} { set msg "the proxy server responded to the HTTP request with an\ inappropriate 401 request for target-host credentials" } else { } } else { set msg "connection to proxy failed with status code $code" } # - ${token2}(sock) has already been closed because -keepalive 0. # - Error return does not pass the socket ID to the # $token transaction, which retains its socket placeholder. cleanup $token2 return -code error $msg } proc http::AllDone {varName args} { set $varName done return } # ------------------------------------------------------------------------------ # Proc http::AltSocket # ------------------------------------------------------------------------------ # This command is a drop-in replacement for ::socket. # Arguments and return value as for ::socket. # # Notes. # - http::AltSocket is specified in place of ::socket by the definition of # urlTypes in the namespace header of this file (http.tcl). # - The command makes a simple call to ::socket unless the user has called # http::config to change the value of -threadlevel from the default value 0. # - For -threadlevel 1 or 2, if the Thread package is available, the command # waits in the event loop while the socket is opened in another thread. This # is a workaround for bug [824251] - it prevents http::geturl from blocking # the event loop if the DNS lookup or server connection is slow. # - FIXME Use a thread pool if connections are very frequent. # - FIXME The peer thread can transfer the socket only to the main interpreter # in the present thread. Therefore this code works only if this script runs # in the main interpreter. In a child interpreter, the parent must alias a # command to ::http::AltSocket in the child, run http::AltSocket in the # parent, and then transfer the socket to the child. # - The http::AltSocket command is simple, and can easily be replaced with an # alternative command that uses a different technique to open a socket while # entering the event loop. # - Unexpected behaviour by thread::send -async (Thread 2.8.6). # An error in thread::send -async causes return of just the error message # (not the expected 3 elements), and raises a bgerror in the main thread. # Hence wrap the command with catch as a precaution. # - Bug in Thread 2.8.8 - on Windows, read/write operations fail on a socket # moved from another thread by thread::transfer. # ------------------------------------------------------------------------------ proc http::AltSocket {args} { variable ThreadVar variable ThreadCounter variable http LoadThreadIfNeeded set targ [lsearch -exact $args -type] if {$targ != -1} { set token [lindex $args $targ+1] set args [lreplace $args $targ $targ+1] upvar 0 $token state } if {$http(usingThread) && [info exists state] && $state(protoSockThread)} { } else { # Use plain "::socket". This is the default. return [eval ::socket $args] } set defcmd ::socket set sockargs $args set script " set code \[catch { [list proc ::SockInThread {caller defcmd sockargs} [info body ::http::SockInThread]] [list ::SockInThread [thread::id] $defcmd $sockargs] } result opts\] list \$code \$opts \$result " set state(tid) [thread::create] set varName ::http::ThreadVar([incr ThreadCounter]) thread::send -async $state(tid) $script $varName Log >T Thread Start Wait $args -- coro [info coroutine] $varName if {[info coroutine] ne {}} { # All callers in the http package are coroutines launched by # the event loop. # The cwait command requires a coroutine because it yields # to the caller; $varName is traced and the coroutine resumes # when the variable is written. cwait $varName } else { return -code error {code must run in a coroutine} # For testing with a non-coroutine caller outside the http package. # vwait $varName } Log >U Thread End Wait $args -- coro [info coroutine] $varName [set $varName] thread::release $state(tid) set state(tid) {} set result [set $varName] unset $varName if {(![string is list $result]) || ([llength $result] != 3)} { return -code error "result from peer thread is not a list of\ length 3: it is \n$result" } lassign $result threadCode threadDict threadResult if {($threadCode != 0)} { # This is an error in thread::send. Return the lot. return -options $threadDict -code error $threadResult } # Now the results of the catch in the peer thread. lassign $threadResult catchCode errdict sock if {($catchCode == 0) && ($sock ni [chan names])} { return -code error {Transfer of socket from peer thread failed.\ Check that this script is not running in a child interpreter.} } return -options $errdict -code $catchCode $sock } # The commands below are dependencies of http::AltSocket and # http::SecureProxyConnect and are not used elsewhere. # ------------------------------------------------------------------------------ # Proc http::LoadThreadIfNeeded # ------------------------------------------------------------------------------ # Command to load the Thread package if it is needed. If it is needed and not # loadable, the outcome depends on $http(-threadlevel): # value 0 => Thread package not required, no problem # value 1 => operate as if -threadlevel 0 # value 2 => error return # # The command assigns a value to http(usingThread), which records whether # command http::AltSocket can use a separate thread. # # Arguments: none # Return Value: none # ------------------------------------------------------------------------------ proc http::LoadThreadIfNeeded {} { variable http if {$http(-threadlevel) == 0} { set http(usingThread) 0 return } if {[catch {package require Thread}]} { if {$http(-threadlevel) == 2} { set msg {[http::config -threadlevel] has value 2,\ but the Thread package is not available} return -code error $msg } set http(usingThread) 0 return } set http(usingThread) 1 return } # ------------------------------------------------------------------------------ # Proc http::SockInThread # ------------------------------------------------------------------------------ # Command http::AltSocket is a ::socket replacement. It defines and runs this # command, http::SockInThread, in a peer thread. # # Arguments: # caller # defcmd # sockargs # # Return value: list of values that describe the outcome. The return is # intended to be a normal (non-error) return in all cases. # ------------------------------------------------------------------------------ proc http::SockInThread {caller defcmd sockargs} { package require Thread set catchCode [catch {eval $defcmd $sockargs} sock errdict] if {$catchCode == 0} { set catchCode [catch {thread::transfer $caller $sock; set sock} sock errdict] } return [list $catchCode $errdict $sock] } # ------------------------------------------------------------------------------ # Proc http::cwaiter::cwait # ------------------------------------------------------------------------------ # Command to substitute for vwait, without the ordering issues. # A command that uses cwait must be a coroutine that is launched by an event, # e.g. fileevent or after idle, and has no calling code to be resumed upon # "yield". It cannot return a value. # # Arguments: # varName - fully-qualified name of the variable that the calling script # will write to resume the coroutine. Any scalar variable or # array element is permitted. # coroName - (optional) name of the coroutine to be called when varName is # written - defaults to this coroutine # timeout - (optional) timeout value in ms # timeoutValue - (optional) value to assign to varName if there is a timeout # # Return Value: none # ------------------------------------------------------------------------------ namespace eval http::cwaiter { namespace export cwait variable log {} variable logOn 0 } proc http::cwaiter::cwait { varName {coroName {}} {timeout {}} {timeoutValue {}} } { set thisCoro [info coroutine] if {$thisCoro eq {}} { return -code error {cwait cannot be called outside a coroutine} } if {$coroName eq {}} { set coroName $thisCoro } if {[string range $varName 0 1] ne {::}} { return -code error {argument varName must be fully qualified} } if {$timeout eq {}} { set toe {} } elseif {[string is integer -strict $timeout] && ($timeout > 0)} { set toe [after $timeout [list set $varName $timeoutValue]] } else { return -code error {if timeout is supplied it must be a positive integer} } set cmd [list ::http::cwaiter::CwaitHelper $varName $coroName $toe] trace add variable $varName write $cmd CoLog "Yield $varName $coroName" yield CoLog "Resume $varName $coroName" return } # ------------------------------------------------------------------------------ # Proc http::cwaiter::CwaitHelper # ------------------------------------------------------------------------------ # Helper command called by the trace set by cwait. # - Ignores the arguments added by trace. # - A simple call to $coroName works, and in error cases gives a suitable stack # trace, but because it is inside a trace the headline error message is # something like {can't set "::Result(6)": error}, not the actual # error. So let the trace command return. # - Remove the trace immediately. We don't want multiple calls. # ------------------------------------------------------------------------------ proc http::cwaiter::CwaitHelper {varName coroName toe args} { CoLog "got $varName for $coroName" set cmd [list ::http::cwaiter::CwaitHelper $varName $coroName $toe] trace remove variable $varName write $cmd after cancel $toe after 0 $coroName return } # ------------------------------------------------------------------------------ # Proc http::cwaiter::LogInit # ------------------------------------------------------------------------------ # Call this command to initiate debug logging and clear the log. # ------------------------------------------------------------------------------ proc http::cwaiter::LogInit {} { variable log variable logOn set log {} set logOn 1 return } proc http::cwaiter::LogRead {} { variable log return $log } proc http::cwaiter::CoLog {msg} { variable log variable logOn if {$logOn} { append log $msg \n } return } namespace eval http { namespace import ::http::cwaiter::* } # Local variables: # indent-tabs-mode: t # End: tcl9.0.1/library/http/pkgIndex.tcl0000644000175000017500000000043014726623136016453 0ustar sergeisergeiif {![package vsatisfies [package provide Tcl] 8.6-]} {return} package ifneeded http 2.10.0 [list tclPkgSetup $dir http 2.10.0 {{http.tcl source {::http::config ::http::formatQuery ::http::geturl ::http::reset ::http::wait ::http::register ::http::unregister ::http::mapReply}}}] tcl9.0.1/library/opt/0000755000175000017500000000000014731057544014024 5ustar sergeisergeitcl9.0.1/library/opt/optparse.tcl0000644000175000017500000007350614726623136016400 0ustar sergeisergei# optparse.tcl -- # # (private) Option parsing package # Primarily used internally by the safe:: code. # # WARNING: This code will go away in a future release # of Tcl. It is NOT supported and you should not rely # on it. If your code does rely on this package you # may directly incorporate this code into your application. package require Tcl 8.5- # When this version number changes, update the pkgIndex.tcl file # and the install directory in the Makefiles. package provide opt 0.4.9 namespace eval ::tcl { # Exported APIs namespace export OptKeyRegister OptKeyDelete OptKeyError OptKeyParse \ OptProc OptProcArgGiven OptParse \ Lempty Lget \ Lassign Lvarpop Lvarpop1 Lvarset Lvarincr \ SetMax SetMin ################# Example of use / 'user documentation' ################### proc OptCreateTestProc {} { # Defines ::tcl::OptParseTest as a test proc with parsed arguments # (can't be defined before the code below is loaded (before "OptProc")) # Every OptProc give usage information on "procname -help". # Try "tcl::OptParseTest -help" and "tcl::OptParseTest -a" and # then other arguments. # # example of 'valid' call: # ::tcl::OptParseTest save -4 -pr 23 -libsok SybTcl\ # -nostatics false ch1 OptProc OptParseTest { {subcommand -choice {save print} "sub command"} {arg1 3 "some number"} {-aflag} {-intflag 7} {-weirdflag "help string"} {-noStatics "Not ok to load static packages"} {-nestedloading1 true "OK to load into nested children"} {-nestedloading2 -boolean true "OK to load into nested children"} {-libsOK -choice {Tk SybTcl} "List of packages that can be loaded"} {-precision -int 12 "Number of digits of precision"} {-intval 7 "An integer"} {-scale -float 1.0 "Scale factor"} {-zoom 1.0 "Zoom factor"} {-arbitrary foobar "Arbitrary string"} {-random -string 12 "Random string"} {-listval -list {} "List value"} {-blahflag -blah abc "Funny type"} {arg2 -boolean "a boolean"} {arg3 -choice "ch1 ch2"} {?optarg? -list {} "optional argument"} } { foreach v [info locals] { puts stderr [format "%14s : %s" $v [set $v]] } } } ################### No User serviceable part below ! ############### # Array storing the parsed descriptions variable OptDesc array set OptDesc {} # Next potentially free key id (numeric) variable OptDescN 0 # Inside algorithm/mechanism description: # (not for the faint-hearted ;-) # # The argument description is parsed into a "program tree" # It is called a "program" because it is the program used by # the state machine interpreter that use that program to # actually parse the arguments at run time. # # The general structure of a "program" is # notation (pseudo bnf like) # name :== definition defines "name" as being "definition" # { x y z } means list of x, y, and z # x* means x repeated 0 or more time # x+ means "x x*" # x? means optionally x # x | y means x or y # "cccc" means the literal string # # program :== { programCounter programStep* } # # programStep :== program | singleStep # # programCounter :== {"P" integer+ } # # singleStep :== { instruction parameters* } # # instruction :== single element list # # (the difference between singleStep and program is that \ # llength [lindex $program 0] >= 2 # while # llength [lindex $singleStep 0] == 1 # ) # # And for this application: # # singleStep :== { instruction varname {hasBeenSet currentValue} type # typeArgs help } # instruction :== "flags" | "value" # type :== knowType | anyword # knowType :== "string" | "int" | "boolean" | "boolflag" | "float" # | "choice" # # for type "choice" typeArgs is a list of possible choices, the first one # is the default value. for all other types the typeArgs is the default value # # a "boolflag" is the type for a flag whose presence or absence, without # additional arguments means respectively true or false (default flag type). # # programCounter is the index in the list of the currently processed # programStep (thus starting at 1 (0 is {"P" prgCounterValue}). # If it is a list it points toward each currently selected programStep. # (like for "flags", as they are optional, form a set and programStep). # Performance/Implementation issues # --------------------------------- # We use tcl lists instead of arrays because with tcl8.0 # they should start to be much faster. # But this code use a lot of helper procs (like Lvarset) # which are quite slow and would be helpfully optimized # for instance by being written in C. Also our structure # is complex and there is maybe some places where the # string rep might be calculated at great expense. to be checked. # # Parse a given description and saves it here under the given key # generate a unused keyid if not given # proc ::tcl::OptKeyRegister {desc {key ""}} { variable OptDesc variable OptDescN if {[string equal $key ""]} { # in case a key given to us as a parameter was a number while {[info exists OptDesc($OptDescN)]} {incr OptDescN} set key $OptDescN incr OptDescN } # program counter set program [list [list "P" 1]] # are we processing flags (which makes a single program step) set inflags 0 set state {} # flag used to detect that we just have a single (flags set) subprogram. set empty 1 foreach item $desc { if {$state == "args"} { # more items after 'args'... return -code error "'args' special argument must be the last one" } set res [OptNormalizeOne $item] set state [lindex $res 0] if {$inflags} { if {$state == "flags"} { # add to 'subprogram' lappend flagsprg $res } else { # put in the flags # structure for flag programs items is a list of # {subprgcounter {prg flag 1} {prg flag 2} {...}} lappend program $flagsprg # put the other regular stuff lappend program $res set inflags 0 set empty 0 } } else { if {$state == "flags"} { set inflags 1 # sub program counter + first sub program set flagsprg [list [list "P" 1] $res] } else { lappend program $res set empty 0 } } } if {$inflags} { if {$empty} { # We just have the subprogram, optimize and remove # unneeded level: set program $flagsprg } else { lappend program $flagsprg } } set OptDesc($key) $program return $key } # # Free the storage for that given key # proc ::tcl::OptKeyDelete {key} { variable OptDesc unset OptDesc($key) } # Get the parsed description stored under the given key. proc OptKeyGetDesc {descKey} { variable OptDesc if {![info exists OptDesc($descKey)]} { return -code error "Unknown option description key \"$descKey\"" } set OptDesc($descKey) } # Parse entry point for people who don't want to register with a key, # for instance because the description changes dynamically. # (otherwise one should really use OptKeyRegister once + OptKeyParse # as it is way faster or simply OptProc which does it all) # Assign a temporary key, call OptKeyParse and then free the storage proc ::tcl::OptParse {desc arglist} { set tempkey [OptKeyRegister $desc] set ret [catch {uplevel 1 [list ::tcl::OptKeyParse $tempkey $arglist]} res] OptKeyDelete $tempkey return -code $ret $res } # Helper function, replacement for proc that both # register the description under a key which is the name of the proc # (and thus unique to that code) # and add a first line to the code to call the OptKeyParse proc # Stores the list of variables that have been actually given by the user # (the other will be sets to their default value) # into local variable named "Args". proc ::tcl::OptProc {name desc body} { set namespace [uplevel 1 [list ::namespace current]] if {[string match "::*" $name] || [string equal $namespace "::"]} { # absolute name or global namespace, name is the key set key $name } else { # we are relative to some non top level namespace: set key "${namespace}::${name}" } OptKeyRegister $desc $key uplevel 1 [list ::proc $name args "set Args \[::tcl::OptKeyParse $key \$args\]\n$body"] return $key } # Check that a argument has been given # assumes that "OptProc" has been used as it will check in "Args" list proc ::tcl::OptProcArgGiven {argname} { upvar Args alist expr {[lsearch $alist $argname] >=0} } ####### # Programs/Descriptions manipulation # Return the instruction word/list of a given step/(sub)program proc OptInstr {lst} { lindex $lst 0 } # Is a (sub) program or a plain instruction ? proc OptIsPrg {lst} { expr {[llength [OptInstr $lst]]>=2} } # Is this instruction a program counter or a real instr proc OptIsCounter {item} { expr {[lindex $item 0]=="P"} } # Current program counter (2nd word of first word) proc OptGetPrgCounter {lst} { Lget $lst {0 1} } # Current program counter (2nd word of first word) proc OptSetPrgCounter {lstName newValue} { upvar $lstName lst set lst [lreplace $lst 0 0 [concat "P" $newValue]] } # returns a list of currently selected items. proc OptSelection {lst} { set res {} foreach idx [lrange [lindex $lst 0] 1 end] { lappend res [Lget $lst $idx] } return $res } # Advance to next description proc OptNextDesc {descName} { uplevel 1 [list Lvarincr $descName {0 1}] } # Get the current description, eventually descend proc OptCurDesc {descriptions} { lindex $descriptions [OptGetPrgCounter $descriptions] } # get the current description, eventually descend # through sub programs as needed. proc OptCurDescFinal {descriptions} { set item [OptCurDesc $descriptions] # Descend untill we get the actual item and not a sub program while {[OptIsPrg $item]} { set item [OptCurDesc $item] } return $item } # Current final instruction adress proc OptCurAddr {descriptions {start {}}} { set adress [OptGetPrgCounter $descriptions] lappend start $adress set item [lindex $descriptions $adress] if {[OptIsPrg $item]} { return [OptCurAddr $item $start] } else { return $start } } # Set the value field of the current instruction. proc OptCurSetValue {descriptionsName value} { upvar $descriptionsName descriptions # Get the current item full address. set adress [OptCurAddr $descriptions] # Use the 3rd field of the item (see OptValue / OptNewInst). lappend adress 2 Lvarset descriptions $adress [list 1 $value] # ^hasBeenSet flag } # Empty state means done/paste the end of the program. proc OptState {item} { lindex $item 0 } # current state proc OptCurState {descriptions} { OptState [OptCurDesc $descriptions] } ####### # Arguments manipulation # Returns the argument that has to be processed now. proc OptCurrentArg {lst} { lindex $lst 0 } # Advance to next argument. proc OptNextArg {argsName} { uplevel 1 [list Lvarpop1 $argsName] } ####### # Loop over all descriptions, calling OptDoOne which will # eventually eat all the arguments. proc OptDoAll {descriptionsName argumentsName} { upvar $descriptionsName descriptions upvar $argumentsName arguments # puts "entered DoAll" # Nb: the places where "state" can be set are tricky to figure # because DoOne sets the state to flagsValue and return -continue # when needed... set state [OptCurState $descriptions] # We'll exit the loop in "OptDoOne" or when state is empty. while 1 { set curitem [OptCurDesc $descriptions] # Do subprograms if needed, call ourselves on the sub branch while {[OptIsPrg $curitem]} { OptDoAll curitem arguments # puts "done DoAll sub" # Insert back the results in current tree Lvarset1nc descriptions [OptGetPrgCounter $descriptions]\ $curitem OptNextDesc descriptions set curitem [OptCurDesc $descriptions] set state [OptCurState $descriptions] } # puts "state = \"$state\" - arguments=($arguments)" if {[Lempty $state]} { # Nothing left to do, we are done in this branch: break } # The following statement can make us terminate/continue # as it use return -code {break, continue, return and error} # codes OptDoOne descriptions state arguments # If we are here, no special return code where issued, # we'll step to next instruction : # puts "new state = \"$state\"" OptNextDesc descriptions set state [OptCurState $descriptions] } } # Process one step for the state machine, # eventually consuming the current argument. proc OptDoOne {descriptionsName stateName argumentsName} { upvar $argumentsName arguments upvar $descriptionsName descriptions upvar $stateName state # the special state/instruction "args" eats all # the remaining args (if any) if {($state == "args")} { if {![Lempty $arguments]} { # If there is no additional arguments, leave the default value # in. OptCurSetValue descriptions $arguments set arguments {} } # puts "breaking out ('args' state: consuming every reminding args)" return -code break } if {[Lempty $arguments]} { if {$state == "flags"} { # no argument and no flags : we're done # puts "returning to previous (sub)prg (no more args)" return -code return } elseif {$state == "optValue"} { set state next; # not used, for debug only # go to next state return } else { return -code error [OptMissingValue $descriptions] } } else { set arg [OptCurrentArg $arguments] } switch $state { flags { # A non-dash argument terminates the options, as does -- # Still a flag ? if {![OptIsFlag $arg]} { # don't consume the argument, return to previous prg return -code return } # consume the flag OptNextArg arguments if {[string equal "--" $arg]} { # return from 'flags' state return -code return } set hits [OptHits descriptions $arg] if {$hits > 1} { return -code error [OptAmbigous $descriptions $arg] } elseif {$hits == 0} { return -code error [OptFlagUsage $descriptions $arg] } set item [OptCurDesc $descriptions] if {[OptNeedValue $item]} { # we need a value, next state is set state flagValue } else { OptCurSetValue descriptions 1 } # continue return -code continue } flagValue - value { set item [OptCurDesc $descriptions] # Test the values against their required type if {[catch {OptCheckType $arg\ [OptType $item] [OptTypeArgs $item]} val]} { return -code error [OptBadValue $item $arg $val] } # consume the value OptNextArg arguments # set the value OptCurSetValue descriptions $val # go to next state if {$state == "flagValue"} { set state flags return -code continue } else { set state next; # not used, for debug only return ; # will go on next step } } optValue { set item [OptCurDesc $descriptions] # Test the values against their required type if {![catch {OptCheckType $arg\ [OptType $item] [OptTypeArgs $item]} val]} { # right type, so : # consume the value OptNextArg arguments # set the value OptCurSetValue descriptions $val } # go to next state set state next; # not used, for debug only return ; # will go on next step } } # If we reach this point: an unknown # state as been entered ! return -code error "Bug! unknown state in DoOne \"$state\"\ (prg counter [OptGetPrgCounter $descriptions]:\ [OptCurDesc $descriptions])" } # Parse the options given the key to previously registered description # and arguments list proc ::tcl::OptKeyParse {descKey arglist} { set desc [OptKeyGetDesc $descKey] # make sure -help always give usage if {[string equal -nocase "-help" $arglist]} { return -code error [OptError "Usage information:" $desc 1] } OptDoAll desc arglist if {![Lempty $arglist]} { return -code error [OptTooManyArgs $desc $arglist] } # Analyse the result # Walk through the tree: OptTreeVars $desc "#[expr {[info level]-1}]" } # determine string length for nice tabulated output proc OptTreeVars {desc level {vnamesLst {}}} { foreach item $desc { if {[OptIsCounter $item]} continue if {[OptIsPrg $item]} { set vnamesLst [OptTreeVars $item $level $vnamesLst] } else { set vname [OptVarName $item] upvar $level $vname var if {[OptHasBeenSet $item]} { # puts "adding $vname" # lets use the input name for the returned list # it is more useful, for instance you can check that # no flags at all was given with expr # {![string match "*-*" $Args]} lappend vnamesLst [OptName $item] set var [OptValue $item] } else { set var [OptDefaultValue $item] } } } return $vnamesLst } # Check the type of a value # and emit an error if arg is not of the correct type # otherwise returns the canonical value of that arg (ie 0/1 for booleans) proc ::tcl::OptCheckType {arg type {typeArgs ""}} { # puts "checking '$arg' against '$type' ($typeArgs)" # only types "any", "choice", and numbers can have leading "-" switch -exact -- $type { int { if {![string is integer -strict $arg]} { error "not an integer" } return $arg } float { return [expr {double($arg)}] } script - list { # if llength fail : malformed list if {[llength $arg]==0 && [OptIsFlag $arg]} { error "no values with leading -" } return $arg } boolean { if {![string is boolean -strict $arg]} { error "non canonic boolean" } # convert true/false because expr/if is broken with "!,... return [expr {$arg ? 1 : 0}] } choice { if {$arg ni $typeArgs} { error "invalid choice" } return $arg } any { return $arg } string - default { if {[OptIsFlag $arg]} { error "no values with leading -" } return $arg } } return neverReached } # internal utilities # returns the number of flags matching the given arg # sets the (local) prg counter to the list of matches proc OptHits {descName arg} { upvar $descName desc set hits 0 set hitems {} set i 1 set larg [string tolower $arg] set len [string length $larg] set last [expr {$len-1}] foreach item [lrange $desc 1 end] { set flag [OptName $item] # lets try to match case insensitively # (string length ought to be cheap) set lflag [string tolower $flag] if {$len == [string length $lflag]} { if {[string equal $larg $lflag]} { # Exact match case OptSetPrgCounter desc $i return 1 } } elseif {[string equal $larg [string range $lflag 0 $last]]} { lappend hitems $i incr hits } incr i } if {$hits} { OptSetPrgCounter desc $hitems } return $hits } # Extract fields from the list structure: proc OptName {item} { lindex $item 1 } proc OptHasBeenSet {item} { Lget $item {2 0} } proc OptValue {item} { Lget $item {2 1} } proc OptIsFlag {name} { string match "-*" $name } proc OptIsOpt {name} { string match {\?*} $name } proc OptVarName {item} { set name [OptName $item] if {[OptIsFlag $name]} { return [string range $name 1 end] } elseif {[OptIsOpt $name]} { return [string trim $name "?"] } else { return $name } } proc OptType {item} { lindex $item 3 } proc OptTypeArgs {item} { lindex $item 4 } proc OptHelp {item} { lindex $item 5 } proc OptNeedValue {item} { expr {![string equal [OptType $item] boolflag]} } proc OptDefaultValue {item} { set val [OptTypeArgs $item] switch -exact -- [OptType $item] { choice {return [lindex $val 0]} boolean - boolflag { # convert back false/true to 0/1 because expr !$bool # is broken.. if {$val} { return 1 } else { return 0 } } } return $val } # Description format error helper proc OptOptUsage {item {what ""}} { return -code error "invalid description format$what: $item\n\ should be a list of {varname|-flagname ?-type? ?defaultvalue?\ ?helpstring?}" } # Generate a canonical form single instruction proc OptNewInst {state varname type typeArgs help} { list $state $varname [list 0 {}] $type $typeArgs $help # ^ ^ # | | # hasBeenSet=+ +=currentValue } # Translate one item to canonical form proc OptNormalizeOne {item} { set lg [Lassign $item varname arg1 arg2 arg3] # puts "called optnormalizeone '$item' v=($varname), lg=$lg" set isflag [OptIsFlag $varname] set isopt [OptIsOpt $varname] if {$isflag} { set state "flags" } elseif {$isopt} { set state "optValue" } elseif {![string equal $varname "args"]} { set state "value" } else { set state "args" } # apply 'smart' 'fuzzy' logic to try to make # description writer's life easy, and our's difficult : # let's guess the missing arguments :-) switch $lg { 1 { if {$isflag} { return [OptNewInst $state $varname boolflag false ""] } else { return [OptNewInst $state $varname any "" ""] } } 2 { # varname default # varname help set type [OptGuessType $arg1] if {[string equal $type "string"]} { if {$isflag} { set type boolflag set def false } else { set type any set def "" } set help $arg1 } else { set help "" set def $arg1 } return [OptNewInst $state $varname $type $def $help] } 3 { # varname type value # varname value comment if {[regexp {^-(.+)$} $arg1 x type]} { # flags/optValue as they are optional, need a "value", # on the contrary, for a variable (non optional), # default value is pointless, 'cept for choices : if {$isflag || $isopt || ($type == "choice")} { return [OptNewInst $state $varname $type $arg2 ""] } else { return [OptNewInst $state $varname $type "" $arg2] } } else { return [OptNewInst $state $varname\ [OptGuessType $arg1] $arg1 $arg2] } } 4 { if {[regexp {^-(.+)$} $arg1 x type]} { return [OptNewInst $state $varname $type $arg2 $arg3] } else { return -code error [OptOptUsage $item] } } default { return -code error [OptOptUsage $item] } } } # Auto magic lazy type determination proc OptGuessType {arg} { if { $arg == "true" || $arg == "false" } { return boolean } if {[string is integer -strict $arg]} { return int } if {[string is double -strict $arg]} { return float } return string } # Error messages front ends proc OptAmbigous {desc arg} { OptError "ambigous option \"$arg\", choose from:" [OptSelection $desc] } proc OptFlagUsage {desc arg} { OptError "bad flag \"$arg\", must be one of" $desc } proc OptTooManyArgs {desc arguments} { OptError "too many arguments (unexpected argument(s): $arguments),\ usage:"\ $desc 1 } proc OptParamType {item} { if {[OptIsFlag $item]} { return "flag" } else { return "parameter" } } proc OptBadValue {item arg {err {}}} { # puts "bad val err = \"$err\"" OptError "bad value \"$arg\" for [OptParamType $item]"\ [list $item] } proc OptMissingValue {descriptions} { # set item [OptCurDescFinal $descriptions] set item [OptCurDesc $descriptions] OptError "no value given for [OptParamType $item] \"[OptName $item]\"\ (use -help for full usage) :"\ [list $item] } proc ::tcl::OptKeyError {prefix descKey {header 0}} { OptError $prefix [OptKeyGetDesc $descKey] $header } # determine string length for nice tabulated output proc OptLengths {desc nlName tlName dlName} { upvar $nlName nl upvar $tlName tl upvar $dlName dl foreach item $desc { if {[OptIsCounter $item]} continue if {[OptIsPrg $item]} { OptLengths $item nl tl dl } else { SetMax nl [string length [OptName $item]] SetMax tl [string length [OptType $item]] set dv [OptTypeArgs $item] if {[OptState $item] != "header"} { set dv "($dv)" } set l [string length $dv] # limit the space allocated to potentially big "choices" if {([OptType $item] != "choice") || ($l<=12)} { SetMax dl $l } else { if {![info exists dl]} { set dl 0 } } } } } # output the tree proc OptTree {desc nl tl dl} { set res "" foreach item $desc { if {[OptIsCounter $item]} continue if {[OptIsPrg $item]} { append res [OptTree $item $nl $tl $dl] } else { set dv [OptTypeArgs $item] if {[OptState $item] != "header"} { set dv "($dv)" } append res [string trimright [format "\n %-*s %-*s %-*s %s" \ $nl [OptName $item] $tl [OptType $item] \ $dl $dv [OptHelp $item]]] } } return $res } # Give nice usage string proc ::tcl::OptError {prefix desc {header 0}} { # determine length if {$header} { # add faked instruction set h [list [OptNewInst header Var/FlagName Type Value Help]] lappend h [OptNewInst header ------------ ---- ----- ----] lappend h [OptNewInst header {(-help} "" "" {gives this help)}] set desc [concat $h $desc] } OptLengths $desc nl tl dl # actually output return "$prefix[OptTree $desc $nl $tl $dl]" } ################ General Utility functions ####################### # # List utility functions # Naming convention: # "Lvarxxx" take the list VARiable name as argument # "Lxxxx" take the list value as argument # (which is not costly with Tcl8 objects system # as it's still a reference and not a copy of the values) # # Is that list empty ? proc ::tcl::Lempty {list} { expr {[llength $list]==0} } # Gets the value of one leaf of a lists tree proc ::tcl::Lget {list indexLst} { if {[llength $indexLst] <= 1} { return [lindex $list $indexLst] } Lget [lindex $list [lindex $indexLst 0]] [lrange $indexLst 1 end] } # Sets the value of one leaf of a lists tree # (we use the version that does not create the elements because # it would be even slower... needs to be written in C !) # (nb: there is a non trivial recursive problem with indexes 0, # which appear because there is no difference between a list # of 1 element and 1 element alone : [list "a"] == "a" while # it should be {a} and [listp a] should be 0 while [listp {a b}] would be 1 # and [listp "a b"] maybe 0. listp does not exist either...) proc ::tcl::Lvarset {listName indexLst newValue} { upvar $listName list if {[llength $indexLst] <= 1} { Lvarset1nc list $indexLst $newValue } else { set idx [lindex $indexLst 0] set targetList [lindex $list $idx] # reduce refcount on targetList (not really usefull now, # could be with optimizing compiler) # Lvarset1 list $idx {} # recursively replace in targetList Lvarset targetList [lrange $indexLst 1 end] $newValue # put updated sub list back in the tree Lvarset1nc list $idx $targetList } } # Set one cell to a value, eventually create all the needed elements # (on level-1 of lists) variable emptyList {} proc ::tcl::Lvarset1 {listName index newValue} { upvar $listName list if {$index < 0} {return -code error "invalid negative index"} set lg [llength $list] if {$index >= $lg} { variable emptyList for {set i $lg} {$i<$index} {incr i} { lappend list $emptyList } lappend list $newValue } else { set list [lreplace $list $index $index $newValue] } } # same as Lvarset1 but no bound checking / creation proc ::tcl::Lvarset1nc {listName index newValue} { upvar $listName list set list [lreplace $list $index $index $newValue] } # Increments the value of one leaf of a lists tree # (which must exists) proc ::tcl::Lvarincr {listName indexLst {howMuch 1}} { upvar $listName list if {[llength $indexLst] <= 1} { Lvarincr1 list $indexLst $howMuch } else { set idx [lindex $indexLst 0] set targetList [lindex $list $idx] # reduce refcount on targetList Lvarset1nc list $idx {} # recursively replace in targetList Lvarincr targetList [lrange $indexLst 1 end] $howMuch # put updated sub list back in the tree Lvarset1nc list $idx $targetList } } # Increments the value of one cell of a list proc ::tcl::Lvarincr1 {listName index {howMuch 1}} { upvar $listName list set newValue [expr {[lindex $list $index]+$howMuch}] set list [lreplace $list $index $index $newValue] return $newValue } # Removes the first element of a list # and returns the new list value proc ::tcl::Lvarpop1 {listName} { upvar $listName list set list [lrange $list 1 end] } # Same but returns the removed element # (Like the tclX version) proc ::tcl::Lvarpop {listName} { upvar $listName list set el [lindex $list 0] set list [lrange $list 1 end] return $el } # Assign list elements to variables and return the length of the list proc ::tcl::Lassign {list args} { # faster than direct blown foreach (which does not byte compile) set i 0 set lg [llength $list] foreach vname $args { if {$i>=$lg} break uplevel 1 [list ::set $vname [lindex $list $i]] incr i } return $lg } # Misc utilities # Set the varname to value if value is greater than varname's current value # or if varname is undefined proc ::tcl::SetMax {varname value} { upvar 1 $varname var if {![info exists var] || $value > $var} { set var $value } } # Set the varname to value if value is smaller than varname's current value # or if varname is undefined proc ::tcl::SetMin {varname value} { upvar 1 $varname var if {![info exists var] || $value < $var} { set var $value } } # everything loaded fine, lets create the test proc: # OptCreateTestProc # Don't need the create temp proc anymore: # rename OptCreateTestProc {} } tcl9.0.1/library/opt/pkgIndex.tcl0000644000175000017500000000116014726623136016277 0ustar sergeisergei# Tcl package index file, version 1.1 # This file is generated by the "pkg_mkIndex -direct" command # and sourced either when an application starts up or # by a "package unknown" script. It invokes the # "package ifneeded" command to set up package-related # information so that packages will be loaded automatically # in response to "package require" commands. When this # script is sourced, the variable $dir must contain the # full path name of this file's directory. if {![package vsatisfies [package provide Tcl] 8.5-]} {return} package ifneeded opt 0.4.9 [list source -encoding utf-8 [file join $dir optparse.tcl]] tcl9.0.1/library/msgcat/0000755000175000017500000000000014731057544014500 5ustar sergeisergeitcl9.0.1/library/msgcat/msgcat.tcl0000644000175000017500000011103714726623136016465 0ustar sergeisergei# msgcat.tcl -- # # This file defines various procedures which implement a # message catalog facility for Tcl programs. It should be # loaded with the command "package require msgcat". # # Copyright © 2010-2018 Harald Oehlmann. # Copyright © 1998-2000 Ajuba Solutions. # Copyright © 1998 Mark Harrison. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # We use oo::define::self, which is new in Tcl 8.7 package require Tcl 8.7- # When the version number changes, be sure to update the pkgIndex.tcl file, # and the installation directory in the Makefiles. package provide msgcat 1.7.1 namespace eval msgcat { namespace export mc mcn mcexists mcload mclocale mcmax\ mcmset mcpreferences mcset\ mcunknown mcflset mcflmset mcloadedlocales mcforgetpackage\ mcpackagenamespaceget mcpackageconfig mcpackagelocale mcutil # Records the list of locales to search variable Loclist {} # List of currently loaded locales variable LoadedLocales {} # Records the locale of the currently sourced message catalogue file variable FileLocale # Configuration values per Package (e.g. client namespace). # The dict key is of the form "